From 8a1c966e7f0add23d74708637adcc1eb374bd874 Mon Sep 17 00:00:00 2001 From: heeh Date: Sun, 2 Jul 2023 11:48:02 -0700 Subject: [PATCH 1/9] Update Cookie setup due to Chromium configuration update (#127) Co-authored-by: Hee Hwang --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4e68afc..b775b68 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ cargo install leetcode-cli ## Usage -**Make sure you have logged in to `leetcode.com` with `Chrome`**. See [Cookies](#cookies) for why you need to do this first. +**Make sure you have logged in to `leetcode.com` with `Firefox`**. See [Cookies](#cookies) for why you need to do this first. ```sh leetcode 0.4.0 @@ -168,19 +168,20 @@ csrf = "..." session = "..." ``` -For Example, using Chrome (after logging in to LeetCode): +For Example, using Firefox (after logging in to LeetCode): #### Step 1 -Open Chrome and navigate to the link below: +Open Firefox, press F12, and click `Storage` tab. + +#### Step 2 + +Expand `Cookies` tab on the left and select https://leetcode.com. -```sh -chrome://settings/cookies/detail?site=leetcode.com -``` #### Step 2 -Copy `Content` from `LEETCODE_SESSION` and `csrftoken` to `session` and `csrf` in your configuration file, respectively: +Copy `Value` from `LEETCODE_SESSION` and `csrftoken` to `session` and `csrf` in your configuration file, respectively: ```toml [cookies] From d121ac11e92d0e5dbe09a060839524292db73591 Mon Sep 17 00:00:00 2001 From: Akarsh Jain Date: Tue, 11 Jul 2023 14:35:10 +0530 Subject: [PATCH 2/9] Shell completions (#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 --- Cargo.lock | 10 +++++++ Cargo.toml | 1 + README.md | 17 +++++++++++ src/cli.rs | 16 +++++++---- src/cmds/completions.rs | 62 +++++++++++++++++++++++++++++++++++++++++ src/cmds/mod.rs | 2 ++ src/err.rs | 11 +++++++- 7 files changed, 112 insertions(+), 7 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/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 { From fc4029f94578392f5624e0ef49a43d59d54a113d Mon Sep 17 00:00:00 2001 From: yangliz5 Date: Thu, 20 Jul 2023 00:13:39 -0500 Subject: [PATCH 3/9] docs: Update README.md with configuration instructions (#130) * docs: Update README.md with configuration instructions * docs: add configuration explanation --- README.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a93f4a8..c80ae9d 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,11 @@ 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. @@ -72,8 +74,16 @@ For example, given this config (could be found at `~/.leetcode/leetcode.toml`): [code] editor = 'emacs' # Optional parameter -editor-args = ['-nw'] +editor_args = ['-nw'] lang = 'rust' +edit_code_marker = false +start_marker = "" +end_marker = "" +# if include problem description +comment_problem_desc = false +# comment syntax +comment_leading = "" +test = true [cookies] csrf = '' @@ -86,6 +96,96 @@ root = '~/.leetcode' scripts = 'scripts' ``` +
+ Configuration Explanation + +```toml +[code] +editor = 'emacs' +# Optional parameter +editor_args = ['-nw'] +lang = 'rust' +edit_code_marker = true +start_marker = "start_marker" +end_marker = "end_marker" +# if include problem description +comment_problem_desc = true +# comment syntax +comment_leading = "//" +test = true + +[cookies] +csrf = '' +session = '' + +[storage] +cache = 'Problems' +code = 'code' +root = '~/.leetcode' +scripts = 'scripts' +``` + +If we change the configuration as shown previously, we will get the following code after `leetcode edit 15`. + +```rust +// Category: algorithms +// Level: Medium +// Percent: 32.90331% + +// Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0. +// +// Notice that the solution set must not contain duplicate triplets. +// +//   +// Example 1: +// +// Input: nums = [-1,0,1,2,-1,-4] +// Output: [[-1,-1,2],[-1,0,1]] +// Explanation: +// nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0. +// nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0. +// nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0. +// The distinct triplets are [-1,0,1] and [-1,-1,2]. +// Notice that the order of the output and the order of the triplets does not matter. +// +// +// Example 2: +// +// Input: nums = [0,1,1] +// Output: [] +// Explanation: The only possible triplet does not sum up to 0. +// +// +// Example 3: +// +// Input: nums = [0,0,0] +// Output: [[0,0,0]] +// Explanation: The only possible triplet sums up to 0. +// +// +//   +// Constraints: +// +// +// 3 <= nums.length <= 3000 +// -10⁵ <= nums[i] <= 10⁵ +// + +// start_marker +impl Solution { +pub fn three_sum(nums: Vec) -> Vec> { + + } + +} +// end_marker + +``` + +
+ +
+ #### 1. pick ```sh @@ -195,7 +295,6 @@ Open Firefox, press F12, and click `Storage` tab. Expand `Cookies` tab on the left and select https://leetcode.com. - #### Step 2 Copy `Value` from `LEETCODE_SESSION` and `csrftoken` to `session` and `csrf` in your configuration file, respectively: From 68d269c102aa0013ccebf58355f1da7d8b21f4ab Mon Sep 17 00:00:00 2001 From: Josh <56745535+Subjective@users.noreply.github.com> Date: Thu, 7 Sep 2023 19:09:13 -0700 Subject: [PATCH 4/9] feat: inject code before and after leetcode boilerplate (#132) * Add code templates * chore: cleanup messages * Add "inject_before" and "inject_after" to replace code templates * docs: Update README.md for `inject_before` and `inject_after` --- README.md | 10 +++++++++- src/cache/mod.rs | 4 ++-- src/cmds/edit.rs | 10 ++++++++++ src/config/code.rs | 6 ++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c80ae9d..39c61dc 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ SUBCOMMANDS: ## Example -For example, given this config (could be found at `~/.leetcode/leetcode.toml`): +To configure leetcode-cli, create a file at `~/.leetcode/leetcode.toml`): ```toml [code] @@ -186,6 +186,14 @@ pub fn three_sum(nums: Vec) -> Vec> {
+Some linting tools/lsps will throw errors unless the necessary libraries are imported. leetcode-cli can generate this boilerplate automatically if the `inject_before` key is set. Similarly, if you want to test out your code locally, you can automate that with `inject_after`. For c++ this might look something like: + +```toml +[code] +inject_before = ["#includepick ```sh diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 03e138e..fc3da47 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -356,7 +356,7 @@ impl Cache { ) -> Result { trace!("Exec problem filter —— Test or Submit"); let (json, [url, refer]) = self.pre_run_code(run.clone(), rfid, test_case).await?; - trace!("Pre run code result {:#?}, {}, {}", json, url, refer); + trace!("Pre-run code result {:#?}, {}, {}", json, url, refer); let text = self .0 @@ -367,7 +367,7 @@ impl Cache { .await?; let run_res: RunCode = serde_json::from_str(&text).map_err(|e| { - anyhow!("json error: {e}, plz double check your session and csrf config.") + anyhow!("JSON error: {e}, please double check your session and csrf config.") })?; trace!("Run code result {:#?}", run_res); diff --git a/src/cmds/edit.rs b/src/cmds/edit.rs index 9be45ec..73a9878 100644 --- a/src/cmds/edit.rs +++ b/src/cmds/edit.rs @@ -93,6 +93,11 @@ impl Command for EditCommand { file_code.write_all(p_desc_comment.as_bytes())?; file_code.write_all(question_desc.as_bytes())?; } + if let Some(inject_before) = &conf.code.inject_before { + for line in inject_before { + file_code.write_all((line.to_string() + "\n").as_bytes())?; + } + } if conf.code.edit_code_marker { file_code.write_all( (conf.code.comment_leading.clone() @@ -112,6 +117,11 @@ impl Command for EditCommand { .as_bytes(), )?; } + if let Some(inject_after) = &conf.code.inject_after { + for line in inject_after { + file_code.write_all((line.to_string() + "\n").as_bytes())?; + } + } if test_flag { let mut file_tests = File::create(&test_path)?; diff --git a/src/config/code.rs b/src/config/code.rs index 0384608..f8e6cea 100644 --- a/src/config/code.rs +++ b/src/config/code.rs @@ -22,6 +22,10 @@ pub struct Code { pub start_marker: String, #[serde(default, skip_serializing)] pub end_marker: String, + #[serde(rename(serialize = "inject_before"), alias = "inject_before", default)] + pub inject_before: Option>, + #[serde(rename(serialize = "inject_after"), alias = "inject_after", default)] + pub inject_after: Option>, #[serde(default, skip_serializing)] pub comment_problem_desc: bool, #[serde(default, skip_serializing)] @@ -43,6 +47,8 @@ impl Default for Code { edit_code_marker: false, start_marker: "".into(), end_marker: "".into(), + inject_before: None, + inject_after: None, comment_problem_desc: false, comment_leading: "".into(), test: true, From 03223566df272b38936aa3345a12f41d43579f73 Mon Sep 17 00:00:00 2001 From: Josh <56745535+Subjective@users.noreply.github.com> Date: Sat, 9 Sep 2023 05:25:36 -0700 Subject: [PATCH 5/9] feat: add option to set environment variables for edit command (#133) * feat: add option to configure environment variable for edit command * feat: allow for unlimited environment variables * Update README for Optional environment variables * docs: fix inject_before example in README * chore: fix grammar/typos --- README.md | 6 +++++- src/cache/mod.rs | 8 ++++---- src/cmds/edit.rs | 32 ++++++++++++++++++++++++++++++++ src/config/code.rs | 3 +++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 39c61dc..5a6ad7f 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ To configure leetcode-cli, create a file at `~/.leetcode/leetcode.toml`): editor = 'emacs' # Optional parameter editor_args = ['-nw'] +# Optional environment variables (ex. [ "XDG_DATA_HOME=...", "XDG_CONFIG_HOME=...", "XDG_STATE_HOME=..." ]) +editor_envs = [] lang = 'rust' edit_code_marker = false start_marker = "" @@ -104,6 +106,8 @@ scripts = 'scripts' editor = 'emacs' # Optional parameter editor_args = ['-nw'] +# Optional environment variables (ex. [ "XDG_DATA_HOME=...", "XDG_CONFIG_HOME=...", "XDG_STATE_HOME=..." ]) +editor_envs = [] lang = 'rust' edit_code_marker = true start_marker = "start_marker" @@ -190,7 +194,7 @@ Some linting tools/lsps will throw errors unless the necessary libraries are imp ```toml [code] -inject_before = ["#include", "using namespace std;"] inject_after = ["int main() {\n Solution solution;\n\n}"] ``` diff --git a/src/cache/mod.rs b/src/cache/mod.rs index fc3da47..169c18b 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -115,7 +115,7 @@ impl Cache { let p: Problem = problems.filter(fid.eq(rfid)).first(&mut self.conn()?)?; if p.category != "algorithms" { return Err(Error::FeatureError( - "Not support database and shell questions for now".to_string(), + "No support for database and shell questions yet".to_string(), )); } @@ -129,7 +129,7 @@ impl Cache { .first(&mut self.conn()?)?; if p.category != "algorithms" { return Err(Error::FeatureError( - "Not support database and shell questions for now".to_string(), + "No support for database and shell questions yet".to_string(), )); } Ok(p.fid) @@ -174,7 +174,7 @@ impl Cache { if target.category != "algorithms" { return Err(Error::FeatureError( - "Not support database and shell questions for now".to_string(), + "No support for database and shell questions yet".to_string(), )); } @@ -255,7 +255,7 @@ impl Cache { rfid: i32, test_case: Option, ) -> Result<(HashMap<&'static str, String>, [String; 2]), Error> { - trace!("pre run code..."); + trace!("pre-run code..."); use crate::helper::code_path; use std::fs::File; use std::io::Read; diff --git a/src/cmds/edit.rs b/src/cmds/edit.rs index 73a9878..9f42efe 100644 --- a/src/cmds/edit.rs +++ b/src/cmds/edit.rs @@ -3,6 +3,7 @@ use super::Command; use crate::Error; use async_trait::async_trait; use clap::{Arg, ArgMatches, Command as ClapCommand}; +use std::collections::HashMap; /// Abstract `edit` command /// @@ -161,8 +162,39 @@ impl Command for EditCommand { args.extend_from_slice(&editor_args); } + // Set environment variables for editor + // + // for example: + // + // ```toml + // [code] + // editor = "nvim" + // editor_envs = [ "XDG_DATA_HOME=...", "XDG_CONFIG_HOME=...", "XDG_STATE_HOME=..." ] + // ``` + // + // ```rust + // Command::new("nvim").envs(&[ ("XDG_DATA_HOME", "..."), ("XDG_CONFIG_HOME", "..."), ("XDG_STATE_HOME", "..."), ]); + // ``` + let mut envs: HashMap = Default::default(); + if let Some(editor_envs) = &conf.code.editor_envs { + for env in editor_envs.iter() { + let parts: Vec<&str> = env.split('=').collect(); + if parts.len() == 2 { + let name = parts[0].trim(); + let value = parts[1].trim(); + envs.insert(name.to_string(), value.to_string()); + } else { + return Err(crate::Error::FeatureError(format!( + "Invalid editor environment variable: {}", + &env + ))); + } + } + } + args.push(path); std::process::Command::new(conf.code.editor) + .envs(envs) .args(args) .status()?; Ok(()) diff --git a/src/config/code.rs b/src/config/code.rs index f8e6cea..b842657 100644 --- a/src/config/code.rs +++ b/src/config/code.rs @@ -16,6 +16,8 @@ pub struct Code { pub editor: String, #[serde(rename(serialize = "editor-args"), alias = "editor-args", default)] pub editor_args: Option>, + #[serde(rename(serialize = "editor-envs"), alias = "editor-envs", default)] + pub editor_envs: Option>, #[serde(default, skip_serializing)] pub edit_code_marker: bool, #[serde(default, skip_serializing)] @@ -44,6 +46,7 @@ impl Default for Code { Self { editor: "vim".into(), editor_args: None, + editor_envs: None, edit_code_marker: false, start_marker: "".into(), end_marker: "".into(), From 05e1880e5c6b9331c29e3017a5c2ec0fe342428b Mon Sep 17 00:00:00 2001 From: Josh <56745535+Subjective@users.noreply.github.com> Date: Sat, 9 Sep 2023 08:37:26 -0700 Subject: [PATCH 6/9] fix: shell detection message always outputed when generating completion (#131) * fix: shell detection message always outputed when generating completion * Remove shell detection message * Update README --- README.md | 2 ++ src/cmds/completions.rs | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5a6ad7f..1b616d3 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ You may also obtain specific shell configuration using. leetcode completions fish ``` +If no argument is provided, the shell is inferred from the `SHELL` environment variable. + ## Usage diff --git a/src/cmds/completions.rs b/src/cmds/completions.rs index c57c543..bc675de 100644 --- a/src/cmds/completions.rs +++ b/src/cmds/completions.rs @@ -51,10 +51,7 @@ fn get_completions_string(gen: G, cmd: &mut ClapCommand) -> Result 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)? - } + &Shell::from_env().ok_or(Error::MatchError)?, ); let completions = get_completions_string(shell, cmd)?; println!("{}", completions); From 64a9d1008491c11da6e207235072b0ec0c54a565 Mon Sep 17 00:00:00 2001 From: Kartikay Dubey <90677227+dubeyKartikay@users.noreply.github.com> Date: Tue, 19 Sep 2023 22:11:50 +0530 Subject: [PATCH 7/9] closes #79 fixed python script support (#134) --- src/pym.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pym.rs b/src/pym.rs index dfb409b..002142c 100644 --- a/src/pym.rs +++ b/src/pym.rs @@ -7,6 +7,7 @@ use pyo3::prelude::*; /// Exec python scripts as filter pub fn exec(module: &str) -> Result, crate::Error> { + pyo3::prepare_freethreaded_python(); let script = load_script(&module)?; let cache = Cache::new()?; @@ -20,7 +21,7 @@ pub fn exec(module: &str) -> Result, crate::Error> { let stags = serde_json::to_string(&cache.get_tags()?)?; // ret - let res: Vec = pym.call1("plan", (sps, stags))?.extract()?; + let res: Vec = pym.getattr("plan")?.call1((sps, stags))?.extract()?; Ok(res) } From 4b76c46bfe02c9c4ea3fa2c62f0e603a139ed264 Mon Sep 17 00:00:00 2001 From: Joshua Cox <85255276+qwed81@users.noreply.github.com> Date: Mon, 18 Dec 2023 21:12:28 -0600 Subject: [PATCH 8/9] pick -n supports closest name match (#138) --- src/cmds/pick.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/src/cmds/pick.rs b/src/cmds/pick.rs index 967e205..4a8e395 100644 --- a/src/cmds/pick.rs +++ b/src/cmds/pick.rs @@ -1,5 +1,6 @@ //! Pick command use super::Command; +use crate::cache::models::Problem; use crate::err::Error; use async_trait::async_trait; use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand}; @@ -136,12 +137,12 @@ impl Command for PickCommand { }; let fid = match m.contains_id("name") { - //check for name specified + // check for name specified, or closest name true => { match m.get_one::("name").map(|name| name) { - Some(quesname) => match cache.get_problem_id_from_name(quesname) { - Ok(p) => p, - Err(_) => 1, + Some(quesname) => match closest_named_problem(&problems, quesname) { + Some(p) => p, + None => 1, }, None => { // Pick random without specify id @@ -177,3 +178,69 @@ impl Command for PickCommand { Ok(()) } } + +// Returns the closest problem according to a scoring algorithm +// taking into account both the longest common subsequence and the size +// problem string (to compensate for smaller strings having smaller lcs). +// Returns None if there are no problems in the problem list +fn closest_named_problem(problems: &Vec, lookup_name: &str) -> Option { + let max_name_size: usize = problems.iter().map(|p| p.name.len()).max()?; + // Init table to the max name length of all the problems to share + // the same table allocation + let mut table: Vec = vec![0; (max_name_size + 1) * (lookup_name.len() + 1)]; + + // this is guaranteed because of the earlier max None propegation + assert!(problems.len() > 0); + let mut max_score = 0; + let mut current_problem = &problems[0]; + for problem in problems { + // In case bug becomes bugged, always return the matching string + if problem.name == lookup_name { + return Some(problem.fid); + } + + let this_lcs = longest_common_subsequence(&mut table, &problem.name, lookup_name); + let this_score = this_lcs * (max_name_size - problem.name.len()); + + if this_score > max_score { + max_score = this_score; + current_problem = &problem; + } + } + + Some(current_problem.fid) +} + +// Longest commong subsequence DP approach O(nm) space and time. Table must be at least +// (text1.len() + 1) * (text2.len() + 1) length or greater and is mutated every call +fn longest_common_subsequence(table: &mut Vec, text1: &str, text2: &str) -> usize { + assert!(table.len() >= (text1.len() + 1) * (text2.len() + 1)); + let height: usize = text1.len() + 1; + let width: usize = text2.len() + 1; + + // initialize base cases to 0 + for i in 0..height { + table[i * width + (width - 1)] = 0; + } + for j in 0..width { + table[((height - 1) * width) + j] = 0; + } + + let mut i: usize = height - 1; + let mut j: usize; + for c0 in text1.chars().rev() { + i -= 1; + j = width - 1; + for c1 in text2.chars().rev() { + j -= 1; + if c0.to_lowercase().next() == c1.to_lowercase().next() { + table[i * width + j] = 1 + table[(i + 1) * width + j + 1]; + } else { + let a = table[(i + 1) * width + j]; + let b = table[i * width + j + 1]; + table[i * width + j] = std::cmp::max(a, b); + } + } + } + table[0] +} From 7ef1693ba53f3430b49c0adae16bd8eb05fbed80 Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Tue, 19 Dec 2023 11:15:05 +0800 Subject: [PATCH 9/9] chore(version): bumps version to 0.4.3 (#139) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ec01180..a3b8106 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ path = "src/bin/lc.rs" [package] name = "leetcode-cli" -version = "0.4.1" +version = "0.4.3" authors = ["clearloop "] edition = "2021" description = "Leetcode command-line interface in rust."