2026-04-21 16:30:25 +08:00
|
|
|
use crate::cli;
|
|
|
|
|
use crate::manifest;
|
2026-05-09 20:07:08 +08:00
|
|
|
use comfy_table::Table;
|
|
|
|
|
use std::collections::{HashMap, HashSet};
|
2026-04-21 16:30:25 +08:00
|
|
|
use std::path::Path;
|
2026-01-04 14:05:34 +08:00
|
|
|
use thiserror::Error as TeError;
|
2026-05-09 20:07:08 +08:00
|
|
|
use toml;
|
2026-01-04 14:05:34 +08:00
|
|
|
|
2026-04-21 16:30:25 +08:00
|
|
|
// region: Error Handling
|
|
|
|
|
|
2026-01-04 14:05:34 +08:00
|
|
|
/// Error occurs in this module.
|
|
|
|
|
#[derive(Debug, TeError)]
|
|
|
|
|
pub enum Error {
|
2026-04-22 14:53:22 +08:00
|
|
|
/// Error when parsing Manifest TOML file.
|
2026-04-21 16:30:25 +08:00
|
|
|
#[error("{0}")]
|
|
|
|
|
ParseManifest(#[from] manifest::ParseManifestError),
|
2026-04-22 14:53:22 +08:00
|
|
|
/// Error when parsing Manifest into Schema.
|
2026-04-21 16:30:25 +08:00
|
|
|
#[error("{0}")]
|
|
|
|
|
ParseSchema(#[from] manifest::ParseSchemaError),
|
2026-04-22 14:53:22 +08:00
|
|
|
/// Error when parsing Schema into Program.
|
2026-04-21 16:30:25 +08:00
|
|
|
#[error("{0}")]
|
|
|
|
|
ParseProgram(#[from] wfassoc::highlevel::ParseProgramError),
|
2026-04-22 14:53:22 +08:00
|
|
|
/// Error when operating Program.
|
|
|
|
|
#[error("{0}")]
|
|
|
|
|
Program(#[from] wfassoc::highlevel::ProgramError),
|
2026-05-09 20:07:08 +08:00
|
|
|
/// 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
|
2026-05-10 16:43:25 +08:00
|
|
|
#[error("wildcard extension name \"*\" is not allowed to be used with other extension names")]
|
2026-05-09 20:07:08 +08:00
|
|
|
ExclusiveStarExtName(String),
|
2026-01-04 14:05:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Result type used in this module.
|
|
|
|
|
type Result<T> = std::result::Result<T, Error>;
|
|
|
|
|
|
2026-04-21 16:30:25 +08:00
|
|
|
// endregion
|
2026-01-04 14:05:34 +08:00
|
|
|
|
2026-05-09 20:07:08 +08:00
|
|
|
// region: Utilities
|
2026-04-21 16:30:25 +08:00
|
|
|
|
2026-05-08 14:14:55 +08:00
|
|
|
fn stringified_exts_to_indices(
|
|
|
|
|
program: &wfassoc::Program,
|
|
|
|
|
exts: Vec<String>,
|
|
|
|
|
) -> Result<Vec<usize>> {
|
2026-05-09 20:07:08 +08:00
|
|
|
// 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::<Result<Vec<_>>>()?;
|
|
|
|
|
|
|
|
|
|
Ok(indices)
|
2026-01-04 14:05:34 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 16:30:25 +08:00
|
|
|
// endregion
|
|
|
|
|
|
|
|
|
|
// region: Respective Runners
|
|
|
|
|
|
2026-04-22 14:53:22 +08:00
|
|
|
fn run_register(mut program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> {
|
2026-05-09 20:10:15 +08:00
|
|
|
program.register(scope)?;
|
|
|
|
|
println!("Application now is installed.");
|
|
|
|
|
Ok(())
|
2026-01-04 14:05:34 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-22 14:53:22 +08:00
|
|
|
fn run_unregister(mut program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> {
|
2026-05-09 20:10:15 +08:00
|
|
|
program.unregister(scope)?;
|
|
|
|
|
println!("Application now is uninstalled.");
|
|
|
|
|
Ok(())
|
2026-01-04 14:05:34 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 14:14:55 +08:00
|
|
|
fn run_status(program: wfassoc::Program, scope: wfassoc::Scope) -> Result<()> {
|
|
|
|
|
if program.is_registered(scope)? {
|
2026-04-22 14:53:22 +08:00
|
|
|
println!("Application is installed.");
|
|
|
|
|
} else {
|
|
|
|
|
println!("Application is not installed.");
|
|
|
|
|
}
|
2026-05-08 14:14:55 +08:00
|
|
|
|
2026-04-22 14:53:22 +08:00
|
|
|
Ok(())
|
2026-01-04 14:05:34 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 14:14:55 +08:00
|
|
|
fn run_ext_link(
|
|
|
|
|
mut program: wfassoc::Program,
|
|
|
|
|
scope: wfassoc::Scope,
|
|
|
|
|
exts: Vec<String>,
|
|
|
|
|
) -> Result<()> {
|
2026-04-21 16:30:25 +08:00
|
|
|
let exts = stringified_exts_to_indices(&program, exts)?;
|
2026-04-29 13:11:12 +08:00
|
|
|
for index in exts {
|
|
|
|
|
program.link_ext(scope, index)?;
|
|
|
|
|
}
|
2026-05-09 20:10:15 +08:00
|
|
|
|
|
|
|
|
println!("File extension now is linked.");
|
2026-04-29 13:11:12 +08:00
|
|
|
Ok(())
|
2026-01-04 14:05:34 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 16:30:25 +08:00
|
|
|
fn run_ext_unlink(
|
2026-05-08 14:14:55 +08:00
|
|
|
mut program: wfassoc::Program,
|
2026-04-21 16:30:25 +08:00
|
|
|
scope: wfassoc::Scope,
|
|
|
|
|
exts: Vec<String>,
|
|
|
|
|
) -> Result<()> {
|
|
|
|
|
let exts = stringified_exts_to_indices(&program, exts)?;
|
2026-04-29 13:11:12 +08:00
|
|
|
for index in exts {
|
|
|
|
|
program.link_ext(scope, index)?;
|
|
|
|
|
}
|
2026-05-09 20:10:15 +08:00
|
|
|
|
|
|
|
|
println!("File extension now is unlinked.");
|
2026-04-29 13:11:12 +08:00
|
|
|
Ok(())
|
2026-01-04 14:05:34 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 16:30:25 +08:00
|
|
|
fn run_ext_list(
|
|
|
|
|
program: wfassoc::Program,
|
|
|
|
|
view: wfassoc::View,
|
|
|
|
|
style: cli::ExtListStyle,
|
|
|
|
|
) -> Result<()> {
|
2026-05-09 20:07:08 +08:00
|
|
|
// Fetch info
|
|
|
|
|
let mut ext_list: HashMap<String, Option<String>> = 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(())
|
2026-04-21 16:30:25 +08:00
|
|
|
}
|
2026-01-04 14:05:34 +08:00
|
|
|
|
|
|
|
|
// endregion
|
|
|
|
|
|
2026-04-21 16:30:25 +08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
2026-01-04 14:05:34 +08:00
|
|
|
}
|