Skip to content

Commit d121ac1

Browse files
authored
Shell completions (clearloop#129)
* feat: shell completions for bash, zsh, fish, etc. * fix(crate_name): `crate_name!()` doesn't pick up bin name it picks up package name. Hence hardcoding to sync with shell completions * fix(Readme): Shell completions description * fix: collapsible shell completion description
1 parent 8a1c966 commit d121ac1

File tree

7 files changed

+112
-7
lines changed

7 files changed

+112
-7
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ toml = "0.5.9"
3333
regex = "1.6.0"
3434
scraper = "0.13.0"
3535
anyhow = "1.0.71"
36+
clap_complete = "4.3.2"
3637

3738
[dependencies.diesel]
3839
version = "2.0.3"

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,23 @@
2020
cargo install leetcode-cli
2121
```
2222

23+
<details>
24+
<summary>Shell completions</summary>
25+
26+
For Bash and Zsh (by default picks up `$SHELL` from environment)
27+
```sh
28+
eval "$(leetcode completions)"
29+
```
30+
Copy the line above to `.bash_profile` or `.zshrc`
31+
32+
You may also obtain specific shell configuration using.
33+
34+
```sh
35+
leetcode completions fish
36+
```
37+
38+
</details>
39+
2340
## Usage
2441

2542
**Make sure you have logged in to `leetcode.com` with `Firefox`**. See [Cookies](#cookies) for why you need to do this first.

src/cli.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
//! Clap Commanders
22
use crate::{
33
cmds::{
4-
Command, DataCommand, EditCommand, ExecCommand, ListCommand, PickCommand, StatCommand,
5-
TestCommand,
4+
completion_handler, Command, CompletionCommand, DataCommand, EditCommand, ExecCommand,
5+
ListCommand, PickCommand, StatCommand, TestCommand,
66
},
77
err::Error,
88
flag::{Debug, Flag},
99
};
10-
use clap::{crate_name, crate_version};
10+
use clap::crate_version;
1111
use log::LevelFilter;
1212

1313
/// This should be called before calling any cli method or printing any output.
@@ -26,7 +26,8 @@ pub fn reset_signal_pipe_handler() {
2626
/// Get matches
2727
pub async fn main() -> Result<(), Error> {
2828
reset_signal_pipe_handler();
29-
let m = clap::Command::new(crate_name!())
29+
30+
let mut cmd = clap::Command::new("leetcode")
3031
.version(crate_version!())
3132
.about("May the Code be with You 👻")
3233
.subcommands(vec![
@@ -37,10 +38,12 @@ pub async fn main() -> Result<(), Error> {
3738
PickCommand::usage().display_order(5),
3839
StatCommand::usage().display_order(6),
3940
TestCommand::usage().display_order(7),
41+
CompletionCommand::usage().display_order(8),
4042
])
4143
.arg(Debug::usage())
42-
.arg_required_else_help(true)
43-
.get_matches();
44+
.arg_required_else_help(true);
45+
46+
let m = cmd.clone().get_matches();
4447

4548
if m.get_flag("debug") {
4649
Debug::handler()?;
@@ -59,6 +62,7 @@ pub async fn main() -> Result<(), Error> {
5962
Some(("pick", sub_m)) => Ok(PickCommand::handler(sub_m).await?),
6063
Some(("stat", sub_m)) => Ok(StatCommand::handler(sub_m).await?),
6164
Some(("test", sub_m)) => Ok(TestCommand::handler(sub_m).await?),
65+
Some(("completions", sub_m)) => Ok(completion_handler(sub_m, &mut cmd)?),
6266
_ => Err(Error::MatchError),
6367
}
6468
}

src/cmds/completions.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//! Completions command
2+
3+
use super::Command;
4+
use crate::err::Error;
5+
use async_trait::async_trait;
6+
use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand};
7+
use clap_complete::{generate, Generator, Shell};
8+
9+
/// Abstract shell completions command
10+
///
11+
/// ```sh
12+
/// Generate shell Completions
13+
14+
/// USAGE:
15+
/// leetcode completions <shell>
16+
17+
/// ARGUMENTS:
18+
/// <shell> [possible values: bash, elvish, fish, powershell, zsh]
19+
/// ```
20+
pub struct CompletionCommand;
21+
22+
#[async_trait]
23+
impl Command for CompletionCommand {
24+
/// `pick` usage
25+
fn usage() -> ClapCommand {
26+
ClapCommand::new("completions")
27+
.about("Generate shell Completions")
28+
.visible_alias("c")
29+
.arg(
30+
Arg::new("shell")
31+
.action(ArgAction::Set)
32+
.value_parser(clap::value_parser!(Shell)),
33+
)
34+
}
35+
36+
async fn handler(_m: &ArgMatches) -> Result<(), Error> {
37+
// defining custom handler to print the completions. Handler method signature limits taking
38+
// other params. We need &ArgMatches and &mut ClapCommand to generate completions.
39+
println!("Don't use this handler. Does not implement the functionality to print completions. Use completions_handler() below.");
40+
Ok(())
41+
}
42+
}
43+
44+
fn get_completions_string<G: Generator>(gen: G, cmd: &mut ClapCommand) -> Result<String, Error> {
45+
let mut v: Vec<u8> = Vec::new();
46+
let name = cmd.get_name().to_string();
47+
generate(gen, cmd, name, &mut v);
48+
Ok(String::from_utf8(v)?)
49+
}
50+
51+
pub fn completion_handler(m: &ArgMatches, cmd: &mut ClapCommand) -> Result<(), Error> {
52+
let shell = *m.get_one::<Shell>("shell").unwrap_or(
53+
// if shell value is not provided try to get from the environment
54+
{
55+
println!("# Since shell arg value is not provided trying to get the default shell from the environment.");
56+
&Shell::from_env().ok_or(Error::MatchError)?
57+
}
58+
);
59+
let completions = get_completions_string(shell, cmd)?;
60+
println!("{}", completions);
61+
Ok(())
62+
}

src/cmds/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ pub trait Command {
2424
async fn handler(m: &ArgMatches) -> Result<(), Error>;
2525
}
2626

27+
mod completions;
2728
mod data;
2829
mod edit;
2930
mod exec;
3031
mod list;
3132
mod pick;
3233
mod stat;
3334
mod test;
35+
pub use completions::{completion_handler, CompletionCommand};
3436
pub use data::DataCommand;
3537
pub use edit::EditCommand;
3638
pub use exec::ExecCommand;

src/err.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Errors in leetcode-cli
22
use crate::cmds::{Command, DataCommand};
33
use colored::Colorize;
4-
use std::fmt;
4+
use std::{fmt, string::FromUtf8Error};
55

66
// fixme: use this_error
77
/// Error enum
@@ -17,6 +17,7 @@ pub enum Error {
1717
PremiumError,
1818
DecryptError,
1919
SilentError,
20+
Utf8ParseError,
2021
NoneError,
2122
ChromeNotLogin,
2223
Anyhow(anyhow::Error),
@@ -61,6 +62,7 @@ impl std::fmt::Debug for Error {
6162
),
6263
Error::ChromeNotLogin => write!(f, "maybe you not login on the Chrome, you can login and retry."),
6364
Error::Anyhow(e) => write!(f, "{} {}", e, e),
65+
Error::Utf8ParseError => write!(f, "cannot parse utf8 from buff {}", e),
6466
}
6567
}
6668
}
@@ -72,6 +74,13 @@ impl std::convert::From<reqwest::Error> for Error {
7274
}
7375
}
7476

77+
// utf8 parse
78+
impl std::convert::From<FromUtf8Error> for Error {
79+
fn from(_err: FromUtf8Error) -> Self {
80+
Error::Utf8ParseError
81+
}
82+
}
83+
7584
// nums
7685
impl std::convert::From<std::num::ParseIntError> for Error {
7786
fn from(err: std::num::ParseIntError) -> Self {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy