Thuta Learning
Rust
ProjectsProgrammingbeginner

Project — CLI Content Analyzer

What you'll walk away with

  • Explain the core ideas behind Project — CLI Content Analyzer
  • Run the sample Rust code and verify its output
  • Apply the technique correctly to the Tutorial Platform and production scenarios

Build the mental model

This project is the capstone that pulls together the Basic and Intermediate chapters — parsing a command-line argument (a directory path) from `std::env::args()` (Lesson 5's control flow), reading each file in that directory with `Result<String, io::Error>` (Lesson 11's error handling), accumulating `LessonStat` structs (Lesson 9's struct, Lesson 12's `Vec`), and finally printing a report — four layers, each designed deliberately. Error-handling strategy matters most in a real CLI tool: if one file can't be read (a permission issue, corruption), you don't want the whole program to crash — skipping just that file and logging the error is the practical application of Lesson 20's "external input error → handle gracefully" strategy; a `main` function full of `.unwrap()` calls would let one bad file abort the entire batch. Managing ownership efficiently means reading file content as an owned `String` and then passing `count_words(&content)` (Lesson 7's borrow) — moving the content into that function would make it impossible to also extract an excerpt from the same content afterward. Only by actually running this project do Lessons 6 through 13's theory click into place inside one production-shaped tool.

Connect it to a real scenario

In `fn main()`, parse the directory path argument with `std::env::args().nth(1)` — if no argument is given, print a usage message and exit via `std::process::exit(1)`. Get the file list with `std::fs::read_dir(path)?`, and for each file use the pattern `match std::fs::read_to_string(entry.path()) { Ok(content) => { ... }, Err(e) => eprintln!("skip {path}: {e}") }`, so the rest of the files keep processing even if one fails. Build a `LessonStat::new(file_name, count_words(&content))` struct per file and push it into a `Vec<LessonStat>` — once done, build a `HashMap<String, u32>` (difficulty label to count) and print it as a summary report.

Try the working example

rust
use std::collections::HashMap;
use std::fs;

struct LessonStat { name: String, word_count: u32 }

fn count_words(text: &str) -> u32 {
    text.split_whitespace().count() as u32
}

fn main() {
    let dir = std::env::args().nth(1).unwrap_or_else(|| ".".to_string());
    let mut stats: Vec<LessonStat> = Vec::new();

    if let Ok(entries) = fs::read_dir(&dir) {
        for entry in entries.flatten() {
            let path = entry.path();
            match fs::read_to_string(&path) {
                Ok(content) => {
                    let name = path.display().to_string();
                    stats.push(LessonStat { name, word_count: count_words(&content) });
                }
                Err(e) => eprintln!("skip {}: {e}", path.display()),
            }
        }
    }

    let mut total_by_bucket: HashMap<&str, u32> = HashMap::new();
    for stat in &stats {
        let bucket = if stat.word_count < 500 { "short" } else { "long" };
        *total_by_bucket.entry(bucket).or_insert(0) += 1;
    }
    println!("{total_by_bucket:?}");
}
You should see
Given a directory argument, you print a word-count bucket summary (`{"short": N, "long": M}`) across every file in it.

5-minute try-it

Extend the analyzer above to also track each file's longest line and add "longest line overall" to the final report — also report how many files failed to read.

One important caution

Using `fs::read_to_string(&path).unwrap()` inside the file-reading loop — one bad file (a permission issue, corrupted encoding) can crash the entire batch; the Err arm needs graceful handling.

Assuming the directory argument will always be provided and calling `args().nth(1).unwrap()` directly — missing the argument panics instead of giving the user a helpful error message.

The Rust Programming Language — An I/O ProjectRust

Easy traps

  • Using `fs::read_to_string(&path).unwrap()` inside the file-reading loop — one bad file (a permission issue, corrupted encoding) can crash the entire batch; the Err arm needs graceful handling.
  • Assuming the directory argument will always be provided and calling `args().nth(1).unwrap()` directly — missing the argument panics instead of giving the user a helpful error message.
  • Validate sample code in a local or test environment before applying it to a production system.

Exercise

Extend the analyzer above to also track each file's longest line and add "longest line overall" to the final report — also report how many files failed to read.

You'll know it worked when: Given a directory argument, you print a word-count bucket summary (`{"short": N, "long": M}`) across every file in it.

Project — CLI Content Analyzer | Thuta Learning