After trudging through the grossness of trying to add to $PS1 from a script, I got to thinking it would be fun to have the phase of the moon on my shell prompt[1][2]. You can try my tool out for yourself by running:

$ brew tap pcarleton/moon && brew install moon
... <bunch of installation output> ...
$ moon
๐ŸŒ’
$ PS1="\$(moon)  $PS1"
๐ŸŒ’ $
๐ŸŒ’ $
๐ŸŒ’ $

(Putting that last line in your ~/.profile will keep it around for new sessions).

Goals

I had three goals for this project:

  1. Track the phase of the moon on my shell prompt
  2. Experiment with Rust
  3. Figure out how to distribute code with Homebrew

The first one is pretty self explanatory. I want my shell prompt to lead off with the moon emoji corresponding to the current phase of the moon.

Second, I am trying out Rust for my foray into dumb rewrites of network tools. Writing something that prints a single unicode code point seemed like a good way to refresh myself on the weirdness of Rust.

Third, I use homebrew to install a lot of packages, but never knew quite how it worked or how to set it up for myself. I wanted to figure that out as well.

The rest of this post will dive in to each of the three goals.

Goal #1: Getting the Phase of the Moon

The difficulty of calculating the phase of the moon depends on how accurate you want to be. For crude measurements, you can find a full moon and add 30 days to find the next one. The phases are evenly spaced between.

For more accurate calculations, things get hairy fast[3]. I'm only measuring on the "phase" granularity, so in reality the 30-day addition would probably get me far enough. However, I found an even easier way to get the information and still have it be accurate: The U.S. Navy!

It turns out the US Navy Observatory (USNO) has an API for the phase of the moon. I was really confused as to why they would do this until I read their mission statement:

The Astronomical Applications Department of the U.S. Naval Observatory computes, from fundamental astronomical reference data, the position, brightness, and other observable characteristics of celestial bodies, as well as the circumstances of astronomical phenomena.

This information is of critical importance to navigation, military operations planning, scientific research, surveying, accident reconstruction, architecture, and everyday activities.

In any event, they provide a documented, user friendly HTTP/JSON API to get the phase of the moon. All I needed to do was call the API and convert it to a unicode character.

Brief aside on Moon Emoji

I was curious if the different moon emoji was some of those "combo" emoji (spoiler: they're not). For emoji like: ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ๏ธ, there are actually 3 emoji combined with a "Zero-Width-Joiner" (ZWJ). If you replace the ZWJ with spaces, you get ๐Ÿ‘จโ€ ๐Ÿ‘ฉโ€ ๐Ÿ‘ฆ๏ธ. I was imagining a "MOON" character and a "PHASE" character stuck together to create the different phases of the moon. Unfortunately, it's not as fun, and the Moon emoji are separate code points that are adjacent. They start from "U+1F311" and run through "U+1F31D":

๐ŸŒ‘๐ŸŒ’๐ŸŒ“๐ŸŒ”๐ŸŒ•๐ŸŒ–๐ŸŒ—๐ŸŒ˜๐ŸŒ™๐ŸŒš๐ŸŒ›๐ŸŒœ๐ŸŒ

For a full list of the emoji that DO support ZWJ, checkout this page:
Recommended Emoji ZWJ Sequences, v11.0 (although not all of these are implemented yet.) There's also this Awesome Codepoints page which has some other fun ones like "U+202E" to โ€ฎreverse text. โ€ฎ [4]

Goal #2: Write it in Rust

Working with Rust was a little bit of a wrestling match. The main hiccups I encountered were:

  • str, String, &str, to_owned(), as_str() etc
    • I still don't have a great mental model of this
  • Option vs. Result
  • Converting modified time on a file to a human readable date was surprisingly challenging (it had a lot of steps with errors).
  • Forgetting a semicolon after a match expression.
  • Return values on main (turns out this is fixed now, you can put a return value on main).

Some things that worked well were:

  • serde_json for JSON parsing was very nice to use
  • chrono was reasonably easy to use once I got the timestamp in the right format.
  • reqwest for making an HTTP GET request.

Some things I'm still not clear on:

  • Importing prelude:* from a library
    • I've seen this naming in Haskell before, but I don't really know what it implies
  • How the serde macros work
  • What a Box is and how I should use it for error handling.

Goal #3: Homebrew

Homebrew offers 3 places you can pull software from:

  • "homebrew-core"
  • Casks
  • Thirt party "taps"

Core

Core repo's have requirements laid out in the Acceptable Formulae page. They phrase for me was:

We will reject formulae that seem too obscure, partly because they wonโ€™t get maintained and partly because we have to draw the line somewhere.

My moon tool wasn't going to make the cut.

Cask

Cask attempts to extend Homebrew to GUI's and binaries (homebrew core builds from source). This would work if I wanted to distribute a fully built binary, but since my project is small enough, I figured a tap was the way to go. (My build time is like 2 minutes still, so I may revisit this).

Tap

Distributing via Taps is documented in the docs part of the repo.

Here are the steps I took:

  • Create a "release" on the moon github repo
  • Copy the source tar.gz link
  • Run brew create to generate a skeleton formula
  • Copy the generated formula into homebrew-moon
    • have to name repo "homebrew-$something", (later you'll do "brew tap $github_username/$something", note that homebrew- gets taken off)
  • Edit forumla based on the ripgrep formula to make it work with rust
  • Push this repo (homebrew-moon) to Github
  • Run ``brew tap pcarleton/moon && brew install moon`

Here's how I updated to a new version:

  • Make a new release
  • Get the SHA256 of the tar file
    • curl -L https://github.com/pcarleton/moon/archive/v0.1.0.tar.gz | shasum -a 256 (Make sure to do the -L to get the redirect, this burned me.)
  • Push to homebrew-moon
  • brew upgrade moon

Conclusion

That's all I've got for this post. Hopefully that was helpful, and let me know if you have any


  1. I get a little romantic about the moon. This is like the next incarnation of a D3 demo I made a few years ago. โ†ฉ๏ธŽ

  2. To clarify, this is different from the last post because I always want it to be part of my prompt. In the scenario from my previous post, I wanted to "activate" some state in my session and have that show up on my shell prompt. (similar to virtualenv). In this case, I want the moon emoji to show up unconditionally. โ†ฉ๏ธŽ

  3. Checkout the wikipedia page for Lunar Month to see some of the factors. If you're REALLY interested, checkout Astronomical Algorithms by Jean Meeus. Also check out these drawings by @sailorhg. โ†ฉ๏ธŽ

  4. I put the HTML escape code &#x202E; which caused these letters to be reversed. โ†ฉ๏ธŽ