IS 4010: Application Development with Artificial Intelligence

Week 14: CLI applications

Brandon M. Greenwell

Week 14 overview

Session 1: CLI architecture and clap - The anatomy of a CLI tool - Argument parsing with the clap crate - Subcommands and flags

Session 2: Capstone integration - Managing external crates (rand) - Structuring larger projects (modules) - Synthesizing traits, ownership, and error handling

Session 1: CLI architecture and clap

The value of a CLI

While graphical interfaces (GUIs) are great for end-users, command-line interfaces (CLIs) remain the backbone of developer tooling.

Why build CLI tools? - Automation: Easily scriptable in CI/CD pipelines - Speed: Faster for power users to execute complex commands - Composability: Can be piped together (grep | wc -l) - Low Overhead: Minimal system resources required

Think about the tools you use: git, python, cargo, pytest.

Anatomy of a CLI command

cargo run -- random --length 16 --symbols

Let’s break this down: 1. Executable: cargo run -- builds and starts the Week 14 binary 2. Subcommand: random (A major action the program can take) 3. Option / Argument: --length 16 (A named parameter with a value) 4. Flag: --symbols (A boolean toggle, no value needed)

Argument parsing in Rust

You could parse arguments manually using std::env::args().

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() > 1 && args[1] == "random" {
        // ... string matching, error handling, manual parsing ...
    }
}

Try it yourself: Run in Rust Playground

Why is this bad? - Manual string parsing is error-prone. - Type conversion (String to integer) is tedious. - Generating --help menus manually is a nightmare.

Enter clap

clap (Command Line Argument Parser) is a widely used crate for building CLIs in Rust.

Features: - Declarative macro-based or derive-based API - Automatic --help and --version generation - Type parsing and configurable value validation - Subcommand routing

The course uses clap’s derive API so the command structure is visible in Rust structs and enums.

Setting up clap

First, add it to your Cargo.toml. The derive feature enables the macro API used in these examples.

[dependencies]
clap = { version = "4.5", features = ["derive"] }

Defining a CLI with clap using a struct

We define our expected arguments as a standard Rust struct.

use clap::Parser;

/// A simple password generator
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Cli {
    /// Length of the password
    #[arg(short, long, default_value_t = 12)]
    length: u8,

    /// Include special symbols
    #[arg(short, long)]
    symbols: bool,
}

fn main() {
    let cli = Cli::parse();
    println!("Length: {}, Symbols: {}", cli.length, cli.symbols);
}

Subcommands with clap using an enum

For complex tools (like git or cargo), you use subcommands. We model these using an enum.

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(version, about)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Generate a random password
    Random {
        #[arg(short, long, default_value_t = 12)]
        length: u8,
    },
    /// Validate an existing password
    Validate {
        password: String,
    },
}

Pattern matching on subcommands

Once parsed, use match to route each subcommand:

fn main() {
    let cli = Cli::parse();

    match &cli.command {
        Commands::Random { length } => {
            println!("Generating password of length {}", length);
            // Call generation logic...
        }
        Commands::Validate { password } => {
            println!("Validating: {}", password);
            // Call validation logic...
        }
    }
}

Session 2: capstone integration

Synthesizing six weeks of Rust

Building a CLI application isn’t just about parsing arguments. It requires pulling together everything we’ve learned:

  1. Project structure: Use modules (mod) to separate the interface from logic.
  2. External crates: Use rand for random generation.
  3. Traits and generics: Format output and reuse behavior.
  4. Ownership: Pass strings between validators and generators safely.

Structuring larger projects

Keep main.rs focused on parsing and routing; place password generation and validation in the supplied modules so they can be tested independently.

src/
├── main.rs       # CLI definition and command routing
├── generator.rs  # Password generation logic
└── validator.rs  # Password strength calculation

In main.rs:

mod generator;
mod validator;

// Now you can call generator::generate_random(...)

Working with the rand crate

Randomness is not built into the Rust standard library (unlike Python’s import random). You must use an external crate.

[dependencies]
rand = "0.8"
use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    
    // Generate a random boolean
    let coin_flip: bool = rng.gen();
    
    // Generate a number in a range
    let secret_pin: u32 = rng.gen_range(0..9999);
}

Selecting random characters

To generate a password, we often need to select random characters from a slice.

use rand::Rng;
use rand::seq::SliceRandom;

fn main() {
    let charset: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";
    let mut rng = rand::thread_rng();
    
    // Pick a random byte from the slice and convert to char
    let random_char = *charset.choose(&mut rng).unwrap() as char;
    
    println!("Random char: {}", random_char);
}

Lab 14: the capstone CLI

For your final lab, you will complete a password utility CLI.

Features to implement: 1. random: Generate passwords from character sets. 2. passphrase: Generate correct-horse-battery-staple style passphrases. 3. pin: Generate numeric PINs. 4. validate: Calculate entropy and check for common patterns in existing passwords.

You will implement the logic in generator.rs and validator.rs, and wire it up to the clap parser in main.rs.

cargo test
cargo fmt
cargo clippy -- -D warnings
cargo run -- random --length 20 --symbols

A green Week 14 badge represents the complete 10-point lab.

Instructions: week14/lab14.md

Reviewing AI prompting for Rust

As you tackle the capstone, remember your AI Copilot strategies:

  • Good Prompt: “I need to implement this Rust function: fn generate_pin(length: usize) -> String. I have the rand crate installed. Explain the approach using a for loop before showing any code.”
  • Bad Prompt: “Write my capstone lab.”

Use AI to help you understand compiler errors, explore the clap documentation, and suggest idiomatic iterator patterns.

Wrapping up Rust

You have moved from Python’s dynamic runtime model to Rust’s compiled type and ownership systems.

You now have enough vocabulary to compare the trade-offs and continue learning either language.

Lab 14 is the last graded lab. What remains is the final project.

Optional further reading: the advanced AI workflows appendix collects prompting, review, and verification habits worth carrying into the project.