diff --git a/Cargo.lock b/Cargo.lock index 019e5d1..f967658 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -251,10 +251,8 @@ checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", - "js-sys", "num-traits", "serde", - "wasm-bindgen", "windows-link", ] @@ -1851,18 +1849,16 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_pipeline" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0af3613597e31606b54dd5d62be86b8f50922b40d2b7d3d145146caf5c154c05" +checksum = "8d7043de9eb4072c03851ec3682a133c26b91b9f8fcc4d52bf911abe2614de12" dependencies = [ - "chrono", "clap", "clap_mangen", "once_cell", "pest", "pest_derive", "regex", - "serde_json", "strip-ansi-escapes", ] diff --git a/Cargo.toml b/Cargo.toml index dae4191..8e29cf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ lazy-regex = { version = "3.4.1", features = [ ], default-features = false } ansi-to-tui = "7.0.0" walkdir = "2.5.0" -string_pipeline = "0.11.1" +string_pipeline = "0.12.0" ureq = "3.0.11" serde_json = "1.0.140" colored = "3.0.0" diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 0000000..ce8c0a6 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,522 @@ +# Television CLI Reference + +Television (`tv`) is a cross-platform, fast and extensible general purpose fuzzy finder TUI. This document provides a comprehensive reference for all CLI options, modes, restrictions, and usage patterns. + +## Table of Contents + +- [Overview](#overview) +- [Operating Modes](#operating-modes) +- [Basic Usage](#basic-usage) +- [Arguments](#arguments) +- [Options](#options) +- [Subcommands](#subcommands) +- [Flag Dependencies and Restrictions](#flag-dependencies-and-restrictions) +- [Configuration](#configuration) +- [Template System](#template-system) +- [Examples](#examples) +- [Advanced Usage](#advanced-usage) + +## Overview + +Television supports two primary operating modes that determine how CLI flags are interpreted and validated: + +1. **Channel Mode**: When a channel is specified, the application uses the channel's configuration as a base and CLI flags act as overrides +2. **Ad-hoc Mode**: When no channel is specified, the application creates a custom channel from CLI flags with stricter validation + +## Operating Modes + +### Channel Mode + +**Activated when**: A channel name is provided as the first argument or via `--autocomplete-prompt` + +**Behavior**: +- Channel provides base configuration (source commands, preview commands, UI settings) +- CLI flags act as **overrides** to channel defaults +- More permissive validation - allows most combination of flags +- Minimal dependency checking since channel provides sensible defaults + +**Example**: +```bash +tv files --preview-command "bat -n --color=always {}" +``` + +### Ad-hoc Mode + +**Activated when**: No channel is specified and no `--autocomplete-prompt` is used + +**Behavior**: +- Creates a custom channel on-the-fly from CLI flags +- Requires `--source-command` to generate any entries +- **Stricter validation** ensures necessary components are present +- All functionality depends on explicitly provided flags + +**Example**: +```bash +tv --source-command "find . -name '*.rs'" --preview-command "bat -n --color=always {}" +``` + +## Basic Usage + +``` +tv [OPTIONS] [CHANNEL] [PATH] +``` + +### Arguments + +Television has intelligent positional argument handling with special path detection logic. + +#### Position 1: `[CHANNEL]` +**Purpose**: Channel name to activate Channel Mode + +- **Standard behavior**: When a valid channel name is provided, activates Channel Mode +- **Special path detection**: If the argument exists as a path on the filesystem, it's automatically treated as a working directory instead +- **Effect when path detected**: Switches to Ad-hoc Mode and uses the path as the working directory +- **Required**: No (falls back to `default_channel` from the global config) +- **Examples**: + ```bash + tv files # Uses 'files' channel + tv /home/user/docs # Auto-detects path, uses as working directory + tv ./projects # Auto-detects relative path + ``` + +#### Position 2: `[PATH]` +**Purpose**: Working directory to start in + +- **Behavior**: Sets the working directory for the application +- **Required**: No +- **Precedence**: Only used if Position 1 was not detected as a path +- **Default**: Current directory +- **Example**: `tv files /home/user/projects` + +#### ⚡ Smart Path Detection Logic + +Television automatically detects when the first argument is a filesystem path: + +1. **Path Check**: If Position 1 exists as a file or directory on the filesystem +2. **Mode Switch**: Automatically switches to Ad-hoc Mode (no channel) +3. **Directory Assignment**: Uses the detected path as the working directory +4. **Requirement**: When this happens, `--source-command` becomes required (Ad-hoc Mode rules apply) + +**Examples of Smart Detection**: +```bash +# No arguments - uses default_channel from config +tv + +# Channel name provided - Channel Mode +tv files + +# Existing path provided - triggers path detection → uses default_channel +tv /home/user/docs # Uses default_channel in /home/user/docs directory + +# Non-existent path - treated as channel name → error if channel doesn't exist +tv /nonexistent/path # Error: Channel not found + +# Channel + explicit working directory - Channel Mode +tv files /home/user/docs + +# The key nuance: same name, different behavior based on filesystem +tv myproject # Channel Mode (if 'myproject' is a channel name) +tv ./myproject # Channel Mode with default_channel (if './myproject' directory exists) + +# Ambiguous case - path detection takes precedence +tv docs # If 'docs' directory exists → default_channel + path detection + # If 'docs' directory doesn't exist → 'docs' channel +``` + +> **💡 Tip**: This smart detection makes Television intuitive - you can just specify a directory and it automatically knows you want to work in that location. + +## Options + +Television's options are organized by functionality. Each option behaves differently depending on whether you're using Channel Mode (with a channel specified) or Ad-hoc Mode (no channel). + +### 🎯 Source and Data Options + +#### `--source-command ` +**Purpose**: Defines the command that generates entries for the picker + +- **Channel Mode**: Overrides the channel's default source command +- **Ad-hoc Mode**: ⚠️ **Required** - without this, no entries will be generated +- **Example**: `--source-command "find . -name '*.py'"` + +#### `--source-display ` +**Purpose**: Template for formatting how entries appear in the results list + +- **Channel Mode**: Overrides the channel's display format +- **Ad-hoc Mode**: Customize how entries are shown (default: entries as-is) +- **Requires**: `--source-command` (in ad-hoc mode) +- **Example**: `--source-display "{split:/:-1} ({split:/:0..-1|join:-})"` + +#### `--source-output ` +**Purpose**: Template for formatting the final output when an entry is selected + +- **Channel Mode**: Overrides the channel's output format +- **Ad-hoc Mode**: Customize what gets returned (default: entries as-is) +- **Requires**: `--source-command` (in ad-hoc mode) +- **Example**: `--source-output "code {}"` + +### 👁️ Preview Options + +#### `-p, --preview-command ` +**Purpose**: Command to generate preview content for the selected entry + +- **Channel Mode**: Overrides the channel's preview command +- **Ad-hoc Mode**: Enables preview functionality with the specified command +- **Requires**: `--source-command` (in ad-hoc mode) +- **Conflicts**: Cannot use with `--no-preview` +- **Example**: `--preview-command "bat -n --color=always {}"` + +#### `--preview-header ` +**Purpose**: Template for text displayed above the preview panel + +- **Both Modes**: Sets custom header text +- **Requires**: `--preview-command` (in ad-hoc mode) +- **Conflicts**: Cannot use with `--no-preview` +- **Example**: `--preview-header "File: {split:/:-1|upper}"` + +#### `--preview-footer ` +**Purpose**: Template for text displayed below the preview panel + +- **Both Modes**: Sets custom footer text +- **Requires**: `--preview-command` (in ad-hoc mode) +- **Conflicts**: Cannot use with `--no-preview` + +#### `--preview-offset ` +**Purpose**: Template that determines the scroll position in the preview + +- **Both Modes**: Controls where preview content starts displaying +- **Requires**: `--preview-command` (in ad-hoc mode) +- **Conflicts**: Cannot use with `--no-preview` +- **Example**: `--preview-offset "10"` (start at line 10) + +#### `--preview-size ` +**Purpose**: Width of the preview panel as a percentage + +- **Both Modes**: Controls preview panel size +- **Default**: 50% of screen width +- **Range**: 1-99 +- **Conflicts**: Cannot use with `--no-preview` + +#### `--no-preview` +**Purpose**: Completely disables the preview panel + +- **Both Modes**: Turns off all preview functionality +- **Conflicts**: Cannot use with any `--preview-*` flags +- **Use Case**: Faster performance, simpler interface + +### 🎨 Interface and Layout Options + +#### `--layout ` +**Purpose**: Controls the overall interface orientation + +- **Channel Mode**: Overrides channel's layout setting +- **Ad-hoc Mode**: Sets interface layout +- **Values**: `landscape` (side-by-side), `portrait` (stacked) +- **Default**: `landscape` + +#### `--input-header ` +**Purpose**: Template for text displayed above the input field + +- **Channel Mode**: Overrides channel's input header +- **Ad-hoc Mode**: Sets custom input header +- **Default**: Channel name (channel mode) or empty (ad-hoc mode) +- **Example**: `--input-header "Search files:"` + +#### `--ui-scale ` +**Purpose**: Scales the entire interface size + +- **Both Modes**: Adjusts display size as a percentage +- **Default**: 100% +- **Range**: 10-100% +- **Use Case**: Adapt to different screen sizes or preferences + +#### `--no-help` +**Purpose**: Hides the help panel showing keyboard shortcuts + +- **Both Modes**: Removes help information from display +- **Use Case**: More screen space, cleaner interface for experienced users + +#### `--no-remote` +**Purpose**: Hides the remote control panel + +- **Both Modes**: Removes remote control information from display +- **Use Case**: Simpler interface when remote features aren't needed + +### ⌨️ Input and Interaction Options + +#### `-i, --input ` +**Purpose**: Pre-fills the input prompt with specified text + +- **Both Modes**: Starts with text already in the search box +- **Use Case**: Continue a previous search or provide default query +- **Example**: `-i "main.py"` + +#### `-k, --keybindings ` +**Purpose**: Overrides default keyboard shortcuts + +- **Both Modes**: Customizes key bindings for actions +- **Format**: `action1=["key1","key2"];action2=["key3"]` +- **Example**: `-k 'quit=["q","esc"];select=["enter","space"]'` + +#### `--exact` +**Purpose**: Changes matching behavior from fuzzy to exact substring matching + +- **Channel Mode**: Overrides channel's matching mode +- **Ad-hoc Mode**: Enables exact matching +- **Default**: Fuzzy matching +- **Use Case**: When you need precise substring matches + +### ⚡ Selection Behavior Options + +> **Note**: These options are mutually exclusive - only one can be used at a time. + +#### `--select-1` +**Purpose**: Automatically selects and returns the entry if only one is found + +- **Both Modes**: Bypasses interactive selection when there's only one match +- **Use Case**: Scripting scenarios where single results should be auto-selected + +#### `--take-1` +**Purpose**: Takes the first entry after loading completes + +- **Both Modes**: Automatically selects first item once all entries are loaded +- **Use Case**: Scripts that always want the first/best result + +#### `--take-1-fast` +**Purpose**: Takes the first entry immediately as it appears + +- **Both Modes**: Selects first item as soon as it's available +- **Use Case**: Maximum speed scripts that don't care about all options + +### ⚙️ Performance and Monitoring Options + +#### `-t, --tick-rate ` +**Purpose**: Controls how frequently the interface updates (times per second) + +- **Both Modes**: Sets UI refresh rate +- **Default**: Auto-calculated based on system performance +- **Validation**: Must be positive number +- **Example**: `--tick-rate 30` (30 updates per second) + +#### `--watch ` +**Purpose**: Automatically re-runs the source command at regular intervals + +- **Channel Mode**: Overrides channel's watch interval +- **Ad-hoc Mode**: Enables live monitoring mode +- **Default**: 0 (disabled) +- **Units**: Seconds between updates +- **Conflicts**: Cannot use with selection options (`--select-1`, `--take-1`, `--take-1-fast`) +- **Example**: `--watch 2.0` (update every 2 seconds) + +### 📁 Directory and Configuration Options + +#### `[PATH]` (Positional Argument 2) +**Purpose**: Sets the working directory for the command + +- **Both Modes**: Changes to specified directory before running +- **Default**: Current directory +- **Example**: `tv files /home/user/projects` + +#### `--config-file ` +**Purpose**: Uses a custom configuration file instead of the default + +- **Both Modes**: Loads settings from specified file +- **Default**: `~/.config/tv/config.toml` (Linux/macOS) or `%APPDATA%\tv\config.toml` (Windows) +- **Use Case**: Multiple configurations for different workflows + +#### `--cable-dir ` +**Purpose**: Uses a custom directory for channel definitions + +- **Both Modes**: Loads channels from specified directory +- **Default**: `~/.config/tv/cable/` (Linux/macOS) or `%APPDATA%\tv\cable\` (Windows) +- **Use Case**: Custom channel collections or shared team channels + +### 🔧 Special Mode Options + +#### `--autocomplete-prompt ` +**Purpose**: ⚡ **Activates Channel Mode** - Auto-detects channel from shell command + +- **Effect**: Switches to Channel Mode automatically +- **Behavior**: Analyzes the provided command to determine appropriate channel +- **Conflicts**: Cannot use with `[CHANNEL]` positional argument +- **Use Case**: Shell integration and smart channel detection +- **Example**: `--autocomplete-prompt "git log --oneline"` + +## Subcommands + +### `list-channels` +Lists all available channels in the cable directory. + +```bash +tv list-channels +``` + +### `init ` +Generates shell completion script for the specified shell. + +**Supported shells**: `bash`, `zsh`, `fish`, `powershell`, `cmd` + +```bash +tv init zsh > ~/.zshrc.d/tv-completion.zsh +``` + +### `update-channels` +Downloads the latest channel prototypes from GitHub. + +```bash +tv update-channels +``` + +## Usage Rules and Restrictions + +> **Note**: Detailed requirements and conflicts for each flag are covered in the [Options](#options) section above. This section provides a high-level overview of the key rules. + +### 🎯 Ad-hoc Mode Requirements + +When using Television without a channel, certain flags become mandatory: + +- **`--source-command` is required** - without this, no entries will be generated +- **Preview dependencies** - all `--preview-*` flags require `--preview-command` to be functional +- **Source formatting dependencies** - `--source-display` and `--source-output` require `--source-command` + +### 🚫 Mutually Exclusive Options + +These option groups cannot be used together: + +- **Selection behavior**: Only one of `--select-1`, `--take-1`, or `--take-1-fast` +- **Preview control**: `--no-preview` conflicts with all `--preview-*` flags +- **Channel selection**: Cannot use both `[CHANNEL]` argument and `--autocomplete-prompt` +- **Watch vs selection**: `--watch` cannot be used with auto-selection flags + +### ✅ Channel Mode Benefits + +Channels provide sensible defaults, making the tool more flexible: +- Preview and source flags work independently (channel provides missing pieces) +- All UI options have reasonable defaults +- Less strict validation since channels fill in the gaps + +## Configuration + +### ⚡ Configuration Priority + +Television uses a layered configuration system where each layer can override the previous: + +1. **CLI flags** - Highest priority, overrides everything +2. **Channel configuration** - Channel-specific settings +3. **User config file** - Personal preferences +4. **Built-in defaults** - Fallback values + +### 📁 Configuration Locations + +#### User Configuration File +- **Linux/macOS**: `~/.config/tv/config.toml` +- **Windows**: `%APPDATA%\tv\config.toml` + +#### Channel Definitions (Cable Directory) +- **Linux/macOS**: `~/.config/tv/cable/` +- **Windows**: `%APPDATA%\tv\cable\` + +> **Tip**: Use `--config-file` and `--cable-dir` flags to override these default locations + +## Template System + +Television uses a powerful template system for dynamic content generation. Templates are enclosed in curly braces `{}` and support complex operations. + +### Template-Enabled Flags + +| Flag Category | Flags Using Templates | +|---------------|----------------------| +| **Source** | `--source-command`, `--source-display`, `--source-output` | +| **Preview** | `--preview-command`, `--preview-offset` | +| **Headers** | `--input-header`, `--preview-header`, `--preview-footer` | + +### Basic Template Syntax + +Templates support a wide range of operations that can be chained together: + +```text +{operation1|operation2|operation3} +``` + +### Core Template Operations + +| Operation | Description | Example | +|-----------|-------------|---------| +| `{}` | Full entry (passthrough) | `{}` → original entry | +| `{split:SEPARATOR:RANGE}` | Split text and extract parts | `{split:/:‑1}` → last path component | +| `{upper}` | Convert to uppercase | `{upper}` → "HELLO" | +| `{lower}` | Convert to lowercase | `{lower}` → "hello" | +| `{trim}` | Remove whitespace | `{trim}` → "text" | +| `{append:TEXT}` | Add text to end | `{append:.txt}` → "file.txt" | +| `{prepend:TEXT}` | Add text to beginning | `{prepend:/home/}` → "/home/file" | + +### Advanced Template Operations + +| Operation | Description | Example | +|-----------|-------------|---------| +| `{replace:s/PATTERN/REPLACEMENT/FLAGS}` | Regex find and replace | `{replace:s/\\.py$/.backup/}` | +| `{regex_extract:PATTERN}` | Extract matching text | `{regex_extract:\\d+}` → extract numbers | +| `{filter:PATTERN}` | Keep items matching pattern | `{split:,:..\|filter:^test}` | +| `{sort}` | Sort list items | `{split:,:..\|sort}` | +| `{unique}` | Remove duplicates | `{split:,:..\|unique}` | +| `{join:SEPARATOR}` | Join list with separator | `{split:,:..\|join:-}` | + +### Template Examples + +```text +# File path manipulation +{split:/:-1} # Get filename from path +{split:/:0..-1|join:/} # Get directory from path + +# Text processing +{split: :..|map:{upper}|join:_} # "hello world" → "HELLO_WORLD" +{trim|replace:s/\s+/_/g} # Replace spaces with underscores + +# Data extraction +{regex_extract:@(.+)} # Extract email domain +{split:,:..|filter:^[A-Z]} # Filter items starting with uppercase +``` + +### Range Specifications + +| Syntax | Description | +|--------|-------------| +| `N` | Single index (0-based) | +| `N..M` | Range exclusive (items N to M-1) | +| `N..=M` | Range inclusive (items N to M) | +| `N..` | From N to end | +| `..M` | From start to M-1 | +| `..` | All items | +| `-1` | Last item | +| `-N` | N-th from end | + + +For complete template documentation, see the [Template System Documentation](https://github.com/lalvarezt/string_pipeline/blob/main/docs/template-system.md). + +## Examples + +> **Note**: More detailed examples with explanations are included in each option's documentation above. + +### 🎯 Quick Start Examples + +#### Channel Mode (Recommended) +```bash +# Basic usage - use built-in channels +tv files # Browse files in current directory +tv git-log # Browse git commit history +tv docker-images # Browse Docker images + +# Channel + customization +tv files --preview-command "bat -n --color=always {}" +tv git-log --layout portrait +``` + +#### Ad-hoc Mode (Custom Commands) +```bash +# Simple custom finder +tv --source-command "find . -name '*.md'" + +# Live system monitoring +tv --source-command "ps aux | tail -n +2" \ + --watch 1.0 \ + --no-preview +``` \ No newline at end of file diff --git a/man/tv.1 b/man/tv.1 index 4a85bf9..4c5db6c 100644 --- a/man/tv.1 +++ b/man/tv.1 @@ -4,7 +4,7 @@ .SH NAME television \- A cross\-platform, fast and extensible general purpose fuzzy finder TUI. .SH SYNOPSIS -\fBtelevision\fR [\fB\-\-preview\-offset\fR] [\fB\-\-no\-preview\fR] [\fB\-t\fR|\fB\-\-tick\-rate\fR] [\fB\-f\fR|\fB\-\-frame\-rate\fR] [\fB\-k\fR|\fB\-\-keybindings\fR] [\fB\-i\fR|\fB\-\-input\fR] [\fB\-\-input\-header\fR] [\fB\-\-preview\-header\fR] [\fB\-\-preview\-footer\fR] [\fB\-\-source\-command\fR] [\fB\-\-source\-display\fR] [\fB\-\-source\-output\fR] [\fB\-p\fR|\fB\-\-preview\-command\fR] [\fB\-\-layout\fR] [\fB\-\-autocomplete\-prompt\fR] [\fB\-\-exact\fR] [\fB\-\-select\-1\fR] [\fB\-\-take\-1\fR] [\fB\-\-take\-1\-fast\fR] [\fB\-\-no\-remote\fR] [\fB\-\-no\-help\fR] [\fB\-\-ui\-scale\fR] [\fB\-\-preview\-size\fR] [\fB\-\-config\-file\fR] [\fB\-\-cable\-dir\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fICHANNEL\fR] [\fIPATH\fR] [\fIsubcommands\fR] +\fBtelevision\fR [\fB\-\-preview\-offset\fR] [\fB\-\-no\-preview\fR] [\fB\-t\fR|\fB\-\-tick\-rate\fR] [\fB\-\-watch\fR] [\fB\-k\fR|\fB\-\-keybindings\fR] [\fB\-i\fR|\fB\-\-input\fR] [\fB\-\-input\-header\fR] [\fB\-\-preview\-header\fR] [\fB\-\-preview\-footer\fR] [\fB\-\-source\-command\fR] [\fB\-\-source\-display\fR] [\fB\-\-source\-output\fR] [\fB\-p\fR|\fB\-\-preview\-command\fR] [\fB\-\-layout\fR] [\fB\-\-autocomplete\-prompt\fR] [\fB\-\-exact\fR] [\fB\-\-select\-1\fR] [\fB\-\-take\-1\fR] [\fB\-\-take\-1\-fast\fR] [\fB\-\-no\-remote\fR] [\fB\-\-no\-help\fR] [\fB\-\-ui\-scale\fR] [\fB\-\-preview\-size\fR] [\fB\-\-config\-file\fR] [\fB\-\-cable\-dir\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fICHANNEL\fR] [\fIPATH\fR] [\fIsubcommands\fR] .SH DESCRIPTION A cross\-platform, fast and extensible general purpose fuzzy finder TUI. .SH OPTIONS @@ -13,29 +13,45 @@ A cross\-platform, fast and extensible general purpose fuzzy finder TUI. A preview line number offset template to use to scroll the preview to for each entry. +When a channel is specified: This overrides the offset defined in the channel prototype. +When no channel is specified: This flag requires \-\-preview\-command to be set. + This template uses the same syntax as the `preview` option and will be formatted using the currently selected entry. .TP \fB\-\-no\-preview\fR Disable the preview panel entirely on startup. + +This flag works identically in both channel mode and ad\-hoc mode. +When set, no preview panel will be shown regardless of channel configuration +or preview\-related flags. .TP \fB\-t\fR, \fB\-\-tick\-rate\fR=\fIFLOAT\fR The application\*(Aqs tick rate. +This flag works identically in both channel mode and ad\-hoc mode. + The tick rate is the number of times the application will update per second. This can be used to control responsiveness and CPU usage on very slow machines or very fast ones but the default should be a good compromise for most users. .TP -\fB\-f\fR, \fB\-\-frame\-rate\fR=\fIFLOAT\fR -[DEPRECATED] Frame rate, i.e. number of frames to render per second. +\fB\-\-watch\fR=\fIFLOAT\fR +Watch mode: reload the source command every N seconds. -This option is deprecated and will be removed in a future release. +When a channel is specified: Overrides the watch interval defined in the channel prototype. +When no channel is specified: Sets the watch interval for the ad\-hoc channel. + +When set to a positive number, the application will automatically +reload the source command at the specified interval. This is useful +for monitoring changing data sources. Set to 0 to disable (default). .TP \fB\-k\fR, \fB\-\-keybindings\fR=\fISTRING\fR Keybindings to override the default keybindings. -This can be used to override the default keybindings with a custom subset +This flag works identically in both channel mode and ad\-hoc mode. + +This can be used to override the default keybindings with a custom subset. The keybindings are specified as a semicolon separated list of keybinding expressions using the configuration file formalism. @@ -44,12 +60,17 @@ Example: `tv \-\-keybindings=\*(Aqquit="esc";select_next_entry=["down","ctrl\-j" \fB\-i\fR, \fB\-\-input\fR=\fISTRING\fR Input text to pass to the channel to prefill the prompt. +This flag works identically in both channel mode and ad\-hoc mode. + This can be used to provide a default value for the prompt upon startup. .TP \fB\-\-input\-header\fR=\fISTRING\fR Input field header template. +When a channel is specified: Overrides the input header defined in the channel prototype. +When no channel is specified: Sets the input header for the ad\-hoc channel. + The given value is parsed as a `MultiTemplate`. It is evaluated against the current channel name and the resulting text is shown as the input field title. Defaults to the current channel name when omitted. @@ -57,39 +78,53 @@ field title. Defaults to the current channel name when omitted. \fB\-\-preview\-header\fR=\fISTRING\fR Preview header template +When a channel is specified: This overrides the header defined in the channel prototype. +When no channel is specified: This flag requires \-\-preview\-command to be set. + The given value is parsed as a `MultiTemplate`. It is evaluated for every entry and its result is displayed above the preview panel. .TP \fB\-\-preview\-footer\fR=\fISTRING\fR Preview footer template +When a channel is specified: This overrides the footer defined in the channel prototype. +When no channel is specified: This flag requires \-\-preview\-command to be set. + The given value is parsed as a `MultiTemplate`. It is evaluated for every entry and its result is displayed below the preview panel. .TP \fB\-\-source\-command\fR=\fISTRING\fR Source command to use for the current channel. -This overrides the command defined in the channel prototype. +When a channel is specified: This overrides the command defined in the channel prototype. +When no channel is specified: This creates an ad\-hoc channel with the given command. + Example: `find . \-name \*(Aq*.rs\*(Aq` .TP \fB\-\-source\-display\fR=\fISTRING\fR Source display template to use for the current channel. -This overrides the display template defined in the channel prototype. +When a channel is specified: This overrides the display template defined in the channel prototype. +When no channel is specified: This flag requires \-\-source\-command to be set. + The template is used to format each entry in the results list. Example: `{split:/:\-1}` (show only the last path segment) .TP \fB\-\-source\-output\fR=\fISTRING\fR Source output template to use for the current channel. -This overrides the output template defined in the channel prototype. +When a channel is specified: This overrides the output template defined in the channel prototype. +When no channel is specified: This flag requires \-\-source\-command to be set. + The template is used to format the final output when an entry is selected. Example: "{}" (output the full entry) .TP \fB\-p\fR, \fB\-\-preview\-command\fR=\fISTRING\fR Preview command to use for the current channel. -This overrides the preview command defined in the channel prototype. +When a channel is specified: This overrides the preview command defined in the channel prototype. +When no channel is specified: This enables preview functionality for the ad\-hoc channel. + Example: "cat {}" (where {} will be replaced with the entry) Parts of the entry can be extracted positionally using the `delimiter` @@ -97,15 +132,24 @@ option. Example: "echo {0} {1}" will split the entry by the delimiter and pass the first two fields to the command. .TP -\fB\-\-layout\fR=\fISTRING\fR +\fB\-\-layout\fR=\fILAYOUT\fR Layout orientation for the UI. -This overrides the layout/orientation defined in the channel prototype. +When a channel is specified: Overrides the layout/orientation defined in the channel prototype. +When no channel is specified: Sets the layout orientation for the ad\-hoc channel. + Options are "landscape" or "portrait". +.br + +.br +[\fIpossible values: \fRlandscape, portrait] .TP \fB\-\-autocomplete\-prompt\fR=\fISTRING\fR Try to guess the channel from the provided input prompt. +This flag automatically selects channel mode by guessing the appropriate channel. +It conflicts with manually specifying a channel since it determines the channel automatically. + This can be used to automatically select a channel based on the input prompt by using the `shell_integration` mapping in the configuration file. @@ -113,6 +157,8 @@ file. \fB\-\-exact\fR Use substring matching instead of fuzzy matching. +This flag works identically in both channel mode and ad\-hoc mode. + This will use substring matching instead of fuzzy matching when searching for entries. This is useful when the user wants to search for an exact match instead of a fuzzy match e.g. to improve performance. @@ -121,6 +167,8 @@ an exact match instead of a fuzzy match e.g. to improve performance. Automatically select and output the first entry if there is only one entry. +This flag works identically in both channel mode and ad\-hoc mode. + Note that most channels stream entries asynchronously which means that knowing if there\*(Aqs only one entry will require waiting for the channel to finish loading first. @@ -131,6 +179,8 @@ loading times are usually very short and will go unnoticed by the user. \fB\-\-take\-1\fR Take the first entry from the list after the channel has finished loading. +This flag works identically in both channel mode and ad\-hoc mode. + This will wait for the channel to finish loading all entries and then automatically select and output the first entry. Unlike `select_1`, this will always take the first entry regardless of how many entries are available. @@ -138,6 +188,8 @@ will always take the first entry regardless of how many entries are available. \fB\-\-take\-1\-fast\fR Take the first entry from the list as soon as it becomes available. +This flag works identically in both channel mode and ad\-hoc mode. + This will immediately select and output the first entry as soon as it appears in the results, without waiting for the channel to finish loading. This is the fastest option when you just want the first result. @@ -145,6 +197,8 @@ This is the fastest option when you just want the first result. \fB\-\-no\-remote\fR Disable the remote control. +This flag works identically in both channel mode and ad\-hoc mode. + This will disable the remote control panel and associated actions entirely. This is useful when the remote control is not needed or when the user wants `tv` to run in single\-channel mode (e.g. when @@ -154,6 +208,8 @@ application). \fB\-\-no\-help\fR Disable the help panel. +This flag works identically in both channel mode and ad\-hoc mode. + This will disable the help panel and associated toggling actions entirely. This is useful when the help panel is not needed or when the user wants `tv` to run with a minimal interface (e.g. when @@ -163,19 +219,26 @@ application). \fB\-\-ui\-scale\fR=\fIINTEGER\fR [default: 100] Change the display size in relation to the available area. +This flag works identically in both channel mode and ad\-hoc mode. + This will crop the UI to a centered rectangle of the specified -percentage of the available area (e.g. 0.5 for 50% x 50%). +percentage of the available area. .TP \fB\-\-preview\-size\fR=\fIINTEGER\fR Percentage of the screen to allocate to the preview panel (1\-99). -This value overrides any `preview_size` defined in configuration files or channel prototypes. +When a channel is specified: This overrides any `preview_size` defined in configuration files or channel prototypes. +When no channel is specified: This flag requires \-\-preview\-command to be set. .TP \fB\-\-config\-file\fR=\fIPATH\fR Provide a custom configuration file to use. + +This flag works identically in both channel mode and ad\-hoc mode. .TP \fB\-\-cable\-dir\fR=\fIPATH\fR Provide a custom cable directory to use. + +This flag works identically in both channel mode and ad\-hoc mode. .TP \fB\-h\fR, \fB\-\-help\fR Print help (see a summary with \*(Aq\-h\*(Aq) @@ -186,6 +249,13 @@ Print version [\fICHANNEL\fR] Which channel shall we watch? +When specified: The application operates in \*(Aqchannel mode\*(Aq where the selected +channel provides the base configuration, and CLI flags act as overrides. + +When omitted: The application operates in \*(Aqad\-hoc mode\*(Aq where you must provide +at least \-\-source\-command to create a custom channel. In this mode, preview +and source flags have stricter validation rules. + A list of the available channels can be displayed using the `list\-channels` command. The channel can also be changed from within the application. @@ -193,6 +263,8 @@ the application. [\fIPATH\fR] The working directory to start the application in. +This flag works identically in both channel mode and ad\-hoc mode. + This can be used to specify a different working directory for the application to start in. This is useful when the application is started from a different directory than the one the user wants to diff --git a/television/cli/args.rs b/television/cli/args.rs index b824424..154be58 100644 --- a/television/cli/args.rs +++ b/television/cli/args.rs @@ -1,55 +1,92 @@ use clap::{Parser, Subcommand, ValueEnum}; +/// Television CLI arguments structure. +/// +/// This CLI supports two primary modes of operation: +/// +/// # Channel Mode (when `channel` is specified) +/// In this mode, the specified channel provides base configuration (source commands, +/// preview commands, UI settings, etc.) and CLI flags act as **overrides** to those defaults. +/// This mode is more permissive and allows any combination of flags since they override +/// sensible channel defaults. +/// +/// # Ad-hoc Mode (when `channel` is not specified) +/// In this mode, the CLI creates a custom channel on-the-fly based on the provided flags. +/// This mode has **stricter validation** to ensure the resulting channel is functional: +/// - Source-related flags (`--source-display`, `--source-output`) require `--source-command` +/// - Preview-related flags (`--preview-*`) require `--preview-command` +/// +/// This validation prevents creating broken ad-hoc channels that reference non-existent commands. #[allow(clippy::struct_excessive_bools)] #[derive(Parser, Debug, Default)] #[command(author, version, about, long_about = None)] pub struct Cli { /// Which channel shall we watch? /// + /// When specified: The application operates in 'channel mode' where the selected + /// channel provides the base configuration, and CLI flags act as overrides. + /// + /// When omitted: The application operates in 'ad-hoc mode' where you must provide + /// at least --source-command to create a custom channel. In this mode, preview + /// and source flags have stricter validation rules. + /// /// A list of the available channels can be displayed using the /// `list-channels` command. The channel can also be changed from within /// the application. - #[arg(value_enum, index = 1, verbatim_doc_comment)] + #[arg( + value_enum, + index = 1, + verbatim_doc_comment, + conflicts_with = "autocomplete_prompt" + )] pub channel: Option, /// A preview line number offset template to use to scroll the preview to for each /// entry. /// + /// When a channel is specified: This overrides the offset defined in the channel prototype. + /// When no channel is specified: This flag requires --preview-command to be set. + /// /// This template uses the same syntax as the `preview` option and will be formatted /// using the currently selected entry. #[arg(long, value_name = "STRING", verbatim_doc_comment)] pub preview_offset: Option, /// Disable the preview panel entirely on startup. - #[arg(long, default_value = "false", verbatim_doc_comment)] + /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// When set, no preview panel will be shown regardless of channel configuration + /// or preview-related flags. + #[arg(long, default_value = "false", verbatim_doc_comment, conflicts_with_all = ["preview_offset", "preview_header", "preview_footer", "preview_size", "preview_command"])] pub no_preview: bool, /// The application's tick rate. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// The tick rate is the number of times the application will update per /// second. This can be used to control responsiveness and CPU usage on /// very slow machines or very fast ones but the default should be a good /// compromise for most users. - #[arg(short, long, value_name = "FLOAT", verbatim_doc_comment)] + #[arg(short, long, value_name = "FLOAT", verbatim_doc_comment, value_parser = validate_positive_float)] pub tick_rate: Option, /// Watch mode: reload the source command every N seconds. /// + /// When a channel is specified: Overrides the watch interval defined in the channel prototype. + /// When no channel is specified: Sets the watch interval for the ad-hoc channel. + /// /// When set to a positive number, the application will automatically /// reload the source command at the specified interval. This is useful /// for monitoring changing data sources. Set to 0 to disable (default). - #[arg(long, value_name = "FLOAT", verbatim_doc_comment)] + #[arg(long, value_name = "FLOAT", verbatim_doc_comment, value_parser = validate_non_negative_float, conflicts_with_all = ["select_1", "take_1", "take_1_fast"])] pub watch: Option, - /// [DEPRECATED] Frame rate, i.e. number of frames to render per second. - /// - /// This option is deprecated and will be removed in a future release. - #[arg(short, long, value_name = "FLOAT", verbatim_doc_comment)] - pub frame_rate: Option, - /// Keybindings to override the default keybindings. /// - /// This can be used to override the default keybindings with a custom subset + /// This flag works identically in both channel mode and ad-hoc mode. + /// + /// This can be used to override the default keybindings with a custom subset. /// The keybindings are specified as a semicolon separated list of keybinding /// expressions using the configuration file formalism. /// @@ -59,6 +96,8 @@ pub struct Cli { /// Input text to pass to the channel to prefill the prompt. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// This can be used to provide a default value for the prompt upon /// startup. #[arg(short, long, value_name = "STRING", verbatim_doc_comment)] @@ -66,6 +105,9 @@ pub struct Cli { /// Input field header template. /// + /// When a channel is specified: Overrides the input header defined in the channel prototype. + /// When no channel is specified: Sets the input header for the ad-hoc channel. + /// /// The given value is parsed as a `MultiTemplate`. It is evaluated against /// the current channel name and the resulting text is shown as the input /// field title. Defaults to the current channel name when omitted. @@ -74,29 +116,39 @@ pub struct Cli { /// Preview header template /// + /// When a channel is specified: This overrides the header defined in the channel prototype. + /// When no channel is specified: This flag requires --preview-command to be set. + /// /// The given value is parsed as a `MultiTemplate`. It is evaluated for every /// entry and its result is displayed above the preview panel. #[arg( long = "preview-header", value_name = "STRING", - verbatim_doc_comment + verbatim_doc_comment, + conflicts_with = "no_preview" )] pub preview_header: Option, /// Preview footer template /// + /// When a channel is specified: This overrides the footer defined in the channel prototype. + /// When no channel is specified: This flag requires --preview-command to be set. + /// /// The given value is parsed as a `MultiTemplate`. It is evaluated for every /// entry and its result is displayed below the preview panel. #[arg( long = "preview-footer", value_name = "STRING", - verbatim_doc_comment + verbatim_doc_comment, + conflicts_with = "no_preview" )] pub preview_footer: Option, /// Source command to use for the current channel. /// - /// This overrides the command defined in the channel prototype. + /// When a channel is specified: This overrides the command defined in the channel prototype. + /// When no channel is specified: This creates an ad-hoc channel with the given command. + /// /// Example: `find . -name '*.rs'` #[arg( long = "source-command", @@ -107,7 +159,9 @@ pub struct Cli { /// Source display template to use for the current channel. /// - /// This overrides the display template defined in the channel prototype. + /// When a channel is specified: This overrides the display template defined in the channel prototype. + /// When no channel is specified: This flag requires --source-command to be set. + /// /// The template is used to format each entry in the results list. /// Example: `{split:/:-1}` (show only the last path segment) #[arg( @@ -119,7 +173,9 @@ pub struct Cli { /// Source output template to use for the current channel. /// - /// This overrides the output template defined in the channel prototype. + /// When a channel is specified: This overrides the output template defined in the channel prototype. + /// When no channel is specified: This flag requires --source-command to be set. + /// /// The template is used to format the final output when an entry is selected. /// Example: "{}" (output the full entry) #[arg( @@ -131,7 +187,9 @@ pub struct Cli { /// Preview command to use for the current channel. /// - /// This overrides the preview command defined in the channel prototype. + /// When a channel is specified: This overrides the preview command defined in the channel prototype. + /// When no channel is specified: This enables preview functionality for the ad-hoc channel. + /// /// Example: "cat {}" (where {} will be replaced with the entry) /// /// Parts of the entry can be extracted positionally using the `delimiter` @@ -142,19 +200,24 @@ pub struct Cli { short, long = "preview-command", value_name = "STRING", - verbatim_doc_comment + verbatim_doc_comment, + conflicts_with = "no_preview" )] pub preview_command: Option, /// Layout orientation for the UI. /// - /// This overrides the layout/orientation defined in the channel prototype. + /// When a channel is specified: Overrides the layout/orientation defined in the channel prototype. + /// When no channel is specified: Sets the layout orientation for the ad-hoc channel. + /// /// Options are "landscape" or "portrait". #[arg(long = "layout", value_enum, verbatim_doc_comment)] pub layout: Option, /// The working directory to start the application in. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// This can be used to specify a different working directory for the /// application to start in. This is useful when the application is /// started from a different directory than the one the user wants to @@ -164,14 +227,24 @@ pub struct Cli { /// Try to guess the channel from the provided input prompt. /// + /// This flag automatically selects channel mode by guessing the appropriate channel. + /// It conflicts with manually specifying a channel since it determines the channel automatically. + /// /// This can be used to automatically select a channel based on the input /// prompt by using the `shell_integration` mapping in the configuration /// file. - #[arg(long, value_name = "STRING", verbatim_doc_comment)] + #[arg( + long, + value_name = "STRING", + verbatim_doc_comment, + conflicts_with = "channel" + )] pub autocomplete_prompt: Option, /// Use substring matching instead of fuzzy matching. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// This will use substring matching instead of fuzzy matching when /// searching for entries. This is useful when the user wants to search for /// an exact match instead of a fuzzy match e.g. to improve performance. @@ -181,6 +254,8 @@ pub struct Cli { /// Automatically select and output the first entry if there is only one /// entry. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// Note that most channels stream entries asynchronously which means that /// knowing if there's only one entry will require waiting for the channel /// to finish loading first. @@ -197,6 +272,8 @@ pub struct Cli { /// Take the first entry from the list after the channel has finished loading. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// This will wait for the channel to finish loading all entries and then /// automatically select and output the first entry. Unlike `select_1`, this /// will always take the first entry regardless of how many entries are available. @@ -210,6 +287,8 @@ pub struct Cli { /// Take the first entry from the list as soon as it becomes available. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// This will immediately select and output the first entry as soon as it /// appears in the results, without waiting for the channel to finish loading. /// This is the fastest option when you just want the first result. @@ -223,6 +302,8 @@ pub struct Cli { /// Disable the remote control. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// This will disable the remote control panel and associated actions /// entirely. This is useful when the remote control is not needed or /// when the user wants `tv` to run in single-channel mode (e.g. when @@ -233,6 +314,8 @@ pub struct Cli { /// Disable the help panel. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// This will disable the help panel and associated toggling actions /// entirely. This is useful when the help panel is not needed or /// when the user wants `tv` to run with a minimal interface (e.g. when @@ -243,28 +326,36 @@ pub struct Cli { /// Change the display size in relation to the available area. /// + /// This flag works identically in both channel mode and ad-hoc mode. + /// /// This will crop the UI to a centered rectangle of the specified - /// percentage of the available area (e.g. 0.5 for 50% x 50%). + /// percentage of the available area. #[arg( long, value_name = "INTEGER", default_value = "100", - verbatim_doc_comment + verbatim_doc_comment, + value_parser = clap::value_parser!(u16).range(10..=100) )] pub ui_scale: u16, /// Percentage of the screen to allocate to the preview panel (1-99). /// - /// This value overrides any `preview_size` defined in configuration files or channel prototypes. - #[arg(long, value_name = "INTEGER", verbatim_doc_comment)] + /// When a channel is specified: This overrides any `preview_size` defined in configuration files or channel prototypes. + /// When no channel is specified: This flag requires --preview-command to be set. + #[arg(long, value_name = "INTEGER", verbatim_doc_comment, value_parser = clap::value_parser!(u16).range(1..=99), conflicts_with = "no_preview")] pub preview_size: Option, /// Provide a custom configuration file to use. - #[arg(long, value_name = "PATH", verbatim_doc_comment)] + /// + /// This flag works identically in both channel mode and ad-hoc mode. + #[arg(long, value_name = "PATH", verbatim_doc_comment, value_parser = validate_file_path)] pub config_file: Option, /// Provide a custom cable directory to use. - #[arg(long, value_name = "PATH", verbatim_doc_comment)] + /// + /// This flag works identically in both channel mode and ad-hoc mode. + #[arg(long, value_name = "PATH", verbatim_doc_comment, value_parser = validate_directory_path)] pub cable_dir: Option, #[command(subcommand)] @@ -302,3 +393,40 @@ pub enum LayoutOrientation { Landscape, Portrait, } + +// Add validator functions +fn validate_positive_float(s: &str) -> Result { + match s.parse::() { + Ok(val) if val > 0.0 => Ok(val), + Ok(_) => Err("Value must be positive".to_string()), + Err(_) => Err("Invalid number format".to_string()), + } +} + +fn validate_non_negative_float(s: &str) -> Result { + match s.parse::() { + Ok(val) if val >= 0.0 => Ok(val), + Ok(_) => Err("Value must be non-negative".to_string()), + Err(_) => Err("Invalid number format".to_string()), + } +} + +fn validate_file_path(s: &str) -> Result { + use std::path::Path; + let path = Path::new(s); + if path.exists() && path.is_file() { + Ok(s.to_string()) + } else { + Err(format!("File does not exist: {}", s)) + } +} + +fn validate_directory_path(s: &str) -> Result { + use std::path::Path; + let path = Path::new(s); + if path.exists() && path.is_dir() { + Ok(s.to_string()) + } else { + Err(format!("Directory does not exist: {}", s)) + } +} diff --git a/television/cli/mod.rs b/television/cli/mod.rs index 1568e4a..de2effb 100644 --- a/television/cli/mod.rs +++ b/television/cli/mod.rs @@ -18,6 +18,27 @@ use crate::{ pub mod args; +/// # CLI Use Cases +/// +/// The CLI interface supports two primary use cases: +/// +/// ## 1. Channel-based mode (channel is specified) +/// When a channel is provided, the CLI operates in **override mode**: +/// - The channel provides the base configuration (source, preview, UI settings) +/// - All CLI flags act as **overrides** to the channel's defaults +/// - Most restrictions are enforced at the clap level using `conflicts_with` +/// - Templates and keybindings are validated after clap parsing +/// - More permissive - allows any combination of flags as they override channel defaults +/// +/// ## 2. Ad-hoc mode (no channel specified) +/// When no channel is provided, the CLI creates an **ad-hoc channel**: +/// - Stricter validation rules apply for interdependent flags +/// - `--preview-*` flags require `--preview-command` to be set +/// - `--source-*` flags require `--source-command` to be set +/// - This ensures the ad-hoc channel has all necessary components to function +/// +/// The validation logic in `post_process()` enforces these constraints for ad-hoc mode +/// while allowing full flexibility in channel-based mode. #[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone)] pub struct PostProcessedCli { @@ -26,7 +47,7 @@ pub struct PostProcessedCli { pub source_command_override: Option