leetcode_cli/cmds/
edit.rs1use super::Command;
3use crate::{Error, Result};
4use anyhow::anyhow;
5use async_trait::async_trait;
6use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command as ClapCommand};
7use std::collections::HashMap;
8
9pub struct EditCommand;
26
27#[async_trait]
28impl Command for EditCommand {
29 fn usage() -> ClapCommand {
31 ClapCommand::new("edit")
32 .about("Edit question")
33 .visible_alias("e")
34 .arg(
35 Arg::new("lang")
36 .short('l')
37 .long("lang")
38 .num_args(1)
39 .help("Edit with specific language"),
40 )
41 .arg(
42 Arg::new("id")
43 .num_args(1)
44 .value_parser(clap::value_parser!(i32))
45 .help("question id"),
46 )
47 .arg(
48 Arg::new("daily")
49 .short('d')
50 .long("daily")
51 .help("Edit today's daily challenge")
52 .action(ArgAction::SetTrue),
53 )
54 .group(
55 ArgGroup::new("question-id")
56 .args(["id", "daily"])
57 .multiple(false)
58 .required(true),
59 )
60 }
61
62 async fn handler(m: &ArgMatches) -> Result<()> {
64 use crate::{cache::models::Question, Cache};
65 use std::fs::File;
66 use std::io::Write;
67 use std::path::Path;
68
69 let cache = Cache::new()?;
70
71 let daily = m.get_one::<bool>("daily").unwrap_or(&false);
72 let daily_id = if *daily {
73 Some(cache.get_daily_problem_id().await?)
74 } else {
75 None
76 };
77
78 let id = m
79 .get_one::<i32>("id")
80 .copied()
81 .or(daily_id)
82 .ok_or(Error::NoneError)?;
83
84 let problem = cache.get_problem(id)?;
85 let mut conf = cache.to_owned().0.conf;
86
87 let test_flag = conf.code.test;
88
89 let p_desc_comment = problem.desc_comment(&conf);
90 if m.contains_id("lang") {
92 conf.code.lang = m
93 .get_one::<String>("lang")
94 .ok_or(Error::NoneError)?
95 .to_string();
96 conf.sync()?;
97 }
98
99 let lang = &conf.code.lang;
100 let path = crate::helper::code_path(&problem, Some(lang.to_owned()))?;
101
102 if !Path::new(&path).exists() {
103 let mut qr = serde_json::from_str(&problem.desc);
104 if qr.is_err() {
105 qr = Ok(cache.get_question(id).await?);
106 }
107
108 let question: Question = qr?;
109
110 let mut file_code = File::create(&path)?;
111 let question_desc = question.desc_comment(&conf) + "\n";
112
113 let test_path = crate::helper::test_cases_path(&problem)?;
114
115 let mut flag = false;
116 for d in question.defs.0 {
117 if d.value == *lang {
118 flag = true;
119 if conf.code.comment_problem_desc {
120 file_code.write_all(p_desc_comment.as_bytes())?;
121 file_code.write_all(question_desc.as_bytes())?;
122 }
123 if let Some(inject_before) = &conf.code.inject_before {
124 for line in inject_before {
125 file_code.write_all((line.to_string() + "\n").as_bytes())?;
126 }
127 }
128 if conf.code.edit_code_marker {
129 file_code.write_all(
130 (conf.code.comment_leading.clone()
131 + " "
132 + &conf.code.start_marker
133 + "\n")
134 .as_bytes(),
135 )?;
136 }
137 file_code.write_all((d.code.to_string() + "\n").as_bytes())?;
138 if conf.code.edit_code_marker {
139 file_code.write_all(
140 (conf.code.comment_leading.clone()
141 + " "
142 + &conf.code.end_marker
143 + "\n")
144 .as_bytes(),
145 )?;
146 }
147 if let Some(inject_after) = &conf.code.inject_after {
148 for line in inject_after {
149 file_code.write_all((line.to_string() + "\n").as_bytes())?;
150 }
151 }
152
153 if test_flag {
154 let mut file_tests = File::create(&test_path)?;
155 file_tests.write_all(question.all_cases.as_bytes())?;
156 }
157 }
158 }
159
160 if !flag {
162 std::fs::remove_file(&path)?;
163
164 if test_flag {
165 std::fs::remove_file(&test_path)?;
166 }
167
168 return Err(
169 anyhow!("This question doesn't support {lang}, please try another").into(),
170 );
171 }
172 }
173
174 let mut args: Vec<String> = Default::default();
188 if let Some(editor_args) = conf.code.editor_args {
189 args.extend_from_slice(&editor_args);
190 }
191
192 let mut envs: HashMap<String, String> = Default::default();
206 if let Some(editor_envs) = &conf.code.editor_envs {
207 for env in editor_envs.iter() {
208 let parts: Vec<&str> = env.split('=').collect();
209 if parts.len() == 2 {
210 let name = parts[0].trim();
211 let value = parts[1].trim();
212 envs.insert(name.to_string(), value.to_string());
213 } else {
214 return Err(anyhow!("Invalid editor environment variable: {env}").into());
215 }
216 }
217 }
218
219 args.push(path);
220 std::process::Command::new(conf.code.editor)
221 .envs(envs)
222 .args(args)
223 .status()?;
224 Ok(())
225 }
226}