Do you play guitar? Do you always have to look up guitar chords online when playing songs with some less commonly used chords?
How about let’s build a command line tool (CLI), that takes in the name of a chord, and outputs a diagram about how to play it.
Today, I will share with you how to build a command line tool in Rust from scratch with the above idea.
End Goal
By the end, we would like to build a CLI named chord
. It takes a single string as input — the name of the chord. And it will print the chord diagram. Like this:
$ chord C
x ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ ◯ │
├─┼─┼─┼─┼─┤
│ │ ◯ │ │ │
├─┼─┼─┼─┼─┤
│ ◯ │ │ │ │
└─┴─┴─┴─┴─┘
Why Rust
In my last post, Building a gRPC Server with Rust, I already shared why Rust is interesting and why you should learn Rust. Here is one thing that I want to emphasize from that post:
Based on Stack Overflow Survey: “For the sixth-year, Rust is the most loved language.”
Rust is the most loved language. Stack Overflow Survey 2021
Not only is Rust great for building backend services, it is also super easy to build CLIs. Let’s dive right in.
Install Rust
Install Rust with the following:
$ curl --proto '=https' --tlsv1.2 -sSf <https://sh.rustup.rs> | sh
For more information about installation, check out: https://www.rust-lang.org/tools/install.
Create a new Rust Project
$ cargo new chord --bin
Created binary (application) `chord` package
Let’s compile and run the program and make sure that everything is set up properly:
$ cd chord
$ cargo run
Hello, world!
This is what we have so far:
|-Cargo.toml
|-Cargo.lock
|-src
| |-main.rs
Print the Fretboard
A guitar has 6 strings, and we usually use the fretboard to represent the finger placements.
This is what main.rs
looks like so far:
fn main() {
println!("Hello, world!");
}
Let’s update it to print an empty fretboard:
const FRETBOARD: &str = "◯ ◯ ◯ ◯ ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
└─┴─┴─┴─┴─┘";
fn main() {
println!("{}", FRETBOARD);
}
Running the program, here is what we have so far:
$ cargo run
◯ ◯ ◯ ◯ ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
└─┴─┴─┴─┴─┘
Clap
To add the capability of parsing command line arguments, we will bring in a library https://docs.rs/clap/latest/clap/.
To add clap as a dependency to our project:
$ cargo add clap --features derive
This simply will add the following line to cargo.toml
, which is where all the dependencies are defined for Rust:
clap = { version = "3.2.21", features = ["derive"] }
And update main.rs
to the following:
With Rust macros, it is possible for clap to make it incredibly simple to annotate the Args
and get argument parsing for free.
/// A CLI to show you how to play a guitar chord
#[derive(Parser, Debug)]
#[clap(version, about)]
struct Args {
/// Name of the chord
#[clap()]
name: String,
}
Let’s try it out:
$ cargo run -- --help
chord 0.1.0
A CLI to show you how to play a guitar chord
USAGE:
chord <NAME>
ARGS:
<NAME> Name of the chord
OPTIONS:
-h, --help Print help information
-V, --version Print version information
$ cargo run -- C
This is how you play 'C' chord:
◯ ◯ ◯ ◯ ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ │ │ │ │ │
└─┴─┴─┴─┴─┘
The double dash --
is a very common way to indicate the end of the options for cargo
and the rest is passed on to the CLI program instead. Not only cargo
, but many other shell commands also do this way.
There is another hidden feature that is worth highlighting. Notice that both “A CLI to show you how to play a guitar chord” and “Name of the chord” are comments in our source code? They are also included in the help message.
From the source code:
/// A CLI to show you how to play a guitar chord
#[derive(Parser, Debug)]
#[clap(version, about)]
struct Args {
/// Name of the chord
#[clap()]
name: String,
}
From the output:
chord 0.1.0
A CLI to show you how to play a guitar chord
USAGE:
chord <NAME>
ARGS:
<NAME> Name of the chord
That’s all we need to learn about clap. Next, let’s move on to the actual CLI itself.
The CLI
Update the main function to the following:
Here, if the input chord name is unknown, we will print the “Unknown chord” error message. Otherwise, we overlay the finger placement on top of the empty fretboard we have earlier.
To test things out:
$ cargo run -- C
This is how you play 'C' chord:
x ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ ◯ │
├─┼─┼─┼─┼─┤
│ │ ◯ │ │ │
├─┼─┼─┼─┼─┤
│ ◯ │ │ │ │
└─┴─┴─┴─┴─┘
$ cargo run -- Asus4
Unknown chord 'Asus4'
All looks great! Quick and simple.
Installation
So far, we’ve been compiling and running the CLI. cargo
not only handle building, running, library dependency management, it also handle installation.
Install chord
CLI like so:
cargo install --path .
Now we can use this CLI directly:
$ chord G
This is how you play 'G' chord:
◯ ◯ ◯
┌─┬─┬─┬─┬─┐
│ │ │ │ │ │
├─┼─┼─┼─┼─┤
│ ◯ │ │ │ │
├─┼─┼─┼─┼─┤
◯ │ │ │ │ ◯
└─┴─┴─┴─┴─┘
That, my friend, is how we build a CLI in Rust.
The End
Have you forgotten how to play a chord? Now we have a better tool:) Thanks for reading! As usual, source code in Github.
Check out the blog-post-checkpoint branch for code reference for this blog:
https://github.com/yzhong52/ascii_chord/tree/blog-post-checkpoint
Also, check out the master branch for the most update-to-date version of the CLI:
https://github.com/yzhong52/ascii_chord