leetcode_cli/cmds/
list.rs

1//! list subcomAmand - List leetcode problems
2//!
3//! ```sh
4//! leetcode-list
5//! List problems
6//!
7//! USAGE:
8//!     leetcode list [FLAGS] [OPTIONS] [keyword]
9//!
10//! FLAGS:
11//!     -h, --help       Prints help information
12//!     -s, --stat       Show statistics of listed problems
13//!     -V, --version    Prints version information
14//!
15//! OPTIONS:
16//!     -c, --category <category>    Filter problems by category name
17//!                                  [algorithms, database, shell, concurrency]
18//!     -q, --query <query>          Filter questions by conditions:
19//!                                  Uppercase means negative
20//!                                  e = easy     E = m+h
21//!                                  m = medium   M = e+h
22//!                                  h = hard     H = e+m
23//!                                  d = done     D = not done
24//!                                  l = locked   L = not locked
25//!                                  s = starred  S = not starred
26//!
27//! ARGS:
28//!     <keyword>    Keyword in select query
29//!
30//! EXAMPLES:
31//!     leetcode list               List all questions
32//!     leetcode list array         List questions that has "array" in name
33//!     leetcode list -c database   List questions that in database category
34//!     leetcode list -q eD         List questions that with easy level and not done
35//! ```
36use super::Command;
37use crate::{cache::Cache, err::Error, helper::Digit};
38use async_trait::async_trait;
39use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand};
40/// Abstract `list` command
41///
42/// ## handler
43/// + try to request cache
44///   + prints the list
45/// + if cache doesn't exist, download problems list
46/// + ...
47pub 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/// implement Command trait for `list`
72#[async_trait]
73impl Command for ListCommand {
74    /// `list` command usage
75    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    /// `list` command handler
131    ///
132    /// List commands contains "-c", "-q", "-s" flags.
133    /// + matches with `-c` will override the default <all> keyword.
134    /// + `-qs`
135    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 cache doesn't exist, request a new copy
142        if ps.is_empty() {
143            cache.download_problems().await?;
144            return Self::handler(m).await;
145        }
146
147        // filtering...
148        // pym scripts
149        #[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        // filter tag
158        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        // filter category
166        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        // filter query
176        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        // filter range
182        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        // retain if keyword exists
192        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        // output problem lines sorted by [problem number] like
198        // [ 1 ] Two Sum
199        // [ 2 ] Add Two Numbers
200        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        // one more thing, filter stat
206        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}