1use std::fmt::Write as _;
11
12use crate::api::models::DictEntry;
13use crate::api::{Dictionary, LinkType};
14use crate::render::Context;
15use crate::render::style::Palette;
16use crate::render::table::{Column, render, tally};
17
18#[must_use]
20pub fn one(kind: Dictionary, entries: &[DictEntry], ctx: &Context) -> String {
21 let paint = ctx.painter();
22 let mut out = String::with_capacity(64 + entries.len() * 48);
23
24 let _ = writeln!(out, "{}", paint.paint(kind.label(), Palette::heading()));
25 out.push_str(&rows(kind, entries, ctx));
26 let _ = writeln!(
27 out,
28 "{}",
29 paint.paint(
30 &format!("shown {} of {}", entries.len(), entries.len()),
31 Palette::label()
32 )
33 );
34 out
35}
36
37#[must_use]
42pub fn many(sections: &[(Dictionary, Vec<DictEntry>)], ctx: &Context) -> String {
43 let mut out = String::new();
44 for (index, (kind, entries)) in sections.iter().enumerate() {
45 if index > 0 {
46 out.push('\n');
47 }
48 out.push_str(&one(*kind, entries, ctx));
49 }
50 out
51}
52
53fn rows(kind: Dictionary, entries: &[DictEntry], ctx: &Context) -> String {
54 let categorised = kind == Dictionary::Statuses;
57
58 let columns: Vec<Column> = if categorised {
59 vec![
60 Column::whole("KEY", 20, Palette::key()),
61 Column::new("NAME", 32, anstyle::Style::new()),
62 Column::new("CATEGORY", 12, Palette::label()),
63 ]
64 } else {
65 vec![
66 Column::whole("KEY", 20, Palette::key()),
67 Column::new("NAME", 32, anstyle::Style::new()),
68 ]
69 };
70
71 let rows: Vec<Vec<String>> = entries
72 .iter()
73 .map(|entry| {
74 let mut row = vec![entry.key.clone(), entry.name.clone()];
75 if categorised {
76 row.push(entry.category.clone().unwrap_or_else(|| "-".to_owned()));
77 }
78 row
79 })
80 .collect();
81
82 render(&columns, &rows, ctx)
83}
84
85#[must_use]
98pub fn link_types(types: &[LinkType], ctx: &Context) -> String {
99 let columns = [
100 Column::new("WRITE", 20, Palette::key()),
101 Column::new("MEANS", 26, anstyle::Style::new()),
102 Column::whole("TYPE", 12, Palette::label()),
103 ];
104
105 let mut rows = Vec::with_capacity(types.len() * 2);
106 for kind in types {
107 for (outward, label) in [(true, &kind.outward), (false, &kind.inward)] {
108 if !outward && kind.inward == kind.outward {
111 continue;
112 }
113 rows.push(vec![
114 relationship(&kind.id, outward).unwrap_or("-").to_owned(),
115 label.clone().unwrap_or_else(|| "-".to_owned()),
116 kind.id.clone(),
117 ]);
118 }
119 }
120
121 let mut out = render(&columns, &rows, ctx);
122 out.push_str(&tally(rows.len(), Some(rows.len() as u64), None, ctx));
123 out
124}
125
126fn relationship(type_id: &str, outward: bool) -> Option<&'static str> {
134 Some(match (type_id, outward) {
135 ("relates", _) => "relates",
136 ("depends", true) => "depends on",
137 ("depends", false) => "is dependent by",
138 ("subtask", true) => "is parent task for",
139 ("subtask", false) => "is subtask for",
140 ("duplicates", true) => "is duplicated by",
141 ("duplicates", false) => "duplicates",
142 ("epic", true) => "has epic",
143 ("epic", false) => "is epic of",
144 _ => return None,
145 })
146}