cron-when 0.5.0

A CLI tool to parse cron expressions and display next execution times with human-readable durations
Documentation
# CLI Architecture

This document describes the modular CLI architecture used in this project. This pattern provides excellent separation of concerns and is ideal for template projects.

> **📋 Template Note:** This architecture is designed to be copied to other Rust CLI projects. The separation allows you to keep the entire `src/cli/` directory and only replace your domain-specific logic (like `src/crontab.rs` and `src/output.rs` in this example). See [`.github/TEMPLATE.md`].github/TEMPLATE.md for usage instructions.

## Directory Structure

```
src/cli/
├── actions/           # Action definitions and execution
│   ├── mod.rs        # Action enum
│   ├── single.rs     # Single expression logic
│   ├── file.rs       # File parsing logic
│   └── crontab.rs    # System crontab logic
├── commands/          # CLI command definitions
│   └── mod.rs        # Clap command structure
├── dispatch/          # ArgMatches → Action conversion
│   └── mod.rs         # Handler logic
├── mod.rs             # Module exports
├── start.rs           # Main orchestrator
└── telemetry.rs       # Tracing and logging
```

## Data Flow

```
bin/cron-when.rs
cli::start()
┌─────────────────────────────────────────────┐
│ 1. commands::new().get_matches()            │  Parse CLI arguments
│    ↓                                         │
│ 2. telemetry::Level::from(verbose_count)    │  Extract verbosity
│    ↓                                         │
│ 3. telemetry::init(level)                   │  Initialize logging
│    ↓                                         │
│ 4. dispatch::handler(&matches)              │  Convert to Action
│    ↓                                         │
│ 5. Execute via binary match                 │  Execute action
└─────────────────────────────────────────────┘
```

## Module Responsibilities

### 1. `commands/mod.rs` - CLI Definition

**Purpose:** Define the CLI structure using clap
**Responsibility:** ONLY command-line argument definitions
**No business logic**

```rust
pub fn new() -> Command {
    Command::new(env!("CARGO_PKG_NAME"))
        .arg(arg_cron())
        .arg(arg_file())
        // etc.
}
```

**Key Points:**
- Pure clap definitions
- Uses `env!()` macros for metadata
- Fully testable in isolation
- Helper functions for each argument to keep code clean and satisfy lints

### 2. `dispatch/mod.rs` - ArgMatches → Action

**Purpose:** Convert clap's `ArgMatches` into typed `Action` enum
**Responsibility:** Validation and routing logic

```rust
pub fn handler(matches: &ArgMatches) -> Result<Action> {
    if matches.get_flag("crontab") {
        Ok(Action::Crontab { verbose, color })
    } else if let Some(file) = matches.get_one::<String>("file") {
        Ok(Action::File { path, verbose, color })
    }
    // etc.
}
```

**Key Points:**
- Single source of truth for argument → action mapping
- Error handling for missing/invalid arguments
- Extracts and validates all parameters (including environment variables for colors)
- Returns strongly-typed Action

### 3. `actions/` - Action Definition & Execution

**Purpose:** Define all possible actions and their execution logic

#### `actions/mod.rs` - Action Enum

```rust
#[derive(Debug)]
pub enum Action {
    Single { expression: String, verbose: bool, next: Option<u32>, color: bool },
    File { path: PathBuf, verbose: bool, color: bool },
    Crontab { verbose: bool, color: bool },
}
```

#### `actions/*.rs` - Execution Logic

Each variant has a corresponding module in `src/cli/actions/` that handles its specific execution logic, calling into domain services like `crontab` and `output`.

**Key Points:**
- Action enum is the core contract
- Clear separation between definition and execution
- Easy to add new actions by adding variants and corresponding modules

### 4. `telemetry.rs` - Observability/Tracing

**Purpose:** Production-ready telemetry initialization with OpenTelemetry support
**Responsibility:** Set up logging and distributed tracing compatible with multiple providers

```rust
pub fn init(verbosity_level: Option<tracing::Level>) -> Result<()> {
    // Initialize tracing-subscriber with console and optional OpenTelemetry layers
}

pub fn shutdown_tracer() {
    // Gracefully shutdown tracer provider and flush pending spans
}
```

**Key Points:**
- Uses `std::sync::OnceLock` for safe, one-time initialization (Rust 2024)
- Initializes `tracing-subscriber` for structured logging
- **Production-ready** OpenTelemetry gRPC exporter with TLS and compression
- Graceful shutdown with span flushing

### 5. `start.rs` - Main Orchestrator

**Purpose:** Coordinate the initial CLI setup
**Responsibility:** Execute the setup steps in order

```rust
pub fn start() -> Result<Action> {
    let matches = commands::new().get_matches();
    let verbosity = get_verbosity_level(matches.get_count("verbose"));
    telemetry::init(verbosity)?;
    let action = dispatch::handler(&matches)?;
    Ok(action)
}
```

**Key Points:**
- **No business logic** - pure orchestration
- Returns the `Action` to the binary for execution
- Easy to understand at a glance

### 6. `mod.rs` - Module Exports

**Purpose:** Control public API of the cli module

```rust
pub mod actions;
pub mod commands;
pub mod dispatch;
pub mod telemetry;

mod start;
pub use self::start::start;
```

**Key Points:**
- Only `start` is re-exported for simple usage in `main.rs`
- Other modules are public but not re-exported
- Clear public API: `cli::start()`

## Benefits of This Architecture

### 1. Separation of Concerns

Each module has ONE job:
- `commands` → Define CLI
- `dispatch` → Route arguments
- `actions` → Define & execute
- `telemetry` → Handle logging
- `start` → Orchestrate

### 2. Testability

Every module can be tested independently. Output logic is also testable by using generic `io::Write` targets in the `output` module.

### 3. Maintainability

**Adding a new command/action:**

1. Add argument in `commands/mod.rs` (using a helper function)
2. Add variant in `actions/mod.rs`
3. Add routing in `dispatch/mod.rs`
4. Add execution module in `actions/`
5. Done!

### 4. Scalability

As your CLI grows, the structure remains clean and organized. The use of strict lints ensures high code quality across all modules.

## Summary

This architecture provides a solid foundation for building production-grade Rust CLIs. It emphasizes modularity, testability, and observability, making it an excellent template for new projects.