From e297e89a96ee855fcc49cef207f2712b1b515174 Mon Sep 17 00:00:00 2001 From: nassersaazi Date: Sun, 11 Jun 2023 02:47:07 +0300 Subject: [PATCH 001/196] Add README and .gitignore files --- .gitignore | 1 + README.md | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 README.md diff --git a/.gitignore b/.gitignore index e69de29..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/README.md b/README.md new file mode 100644 index 0000000..799fd74 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +## Leetcode local dev assistant + + A program that given the link to a leetcode problem ,creates a local file where you can develop your solution and then post it back to leetcode. From fedf83af4a0d7fbe6c2fbda2d510b9862692ca9a Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sat, 10 Jun 2023 10:53:59 -0400 Subject: [PATCH 002/196] Move code into lib --- src/lib.rs | 4 ++++ src/main.rs | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3f80424..d8cd014 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,7 @@ pub use leetcode_env::list::ListHead; pub use leetcode_env::list::ListNode; pub use leetcode_env::tree::TreeNode; pub use leetcode_env::tree::TreeRoot; + +pub mod code_snippet; +pub mod daily_challenge; +pub mod write_file; diff --git a/src/main.rs b/src/main.rs index bf6e90b..904a953 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,7 @@ -mod code_snippet; -mod daily_challenge; -mod write_file; - use std::error::Error; +use cargo_leet::{code_snippet, daily_challenge, write_file}; + fn main() -> Result<(), Box> { let mut args = std::env::args(); let title_slug = if args.len() == 1 { From 65f82281d31293a3135111355104810c37332e4c Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sat, 10 Jun 2023 13:28:20 -0400 Subject: [PATCH 003/196] Put dependencies in lexicographical order --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e0e8a12..a16d0d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" [dependencies] convert_case = "0.6" -ureq = { version = "2.6", features = ["json"]} serde = { version = "1", features = ["derive"] } serde_flat_path = "0.1.2" +ureq = { version = "2.6", features = ["json"] } From 45d869f1f2d8c4fc43a39855d1efb61c7ee20169 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sat, 10 Jun 2023 14:39:51 -0400 Subject: [PATCH 004/196] Remove white space --- src/daily_challenge.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/daily_challenge.rs b/src/daily_challenge.rs index c584f5f..b3ae344 100644 --- a/src/daily_challenge.rs +++ b/src/daily_challenge.rs @@ -16,7 +16,7 @@ pub fn get_daily_challenge_slug() -> String { question { titleSlug } - } + } }"#, "variables":{}, "operationName":"questionOfToday" From 1c0e7c29de508d2c478444ac449758ecb9d4b42d Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sat, 10 Jun 2023 21:11:01 -0400 Subject: [PATCH 005/196] Set specific serde version Prefer specific version unless testing that code works with old version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a16d0d0..7b30312 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" [dependencies] convert_case = "0.6" -serde = { version = "1", features = ["derive"] } +serde = { version = "1.0.164", features = ["derive"] } serde_flat_path = "0.1.2" ureq = { version = "2.6", features = ["json"] } From d69d5a4b41665be3bca36b2f0f8c832e4d6ea717 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sat, 10 Jun 2023 22:08:21 -0400 Subject: [PATCH 006/196] Change return type to anyhow::Result Simplify return types for fallible functions --- Cargo.toml | 1 + src/main.rs | 14 +------------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7b30312..81f3d40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = "1.0.71" convert_case = "0.6" serde = { version = "1.0.164", features = ["derive"] } serde_flat_path = "0.1.2" diff --git a/src/main.rs b/src/main.rs index 904a953..57aabc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,5 @@ use std::error::Error; -use cargo_leet::{code_snippet, daily_challenge, write_file}; - -fn main() -> Result<(), Box> { - let mut args = std::env::args(); - let title_slug = if args.len() == 1 { - daily_challenge::get_daily_challenge_slug() - } else if args.len() != 2 { - return Err("Usage: binary SLUG".into()); - } else { - args.nth(1).unwrap() - }; - let code_snippet = code_snippet::generate_code_snippet(&title_slug); - write_file::write_file(&title_slug, code_snippet)?; +fn main() -> anyhow::Result<()> { Ok(()) } From 0152387a6c80e8a2dea396a34174d186d4549d38 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sat, 10 Jun 2023 22:09:36 -0400 Subject: [PATCH 007/196] Add clap --- Cargo.lock | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/cli.rs | 52 +++++++++++ src/lib.rs | 2 + 4 files changed, 321 insertions(+) create mode 100644 src/cli.rs diff --git a/Cargo.lock b/Cargo.lock index a17db06..eb15ab2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,73 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bumpalo" version = "3.13.0" @@ -24,6 +85,8 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" name = "cargo-leet" version = "0.1.0" dependencies = [ + "anyhow", + "clap", "convert_case", "serde", "serde_flat_path", @@ -42,6 +105,55 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8f255e4b8027970e78db75e78831229c9815fdbfa67eb1a1b777a62e24b4a0" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd4f3c17c83b0ba34ffbc4f8bbd74f079413f747f84a6f89292f138057e36ab" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "once_cell", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "convert_case" version = "0.6.0" @@ -60,6 +172,27 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "flate2" version = "1.0.26" @@ -79,6 +212,18 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "idna" version = "0.4.0" @@ -89,6 +234,29 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + [[package]] name = "itoa" version = "1.0.6" @@ -110,6 +278,12 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "log" version = "0.4.18" @@ -170,6 +344,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustls" version = "0.20.8" @@ -246,6 +434,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" @@ -345,6 +539,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "wasm-bindgen" version = "0.2.86" @@ -449,3 +649,69 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index 81f3d40..ebba70c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1.0.71" +clap = { version = "4.3.3", features = ["derive", "cargo"] } convert_case = "0.6" serde = { version = "1.0.164", features = ["derive"] } serde_flat_path = "0.1.2" diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..2b42079 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,52 @@ +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, + + /// Specify the path to the project root (If not provided uses current working directory) + #[arg(long, short, value_name = "FOLDER")] + path: Option, +} + +impl Cli { + /// Changes the current working directory to path if one is given + pub fn set_path(&self) -> anyhow::Result<()> { + if let Some(path) = &self.path { + std::env::set_current_dir(path)? + } + Ok(()) + } +} + +#[derive(Subcommand, Debug)] +pub enum Commands { + Generate(GenerateArgs), +} + +#[derive(Args, Debug)] +#[group(required = true, multiple = false)] +pub struct GenerateArgs { + /// Question slug or url + #[arg(short, long)] + problem: Option, + + /// Set using question of the day + #[arg(long, short)] + daily_challenge: bool, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn verify_cli() { + // Source: https://docs.rs/clap/latest/clap/_derive/_tutorial/index.html#testing + // My understanding it reports most development errors without additional effort + use clap::CommandFactory; + Cli::command().debug_assert() + } +} diff --git a/src/lib.rs b/src/lib.rs index d8cd014..957c07c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ mod leetcode_env; +pub mod cli; + pub use leetcode_env::list::ListHead; pub use leetcode_env::list::ListNode; pub use leetcode_env::tree::TreeNode; From 1d735ed2c6b494ae6d7759ef6fa3d6d670af7055 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sat, 10 Jun 2023 23:06:22 -0400 Subject: [PATCH 008/196] Restructure to put executed code into lib Move main logic into a module called core to make it clear which parts are executing code --- src/{ => core}/code_snippet.rs | 0 src/{ => core}/daily_challenge.rs | 1 + src/core/mod.rs | 20 ++++++++++++++++++++ src/{ => core}/write_file.rs | 0 src/lib.rs | 6 +++--- src/main.rs | 6 ++++-- 6 files changed, 28 insertions(+), 5 deletions(-) rename src/{ => core}/code_snippet.rs (100%) rename src/{ => core}/daily_challenge.rs (91%) create mode 100644 src/core/mod.rs rename src/{ => core}/write_file.rs (100%) diff --git a/src/code_snippet.rs b/src/core/code_snippet.rs similarity index 100% rename from src/code_snippet.rs rename to src/core/code_snippet.rs diff --git a/src/daily_challenge.rs b/src/core/daily_challenge.rs similarity index 91% rename from src/daily_challenge.rs rename to src/core/daily_challenge.rs index b3ae344..2174682 100644 --- a/src/daily_challenge.rs +++ b/src/core/daily_challenge.rs @@ -9,6 +9,7 @@ struct DailyChallengeResponse { } pub fn get_daily_challenge_slug() -> String { + // TODO: Change return type to anyhow and add context for each error let daily_challenge_response = ureq::get("https://leetcode.com/graphql/") .send_json(ureq::json!({ "query": r#"query questionOfToday { diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..2716506 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,20 @@ +use crate::cli::Cli; + +mod code_snippet; +mod daily_challenge; +mod write_file; + +pub fn run(cli: &Cli) -> anyhow::Result<()> { + // TODO: Check for Cargo.toml to ensure we are in a valid folder + // let mut args = std::env::args(); + // let title_slug = if args.len() == 1 { + // daily_challenge::get_daily_challenge_slug() + // } else if args.len() != 2 { + // return Err("Usage: binary SLUG".into()); + // } else { + // args.nth(1).unwrap() + // }; + // let code_snippet = code_snippet::generate_code_snippet(&title_slug); + // write_file::write_file(&title_slug, code_snippet)?; + Ok(()) +} diff --git a/src/write_file.rs b/src/core/write_file.rs similarity index 100% rename from src/write_file.rs rename to src/core/write_file.rs diff --git a/src/lib.rs b/src/lib.rs index 957c07c..4bdf7e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,6 @@ pub use leetcode_env::list::ListNode; pub use leetcode_env::tree::TreeNode; pub use leetcode_env::tree::TreeRoot; -pub mod code_snippet; -pub mod daily_challenge; -pub mod write_file; +mod core; + +pub use crate::core::run; diff --git a/src/main.rs b/src/main.rs index 57aabc9..22bc64a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ -use std::error::Error; +use cargo_leet::{cli::Cli, run}; +use clap::Parser; fn main() -> anyhow::Result<()> { - Ok(()) + let cli = Cli::parse(); + run(&cli) } From 2fc4eac5b4c90932cc430f0848f16f38a9fb7672 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sat, 10 Jun 2023 23:50:05 -0400 Subject: [PATCH 009/196] Add basic logging support --- Cargo.lock | 350 +++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/cli.rs | 33 ++++- src/lib.rs | 11 +- src/log.rs | 19 +++ src/main.rs | 3 +- 6 files changed, 410 insertions(+), 8 deletions(-) create mode 100644 src/log.rs diff --git a/Cargo.lock b/Cargo.lock index eb15ab2..e786cc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,21 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.3.2" @@ -63,6 +78,18 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base64" version = "0.13.1" @@ -88,6 +115,8 @@ dependencies = [ "anyhow", "clap", "convert_case", + "log", + "log4rs", "serde", "serde_flat_path", "ureq", @@ -105,6 +134,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + [[package]] name = "clap" version = "4.3.3" @@ -163,6 +207,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "crc32fast" version = "1.3.2" @@ -172,6 +222,23 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "destructure_traitobject" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" + [[package]] name = "errno" version = "0.3.1" @@ -203,6 +270,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.0" @@ -212,6 +285,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "heck" version = "0.4.1" @@ -224,6 +303,35 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.4.0" @@ -234,6 +342,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -278,17 +396,68 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" -version = "0.4.18" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +dependencies = [ + "serde", +] + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36ca1786d9e79b8193a68d480a0907b612f109537115c6ff655a3a1967533fd" +dependencies = [ + "anyhow", + "arc-swap", + "chrono", + "derivative", + "fnv", + "humantime", + "libc", + "log", + "log-mdc", + "parking_lot", + "serde", + "serde-value", + "serde_json", + "serde_yaml", + "thiserror", + "thread-id", + "typemap-ors", + "winapi", +] [[package]] name = "miniz_oxide" @@ -299,12 +468,53 @@ dependencies = [ "adler", ] +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -329,6 +539,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "ring" version = "0.16.20" @@ -376,6 +604,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "sct" version = "0.7.0" @@ -395,6 +629,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.164" @@ -428,6 +672,24 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "spin" version = "0.5.2" @@ -462,6 +724,48 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "thread-id" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee93aa2b8331c0fec9091548843f2c90019571814057da3b783f9de09349d73" +dependencies = [ + "libc", + "redox_syscall 0.2.16", + "winapi", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi", + "winapi", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -477,6 +781,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "typemap-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" +dependencies = [ + "unsafe-any-ors", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -504,6 +817,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unsafe-any-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" +dependencies = [ + "destructure_traitobject", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -545,6 +867,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasm-bindgen" version = "0.2.86" @@ -650,6 +978,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -715,3 +1052,12 @@ name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml index ebba70c..27926a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" anyhow = "1.0.71" clap = { version = "4.3.3", features = ["derive", "cargo"] } convert_case = "0.6" +log = "0.4.19" +log4rs = "1.2.0" serde = { version = "1.0.164", features = ["derive"] } serde_flat_path = "0.1.2" ureq = { version = "2.6", features = ["json"] } diff --git a/src/cli.rs b/src/cli.rs index 2b42079..b4f312c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,5 @@ -use clap::{Args, Parser, Subcommand}; +use clap::{Args, Parser, Subcommand, ValueEnum}; +use log::LevelFilter; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -9,6 +10,10 @@ pub struct Cli { /// Specify the path to the project root (If not provided uses current working directory) #[arg(long, short, value_name = "FOLDER")] path: Option, + + /// Set logging level to use + #[arg(long, short, value_enum, default_value_t = LogLevel::Error)] + pub log_level: LogLevel, } impl Cli { @@ -38,6 +43,32 @@ pub struct GenerateArgs { daily_challenge: bool, } +/// Exists to provide better help messages variants copied from LevelFilter as that's the type +/// that is actually needed +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] +pub enum LogLevel { + /// Nothing emitted in this mode + Off, + Error, + Warn, + Info, + Debug, + Trace, +} + +impl From for LevelFilter { + fn from(value: LogLevel) -> Self { + match value { + LogLevel::Off => LevelFilter::Off, + LogLevel::Error => LevelFilter::Error, + LogLevel::Warn => LevelFilter::Warn, + LogLevel::Info => LevelFilter::Info, + LogLevel::Debug => LevelFilter::Debug, + LogLevel::Trace => LevelFilter::Trace, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 4bdf7e9..bde947c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,15 @@ +mod cli; +mod core; mod leetcode_env; +mod log; -pub mod cli; - +// For use in external code pub use leetcode_env::list::ListHead; pub use leetcode_env::list::ListNode; pub use leetcode_env::tree::TreeNode; pub use leetcode_env::tree::TreeRoot; -mod core; - +// For use in main.rs pub use crate::core::run; +pub use crate::log::init_logging; +pub use cli::Cli; diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..bf0dc36 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,19 @@ +use anyhow::Context; +use log::LevelFilter; +use log4rs::{ + append::console::ConsoleAppender, + config::{Appender, Root}, + encode::pattern::PatternEncoder, +}; + +pub fn init_logging(log_level: LevelFilter) -> anyhow::Result<()> { + let stdout = ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new("{h({l} - {m})}{n}"))) + .build(); + + let config = log4rs::Config::builder() + .appender(Appender::builder().build("stdout", Box::new(stdout))) + .build(Root::builder().appender("stdout").build(log_level))?; + log4rs::init_config(config).context("Failed to initialize logging")?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 22bc64a..a754442 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ -use cargo_leet::{cli::Cli, run}; +use cargo_leet::{init_logging, run, Cli}; use clap::Parser; fn main() -> anyhow::Result<()> { let cli = Cli::parse(); + init_logging(cli.log_level.into())?; run(&cli) } From 822d57ec54fa8667c42059698abc946d7692cd83 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sat, 10 Jun 2023 23:51:31 -0400 Subject: [PATCH 010/196] Change version of log to stop warning Was getting a warning from crates plugin in vscode saying > The version requirement of the target dependency I couldn't figure out what it meant but keeping it at 0.4.19 wasn't providing any additional value. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 27926a4..f95dd0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" anyhow = "1.0.71" clap = { version = "4.3.3", features = ["derive", "cargo"] } convert_case = "0.6" -log = "0.4.19" +log = "0.4.18" log4rs = "1.2.0" serde = { version = "1.0.164", features = ["derive"] } serde_flat_path = "0.1.2" From 09c62a4ed93f54a97c5be76687b8949e5a9ad712 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 00:34:11 -0400 Subject: [PATCH 011/196] Add description --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index f95dd0d..cacbf2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "cargo-leet" +description = "Utility program to help with working on leetcode locally" version = "0.1.0" edition = "2021" From 25524cf0424d3f3b3d4da9f63a3c3d8f2fdbf2a2 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 00:37:28 -0400 Subject: [PATCH 012/196] Improve logging and error messages Provide more info for debugging and better error messages when things go wrong --- src/cli.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index b4f312c..5e76fcb 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,5 +1,8 @@ +use std::env; + +use anyhow::Context; use clap::{Args, Parser, Subcommand, ValueEnum}; -use log::LevelFilter; +use log::{debug, trace, LevelFilter}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -18,9 +21,21 @@ pub struct Cli { impl Cli { /// Changes the current working directory to path if one is given - pub fn set_path(&self) -> anyhow::Result<()> { + pub fn update_current_working_dir(&self) -> anyhow::Result<()> { + trace!( + "Before attempting update current dir, it is: {}", + env::current_dir()?.display() + ); if let Some(path) = &self.path { - std::env::set_current_dir(path)? + debug!("Going to update working directory to to '{path}'"); + std::env::set_current_dir(path) + .with_context(|| format!("Failed to set current dir to: '{path}'"))?; + trace!( + "After updating current dir, it is: '{}'", + env::current_dir()?.display() + ); + } else { + debug!("No user supplied path found. No change") } Ok(()) } From d8f72deb72bff61f0a36d0322140f4dbad2e8007 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 00:38:16 -0400 Subject: [PATCH 013/196] Add check to ensure correct folder --- src/core/mod.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/core/mod.rs b/src/core/mod.rs index 2716506..259742b 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,7 @@ +use std::{env, path::Path}; + +use anyhow::{bail, Context}; + use crate::cli::Cli; mod code_snippet; @@ -5,7 +9,10 @@ mod daily_challenge; mod write_file; pub fn run(cli: &Cli) -> anyhow::Result<()> { - // TODO: Check for Cargo.toml to ensure we are in a valid folder + cli.update_current_working_dir()?; + + working_directory_validation()?; + // let mut args = std::env::args(); // let title_slug = if args.len() == 1 { // daily_challenge::get_daily_challenge_slug() @@ -18,3 +25,18 @@ pub fn run(cli: &Cli) -> anyhow::Result<()> { // write_file::write_file(&title_slug, code_snippet)?; Ok(()) } + +fn working_directory_validation() -> anyhow::Result<()> { + let req_file = "Cargo.toml"; + let path = Path::new(req_file); + if path.exists() { + Ok(()) + } else { + bail!( + "Failed to find {req_file} in current directory '{}'", + env::current_dir() + .context("Failed to get current working directory")? + .display() + ); + } +} From bbe56ec9e101773af16732507280630ebc04a1af Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 00:53:23 -0400 Subject: [PATCH 014/196] Change return types to anyhow --- src/core/write_file.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/write_file.rs b/src/core/write_file.rs index 4c1f866..46e8238 100644 --- a/src/core/write_file.rs +++ b/src/core/write_file.rs @@ -1,20 +1,19 @@ use convert_case::{Case, Casing}; use std::{ - error::Error, fs::{remove_file, OpenOptions}, io::Write, path::PathBuf, process::Command, }; -fn update_lib(slug_snake: &str) -> Result<(), Box> { +fn update_lib(slug_snake: &str) -> anyhow::Result<()> { let lib_path = PathBuf::from(format!("{}/../src/lib.rs", env!("CARGO_MANIFEST_DIR"))); let mut lib = OpenOptions::new().append(true).open(lib_path)?; let _ = lib.write(format!("pub mod {slug_snake};").as_bytes())?; Ok(()) } -pub fn write_file(title_slug: &str, code_snippet: String) -> Result<(), Box> { +pub fn write_file(title_slug: &str, code_snippet: String) -> anyhow::Result<()> { let slug_snake = title_slug.to_case(Case::Snake); let path = PathBuf::from(format!( "{}/../src/{slug_snake}.rs", From 3cb91b7de50484b730edf66d15f811c84de2c778 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 00:53:31 -0400 Subject: [PATCH 015/196] Connect up existing code --- src/cli.rs | 4 ++-- src/core/generation.rs | 24 ++++++++++++++++++++++++ src/core/mod.rs | 26 +++++++++----------------- 3 files changed, 35 insertions(+), 19 deletions(-) create mode 100644 src/core/generation.rs diff --git a/src/cli.rs b/src/cli.rs index 5e76fcb..08522ab 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -51,11 +51,11 @@ pub enum Commands { pub struct GenerateArgs { /// Question slug or url #[arg(short, long)] - problem: Option, + pub problem: Option, /// Set using question of the day #[arg(long, short)] - daily_challenge: bool, + pub daily_challenge: bool, } /// Exists to provide better help messages variants copied from LevelFilter as that's the type diff --git a/src/core/generation.rs b/src/core/generation.rs new file mode 100644 index 0000000..0bd47e2 --- /dev/null +++ b/src/core/generation.rs @@ -0,0 +1,24 @@ +use std::borrow::Cow; + +use crate::core::{code_snippet, daily_challenge, write_file}; + +pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> { + assert!( + args.daily_challenge ^ args.problem.is_some(), + "Invalid state. Must either be daily challenge or specific problem but not both or none" + ); + + let title_slug: Cow = if let Some(specific_problem) = &args.problem { + // Problem specified + // TODO: Parse url if specified + Cow::Borrowed(specific_problem) + } else { + // Daily problem + debug_assert!(args.daily_challenge); + Cow::Owned(daily_challenge::get_daily_challenge_slug()) + }; + + let code_snippet = code_snippet::generate_code_snippet(&title_slug); + write_file::write_file(&title_slug, code_snippet)?; + Ok(()) +} diff --git a/src/core/mod.rs b/src/core/mod.rs index 259742b..81ea3ae 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,29 +1,21 @@ -use std::{env, path::Path}; - -use anyhow::{bail, Context}; - -use crate::cli::Cli; - mod code_snippet; mod daily_challenge; +mod generation; mod write_file; +use self::generation::do_generate; +use crate::cli::Cli; +use anyhow::{bail, Context}; +use std::{env, path::Path}; + pub fn run(cli: &Cli) -> anyhow::Result<()> { cli.update_current_working_dir()?; working_directory_validation()?; - // let mut args = std::env::args(); - // let title_slug = if args.len() == 1 { - // daily_challenge::get_daily_challenge_slug() - // } else if args.len() != 2 { - // return Err("Usage: binary SLUG".into()); - // } else { - // args.nth(1).unwrap() - // }; - // let code_snippet = code_snippet::generate_code_snippet(&title_slug); - // write_file::write_file(&title_slug, code_snippet)?; - Ok(()) + match &cli.command { + crate::cli::Commands::Generate(args) => do_generate(args), + } } fn working_directory_validation() -> anyhow::Result<()> { From 0edfaef1e033f565d7678d8aa2237267299f61ea Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 01:00:47 -0400 Subject: [PATCH 016/196] Move leetcode url into config --- src/config.rs | 5 +++++ src/core/code_snippet.rs | 3 ++- src/lib.rs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/config.rs diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..1656977 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,5 @@ +pub struct Config {} + +impl Config { + pub const LEETCODE_URL: &str = "https://leetcode.com/problems/"; +} diff --git a/src/core/code_snippet.rs b/src/core/code_snippet.rs index 41e972c..97ee56d 100644 --- a/src/core/code_snippet.rs +++ b/src/core/code_snippet.rs @@ -1,3 +1,4 @@ +use crate::config::Config; use serde::Deserialize; use serde_flat_path::flat_path; @@ -59,7 +60,7 @@ fn get_test_cases(_title_slug: &str, is_design: bool) -> String { pub fn generate_code_snippet(title_slug: &str) -> String { // add URL - let mut code_snippet = format!("//! Solution for https://leetcode.com/problems/{title_slug}\n"); + let mut code_snippet = format!("//! Solution for {}{title_slug}\n", Config::LEETCODE_URL); // get code snippet let code = get_code_snippet_question(title_slug); diff --git a/src/lib.rs b/src/lib.rs index bde947c..bbead09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ mod cli; +mod config; mod core; mod leetcode_env; mod log; From 24b77ac914138c7dbeb79d7e48c2aeb6e2791961 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 01:12:29 -0400 Subject: [PATCH 017/196] Remove parts of paths that look like they should change --- src/core/write_file.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/core/write_file.rs b/src/core/write_file.rs index 46e8238..92ea43a 100644 --- a/src/core/write_file.rs +++ b/src/core/write_file.rs @@ -15,10 +15,7 @@ fn update_lib(slug_snake: &str) -> anyhow::Result<()> { pub fn write_file(title_slug: &str, code_snippet: String) -> anyhow::Result<()> { let slug_snake = title_slug.to_case(Case::Snake); - let path = PathBuf::from(format!( - "{}/../src/{slug_snake}.rs", - env!("CARGO_MANIFEST_DIR") - )); + let path = PathBuf::from(format!("src/{slug_snake}.rs")); let mut file = OpenOptions::new() .write(true) .create_new(true) @@ -33,7 +30,6 @@ pub fn write_file(title_slug: &str, code_snippet: String) -> anyhow::Result<()> Command::new("cargo") .arg("fmt") .arg("--all") - .current_dir(format!("{}/../", env!("CARGO_MANIFEST_DIR"))) .spawn()? .wait()?; Ok(()) From 9bc6e1e918b1621da9f50a4d73470496fbf37a98 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 01:12:55 -0400 Subject: [PATCH 018/196] Move GraphQL link into config --- src/config.rs | 1 + src/core/code_snippet.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config.rs b/src/config.rs index 1656977..5caa289 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,4 +2,5 @@ pub struct Config {} impl Config { pub const LEETCODE_URL: &str = "https://leetcode.com/problems/"; + pub const LEETCODE_GRAPH_QL: &str = "https://leetcode.com/graphql/"; } diff --git a/src/core/code_snippet.rs b/src/core/code_snippet.rs index 97ee56d..97093b5 100644 --- a/src/core/code_snippet.rs +++ b/src/core/code_snippet.rs @@ -15,7 +15,7 @@ struct CodeSnippet { } fn get_code_snippet_question(title_slug: &str) -> String { - let code_snippets_res = ureq::get("https://leetcode.com/graphql/") + let code_snippets_res = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ "query": r#"query questionEditorData($titleSlug: String!) { question(titleSlug: $titleSlug) { From ddc9af7f5cfe4445e9ce3fd505a2919e8fceed56 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 01:15:14 -0400 Subject: [PATCH 019/196] Add TODOs to document what needs to be done --- src/core/code_snippet.rs | 4 ++++ src/core/generation.rs | 4 +++- src/core/write_file.rs | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core/code_snippet.rs b/src/core/code_snippet.rs index 97093b5..b56e71c 100644 --- a/src/core/code_snippet.rs +++ b/src/core/code_snippet.rs @@ -2,6 +2,8 @@ use crate::config::Config; use serde::Deserialize; use serde_flat_path::flat_path; +// TODO: Add logging to all functions + #[flat_path] #[derive(Deserialize)] struct CodeSnippetResponse { @@ -15,6 +17,7 @@ struct CodeSnippet { } fn get_code_snippet_question(title_slug: &str) -> String { + // TODO: Change return type to be a Result let code_snippets_res = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ "query": r#"query questionEditorData($titleSlug: String!) { @@ -45,6 +48,7 @@ fn get_test_cases(_title_slug: &str, is_design: bool) -> String { "# .to_string() } else { + // TODO: Get test cases for design problems "".to_string() }; format!( diff --git a/src/core/generation.rs b/src/core/generation.rs index 0bd47e2..9ac6f6a 100644 --- a/src/core/generation.rs +++ b/src/core/generation.rs @@ -2,6 +2,8 @@ use std::borrow::Cow; use crate::core::{code_snippet, daily_challenge, write_file}; +// TODO: Add logging to all functions + pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> { assert!( args.daily_challenge ^ args.problem.is_some(), @@ -10,7 +12,7 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> let title_slug: Cow = if let Some(specific_problem) = &args.problem { // Problem specified - // TODO: Parse url if specified + // TODO: Parse url if given instead of slug Cow::Borrowed(specific_problem) } else { // Daily problem diff --git a/src/core/write_file.rs b/src/core/write_file.rs index 92ea43a..ff31993 100644 --- a/src/core/write_file.rs +++ b/src/core/write_file.rs @@ -6,6 +6,8 @@ use std::{ process::Command, }; +// TODO: Add logging to all functions + fn update_lib(slug_snake: &str) -> anyhow::Result<()> { let lib_path = PathBuf::from(format!("{}/../src/lib.rs", env!("CARGO_MANIFEST_DIR"))); let mut lib = OpenOptions::new().append(true).open(lib_path)?; @@ -15,6 +17,7 @@ fn update_lib(slug_snake: &str) -> anyhow::Result<()> { pub fn write_file(title_slug: &str, code_snippet: String) -> anyhow::Result<()> { let slug_snake = title_slug.to_case(Case::Snake); + // TODO: Find way to specify desired new file name from a config let path = PathBuf::from(format!("src/{slug_snake}.rs")); let mut file = OpenOptions::new() .write(true) @@ -27,6 +30,7 @@ pub fn write_file(title_slug: &str, code_snippet: String) -> anyhow::Result<()> remove_file(path)?; output?; } + // TODO: Check if there is a simpler way to do this Command::new("cargo") .arg("fmt") .arg("--all") From 64746e5b706e032e720135020ea4d8c15639aa8e Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 02:03:58 -0400 Subject: [PATCH 020/196] Convert from url to slug --- src/config.rs | 3 ++- src/core/code_snippet.rs | 5 ++++- src/core/generation.rs | 39 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/config.rs b/src/config.rs index 5caa289..0f774f4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ pub struct Config {} impl Config { - pub const LEETCODE_URL: &str = "https://leetcode.com/problems/"; + // URLs Must include trailing "/" + pub const LEETCODE_PROBLEM_URL: &str = "https://leetcode.com/problems/"; pub const LEETCODE_GRAPH_QL: &str = "https://leetcode.com/graphql/"; } diff --git a/src/core/code_snippet.rs b/src/core/code_snippet.rs index b56e71c..1bd80f7 100644 --- a/src/core/code_snippet.rs +++ b/src/core/code_snippet.rs @@ -64,7 +64,10 @@ fn get_test_cases(_title_slug: &str, is_design: bool) -> String { pub fn generate_code_snippet(title_slug: &str) -> String { // add URL - let mut code_snippet = format!("//! Solution for {}{title_slug}\n", Config::LEETCODE_URL); + let mut code_snippet = format!( + "//! Solution for {}{title_slug}\n", + Config::LEETCODE_PROBLEM_URL + ); // get code snippet let code = get_code_snippet_question(title_slug); diff --git a/src/core/generation.rs b/src/core/generation.rs index 9ac6f6a..8bad46c 100644 --- a/src/core/generation.rs +++ b/src/core/generation.rs @@ -1,19 +1,36 @@ use std::borrow::Cow; -use crate::core::{code_snippet, daily_challenge, write_file}; +use anyhow::bail; +use log::debug; + +use crate::{ + config::Config, + core::{code_snippet, daily_challenge, write_file}, +}; // TODO: Add logging to all functions pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> { assert!( args.daily_challenge ^ args.problem.is_some(), + // This shouldn't happen, should be enforced by clap "Invalid state. Must either be daily challenge or specific problem but not both or none" ); let title_slug: Cow = if let Some(specific_problem) = &args.problem { // Problem specified // TODO: Parse url if given instead of slug - Cow::Borrowed(specific_problem) + if specific_problem.contains('/') { + // Working with a url + debug!("Using '{specific_problem}' as a url"); + let slug = url_to_slug(specific_problem)?; + debug!("Extracted slug '{slug}' from url"); + Cow::Owned(slug) + } else { + // This is expected to be a valid slug + debug!("Using '{specific_problem}' as a slug"); + Cow::Borrowed(specific_problem) + } } else { // Daily problem debug_assert!(args.daily_challenge); @@ -21,6 +38,22 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> }; let code_snippet = code_snippet::generate_code_snippet(&title_slug); - write_file::write_file(&title_slug, code_snippet)?; + // TODO: Enable writing after debugging + //write_file::write_file(&title_slug, code_snippet)?; Ok(()) } + +fn url_to_slug(url: &str) -> anyhow::Result { + assert!(Config::LEETCODE_PROBLEM_URL.ends_with('/')); + if !url.starts_with(Config::LEETCODE_PROBLEM_URL) { + bail!( + "Expected a leetcode url that starts with '{}' but got '{url}'", + Config::LEETCODE_PROBLEM_URL + ) + } + let split_url: Vec<_> = url.split('/').collect(); + let split_prefix: Vec<_> = Config::LEETCODE_PROBLEM_URL.split('/').collect(); + dbg!(&split_url, &split_prefix); + debug_assert!(split_prefix.len() < split_url.len()); + Ok(split_url[split_prefix.len() - 1].to_string()) +} From 26a0370fe682a0b3f47f748c78ee5f090827b5aa Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 02:13:10 -0400 Subject: [PATCH 021/196] Add where logs come from Getting quite a few if using debug rn so going to change those in this module to info. Side stepping the problem for now. Can be filtered out instead. --- src/log.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/log.rs b/src/log.rs index bf0dc36..14f8151 100644 --- a/src/log.rs +++ b/src/log.rs @@ -8,7 +8,7 @@ use log4rs::{ pub fn init_logging(log_level: LevelFilter) -> anyhow::Result<()> { let stdout = ConsoleAppender::builder() - .encoder(Box::new(PatternEncoder::new("{h({l} - {m})}{n}"))) + .encoder(Box::new(PatternEncoder::new("{h({M} {l} - {m})}{n}"))) .build(); let config = log4rs::Config::builder() From 12608102e8d9c6b26fbd6e9b7a23a2c6c67748ea Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 02:24:44 -0400 Subject: [PATCH 022/196] Change all log messages to info To side step problem of other creates logs showing up --- src/cli.rs | 10 +++++----- src/core/generation.rs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 08522ab..00235b7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,7 +2,7 @@ use std::env; use anyhow::Context; use clap::{Args, Parser, Subcommand, ValueEnum}; -use log::{debug, trace, LevelFilter}; +use log::{info, LevelFilter}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -22,20 +22,20 @@ pub struct Cli { impl Cli { /// Changes the current working directory to path if one is given pub fn update_current_working_dir(&self) -> anyhow::Result<()> { - trace!( + info!( "Before attempting update current dir, it is: {}", env::current_dir()?.display() ); if let Some(path) = &self.path { - debug!("Going to update working directory to to '{path}'"); + info!("Going to update working directory to to '{path}'"); std::env::set_current_dir(path) .with_context(|| format!("Failed to set current dir to: '{path}'"))?; - trace!( + info!( "After updating current dir, it is: '{}'", env::current_dir()?.display() ); } else { - debug!("No user supplied path found. No change") + info!("No user supplied path found. No change") } Ok(()) } diff --git a/src/core/generation.rs b/src/core/generation.rs index 8bad46c..c95a1a3 100644 --- a/src/core/generation.rs +++ b/src/core/generation.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use anyhow::bail; -use log::debug; +use log::info; use crate::{ config::Config, @@ -22,13 +22,13 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> // TODO: Parse url if given instead of slug if specific_problem.contains('/') { // Working with a url - debug!("Using '{specific_problem}' as a url"); + info!("Using '{specific_problem}' as a url"); let slug = url_to_slug(specific_problem)?; - debug!("Extracted slug '{slug}' from url"); + info!("Extracted slug '{slug}' from url"); Cow::Owned(slug) } else { // This is expected to be a valid slug - debug!("Using '{specific_problem}' as a slug"); + info!("Using '{specific_problem}' as a slug"); Cow::Borrowed(specific_problem) } } else { From 8fa86806fdca8a48db885fce4fc78d270ad8416e Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 02:45:30 -0400 Subject: [PATCH 023/196] Fix path missed --- src/core/write_file.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/core/write_file.rs b/src/core/write_file.rs index ff31993..df8d4b2 100644 --- a/src/core/write_file.rs +++ b/src/core/write_file.rs @@ -6,12 +6,11 @@ use std::{ process::Command, }; -// TODO: Add logging to all functions - -fn update_lib(slug_snake: &str) -> anyhow::Result<()> { - let lib_path = PathBuf::from(format!("{}/../src/lib.rs", env!("CARGO_MANIFEST_DIR"))); +fn update_lib(module_name: &str) -> anyhow::Result<()> { + info!("Adding {module_name} to libs.rs"); + let lib_path = PathBuf::from("src/lib.rs"); let mut lib = OpenOptions::new().append(true).open(lib_path)?; - let _ = lib.write(format!("pub mod {slug_snake};").as_bytes())?; + let _ = lib.write(format!("pub mod {module_name};").as_bytes())?; Ok(()) } From 9c64407bae6c0dca296e7dcec501394bccb91c28 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 02:46:03 -0400 Subject: [PATCH 024/196] Add logging to functions --- src/core/code_snippet.rs | 8 +++++--- src/core/generation.rs | 7 +++---- src/core/write_file.rs | 22 ++++++++++++++-------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/core/code_snippet.rs b/src/core/code_snippet.rs index 1bd80f7..a3cdea3 100644 --- a/src/core/code_snippet.rs +++ b/src/core/code_snippet.rs @@ -1,9 +1,8 @@ use crate::config::Config; +use log::info; use serde::Deserialize; use serde_flat_path::flat_path; -// TODO: Add logging to all functions - #[flat_path] #[derive(Deserialize)] struct CodeSnippetResponse { @@ -18,6 +17,7 @@ struct CodeSnippet { fn get_code_snippet_question(title_slug: &str) -> String { // TODO: Change return type to be a Result + info!("Going to get code for {title_slug}"); let code_snippets_res = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ "query": r#"query questionEditorData($titleSlug: String!) { @@ -41,7 +41,8 @@ fn get_code_snippet_question(title_slug: &str) -> String { .unwrap() } -fn get_test_cases(_title_slug: &str, is_design: bool) -> String { +fn get_test_cases(title_slug: &str, is_design: bool) -> String { + info!("Going to get tests for {title_slug}"); let tests = if is_design { r#" use rstest::rstest; @@ -63,6 +64,7 @@ fn get_test_cases(_title_slug: &str, is_design: bool) -> String { } pub fn generate_code_snippet(title_slug: &str) -> String { + info!("Building code snippet for {title_slug}"); // add URL let mut code_snippet = format!( "//! Solution for {}{title_slug}\n", diff --git a/src/core/generation.rs b/src/core/generation.rs index c95a1a3..7e96b22 100644 --- a/src/core/generation.rs +++ b/src/core/generation.rs @@ -8,8 +8,6 @@ use crate::{ core::{code_snippet, daily_challenge, write_file}, }; -// TODO: Add logging to all functions - pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> { assert!( args.daily_challenge ^ args.problem.is_some(), @@ -34,7 +32,9 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> } else { // Daily problem debug_assert!(args.daily_challenge); - Cow::Owned(daily_challenge::get_daily_challenge_slug()) + let slug = daily_challenge::get_daily_challenge_slug(); + info!("Slug for daily problem is: '{slug}'"); + Cow::Owned(slug) }; let code_snippet = code_snippet::generate_code_snippet(&title_slug); @@ -53,7 +53,6 @@ fn url_to_slug(url: &str) -> anyhow::Result { } let split_url: Vec<_> = url.split('/').collect(); let split_prefix: Vec<_> = Config::LEETCODE_PROBLEM_URL.split('/').collect(); - dbg!(&split_url, &split_prefix); debug_assert!(split_prefix.len() < split_url.len()); Ok(split_url[split_prefix.len() - 1].to_string()) } diff --git a/src/core/write_file.rs b/src/core/write_file.rs index df8d4b2..089e45b 100644 --- a/src/core/write_file.rs +++ b/src/core/write_file.rs @@ -1,4 +1,6 @@ +use anyhow::Context; use convert_case::{Case, Casing}; +use log::{info, warn}; use std::{ fs::{remove_file, OpenOptions}, io::Write, @@ -15,25 +17,29 @@ fn update_lib(module_name: &str) -> anyhow::Result<()> { } pub fn write_file(title_slug: &str, code_snippet: String) -> anyhow::Result<()> { + info!("Writing code to disk for {title_slug}"); let slug_snake = title_slug.to_case(Case::Snake); - // TODO: Find way to specify desired new file name from a config - let path = PathBuf::from(format!("src/{slug_snake}.rs")); + let module_name = slug_snake; // TODO: Find way to specify desired new file name from a config + info!("Module name is: {module_name}"); + let path = PathBuf::from(format!("src/{module_name}.rs")); let mut file = OpenOptions::new() .write(true) .create_new(true) .open(path.clone())?; file.write_all(code_snippet.as_bytes())?; - let output = update_lib(&slug_snake); - if output.is_err() { + let lib_update_status = update_lib(&module_name); + if lib_update_status.is_err() { + warn!("Cleaning up after updating lib.rs failed"); // clean up remove_file(path)?; - output?; + lib_update_status?; } - // TODO: Check if there is a simpler way to do this + + info!("Going to run rustfmt on files"); Command::new("cargo") .arg("fmt") .arg("--all") - .spawn()? - .wait()?; + .output() + .context("Error running rustfmt")?; Ok(()) } From 9b085d730970a6523c1fbb13fb48daeaa5758347 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 02:49:30 -0400 Subject: [PATCH 025/196] Enable writing out files --- src/core/generation.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/generation.rs b/src/core/generation.rs index 7e96b22..c77b8d9 100644 --- a/src/core/generation.rs +++ b/src/core/generation.rs @@ -38,8 +38,7 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> }; let code_snippet = code_snippet::generate_code_snippet(&title_slug); - // TODO: Enable writing after debugging - //write_file::write_file(&title_slug, code_snippet)?; + write_file::write_file(&title_slug, code_snippet)?; Ok(()) } From a3b58263c18ded8a0129b992471806f316073f0c Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 03:13:04 -0400 Subject: [PATCH 026/196] Add install instructions to readme --- README.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 799fd74..7a4ad6b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,28 @@ ## Leetcode local dev assistant - - A program that given the link to a leetcode problem ,creates a local file where you can develop your solution and then post it back to leetcode. + + A program that given the link to a leetcode problem, + creates a local file where you can develop your solution and then post it back to leetcode. + +## Installation + +NB: Installing from another source overwrites if it is already installed + +### From GitHub + +```sh +cargo install --git https://github.com/rust-practice/cargo-leet.git --branch main +``` + +### From Clone + +After cloning the repo run + +```sh +cargo install --path . +``` + +## Uninstallation + +```sh +cargo uninstall cargo-leet +``` \ No newline at end of file From 385ab3b558274249cf1798d55a1e6fa4fd50215c Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 03:26:43 -0400 Subject: [PATCH 027/196] Update CLI to work with cargo properly Based on example https://docs.rs/clap/latest/clap/_derive/_cookbook/cargo_example_derive/ --- src/cli.rs | 9 ++++++++- src/lib.rs | 2 +- src/main.rs | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 00235b7..3d3cb27 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,7 +4,14 @@ use anyhow::Context; use clap::{Args, Parser, Subcommand, ValueEnum}; use log::{info, LevelFilter}; -#[derive(Parser, Debug)] +// Taken from example https://docs.rs/clap/latest/clap/_derive/_cookbook/cargo_example_derive/ +#[derive(Parser)] +#[command(name = "cargo")] +#[command(bin_name = "cargo")] +pub enum CargoCli { + Leet(Cli), +} +#[derive(Args, Debug)] #[command(author, version, about, long_about = None)] pub struct Cli { #[command(subcommand)] diff --git a/src/lib.rs b/src/lib.rs index bbead09..a9c0c33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,4 +13,4 @@ pub use leetcode_env::tree::TreeRoot; // For use in main.rs pub use crate::core::run; pub use crate::log::init_logging; -pub use cli::Cli; +pub use cli::CargoCli; diff --git a/src/main.rs b/src/main.rs index a754442..8a3ee25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ -use cargo_leet::{init_logging, run, Cli}; +use cargo_leet::{init_logging, run, CargoCli}; use clap::Parser; fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); + let CargoCli::Leet(cli) = CargoCli::parse(); init_logging(cli.log_level.into())?; run(&cli) } From 84c56dc06c8bc6ce3ccf3ad66db0dd17c831dcfe Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 03:30:15 -0400 Subject: [PATCH 028/196] Update test Test had to be updated as top of parser had changed when moving to proper cargo subcommand --- src/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli.rs b/src/cli.rs index 3d3cb27..9474859 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -100,6 +100,6 @@ mod tests { // Source: https://docs.rs/clap/latest/clap/_derive/_tutorial/index.html#testing // My understanding it reports most development errors without additional effort use clap::CommandFactory; - Cli::command().debug_assert() + CargoCli::command().debug_assert() } } From 30d3ebfdc06b8372d94dd9595de99ff2b33d6fd1 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 03:49:27 -0400 Subject: [PATCH 029/196] Add screenshots --- README.md | 8 ++++++++ assets/help_scr_shot_generate.png | Bin 0 -> 18215 bytes assets/help_scr_shot_top.png | Bin 0 -> 42798 bytes 3 files changed, 8 insertions(+) create mode 100644 assets/help_scr_shot_generate.png create mode 100644 assets/help_scr_shot_top.png diff --git a/README.md b/README.md index 7a4ad6b..60c3b17 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,14 @@ A program that given the link to a leetcode problem, creates a local file where you can develop your solution and then post it back to leetcode. + ## ScreenShots + + ### `cargo leet` + ![ScreenShot](assets/help_scr_shot_top.png) + + ### `cargo leet generate --help` + ![ScreenShot](assets/help_scr_shot_generate.png) + ## Installation NB: Installing from another source overwrites if it is already installed diff --git a/assets/help_scr_shot_generate.png b/assets/help_scr_shot_generate.png new file mode 100644 index 0000000000000000000000000000000000000000..54f9a8a72d36c398d9e87f58d7e37937de15baf0 GIT binary patch literal 18215 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 EA1Zv-hyVZp literal 0 HcmV?d00001 From fca7be396c1d6f6796ec91b7e6c781cc322f9f96 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 03:59:00 -0400 Subject: [PATCH 030/196] Remove completed todo --- src/core/generation.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/generation.rs b/src/core/generation.rs index c77b8d9..d040abf 100644 --- a/src/core/generation.rs +++ b/src/core/generation.rs @@ -17,7 +17,6 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> let title_slug: Cow = if let Some(specific_problem) = &args.problem { // Problem specified - // TODO: Parse url if given instead of slug if specific_problem.contains('/') { // Working with a url info!("Using '{specific_problem}' as a url"); From 8d19c1d7043843e5652b131b11b6669a1e6cdc96 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 04:07:16 -0400 Subject: [PATCH 031/196] Add license --- LICENSE-APACHE | 200 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 19 +++++ README.md | 20 ++++- 3 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..4fe2d18 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,200 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..9cf1062 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 60c3b17..3310363 100644 --- a/README.md +++ b/README.md @@ -33,4 +33,22 @@ cargo install --path . ```sh cargo uninstall cargo-leet -``` \ No newline at end of file +``` + +## License + +All code in this repository is dual-licensed under either: + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. +This means you can select the license you prefer! +This dual-licensing approach is the de-facto standard in the Rust ecosystem and there are very good reasons to include both as noted in +this [issue](https://github.com/bevyengine/bevy/issues/2373) on [Bevy](https://bevyengine.org)'s repo. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. From 4bfd0f0c09f36adce2b1fabd36a63b76fbe82c3c Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 04:07:34 -0400 Subject: [PATCH 032/196] Add more package metadata --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index cacbf2c..16cf29b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,11 @@ [package] name = "cargo-leet" description = "Utility program to help with working on leetcode locally" +repository = "https://github.com/rust-practice/cargo-leet" version = "0.1.0" +authors = ["Members of Rust Practice Discord Server"] +readme = "README.md" +license = "MIT OR Apache-2.0" edition = "2021" [dependencies] From d503a5d17d5c5e4bf735dc1a58690d29454ddb36 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 20:35:58 -0400 Subject: [PATCH 033/196] Simplify arguments "-d" was not sufficient value for the added complexity. It created the possibility for invalid states of the cli struct without corresponding value. While this was "protected" by clap config, it still created more overhead for the user. --- assets/help_scr_shot_generate.png | Bin 18215 -> 19353 bytes src/cli.rs | 7 +------ src/core/generation.rs | 7 ------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/assets/help_scr_shot_generate.png b/assets/help_scr_shot_generate.png index 54f9a8a72d36c398d9e87f58d7e37937de15baf0..e504183f205afe920f44993ba75be034a53dd557 100644 GIT binary patch literal 19353 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, - - /// Set using question of the day - #[arg(long, short)] - pub daily_challenge: bool, } /// Exists to provide better help messages variants copied from LevelFilter as that's the type diff --git a/src/core/generation.rs b/src/core/generation.rs index d040abf..d837f54 100644 --- a/src/core/generation.rs +++ b/src/core/generation.rs @@ -9,12 +9,6 @@ use crate::{ }; pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> { - assert!( - args.daily_challenge ^ args.problem.is_some(), - // This shouldn't happen, should be enforced by clap - "Invalid state. Must either be daily challenge or specific problem but not both or none" - ); - let title_slug: Cow = if let Some(specific_problem) = &args.problem { // Problem specified if specific_problem.contains('/') { @@ -30,7 +24,6 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> } } else { // Daily problem - debug_assert!(args.daily_challenge); let slug = daily_challenge::get_daily_challenge_slug(); info!("Slug for daily problem is: '{slug}'"); Cow::Owned(slug) From 2821d63f3f86866e1a00c91ae950d4d158a0bea2 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 22:44:02 -0400 Subject: [PATCH 034/196] Pull out log4rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Causing issue in advisory checking error[vulnerability]: Potential segfault in the time crate time v0.1.45 └── chrono v0.4.26 └── log4rs v1.2.0 └── cargo-leet v0.1.0 --- Cargo.lock | 345 ----------------------------------------------------- Cargo.toml | 1 - src/log.rs | 16 +-- 3 files changed, 1 insertion(+), 361 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e786cc2..8d4dacd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,21 +8,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anstream" version = "0.3.2" @@ -78,18 +63,6 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" -[[package]] -name = "arc-swap" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - [[package]] name = "base64" version = "0.13.1" @@ -116,7 +89,6 @@ dependencies = [ "clap", "convert_case", "log", - "log4rs", "serde", "serde_flat_path", "ureq", @@ -134,21 +106,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "time", - "wasm-bindgen", - "winapi", -] - [[package]] name = "clap" version = "4.3.3" @@ -207,12 +164,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - [[package]] name = "crc32fast" version = "1.3.2" @@ -222,23 +173,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "destructure_traitobject" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" - [[package]] name = "errno" version = "0.3.1" @@ -270,12 +204,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "form_urlencoded" version = "1.2.0" @@ -285,12 +213,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "heck" version = "0.4.1" @@ -303,35 +225,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "iana-time-zone" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "idna" version = "0.4.0" @@ -342,16 +235,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -396,68 +279,17 @@ version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" -[[package]] -name = "lock_api" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "log" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" -dependencies = [ - "serde", -] - -[[package]] -name = "log-mdc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" - -[[package]] -name = "log4rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36ca1786d9e79b8193a68d480a0907b612f109537115c6ff655a3a1967533fd" -dependencies = [ - "anyhow", - "arc-swap", - "chrono", - "derivative", - "fnv", - "humantime", - "libc", - "log", - "log-mdc", - "parking_lot", - "serde", - "serde-value", - "serde_json", - "serde_yaml", - "thiserror", - "thread-id", - "typemap-ors", - "winapi", -] [[package]] name = "miniz_oxide" @@ -468,53 +300,12 @@ dependencies = [ "adler", ] -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "ordered-float" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" -dependencies = [ - "num-traits", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.3.5", - "smallvec", - "windows-targets", -] - [[package]] name = "percent-encoding" version = "2.3.0" @@ -539,24 +330,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - [[package]] name = "ring" version = "0.16.20" @@ -604,12 +377,6 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - [[package]] name = "sct" version = "0.7.0" @@ -629,16 +396,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.164" @@ -672,24 +429,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" -dependencies = [ - "indexmap", - "ryu", - "serde", - "yaml-rust", -] - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - [[package]] name = "spin" version = "0.5.2" @@ -724,48 +463,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.18", -] - -[[package]] -name = "thread-id" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee93aa2b8331c0fec9091548843f2c90019571814057da3b783f9de09349d73" -dependencies = [ - "libc", - "redox_syscall 0.2.16", - "winapi", -] - -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi", - "winapi", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -781,15 +478,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "typemap-ors" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" -dependencies = [ - "unsafe-any-ors", -] - [[package]] name = "unicode-bidi" version = "0.3.13" @@ -817,15 +505,6 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" -[[package]] -name = "unsafe-any-ors" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" -dependencies = [ - "destructure_traitobject", -] - [[package]] name = "untrusted" version = "0.7.1" @@ -867,12 +546,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasm-bindgen" version = "0.2.86" @@ -978,15 +651,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -1052,12 +716,3 @@ name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] diff --git a/Cargo.toml b/Cargo.toml index 16cf29b..fbfb78d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ anyhow = "1.0.71" clap = { version = "4.3.3", features = ["derive", "cargo"] } convert_case = "0.6" log = "0.4.18" -log4rs = "1.2.0" serde = { version = "1.0.164", features = ["derive"] } serde_flat_path = "0.1.2" ureq = { version = "2.6", features = ["json"] } diff --git a/src/log.rs b/src/log.rs index 14f8151..908dbfa 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,19 +1,5 @@ -use anyhow::Context; use log::LevelFilter; -use log4rs::{ - append::console::ConsoleAppender, - config::{Appender, Root}, - encode::pattern::PatternEncoder, -}; -pub fn init_logging(log_level: LevelFilter) -> anyhow::Result<()> { - let stdout = ConsoleAppender::builder() - .encoder(Box::new(PatternEncoder::new("{h({M} {l} - {m})}{n}"))) - .build(); - - let config = log4rs::Config::builder() - .appender(Appender::builder().build("stdout", Box::new(stdout))) - .build(Root::builder().appender("stdout").build(log_level))?; - log4rs::init_config(config).context("Failed to initialize logging")?; +pub fn init_logging(_log_level: LevelFilter) -> anyhow::Result<()> { Ok(()) } From d45e33478f8185a19bac55a630689b03183a5dc5 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 22:48:09 -0400 Subject: [PATCH 035/196] Cargo deny init --- deny.toml | 269 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 deny.toml diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..c32be25 --- /dev/null +++ b/deny.toml @@ -0,0 +1,269 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "deny" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + #"MIT", + #"Apache-2.0", + #"Apache-2.0 WITH LLVM-exception", +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "warn" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The name of the crate the clarification applies to +#name = "ring" +# The optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + #{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overriden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overriden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#name = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +github = [""] +# 1 or more gitlab.com organizations to allow git sources for +gitlab = [""] +# 1 or more bitbucket.org organizations to allow git sources for +bitbucket = [""] From 9f09676c53e05bf6c65a628a520aececda4be76f Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 23:04:49 -0400 Subject: [PATCH 036/196] Prevent licenses from causing CI to fail --- deny.toml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/deny.toml b/deny.toml index c32be25..17bf962 100644 --- a/deny.toml +++ b/deny.toml @@ -66,7 +66,7 @@ vulnerability = "deny" # The lint level for unmaintained crates unmaintained = "warn" # The lint level for crates that have been yanked from their source registry -yanked = "warn" +yanked = "deny" # The lint level for crates with security notices. Note that as of # 2019-12-17 there are no security notice advisories in # https://github.com/rustsec/advisory-db @@ -97,15 +97,11 @@ ignore = [ # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] # The lint level for crates which do not have a detectable license -unlicensed = "deny" +unlicensed = "warn" # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. -allow = [ - #"MIT", - #"Apache-2.0", - #"Apache-2.0 WITH LLVM-exception", -] +allow = ["MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception"] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. @@ -125,7 +121,7 @@ allow-osi-fsf-free = "neither" # 1. License isn't in the allow or deny lists # 2. License isn't copyleft # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" -default = "deny" +default = "warn" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. @@ -155,8 +151,8 @@ exceptions = [ # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration #license-files = [ - # Each entry is a crate relative path, and the (opaque) hash of its contents - #{ path = "LICENSE", hash = 0xbd0eed23 } +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } #] [licenses.private] From 848ab7a5af696da0ce57d6d367d85be9ec3bd707 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 23:39:44 -0400 Subject: [PATCH 037/196] Plug in replacement logger --- Cargo.lock | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/log.rs | 4 +++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8d4dacd..58fb16b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.3.2" @@ -88,6 +97,7 @@ dependencies = [ "anyhow", "clap", "convert_case", + "env_logger", "log", "serde", "serde_flat_path", @@ -173,6 +183,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "errno" version = "0.3.1" @@ -225,6 +248,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "idna" version = "0.4.0" @@ -291,6 +320,12 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -330,6 +365,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" + [[package]] name = "ring" version = "0.16.20" @@ -463,6 +515,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -645,6 +706,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index fbfb78d..37cebb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" anyhow = "1.0.71" clap = { version = "4.3.3", features = ["derive", "cargo"] } convert_case = "0.6" +env_logger = "0.10.0" log = "0.4.18" serde = { version = "1.0.164", features = ["derive"] } serde_flat_path = "0.1.2" diff --git a/src/log.rs b/src/log.rs index 908dbfa..6e30bdb 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,5 +1,7 @@ +use env_logger::Builder; use log::LevelFilter; -pub fn init_logging(_log_level: LevelFilter) -> anyhow::Result<()> { +pub fn init_logging(level: LevelFilter) -> anyhow::Result<()> { + Builder::new().filter(None, level).try_init()?; Ok(()) } From 2f81d11c92ffff860c2c2f7db4b897bfade6ee0b Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Sun, 11 Jun 2023 23:54:32 -0400 Subject: [PATCH 038/196] Change return types to result Make functions that can fail fallible. Remove avoidable panics and add context where possible to the best of my understanding. --- src/core/code_snippet.rs | 30 +++++++++++++++++------------- src/core/daily_challenge.rs | 10 +++++----- src/core/generation.rs | 4 ++-- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/core/code_snippet.rs b/src/core/code_snippet.rs index a3cdea3..97cc878 100644 --- a/src/core/code_snippet.rs +++ b/src/core/code_snippet.rs @@ -1,4 +1,5 @@ use crate::config::Config; +use anyhow::{bail, Context}; use log::info; use serde::Deserialize; use serde_flat_path::flat_path; @@ -15,8 +16,7 @@ struct CodeSnippet { code: String, } -fn get_code_snippet_question(title_slug: &str) -> String { - // TODO: Change return type to be a Result +fn get_code_snippet_question(title_slug: &str) -> anyhow::Result { info!("Going to get code for {title_slug}"); let code_snippets_res = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ @@ -31,17 +31,21 @@ fn get_code_snippet_question(title_slug: &str) -> String { "variables":{"titleSlug": title_slug}, "operationName":"questionEditorData" })) - .unwrap() + .context("Get request for code_snippet failed")? .into_json::() - .unwrap(); - code_snippets_res + .context("Failed to convert codes_snippet response from json")?; + + match code_snippets_res .code_snippets .into_iter() .find_map(|cs| (cs.lang == "Rust").then_some(cs.code)) - .unwrap() + { + Some(result) => Ok(result), + None => bail!("Rust not supported for this problem"), + } } -fn get_test_cases(title_slug: &str, is_design: bool) -> String { +fn get_test_cases(title_slug: &str, is_design: bool) -> anyhow::Result { info!("Going to get tests for {title_slug}"); let tests = if is_design { r#" @@ -52,7 +56,7 @@ fn get_test_cases(title_slug: &str, is_design: bool) -> String { // TODO: Get test cases for design problems "".to_string() }; - format!( + Ok(format!( r#" #[cfg(test)] mod tests {{ @@ -60,10 +64,10 @@ fn get_test_cases(title_slug: &str, is_design: bool) -> String { {tests} }} "# - ) + )) } -pub fn generate_code_snippet(title_slug: &str) -> String { +pub fn generate_code_snippet(title_slug: &str) -> anyhow::Result { info!("Building code snippet for {title_slug}"); // add URL let mut code_snippet = format!( @@ -72,7 +76,7 @@ pub fn generate_code_snippet(title_slug: &str) -> String { ); // get code snippet - let code = get_code_snippet_question(title_slug); + let code = get_code_snippet_question(title_slug)?; code_snippet.push_str(&code); // handle non design snippets @@ -82,7 +86,7 @@ pub fn generate_code_snippet(title_slug: &str) -> String { } // add tests - let test = get_test_cases(title_slug, is_design); + let test = get_test_cases(title_slug, is_design)?; code_snippet.push_str(&test); - code_snippet + Ok(code_snippet) } diff --git a/src/core/daily_challenge.rs b/src/core/daily_challenge.rs index 2174682..e64929f 100644 --- a/src/core/daily_challenge.rs +++ b/src/core/daily_challenge.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use serde::Deserialize; use serde_flat_path::flat_path; @@ -8,8 +9,7 @@ struct DailyChallengeResponse { title_slug: String, } -pub fn get_daily_challenge_slug() -> String { - // TODO: Change return type to anyhow and add context for each error +pub fn get_daily_challenge_slug() -> anyhow::Result { let daily_challenge_response = ureq::get("https://leetcode.com/graphql/") .send_json(ureq::json!({ "query": r#"query questionOfToday { @@ -22,8 +22,8 @@ pub fn get_daily_challenge_slug() -> String { "variables":{}, "operationName":"questionOfToday" })) - .unwrap() + .context("Get request for daily challenge failed")? .into_json::() - .unwrap(); - daily_challenge_response.title_slug + .context("Failed to convert response for daily challenge from json")?; + Ok(daily_challenge_response.title_slug) } diff --git a/src/core/generation.rs b/src/core/generation.rs index d837f54..9e42b42 100644 --- a/src/core/generation.rs +++ b/src/core/generation.rs @@ -24,12 +24,12 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> } } else { // Daily problem - let slug = daily_challenge::get_daily_challenge_slug(); + let slug = daily_challenge::get_daily_challenge_slug()?; info!("Slug for daily problem is: '{slug}'"); Cow::Owned(slug) }; - let code_snippet = code_snippet::generate_code_snippet(&title_slug); + let code_snippet = code_snippet::generate_code_snippet(&title_slug)?; write_file::write_file(&title_slug, code_snippet)?; Ok(()) } From d8d822760ff177bd4d03b1d16b1da8967b837d1d Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Mon, 12 Jun 2023 19:59:10 -0400 Subject: [PATCH 039/196] Restructure to match command line subcommands --- src/core/{generation.rs => generate.rs} | 2 +- src/core/{ => helpers}/code_snippet.rs | 0 src/core/{ => helpers}/daily_challenge.rs | 0 src/core/helpers/mod.rs | 3 +++ src/core/{ => helpers}/write_file.rs | 0 src/core/mod.rs | 8 +++----- 6 files changed, 7 insertions(+), 6 deletions(-) rename src/core/{generation.rs => generate.rs} (96%) rename src/core/{ => helpers}/code_snippet.rs (100%) rename src/core/{ => helpers}/daily_challenge.rs (100%) create mode 100644 src/core/helpers/mod.rs rename src/core/{ => helpers}/write_file.rs (100%) diff --git a/src/core/generation.rs b/src/core/generate.rs similarity index 96% rename from src/core/generation.rs rename to src/core/generate.rs index 9e42b42..14ab32f 100644 --- a/src/core/generation.rs +++ b/src/core/generate.rs @@ -5,7 +5,7 @@ use log::info; use crate::{ config::Config, - core::{code_snippet, daily_challenge, write_file}, + core::helpers::{code_snippet, daily_challenge, write_file}, }; pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> { diff --git a/src/core/code_snippet.rs b/src/core/helpers/code_snippet.rs similarity index 100% rename from src/core/code_snippet.rs rename to src/core/helpers/code_snippet.rs diff --git a/src/core/daily_challenge.rs b/src/core/helpers/daily_challenge.rs similarity index 100% rename from src/core/daily_challenge.rs rename to src/core/helpers/daily_challenge.rs diff --git a/src/core/helpers/mod.rs b/src/core/helpers/mod.rs new file mode 100644 index 0000000..c33bc89 --- /dev/null +++ b/src/core/helpers/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod code_snippet; +pub(crate) mod daily_challenge; +pub(crate) mod write_file; diff --git a/src/core/write_file.rs b/src/core/helpers/write_file.rs similarity index 100% rename from src/core/write_file.rs rename to src/core/helpers/write_file.rs diff --git a/src/core/mod.rs b/src/core/mod.rs index 81ea3ae..c16a4c6 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,9 +1,7 @@ -mod code_snippet; -mod daily_challenge; -mod generation; -mod write_file; +mod generate; +mod helpers; -use self::generation::do_generate; +use self::generate::do_generate; use crate::cli::Cli; use anyhow::{bail, Context}; use std::{env, path::Path}; From ea1ea23f3d4282cc51b61c19c85fdf55a00ea961 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Mon, 12 Jun 2023 20:28:39 -0400 Subject: [PATCH 040/196] Add context for errors --- src/core/generate.rs | 4 ++-- src/core/helpers/write_file.rs | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/core/generate.rs b/src/core/generate.rs index 14ab32f..3f9c2eb 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use anyhow::bail; +use anyhow::{bail, Context}; use log::info; use crate::{ @@ -30,7 +30,7 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> }; let code_snippet = code_snippet::generate_code_snippet(&title_slug)?; - write_file::write_file(&title_slug, code_snippet)?; + write_file::write_file(&title_slug, code_snippet).context("Failed to write files")?; Ok(()) } diff --git a/src/core/helpers/write_file.rs b/src/core/helpers/write_file.rs index 089e45b..88758c4 100644 --- a/src/core/helpers/write_file.rs +++ b/src/core/helpers/write_file.rs @@ -1,6 +1,6 @@ use anyhow::Context; use convert_case::{Case, Casing}; -use log::{info, warn}; +use log::{error, info}; use std::{ fs::{remove_file, OpenOptions}, io::Write, @@ -25,14 +25,21 @@ pub fn write_file(title_slug: &str, code_snippet: String) -> anyhow::Result<()> let mut file = OpenOptions::new() .write(true) .create_new(true) - .open(path.clone())?; - file.write_all(code_snippet.as_bytes())?; + .open(&path) + .with_context(|| format!("Failed to create '{}'", path.display()))?; + file.write_all(code_snippet.as_bytes()) + .with_context(|| format!("Failed writing to '{}'", path.display()))?; let lib_update_status = update_lib(&module_name); if lib_update_status.is_err() { - warn!("Cleaning up after updating lib.rs failed"); + error!("Failed to update lib.rs: Performing cleanup of partially completed command"); // clean up - remove_file(path)?; - lib_update_status?; + remove_file(&path).with_context(|| { + format!( + "Failed to remove '{}' during cleanup after failing to update lib.rs", + path.display() + ) + })?; + lib_update_status.context("Failed to update lib.rs")?; } info!("Going to run rustfmt on files"); From 67fcb9f1f5e2b62dd930e95f2a4ec7ec6f113a63 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Mon, 12 Jun 2023 20:40:13 -0400 Subject: [PATCH 041/196] Move check for url into function To make testing easier later --- src/core/generate.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/generate.rs b/src/core/generate.rs index 3f9c2eb..eb6b058 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -11,7 +11,7 @@ use crate::{ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> { let title_slug: Cow = if let Some(specific_problem) = &args.problem { // Problem specified - if specific_problem.contains('/') { + if is_url(specific_problem) { // Working with a url info!("Using '{specific_problem}' as a url"); let slug = url_to_slug(specific_problem)?; @@ -34,6 +34,11 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> Ok(()) } +/// Quick and dirty test to see if this is a url +fn is_url(value: &str) -> bool { + value.contains('/') +} + fn url_to_slug(url: &str) -> anyhow::Result { assert!(Config::LEETCODE_PROBLEM_URL.ends_with('/')); if !url.starts_with(Config::LEETCODE_PROBLEM_URL) { From 112d63d6f880c11af98e02ffd0d87d46761fa9b8 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Mon, 12 Jun 2023 20:45:36 -0400 Subject: [PATCH 042/196] Add context --- src/core/generate.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/generate.rs b/src/core/generate.rs index eb6b058..89ee93e 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -29,7 +29,8 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> Cow::Owned(slug) }; - let code_snippet = code_snippet::generate_code_snippet(&title_slug)?; + let code_snippet = code_snippet::generate_code_snippet(&title_slug) + .context("Failed to generate code snippet")?; write_file::write_file(&title_slug, code_snippet).context("Failed to write files")?; Ok(()) } From 48cc562c5ce14e01284f4e6482416f975a98dbde Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:21:45 -0400 Subject: [PATCH 043/196] Tweak output code to be easier to copy --- src/core/helpers/code_snippet.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/helpers/code_snippet.rs b/src/core/helpers/code_snippet.rs index 97cc878..e7c627f 100644 --- a/src/core/helpers/code_snippet.rs +++ b/src/core/helpers/code_snippet.rs @@ -79,6 +79,9 @@ pub fn generate_code_snippet(title_slug: &str) -> anyhow::Result { let code = get_code_snippet_question(title_slug)?; code_snippet.push_str(&code); + // Add 2 empty lines between code and "other stuff (like tests and struct definition" + code_snippet.push_str("\n\n"); + // handle non design snippets let is_design = code.starts_with("impl Solution {"); if is_design { From 4ed136c68c9177b0706c55b168e3528c6f0933ba Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:23:56 -0400 Subject: [PATCH 044/196] Invert `is_design` to make it correct --- src/core/helpers/code_snippet.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/helpers/code_snippet.rs b/src/core/helpers/code_snippet.rs index e7c627f..b0cc0af 100644 --- a/src/core/helpers/code_snippet.rs +++ b/src/core/helpers/code_snippet.rs @@ -83,8 +83,8 @@ pub fn generate_code_snippet(title_slug: &str) -> anyhow::Result { code_snippet.push_str("\n\n"); // handle non design snippets - let is_design = code.starts_with("impl Solution {"); - if is_design { + let is_design = !code.starts_with("impl Solution {"); + if !is_design { code_snippet.push_str("\npub struct Solution;\n") } From 0e42ef25241b406c156e8b66605162fbf85dc9a3 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Tue, 13 Jun 2023 16:06:19 -0400 Subject: [PATCH 045/196] Simplify argument passing for generate --- README.md | 2 +- src/cli.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 3310363..3115195 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ### `cargo leet` ![ScreenShot](assets/help_scr_shot_top.png) - ### `cargo leet generate --help` + ### `cargo leet generate --help` (Changing, screenshot not updated yet) ![ScreenShot](assets/help_scr_shot_generate.png) ## Installation diff --git a/src/cli.rs b/src/cli.rs index 15b04c1..eb4d4f1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -56,7 +56,6 @@ pub enum Commands { #[derive(Args, Debug)] pub struct GenerateArgs { /// Question slug or url (If none specified then daily challenge is used) - #[arg(short, long)] pub problem: Option, } From 60fae395ef2e45367d43e57357ba80d1c84de1b8 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Tue, 13 Jun 2023 17:24:43 -0400 Subject: [PATCH 046/196] Add new cli option to have number included --- src/cli.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index eb4d4f1..72723d3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -57,6 +57,9 @@ pub enum Commands { pub struct GenerateArgs { /// Question slug or url (If none specified then daily challenge is used) pub problem: Option, + /// If set the module name generated includes the number for the problem + #[arg(short = 'n', long = "number_in_name", default_value_t = false)] + pub should_include_problem_number: bool, } /// Exists to provide better help messages variants copied from LevelFilter as that's the type From 446873cefe5b16c323d7e600513434de393ae8eb Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 09:42:14 -0400 Subject: [PATCH 047/196] Restructure code in for new functionality Current setup would have required two requests to get the required information, the tests and the problem number. However, both of these can be acquired in one request therefore the code has been restructured to reflect that. --- src/core/generate.rs | 58 +++++++++++++++++-- src/core/helpers/code_snippet.rs | 35 ++--------- src/core/helpers/mod.rs | 2 +- .../{write_file.rs => write_to_disk.rs} | 12 ++-- 4 files changed, 61 insertions(+), 46 deletions(-) rename src/core/helpers/{write_file.rs => write_to_disk.rs} (76%) diff --git a/src/core/generate.rs b/src/core/generate.rs index 89ee93e..2ce42b4 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -1,11 +1,14 @@ -use std::borrow::Cow; - use anyhow::{bail, Context}; +use convert_case::{Case, Casing}; use log::info; +use std::borrow::Cow; use crate::{ config::Config, - core::helpers::{code_snippet, daily_challenge, write_file}, + core::helpers::{ + code_snippet::{get_code_snippet_for_problem, get_test_cases}, + daily_challenge, write_to_disk, + }, }; pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> { @@ -29,12 +32,55 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> Cow::Owned(slug) }; - let code_snippet = code_snippet::generate_code_snippet(&title_slug) - .context("Failed to generate code snippet")?; - write_file::write_file(&title_slug, code_snippet).context("Failed to write files")?; + let (module_name, module_code) = create_module_code(title_slug, args) + .context("Failed to generate the name and module code")?; + write_to_disk::write_file(&module_name, module_code).context("Failed to write to disk")?; Ok(()) } +/// Gets the code and other data from leetcode and generates the suitable code for the module and the name of the module +/// Returns the module name and the module code +/// +/// NB: Did not return `Cow` because `module_name` is always a modified version of the input +pub fn create_module_code( + title_slug: Cow, + args: &crate::cli::GenerateArgs, +) -> anyhow::Result<(String, String)> { + info!("Building module contents for {title_slug}"); + + // Add problem URL + let mut code_snippet = format!( + "//! Solution for {}{title_slug}\n", + Config::LEETCODE_PROBLEM_URL + ); + + // Get code snippet + let code = get_code_snippet_for_problem(&title_slug)?; + code_snippet.push_str(&code); + + // Add 2 empty lines between code and "other stuff (like tests and struct definition" + code_snippet.push_str("\n\n"); + + // Handle non design snippets + let is_design = !code.starts_with("impl Solution {"); + if !is_design { + code_snippet.push_str("\npub struct Solution;\n") + } + + // Add tests + let test = get_test_cases(&title_slug, is_design)?; + code_snippet.push_str(&test); + + // Set module name + let module_name = if args.should_include_problem_number { + unimplemented!("Haven't retrieved the data yet") + } else { + title_slug.to_case(Case::Snake) + }; + + Ok((module_name, code_snippet)) +} + /// Quick and dirty test to see if this is a url fn is_url(value: &str) -> bool { value.contains('/') diff --git a/src/core/helpers/code_snippet.rs b/src/core/helpers/code_snippet.rs index b0cc0af..758529d 100644 --- a/src/core/helpers/code_snippet.rs +++ b/src/core/helpers/code_snippet.rs @@ -6,17 +6,17 @@ use serde_flat_path::flat_path; #[flat_path] #[derive(Deserialize)] -struct CodeSnippetResponse { +pub struct CodeSnippetResponse { #[flat_path("data.question.codeSnippets")] code_snippets: Vec, } #[derive(Deserialize)] -struct CodeSnippet { +pub struct CodeSnippet { lang: String, code: String, } -fn get_code_snippet_question(title_slug: &str) -> anyhow::Result { +pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result { info!("Going to get code for {title_slug}"); let code_snippets_res = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ @@ -45,7 +45,7 @@ fn get_code_snippet_question(title_slug: &str) -> anyhow::Result { } } -fn get_test_cases(title_slug: &str, is_design: bool) -> anyhow::Result { +pub fn get_test_cases(title_slug: &str, is_design: bool) -> anyhow::Result { info!("Going to get tests for {title_slug}"); let tests = if is_design { r#" @@ -66,30 +66,3 @@ fn get_test_cases(title_slug: &str, is_design: bool) -> anyhow::Result { "# )) } - -pub fn generate_code_snippet(title_slug: &str) -> anyhow::Result { - info!("Building code snippet for {title_slug}"); - // add URL - let mut code_snippet = format!( - "//! Solution for {}{title_slug}\n", - Config::LEETCODE_PROBLEM_URL - ); - - // get code snippet - let code = get_code_snippet_question(title_slug)?; - code_snippet.push_str(&code); - - // Add 2 empty lines between code and "other stuff (like tests and struct definition" - code_snippet.push_str("\n\n"); - - // handle non design snippets - let is_design = !code.starts_with("impl Solution {"); - if !is_design { - code_snippet.push_str("\npub struct Solution;\n") - } - - // add tests - let test = get_test_cases(title_slug, is_design)?; - code_snippet.push_str(&test); - Ok(code_snippet) -} diff --git a/src/core/helpers/mod.rs b/src/core/helpers/mod.rs index c33bc89..781ebd1 100644 --- a/src/core/helpers/mod.rs +++ b/src/core/helpers/mod.rs @@ -1,3 +1,3 @@ pub(crate) mod code_snippet; pub(crate) mod daily_challenge; -pub(crate) mod write_file; +pub(crate) mod write_to_disk; diff --git a/src/core/helpers/write_file.rs b/src/core/helpers/write_to_disk.rs similarity index 76% rename from src/core/helpers/write_file.rs rename to src/core/helpers/write_to_disk.rs index 88758c4..59edb79 100644 --- a/src/core/helpers/write_file.rs +++ b/src/core/helpers/write_to_disk.rs @@ -1,5 +1,4 @@ use anyhow::Context; -use convert_case::{Case, Casing}; use log::{error, info}; use std::{ fs::{remove_file, OpenOptions}, @@ -16,20 +15,17 @@ fn update_lib(module_name: &str) -> anyhow::Result<()> { Ok(()) } -pub fn write_file(title_slug: &str, code_snippet: String) -> anyhow::Result<()> { - info!("Writing code to disk for {title_slug}"); - let slug_snake = title_slug.to_case(Case::Snake); - let module_name = slug_snake; // TODO: Find way to specify desired new file name from a config - info!("Module name is: {module_name}"); +pub fn write_file(module_name: &str, module_code: String) -> anyhow::Result<()> { + info!("Writing code to disk for module {module_name}"); let path = PathBuf::from(format!("src/{module_name}.rs")); let mut file = OpenOptions::new() .write(true) .create_new(true) .open(&path) .with_context(|| format!("Failed to create '{}'", path.display()))?; - file.write_all(code_snippet.as_bytes()) + file.write_all(module_code.as_bytes()) .with_context(|| format!("Failed writing to '{}'", path.display()))?; - let lib_update_status = update_lib(&module_name); + let lib_update_status = update_lib(module_name); if lib_update_status.is_err() { error!("Failed to update lib.rs: Performing cleanup of partially completed command"); // clean up From 49408c8a9b67ff395e6ae5c3846295240915a439 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 10:19:14 -0400 Subject: [PATCH 048/196] Forbid Unsafe --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a9c0c33..bf9cb7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![forbid(unsafe_code)] + mod cli; mod config; mod core; From 09b38151e22d8973a1b64ecc5ad243c988879842 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 13:09:56 -0400 Subject: [PATCH 049/196] Create module for problem metadata --- src/core/generate.rs | 9 ++++++++- src/core/helpers/code_snippet.rs | 1 + src/core/helpers/mod.rs | 1 + src/core/helpers/problem_metadata.rs | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/core/helpers/problem_metadata.rs diff --git a/src/core/generate.rs b/src/core/generate.rs index 2ce42b4..628ce97 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -7,7 +7,9 @@ use crate::{ config::Config, core::helpers::{ code_snippet::{get_code_snippet_for_problem, get_test_cases}, - daily_challenge, write_to_disk, + daily_challenge, + problem_metadata::get_problem_metadata, + write_to_disk, }, }; @@ -48,6 +50,9 @@ pub fn create_module_code( ) -> anyhow::Result<(String, String)> { info!("Building module contents for {title_slug}"); + let meta_data = + get_problem_metadata(&title_slug).context("Failed to retrieve problem meta data")?; + // Add problem URL let mut code_snippet = format!( "//! Solution for {}{title_slug}\n", @@ -73,8 +78,10 @@ pub fn create_module_code( // Set module name let module_name = if args.should_include_problem_number { + info!("Including problem number in module name"); unimplemented!("Haven't retrieved the data yet") } else { + info!("Using snake case slug for module name"); title_slug.to_case(Case::Snake) }; diff --git a/src/core/helpers/code_snippet.rs b/src/core/helpers/code_snippet.rs index 758529d..fd690e8 100644 --- a/src/core/helpers/code_snippet.rs +++ b/src/core/helpers/code_snippet.rs @@ -46,6 +46,7 @@ pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result } pub fn get_test_cases(title_slug: &str, is_design: bool) -> anyhow::Result { + // TODO Move this to an impl on problem_meta_data info!("Going to get tests for {title_slug}"); let tests = if is_design { r#" diff --git a/src/core/helpers/mod.rs b/src/core/helpers/mod.rs index 781ebd1..57dee72 100644 --- a/src/core/helpers/mod.rs +++ b/src/core/helpers/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod code_snippet; pub(crate) mod daily_challenge; +pub(crate) mod problem_metadata; pub(crate) mod write_to_disk; diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs new file mode 100644 index 0000000..41ce44c --- /dev/null +++ b/src/core/helpers/problem_metadata.rs @@ -0,0 +1,3 @@ +pub fn get_problem_metadata(title_slug: &str) -> anyhow::Result { + todo!() +} From 8ef9d1e5e0aa9e3012ab66c79598267ad28ef5d2 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 13:31:33 -0400 Subject: [PATCH 050/196] Remove URL literal missed earlier --- src/core/helpers/daily_challenge.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/helpers/daily_challenge.rs b/src/core/helpers/daily_challenge.rs index e64929f..21f9dbd 100644 --- a/src/core/helpers/daily_challenge.rs +++ b/src/core/helpers/daily_challenge.rs @@ -2,6 +2,8 @@ use anyhow::Context; use serde::Deserialize; use serde_flat_path::flat_path; +use crate::config::Config; + #[flat_path] #[derive(Deserialize)] struct DailyChallengeResponse { @@ -10,7 +12,7 @@ struct DailyChallengeResponse { } pub fn get_daily_challenge_slug() -> anyhow::Result { - let daily_challenge_response = ureq::get("https://leetcode.com/graphql/") + let daily_challenge_response = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ "query": r#"query questionOfToday { activeDailyCodingChallengeQuestion { From add2b4f45cff51346cad1ba1b355bd474a97e98c Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 14:18:40 -0400 Subject: [PATCH 051/196] Change unimplemented to todo Used wrong macro in error. I do plan to implement this --- src/core/generate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/generate.rs b/src/core/generate.rs index 628ce97..0fd6c82 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -79,7 +79,7 @@ pub fn create_module_code( // Set module name let module_name = if args.should_include_problem_number { info!("Including problem number in module name"); - unimplemented!("Haven't retrieved the data yet") + todo!("Haven't retrieved the data yet") } else { info!("Using snake case slug for module name"); title_slug.to_case(Case::Snake) From ea9dd6785415ad94b07dfbe62cabbff80e79329c Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 17:46:28 -0400 Subject: [PATCH 052/196] Implement retrieval of metadata --- src/core/generate.rs | 16 +++--- src/core/helpers/code_snippet.rs | 25 +------- src/core/helpers/problem_metadata.rs | 85 +++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 33 deletions(-) diff --git a/src/core/generate.rs b/src/core/generate.rs index 0fd6c82..dcd284d 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -6,10 +6,8 @@ use std::borrow::Cow; use crate::{ config::Config, core::helpers::{ - code_snippet::{get_code_snippet_for_problem, get_test_cases}, - daily_challenge, - problem_metadata::get_problem_metadata, - write_to_disk, + code_snippet::get_code_snippet_for_problem, daily_challenge, + problem_metadata::get_problem_metadata, write_to_disk, }, }; @@ -73,13 +71,17 @@ pub fn create_module_code( } // Add tests - let test = get_test_cases(&title_slug, is_design)?; - code_snippet.push_str(&test); + let tests = meta_data.get_test_cases(&code, is_design)?; + code_snippet.push_str(&tests); // Set module name let module_name = if args.should_include_problem_number { info!("Including problem number in module name"); - todo!("Haven't retrieved the data yet") + format!( + "_{}_{}", + meta_data.get_id()?, + title_slug.to_case(Case::Snake) + ) } else { info!("Using snake case slug for module name"); title_slug.to_case(Case::Snake) diff --git a/src/core/helpers/code_snippet.rs b/src/core/helpers/code_snippet.rs index fd690e8..ff65ae6 100644 --- a/src/core/helpers/code_snippet.rs +++ b/src/core/helpers/code_snippet.rs @@ -33,7 +33,7 @@ pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result })) .context("Get request for code_snippet failed")? .into_json::() - .context("Failed to convert codes_snippet response from json")?; + .context("Failed to convert response from json to codes_snippet")?; match code_snippets_res .code_snippets @@ -44,26 +44,3 @@ pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result None => bail!("Rust not supported for this problem"), } } - -pub fn get_test_cases(title_slug: &str, is_design: bool) -> anyhow::Result { - // TODO Move this to an impl on problem_meta_data - info!("Going to get tests for {title_slug}"); - let tests = if is_design { - r#" - use rstest::rstest; - "# - .to_string() - } else { - // TODO: Get test cases for design problems - "".to_string() - }; - Ok(format!( - r#" - #[cfg(test)] - mod tests {{ - use super::*; - {tests} - }} - "# - )) -} diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index 41ce44c..e8d8b69 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -1,3 +1,84 @@ -pub fn get_problem_metadata(title_slug: &str) -> anyhow::Result { - todo!() +use crate::config::Config; +use anyhow::Context; +use log::info; +use serde::Deserialize; +use serde_flat_path::flat_path; + +/// This struct is only used because there are two fields that we are interested in that start with the same path and flat_path does not support that yet +#[flat_path] +#[derive(Deserialize, Debug)] +struct QuestionWrapper { + #[flat_path("data.question")] + inner: ProblemMetadata, +} + +#[flat_path] +#[derive(Deserialize, Debug)] +pub struct ProblemMetadata { + #[serde(rename = "questionFrontendId")] + id: String, + #[serde(rename = "exampleTestcaseList")] + example_test_case_list: Vec, +} + +impl ProblemMetadata { + /// Checks if the data is valid + fn validate(&self) -> anyhow::Result<()> { + let _: u16 = self.get_id()?; + Ok(()) + } + + pub fn get_id(&self) -> anyhow::Result { + let result = self + .id + .parse() + .with_context(|| format!("ID is not a valid u16. Got: {}", self.id))?; + Ok(result) + } + + pub fn get_test_cases(&self, problem_code: &str, is_design: bool) -> anyhow::Result { + info!("Going to get tests"); + // TODO implement generation of test cases + let tests = if is_design { + r#" +use rstest::rstest; +"# + .to_string() + } else { + "".to_string() + }; + + Ok(format!( + r#" +#[cfg(test)] +mod tests {{ + use super::*; + {tests} +}} +"# + )) + } +} + +pub fn get_problem_metadata(title_slug: &str) -> anyhow::Result { + info!("Going to get problem metadata"); + let QuestionWrapper { inner: result } = ureq::get(Config::LEETCODE_GRAPH_QL) + .send_json(ureq::json!({ + "query": r#"query consolePanelConfig($titleSlug: String!) { + question(titleSlug: $titleSlug) { + questionFrontendId + exampleTestcaseList + } + }"#, + "variables":{"titleSlug": title_slug}, + "operationName":"consolePanelConfig" + })) + .context("Get request for problem metadata failed")? + .into_json() + .context("Failed to convert response from json to problem metadata")?; + + result + .validate() + .context("Failed to validate problem metadata")?; + Ok(result) } From 61e5fc660c0a632d0a02039d8bca4813367a7d61 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 18:02:00 -0400 Subject: [PATCH 053/196] Move Problem Code into custom struct --- src/core/generate.rs | 11 +++++------ src/core/helpers/code_snippet.rs | 5 +++-- src/core/helpers/mod.rs | 1 + src/core/helpers/problem_code.rs | 27 +++++++++++++++++++++++++++ src/core/helpers/problem_metadata.rs | 6 ++++-- 5 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 src/core/helpers/problem_code.rs diff --git a/src/core/generate.rs b/src/core/generate.rs index dcd284d..038ab85 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -58,20 +58,19 @@ pub fn create_module_code( ); // Get code snippet - let code = get_code_snippet_for_problem(&title_slug)?; - code_snippet.push_str(&code); + let problem_code = get_code_snippet_for_problem(&title_slug)?; + code_snippet.push_str(problem_code.as_ref()); // Add 2 empty lines between code and "other stuff (like tests and struct definition" code_snippet.push_str("\n\n"); - // Handle non design snippets - let is_design = !code.starts_with("impl Solution {"); - if !is_design { + // Add struct for non design questions + if !problem_code.is_design() { code_snippet.push_str("\npub struct Solution;\n") } // Add tests - let tests = meta_data.get_test_cases(&code, is_design)?; + let tests = meta_data.get_test_cases(&problem_code)?; code_snippet.push_str(&tests); // Set module name diff --git a/src/core/helpers/code_snippet.rs b/src/core/helpers/code_snippet.rs index ff65ae6..e9a0801 100644 --- a/src/core/helpers/code_snippet.rs +++ b/src/core/helpers/code_snippet.rs @@ -1,3 +1,4 @@ +use super::problem_code::ProblemCode; use crate::config::Config; use anyhow::{bail, Context}; use log::info; @@ -16,7 +17,7 @@ pub struct CodeSnippet { code: String, } -pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result { +pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result { info!("Going to get code for {title_slug}"); let code_snippets_res = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ @@ -40,7 +41,7 @@ pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result .into_iter() .find_map(|cs| (cs.lang == "Rust").then_some(cs.code)) { - Some(result) => Ok(result), + Some(result) => Ok(result.into()), None => bail!("Rust not supported for this problem"), } } diff --git a/src/core/helpers/mod.rs b/src/core/helpers/mod.rs index 57dee72..5a6f015 100644 --- a/src/core/helpers/mod.rs +++ b/src/core/helpers/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod code_snippet; pub(crate) mod daily_challenge; +pub(crate) mod problem_code; pub(crate) mod problem_metadata; pub(crate) mod write_to_disk; diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs new file mode 100644 index 0000000..0e12c99 --- /dev/null +++ b/src/core/helpers/problem_code.rs @@ -0,0 +1,27 @@ +pub struct ProblemCode { + code: String, +} + +impl From for ProblemCode { + fn from(value: String) -> Self { + Self { code: value } + } +} + +impl From for String { + fn from(value: ProblemCode) -> Self { + value.code + } +} + +impl AsRef for ProblemCode { + fn as_ref(&self) -> &str { + &self.code + } +} + +impl ProblemCode { + pub fn is_design(&self) -> bool { + !self.code.starts_with("impl Solution {") + } +} diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index e8d8b69..bf85e1b 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -4,6 +4,8 @@ use log::info; use serde::Deserialize; use serde_flat_path::flat_path; +use super::problem_code::ProblemCode; + /// This struct is only used because there are two fields that we are interested in that start with the same path and flat_path does not support that yet #[flat_path] #[derive(Deserialize, Debug)] @@ -36,10 +38,10 @@ impl ProblemMetadata { Ok(result) } - pub fn get_test_cases(&self, problem_code: &str, is_design: bool) -> anyhow::Result { + pub fn get_test_cases(&self, problem_code: &ProblemCode) -> anyhow::Result { info!("Going to get tests"); // TODO implement generation of test cases - let tests = if is_design { + let tests = if problem_code.is_design() { r#" use rstest::rstest; "# From 94e78faa5f6174d1c914cf05ad8c684d550b1d90 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 18:07:51 -0400 Subject: [PATCH 054/196] Invert check as design means the opposite --- src/core/helpers/problem_metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index bf85e1b..f496b4d 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -41,7 +41,7 @@ impl ProblemMetadata { pub fn get_test_cases(&self, problem_code: &ProblemCode) -> anyhow::Result { info!("Going to get tests"); // TODO implement generation of test cases - let tests = if problem_code.is_design() { + let tests = if !problem_code.is_design() { r#" use rstest::rstest; "# From dc394f900b98290f2ef4f8d7b712ec73a6610109 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 18:14:25 -0400 Subject: [PATCH 055/196] Add output on successful run --- src/core/generate.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/generate.rs b/src/core/generate.rs index 038ab85..5a5d24c 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -35,6 +35,7 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> let (module_name, module_code) = create_module_code(title_slug, args) .context("Failed to generate the name and module code")?; write_to_disk::write_file(&module_name, module_code).context("Failed to write to disk")?; + println!("Generated module: {module_name}"); Ok(()) } From 4561ac09b25a5c1ad9f64491039790102faeabbd Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 19:28:58 -0400 Subject: [PATCH 056/196] Stub out main functionality for test cases --- .vscode/settings.json | 5 +++ src/core/helpers/problem_code.rs | 45 ++++++++++++++++++++++++ src/core/helpers/problem_metadata.rs | 52 ++++++++++++++++++++++++---- 3 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..65be3a1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "Veci" + ] +} \ No newline at end of file diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index 0e12c99..5663f6f 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -24,4 +24,49 @@ impl ProblemCode { pub fn is_design(&self) -> bool { !self.code.starts_with("impl Solution {") } + + pub fn get_fn_info(&self) -> anyhow::Result { + todo!() + } +} + +pub struct FunctionInfo { + pub name: String, + pub args: FunctionArgs, + pub return_type: Option, +} + +impl FunctionInfo { + pub fn get_args_with_case(&self) -> anyhow::Result { + todo!() + } + + pub fn get_args_names(&self) -> anyhow::Result { + todo!() + } + + pub(crate) fn get_test_case(&self, raw_str: &str) -> String { + todo!() + } +} + +pub struct FunctionArgs { + raw_str: String, + pub args: Vec, +} + +impl FunctionArgs { + pub fn new(raw_string: String) -> Self { + todo!() + } +} + +/// Function Arg Type (FAT) +pub enum FunctionArgType { + FATi32, + FATVeci32, + FATVecVeci32, + FATString, + FATList, + FATTree, } diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index f496b4d..d175c4b 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -4,7 +4,7 @@ use log::info; use serde::Deserialize; use serde_flat_path::flat_path; -use super::problem_code::ProblemCode; +use super::problem_code::{FunctionInfo, ProblemCode}; /// This struct is only used because there are two fields that we are interested in that start with the same path and flat_path does not support that yet #[flat_path] @@ -40,14 +40,19 @@ impl ProblemMetadata { pub fn get_test_cases(&self, problem_code: &ProblemCode) -> anyhow::Result { info!("Going to get tests"); - // TODO implement generation of test cases + + let fn_info = problem_code + .get_fn_info() + .context("Failed to get function info")?; + let tests = if !problem_code.is_design() { - r#" -use rstest::rstest; -"# - .to_string() + info!("This is NOT a design problem"); + self.get_test_cases_is_not_design(fn_info) + .context("Failed to get test cases for non-design problem")? } else { - "".to_string() + info!("This is a design problem"); + self.get_test_cases_is_design(fn_info) + .context("Failed to get test cases for design problem")? }; Ok(format!( @@ -60,6 +65,39 @@ mod tests {{ "# )) } + + fn get_test_cases_is_not_design(&self, fn_info: FunctionInfo) -> anyhow::Result { + let mut result = "use rstest::rstest; + + #[rstest] +" + .to_string(); + + // Add test cases + for raw_str in self.example_test_case_list.iter() { + let test_case = fn_info.get_test_case(raw_str); + result.push_str(&format!(" #[case({})]\n", test_case)) + } + + // Add test case function body + let test_fn = format!( + " fn case({}) {{ + let mut actual = Solution::{}({}); + assert_eq!(actual, expected); + }}", + fn_info.get_args_with_case()?, + fn_info.name, + fn_info.get_args_names()? + ); + result.push_str(&test_fn); + + Ok(result) + } + + fn get_test_cases_is_design(&self, fn_info: FunctionInfo) -> anyhow::Result { + let mut result = "".to_string(); + Ok(result) + } } pub fn get_problem_metadata(title_slug: &str) -> anyhow::Result { From 136d16ac14485d04301acaad53688e017411e7e3 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 19:30:22 -0400 Subject: [PATCH 057/196] Disable rustfmt for testing --- src/core/helpers/write_to_disk.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/helpers/write_to_disk.rs b/src/core/helpers/write_to_disk.rs index 59edb79..6d1e7c4 100644 --- a/src/core/helpers/write_to_disk.rs +++ b/src/core/helpers/write_to_disk.rs @@ -38,11 +38,12 @@ pub fn write_file(module_name: &str, module_code: String) -> anyhow::Result<()> lib_update_status.context("Failed to update lib.rs")?; } - info!("Going to run rustfmt on files"); - Command::new("cargo") - .arg("fmt") - .arg("--all") - .output() - .context("Error running rustfmt")?; + // TODO Enable rustfmt + // info!("Going to run rustfmt on files"); + // Command::new("cargo") + // .arg("fmt") + // .arg("--all") + // .output() + // .context("Error running rustfmt")?; Ok(()) } From 18dbc96fc42cd29488186d0600dd41031e844cd8 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 20:29:55 -0400 Subject: [PATCH 058/196] Implement get_fn_info --- Cargo.lock | 1 + Cargo.toml | 1 + src/core/helpers/problem_code.rs | 54 +++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 58fb16b..7bab02a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,7 @@ dependencies = [ "convert_case", "env_logger", "log", + "regex", "serde", "serde_flat_path", "ureq", diff --git a/Cargo.toml b/Cargo.toml index 37cebb8..f56ca2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ clap = { version = "4.3.3", features = ["derive", "cargo"] } convert_case = "0.6" env_logger = "0.10.0" log = "0.4.18" +regex = "1.8.4" serde = { version = "1.0.164", features = ["derive"] } serde_flat_path = "0.1.2" ureq = { version = "2.6", features = ["json"] } diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index 5663f6f..add82ae 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -1,3 +1,6 @@ +use anyhow::{bail, Context}; +use regex::Regex; + pub struct ProblemCode { code: String, } @@ -26,7 +29,39 @@ impl ProblemCode { } pub fn get_fn_info(&self) -> anyhow::Result { - todo!() + let re = Regex::new(r#"pub fn ([a-z_0-9]*)\((.*)\)(?: ?-> ?(.*))? \{"#)?; + let caps = if let Some(caps) = re.captures(&self.code) { + caps + } else { + bail!("Regex failed to match"); + }; + + let name = if let Some(name) = caps.get(1) { + name.as_str().to_string() + } else { + bail!("Function name not found in code") + }; + + let args = FunctionArgs::new(if let Some(args) = caps.get(2) { + args.as_str().to_string() + } else { + bail!("Function arguments not matched") + }); + + let return_type: Option = match caps.get(3) { + Some(s) => Some( + s.as_str() + .try_into() + .context("Failed to convert return type")?, + ), + None => None, + }; + + Ok(FunctionInfo { + name, + args, + return_type, + }) } } @@ -70,3 +105,20 @@ pub enum FunctionArgType { FATList, FATTree, } + +impl TryFrom<&str> for FunctionArgType { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result { + use FunctionArgType::*; + Ok(match value { + "i32" => FATi32, + "Vec" => FATVeci32, + "Vec>" => FATVecVeci32, + "String" => FATString, + "Option>" => FATList, + "Option>>" => FATTree, + _ => bail!("Unknown type: '{value}'"), + }) + } +} From 3196d087bec53fdd08f0865e15c031794af5e64b Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 22:16:14 -0400 Subject: [PATCH 059/196] Implement new for FunctionArgs --- src/core/helpers/problem_code.rs | 37 +++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index add82ae..9d52525 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -46,7 +46,8 @@ impl ProblemCode { args.as_str().to_string() } else { bail!("Function arguments not matched") - }); + }) + .context("Failed to parse function arguments")?; let return_type: Option = match caps.get(3) { Some(s) => Some( @@ -85,18 +86,44 @@ impl FunctionInfo { } } +#[derive(Debug)] +pub struct FunctionArg { + pub identifier: String, + pub arg_type: FunctionArgType, +} + +#[derive(Debug)] pub struct FunctionArgs { raw_str: String, - pub args: Vec, + pub args: Vec, } impl FunctionArgs { - pub fn new(raw_string: String) -> Self { - todo!() + pub fn new(raw_str: String) -> anyhow::Result { + let re = Regex::new(r#"([a-z_0-9]*?)\s*:\s*([A-Za-z0-9<>]*)"#)?; + let caps: Vec<_> = re.captures_iter(&raw_str).collect(); + let mut args: Vec = vec![]; + for cap in caps { + let identifier = cap.get(1).expect("Required to match").as_str().to_string(); + let arg_type = cap + .get(2) + .expect("Required to match") + .as_str() + .try_into() + .context("Failed to get argument type")?; + + args.push(FunctionArg { + identifier, + arg_type, + }) + } + + Ok(Self { raw_str, args }) } } /// Function Arg Type (FAT) +#[derive(Debug)] pub enum FunctionArgType { FATi32, FATVeci32, @@ -111,7 +138,7 @@ impl TryFrom<&str> for FunctionArgType { fn try_from(value: &str) -> Result { use FunctionArgType::*; - Ok(match value { + Ok(match value.trim() { "i32" => FATi32, "Vec" => FATVeci32, "Vec>" => FATVecVeci32, From 818cbf4144d054319b529e1f2015eabbb33d3255 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:06:57 -0400 Subject: [PATCH 060/196] Implement get_test_case --- src/core/helpers/problem_code.rs | 89 ++++++++++++++++++++++++++-- src/core/helpers/problem_metadata.rs | 6 +- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index 9d52525..aeda0a1 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -60,7 +60,7 @@ impl ProblemCode { Ok(FunctionInfo { name, - args, + fn_args: args, return_type, }) } @@ -68,7 +68,7 @@ impl ProblemCode { pub struct FunctionInfo { pub name: String, - pub args: FunctionArgs, + pub fn_args: FunctionArgs, pub return_type: Option, } @@ -81,8 +81,41 @@ impl FunctionInfo { todo!() } - pub(crate) fn get_test_case(&self, raw_str: &str) -> String { - todo!() + pub(crate) fn get_test_case(&self, example_test_case_raw: &str) -> anyhow::Result { + let mut result = String::new(); + let n = self.fn_args.len(); + let lines: Vec<_> = example_test_case_raw.lines().collect(); + + if lines.len() != self.fn_args.len() { + bail!( + "Expected number of augments ({}) to match the number of lines download ({})", + self.fn_args.len(), + lines.len() + ) + } + + for (i, (&line, arg_type)) in lines + .iter() + .zip(self.fn_args.args.iter().map(|arg| &arg.arg_type)) + .enumerate() + { + result.push_str( + &arg_type + .apply(line) + .context("Failed to apply type information to the example from leetcode")?, + ); + + if i < n - 1 { + result.push_str(", "); + } + } + + // Include return type + if self.return_type.is_some() { + result.push_str(", todo!(\"return type\")"); + } + + Ok(result) } } @@ -120,6 +153,10 @@ impl FunctionArgs { Ok(Self { raw_str, args }) } + + fn len(&self) -> usize { + self.args.len() + } } /// Function Arg Type (FAT) @@ -132,6 +169,50 @@ pub enum FunctionArgType { FATList, FATTree, } +impl FunctionArgType { + /// Applies any special changes needed to the value based on the type + fn apply(&self, line: &str) -> anyhow::Result { + Ok(match self { + FunctionArgType::FATi32 => { + let _: i32 = line.parse()?; + line.to_string() + } + FunctionArgType::FATVeci32 => { + Self::does_pass_basic_vec_tests(line)?; + format!("vec!{line}") + } + FunctionArgType::FATVecVeci32 => { + Self::does_pass_basic_vec_tests(line)?; + let mut result = String::new(); + for c in line.chars() { + match c { + '[' => result.push_str("vec!["), + _ => result.push(c), + } + } + result + } + FunctionArgType::FATString => line.to_string(), + FunctionArgType::FATList => { + Self::does_pass_basic_vec_tests(line)?; + // TODO finish converting input into correct type + format!("\"{line}\"") + } + FunctionArgType::FATTree => { + Self::does_pass_basic_vec_tests(line)?; + // TODO finish converting input into correct type + format!("\"{line}\"") + } + }) + } + + fn does_pass_basic_vec_tests(s: &str) -> anyhow::Result<()> { + if !s.starts_with('[') || !s.ends_with(']') { + bail!("Expecting something that can be represented as a vec but got '{s}'"); + } + Ok(()) + } +} impl TryFrom<&str> for FunctionArgType { type Error = anyhow::Error; diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index d175c4b..fee19f8 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -74,8 +74,10 @@ mod tests {{ .to_string(); // Add test cases - for raw_str in self.example_test_case_list.iter() { - let test_case = fn_info.get_test_case(raw_str); + for example_test_case_raw in self.example_test_case_list.iter() { + let test_case = fn_info + .get_test_case(example_test_case_raw) + .context("Failed to convert downloaded test case into macro of input")?; result.push_str(&format!(" #[case({})]\n", test_case)) } From 00825c9fc257580f37eef7734a36328d08ac7526 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:13:48 -0400 Subject: [PATCH 061/196] Implement get_args_with_case --- src/core/helpers/problem_code.rs | 11 +++++++++-- src/core/helpers/problem_metadata.rs | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index aeda0a1..3fad82d 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -73,8 +73,15 @@ pub struct FunctionInfo { } impl FunctionInfo { - pub fn get_args_with_case(&self) -> anyhow::Result { - todo!() + pub fn get_args_with_case(&self) -> String { + let mut result = String::from("#[case] "); + for c in self.fn_args.raw_str.chars() { + match c { + ',' => result.push_str(", #[case] "), + _ => result.push(c), + } + } + result } pub fn get_args_names(&self) -> anyhow::Result { diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index fee19f8..b43e2da 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -87,7 +87,7 @@ mod tests {{ let mut actual = Solution::{}({}); assert_eq!(actual, expected); }}", - fn_info.get_args_with_case()?, + fn_info.get_args_with_case(), fn_info.name, fn_info.get_args_names()? ); From ba4071b5b77b8dfb0c2dc2c726ffc090ed9081f5 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:16:16 -0400 Subject: [PATCH 062/196] Clear lints for function and replace with TODO --- src/core/helpers/problem_metadata.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index b43e2da..c978de3 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -96,9 +96,9 @@ mod tests {{ Ok(result) } - fn get_test_cases_is_design(&self, fn_info: FunctionInfo) -> anyhow::Result { - let mut result = "".to_string(); - Ok(result) + fn get_test_cases_is_design(&self, _fn_info: FunctionInfo) -> anyhow::Result { + // TODO Create the test cases for design problems + Ok("".to_string()) } } From e3d981c74a6462ea4777a4a126c8770bc4ee39f6 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:44:37 -0400 Subject: [PATCH 063/196] Implement get_args_names --- src/core/helpers/problem_code.rs | 10 ++++++++-- src/core/helpers/problem_metadata.rs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index 3fad82d..91f113d 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -84,8 +84,14 @@ impl FunctionInfo { result } - pub fn get_args_names(&self) -> anyhow::Result { - todo!() + pub fn get_args_names(&self) -> String { + let names: Vec<_> = self + .fn_args + .args + .iter() + .map(|arg| arg.identifier.clone()) + .collect(); + names.join(", ") } pub(crate) fn get_test_case(&self, example_test_case_raw: &str) -> anyhow::Result { diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index c978de3..d383723 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -89,7 +89,7 @@ mod tests {{ }}", fn_info.get_args_with_case(), fn_info.name, - fn_info.get_args_names()? + fn_info.get_args_names() ); result.push_str(&test_fn); From e9660509fec8eba93168d784395e30d589a7b4e0 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:54:02 -0400 Subject: [PATCH 064/196] Add argument for expected value --- src/core/helpers/problem_code.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index 91f113d..d94b6ec 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use anyhow::{bail, Context}; use regex::Regex; @@ -81,6 +83,10 @@ impl FunctionInfo { _ => result.push(c), } } + + if let Some(return_type) = self.return_type.as_ref() { + result.push_str(&format!(", #[case] expected: {return_type}")) + } result } @@ -182,6 +188,7 @@ pub enum FunctionArgType { FATList, FATTree, } + impl FunctionArgType { /// Applies any special changes needed to the value based on the type fn apply(&self, line: &str) -> anyhow::Result { @@ -227,6 +234,21 @@ impl FunctionArgType { } } +impl Display for FunctionArgType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + FunctionArgType::FATi32 => "i32", + FunctionArgType::FATVeci32 => "Vec", + FunctionArgType::FATVecVeci32 => "Vec>", + FunctionArgType::FATString => "String", + FunctionArgType::FATList => "Option>", + FunctionArgType::FATTree => "Option>>", + }; + + write!(f, "{s}") + } +} + impl TryFrom<&str> for FunctionArgType { type Error = anyhow::Error; From e73e145355e242399f4302fd4bf85e91dde4c2f7 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:54:18 -0400 Subject: [PATCH 065/196] Fix up white spaces generated --- src/core/helpers/problem_metadata.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index d383723..552b4f5 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -78,13 +78,13 @@ mod tests {{ let test_case = fn_info .get_test_case(example_test_case_raw) .context("Failed to convert downloaded test case into macro of input")?; - result.push_str(&format!(" #[case({})]\n", test_case)) + result.push_str(&format!(" #[case({})]\n", test_case)) } // Add test case function body let test_fn = format!( - " fn case({}) {{ - let mut actual = Solution::{}({}); + " fn case({}) {{ + let actual = Solution::{}({}); assert_eq!(actual, expected); }}", fn_info.get_args_with_case(), From 8b1915c201d4f69cdf4b485e5407a535963d9ff0 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:54:52 -0400 Subject: [PATCH 066/196] Enable rustfmt --- src/core/helpers/write_to_disk.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/core/helpers/write_to_disk.rs b/src/core/helpers/write_to_disk.rs index 6d1e7c4..59edb79 100644 --- a/src/core/helpers/write_to_disk.rs +++ b/src/core/helpers/write_to_disk.rs @@ -38,12 +38,11 @@ pub fn write_file(module_name: &str, module_code: String) -> anyhow::Result<()> lib_update_status.context("Failed to update lib.rs")?; } - // TODO Enable rustfmt - // info!("Going to run rustfmt on files"); - // Command::new("cargo") - // .arg("fmt") - // .arg("--all") - // .output() - // .context("Error running rustfmt")?; + info!("Going to run rustfmt on files"); + Command::new("cargo") + .arg("fmt") + .arg("--all") + .output() + .context("Error running rustfmt")?; Ok(()) } From 6afa5a307504d0bf7541831950711beb35c81818 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Wed, 14 Jun 2023 23:56:18 -0400 Subject: [PATCH 067/196] Change wording to be more clear --- src/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index d94b6ec..90a2435 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -131,7 +131,7 @@ impl FunctionInfo { // Include return type if self.return_type.is_some() { - result.push_str(", todo!(\"return type\")"); + result.push_str(", todo!(\"Expected Result\")"); } Ok(result) From 03dbc93f94a365ffe2748c55f9c22dc158079a24 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Thu, 15 Jun 2023 00:08:41 -0400 Subject: [PATCH 068/196] Update generate screenshot --- README.md | 2 +- assets/help_scr_shot_generate.png | Bin 19353 -> 19252 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3115195..3310363 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ### `cargo leet` ![ScreenShot](assets/help_scr_shot_top.png) - ### `cargo leet generate --help` (Changing, screenshot not updated yet) + ### `cargo leet generate --help` ![ScreenShot](assets/help_scr_shot_generate.png) ## Installation diff --git a/assets/help_scr_shot_generate.png b/assets/help_scr_shot_generate.png index e504183f205afe920f44993ba75be034a53dd557..a323e72bd9d9ca137714347fb6f80786dc3f7d95 100644 GIT binary patch literal 19252 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 ALI3~& literal 19353 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 Date: Thu, 15 Jun 2023 00:35:25 -0400 Subject: [PATCH 069/196] Update regex to not select line in comments --- src/core/helpers/problem_code.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index 90a2435..d8ba014 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -31,8 +31,8 @@ impl ProblemCode { } pub fn get_fn_info(&self) -> anyhow::Result { - let re = Regex::new(r#"pub fn ([a-z_0-9]*)\((.*)\)(?: ?-> ?(.*))? \{"#)?; - let caps = if let Some(caps) = re.captures(&self.code) { + let re = Regex::new(r#"\n\s*pub fn ([a-z_0-9]*)\((.*)\)(?: ?-> ?(.*))? \{"#)?; + let caps = if let Some(caps) = re.captures(dbg!(&self.code)) { caps } else { bail!("Regex failed to match"); From 1e40870b39e0c2e3b126e570d1f90bba314781d8 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Thu, 15 Jun 2023 00:36:20 -0400 Subject: [PATCH 070/196] Change method of checking for design problem Sometimes they include comments at the top of the file like for trees --- src/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index d8ba014..3dfd143 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -27,7 +27,7 @@ impl AsRef for ProblemCode { impl ProblemCode { pub fn is_design(&self) -> bool { - !self.code.starts_with("impl Solution {") + !self.code.contains("impl Solution {") } pub fn get_fn_info(&self) -> anyhow::Result { From 201fa4da87e42acfb632ef240702f932e1b808f9 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Thu, 15 Jun 2023 01:11:19 -0400 Subject: [PATCH 071/196] Add support for tree and list questions --- src/core/generate.rs | 10 +++++- src/core/helpers/code_snippet.rs | 2 +- src/core/helpers/problem_code.rs | 50 +++++++++++++++++++--------- src/core/helpers/problem_metadata.rs | 20 +++++++---- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/core/generate.rs b/src/core/generate.rs index 5a5d24c..17dd98b 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -67,7 +67,15 @@ pub fn create_module_code( // Add struct for non design questions if !problem_code.is_design() { - code_snippet.push_str("\npub struct Solution;\n") + code_snippet.push_str("\nstruct Solution;\n") + } + + // Add leet code types + if problem_code.has_tree() { + code_snippet.push_str("use cargo_leet::TreeNode;\n"); + } + if problem_code.has_list() { + code_snippet.push_str("use cargo_leet::ListNode;\n"); } // Add tests diff --git a/src/core/helpers/code_snippet.rs b/src/core/helpers/code_snippet.rs index e9a0801..db87dc3 100644 --- a/src/core/helpers/code_snippet.rs +++ b/src/core/helpers/code_snippet.rs @@ -41,7 +41,7 @@ pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result Ok(result.into()), + Some(result) => Ok(result.try_into()?), None => bail!("Rust not supported for this problem"), } } diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index 3dfd143..c97fa16 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -5,17 +5,15 @@ use regex::Regex; pub struct ProblemCode { code: String, + pub fn_info: FunctionInfo, } -impl From for ProblemCode { - fn from(value: String) -> Self { - Self { code: value } - } -} +impl TryFrom for ProblemCode { + type Error = anyhow::Error; -impl From for String { - fn from(value: ProblemCode) -> Self { - value.code + fn try_from(code: String) -> Result { + let fn_info = Self::get_fn_info(&code).context("Failed to get function info")?; + Ok(Self { code, fn_info }) } } @@ -30,9 +28,9 @@ impl ProblemCode { !self.code.contains("impl Solution {") } - pub fn get_fn_info(&self) -> anyhow::Result { + fn get_fn_info(code: &str) -> anyhow::Result { let re = Regex::new(r#"\n\s*pub fn ([a-z_0-9]*)\((.*)\)(?: ?-> ?(.*))? \{"#)?; - let caps = if let Some(caps) = re.captures(dbg!(&self.code)) { + let caps = if let Some(caps) = re.captures(code) { caps } else { bail!("Regex failed to match"); @@ -66,6 +64,14 @@ impl ProblemCode { return_type, }) } + + pub fn has_tree(&self) -> bool { + self.fn_info.has_tree() + } + + pub fn has_list(&self) -> bool { + self.fn_info.has_list() + } } pub struct FunctionInfo { @@ -100,7 +106,7 @@ impl FunctionInfo { names.join(", ") } - pub(crate) fn get_test_case(&self, example_test_case_raw: &str) -> anyhow::Result { + pub fn get_test_case(&self, example_test_case_raw: &str) -> anyhow::Result { let mut result = String::new(); let n = self.fn_args.len(); let lines: Vec<_> = example_test_case_raw.lines().collect(); @@ -136,6 +142,14 @@ impl FunctionInfo { Ok(result) } + + pub fn has_tree(&self) -> bool { + self.fn_args.args.iter().any(|arg| arg.arg_type.is_tree()) + } + + pub fn has_list(&self) -> bool { + self.fn_args.args.iter().any(|arg| arg.arg_type.is_list()) + } } #[derive(Debug)] @@ -215,13 +229,11 @@ impl FunctionArgType { FunctionArgType::FATString => line.to_string(), FunctionArgType::FATList => { Self::does_pass_basic_vec_tests(line)?; - // TODO finish converting input into correct type - format!("\"{line}\"") + format!("ListHead::from(\"{line}\").into()") } FunctionArgType::FATTree => { Self::does_pass_basic_vec_tests(line)?; - // TODO finish converting input into correct type - format!("\"{line}\"") + format!("TreeRoot::from(\"{line}\").into()") } }) } @@ -232,6 +244,14 @@ impl FunctionArgType { } Ok(()) } + + fn is_tree(&self) -> bool { + matches!(self, FunctionArgType::FATTree) + } + + fn is_list(&self) -> bool { + matches!(self, FunctionArgType::FATList) + } } impl Display for FunctionArgType { diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index 552b4f5..ca4fbc9 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -41,17 +41,21 @@ impl ProblemMetadata { pub fn get_test_cases(&self, problem_code: &ProblemCode) -> anyhow::Result { info!("Going to get tests"); - let fn_info = problem_code - .get_fn_info() - .context("Failed to get function info")?; + let mut imports = String::new(); let tests = if !problem_code.is_design() { info!("This is NOT a design problem"); - self.get_test_cases_is_not_design(fn_info) + if problem_code.has_tree() { + imports.push_str("use cargo_leet::TreeRoot;\n"); + } + if problem_code.has_list() { + imports.push_str("use cargo_leet::ListHead;\n"); + } + self.get_test_cases_is_not_design(&problem_code.fn_info) .context("Failed to get test cases for non-design problem")? } else { info!("This is a design problem"); - self.get_test_cases_is_design(fn_info) + self.get_test_cases_is_design(&problem_code.fn_info) .context("Failed to get test cases for design problem")? }; @@ -60,13 +64,15 @@ impl ProblemMetadata { #[cfg(test)] mod tests {{ use super::*; + {imports} + {tests} }} "# )) } - fn get_test_cases_is_not_design(&self, fn_info: FunctionInfo) -> anyhow::Result { + fn get_test_cases_is_not_design(&self, fn_info: &FunctionInfo) -> anyhow::Result { let mut result = "use rstest::rstest; #[rstest] @@ -96,7 +102,7 @@ mod tests {{ Ok(result) } - fn get_test_cases_is_design(&self, _fn_info: FunctionInfo) -> anyhow::Result { + fn get_test_cases_is_design(&self, _fn_info: &FunctionInfo) -> anyhow::Result { // TODO Create the test cases for design problems Ok("".to_string()) } From d0205ca640cb22841079daf4e485c34e818c66cd Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Thu, 15 Jun 2023 01:43:53 -0400 Subject: [PATCH 072/196] Refactor to better model domain Design problems do not have function info (they have multiple functions) --- src/core/generate.rs | 2 +- src/core/helpers/problem_code.rs | 44 +++++++++++++++++++++++----- src/core/helpers/problem_metadata.rs | 31 +++++++++++--------- 3 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/core/generate.rs b/src/core/generate.rs index 17dd98b..c6635ec 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -66,7 +66,7 @@ pub fn create_module_code( code_snippet.push_str("\n\n"); // Add struct for non design questions - if !problem_code.is_design() { + if problem_code.type_.is_non_design() { code_snippet.push_str("\nstruct Solution;\n") } diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index c97fa16..f85b92b 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -1,19 +1,41 @@ use std::fmt::Display; use anyhow::{bail, Context}; +use log::info; use regex::Regex; pub struct ProblemCode { code: String, - pub fn_info: FunctionInfo, + pub type_: ProblemType, +} + +pub enum ProblemType { + NonDesign(FunctionInfo), + Design, +} + +impl ProblemType { + /// Returns `true` if the problem type is [`NonDesign`]. + /// + /// [`NonDesign`]: ProblemType::NonDesign + #[must_use] + pub fn is_non_design(&self) -> bool { + matches!(self, Self::NonDesign(..)) + } } impl TryFrom for ProblemCode { type Error = anyhow::Error; fn try_from(code: String) -> Result { - let fn_info = Self::get_fn_info(&code).context("Failed to get function info")?; - Ok(Self { code, fn_info }) + let type_ = if Self::is_design(&code) { + info!("Problem Type is Design"); + ProblemType::Design + } else { + info!("Problem Type is NonDesign"); + ProblemType::NonDesign(Self::get_fn_info(&code).context("Failed to get function info")?) + }; + Ok(Self { code, type_ }) } } @@ -24,8 +46,8 @@ impl AsRef for ProblemCode { } impl ProblemCode { - pub fn is_design(&self) -> bool { - !self.code.contains("impl Solution {") + fn is_design(code: &str) -> bool { + !code.contains("impl Solution {") } fn get_fn_info(code: &str) -> anyhow::Result { @@ -66,11 +88,19 @@ impl ProblemCode { } pub fn has_tree(&self) -> bool { - self.fn_info.has_tree() + if let ProblemType::NonDesign(fn_info) = &self.type_ { + fn_info.has_tree() + } else { + false + } } pub fn has_list(&self) -> bool { - self.fn_info.has_list() + if let ProblemType::NonDesign(fn_info) = &self.type_ { + fn_info.has_list() + } else { + false + } } } diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index ca4fbc9..47e3e09 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -1,4 +1,4 @@ -use crate::config::Config; +use crate::{config::Config, core::helpers::problem_code::ProblemType}; use anyhow::Context; use log::info; use serde::Deserialize; @@ -43,20 +43,23 @@ impl ProblemMetadata { let mut imports = String::new(); - let tests = if !problem_code.is_design() { - info!("This is NOT a design problem"); - if problem_code.has_tree() { - imports.push_str("use cargo_leet::TreeRoot;\n"); + let tests = match &problem_code.type_ { + ProblemType::NonDesign(fn_info) => { + info!("This is NOT a design problem"); + if problem_code.has_tree() { + imports.push_str("use cargo_leet::TreeRoot;\n"); + } + if problem_code.has_list() { + imports.push_str("use cargo_leet::ListHead;\n"); + } + self.get_test_cases_is_not_design(fn_info) + .context("Failed to get test cases for non-design problem")? } - if problem_code.has_list() { - imports.push_str("use cargo_leet::ListHead;\n"); + ProblemType::Design => { + info!("This is a design problem"); + self.get_test_cases_is_design() + .context("Failed to get test cases for design problem")? } - self.get_test_cases_is_not_design(&problem_code.fn_info) - .context("Failed to get test cases for non-design problem")? - } else { - info!("This is a design problem"); - self.get_test_cases_is_design(&problem_code.fn_info) - .context("Failed to get test cases for design problem")? }; Ok(format!( @@ -102,7 +105,7 @@ mod tests {{ Ok(result) } - fn get_test_cases_is_design(&self, _fn_info: &FunctionInfo) -> anyhow::Result { + fn get_test_cases_is_design(&self) -> anyhow::Result { // TODO Create the test cases for design problems Ok("".to_string()) } From eb6c6dc7c921eb8bd744a43da4f73c60fc452cd5 Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Thu, 15 Jun 2023 01:45:16 -0400 Subject: [PATCH 073/196] Remove unneeded log messages --- src/core/helpers/problem_metadata.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index 47e3e09..5a31d6b 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -45,7 +45,6 @@ impl ProblemMetadata { let tests = match &problem_code.type_ { ProblemType::NonDesign(fn_info) => { - info!("This is NOT a design problem"); if problem_code.has_tree() { imports.push_str("use cargo_leet::TreeRoot;\n"); } @@ -55,11 +54,9 @@ impl ProblemMetadata { self.get_test_cases_is_not_design(fn_info) .context("Failed to get test cases for non-design problem")? } - ProblemType::Design => { - info!("This is a design problem"); - self.get_test_cases_is_design() - .context("Failed to get test cases for design problem")? - } + ProblemType::Design => self + .get_test_cases_is_design() + .context("Failed to get test cases for design problem")?, }; Ok(format!( From 157f9f684651a444a2b3ad0aa53b2e98ccacc81f Mon Sep 17 00:00:00 2001 From: Che <43485962+c-git@users.noreply.github.com> Date: Thu, 15 Jun 2023 01:49:11 -0400 Subject: [PATCH 074/196] List currently only implemented for vec --- src/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/helpers/problem_code.rs b/src/core/helpers/problem_code.rs index f85b92b..86c67cb 100644 --- a/src/core/helpers/problem_code.rs +++ b/src/core/helpers/problem_code.rs @@ -259,7 +259,7 @@ impl FunctionArgType { FunctionArgType::FATString => line.to_string(), FunctionArgType::FATList => { Self::does_pass_basic_vec_tests(line)?; - format!("ListHead::from(\"{line}\").into()") + format!("ListHead::from(vec!{line}).into()") } FunctionArgType::FATTree => { Self::does_pass_basic_vec_tests(line)?; From 646a80fef82a273f31ef50d4bd56a47bd582ad5c Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 16 Jun 2023 09:13:25 -0400 Subject: [PATCH 075/196] Add problem number and title to the code output --- src/core/generate.rs | 11 +++++++++++ src/core/helpers/problem_metadata.rs | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/src/core/generate.rs b/src/core/generate.rs index c6635ec..2440276 100644 --- a/src/core/generate.rs +++ b/src/core/generate.rs @@ -58,6 +58,17 @@ pub fn create_module_code( Config::LEETCODE_PROBLEM_URL ); + // Add problem number and title + code_snippet.push_str(&format!( + "//! {}\n", + meta_data + .get_num_and_title() + .context("Failed to get problem number and title")? + )); + + // Add blank line between docstring and code + code_snippet.push('\n'); + // Get code snippet let problem_code = get_code_snippet_for_problem(&title_slug)?; code_snippet.push_str(problem_code.as_ref()); diff --git a/src/core/helpers/problem_metadata.rs b/src/core/helpers/problem_metadata.rs index 5a31d6b..a080b46 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/core/helpers/problem_metadata.rs @@ -19,6 +19,8 @@ struct QuestionWrapper { pub struct ProblemMetadata { #[serde(rename = "questionFrontendId")] id: String, + #[serde(rename = "questionTitle")] + title: String, #[serde(rename = "exampleTestcaseList")] example_test_case_list: Vec, } @@ -38,6 +40,10 @@ impl ProblemMetadata { Ok(result) } + pub fn get_num_and_title(&self) -> anyhow::Result { + Ok(format!("{}. {}", self.get_id()?, self.title)) + } + pub fn get_test_cases(&self, problem_code: &ProblemCode) -> anyhow::Result { info!("Going to get tests"); @@ -115,6 +121,7 @@ pub fn get_problem_metadata(title_slug: &str) -> anyhow::Result "query": r#"query consolePanelConfig($titleSlug: String!) { question(titleSlug: $titleSlug) { questionFrontendId + questionTitle exampleTestcaseList } }"#, From 2f24ef8418870f1c99cf9d760d4c0f64ccc2e336 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 16 Jun 2023 12:16:11 -0400 Subject: [PATCH 076/196] Add feature flags --- Cargo.toml | 36 ++++++++++++++----- src/lib.rs | 20 +++++++---- src/{ => tool}/cli.rs | 0 src/{ => tool}/config.rs | 0 src/{ => tool}/core/generate.rs | 7 ++-- src/{ => tool}/core/helpers/code_snippet.rs | 2 +- .../core/helpers/daily_challenge.rs | 2 +- src/{ => tool}/core/helpers/mod.rs | 0 src/{ => tool}/core/helpers/problem_code.rs | 0 .../core/helpers/problem_metadata.rs | 2 +- src/{ => tool}/core/helpers/write_to_disk.rs | 0 src/{ => tool}/core/mod.rs | 4 +-- src/{ => tool}/log.rs | 1 + src/tool/mod.rs | 4 +++ 14 files changed, 54 insertions(+), 24 deletions(-) rename src/{ => tool}/cli.rs (100%) rename src/{ => tool}/config.rs (100%) rename src/{ => tool}/core/generate.rs (96%) rename src/{ => tool}/core/helpers/code_snippet.rs (97%) rename src/{ => tool}/core/helpers/daily_challenge.rs (96%) rename src/{ => tool}/core/helpers/mod.rs (100%) rename src/{ => tool}/core/helpers/problem_code.rs (100%) rename src/{ => tool}/core/helpers/problem_metadata.rs (98%) rename src/{ => tool}/core/helpers/write_to_disk.rs (100%) rename src/{ => tool}/core/mod.rs (87%) rename src/{ => tool}/log.rs (87%) create mode 100644 src/tool/mod.rs diff --git a/Cargo.toml b/Cargo.toml index f56ca2b..8dd5683 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,30 @@ license = "MIT OR Apache-2.0" edition = "2021" [dependencies] -anyhow = "1.0.71" -clap = { version = "4.3.3", features = ["derive", "cargo"] } -convert_case = "0.6" -env_logger = "0.10.0" -log = "0.4.18" -regex = "1.8.4" -serde = { version = "1.0.164", features = ["derive"] } -serde_flat_path = "0.1.2" -ureq = { version = "2.6", features = ["json"] } +anyhow = { version = "1.0.71", optional = true } +convert_case = { version = "0.6", optional = true } +env_logger = { version = "0.10.0", optional = true } +log = { version = "0.4.18", optional = true } +regex = { version = "1.8.4", optional = true } +serde_flat_path = { version = "0.1.2", optional = true } +clap = { version = "4.3.3", features = ["derive", "cargo"], optional = true } +serde = { version = "1.0.164", features = ["derive"], optional = true } +ureq = { version = "2.6", features = ["json"], optional = true } + + +[features] +default = ["tool"] +# Add support for leetcode's environment +leet_env = [] +# Items used when running as a binary +tool = [ + "dep:anyhow", + "dep:convert_case", + "dep:env_logger", + "dep:log", + "dep:regex", + "dep:serde_flat_path", + "dep:clap", + "dep:serde", + "dep:ureq", +] diff --git a/src/lib.rs b/src/lib.rs index bf9cb7d..e60cd8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,24 @@ #![forbid(unsafe_code)] -mod cli; -mod config; -mod core; +#[cfg(feature = "leet_env")] mod leetcode_env; -mod log; +#[cfg(feature = "tool")] +mod tool; // For use in external code +#[cfg(feature = "leet_env")] pub use leetcode_env::list::ListHead; +#[cfg(feature = "leet_env")] pub use leetcode_env::list::ListNode; +#[cfg(feature = "leet_env")] pub use leetcode_env::tree::TreeNode; +#[cfg(feature = "leet_env")] pub use leetcode_env::tree::TreeRoot; // For use in main.rs -pub use crate::core::run; -pub use crate::log::init_logging; -pub use cli::CargoCli; +#[cfg(feature = "tool")] +pub use crate::tool::cli::CargoCli; +#[cfg(feature = "tool")] +pub use crate::tool::core::run; +#[cfg(feature = "tool")] +pub use crate::tool::log::init_logging; diff --git a/src/cli.rs b/src/tool/cli.rs similarity index 100% rename from src/cli.rs rename to src/tool/cli.rs diff --git a/src/config.rs b/src/tool/config.rs similarity index 100% rename from src/config.rs rename to src/tool/config.rs diff --git a/src/core/generate.rs b/src/tool/core/generate.rs similarity index 96% rename from src/core/generate.rs rename to src/tool/core/generate.rs index 2440276..4a8bff5 100644 --- a/src/core/generate.rs +++ b/src/tool/core/generate.rs @@ -3,7 +3,8 @@ use convert_case::{Case, Casing}; use log::info; use std::borrow::Cow; -use crate::{ +use crate::tool::{ + cli, config::Config, core::helpers::{ code_snippet::get_code_snippet_for_problem, daily_challenge, @@ -11,7 +12,7 @@ use crate::{ }, }; -pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> { +pub(crate) fn do_generate(args: &cli::GenerateArgs) -> anyhow::Result<()> { let title_slug: Cow = if let Some(specific_problem) = &args.problem { // Problem specified if is_url(specific_problem) { @@ -45,7 +46,7 @@ pub(crate) fn do_generate(args: &crate::cli::GenerateArgs) -> anyhow::Result<()> /// NB: Did not return `Cow` because `module_name` is always a modified version of the input pub fn create_module_code( title_slug: Cow, - args: &crate::cli::GenerateArgs, + args: &cli::GenerateArgs, ) -> anyhow::Result<(String, String)> { info!("Building module contents for {title_slug}"); diff --git a/src/core/helpers/code_snippet.rs b/src/tool/core/helpers/code_snippet.rs similarity index 97% rename from src/core/helpers/code_snippet.rs rename to src/tool/core/helpers/code_snippet.rs index db87dc3..9e1fdb4 100644 --- a/src/core/helpers/code_snippet.rs +++ b/src/tool/core/helpers/code_snippet.rs @@ -1,5 +1,5 @@ use super::problem_code::ProblemCode; -use crate::config::Config; +use crate::tool::config::Config; use anyhow::{bail, Context}; use log::info; use serde::Deserialize; diff --git a/src/core/helpers/daily_challenge.rs b/src/tool/core/helpers/daily_challenge.rs similarity index 96% rename from src/core/helpers/daily_challenge.rs rename to src/tool/core/helpers/daily_challenge.rs index 21f9dbd..5d43451 100644 --- a/src/core/helpers/daily_challenge.rs +++ b/src/tool/core/helpers/daily_challenge.rs @@ -2,7 +2,7 @@ use anyhow::Context; use serde::Deserialize; use serde_flat_path::flat_path; -use crate::config::Config; +use crate::tool::config::Config; #[flat_path] #[derive(Deserialize)] diff --git a/src/core/helpers/mod.rs b/src/tool/core/helpers/mod.rs similarity index 100% rename from src/core/helpers/mod.rs rename to src/tool/core/helpers/mod.rs diff --git a/src/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs similarity index 100% rename from src/core/helpers/problem_code.rs rename to src/tool/core/helpers/problem_code.rs diff --git a/src/core/helpers/problem_metadata.rs b/src/tool/core/helpers/problem_metadata.rs similarity index 98% rename from src/core/helpers/problem_metadata.rs rename to src/tool/core/helpers/problem_metadata.rs index a080b46..feef108 100644 --- a/src/core/helpers/problem_metadata.rs +++ b/src/tool/core/helpers/problem_metadata.rs @@ -1,4 +1,4 @@ -use crate::{config::Config, core::helpers::problem_code::ProblemType}; +use crate::tool::{config::Config, core::helpers::problem_code::ProblemType}; use anyhow::Context; use log::info; use serde::Deserialize; diff --git a/src/core/helpers/write_to_disk.rs b/src/tool/core/helpers/write_to_disk.rs similarity index 100% rename from src/core/helpers/write_to_disk.rs rename to src/tool/core/helpers/write_to_disk.rs diff --git a/src/core/mod.rs b/src/tool/core/mod.rs similarity index 87% rename from src/core/mod.rs rename to src/tool/core/mod.rs index c16a4c6..c748b9d 100644 --- a/src/core/mod.rs +++ b/src/tool/core/mod.rs @@ -2,7 +2,7 @@ mod generate; mod helpers; use self::generate::do_generate; -use crate::cli::Cli; +use crate::tool::cli::{self, Cli}; use anyhow::{bail, Context}; use std::{env, path::Path}; @@ -12,7 +12,7 @@ pub fn run(cli: &Cli) -> anyhow::Result<()> { working_directory_validation()?; match &cli.command { - crate::cli::Commands::Generate(args) => do_generate(args), + cli::Commands::Generate(args) => do_generate(args), } } diff --git a/src/log.rs b/src/tool/log.rs similarity index 87% rename from src/log.rs rename to src/tool/log.rs index 6e30bdb..70a66f0 100644 --- a/src/log.rs +++ b/src/tool/log.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "tool")] use env_logger::Builder; use log::LevelFilter; diff --git a/src/tool/mod.rs b/src/tool/mod.rs new file mode 100644 index 0000000..6850096 --- /dev/null +++ b/src/tool/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod cli; +pub(crate) mod config; +pub(crate) mod core; +pub(crate) mod log; From ccfca010f80a26efa582827198a4fa6c9b311296 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 16 Jun 2023 12:18:51 -0400 Subject: [PATCH 077/196] Use implicit features because of old rust on leetcode --- Cargo.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8dd5683..e75c718 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,13 +26,13 @@ default = ["tool"] leet_env = [] # Items used when running as a binary tool = [ - "dep:anyhow", - "dep:convert_case", - "dep:env_logger", - "dep:log", - "dep:regex", - "dep:serde_flat_path", - "dep:clap", - "dep:serde", - "dep:ureq", + "anyhow", + "convert_case", + "env_logger", + "log", + "regex", + "serde_flat_path", + "clap", + "serde", + "ureq", ] From b4ce96a1ee7fe3d2a615840883499ba38d106dcb Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:17:25 -0400 Subject: [PATCH 078/196] Remove comments and unmodified settings --- deny.toml | 258 ------------------------------------------------------ 1 file changed, 258 deletions(-) diff --git a/deny.toml b/deny.toml index 17bf962..172adf9 100644 --- a/deny.toml +++ b/deny.toml @@ -1,265 +1,7 @@ -# This template contains all of the possible sections and their default values - -# Note that all fields that take a lint level have these possible values: -# * deny - An error will be produced and the check will fail -# * warn - A warning will be produced, but the check will not fail -# * allow - No warning or error will be produced, though in some cases a note -# will be - -# The values provided in this template are the default values that will be used -# when any section or field is not specified in your own configuration - -# Root options - -# If 1 or more target triples (and optionally, target_features) are specified, -# only the specified targets will be checked when running `cargo deny check`. -# This means, if a particular package is only ever used as a target specific -# dependency, such as, for example, the `nix` crate only being used via the -# `target_family = "unix"` configuration, that only having windows targets in -# this list would mean the nix crate, as well as any of its exclusive -# dependencies not shared by any other crates, would be ignored, as the target -# list here is effectively saying which targets you are building for. -targets = [ - # The triple can be any string, but only the target triples built in to - # rustc (as of 1.40) can be checked against actual config expressions - #{ triple = "x86_64-unknown-linux-musl" }, - # You can also specify which target_features you promise are enabled for a - # particular target. target_features are currently not validated against - # the actual valid features supported by the target architecture. - #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, -] -# When creating the dependency graph used as the source of truth when checks are -# executed, this field can be used to prune crates from the graph, removing them -# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate -# is pruned from the graph, all of its dependencies will also be pruned unless -# they are connected to another crate in the graph that hasn't been pruned, -# so it should be used with care. The identifiers are [Package ID Specifications] -# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) -#exclude = [] -# If true, metadata will be collected with `--all-features`. Note that this can't -# be toggled off if true, if you want to conditionally enable `--all-features` it -# is recommended to pass `--all-features` on the cmd line instead -all-features = false -# If true, metadata will be collected with `--no-default-features`. The same -# caveat with `all-features` applies -no-default-features = false -# If set, these feature will be enabled when collecting metadata. If `--features` -# is specified on the cmd line they will take precedence over this option. -#features = [] -# When outputting inclusion graphs in diagnostics that include features, this -# option can be used to specify the depth at which feature edges will be added. -# This option is included since the graphs can be quite large and the addition -# of features from the crate(s) to all of the graph roots can be far too verbose. -# This option can be overridden via `--feature-depth` on the cmd line -feature-depth = 1 - -# This section is considered when running `cargo deny check advisories` -# More documentation for the advisories section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] -# The path where the advisory database is cloned/fetched into -db-path = "~/.cargo/advisory-db" -# The url(s) of the advisory databases to use -db-urls = ["https://github.com/rustsec/advisory-db"] -# The lint level for security vulnerabilities -vulnerability = "deny" -# The lint level for unmaintained crates -unmaintained = "warn" -# The lint level for crates that have been yanked from their source registry yanked = "deny" -# The lint level for crates with security notices. Note that as of -# 2019-12-17 there are no security notice advisories in -# https://github.com/rustsec/advisory-db -notice = "warn" -# A list of advisory IDs to ignore. Note that ignored advisories will still -# output a note when they are encountered. -ignore = [ - #"RUSTSEC-0000-0000", -] -# Threshold for security vulnerabilities, any vulnerability with a CVSS score -# lower than the range specified will be ignored. Note that ignored advisories -# will still output a note when they are encountered. -# * None - CVSS Score 0.0 -# * Low - CVSS Score 0.1 - 3.9 -# * Medium - CVSS Score 4.0 - 6.9 -# * High - CVSS Score 7.0 - 8.9 -# * Critical - CVSS Score 9.0 - 10.0 -#severity-threshold = - -# If this is true, then cargo deny will use the git executable to fetch advisory database. -# If this is false, then it uses a built-in git library. -# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. -# See Git Authentication for more information about setting up git authentication. -#git-fetch-with-cli = true -# This section is considered when running `cargo deny check licenses` -# More documentation for the licenses section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] -# The lint level for crates which do not have a detectable license unlicensed = "warn" -# List of explicitly allowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = ["MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception"] -# List of explicitly disallowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. -deny = [ - #"Nokia", -] -# Lint level for licenses considered copyleft -copyleft = "warn" -# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses -# * both - The license will be approved if it is both OSI-approved *AND* FSF -# * either - The license will be approved if it is either OSI-approved *OR* FSF -# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF -# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved -# * neither - This predicate is ignored and the default lint level is used -allow-osi-fsf-free = "neither" -# Lint level used when no other predicates are matched -# 1. License isn't in the allow or deny lists -# 2. License isn't copyleft -# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" default = "warn" -# The confidence threshold for detecting a license from license text. -# The higher the value, the more closely the license text must be to the -# canonical license text of a valid SPDX license file. -# [possible values: any between 0.0 and 1.0]. -confidence-threshold = 0.8 -# Allow 1 or more licenses on a per-crate basis, so that particular licenses -# aren't accepted for every possible crate as with the normal allow list -exceptions = [ - # Each entry is the crate and version constraint, and its specific allow - # list - #{ allow = ["Zlib"], name = "adler32", version = "*" }, -] - -# Some crates don't have (easily) machine readable licensing information, -# adding a clarification entry for it allows you to manually specify the -# licensing information -#[[licenses.clarify]] -# The name of the crate the clarification applies to -#name = "ring" -# The optional version constraint for the crate -#version = "*" -# The SPDX expression for the license requirements of the crate -#expression = "MIT AND ISC AND OpenSSL" -# One or more files in the crate's source used as the "source of truth" for -# the license expression. If the contents match, the clarification will be used -# when running the license check, otherwise the clarification will be ignored -# and the crate will be checked normally, which may produce warnings or errors -# depending on the rest of your configuration -#license-files = [ -# Each entry is a crate relative path, and the (opaque) hash of its contents -#{ path = "LICENSE", hash = 0xbd0eed23 } -#] - -[licenses.private] -# If true, ignores workspace crates that aren't published, or are only -# published to private registries. -# To see how to mark a crate as unpublished (to the official registry), -# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. -ignore = false -# One or more private registries that you might publish crates to, if a crate -# is only published to private registries, and ignore is true, the crate will -# not have its license(s) checked -registries = [ - #"https://sekretz.com/registry -] - -# This section is considered when running `cargo deny check bans`. -# More documentation about the 'bans' section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html -[bans] -# Lint level for when multiple versions of the same crate are detected -multiple-versions = "warn" -# Lint level for when a crate version requirement is `*` -wildcards = "allow" -# The graph highlighting used when creating dotgraphs for crates -# with multiple versions -# * lowest-version - The path to the lowest versioned duplicate is highlighted -# * simplest-path - The path to the version with the fewest edges is highlighted -# * all - Both lowest-version and simplest-path are used -highlight = "all" -# The default lint level for `default` features for crates that are members of -# the workspace that is being checked. This can be overriden by allowing/denying -# `default` on a crate-by-crate basis if desired. -workspace-default-features = "allow" -# The default lint level for `default` features for external crates that are not -# members of the workspace. This can be overriden by allowing/denying `default` -# on a crate-by-crate basis if desired. -external-default-features = "allow" -# List of crates that are allowed. Use with care! -allow = [ - #{ name = "ansi_term", version = "=0.11.0" }, -] -# List of crates to deny -deny = [ - # Each entry the name of a crate and a version range. If version is - # not specified, all versions will be matched. - #{ name = "ansi_term", version = "=0.11.0" }, - # - # Wrapper crates can optionally be specified to allow the crate when it - # is a direct dependency of the otherwise banned crate - #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, -] - -# List of features to allow/deny -# Each entry the name of a crate and a version range. If version is -# not specified, all versions will be matched. -#[[bans.features]] -#name = "reqwest" -# Features to not allow -#deny = ["json"] -# Features to allow -#allow = [ -# "rustls", -# "__rustls", -# "__tls", -# "hyper-rustls", -# "rustls", -# "rustls-pemfile", -# "rustls-tls-webpki-roots", -# "tokio-rustls", -# "webpki-roots", -#] -# If true, the allowed features must exactly match the enabled feature set. If -# this is set there is no point setting `deny` -#exact = true - -# Certain crates/versions that will be skipped when doing duplicate detection. -skip = [ - #{ name = "ansi_term", version = "=0.11.0" }, -] -# Similarly to `skip` allows you to skip certain crates during duplicate -# detection. Unlike skip, it also includes the entire tree of transitive -# dependencies starting at the specified crate, up to a certain depth, which is -# by default infinite. -skip-tree = [ - #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, -] - -# This section is considered when running `cargo deny check sources`. -# More documentation about the 'sources' section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html -[sources] -# Lint level for what to happen when a crate from a crate registry that is not -# in the allow list is encountered -unknown-registry = "warn" -# Lint level for what to happen when a crate from a git repository that is not -# in the allow list is encountered -unknown-git = "warn" -# List of URLs for allowed crate registries. Defaults to the crates.io index -# if not specified. If it is specified but empty, no registries are allowed. -allow-registry = ["https://github.com/rust-lang/crates.io-index"] -# List of URLs for allowed Git repositories -allow-git = [] - -[sources.allow-org] -# 1 or more github.com organizations to allow git sources for -github = [""] -# 1 or more gitlab.com organizations to allow git sources for -gitlab = [""] -# 1 or more bitbucket.org organizations to allow git sources for -bitbucket = [""] From b7e13f3e153d76245f8411c7b3c129e263c5af04 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:23:34 -0400 Subject: [PATCH 079/196] Clarify documentation --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3310363..9e50297 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ## Installation -NB: Installing from another source overwrites if it is already installed +NB: If cargo-leet is already installed you do the install it will just replace it even it it was previously installed from a different source. For example if you install it from a clone then run the command to install from git it will replace the existing version that is installed (they will not both be installed). ### From GitHub From 5cbef9ae9108dadb0054fce0ba1f067e8099da84 Mon Sep 17 00:00:00 2001 From: c-git <43485962+c-git@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:40:15 +0000 Subject: [PATCH 080/196] Add alias for generate and short --- README.md | 2 +- src/tool/cli.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9e50297..8776f4e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## ScreenShots - ### `cargo leet` + ### `cargo leet (needs to be updated)` ![ScreenShot](assets/help_scr_shot_top.png) ### `cargo leet generate --help` diff --git a/src/tool/cli.rs b/src/tool/cli.rs index 72723d3..7c0ee29 100644 --- a/src/tool/cli.rs +++ b/src/tool/cli.rs @@ -18,7 +18,7 @@ pub struct Cli { pub command: Commands, /// Specify the path to the project root (If not provided uses current working directory) - #[arg(long, short, value_name = "FOLDER")] + #[arg(long, short, value_name = "FOLDER")] // This is an example where I use it path: Option, /// Set logging level to use @@ -50,6 +50,7 @@ impl Cli { #[derive(Subcommand, Debug)] pub enum Commands { + #[clap(visible_alias = "gen", short_flag = 'g')] Generate(GenerateArgs), } From 5f760ea5fa835b9613af96dc94945d2d2c5d14cf Mon Sep 17 00:00:00 2001 From: c-git <43485962+c-git@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:06:13 +0000 Subject: [PATCH 081/196] Turn on auto save --- .vscode/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 65be3a1..cc45aba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { "cSpell.words": [ "Veci" - ] + ], + "editor.formatOnSave": true, + "files.autoSave": "onFocusChange" } \ No newline at end of file From 12c637a31973b888428247ad3ed8faac8661e601 Mon Sep 17 00:00:00 2001 From: c-git <43485962+c-git@users.noreply.github.com> Date: Tue, 20 Jun 2023 16:53:18 +0000 Subject: [PATCH 082/196] Added link to default template --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index 172adf9..b9aa8ec 100644 --- a/deny.toml +++ b/deny.toml @@ -1,3 +1,4 @@ +# See defaults at https://github.com/EmbarkStudios/cargo-deny/blob/main/deny.template.toml [advisories] yanked = "deny" From d4482a7f13cc42b72bfa95fe6e00f5bce4d34884 Mon Sep 17 00:00:00 2001 From: c-git <43485962+c-git@users.noreply.github.com> Date: Tue, 20 Jun 2023 17:03:24 +0000 Subject: [PATCH 083/196] Add context for better error messages on invalid input --- src/tool/core/generate.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index 4a8bff5..e5ee4c5 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -14,18 +14,8 @@ use crate::tool::{ pub(crate) fn do_generate(args: &cli::GenerateArgs) -> anyhow::Result<()> { let title_slug: Cow = if let Some(specific_problem) = &args.problem { - // Problem specified - if is_url(specific_problem) { - // Working with a url - info!("Using '{specific_problem}' as a url"); - let slug = url_to_slug(specific_problem)?; - info!("Extracted slug '{slug}' from url"); - Cow::Owned(slug) - } else { - // This is expected to be a valid slug - info!("Using '{specific_problem}' as a slug"); - Cow::Borrowed(specific_problem) - } + get_slug_from_args(specific_problem) + .with_context(|| format!("Expected URL or slug but got {specific_problem}"))? } else { // Daily problem let slug = daily_challenge::get_daily_challenge_slug()?; @@ -40,6 +30,21 @@ pub(crate) fn do_generate(args: &cli::GenerateArgs) -> anyhow::Result<()> { Ok(()) } +fn get_slug_from_args(specific_problem: &String) -> anyhow::Result> { + Ok(if is_url(specific_problem) { + // Working with a url + info!("Using '{specific_problem}' as a url"); + let slug = url_to_slug(specific_problem)?; + info!("Extracted slug '{slug}' from url"); + Cow::Owned(slug) + } else { + // This is expected to be a valid slug + //TODO: Validate slug + info!("Using '{specific_problem}' as a slug"); + Cow::Borrowed(specific_problem) + }) +} + /// Gets the code and other data from leetcode and generates the suitable code for the module and the name of the module /// Returns the module name and the module code /// From 242dec4ec750433de667204fba9199da0ffddaa6 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:53:19 -0400 Subject: [PATCH 084/196] Document current reasoning --- src/tool/core/generate.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index e5ee4c5..7977a43 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -116,6 +116,7 @@ pub fn create_module_code( } /// Quick and dirty test to see if this is a url +/// Uses a character that is not allowed in slugs but must be in a url to decide between the two fn is_url(value: &str) -> bool { value.contains('/') } From b1d45757b9a35b5827880d9cc95300a6cfae3338 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:41:06 -0400 Subject: [PATCH 085/196] Make it easier to install when testing --- .cargo/config.toml | 2 ++ README.md | 6 ++++++ 2 files changed, 8 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..260f33c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[alias] +i = "install --path ." diff --git a/README.md b/README.md index 8776f4e..34cbba2 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,12 @@ After cloning the repo run cargo install --path . ``` +or using alias from `.cargo/config.toml` + +```sh +cargo i +``` + ## Uninstallation ```sh From b381b94d530ba792e76139a89d6a280d2f917a9f Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:48:32 -0400 Subject: [PATCH 086/196] Add support for i64 and any other arbitrary type --- src/tool/core/helpers/problem_code.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 86c67cb..fb6336f 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use anyhow::{bail, Context}; -use log::info; +use log::{error, info}; use regex::Regex; pub struct ProblemCode { @@ -226,11 +226,13 @@ impl FunctionArgs { #[derive(Debug)] pub enum FunctionArgType { FATi32, + FATi64, FATVeci32, FATVecVeci32, FATString, FATList, FATTree, + FATOther { raw: String }, } impl FunctionArgType { @@ -241,6 +243,10 @@ impl FunctionArgType { let _: i32 = line.parse()?; line.to_string() } + FunctionArgType::FATi64 => { + let _: i64 = line.parse()?; + line.to_string() + } FunctionArgType::FATVeci32 => { Self::does_pass_basic_vec_tests(line)?; format!("vec!{line}") @@ -265,6 +271,7 @@ impl FunctionArgType { Self::does_pass_basic_vec_tests(line)?; format!("TreeRoot::from(\"{line}\").into()") } + FunctionArgType::FATOther { raw: _ } => line.to_string(), // Assume input is fine and pass on verbatim, }) } @@ -288,11 +295,13 @@ impl Display for FunctionArgType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { FunctionArgType::FATi32 => "i32", + FunctionArgType::FATi64 => "i64", FunctionArgType::FATVeci32 => "Vec", FunctionArgType::FATVecVeci32 => "Vec>", FunctionArgType::FATString => "String", FunctionArgType::FATList => "Option>", FunctionArgType::FATTree => "Option>>", + FunctionArgType::FATOther { raw } => raw, }; write!(f, "{s}") @@ -306,12 +315,18 @@ impl TryFrom<&str> for FunctionArgType { use FunctionArgType::*; Ok(match value.trim() { "i32" => FATi32, + "i64" => FATi64, "Vec" => FATVeci32, "Vec>" => FATVecVeci32, "String" => FATString, "Option>" => FATList, "Option>>" => FATTree, - _ => bail!("Unknown type: '{value}'"), + trimmed_value => { + error!("Unknown type \"{trimmed_value}\" found please report this in an issue https://github.com/rust-practice/cargo-leet/issues/new"); + FATOther { + raw: trimmed_value.to_string(), + } + } }) } } From 13f0c7071a0e713b8a4085c7f65004d5afb1d356 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:52:23 -0400 Subject: [PATCH 087/196] Add warnings instead of failing on parse error --- src/tool/core/helpers/problem_code.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index fb6336f..9f71229 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use anyhow::{bail, Context}; -use log::{error, info}; +use log::{error, info, warn}; use regex::Regex; pub struct ProblemCode { @@ -240,11 +240,15 @@ impl FunctionArgType { fn apply(&self, line: &str) -> anyhow::Result { Ok(match self { FunctionArgType::FATi32 => { - let _: i32 = line.parse()?; + if let Err(e) = line.parse::() { + warn!("In testing the test input \"{line}\" the parsing to i32 failed with error: {e}") + }; line.to_string() } FunctionArgType::FATi64 => { - let _: i64 = line.parse()?; + if let Err(e) = line.parse::() { + warn!("In testing the test input \"{line}\" the parsing to i64 failed with error: {e}") + }; line.to_string() } FunctionArgType::FATVeci32 => { From f06a3b48137e60290638b4e821cff4f93796e780 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 22 Jun 2023 19:52:11 -0400 Subject: [PATCH 088/196] Remove prefix from enum names --- src/tool/core/helpers/problem_code.rs | 70 ++++++++++++++------------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 9f71229..db740e7 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -225,37 +225,38 @@ impl FunctionArgs { /// Function Arg Type (FAT) #[derive(Debug)] pub enum FunctionArgType { - FATi32, - FATi64, - FATVeci32, - FATVecVeci32, - FATString, - FATList, - FATTree, - FATOther { raw: String }, + I32, + I64, + VecI32, + VecVecI32, + String_, + List, + Tree, + Other { raw: String }, } impl FunctionArgType { /// Applies any special changes needed to the value based on the type fn apply(&self, line: &str) -> anyhow::Result { + use FunctionArgType::*; Ok(match self { - FunctionArgType::FATi32 => { + I32 => { if let Err(e) = line.parse::() { warn!("In testing the test input \"{line}\" the parsing to i32 failed with error: {e}") }; line.to_string() } - FunctionArgType::FATi64 => { + I64 => { if let Err(e) = line.parse::() { warn!("In testing the test input \"{line}\" the parsing to i64 failed with error: {e}") }; line.to_string() } - FunctionArgType::FATVeci32 => { + VecI32 => { Self::does_pass_basic_vec_tests(line)?; format!("vec!{line}") } - FunctionArgType::FATVecVeci32 => { + VecVecI32 => { Self::does_pass_basic_vec_tests(line)?; let mut result = String::new(); for c in line.chars() { @@ -266,16 +267,16 @@ impl FunctionArgType { } result } - FunctionArgType::FATString => line.to_string(), - FunctionArgType::FATList => { + String_ => line.to_string(), + List => { Self::does_pass_basic_vec_tests(line)?; format!("ListHead::from(vec!{line}).into()") } - FunctionArgType::FATTree => { + Tree => { Self::does_pass_basic_vec_tests(line)?; format!("TreeRoot::from(\"{line}\").into()") } - FunctionArgType::FATOther { raw: _ } => line.to_string(), // Assume input is fine and pass on verbatim, + Other { raw: _ } => line.to_string(), // Assume input is fine and pass on verbatim, }) } @@ -287,25 +288,26 @@ impl FunctionArgType { } fn is_tree(&self) -> bool { - matches!(self, FunctionArgType::FATTree) + matches!(self, FunctionArgType::Tree) } fn is_list(&self) -> bool { - matches!(self, FunctionArgType::FATList) + matches!(self, FunctionArgType::List) } } impl Display for FunctionArgType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use FunctionArgType::*; let s = match self { - FunctionArgType::FATi32 => "i32", - FunctionArgType::FATi64 => "i64", - FunctionArgType::FATVeci32 => "Vec", - FunctionArgType::FATVecVeci32 => "Vec>", - FunctionArgType::FATString => "String", - FunctionArgType::FATList => "Option>", - FunctionArgType::FATTree => "Option>>", - FunctionArgType::FATOther { raw } => raw, + I32 => "i32", + I64 => "i64", + VecI32 => "Vec", + VecVecI32 => "Vec>", + String_ => "String", + List => "Option>", + Tree => "Option>>", + Other { raw } => raw, }; write!(f, "{s}") @@ -318,16 +320,16 @@ impl TryFrom<&str> for FunctionArgType { fn try_from(value: &str) -> Result { use FunctionArgType::*; Ok(match value.trim() { - "i32" => FATi32, - "i64" => FATi64, - "Vec" => FATVeci32, - "Vec>" => FATVecVeci32, - "String" => FATString, - "Option>" => FATList, - "Option>>" => FATTree, + "i32" => I32, + "i64" => I64, + "Vec" => VecI32, + "Vec>" => VecVecI32, + "String" => String_, + "Option>" => List, + "Option>>" => Tree, trimmed_value => { error!("Unknown type \"{trimmed_value}\" found please report this in an issue https://github.com/rust-practice/cargo-leet/issues/new"); - FATOther { + Other { raw: trimmed_value.to_string(), } } From 3281c4515e111cc3a29f2d60b49823143e8fc207 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 22 Jun 2023 19:57:23 -0400 Subject: [PATCH 089/196] Add support for `bool` and `Vec` --- .vscode/settings.json | 1 + src/tool/core/helpers/problem_code.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index cc45aba..7af3f12 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "cSpell.words": [ + "Vecbool", "Veci" ], "editor.formatOnSave": true, diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index db740e7..655748a 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -227,7 +227,9 @@ impl FunctionArgs { pub enum FunctionArgType { I32, I64, + Bool, VecI32, + VecBool, VecVecI32, String_, List, @@ -252,7 +254,7 @@ impl FunctionArgType { }; line.to_string() } - VecI32 => { + VecI32 | VecBool => { Self::does_pass_basic_vec_tests(line)?; format!("vec!{line}") } @@ -267,7 +269,7 @@ impl FunctionArgType { } result } - String_ => line.to_string(), + String_ | Bool => line.to_string(), List => { Self::does_pass_basic_vec_tests(line)?; format!("ListHead::from(vec!{line}).into()") @@ -302,7 +304,9 @@ impl Display for FunctionArgType { let s = match self { I32 => "i32", I64 => "i64", + Bool => "bool", VecI32 => "Vec", + VecBool => "Vec", VecVecI32 => "Vec>", String_ => "String", List => "Option>", @@ -322,7 +326,9 @@ impl TryFrom<&str> for FunctionArgType { Ok(match value.trim() { "i32" => I32, "i64" => I64, + "bool" => Bool, "Vec" => VecI32, + "Vec" => VecBool, "Vec>" => VecVecI32, "String" => String_, "Option>" => List, From 8f68e5b97e2435ce409e6836004f982f748e69fa Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 24 Jun 2023 10:14:45 -0400 Subject: [PATCH 090/196] Update screenshot for readme --- README.md | 2 +- assets/help_scr_shot_top.png | Bin 42798 -> 37294 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 34cbba2..98e3b98 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ## ScreenShots - ### `cargo leet (needs to be updated)` + ### `cargo leet` ![ScreenShot](assets/help_scr_shot_top.png) ### `cargo leet generate --help` diff --git a/assets/help_scr_shot_top.png b/assets/help_scr_shot_top.png index a78791c9ff0f3464704093e0e23266973a11ec94..776c45beb271e4efbc0ef609d50caef6f8d4e775 100644 GIT binary patch literal 37294 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%0Al>^s`H%Gy0Cv*9^{NNHKkaFG;Nr)j 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 EA1Zv-hyVZp From 89fedf0d42f658923679cef3d6ee51b70c97c745 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 24 Jun 2023 11:49:01 -0400 Subject: [PATCH 091/196] Reword to make more clear --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 98e3b98..0fd1f83 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -## Leetcode local dev assistant +## Leetcode local development assistant - A program that given the link to a leetcode problem, - creates a local file where you can develop your solution and then post it back to leetcode. + A program that given the link or slug to a leetcode problem, + creates a local file where you can develop and test your solution before post it back to leetcode. ## ScreenShots From ccd7153e806d5470b3de87fbdd675531fbc90e48 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 24 Jun 2023 11:49:37 -0400 Subject: [PATCH 092/196] Add instructions to add library support --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0fd1f83..fac43e8 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,18 @@ ### `cargo leet generate --help` ![ScreenShot](assets/help_scr_shot_generate.png) -## Installation + ## Using Library Support + + Using the library to "mimic" leetcode environment. Add library as a dependency as below. Then add use statements as necessary (automatically added if tool is used to generate the file). + + ```toml + cargo-leet = { git = "https://github.com/rust-practice/cargo-leet.git", branch = "develop", default-features = false, features = [ + "leet_env", +] } + ``` + + +## Tool Installation NB: If cargo-leet is already installed you do the install it will just replace it even it it was previously installed from a different source. For example if you install it from a clone then run the command to install from git it will replace the existing version that is installed (they will not both be installed). @@ -35,7 +46,7 @@ or using alias from `.cargo/config.toml` cargo i ``` -## Uninstallation +## Tool Uninstallation ```sh cargo uninstall cargo-leet From 0671adf14785a07aef190ee686aefc28ee114440 Mon Sep 17 00:00:00 2001 From: sak96 <26397224+sak96@users.noreply.github.com> Date: Mon, 26 Jun 2023 19:40:58 +0530 Subject: [PATCH 093/196] Make solution public to avoid unused function lints. --- src/tool/core/generate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index e5ee4c5..232ba18 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -84,7 +84,7 @@ pub fn create_module_code( // Add struct for non design questions if problem_code.type_.is_non_design() { - code_snippet.push_str("\nstruct Solution;\n") + code_snippet.push_str("\npub struct Solution;\n") } // Add leet code types From 1f09132ffcc578883b04694a49c35a61d285a305 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 27 Jun 2023 23:44:44 -0400 Subject: [PATCH 094/196] Fix typo --- src/tool/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 655748a..d484f18 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -249,7 +249,7 @@ impl FunctionArgType { line.to_string() } I64 => { - if let Err(e) = line.parse::() { + if let Err(e) = line.parse::() { warn!("In testing the test input \"{line}\" the parsing to i64 failed with error: {e}") }; line.to_string() From 62befd22077cb59c7de9ecac170cb2ba2b55f361 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 27 Jun 2023 23:48:32 -0400 Subject: [PATCH 095/196] Add F64 and VecF64 --- src/tool/core/helpers/problem_code.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index d484f18..626a54f 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -227,8 +227,10 @@ impl FunctionArgs { pub enum FunctionArgType { I32, I64, + F64, Bool, VecI32, + VecF64, VecBool, VecVecI32, String_, @@ -254,7 +256,13 @@ impl FunctionArgType { }; line.to_string() } - VecI32 | VecBool => { + F64 => { + if let Err(e) = line.parse::() { + warn!("In testing the test input \"{line}\" the parsing to f64 failed with error: {e}") + }; + line.to_string() + } + VecI32 | VecBool | VecF64 => { Self::does_pass_basic_vec_tests(line)?; format!("vec!{line}") } @@ -304,8 +312,10 @@ impl Display for FunctionArgType { let s = match self { I32 => "i32", I64 => "i64", + F64 => "f64", Bool => "bool", VecI32 => "Vec", + VecF64 => "Vec", VecBool => "Vec", VecVecI32 => "Vec>", String_ => "String", @@ -326,8 +336,10 @@ impl TryFrom<&str> for FunctionArgType { Ok(match value.trim() { "i32" => I32, "i64" => I64, + "f64" => F64, "bool" => Bool, "Vec" => VecI32, + "Vec" => VecF64, "Vec" => VecBool, "Vec>" => VecVecI32, "String" => String_, From 77b1311976e6bce74a1291f83c078156dbf6540f Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 27 Jun 2023 23:51:58 -0400 Subject: [PATCH 096/196] Record intent to add support --- src/tool/core/helpers/problem_metadata.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_metadata.rs b/src/tool/core/helpers/problem_metadata.rs index feef108..d2b6edf 100644 --- a/src/tool/core/helpers/problem_metadata.rs +++ b/src/tool/core/helpers/problem_metadata.rs @@ -98,7 +98,7 @@ mod tests {{ " fn case({}) {{ let actual = Solution::{}({}); assert_eq!(actual, expected); - }}", + }}", // TODO add support for same value within 5 decimal places fn_info.get_args_with_case(), fn_info.name, fn_info.get_args_names() From c06fb0e08b08eb5c8debe26999010da140108c51 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 01:28:19 -0400 Subject: [PATCH 097/196] Implement approximately equal to 5dp for f64 --- src/tool/core/helpers/problem_code.rs | 9 +++++++++ src/tool/core/helpers/problem_metadata.rs | 7 ++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 626a54f..888b81b 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -136,6 +136,15 @@ impl FunctionInfo { names.join(", ") } + pub fn get_solution_comparison_code(&self) -> String { + if let Some(FunctionArgType::F64) = &self.return_type { + "assert!((actual - expected).abs() < 1e-5, \"Assertion failed: actual {actual:.5} but expected {expected:.5}. Diff is more than 1e-5.\");" + } else { + "assert_eq!(actual, expected);" + } + .to_string() + } + pub fn get_test_case(&self, example_test_case_raw: &str) -> anyhow::Result { let mut result = String::new(); let n = self.fn_args.len(); diff --git a/src/tool/core/helpers/problem_metadata.rs b/src/tool/core/helpers/problem_metadata.rs index d2b6edf..fb6dc5c 100644 --- a/src/tool/core/helpers/problem_metadata.rs +++ b/src/tool/core/helpers/problem_metadata.rs @@ -97,11 +97,12 @@ mod tests {{ let test_fn = format!( " fn case({}) {{ let actual = Solution::{}({}); - assert_eq!(actual, expected); - }}", // TODO add support for same value within 5 decimal places + {} + }}", fn_info.get_args_with_case(), fn_info.name, - fn_info.get_args_names() + fn_info.get_args_names(), + fn_info.get_solution_comparison_code(), ); result.push_str(&test_fn); From 75c51323133243c357ece8eda054a3f2a988b633 Mon Sep 17 00:00:00 2001 From: sak96 <26397224+sak96@users.noreply.github.com> Date: Wed, 28 Jun 2023 22:06:10 +0530 Subject: [PATCH 098/196] Merge binary and library and change default feature. --- .cargo/config.toml | 2 +- Cargo.toml | 7 ++++++- README.md | 8 +++----- src/lib.rs | 23 +++++++---------------- 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 260f33c..cb9ee30 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [alias] -i = "install --path ." +i = "install --path . --features=tool" diff --git a/Cargo.toml b/Cargo.toml index e75c718..ef57b22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,13 @@ serde = { version = "1.0.164", features = ["derive"], optional = true } ureq = { version = "2.6", features = ["json"], optional = true } +[[bin]] +name = "cargo-leet" +path = "src/main.rs" +required-features = ["tool"] + [features] -default = ["tool"] +default = ["leet_env"] # Add support for leetcode's environment leet_env = [] # Items used when running as a binary diff --git a/README.md b/README.md index fac43e8..79dc86d 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,7 @@ Using the library to "mimic" leetcode environment. Add library as a dependency as below. Then add use statements as necessary (automatically added if tool is used to generate the file). ```toml - cargo-leet = { git = "https://github.com/rust-practice/cargo-leet.git", branch = "develop", default-features = false, features = [ - "leet_env", -] } + cargo-leet = { git = "https://github.com/rust-practice/cargo-leet.git", branch = "develop" } ``` @@ -29,7 +27,7 @@ NB: If cargo-leet is already installed you do the install it will just replace i ### From GitHub ```sh -cargo install --git https://github.com/rust-practice/cargo-leet.git --branch main +cargo install --git https://github.com/rust-practice/cargo-leet.git --branch main --features=tool ``` ### From Clone @@ -37,7 +35,7 @@ cargo install --git https://github.com/rust-practice/cargo-leet.git --branch mai After cloning the repo run ```sh -cargo install --path . +cargo install --path . --features=tool ``` or using alias from `.cargo/config.toml` diff --git a/src/lib.rs b/src/lib.rs index e60cd8a..398f91f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,24 +1,15 @@ #![forbid(unsafe_code)] -#[cfg(feature = "leet_env")] -mod leetcode_env; -#[cfg(feature = "tool")] -mod tool; - // For use in external code #[cfg(feature = "leet_env")] -pub use leetcode_env::list::ListHead; -#[cfg(feature = "leet_env")] -pub use leetcode_env::list::ListNode; -#[cfg(feature = "leet_env")] -pub use leetcode_env::tree::TreeNode; +mod leetcode_env; #[cfg(feature = "leet_env")] -pub use leetcode_env::tree::TreeRoot; +pub use leetcode_env::{ + list::{ListHead, ListNode}, + tree::{TreeNode, TreeRoot}, +}; -// For use in main.rs #[cfg(feature = "tool")] -pub use crate::tool::cli::CargoCli; -#[cfg(feature = "tool")] -pub use crate::tool::core::run; +mod tool; #[cfg(feature = "tool")] -pub use crate::tool::log::init_logging; +pub use crate::tool::{cli::CargoCli, core::run, log::init_logging}; From 0372f886c842e75a75bec53c969ff0f97e1665b9 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:47:14 -0400 Subject: [PATCH 099/196] Enable all features for rust-analyzer in vscode --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7af3f12..f3db509 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ "Veci" ], "editor.formatOnSave": true, - "files.autoSave": "onFocusChange" + "files.autoSave": "onFocusChange", + "rust-analyzer.cargo.features": "all", //Uncomment to use rust-analyzer on wasm code instead } \ No newline at end of file From 5f812cf1f151554234963b3ceb024ee5ea01925d Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:47:43 -0400 Subject: [PATCH 100/196] Add separator between code and tests --- src/tool/core/generate.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index 67470dd..c86332c 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -80,7 +80,9 @@ pub fn create_module_code( code_snippet.push_str(problem_code.as_ref()); // Add 2 empty lines between code and "other stuff (like tests and struct definition" - code_snippet.push_str("\n\n"); + code_snippet.push_str( + "\n\n// << ---------------- Code below here is only for local use ---------------- >>\n", + ); // Add struct for non design questions if problem_code.type_.is_non_design() { From c3455e270a94f6f722eefe27a3806d9768c9dea6 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:57:00 -0400 Subject: [PATCH 101/196] Decrease default logging level to warn --- src/tool/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/cli.rs b/src/tool/cli.rs index 7c0ee29..93e9bff 100644 --- a/src/tool/cli.rs +++ b/src/tool/cli.rs @@ -22,7 +22,7 @@ pub struct Cli { path: Option, /// Set logging level to use - #[arg(long, short, value_enum, default_value_t = LogLevel::Error)] + #[arg(long, short, value_enum, default_value_t = LogLevel::Warn)] pub log_level: LogLevel, } From b85386a7a3ce6e4f867930a0cfe8b372c8843ec5 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:05:39 -0400 Subject: [PATCH 102/196] Add instructions to run direct from source code --- .cargo/config.toml | 1 + README.md | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index cb9ee30..95eb4b6 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,3 @@ [alias] i = "install --path . --features=tool" +lg = "run --features=tool -- leet gen" diff --git a/README.md b/README.md index 79dc86d..d0de216 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,20 @@ or using alias from `.cargo/config.toml` cargo i ``` +## Running Directly from source without install + +These commands allow you to run the tool directly from the source code without installation. + +```sh +cargo run --features=tool -- leet gen +``` + +or using alias from `.cargo/config.toml` + +```sh +cargo lg +``` + ## Tool Uninstallation ```sh From a453e35e703868f902810a8ac5f24eb9f9afb8ed Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:36:20 -0400 Subject: [PATCH 103/196] Replace for loop with function from std --- src/tool/core/helpers/problem_code.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 888b81b..686a647 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -277,14 +277,7 @@ impl FunctionArgType { } VecVecI32 => { Self::does_pass_basic_vec_tests(line)?; - let mut result = String::new(); - for c in line.chars() { - match c { - '[' => result.push_str("vec!["), - _ => result.push(c), - } - } - result + line.replace('[', "vec![") } String_ | Bool => line.to_string(), List => { From d2e0ab2bf42b4a838a83113b30561fdc52b9ff12 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:38:07 -0400 Subject: [PATCH 104/196] Link run from code back to help --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0de216..7a4bddf 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ cargo i ## Running Directly from source without install -These commands allow you to run the tool directly from the source code without installation. +These commands allow you to run the tool directly from the source code without installation. For more options see [generate help](#cargo-leet-generate---help) ```sh cargo run --features=tool -- leet gen From c07874549fc9a0dee8704c5186eea518efaf8477 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 21:17:35 -0400 Subject: [PATCH 105/196] Reduce log level to warn to better match severity --- src/tool/core/helpers/problem_code.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 686a647..738ffa3 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use anyhow::{bail, Context}; -use log::{error, info, warn}; +use log::{info, warn}; use regex::Regex; pub struct ProblemCode { @@ -348,7 +348,7 @@ impl TryFrom<&str> for FunctionArgType { "Option>" => List, "Option>>" => Tree, trimmed_value => { - error!("Unknown type \"{trimmed_value}\" found please report this in an issue https://github.com/rust-practice/cargo-leet/issues/new"); + warn!("Unknown type \"{trimmed_value}\" found please report this in an issue https://github.com/rust-practice/cargo-leet/issues/new"); Other { raw: trimmed_value.to_string(), } From d631be4fcbe788a1b99c4aebda183692c1d92446 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 21:18:14 -0400 Subject: [PATCH 106/196] Add support for Vec --- src/tool/core/helpers/problem_code.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 738ffa3..a0219b5 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -242,6 +242,7 @@ pub enum FunctionArgType { VecF64, VecBool, VecVecI32, + VecString, String_, List, Tree, @@ -275,6 +276,12 @@ impl FunctionArgType { Self::does_pass_basic_vec_tests(line)?; format!("vec!{line}") } + VecString => { + Self::does_pass_basic_vec_tests(line)?; + let mut result = line.replace("\",", "\".into(),"); // Replace ones before end + result = result.replace("\"]", "\".into()]"); // Replace end + format!("vec!{result}") + } VecVecI32 => { Self::does_pass_basic_vec_tests(line)?; line.replace('[', "vec![") @@ -320,6 +327,7 @@ impl Display for FunctionArgType { VecF64 => "Vec", VecBool => "Vec", VecVecI32 => "Vec>", + VecString => "Vec", String_ => "String", List => "Option>", Tree => "Option>>", @@ -344,6 +352,7 @@ impl TryFrom<&str> for FunctionArgType { "Vec" => VecF64, "Vec" => VecBool, "Vec>" => VecVecI32, + "Vec" => VecString, "String" => String_, "Option>" => List, "Option>>" => Tree, From 9bb2ea47c12e79a7c6536bf147f3a6a4d81a03b8 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 28 Jun 2023 21:33:40 -0400 Subject: [PATCH 107/196] Add todo!() to empty function body --- src/tool/core/helpers/code_snippet.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/tool/core/helpers/code_snippet.rs b/src/tool/core/helpers/code_snippet.rs index 9e1fdb4..94bc297 100644 --- a/src/tool/core/helpers/code_snippet.rs +++ b/src/tool/core/helpers/code_snippet.rs @@ -2,6 +2,7 @@ use super::problem_code::ProblemCode; use crate::tool::config::Config; use anyhow::{bail, Context}; use log::info; +use regex::Regex; use serde::Deserialize; use serde_flat_path::flat_path; @@ -36,12 +37,20 @@ pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result() .context("Failed to convert response from json to codes_snippet")?; - match code_snippets_res + let mut result = match code_snippets_res .code_snippets .into_iter() .find_map(|cs| (cs.lang == "Rust").then_some(cs.code)) { - Some(result) => Ok(result.try_into()?), + Some(result) => result, None => bail!("Rust not supported for this problem"), - } + }; + + // Add todo!() placeholders in function bodies + let re = Regex::new(r#"\{\s*\}"#)?; + result = re + .replace_all(&result, "{ todo!(\"Fill in body\") }") + .to_string(); + + result.try_into() } From eea1ee1bc2b551b980f5319774ccd4749a9efd35 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:12:57 -0400 Subject: [PATCH 108/196] Improve error message Provide suggestions to the user and more info about which file is trying to be opened. During testing this problem usually occurs because lib.rs does not exist and it's not always obvious which lib.rs it is trying to update. --- src/tool/core/helpers/write_to_disk.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/tool/core/helpers/write_to_disk.rs b/src/tool/core/helpers/write_to_disk.rs index 59edb79..b9db3ef 100644 --- a/src/tool/core/helpers/write_to_disk.rs +++ b/src/tool/core/helpers/write_to_disk.rs @@ -1,6 +1,7 @@ use anyhow::Context; use log::{error, info}; use std::{ + env, fs::{remove_file, OpenOptions}, io::Write, path::PathBuf, @@ -10,7 +11,17 @@ use std::{ fn update_lib(module_name: &str) -> anyhow::Result<()> { info!("Adding {module_name} to libs.rs"); let lib_path = PathBuf::from("src/lib.rs"); - let mut lib = OpenOptions::new().append(true).open(lib_path)?; + let mut lib = OpenOptions::new() + .append(true) + .open(&lib_path) + .with_context(|| { + format!( + "Failed to open {:?}", + env::current_dir() + .expect("Unable to resolve current directory") + .join(lib_path) + ) + })?; let _ = lib.write(format!("pub mod {module_name};").as_bytes())?; Ok(()) } @@ -35,7 +46,9 @@ pub fn write_file(module_name: &str, module_code: String) -> anyhow::Result<()> path.display() ) })?; - lib_update_status.context("Failed to update lib.rs")?; + lib_update_status.context( + "Failed to update lib.rs. Does the file exists? Is it able to be written to?", + )?; } info!("Going to run rustfmt on files"); From e192e1d8eaf36af8a2c93962b0a9b69668ae62f3 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:26:08 -0400 Subject: [PATCH 109/196] Enable path as global option This is to allow it to be used after the generate command --- src/tool/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/cli.rs b/src/tool/cli.rs index 93e9bff..4129141 100644 --- a/src/tool/cli.rs +++ b/src/tool/cli.rs @@ -18,7 +18,7 @@ pub struct Cli { pub command: Commands, /// Specify the path to the project root (If not provided uses current working directory) - #[arg(long, short, value_name = "FOLDER")] // This is an example where I use it + #[arg(long, short, global = true, value_name = "FOLDER")] path: Option, /// Set logging level to use From b06b15dd81f1ae3a11f5cff730cf7017b5b5cd50 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:28:06 -0400 Subject: [PATCH 110/196] Add instructions on use of path and change g to lg --- .cargo/config.toml | 2 +- README.md | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 95eb4b6..ccddced 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,3 @@ [alias] i = "install --path . --features=tool" -lg = "run --features=tool -- leet gen" +g = "run --features=tool -- leet gen" diff --git a/README.md b/README.md index 7a4bddf..dd7d538 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,15 @@ or using alias from `.cargo/config.toml` cargo i ``` -## Running Directly from source without install +## Running Directly from source without install (When developing the tool) -These commands allow you to run the tool directly from the source code without installation. For more options see [generate help](#cargo-leet-generate---help) +These commands allow you to run the tool directly from the source code without installation. +By default they will run the tool on the current working directory. +This means that it will run in the current project folder for cargo-leet. +This may be fine for testing but if you want to be able to actually run the code, +it might be more appropriate to pass the path parameter and specify the path to the repository you want to to run against. +Eg. `cargo g --path $TEST_REPO` +For more options see [generate help](#cargo-leet-generate---help) ```sh cargo run --features=tool -- leet gen @@ -55,9 +61,11 @@ cargo run --features=tool -- leet gen or using alias from `.cargo/config.toml` ```sh -cargo lg +cargo g ``` + + ## Tool Uninstallation ```sh From f882da05505e7c09ea56836cea7e4cbe0312487b Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 12:47:00 -0400 Subject: [PATCH 111/196] Correct comment --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index f3db509..4ecd8e5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,5 @@ ], "editor.formatOnSave": true, "files.autoSave": "onFocusChange", - "rust-analyzer.cargo.features": "all", //Uncomment to use rust-analyzer on wasm code instead + "rust-analyzer.cargo.features": "all", // Sets the features used by rust analyzer } \ No newline at end of file From 279a12fc881017a6acfd28be5b6a01bb9e88dcd4 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:31:15 -0400 Subject: [PATCH 112/196] Add warnings for undocumented items --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 398f91f..919f644 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,7 @@ #![forbid(unsafe_code)] +#![warn(missing_docs)] +#![warn(rustdoc::missing_crate_level_docs)] +#![warn(rustdoc::missing_doc_code_examples)] // For use in external code #[cfg(feature = "leet_env")] From cccfd10c182b4634108f03cfbad3cc6442d92040 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:36:15 -0400 Subject: [PATCH 113/196] Ensure all types added to leetcode impl debug All current types do but this will ensure if any are added debug is not accidentally omitted. (Idea came from Tokio crate) --- src/leetcode_env/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/leetcode_env/mod.rs b/src/leetcode_env/mod.rs index d6c6485..bf14af2 100644 --- a/src/leetcode_env/mod.rs +++ b/src/leetcode_env/mod.rs @@ -1,3 +1,4 @@ +#![warn(missing_debug_implementations)] //! Add support for "types" defined on leetcode and methods to facilitate conversion from example format pub mod list; From 0196331e3411d7cf593ab4b8e2212c961e15a72a Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:45:08 -0400 Subject: [PATCH 114/196] Add warning to prevent accidentally confusing code --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 919f644..34a8f6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ #![warn(missing_docs)] #![warn(rustdoc::missing_crate_level_docs)] #![warn(rustdoc::missing_doc_code_examples)] +#![warn(unreachable_pub)] // For use in external code #[cfg(feature = "leet_env")] From d857ad09f41df3454592cc9c68e9634d490b49f9 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:10:06 -0400 Subject: [PATCH 115/196] Clean up what showed as pub All of these either did not need to be pub or were only visible inside of the crate anyway so they showing pub was just a bit misleading. --- src/leetcode_env/mod.rs | 4 +- src/tool/config.rs | 6 +-- src/tool/core/generate.rs | 2 +- src/tool/core/helpers/code_snippet.rs | 6 +-- src/tool/core/helpers/daily_challenge.rs | 2 +- src/tool/core/helpers/problem_code.rs | 46 +++++++++++------------ src/tool/core/helpers/problem_metadata.rs | 10 ++--- src/tool/core/helpers/write_to_disk.rs | 2 +- 8 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/leetcode_env/mod.rs b/src/leetcode_env/mod.rs index bf14af2..d58f232 100644 --- a/src/leetcode_env/mod.rs +++ b/src/leetcode_env/mod.rs @@ -1,5 +1,5 @@ #![warn(missing_debug_implementations)] //! Add support for "types" defined on leetcode and methods to facilitate conversion from example format -pub mod list; -pub mod tree; +pub(crate) mod list; +pub(crate) mod tree; diff --git a/src/tool/config.rs b/src/tool/config.rs index 0f774f4..cc340b5 100644 --- a/src/tool/config.rs +++ b/src/tool/config.rs @@ -1,7 +1,7 @@ -pub struct Config {} +pub(crate) struct Config {} impl Config { // URLs Must include trailing "/" - pub const LEETCODE_PROBLEM_URL: &str = "https://leetcode.com/problems/"; - pub const LEETCODE_GRAPH_QL: &str = "https://leetcode.com/graphql/"; + pub(crate) const LEETCODE_PROBLEM_URL: &str = "https://leetcode.com/problems/"; + pub(crate) const LEETCODE_GRAPH_QL: &str = "https://leetcode.com/graphql/"; } diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index c86332c..cad0a50 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -49,7 +49,7 @@ fn get_slug_from_args(specific_problem: &String) -> anyhow::Result, args: &cli::GenerateArgs, ) -> anyhow::Result<(String, String)> { diff --git a/src/tool/core/helpers/code_snippet.rs b/src/tool/core/helpers/code_snippet.rs index 94bc297..9cafdd6 100644 --- a/src/tool/core/helpers/code_snippet.rs +++ b/src/tool/core/helpers/code_snippet.rs @@ -8,17 +8,17 @@ use serde_flat_path::flat_path; #[flat_path] #[derive(Deserialize)] -pub struct CodeSnippetResponse { +struct CodeSnippetResponse { #[flat_path("data.question.codeSnippets")] code_snippets: Vec, } #[derive(Deserialize)] -pub struct CodeSnippet { +struct CodeSnippet { lang: String, code: String, } -pub fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result { +pub(crate) fn get_code_snippet_for_problem(title_slug: &str) -> anyhow::Result { info!("Going to get code for {title_slug}"); let code_snippets_res = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ diff --git a/src/tool/core/helpers/daily_challenge.rs b/src/tool/core/helpers/daily_challenge.rs index 5d43451..6b67912 100644 --- a/src/tool/core/helpers/daily_challenge.rs +++ b/src/tool/core/helpers/daily_challenge.rs @@ -11,7 +11,7 @@ struct DailyChallengeResponse { title_slug: String, } -pub fn get_daily_challenge_slug() -> anyhow::Result { +pub(crate) fn get_daily_challenge_slug() -> anyhow::Result { let daily_challenge_response = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ "query": r#"query questionOfToday { diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index a0219b5..84fda6f 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -4,12 +4,12 @@ use anyhow::{bail, Context}; use log::{info, warn}; use regex::Regex; -pub struct ProblemCode { +pub(crate) struct ProblemCode { code: String, - pub type_: ProblemType, + pub(crate) type_: ProblemType, } -pub enum ProblemType { +pub(crate) enum ProblemType { NonDesign(FunctionInfo), Design, } @@ -19,7 +19,7 @@ impl ProblemType { /// /// [`NonDesign`]: ProblemType::NonDesign #[must_use] - pub fn is_non_design(&self) -> bool { + pub(crate) fn is_non_design(&self) -> bool { matches!(self, Self::NonDesign(..)) } } @@ -87,7 +87,7 @@ impl ProblemCode { }) } - pub fn has_tree(&self) -> bool { + pub(crate) fn has_tree(&self) -> bool { if let ProblemType::NonDesign(fn_info) = &self.type_ { fn_info.has_tree() } else { @@ -95,7 +95,7 @@ impl ProblemCode { } } - pub fn has_list(&self) -> bool { + pub(crate) fn has_list(&self) -> bool { if let ProblemType::NonDesign(fn_info) = &self.type_ { fn_info.has_list() } else { @@ -104,14 +104,14 @@ impl ProblemCode { } } -pub struct FunctionInfo { - pub name: String, - pub fn_args: FunctionArgs, - pub return_type: Option, +pub(crate) struct FunctionInfo { + pub(crate) name: String, + fn_args: FunctionArgs, + return_type: Option, } impl FunctionInfo { - pub fn get_args_with_case(&self) -> String { + pub(crate) fn get_args_with_case(&self) -> String { let mut result = String::from("#[case] "); for c in self.fn_args.raw_str.chars() { match c { @@ -126,7 +126,7 @@ impl FunctionInfo { result } - pub fn get_args_names(&self) -> String { + pub(crate) fn get_args_names(&self) -> String { let names: Vec<_> = self .fn_args .args @@ -136,7 +136,7 @@ impl FunctionInfo { names.join(", ") } - pub fn get_solution_comparison_code(&self) -> String { + pub(crate) fn get_solution_comparison_code(&self) -> String { if let Some(FunctionArgType::F64) = &self.return_type { "assert!((actual - expected).abs() < 1e-5, \"Assertion failed: actual {actual:.5} but expected {expected:.5}. Diff is more than 1e-5.\");" } else { @@ -145,7 +145,7 @@ impl FunctionInfo { .to_string() } - pub fn get_test_case(&self, example_test_case_raw: &str) -> anyhow::Result { + pub(crate) fn get_test_case(&self, example_test_case_raw: &str) -> anyhow::Result { let mut result = String::new(); let n = self.fn_args.len(); let lines: Vec<_> = example_test_case_raw.lines().collect(); @@ -182,29 +182,29 @@ impl FunctionInfo { Ok(result) } - pub fn has_tree(&self) -> bool { + fn has_tree(&self) -> bool { self.fn_args.args.iter().any(|arg| arg.arg_type.is_tree()) } - pub fn has_list(&self) -> bool { + fn has_list(&self) -> bool { self.fn_args.args.iter().any(|arg| arg.arg_type.is_list()) } } #[derive(Debug)] -pub struct FunctionArg { - pub identifier: String, - pub arg_type: FunctionArgType, +pub(crate) struct FunctionArg { + identifier: String, + arg_type: FunctionArgType, } #[derive(Debug)] -pub struct FunctionArgs { +struct FunctionArgs { raw_str: String, - pub args: Vec, + args: Vec, } impl FunctionArgs { - pub fn new(raw_str: String) -> anyhow::Result { + fn new(raw_str: String) -> anyhow::Result { let re = Regex::new(r#"([a-z_0-9]*?)\s*:\s*([A-Za-z0-9<>]*)"#)?; let caps: Vec<_> = re.captures_iter(&raw_str).collect(); let mut args: Vec = vec![]; @@ -233,7 +233,7 @@ impl FunctionArgs { /// Function Arg Type (FAT) #[derive(Debug)] -pub enum FunctionArgType { +enum FunctionArgType { I32, I64, F64, diff --git a/src/tool/core/helpers/problem_metadata.rs b/src/tool/core/helpers/problem_metadata.rs index fb6dc5c..50c44ca 100644 --- a/src/tool/core/helpers/problem_metadata.rs +++ b/src/tool/core/helpers/problem_metadata.rs @@ -16,7 +16,7 @@ struct QuestionWrapper { #[flat_path] #[derive(Deserialize, Debug)] -pub struct ProblemMetadata { +pub(crate) struct ProblemMetadata { #[serde(rename = "questionFrontendId")] id: String, #[serde(rename = "questionTitle")] @@ -32,7 +32,7 @@ impl ProblemMetadata { Ok(()) } - pub fn get_id(&self) -> anyhow::Result { + pub(crate) fn get_id(&self) -> anyhow::Result { let result = self .id .parse() @@ -40,11 +40,11 @@ impl ProblemMetadata { Ok(result) } - pub fn get_num_and_title(&self) -> anyhow::Result { + pub(crate) fn get_num_and_title(&self) -> anyhow::Result { Ok(format!("{}. {}", self.get_id()?, self.title)) } - pub fn get_test_cases(&self, problem_code: &ProblemCode) -> anyhow::Result { + pub(crate) fn get_test_cases(&self, problem_code: &ProblemCode) -> anyhow::Result { info!("Going to get tests"); let mut imports = String::new(); @@ -115,7 +115,7 @@ mod tests {{ } } -pub fn get_problem_metadata(title_slug: &str) -> anyhow::Result { +pub(crate) fn get_problem_metadata(title_slug: &str) -> anyhow::Result { info!("Going to get problem metadata"); let QuestionWrapper { inner: result } = ureq::get(Config::LEETCODE_GRAPH_QL) .send_json(ureq::json!({ diff --git a/src/tool/core/helpers/write_to_disk.rs b/src/tool/core/helpers/write_to_disk.rs index b9db3ef..f678217 100644 --- a/src/tool/core/helpers/write_to_disk.rs +++ b/src/tool/core/helpers/write_to_disk.rs @@ -26,7 +26,7 @@ fn update_lib(module_name: &str) -> anyhow::Result<()> { Ok(()) } -pub fn write_file(module_name: &str, module_code: String) -> anyhow::Result<()> { +pub(crate) fn write_file(module_name: &str, module_code: String) -> anyhow::Result<()> { info!("Writing code to disk for module {module_name}"); let path = PathBuf::from(format!("src/{module_name}.rs")); let mut file = OpenOptions::new() From d82e1ef963b8fa4d6f67fbb809e25fd85767752f Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:32:57 -0400 Subject: [PATCH 116/196] Add way to wrap doc strings --- rustfmt.toml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 rustfmt.toml diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..503f06b --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +# This is unsable so to use you need to use nightly. +# Uncomment the line below then run `rustfmt +nightly src/lib.rs` +# Or any file as needed +# wrap_comments = true From 47d7ba910194707131a4b386b23bbea905ba9750 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:48:21 -0400 Subject: [PATCH 117/196] Top level documentation --- src/lib.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 34a8f6e..8c1788d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,47 @@ #![warn(rustdoc::missing_doc_code_examples)] #![warn(unreachable_pub)] -// For use in external code +//! The main aim of **cargo-leet** is to make it easier to develop solutions to +//! leetcode problems locally on your machine. And as such it is composed of two +//! parts, a tool to help with code download and testing setup, and helper code +//! to allow running solutions developed locally. +//! +//! ### Tool +//! +//! The **cargo-leet** subcommand is a command line tool developed with clap and +//! the associated help is probably the best way to get an idea of how to use +//! the tool. Screenshots of the help can be found in the +//! [readme](https://github.com/rust-practice/cargo-leet#screenshots) on github. +//! For the sake of maintainability features added will be documented there +//! instead of always needing to update multiple places. +//! +//! ### Leetcode Environment Support +//! +//! **cargo-leet** also includes helper code with structs and traits to simulate +//! the environment that you code would run in on the leetcode servers so that +//! you are able to run tests on your code locally. It also provides a few extra +//! types that facilitate testing especially as it relates to creating test +//! cases from the text provided by leetcode. +//! +//! ## Feature flags +//! +//! **cargo-leet** uses feature flags to control which code gets compiled based +//! on how the create is being used. This is especially important for the code +//! imported in the solution repository as this repo may be using a much older +//! version of the rust toolchain due to the fact that leetcode uses a much +//! older version on their servers and some users may want to use the same +//! version to ensure their code will always work upon upload. However, because +//! it is such an old version many of the creates used in the development of the +//! tool are not able to be compiled with that toolchain and as such they being +//! only compiled behind a feature flag makes that a non-issue. It also allows +//! users to not compile the code only needed to support leetcode solution +//! development when working on or using the tool. +//! +//! - `default`: Enables the `leet_env` feature as this is the most common use +//! case +//! - `leet_env`: Includes the code for working on leetcode problem solutions +//! - `tool`: Enables the code and dependencies used to create the tool. + #[cfg(feature = "leet_env")] mod leetcode_env; #[cfg(feature = "leet_env")] From 1202a023f465092307501a2265d26bc306af04ad Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:50:07 -0400 Subject: [PATCH 118/196] Update comment based on new info Noticed it also applied it to other files as well. --- rustfmt.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rustfmt.toml b/rustfmt.toml index 503f06b..f3503da 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,6 @@ # This is unsable so to use you need to use nightly. # Uncomment the line below then run `rustfmt +nightly src/lib.rs` -# Or any file as needed +# This well cause rustfmt to be applied to all the files included +# in lib.rs (the whole create except for main.rs) + # wrap_comments = true From c912fb3c7970b9cdafec281624951f1409c34e51 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:50:28 -0400 Subject: [PATCH 119/196] Add program name to top level heading --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dd7d538..5ef2350 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Leetcode local development assistant +## cargo-leet - A leetcode local development assistant A program that given the link or slug to a leetcode problem, creates a local file where you can develop and test your solution before post it back to leetcode. From 4be9f357bd8480fd2ae7a2c4d6e119ea4abd4602 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:54:03 -0400 Subject: [PATCH 120/196] Apply rustfmt comments wrapping --- src/leetcode_env/mod.rs | 3 ++- src/leetcode_env/tree.rs | 4 ++-- src/tool/cli.rs | 7 ++++--- src/tool/core/generate.rs | 14 +++++++++----- src/tool/core/helpers/problem_metadata.rs | 3 ++- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/leetcode_env/mod.rs b/src/leetcode_env/mod.rs index d58f232..cd4fea4 100644 --- a/src/leetcode_env/mod.rs +++ b/src/leetcode_env/mod.rs @@ -1,5 +1,6 @@ #![warn(missing_debug_implementations)] -//! Add support for "types" defined on leetcode and methods to facilitate conversion from example format +//! Add support for "types" defined on leetcode and methods to facilitate +//! conversion from example format pub(crate) mod list; pub(crate) mod tree; diff --git a/src/leetcode_env/tree.rs b/src/leetcode_env/tree.rs index 3a06be8..822fe10 100644 --- a/src/leetcode_env/tree.rs +++ b/src/leetcode_env/tree.rs @@ -90,8 +90,8 @@ impl From>>> for TreeRoot { } impl From<&str> for TreeRoot { - /// Expects the "[]" around the values, separated by comma "," and only integers and "null" - /// (which is the format you'll get on LeetCode) + /// Expects the "[]" around the values, separated by comma "," and only + /// integers and "null" (which is the format you'll get on LeetCode) /// /// # Panics /// diff --git a/src/tool/cli.rs b/src/tool/cli.rs index 4129141..f6e7be5 100644 --- a/src/tool/cli.rs +++ b/src/tool/cli.rs @@ -17,7 +17,8 @@ pub struct Cli { #[command(subcommand)] pub command: Commands, - /// Specify the path to the project root (If not provided uses current working directory) + /// Specify the path to the project root (If not provided uses current + /// working directory) #[arg(long, short, global = true, value_name = "FOLDER")] path: Option, @@ -63,8 +64,8 @@ pub struct GenerateArgs { pub should_include_problem_number: bool, } -/// Exists to provide better help messages variants copied from LevelFilter as that's the type -/// that is actually needed +/// Exists to provide better help messages variants copied from LevelFilter as +/// that's the type that is actually needed #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] pub enum LogLevel { /// Nothing emitted in this mode diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index cad0a50..3efebdf 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -45,10 +45,12 @@ fn get_slug_from_args(specific_problem: &String) -> anyhow::Result, args: &cli::GenerateArgs, @@ -79,7 +81,8 @@ fn create_module_code( let problem_code = get_code_snippet_for_problem(&title_slug)?; code_snippet.push_str(problem_code.as_ref()); - // Add 2 empty lines between code and "other stuff (like tests and struct definition" + // Add 2 empty lines between code and "other stuff (like tests and struct + // definition" code_snippet.push_str( "\n\n// << ---------------- Code below here is only for local use ---------------- >>\n", ); @@ -118,7 +121,8 @@ fn create_module_code( } /// Quick and dirty test to see if this is a url -/// Uses a character that is not allowed in slugs but must be in a url to decide between the two +/// Uses a character that is not allowed in slugs but must be in a url to decide +/// between the two fn is_url(value: &str) -> bool { value.contains('/') } diff --git a/src/tool/core/helpers/problem_metadata.rs b/src/tool/core/helpers/problem_metadata.rs index 50c44ca..965741f 100644 --- a/src/tool/core/helpers/problem_metadata.rs +++ b/src/tool/core/helpers/problem_metadata.rs @@ -6,7 +6,8 @@ use serde_flat_path::flat_path; use super::problem_code::{FunctionInfo, ProblemCode}; -/// This struct is only used because there are two fields that we are interested in that start with the same path and flat_path does not support that yet +/// This struct is only used because there are two fields that we are interested +/// in that start with the same path and flat_path does not support that yet #[flat_path] #[derive(Deserialize, Debug)] struct QuestionWrapper { From f74abdbb266548f07f04dcc8e3170ef45004e72b Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:16:16 -0400 Subject: [PATCH 121/196] Removed lint because it is unstable --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8c1788d..4feb70e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ #![forbid(unsafe_code)] #![warn(missing_docs)] #![warn(rustdoc::missing_crate_level_docs)] -#![warn(rustdoc::missing_doc_code_examples)] #![warn(unreachable_pub)] //! The main aim of **cargo-leet** is to make it easier to develop solutions to From a49aa5e3997dcbdaddbf741385ab34c6c33a0c88 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:34:53 -0400 Subject: [PATCH 122/196] Document public items --- src/leetcode_env/list.rs | 4 ++++ src/leetcode_env/tree.rs | 9 ++++++++- src/tool/cli.rs | 6 +++++- src/tool/core/mod.rs | 2 ++ src/tool/log.rs | 1 + 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/leetcode_env/list.rs b/src/leetcode_env/list.rs index 9651d39..d5aeb6f 100644 --- a/src/leetcode_env/list.rs +++ b/src/leetcode_env/list.rs @@ -2,9 +2,12 @@ use std::fmt::{Debug, Formatter}; +/// Definition for singly-linked list. #[derive(PartialEq, Eq)] pub struct ListNode { + /// The value stored at this node pub val: i32, + /// Links to the next node if it exists pub next: Option>, } @@ -24,6 +27,7 @@ impl Debug for ListNode { impl ListNode { #[inline] + /// Creates a new unlinked [ListNode] with the value passed pub fn new(val: i32) -> Self { ListNode { next: None, val } } diff --git a/src/leetcode_env/tree.rs b/src/leetcode_env/tree.rs index 822fe10..f35c5a2 100644 --- a/src/leetcode_env/tree.rs +++ b/src/leetcode_env/tree.rs @@ -7,15 +7,20 @@ use std::{ rc::Rc, }; +///Definition for a binary tree node. #[derive(PartialEq, Eq)] pub struct TreeNode { + /// The value stored at this node pub val: i32, + /// Link to the left child if one exists pub left: Option>>, + /// Link to the right child if one exists pub right: Option>>, } impl TreeNode { #[inline] + /// Creates a new [TreeNode] with no children and the value passed pub fn new(val: i32) -> Self { TreeNode { val, @@ -29,9 +34,11 @@ impl TreeNode { } } -// Wrapper class to make handling empty trees easier +/// Wrapper class to make handling empty trees easier and building of trees +/// easier via [From] impls #[derive(PartialEq, Eq)] pub struct TreeRoot { + /// The root of the tree held pub root: Option>>, } diff --git a/src/tool/cli.rs b/src/tool/cli.rs index f6e7be5..70e9a51 100644 --- a/src/tool/cli.rs +++ b/src/tool/cli.rs @@ -4,11 +4,15 @@ use anyhow::Context; use clap::{Args, Parser, Subcommand, ValueEnum}; use log::{info, LevelFilter}; -// Taken from example https://docs.rs/clap/latest/clap/_derive/_cookbook/cargo_example_derive/ +/// Top level entry point for command line arguments parsing +/// +/// Based on example #[derive(Parser)] #[command(name = "cargo")] #[command(bin_name = "cargo")] pub enum CargoCli { + /// This is necessary because it a cargo subcommand so the first argument + /// needs to be the command name Leet(Cli), } #[derive(Args, Debug)] diff --git a/src/tool/core/mod.rs b/src/tool/core/mod.rs index c748b9d..d5022d4 100644 --- a/src/tool/core/mod.rs +++ b/src/tool/core/mod.rs @@ -6,6 +6,8 @@ use crate::tool::cli::{self, Cli}; use anyhow::{bail, Context}; use std::{env, path::Path}; +/// Entry point used by the tool. The `main.rs` is pretty thin shim around this +/// function. pub fn run(cli: &Cli) -> anyhow::Result<()> { cli.update_current_working_dir()?; diff --git a/src/tool/log.rs b/src/tool/log.rs index 70a66f0..7317339 100644 --- a/src/tool/log.rs +++ b/src/tool/log.rs @@ -2,6 +2,7 @@ use env_logger::Builder; use log::LevelFilter; +/// Initializes the logging based on the log level passed pub fn init_logging(level: LevelFilter) -> anyhow::Result<()> { Builder::new().filter(None, level).try_init()?; Ok(()) From 66a18f9d9d59d1a196da83f5751946d90ec1f25d Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 22:46:44 -0400 Subject: [PATCH 123/196] Change to debug_assert Doesn't need to run in release mode --- src/tool/core/generate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index 3efebdf..8aaa679 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -128,7 +128,7 @@ fn is_url(value: &str) -> bool { } fn url_to_slug(url: &str) -> anyhow::Result { - assert!(Config::LEETCODE_PROBLEM_URL.ends_with('/')); + debug_assert!(Config::LEETCODE_PROBLEM_URL.ends_with('/')); if !url.starts_with(Config::LEETCODE_PROBLEM_URL) { bail!( "Expected a leetcode url that starts with '{}' but got '{url}'", From bef4c1c45a77a691a154c96ebb340395fdbda822 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:21:52 -0400 Subject: [PATCH 124/196] Remove outdated comment --- src/tool/core/generate.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index 8aaa679..c45072d 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -81,8 +81,6 @@ fn create_module_code( let problem_code = get_code_snippet_for_problem(&title_slug)?; code_snippet.push_str(problem_code.as_ref()); - // Add 2 empty lines between code and "other stuff (like tests and struct - // definition" code_snippet.push_str( "\n\n// << ---------------- Code below here is only for local use ---------------- >>\n", ); From 9bf33f7955ca88a21cdeb31d0a1564311dc951e2 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:49:55 -0400 Subject: [PATCH 125/196] Remove unused code --- src/leetcode_env/list.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/leetcode_env/list.rs b/src/leetcode_env/list.rs index d5aeb6f..f208f09 100644 --- a/src/leetcode_env/list.rs +++ b/src/leetcode_env/list.rs @@ -60,18 +60,6 @@ impl From>> for ListHead { impl From> for ListHead { fn from(values: Vec) -> Self { - // Reverse version before looking at - // https://github.com/zwhitchcox/leetcode/blob/master/src/0002_add_two_numbers.rs - // to see how it could be done going forward instead of backward - // - // let mut last: Option> = None; - // for &n in values.iter().rev() { - // let mut temp = ListNode::new(n); - // temp.next = last; - // last = Some(Box::new(temp)); - // } - // ListHead::new(last) - let mut result = Self { head: None }; let mut curr = &mut result.head; for &num in &values { From 218af693da23db16397abc94dcd10cd48e93d82e Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:51:00 -0400 Subject: [PATCH 126/196] Add TODOs for tests to be added --- src/leetcode_env/list.rs | 2 ++ src/leetcode_env/tree.rs | 2 ++ src/tool/config.rs | 3 ++- src/tool/core/generate.rs | 5 +++++ src/tool/core/helpers/problem_code.rs | 8 ++++++++ 5 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/leetcode_env/list.rs b/src/leetcode_env/list.rs index f208f09..f3c7ec0 100644 --- a/src/leetcode_env/list.rs +++ b/src/leetcode_env/list.rs @@ -58,6 +58,7 @@ impl From>> for ListHead { } } +// TODO: Test the happy path of getting a linked list from a vec impl From> for ListHead { fn from(values: Vec) -> Self { let mut result = Self { head: None }; @@ -71,6 +72,7 @@ impl From> for ListHead { } } +// TODO: Test the happy path of going from a linked list to a vec impl From<&ListHead> for Vec { fn from(list_head: &ListHead) -> Self { let mut result = vec![]; diff --git a/src/leetcode_env/tree.rs b/src/leetcode_env/tree.rs index f35c5a2..76e5a36 100644 --- a/src/leetcode_env/tree.rs +++ b/src/leetcode_env/tree.rs @@ -65,6 +65,7 @@ impl Debug for TreeRoot { } } +// TODO: Test going from a tree to a vec impl From<&TreeRoot> for Vec> { fn from(value: &TreeRoot) -> Self { let mut result = vec![]; @@ -96,6 +97,7 @@ impl From>>> for TreeRoot { } } +// TODO: Test going from a string to a tree impl From<&str> for TreeRoot { /// Expects the "[]" around the values, separated by comma "," and only /// integers and "null" (which is the format you'll get on LeetCode) diff --git a/src/tool/config.rs b/src/tool/config.rs index cc340b5..9733992 100644 --- a/src/tool/config.rs +++ b/src/tool/config.rs @@ -1,7 +1,8 @@ pub(crate) struct Config {} impl Config { - // URLs Must include trailing "/" + // TODO: Add tests on URLs to ensure they end in the trailing "/" as this is + // assumed in the code using them URLs Must include trailing "/" pub(crate) const LEETCODE_PROBLEM_URL: &str = "https://leetcode.com/problems/"; pub(crate) const LEETCODE_GRAPH_QL: &str = "https://leetcode.com/graphql/"; } diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index c45072d..6ccb335 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -30,6 +30,8 @@ pub(crate) fn do_generate(args: &cli::GenerateArgs) -> anyhow::Result<()> { Ok(()) } +// TODO: Add test where string is interpreted as a slug +// TODO: Add test where string is interpreted as url fn get_slug_from_args(specific_problem: &String) -> anyhow::Result> { Ok(if is_url(specific_problem) { // Working with a url @@ -55,6 +57,9 @@ fn create_module_code( title_slug: Cow, args: &cli::GenerateArgs, ) -> anyhow::Result<(String, String)> { + // TODO: Test the code generation, will need to split this function into the + // parts that access the network and the parts that compose the results of + // those calls info!("Building module contents for {title_slug}"); let meta_data = diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 84fda6f..3644805 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -50,6 +50,7 @@ impl ProblemCode { !code.contains("impl Solution {") } + // TODO: Test extracting arguments (include all argument types) fn get_fn_info(code: &str) -> anyhow::Result { let re = Regex::new(r#"\n\s*pub fn ([a-z_0-9]*)\((.*)\)(?: ?-> ?(.*))? \{"#)?; let caps = if let Some(caps) = re.captures(code) { @@ -87,6 +88,7 @@ impl ProblemCode { }) } + // TODO: Test has_tree with 3 cases, with tree, no tree and design problem pub(crate) fn has_tree(&self) -> bool { if let ProblemType::NonDesign(fn_info) = &self.type_ { fn_info.has_tree() @@ -95,6 +97,7 @@ impl ProblemCode { } } + // TODO: Test has_list with 3 cases, with list, no list and design problem pub(crate) fn has_list(&self) -> bool { if let ProblemType::NonDesign(fn_info) = &self.type_ { fn_info.has_list() @@ -111,8 +114,10 @@ pub(crate) struct FunctionInfo { } impl FunctionInfo { + // TODO: Test output of args generation pub(crate) fn get_args_with_case(&self) -> String { let mut result = String::from("#[case] "); + // TODO: After test has been added, change this implementation to use [replace](https://doc.rust-lang.org/std/string/struct.String.html#method.replace) for c in self.fn_args.raw_str.chars() { match c { ',' => result.push_str(", #[case] "), @@ -126,6 +131,7 @@ impl FunctionInfo { result } + // TODO: Test that expected names are returned pub(crate) fn get_args_names(&self) -> String { let names: Vec<_> = self .fn_args @@ -145,6 +151,7 @@ impl FunctionInfo { .to_string() } + // TODO: Test parsing of test cases downloaded from leetcode pub(crate) fn get_test_case(&self, example_test_case_raw: &str) -> anyhow::Result { let mut result = String::new(); let n = self.fn_args.len(); @@ -252,6 +259,7 @@ enum FunctionArgType { impl FunctionArgType { /// Applies any special changes needed to the value based on the type fn apply(&self, line: &str) -> anyhow::Result { + // TODO: Test leetcode test case conversion based on type use FunctionArgType::*; Ok(match self { I32 => { From 0bcfde12d3b73f38edacd2d8befdb4dd82ab50dc Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 18:03:02 -0400 Subject: [PATCH 127/196] Test vec to linked list --- src/leetcode_env/list.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/leetcode_env/list.rs b/src/leetcode_env/list.rs index f3c7ec0..faf9fe7 100644 --- a/src/leetcode_env/list.rs +++ b/src/leetcode_env/list.rs @@ -58,7 +58,6 @@ impl From>> for ListHead { } } -// TODO: Test the happy path of getting a linked list from a vec impl From> for ListHead { fn from(values: Vec) -> Self { let mut result = Self { head: None }; @@ -84,3 +83,27 @@ impl From<&ListHead> for Vec { result } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn from_vec_to_linked_list() { + // Arrange + let start_vec = vec![1, 2, 3, 4, 5]; + let mut expected = None; + for i in (1..=5).rev() { + let mut new_node = Some(Box::new(ListNode::new(i))); + new_node.as_mut().unwrap().next = expected; + expected = new_node; + } + + // Act + let list_head: ListHead = start_vec.into(); + let actual: Option> = list_head.into(); + + // Assert + assert_eq!(actual, expected); + } +} From 66a6ac4bdfecc368825ed48184598e47bb43f553 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 18:19:38 -0400 Subject: [PATCH 128/196] Test linked list to vec --- src/leetcode_env/list.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/leetcode_env/list.rs b/src/leetcode_env/list.rs index faf9fe7..b24c266 100644 --- a/src/leetcode_env/list.rs +++ b/src/leetcode_env/list.rs @@ -71,7 +71,6 @@ impl From> for ListHead { } } -// TODO: Test the happy path of going from a linked list to a vec impl From<&ListHead> for Vec { fn from(list_head: &ListHead) -> Self { let mut result = vec![]; @@ -92,16 +91,34 @@ mod tests { fn from_vec_to_linked_list() { // Arrange let start_vec = vec![1, 2, 3, 4, 5]; + let expected = create_linked_list(1..=5); + + // Act + let list_head: ListHead = start_vec.into(); + let actual: Option> = list_head.into(); + + // Assert + assert_eq!(actual, expected); + } + + fn create_linked_list>(values: I) -> Option> { let mut expected = None; - for i in (1..=5).rev() { + for i in values.rev() { let mut new_node = Some(Box::new(ListNode::new(i))); new_node.as_mut().unwrap().next = expected; expected = new_node; } + expected + } + + #[test] + fn from_linked_list_to_vec() { + // Arrange + let start: ListHead = create_linked_list(1..=5).into(); + let expected = vec![1, 2, 3, 4, 5]; // Act - let list_head: ListHead = start_vec.into(); - let actual: Option> = list_head.into(); + let actual: Vec = (&start).into(); // Assert assert_eq!(actual, expected); From 06034110b67d68f440d137d58b84b9013cabb4c0 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 18:57:31 -0400 Subject: [PATCH 129/196] Test tree to vec Failing right now because ours includes trailing null's --- src/leetcode_env/tree.rs | 63 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/leetcode_env/tree.rs b/src/leetcode_env/tree.rs index 76e5a36..d45a5f9 100644 --- a/src/leetcode_env/tree.rs +++ b/src/leetcode_env/tree.rs @@ -65,7 +65,6 @@ impl Debug for TreeRoot { } } -// TODO: Test going from a tree to a vec impl From<&TreeRoot> for Vec> { fn from(value: &TreeRoot) -> Self { let mut result = vec![]; @@ -200,3 +199,65 @@ impl From for Option>> { value.root } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Creates the test tree seen below + /// Leetcode rep: [1,2,5,3,null,6,7,null,4,null,null,8] + /// 1 + /// / \ + /// / \ + /// / \ + /// 2 5 + /// / \ / \ + /// 3 - 6 7 + /// / \ / \ / \ + /// - 4 - - 8 - + #[allow(unused_mut)] // It's easier to read the code if they all line up but the leaves don't need to be mutable + fn test_tree() -> Option>> { + let mut node1 = Some(Rc::new(RefCell::new(TreeNode::new(1)))); + let mut node2 = Some(Rc::new(RefCell::new(TreeNode::new(2)))); + let mut node3 = Some(Rc::new(RefCell::new(TreeNode::new(3)))); + let mut node4 = Some(Rc::new(RefCell::new(TreeNode::new(4)))); + let mut node5 = Some(Rc::new(RefCell::new(TreeNode::new(5)))); + let mut node6 = Some(Rc::new(RefCell::new(TreeNode::new(6)))); + let mut node7 = Some(Rc::new(RefCell::new(TreeNode::new(7)))); + let mut node8 = Some(Rc::new(RefCell::new(TreeNode::new(8)))); + node3.as_mut().unwrap().borrow_mut().right = node4; + node7.as_mut().unwrap().borrow_mut().left = node8; + node2.as_mut().unwrap().borrow_mut().left = node3; + node5.as_mut().unwrap().borrow_mut().left = node6; + node5.as_mut().unwrap().borrow_mut().right = node7; + node1.as_mut().unwrap().borrow_mut().left = node2; + node1.as_mut().unwrap().borrow_mut().right = node5; + node1 + } + + #[test] + fn from_tree_to_vec() { + // Arrange + let start: TreeRoot = test_tree().into(); + let expected = vec![ + Some(1), + Some(2), + Some(5), + Some(3), + None, + Some(6), + Some(7), + None, + Some(4), + None, + None, + Some(8), + ]; + + // Act + let actual: Vec> = (&start).into(); + + // Assert + assert_eq!(actual, expected); + } +} From 7c990a9772f723d09631099b182b3bf15fe651ba Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 18:57:47 -0400 Subject: [PATCH 130/196] Add pretty assertions --- Cargo.lock | 44 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +++ src/leetcode_env/tree.rs | 1 + 3 files changed, 48 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 7bab02a..010183c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,7 @@ dependencies = [ "convert_case", "env_logger", "log", + "pretty_assertions", "regex", "serde", "serde_flat_path", @@ -184,6 +185,22 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "env_logger" version = "0.10.0" @@ -342,12 +359,33 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pretty_assertions" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +dependencies = [ + "ctor", + "diff", + "output_vt100", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.60" @@ -787,3 +825,9 @@ name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index ef57b22..bc4bd26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,9 @@ clap = { version = "4.3.3", features = ["derive", "cargo"], optional = true } serde = { version = "1.0.164", features = ["derive"], optional = true } ureq = { version = "2.6", features = ["json"], optional = true } +[dev-dependencies] +pretty_assertions = "1.3.0" + [[bin]] name = "cargo-leet" diff --git a/src/leetcode_env/tree.rs b/src/leetcode_env/tree.rs index d45a5f9..59c856f 100644 --- a/src/leetcode_env/tree.rs +++ b/src/leetcode_env/tree.rs @@ -203,6 +203,7 @@ impl From for Option>> { #[cfg(test)] mod tests { use super::*; + use pretty_assertions::assert_eq; // COMMENT THIS OUT IF YOU WANT NO COLOR /// Creates the test tree seen below /// Leetcode rep: [1,2,5,3,null,6,7,null,4,null,null,8] From 4c2338ce6b30b52e048ba70f6a7be5ba1da1ec98 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 19:05:00 -0400 Subject: [PATCH 131/196] Trim trailing white space --- src/leetcode_env/tree.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/leetcode_env/tree.rs b/src/leetcode_env/tree.rs index 59c856f..2d4d145 100644 --- a/src/leetcode_env/tree.rs +++ b/src/leetcode_env/tree.rs @@ -86,6 +86,15 @@ impl From<&TreeRoot> for Vec> { } } } + + // Trim trailing None + while let Some(_last) = result.last() { + if _last.is_none() { + result.pop(); + } else { + break; + } + } result } } From 6738e47ce1a48ddd07246881c67453f774fd731f Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 19:08:52 -0400 Subject: [PATCH 132/196] Test from str to tree --- src/leetcode_env/tree.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/leetcode_env/tree.rs b/src/leetcode_env/tree.rs index 2d4d145..598b179 100644 --- a/src/leetcode_env/tree.rs +++ b/src/leetcode_env/tree.rs @@ -105,7 +105,6 @@ impl From>>> for TreeRoot { } } -// TODO: Test going from a string to a tree impl From<&str> for TreeRoot { /// Expects the "[]" around the values, separated by comma "," and only /// integers and "null" (which is the format you'll get on LeetCode) @@ -270,4 +269,18 @@ mod tests { // Assert assert_eq!(actual, expected); } + + #[test] + fn from_str_to_tree() { + // Arrange + let start = "[1,2,5,3,null,6,7,null,4,null,null,8]"; + let expected = test_tree(); + + // Act + let root: TreeRoot = start.into(); + let actual: Option>> = root.into(); + + // Assert + assert_eq!(actual, expected); + } } From d6bb0591fb7dd749a97ede54826eaf3b52a6715a Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 19:31:56 -0400 Subject: [PATCH 133/196] Partially completed test Want to test if CI runs with all features enabled --- src/tool/core/helpers/problem_code.rs | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 3644805..b4b2249 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -373,3 +373,48 @@ impl TryFrom<&str> for FunctionArgType { }) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn create_code_stub_all_arg_types_non_design() -> &'static str { + " +impl Solution { + pub fn func_name(arg01: i32, arg02: i64, arg03: f64) -> UnknownType { + + } +} +" + // I32 => "i32", + // I64 => "i64", + // F64 => "f64", + // Bool => "bool", + // VecI32 => "Vec", + // VecF64 => "Vec", + // VecBool => "Vec", + // VecVecI32 => "Vec>", + // VecString => "Vec", + // String_ => "String", + // List => "Option>", + // Tree => "Option>>", + // Other { raw } => raw, + } + #[test] + fn function_parsing() { + // Arrange + let code = create_code_stub_all_arg_types_non_design(); + + // Act + let problem_code: ProblemCode = code.to_string().try_into().expect("Should be valid code"); + let fn_info = if let ProblemType::NonDesign(info) = problem_code.type_ { + info + } else { + panic!("Expected Non Design Problem") + }; + + // Assert + assert_eq!(fn_info.name, "func_name"); + todo!("Want to fail as I'm testing the CI") + } +} From f9742b5564ea82ec252b3c4e4fef005840008b49 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 19:33:32 -0400 Subject: [PATCH 134/196] Set feature to be used during testing in CI --- .github/workflows/general.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index 1abddc2..c955d78 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -22,7 +22,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Run tests - run: cargo test + run: cargo test --features=tool fmt: name: Rustfmt From 4fb89695ffe0ee97039e88eaa8bbfb07b65885f9 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 19:38:02 -0400 Subject: [PATCH 135/196] Remove pretty_assertions, too much work to remember to use it --- Cargo.lock | 44 ---------------------------------------- Cargo.toml | 4 ---- src/leetcode_env/tree.rs | 1 - 3 files changed, 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 010183c..7bab02a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,7 +99,6 @@ dependencies = [ "convert_case", "env_logger", "log", - "pretty_assertions", "regex", "serde", "serde_flat_path", @@ -185,22 +184,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "env_logger" version = "0.10.0" @@ -359,33 +342,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "output_vt100" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", -] - [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" -[[package]] -name = "pretty_assertions" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" -dependencies = [ - "ctor", - "diff", - "output_vt100", - "yansi", -] - [[package]] name = "proc-macro2" version = "1.0.60" @@ -825,9 +787,3 @@ name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index bc4bd26..bbf655d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,10 +19,6 @@ clap = { version = "4.3.3", features = ["derive", "cargo"], optional = true } serde = { version = "1.0.164", features = ["derive"], optional = true } ureq = { version = "2.6", features = ["json"], optional = true } -[dev-dependencies] -pretty_assertions = "1.3.0" - - [[bin]] name = "cargo-leet" path = "src/main.rs" diff --git a/src/leetcode_env/tree.rs b/src/leetcode_env/tree.rs index 598b179..ea2b954 100644 --- a/src/leetcode_env/tree.rs +++ b/src/leetcode_env/tree.rs @@ -211,7 +211,6 @@ impl From for Option>> { #[cfg(test)] mod tests { use super::*; - use pretty_assertions::assert_eq; // COMMENT THIS OUT IF YOU WANT NO COLOR /// Creates the test tree seen below /// Leetcode rep: [1,2,5,3,null,6,7,null,4,null,null,8] From 348d2c3ca7c02ff0239399990c81e6f74e6af5e4 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 19:38:41 -0400 Subject: [PATCH 136/196] Configure VSCode Plugin to test Set command used for testing to include the tool feature --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ecd8e5..691e9d3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,4 +6,5 @@ "editor.formatOnSave": true, "files.autoSave": "onFocusChange", "rust-analyzer.cargo.features": "all", // Sets the features used by rust analyzer + "code-runner.customCommand": "cargo test --features=tool", } \ No newline at end of file From ce47862d687ebd4c945e71e8cdc852d5edb1b9b5 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 20:18:03 -0400 Subject: [PATCH 137/196] Allow "." to match new line Needed if function signature goes beyond one line --- src/tool/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index b4b2249..0974bf2 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -52,7 +52,7 @@ impl ProblemCode { // TODO: Test extracting arguments (include all argument types) fn get_fn_info(code: &str) -> anyhow::Result { - let re = Regex::new(r#"\n\s*pub fn ([a-z_0-9]*)\((.*)\)(?: ?-> ?(.*))? \{"#)?; + let re = Regex::new(r#"(?s)\n\s*pub fn ([a-z_0-9]*)\((.*)\)(?: ?-> ?(.*))? \{"#)?; let caps = if let Some(caps) = re.captures(code) { caps } else { From d691ac4fbb0fe9b8189626bc4532921a4c09c4a4 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 22:24:58 -0400 Subject: [PATCH 138/196] Allow Uppercase Letters and require identifier It's not idiomatic to user uppercase but it's allowed. And an identifier is required --- src/tool/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 0974bf2..ca69a79 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -212,7 +212,7 @@ struct FunctionArgs { impl FunctionArgs { fn new(raw_str: String) -> anyhow::Result { - let re = Regex::new(r#"([a-z_0-9]*?)\s*:\s*([A-Za-z0-9<>]*)"#)?; + let re = Regex::new(r#"([A-Za-z_0-9]+?)\s*:\s*([A-Za-z0-9<>]*)"#)?; let caps: Vec<_> = re.captures_iter(&raw_str).collect(); let mut args: Vec = vec![]; for cap in caps { From f0e1b9c2e4cfc0729e6a6be739ab1544f442506e Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 30 Jun 2023 22:44:13 -0400 Subject: [PATCH 139/196] Test argument type parsing --- Cargo.lock | 29 +++++++++ Cargo.toml | 1 + src/tool/core/helpers/problem_code.rs | 87 +++++++++++++++++++++------ 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bab02a..a5d2507 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,7 @@ dependencies = [ "regex", "serde", "serde_flat_path", + "strum", "ureq", ] @@ -424,6 +425,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + [[package]] name = "ryu" version = "1.0.13" @@ -494,6 +501,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9f3bd7d2e45dcc5e265fbb88d6513e4747d8ef9444cf01a533119bce28a157" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.18", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index bbf655d..426144b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ serde_flat_path = { version = "0.1.2", optional = true } clap = { version = "4.3.3", features = ["derive", "cargo"], optional = true } serde = { version = "1.0.164", features = ["derive"], optional = true } ureq = { version = "2.6", features = ["json"], optional = true } +strum = { version = "0.25", features = ["derive"] } [[bin]] name = "cargo-leet" diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index ca69a79..84ef148 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -3,6 +3,7 @@ use std::fmt::Display; use anyhow::{bail, Context}; use log::{info, warn}; use regex::Regex; +use strum::EnumIter; pub(crate) struct ProblemCode { code: String, @@ -50,7 +51,6 @@ impl ProblemCode { !code.contains("impl Solution {") } - // TODO: Test extracting arguments (include all argument types) fn get_fn_info(code: &str) -> anyhow::Result { let re = Regex::new(r#"(?s)\n\s*pub fn ([a-z_0-9]*)\((.*)\)(?: ?-> ?(.*))? \{"#)?; let caps = if let Some(caps) = re.captures(code) { @@ -239,6 +239,7 @@ impl FunctionArgs { } /// Function Arg Type (FAT) +#[cfg_attr(debug_assertions, derive(EnumIter, Eq, Hash, PartialEq))] #[derive(Debug)] enum FunctionArgType { I32, @@ -376,35 +377,72 @@ impl TryFrom<&str> for FunctionArgType { #[cfg(test)] mod tests { + use std::collections::HashSet; + + use strum::IntoEnumIterator; + use super::*; fn create_code_stub_all_arg_types_non_design() -> &'static str { " impl Solution { - pub fn func_name(arg01: i32, arg02: i64, arg03: f64) -> UnknownType { - + pub fn func_name( + L2AC6p: i32, + q7kv5k: i64, + pP7GhC: f64, + HFGzdD: bool, + ePfFj3: Vec, + kRubF2: Vec, + ykyF5X: Vec, + bBtcWe: Vec>, + NkCeR6: Vec, + kjACSr: String, + bJy3HH: Option>, + ndQLTu: Option>>, + PRnJhw: UnknownType, + ) { } } " - // I32 => "i32", - // I64 => "i64", - // F64 => "f64", - // Bool => "bool", - // VecI32 => "Vec", - // VecF64 => "Vec", - // VecBool => "Vec", - // VecVecI32 => "Vec>", - // VecString => "Vec", - // String_ => "String", - // List => "Option>", - // Tree => "Option>>", - // Other { raw } => raw, } + + fn fn_type_to_id(fat: &FunctionArgType) -> &'static str { + match fat { + FunctionArgType::I32 => "L2AC6p", + FunctionArgType::I64 => "q7kv5k", + FunctionArgType::F64 => "pP7GhC", + FunctionArgType::Bool => "HFGzdD", + FunctionArgType::VecI32 => "ePfFj3", + FunctionArgType::VecF64 => "kRubF2", + FunctionArgType::VecBool => "ykyF5X", + FunctionArgType::VecVecI32 => "bBtcWe", + FunctionArgType::VecString => "NkCeR6", + FunctionArgType::String_ => "kjACSr", + FunctionArgType::List => "bJy3HH", + FunctionArgType::Tree => "ndQLTu", + FunctionArgType::Other { .. } => "PRnJhw", + } + } + #[test] fn function_parsing() { // Arrange let code = create_code_stub_all_arg_types_non_design(); + // Create hashset and fill with the possible argument types + let mut left_to_see = HashSet::new(); + FunctionArgType::iter().for_each(|x| { + left_to_see.insert(x); + }); + + // Add special handling for Other variant + left_to_see.remove(&FunctionArgType::Other { + raw: "".to_string(), + }); + left_to_see.insert(FunctionArgType::Other { + raw: "UnknownType".to_string(), + }); + // Act let problem_code: ProblemCode = code.to_string().try_into().expect("Should be valid code"); let fn_info = if let ProblemType::NonDesign(info) = problem_code.type_ { @@ -415,6 +453,21 @@ impl Solution { // Assert assert_eq!(fn_info.name, "func_name"); - todo!("Want to fail as I'm testing the CI") + for arg in fn_info.fn_args.args.iter() { + // if !left_to_see.contains(&arg.arg_type) { + // panic!("Duplicate type seen. Each type should show up EXACTLY ONCE. Duplicate type: {}",arg.arg_type); + // } + left_to_see.remove(&arg.arg_type); + assert_eq!( + arg.identifier, + fn_type_to_id(&arg.arg_type), + "ArgType: {}", + arg.arg_type + ); + } + assert!( + left_to_see.is_empty(), + "Expected all argument types to be seen but haven't seen {left_to_see:?}", + ); } } From df0ad69e3288f01eb8e0a1a744f269c1a42ea244 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:24:48 -0400 Subject: [PATCH 140/196] Test has_tree --- src/tool/core/helpers/problem_code.rs | 114 +++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 84ef148..83c6a27 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -88,7 +88,6 @@ impl ProblemCode { }) } - // TODO: Test has_tree with 3 cases, with tree, no tree and design problem pub(crate) fn has_tree(&self) -> bool { if let ProblemType::NonDesign(fn_info) = &self.type_ { fn_info.has_tree() @@ -470,4 +469,117 @@ impl Solution { "Expected all argument types to be seen but haven't seen {left_to_see:?}", ); } + + fn get_100_same_tree() -> &'static str { + "// Definition for a binary tree node. +// #[derive(Debug, PartialEq, Eq)] +// pub struct TreeNode { +// pub val: i32, +// pub left: Option>>, +// pub right: Option>>, +// } +// +// impl TreeNode { +// #[inline] +// pub fn new(val: i32) -> Self { +// TreeNode { +// val, +// left: None, +// right: None +// } +// } +// } +use std::rc::Rc; +use std::cell::RefCell; +impl Solution { + pub fn is_same_tree(p: Option>>, q: Option>>) -> bool { + + } +} +" + } + + fn get_97_interleaving_string() -> &'static str { + "impl Solution { + pub fn is_interleave(s1: String, s2: String, s3: String) -> bool { + + } +} +" + } + + fn get_706_design_hashmap() -> &'static str { + "struct MyHashMap { + +} + + +/** + * `&self` means the method takes an immutable reference. + * If you need a mutable reference, change it to `&mut self` instead. + */ +impl MyHashMap { + + fn new() -> Self { + + } + + fn put(&self, key: i32, value: i32) { + + } + + fn get(&self, key: i32) -> i32 { + + } + + fn remove(&self, key: i32) { + + } +} + +/** + * Your MyHashMap object will be instantiated and called as such: + * let obj = MyHashMap::new(); + * obj.put(key, value); + * let ret_2: i32 = obj.get(key); + * obj.remove(key); + */ +" + } + + #[test] + fn has_tree_with_tree() { + // Arrange / Act + let problem_code: ProblemCode = get_100_same_tree() + .to_string() + .try_into() + .expect("Should be valid code"); + + // Assert + assert!(problem_code.has_tree()); + } + + #[test] + fn has_tree_without_tree() { + // Arrange / Act + let problem_code: ProblemCode = get_97_interleaving_string() + .to_string() + .try_into() + .expect("Should be valid code"); + + // Assert + assert!(!problem_code.has_tree()); + } + + #[test] + fn has_tree_design_question() { + // Arrange / Act + let problem_code: ProblemCode = get_706_design_hashmap() + .to_string() + .try_into() + .expect("Should be valid code"); + + // Assert + assert!(!problem_code.has_tree()); + } } From ef464b57a8c845ab734d3d53e470c34ea96ddb2f Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:39:12 -0400 Subject: [PATCH 141/196] Test has_list --- src/tool/core/helpers/problem_code.rs | 63 ++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 83c6a27..3396eeb 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -96,7 +96,6 @@ impl ProblemCode { } } - // TODO: Test has_list with 3 cases, with list, no list and design problem pub(crate) fn has_list(&self) -> bool { if let ProblemType::NonDesign(fn_info) = &self.type_ { fn_info.has_list() @@ -547,6 +546,32 @@ impl MyHashMap { " } + fn get_2_add_two_numbers() -> &'static str { + " +// Definition for singly-linked list. +// #[derive(PartialEq, Eq, Clone, Debug)] +// pub struct ListNode { +// pub val: i32, +// pub next: Option> +// } +// +// impl ListNode { +// #[inline] +// fn new(val: i32) -> Self { +// ListNode { +// next: None, +// val +// } +// } +// } +impl Solution { + pub fn add_two_numbers(l1: Option>, l2: Option>) -> Option> { + + } +} +" + } + #[test] fn has_tree_with_tree() { // Arrange / Act @@ -582,4 +607,40 @@ impl MyHashMap { // Assert assert!(!problem_code.has_tree()); } + + #[test] + fn has_list_with_list() { + // Arrange / Act + let problem_code: ProblemCode = get_2_add_two_numbers() + .to_string() + .try_into() + .expect("Should be valid code"); + + // Assert + assert!(problem_code.has_list()); + } + + #[test] + fn has_list_without_list() { + // Arrange / Act + let problem_code: ProblemCode = get_97_interleaving_string() + .to_string() + .try_into() + .expect("Should be valid code"); + + // Assert + assert!(!problem_code.has_list()); + } + + #[test] + fn has_list_design_question() { + // Arrange / Act + let problem_code: ProblemCode = get_706_design_hashmap() + .to_string() + .try_into() + .expect("Should be valid code"); + + // Assert + assert!(!problem_code.has_list()); + } } From b67df4488dcb2b34b2a5d873b1ba82a07f23dadd Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:42:02 -0400 Subject: [PATCH 142/196] Confirm return type is none --- src/tool/core/helpers/problem_code.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 3396eeb..0076097 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -451,6 +451,7 @@ impl Solution { // Assert assert_eq!(fn_info.name, "func_name"); + assert!(fn_info.return_type.is_none()); for arg in fn_info.fn_args.args.iter() { // if !left_to_see.contains(&arg.arg_type) { // panic!("Duplicate type seen. Each type should show up EXACTLY ONCE. Duplicate type: {}",arg.arg_type); From 7c3ed74d8b289989709a446af69f6574715c6e46 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:47:25 -0400 Subject: [PATCH 143/196] Test get_args_with_case --- src/tool/core/helpers/problem_code.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 0076097..714553c 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -112,7 +112,6 @@ pub(crate) struct FunctionInfo { } impl FunctionInfo { - // TODO: Test output of args generation pub(crate) fn get_args_with_case(&self) -> String { let mut result = String::from("#[case] "); // TODO: After test has been added, change this implementation to use [replace](https://doc.rust-lang.org/std/string/struct.String.html#method.replace) @@ -644,4 +643,24 @@ impl Solution { // Assert assert!(!problem_code.has_list()); } + + #[test] + fn get_args_with_case() { + // Arrange / Act + let problem_code: ProblemCode = get_97_interleaving_string() + .to_string() + .try_into() + .expect("Should be valid code"); + let fn_info = if let ProblemType::NonDesign(info) = problem_code.type_ { + info + } else { + panic!("Expected Non Design Problem") + }; + + // Assert + assert_eq!( + fn_info.get_args_with_case(), + "#[case] s1: String, #[case] s2: String, #[case] s3: String, #[case] expected: bool" + ); + } } From 14d0166fd3474237988fe331c735764c54ca22ad Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:47:50 -0400 Subject: [PATCH 144/196] Change Implementation to use replace --- src/tool/core/helpers/problem_code.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 714553c..5baa1a7 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -114,13 +114,7 @@ pub(crate) struct FunctionInfo { impl FunctionInfo { pub(crate) fn get_args_with_case(&self) -> String { let mut result = String::from("#[case] "); - // TODO: After test has been added, change this implementation to use [replace](https://doc.rust-lang.org/std/string/struct.String.html#method.replace) - for c in self.fn_args.raw_str.chars() { - match c { - ',' => result.push_str(", #[case] "), - _ => result.push(c), - } - } + result.push_str(&self.fn_args.raw_str.replace(',', ", #[case] ")); if let Some(return_type) = self.return_type.as_ref() { result.push_str(&format!(", #[case] expected: {return_type}")) From 67fb205bb69e104eace770d81858402f265af258 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:50:22 -0400 Subject: [PATCH 145/196] Test get_args_names --- src/tool/core/helpers/problem_code.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 5baa1a7..2326125 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -122,7 +122,6 @@ impl FunctionInfo { result } - // TODO: Test that expected names are returned pub(crate) fn get_args_names(&self) -> String { let names: Vec<_> = self .fn_args @@ -657,4 +656,21 @@ impl Solution { "#[case] s1: String, #[case] s2: String, #[case] s3: String, #[case] expected: bool" ); } + + #[test] + fn get_args_names() { + // Arrange / Act + let problem_code: ProblemCode = get_97_interleaving_string() + .to_string() + .try_into() + .expect("Should be valid code"); + let fn_info = if let ProblemType::NonDesign(info) = problem_code.type_ { + info + } else { + panic!("Expected Non Design Problem") + }; + + // Assert + assert_eq!(fn_info.get_args_names(), "s1, s2, s3"); + } } From a4182d12a0236872224f832daf5b79714b1e7351 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 1 Jul 2023 00:55:07 -0400 Subject: [PATCH 146/196] Refactor common code out --- src/tool/core/helpers/problem_code.rs | 37 ++++++++++----------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 2326125..c6f49ed 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -414,6 +414,16 @@ impl Solution { } } + fn extract_function_info(code: &str) -> FunctionInfo { + let problem_code: ProblemCode = code.to_string().try_into().expect("Should be valid code"); + + if let ProblemType::NonDesign(info) = problem_code.type_ { + info + } else { + panic!("Expected Non Design Problem") + } + } + #[test] fn function_parsing() { // Arrange @@ -434,12 +444,7 @@ impl Solution { }); // Act - let problem_code: ProblemCode = code.to_string().try_into().expect("Should be valid code"); - let fn_info = if let ProblemType::NonDesign(info) = problem_code.type_ { - info - } else { - panic!("Expected Non Design Problem") - }; + let fn_info = extract_function_info(code); // Assert assert_eq!(fn_info.name, "func_name"); @@ -640,15 +645,7 @@ impl Solution { #[test] fn get_args_with_case() { // Arrange / Act - let problem_code: ProblemCode = get_97_interleaving_string() - .to_string() - .try_into() - .expect("Should be valid code"); - let fn_info = if let ProblemType::NonDesign(info) = problem_code.type_ { - info - } else { - panic!("Expected Non Design Problem") - }; + let fn_info = extract_function_info(get_97_interleaving_string()); // Assert assert_eq!( @@ -660,15 +657,7 @@ impl Solution { #[test] fn get_args_names() { // Arrange / Act - let problem_code: ProblemCode = get_97_interleaving_string() - .to_string() - .try_into() - .expect("Should be valid code"); - let fn_info = if let ProblemType::NonDesign(info) = problem_code.type_ { - info - } else { - panic!("Expected Non Design Problem") - }; + let fn_info = extract_function_info(get_97_interleaving_string()); // Assert assert_eq!(fn_info.get_args_names(), "s1, s2, s3"); From cb77670a76b5c5862a99ba4bdeaacd30f22444f0 Mon Sep 17 00:00:00 2001 From: /nasser Date: Tue, 4 Jul 2023 13:48:18 +0300 Subject: [PATCH 147/196] Update src/lib.rs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4feb70e..b357467 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ //! ## Feature flags //! //! **cargo-leet** uses feature flags to control which code gets compiled based -//! on how the create is being used. This is especially important for the code +//! on how the crate is being used. This is especially important for the code //! imported in the solution repository as this repo may be using a much older //! version of the rust toolchain due to the fact that leetcode uses a much //! older version on their servers and some users may want to use the same From 1eaf53f06e7457163109cab2472796586d7b38f6 Mon Sep 17 00:00:00 2001 From: /nasser Date: Tue, 4 Jul 2023 13:49:02 +0300 Subject: [PATCH 148/196] Apply suggestions from code review --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b357467..80d9df5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ //! ### Leetcode Environment Support //! //! **cargo-leet** also includes helper code with structs and traits to simulate -//! the environment that you code would run in on the leetcode servers so that +//! the environment that your code would run in on the leetcode servers so that //! you are able to run tests on your code locally. It also provides a few extra //! types that facilitate testing especially as it relates to creating test //! cases from the text provided by leetcode. From 3b6cdc177ce2dd0bca21b5d86c67636179f946dc Mon Sep 17 00:00:00 2001 From: /nasser Date: Tue, 4 Jul 2023 13:49:58 +0300 Subject: [PATCH 149/196] Update src/lib.rs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 80d9df5..69522f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ //! version of the rust toolchain due to the fact that leetcode uses a much //! older version on their servers and some users may want to use the same //! version to ensure their code will always work upon upload. However, because -//! it is such an old version many of the creates used in the development of the +//! it is such an old version many of the crates used in the development of the //! tool are not able to be compiled with that toolchain and as such they being //! only compiled behind a feature flag makes that a non-issue. It also allows //! users to not compile the code only needed to support leetcode solution From ce3b5805a5feeffde426b5602a6607c9733407ed Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 4 Jul 2023 22:19:59 -0400 Subject: [PATCH 150/196] Removed TODO item This will only make sense after this feature is implemented --- src/tool/core/helpers/problem_metadata.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tool/core/helpers/problem_metadata.rs b/src/tool/core/helpers/problem_metadata.rs index 965741f..2e383b2 100644 --- a/src/tool/core/helpers/problem_metadata.rs +++ b/src/tool/core/helpers/problem_metadata.rs @@ -111,7 +111,6 @@ mod tests {{ } fn get_test_cases_is_design(&self) -> anyhow::Result { - // TODO Create the test cases for design problems Ok("".to_string()) } } From 24639c06d30ed55e3fb448b995c9e7edc6e02d9b Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 4 Jul 2023 22:35:52 -0400 Subject: [PATCH 151/196] Test slug/url detection and extraction --- src/tool/core/generate.rs | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index 6ccb335..f9a32dd 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -30,8 +30,6 @@ pub(crate) fn do_generate(args: &cli::GenerateArgs) -> anyhow::Result<()> { Ok(()) } -// TODO: Add test where string is interpreted as a slug -// TODO: Add test where string is interpreted as url fn get_slug_from_args(specific_problem: &String) -> anyhow::Result> { Ok(if is_url(specific_problem) { // Working with a url @@ -143,3 +141,31 @@ fn url_to_slug(url: &str) -> anyhow::Result { debug_assert!(split_prefix.len() < split_url.len()); Ok(split_url[split_prefix.len() - 1].to_string()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn slug_in_slug_out() { + let slug = "two-sum".to_string(); + let actual = get_slug_from_args(&slug).expect("Expect value to be valid"); + assert_eq!(actual.to_string(), slug); + } + + #[test] + fn url_in_slug_out() { + let url = "https://leetcode.com/problems/two-sum/".to_string(); + let expected = "two-sum"; + let actual = get_slug_from_args(&url).expect("Expect value to be valid"); + assert_eq!(actual.to_string(), expected); + } + + #[test] + fn invalid_url() { + // Missing "s" in https + let url = "http://leetcode.com/problems/two-sum/".to_string(); + let actual = get_slug_from_args(&url); + assert!(actual.is_err()); + } +} From f4857c4f2c2fa35924a5558c396bb4d3637e1efb Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 4 Jul 2023 22:41:38 -0400 Subject: [PATCH 152/196] Ensure URLs end in "/" --- src/tool/config.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/tool/config.rs b/src/tool/config.rs index 9733992..be41f44 100644 --- a/src/tool/config.rs +++ b/src/tool/config.rs @@ -1,8 +1,22 @@ pub(crate) struct Config {} impl Config { - // TODO: Add tests on URLs to ensure they end in the trailing "/" as this is - // assumed in the code using them URLs Must include trailing "/" + // assumed in the code using them URLs Must end with trailing "/" pub(crate) const LEETCODE_PROBLEM_URL: &str = "https://leetcode.com/problems/"; pub(crate) const LEETCODE_GRAPH_QL: &str = "https://leetcode.com/graphql/"; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn problem_url_ends_with_slash() { + assert!(Config::LEETCODE_PROBLEM_URL.ends_with('/')); + } + + #[test] + fn graph_ql_url_ends_with_slash() { + assert!(Config::LEETCODE_GRAPH_QL.ends_with('/')); + } +} From cc88ad4061f1a990ca38a27dfc2501f4e69f8165 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 11 Jul 2023 23:05:40 -0400 Subject: [PATCH 153/196] Test conversion of test cases to case string Not sure how to test other part separately without pinning to implementation details --- src/tool/core/helpers/problem_code.rs | 49 +++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index c6f49ed..7678347 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -141,7 +141,6 @@ impl FunctionInfo { .to_string() } - // TODO: Test parsing of test cases downloaded from leetcode pub(crate) fn get_test_case(&self, example_test_case_raw: &str) -> anyhow::Result { let mut result = String::new(); let n = self.fn_args.len(); @@ -250,7 +249,6 @@ enum FunctionArgType { impl FunctionArgType { /// Applies any special changes needed to the value based on the type fn apply(&self, line: &str) -> anyhow::Result { - // TODO: Test leetcode test case conversion based on type use FunctionArgType::*; Ok(match self { I32 => { @@ -662,4 +660,51 @@ impl Solution { // Assert assert_eq!(fn_info.get_args_names(), "s1, s2, s3"); } + + fn get_fn_info_min_sub_array_len() -> FunctionInfo { + FunctionInfo { + name: "min_sub_array_len".into(), + fn_args: FunctionArgs { + raw_str: "target: i32, nums: Vec".into(), + args: vec![ + FunctionArg { + identifier: "target".into(), + arg_type: FunctionArgType::I32, + }, + FunctionArg { + identifier: "nums".into(), + arg_type: FunctionArgType::VecI32, + }, + ], + }, + return_type: Some(FunctionArgType::I32), + } + } + + #[test] + fn get_test_case_ok() { + // Arrange + let expected = "7, vec![2,3,1,2,4,3], todo!(\"Expected Result\")"; + let fn_info = get_fn_info_min_sub_array_len(); + let input = "7\n[2,3,1,2,4,3]"; + + // Act + let actual = fn_info.get_test_case(input).expect("Expected Ok"); + + // Assert + assert_eq!(actual, expected); + } + + #[test] + fn get_test_case_invalid_num_args() { + // Arrange + let fn_info = get_fn_info_min_sub_array_len(); + let input = "[2,3,1,2,4,3]"; + + // Act + let actual = fn_info.get_test_case(input); + + // Assert + assert!(actual.is_err()); + } } From d3e574d659be57b8dc98a4e272c70d9ae6e51422 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 11 Jul 2023 23:14:49 -0400 Subject: [PATCH 154/196] Remove plan to do test It feels like it will only end up testing implementation details. Problem does not feel wll sufficiently defined to be able to test --- src/tool/core/generate.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index f9a32dd..d38239d 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -55,9 +55,6 @@ fn create_module_code( title_slug: Cow, args: &cli::GenerateArgs, ) -> anyhow::Result<(String, String)> { - // TODO: Test the code generation, will need to split this function into the - // parts that access the network and the parts that compose the results of - // those calls info!("Building module contents for {title_slug}"); let meta_data = From d9c477111aeee5dd1e8994e462a1e94ab0394513 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 12 Jul 2023 12:17:45 -0400 Subject: [PATCH 155/196] Make logging a global option - Makes changing logging level easier when using "g" alias --- src/tool/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/cli.rs b/src/tool/cli.rs index 70e9a51..134328e 100644 --- a/src/tool/cli.rs +++ b/src/tool/cli.rs @@ -27,7 +27,7 @@ pub struct Cli { path: Option, /// Set logging level to use - #[arg(long, short, value_enum, default_value_t = LogLevel::Warn)] + #[arg(long, short, global = true, value_enum, default_value_t = LogLevel::Warn)] pub log_level: LogLevel, } From 8d906ff9180388f157f00776d5c118d561851735 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 12 Jul 2023 12:37:05 -0400 Subject: [PATCH 156/196] Move logging in "normal" path to debug These messages are not really needed unless something is going wrong. Also they are pretty close to the beginning and not that hard to find. --- src/tool/cli.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tool/cli.rs b/src/tool/cli.rs index 134328e..e1d0267 100644 --- a/src/tool/cli.rs +++ b/src/tool/cli.rs @@ -2,7 +2,7 @@ use std::env; use anyhow::Context; use clap::{Args, Parser, Subcommand, ValueEnum}; -use log::{info, LevelFilter}; +use log::{debug, info, LevelFilter}; /// Top level entry point for command line arguments parsing /// @@ -34,7 +34,7 @@ pub struct Cli { impl Cli { /// Changes the current working directory to path if one is given pub fn update_current_working_dir(&self) -> anyhow::Result<()> { - info!( + debug!( "Before attempting update current dir, it is: {}", env::current_dir()?.display() ); @@ -47,7 +47,7 @@ impl Cli { env::current_dir()?.display() ); } else { - info!("No user supplied path found. No change") + debug!("No user supplied path found. No change") } Ok(()) } From 2ced5827accc299fcdfae672156c059a2572e350 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 12 Jul 2023 12:51:44 -0400 Subject: [PATCH 157/196] Add comments to make code easier to follow --- src/tool/core/helpers/problem_metadata.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tool/core/helpers/problem_metadata.rs b/src/tool/core/helpers/problem_metadata.rs index 2e383b2..bc91c7f 100644 --- a/src/tool/core/helpers/problem_metadata.rs +++ b/src/tool/core/helpers/problem_metadata.rs @@ -52,12 +52,15 @@ impl ProblemMetadata { let tests = match &problem_code.type_ { ProblemType::NonDesign(fn_info) => { + // Add imports if problem_code.has_tree() { imports.push_str("use cargo_leet::TreeRoot;\n"); } if problem_code.has_list() { imports.push_str("use cargo_leet::ListHead;\n"); } + + // Add actual test cases self.get_test_cases_is_not_design(fn_info) .context("Failed to get test cases for non-design problem")? } From b34eb433a6c04ef189228a3f58903befb7adfc18 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:37:05 -0400 Subject: [PATCH 158/196] Add more debug info at debug log level --- src/tool/core/helpers/problem_code.rs | 10 ++++++++-- src/tool/core/helpers/problem_metadata.rs | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 7678347..3be65e4 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -1,15 +1,17 @@ use std::fmt::Display; use anyhow::{bail, Context}; -use log::{info, warn}; +use log::{debug, info, warn}; use regex::Regex; use strum::EnumIter; +#[derive(Debug)] pub(crate) struct ProblemCode { code: String, pub(crate) type_: ProblemType, } +#[derive(Debug)] pub(crate) enum ProblemType { NonDesign(FunctionInfo), Design, @@ -36,7 +38,9 @@ impl TryFrom for ProblemCode { info!("Problem Type is NonDesign"); ProblemType::NonDesign(Self::get_fn_info(&code).context("Failed to get function info")?) }; - Ok(Self { code, type_ }) + let result = Self { code, type_ }; + debug!("ProblemCode built: {result:#?}"); + Ok(result) } } @@ -105,6 +109,7 @@ impl ProblemCode { } } +#[derive(Debug)] pub(crate) struct FunctionInfo { pub(crate) name: String, fn_args: FunctionArgs, @@ -249,6 +254,7 @@ enum FunctionArgType { impl FunctionArgType { /// Applies any special changes needed to the value based on the type fn apply(&self, line: &str) -> anyhow::Result { + debug!("Going to apply changes to argument input for {self:#?} to {line:?}"); use FunctionArgType::*; Ok(match self { I32 => { diff --git a/src/tool/core/helpers/problem_metadata.rs b/src/tool/core/helpers/problem_metadata.rs index bc91c7f..a8095b7 100644 --- a/src/tool/core/helpers/problem_metadata.rs +++ b/src/tool/core/helpers/problem_metadata.rs @@ -1,6 +1,6 @@ use crate::tool::{config::Config, core::helpers::problem_code::ProblemType}; use anyhow::Context; -use log::info; +use log::{debug, info}; use serde::Deserialize; use serde_flat_path::flat_path; @@ -139,5 +139,6 @@ pub(crate) fn get_problem_metadata(title_slug: &str) -> anyhow::Result Date: Wed, 12 Jul 2023 15:01:01 -0400 Subject: [PATCH 159/196] Update link to file issue - Removing the new gives them the option to select the right issue type - Simplify getting quotes around the type --- src/tool/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 3be65e4..dc64aa4 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -360,7 +360,7 @@ impl TryFrom<&str> for FunctionArgType { "Option>" => List, "Option>>" => Tree, trimmed_value => { - warn!("Unknown type \"{trimmed_value}\" found please report this in an issue https://github.com/rust-practice/cargo-leet/issues/new"); + warn!("Unknown type {trimmed_value:?} found please report this in an issue https://github.com/rust-practice/cargo-leet/issues/"); Other { raw: trimmed_value.to_string(), } From f87a986b817a5288989413e776b158f889bb3fde Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:06:03 -0400 Subject: [PATCH 160/196] Add TODO for intended fix / workaround --- src/tool/core/helpers/problem_code.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index dc64aa4..25a6202 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -255,6 +255,7 @@ impl FunctionArgType { /// Applies any special changes needed to the value based on the type fn apply(&self, line: &str) -> anyhow::Result { debug!("Going to apply changes to argument input for {self:#?} to {line:?}"); + // TODO: Add support for type mismatch use FunctionArgType::*; Ok(match self { I32 => { From cdf7c1740c523b80f6c85541dab7ac4fe29127a5 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:06:16 -0400 Subject: [PATCH 161/196] Add custom bug report types --- .github/ISSUE_TEMPLATE/bug_report.md | 33 ++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/missing_type.md | 16 +++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/missing_type.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..eb0238e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: 'bug' +assignees: '' + +--- + +**Describe the bug** + + +**Leetcode problem** + + +**To Reproduce** + +`` + +**Error Message** + +``` +``` + +**Expected behavior** + + +**Environment Info:** + + - Cargo Version: + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/missing_type.md b/.github/ISSUE_TEMPLATE/missing_type.md new file mode 100644 index 0000000..04ba8e9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/missing_type.md @@ -0,0 +1,16 @@ +--- +name: Missing Type +about: Report the missing type so we can try to add it +title: '' +labels: 'bug' +assignees: '' + +--- + +**Leetcode problem** + + +**Error Message** + +``` +``` \ No newline at end of file From 67ba1070df09ced3fee9e7bb425944db4ef5f7be Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:24:14 -0400 Subject: [PATCH 162/196] Wrap unknown input type in TODO Make consistent with planned implementation for mismatched types --- src/tool/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 25a6202..df06c16 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -299,7 +299,7 @@ impl FunctionArgType { Self::does_pass_basic_vec_tests(line)?; format!("TreeRoot::from(\"{line}\").into()") } - Other { raw: _ } => line.to_string(), // Assume input is fine and pass on verbatim, + Other { raw: _ } => format!("todo!(\"{line}\")"), }) } From e7cc139617020a91462e2677164a3f8580d74383 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 12 Jul 2023 21:49:54 -0400 Subject: [PATCH 163/196] Wrap unexpected input types in todo!() --- src/tool/core/helpers/problem_code.rs | 98 +++++++++++++++------------ 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index df06c16..119d616 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -255,57 +255,65 @@ impl FunctionArgType { /// Applies any special changes needed to the value based on the type fn apply(&self, line: &str) -> anyhow::Result { debug!("Going to apply changes to argument input for {self:#?} to {line:?}"); - // TODO: Add support for type mismatch use FunctionArgType::*; - Ok(match self { - I32 => { - if let Err(e) = line.parse::() { - warn!("In testing the test input \"{line}\" the parsing to i32 failed with error: {e}") - }; - line.to_string() - } - I64 => { - if let Err(e) = line.parse::() { - warn!("In testing the test input \"{line}\" the parsing to i64 failed with error: {e}") - }; - line.to_string() - } - F64 => { - if let Err(e) = line.parse::() { - warn!("In testing the test input \"{line}\" the parsing to f64 failed with error: {e}") - }; - line.to_string() - } - VecI32 | VecBool | VecF64 => { - Self::does_pass_basic_vec_tests(line)?; - format!("vec!{line}") - } - VecString => { - Self::does_pass_basic_vec_tests(line)?; - let mut result = line.replace("\",", "\".into(),"); // Replace ones before end - result = result.replace("\"]", "\".into()]"); // Replace end - format!("vec!{result}") - } - VecVecI32 => { - Self::does_pass_basic_vec_tests(line)?; - line.replace('[', "vec![") - } - String_ | Bool => line.to_string(), - List => { - Self::does_pass_basic_vec_tests(line)?; - format!("ListHead::from(vec!{line}).into()") - } - Tree => { - Self::does_pass_basic_vec_tests(line)?; - format!("TreeRoot::from(\"{line}\").into()") + let result = match self { + I32 => match line.parse::() { + Ok(_) => Ok(line.to_string()), + Err(e) => Err(format!( + "In testing the test input {line:?} the parsing to i32 failed with error: {e}" + )), + }, + I64 => match line.parse::() { + Ok(_) => Ok(line.to_string()), + Err(e) => Err(format!( + "In testing the test input {line:?} the parsing to i64 failed with error: {e}" + )), + }, + F64 => match line.parse::() { + Ok(_) => Ok(line.to_string()), + Err(e) => Err(format!( + "In testing the test input {line:?} the parsing to f64 failed with error: {e}" + )), + }, + VecI32 | VecBool | VecF64 => match Self::does_pass_basic_vec_tests(line) { + Ok(_) => Ok(format!("vec!{line}")), + Err(e) => Err(e.to_string()), + }, + VecString => match Self::does_pass_basic_vec_tests(line) { + Ok(_) => { + let mut result = line.replace("\",", "\".into(),"); // Replace ones before end + result = result.replace("\"]", "\".into()]"); // Replace end + Ok(format!("vec!{result}")) + } + Err(e) => Err(e.to_string()), + }, + VecVecI32 => match Self::does_pass_basic_vec_tests(line) { + Ok(_) => Ok(line.replace('[', "vec![")), + Err(e) => Err(e.to_string()), + }, + String_ | Bool => Ok(line.to_string()), + List => match Self::does_pass_basic_vec_tests(line) { + Ok(_) => Ok(format!("ListHead::from(vec!{line}).into()")), + Err(e) => Err(e.to_string()), + }, + Tree => match Self::does_pass_basic_vec_tests(line) { + Ok(_) => Ok(format!("TreeRoot::from(\"{line}\").into()")), + Err(e) => Err(e.to_string()), + }, + Other { raw: _ } => Ok(format!("todo!(\"{line}\")")), + }; + match result { + Ok(result) => Ok(result), + Err(e) => { + warn!("Type Mismatch? Type detected as '{self:?}' but got argument value of {line:?}. Error: {e}"); + Ok(format!("todo!({line:?})")) } - Other { raw: _ } => format!("todo!(\"{line}\")"), - }) + } } fn does_pass_basic_vec_tests(s: &str) -> anyhow::Result<()> { if !s.starts_with('[') || !s.ends_with(']') { - bail!("Expecting something that can be represented as a vec but got '{s}'"); + bail!("Expecting something that can be represented as a vec but got {s:?}"); } Ok(()) } From dbafdd2ad4332fbb5135453faee6cad2112986d6 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 12 Jul 2023 21:58:13 -0400 Subject: [PATCH 164/196] Remove TODO Will track with Discussion instead of leaving a TODO in the code --- src/tool/core/generate.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tool/core/generate.rs b/src/tool/core/generate.rs index d38239d..433032a 100644 --- a/src/tool/core/generate.rs +++ b/src/tool/core/generate.rs @@ -39,7 +39,6 @@ fn get_slug_from_args(specific_problem: &String) -> anyhow::Result Date: Wed, 19 Jul 2023 00:42:04 -0400 Subject: [PATCH 165/196] Add template to link --- src/tool/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 119d616..3883be8 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -369,7 +369,7 @@ impl TryFrom<&str> for FunctionArgType { "Option>" => List, "Option>>" => Tree, trimmed_value => { - warn!("Unknown type {trimmed_value:?} found please report this in an issue https://github.com/rust-practice/cargo-leet/issues/"); + warn!("Unknown type {trimmed_value:?} found please report this in an issue https://github.com/rust-practice/cargo-leet/issues/new?&labels=bug&template=missing_type.md"); Other { raw: trimmed_value.to_string(), } From 61ce21876632914cd58b1edd4cb0808f016ce157 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:38:09 -0400 Subject: [PATCH 166/196] Override cargo test to test all features --- .cargo/config.toml | 2 ++ .vscode/settings.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index ccddced..d6b5584 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,5 @@ [alias] i = "install --path . --features=tool" g = "run --features=tool -- leet gen" +t = "test --all-features" +test = "test --all-features" diff --git a/.vscode/settings.json b/.vscode/settings.json index 691e9d3..c553aed 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,5 @@ "editor.formatOnSave": true, "files.autoSave": "onFocusChange", "rust-analyzer.cargo.features": "all", // Sets the features used by rust analyzer - "code-runner.customCommand": "cargo test --features=tool", + "code-runner.customCommand": "cargo t", } \ No newline at end of file From ea7bdf69cf3200c9b7dd589370b0940c0e6b25a4 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:39:28 -0400 Subject: [PATCH 167/196] Merge cases for vecs --- src/tool/core/helpers/problem_code.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 3883be8..4177897 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -275,8 +275,9 @@ impl FunctionArgType { "In testing the test input {line:?} the parsing to f64 failed with error: {e}" )), }, - VecI32 | VecBool | VecF64 => match Self::does_pass_basic_vec_tests(line) { - Ok(_) => Ok(format!("vec!{line}")), + + VecI32 | VecBool | VecF64 | VecVecI32 => match Self::does_pass_basic_vec_tests(line) { + Ok(_) => Ok(line.replace('[', "vec![")), Err(e) => Err(e.to_string()), }, VecString => match Self::does_pass_basic_vec_tests(line) { @@ -287,10 +288,6 @@ impl FunctionArgType { } Err(e) => Err(e.to_string()), }, - VecVecI32 => match Self::does_pass_basic_vec_tests(line) { - Ok(_) => Ok(line.replace('[', "vec![")), - Err(e) => Err(e.to_string()), - }, String_ | Bool => Ok(line.to_string()), List => match Self::does_pass_basic_vec_tests(line) { Ok(_) => Ok(format!("ListHead::from(vec!{line}).into()")), From 8f67ef95139a090efea984861fdeb25af0627f19 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 21 Jul 2023 13:41:22 -0400 Subject: [PATCH 168/196] Reorder cases by complexity --- src/tool/core/helpers/problem_code.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 4177897..cfaf834 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -257,6 +257,7 @@ impl FunctionArgType { debug!("Going to apply changes to argument input for {self:#?} to {line:?}"); use FunctionArgType::*; let result = match self { + String_ | Bool => Ok(line.to_string()), I32 => match line.parse::() { Ok(_) => Ok(line.to_string()), Err(e) => Err(format!( @@ -275,7 +276,6 @@ impl FunctionArgType { "In testing the test input {line:?} the parsing to f64 failed with error: {e}" )), }, - VecI32 | VecBool | VecF64 | VecVecI32 => match Self::does_pass_basic_vec_tests(line) { Ok(_) => Ok(line.replace('[', "vec![")), Err(e) => Err(e.to_string()), @@ -288,7 +288,6 @@ impl FunctionArgType { } Err(e) => Err(e.to_string()), }, - String_ | Bool => Ok(line.to_string()), List => match Self::does_pass_basic_vec_tests(line) { Ok(_) => Ok(format!("ListHead::from(vec!{line}).into()")), Err(e) => Err(e.to_string()), From 593dfd00a91b9e4ca6d91aa2385cacf666c8d503 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 21 Jul 2023 16:15:26 -0400 Subject: [PATCH 169/196] Move test to end to make it easier to find --- src/tool/core/helpers/problem_code.rs | 188 +++++++++++++------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index cfaf834..ad85d4b 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -382,100 +382,6 @@ mod tests { use super::*; - fn create_code_stub_all_arg_types_non_design() -> &'static str { - " -impl Solution { - pub fn func_name( - L2AC6p: i32, - q7kv5k: i64, - pP7GhC: f64, - HFGzdD: bool, - ePfFj3: Vec, - kRubF2: Vec, - ykyF5X: Vec, - bBtcWe: Vec>, - NkCeR6: Vec, - kjACSr: String, - bJy3HH: Option>, - ndQLTu: Option>>, - PRnJhw: UnknownType, - ) { - } -} -" - } - - fn fn_type_to_id(fat: &FunctionArgType) -> &'static str { - match fat { - FunctionArgType::I32 => "L2AC6p", - FunctionArgType::I64 => "q7kv5k", - FunctionArgType::F64 => "pP7GhC", - FunctionArgType::Bool => "HFGzdD", - FunctionArgType::VecI32 => "ePfFj3", - FunctionArgType::VecF64 => "kRubF2", - FunctionArgType::VecBool => "ykyF5X", - FunctionArgType::VecVecI32 => "bBtcWe", - FunctionArgType::VecString => "NkCeR6", - FunctionArgType::String_ => "kjACSr", - FunctionArgType::List => "bJy3HH", - FunctionArgType::Tree => "ndQLTu", - FunctionArgType::Other { .. } => "PRnJhw", - } - } - - fn extract_function_info(code: &str) -> FunctionInfo { - let problem_code: ProblemCode = code.to_string().try_into().expect("Should be valid code"); - - if let ProblemType::NonDesign(info) = problem_code.type_ { - info - } else { - panic!("Expected Non Design Problem") - } - } - - #[test] - fn function_parsing() { - // Arrange - let code = create_code_stub_all_arg_types_non_design(); - - // Create hashset and fill with the possible argument types - let mut left_to_see = HashSet::new(); - FunctionArgType::iter().for_each(|x| { - left_to_see.insert(x); - }); - - // Add special handling for Other variant - left_to_see.remove(&FunctionArgType::Other { - raw: "".to_string(), - }); - left_to_see.insert(FunctionArgType::Other { - raw: "UnknownType".to_string(), - }); - - // Act - let fn_info = extract_function_info(code); - - // Assert - assert_eq!(fn_info.name, "func_name"); - assert!(fn_info.return_type.is_none()); - for arg in fn_info.fn_args.args.iter() { - // if !left_to_see.contains(&arg.arg_type) { - // panic!("Duplicate type seen. Each type should show up EXACTLY ONCE. Duplicate type: {}",arg.arg_type); - // } - left_to_see.remove(&arg.arg_type); - assert_eq!( - arg.identifier, - fn_type_to_id(&arg.arg_type), - "ArgType: {}", - arg.arg_type - ); - } - assert!( - left_to_see.is_empty(), - "Expected all argument types to be seen but haven't seen {left_to_see:?}", - ); - } - fn get_100_same_tree() -> &'static str { "// Definition for a binary tree node. // #[derive(Debug, PartialEq, Eq)] @@ -718,4 +624,98 @@ impl Solution { // Assert assert!(actual.is_err()); } + + fn create_code_stub_all_arg_types_non_design() -> &'static str { + " +impl Solution { + pub fn func_name( + L2AC6p: i32, + q7kv5k: i64, + pP7GhC: f64, + HFGzdD: bool, + ePfFj3: Vec, + kRubF2: Vec, + ykyF5X: Vec, + bBtcWe: Vec>, + NkCeR6: Vec, + kjACSr: String, + bJy3HH: Option>, + ndQLTu: Option>>, + PRnJhw: UnknownType, + ) { + } +} +" + } + + fn fn_type_to_id(fat: &FunctionArgType) -> &'static str { + match fat { + FunctionArgType::I32 => "L2AC6p", + FunctionArgType::I64 => "q7kv5k", + FunctionArgType::F64 => "pP7GhC", + FunctionArgType::Bool => "HFGzdD", + FunctionArgType::VecI32 => "ePfFj3", + FunctionArgType::VecF64 => "kRubF2", + FunctionArgType::VecBool => "ykyF5X", + FunctionArgType::VecVecI32 => "bBtcWe", + FunctionArgType::VecString => "NkCeR6", + FunctionArgType::String_ => "kjACSr", + FunctionArgType::List => "bJy3HH", + FunctionArgType::Tree => "ndQLTu", + FunctionArgType::Other { .. } => "PRnJhw", + } + } + + fn extract_function_info(code: &str) -> FunctionInfo { + let problem_code: ProblemCode = code.to_string().try_into().expect("Should be valid code"); + + if let ProblemType::NonDesign(info) = problem_code.type_ { + info + } else { + panic!("Expected Non Design Problem") + } + } + + #[test] + fn function_parsing() { + // Arrange + let code = create_code_stub_all_arg_types_non_design(); + + // Create hashset and fill with the possible argument types + let mut left_to_see = HashSet::new(); + FunctionArgType::iter().for_each(|x| { + left_to_see.insert(x); + }); + + // Add special handling for Other variant + left_to_see.remove(&FunctionArgType::Other { + raw: "".to_string(), + }); + left_to_see.insert(FunctionArgType::Other { + raw: "UnknownType".to_string(), + }); + + // Act + let fn_info = extract_function_info(code); + + // Assert + assert_eq!(fn_info.name, "func_name"); + assert!(fn_info.return_type.is_none()); + for arg in fn_info.fn_args.args.iter() { + // if !left_to_see.contains(&arg.arg_type) { + // panic!("Duplicate type seen. Each type should show up EXACTLY ONCE. Duplicate type: {}",arg.arg_type); + // } + left_to_see.remove(&arg.arg_type); + assert_eq!( + arg.identifier, + fn_type_to_id(&arg.arg_type), + "ArgType: {}", + arg.arg_type + ); + } + assert!( + left_to_see.is_empty(), + "Expected all argument types to be seen but haven't seen {left_to_see:?}", + ); + } } From e5c623f6f5608ffe425312c5d7b5cc83cf4755dd Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 21 Jul 2023 19:50:58 -0400 Subject: [PATCH 170/196] This should not have been commented out Not sure what happened --- src/tool/core/helpers/problem_code.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index ad85d4b..ef4cbc3 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -702,9 +702,9 @@ impl Solution { assert_eq!(fn_info.name, "func_name"); assert!(fn_info.return_type.is_none()); for arg in fn_info.fn_args.args.iter() { - // if !left_to_see.contains(&arg.arg_type) { - // panic!("Duplicate type seen. Each type should show up EXACTLY ONCE. Duplicate type: {}",arg.arg_type); - // } + if !left_to_see.contains(&arg.arg_type) { + panic!("Duplicate type seen. Each type should show up EXACTLY ONCE. Duplicate type: {}",arg.arg_type); + } left_to_see.remove(&arg.arg_type); assert_eq!( arg.identifier, From 1195cb623e0216994b3c8826beef4669eaabc413 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 21 Jul 2023 23:53:37 -0400 Subject: [PATCH 171/196] Test outputs from apply --- src/tool/core/helpers/problem_code.rs | 59 +++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index ef4cbc3..361f4f5 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -718,4 +718,63 @@ impl Solution { "Expected all argument types to be seen but haven't seen {left_to_see:?}", ); } + + #[test] + fn function_arg_type_apply() { + // Using an array instead of rstest because we need to ensure all inputs are covered + use FunctionArgType::*; + let inputs = [ + (I32, "1"), + (I64, "2"), + (F64, "2.00000"), + (Bool, "true"), + (VecI32, "[1,2,3,4]"), + (VecF64, "[6.00000,0.50000,-1.00000,1.00000,-1.00000]"), + (VecBool, "[true,false,false,false,false]"), + (VecVecI32, "[[2,2,3],[7]]"), + (VecString, "[\"@..aA\",\"..B#.\",\"....b\"]"), + (String_, "\"leetcode\""), + (List, "[1,2,4]"), + (Tree, "[1,null,2,3]"), + (Other { raw: "".into() }, "1"), + ]; + + // Create hashset and fill with the possible argument types + let mut left_to_see = HashSet::new(); + FunctionArgType::iter().for_each(|x| { + left_to_see.insert(x); + }); + + // Ensure each is there exactly once + for (fat, _) in inputs.iter() { + if !left_to_see.contains(fat) { + panic!("Duplicate type seen. Each type should show up EXACTLY ONCE. Duplicate type: {fat}"); + } + left_to_see.remove(fat); + } + assert!( + left_to_see.is_empty(), + "Expected all argument types to be seen but haven't seen {left_to_see:?}", + ); + + for (fat, input) in inputs { + let expected = match fat { + I32 => "1", + I64 => "2", + F64 => "2.00000", + Bool => "true", + VecI32 => "vec![1,2,3,4]", + VecF64 => "vec![6.00000,0.50000,-1.00000,1.00000,-1.00000]", + VecBool => "vec![true,false,false,false,false]", + VecVecI32 => "vec![vec![2,2,3],vec![7]]", + VecString => "vec![\"@..aA\".into(),\"..B#.\".into(),\"....b\".into()]", + String_ => "\"leetcode\"", + List => "ListHead::from(vec![1,2,4]).into()", + Tree => "TreeRoot::from(\"[1,null,2,3]\").into()", + Other { raw: _ } => "todo!(\"1\")", + }; + let actual = fat.apply(input).expect("Should be valid input"); + assert_eq!(actual, expected); + } + } } From 970067669beac7e15f29781e9fb2dccf704323fb Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Fri, 21 Jul 2023 23:58:22 -0400 Subject: [PATCH 172/196] Merge Vec cases --- src/tool/core/helpers/problem_code.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 361f4f5..4138009 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -276,18 +276,19 @@ impl FunctionArgType { "In testing the test input {line:?} the parsing to f64 failed with error: {e}" )), }, - VecI32 | VecBool | VecF64 | VecVecI32 => match Self::does_pass_basic_vec_tests(line) { - Ok(_) => Ok(line.replace('[', "vec![")), - Err(e) => Err(e.to_string()), - }, - VecString => match Self::does_pass_basic_vec_tests(line) { - Ok(_) => { - let mut result = line.replace("\",", "\".into(),"); // Replace ones before end - result = result.replace("\"]", "\".into()]"); // Replace end - Ok(format!("vec!{result}")) + VecI32 | VecBool | VecF64 | VecVecI32 | VecString => { + match Self::does_pass_basic_vec_tests(line) { + Ok(_) => { + let mut result = line.to_string(); + if [VecString].contains(self) { + result = result.replace("\",", "\".into(),"); // Replace ones before end + result = result.replace("\"]", "\".into()]"); // Replace end + } + Ok(result.replace('[', "vec![")) + } + Err(e) => Err(e.to_string()), } - Err(e) => Err(e.to_string()), - }, + } List => match Self::does_pass_basic_vec_tests(line) { Ok(_) => Ok(format!("ListHead::from(vec!{line}).into()")), Err(e) => Err(e.to_string()), From a30807654b7346155adccc11298a9c1cfdfd5c91 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 22 Jul 2023 00:06:54 -0400 Subject: [PATCH 173/196] Group similar types --- src/tool/core/helpers/problem_code.rs | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 4138009..ddb59d7 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -240,12 +240,12 @@ enum FunctionArgType { I64, F64, Bool, + String_, VecI32, VecF64, VecBool, - VecVecI32, VecString, - String_, + VecVecI32, List, Tree, Other { raw: String }, @@ -332,12 +332,12 @@ impl Display for FunctionArgType { I64 => "i64", F64 => "f64", Bool => "bool", + String_ => "String", VecI32 => "Vec", VecF64 => "Vec", VecBool => "Vec", - VecVecI32 => "Vec>", VecString => "Vec", - String_ => "String", + VecVecI32 => "Vec>", List => "Option>", Tree => "Option>>", Other { raw } => raw, @@ -357,12 +357,12 @@ impl TryFrom<&str> for FunctionArgType { "i64" => I64, "f64" => F64, "bool" => Bool, + "String" => String_, "Vec" => VecI32, "Vec" => VecF64, "Vec" => VecBool, - "Vec>" => VecVecI32, "Vec" => VecString, - "String" => String_, + "Vec>" => VecVecI32, "Option>" => List, "Option>>" => Tree, trimmed_value => { @@ -634,12 +634,12 @@ impl Solution { q7kv5k: i64, pP7GhC: f64, HFGzdD: bool, + kjACSr: String, ePfFj3: Vec, kRubF2: Vec, ykyF5X: Vec, - bBtcWe: Vec>, NkCeR6: Vec, - kjACSr: String, + bBtcWe: Vec>, bJy3HH: Option>, ndQLTu: Option>>, PRnJhw: UnknownType, @@ -655,12 +655,12 @@ impl Solution { FunctionArgType::I64 => "q7kv5k", FunctionArgType::F64 => "pP7GhC", FunctionArgType::Bool => "HFGzdD", + FunctionArgType::String_ => "kjACSr", FunctionArgType::VecI32 => "ePfFj3", FunctionArgType::VecF64 => "kRubF2", FunctionArgType::VecBool => "ykyF5X", - FunctionArgType::VecVecI32 => "bBtcWe", FunctionArgType::VecString => "NkCeR6", - FunctionArgType::String_ => "kjACSr", + FunctionArgType::VecVecI32 => "bBtcWe", FunctionArgType::List => "bJy3HH", FunctionArgType::Tree => "ndQLTu", FunctionArgType::Other { .. } => "PRnJhw", @@ -729,12 +729,12 @@ impl Solution { (I64, "2"), (F64, "2.00000"), (Bool, "true"), + (String_, "\"leetcode\""), (VecI32, "[1,2,3,4]"), (VecF64, "[6.00000,0.50000,-1.00000,1.00000,-1.00000]"), (VecBool, "[true,false,false,false,false]"), - (VecVecI32, "[[2,2,3],[7]]"), (VecString, "[\"@..aA\",\"..B#.\",\"....b\"]"), - (String_, "\"leetcode\""), + (VecVecI32, "[[2,2,3],[7]]"), (List, "[1,2,4]"), (Tree, "[1,null,2,3]"), (Other { raw: "".into() }, "1"), @@ -764,12 +764,12 @@ impl Solution { I64 => "2", F64 => "2.00000", Bool => "true", + String_ => "\"leetcode\"", VecI32 => "vec![1,2,3,4]", VecF64 => "vec![6.00000,0.50000,-1.00000,1.00000,-1.00000]", VecBool => "vec![true,false,false,false,false]", - VecVecI32 => "vec![vec![2,2,3],vec![7]]", VecString => "vec![\"@..aA\".into(),\"..B#.\".into(),\"....b\".into()]", - String_ => "\"leetcode\"", + VecVecI32 => "vec![vec![2,2,3],vec![7]]", List => "ListHead::from(vec![1,2,4]).into()", Tree => "TreeRoot::from(\"[1,null,2,3]\").into()", Other { raw: _ } => "todo!(\"1\")", From bfa74b8ea1969737803fd6310a780d533df437c6 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 22 Jul 2023 00:12:53 -0400 Subject: [PATCH 174/196] Add VecVecString --- src/tool/core/helpers/problem_code.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index ddb59d7..055ecb1 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -246,6 +246,7 @@ enum FunctionArgType { VecBool, VecString, VecVecI32, + VecVecString, List, Tree, Other { raw: String }, @@ -276,11 +277,11 @@ impl FunctionArgType { "In testing the test input {line:?} the parsing to f64 failed with error: {e}" )), }, - VecI32 | VecBool | VecF64 | VecVecI32 | VecString => { + VecI32 | VecBool | VecF64 | VecVecI32 | VecString | VecVecString => { match Self::does_pass_basic_vec_tests(line) { Ok(_) => { let mut result = line.to_string(); - if [VecString].contains(self) { + if [VecString, VecVecString].contains(self) { result = result.replace("\",", "\".into(),"); // Replace ones before end result = result.replace("\"]", "\".into()]"); // Replace end } @@ -338,6 +339,7 @@ impl Display for FunctionArgType { VecBool => "Vec", VecString => "Vec", VecVecI32 => "Vec>", + VecVecString => "Vec>", List => "Option>", Tree => "Option>>", Other { raw } => raw, @@ -363,6 +365,7 @@ impl TryFrom<&str> for FunctionArgType { "Vec" => VecBool, "Vec" => VecString, "Vec>" => VecVecI32, + "Vec>" => VecVecString, "Option>" => List, "Option>>" => Tree, trimmed_value => { @@ -640,6 +643,7 @@ impl Solution { ykyF5X: Vec, NkCeR6: Vec, bBtcWe: Vec>, + ndi4ny: Vec>, bJy3HH: Option>, ndQLTu: Option>>, PRnJhw: UnknownType, @@ -661,6 +665,7 @@ impl Solution { FunctionArgType::VecBool => "ykyF5X", FunctionArgType::VecString => "NkCeR6", FunctionArgType::VecVecI32 => "bBtcWe", + FunctionArgType::VecVecString => "ndi4ny", FunctionArgType::List => "bJy3HH", FunctionArgType::Tree => "ndQLTu", FunctionArgType::Other { .. } => "PRnJhw", @@ -735,6 +740,10 @@ impl Solution { (VecBool, "[true,false,false,false,false]"), (VecString, "[\"@..aA\",\"..B#.\",\"....b\"]"), (VecVecI32, "[[2,2,3],[7]]"), + ( + VecVecString, + "[[\"java\"],[\"nodejs\"],[\"nodejs\",\"reactjs\"]]", + ), (List, "[1,2,4]"), (Tree, "[1,null,2,3]"), (Other { raw: "".into() }, "1"), @@ -770,6 +779,9 @@ impl Solution { VecBool => "vec![true,false,false,false,false]", VecString => "vec![\"@..aA\".into(),\"..B#.\".into(),\"....b\".into()]", VecVecI32 => "vec![vec![2,2,3],vec![7]]", + VecVecString => { + "vec![vec![\"java\".into()],vec![\"nodejs\".into()],vec![\"nodejs\".into(),\"reactjs\".into()]]" + } List => "ListHead::from(vec![1,2,4]).into()", Tree => "TreeRoot::from(\"[1,null,2,3]\").into()", Other { raw: _ } => "todo!(\"1\")", From 8c0f33a292826a47f31fcd95da69264356bc854a Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 22 Jul 2023 00:16:19 -0400 Subject: [PATCH 175/196] Set CI to test all features --- .github/workflows/general.yml | 2 +- .vscode/settings.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/general.yml b/.github/workflows/general.yml index c955d78..347d850 100644 --- a/.github/workflows/general.yml +++ b/.github/workflows/general.yml @@ -22,7 +22,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Run tests - run: cargo test --features=tool + run: cargo test --all-features fmt: name: Rustfmt diff --git a/.vscode/settings.json b/.vscode/settings.json index c553aed..4ecd8e5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,4 @@ "editor.formatOnSave": true, "files.autoSave": "onFocusChange", "rust-analyzer.cargo.features": "all", // Sets the features used by rust analyzer - "code-runner.customCommand": "cargo t", } \ No newline at end of file From eff2cb56bcdd1ff4c944190b335ab24212f0b40c Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 25 Jul 2023 11:42:54 -0400 Subject: [PATCH 176/196] Stop unused import when compiling in release mode --- src/tool/core/helpers/problem_code.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 055ecb1..aed81d0 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -3,7 +3,6 @@ use std::fmt::Display; use anyhow::{bail, Context}; use log::{debug, info, warn}; use regex::Regex; -use strum::EnumIter; #[derive(Debug)] pub(crate) struct ProblemCode { @@ -233,7 +232,7 @@ impl FunctionArgs { } /// Function Arg Type (FAT) -#[cfg_attr(debug_assertions, derive(EnumIter, Eq, Hash, PartialEq))] +#[cfg_attr(debug_assertions, derive(strum::EnumIter, Eq, Hash, PartialEq))] #[derive(Debug)] enum FunctionArgType { I32, From e0a627f61f7830e0359cffe0c65a656ee171effe Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 25 Jul 2023 11:47:23 -0400 Subject: [PATCH 177/196] Add PartialEq to release builds - Needed for new way of checking if type is a String type --- src/tool/core/helpers/problem_code.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index aed81d0..1c2083a 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -232,8 +232,8 @@ impl FunctionArgs { } /// Function Arg Type (FAT) -#[cfg_attr(debug_assertions, derive(strum::EnumIter, Eq, Hash, PartialEq))] -#[derive(Debug)] +#[cfg_attr(debug_assertions, derive(strum::EnumIter))] +#[derive(Debug, Eq, Hash, PartialEq)] enum FunctionArgType { I32, I64, From 7bc18b293ab2d38b929846251710ad18f30ff96d Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 25 Jul 2023 11:54:56 -0400 Subject: [PATCH 178/196] Test new workflow for release compilation Ensure code also compiles in release mode --- .github/workflows/release_check.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/release_check.yml diff --git a/.github/workflows/release_check.yml b/.github/workflows/release_check.yml new file mode 100644 index 0000000..bb47c02 --- /dev/null +++ b/.github/workflows/release_check.yml @@ -0,0 +1,24 @@ +name: Release Build Confirmation + +on: + push: + branches: + - main + pull_request: + types: [ opened, synchronize, reopened ] + branches: + - main + - develop +env: + CARGO_TERM_COLOR: always + +jobs: + release_compile: + name: Release Compile + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Run Release Compile + run: cargo check --all-features --release \ No newline at end of file From 664c838d31ce84d68ce255d9278e97888b65ca13 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Tue, 25 Jul 2023 12:04:21 -0400 Subject: [PATCH 179/196] Switch to only run on main Develop was only there for testing --- .github/workflows/release_check.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release_check.yml b/.github/workflows/release_check.yml index bb47c02..4498453 100644 --- a/.github/workflows/release_check.yml +++ b/.github/workflows/release_check.yml @@ -8,7 +8,6 @@ on: types: [ opened, synchronize, reopened ] branches: - main - - develop env: CARGO_TERM_COLOR: always From 4970d62dc2ed7f6cc55da6e8547e9b6ba98de85e Mon Sep 17 00:00:00 2001 From: nassersaazi Date: Thu, 27 Jul 2023 21:31:32 +0300 Subject: [PATCH 180/196] Add contribution guide --- CONTRIBUTING.md | 200 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..61c1f7c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,200 @@ +# Contributing + +## Where to start + +All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome. + +The best place to start is to check the [issues](https://github.com/rust-practice/cargo-leet) +for something that interests you. + +## Bug Reports + +* Explain what is currently happening and what you expect instead. + +## Working on the code + +### Fork the project + +In order to work on the project you will need your own fork. To do this click the "Fork" button on +this project. + +Once the project is forked clone it to your local machine: + +```sh +git clone git@github.com:your-user-name/cargo-leet +cd cargo-leet +git remote add upstream git@github.com:rust-practice/cargo-leet.git +``` + +This creates the directory cargo-leet and connects your repository to the upstream (main project) repository. + +### Creating a branch + +You want your main branch to reflect only production-ready code, so create a feature branch for +making your changes. For example: + +```sh +git checkout -b my-new-feature +``` + +This changes your working directory to the my-new-feature branch. Keep any changes in this branch +specific to one bug or feature so the purpose is clear. You can have many my-new-features and switch +in between them using the git checkout command. + +When creating this branch, make sure your main branch is up to date with the latest upstream +main version. To update your local main branch, you can do: + +```sh +git checkout main +git pull upstream main --ff-only +``` + +### Code linting, formatting, and tests + +You can run linting on your code at any time with: + +```sh +cargo clippy +``` + +To format the code run: + +```sh +cargo fmt +``` + +To run the tests: + +```sh +cargo test +``` + +To ensure the code compiles run: + +```sh +cargo check +``` + +Be sure to run all these checks before submitting your pull request. + +## Committing your code + +Once you have made changes to the code on your branch you can see which files have changed by running: + +```sh +git status +``` + +If new files were created that and are not tracked by git they can be added by running: + +```sh +git add . +``` + +Now you can commit your changes in your local repository: + +```sh +git commit -am 'Some short helpful message to describe your changes' +``` + +## Push your changes + +Once your changes are ready and all linting/tests are passing you can push your changes to your forked repositry: + +```sh +git push origin my-new-feature +``` + +origin is the default name of your remote repositry on GitHub. You can see all of your remote repositories by running: + +```sh +git remote -v +``` + +## Making a Pull Request + +After pushing your code to origin it is now on GitHub but not yet part of the cargo-leet project. +When you’re ready to ask for a code review, file a pull request. Before you do, once again make sure +that you have followed all the guidelines outlined in this document regarding code style, tests, and +documentation. You should also double check your branch changes against the branch it was based on by: + +1. Navigating to your repository on GitHub +1. Click on Branches +1. Click on the Compare button for your feature branch +1. Select the base and compare branches, if necessary. This will be main and my-new-feature, respectively. + +### Make the pull request + +If everything looks good, you are ready to make a pull request. This is how you let the maintainers +of the cargo-leet project know you have code ready to be reviewed. To submit the pull request: + +1. Navigate to your repository on GitHub +1. Click on the Pull Request button for your feature branch +1. You can then click on Commits and Files Changed to make sure everything looks okay one last time +1. Write a description of your changes in the Conversation tab +1. Click Send Pull Request + +This request then goes to the repository maintainers, and they will review the code. + +### Updating your pull request + +Changes to your code may be needed based on the review of your pull request. If this is the case you +can make them in your branch, add a new commit to that branch, push it to GitHub, and the pull +request will be automatically updated. Pushing them to GitHub again is done by: + +```sh +git push origin my-new-feature +``` + +This will automatically update your pull request with the latest code and restart the Continuous +Integration tests. + +Another reason you might need to update your pull request is to solve conflicts with changes that +have been merged into the main branch since you opened your pull request. + +To do this, you need to rebase your branch: + +```sh +git checkout my-new-feature +git fetch upstream +git rebase upstream/main +``` + +There may be some merge conficts that need to be resolved. After the feature branch has been update +locally, you can now update your pull request by pushing to the branch on GitHub: + +```sh +git push origin my-new-feature +``` + +If you rebased and get an error when pushing your changes you can resolve it with: + +```sh +git push origin my-new-feature --force +``` + +## Delete your merged branch (optional) + +Once your feature branch is accepted into upstream, you’ll probably want to get rid of the branch. +First, merge upstream main into your main branch so git knows it is safe to delete your branch: + +```sh +git fetch upstream +git checkout main +git merge upstream/main +``` + +Then you can do: + +```sh +git branch -d my-new-feature +``` + +Make sure you use a lower-case -d, or else git won’t warn you if your feature branch has not actually been merged. + +The branch will still exist on GitHub, so to delete it there do: + +```sh +git push origin --delete my-new-feature +``` + From 101b9e2c8b8a99b1733975170ff0192e0b854801 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:04:22 -0400 Subject: [PATCH 181/196] Wrap long lines using rustfmt with nightly --- rustfmt.toml | 2 +- src/leetcode_env/tree.rs | 5 +++-- src/tool/core/helpers/problem_code.rs | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index f3503da..927c904 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,5 @@ # This is unsable so to use you need to use nightly. -# Uncomment the line below then run `rustfmt +nightly src/lib.rs` +# Uncomment the line below then run `cargo +nightly fmt` # This well cause rustfmt to be applied to all the files included # in lib.rs (the whole create except for main.rs) diff --git a/src/leetcode_env/tree.rs b/src/leetcode_env/tree.rs index ea2b954..6e10052 100644 --- a/src/leetcode_env/tree.rs +++ b/src/leetcode_env/tree.rs @@ -222,8 +222,9 @@ mod tests { /// / \ / \ /// 3 - 6 7 /// / \ / \ / \ - /// - 4 - - 8 - - #[allow(unused_mut)] // It's easier to read the code if they all line up but the leaves don't need to be mutable + /// - 4 - - 8 - + #[allow(unused_mut)] // It's easier to read the code if they all line up but the leaves don't need + // to be mutable fn test_tree() -> Option>> { let mut node1 = Some(Rc::new(RefCell::new(TreeNode::new(1)))); let mut node2 = Some(Rc::new(RefCell::new(TreeNode::new(2)))); diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 1c2083a..573d2d2 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -726,7 +726,8 @@ impl Solution { #[test] fn function_arg_type_apply() { - // Using an array instead of rstest because we need to ensure all inputs are covered + // Using an array instead of rstest because we need to ensure all inputs are + // covered use FunctionArgType::*; let inputs = [ (I32, "1"), From 7692b7f0cfd3308fc55d7e0980bb21f4ccf20062 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:08:53 -0400 Subject: [PATCH 182/196] Remove unneeded comment --- rustfmt.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 927c904..3e957c4 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,6 +1,4 @@ # This is unsable so to use you need to use nightly. # Uncomment the line below then run `cargo +nightly fmt` -# This well cause rustfmt to be applied to all the files included -# in lib.rs (the whole create except for main.rs) # wrap_comments = true From c540a245b48fa513e887e8162cf616b8562db6b5 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:17:05 -0400 Subject: [PATCH 183/196] Fix typo --- rustfmt.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustfmt.toml b/rustfmt.toml index 3e957c4..cc2b6b6 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ -# This is unsable so to use you need to use nightly. +# This is unstable so to use you need to use nightly. # Uncomment the line below then run `cargo +nightly fmt` # wrap_comments = true From 4e7ee15b4ec55987a54165f3541474c2687fb2ee Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:19:10 -0400 Subject: [PATCH 184/196] Replace this to improve readability --- rustfmt.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rustfmt.toml b/rustfmt.toml index cc2b6b6..705974a 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,4 @@ -# This is unstable so to use you need to use nightly. +# `wrap_comments` is unstable so to use you need to use nightly. # Uncomment the line below then run `cargo +nightly fmt` # wrap_comments = true From 885d92a5c2d17ff9e50db6a574ad8637c80f601f Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:16:22 -0500 Subject: [PATCH 185/196] Make updates to contribution guide in preparation for publishing --- CONTRIBUTING.md | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 61c1f7c..eeb733f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,10 +6,11 @@ All contributions, bug reports, bug fixes, documentation improvements, enhanceme The best place to start is to check the [issues](https://github.com/rust-practice/cargo-leet) for something that interests you. +There are also other thing in [discussion](https://github.com/rust-practice/cargo-leet/discussions) feel free to pick from there as well. ## Bug Reports -* Explain what is currently happening and what you expect instead. +Please see the [issue templates](https://github.com/rust-practice/cargo-leet/issues/new/choose) that describe the types of information that we are looking for but no worries just fill it the best you can and we'll go from there. ## Working on the code @@ -18,16 +19,18 @@ for something that interests you. In order to work on the project you will need your own fork. To do this click the "Fork" button on this project. -Once the project is forked clone it to your local machine: +Once the project is forked you can work on it directly in github codespaces without needing to install anything by clicking the green button near the top and switching to code spaces. +According to [github docs](https://docs.github.com/en/codespaces/overview#billing-for-codespaces) by default you can only use it up to the free amount so you don't need to worry about charges. +Feel free to check some [notes collected](https://c-git.github.io/github/codespaces/) on how to use codespaces with rust (You don't need trunk for this project). + +Alternatively you can clone it to your local machine. The following commands creates the directory cargo-leet and connects your repository to the upstream (main project) repository. ```sh -git clone git@github.com:your-user-name/cargo-leet +git clone https://github.com/your-user-name/cargo-leet.git cd cargo-leet git remote add upstream git@github.com:rust-practice/cargo-leet.git ``` -This creates the directory cargo-leet and connects your repository to the upstream (main project) repository. - ### Creating a branch You want your main branch to reflect only production-ready code, so create a feature branch for @@ -65,8 +68,10 @@ cargo fmt To run the tests: +Note the follow is overridden in `.cargo/config.toml` to run with all features enabled + ```sh -cargo test +cargo t ``` To ensure the code compiles run: @@ -75,7 +80,7 @@ To ensure the code compiles run: cargo check ``` -Be sure to run all these checks before submitting your pull request. +Please run the tests before submitting your pull request. ## Committing your code @@ -99,13 +104,13 @@ git commit -am 'Some short helpful message to describe your changes' ## Push your changes -Once your changes are ready and all linting/tests are passing you can push your changes to your forked repositry: +Once your changes are ready and all linting/tests are passing you can push your changes to your forked repository: ```sh git push origin my-new-feature ``` -origin is the default name of your remote repositry on GitHub. You can see all of your remote repositories by running: +origin is the default name of your remote repository on GitHub. You can see all of your remote repositories by running: ```sh git remote -v @@ -138,19 +143,17 @@ This request then goes to the repository maintainers, and they will review the c ### Updating your pull request -Changes to your code may be needed based on the review of your pull request. If this is the case you -can make them in your branch, add a new commit to that branch, push it to GitHub, and the pull -request will be automatically updated. Pushing them to GitHub again is done by: +Changes to your code may be needed based on the review of your pull request. +If this is the case you can make them in your branch, add a new commit to that branch, push it to GitHub, and the pull request will be automatically updated. +Pushing them to GitHub again is done by: ```sh git push origin my-new-feature ``` -This will automatically update your pull request with the latest code and restart the Continuous -Integration tests. +This will automatically update your pull request with the latest code and restart the Continuous Integration tests. -Another reason you might need to update your pull request is to solve conflicts with changes that -have been merged into the main branch since you opened your pull request. +Another reason you might need to update your pull request is to solve conflicts with changes that have been merged into the main branch since you opened your pull request. To do this, you need to rebase your branch: @@ -160,8 +163,8 @@ git fetch upstream git rebase upstream/main ``` -There may be some merge conficts that need to be resolved. After the feature branch has been update -locally, you can now update your pull request by pushing to the branch on GitHub: +There may be some merge conflicts that need to be resolved. +After the feature branch has been update locally, you can now update your pull request by pushing to the branch on GitHub: ```sh git push origin my-new-feature @@ -197,4 +200,3 @@ The branch will still exist on GitHub, so to delete it there do: ```sh git push origin --delete my-new-feature ``` - From e22474fb5f1eff9c9b040152d1a93ab23fe3ce76 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:20:49 -0500 Subject: [PATCH 186/196] Ask contributors to use develop instead of main --- CONTRIBUTING.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eeb733f..35a85a4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,8 +33,10 @@ git remote add upstream git@github.com:rust-practice/cargo-leet.git ### Creating a branch -You want your main branch to reflect only production-ready code, so create a feature branch for -making your changes. For example: +You want your main branch to reflect only production-ready code. +Please base your branch on the develop branch which is the default in cargo-leet repo so create a feature branch for +making your changes. +For example: ```sh git checkout -b my-new-feature @@ -44,12 +46,12 @@ This changes your working directory to the my-new-feature branch. Keep any chang specific to one bug or feature so the purpose is clear. You can have many my-new-features and switch in between them using the git checkout command. -When creating this branch, make sure your main branch is up to date with the latest upstream -main version. To update your local main branch, you can do: +When creating this branch, make sure your develop branch is up to date with the latest upstream +develop version. To update your local develop branch, you can do: ```sh -git checkout main -git pull upstream main --ff-only +git checkout develop +git pull upstream develop --ff-only ``` ### Code linting, formatting, and tests @@ -126,7 +128,7 @@ documentation. You should also double check your branch changes against the bran 1. Navigating to your repository on GitHub 1. Click on Branches 1. Click on the Compare button for your feature branch -1. Select the base and compare branches, if necessary. This will be main and my-new-feature, respectively. +1. Select the base and compare branches, if necessary. This will be develop and my-new-feature, respectively. ### Make the pull request @@ -153,14 +155,14 @@ git push origin my-new-feature This will automatically update your pull request with the latest code and restart the Continuous Integration tests. -Another reason you might need to update your pull request is to solve conflicts with changes that have been merged into the main branch since you opened your pull request. +Another reason you might need to update your pull request is to solve conflicts with changes that have been merged into the develop branch since you opened your pull request. To do this, you need to rebase your branch: ```sh git checkout my-new-feature git fetch upstream -git rebase upstream/main +git rebase upstream/develop ``` There may be some merge conflicts that need to be resolved. @@ -179,12 +181,12 @@ git push origin my-new-feature --force ## Delete your merged branch (optional) Once your feature branch is accepted into upstream, you’ll probably want to get rid of the branch. -First, merge upstream main into your main branch so git knows it is safe to delete your branch: +First, merge upstream develop into your develop branch so git knows it is safe to delete your branch: ```sh git fetch upstream -git checkout main -git merge upstream/main +git checkout develop +git merge upstream/develop ``` Then you can do: From b796e7952e751c516ae3c2c1ddd1a22c6ff200f6 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:21:59 -0500 Subject: [PATCH 187/196] Remove cargo check as it's just duplicated work --- CONTRIBUTING.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 35a85a4..bc59284 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,19 +70,13 @@ cargo fmt To run the tests: -Note the follow is overridden in `.cargo/config.toml` to run with all features enabled +Note the following is overridden in `.cargo/config.toml` to run with all features enabled ```sh cargo t ``` -To ensure the code compiles run: - -```sh -cargo check -``` - -Please run the tests before submitting your pull request. +Please run these checks before submitting your pull request. ## Committing your code From 707c623f39bb5eb74b8d7bf1f1d52e7dfac4fc33 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:33:31 -0500 Subject: [PATCH 188/196] use dprint to format files --- .github/ISSUE_TEMPLATE/bug_report.md | 12 +++++++++++- .github/ISSUE_TEMPLATE/missing_type.md | 5 ++++- .vscode/settings.json | 16 +++++++-------- README.md | 27 +++++++++++++------------- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index eb0238e..4ca595c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,26 +8,36 @@ assignees: '' --- **Describe the bug** + **Leetcode problem** + **To Reproduce** + + `` **Error Message** + + ``` ``` **Expected behavior** + **Environment Info:** + - - Cargo Version: + +- Cargo Version: **Additional context** + diff --git a/.github/ISSUE_TEMPLATE/missing_type.md b/.github/ISSUE_TEMPLATE/missing_type.md index 04ba8e9..a82784c 100644 --- a/.github/ISSUE_TEMPLATE/missing_type.md +++ b/.github/ISSUE_TEMPLATE/missing_type.md @@ -8,9 +8,12 @@ assignees: '' --- **Leetcode problem** + **Error Message** + + +``` ``` -``` \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ecd8e5..2e460b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,9 @@ { - "cSpell.words": [ - "Vecbool", - "Veci" - ], - "editor.formatOnSave": true, - "files.autoSave": "onFocusChange", - "rust-analyzer.cargo.features": "all", // Sets the features used by rust analyzer -} \ No newline at end of file + "cSpell.words": [ + "Vecbool", + "Veci" + ], + "editor.formatOnSave": true, + "files.autoSave": "onFocusChange", + "rust-analyzer.cargo.features": "all" // Sets the features used by rust analyzer +} diff --git a/README.md b/README.md index 5ef2350..8903ecc 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,25 @@ ## cargo-leet - A leetcode local development assistant - A program that given the link or slug to a leetcode problem, - creates a local file where you can develop and test your solution before post it back to leetcode. +A program that given the link or slug to a leetcode problem, +creates a local file where you can develop and test your solution before post it back to leetcode. - ## ScreenShots +## ScreenShots - ### `cargo leet` - ![ScreenShot](assets/help_scr_shot_top.png) +### `cargo leet` - ### `cargo leet generate --help` - ![ScreenShot](assets/help_scr_shot_generate.png) +![ScreenShot](assets/help_scr_shot_top.png) - ## Using Library Support +### `cargo leet generate --help` - Using the library to "mimic" leetcode environment. Add library as a dependency as below. Then add use statements as necessary (automatically added if tool is used to generate the file). +![ScreenShot](assets/help_scr_shot_generate.png) - ```toml - cargo-leet = { git = "https://github.com/rust-practice/cargo-leet.git", branch = "develop" } - ``` +## Using Library Support +Using the library to "mimic" leetcode environment. Add library as a dependency as below. Then add use statements as necessary (automatically added if tool is used to generate the file). + +```toml +cargo-leet = { git = "https://github.com/rust-practice/cargo-leet.git", branch = "develop" } +``` ## Tool Installation @@ -64,8 +65,6 @@ or using alias from `.cargo/config.toml` cargo g ``` - - ## Tool Uninstallation ```sh From e26155ff3d9280898a9d6a6557438b2cec8788ec Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:45:22 +0000 Subject: [PATCH 189/196] Remove misleading line as it doesn't actually work --- .cargo/config.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index d6b5584..e982631 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,4 +2,3 @@ i = "install --path . --features=tool" g = "run --features=tool -- leet gen" t = "test --all-features" -test = "test --all-features" From 9166c371ceb268aba87e844307c2e378ffb819a8 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:49:25 +0000 Subject: [PATCH 190/196] FIx lints from new version of rust --- src/tool/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tool/config.rs b/src/tool/config.rs index be41f44..d8c7aa1 100644 --- a/src/tool/config.rs +++ b/src/tool/config.rs @@ -2,8 +2,8 @@ pub(crate) struct Config {} impl Config { // assumed in the code using them URLs Must end with trailing "/" - pub(crate) const LEETCODE_PROBLEM_URL: &str = "https://leetcode.com/problems/"; - pub(crate) const LEETCODE_GRAPH_QL: &str = "https://leetcode.com/graphql/"; + pub(crate) const LEETCODE_PROBLEM_URL: &'static str = "https://leetcode.com/problems/"; + pub(crate) const LEETCODE_GRAPH_QL: &'static str = "https://leetcode.com/graphql/"; } #[cfg(test)] From 8f4853a0730c279bc33fe4ecd2f263c49825560d Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Sat, 13 Jan 2024 15:18:43 -0500 Subject: [PATCH 191/196] Clarify statement in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8903ecc..d6980e4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ creates a local file where you can develop and test your solution before post it ## Using Library Support -Using the library to "mimic" leetcode environment. Add library as a dependency as below. Then add use statements as necessary (automatically added if tool is used to generate the file). +Using the library to "mimic" leetcode environment. Add library as a dependency as below. Then add use statements as necessary. The use statements are automatically added if tool is used to generate the file for the problem. ```toml cargo-leet = { git = "https://github.com/rust-practice/cargo-leet.git", branch = "develop" } From 6775ba56ae6dc9dc40cf0ab0ee50f90b737db76c Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Mon, 22 Jan 2024 10:24:08 -0500 Subject: [PATCH 192/196] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc59284..738c6ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ All contributions, bug reports, bug fixes, documentation improvements, enhanceme The best place to start is to check the [issues](https://github.com/rust-practice/cargo-leet) for something that interests you. -There are also other thing in [discussion](https://github.com/rust-practice/cargo-leet/discussions) feel free to pick from there as well. +There are also other options in [discussions](https://github.com/rust-practice/cargo-leet/discussions), so feel free to pick from there as well. ## Bug Reports From ca43b309b9fdf2e06dd71631347c9c65be94cdc9 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 24 Jan 2024 09:25:41 -0500 Subject: [PATCH 193/196] Allow spaces after function name Needed for today's problem https://leetcode.com/problems/pseudo-palindromic-paths-in-a-binary-tree --- src/tool/core/helpers/problem_code.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tool/core/helpers/problem_code.rs b/src/tool/core/helpers/problem_code.rs index 573d2d2..4e75775 100644 --- a/src/tool/core/helpers/problem_code.rs +++ b/src/tool/core/helpers/problem_code.rs @@ -55,7 +55,7 @@ impl ProblemCode { } fn get_fn_info(code: &str) -> anyhow::Result { - let re = Regex::new(r#"(?s)\n\s*pub fn ([a-z_0-9]*)\((.*)\)(?: ?-> ?(.*))? \{"#)?; + let re = Regex::new(r#"(?s)\n\s*pub fn ([a-z_0-9]*)\s*\((.*)\)(?: ?-> ?(.*))? \{"#)?; let caps = if let Some(caps) = re.captures(code) { caps } else { From ac7ec6316a97bd5dc4de842dd00679b7544b7808 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 24 Jan 2024 09:26:17 -0500 Subject: [PATCH 194/196] Add TODO to change how constant is validated --- src/tool/config.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tool/config.rs b/src/tool/config.rs index d8c7aa1..52a600d 100644 --- a/src/tool/config.rs +++ b/src/tool/config.rs @@ -12,6 +12,7 @@ mod tests { #[test] fn problem_url_ends_with_slash() { + // TODO: Switch to using https://docs.rs/static_assertions/latest/static_assertions/ assert!(Config::LEETCODE_PROBLEM_URL.ends_with('/')); } From 4257a47dd1b2e60b44bb21e372c4252d8509a7e9 Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 24 Jan 2024 09:26:50 -0500 Subject: [PATCH 195/196] Version Bump --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5d2507..8390990 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,7 +92,7 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "cargo-leet" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 426144b..ff19c78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "cargo-leet" description = "Utility program to help with working on leetcode locally" repository = "https://github.com/rust-practice/cargo-leet" -version = "0.1.0" +version = "0.1.1" authors = ["Members of Rust Practice Discord Server"] readme = "README.md" license = "MIT OR Apache-2.0" From 20adebd519e9ac4aa6fdf4eb0ef68476694b24ca Mon Sep 17 00:00:00 2001 From: One <43485962+c-git@users.noreply.github.com> Date: Wed, 24 Jan 2024 09:28:40 -0500 Subject: [PATCH 196/196] Increasing version number given the number of changes --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8390990..5350977 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,7 +92,7 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "cargo-leet" -version = "0.1.1" +version = "0.2.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index ff19c78..e33bfee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "cargo-leet" description = "Utility program to help with working on leetcode locally" repository = "https://github.com/rust-practice/cargo-leet" -version = "0.1.1" +version = "0.2.0" authors = ["Members of Rust Practice Discord Server"] readme = "README.md" license = "MIT OR Apache-2.0"