luff 0.2.1

Print files with formatting
Documentation
# CLI Module

**Purpose**: Entry point orchestration — argument parsing, logging setup, output dispatch, and clipboard buffering.

## File Responsibilities

### args.rs

- **Clap derive definitions**: All CLI flags with validation and help text
- **OutputFormat enum**: String parsing (markdown/md/tree)
- **OutputMode enum**: Type-safe combination of `--clip` and `--suppress-stdout`
- **Getters**: Expose parsed values via `const fn` where possible
- **`to_cli_override()`**: Converts explicit CLI flags into `CliOverride` for config precedence
- **Defaults**: Markdown format, unlimited depth, respect gitignore
- **Logic mapping**: Uses `overrides_with` for `--dotfiles` / `--no-dotfiles` mutual exclusion

### buffer.rs

- **`buffer_entries_with_limit()`**: Markdown buffering with safe truncation and size limits
- **`buffer_tree_entries()`**: Tree buffering (batch render, all-or-nothing size gate)
- **`handle_clipboard_mode()`**: Top-level clipboard orchestration — buffers, writes stdout, copies clipboard
- **`MarkdownState`**: Tracks open code fences for rollback on size-limit truncation
- **`BufferResult`**: Carries buffered output, file counts, and whether the limit was hit

### clipboard.rs

- **`copy_to_clipboard()`**: Cross-platform clipboard write via arboard
- **Size guard**: Rejects content > 1 GB before attempting copy
- **Timeout**: 10 s deadline via a spawned thread + channel, prevents hangs on locked clipboards
- **Platform support**: X11/Wayland (Linux), NSPasteboard (macOS), Win32 (Windows)

### estimation.rs

- **`estimate_output_size()`**: O(1) pre-allocation hint based on file count
- **Conservative heuristic**: 1 536 bytes/file × 4/3 (markdown overhead buffer)
- **Overflow-safe**: Saturating arithmetic throughout

### run.rs

- **`run()`**: Main entry point — parses config, dispatches to file-list or directory-walk mode
- **Printer actor**: Bounded channel (cap 16) to a dedicated writer thread for contention-free stdout
- **`PrintMessage` enum**: `Block(String)` for markdown, `TreeEntry` for deferred tree rendering
- **`stream_to_stdout()`**: Common streaming path with backpressure
- **Panic propagation**: `join_writer()` resumes unwind instead of swallowing thread panics

### schema.rs

- **`generate_schema()`**: Emits JSON Schema for `luff.yaml` config files (for IDE autocomplete)
- **Uses crate error type**: Consistent with the rest of the codebase

### mod.rs

- Re-exports: `Args`, `Commands`, `OutputFormat`, `OutputMode`, `run`, `copy_to_clipboard`

## Output Modes

| Flags   | `OutputMode` variant              | Behavior                              |
| ------- | --------------------------------- | ------------------------------------- |
| (none)  | `Stdout`                          | Stream to stdout (default)            |
| `-c`    | `Clipboard { show_stdout: true }` | Buffer → stdout AND clipboard         |
| `-c -S` | `Clipboard { show_stdout: false }`| Buffer → clipboard only               |
| `-S`    | `Stdout`                          | No output (edge case, allowed)        |

**Key Design**: Clipboard requires buffering the entire output (can't stream), so `-c` changes the execution strategy from streaming to collection.

## Ignore Logic

| Flag                 | Config Setting           | Behavior                                     |
| -------------------- | ------------------------ | -------------------------------------------- |
| (default)            | `respect_gitignore=true` | Respects .gitignore                          |
| `--add` / `-a`       | `respect_gitignore=false`| Ignores .gitignore (includes ignored files)  |
| `--ignore <PATTERN>` | `ignore_globs`           | Adds explicit glob patterns to exclude       |

## Data Flow

```text
CLI args
  → Args::parse_args()
  → Config::from_args()       (compiles globs, resolves root)
  → Walker                    (lazy iterator of WalkerItem)
  ↓
  ├─ Streaming mode (default)
  │    Walker → bounded channel (cap 16) → writer thread → BufWriter<Stdout>
  │    - Markdown: MarkdownPrinter::format_entry() → PrintMessage::Block
  │    - Tree:     PrintMessage::TreeEntry → TreePrinter (batch render on EOF)
  │
  └─ Clipboard mode (--clip)
       Walker → buffer_entries_with_limit / buffer_tree_entries
       → BufferResult { output, processed, total_seen, size_exceeded }
       → stdout (if enabled) + clipboard copy
```

## Key Invariants

- `Args` is parsed exactly once at startup (`main.rs`)
- `run()` takes `&Args` (borrowing, not consuming)
- Clipboard failures print to stderr but don't return errors (graceful degradation)
- Streaming mode (default) has O(1) memory — files processed one at a time through the channel
- Buffered mode (clipboard) is O(n) with configurable size limits and safe truncation
- **SIGPIPE Handling**: Gracefully exits on broken pipe (e.g., `luff | head`)
- Writer thread panics are propagated, not swallowed

## Performance Notes

- Streaming mode: bounded channel provides backpressure; writer thread uses `BufWriter`
- Buffered mode: pre-allocates based on `estimate_output_size()`, truncates at safe markdown boundaries
- Clipboard write is a single operation (no chunking)
- Tree rendering is deferred until all paths are collected (inherently batch)

## Extension Points

- **New output format**: Add variant to `OutputFormat`, handle in `stream_to_stdout` and `buffer.rs` dispatch
- **Progress reporting**: Add a `PrintMessage::Progress` variant to the actor channel
- **Pager support**: Detect TTY, pipe through `less`/`bat` for large outputs