leetcode_cli/cmd/
edit.rs

1//! Edit command
2use crate::{Error, Result};
3use anyhow::anyhow;
4use clap::Args;
5use std::collections::HashMap;
6
7/// Edit command arguments
8#[derive(Args)]
9#[command(group = clap::ArgGroup::new("question-id").args(&["id", "daily"]).required(true))]
10pub struct EditArgs {
11    /// Question id
12    #[arg(value_parser = clap::value_parser!(i32))]
13    pub id: Option<i32>,
14
15    /// Edit today's daily challenge
16    #[arg(short = 'd', long)]
17    pub daily: bool,
18
19    /// Edit with specific language
20    #[arg(short, long)]
21    pub lang: Option<String>,
22}
23
24impl EditArgs {
25    /// `edit` handler
26    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        // condition language
49        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 language is not found in the list of supported languges clean up files
116            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        // Get arguments of the editor
130        //
131        // for example:
132        //
133        // ```toml
134        // [code]
135        // editor = "emacsclient"
136        // editor_args = [ "-n", "-s", "doom" ]
137        // ```
138        //
139        // ```rust
140        // Command::new("emacsclient").args(&[ "-n", "-s", "doom", "<problem>" ])
141        // ```
142        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        // Set environment variables for editor
148        //
149        // for example:
150        //
151        // ```toml
152        // [code]
153        // editor = "nvim"
154        // editor_envs = [ "XDG_DATA_HOME=...", "XDG_CONFIG_HOME=...", "XDG_STATE_HOME=..." ]
155        // ```
156        //
157        // ```rust
158        // Command::new("nvim").envs(&[ ("XDG_DATA_HOME", "..."), ("XDG_CONFIG_HOME", "..."), ("XDG_STATE_HOME", "..."), ]);
159        // ```
160        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}