From 1918ae435ac0d6221fed23ec97141a20a6b488e8 Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sun, 14 Aug 2022 18:16:50 +0800 Subject: [PATCH 01/12] feat(config): add module config instead of cfg.rs --- src/cache/mod.rs | 4 ++-- src/cache/models.rs | 1 + src/{cfg.rs => config/mod.rs} | 29 +++-------------------------- src/err.rs | 2 +- src/helper.rs | 6 +++--- src/lib.rs | 4 ++-- src/plugins/chrome.rs | 2 +- src/plugins/leetcode.rs | 4 ++-- 8 files changed, 15 insertions(+), 37 deletions(-) rename src/{cfg.rs => config/mod.rs} (93%) diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 345b3ea..a39ad3e 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -7,7 +7,7 @@ use self::models::*; use self::schemas::{problems::dsl::*, tags::dsl::*}; use self::sql::*; use crate::helper::test_cases_path; -use crate::{cfg, err::Error, plugins::LeetCode}; +use crate::{config, err::Error, plugins::LeetCode}; use colored::Colorize; use diesel::prelude::*; use reqwest::Response; @@ -395,7 +395,7 @@ impl Cache { /// New cache pub fn new() -> Result { - let conf = cfg::locate()?; + let conf = config::locate()?; let c = conn(conf.storage.cache()?); diesel::sql_query(CREATE_PROBLEMS_IF_NOT_EXISTS).execute(&c)?; diesel::sql_query(CREATE_TAGS_IF_NOT_EXISTS).execute(&c)?; diff --git a/src/cache/models.rs b/src/cache/models.rs index 0a9a6ab..2caf94f 100644 --- a/src/cache/models.rs +++ b/src/cache/models.rs @@ -39,6 +39,7 @@ impl Problem { _ => "Unknown", } } + pub fn desc_comment(&self, conf: &Config) -> String { let mut res = String::new(); let comment_leading = &conf.code.comment_leading; diff --git a/src/cfg.rs b/src/config/mod.rs similarity index 93% rename from src/cfg.rs rename to src/config/mod.rs index 184e3e0..451e094 100644 --- a/src/cfg.rs +++ b/src/config/mod.rs @@ -19,25 +19,6 @@ categories = [ "shell" ] -langs = [ - "bash", - "c", - "cpp", - "csharp", - "golang", - "java", - "javascript", - "kotlin", - "mysql", - "php", - "python", - "python3", - "ruby", - "rust", - "scala", - "swift" -] - [sys.urls] base = "https://leetcode.com" graphql = "https://leetcode.com/graphql" @@ -74,8 +55,8 @@ session = "" root = "~/.leetcode" scripts = "scripts" code = "code" -# absolutely path for the cache, other use root as parent dir -cache = "~/.cache/leetcode" +# Relative path for the cache, otherwise use root as parent dir +cache = "Problems" "##; /// Sync with `~/.leetcode/config.toml` @@ -109,7 +90,6 @@ pub struct Cookies { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Sys { pub categories: Vec, - pub langs: Vec, pub urls: HashMap, } @@ -131,10 +111,7 @@ pub struct Urls { pub favorite_delete: String, } -/// default editor and langs -/// -/// + support editor: [emacs, vim] -/// + support langs: all in config +/// Code config #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Code { pub editor: String, diff --git a/src/err.rs b/src/err.rs index 5f007e9..ffffe77 100644 --- a/src/err.rs +++ b/src/err.rs @@ -1,6 +1,6 @@ //! Errors in leetcode-cli -use crate::cfg::{root, DEFAULT_CONFIG}; use crate::cmds::{Command, DataCommand}; +use crate::config::{root, DEFAULT_CONFIG}; use colored::Colorize; use std::fmt; diff --git a/src/helper.rs b/src/helper.rs index 60efcd8..7a1b40b 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -190,7 +190,7 @@ mod file { /// Generate test cases path by fid pub fn test_cases_path(problem: &Problem) -> Result { - let conf = crate::cfg::locate()?; + let conf = crate::config::locate()?; let mut path = format!("{}/{}.tests.dat", conf.storage.code()?, conf.code.pick); path = path.replace("${fid}", &problem.fid.to_string()); @@ -200,7 +200,7 @@ mod file { /// Generate code path by fid pub fn code_path(problem: &Problem, l: Option) -> Result { - let conf = crate::cfg::locate()?; + let conf = crate::config::locate()?; let mut lang = conf.code.lang; if l.is_some() { lang = l.ok_or(Error::NoneError)?; @@ -223,7 +223,7 @@ mod file { pub fn load_script(module: &str) -> Result { use std::fs::File; use std::io::Read; - let conf = crate::cfg::locate()?; + let conf = crate::config::locate()?; let mut script = "".to_string(); File::open(format!("{}/{}.py", conf.storage.scripts()?, module))? .read_to_string(&mut script)?; diff --git a/src/lib.rs b/src/lib.rs index e97fb6e..4bc2a53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -235,9 +235,9 @@ extern crate diesel; // show docs pub mod cache; -pub mod cfg; pub mod cli; pub mod cmds; +pub mod config; pub mod err; pub mod flag; pub mod helper; @@ -247,5 +247,5 @@ pub mod pym; // re-exports pub use cache::Cache; -pub use cfg::Config; +pub use config::Config; pub use err::Error; diff --git a/src/plugins/chrome.rs b/src/plugins/chrome.rs index 20ba976..1105f48 100644 --- a/src/plugins/chrome.rs +++ b/src/plugins/chrome.rs @@ -41,7 +41,7 @@ impl std::string::ToString for Ident { /// Get cookies from chrome storage pub fn cookies() -> Result { - let ccfg = crate::cfg::locate()?.cookies; + let ccfg = crate::config::locate()?.cookies; if !ccfg.csrf.is_empty() && !ccfg.session.is_empty() { return Ok(Ident { csrf: ccfg.csrf, diff --git a/src/plugins/leetcode.rs b/src/plugins/leetcode.rs index e06627f..e8ccb91 100644 --- a/src/plugins/leetcode.rs +++ b/src/plugins/leetcode.rs @@ -1,6 +1,6 @@ use self::req::{Json, Mode, Req}; use crate::{ - cfg::{self, Config}, + config::{self, Config}, err::Error, plugins::chrome, }; @@ -36,7 +36,7 @@ impl LeetCode { /// New LeetCode client pub fn new() -> Result { - let conf = cfg::locate()?; + let conf = config::locate()?; let cookies = chrome::cookies()?; let default_headers = LeetCode::headers( HeaderMap::new(), From 5a9322f6c80de24914da2cb5558251549a727b26 Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sun, 14 Aug 2022 20:56:40 +0800 Subject: [PATCH 02/12] feat(config): refactor sys --- src/cache/mod.rs | 25 ++--------- src/config/mod.rs | 64 ++++------------------------ src/config/sys.rs | 93 +++++++++++++++++++++++++++++++++++++++++ src/plugins/leetcode.rs | 39 +++++------------ 4 files changed, 114 insertions(+), 107 deletions(-) create mode 100644 src/config/sys.rs diff --git a/src/cache/mod.rs b/src/cache/mod.rs index a39ad3e..6a70643 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -307,33 +307,14 @@ impl Cache { json.insert("data_input", test_case); let url = match run { - Run::Test => conf - .sys - .urls - .get("test") - .ok_or(Error::NoneError)? - .replace("$slug", &p.slug), + Run::Test => conf.sys.urls.test(&p.slug), Run::Submit => { json.insert("judge_type", "large".to_string()); - conf.sys - .urls - .get("submit") - .ok_or(Error::NoneError)? - .replace("$slug", &p.slug) + conf.sys.urls.submit(&p.slug) } }; - Ok(( - json, - [ - url, - conf.sys - .urls - .get("problems") - .ok_or(Error::NoneError)? - .replace("$slug", &p.slug), - ], - )) + Ok((json, [url, conf.sys.urls.problem(&p.slug)])) } /// TODO: The real delay diff --git a/src/config/mod.rs b/src/config/mod.rs index 451e094..8081966 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -5,36 +5,13 @@ //! //! + Edit leetcode.toml at `~/.leetcode/leetcode.toml` directly //! + Use `leetcode config` to update it -use crate::Error; +use crate::{config::sys::Sys, Error}; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs, path::PathBuf}; - -pub const DEFAULT_CONFIG: &str = r##" -# usually you don't wanna change those -[sys] -categories = [ - "algorithms", - "concurrency", - "database", - "shell" -] - -[sys.urls] -base = "https://leetcode.com" -graphql = "https://leetcode.com/graphql" -login = "https://leetcode.com/accounts/login/" -problems = "https://leetcode.com/api/problems/$category/" -problem = "https://leetcode.com/problems/$slug/description/" -tag = "https://leetcode.com/tag/$slug/" -test = "https://leetcode.com/problems/$slug/interpret_solution/" -session = "https://leetcode.com/session/" -submit = "https://leetcode.com/problems/$slug/submit/" -submissions = "https://leetcode.com/api/submissions/$slug" -submission = "https://leetcode.com/submissions/detail/$id/" -verify = "https://leetcode.com/submissions/detail/$id/check/" -favorites = "https://leetcode.com/list/api/questions" -favorite_delete = "https://leetcode.com/list/api/questions/$hash/$id" +use std::{fs, path::PathBuf}; +mod sys; + +pub const DEFAULT_CONFIG: &str = r#" [code] editor = "vim" lang = "rust" @@ -55,13 +32,13 @@ session = "" root = "~/.leetcode" scripts = "scripts" code = "code" -# Relative path for the cache, otherwise use root as parent dir cache = "Problems" -"##; +"#; /// Sync with `~/.leetcode/config.toml` #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Config { + #[serde(skip)] pub sys: Sys, pub code: Code, pub cookies: Cookies, @@ -86,31 +63,6 @@ pub struct Cookies { pub session: String, } -/// System settings, for leetcode api mainly -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Sys { - pub categories: Vec, - pub urls: HashMap, -} - -/// Leetcode API -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Urls { - pub base: String, - pub graphql: String, - pub login: String, - pub problems: String, - pub problem: String, - pub test: String, - pub session: String, - pub submit: String, - pub submissions: String, - pub submission: String, - pub verify: String, - pub favorites: String, - pub favorite_delete: String, -} - /// Code config #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Code { @@ -197,7 +149,7 @@ impl Storage { pub fn locate() -> Result { let conf = root()?.join("leetcode.toml"); if !conf.is_file() { - fs::write(&conf, &DEFAULT_CONFIG[1..])?; + fs::write(&conf, &DEFAULT_CONFIG.trim())?; } let s = fs::read_to_string(&conf)?; diff --git a/src/config/sys.rs b/src/config/sys.rs new file mode 100644 index 0000000..4386bd9 --- /dev/null +++ b/src/config/sys.rs @@ -0,0 +1,93 @@ +//! System section +//! +//! This section is a set of constants after #88 + +const CATEGORIES: [&str; 4] = ["algorithms", "concurrency", "database", "shell"]; + +/// Leetcode API +#[derive(Clone, Debug)] +pub struct Urls { + pub base: String, + pub graphql: String, + pub login: String, + pub problems: String, + pub problem: String, + pub tag: String, + pub test: String, + pub session: String, + pub submit: String, + pub submissions: String, + pub submission: String, + pub verify: String, + pub favorites: String, + pub favorite_delete: String, +} + +impl Default for Urls { + fn default() -> Self { + Self { + base: "https://leetcode.com".into(), + graphql: "https://leetcode.com/graphql".into(), + login: "https://leetcode.com/accounts/login/".into(), + problems: "https://leetcode.com/api/problems/$category/".into(), + problem: "https://leetcode.com/problems/$slug/description/".into(), + tag: "https://leetcode.com/tag/$slug/".into(), + test: "https://leetcode.com/problems/$slug/interpret_solution/".into(), + session: "https://leetcode.com/session/".into(), + submit: "https://leetcode.com/problems/$slug/submit/".into(), + submissions: "https://leetcode.com/submissions/detail/$id/".into(), + submission: "https://leetcode.com/submissions/detail/$id/".into(), + verify: "https://leetcode.com/submissions/detail/$id/check/".into(), + favorites: "https://leetcode.com/list/api/questions".into(), + favorite_delete: "https://leetcode.com/list/api/questions/$hash/$id".into(), + } + } +} + +impl Urls { + /// problem url with specific `$slug` + pub fn problem(&self, slug: &str) -> String { + self.problem.replace("$slug", slug) + } + + /// problems url with specific `$category` + pub fn problems(&self, category: &str) -> String { + self.problem.replace("$category", category) + } + + /// submit url with specific `$slug` + pub fn submit(&self, slug: &str) -> String { + self.submit.replace("$slug", slug) + } + + /// tag url with specific `$slug` + pub fn tag(&self, slug: &str) -> String { + self.tag.replace("$slug", slug) + } + + /// test url with specific `$slug` + pub fn test(&self, slug: &str) -> String { + self.test.replace("$slug", slug) + } + + /// verify url with specific `$id` + pub fn verify(&self, id: &str) -> String { + self.verify.replace("$id", id) + } +} + +/// System settings, for leetcode api mainly +#[derive(Clone, Debug)] +pub struct Sys { + pub categories: Vec, + pub urls: Urls, +} + +impl Default for Sys { + fn default() -> Self { + Self { + categories: CATEGORIES.into_iter().map(|s| s.into()).collect(), + urls: Default::default(), + } + } +} diff --git a/src/plugins/leetcode.rs b/src/plugins/leetcode.rs index e8ccb91..1e7b17f 100644 --- a/src/plugins/leetcode.rs +++ b/src/plugins/leetcode.rs @@ -44,7 +44,7 @@ impl LeetCode { ("Cookie", cookies.to_string().as_str()), ("x-csrftoken", &cookies.csrf), ("x-requested-with", "XMLHttpRequest"), - ("Origin", &conf.sys.urls["base"]), + ("Origin", &conf.sys.urls.base), ], )?; @@ -68,13 +68,7 @@ impl LeetCode { /// Get category problems pub async fn get_category_problems(self, category: &str) -> Result { trace!("Requesting {} problems...", &category); - let url = &self - .conf - .sys - .urls - .get("problems") - .ok_or(Error::NoneError)? - .replace("$category", category); + let url = &self.conf.sys.urls.problems(category); Req { default_headers: self.default_headers, @@ -91,7 +85,7 @@ impl LeetCode { pub async fn get_question_ids_by_tag(self, slug: &str) -> Result { trace!("Requesting {} ref problems...", &slug); - let url = &self.conf.sys.urls.get("graphql").ok_or(Error::NoneError)?; + let url = &self.conf.sys.urls.graphql; let mut json: Json = HashMap::new(); json.insert("operationName", "getTopicTag".to_string()); json.insert("variables", r#"{"slug": "$slug"}"#.replace("$slug", slug)); @@ -111,9 +105,7 @@ impl LeetCode { Req { default_headers: self.default_headers, - refer: Some( - (self.conf.sys.urls.get("tag").ok_or(Error::NoneError)?).replace("$slug", slug), - ), + refer: Some(self.conf.sys.urls.tag(slug)), info: false, json: Some(json), mode: Mode::Post, @@ -126,7 +118,7 @@ impl LeetCode { pub async fn get_user_info(self) -> Result { trace!("Requesting user info..."); - let url = &self.conf.sys.urls.get("graphql").ok_or(Error::NoneError)?; + let url = &self.conf.sys.urls.graphql; let mut json: Json = HashMap::new(); json.insert("operationName", "a".to_string()); json.insert( @@ -156,7 +148,7 @@ impl LeetCode { /// Get daily problem pub async fn get_question_daily(self) -> Result { trace!("Requesting daily problem..."); - let url = &self.conf.sys.urls.get("graphql").ok_or(Error::NoneError)?; + let url = &self.conf.sys.urls.graphql; let mut json: Json = HashMap::new(); json.insert("operationName", "daily".to_string()); json.insert( @@ -189,13 +181,7 @@ impl LeetCode { /// Get specific problem detail pub async fn get_question_detail(self, slug: &str) -> Result { trace!("Requesting {} detail...", &slug); - let refer = self - .conf - .sys - .urls - .get("problems") - .ok_or(Error::NoneError)? - .replace("$slug", slug); + let refer = self.conf.sys.urls.problem(slug); let mut json: Json = HashMap::new(); json.insert( "query", @@ -230,7 +216,7 @@ impl LeetCode { json: Some(json), mode: Mode::Post, name: "get_problem_detail", - url: (&self.conf.sys.urls["graphql"]).to_string(), + url: self.conf.sys.urls.graphql.into(), } .send(&self.client) .await @@ -255,13 +241,8 @@ impl LeetCode { /// Get the result of submission / testing pub async fn verify_result(self, id: String) -> Result { trace!("Verifying result..."); - let url = self - .conf - .sys - .urls - .get("verify") - .ok_or(Error::NoneError)? - .replace("$id", &id); + let url = self.conf.sys.urls.verify(&id); + Req { default_headers: self.default_headers, refer: None, From 9bd3a4712ba717f4d7107ff2b17606b3b2c127d1 Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sun, 14 Aug 2022 21:15:39 +0800 Subject: [PATCH 03/12] chore(config): wrap util functions in to Config --- src/cache/mod.rs | 4 +- src/config/code.rs | 19 ++++++ src/config/cookies.rs | 9 +++ src/config/mod.rs | 139 ++++++++-------------------------------- src/config/storage.rs | 68 ++++++++++++++++++++ src/err.rs | 4 +- src/helper.rs | 6 +- src/plugins/chrome.rs | 2 +- src/plugins/leetcode.rs | 2 +- 9 files changed, 133 insertions(+), 120 deletions(-) create mode 100644 src/config/code.rs create mode 100644 src/config/cookies.rs create mode 100644 src/config/storage.rs diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 6a70643..fe88cfc 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -7,7 +7,7 @@ use self::models::*; use self::schemas::{problems::dsl::*, tags::dsl::*}; use self::sql::*; use crate::helper::test_cases_path; -use crate::{config, err::Error, plugins::LeetCode}; +use crate::{config::Config, err::Error, plugins::LeetCode}; use colored::Colorize; use diesel::prelude::*; use reqwest::Response; @@ -376,7 +376,7 @@ impl Cache { /// New cache pub fn new() -> Result { - let conf = config::locate()?; + let conf = Config::locate()?; let c = conn(conf.storage.cache()?); diesel::sql_query(CREATE_PROBLEMS_IF_NOT_EXISTS).execute(&c)?; diesel::sql_query(CREATE_TAGS_IF_NOT_EXISTS).execute(&c)?; diff --git a/src/config/code.rs b/src/config/code.rs new file mode 100644 index 0000000..1f8f49e --- /dev/null +++ b/src/config/code.rs @@ -0,0 +1,19 @@ +//! Code in config +use serde::{Deserialize, Serialize}; + +/// Code config +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Code { + pub editor: String, + #[serde(rename(serialize = "editor-args", deserialize = "editor-args"))] + pub editor_args: Option>, + pub edit_code_marker: bool, + pub start_marker: String, + pub end_marker: String, + pub comment_problem_desc: bool, + pub comment_leading: String, + pub test: bool, + pub lang: String, + pub pick: String, + pub submission: String, +} diff --git a/src/config/cookies.rs b/src/config/cookies.rs new file mode 100644 index 0000000..0c75a90 --- /dev/null +++ b/src/config/cookies.rs @@ -0,0 +1,9 @@ +//! Cookies in config +use serde::{Deserialize, Serialize}; + +/// Cookies settings +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Cookies { + pub csrf: String, + pub session: String, +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 8081966..4eb60f9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -5,10 +5,16 @@ //! //! + Edit leetcode.toml at `~/.leetcode/leetcode.toml` directly //! + Use `leetcode config` to update it -use crate::{config::sys::Sys, Error}; +use crate::{ + config::{code::Code, cookies::Cookies, storage::Storage, sys::Sys}, + Error, +}; use serde::{Deserialize, Serialize}; -use std::{fs, path::PathBuf}; +use std::fs; +mod code; +mod cookies; +mod storage; mod sys; pub const DEFAULT_CONFIG: &str = r#" @@ -46,123 +52,34 @@ pub struct Config { } impl Config { - /// Sync new config to config.toml - pub fn sync(&self) -> Result<(), Error> { - let home = dirs::home_dir().ok_or(Error::NoneError)?; - let conf = home.join(".leetcode/leetcode.toml"); - fs::write(conf, toml::ser::to_string_pretty(&self)?)?; - - Ok(()) - } -} - -/// Cookie settings -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Cookies { - pub csrf: String, - pub session: String, -} - -/// Code config -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Code { - pub editor: String, - #[serde(rename(serialize = "editor-args", deserialize = "editor-args"))] - pub editor_args: Option>, - pub edit_code_marker: bool, - pub start_marker: String, - pub end_marker: String, - pub comment_problem_desc: bool, - pub comment_leading: String, - pub test: bool, - pub lang: String, - pub pick: String, - pub submission: String, -} - -/// Locate code files -/// -/// + cache -> the path to cache -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Storage { - cache: String, - code: String, - root: String, - scripts: Option, -} - -impl Storage { - /// convert root path - pub fn root(&self) -> Result { - let home = dirs::home_dir() - .ok_or(Error::NoneError)? - .to_string_lossy() - .to_string(); - let path = self.root.replace('~', &home); - Ok(path) - } - - /// get cache path - pub fn cache(&self) -> Result { - let home = dirs::home_dir() - .ok_or(Error::NoneError)? - .to_string_lossy() - .to_string(); - let path = PathBuf::from(self.cache.replace('~', &home)); - if !path.is_dir() { - info!("Generate cache dir at {:?}.", &path); - fs::DirBuilder::new().recursive(true).create(&path)?; - } - - Ok(path.join("Problems").to_string_lossy().to_string()) - } - - /// get code path - pub fn code(&self) -> Result { - let root = &self.root()?; - let p = PathBuf::from(root).join(&self.code); - if !PathBuf::from(&p).exists() { - fs::create_dir(&p)? + /// Locate lc's config file + pub fn locate() -> Result { + let conf = Self::root()?.join("leetcode.toml"); + if !conf.is_file() { + fs::write(&conf, &DEFAULT_CONFIG.trim())?; } - Ok(p.to_string_lossy().to_string()) + let s = fs::read_to_string(&conf)?; + Ok(toml::from_str::(&s)?) } - /// get scripts path - pub fn scripts(mut self) -> Result { - let root = &self.root()?; - if self.scripts.is_none() { - let tmp = toml::from_str::(DEFAULT_CONFIG)?; - self.scripts = Some(tmp.storage.scripts.ok_or(Error::NoneError)?); - } - - let p = PathBuf::from(root).join(&self.scripts.ok_or(Error::NoneError)?); - if !PathBuf::from(&p).exists() { - std::fs::create_dir(&p)? + /// Get root path of leetcode-cli + pub fn root() -> Result { + let dir = dirs::home_dir().ok_or(Error::NoneError)?.join(".leetcode"); + if !dir.is_dir() { + info!("Generate root dir at {:?}.", &dir); + fs::DirBuilder::new().recursive(true).create(&dir)?; } - Ok(p.to_string_lossy().to_string()) + Ok(dir) } -} -/// Locate lc's config file -pub fn locate() -> Result { - let conf = root()?.join("leetcode.toml"); - if !conf.is_file() { - fs::write(&conf, &DEFAULT_CONFIG.trim())?; - } - - let s = fs::read_to_string(&conf)?; - Ok(toml::from_str::(&s)?) -} + /// Sync new config to config.toml + pub fn sync(&self) -> Result<(), Error> { + let home = dirs::home_dir().ok_or(Error::NoneError)?; + let conf = home.join(".leetcode/leetcode.toml"); + fs::write(conf, toml::ser::to_string_pretty(&self)?)?; -/// Get root path of leetcode-cli -pub fn root() -> Result { - let dir = dirs::home_dir().ok_or(Error::NoneError)?.join(".leetcode"); - if !dir.is_dir() { - info!("Generate root dir at {:?}.", &dir); - fs::DirBuilder::new().recursive(true).create(&dir)?; + Ok(()) } - - Ok(dir) } diff --git a/src/config/storage.rs b/src/config/storage.rs new file mode 100644 index 0000000..6a0284e --- /dev/null +++ b/src/config/storage.rs @@ -0,0 +1,68 @@ +//! Storage in config. +use crate::Error; +use serde::{Deserialize, Serialize}; +use std::{fs, path::PathBuf}; + +/// Locate code files +/// +/// + cache -> the path to cache +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Storage { + cache: String, + code: String, + root: String, + scripts: Option, +} + +impl Storage { + /// convert root path + pub fn root(&self) -> Result { + let home = dirs::home_dir() + .ok_or(Error::NoneError)? + .to_string_lossy() + .to_string(); + let path = self.root.replace('~', &home); + Ok(path) + } + + /// get cache path + pub fn cache(&self) -> Result { + let home = dirs::home_dir() + .ok_or(Error::NoneError)? + .to_string_lossy() + .to_string(); + let path = PathBuf::from(self.cache.replace('~', &home)); + if !path.is_dir() { + info!("Generate cache dir at {:?}.", &path); + fs::DirBuilder::new().recursive(true).create(&path)?; + } + + Ok(path.join("Problems").to_string_lossy().to_string()) + } + + /// get code path + pub fn code(&self) -> Result { + let root = &self.root()?; + let p = PathBuf::from(root).join(&self.code); + if !PathBuf::from(&p).exists() { + fs::create_dir(&p)? + } + + Ok(p.to_string_lossy().to_string()) + } + + /// get scripts path + pub fn scripts(mut self) -> Result { + let root = &self.root()?; + if self.scripts.is_none() { + self.scripts = Some("scripts".into()); + } + + let p = PathBuf::from(root).join(&self.scripts.ok_or(Error::NoneError)?); + if !PathBuf::from(&p).exists() { + std::fs::create_dir(&p)? + } + + Ok(p.to_string_lossy().to_string()) + } +} diff --git a/src/err.rs b/src/err.rs index ffffe77..a2d82b0 100644 --- a/src/err.rs +++ b/src/err.rs @@ -1,6 +1,6 @@ //! Errors in leetcode-cli use crate::cmds::{Command, DataCommand}; -use crate::config::{root, DEFAULT_CONFIG}; +use crate::config::{Config, DEFAULT_CONFIG}; use colored::Colorize; use std::fmt; @@ -100,7 +100,7 @@ impl std::convert::From for Error { // toml impl std::convert::From for Error { fn from(_err: toml::de::Error) -> Self { - let conf = root().unwrap().join("leetcode_tmp.toml"); + let conf = Config::root().unwrap().join("leetcode_tmp.toml"); std::fs::write(&conf, &DEFAULT_CONFIG[1..]).unwrap(); #[cfg(debug_assertions)] let err_msg = format!( diff --git a/src/helper.rs b/src/helper.rs index 7a1b40b..1d64ed7 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -190,7 +190,7 @@ mod file { /// Generate test cases path by fid pub fn test_cases_path(problem: &Problem) -> Result { - let conf = crate::config::locate()?; + let conf = crate::config::Config::locate()?; let mut path = format!("{}/{}.tests.dat", conf.storage.code()?, conf.code.pick); path = path.replace("${fid}", &problem.fid.to_string()); @@ -200,7 +200,7 @@ mod file { /// Generate code path by fid pub fn code_path(problem: &Problem, l: Option) -> Result { - let conf = crate::config::locate()?; + let conf = crate::config::Config::locate()?; let mut lang = conf.code.lang; if l.is_some() { lang = l.ok_or(Error::NoneError)?; @@ -223,7 +223,7 @@ mod file { pub fn load_script(module: &str) -> Result { use std::fs::File; use std::io::Read; - let conf = crate::config::locate()?; + let conf = crate::config::Config::locate()?; let mut script = "".to_string(); File::open(format!("{}/{}.py", conf.storage.scripts()?, module))? .read_to_string(&mut script)?; diff --git a/src/plugins/chrome.rs b/src/plugins/chrome.rs index 1105f48..7e7f368 100644 --- a/src/plugins/chrome.rs +++ b/src/plugins/chrome.rs @@ -41,7 +41,7 @@ impl std::string::ToString for Ident { /// Get cookies from chrome storage pub fn cookies() -> Result { - let ccfg = crate::config::locate()?.cookies; + let ccfg = crate::config::Config::locate()?.cookies; if !ccfg.csrf.is_empty() && !ccfg.session.is_empty() { return Ok(Ident { csrf: ccfg.csrf, diff --git a/src/plugins/leetcode.rs b/src/plugins/leetcode.rs index 1e7b17f..51c3180 100644 --- a/src/plugins/leetcode.rs +++ b/src/plugins/leetcode.rs @@ -36,7 +36,7 @@ impl LeetCode { /// New LeetCode client pub fn new() -> Result { - let conf = config::locate()?; + let conf = config::Config::locate()?; let cookies = chrome::cookies()?; let default_headers = LeetCode::headers( HeaderMap::new(), From ea9af32de55710a93cb3d3a65e6ba2b4416546cb Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sun, 14 Aug 2022 21:30:18 +0800 Subject: [PATCH 04/12] feat(config): add default values for config --- src/config/code.rs | 18 ++++++++++++++++++ src/config/cookies.rs | 2 +- src/config/mod.rs | 28 ++-------------------------- src/config/storage.rs | 11 +++++++++++ src/err.rs | 3 --- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/config/code.rs b/src/config/code.rs index 1f8f49e..d7c583d 100644 --- a/src/config/code.rs +++ b/src/config/code.rs @@ -17,3 +17,21 @@ pub struct Code { pub pick: String, pub submission: String, } + +impl Default for Code { + fn default() -> Self { + Self { + editor: "vim".into(), + editor_args: None, + edit_code_marker: false, + start_marker: "".into(), + end_marker: "".into(), + comment_problem_desc: false, + comment_leading: "".into(), + test: true, + lang: "rust".into(), + pick: "${fid}.${slug}".into(), + submission: "${fid}.${slug}.${sid}.${ac}".into(), + } + } +} diff --git a/src/config/cookies.rs b/src/config/cookies.rs index 0c75a90..34dbda8 100644 --- a/src/config/cookies.rs +++ b/src/config/cookies.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; /// Cookies settings -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Default, Clone, Debug, Deserialize, Serialize)] pub struct Cookies { pub csrf: String, pub session: String, diff --git a/src/config/mod.rs b/src/config/mod.rs index 4eb60f9..808d6be 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -17,32 +17,8 @@ mod cookies; mod storage; mod sys; -pub const DEFAULT_CONFIG: &str = r#" -[code] -editor = "vim" -lang = "rust" -edit_code_marker = false -comment_problem_desc = false -comment_leading = "///" -start_marker = "@lc code=start" -end_marker = "@lc code=start" -test = true -pick = "${fid}.${slug}" -submission = "${fid}.${slug}.${sid}.${ac}" - -[cookies] -csrf = "" -session = "" - -[storage] -root = "~/.leetcode" -scripts = "scripts" -code = "code" -cache = "Problems" -"#; - /// Sync with `~/.leetcode/config.toml` -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Config { #[serde(skip)] pub sys: Sys, @@ -56,7 +32,7 @@ impl Config { pub fn locate() -> Result { let conf = Self::root()?.join("leetcode.toml"); if !conf.is_file() { - fs::write(&conf, &DEFAULT_CONFIG.trim())?; + fs::write(&conf, toml::ser::to_string_pretty(&Self::default())?)?; } let s = fs::read_to_string(&conf)?; diff --git a/src/config/storage.rs b/src/config/storage.rs index 6a0284e..301aca7 100644 --- a/src/config/storage.rs +++ b/src/config/storage.rs @@ -14,6 +14,17 @@ pub struct Storage { scripts: Option, } +impl Default for Storage { + fn default() -> Self { + Self { + cache: "Problems".into(), + code: "code".into(), + scripts: Some("scripts".into()), + root: "~/.leetcode".into(), + } + } +} + impl Storage { /// convert root path pub fn root(&self) -> Result { diff --git a/src/err.rs b/src/err.rs index a2d82b0..f5bfff6 100644 --- a/src/err.rs +++ b/src/err.rs @@ -1,6 +1,5 @@ //! Errors in leetcode-cli use crate::cmds::{Command, DataCommand}; -use crate::config::{Config, DEFAULT_CONFIG}; use colored::Colorize; use std::fmt; @@ -100,8 +99,6 @@ impl std::convert::From for Error { // toml impl std::convert::From for Error { fn from(_err: toml::de::Error) -> Self { - let conf = Config::root().unwrap().join("leetcode_tmp.toml"); - std::fs::write(&conf, &DEFAULT_CONFIG[1..]).unwrap(); #[cfg(debug_assertions)] let err_msg = format!( "{}, {}{}{}{}{}{}", From 7d055a5df1f7940f9685f9d99c68720b3f40c57e Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sat, 24 Jun 2023 06:51:08 +0800 Subject: [PATCH 05/12] feat(config): make config more simple --- src/cfg.rs | 241 --------------------------------------------- src/config/code.rs | 9 +- src/config/mod.rs | 24 ++++- src/config/sys.rs | 6 +- src/err.rs | 2 +- 5 files changed, 32 insertions(+), 250 deletions(-) delete mode 100644 src/cfg.rs diff --git a/src/cfg.rs b/src/cfg.rs deleted file mode 100644 index de152a5..0000000 --- a/src/cfg.rs +++ /dev/null @@ -1,241 +0,0 @@ -//! Soft-link with `config.toml` -//! -//! leetcode-cli will generate a `leetcode.toml` by default, -//! if you wanna change to it, you can: -//! -//! + Edit leetcode.toml at `~/.leetcode/leetcode.toml` directly -//! + Use `leetcode config` to update it -use crate::Error; -use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs, path::PathBuf}; - -pub const DEFAULT_CONFIG: &str = r##" -# usually you don't wanna change those -[sys] -categories = [ - "algorithms", - "concurrency", - "database", - "shell" -] - -langs = [ - "bash", - "c", - "cpp", - "csharp", - "elixir", - "golang", - "java", - "javascript", - "kotlin", - "mysql", - "php", - "python", - "python3", - "ruby", - "rust", - "scala", - "swift", - "typescript" -] - -[sys.urls] -base = "https://leetcode.com" -graphql = "https://leetcode.com/graphql" -login = "https://leetcode.com/accounts/login/" -problems = "https://leetcode.com/api/problems/$category/" -problem = "https://leetcode.com/problems/$slug/description/" -tag = "https://leetcode.com/tag/$slug/" -test = "https://leetcode.com/problems/$slug/interpret_solution/" -session = "https://leetcode.com/session/" -submit = "https://leetcode.com/problems/$slug/submit/" -submissions = "https://leetcode.com/api/submissions/$slug" -submission = "https://leetcode.com/submissions/detail/$id/" -verify = "https://leetcode.com/submissions/detail/$id/check/" -favorites = "https://leetcode.com/list/api/questions" -favorite_delete = "https://leetcode.com/list/api/questions/$hash/$id" - -[code] -editor = "vim" -lang = "rust" -edit_code_marker = false -comment_problem_desc = false -comment_leading = "///" -start_marker = "@lc code=start" -end_marker = "@lc code=start" -test = true -pick = "${fid}.${slug}" -submission = "${fid}.${slug}.${sid}.${ac}" - -[cookies] -csrf = "" -session = "" - -[storage] -root = "~/.leetcode" -scripts = "scripts" -code = "code" -# absolutely path for the cache, other use root as parent dir -cache = "~/.cache/leetcode" -"##; - -/// Sync with `~/.leetcode/config.toml` -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Config { - pub sys: Sys, - pub code: Code, - pub cookies: Cookies, - pub storage: Storage, -} - -impl Config { - /// Sync new config to config.toml - pub fn sync(&self) -> Result<(), Error> { - let home = dirs::home_dir().ok_or(Error::NoneError)?; - let conf = home.join(".leetcode/leetcode.toml"); - fs::write(conf, toml::ser::to_string_pretty(&self)?)?; - - Ok(()) - } -} - -/// Cookie settings -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Cookies { - pub csrf: String, - pub session: String, -} - -/// System settings, for leetcode api mainly -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Sys { - pub categories: Vec, - pub langs: Vec, - pub urls: HashMap, -} - -/// Leetcode API -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Urls { - pub base: String, - pub graphql: String, - pub login: String, - pub problems: String, - pub problem: String, - pub test: String, - pub session: String, - pub submit: String, - pub submissions: String, - pub submission: String, - pub verify: String, - pub favorites: String, - pub favorite_delete: String, -} - -/// default editor and langs -/// -/// + support editor: [emacs, vim] -/// + support langs: all in config -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Code { - pub editor: String, - #[serde(rename(serialize = "editor_args", deserialize = "editor_args"))] - pub editor_args: Option>, - pub edit_code_marker: bool, - pub start_marker: String, - pub end_marker: String, - pub comment_problem_desc: bool, - pub comment_leading: String, - pub test: bool, - pub lang: String, - pub pick: String, - pub submission: String, -} - -/// Locate code files -/// -/// + cache -> the path to cache -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Storage { - cache: String, - code: String, - root: String, - scripts: Option, -} - -impl Storage { - /// convert root path - pub fn root(&self) -> Result { - let home = dirs::home_dir() - .ok_or(Error::NoneError)? - .to_string_lossy() - .to_string(); - let path = self.root.replace('~', &home); - Ok(path) - } - - /// get cache path - pub fn cache(&self) -> Result { - let home = dirs::home_dir() - .ok_or(Error::NoneError)? - .to_string_lossy() - .to_string(); - let path = PathBuf::from(self.cache.replace('~', &home)); - if !path.is_dir() { - info!("Generate cache dir at {:?}.", &path); - fs::DirBuilder::new().recursive(true).create(&path)?; - } - - Ok(path.join("Problems").to_string_lossy().to_string()) - } - - /// get code path - pub fn code(&self) -> Result { - let root = &self.root()?; - let p = PathBuf::from(root).join(&self.code); - if !PathBuf::from(&p).exists() { - fs::create_dir(&p)? - } - - Ok(p.to_string_lossy().to_string()) - } - - /// get scripts path - pub fn scripts(mut self) -> Result { - let root = &self.root()?; - if self.scripts.is_none() { - let tmp = toml::from_str::(DEFAULT_CONFIG)?; - self.scripts = Some(tmp.storage.scripts.ok_or(Error::NoneError)?); - } - - let p = PathBuf::from(root).join(self.scripts.ok_or(Error::NoneError)?); - if !PathBuf::from(&p).exists() { - std::fs::create_dir(&p)? - } - - Ok(p.to_string_lossy().to_string()) - } -} - -/// Locate lc's config file -pub fn locate() -> Result { - let conf = root()?.join("leetcode.toml"); - if !conf.is_file() { - fs::write(&conf, &DEFAULT_CONFIG[1..])?; - } - - let s = fs::read_to_string(&conf)?; - Ok(toml::from_str::(&s)?) -} - -/// Get root path of leetcode-cli -pub fn root() -> Result { - let dir = dirs::home_dir().ok_or(Error::NoneError)?.join(".leetcode"); - if !dir.is_dir() { - info!("Generate root dir at {:?}.", &dir); - fs::DirBuilder::new().recursive(true).create(&dir)?; - } - - Ok(dir) -} diff --git a/src/config/code.rs b/src/config/code.rs index d7c583d..9c7a7a0 100644 --- a/src/config/code.rs +++ b/src/config/code.rs @@ -4,14 +4,21 @@ use serde::{Deserialize, Serialize}; /// Code config #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Code { + #[serde(default)] pub editor: String, - #[serde(rename(serialize = "editor-args", deserialize = "editor-args"))] + #[serde(rename(serialize = "editor-args"), alias = "editor-args", default)] pub editor_args: Option>, + #[serde(default, skip_serializing)] pub edit_code_marker: bool, + #[serde(default, skip_serializing)] pub start_marker: String, + #[serde(default, skip_serializing)] pub end_marker: String, + #[serde(default, skip_serializing)] pub comment_problem_desc: bool, + #[serde(default, skip_serializing)] pub comment_leading: String, + #[serde(default, skip_serializing)] pub test: bool, pub lang: String, pub pick: String, diff --git a/src/config/mod.rs b/src/config/mod.rs index 808d6be..d2e7d1d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -10,17 +10,17 @@ use crate::{ Error, }; use serde::{Deserialize, Serialize}; -use std::fs; +use std::{fs, path::Path}; mod code; mod cookies; mod storage; mod sys; -/// Sync with `~/.leetcode/config.toml` +/// Sync with `~/.leetcode/leetcode.toml` #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Config { - #[serde(skip)] + #[serde(default, skip_serializing)] pub sys: Sys, pub code: Code, pub cookies: Cookies, @@ -28,15 +28,29 @@ pub struct Config { } impl Config { + fn write_default(p: impl AsRef) -> Result<(), crate::Error> { + fs::write(p.as_ref(), toml::ser::to_string_pretty(&Self::default())?)?; + + Ok(()) + } + /// Locate lc's config file pub fn locate() -> Result { let conf = Self::root()?.join("leetcode.toml"); + if !conf.is_file() { - fs::write(&conf, toml::ser::to_string_pretty(&Self::default())?)?; + Self::write_default(&conf)?; } let s = fs::read_to_string(&conf)?; - Ok(toml::from_str::(&s)?) + match toml::from_str::(&s) { + Ok(config) => Ok(config), + Err(e) => { + let tmp = Self::root()?.join("leetcode.tmp.toml"); + Self::write_default(tmp)?; + Err(e.into()) + } + } } /// Get root path of leetcode-cli diff --git a/src/config/sys.rs b/src/config/sys.rs index 4386bd9..82cfa4c 100644 --- a/src/config/sys.rs +++ b/src/config/sys.rs @@ -2,10 +2,12 @@ //! //! This section is a set of constants after #88 +use serde::{Deserialize, Serialize}; + const CATEGORIES: [&str; 4] = ["algorithms", "concurrency", "database", "shell"]; /// Leetcode API -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Urls { pub base: String, pub graphql: String, @@ -77,7 +79,7 @@ impl Urls { } /// System settings, for leetcode api mainly -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Sys { pub categories: Vec, pub urls: Urls, diff --git a/src/err.rs b/src/err.rs index 9edfe93..3e96a37 100644 --- a/src/err.rs +++ b/src/err.rs @@ -108,7 +108,7 @@ impl std::convert::From for Error { _err, "Parse config file failed, ", "leetcode-cli has just generated a new leetcode.toml at ", - "~/.leetcode/leetcode_tmp.toml,".green().bold().underline(), + "~/.leetcode/leetcode.tmp.toml,".green().bold().underline(), " the current one at ", "~/.leetcode/leetcode.toml".yellow().bold().underline(), " seems missing some keys, Please compare the new file and add the missing keys.\n", From c61e620125eb4b6242f5a885ee5880c36479a3c6 Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sat, 24 Jun 2023 07:04:47 +0800 Subject: [PATCH 06/12] docs(README): add telegram link --- Cargo.toml | 4 ++-- README.md | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce78e55..b9010c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ path = "src/bin/lc.rs" [package] name = "leetcode-cli" -version = "0.3.13" -authors = ["clearloop "] +version = "0.4.0" +authors = ["clearloop "] edition = "2021" description = "Leet your code in command-line." repository = "https://github.com/clearloop/leetcode-cli" diff --git a/README.md b/README.md index 25d46a3..c5f8cab 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ # leetcode-cli -![Rust](https://github.com/clearloop/leetcode-cli/workflows/Rust/badge.svg) + +![Rust](https://github.com/clearloop/leetcode-cli/workflows/leetcode-cli/badge.svg) [![crate](https://img.shields.io/crates/v/leetcode-cli.svg)](https://crates.io/crates/leetcode-cli) [![doc](https://img.shields.io/badge/current-docs-brightgreen.svg)](https://docs.rs/leetcode-cli/) [![downloads](https://img.shields.io/crates/d/leetcode-cli.svg)](https://crates.io/crates/leetcode-cli) -[![gitter](https://img.shields.io/gitter/room/odditypark/leetcode-cli)](https://gitter.im/Odditypark/leetcode-cli) +[![telegram](https://img.shields.io/badge/telegram-blue?logo=telegram)](https://t.me/+U_5si6PhWykxZTI1) [![LICENSE](https://img.shields.io/crates/l/leetcode-cli.svg)](https://choosealicense.com/licenses/mit/) ## Installing ```sh # Required dependencies: -# +# # gcc # libssl-dev # libdbus-1-dev @@ -48,14 +49,14 @@ SUBCOMMANDS: ## Example -For example, given this config (can be found in `~/.leetcode/leetcode.toml`, it can be generated automatically with command: `leetcode list` if you are a new user): +For example, given this config (could be found at `~/.leetcode/leetcode.toml`): ```toml [code] lang = "rust" editor = "emacs" # Optional parameter -editor_args = ['-nw'] +editor-args = ['-nw'] ``` #### 1. pick @@ -144,7 +145,8 @@ leetcode exec 1 ## Cookies -The cookie plugin of leetcode-cli can work on OSX and [Linux][#1]. **If you are on a different platform, there are problems with caching the cookies**, you can manually input your LeetCode Cookies to the configuration file. +The cookie plugin of leetcode-cli can work on OSX and [Linux][#1]. **If you are on a different platform, there are problems with caching the cookies**, +you can manually input your LeetCode Cookies to the configuration file. ```toml [cookies] @@ -154,7 +156,6 @@ session = "..." For Example, using Chrome (after logging in to LeetCode): - #### Step 1 Open Chrome and navigate to the link below: @@ -166,6 +167,7 @@ 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: + ```toml [cookies] csrf = "${csrftoken}" @@ -189,13 +191,13 @@ import json; def plan(sps, stags): ## - # `print` in python is supported, - # if you want to know the data structures of these two args, + # `print` in python is supported, + # if you want to know the data structures of these two args, # just print them ## problems = json.loads(sps) tags = json.loads(stags) - + ret = [] tm = {} for tag in tags: @@ -215,17 +217,15 @@ Then run `list` with the filter that you just wrote: leetcode list -p plan1 ``` -And that's it! Enjoy! - +That's it! Enjoy! -## PR +## Contributions -[PRs][pr] are more than welcome! +Feel free to add your names and emails in the `authors` field of `Cargo.toml` ! ## LICENSE MIT - [pr]: https://github.com/clearloop/leetcode-cli/pulls [#1]: https://github.com/clearloop/leetcode-cli/issues/1 From 87110b2cf070be08db8cff0f9ac9d8c89a17edfb Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sat, 24 Jun 2023 07:06:52 +0800 Subject: [PATCH 07/12] chore(lock): udpate cargo.lock --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04b9ebb..ebff3cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,18 +232,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.3.6" +version = "4.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6320c6d1c98b6981da7bb2dcecbd0be9dc98d42165fa8326b21000f7dbfde6d0" +checksum = "d9394150f5b4273a1763355bd1c2ec54cc5a2593f790587bcd6b2c947cfa9211" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.3.5" +version = "4.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e53afce1efce6ed1f633cf0e57612fe51db54a1ee4fd8f8503d078fe02d69ae" +checksum = "9a78fbdd3cc2914ddf37ba444114bc7765bbdcb55ec9cbe6fa054f0137400717" dependencies = [ "anstream", "anstyle", @@ -1025,7 +1025,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "leetcode-cli" -version = "0.3.13" +version = "0.4.0" dependencies = [ "async-trait", "cargo-husky", From bd8fd18b5ca9cdc1a4ccc3adcf8c9b0d7467bba2 Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sat, 24 Jun 2023 07:34:49 +0800 Subject: [PATCH 08/12] fix(config): path of cache --- src/config/storage.rs | 14 +++++--------- src/config/sys.rs | 9 ++++++++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/config/storage.rs b/src/config/storage.rs index 301aca7..72a01fc 100644 --- a/src/config/storage.rs +++ b/src/config/storage.rs @@ -38,17 +38,13 @@ impl Storage { /// get cache path pub fn cache(&self) -> Result { - let home = dirs::home_dir() - .ok_or(Error::NoneError)? - .to_string_lossy() - .to_string(); - let path = PathBuf::from(self.cache.replace('~', &home)); - if !path.is_dir() { - info!("Generate cache dir at {:?}.", &path); - fs::DirBuilder::new().recursive(true).create(&path)?; + let root = PathBuf::from(self.root()?); + if !root.exists() { + info!("Generate cache dir at {:?}.", &root); + fs::DirBuilder::new().recursive(true).create(&root)?; } - Ok(path.join("Problems").to_string_lossy().to_string()) + Ok(root.join("Problems").to_string_lossy().to_string()) } /// get code path diff --git a/src/config/sys.rs b/src/config/sys.rs index 82cfa4c..7c6f77e 100644 --- a/src/config/sys.rs +++ b/src/config/sys.rs @@ -6,6 +6,11 @@ use serde::{Deserialize, Serialize}; const CATEGORIES: [&str; 4] = ["algorithms", "concurrency", "database", "shell"]; +// TODO: find a better solution. +fn categories() -> Vec { + CATEGORIES.into_iter().map(|s| s.into()).collect() +} + /// Leetcode API #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Urls { @@ -54,7 +59,7 @@ impl Urls { /// problems url with specific `$category` pub fn problems(&self, category: &str) -> String { - self.problem.replace("$category", category) + self.problems.replace("$category", category) } /// submit url with specific `$slug` @@ -81,7 +86,9 @@ impl Urls { /// System settings, for leetcode api mainly #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Sys { + #[serde(default = "categories")] pub categories: Vec, + #[serde(default)] pub urls: Urls, } From fae42dea0f6c074fec0361a1e036be06c32d8c89 Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sat, 24 Jun 2023 10:48:35 +0800 Subject: [PATCH 09/12] chore(plugin): disable chrome plugin --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/cache/mod.rs | 12 +++++++++--- src/config/cookies.rs | 6 ++++++ src/err.rs | 11 +++++++++-- src/plugins/leetcode.rs | 9 ++------- src/plugins/mod.rs | 4 +++- 7 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ebff3cc..c2dc11d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,6 +78,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + [[package]] name = "async-compression" version = "0.4.0" @@ -1027,6 +1033,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" name = "leetcode-cli" version = "0.4.0" dependencies = [ + "anyhow", "async-trait", "cargo-husky", "clap", diff --git a/Cargo.toml b/Cargo.toml index b9010c7..e5ac98f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ serde_json = "1.0.82" toml = "0.5.9" regex = "1.6.0" scraper = "0.13.0" +anyhow = "1.0.71" [dependencies.diesel] version = "2.0.3" diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 9b7bca9..b15f677 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -8,6 +8,7 @@ use self::schemas::{problems::dsl::*, tags::dsl::*}; use self::sql::*; use crate::helper::test_cases_path; use crate::{config::Config, err::Error, plugins::LeetCode}; +use anyhow::anyhow; use colored::Colorize; use diesel::prelude::*; use reqwest::Response; @@ -342,15 +343,20 @@ 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 run_res: RunCode = self + let text = self .0 .clone() .run_code(json.clone(), url.clone(), refer.clone()) .await? - .json() // does not require LEETCODE_SESSION (very oddly) + .text() .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.") + })?; + trace!("Run code result {:#?}", run_res); // Check if leetcode accepted the Run request diff --git a/src/config/cookies.rs b/src/config/cookies.rs index 34dbda8..d03cd4a 100644 --- a/src/config/cookies.rs +++ b/src/config/cookies.rs @@ -7,3 +7,9 @@ pub struct Cookies { pub csrf: String, pub session: String, } + +impl std::string::ToString for Cookies { + fn to_string(&self) -> String { + format!("LEETCODE_SESSION={};csrftoken={};", self.session, self.csrf) + } +} diff --git a/src/err.rs b/src/err.rs index 3e96a37..6421cfd 100644 --- a/src/err.rs +++ b/src/err.rs @@ -5,7 +5,6 @@ use std::fmt; // fixme: use this_error /// Error enum -#[derive(Clone)] pub enum Error { MatchError, DownloadError(String), @@ -20,6 +19,7 @@ pub enum Error { SilentError, NoneError, ChromeNotLogin, + Anyhow(anyhow::Error), } impl std::fmt::Debug for Error { @@ -59,7 +59,8 @@ impl std::fmt::Debug for Error { "json from response parse failed, please open a new issue at: {}.", "https://github.com/clearloop/leetcode-cli/".underline(), ), - Error::ChromeNotLogin => write!(f, "maybe you not login on the Chrome, you can login and retry.") + Error::ChromeNotLogin => write!(f, "maybe you not login on the Chrome, you can login and retry."), + Error::Anyhow(e) => write!(f, "{} {}", e, e), } } } @@ -147,6 +148,12 @@ impl std::convert::From for Error { } } +impl From for Error { + fn from(err: anyhow::Error) -> Self { + Error::Anyhow(err) + } +} + // pyo3 #[cfg(feature = "pym")] impl std::convert::From for Error { diff --git a/src/plugins/leetcode.rs b/src/plugins/leetcode.rs index 51c3180..6463ff1 100644 --- a/src/plugins/leetcode.rs +++ b/src/plugins/leetcode.rs @@ -2,7 +2,7 @@ use self::req::{Json, Mode, Req}; use crate::{ config::{self, Config}, err::Error, - plugins::chrome, + // plugins::chrome, }; use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue}, @@ -37,7 +37,7 @@ impl LeetCode { /// New LeetCode client pub fn new() -> Result { let conf = config::Config::locate()?; - let cookies = chrome::cookies()?; + let cookies = conf.cookies.clone(); let default_headers = LeetCode::headers( HeaderMap::new(), vec![ @@ -53,11 +53,6 @@ impl LeetCode { .connect_timeout(Duration::from_secs(30)) .build()?; - // Sync conf - if conf.cookies.csrf != cookies.csrf { - conf.sync()?; - } - Ok(LeetCode { conf, client, diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 5a84870..9b2d903 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -6,6 +6,8 @@ //! ## login to `leetcode.com` //! Leetcode-cli use chrome cookie directly, do not need to login, please make sure you have loggined in `leetcode.com` before usnig `leetcode-cli` //! -mod chrome; + +// FIXME: Read cookies from local storage. (issue #122) +// mod chrome; mod leetcode; pub use leetcode::LeetCode; From 7418a070808986ed4be34f4bcd404f66006fdf32 Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sat, 24 Jun 2023 10:52:16 +0800 Subject: [PATCH 10/12] docs(README): update config example --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c5f8cab..2dfb39b 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,22 @@ For example, given this config (could be found at `~/.leetcode/leetcode.toml`): ```toml [code] -lang = "rust" -editor = "emacs" +editor = emacs # Optional parameter editor-args = ['-nw'] +lang = 'rust' +pick = '${fid}.${slug}' +submission = '${fid}.${slug}.${sid}.${ac}' + +[cookies] +csrf = '' +session = '' + +[storage] +cache = 'Problems' +code = 'code' +root = '~/.leetcode' +scripts = 'scripts' ``` #### 1. pick From 05aeeaa773a755da7cd8b003d586bb932af1aba6 Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sat, 24 Jun 2023 10:58:17 +0800 Subject: [PATCH 11/12] feat(config): hide submission and pick --- README.md | 2 -- src/config/code.rs | 10 ++++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2dfb39b..f254d92 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,6 @@ editor = emacs # Optional parameter editor-args = ['-nw'] lang = 'rust' -pick = '${fid}.${slug}' -submission = '${fid}.${slug}.${sid}.${ac}' [cookies] csrf = '' diff --git a/src/config/code.rs b/src/config/code.rs index 9c7a7a0..0384608 100644 --- a/src/config/code.rs +++ b/src/config/code.rs @@ -1,6 +1,14 @@ //! Code in config use serde::{Deserialize, Serialize}; +fn default_pick() -> String { + "${fid}.${slug}".into() +} + +fn default_submission() -> String { + "${fid}.${slug}.${sid}.${ac}".into() +} + /// Code config #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Code { @@ -21,7 +29,9 @@ pub struct Code { #[serde(default, skip_serializing)] pub test: bool, pub lang: String, + #[serde(default = "default_pick", skip_serializing)] pub pick: String, + #[serde(default = "default_submission", skip_serializing)] pub submission: String, } From 1535c1acc54d14d88723250617ef9b9314d52d6a Mon Sep 17 00:00:00 2001 From: clearloop <26088946+clearloop@users.noreply.github.com> Date: Sat, 24 Jun 2023 10:58:53 +0800 Subject: [PATCH 12/12] chore(husky): remove cargo-husky --- .cargo-husky/hooks/pre-commit | 11 --------- Cargo.lock | 43 +++++++++++++++++++++++------------ Cargo.toml | 5 ---- 3 files changed, 29 insertions(+), 30 deletions(-) delete mode 100755 .cargo-husky/hooks/pre-commit diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit deleted file mode 100755 index f735ef4..0000000 --- a/.cargo-husky/hooks/pre-commit +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-commit". - -cargo build -cargo test diff --git a/Cargo.lock b/Cargo.lock index c2dc11d..e75ea22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,12 +209,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "cargo-husky" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b02b629252fe8ef6460461409564e2c21d0c8e77e0944f3d189ff06c4e932ad" - [[package]] name = "cc" version = "1.0.79" @@ -526,6 +520,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" + [[package]] name = "errno" version = "0.3.1" @@ -778,7 +778,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -791,6 +791,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -943,7 +949,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", ] [[package]] @@ -1035,7 +1051,6 @@ version = "0.4.0" dependencies = [ "anyhow", "async-trait", - "cargo-husky", "clap", "colored", "diesel", @@ -2306,17 +2321,17 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.10" +version = "0.19.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" +checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" dependencies = [ - "indexmap", + "indexmap 2.0.0", "toml_datetime", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index e5ac98f..fcaafac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,11 +42,6 @@ features = ["sqlite"] version = "0.11.11" features = ["gzip", "json"] -[dev-dependencies.cargo-husky] -version = "1.5.0" -default-features = false -features = ["precommit-hook", "user-hooks"] - [features] pym = ["pyo3"]