mtracker/
list.rs

1use anyhow::Result;
2use clap::{ArgMatches, Command};
3
4use crate::arg_util;
5use crate::args;
6use crate::media;
7
8pub fn command() -> Command {
9    Command::new("ls")
10        .visible_aliases(["list"])
11        .about("List items")
12        .arg_required_else_help(false)
13        .arg(args::term().help("Terms to search for (tag, year)"))
14        .arg(args::note_bool().help("Whether to display notes"))
15        .arg(args::tags_bool().help("Whether to display tags"))
16}
17
18pub fn handle(matches: &ArgMatches) -> Result<()> {
19    let repo = arg_util::repo_from_matches(matches)?;
20    let mut items = repo.get_all();
21
22    let options = media::format::ListOptions {
23        note: *matches.get_one::<bool>("NOTE").unwrap_or(&false),
24        tags: *matches.get_one::<bool>("TAGS").unwrap_or(&false),
25
26        // Get max rating BEFORE filtering
27        max_rating: items
28            .iter()
29            .map(|m| m.rating.unwrap_or(0))
30            .max()
31            .unwrap_or(0),
32    };
33
34    for t in arg_util::terms_from_matches(matches) {
35        // Filter by year
36        if let Some(range) = try_parse_year_range(t) {
37            items.retain(|i| match i.year {
38                Some(y) => y >= range.0 && y <= range.1,
39                None => false,
40            });
41        }
42
43        // Filter by special terms
44        else if t == "rated" {
45            items.retain(|i| i.rating.is_some());
46        } else if t == "unrated" {
47            items.retain(|i| i.rating.is_none());
48        }
49
50        // Filter by tags
51        else {
52            items.retain(|i| i.has_tag(t));
53        }
54    }
55
56    // Sort (watchlist, rating, unrated, alphabetic)
57    items.sort_by(|a, b| {
58        let a_weight = get_weight(a);
59        let b_weight = get_weight(b);
60
61        if a_weight == b_weight {
62            a.name.to_lowercase().cmp(&b.name.to_lowercase())
63        } else {
64            b_weight.cmp(&a_weight)
65        }
66    });
67
68    // Print
69    for item in &items {
70        println!("{}", item.as_line(&options));
71    }
72
73    Ok(())
74}
75
76fn get_weight(item: &media::Media) -> usize {
77    item.rating.unwrap_or(0) as usize + 1 + if item.has_tag("watchlist") { 1000 } else { 0 }
78}
79
80pub fn try_parse_year_range(input: &str) -> Option<(u16, u16)> {
81    // 2024
82    if input.len() == 4 {
83        return match input.parse::<u16>() {
84            Ok(y) => Some((y, y)),
85            Err(_) => None
86        }
87    }
88
89    // -2024, 2024-
90    if input.len() == 5 {
91        if &input[..1] == "-" {
92            return match input[1..].parse::<u16>() {
93                Ok(y) => Some((0, y)),
94                Err(_) => None
95            }
96        } else if &input[4..] == "-" {
97            return match input[..4].parse::<u16>() {
98                Ok(y) => Some((y, 9999)),
99                Err(_) => None
100            }
101        }
102    }
103
104    // 2023-2024
105    if input.len() == 9 && &input[4..5] == "-" {
106        return match input[..4].parse::<u16>() {
107            Ok(from) => match input[5..].parse::<u16>() {
108                Ok(to) => if from <= to {Some((from, to))} else {None},
109                Err(_) => None
110            }
111            Err(_) => None
112        }
113    }
114
115    None
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn try_parse_year_range_works() {
124        // Valid input
125        assert_eq!(try_parse_year_range("2023").unwrap(), (2023, 2023));
126        assert_eq!(try_parse_year_range("2024").unwrap(), (2024, 2024));
127        assert_eq!(try_parse_year_range("2020-").unwrap(), (2020, 9999));
128        assert_eq!(try_parse_year_range("-2020").unwrap(), (0, 2020));
129        assert_eq!(try_parse_year_range("1999-2010").unwrap(), (1999,2010));
130
131        // Invalid input
132        assert!(try_parse_year_range("foob").is_none());
133        assert!(try_parse_year_range("-foob").is_none());
134        assert!(try_parse_year_range("#2024").is_none());
135        assert!(try_parse_year_range("20244").is_none());
136        assert!(try_parse_year_range("2020-2010").is_none());
137    }
138}