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/README.md b/README.md
index b775b68..a93f4a8 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,23 @@
cargo install leetcode-cli
```
+
+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 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.
diff --git a/src/cli.rs b/src/cli.rs
index 90d1756..63107a2 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -1,13 +1,13 @@
//! 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},
};
-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 m = clap::Command::new(crate_name!())
+
+ let mut cmd = clap::Command::new("leetcode")
.version(crate_version!())
.about("May the Code be with You 👻")
.subcommands(vec![
@@ -37,10 +38,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 +62,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..c57c543
--- /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::{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();
+ let name = cmd.get_name().to_string();
+ generate(gen, cmd, 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 {