leetcode_cli/cmds/
list.rs1use super::Command;
37use crate::{cache::Cache, err::Error, helper::Digit};
38use async_trait::async_trait;
39use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand};
40pub struct ListCommand;
48
49static CATEGORY_HELP: &str = r#"Filter problems by category name
50[algorithms, database, shell, concurrency]
51"#;
52
53static QUERY_HELP: &str = r#"Filter questions by conditions:
54Uppercase means negative
55e = easy E = m+h
56m = medium M = e+h
57h = hard H = e+m
58d = done D = not done
59l = locked L = not locked
60s = starred S = not starred"#;
61
62static LIST_AFTER_HELP: &str = r#"EXAMPLES:
63 leetcode list List all questions
64 leetcode list array List questions that has "array" in name, and this is letter non-sensitive
65 leetcode list -c database List questions that in database category
66 leetcode list -q eD List questions that with easy level and not done
67 leetcode list -t linked-list List questions that under tag "linked-list"
68 leetcode list -r 50 100 List questions that has id in between 50 and 100
69"#;
70
71#[async_trait]
73impl Command for ListCommand {
74 fn usage() -> ClapCommand {
76 ClapCommand::new("list")
77 .about("List problems")
78 .visible_alias("l")
79 .arg(
80 Arg::new("category")
81 .short('c')
82 .long("category")
83 .num_args(1)
84 .help(CATEGORY_HELP),
85 )
86 .arg(
87 Arg::new("plan")
88 .short('p')
89 .long("plan")
90 .num_args(1)
91 .help("Invoking python scripts to filter questions"),
92 )
93 .arg(
94 Arg::new("query")
95 .short('q')
96 .long("query")
97 .num_args(1)
98 .help(QUERY_HELP),
99 )
100 .arg(
101 Arg::new("range")
102 .short('r')
103 .long("range")
104 .num_args(2..)
105 .value_parser(clap::value_parser!(i32))
106 .help("Filter questions by id range"),
107 )
108 .after_help(LIST_AFTER_HELP)
109 .arg(
110 Arg::new("stat")
111 .short('s')
112 .long("stat")
113 .help("Show statistics of listed problems")
114 .action(ArgAction::SetTrue),
115 )
116 .arg(
117 Arg::new("tag")
118 .short('t')
119 .long("tag")
120 .num_args(1)
121 .help("Filter questions by tag"),
122 )
123 .arg(
124 Arg::new("keyword")
125 .num_args(1)
126 .help("Keyword in select query"),
127 )
128 }
129
130 async fn handler(m: &ArgMatches) -> Result<(), Error> {
136 trace!("Input list command...");
137
138 let cache = Cache::new()?;
139 let mut ps = cache.get_problems()?;
140
141 if ps.is_empty() {
143 cache.download_problems().await?;
144 return Self::handler(m).await;
145 }
146
147 #[cfg(feature = "pym")]
150 {
151 if m.contains_id("plan") {
152 let ids = crate::pym::exec(m.get_one::<String>("plan").unwrap_or(&"".to_string()))?;
153 crate::helper::squash(&mut ps, ids)?;
154 }
155 }
156
157 if m.contains_id("tag") {
159 let ids = cache
160 .get_tagged_questions(m.get_one::<String>("tag").unwrap_or(&"".to_string()))
161 .await?;
162 crate::helper::squash(&mut ps, ids)?;
163 }
164
165 if m.contains_id("category") {
167 ps.retain(|x| {
168 x.category
169 == *m
170 .get_one::<String>("category")
171 .unwrap_or(&"algorithms".to_string())
172 });
173 }
174
175 if m.contains_id("query") {
177 let query = m.get_one::<String>("query").ok_or(Error::NoneError)?;
178 crate::helper::filter(&mut ps, query.to_string());
179 }
180
181 if m.contains_id("range") {
183 let num_range: Vec<i32> = m
184 .get_many::<i32>("range")
185 .ok_or(Error::NoneError)?
186 .copied()
187 .collect();
188 ps.retain(|x| num_range[0] <= x.fid && x.fid <= num_range[1]);
189 }
190
191 if let Some(keyword) = m.get_one::<String>("keyword") {
193 let lowercase_kw = keyword.to_lowercase();
194 ps.retain(|x| x.name.to_lowercase().contains(&lowercase_kw));
195 }
196
197 ps.sort_unstable_by_key(|p| p.fid);
201
202 let out: Vec<String> = ps.iter().map(ToString::to_string).collect();
203 println!("{}", out.join("\n"));
204
205 if m.contains_id("stat") {
207 let mut listed = 0;
208 let mut locked = 0;
209 let mut starred = 0;
210 let mut ac = 0;
211 let mut notac = 0;
212 let mut easy = 0;
213 let mut medium = 0;
214 let mut hard = 0;
215
216 for p in ps {
217 listed += 1;
218 if p.starred {
219 starred += 1;
220 }
221 if p.locked {
222 locked += 1;
223 }
224
225 match p.status.as_str() {
226 "ac" => ac += 1,
227 "notac" => notac += 1,
228 _ => {}
229 }
230
231 match p.level {
232 1 => easy += 1,
233 2 => medium += 1,
234 3 => hard += 1,
235 _ => {}
236 }
237 }
238
239 let remain = listed - ac - notac;
240 println!(
241 "
242 Listed: {} Locked: {} Starred: {}
243 Accept: {} Not-Ac: {} Remain: {}
244 Easy : {} Medium: {} Hard: {}",
245 listed.digit(4),
246 locked.digit(4),
247 starred.digit(4),
248 ac.digit(4),
249 notac.digit(4),
250 remain.digit(4),
251 easy.digit(4),
252 medium.digit(4),
253 hard.digit(4),
254 );
255 }
256 Ok(())
257 }
258}