My handy Rust CLI

A week or two ago I started on creating a rust program that would accomplish two things:

  1. Improve my understanding of the package management system for Rust. More specifically, I wanted to understand how to organize modules a little bit better
  2. Create something that I could use to create challenges for myself using an automated and randomized method

I worked on it for a while and then get hung up on the specifics for parsing command line arguments. The approaches I thought about either seemed way too complicated, or were way too hacky to really be proud of writing them. I let the project for a sit for a little bit.

After some time, I realized that I could probably just outsource the parsing logic. Given that I was, at the time, the furthest from the first person ever needing to parse command line arguments, there had to be a library for it.

I was indeed correct and found clap. This library turned my project from a headache into a neat little organized oasis. More on that later. First, I’m gonna describe how the program works.

How the program works

The utility will look at a set of text files that you give it and create a set of challenges. For example, you could have some text files that describe pixel art challenges you can try.

theme.txt

Draw a house
Draw a mountain
Draw a bear

size.txt

at 128x64 pixels
at 256x128 pixels
at 64x64 pixels

palette.txt

with Midnight Ablaze Palette
with Oil 6 Palette
with Apollo Palette
with Spanish sunset palette

With these files in the root directory, you can run something like this:

cargo run -- -f theme.txt size.txt palette.txt -c 5

This will…

  1. Look through the listed files indicated by -f
  2. Print out the challenges’ details in the order they were listed, randomly picking an entry in each file. This will be done five times, as indicated by the -c 5 argument. --count can also be used instead.

One example output could be the following:

Draw a house at 64x64 pixels with Midnight Ablaze Palette 
Draw a mountain at 256x128 pixels with Oil 6 Palette
Draw a house at 128x64 pixels with Apollo Palette
Draw a bear at 128x64 pixels with Midnight Ablaze Palette
Draw a mountain at 256x128 pixels with Midnight Ablaze Palette

Now I have 5 challenges for pixel art I can work with! This is great for generating ideas, especially if you fill a text file with a million different ideas, then also fill another text file with a million other ideas to combine with it.

This could also be good for quizzing yourself, learning a new language, inspiration, you name it.

Anyway, so on the topic of my code being a neat organized oasis, Here is the parser and how it is used by my main program:

Parser & friends

cli.rs

use clap::Parser;

#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
pub struct Args {
    #[arg(short, long, num_args = 1.., required = true)]
    pub files: Vec<String>,

    #[arg(short, long, default_value_t = String::from(" "))]
    pub separator: String,

    #[arg(short, long, default_value_t = 1)]
    pub count: i32
}

main.rs

use chalgen::{file_processor, cli};
use clap::Parser;

fn main() {
    let args = cli::Args::parse();
    file_processor::output_challenges(args);
}

The code above does the following:

  1. Define the command line arguments for the library to look for and what characteristics they should have (cli.rs)
  2. Pass configuration that was gathered from command line arguments to the program doing the actual work (main.rs)

To provide some context for cli.rs, the following keywords are important:

  1. short: the flag can be represented by the first letter of the property name. e.g. files can be referred to as -f when calling the command line utility
  2. long: Effectively the same sort of option as short, except the flag is able to be represented by its full name. e.g. count can be represented as --count when calling the command line utility
  3. default_value_t: Essentially, this is what the flag will be set to if it is not mentioned when calling the command line utility
  4. num_args: How many arguments that are expected from the flag. In this case, I use it once with files and the number of arguments is represented by 1.. which essentially means “at least 1.” I specify it like this to provide the user the ability to provide any number of files they want
  5. required: The flag must be used. In this case, the files flag is required

All of the restrictions and allowances defined above are automatically enforced by the clap library. This is a huge weight off my back and allowed me to focus on the actual program (AKA the file_processor::output_challenges(args) part of the code that I don’t show.

Anyway, that’s about it. Here is the repository for the curious! Stay safe and make good choices.