Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repository = "https://github.com/coding-kelps/leetcode-cli"
rust-version = "1.83"

[[bin]]
name = "leetcode_cli"
name = "leetcode-cli"
path = "src/main.rs"

[lib]
Expand All @@ -35,3 +35,5 @@ colored = "3.0.0"
nanohtml2text = "0.2.1"
html2md = "0.2.15"
tempfile = "3.20.0"
regex = "1.11.1"
thiserror = "2.0.12"
2 changes: 1 addition & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub enum Commands {
id: u32,

#[arg(short = 'l', long = "lang")]
language: String,
language: Option<String>,
},
Test {
#[arg(short = 'i', long)]
Expand Down
10 changes: 8 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ pub struct RuntimeConfigSetup {
pub config: ConfigFile,
}

impl Default for RuntimeConfigSetup {
fn default() -> Self {
Self::new()
}
}

impl RuntimeConfigSetup {
pub fn new() -> Self {
let home_dir =
Expand Down Expand Up @@ -87,9 +93,9 @@ impl RuntimeConfigSetup {
let raw_str = raw.as_ref();
let mut path = if raw_str == "~" {
self.home_dir.clone()
} else if raw_str.starts_with("~/") {
} else if let Some(stripped) = raw_str.strip_prefix("~/") {
let home = self.home_dir.clone();
home.join(&raw_str[2..])
home.join(stripped)
} else {
PathBuf::from(raw_str)
};
Expand Down
10 changes: 5 additions & 5 deletions src/leetcode_api_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ pub struct LeetcodeApiRunner {
}

impl LeetcodeApiRunner {
pub async fn new(rcs: RuntimeConfigSetup) -> Self {
pub async fn new(rcs: &RuntimeConfigSetup) -> Self {
let api = UserApi::new(&rcs.config.leetcode_token).await.unwrap();
LeetcodeApiRunner {
rcs,
rcs: rcs.clone(),
api,
}
}
Expand Down Expand Up @@ -155,19 +155,19 @@ impl LeetcodeApiRunner {

let result = match language {
ProgrammingLanguage::Rust => Command::new("cargo")
.args(&["init", "--name", pb_name, "--vcs", "none"])
.args(["init", "--name", pb_name, "--vcs", "none"])
.current_dir(problem_dir)
.output(),
ProgrammingLanguage::JavaScript
| ProgrammingLanguage::TypeScript => Command::new("npm")
.args(&["init", "-y"])
.args(["init", "-y"])
.current_dir(problem_dir)
.output(),
ProgrammingLanguage::Go => {
let module_name =
format!("leetcode-{}", pb_name.replace("_", "-"));
Command::new("go")
.args(&["mod", "init", &module_name])
.args(["mod", "init", &module_name])
.current_dir(problem_dir)
.output()
},
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod cli;
pub mod config;
pub mod leetcode_api_runner;
pub mod readme_parser;
pub mod utils;

pub use cli::{
Expand Down
14 changes: 8 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
let mut rcs = RuntimeConfigSetup::new();
rcs.status()?;
let api_runner = LeetcodeApiRunner::new(rcs).await;
let api_runner = LeetcodeApiRunner::new(&rcs).await;

match &cli.command {
Commands::Info {
Expand All @@ -24,11 +24,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
id,
language,
} => {
let language = utils::parse_programming_language(language);
println!(
"{}\nHappy Coding :)",
api_runner.start_problem(*id, language).await?
);
let default = &rcs.config.default_language.unwrap();
let lang = match language {
Some(lang) => utils::parse_programming_language(lang)?,
None => utils::parse_programming_language(default)?,
};
let start_problem = api_runner.start_problem(*id, lang).await?;
println!("{}\n\nHappy coding :)", start_problem);
},
Commands::Test {
id,
Expand Down
89 changes: 89 additions & 0 deletions src/readme_parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use regex::Regex;
use thiserror;

pub struct LeetcodeReadmeParser {
pub raw: String,
}

pub struct ProblemTestData {
pub example_count: usize,
pub inputs: Vec<String>,
pub outputs: Vec<String>,
}

#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum LeetcodeReadmeParserError {
#[error("can't parse empty readme")]
EmptyReadme,
}

impl LeetcodeReadmeParser {
pub fn new(readme: &str) -> Self {
LeetcodeReadmeParser {
raw: readme.to_string(),
}
}

pub fn parse(&self) -> Result<ProblemTestData, LeetcodeReadmeParserError> {
if self.raw.is_empty() {
return Err(LeetcodeReadmeParserError::EmptyReadme);
}
Ok(ProblemTestData {
example_count: self.count_examples(),
inputs: self.extract_inputs(),
outputs: self.extract_outputs(),
})
}

fn count_examples(&self) -> usize {
self.raw
.lines()
.filter(|line| line.starts_with("**Example"))
.count()
}

fn extract_inputs(&self) -> Vec<String> {
self.extract_from_pattern(r"(?m)^\s*\*?\*?Input:\*?\*?\s*(.*)$")
}

fn extract_outputs(&self) -> Vec<String> {
self.extract_from_pattern(r"(?m)^\s*\*?\*?Output:\*?\*?\s*(.*)$")
}

fn extract_from_pattern(&self, pattern: &str) -> Vec<String> {
let re = Regex::new(pattern).unwrap();

let mut result = Vec::new();
for capture in re.captures_iter(&self.raw) {
if let Some(matched) = capture.get(1) {
let input = matched
.as_str()
.replace(['\n', '\t'], " ")
.trim()
.to_string();

let trimmed = if input.contains('=') {
input
.split(',')
.filter_map(|part| {
if let Some(eq_pos) = part.find('=') {
Some(part[eq_pos + 1..].trim())
} else {
// Handle continuation of previous array/value
let trimmed_part = part.trim();
(!trimmed_part.is_empty())
.then_some(trimmed_part)
}
})
.collect::<Vec<_>>()
.join(",")
} else {
input.to_string()
};

result.push(trimmed);
}
}
result
}
}
52 changes: 28 additions & 24 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,31 +34,35 @@ pub fn write_to_file(

pub fn parse_programming_language(
lang: &str,
) -> leetcoderustapi::ProgrammingLanguage {
) -> Result<leetcoderustapi::ProgrammingLanguage, String> {
match lang.to_ascii_lowercase().as_str() {
"cpp" | "c++" => leetcoderustapi::ProgrammingLanguage::CPP,
"java" => leetcoderustapi::ProgrammingLanguage::Java,
"python" | "py" => leetcoderustapi::ProgrammingLanguage::Python,
"python3" | "py3" => leetcoderustapi::ProgrammingLanguage::Python3,
"c" => leetcoderustapi::ProgrammingLanguage::C,
"csharp" | "c#" => leetcoderustapi::ProgrammingLanguage::CSharp,
"javascript" | "js" => leetcoderustapi::ProgrammingLanguage::JavaScript,
"typescript" | "ts" => leetcoderustapi::ProgrammingLanguage::TypeScript,
"ruby" => leetcoderustapi::ProgrammingLanguage::Ruby,
"swift" => leetcoderustapi::ProgrammingLanguage::Swift,
"go" | "golang" => leetcoderustapi::ProgrammingLanguage::Go,
"bash" | "shell" => leetcoderustapi::ProgrammingLanguage::Bash,
"scala" => leetcoderustapi::ProgrammingLanguage::Scala,
"kotlin" | "kt" => leetcoderustapi::ProgrammingLanguage::Kotlin,
"rust" | "rs" => leetcoderustapi::ProgrammingLanguage::Rust,
"php" => leetcoderustapi::ProgrammingLanguage::PHP,
"racket" => leetcoderustapi::ProgrammingLanguage::Racket,
"erlang" => leetcoderustapi::ProgrammingLanguage::Erlang,
"elixir" => leetcoderustapi::ProgrammingLanguage::Elixir,
"dart" => leetcoderustapi::ProgrammingLanguage::Dart,
"pandas" => leetcoderustapi::ProgrammingLanguage::Pandas,
"react" => leetcoderustapi::ProgrammingLanguage::React,
_ => panic!("Unsupported language: {}", lang),
"cpp" | "c++" => Ok(leetcoderustapi::ProgrammingLanguage::CPP),
"java" => Ok(leetcoderustapi::ProgrammingLanguage::Java),
"python" | "py" => Ok(leetcoderustapi::ProgrammingLanguage::Python),
"python3" | "py3" => Ok(leetcoderustapi::ProgrammingLanguage::Python3),
"c" => Ok(leetcoderustapi::ProgrammingLanguage::C),
"csharp" | "c#" => Ok(leetcoderustapi::ProgrammingLanguage::CSharp),
"javascript" | "js" => {
Ok(leetcoderustapi::ProgrammingLanguage::JavaScript)
},
"typescript" | "ts" => {
Ok(leetcoderustapi::ProgrammingLanguage::TypeScript)
},
"ruby" => Ok(leetcoderustapi::ProgrammingLanguage::Ruby),
"swift" => Ok(leetcoderustapi::ProgrammingLanguage::Swift),
"go" | "golang" => Ok(leetcoderustapi::ProgrammingLanguage::Go),
"bash" | "shell" => Ok(leetcoderustapi::ProgrammingLanguage::Bash),
"scala" => Ok(leetcoderustapi::ProgrammingLanguage::Scala),
"kotlin" | "kt" => Ok(leetcoderustapi::ProgrammingLanguage::Kotlin),
"rust" | "rs" => Ok(leetcoderustapi::ProgrammingLanguage::Rust),
"php" => Ok(leetcoderustapi::ProgrammingLanguage::PHP),
"racket" => Ok(leetcoderustapi::ProgrammingLanguage::Racket),
"erlang" => Ok(leetcoderustapi::ProgrammingLanguage::Erlang),
"elixir" => Ok(leetcoderustapi::ProgrammingLanguage::Elixir),
"dart" => Ok(leetcoderustapi::ProgrammingLanguage::Dart),
"pandas" => Ok(leetcoderustapi::ProgrammingLanguage::Pandas),
"react" => Ok(leetcoderustapi::ProgrammingLanguage::React),
_ => Err(format!("Unsupported language: {}", lang)),
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn test_cli_start_command() {
language,
} => {
assert_eq!(id, 1);
assert_eq!(language, "rust");
assert_eq!(language.unwrap(), "rust");
},
_ => panic!("Expected Start command"),
}
Expand Down
28 changes: 28 additions & 0 deletions tests/data/1004.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Problem 1004: Max_Consecutive_Ones_III

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.

**Example 1:**

```
Input: nums = [1,1,1,0,0,0,1,1,1,1,0], k = 2
Output: 6
Explanation: [1,1,1,0,0,1,1,1,1,1,1]
Bolded numbers were flipped from 0 to 1. The longest subarray is underlined.
```

**Example 2:**

```
Input: nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], k = 3
Output: 10
Explanation: [0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
Bolded numbers were flipped from 0 to 1. The longest subarray is underlined.

```

**Constraints:**

* `1 <= nums.length <= 10<sup>5</sup>`
* `nums[i]` is either `0` or `1`.
* `0 <= k <= nums.length`
46 changes: 46 additions & 0 deletions tests/data/1768.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Problem 1768: Merge_Strings_Alternately

You are given two strings `word1` and `word2`. Merge the strings by adding letters in alternating order, starting with `word1`. If a string is longer than the other, append the additional letters onto the end of the merged string.

Return *the merged string.*

**Example 1:**

```
Input: word1 = "abc", word2 = "pqr"
Output: "apbqcr"
Explanation: The merged string will be merged as so:
word1: a b c
word2: p q r
merged: a p b q c r

```

**Example 2:**

```
Input: word1 = "ab", word2 = "pqrs"
Output: "apbqrs"
Explanation: Notice that as word2 is longer, "rs" is appended to the end.
word1: a b
word2: p q r s
merged: a p b q r s

```

**Example 3:**

```
Input: word1 = "abcd", word2 = "pq"
Output: "apbqcd"
Explanation: Notice that as word1 is longer, "cd" is appended to the end.
word1: a b c d
word2: p q
merged: a p b q c d

```

**Constraints:**

* `1 <= word1.length, word2.length <= 100`
* `word1` and `word2` consist of lowercase English letters.
38 changes: 38 additions & 0 deletions tests/data/221.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Problem 221: Maximal_Square

Given an `m x n` binary `matrix` filled with `0`'s and `1`'s, *find the largest square containing only* `1`'s *and return its area*.

**Example 1:**

![](https://assets.leetcode.com/uploads/2020/11/26/max1grid.jpg)

```
Input: matrix = [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]
Output: 4

```

**Example 2:**

![](https://assets.leetcode.com/uploads/2020/11/26/max2grid.jpg)

```
Input: matrix = [["0","1"],["1","0"]]
Output: 1

```

**Example 3:**

```
Input: matrix = [["0"]]
Output: 0

```

**Constraints:**

* `m == matrix.length`
* `n == matrix[i].length`
* `1 <= m, n <= 300`
* `matrix[i][j]` is `'0'` or `'1'`.
Loading