From b6da7f7c609769d29c6fac638680982f3b706503 Mon Sep 17 00:00:00 2001 From: Akarsh Jain Date: Tue, 11 Jul 2023 12:05:20 +0530 Subject: [PATCH 1/4] feat: shell completions for bash, zsh, fish, etc. --- Cargo.lock | 10 +++++++ Cargo.toml | 1 + src/cli.rs | 13 +++++---- src/cmds/completions.rs | 62 +++++++++++++++++++++++++++++++++++++++++ src/cmds/mod.rs | 2 ++ src/err.rs | 11 +++++++- 6 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 src/cmds/completions.rs diff --git a/Cargo.lock b/Cargo.lock index b0217c9..a922655 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,6 +253,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" +dependencies = [ + "clap", +] + [[package]] name = "clap_lex" version = "0.5.0" @@ -1052,6 +1061,7 @@ dependencies = [ "anyhow", "async-trait", "clap", + "clap_complete", "colored", "diesel", "dirs", diff --git a/Cargo.toml b/Cargo.toml index 62c2cd8..ec01180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ toml = "0.5.9" regex = "1.6.0" scraper = "0.13.0" anyhow = "1.0.71" +clap_complete = "4.3.2" [dependencies.diesel] version = "2.0.3" diff --git a/src/cli.rs b/src/cli.rs index 90d1756..5383b0d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,8 +1,8 @@ //! Clap Commanders use crate::{ cmds::{ - Command, DataCommand, EditCommand, ExecCommand, ListCommand, PickCommand, StatCommand, - TestCommand, + completion_handler, Command, CompletionCommand, DataCommand, EditCommand, ExecCommand, + ListCommand, PickCommand, StatCommand, TestCommand, }, err::Error, flag::{Debug, Flag}, @@ -26,7 +26,7 @@ pub fn reset_signal_pipe_handler() { /// Get matches pub async fn main() -> Result<(), Error> { reset_signal_pipe_handler(); - let m = clap::Command::new(crate_name!()) + let mut cmd = clap::Command::new(crate_name!()) .version(crate_version!()) .about("May the Code be with You 👻") .subcommands(vec![ @@ -37,10 +37,12 @@ pub async fn main() -> Result<(), Error> { PickCommand::usage().display_order(5), StatCommand::usage().display_order(6), TestCommand::usage().display_order(7), + CompletionCommand::usage().display_order(8), ]) .arg(Debug::usage()) - .arg_required_else_help(true) - .get_matches(); + .arg_required_else_help(true); + + let m = cmd.clone().get_matches(); if m.get_flag("debug") { Debug::handler()?; @@ -59,6 +61,7 @@ pub async fn main() -> Result<(), Error> { Some(("pick", sub_m)) => Ok(PickCommand::handler(sub_m).await?), Some(("stat", sub_m)) => Ok(StatCommand::handler(sub_m).await?), Some(("test", sub_m)) => Ok(TestCommand::handler(sub_m).await?), + Some(("completions", sub_m)) => Ok(completion_handler(sub_m, &mut cmd)?), _ => Err(Error::MatchError), } } diff --git a/src/cmds/completions.rs b/src/cmds/completions.rs new file mode 100644 index 0000000..06f9c55 --- /dev/null +++ b/src/cmds/completions.rs @@ -0,0 +1,62 @@ +//! Completions command + +use super::Command; +use crate::err::Error; +use async_trait::async_trait; +use clap::crate_name; +use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand}; +use clap_complete::{generate, Generator, Shell}; + +/// Abstract shell completions command +/// +/// ```sh +/// Generate shell Completions + +/// USAGE: +/// leetcode completions + +/// ARGUMENTS: +/// [possible values: bash, elvish, fish, powershell, zsh] +/// ``` +pub struct CompletionCommand; + +#[async_trait] +impl Command for CompletionCommand { + /// `pick` usage + fn usage() -> ClapCommand { + ClapCommand::new("completions") + .about("Generate shell Completions") + .visible_alias("c") + .arg( + Arg::new("shell") + .action(ArgAction::Set) + .value_parser(clap::value_parser!(Shell)), + ) + } + + async fn handler(_m: &ArgMatches) -> Result<(), Error> { + // defining custom handler to print the completions. Handler method signature limits taking + // other params. We need &ArgMatches and &mut ClapCommand to generate completions. + println!("Don't use this handler. Does not implement the functionality to print completions. Use completions_handler() below."); + Ok(()) + } +} + +fn get_completions_string(gen: G, cmd: &mut ClapCommand) -> Result { + let mut v: Vec = Vec::new(); + generate(gen, cmd, crate_name!(), &mut v); + Ok(String::from_utf8(v)?) +} + +pub fn completion_handler(m: &ArgMatches, cmd: &mut ClapCommand) -> Result<(), Error> { + let shell = *m.get_one::("shell").unwrap_or( + // if shell value is not provided try to get from the environment + { + println!("# Since shell arg value is not provided trying to get the default shell from the environment."); + &Shell::from_env().ok_or(Error::MatchError)? + } + ); + let completions = get_completions_string(shell, cmd)?; + println!("{}", completions); + Ok(()) +} diff --git a/src/cmds/mod.rs b/src/cmds/mod.rs index 7bf957f..1124532 100644 --- a/src/cmds/mod.rs +++ b/src/cmds/mod.rs @@ -24,6 +24,7 @@ pub trait Command { async fn handler(m: &ArgMatches) -> Result<(), Error>; } +mod completions; mod data; mod edit; mod exec; @@ -31,6 +32,7 @@ mod list; mod pick; mod stat; mod test; +pub use completions::{completion_handler, CompletionCommand}; pub use data::DataCommand; pub use edit::EditCommand; pub use exec::ExecCommand; diff --git a/src/err.rs b/src/err.rs index 6421cfd..fad490f 100644 --- a/src/err.rs +++ b/src/err.rs @@ -1,7 +1,7 @@ //! Errors in leetcode-cli use crate::cmds::{Command, DataCommand}; use colored::Colorize; -use std::fmt; +use std::{fmt, string::FromUtf8Error}; // fixme: use this_error /// Error enum @@ -17,6 +17,7 @@ pub enum Error { PremiumError, DecryptError, SilentError, + Utf8ParseError, NoneError, ChromeNotLogin, Anyhow(anyhow::Error), @@ -61,6 +62,7 @@ impl std::fmt::Debug for Error { ), Error::ChromeNotLogin => write!(f, "maybe you not login on the Chrome, you can login and retry."), Error::Anyhow(e) => write!(f, "{} {}", e, e), + Error::Utf8ParseError => write!(f, "cannot parse utf8 from buff {}", e), } } } @@ -72,6 +74,13 @@ impl std::convert::From for Error { } } +// utf8 parse +impl std::convert::From for Error { + fn from(_err: FromUtf8Error) -> Self { + Error::Utf8ParseError + } +} + // nums impl std::convert::From for Error { fn from(err: std::num::ParseIntError) -> Self { From 3d268febfde0ff027d3cf375348c982209b518a9 Mon Sep 17 00:00:00 2001 From: Akarsh Jain Date: Tue, 11 Jul 2023 13:24:06 +0530 Subject: [PATCH 2/4] fix(crate_name): `crate_name!()` doesn't pick up bin name it picks up package name. Hence hardcoding to sync with shell completions --- src/cli.rs | 5 +++-- src/cmds/completions.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 5383b0d..63107a2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -7,7 +7,7 @@ use crate::{ err::Error, flag::{Debug, Flag}, }; -use clap::{crate_name, crate_version}; +use clap::crate_version; use log::LevelFilter; /// This should be called before calling any cli method or printing any output. @@ -26,7 +26,8 @@ pub fn reset_signal_pipe_handler() { /// Get matches pub async fn main() -> Result<(), Error> { reset_signal_pipe_handler(); - let mut cmd = clap::Command::new(crate_name!()) + + let mut cmd = clap::Command::new("leetcode") .version(crate_version!()) .about("May the Code be with You 👻") .subcommands(vec![ diff --git a/src/cmds/completions.rs b/src/cmds/completions.rs index 06f9c55..c57c543 100644 --- a/src/cmds/completions.rs +++ b/src/cmds/completions.rs @@ -3,7 +3,6 @@ use super::Command; use crate::err::Error; use async_trait::async_trait; -use clap::crate_name; use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand}; use clap_complete::{generate, Generator, Shell}; @@ -44,7 +43,8 @@ impl Command for CompletionCommand { fn get_completions_string(gen: G, cmd: &mut ClapCommand) -> Result { let mut v: Vec = Vec::new(); - generate(gen, cmd, crate_name!(), &mut v); + let name = cmd.get_name().to_string(); + generate(gen, cmd, name, &mut v); Ok(String::from_utf8(v)?) } From 51f3e0ac240f437d4d6f40568848fab6710d1164 Mon Sep 17 00:00:00 2001 From: Akarsh Jain Date: Tue, 11 Jul 2023 13:24:34 +0530 Subject: [PATCH 3/4] fix(Readme): Shell completions description --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index b775b68..fcb2ccd 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,20 @@ cargo install leetcode-cli ``` +### For shell completions: + +For Bash and Zsh (by default picks up `$SHELL` from environment) +```sh +eval "$(leetcode completions)" +``` +Copy the line above to `.bash_profile` or `.zshrc` + +You can obtain specific shell configuration using. + +```sh +leetcode completions fish +``` + ## Usage **Make sure you have logged in to `leetcode.com` with `Firefox`**. See [Cookies](#cookies) for why you need to do this first. From 84423285d85840679053d59bfc799de75617b397 Mon Sep 17 00:00:00 2001 From: Akarsh Jain Date: Tue, 11 Jul 2023 13:33:50 +0530 Subject: [PATCH 4/4] fix: collapsible shell completion description --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fcb2ccd..a93f4a8 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ cargo install leetcode-cli ``` -### For shell completions: +
+Shell completions For Bash and Zsh (by default picks up `$SHELL` from environment) ```sh @@ -28,12 +29,14 @@ eval "$(leetcode completions)" ``` Copy the line above to `.bash_profile` or `.zshrc` -You can obtain specific shell configuration using. +You may also obtain specific shell configuration using. ```sh leetcode completions fish ``` +
+ ## Usage **Make sure you have logged in to `leetcode.com` with `Firefox`**. See [Cookies](#cookies) for why you need to do this first.