use crate::cli; use crate::manifest; use comfy_table::Table; use std::collections::{HashMap, HashSet}; use std::path::Path; use thiserror::Error as TeError; use toml; // region: Error Handling /// Error occurs in this module. #[derive(Debug, TeError)] pub enum Error { /// Error when parsing Manifest TOML file. #[error("{0}")] ParseManifest(#[from] manifest::ParseManifestError), /// Error when parsing Manifest into Schema. #[error("{0}")] ParseSchema(#[from] manifest::ParseSchemaError), /// Error when parsing Schema into Program. #[error("{0}")] ParseProgram(#[from] wfassoc::highlevel::ParseProgramError), /// Error when operating Program. #[error("{0}")] Program(#[from] wfassoc::highlevel::ProgramError), /// Error when serializing TOML #[error("{0}")] SerializeToml(#[from] toml::ser::Error), /// Find duplicated name when converting extension name to index #[error("given extension name {0} has been specified more than one time")] DupExtName(String), /// Find invalid name when converting extension name to index #[error("given extension name {0} is not presented in application")] BadExtName(String), /// Find star (*) extension name with other extension names when converting extension name to index #[error("wildcard extension name \"*\" is not allowed to be used with other extension names")] ExclusiveStarExtName(String), } /// Result type used in this module. type Result = std::result::Result; // endregion // region: Utilities fn stringified_exts_to_indices( program: &wfassoc::Program, exts: Vec, ) -> Result> { // Check for duplicate extension names let mut seen = HashSet::new(); for ext in &exts { if !seen.insert(ext.as_str()) { return Err(Error::DupExtName(ext.clone())); } } // Check for star (*) with other extensions let has_star = exts.iter().any(|ext| ext == "*"); if has_star && exts.len() > 1 { return Err(Error::ExclusiveStarExtName("*".to_string())); } // If star is present alone, return fixed list from zero to the maximum ext index. if has_star { return Ok((0..program.get_ext_count()).collect()); } // Convert each extension name to index using program.find_ext() let indices = exts .into_iter() .map(|ext| program.find_ext(&ext).ok_or(Error::BadExtName(ext))) .collect::>>()?; Ok(indices) } // endregion // region: Respective Runners fn run_register(mut program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> { program.register(scope)?; println!("Application now is installed."); Ok(()) } fn run_unregister(mut program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> { program.unregister(scope)?; println!("Application now is uninstalled."); Ok(()) } fn run_status(program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> { if program.is_registered(scope)? { println!("Application is installed."); } else { println!("Application is not installed."); } Ok(()) } fn run_ext_link( mut program: wfassoc::Program, scope: wfassoc::Scope, exts: Vec, ) -> Result<()> { let exts = stringified_exts_to_indices(&program, exts)?; for index in exts { program.link_ext(scope, index)?; } println!("File extension now is linked."); Ok(()) } fn run_ext_unlink( mut program: wfassoc::Program, scope: wfassoc::Scope, exts: Vec, ) -> Result<()> { let exts = stringified_exts_to_indices(&program, exts)?; for index in exts { program.link_ext(scope, index)?; } println!("File extension now is unlinked."); Ok(()) } fn run_ext_list( program: wfassoc::Program, view: wfassoc::View, style: cli::ExtListStyle, ) -> Result<()> { // Fetch info let mut ext_list: HashMap> = HashMap::new(); for index in 0..program.get_ext_count() { let ext = program.get_ext(index)?; let status = program.query_ext(view, index)?; ext_list.insert(ext.dotted_inner(), status.map(|s| s.get_name().to_string())); } // Output by styles use cli::ExtListStyle; match style { ExtListStyle::Human => { let mut table = Table::new(); table.set_header(["Extension", "Association"]); for (k, v) in ext_list { table.add_row([k, v.unwrap_or("".to_string())]); } println!("{table}"); } ExtListStyle::Machine => { let stoml = toml::to_string(&ext_list)?; println!("{stoml}") } } Ok(()) } // endregion pub fn run(c: cli::Cli) -> Result<()> { // Read manifest file first let mf = manifest::Manifest::from_file(Path::new(&c.manifest_file))?; println!("{:?}", mf); // Parse it into schema let schema = mf.into_schema()?; // Parse it into program let program = schema.into_program()?; match c.command { cli::CliCommands::Register { target } => run_register(program, target.into()), cli::CliCommands::Unregister { target } => run_unregister(program, target.into()), cli::CliCommands::Status { target } => run_status(program, target.into()), cli::CliCommands::Ext { command } => match command { cli::CliExtCommands::Link { target, exts } => { run_ext_link(program, target.into(), exts) } cli::CliExtCommands::Unlink { target, exts } => { run_ext_unlink(program, target.into(), exts) } cli::CliExtCommands::List { target, style } => { run_ext_list(program, target.into(), style) } }, } }