Loading/progress indicator for shell with aging emojis



Recently, while waiting for a long-running script to finish, I thought that it would be nice to have some sort of loader with aging emojis. TLDR: we-are-waiting.

The โ€œlifeโ€ of an emoji is simple:

๐Ÿ‘ถ๐Ÿฟ โ†’ ๐Ÿ‘ง๐Ÿฟ โ†’ ๐Ÿ‘ฉ๐Ÿฟ โ†’ ๐Ÿ‘ฑ๐Ÿฟโ€โ™€๏ธ โ†’ ๐Ÿ‘ฉ๐Ÿฟโ€โš•๏ธ โ†’ ๐Ÿ‘ต๐Ÿฟ

It contains aging from a baby to grown-up person, one profession, and oldness.

And as we have four colors, two genders, five ages, and 22 professions. We can have a great variety of lives. So as the first thing to do I decided to generate all those variants. Initially, I was planning to implement everything in Go, but itโ€™s not possible to use emojis in Go code, only codepoints. Because of that, I decided to write a little Python script, that will generate Go code with all variants with codepoints instead of emojis.

For that I just copied lines with emojis from getemoji.com and put them in lists:

ages = [
    "๐Ÿ‘ถ ๐Ÿ‘ฆ ๐Ÿ‘ง ๐Ÿ‘จ ๐Ÿ‘ฉ ๐Ÿ‘ฑโ€โ™€๏ธ ๐Ÿ‘ฑ ๐Ÿ‘ด ๐Ÿ‘ต",
    "๐Ÿ‘ถ๐Ÿป ๐Ÿ‘ฆ๐Ÿป ๐Ÿ‘ง๐Ÿป ๐Ÿ‘จ๐Ÿป ๐Ÿ‘ฉ๐Ÿป ๐Ÿ‘ฑ๐Ÿปโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿป ๐Ÿ‘ด๐Ÿป ๐Ÿ‘ต๐Ÿป",
    "๐Ÿ‘ถ๐Ÿผ ๐Ÿ‘ฆ๐Ÿผ ๐Ÿ‘ง๐Ÿผ ๐Ÿ‘จ๐Ÿผ ๐Ÿ‘ฉ๐Ÿผ ๐Ÿ‘ฑ๐Ÿผโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿผ ๐Ÿ‘ด๐Ÿผ ๐Ÿ‘ต๐Ÿผ",
    "๐Ÿ‘ถ๐Ÿฝ ๐Ÿ‘ฆ๐Ÿฝ ๐Ÿ‘ง๐Ÿฝ ๐Ÿ‘จ๐Ÿฝ ๐Ÿ‘ฉ๐Ÿฝ ๐Ÿ‘ฑ๐Ÿฝโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿฝ ๐Ÿ‘ด๐Ÿฝ ๐Ÿ‘ต๐Ÿฝ",
    "๐Ÿ‘ถ๐Ÿพ ๐Ÿ‘ฆ๐Ÿพ ๐Ÿ‘ง๐Ÿพ ๐Ÿ‘จ๐Ÿพ ๐Ÿ‘ฉ๐Ÿพ ๐Ÿ‘ฑ๐Ÿพโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿพ ๐Ÿ‘ด๐Ÿพ ๐Ÿ‘ต๐Ÿพ",
    "๐Ÿ‘ถ๐Ÿฟ ๐Ÿ‘ฆ๐Ÿฟ ๐Ÿ‘ง๐Ÿฟ ๐Ÿ‘จ๐Ÿฟ ๐Ÿ‘ฉ๐Ÿฟ ๐Ÿ‘ฑ๐Ÿฟโ€โ™€๏ธ ๐Ÿ‘ฑ๐Ÿฟ ๐Ÿ‘ด๐Ÿฟ ๐Ÿ‘ต๐Ÿฟ",
]
ages = [x.split(' ') for x in ages]

roles = [
    "๐Ÿ‘ฎโ€โ™€๏ธ ๐Ÿ‘ฎ ๐Ÿ‘ทโ€โ™€๏ธ ๐Ÿ‘ท ๐Ÿ’‚โ€โ™€๏ธ ๐Ÿ’‚ ๐Ÿ•ต๏ธโ€โ™€๏ธ ๐Ÿ•ต๏ธ ๐Ÿ‘ฉโ€โš•๏ธ ๐Ÿ‘จโ€โš•๏ธ ๐Ÿ‘ฉโ€๐ŸŒพ ๐Ÿ‘จโ€๐ŸŒพ ๐Ÿ‘ฉโ€๐Ÿณ ๐Ÿ‘จโ€๐Ÿณ ๐Ÿ‘ฉโ€๐ŸŽ“ ๐Ÿ‘จโ€๐ŸŽ“ ๐Ÿ‘ฉโ€๐ŸŽค ๐Ÿ‘จโ€๐ŸŽค ๐Ÿ‘ฉโ€๐Ÿซ ๐Ÿ‘จโ€๐Ÿซ ๐Ÿ‘ฉโ€๐Ÿญ ๐Ÿ‘จโ€๐Ÿญ ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿ‘จโ€๐Ÿ’ป ๐Ÿ‘ฉโ€๐Ÿ’ผ ๐Ÿ‘จโ€๐Ÿ’ผ ๐Ÿ‘ฉโ€๐Ÿ”ง ๐Ÿ‘จโ€๐Ÿ”ง ๐Ÿ‘ฉโ€๐Ÿ”ฌ ๐Ÿ‘จโ€๐Ÿ”ฌ ๐Ÿ‘ฉโ€๐ŸŽจ ๐Ÿ‘จโ€๐ŸŽจ ๐Ÿ‘ฉโ€๐Ÿš’ ๐Ÿ‘จโ€๐Ÿš’ ๐Ÿ‘ฉโ€โœˆ๏ธ ๐Ÿ‘จโ€โœˆ๏ธ ๐Ÿ‘ฉโ€๐Ÿš€ ๐Ÿ‘จโ€๐Ÿš€ ๐Ÿ‘ฉโ€โš–๏ธ ๐Ÿ‘จโ€โš–๏ธ ๐Ÿคถ ๐ŸŽ… ๐Ÿ‘ธ ๐Ÿคด",
    "๐Ÿ‘ฎ๐Ÿปโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿป ๐Ÿ‘ท๐Ÿปโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿป ๐Ÿ’‚๐Ÿปโ€โ™€๏ธ ๐Ÿ’‚๐Ÿป ๐Ÿ•ต๐Ÿปโ€โ™€๏ธ ๐Ÿ•ต๐Ÿป ๐Ÿ‘ฉ๐Ÿปโ€โš•๏ธ ๐Ÿ‘จ๐Ÿปโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿปโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿณ ๐Ÿ‘จ๐Ÿปโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿปโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽค ๐Ÿ‘จ๐Ÿปโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿซ ๐Ÿ‘จ๐Ÿปโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿญ ๐Ÿ‘จ๐Ÿปโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿปโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿปโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿปโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿปโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿปโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿปโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿปโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿปโ€โš–๏ธ ๐Ÿ‘จ๐Ÿปโ€โš–๏ธ ๐Ÿคถ๐Ÿป ๐ŸŽ…๐Ÿป ๐Ÿ‘ธ๐Ÿป ๐Ÿคด๐Ÿป",
    "๐Ÿ‘ฎ๐Ÿผโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿผ ๐Ÿ‘ท๐Ÿผโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿผ ๐Ÿ’‚๐Ÿผโ€โ™€๏ธ ๐Ÿ’‚๐Ÿผ ๐Ÿ•ต๐Ÿผโ€โ™€๏ธ ๐Ÿ•ต๐Ÿผ ๐Ÿ‘ฉ๐Ÿผโ€โš•๏ธ ๐Ÿ‘จ๐Ÿผโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿผโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿณ ๐Ÿ‘จ๐Ÿผโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿผโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽค ๐Ÿ‘จ๐Ÿผโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿซ ๐Ÿ‘จ๐Ÿผโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿญ ๐Ÿ‘จ๐Ÿผโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿผโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿผโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿผโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿผโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿผโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿผโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿผโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿผโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿผโ€โš–๏ธ ๐Ÿ‘จ๐Ÿผโ€โš–๏ธ ๐Ÿคถ๐Ÿผ ๐ŸŽ…๐Ÿผ ๐Ÿ‘ธ๐Ÿผ ๐Ÿคด๐Ÿผ",
    "๐Ÿ‘ฎ๐Ÿฝโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿฝ ๐Ÿ‘ท๐Ÿฝโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿฝ ๐Ÿ’‚๐Ÿฝโ€โ™€๏ธ ๐Ÿ’‚๐Ÿฝ ๐Ÿ•ต๐Ÿฝโ€โ™€๏ธ ๐Ÿ•ต๐Ÿฝ ๐Ÿ‘ฉ๐Ÿฝโ€โš•๏ธ ๐Ÿ‘จ๐Ÿฝโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿฝโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿณ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽค ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿญ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿฝโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿฝโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿฝโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿฝโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿฝโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿฝโ€โš–๏ธ ๐Ÿ‘จ๐Ÿฝโ€โš–๏ธ ๐Ÿคถ๐Ÿฝ ๐ŸŽ…๐Ÿฝ ๐Ÿ‘ธ๐Ÿฝ ๐Ÿคด๐Ÿฝ",
    "๐Ÿ‘ฎ๐Ÿพโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿพ ๐Ÿ‘ท๐Ÿพโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿพ ๐Ÿ’‚๐Ÿพโ€โ™€๏ธ ๐Ÿ’‚๐Ÿพ ๐Ÿ•ต๐Ÿพโ€โ™€๏ธ ๐Ÿ•ต๐Ÿพ ๐Ÿ‘ฉ๐Ÿพโ€โš•๏ธ ๐Ÿ‘จ๐Ÿพโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿพโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿณ ๐Ÿ‘จ๐Ÿพโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿพโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽค ๐Ÿ‘จ๐Ÿพโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿซ ๐Ÿ‘จ๐Ÿพโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿญ ๐Ÿ‘จ๐Ÿพโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿพโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿพโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿพโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿพโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿพโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿพโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿพโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿพโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿพโ€โš–๏ธ ๐Ÿ‘จ๐Ÿพโ€โš–๏ธ ๐Ÿคถ๐Ÿพ ๐ŸŽ…๐Ÿพ ๐Ÿ‘ธ๐Ÿพ ๐Ÿคด๐Ÿพ",
    "๐Ÿ‘ฎ๐Ÿฟโ€โ™€๏ธ ๐Ÿ‘ฎ๐Ÿฟ ๐Ÿ‘ท๐Ÿฟโ€โ™€๏ธ ๐Ÿ‘ท๐Ÿฟ ๐Ÿ’‚๐Ÿฟโ€โ™€๏ธ ๐Ÿ’‚๐Ÿฟ ๐Ÿ•ต๐Ÿฟโ€โ™€๏ธ ๐Ÿ•ต๐Ÿฟ ๐Ÿ‘ฉ๐Ÿฟโ€โš•๏ธ ๐Ÿ‘จ๐Ÿฟโ€โš•๏ธ ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŒพ ๐Ÿ‘จ๐Ÿฟโ€๐ŸŒพ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿณ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿณ ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽ“ ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽ“ ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽค ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽค ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿซ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿซ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿญ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿญ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ป ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ป ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ’ผ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ’ผ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ง ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ง ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿ”ฌ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿ”ฌ ๐Ÿ‘ฉ๐Ÿฟโ€๐ŸŽจ ๐Ÿ‘จ๐Ÿฟโ€๐ŸŽจ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš’ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿš’ ๐Ÿ‘ฉ๐Ÿฟโ€โœˆ๏ธ ๐Ÿ‘จ๐Ÿฟโ€โœˆ๏ธ ๐Ÿ‘ฉ๐Ÿฟโ€๐Ÿš€ ๐Ÿ‘จ๐Ÿฟโ€๐Ÿš€ ๐Ÿ‘ฉ๐Ÿฟโ€โš–๏ธ ๐Ÿ‘จ๐Ÿฟโ€โš–๏ธ ๐Ÿคถ๐Ÿฟ ๐ŸŽ…๐Ÿฟ ๐Ÿ‘ธ๐Ÿฟ ๐Ÿคด๐Ÿฟ",
]
roles = [x.split(' ') for x in roles]

As emojis have a strange order, generation of all variants is a bit tricky, but itโ€™s easier than rearranging them in code because my editor doesnโ€™t work quite well with emojis:

def get_life(color, gender, role):
    yield ages[color][0]
    yield ages[color][1 + gender]
    yield ages[color][3 + gender]
    yield ages[color][6 - gender]
    yield roles[color][role * 2 + 1 - gender]
    yield ages[color][7 + gender]
>>> list(get_life(0, 0, 0))
['๐Ÿ‘ถ', '๐Ÿ‘ฆ', '๐Ÿ‘จ', '๐Ÿ‘ฑ', '๐Ÿ‘ฎ', '๐Ÿ‘ด']
def get_variants():
    for color in range(len(ages)):
        for gender in (0, 1):
            for role in range(len(roles[0]) // 2):
                yield color, gender, role
>>> list(get_life(*list(get_variants())[23]))
['๐Ÿ‘ถ', '๐Ÿ‘ง', '๐Ÿ‘ฉ', '๐Ÿ‘ฑ\u200dโ™€๏ธ', '๐Ÿ‘ท\u200dโ™€๏ธ', '๐Ÿ‘ต']

And after that itโ€™s very easy to generate Go package with all possible variants:

code = b'package variants\n\nvar All = [][]string{\n'

for variant in get_variants():
    code += b'\t{\n'
    for emoji in get_life(*variant):
        code += b'\t\t"' + emoji.encode('unicode-escape') + b'",\n'
    code += b'\t},\n'

code += b'}\n'

with open('variants/variants.go', 'wb') as f:
    f.write(code)

So weโ€™ll have something like this in variants/variants.go:

package variants

var All = [][]string{
	{
		"\U0001f476",
		"\U0001f466",
		"\U0001f468",
		"\U0001f471",
		"\U0001f46e",
		"\U0001f474",
	},
	...
}

The logic of the loader isnโ€™t that interesting, although I want to highlight some moments. At the high level we just read lines from a pipe, if thereโ€™s no new line arrived before tick seconds, we update our emojis:

func main() {
        ...
        go watchApp(lines)
        
        for {
            select {
            case line, isOpen := <-lines:
                ...
                os.Stdout.WriteString(line)
                ...
                printPeople(people)
            case <-time.After(time.Duration(*tick) * time.Second):
                people = updatePeople(people, *count)
                printPeople(people)
            }
        }
}

While updating, we can add new emoji, make one emoji older or โ€œkillโ€ the oldest:

func updatePeople(people []*human, count int) []*human {
	addNew := rand.Intn(5) == 0
	toMakeOlder := canMakeOlder(people)

	if addNew || len(toMakeOlder) == 0 {
		people = append(people, getRandomHuman())
	} else {
		index := toMakeOlder[rand.Intn(len(toMakeOlder))]
		people[index].position += 1
	}

	if len(people) > count {
		oldest := getOldest(people)
		return append(people[:oldest], people[oldest+1:]...)
	} else {
		return people
	}
}

And thatโ€™s all. You can find the source code on GitHub.

Mark Jason Dominus: Higher-Order Perl



book cover As I sometimes use Perl, I decided to read something about it. And Higher-Order Perl by Mark Jason Dominus was looking interesting. The book is really nice, it shows that Perl can be kind of a functional programming language and that itโ€™s possible to implement almost every feature from other languages in Perl. Also, after reading the book, I think thereโ€™s copious amount of ways to shot yourself in the leg in Perl.

The problems in the book are interesting and a bit challenging, so I think it can be worth reading even for people who donโ€™t work with Perl.

Soundlights with ESP8266 and NeoPixel Strip



About a year ago I made soundlights with Raspberry Pi. But RPI is a bit of an overkill for this simple task and itโ€™s quite big, doesnโ€™t have WiFi out of the box and practically canโ€™t be used without a power adapter.

So I decided to port soundlights to ESP8266. The main idea was to reuse as much as possible from the previous implementation, so the parts with patched audio visualizer and colors generation are the same. In a few words, Iโ€™ve patched cava to print numbers instead of showing pretty bars in a terminal. And Iโ€™ve generated colors with a code found on Quora.

And in current implementation I decided to make it very simple to use, the only requirement is to have a machine with cava and ESP8266 on the same WiFi network. So I chose UDP broadcasting as a way to send data to ESP8266. And because thereโ€™s just 60 LEDs and color of a LED is three values from 0 to 255, colors for all strip is just 180 bytes. So it fits in one UDP packet.

Letโ€™s start with the part with cava:

import sys
import socket
import array


COLORS_COUNT = 256
COLORS_OFFSET = 50


def get_spaced_colors(n):
    max_value = 16581375
    interval = int(max_value / n)
    colors = [hex(i)[2:].zfill(6) for i in range(0, max_value, interval)]

    return [(int(color[:2], 16),
             int(color[2:4], 16),
             int(color[4:6], 16)) for color in colors]


def send(colors):
    line = array.array('B', colors).tostring()
    sock.sendto(line, ('255.255.255.255', 42424))


sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

colors = get_spaced_colors(COLORS_COUNT)

while True:
    try:
        nums = map(int, sys.stdin.readline()[:-1].split())
        led_colors = [c for num in nums for c in colors[num]]
        send(led_colors)
    except Exception as e:
        print(e)

It can be used like:

unbuffer ./cava -p soundlights/cava_config | python cava/soundlights/esp/client.py

And it just reads numbers from cava output, generates colors, transforms them to bytes and broadcasts them at 42424 port.

The ESP8266 part is even simpler:

import socket
import machine
import neopixel


np = neopixel.NeoPixel(machine.Pin(5), 60)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', 42424))


while True:
    line, _ = sock.recvfrom(180)

    if len(line) < 180:
        continue

    for i in range(60):
        np[i] = (line[i * 3], line[i * 3 + 1], line[i * 3 + 2])

    np.write()

It just receives broadcasts from 42424 port and changes colors of LEDs.

At the end, this version has less code and just works. Itโ€™s even some sort of IoT and with some effort can become a wearable device.

Github.

Language agnostic REPL driven development with Visual Studio Code



A few years ago I was using Light Table, the integration with Clojure REPL was so nice. But the other parts of the editor and other languages support werenโ€™t that good. And at the moment the editor looks almost dead.

After that, for Clojure, I switched to Cursive, and I still use it. It has a nice feature โ€“ send to REPL, which allows users to execute selected code in REPL. But itโ€™s Clojure/ClojureScript only and requires some hassle to configure.

Nowadays for some stuff, I use Visual Studio Code. It doesnโ€™t have a nice integration with REPL, but it has integrated terminal. So I thought, wouldnโ€™t it be nice to just open any REPL in a terminal and somehow send selected code to the REPL. Without any configuration, even with REPL on a remote server.

So I wrote a little extension โ€“ SendToREPL. In action with Python REPL:

How does it work? Letโ€™s look at the initial version of the extension:

const vscode = require('vscode');
let terminal;

function activate(context) {
    terminal = vscode.window.createTerminal('SendToREPL terminal');
    terminal.show();

    const command = vscode.commands.registerTextEditorCommand('extension.sendToREPL', (textEditor) => {
        const code = textEditor.document.getText(textEditor.selection);
        terminal.sendText(code);
    });

    context.subscriptions.push(command);
}

exports.activate = activate;

function deactivate() {
    if (terminal) {
        terminal.dispose();
    }
}

exports.deactivate = deactivate;

It just creates a terminal when extension loaded and registers extension.sendToREPL command. When the command is triggered (by Ctrl+โ€™/Cmd+โ€™ hotkey or from Quick Open) it gets selected code and sends it to the terminal.

The current version is a bit more advanced, it sends the line with cursor if nothing selected and squash code in one line for some languages e.g. Perl.

Marketplace, github.

Michael Feathers: Working Effectively with Legacy Code



book cover Recently I wanted to read something about refactoring and about working with not so good code, so I decided to read Working Effectively with Legacy Code by Michael Feathers. And it seems to be a good book, it contains a lot of recipes and techniques for making the code more testable, for removing dependencies and for making the code better generally.

Although the book is a bit too OOPish and a bit old, I think itโ€™s still useful.

Change iTerm2 tab and window title colors depending on ssh host



At my work, I use macOS with iTerm2 as a terminal. And iTerm2 has fancy escape codes for changing tab and window titles colors:

\033]6;1;bg;red;brightness;255\a
\033]6;1;bg;green;brightness;255\a
\033]6;1;bg;blue;brightness;255\a

So I thought that it will be nice to distinguish different ssh hosts by color. I found on Stack Overflow how to generate color from a string and wrote a python script that extracts host from command line arguments and prints fancy sequences:

#!/usr/bin/python

import sys


def get_host():
    for arg in sys.argv[1:]:
        if not arg.startswith('-'):
            return arg


def str_to_color(s):
    hash = 0
    for c in s:
        hash = ord(c) + ((hash << 5) - hash)

    for i in range(3):
        yield (hash >> (i * 8)) & 0xff


def generate_seqs(color):
    seq = '\033]6;1;bg;{};brightness;{}\a'
    names = ['red', 'green', 'blue']
    for name, v in zip(names, color):
        yield seq.format(name, v)


if __name__ == '__main__':
    host = get_host()
    if host:
        color = str_to_color(host)
        for seq in generate_seqs(color):
            sys.stdout.write(seq)

In action:

โžœ ./ssh_color.py mrw.wtf
]6;1;bg;red;brightness;173]6;1;bg;green;brightness;84]6;1;bg;blue;brightness;51

Now we need to create a bash/zsh function that will call our script, run ssh and reset color on exit:

ssh_color () {
    ssh_color.py $*  # I put script in /usr/local/bin/
    trap 'echo -e "\033]6;1;bg;*;default\a"' INT EXIT
    ssh $*
}

alias ssh=ssh_color

And it just works:

screenshot

Nigel Poulton: The Kubernetes Book



book cover white Recently I decided to read something more about Kubernetes and found The Kubernetes Book by Nigel Poulton. And Iโ€™ve made a wrong choice because itโ€™s an introductory book explaining basic concepts with very simple examples.

I donโ€™t know, maybe itโ€™s a good book to start working with Kubernetes.

Jim Webber, Ian Robinson, Emil Eifrem: Graph Databases



book cover white Recently I wanted to read something about graph databases and in the Humble Book Bundle, I found Graph Databases by Jim Webber, Ian Robinson and Emil Eifrem. The book is mostly focused on neo4j but has a bit of information about other databases. It has examples of graph data models and real world use cases, also the book contains an information about theoretical parts and neo4j internals. And thereโ€™s a lot about Cypher language.

Although in some chapters the book can be described by anythingโ€™s a graph if youโ€™re brave enough.

Building a graph of flights from airport codes in tweets



A lot of people (at least me) tweet airports codes like PRG โœˆ AMS before flights. So I thought it will be interesting to draw a directed graph of flights and airports. Where airports are nodes and flights are edges.

First of all, I created a twitter application, authorized my account within it and got all necessary credentials:

TWITTER_CONSUMER_KEY = ''
TWITTER_CONSUMER_SECRET = ''
TWITTER_ACCESS_TOKEN = ''
TWITTER_ACCESS_TOKEN_SECRET = ''
USER_ID = ''

As a special marker I chose airplane emoji:

MARKER = 'โœˆ'

Then I tried to receive all my tweets with that marker but stuck with a huge problem, twitter REST API doesnโ€™t work with emojis in a search query. So I decided to receive a whole timeline and filter it manually. So only the last 3200 tweets will be parsed. Working with twitter API is very easy with tweepy:

import tweepy


def get_tweets():
    auth = tweepy.OAuthHandler(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET)
    auth.set_access_token(TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET)
    api = tweepy.API(auth)
    cursor = tweepy.Cursor(api.user_timeline,
                           user_id=USER_ID,
                           exclude_replies='true',
                           include_rts='false',
                           count=200)
    return cursor.items()
>>> for tweet in get_tweets():
...     print(tweet)
... 
Status(_api=<tweepy.api.API object at 0x7f876a303ac8>, ...)

Then I filtered tweets with โœˆ in its text:

flight_texts = (tweet.text for tweet in get_tweets()
                if MARKER in tweet.text)
>>> for text in flight_texts:
...     print(text)
...
ICN โœˆ๏ธ IKT
IKT โœˆ๏ธ ICN
DME โœˆ๏ธ IKT

As some tweets may contain more than one flight, like LED โœˆ DME โœˆ AUH, itโ€™s convenient to extract all three letter parts and build flights like LED โœˆ DME and DME โœˆ AUH:

def get_flights(text):
    parts = [part for part in text.split(' ') if len(part) == 3]
    if len(parts) < 2:
        return []

    return zip(parts[:-1], parts[1:])


flights = [flight for text in flight_texts
           for flight in get_flights(text)]
uniq_flights = list(set(flights))
>>> uniq_flights
[('ICN', 'IKT'), ('IKT', 'ICN'), ('DME', 'IKT')]

From edges in uniq_flights itโ€™s very easy to get all nodes:

airports = [airport for flight in flights
            for airport in flight]
uniq_airports = list(set(airports))
>>> uniq_airports
['ICN', 'IKT', 'DME']

So now itโ€™s possible to create a graph with networkx and draw it with matplotlib:

import networkx
from matplotlib import pyplot


graph = networkx.DiGraph()
graph.add_nodes_from(uniq_airports)
graph.add_edges_from(uniq_flights)
networkx.draw(graph, with_labels=True, node_size=1000)
pyplot.draw()
pyplot.show()

The graph is very ugly:

But itโ€™s simple to improve it by using different colors depending on nodes and edges weight, and by using graphviz.

from collections import Counter
from matplotlib import cm


def get_colors(all_records, uniq_records):
    counter = Counter(all_records)
    max_val = max(counter.values())
    return [counter[record] / max_val
            for record in uniq_records]


networkx.draw(graph, 
              with_labels=True,
              node_size=1000,
              width=1.5,
              pos=networkx.nx_pydot.graphviz_layout(graph, prog='neato'),
              cmap=cm.get_cmap('Pastel1'),
              edge_cmap=cm.get_cmap('Pastel2'),
              edge_color=get_colors(flights, uniq_flights),
              node_color=get_colors(airports, uniq_airports))
pyplot.draw()
pyplot.show()

So now itโ€™s much nicer:

Gist with sources.

Updated graph from May 2018.