hyper_scripter/list/
list_impl.rs

1use super::{
2    exec_time_str, extract_help, get_screen_width, style, style_name,
3    table_lib::{Cell, Collumn, Table},
4    time_fmt, tree, DisplayIdentStyle, DisplayStyle, Grid, Grouping, ListOptions, LONG_LATEST_TXT,
5    SHORT_LATEST_TXT,
6};
7use crate::error::Result;
8use crate::query::{do_list_query, ListQuery};
9use crate::script::ScriptInfo;
10use crate::script_repo::{ScriptRepo, Visibility};
11use crate::tag::Tag;
12use crate::util::get_display_type;
13use fxhash::FxHashMap as HashMap;
14use std::cmp::Reverse;
15use std::hash::Hash;
16use std::io::Write;
17
18type ListOptionWithOutput = ListOptions<Table, Grid>;
19
20fn ident_string(style: &DisplayIdentStyle, ty: &str, script: &ScriptInfo) -> String {
21    match style {
22        DisplayIdentStyle::Normal => format!("{}({})", script.name, ty),
23        DisplayIdentStyle::File => script.file_path_fallback().to_string_lossy().to_string(),
24        DisplayIdentStyle::Name => script.name.to_string(),
25        DisplayIdentStyle::NameAndFile => format!(
26            "{}({})",
27            script.name.to_string(),
28            script.file_path_fallback().to_string_lossy().to_string()
29        ),
30    }
31}
32
33#[derive(PartialEq, Eq, Hash)]
34struct TagsKey(Vec<Tag>);
35impl std::fmt::Display for TagsKey {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        if self.is_empty() {
38            write!(f, "(no tag)")?;
39            return Ok(());
40        }
41        write!(f, "[")?;
42        let mut first = true;
43        for tag in &self.0 {
44            if !first {
45                write!(f, " ")?;
46            }
47            first = false;
48            write!(f, "#{}", AsRef::<str>::as_ref(tag))?;
49        }
50        write!(f, "]")?;
51        Ok(())
52    }
53}
54impl TagsKey {
55    fn new(tags: impl Iterator<Item = Tag>) -> Self {
56        let mut tags: Vec<_> = tags.collect();
57        tags.sort();
58        TagsKey(tags)
59    }
60    fn is_empty(&self) -> bool {
61        self.0.is_empty()
62    }
63}
64
65fn sort_scripts(v: &mut Vec<&ScriptInfo>) {
66    v.sort_by_key(|s| Reverse(s.last_time()));
67}
68
69fn convert_opt<T>(opt: ListOptions, t: T) -> ListOptions<Table, T> {
70    ListOptions {
71        display_style: match opt.display_style {
72            DisplayStyle::Short(style, _) => DisplayStyle::Short(style, t),
73            DisplayStyle::Long(_) => {
74                time_fmt::init();
75                let mut table = Table::new(gen_title());
76                table.set_width(get_screen_width());
77                table.set_plain(opt.plain);
78                DisplayStyle::Long(table)
79            }
80        },
81        grouping: opt.grouping,
82        plain: opt.plain,
83        limit: opt.limit,
84    }
85}
86fn extract_table<U>(opt: ListOptions<Table, U>) -> Option<Table> {
87    match opt.display_style {
88        DisplayStyle::Short(..) => None,
89        DisplayStyle::Long(table) => Some(table),
90    }
91}
92pub fn fmt_meta(
93    script: &ScriptInfo,
94    is_latest: bool,
95    opt: &mut ListOptionWithOutput,
96) -> Result<()> {
97    let ty = get_display_type(&script.ty);
98    let color = ty.color();
99    match &mut opt.display_style {
100        DisplayStyle::Long(table) => {
101            let (name_txt, name_width) = style_name(
102                opt.plain,
103                is_latest,
104                LONG_LATEST_TXT,
105                color,
106                &script.name.key(),
107            )?;
108            let ty = ty.display();
109            let ty_width = ty.len();
110            let ty_txt = style(opt.plain, ty, |s| s.color(color).bold().done());
111
112            let help_msg = extract_help(script);
113
114            let row = vec![
115                Cell::new_with_len(name_txt, name_width),
116                Cell::new_with_len(ty_txt.to_string(), ty_width),
117                Cell::new(time_fmt::fmt(&script.write_time).to_string()),
118                Cell::new(exec_time_str(script).to_string()),
119                Cell::new(help_msg),
120            ];
121            table.add_row(row);
122        }
123        DisplayStyle::Short(ident_style, grid) => {
124            let ident = ident_string(ident_style, &*ty.display(), script);
125            let (ident, width) = style_name(opt.plain, is_latest, SHORT_LATEST_TXT, color, &ident)?;
126            grid.add(ident, width);
127        }
128    }
129    Ok(())
130}
131
132enum ScriptsEither<'a, I> {
133    Iter(I),
134    V(Vec<&'a ScriptInfo>),
135}
136impl<'a, I: ExactSizeIterator<Item = &'a ScriptInfo>> ScriptsEither<'a, I> {
137    fn new(iter: I, limit: Option<usize>) -> Self {
138        if let Some(limit) = limit {
139            let mut v: Vec<_> = iter.collect();
140            sort_scripts(&mut v);
141            v.truncate(limit);
142            Self::V(v)
143        } else {
144            Self::Iter(iter)
145        }
146    }
147    fn collect(self) -> Vec<&'a ScriptInfo> {
148        match self {
149            Self::Iter(iter) => iter.collect(),
150            Self::V(v) => v,
151        }
152    }
153    fn len(&self) -> usize {
154        match self {
155            Self::Iter(iter) => iter.len(),
156            Self::V(v) => v.len(),
157        }
158    }
159    fn sorted(&self) -> bool {
160        matches!(self, Self::V(..))
161    }
162    fn for_each<F: FnMut(&'a ScriptInfo)>(self, mut f: F) {
163        match self {
164            Self::Iter(iter) => {
165                for s in iter {
166                    f(s);
167                }
168            }
169            Self::V(v) => {
170                for s in v.into_iter() {
171                    f(s);
172                }
173            }
174        }
175    }
176}
177
178fn gen_title() -> Vec<Collumn> {
179    vec![
180        Collumn::new_fixed("Script"),
181        Collumn::new_fixed("Type"),
182        Collumn::new("Write"),
183        Collumn::new("Execute"),
184        Collumn::new("Help Message"),
185    ]
186}
187
188pub async fn fmt_list<W: Write>(
189    w: &mut W,
190    script_repo: &mut ScriptRepo,
191    opt: ListOptions,
192    queries: Vec<ListQuery>,
193) -> Result<()> {
194    let latest_script_id = script_repo
195        .latest_mut(1, Visibility::Normal)
196        .map_or(-1, |s| s.id);
197
198    let scripts_iter = do_list_query(script_repo, queries)
199        .await?
200        .into_iter()
201        .map(|e| &*e.into_inner());
202    let scripts_either = ScriptsEither::new(scripts_iter, opt.limit.map(|l| l.get()));
203    let sorted = scripts_either.sorted();
204
205    let final_table: Option<Table>;
206    match opt.grouping {
207        Grouping::None => {
208            let mut opt = convert_opt(opt, Grid::new(scripts_either.len()));
209            let scripts = scripts_either.collect();
210            fmt_group(w, scripts, sorted, latest_script_id, &mut opt)?;
211            final_table = extract_table(opt);
212        }
213        Grouping::Tree => {
214            let mut opt = convert_opt(opt, &mut *w);
215            let scripts = scripts_either.collect();
216            tree::fmt(scripts, latest_script_id, &mut opt)?;
217            final_table = extract_table(opt);
218        }
219        Grouping::Tag => {
220            let mut opt = convert_opt(opt, Grid::new(scripts_either.len()));
221            let mut script_map: HashMap<TagsKey, Vec<&ScriptInfo>> = HashMap::default();
222            scripts_either.for_each(|script| {
223                let key = TagsKey::new(script.tags.iter().cloned());
224                let v = script_map.entry(key).or_default();
225                v.push(script);
226            });
227
228            let mut scripts: Vec<_> = script_map
229                .into_iter()
230                .map(|(k, v)| {
231                    // NOTE: 以群組中執行次數的最大值排序, 無標籤永遠在上
232                    let sort_key = if k.is_empty() {
233                        None
234                    } else {
235                        v.iter()
236                            .map(|s| {
237                                if s.exec_time.is_none() {
238                                    0
239                                } else {
240                                    s.exec_count
241                                }
242                            })
243                            .max()
244                    };
245                    (sort_key, k, v)
246                })
247                .collect();
248
249            scripts.sort_by_key(|(sort_key, _, _)| *sort_key);
250
251            for (_, tags, scripts) in scripts.into_iter() {
252                if !opt.grouping.is_none() {
253                    let tags_txt = style(opt.plain, tags, |s| s.dimmed().italic().done());
254                    match &mut opt.display_style {
255                        DisplayStyle::Long(table) => {
256                            table.add_row(vec![Cell::new_with_len(tags_txt.to_string(), 0)]);
257                        }
258                        DisplayStyle::Short(_, _) => {
259                            writeln!(w, "{}", tags_txt)?;
260                        }
261                    }
262                }
263                fmt_group(w, scripts, sorted, latest_script_id, &mut opt)?;
264            }
265            final_table = extract_table(opt);
266        }
267    }
268    if let Some(mut table) = final_table {
269        write!(w, "{}", table.display())?;
270        log::debug!("tree table: {:?}", table);
271    }
272    Ok(())
273}
274
275fn fmt_group<W: Write>(
276    w: &mut W,
277    mut scripts: Vec<&ScriptInfo>,
278    sorted: bool,
279    latest_script_id: i64,
280    opt: &mut ListOptionWithOutput,
281) -> Result<()> {
282    if !sorted {
283        sort_scripts(&mut scripts);
284    }
285    for script in scripts.into_iter() {
286        let is_latest = script.id == latest_script_id;
287        fmt_meta(script, is_latest, opt)?;
288    }
289    match &mut opt.display_style {
290        DisplayStyle::Short(_, grid) => {
291            let grid_display = grid.fit_into_screen();
292            write!(w, "{}", grid_display)?;
293            drop(grid_display);
294            grid.clear();
295        }
296        _ => (),
297    }
298    Ok(())
299}