1use crate::{Error, Result};
3use anyhow::anyhow;
4use clap::Args;
5use std::collections::HashMap;
6
7#[derive(Args)]
9#[command(group = clap::ArgGroup::new("question-id").args(&["id", "daily"]).required(true))]
10pub struct EditArgs {
11 #[arg(value_parser = clap::value_parser!(i32))]
13 pub id: Option<i32>,
14
15 #[arg(short = 'd', long)]
17 pub daily: bool,
18
19 #[arg(short, long)]
21 pub lang: Option<String>,
22}
23
24impl EditArgs {
25 pub async fn run(&self) -> Result<()> {
27 use crate::{Cache, cache::models::Question};
28 use std::fs::File;
29 use std::io::Write;
30 use std::path::Path;
31
32 let cache = Cache::new()?;
33
34 let daily_id = if self.daily {
35 Some(cache.get_daily_problem_id().await?)
36 } else {
37 None
38 };
39
40 let id = self.id.or(daily_id).ok_or(Error::NoneError)?;
41
42 let problem = cache.get_problem(id)?;
43 let mut conf = cache.to_owned().0.conf;
44
45 let test_flag = conf.code.test;
46
47 let p_desc_comment = problem.desc_comment(&conf);
48 if let Some(ref lang) = self.lang {
50 conf.code.lang = lang.clone();
51 conf.sync()?;
52 }
53
54 let lang = &conf.code.lang;
55 let path = crate::helper::code_path(&problem, Some(lang.to_owned()))?;
56
57 if !Path::new(&path).exists() {
58 let mut qr = serde_json::from_str(&problem.desc);
59 if qr.is_err() {
60 qr = Ok(cache.get_question(id).await?);
61 }
62
63 let question: Question = qr?;
64
65 let mut file_code = File::create(&path)?;
66 let question_desc = question.desc_comment(&conf) + "\n";
67
68 let test_path = crate::helper::test_cases_path(&problem)?;
69
70 let mut flag = false;
71 for d in question.defs.0 {
72 if d.value == *lang {
73 flag = true;
74 if conf.code.comment_problem_desc {
75 file_code.write_all(p_desc_comment.as_bytes())?;
76 file_code.write_all(question_desc.as_bytes())?;
77 }
78 if let Some(inject_before) = &conf.code.inject_before {
79 for line in inject_before {
80 file_code.write_all((line.to_string() + "\n").as_bytes())?;
81 }
82 }
83 if conf.code.edit_code_marker {
84 file_code.write_all(
85 (conf.code.comment_leading.clone()
86 + " "
87 + &conf.code.start_marker
88 + "\n")
89 .as_bytes(),
90 )?;
91 }
92 file_code.write_all((d.code.to_string() + "\n").as_bytes())?;
93 if conf.code.edit_code_marker {
94 file_code.write_all(
95 (conf.code.comment_leading.clone()
96 + " "
97 + &conf.code.end_marker
98 + "\n")
99 .as_bytes(),
100 )?;
101 }
102 if let Some(inject_after) = &conf.code.inject_after {
103 for line in inject_after {
104 file_code.write_all((line.to_string() + "\n").as_bytes())?;
105 }
106 }
107
108 if test_flag {
109 let mut file_tests = File::create(&test_path)?;
110 file_tests.write_all(question.all_cases.as_bytes())?;
111 }
112 }
113 }
114
115 if !flag {
117 std::fs::remove_file(&path)?;
118
119 if test_flag {
120 std::fs::remove_file(&test_path)?;
121 }
122
123 return Err(
124 anyhow!("This question doesn't support {lang}, please try another").into(),
125 );
126 }
127 }
128
129 let mut args: Vec<String> = Default::default();
143 if let Some(editor_args) = conf.code.editor_args {
144 args.extend_from_slice(&editor_args);
145 }
146
147 let mut envs: HashMap<String, String> = Default::default();
161 if let Some(editor_envs) = &conf.code.editor_envs {
162 for env in editor_envs.iter() {
163 let parts: Vec<&str> = env.split('=').collect();
164 if parts.len() == 2 {
165 let name = parts[0].trim();
166 let value = parts[1].trim();
167 envs.insert(name.to_string(), value.to_string());
168 } else {
169 return Err(anyhow!("Invalid editor environment variable: {env}").into());
170 }
171 }
172 }
173
174 args.push(path);
175 std::process::Command::new(conf.code.editor)
176 .envs(envs)
177 .args(args)
178 .status()?;
179 Ok(())
180 }
181}