cli_fmt 0.2.0

CLI output formatting utilities for command-line applications
Documentation
# Technical Specification: `cli_fmt`

## Section 1: Overview

### 1.1. Purpose

CLI application building blocks for command-line tools. Provides utilities specifically designed for CLI applications, distinct from general-purpose string manipulation.

### 1.2. Architectural Distinction

**cli_fmt vs strs_tools:**

- **`strs_tools`**: General-purpose string/ANSI manipulation (any application)
  - Pure text transformation utilities
  - No assumptions about use case (terminals, logs, files, any display)
  - Reusable across web apps, embedded systems, servers, CLI tools

- **`cli_fmt`**: CLI-application-specific helpers (command-line tools only)
  - Policy decisions specific to CLI conventions
  - Stream ordering (stderr before stdout)
  - Output formats specific to command execution
  - CLI-focused configuration and APIs

**Policy Decisions in cli_fmt:**
- Stream merging: stderr appears before stdout (CLI convention)
- Output filtering: head/tail semantics match Unix tools
- Builder pattern: optimized for CLI configuration chains

### 1.3. Migration History

**Evolution:**
1. **v0.30.x**: Original implementation in `unilang::output` (449 lines)
2. **v0.31.0-0.43.0**: Migrated to `strs_tools::output` (eliminated duplication)
3. **v0.44.0+**: Moved to dedicated `cli_fmt` crate (proper architectural separation)

**Rationale for Separation:**
- Architectural boundary: CLI-specific ≠ general-purpose string utilities
- Single Responsibility: strs_tools focuses on text manipulation, cli_fmt on CLI helpers
- Future growth: Room for additional CLI utilities (progress bars, tables, prompts)

---

## Section 2: Module Specifications

### 2.1. Module: `output`

#### Purpose

Unified CLI output processing utilities for command-line applications. Provides head/tail line filtering, width truncation, and stream merging with ANSI preservation.

**Key Features:**
- Head/tail line filtering (show first N and/or last M lines)
- ANSI-aware width truncation (preserves color codes during truncation)
- Stream merging (combine stdout/stderr with configurable ordering)
- Builder pattern API for clean configuration
- Zero data loss - tracks omitted lines and truncation status

#### Core Data Structures & API

**Configuration:**

*   **`struct OutputConfig`**: Configuration for output processing with builder pattern
    *   `head: Option<usize>` - Show only first N lines
    *   `tail: Option<usize>` - Show only last N lines
    *   `width: Option<usize>` - Maximum visible characters per line (ANSI-aware)
    *   `width_suffix: String` - Suffix when width-truncated (default: "→")
    *   `stream_filter: StreamFilter` - Which streams to process
    *   `unicode_aware: bool` - Use grapheme-based width (requires `ansi_unicode` feature)

*   **Builder Methods:**
    *   `OutputConfig::new() -> Self` - Create with defaults
    *   `.with_head(count: usize) -> Self` - Set head line limit
    *   `.with_tail(count: usize) -> Self` - Set tail line limit
    *   `.with_width(width: usize) -> Self` - Set width limit
    *   `.with_width_suffix(suffix: impl Into<String>) -> Self` - Customize truncation suffix
    *   `.with_stream_filter(filter: StreamFilter) -> Self` - Set stream selection
    *   `.with_unicode_aware(enabled: bool) -> Self` - Enable grapheme-based width

*   **Helper Methods:**
    *   `.is_default() -> bool` - Check if config is default (no processing)
    *   `.has_processing() -> bool` - Check if any processing is configured

**Stream Selection:**

*   **`enum StreamFilter`**: Controls which streams are processed
    *   `Both` - Process both stdout and stderr (default, stderr appears before stdout)
    *   `Stdout` - Process only stdout
    *   `Stderr` - Process only stderr

**Result Type:**

*   **`struct ProcessedOutput`**: Result of output processing
    *   `content: String` - Processed output text
    *   `lines_omitted: usize` - Number of lines omitted by head/tail filtering
    *   `width_truncated: bool` - Whether any line was truncated by width limit

**Processing Functions:**

*   **`process_output(stdout: &str, stderr: &str, config: &OutputConfig) -> ProcessedOutput`**
    - Main entry point for CLI output processing
    - Applies stream selection → line filtering → width truncation in sequence
    - Preserves ANSI escape codes throughout processing
    - Returns processed output with metadata about omissions

*   **`merge_streams(stdout: &str, stderr: &str, filter: &StreamFilter) -> String`**
    - Merge stdout and stderr streams based on filter
    - stderr appears before stdout when both selected (CLI convention)
    - Returns empty string if both inputs empty

#### Processing Pipeline

Output processing follows this sequence:

1. **Stream Selection**: Merge stdout/stderr based on `stream_filter`
2. **Line Filtering**: Apply head/tail limits via `strs_tools::string::lines::{head, tail, head_and_tail}`
3. **Width Truncation**: Truncate each line if `width` configured
   - Only truncates if line exceeds max_width (uses `strs_tools::ansi::truncate_lines()`)
   - Preserves ANSI codes
   - Adds `width_suffix` indicator when truncated

#### ANSI Handling

*   Width measurement excludes ANSI escape sequences
*   Truncation preserves ANSI formatting
*   Reset codes automatically added after truncation for safety
*   Lines that fit exactly are NOT truncated (respects visible width boundary)

#### Unicode Support

*   **Char-based (default)**: Fast, works for ASCII/Latin text
*   **Grapheme-based (`unicode_aware: true`)**: Accurate for CJK, emoji, combining marks
    - Requires `ansi_unicode` feature
    - Delegates to `strs_tools::ansi::truncate_lines_unicode()`

#### Dependencies on strs_tools

This module uses the following general-purpose functions from `strs_tools`:

- **`strs_tools::ansi::truncate_lines()`** - Multi-line ANSI truncation with boundary detection
- **`strs_tools::ansi::truncate_lines_unicode()`** - Unicode-aware version
- **`strs_tools::string::lines::head()`** - Extract first N lines
- **`strs_tools::string::lines::tail()`** - Extract last N lines
- **`strs_tools::string::lines::head_and_tail()`** - Extract first N and last M lines

These functions were extracted from output to make them available as general-purpose
utilities for any application requiring ANSI-aware text processing.

#### Feature Dependencies

*   **`enabled`**: Master switch that activates core dependencies (included in default features)
*   **`std`**: Standard library support (included in default features)
*   **`full`**: Convenience feature enabling all functionality (`enabled` + `output` + `ansi_unicode`)
*   **`output`**: Main feature flag (requires `enabled`, `std`, `string_split`)
*   **`ansi_unicode`**: Optional, enables grapheme-based Unicode support (passes through to strs_tools)
*   **`string_split`**: Internal feature for string splitting functionality
*   **`use_alloc`**: Allocation support (for no_std environments with allocator)

#### Usage Example

```rust
use cli_fmt::output::*;

// Show first 5 and last 5 lines, max 80 chars width
let config = OutputConfig::default()
  .with_head(5)
  .with_tail(5)
  .with_width(80);

let result = process_output(stdout_str, stderr_str, &config);

println!("{}", result.content);
println!("Lines omitted: {}", result.lines_omitted);
println!("Width truncated: {}", result.width_truncated);
```

#### Migration from unilang::output

| Old (unilang 0.31.x) | New (cli_fmt) |
|---------------------|------------------|
| `TruncationConfig { head: Some(10), .. }` | `OutputConfig::default().with_head(10)` |
| `apply_truncation(stdout, stderr, &config)` | `process_output(stdout, stderr, &config)` |
| `TruncatedOutput` | `ProcessedOutput` |
| `OutputFilter::Both` | `StreamFilter::Both` |
| `truncate_head(text, 10)` | `strs_tools::string::lines::head(text, 10)` |
| `truncate_tail(text, 10)` | `strs_tools::string::lines::tail(text, 10)` |
| `truncate_width(text, 80)` | `strs_tools::ansi::truncate_if_needed(text, 80, &opts)` |

**Key Improvements:**
- Builder pattern replaces struct initialization
- Configurable suffix (vs hardcoded arrow)
- Proper width boundary detection (doesn't truncate text that fits exactly)
- Two-tier Unicode support (char vs grapheme)

---

## Section 3: Architecture Compliance

### 3.1. Architectural Principles

This crate adheres to clean separation of concerns:

1. **Delegates to strs_tools**: Uses general-purpose functions for text transformation
2. **CLI-Specific Policy**: Implements CLI conventions (stream ordering, output formats)
3. **Builder Pattern**: Provides ergonomic configuration for CLI tools
4. **Zero Dependencies**: Only depends on strs_tools (which is general-purpose)

### 3.2. Design Decisions

**Why stderr before stdout?**
CLI convention - errors should be visible immediately without scrolling past normal output.

**Why builder pattern?**
CLI tools often need to configure multiple options. Builder pattern enables:
```rust
let config = OutputConfig::default()
  .with_head(10)
  .with_tail(5)
  .with_width(80)
  .with_stream_filter(StreamFilter::Both);
```

Instead of verbose struct initialization:
```rust
let config = OutputConfig {
  head: Some(10),
  tail: Some(5),
  width: Some(80),
  stream_filter: StreamFilter::Both,
  ..Default::default()
};
```

**Why track metadata (lines_omitted, width_truncated)?**
CLI tools often need to inform users about data omissions for transparency.

---

## Section 4: Future Roadmap

Potential additions to cli_fmt (as separate modules):

- **Progress Bars**: CLI progress indication
- **Tables**: Formatted table output with ANSI support
- **Prompts**: Interactive CLI prompts
- **Colors**: CLI color scheme management
- **Spinners**: Loading indicators

These would follow the same principle: CLI-specific helpers that delegate to
general-purpose utilities in strs_tools where applicable.

---

## Section 5: Testing

**Test Coverage:**
- 31 integration tests (output module)
- 4 doc tests
- Total: 35 tests

**Test Categories:**
- OutputConfig behavior
- Stream filtering (stdout/stderr selection)
- Head/tail line filtering
- Width truncation with ANSI preservation
- Combined operations (head+tail+width)
- Unicode handling

**Test Location:** `tests/output.rs`

---

## Section 6: Performance Characteristics

| Operation | Complexity | Notes |
|-----------|------------|-------|
| `process_output()` | O(n) | Single pass through text |
| Stream merging | O(n) | Simple concatenation |
| Line filtering | O(n) | Uses strs_tools::string::lines |
| Width truncation | O(n × m) | n = text length, m = avg line count |

All operations minimize allocations and use efficient string handling.

---

## Appendix A: Version History

See `changelog.md` for detailed version history.

**Current Version:** 0.1.0 (Initial release)