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