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 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 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 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 else {
52 items.retain(|i| i.has_tag(t));
53 }
54 }
55
56 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 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 if input.len() == 4 {
83 return match input.parse::<u16>() {
84 Ok(y) => Some((y, y)),
85 Err(_) => None
86 }
87 }
88
89 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 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 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 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}