use crate::{
cli::Params,
engine::{CommandMap, ProfileDef},
error::WefterErr,
fs::{self, dirs::DirCfg, hist::HistoryAction, res::ResourceDirTable},
};
use anyhow::Result;
use clap::CommandFactory;
use clap_help::Printer;
use termimad::{
MadSkin,
crossterm::style::Color,
minimad::{OwningTemplateExpander, TextTemplate},
};
const LUA_API_META: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/lua/wefter.d.lua"
));
const HELP_TEMPLATE_MD: &str =
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/static/cli/about.md"));
const PROFILE_DEF_TEMPLATE_MD: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/cli/profile_def.md"
));
const PROFILE_LIST_TEMPLATE_MD: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/cli/profile_list.md"
));
const ERRORS_GENERIC_TEMPLATE_MD: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/errors/generic_error.md"
));
const ERRORS_NO_AVAILABLE_PROFILES_TEMPLATE_MD: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/errors/no_available_profiles.md"
));
const ERRORS_EMPTY_PARAMETERS_TEMPLATE_MD: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/errors/empty_parameters.md"
));
pub struct TuiInterface {
skin: MadSkin,
}
impl Default for TuiInterface {
fn default() -> Self {
let mut skin = MadSkin::default();
skin.set_headers_fg(Color::Blue);
skin.table.align = termimad::Alignment::Left;
skin.bold.set_fg(Color::Blue);
skin.italic.set_fg(Color::Magenta);
Self { skin }
}
}
impl TuiInterface {
pub fn new() -> Self {
Self::default()
}
pub fn print_markdown(&self, content: String) {
self.skin.print_text(&content);
}
pub fn print_lua_meta(&self) {
println!("{}", LUA_API_META)
}
pub fn print_help(&self) {
Printer::new(Params::command())
.with("introduction", HELP_TEMPLATE_MD)
.with("options", clap_help::TEMPLATE_OPTIONS_MERGED_VALUE)
.with_skin(self.skin.clone())
.print_help();
}
pub fn print_profile_list(&self, resources: &ResourceDirTable, dirs: &DirCfg) {
let mdtemplate = TextTemplate::from(PROFILE_LIST_TEMPLATE_MD);
let mut mdexpander = OwningTemplateExpander::new();
mdexpander.set("system-source", format!("{:?}", dirs.data));
mdexpander.set(
"local-source",
if let Some(local) = &dirs.local {
format!("{:?}", local)
} else {
format!("Not found")
},
);
for (k, v) in resources.iter() {
mdexpander
.sub("resource-rows")
.set("resource-profile", &k)
.set("resource-path", format!("{:?}", &v.path))
.set_md(
"resource-auto",
if v.auto.is_some() { "**✓**" } else { "*✗*" },
);
}
self.skin.print_owning_expander(&mdexpander, &mdtemplate);
}
pub fn print_error(&self, err: &WefterErr) {
let mdtemplate = TextTemplate::from(ERRORS_GENERIC_TEMPLATE_MD);
let mut mdexpander = OwningTemplateExpander::new();
mdexpander.set("error-message", &err.to_string());
println!(); self.skin.print_owning_expander(&mdexpander, &mdtemplate);
}
pub fn print_err_no_available_profiles(&self, dirs: &DirCfg) {
let mdtemplate = TextTemplate::from(ERRORS_NO_AVAILABLE_PROFILES_TEMPLATE_MD);
let mut mdexpander = OwningTemplateExpander::new();
mdexpander.set("error-message", WefterErr::NoAvailableProfiles.to_string());
mdexpander.set("path", format!("{:?}", dirs.data));
self.skin.print_owning_expander(&mdexpander, &mdtemplate);
}
pub fn print_err_empty_parameters(&self, profile: &String, def: &ProfileDef) {
let mdtemplate = TextTemplate::from(ERRORS_EMPTY_PARAMETERS_TEMPLATE_MD);
let mut mdexpander = OwningTemplateExpander::new();
mdexpander.set("error-message", WefterErr::EmptyParameters.to_string());
self.skin.print_owning_expander(&mdexpander, &mdtemplate);
self.print_profile(profile, def);
}
pub fn print_history(&self, history: fs::hist::History) {
println!(); self.skin.print_inline("**Files changed: **");
println!(); for action in history {
self.skin.print_inline(
match action {
HistoryAction::CreateFile(path) => {
format!("*[CREATED]* {:?}", path)
}
HistoryAction::ModifyFile(path, point) => {
format!("*[MODIFIED at {}]* {:?}", point, path)
}
HistoryAction::CreateDirectory(path) => {
format!("*[CREATED]* (directory) {:?}", path)
}
HistoryAction::FileRenamed { previous, new } => {
format!("*[RENAMED]* file {:?} to {:?}", previous, new.file_name())
}
HistoryAction::FileMoved { previous, new } => {
format!("*[MOVED]* file {:?} to {:?}", previous, new)
}
}
.as_str(),
);
println!(); }
}
fn render_subcommands(&self, out: &mut String, sub: &CommandMap, level: u16) {
for (i, item) in sub.iter().enumerate() {
let (k, v) = item;
let is_root = level == 0;
let is_last = sub.len() == i + 1;
let is_exec = v.exec.is_some();
for _ in 0..level {
out.push_str(" ");
}
if !is_root {
out.push_str(if is_last { "└── " } else { "├── " });
}
out.push_str(&if is_exec {
format!("**{}** `exec`", k)
} else {
format!("*{}*", k)
});
if let Some(desc) = &v.description {
out.push_str(&format!(" {}", desc));
}
out.push_str("\n");
if let Some(sub) = &v.subcommand {
self.render_subcommands(out, sub, level + 1);
}
}
}
pub fn print_profile(&self, name: &String, def: &ProfileDef) {
let mut out = String::new();
self.render_subcommands(&mut out, &def.0, 0);
let mdtemplate = TextTemplate::from(PROFILE_DEF_TEMPLATE_MD);
let mut mdexpander = OwningTemplateExpander::new();
mdexpander.set("profile", name);
mdexpander.set_md("tree", out);
self.skin.print_owning_expander(&mdexpander, &mdtemplate);
}
pub fn select(&self, prompt: &str, opts: &Vec<String>) -> Result<String> {
Ok(inquire::Select::new(prompt, opts.clone()).prompt()?)
}
pub fn input(&self, prompt: String) -> Result<String> {
Ok(inquire::Text::new(&format!("{}:", prompt)).prompt()?)
}
pub fn integer(&self, prompt: &str, min: i32, max: i32) -> Result<i32> {
Ok(inquire::CustomType::<i32>::new(&format!(
"{} (Between {} and {}):",
prompt,
if min == i32::MIN {
"MIN".to_string()
} else {
min.to_string()
},
if max == i32::MAX {
"MAX".to_string()
} else {
max.to_string()
},
))
.with_error_message(&format!("Please enter a valid integer between"))
.with_validator(move |val: &i32| {
Ok(if *val >= min && *val <= max {
inquire::validator::Validation::Valid
} else {
inquire::validator::Validation::Invalid(inquire::validator::ErrorMessage::Custom(
format!("Value must be between {} and {}", min, max),
))
})
})
.prompt()?)
}
pub fn confirm(&self, prompt: &str) -> Result<bool> {
Ok(inquire::Confirm::new(prompt).with_default(true).prompt()?)
}
}