From 6f82b0a9c9f73ad3104d73f19fd172956ea2f681 Mon Sep 17 00:00:00 2001 From: jackfiled Date: Fri, 12 Jan 2024 23:07:53 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E5=86=99=E9=A2=98?= =?UTF-8?q?=E7=9B=AE=E8=8E=B7=E5=BE=97=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/fetch_problem.rs | 92 +++++++++ src/fetch_problem/fetcher.rs | 104 ++++++++++ src/fetch_problem/manager.rs | 72 +++++++ src/fetcher.rs | 231 --------------------- src/lib.rs | 2 - src/main.rs | 387 ++++------------------------------- 6 files changed, 311 insertions(+), 577 deletions(-) create mode 100644 src/fetch_problem.rs create mode 100644 src/fetch_problem/fetcher.rs create mode 100644 src/fetch_problem/manager.rs delete mode 100644 src/fetcher.rs diff --git a/src/fetch_problem.rs b/src/fetch_problem.rs new file mode 100644 index 0000000..6c3867a --- /dev/null +++ b/src/fetch_problem.rs @@ -0,0 +1,92 @@ +use serde_derive::{Deserialize, Serialize}; +use serde_json::json; + +pub mod fetcher; +pub mod manager; + +#[derive(Serialize, Deserialize)] +pub struct CodeDefinition { + pub value: String, + pub text: String, + #[serde(rename = "defaultCode")] + pub default_code: String +} + +/// LeetCode 单个问题 +pub struct Problem { + pub title: String, + pub title_slug: String, + pub content: String, + pub code_definition: Vec, + pub question_id: u32, + pub return_type: String +} + +#[derive(Serialize, Deserialize)] +pub struct Stat { + question_id: u32, + #[serde(rename = "question__article__slug")] + question_article_slug: Option, + #[serde(rename = "question__title")] + pub question_title: Option, + #[serde(rename = "question__title_slug")] + pub question_title_slug: Option, + #[serde(rename = "question__hide")] + question_hide: bool, + total_acs: u32, + total_submitted: u32, + pub frontend_question_id: String, + is_new_question: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct StatWithStatus { + pub stat: Stat, + pub paid_only: bool, +} + +#[derive(Serialize, Deserialize)] +pub struct Problems { + pub stat_status_pairs: Vec +} + +const QUESTION_QUERY_STRING: &str = r#" +query questionData($titleSlug: String!) { + question(titleSlug: $titleSlug) { + content + stats + codeDefinition + sampleTestCase + metaData + } +}"#; +const QUESTION_QUERY_OPERATION: &str = "questionData"; + +/// 题目查询 +#[derive(Serialize, Deserialize)] +pub struct Query { + #[serde(rename = "operationName")] + operation_name: String, + variables: serde_json::Value, + query: String +} + +impl Query { + pub fn new(title: &str) -> Query { + Query { + operation_name: QUESTION_QUERY_OPERATION.to_owned(), + variables: json!({ + "titleSlug": title + }), + query: QUESTION_QUERY_STRING.to_owned() + } + } +} + +pub struct Fetcher { + client: reqwest::Client +} + +pub struct ProblemManager { + pub problem_list: Vec, +} \ No newline at end of file diff --git a/src/fetch_problem/fetcher.rs b/src/fetch_problem/fetcher.rs new file mode 100644 index 0000000..5fc4c10 --- /dev/null +++ b/src/fetch_problem/fetcher.rs @@ -0,0 +1,104 @@ +use std::error::Error; +use serde_derive::{Deserialize, Serialize}; +use super::{Problem, Problems, Query, Fetcher}; + +const PROBLEMS_URL: &str = "https://leetcode.cn/api/problems/algorithms/"; +const GRAPHQL_URL: &str = "https://leetcode.cn/graphql"; + +impl Fetcher { + pub fn new() -> Fetcher { + Fetcher { + client: reqwest::Client::new() + } + } + + pub async fn get_problems(&self) -> Result { + Ok(reqwest::get(PROBLEMS_URL) + .await? + .json() + .await? + ) + } + + pub async fn get_problem(self, question_id: u32) -> Result> { + let problems = self.get_problems().await?; + + for problem in &problems.stat_status_pairs { + match problem.stat.frontend_question_id.parse::() { + Ok(id) => { + if id == question_id { + if problem.paid_only { + return Err("failed to get paid only problem".into()) + } + + let query = match &problem.stat.question_title_slug { + None => { + Err::>("failed to get problem title slug".into()) + } + Some(value) => { + Ok(Query::new(value.as_str())) + } + }?; + + let response = self.client + .post(GRAPHQL_URL) + .json(&query) + .send() + .await? + .json::() + .await?; + + let title = problem.stat.question_title.clone() + .ok_or::>("failed to get problem title".into())?; + let title_slug = problem.stat.question_title_slug.clone() + .ok_or::>("failed to get problem title slug".into())?; + let return_type = { + let v = serde_json::from_str::( + &response.data.question.meta_data); + v.and_then(|x| { + return Ok(x.to_string().replace("\"", "")) + }) + }?; + let code_definition = serde_json::from_str( + &response.data.question.code_definition + )?; + + return Ok(Problem { + title, + title_slug, + code_definition, + content: response.data.question.content, + question_id: id, + return_type + }) + } + } + Err(_) => {} + } + } + + Err("failed to get target problem".into()) + } +} + +#[derive(Debug, Serialize, Deserialize)] +struct RawProblem { + data: Data, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Data { + question: Question, +} + +#[derive(Debug, Serialize, Deserialize)] +struct Question { + content: String, + stats: String, + #[serde(rename = "codeDefinition")] + code_definition: String, + #[serde(rename = "sampleTestCase")] + sample_test_case: String, + #[serde(rename = "metaData")] + meta_data: String, +} \ No newline at end of file diff --git a/src/fetch_problem/manager.rs b/src/fetch_problem/manager.rs new file mode 100644 index 0000000..7a12977 --- /dev/null +++ b/src/fetch_problem/manager.rs @@ -0,0 +1,72 @@ +use super::{Problem, ProblemManager}; +use std::fs; +use regex::{Regex}; + +impl ProblemManager { + pub fn scan() -> Result> { + let pattern = Regex::new(r"p(\d{4})_")?; + let mut problems = Vec::new(); + let mod_content = fs::read_to_string("./src/problem/mod.rs")?; + + for i in pattern.captures_iter(&mod_content) { + match i.get(1) { + None => {} + Some(value) => { + match value.as_str().parse::() { + Ok(id) => { + problems.push(id); + } + Err(_) => {} + } + } + } + } + + Ok(ProblemManager { + problem_list: problems + }) + } +} + +impl Problem { + pub fn get_filename(&self) -> String { + format!("p{}_{}", self.question_id, self.title_slug.replace('-', "_")) + } + + pub fn get_file_content(&self) -> Result> { + let template = fs::read_to_string("./template.rs")?; + + let code = self.code_definition + .iter() + .find(|x| x.value == "rust"); + + let code = code.ok_or::>( + format!("problem {} doesn't have rust version", self.question_id).into() + )?; + + let source = template + .replace("__PROBLEM_TITLE__", &self.title) + .replace("__PROBLEM_ID__", self.question_id.to_string().as_str()) + .replace( + "__PROBLEM_DEFAULT_CODE__", + &code.default_code) + .replace("__EXTRA_USE__", &parse_extra_use(&code.default_code)); + + Ok(source) + } +} + +fn parse_extra_use(code: &str) -> String { + let mut extra_use_line = String::new(); + // a linked-list problem + if code.contains("pub struct ListNode") { + extra_use_line.push_str("\nuse crate::util::linked_list::{ListNode, to_list};") + } + if code.contains("pub struct TreeNode") { + extra_use_line.push_str("\nuse crate::util::tree::{TreeNode, to_tree};") + } + if code.contains("pub struct Point") { + extra_use_line.push_str("\nuse crate::util::point::Point;") + } + extra_use_line +} \ No newline at end of file diff --git a/src/fetcher.rs b/src/fetcher.rs deleted file mode 100644 index f8cdd9d..0000000 --- a/src/fetcher.rs +++ /dev/null @@ -1,231 +0,0 @@ -extern crate reqwest; -extern crate serde_json; - -use serde_json::Value; -use std::fmt::{Display, Error, Formatter}; - -const PROBLEMS_URL: &str = "https://leetcode.cn/api/problems/algorithms/"; -const GRAPHQL_URL: &str = "https://leetcode.cn/graphql"; -const QUESTION_QUERY_STRING: &str = r#" -query questionData($titleSlug: String!) { - question(titleSlug: $titleSlug) { - content - stats - codeDefinition - sampleTestCase - metaData - } -}"#; -const QUESTION_QUERY_OPERATION: &str = "questionData"; - -pub async fn get_problem(frontend_question_id: u32) -> Option { - let problems = get_problems().await.unwrap(); - for problem in problems.stat_status_pairs.iter() { - match problem.stat.frontend_question_id.parse::() { - Ok(id) => { - if id == frontend_question_id { - if problem.paid_only { - return None; - } - - let client = reqwest::Client::new(); - let resp: RawProblem = client - .post(GRAPHQL_URL) - .json(&Query::question_query( - problem.stat.question_title_slug.as_ref().unwrap(), - )) - .send() - .await - .unwrap() - .json() - .await - .unwrap(); - return Some(Problem { - title: problem.stat.question_title.clone().unwrap(), - title_slug: problem.stat.question_title_slug.clone().unwrap(), - code_definition: serde_json::from_str(&resp.data.question.code_definition).unwrap(), - content: resp.data.question.content, - sample_test_case: resp.data.question.sample_test_case, - difficulty: problem.difficulty.to_string(), - question_id: id, - return_type: { - let v: Value = serde_json::from_str(&resp.data.question.meta_data).unwrap(); - v["return"]["type"].to_string().replace("\"", "") - }, - }); - } - } - _ => {} - } - } - None -} - -pub async fn get_problem_async(problem_stat: StatWithStatus) -> Option { - if problem_stat.paid_only { - println!( - "Problem {} is paid-only", - &problem_stat.stat.frontend_question_id - ); - return None; - } - let resp = surf::post(GRAPHQL_URL).body_json(&Query::question_query( - problem_stat.stat.question_title_slug.as_ref().unwrap(), - )); - if resp.is_err() { - println!( - "Problem {} not initialized due to some error", - &problem_stat.stat.frontend_question_id - ); - return None; - } - let resp = resp.unwrap().recv_json().await; - if resp.is_err() { - println!( - "Problem {} not initialized due to some error", - &problem_stat.stat.frontend_question_id - ); - return None; - } - let resp: RawProblem = resp.unwrap(); - match problem_stat.stat.frontend_question_id.parse::() { - Ok(id) => { - return Some(Problem { - title: problem_stat.stat.question_title.clone().unwrap(), - title_slug: problem_stat.stat.question_title_slug.clone().unwrap(), - code_definition: serde_json::from_str(&resp.data.question.code_definition).unwrap(), - content: resp.data.question.content, - sample_test_case: resp.data.question.sample_test_case, - difficulty: problem_stat.difficulty.to_string(), - question_id: id, - return_type: { - let v: Value = serde_json::from_str(&resp.data.question.meta_data).unwrap(); - v["return"]["type"].to_string().replace("\"", "") - }, - }); - } - _ => { - None - } - } -} - -pub async fn get_problems() -> Option { - reqwest::get(PROBLEMS_URL).await.unwrap().json().await.unwrap() -} - -#[derive(Serialize, Deserialize)] -pub struct Problem { - pub title: String, - pub title_slug: String, - pub content: String, - #[serde(rename = "codeDefinition")] - pub code_definition: Vec, - #[serde(rename = "sampleTestCase")] - pub sample_test_case: String, - pub difficulty: String, - pub question_id: u32, - pub return_type: String, -} - -#[derive(Serialize, Deserialize)] -pub struct CodeDefinition { - pub value: String, - pub text: String, - #[serde(rename = "defaultCode")] - pub default_code: String, -} - -#[derive(Debug, Serialize, Deserialize)] -struct Query { - #[serde(rename = "operationName")] - operation_name: String, - variables: serde_json::Value, - query: String, -} - -impl Query { - fn question_query(title_slug: &str) -> Query { - Query { - operation_name: QUESTION_QUERY_OPERATION.to_owned(), - variables: json!({ "titleSlug": title_slug }), - query: QUESTION_QUERY_STRING.to_owned(), - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -struct RawProblem { - data: Data, -} - -#[derive(Debug, Serialize, Deserialize)] -struct Data { - question: Question, -} - -#[derive(Debug, Serialize, Deserialize)] -struct Question { - content: String, - stats: String, - #[serde(rename = "codeDefinition")] - code_definition: String, - #[serde(rename = "sampleTestCase")] - sample_test_case: String, - #[serde(rename = "metaData")] - meta_data: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Problems { - user_name: String, - num_solved: u32, - num_total: u32, - ac_easy: u32, - ac_medium: u32, - ac_hard: u32, - pub stat_status_pairs: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct StatWithStatus { - pub stat: Stat, - difficulty: Difficulty, - paid_only: bool, - is_favor: bool, - frequency: u32, - progress: u32, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Stat { - question_id: u32, - #[serde(rename = "question__article__slug")] - question_article_slug: Option, - #[serde(rename = "question__title")] - question_title: Option, - #[serde(rename = "question__title_slug")] - question_title_slug: Option, - #[serde(rename = "question__hide")] - question_hide: bool, - total_acs: u32, - total_submitted: u32, - pub frontend_question_id: String, - is_new_question: bool, -} - -#[derive(Debug, Serialize, Deserialize)] -struct Difficulty { - level: u32, -} - -impl Display for Difficulty { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - match self.level { - 1 => f.write_str("Easy"), - 2 => f.write_str("Medium"), - 3 => f.write_str("Hard"), - _ => f.write_str("Unknown"), - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 55b0298..2f338b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ #[macro_use] pub mod util; - -pub mod solution; pub mod problem; diff --git a/src/main.rs b/src/main.rs index 4842082..9778ce6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,366 +1,65 @@ -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate serde_json; - -mod fetcher; - -use crate::fetcher::{CodeDefinition, Problem}; -use regex::Regex; use std::fs; -use std::fs::File; -use std::io; -use std::io::{BufRead, Write}; +use std::io::Write; use std::path::Path; +use crate::fetch_problem::{Fetcher, ProblemManager}; -use futures::executor::block_on; -use futures::executor::ThreadPool; -use futures::future::join_all; -use futures::stream::StreamExt; -use futures::task::SpawnExt; -use std::sync::{Arc, Mutex}; +mod fetch_problem; -/// main() helps to generate the submission template .rs #[tokio::main] -async fn main() { - println!("Welcome to leetcode-rust system.\n"); - let mut initialized_ids = get_initialized_ids(); - loop { - println!( - "Please enter a frontend problem id, \n\ - or \"random\" to generate a random one, \n\ - or \"solve $i\" to move problem to solution/, \n\ - or \"all\" to initialize all problems \n" - ); - let mut is_random = false; - let mut is_solving = false; - let mut id: u32 = 0; - let mut id_arg = String::new(); - io::stdin() - .read_line(&mut id_arg) - .expect("Failed to read line"); - let id_arg = id_arg.trim(); +async fn main() { + let manager = ProblemManager::scan().expect("Failed to scan mod file."); + let fetcher = Fetcher::new(); - let random_pattern = Regex::new(r"^random$").unwrap(); - let solving_pattern = Regex::new(r"^solve (\d+)$").unwrap(); - let all_pattern = Regex::new(r"^all$").unwrap(); + let args: Vec = std::env::args().collect(); + let operation = &args[1].as_str(); - if random_pattern.is_match(id_arg) { - println!("You select random mode."); - id = generate_random_id(&initialized_ids); - is_random = true; - println!("Generate random problem: {}", &id); - } else if solving_pattern.is_match(id_arg) { - // solve a problem - // move it from problem/ to solution/ - is_solving = true; - id = solving_pattern - .captures(id_arg) - .unwrap() - .get(1) - .unwrap() - .as_str() - .parse() - .unwrap(); - deal_solving(&id).await; - break; - } else if all_pattern.is_match(id_arg) { - // deal all problems - let pool = ThreadPool::new().unwrap(); - let mut tasks = vec![]; - let problems = fetcher::get_problems().await.unwrap(); - let mut mod_file_addon = Arc::new(Mutex::new(vec![])); - for problem_stat in problems.stat_status_pairs { - match problem_stat.stat.frontend_question_id.parse::() { - Ok(id) => { - if initialized_ids.contains(&id) { - continue; - } + match operation { + &"get" => { + let id = args[2].parse::(); + + match id { + Ok(id) => { + if manager.problem_list.contains(&id) { + eprintln!("Problem {} exists.", id); + return; } - _ => {} - }; - let mod_file_addon = mod_file_addon.clone(); - tasks.push( - pool.spawn_with_handle(async move { - let problem = fetcher::get_problem_async(problem_stat).await; - if problem.is_none() { - return; - } - let problem = problem.unwrap(); - let code = problem - .code_definition - .iter() - .find(|&d| d.value == "rust".to_string()); - if code.is_none() { - println!("Problem {} has no rust version.", problem.question_id); - return; - } - // not sure this can be async - async { - mod_file_addon.lock().unwrap().push(format!( - "mod p{:04}_{};", - problem.question_id, - problem.title_slug.replace("-", "_") - )); - } - .await; - let code = code.unwrap(); - // not sure this can be async - // maybe should use async-std io - async { deal_problem(&problem, &code, false) }.await - }) - .unwrap(), - ); - } - block_on(join_all(tasks)); - let mut lib_file = fs::OpenOptions::new() - .write(true) - .append(true) - .open("./src/problem/mod.rs") - .unwrap(); - writeln!(lib_file, "{}", mod_file_addon.lock().unwrap().join("\n")); - break; - } else { - id = id_arg - .parse::() - .unwrap_or_else(|_| panic!("not a number: {}", id_arg)); - if initialized_ids.contains(&id) { - println!("The problem you chose has been initialized in problem/"); - continue; + + println!("Try to get problem {}...", id); + + let problem = fetcher.get_problem(id).await + .expect(&*format!("Failed to get problem {}.", id)); + + let file_name = problem.get_filename(); + println!("Get problem: {}.", file_name); + let content = problem.get_file_content().expect("Failed to format file content"); + + write_file(&file_name, &content).expect("Failed to write problem file."); + } + Err(_) => { + eprintln!("Get operation needs a usize param.") + } } } - - let problem = fetcher::get_problem(id).await.unwrap_or_else(|| { - panic!( - "Error: failed to get problem #{} \ - (The problem may be paid-only or may not be exist).", - id - ) - }); - let code = problem - .code_definition - .iter() - .find(|&d| d.value == "rust".to_string()); - if code.is_none() { - println!("Problem {} has no rust version.", &id); - initialized_ids.push(problem.question_id); - continue; - } - let code = code.unwrap(); - deal_problem(&problem, &code, true); - break; + _ => {} } } -fn generate_random_id(except_ids: &[u32]) -> u32 { - use rand::Rng; - let mut rng = rand::thread_rng(); - loop { - let res: u32 = rng.gen_range(1, 1106); - if !except_ids.contains(&res) { - return res; - } - println!( - "Generate a random num ({}), but it is invalid (the problem may have been solved \ - or may have no rust version). Regenerate..", - res - ); - } -} - -fn get_initialized_ids() -> Vec { - let content = fs::read_to_string("./src/problem/mod.rs").unwrap(); - let id_pattern = Regex::new(r"p(\d{4})_").unwrap(); - id_pattern - .captures_iter(&content) - .map(|x| x.get(1).unwrap().as_str().parse().unwrap()) - .collect() -} - -fn parse_extra_use(code: &str) -> String { - let mut extra_use_line = String::new(); - // a linked-list problem - if code.contains("pub struct ListNode") { - extra_use_line.push_str("\nuse crate::util::linked_list::{ListNode, to_list};") - } - if code.contains("pub struct TreeNode") { - extra_use_line.push_str("\nuse crate::util::tree::{TreeNode, to_tree};") - } - if code.contains("pub struct Point") { - extra_use_line.push_str("\nuse crate::util::point::Point;") - } - extra_use_line -} - -fn parse_problem_link(problem: &Problem) -> String { - format!("https://leetcode.cn/problems/{}/", problem.title_slug) -} - -fn parse_discuss_link(problem: &Problem) -> String { - format!( - "https://leetcode.cn/problems/{}/discuss/?currentPage=1&orderBy=most_votes&query=", - problem.title_slug - ) -} - -fn insert_return_in_code(return_type: &str, code: &str) -> String { - let re = Regex::new(r"\{[ \n]+}").unwrap(); - match return_type { - "ListNode" => re - .replace(&code, "{\n Some(Box::new(ListNode::new(0)))\n }") - .to_string(), - "ListNode[]" => re.replace(&code, "{\n vec![]\n }").to_string(), - "TreeNode" => re - .replace( - &code, - "{\n Some(Rc::new(RefCell::new(TreeNode::new(0))))\n }", - ) - .to_string(), - "boolean" => re.replace(&code, "{\n false\n }").to_string(), - "character" => re.replace(&code, "{\n '0'\n }").to_string(), - "character[][]" => re.replace(&code, "{\n vec![]\n }").to_string(), - "double" => re.replace(&code, "{\n 0f64\n }").to_string(), - "double[]" => re.replace(&code, "{\n vec![]\n }").to_string(), - "int[]" => re.replace(&code, "{\n vec![]\n }").to_string(), - "integer" => re.replace(&code, "{\n 0\n }").to_string(), - "integer[]" => re.replace(&code, "{\n vec![]\n }").to_string(), - "integer[][]" => re.replace(&code, "{\n vec![]\n }").to_string(), - "list" => re.replace(&code, "{\n vec![]\n }").to_string(), - "list" => re.replace(&code, "{\n vec![]\n }").to_string(), - "list" => re.replace(&code, "{\n vec![]\n }").to_string(), - "list" => re.replace(&code, "{\n vec![]\n }").to_string(), - "list" => re.replace(&code, "{\n vec![]\n }").to_string(), - "list>" => re.replace(&code, "{\n vec![]\n }").to_string(), - "list>" => re.replace(&code, "{\n vec![]\n }").to_string(), - "list" => re.replace(&code, "{\n vec![]\n }").to_string(), - "null" => code.to_string(), - "string" => re - .replace(&code, "{\n String::new()\n }") - .to_string(), - "string[]" => re.replace(&code, "{\n vec![]\n }").to_string(), - "void" => code.to_string(), - "NestedInteger" => code.to_string(), - "Node" => code.to_string(), - _ => code.to_string(), - } -} - -fn build_desc(content: &str) -> String { - // TODO: fix this shit - content - .replace("", "") - .replace("", "") - .replace("", "") - .replace("", "") - .replace("

", "") - .replace("

", "") - .replace("", "") - .replace("", "") - .replace("

", "")
-        .replace("
", "") - .replace("
    ", "") - .replace("
", "") - .replace("
  • ", "") - .replace("
  • ", "") - .replace("", "") - .replace("", "") - .replace("", "") - .replace("", "") - .replace("", "") - .replace("", "") - .replace("", "") - .replace("", "^") - .replace(" ", " ") - .replace(">", ">") - .replace("<", "<") - .replace(""", "\"") - .replace("−", "-") - .replace("'", "'") - .replace("\n\n", "\n") - .replace("\n", "\n * ") -} - -async fn deal_solving(id: &u32) { - let problem = fetcher::get_problem(*id).await.unwrap(); - let file_name = format!( - "p{:04}_{}", - problem.question_id, - problem.title_slug.replace("-", "_") - ); +fn write_file(file_name: &String, file_content: &String) -> std::io::Result<()> { let file_path = Path::new("./src/problem").join(format!("{}.rs", file_name)); - // check problem/ existence - if !file_path.exists() { - panic!("problem does not exist"); - } - // check solution/ no existence - let solution_name = format!( - "s{:04}_{}", - problem.question_id, - problem.title_slug.replace("-", "_") - ); - let solution_path = Path::new("./src/solution").join(format!("{}.rs", solution_name)); - if solution_path.exists() { - panic!("solution exists"); - } - // rename/move file - fs::rename(file_path, solution_path).unwrap(); - // remove from problem/mod.rs - let mod_file = "./src/problem/mod.rs"; - let target_line = format!("mod {};", file_name); - let lines: Vec = io::BufReader::new(File::open(mod_file).unwrap()) - .lines() - .map(|x| x.unwrap()) - .filter(|x| *x != target_line) - .collect(); - fs::write(mod_file, lines.join("\n")); - // insert into solution/mod.rs - let mut lib_file = fs::OpenOptions::new() - .append(true) - .open("./src/solution/mod.rs") - .unwrap(); - writeln!(lib_file, "mod {};", solution_name); -} - -fn deal_problem(problem: &Problem, code: &CodeDefinition, write_mod_file: bool) { - let file_name = format!( - "p{:04}_{}", - problem.question_id, - problem.title_slug.replace("-", "_") - ); - let file_path = Path::new("./src/problem").join(format!("{}.rs", file_name)); - if file_path.exists() { - panic!("problem already initialized"); - } - - let template = fs::read_to_string("./template.rs").unwrap(); - let source = template - .replace("__PROBLEM_TITLE__", &problem.title) - .replace("__PROBLEM_DESC__", &build_desc(&problem.content)) - .replace( - "__PROBLEM_DEFAULT_CODE__", - &insert_return_in_code(&problem.return_type, &code.default_code), - ) - .replace("__PROBLEM_ID__", &format!("{}", problem.question_id)) - .replace("__EXTRA_USE__", &parse_extra_use(&code.default_code)) - .replace("__PROBLEM_LINK__", &parse_problem_link(problem)) - .replace("__DISCUSS_LINK__", &parse_discuss_link(problem)); let mut file = fs::OpenOptions::new() .write(true) .create(true) .truncate(true) - .open(&file_path) - .unwrap(); + .open(&file_path)?; - file.write_all(source.as_bytes()).unwrap(); + file.write_all(file_content.as_bytes())?; drop(file); - if write_mod_file { - let mut lib_file = fs::OpenOptions::new() - .write(true) - .append(true) - .open("./src/problem/mod.rs") - .unwrap(); - writeln!(lib_file, "mod {};", file_name); - } -} + let mut mod_file = fs::OpenOptions::new() + .write(true) + .append(true) + .open("./src/problem/mod.rs")?; + + write!(mod_file, "\nmod {};", file_name) +} \ No newline at end of file