From aa2a84cf6339b8a0ab62e916232a1632bdf136b5 Mon Sep 17 00:00:00 2001 From: Louis <836250617@qq.com> Date: Mon, 5 Feb 2024 21:52:04 +0800 Subject: [PATCH 1/2] compile time complete --- Cargo.toml | 2 + build.rs | 193 +++++++++++++++++++++++++++++++++++++++++++++++ src/cli/utils.rs | 3 +- 3 files changed, 196 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8dac75a..934b8b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,8 @@ chrono = "0.4.33" [build-dependencies] protobuf-codegen = { version = "3" } +clap_complete = "4" +clap = { version = "4.4", features = ["derive"]} [features] default = ["bili"] diff --git a/build.rs b/build.rs index c5b65df..605841f 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,13 @@ +//! This is the build script for the project. +//! - It generates the protobuf file for the project. +//! - It generates the completion file for the CLI. +//! Caution: +//! You should copy `Cli` relevant code from `src/cli/mod.rs` to here and `Qn` from `src/proto/data`. +//! So that the completion file can be generated correctly. + +use clap::{builder::PossibleValue, CommandFactory, Parser, Subcommand, ValueEnum}; +use clap_complete::{generate_to, Shell}; + fn main() -> Result<(), Box> { protobuf_codegen::Codegen::new() .pure() @@ -6,5 +16,188 @@ fn main() -> Result<(), Box> { .out_dir("./src/proto") .run() .unwrap(); + + if let Some(outdir) = std::env::var_os("OUT_DIR") { + let mut cmd = Cli::command(); + for &shell in Shell::value_variants() { + let path = generate_to( + shell, &mut cmd, // We need to specify what generator to use + "fav", // We need to specify the bin name manually + &outdir, // We need to specify where to write to + )?; + let filename = path.file_name().unwrap(); + let release_path = std::path::PathBuf::from(&outdir) + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .join(filename); + std::fs::rename(&path, &release_path).unwrap(); + println!("cargo:warning=Completion file is generated: {release_path:?}"); + } + } Ok(()) } + +/// The main CLI entry point. +#[derive(Parser)] +#[command(author, version, about)] +pub struct Cli { + #[clap(subcommand)] + pub(crate) subcmd: Commands, +} + +#[derive(Subcommand)] +pub(crate) enum Commands { + /// Initialize the folder for fav + Init { + #[arg(value_enum)] + kind: Kind, + /// The path to store the fav + path: Option, + }, + /// Login your account + Auth { + /// Login method + #[clap(subcommand)] + subcmd: AuthCommands, + }, + /// Fetch from remote + Fetch { + /// Prune data no longer on remote + #[arg(long, short)] + prune: bool, + }, + /// Show status of local, default to show video status + Status { + /// Show resource status + id: Option, + /// Show all list status + #[arg(long, short)] + list: bool, + /// Show all video status + #[arg(long, short)] + video: bool, + /// Show tracked only + #[arg(long, short)] + tracked: bool, + }, + /// Track a remote source + Track { + /// The id of the source to track + id: Vec, + }, + /// Untrack a remote source + Untrack { + /// The id of the source to untrack + id: Vec, + }, + /// Pull remote data + Pull { + /// The id of the source to pull + id: Option>, + }, + /// Push local data + Push, + /// Like a video + Like { + /// The id of the video to like + bvid: Option>, + /// Like all videos tracked + #[arg(long, short)] + all: bool, + }, + /// Set the path of ffmpeg + Ffmpeg { + /// Set the path of ffmpeg + path: String, + }, + /// Interval fetch and pull + Daemon { + /// The interval to fetch and pull (in minutes, greater than 15) + interval: u64, + }, + /// Modify resource status + Modify { + /// The id of the resources to modify + id: Vec, + /// Mark saved true or false + #[arg(long, short)] + saved: Option, + /// modify the clarity + #[arg(long, short, value_enum)] + clarity: Option, + }, +} + +#[derive(Subcommand)] +pub(crate) enum AuthCommands { + /// Login with password + Login, + /// Login with QR code + Logout, +} + +#[derive(ValueEnum, Clone, Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub(crate) enum Kind { + #[cfg(feature = "bili")] + Bili, +} + +#[derive(Clone)] +enum Qn { + Default = 0, + EightK = 127, + Dolby = 126, + HDR = 125, + FourK = 120, + FullHDHighFrame = 116, + FullHDHighCode = 112, + FullHD = 80, + HDHighFrame = 74, + HD = 64, + SD = 32, + LD = 16, + VLD = 6, +} + +impl ValueEnum for Qn { + fn value_variants<'a>() -> &'a [Self] { + &[ + Self::Default, + Self::EightK, + Self::Dolby, + Self::HDR, + Self::FourK, + Self::FullHDHighFrame, + Self::FullHDHighCode, + Self::FullHD, + Self::HDHighFrame, + Self::HD, + Self::SD, + Self::LD, + Self::VLD, + ] + } + + fn to_possible_value(&self) -> Option { + match self { + Qn::Default => Some(PossibleValue::new("default")), + Qn::EightK => Some(PossibleValue::new("8k")), + Qn::Dolby => Some(PossibleValue::new("dolby")), + Qn::HDR => Some(PossibleValue::new("hdr")), + Qn::FourK => Some(PossibleValue::new("4k")), + Qn::FullHDHighFrame => Some(PossibleValue::new("1080p60")), + Qn::FullHDHighCode => Some(PossibleValue::new("1080p+")), + Qn::FullHD => Some(PossibleValue::new("1080p")), + Qn::HDHighFrame => Some(PossibleValue::new("720p60")), + Qn::HD => Some(PossibleValue::new("720p")), + Qn::SD => Some(PossibleValue::new("480p")), + Qn::LD => Some(PossibleValue::new("360p")), + Qn::VLD => Some(PossibleValue::new("240p")), + } + } +} diff --git a/src/cli/utils.rs b/src/cli/utils.rs index bdbf4a4..e32778b 100644 --- a/src/cli/utils.rs +++ b/src/cli/utils.rs @@ -1,3 +1,4 @@ +use crate::proto::data::Qn; use clap::{builder::PossibleValue, ValueEnum}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; use std::sync::OnceLock; @@ -6,8 +7,6 @@ use tabled::{ settings::{object::Rows, Alignment, Style}, }; -use crate::proto::data::Qn; - pub(crate) fn show_table(header: H, rows: R) where H: IntoIterator, From 503ec7a2384ee9f7222ec5ee3c4461ac1d491f4f Mon Sep 17 00:00:00 2001 From: Louis <836250617@qq.com> Date: Tue, 6 Feb 2024 00:12:09 +0800 Subject: [PATCH 2/2] auto complete --- CHANGELOG.md | 4 +- Cargo.toml | 5 +- build.rs | 193 ------------------------------------------------- src/cli/mod.rs | 16 +++- 4 files changed, 20 insertions(+), 198 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa43406..743db2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,10 @@ Note: In this file, do not use the hard wrap in the middle of a sentence for com ## [Unreleased] -### [0.1.9] - 2024-02-05 +### [0.1.9] - 2024-02-06 +- auto complete support for `zsh` and `fish`; Run `fav complete -h` for more information. (e.g. run `fav complete --shell fish --register ~/.config/fish/completions` to register the auto completion script for `fish`) +- I'll also upload some other auto completion scripts for `bash` and `powershell` and so on. ## [0.1.8] - 2024-02-05 diff --git a/Cargo.toml b/Cargo.toml index 934b8b9..d989c1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fav" -version = "0.1.8" +version = "0.1.9" authors = ["Louis <836250617@qq.com>"] description = "Back up your favorite online resources with CLI." license = "MIT" @@ -15,6 +15,7 @@ documentation = "" clap = { version = "4.4", features = ["derive"]} tabled = "0.15.0" indicatif = { version = "0.17.7", features = ["tokio"] } +clap_complete = { version = "4", features = ["unstable-dynamic"] } # Error snafu = { version = "0.8" } # Serde @@ -41,8 +42,6 @@ chrono = "0.4.33" [build-dependencies] protobuf-codegen = { version = "3" } -clap_complete = "4" -clap = { version = "4.4", features = ["derive"]} [features] default = ["bili"] diff --git a/build.rs b/build.rs index 605841f..c5b65df 100644 --- a/build.rs +++ b/build.rs @@ -1,13 +1,3 @@ -//! This is the build script for the project. -//! - It generates the protobuf file for the project. -//! - It generates the completion file for the CLI. -//! Caution: -//! You should copy `Cli` relevant code from `src/cli/mod.rs` to here and `Qn` from `src/proto/data`. -//! So that the completion file can be generated correctly. - -use clap::{builder::PossibleValue, CommandFactory, Parser, Subcommand, ValueEnum}; -use clap_complete::{generate_to, Shell}; - fn main() -> Result<(), Box> { protobuf_codegen::Codegen::new() .pure() @@ -16,188 +6,5 @@ fn main() -> Result<(), Box> { .out_dir("./src/proto") .run() .unwrap(); - - if let Some(outdir) = std::env::var_os("OUT_DIR") { - let mut cmd = Cli::command(); - for &shell in Shell::value_variants() { - let path = generate_to( - shell, &mut cmd, // We need to specify what generator to use - "fav", // We need to specify the bin name manually - &outdir, // We need to specify where to write to - )?; - let filename = path.file_name().unwrap(); - let release_path = std::path::PathBuf::from(&outdir) - .parent() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap() - .join(filename); - std::fs::rename(&path, &release_path).unwrap(); - println!("cargo:warning=Completion file is generated: {release_path:?}"); - } - } Ok(()) } - -/// The main CLI entry point. -#[derive(Parser)] -#[command(author, version, about)] -pub struct Cli { - #[clap(subcommand)] - pub(crate) subcmd: Commands, -} - -#[derive(Subcommand)] -pub(crate) enum Commands { - /// Initialize the folder for fav - Init { - #[arg(value_enum)] - kind: Kind, - /// The path to store the fav - path: Option, - }, - /// Login your account - Auth { - /// Login method - #[clap(subcommand)] - subcmd: AuthCommands, - }, - /// Fetch from remote - Fetch { - /// Prune data no longer on remote - #[arg(long, short)] - prune: bool, - }, - /// Show status of local, default to show video status - Status { - /// Show resource status - id: Option, - /// Show all list status - #[arg(long, short)] - list: bool, - /// Show all video status - #[arg(long, short)] - video: bool, - /// Show tracked only - #[arg(long, short)] - tracked: bool, - }, - /// Track a remote source - Track { - /// The id of the source to track - id: Vec, - }, - /// Untrack a remote source - Untrack { - /// The id of the source to untrack - id: Vec, - }, - /// Pull remote data - Pull { - /// The id of the source to pull - id: Option>, - }, - /// Push local data - Push, - /// Like a video - Like { - /// The id of the video to like - bvid: Option>, - /// Like all videos tracked - #[arg(long, short)] - all: bool, - }, - /// Set the path of ffmpeg - Ffmpeg { - /// Set the path of ffmpeg - path: String, - }, - /// Interval fetch and pull - Daemon { - /// The interval to fetch and pull (in minutes, greater than 15) - interval: u64, - }, - /// Modify resource status - Modify { - /// The id of the resources to modify - id: Vec, - /// Mark saved true or false - #[arg(long, short)] - saved: Option, - /// modify the clarity - #[arg(long, short, value_enum)] - clarity: Option, - }, -} - -#[derive(Subcommand)] -pub(crate) enum AuthCommands { - /// Login with password - Login, - /// Login with QR code - Logout, -} - -#[derive(ValueEnum, Clone, Debug)] -#[cfg_attr(test, derive(PartialEq))] -pub(crate) enum Kind { - #[cfg(feature = "bili")] - Bili, -} - -#[derive(Clone)] -enum Qn { - Default = 0, - EightK = 127, - Dolby = 126, - HDR = 125, - FourK = 120, - FullHDHighFrame = 116, - FullHDHighCode = 112, - FullHD = 80, - HDHighFrame = 74, - HD = 64, - SD = 32, - LD = 16, - VLD = 6, -} - -impl ValueEnum for Qn { - fn value_variants<'a>() -> &'a [Self] { - &[ - Self::Default, - Self::EightK, - Self::Dolby, - Self::HDR, - Self::FourK, - Self::FullHDHighFrame, - Self::FullHDHighCode, - Self::FullHD, - Self::HDHighFrame, - Self::HD, - Self::SD, - Self::LD, - Self::VLD, - ] - } - - fn to_possible_value(&self) -> Option { - match self { - Qn::Default => Some(PossibleValue::new("default")), - Qn::EightK => Some(PossibleValue::new("8k")), - Qn::Dolby => Some(PossibleValue::new("dolby")), - Qn::HDR => Some(PossibleValue::new("hdr")), - Qn::FourK => Some(PossibleValue::new("4k")), - Qn::FullHDHighFrame => Some(PossibleValue::new("1080p60")), - Qn::FullHDHighCode => Some(PossibleValue::new("1080p+")), - Qn::FullHD => Some(PossibleValue::new("1080p")), - Qn::HDHighFrame => Some(PossibleValue::new("720p60")), - Qn::HD => Some(PossibleValue::new("720p")), - Qn::SD => Some(PossibleValue::new("480p")), - Qn::LD => Some(PossibleValue::new("360p")), - Qn::VLD => Some(PossibleValue::new("240p")), - } - } -} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index c991769..fc887bc 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,7 +1,9 @@ //! The CLI module. pub(crate) mod utils; -use clap::{error::ErrorKind, CommandFactory, Parser, Subcommand, ValueEnum}; +use clap::{ + error::ErrorKind, Command, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum, +}; use crate::{ api::{ @@ -125,8 +127,20 @@ pub(crate) enum Kind { } impl Cli { + fn build_cli() -> Command { + clap_complete::dynamic::shells::CompleteCommand::augment_subcommands(Self::command()) + } + /// Run the CLI. pub async fn run() { + let cli = Self::build_cli(); + let matches = cli.get_matches(); + if let Ok(completions) = + clap_complete::dynamic::shells::CompleteCommand::from_arg_matches(&matches) + { + completions.complete(&mut Self::build_cli()); + return; + }; let args = Self::parse(); match args.subcmd { Commands::Init { path, kind } => init(path, kind).await.unwrap(),