diff --git a/Cargo.toml b/Cargo.toml index 1bce70f..a73df5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ clap = { version = "3.2.8", features = ["cargo"] } colored = "2.0.0" dirs = "4.0.0" env_logger = "0.9.0" -escaper = "0.1.1" keyring = "1.1.2" log = "0.4.17" openssl = "0.10.40" @@ -32,6 +31,7 @@ serde = { version = "1.0.138", features = ["derive"] } serde_json = "1.0.82" toml = "0.5.9" regex = "1.5.6" +scraper = "0.13.0" [dependencies.diesel] version = "1.4.8" diff --git a/src/cache/mod.rs b/src/cache/mod.rs index a3cc7fa..2eec037 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -6,6 +6,8 @@ mod sql; use self::models::*; use self::schemas::{problems::dsl::*, tags::dsl::*}; use self::sql::*; +use crate::cmds::{CODE_END, CODE_START}; +use crate::helper::test_cases_path; use crate::{cfg, err::Error, plugins::LeetCode}; use colored::Colorize; use diesel::prelude::*; @@ -26,7 +28,7 @@ pub enum Run { Submit, } -impl std::default::Default for Run { +impl Default for Run { fn default() -> Self { Run::Submit } @@ -37,7 +39,7 @@ impl std::default::Default for Run { pub struct Cache(pub LeetCode); impl Cache { - /// Ref to sqliteconnection + /// Ref to sqlite connection fn conn(&self) -> Result { Ok(conn(self.0.conf.storage.cache()?)) } @@ -236,10 +238,10 @@ impl Cache { &self, run: Run, rfid: i32, - testcase: Option, + test_case: Option, ) -> Result<(HashMap<&'static str, String>, [String; 2]), Error> { trace!("pre run code..."); - use crate::helper::{code_path, test_cases_path}; + use crate::helper::code_path; use std::fs::File; use std::io::Read; @@ -256,30 +258,48 @@ impl Cache { let mut code: String = "".to_string(); let maybe_file_testcases: Option = test_cases_path(&p) - .map(|filename| { + .map(|file_name| { let mut tests = "".to_string(); - File::open(filename) + File::open(file_name) .and_then(|mut file_descriptor| file_descriptor.read_to_string(&mut tests)) .map(|_| Some(tests)) .unwrap_or(None) }) .unwrap_or(None); + let maybe_all_testcases: Option = if d.all_cases.is_empty() { + None + } else { + Some(d.all_cases.to_string()) + }; + // Takes test cases using following priority // 1. cli parameter - // 2. test cases from the file - // 3. sample test case from the task - let testcase = testcase.or(maybe_file_testcases).unwrap_or(d.case); + // 2. if test cases file exist, use the file test cases(user can edit it) + // 3. test cases from problem desc all test cases + // 4. sample test case from the task + let test_case = test_case + .or(maybe_file_testcases) + .or(maybe_all_testcases) + .unwrap_or(d.case); File::open(code_path(&p, None)?)?.read_to_string(&mut code)?; + let begin = code.find(CODE_START).unwrap_or(0); + let end = code.find(CODE_END).unwrap_or(code.len()); + let code = if let Some(solution) = code.get(begin..end) { + solution.to_string() + } else { + code + }; + json.insert("lang", conf.code.lang.to_string()); json.insert("question_id", p.id.to_string()); json.insert("typed_code", code); // pass manually data json.insert("name", p.name.to_string()); - json.insert("data_input", testcase); + json.insert("data_input", test_case); let url = match run { Run::Test => conf @@ -315,7 +335,7 @@ impl Cache { async fn recur_verify(&self, rid: String) -> Result { use std::time::Duration; - trace!("Run veriy recursion..."); + trace!("Run verify recursion..."); std::thread::sleep(Duration::from_micros(3000)); let json: VerifyResult = self @@ -330,10 +350,10 @@ impl Cache { &self, rfid: i32, run: Run, - testcase: Option, + test_case: Option, ) -> Result { trace!("Exec problem filter โ€”โ€” Test or Submit"); - let (json, [url, refer]) = self.pre_run_code(run.clone(), rfid, testcase).await?; + let (json, [url, refer]) = self.pre_run_code(run.clone(), rfid, test_case).await?; trace!("Pre run code result {:?}, {:?}, {:?}", json, url, refer); let run_res: RunCode = self diff --git a/src/cache/models.rs b/src/cache/models.rs index bedc089..b338adf 100644 --- a/src/cache/models.rs +++ b/src/cache/models.rs @@ -30,6 +30,25 @@ pub struct Problem { pub desc: String, } +impl Problem { + fn display_level(&self) -> &str { + match self.level { + 1 => "Easy", + 2 => "Medium", + 3 => "Hard", + _ => "Unknown", + } + } + pub fn desc_comment(&self) -> String { + let mut res = String::new(); + res += format!("// Category: {}\n", self.category).as_str(); + res += format!("// Level: {}\n", self.display_level(),).as_str(); + res += format!("// Percent: {}%\n\n", self.percent).as_str(); + + res + "\n" + } +} + static DONE: &str = " โœ”"; static ETC: &str = "..."; static LOCK: &str = "๐Ÿ”’"; @@ -124,9 +143,20 @@ pub struct Question { pub t_content: String, } -impl std::fmt::Display for Question { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", &self.content.render()) +impl Question { + pub fn desc(&self) -> String { + self.content.render() + } + + pub fn desc_comment(&self) -> String { + let desc = self.content.render(); + + let mut res = desc + .lines() + .fold("/*\n".to_string(), |acc, e| acc + " * " + e + "\n"); + res += " */\n"; + + res } } diff --git a/src/cache/parser.rs b/src/cache/parser.rs index fb9ad2b..dbbc87e 100644 --- a/src/cache/parser.rs +++ b/src/cache/parser.rs @@ -50,10 +50,11 @@ pub fn desc(q: &mut Question, v: Value) -> Option { stats: serde_json::from_str(o.get("stats")?.as_str()?).ok()?, defs: serde_json::from_str(o.get("codeDefinition")?.as_str()?).ok()?, case: o.get("sampleTestCase")?.as_str()?.to_string(), - all_cases: o.get("exampleTestcases") - .unwrap_or(o.get("sampleTestCase")?) // soft fail to the sampleTestCase - .as_str()? - .to_string(), + all_cases: o + .get("exampleTestcases") + .unwrap_or(o.get("sampleTestCase")?) // soft fail to the sampleTestCase + .as_str()? + .to_string(), metadata: serde_json::from_str(o.get("metaData")?.as_str()?).ok()?, test: o.get("enableRunCode")?.as_bool()?, t_content: o @@ -85,29 +86,35 @@ pub fn tags(v: Value) -> Option> { Some(res) } -/// daily parser +/// daily parser pub fn daily(v: Value) -> Option { trace!("Parse daily..."); v.as_object()? - .get("data")?.as_object()? - .get("activeDailyCodingChallengeQuestion")?.as_object()? - .get("question")?.as_object()? - .get("questionFrontendId")?.as_str()? - .parse().ok() + .get("data")? + .as_object()? + .get("activeDailyCodingChallengeQuestion")? + .as_object()? + .get("question")? + .as_object()? + .get("questionFrontendId")? + .as_str()? + .parse() + .ok() } /// user parser -pub fn user(v: Value) -> Option> { +pub fn user(v: Value) -> Option> { // None => error while parsing // Some(None) => User not found // Some("...") => username - let user = v.as_object()?.get("data")? - .as_object()?.get("user")?; - if *user == Value::Null { return Some(None) } + let user = v.as_object()?.get("data")?.as_object()?.get("user")?; + if *user == Value::Null { + return Some(None); + } let user = user.as_object()?; Some(Some(( user.get("username")?.as_str()?.to_owned(), - user.get("isCurrentUserPremium")?.as_bool()? + user.get("isCurrentUserPremium")?.as_bool()?, ))) } diff --git a/src/cfg.rs b/src/cfg.rs index 410088d..ff91601 100644 --- a/src/cfg.rs +++ b/src/cfg.rs @@ -9,7 +9,7 @@ use crate::Error; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, fs, path::PathBuf}; -const DEFAULT_CONFIG: &str = r#" +const DEFAULT_CONFIG: &str = r##" # usually you don't wanna change those [sys] categories = [ @@ -65,11 +65,12 @@ csrf = "" session = "" [storage] -cache = "Problems" -code = "code" root = "~/.leetcode" +cache = "Problems" scripts = "scripts" -"#; +# absolutely path for the code, other use root as parent dir +code = "code" +"##; /// Sync with `~/.leetcode/config.toml` #[derive(Clone, Debug, Deserialize, Serialize)] @@ -169,10 +170,9 @@ impl Storage { /// get code path pub fn code(&self) -> Result { - let root = &self.root()?; - let p = PathBuf::from(root).join(&self.code); + let p = PathBuf::from(&self.code); if !PathBuf::from(&p).exists() { - std::fs::create_dir(&p)? + fs::create_dir(&p)? } Ok(p.to_string_lossy().to_string()) diff --git a/src/cli.rs b/src/cli.rs index f232a2d..3ec98bf 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -7,7 +7,7 @@ use crate::{ err::Error, flag::{Debug, Flag}, }; -use clap::{crate_name, crate_version, App, AppSettings}; +use clap::{crate_name, crate_version}; /// This should be called before calling any cli method or printing any output. pub fn reset_signal_pipe_handler() { @@ -24,8 +24,8 @@ pub fn reset_signal_pipe_handler() { /// Get maches pub async fn main() -> Result<(), Error> { - let _ = reset_signal_pipe_handler(); - let m = App::new(crate_name!()) + reset_signal_pipe_handler(); + let m = clap::Command::new(crate_name!()) .version(crate_version!()) .about("May the Code be with You ๐Ÿ‘ป") .subcommands(vec![ @@ -38,10 +38,10 @@ pub async fn main() -> Result<(), Error> { TestCommand::usage().display_order(7), ]) .arg(Debug::usage()) - .setting(AppSettings::ArgRequiredElseHelp) + .arg_required_else_help(true) .get_matches(); - if m.is_present("debug") { + if m.contains_id("debug") { Debug::handler()?; } else { env_logger::Builder::from_env(env_logger::Env::new().default_filter_or("info")) diff --git a/src/cmds/data.rs b/src/cmds/data.rs index 2e837c9..c590458 100644 --- a/src/cmds/data.rs +++ b/src/cmds/data.rs @@ -2,7 +2,7 @@ use super::Command; use crate::{cache::Cache, helper::Digit, Error}; use async_trait::async_trait; -use clap::{Command as ClapCommand, Arg, ArgMatches}; +use clap::{Arg, ArgMatches, Command as ClapCommand}; use colored::Colorize; /// Abstract `data` command @@ -58,7 +58,8 @@ impl Command for DataCommand { let out = format!( " {}{}", Path::new(&path) - .file_name().ok_or(Error::NoneError)? + .file_name() + .ok_or(Error::NoneError)? .to_string_lossy() .to_string() .digit(65 - (len.len() as i32)) @@ -72,13 +73,13 @@ impl Command for DataCommand { title.push_str(&"-".repeat(65)); let mut flags = 0; - if m.is_present("delete") { + if m.contains_id("delete") { flags += 1; cache.clean()?; println!("{}", "ok!".bright_green()); } - if m.is_present("update") { + if m.contains_id("update") { flags += 1; cache.update().await?; println!("{}", "ok!".bright_green()); diff --git a/src/cmds/edit.rs b/src/cmds/edit.rs index 3252052..1acff43 100644 --- a/src/cmds/edit.rs +++ b/src/cmds/edit.rs @@ -1,8 +1,8 @@ //! Edit command -use crate::Error; use super::Command; +use crate::Error; use async_trait::async_trait; -use clap::{Command as ClapCommand, ArgMatches, Arg}; +use clap::{Arg, ArgMatches, Command as ClapCommand}; /// Abstract `edit` command /// @@ -22,6 +22,9 @@ use clap::{Command as ClapCommand, ArgMatches, Arg}; /// ``` pub struct EditCommand; +pub const CODE_START: &str = r#"// @lc code=start"#; +pub const CODE_END: &str = r#"// @lc code=end"#; + #[async_trait] impl Command for EditCommand { /// `edit` usage @@ -42,6 +45,13 @@ impl Command for EditCommand { .required(true) .help("question id"), ) + .arg( + Arg::with_name("test") + .long("test") + .short('t') + .required(false) + .help("write test file"), + ) } /// `edit` handler @@ -53,21 +63,23 @@ impl Command for EditCommand { let id: i32 = m.value_of("id").ok_or(Error::NoneError)?.parse()?; let cache = Cache::new()?; - let target = cache.get_problem(id)?; + let problem = cache.get_problem(id)?; let mut conf = cache.to_owned().0.conf; + let test_flag = m.contains_id("test"); + + let p_desc_comment = problem.desc_comment(); // condition language - if m.is_present("lang") { + if m.contains_id("lang") { conf.code.lang = m.value_of("lang").ok_or(Error::NoneError)?.to_string(); conf.sync()?; } let lang = conf.code.lang; - let path = crate::helper::code_path(&target, Some(lang.to_owned()))?; - let tests_path = crate::helper::test_cases_path(&target)?; + let path = crate::helper::code_path(&problem, Some(lang.to_owned()))?; if !Path::new(&path).exists() { - let mut qr = serde_json::from_str(&target.desc); + let mut qr = serde_json::from_str(&problem.desc); if qr.is_err() { qr = Ok(cache.get_question(id).await?); } @@ -75,22 +87,33 @@ impl Command for EditCommand { let question: Question = qr?; let mut file_code = File::create(&path)?; - let mut file_tests = File::create(&tests_path)?; + let question_desc = question.desc_comment() + "\n"; + + let test_path = crate::helper::test_cases_path(&problem)?; + let mut file_tests = File::create(&test_path)?; let mut flag = false; for d in question.defs.0 { if d.value == lang { flag = true; - file_code.write_all(d.code.to_string().as_bytes())?; - file_tests.write_all(question.all_cases.as_bytes())?; + file_code.write_all(p_desc_comment.as_bytes())?; + file_code.write_all(question_desc.as_bytes())?; + file_code.write_all((CODE_START.to_string() + "\n").as_bytes())?; + file_code.write_all((d.code.to_string() + "\n").as_bytes())?; + file_code.write_all((CODE_END.to_string() + "\n").as_bytes())?; + + if test_flag { + file_tests.write_all(question.all_cases.as_bytes())?; + } } } // if language is not found in the list of supported languges clean up files if !flag { std::fs::remove_file(&path)?; - std::fs::remove_file(&tests_path)?; - + if test_flag { + std::fs::remove_file(&test_path)?; + } return Err(crate::Error::FeatureError(format!( "This question doesn't support {}, please try another", &lang diff --git a/src/cmds/exec.rs b/src/cmds/exec.rs index e660f58..85b6d1a 100644 --- a/src/cmds/exec.rs +++ b/src/cmds/exec.rs @@ -1,8 +1,8 @@ //! Exec command -use crate::Error; use super::Command; +use crate::Error; use async_trait::async_trait; -use clap::{Command as ClapCommand, ArgMatches, Arg}; +use clap::{Arg, ArgMatches, Command as ClapCommand}; /// Abstract Exec Command /// diff --git a/src/cmds/list.rs b/src/cmds/list.rs index 1adc6c7..0f39329 100644 --- a/src/cmds/list.rs +++ b/src/cmds/list.rs @@ -36,7 +36,7 @@ use super::Command; use crate::{cache::Cache, err::Error, helper::Digit}; use async_trait::async_trait; -use clap::{Command as ClapCommand, Arg, ArgMatches}; +use clap::{Arg, ArgMatches, Command as ClapCommand}; /// Abstract `list` command /// /// ## handler @@ -147,14 +147,14 @@ impl Command for ListCommand { // pym scripts #[cfg(feature = "pym")] { - if m.is_present("plan") { + if m.contains_id("plan") { let ids = crate::pym::exec(m.value_of("plan").unwrap_or(""))?; crate::helper::squash(&mut ps, ids)?; } } // filter tag - if m.is_present("tag") { + if m.contains_id("tag") { let ids = cache .get_tagged_questions(m.value_of("tag").unwrap_or("")) .await?; @@ -162,19 +162,21 @@ impl Command for ListCommand { } // filter category - if m.is_present("category") { + if m.contains_id("category") { ps.retain(|x| x.category == m.value_of("category").unwrap_or("algorithms")); } // filter query - if m.is_present("query") { + if m.contains_id("query") { let query = m.value_of("query").ok_or(Error::NoneError)?; crate::helper::filter(&mut ps, query.to_string()); } // filter range - if m.is_present("range") { - let num_range: Vec = m.values_of("range").ok_or(Error::NoneError)? + if m.contains_id("range") { + let num_range: Vec = m + .values_of("range") + .ok_or(Error::NoneError)? .into_iter() .map(|x| x.parse::().unwrap_or(0)) .collect(); @@ -191,7 +193,7 @@ impl Command for ListCommand { println!("{}", out.join("\n")); // one more thing, filter stat - if m.is_present("stat") { + if m.contains_id("stat") { let mut listed = 0; let mut locked = 0; let mut starred = 0; diff --git a/src/cmds/mod.rs b/src/cmds/mod.rs index cc00a0f..30e5841 100644 --- a/src/cmds/mod.rs +++ b/src/cmds/mod.rs @@ -12,7 +12,7 @@ //! ``` use crate::err::Error; use async_trait::async_trait; -use clap::{Command as ClapCommand, ArgMatches}; +use clap::{ArgMatches, Command as ClapCommand}; /// Abstract commands' trait. #[async_trait] @@ -38,3 +38,5 @@ pub use list::ListCommand; pub use pick::PickCommand; pub use stat::StatCommand; pub use test::TestCommand; + +pub use edit::{CODE_END, CODE_START}; diff --git a/src/cmds/pick.rs b/src/cmds/pick.rs index 1308aa7..cba7e5d 100644 --- a/src/cmds/pick.rs +++ b/src/cmds/pick.rs @@ -95,14 +95,14 @@ impl Command for PickCommand { // pym scripts #[cfg(feature = "pym")] { - if m.is_present("plan") { + if m.contains_id("plan") { let ids = crate::pym::exec(m.value_of("plan").unwrap_or(""))?; crate::helper::squash(&mut problems, ids)?; } } // tag filter - if m.is_present("tag") { + if m.contains_id("tag") { let ids = cache .clone() .get_tagged_questions(m.value_of("tag").unwrap_or("")) @@ -111,12 +111,12 @@ impl Command for PickCommand { } // query filter - if m.is_present("query") { + if m.contains_id("query") { let query = m.value_of("query").ok_or(Error::NoneError)?; crate::helper::filter(&mut problems, query.to_string()); } - let daily_id = if m.is_present("daily") { + let daily_id = if m.contains_id("daily") { Some(cache.get_daily_problem_id().await?) } else { None @@ -133,14 +133,15 @@ impl Command for PickCommand { }); let r = cache.get_question(fid).await; - if r.is_err() { - let e = r.err().ok_or(Error::NoneError)?; - eprintln!("{:?}", &e); - if let Error::FeatureError(_) | Error::NetworkError(_) = e { - Self::handler(m).await?; + + match r { + Ok(q) => println!("{}", q.desc()), + Err(e) => { + eprintln!("{:?}", e); + if let Error::NetworkError(_) = e { + Self::handler(m).await?; + } } - } else { - println!("{}", r?); } Ok(()) diff --git a/src/cmds/stat.rs b/src/cmds/stat.rs index 661ca98..147392b 100644 --- a/src/cmds/stat.rs +++ b/src/cmds/stat.rs @@ -1,7 +1,7 @@ //! status command use super::Command; use async_trait::async_trait; -use clap::{Command as ClapCommand, ArgMatches}; +use clap::{ArgMatches, Command as ClapCommand}; use colored::Colorize; /// Abstract statues command diff --git a/src/helper.rs b/src/helper.rs index b68fe68..c4a70a3 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -92,167 +92,27 @@ mod filter { /// Render html to command-line mod html { - // use crate::Error; - use colored::{Color, Colorize}; - use escaper::decode_html; - use regex::Regex; - pub enum Token { - Plain(String), - Bold(String), - Sup(String), - Sub(String), - Font((String, Color)), - Eof(String), - } - + use scraper::Html; /// Html render plugin pub trait HTML { - fn ser(&self) -> Vec; fn render(&self) -> String; } - pub fn superscript(n: u8) -> String { - match n { - x if x >= 10 => format!("{}{}", superscript(n / 10), superscript(n % 10)), - 0 => "โฐ".to_string(), - 1 => "ยน".to_string(), - 2 => "ยฒ".to_string(), - 3 => "ยณ".to_string(), - 4 => "โด".to_string(), - 5 => "โต".to_string(), - 6 => "โถ".to_string(), - 7 => "โท".to_string(), - 8 => "โธ".to_string(), - 9 => "โน".to_string(), - _ => n.to_string(), - } - } - - pub fn subscript(n: u8) -> String { - match n { - x if x >= 10 => format!("{}{}", subscript(n / 10), subscript(n % 10)), - 0 => "โ‚€".to_string(), - 1 => "โ‚".to_string(), - 2 => "โ‚‚".to_string(), - 3 => "โ‚ƒ".to_string(), - 4 => "โ‚„".to_string(), - 5 => "โ‚…".to_string(), - 6 => "โ‚†".to_string(), - 7 => "โ‚‡".to_string(), - 8 => "โ‚ˆ".to_string(), - 9 => "โ‚‰".to_string(), - _ => n.to_string(), - } - } impl HTML for String { - fn ser(&self) -> Vec { - // empty tags - let tks = { - let mut s = self.clone(); - // some problems (e.g. 1653) have ZWSPs. - s.retain(|x| x != '\u{200B}'); - s }; - let res: Vec; - // styled - { - let mut ptr = 0; - let mut output = vec![]; - let mut bold = false; - let mut sup = false; - let mut sub = false; - let mut color: Option = None; - - // TODO: check how to make this `unwrap` more flexible.. - // - // or looks better. - // - // or do some handwrite matching. - let re_color = Regex::new(r#"color=['"]([^'"]+)"#).unwrap(); - for (i, e) in tks.chars().enumerate() { - match e { - '<' => { - if bold { - output.push(Token::Bold(tks[ptr..i].to_string())); - bold = false; - } else if sup { - output.push(Token::Sup(tks[ptr..i].to_string())); - sup = false; - } else if sub { - output.push(Token::Sub(tks[ptr..i].to_string())); - sub = false; - } else if color.is_some() { - output.push(Token::Font((tks[ptr..i].to_string(), color.unwrap()))); - color = None; - } else { - output.push(Token::Plain(tks[ptr..i].to_string())); - } - ptr = i; - } - '>' => { - match &tks[i - 1..i] { - "-" => continue, - _ => match &tks[(ptr + 1)..i] { - "b" | "strong" => bold = true, - "sup" => sup = true, - "sub" => sub = true, - s if s.starts_with("font") => { - color = re_color - .captures(s) - .and_then(|caps| caps.get(1)) - .and_then(|cap| cap.as_str().parse().ok()); - } - _ => {} - }, - } - ptr = i + 1; - } - _ => {} - } - } - output.push(Token::Eof(tks[ptr..tks.len()].to_string())); - res = output; - } - - res - } - fn render(&self) -> String { - let ts = self.ser(); - let mut tks: Vec = vec![]; - - for i in ts { - match i { - Token::Plain(s) => tks.push(s.normal().to_string()), - Token::Bold(s) => { - if s.contains("Example") { - let mut br = "-".repeat(50).dimmed().to_string(); - br.push_str("\n\n"); - tks.push(br); - } else if s.contains("Note") { - let mut br = "* ".repeat(25).dimmed().to_string(); - br.push_str("\n\n"); - tks.push(br); - } - - tks.push(s.bold().to_string()); - } - Token::Sup(s) => tks.push(match s.parse::() { - Ok(n) => superscript(n), - _ => s, - }), - Token::Sub(s) => tks.push(match s.parse::() { - Ok(n) => subscript(n), - _ => s, - }), - Token::Font((s, color)) => tks.push(s.color(color).to_string()), - Token::Eof(s) => tks.push(s.normal().to_string()), - } - } - - // post replace - let tks = tks.join(""); + let rep = self + .replace(r#""#, "") + .replace(r#""#, "^") + .replace(r#""#, "") + .replace(r#""#, "_"); + let frag = Html::parse_fragment(rep.as_str()); + + let res = frag + .root_element() + .text() + .fold(String::new(), |acc, e| acc + e); - decode_html(&tks).unwrap_or(tks) + res } } } @@ -283,24 +143,18 @@ mod file { use crate::{cache::models::Problem, Error}; - /// Generate test casese path by fid - pub fn test_cases_path(target: &Problem) -> Result { + /// Generate test cases path by fid + pub fn test_cases_path(problem: &Problem) -> Result { let conf = crate::cfg::locate()?; + let mut path = format!("{}/{}.tests.dat", conf.storage.code()?, conf.code.pick); - let mut path = format!( - "{}/{}.tests.dat", - conf.storage.code()?, - conf.code.pick, - ); - - path = path.replace("${fid}", &target.fid.to_string()); - path = path.replace("${slug}", &target.slug.to_string()); - + path = path.replace("${fid}", &problem.fid.to_string()); + path = path.replace("${slug}", &problem.slug.to_string()); Ok(path) } /// Generate code path by fid - pub fn code_path(target: &Problem, l: Option) -> Result { + pub fn code_path(problem: &Problem, l: Option) -> Result { let conf = crate::cfg::locate()?; let mut lang = conf.code.lang; if l.is_some() { @@ -314,8 +168,8 @@ mod file { suffix(&lang)?, ); - path = path.replace("${fid}", &target.fid.to_string()); - path = path.replace("${slug}", &target.slug.to_string()); + path = path.replace("${fid}", &problem.fid.to_string()); + path = path.replace("${slug}", &problem.slug.to_string()); Ok(path) } diff --git a/src/plugins/leetcode.rs b/src/plugins/leetcode.rs index 4394532..e06627f 100644 --- a/src/plugins/leetcode.rs +++ b/src/plugins/leetcode.rs @@ -72,7 +72,8 @@ impl LeetCode { .conf .sys .urls - .get("problems").ok_or(Error::NoneError)? + .get("problems") + .ok_or(Error::NoneError)? .replace("$category", category); Req { @@ -110,7 +111,9 @@ 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.get("tag").ok_or(Error::NoneError)?).replace("$slug", slug), + ), info: false, json: Some(json), mode: Mode::Post, @@ -133,7 +136,8 @@ impl LeetCode { username isCurrentUserPremium } - }".to_owned() + }" + .to_owned(), ); Req { @@ -185,7 +189,13 @@ 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 + .get("problems") + .ok_or(Error::NoneError)? + .replace("$slug", slug); let mut json: Json = HashMap::new(); json.insert( "query", @@ -245,7 +255,13 @@ 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 + .get("verify") + .ok_or(Error::NoneError)? + .replace("$id", &id); Req { default_headers: self.default_headers, refer: None,