Flecks of Rust #5: Starter template for command-line tools

The Cargo tool can generate a new application or library project for you with cargo new. It's very basic: just src/main.rs that prints "Hello, world!", a TOML file and a .gitignore file. By default you also get a Git repository.

At the other end of the spectrum there is cargo-generate, which lets you create a project based on a Git repository. It's very configurable, but can be overkill in many cases.

People have suggested that a template feature for Cargo would be nice, but the response hasn't been too enthusiastic, maybe because cargo-generate already exists.

In between these, I've found that I can get by with my own main.rs and Cargo.toml files, which I just copy over those generated by the cargo new. It's manual labor and not configurable, but it saves me a lot of typing, because I usually need the same crates and the same main program structure.

This is the main.rs I currently use:

use std::env;
use std::io::Error;

fn run(args: &[String]) -> Result<(), &'static str> {
    for (i, arg) in args.iter().enumerate() {
        println!("{}: {}", i + 1, arg);
    }

    Ok(())
}

fn main() -> Result<(), Error> {
    env_logger::init();

    let args: Vec<String> = env::args().collect();

    std::process::exit(match run(&args[1..]) {
        Ok(_) => exitcode::OK,
        Err(err) => {
            eprintln!("error: {:?}", err);
            exitcode::USAGE
        }
    });
}

I save this as main.rs in a subdirectory called _template in the directory where I keep my Rust projects. When I want to use it, I just change to the directory of a newly created Rust project and copy the file over:

cp ../_template/main.rs src/main.rs

This is what needs to be appended to the Cargo.toml file's [dependencies] section to be able to compile that (currently; as new versions appear I will update it):

exitcode = "1.1.2"
env_logger = "0.9.0"
log = "0.4.14"

I save this as Cargo.toml in ../_templates and use the cat command:

cat ../_template/Cargo.toml >>Cargo.toml

To get a README file in Markdown format I just generate a very simple file with the name of the project, picked up from the current directory:

echo "# $(basename $(pwd))" >>README.md

Finally, since any open source code I publish is usually under the MIT license, I drop a LICENSE file in:

cp ../_template/LICENSE .

All of this collected into a shell script:

#!/usr/bin/env bash

echo "Copying main.rs"
cp ../_template/main.rs src/main.rs

echo "Updating Cargo.toml"
cat ../_template/Cargo.toml >>Cargo.toml

echo "Creating README.md"
echo "# $(basename $(pwd))" >>README.md

echo "Copying LICENSE"
cp ../_template/LICENSE .

echo "Building"
cargo build

retval=$?
if [ $retval -eq 0 ]; then
    echo "Committing changes"
    git add .
    git commit -m "Initial commit"
fi

exit $retval

I put this in the ../template directory as init and make it executable. A new Cargo project is then created, initialized and built like this:

cargo new flub; cd flub; ../_template/init

You should see something like this:

Created binary (application) `flub` package
Copying main.rs
Updating Cargo.toml
Creating README.md
Copying LICENSE
Building
    Updating crates.io index
    Compiling memchr v2.4.1
    Compiling libc v0.2.114
    Compiling log v0.4.14
    Compiling cfg-if v1.0.0
    Compiling regex-syntax v0.6.25
    Compiling humantime v2.1.0
    Compiling termcolor v1.1.2
    Compiling exitcode v1.1.2
    Compiling aho-corasick v0.7.18
    Compiling atty v0.2.14
    Compiling regex v1.5.4
    Compiling env_logger v0.9.0
    Compiling flub v0.1.0 (/Users/me/Projects/Rust/flub)
    Finished dev [unoptimized + debuginfo] target(s) in 6.34s
Committing changes
[main (root-commit) 4016e01] Initial commit
    6 files changed, 208 insertions(+)
    create mode 100644 .gitignore
    create mode 100644 Cargo.lock
    create mode 100644 Cargo.toml
    create mode 100644 LICENSE
    create mode 100644 README.md
    create mode 100644 src/main.rs

When you run the program, it prints the command-line arguments:

% cargo run --quiet foo bar baz
1: foo
2: bar
3: baz

Hope you found this useful!