leetcode_cli/cmd/
list.rs

1//! list subcommand - List leetcode problems
2use crate::{cache::Cache, err::Error, helper::Digit};
3use clap::Args;
4
5static CATEGORY_HELP: &str = r#"Filter problems by category name
6[algorithms, database, shell, concurrency]
7"#;
8
9static QUERY_HELP: &str = r#"Filter questions by conditions:
10Uppercase means negative
11e = easy     E = m+h
12m = medium   M = e+h
13h = hard     H = e+m
14d = done     D = not done
15l = locked   L = not locked
16s = starred  S = not starred"#;
17
18static LIST_AFTER_HELP: &str = r#"EXAMPLES:
19    leetcode list                   List all questions
20    leetcode list array             List questions that has "array" in name, and this is letter non-sensitive
21    leetcode list -c database       List questions that in database category
22    leetcode list -q eD             List questions that with easy level and not done
23    leetcode list -t linked-list    List questions that under tag "linked-list"
24    leetcode list -r 50 100         List questions that has id in between 50 and 100
25"#;
26
27/// List command arguments
28#[derive(Args)]
29#[command(after_help = LIST_AFTER_HELP)]
30pub struct ListArgs {
31    /// Keyword in select query
32    pub keyword: Option<String>,
33
34    /// Filter problems by category name
35    #[arg(short, long, help = CATEGORY_HELP)]
36    pub category: Option<String>,
37
38    /// Invoking python scripts to filter questions
39    #[arg(short, long)]
40    pub plan: Option<String>,
41
42    /// Filter questions by conditions
43    #[arg(short, long, help = QUERY_HELP)]
44    pub query: Option<String>,
45
46    /// Filter questions by id range
47    #[arg(short, long, num_args = 2.., value_parser = clap::value_parser!(i32))]
48    pub range: Vec<i32>,
49
50    /// Show statistics of listed problems
51    #[arg(short, long)]
52    pub stat: bool,
53
54    /// Filter questions by tag
55    #[arg(short, long)]
56    pub tag: Option<String>,
57}
58
59impl ListArgs {
60    /// `list` command handler
61    pub async fn run(&self) -> Result<(), Error> {
62        trace!("Input list command...");
63
64        let cache = Cache::new()?;
65        let mut ps = cache.get_problems()?;
66
67        // if cache doesn't exist, request a new copy
68        if ps.is_empty() {
69            cache.download_problems().await?;
70            return Box::pin(self.run()).await;
71        }
72
73        // filtering...
74        // pym scripts
75        #[cfg(feature = "pym")]
76        {
77            if let Some(ref plan) = self.plan {
78                let ids = crate::pym::exec(plan)?;
79                crate::helper::squash(&mut ps, ids)?;
80            }
81        }
82
83        // filter tag
84        if let Some(ref tag) = self.tag {
85            let ids = cache.get_tagged_questions(tag).await?;
86            crate::helper::squash(&mut ps, ids)?;
87        }
88
89        // filter category
90        if let Some(ref category) = self.category {
91            ps.retain(|x| x.category == *category);
92        }
93
94        // filter query
95        if let Some(ref query) = self.query {
96            crate::helper::filter(&mut ps, query.to_string());
97        }
98
99        // filter range
100        if self.range.len() >= 2 {
101            ps.retain(|x| self.range[0] <= x.fid && x.fid <= self.range[1]);
102        }
103
104        // retain if keyword exists
105        if let Some(ref keyword) = self.keyword {
106            let lowercase_kw = keyword.to_lowercase();
107            ps.retain(|x| x.name.to_lowercase().contains(&lowercase_kw));
108        }
109
110        // output problem lines sorted by [problem number] like
111        // [ 1 ] Two Sum
112        // [ 2 ] Add Two Numbers
113        ps.sort_unstable_by_key(|p| p.fid);
114
115        let out: Vec<String> = ps.iter().map(ToString::to_string).collect();
116        println!("{}", out.join("\n"));
117
118        // one more thing, filter stat
119        if self.stat {
120            let mut listed = 0;
121            let mut locked = 0;
122            let mut starred = 0;
123            let mut ac = 0;
124            let mut notac = 0;
125            let mut easy = 0;
126            let mut medium = 0;
127            let mut hard = 0;
128
129            for p in ps {
130                listed += 1;
131                if p.starred {
132                    starred += 1;
133                }
134                if p.locked {
135                    locked += 1;
136                }
137
138                match p.status.as_str() {
139                    "ac" => ac += 1,
140                    "notac" => notac += 1,
141                    _ => {}
142                }
143
144                match p.level {
145                    1 => easy += 1,
146                    2 => medium += 1,
147                    3 => hard += 1,
148                    _ => {}
149                }
150            }
151
152            let remain = listed - ac - notac;
153            println!(
154                "
155        Listed: {}     Locked: {}     Starred: {}
156        Accept: {}     Not-Ac: {}     Remain:  {}
157        Easy  : {}     Medium: {}     Hard:    {}",
158                listed.digit(4),
159                locked.digit(4),
160                starred.digit(4),
161                ac.digit(4),
162                notac.digit(4),
163                remain.digit(4),
164                easy.digit(4),
165                medium.digit(4),
166                hard.digit(4),
167            );
168        }
169        Ok(())
170    }
171}