diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 4f45af9..948dd8b 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -53,7 +53,6 @@ jobs: target: x86_64-apple-darwin architecture: x86_64 binary-postfix: "" - binary-name: tv use-cross: false - os: macos-latest os-name: macos @@ -61,35 +60,30 @@ jobs: architecture: arm64 binary-postfix: "" use-cross: false - binary-name: tv - os: ubuntu-latest os-name: linux target: x86_64-unknown-linux-gnu architecture: x86_64 binary-postfix: "" use-cross: false - binary-name: tv - os: windows-latest os-name: windows target: x86_64-pc-windows-msvc architecture: x86_64 binary-postfix: ".exe" use-cross: false - binary-name: tv - os: ubuntu-latest os-name: linux target: aarch64-unknown-linux-gnu architecture: arm64 binary-postfix: "" use-cross: true - binary-name: tv - os: ubuntu-latest os-name: linux target: i686-unknown-linux-gnu architecture: i686 binary-postfix: "" use-cross: true - binary-name: tv steps: - name: Checkout repository @@ -129,27 +123,30 @@ jobs: shell: bash run: | - cd target/${{ matrix.target }}/release - - ####### reduce binary size by removing debug symbols ####### - BINARY_NAME=${{ matrix.binary-name }}${{ matrix.binary-postfix }} - echo "BINARY_NAME=$BINARY_NAME" >> "$GITHUB_ENV" + BIN=target/${{ matrix.target }}/release/tv${{ matrix.binary-postfix }} + echo "BIN=$BIN" >> "$GITHUB_ENV" if [[ ${{ matrix.target }} == aarch64-unknown-linux-gnu ]]; then GCC_PREFIX="aarch64-linux-gnu-" else GCC_PREFIX="" fi - "$GCC_PREFIX"strip $BINARY_NAME + "$GCC_PREFIX"strip $BIN ########## create tar.gz ########## - RELEASE_NAME=${{ matrix.binary-name }}-${GITHUB_REF/refs\/tags\//}-${{ matrix.os-name }}-${{ matrix.architecture }} + RELEASE_NAME=tv-${GITHUB_REF/refs\/tags\//}-${{ matrix.os-name }}-${{ matrix.architecture }} echo "RELEASE_NAME=$RELEASE_NAME" >> "$GITHUB_ENV" - tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME + # create the directory for the archive + mkdir -p "$RELEASE_NAME"/doc + cp $BIN "$RELEASE_NAME"/ + cp {README.md,LICENSE} "$RELEASE_NAME"/ + cp {CHANGELOG.md,docs/*,man/*} "$RELEASE_NAME"/doc/ + + tar czvf "$RELEASE_NAME".tar.gz "$RELEASE_NAME" ########## create sha256 ########## @@ -161,9 +158,8 @@ jobs: fi - name: Packaging with zip format if: runner.os == 'Windows' - working-directory: 'target/${{ matrix.target }}/release' run: | - Compress-Archive -Path "$env:BINARY_NAME" -Destination "$($env:RELEASE_NAME).zip" + Compress-Archive -Path "$env:BIN" -Destination "$($env:RELEASE_NAME).zip" (Get-FileHash "$($env:RELEASE_NAME).zip" -Algorithm SHA256).Hash.ToLower() > "$($env:RELEASE_NAME).zip.sha256" - name: Releasing assets @@ -171,9 +167,9 @@ jobs: with: files: | - target/${{ matrix.target }}/release/${{ matrix.binary-name }}-*.tar.gz - target/${{ matrix.target }}/release/${{ matrix.binary-name }}-*.zip - target/${{ matrix.target }}/release/${{ matrix.binary-name }}-*.sha256 + tv-*.tar.gz + tv-*.zip + tv-*.sha256 env: @@ -235,6 +231,11 @@ jobs: mkdir -p "$dir" echo "DEPLOY_DIR=$dir" >> $GITHUB_ENV + - name: Copy man page + shell: bash + run: | + cp man/tv.1 "$DEPLOY_DIR/" + - name: Build release binary shell: bash run: | diff --git a/Cargo.lock b/Cargo.lock index 2fee535..81282f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -426,6 +426,16 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "clap_mangen" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "724842fa9b144f9b89b3f3d371a89f3455eea660361d13a554f68f8ae5d6c13a" +dependencies = [ + "clap", + "roff", +] + [[package]] name = "clipboard-win" version = "5.4.0" @@ -1839,6 +1849,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "roff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2168,6 +2184,7 @@ dependencies = [ "bat", "better-panic", "clap", + "clap_mangen", "clipboard-win", "criterion", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 34c7223..28f970d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ include = [ "cable", ] rust-version = "1.83" +build = "build.rs" [lib] path = "television/lib.rs" @@ -74,6 +75,12 @@ simd = ["dep:simdutf8"] zero-copy = [] default = ["zero-copy", "simd"] + +[build-dependencies] +clap = { version = "4.5", features = ["derive", "cargo"] } +clap_mangen = "0.2.26" + + [[bin]] bench = false path = "television/main.rs" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e2034f2 --- /dev/null +++ b/build.rs @@ -0,0 +1,28 @@ +use clap::CommandFactory; +use std::path::PathBuf; + +include!("television/cli/args.rs"); + +/// generate the man page from the Clap configuration +fn build_man_page() -> std::io::Result<()> { + let out_dir = PathBuf::from( + std::env::var_os("OUT_DIR").ok_or(std::io::ErrorKind::NotFound)?, + ); + std::fs::create_dir_all(&out_dir)?; + let cmd = Cli::command(); + let man = clap_mangen::Man::new(cmd); + let mut buffer = Vec::::default(); + man.render(&mut buffer)?; + + let out_path = out_dir.join("tv.1"); + std::fs::write(&out_path, &buffer)?; + eprintln!("Wrote man page to {out_path:?}"); + std::fs::write(PathBuf::from("./man").join("tv.1"), &buffer)?; + eprintln!("Wrote man page to ./man directory."); + Ok(()) +} + +fn main() { + println!("cargo::rerun-if-changed=television/cli/args.rs"); + build_man_page().expect("Failed to generate man page."); +} diff --git a/man/tv.1 b/man/tv.1 new file mode 100644 index 0000000..67796ce --- /dev/null +++ b/man/tv.1 @@ -0,0 +1,100 @@ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.TH television 1 "television 0.10.9" +.SH NAME +television \- A cross\-platform, fast and extensible general purpose fuzzy finder TUI. +.SH SYNOPSIS +\fBtelevision\fR [\fB\-p\fR|\fB\-\-preview\fR] [\fB\-\-no\-preview\fR] [\fB\-\-delimiter\fR] [\fB\-t\fR|\fB\-\-tick\-rate\fR] [\fB\-f\fR|\fB\-\-frame\-rate\fR] [\fB\-\-passthrough\-keybindings\fR] [\fB\-i\fR|\fB\-\-input\fR] [\fB\-\-autocomplete\-prompt\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 +.TP +\fB\-p\fR, \fB\-\-preview\fR=\fISTRING\fR +A preview command to use with the stdin channel. + +If provided, the preview command will be executed and formatted using +the entry. +Example: "bat \-n \-\-color=always {}" (where {} will be replaced with +the entry) + +Parts of the entry can be extracted positionally using the `delimiter` +option. +Example: "echo {0} {1}" will split the entry by the delimiter and pass +the first two fields to the command. +.TP +\fB\-\-no\-preview\fR +Disable the preview panel entirely on startup. +.TP +\fB\-\-delimiter\fR=\fISTRING\fR [default: ] +The delimiter used to extract fields from the entry to provide to the +preview command. + +See the `preview` option for more information. +.TP +\fB\-t\fR, \fB\-\-tick\-rate\fR=\fIFLOAT\fR +The application\*(Aqs tick rate. + +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. + +This option is deprecated and will be removed in a future release. +.TP +\fB\-\-passthrough\-keybindings\fR=\fISTRING\fR +Passthrough keybindings (comma separated, e.g. "q,ctrl\-w,ctrl\-t") + +These keybindings will trigger selection of the current entry and be +passed through to stdout along with the entry to be handled by the +parent process. +.TP +\fB\-i\fR, \fB\-\-input\fR=\fISTRING\fR +Input text to pass to the channel to prefill the prompt. + +This can be used to provide a default value for the prompt upon +startup. +.TP +\fB\-\-autocomplete\-prompt\fR=\fISTRING\fR +Try to guess the channel from the provided input prompt. + +This can be used to automatically select a channel based on the input +prompt by using the `shell_integration` mapping in the configuration +file. +.TP +\fB\-h\fR, \fB\-\-help\fR +Print help (see a summary with \*(Aq\-h\*(Aq) +.TP +\fB\-V\fR, \fB\-\-version\fR +Print version +.TP +[\fICHANNEL\fR] [default: files] +Which channel shall we watch? + +A list of the available channels can be displayed using the +`list\-channels` command. The channel can also be changed from within +the application. +.TP +[\fIPATH\fR] +The working directory to start the application in. + +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 +interact with. +.SH SUBCOMMANDS +.TP +television\-list\-channels(1) +Lists the available channels +.TP +television\-init(1) +Initializes shell completion ("tv init zsh") +.TP +television\-help(1) +Print this message or the help of the given subcommand(s) +.SH VERSION +v0.10.9 +.SH AUTHORS +Alexandre Pasmantier diff --git a/television/cli/args.rs b/television/cli/args.rs new file mode 100644 index 0000000..fdaf882 --- /dev/null +++ b/television/cli/args.rs @@ -0,0 +1,124 @@ +use clap::{Parser, Subcommand, ValueEnum}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + /// Which channel shall we watch? + /// + /// 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, + default_value = "files", + index = 1, + verbatim_doc_comment + )] + pub channel: String, + + /// A preview command to use with the stdin channel. + /// + /// If provided, the preview command will be executed and formatted using + /// the entry. + /// Example: "bat -n --color=always {}" (where {} will be replaced with + /// the entry) + /// + /// Parts of the entry can be extracted positionally using the `delimiter` + /// option. + /// Example: "echo {0} {1}" will split the entry by the delimiter and pass + /// the first two fields to the command. + #[arg(short, long, value_name = "STRING", verbatim_doc_comment)] + pub preview: Option, + + /// Disable the preview panel entirely on startup. + #[arg(long, default_value = "false", verbatim_doc_comment)] + pub no_preview: bool, + + /// The delimiter used to extract fields from the entry to provide to the + /// preview command. + /// + /// See the `preview` option for more information. + #[arg(long, value_name = "STRING", default_value = " ", value_parser = delimiter_parser, verbatim_doc_comment)] + pub delimiter: String, + + /// The application's tick rate. + /// + /// 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)] + pub tick_rate: 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, + + /// Passthrough keybindings (comma separated, e.g. "q,ctrl-w,ctrl-t") + /// + /// These keybindings will trigger selection of the current entry and be + /// passed through to stdout along with the entry to be handled by the + /// parent process. + #[arg(long, value_name = "STRING", verbatim_doc_comment)] + pub passthrough_keybindings: Option, + + /// Input text to pass to the channel to prefill the prompt. + /// + /// This can be used to provide a default value for the prompt upon + /// startup. + #[arg(short, long, value_name = "STRING", verbatim_doc_comment)] + pub input: Option, + + /// The working directory to start the application in. + /// + /// 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 + /// interact with. + #[arg(value_name = "PATH", index = 2, verbatim_doc_comment)] + pub working_directory: Option, + + /// Try to guess the channel from the provided input prompt. + /// + /// 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)] + pub autocomplete_prompt: Option, + + #[command(subcommand)] + pub command: Option, +} + +#[derive(Subcommand, Debug, PartialEq, Clone)] +pub enum Command { + /// Lists the available channels. + ListChannels, + /// Initializes shell completion ("tv init zsh") + #[clap(name = "init")] + InitShell { + /// The shell for which to generate the autocompletion script + #[arg(value_enum)] + shell: Shell, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)] +pub enum Shell { + Bash, + Zsh, + Fish, + PowerShell, + Cmd, +} + +#[allow(clippy::unnecessary_wraps)] +fn delimiter_parser(s: &str) -> Result { + Ok(match s { + "" => " ".to_string(), + "\\t" => "\t".to_string(), + _ => s.to_string(), + }) +} diff --git a/television/cli.rs b/television/cli/mod.rs similarity index 70% rename from television/cli.rs rename to television/cli/mod.rs index 6ab7a1d..57ed260 100644 --- a/television/cli.rs +++ b/television/cli/mod.rs @@ -2,131 +2,19 @@ use rustc_hash::FxHashMap; use std::path::Path; use anyhow::{anyhow, Result}; -use clap::{Parser, Subcommand, ValueEnum}; use tracing::debug; use crate::channels::{ cable::CableChannelPrototype, entry::PreviewCommand, CliTvChannel, }; +use crate::cli::args::{Cli, Command, Shell}; use crate::utils::shell::Shell as UtilShell; use crate::{ cable, config::{get_config_dir, get_data_dir}, }; -#[derive(Parser, Debug)] -#[command(version, about, long_about = None)] -pub struct Cli { - /// Which channel shall we watch? - /// - /// 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, - default_value = "files", - index = 1, - verbatim_doc_comment - )] - pub channel: String, - - /// A preview command to use with the stdin channel. - /// - /// If provided, the preview command will be executed and formatted using - /// the entry. - /// Example: "bat -n --color=always {}" (where {} will be replaced with - /// the entry) - /// - /// Parts of the entry can be extracted positionally using the `delimiter` - /// option. - /// Example: "echo {0} {1}" will split the entry by the delimiter and pass - /// the first two fields to the command. - #[arg(short, long, value_name = "STRING", verbatim_doc_comment)] - pub preview: Option, - - /// Disable the preview panel entirely on startup. - #[arg(long, default_value = "false", verbatim_doc_comment)] - pub no_preview: bool, - - /// The delimiter used to extract fields from the entry to provide to the - /// preview command. - /// - /// See the `preview` option for more information. - #[arg(long, value_name = "STRING", default_value = " ", value_parser = delimiter_parser, verbatim_doc_comment)] - pub delimiter: String, - - /// The application's tick rate. - /// - /// 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)] - pub tick_rate: 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, - - /// Passthrough keybindings (comma separated, e.g. "q,ctrl-w,ctrl-t") - /// - /// These keybindings will trigger selection of the current entry and be - /// passed through to stdout along with the entry to be handled by the - /// parent process. - #[arg(long, value_name = "STRING", verbatim_doc_comment)] - pub passthrough_keybindings: Option, - - /// Input text to pass to the channel to prefill the prompt. - /// - /// This can be used to provide a default value for the prompt upon - /// startup. - #[arg(short, long, value_name = "STRING", verbatim_doc_comment)] - pub input: Option, - - /// The working directory to start the application in. - /// - /// 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 - /// interact with. - #[arg(value_name = "PATH", index = 2, verbatim_doc_comment)] - pub working_directory: Option, - - /// Try to guess the channel from the provided input prompt. - /// - /// 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)] - pub autocomplete_prompt: Option, - - #[command(subcommand)] - command: Option, -} - -#[derive(Subcommand, Debug, PartialEq, Clone)] -pub enum Command { - /// Lists the available channels. - ListChannels, - /// Initializes shell completion ("tv init zsh") - #[clap(name = "init")] - InitShell { - /// The shell for which to generate the autocompletion script - #[arg(value_enum)] - shell: Shell, - }, -} - -#[derive(Debug, Clone, Copy, PartialEq, ValueEnum)] -pub enum Shell { - Bash, - Zsh, - Fish, - PowerShell, - Cmd, -} +pub mod args; impl From for UtilShell { fn from(val: Shell) -> Self { @@ -342,15 +230,6 @@ pub fn guess_channel_from_prompt( Err(anyhow!("No channel found for prompt: {}", prompt)) } -#[allow(clippy::unnecessary_wraps)] -fn delimiter_parser(s: &str) -> Result { - Ok(match s { - "" => " ".to_string(), - "\\t" => "\t".to_string(), - _ => s.to_string(), - }) -} - const VERSION_MESSAGE: &str = env!("CARGO_PKG_VERSION"); pub fn version() -> String { diff --git a/television/main.rs b/television/main.rs index df04224..2a052ac 100644 --- a/television/main.rs +++ b/television/main.rs @@ -13,7 +13,8 @@ use television::channels::{ entry::PreviewType, stdin::Channel as StdinChannel, TelevisionChannel, }; use television::cli::{ - guess_channel_from_prompt, list_channels, Cli, ParsedCliChannel, + args::{Cli, Command}, + guess_channel_from_prompt, list_channels, ParsedCliChannel, PostProcessedCli, }; @@ -36,11 +37,11 @@ async fn main() -> Result<()> { if let Some(command) = args.command { match command { - television::cli::Command::ListChannels => { + Command::ListChannels => { list_channels(); exit(0); } - television::cli::Command::InitShell { shell } => { + Command::InitShell { shell } => { let target_shell = Shell::from(shell); // the completion scripts for the various shells are templated // so that it's possible to override the keybindings triggering