hyper-scripter 0.5.12

The script managing tool for script lovers
Documentation
use super::{
    exec_time_str, extract_help, style,
    tree_lib::{self, TreeFormatter},
    DisplayIdentStyle, DisplayStyle, ListOptions,
};
use crate::error::Result;
use crate::script::ScriptInfo;
use crate::util::get_display_type;
use colored::{Color, Colorize};
use fxhash::FxHashMap as HashMap;
use prettytable::{cell, format, row, Row, Table};
use std::borrow::Cow;
use std::cmp::Ordering;
use std::io::Write;

const TITLE: &[&str] = &["type", "write", "execute", "help message"];

struct ShortFormatter {
    plain: bool,
    ident_style: DisplayIdentStyle,
    latest_script_id: i64,
}
struct LongFormatter {
    table: Table,
    plain: bool,
    latest_script_id: i64,
}
struct TrimmedScriptInfo<'b>(Cow<'b, str>, &'b ScriptInfo);

fn ident_string(style: DisplayIdentStyle, ty: &str, t: &TrimmedScriptInfo<'_>) -> String {
    let TrimmedScriptInfo(name, script) = t;
    match style {
        DisplayIdentStyle::Normal => format!("{}({})", name, ty),
        DisplayIdentStyle::File => script.file_path_fallback().to_string_lossy().to_string(),
        DisplayIdentStyle::Name => name.to_string(),
        DisplayIdentStyle::NameAndFile => format!(
            "{}({})",
            name.to_string(),
            script.file_path_fallback().to_string_lossy().to_string()
        ),
    }
}

impl<'b> tree_lib::TreeValue<'b> for TrimmedScriptInfo<'b> {
    fn tree_cmp(&self, other: &Self) -> Ordering {
        other.1.last_time().cmp(&self.1.last_time())
    }
    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>, W> for ShortFormatter {
    fn fmt_leaf(&mut self, f: &mut W, t: &TrimmedScriptInfo<'b>) -> Result {
        let TrimmedScriptInfo(_, script) = t;
        let ty = get_display_type(&script.ty);
        let ident = ident_string(self.ident_style, &*ty.display(), t);
        let ident = style(self.plain, ident, |s| s.color(ty.color()).bold());
        if self.latest_script_id == script.id && !self.plain {
            write!(f, "{}", "*".color(Color::Yellow).bold())?;
        }
        write!(f, "{}", ident)?;
        Ok(())
    }
    fn fmt_nonleaf(&mut self, f: &mut W, t: &str) -> Result {
        let ident = style(self.plain, t, |s| s.dimmed().italic());
        write!(f, "{}", ident)?;
        Ok(())
    }
}
impl<'b, W: Write> TreeFormatter<'b, TrimmedScriptInfo<'b>, W> for LongFormatter {
    fn fmt_leaf(&mut self, f: &mut W, t: &TrimmedScriptInfo<'b>) -> Result {
        let TrimmedScriptInfo(name, script) = t;
        let ty = get_display_type(&script.ty);
        let color = ty.color();
        let ident = style(self.plain, name, |s| s.color(color).bold());
        let ty_txt = style(self.plain, ty.display(), |s| s.color(color).bold());
        if self.latest_script_id == script.id && !self.plain {
            write!(f, "{}", "*".color(Color::Yellow).bold())?;
        }
        write!(f, "{}", ident)?;

        let mut buff = String::new();
        let help_msg = extract_help(&mut buff, script);

        let row = row![c->ty_txt, c->script.write_time, c->exec_time_str(script), help_msg];
        self.table.add_row(row);
        Ok(())
    }
    fn fmt_nonleaf(&mut self, f: &mut W, t: &str) -> Result {
        let ident = style(self.plain, t, |s| s.dimmed().italic());
        let empty = style(self.plain, "----", |s| s.dimmed());
        write!(f, "{}", ident)?;
        self.table
            .add_row(Row::new(vec![cell!(c->empty).with_hspan(TITLE.len())]));
        Ok(())
    }
}

type TreeNode<'b> = tree_lib::TreeNode<'b, TrimmedScriptInfo<'b>>;

fn build_forest(scripts: Vec<&ScriptInfo>) -> Vec<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 mut path: Vec<_> = name_key.split('/').collect();
        let name = Cow::Borrowed(path.pop().unwrap());
        let leaf = TreeNode::new_leaf(TrimmedScriptInfo(name, script));
        TreeNode::insert_to_map(&mut m, &path, leaf);
    }
    let mut forest: Vec<_> = m.into_iter().map(|(_, t)| t).collect();
    forest.sort_by(|a, b| a.simple_cmp(b));
    forest
}

pub fn fmt<W: Write>(
    scripts: Vec<&ScriptInfo>,
    latest_script_id: i64,
    opt: &mut ListOptions<Table, &mut W>,
) -> Result<()> {
    let forest = build_forest(scripts);
    match &mut opt.display_style {
        DisplayStyle::Long(table) => {
            let mut right_table = Table::new();
            right_table.set_format(*format::consts::FORMAT_CLEAN);
            right_table.set_titles(Row::new(TITLE.iter().map(|t| cell!(c->t)).collect()));
            let mut fmter = LongFormatter {
                plain: opt.plain,
                latest_script_id,
                table: right_table,
            };
            let mut left = Vec::<u8>::new();
            writeln!(left)?;
            fmter.fmt_all(&mut left, forest.into_iter())?;
            let left = std::str::from_utf8(&left)?;
            table.add_row(row![left, fmter.table.to_string()]);
        }
        DisplayStyle::Short(ident_style, w) => {
            let mut fmter = ShortFormatter {
                plain: opt.plain,
                ident_style: *ident_style,
                latest_script_id,
            };
            fmter.fmt_all(w, forest.into_iter())?;
        }
    }
    Ok(())
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::script::IntoScriptName;
    use chrono::NaiveDateTime;

    fn build(v: Vec<(&'static str, &'static str)>) -> Vec<ScriptInfo> {
        v.into_iter()
            .enumerate()
            .map(|(id, (name, ty))| {
                let id = id as i64;
                let time = NaiveDateTime::from_timestamp(id, 0);
                let mut builder = ScriptInfo::builder(
                    id,
                    name.to_owned().into_script_name().unwrap(),
                    ty.into(),
                    vec![].into_iter(),
                );
                builder.created_time(time);
                builder.build()
            })
            .collect()
    }
    #[test]
    fn test_fmt_tree_short() {
        let _ = env_logger::try_init();
        let scripts = build(vec![
            ("bbb/ccc/ggg/rrr", "tmux"),
            ("aaa/bbb", "rb"),
            ("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"),
        ]);
        let forest = build_forest(scripts.iter().collect());
        let mut fmter = ShortFormatter {
            plain: true,
            ident_style: DisplayIdentStyle::Normal,
            latest_script_id: 1,
        };
        let ans = "
.2(txt)
aaa(sh)
aaa
└── bbb(rb)
bbb
├── ddd(tmux)
└── ccc
    ├── yyy(js)
    ├── ddd(tmux)
    ├── ddd
    │   ├── www(rb)
    │   └── eee(tmux)
    └── ggg
        ├── xxx(tmux)
        ├── fff(tmux)
        └── rrr(tmux)
"
        .trim();
        let mut v8 = Vec::<u8>::new();
        fmter.fmt_all(&mut v8, forest.into_iter()).unwrap();
        assert_eq!(std::str::from_utf8(&v8).unwrap().trim(), ans);
    }
}