-
Notifications
You must be signed in to change notification settings - Fork 71
Expand file tree
/
Copy pathedit.rs
More file actions
269 lines (241 loc) · 8.7 KB
/
edit.rs
File metadata and controls
269 lines (241 loc) · 8.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
//! Edit command
use super::Command;
use crate::{Error, Result};
use anyhow::anyhow;
use async_trait::async_trait;
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command as ClapCommand};
use std::collections::HashMap;
/// Abstract `edit` command
///
/// ```sh
/// leetcode-edit
/// Edit question by id
///
/// USAGE:
/// leetcode edit <id>
///
/// FLAGS:
/// -h, --help Prints help information
/// -V, --version Prints version information
///
/// ARGS:
/// <id> question id
/// ```
pub struct EditCommand;
#[async_trait]
impl Command for EditCommand {
/// `edit` usage
fn usage() -> ClapCommand {
ClapCommand::new("edit")
.about("Edit question")
.visible_alias("e")
.arg(
Arg::new("lang")
.short('l')
.long("lang")
.num_args(1)
.help("Edit with specific language"),
)
.arg(
Arg::new("id")
.num_args(1)
.value_parser(clap::value_parser!(i32))
.help("question id"),
)
.arg(
Arg::new("daily")
.short('d')
.long("daily")
.help("Edit today's daily challenge")
.action(ArgAction::SetTrue),
)
.group(
ArgGroup::new("question-id")
.args(["id", "daily"])
.multiple(false)
.required(true),
)
}
/// `edit` handler
async fn handler(m: &ArgMatches) -> Result<()> {
use crate::{cache::models::Question, Cache};
use crate::helper::suffix;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
let cache = Cache::new()?;
let daily = m.get_one::<bool>("daily").unwrap_or(&false);
let daily_id = if *daily {
Some(cache.get_daily_problem_id().await?)
} else {
None
};
let id = m
.get_one::<i32>("id")
.copied()
.or(daily_id)
.ok_or(Error::NoneError)?;
let problem = cache.get_problem(id)?;
let mut conf = cache.to_owned().0.conf;
let test_flag = conf.code.test;
let p_desc_comment = problem.desc_comment(&conf);
// condition language
if m.contains_id("lang") {
conf.code.lang = m
.get_one::<String>("lang")
.ok_or(Error::NoneError)?
.to_string();
conf.sync()?;
}
let lang = &conf.code.lang;
let path = crate::helper::code_path(&problem, Some(lang.to_owned()))?;
if !Path::new(&path).exists() {
let mut qr = serde_json::from_str(&problem.desc);
if qr.is_err() {
qr = Ok(cache.get_question(id).await?);
}
let question: Question = qr?;
if *lang == "rust" && conf.code.enable_rust_crates {
let flat_suffix = suffix(&lang).map_err(anyhow::Error::msg)?; // Since suffix returns Result<&str>
let pick_replaced = conf.code.pick.replace("${fid}", &problem.fid.to_string()).replace("${slug}", &problem.slug.to_string());
let flat_path_str = format!("{}/{}.{}", conf.storage.code()?, pick_replaced, flat_suffix);
if Path::new(&flat_path_str).exists() {
println!("Note: Existing flat file at {}. Consider migrating content to new subdir structure.", flat_path_str);
}
let sanitized_slug = problem.slug.to_lowercase().replace(|c: char| !c.is_alphanumeric(), "_");
let code_dir_str = format!("{}/{}-{}", conf.storage.code()?, problem.fid, sanitized_slug);
let code_dir = Path::new(&code_dir_str);
fs::create_dir_all(code_dir)?;
let src_dir_str = format!("{}/src", code_dir_str);
let src_dir = Path::new(&src_dir_str);
fs::create_dir_all(src_dir)?;
let cargo_path_str = format!("{}/Cargo.toml", code_dir_str);
let cargo_path = Path::new(&cargo_path_str);
if !cargo_path.exists() {
let package_name = format!("prob-{}-{}", problem.fid, sanitized_slug);
let cargo_content = format!(
r#"[package]
name = "{}"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/lib.rs"
[dependencies]
# Uncomment and add crates as needed for LeetCode problems, e.g.:
# itertools = "0.12"
# regex = "1"
"#,
package_name
);
let mut cargo_file = File::create(&cargo_path_str)?;
cargo_file.write_all(cargo_content.as_bytes())?;
}
}
let mut file_code = File::create(&path)?;
let question_desc = question.desc_comment(&conf) + "\n";
let test_path = crate::helper::test_cases_path(&problem)?;
let mut flag = false;
for d in question.defs.0 {
if d.value == *lang {
flag = true;
if conf.code.comment_problem_desc {
file_code.write_all(p_desc_comment.as_bytes())?;
file_code.write_all(question_desc.as_bytes())?;
}
if let Some(inject_before) = &conf.code.inject_before {
for line in inject_before {
file_code.write_all((line.to_string() + "\n").as_bytes())?;
}
}
if conf.code.edit_code_marker {
file_code.write_all(
(conf.code.comment_leading.clone()
+ " "
+ &conf.code.start_marker
+ "\n")
.as_bytes(),
)?;
}
file_code.write_all((d.code.to_string() + "\n").as_bytes())?;
if conf.code.edit_code_marker {
file_code.write_all(
(conf.code.comment_leading.clone()
+ " "
+ &conf.code.end_marker
+ "\n")
.as_bytes(),
)?;
}
if let Some(inject_after) = &conf.code.inject_after {
for line in inject_after {
file_code.write_all((line.to_string() + "\n").as_bytes())?;
}
}
if test_flag {
let mut file_tests = File::create(&test_path)?;
file_tests.write_all(question.all_cases.as_bytes())?;
}
}
}
// if language is not found in the list of supported languges clean up files
if !flag {
std::fs::remove_file(&path)?;
if test_flag {
std::fs::remove_file(&test_path)?;
}
return Err(
anyhow!("This question doesn't support {lang}, please try another").into(),
);
}
}
// Get arguments of the editor
//
// for example:
//
// ```toml
// [code]
// editor = "emacsclient"
// editor_args = [ "-n", "-s", "doom" ]
// ```
//
// ```rust
// Command::new("emacsclient").args(&[ "-n", "-s", "doom", "<problem>" ])
// ```
let mut args: Vec<String> = Default::default();
if let Some(editor_args) = conf.code.editor_args {
args.extend_from_slice(&editor_args);
}
// Set environment variables for editor
//
// for example:
//
// ```toml
// [code]
// editor = "nvim"
// editor_envs = [ "XDG_DATA_HOME=...", "XDG_CONFIG_HOME=...", "XDG_STATE_HOME=..." ]
// ```
//
// ```rust
// Command::new("nvim").envs(&[ ("XDG_DATA_HOME", "..."), ("XDG_CONFIG_HOME", "..."), ("XDG_STATE_HOME", "..."), ]);
// ```
let mut envs: HashMap<String, String> = Default::default();
if let Some(editor_envs) = &conf.code.editor_envs {
for env in editor_envs.iter() {
let parts: Vec<&str> = env.split('=').collect();
if parts.len() == 2 {
let name = parts[0].trim();
let value = parts[1].trim();
envs.insert(name.to_string(), value.to_string());
} else {
return Err(anyhow!("Invalid editor environment variable: {env}").into());
}
}
}
args.push(path);
std::process::Command::new(conf.code.editor)
.envs(envs)
.args(args)
.status()?;
Ok(())
}
}