Skip to content

Commit 8892e15

Browse files
authored
Merge pull request #15 from coding-kelps/13-parse-readme-to-collect-test-values
13 parse readme to collect test values
2 parents 6d6ed31 + 0df4156 commit 8892e15

File tree

17 files changed

+638
-43
lines changed

17 files changed

+638
-43
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ repository = "https://github.com/coding-kelps/leetcode-cli"
1414
rust-version = "1.83"
1515

1616
[[bin]]
17-
name = "leetcode_cli"
17+
name = "leetcode-cli"
1818
path = "src/main.rs"
1919

2020
[lib]
@@ -35,3 +35,5 @@ colored = "3.0.0"
3535
nanohtml2text = "0.2.1"
3636
html2md = "0.2.15"
3737
tempfile = "3.20.0"
38+
regex = "1.11.1"
39+
thiserror = "2.0.12"

src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ pub enum Commands {
2020
id: u32,
2121

2222
#[arg(short = 'l', long = "lang")]
23-
language: String,
23+
language: Option<String>,
2424
},
2525
Test {
2626
#[arg(short = 'i', long)]

src/config.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ pub struct RuntimeConfigSetup {
2323
pub config: ConfigFile,
2424
}
2525

26+
impl Default for RuntimeConfigSetup {
27+
fn default() -> Self {
28+
Self::new()
29+
}
30+
}
31+
2632
impl RuntimeConfigSetup {
2733
pub fn new() -> Self {
2834
let home_dir =
@@ -87,9 +93,9 @@ impl RuntimeConfigSetup {
8793
let raw_str = raw.as_ref();
8894
let mut path = if raw_str == "~" {
8995
self.home_dir.clone()
90-
} else if raw_str.starts_with("~/") {
96+
} else if let Some(stripped) = raw_str.strip_prefix("~/") {
9197
let home = self.home_dir.clone();
92-
home.join(&raw_str[2..])
98+
home.join(stripped)
9399
} else {
94100
PathBuf::from(raw_str)
95101
};

src/leetcode_api_runner.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ pub struct LeetcodeApiRunner {
2323
}
2424

2525
impl LeetcodeApiRunner {
26-
pub async fn new(rcs: RuntimeConfigSetup) -> Self {
26+
pub async fn new(rcs: &RuntimeConfigSetup) -> Self {
2727
let api = UserApi::new(&rcs.config.leetcode_token).await.unwrap();
2828
LeetcodeApiRunner {
29-
rcs,
29+
rcs: rcs.clone(),
3030
api,
3131
}
3232
}
@@ -155,19 +155,19 @@ impl LeetcodeApiRunner {
155155

156156
let result = match language {
157157
ProgrammingLanguage::Rust => Command::new("cargo")
158-
.args(&["init", "--name", pb_name, "--vcs", "none"])
158+
.args(["init", "--name", pb_name, "--vcs", "none"])
159159
.current_dir(problem_dir)
160160
.output(),
161161
ProgrammingLanguage::JavaScript
162162
| ProgrammingLanguage::TypeScript => Command::new("npm")
163-
.args(&["init", "-y"])
163+
.args(["init", "-y"])
164164
.current_dir(problem_dir)
165165
.output(),
166166
ProgrammingLanguage::Go => {
167167
let module_name =
168168
format!("leetcode-{}", pb_name.replace("_", "-"));
169169
Command::new("go")
170-
.args(&["mod", "init", &module_name])
170+
.args(["mod", "init", &module_name])
171171
.current_dir(problem_dir)
172172
.output()
173173
},

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod cli;
22
pub mod config;
33
pub mod leetcode_api_runner;
4+
pub mod readme_parser;
45
pub mod utils;
56

67
pub use cli::{

src/main.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
1212
let cli = Cli::parse();
1313
let mut rcs = RuntimeConfigSetup::new();
1414
rcs.status()?;
15-
let api_runner = LeetcodeApiRunner::new(rcs).await;
15+
let api_runner = LeetcodeApiRunner::new(&rcs).await;
1616

1717
match &cli.command {
1818
Commands::Info {
@@ -24,11 +24,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2424
id,
2525
language,
2626
} => {
27-
let language = utils::parse_programming_language(language);
28-
println!(
29-
"{}\nHappy Coding :)",
30-
api_runner.start_problem(*id, language).await?
31-
);
27+
let default = &rcs.config.default_language.unwrap();
28+
let lang = match language {
29+
Some(lang) => utils::parse_programming_language(lang)?,
30+
None => utils::parse_programming_language(default)?,
31+
};
32+
let start_problem = api_runner.start_problem(*id, lang).await?;
33+
println!("{}\n\nHappy coding :)", start_problem);
3234
},
3335
Commands::Test {
3436
id,

src/readme_parser.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use regex::Regex;
2+
use thiserror;
3+
4+
pub struct LeetcodeReadmeParser {
5+
pub raw: String,
6+
}
7+
8+
pub struct ProblemTestData {
9+
pub example_count: usize,
10+
pub inputs: Vec<String>,
11+
pub outputs: Vec<String>,
12+
}
13+
14+
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
15+
pub enum LeetcodeReadmeParserError {
16+
#[error("can't parse empty readme")]
17+
EmptyReadme,
18+
}
19+
20+
impl LeetcodeReadmeParser {
21+
pub fn new(readme: &str) -> Self {
22+
LeetcodeReadmeParser {
23+
raw: readme.to_string(),
24+
}
25+
}
26+
27+
pub fn parse(&self) -> Result<ProblemTestData, LeetcodeReadmeParserError> {
28+
if self.raw.is_empty() {
29+
return Err(LeetcodeReadmeParserError::EmptyReadme);
30+
}
31+
Ok(ProblemTestData {
32+
example_count: self.count_examples(),
33+
inputs: self.extract_inputs(),
34+
outputs: self.extract_outputs(),
35+
})
36+
}
37+
38+
fn count_examples(&self) -> usize {
39+
self.raw
40+
.lines()
41+
.filter(|line| line.starts_with("**Example"))
42+
.count()
43+
}
44+
45+
fn extract_inputs(&self) -> Vec<String> {
46+
self.extract_from_pattern(r"(?m)^\s*\*?\*?Input:\*?\*?\s*(.*)$")
47+
}
48+
49+
fn extract_outputs(&self) -> Vec<String> {
50+
self.extract_from_pattern(r"(?m)^\s*\*?\*?Output:\*?\*?\s*(.*)$")
51+
}
52+
53+
fn extract_from_pattern(&self, pattern: &str) -> Vec<String> {
54+
let re = Regex::new(pattern).unwrap();
55+
56+
let mut result = Vec::new();
57+
for capture in re.captures_iter(&self.raw) {
58+
if let Some(matched) = capture.get(1) {
59+
let input = matched
60+
.as_str()
61+
.replace(['\n', '\t'], " ")
62+
.trim()
63+
.to_string();
64+
65+
let trimmed = if input.contains('=') {
66+
input
67+
.split(',')
68+
.filter_map(|part| {
69+
if let Some(eq_pos) = part.find('=') {
70+
Some(part[eq_pos + 1..].trim())
71+
} else {
72+
// Handle continuation of previous array/value
73+
let trimmed_part = part.trim();
74+
(!trimmed_part.is_empty())
75+
.then_some(trimmed_part)
76+
}
77+
})
78+
.collect::<Vec<_>>()
79+
.join(",")
80+
} else {
81+
input.to_string()
82+
};
83+
84+
result.push(trimmed);
85+
}
86+
}
87+
result
88+
}
89+
}

src/utils.rs

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,35 @@ pub fn write_to_file(
3434

3535
pub fn parse_programming_language(
3636
lang: &str,
37-
) -> leetcoderustapi::ProgrammingLanguage {
37+
) -> Result<leetcoderustapi::ProgrammingLanguage, String> {
3838
match lang.to_ascii_lowercase().as_str() {
39-
"cpp" | "c++" => leetcoderustapi::ProgrammingLanguage::CPP,
40-
"java" => leetcoderustapi::ProgrammingLanguage::Java,
41-
"python" | "py" => leetcoderustapi::ProgrammingLanguage::Python,
42-
"python3" | "py3" => leetcoderustapi::ProgrammingLanguage::Python3,
43-
"c" => leetcoderustapi::ProgrammingLanguage::C,
44-
"csharp" | "c#" => leetcoderustapi::ProgrammingLanguage::CSharp,
45-
"javascript" | "js" => leetcoderustapi::ProgrammingLanguage::JavaScript,
46-
"typescript" | "ts" => leetcoderustapi::ProgrammingLanguage::TypeScript,
47-
"ruby" => leetcoderustapi::ProgrammingLanguage::Ruby,
48-
"swift" => leetcoderustapi::ProgrammingLanguage::Swift,
49-
"go" | "golang" => leetcoderustapi::ProgrammingLanguage::Go,
50-
"bash" | "shell" => leetcoderustapi::ProgrammingLanguage::Bash,
51-
"scala" => leetcoderustapi::ProgrammingLanguage::Scala,
52-
"kotlin" | "kt" => leetcoderustapi::ProgrammingLanguage::Kotlin,
53-
"rust" | "rs" => leetcoderustapi::ProgrammingLanguage::Rust,
54-
"php" => leetcoderustapi::ProgrammingLanguage::PHP,
55-
"racket" => leetcoderustapi::ProgrammingLanguage::Racket,
56-
"erlang" => leetcoderustapi::ProgrammingLanguage::Erlang,
57-
"elixir" => leetcoderustapi::ProgrammingLanguage::Elixir,
58-
"dart" => leetcoderustapi::ProgrammingLanguage::Dart,
59-
"pandas" => leetcoderustapi::ProgrammingLanguage::Pandas,
60-
"react" => leetcoderustapi::ProgrammingLanguage::React,
61-
_ => panic!("Unsupported language: {}", lang),
39+
"cpp" | "c++" => Ok(leetcoderustapi::ProgrammingLanguage::CPP),
40+
"java" => Ok(leetcoderustapi::ProgrammingLanguage::Java),
41+
"python" | "py" => Ok(leetcoderustapi::ProgrammingLanguage::Python),
42+
"python3" | "py3" => Ok(leetcoderustapi::ProgrammingLanguage::Python3),
43+
"c" => Ok(leetcoderustapi::ProgrammingLanguage::C),
44+
"csharp" | "c#" => Ok(leetcoderustapi::ProgrammingLanguage::CSharp),
45+
"javascript" | "js" => {
46+
Ok(leetcoderustapi::ProgrammingLanguage::JavaScript)
47+
},
48+
"typescript" | "ts" => {
49+
Ok(leetcoderustapi::ProgrammingLanguage::TypeScript)
50+
},
51+
"ruby" => Ok(leetcoderustapi::ProgrammingLanguage::Ruby),
52+
"swift" => Ok(leetcoderustapi::ProgrammingLanguage::Swift),
53+
"go" | "golang" => Ok(leetcoderustapi::ProgrammingLanguage::Go),
54+
"bash" | "shell" => Ok(leetcoderustapi::ProgrammingLanguage::Bash),
55+
"scala" => Ok(leetcoderustapi::ProgrammingLanguage::Scala),
56+
"kotlin" | "kt" => Ok(leetcoderustapi::ProgrammingLanguage::Kotlin),
57+
"rust" | "rs" => Ok(leetcoderustapi::ProgrammingLanguage::Rust),
58+
"php" => Ok(leetcoderustapi::ProgrammingLanguage::PHP),
59+
"racket" => Ok(leetcoderustapi::ProgrammingLanguage::Racket),
60+
"erlang" => Ok(leetcoderustapi::ProgrammingLanguage::Erlang),
61+
"elixir" => Ok(leetcoderustapi::ProgrammingLanguage::Elixir),
62+
"dart" => Ok(leetcoderustapi::ProgrammingLanguage::Dart),
63+
"pandas" => Ok(leetcoderustapi::ProgrammingLanguage::Pandas),
64+
"react" => Ok(leetcoderustapi::ProgrammingLanguage::React),
65+
_ => Err(format!("Unsupported language: {}", lang)),
6266
}
6367
}
6468

tests/cli_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ fn test_cli_start_command() {
2828
language,
2929
} => {
3030
assert_eq!(id, 1);
31-
assert_eq!(language, "rust");
31+
assert_eq!(language.unwrap(), "rust");
3232
},
3333
_ => panic!("Expected Start command"),
3434
}

tests/data/1004.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Problem 1004: Max_Consecutive_Ones_III
2+
3+
Given a binary array `nums` and an integer `k`, return *the maximum number of consecutive* `1`*'s in the array if you can flip at most* `k` `0`'s.
4+
5+
**Example 1:**
6+
7+
```
8+
Input: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
9+
Output: 6
10+
Explanation: [1,1,1,0,0,1,1,1,1,1,1]
11+
Bolded numbers were flipped from 0 to 1. The longest subarray is underlined.
12+
```
13+
14+
**Example 2:**
15+
16+
```
17+
Input: nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], k = 3
18+
Output: 10
19+
Explanation: [0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
20+
Bolded numbers were flipped from 0 to 1. The longest subarray is underlined.
21+
22+
```
23+
24+
**Constraints:**
25+
26+
* `1 <= nums.length <= 10<sup>5</sup>`
27+
* `nums[i]` is either `0` or `1`.
28+
* `0 <= k <= nums.length`

0 commit comments

Comments
 (0)