use super::{
exec_time_str, extract_help, ident_string, style, style_name_w,
table_lib::{Cell, Table},
time_fmt,
tree_lib::{self, LeadingDisplay, TreeFormatter},
DisplayStyle, ListOptions, SHORT_LATEST_TXT,
};
use crate::error::Result;
use crate::script::ScriptInfo;
use crate::util::writable::{FmtWrite, IoWrite};
use crate::util::{get_display_type, DisplayType};
use fxhash::FxHashMap as HashMap;
use std::borrow::Cow;
use std::fmt::Write as FmtWriteTrait;
use std::io::Write;
struct ShortFormatter<W: Write> {
w: W,
plain: bool,
format: String,
latest_script_id: i64,
}
struct LongFormatter<'a> {
table: &'a mut Table,
plain: bool,
latest_script_id: i64,
}
struct TrimmedScriptInfo<'b>(Cow<'b, str>, &'b ScriptInfo);
fn ident_string_tree(format: &str, ty: &DisplayType, t: &TrimmedScriptInfo<'_>) -> Result<String> {
let TrimmedScriptInfo(name, script) = t;
ident_string(format, &*name, ty, script)
}
impl<'b> tree_lib::TreeValue<'b> for TrimmedScriptInfo<'b> {
type CmpKey = u64;
fn cmp_key(&self) -> u64 {
let s = &self.1;
if s.exec_time.is_none() {
0
} else {
s.exec_count
}
}
fn display_key(&self) -> Cow<'b, str> {
match &self.0 {
Cow::Borrowed(s) => Cow::Borrowed(s),
Cow::Owned(_) => self.1.name.key(),
}
}
}
impl<'b, W: Write> TreeFormatter<'b, TrimmedScriptInfo<'b>, u64> for ShortFormatter<W> {
fn fmt_leaf(&mut self, l: LeadingDisplay, t: &TrimmedScriptInfo<'b>) -> Result {
let TrimmedScriptInfo(_, script) = t;
let ty = get_display_type(&script.ty);
let ident = ident_string_tree(&self.format, &ty, t)?;
let l = style(self.plain, l, |s| s.dimmed().done());
write!(self.w, "{}", l)?;
style_name_w(
IoWrite(&mut self.w),
self.plain,
self.latest_script_id == script.id,
SHORT_LATEST_TXT,
ty.color(),
&ident,
)?;
writeln!(self.w, "")?;
Ok(())
}
fn fmt_nonleaf(&mut self, l: LeadingDisplay, t: &str) -> Result {
let ident = style(self.plain, t, |s| s.dimmed().italic().done());
let l = style(self.plain, l, |s| s.dimmed().done());
writeln!(self.w, "{}{}", l, ident)?;
Ok(())
}
}
impl<'b> TreeFormatter<'b, TrimmedScriptInfo<'b>, u64> for LongFormatter<'b> {
fn fmt_leaf(&mut self, l: LeadingDisplay, t: &TrimmedScriptInfo<'b>) -> Result {
let TrimmedScriptInfo(name, script) = t;
let ty = get_display_type(&script.ty);
let color = ty.color();
let mut ident_width = l.width();
let mut ident_txt = style(self.plain, l, |s| s.dimmed().done()).to_string();
{
let name_width = style_name_w(
FmtWrite(&mut ident_txt),
self.plain,
self.latest_script_id == script.id,
SHORT_LATEST_TXT,
ty.color(),
&name,
)?;
ident_width += name_width;
}
let ty = ty.display();
let ty_width = ty.len();
let ty_txt = style(self.plain, ty, |s| s.color(color).bold().done());
let help_msg = extract_help(script);
let row = vec![
Cell::new_with_len(ident_txt, ident_width),
Cell::new_with_len(ty_txt.to_string(), ty_width),
Cell::new(time_fmt::fmt(&script.write_time).to_string()),
Cell::new(exec_time_str(script).to_string()),
Cell::new(help_msg),
];
self.table.add_row(row);
Ok(())
}
fn fmt_nonleaf(&mut self, l: LeadingDisplay, name: &str) -> Result {
let mut ident_width = l.width();
let mut ident_txt = style(self.plain, l, |s| s.dimmed().done()).to_string();
ident_width += name.len();
let name = style(self.plain, name, |s| s.dimmed().italic().done());
write!(&mut ident_txt, "{}", name)?;
let row = vec![Cell::new_with_len(ident_txt, ident_width)];
self.table.add_row(row);
Ok(())
}
}
type TreeNode<'b> = tree_lib::TreeNode<'b, TrimmedScriptInfo<'b>, u64>;
fn split_script_name(full_name: &str) -> (&'_ str, impl Iterator<Item = &'_ str>) {
let (name, path) = if let Some((prefix, name)) = full_name.rsplit_once('/') {
(name, Some(prefix.split('/')))
} else {
(full_name, None)
};
let path = path.into_iter().flatten();
(name, path)
}
fn build_forest(scripts: Vec<&ScriptInfo>) -> TreeNode<'_> {
let mut m = HashMap::default();
for script in scripts.into_iter() {
let name = script.name.key();
let name_key = match name {
Cow::Borrowed(s) => s,
_ => {
m.insert(
(false, name.clone()),
TreeNode::new_leaf(TrimmedScriptInfo(name, script)),
);
continue;
}
};
let (name, path) = split_script_name(name_key);
let name = Cow::Borrowed(name);
let leaf = TreeNode::new_leaf(TrimmedScriptInfo(name, script));
TreeNode::insert_to_map(&mut m, path, leaf);
}
TreeNode::new_nonleaf(".", m)
}
pub fn fmt<W: Write>(
scripts: Vec<&ScriptInfo>,
latest_script_id: i64,
opt: &mut ListOptions<Table, &mut W>,
) -> Result<()> {
let mut root = build_forest(scripts);
match &mut opt.display_style {
DisplayStyle::Long(table) => {
let mut fmter = LongFormatter {
plain: opt.plain,
latest_script_id,
table,
};
fmter.fmt(&mut root)?;
}
DisplayStyle::Short(format, w) => {
let mut fmter = ShortFormatter {
w,
plain: opt.plain,
format: format.clone(),
latest_script_id,
};
fmter.fmt(&mut root)?;
}
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use crate::{my_env_logger, script::IntoScriptName};
use chrono::NaiveDateTime;
fn build(v: Vec<(&'static str, &'static str)>) -> Vec<ScriptInfo> {
v.into_iter()
.enumerate()
.map(|(idx, (name, ty))| {
let idx = idx as i64;
let time = NaiveDateTime::from_timestamp_opt(idx, 0).unwrap();
let mut builder = ScriptInfo::builder(
idx,
0,
name.to_owned().into_script_name().unwrap(),
ty.into(),
vec![].into_iter(),
);
builder.created_time(time);
builder.exec_time(time);
builder.exec_count(idx as u64);
builder.build()
})
.collect()
}
#[test]
fn test_fmt_tree_short() {
let _ = my_env_logger::try_init();
let scripts = build(vec![
("bbb/ccc/ggg/rrr", "tmux"),
("bbb/ccc/ddd", "tmux"),
("bbb/ccc/ggg/fff", "tmux"),
("aaa", "sh"),
("bbb/ccc/ddd/eee", "tmux"),
(".2", "txt"),
("bbb/ccc/yyy", "js"),
("bbb/ccc/ddd/www", "rb"),
("bbb/ccc/ggg/xxx", "tmux"),
("bbb/ddd", "tmux"),
("aaa/bbb", "rb"),
]);
let mut root = build_forest(scripts.iter().collect());
let mut fmter = ShortFormatter {
w: Vec::<u8>::new(),
plain: true,
format: "{{name}}({{ty}})".to_owned(),
latest_script_id: 1,
};
let ans = "
.
├── aaa(sh)
├── .2(txt)
├── bbb
│ ├── ddd(tmux)
│ └── ccc
│ ├── ddd(tmux)
│ ├── yyy(js)
│ ├── ddd
│ │ ├── eee(tmux)
│ │ └── www(rb)
│ └── ggg
│ ├── rrr(tmux)
│ ├── fff(tmux)
│ └── xxx(tmux)
└── aaa
└── bbb(rb)
"
.trim();
fmter.fmt(&mut root).unwrap();
println!("{}", std::str::from_utf8(&fmter.w).unwrap().trim());
assert_eq!(std::str::from_utf8(&fmter.w).unwrap().trim(), ans);
}
}