From a6322bff51a4e91ee334276c0a67eead042af0d7 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sat, 9 May 2026 20:07:08 +0800 Subject: [PATCH] feat: finish exec runner --- wfassoc-exec/src/cli.rs | 2 +- wfassoc-exec/src/runner.rs | 71 ++++++++++++++++++++++++++++++++++++-- wfassoc/src/highlevel.rs | 59 ++++++++++++++++++++++++++++--- 3 files changed, 123 insertions(+), 9 deletions(-) diff --git a/wfassoc-exec/src/cli.rs b/wfassoc-exec/src/cli.rs index 1320269..120c886 100644 --- a/wfassoc-exec/src/cli.rs +++ b/wfassoc-exec/src/cli.rs @@ -114,7 +114,7 @@ pub enum CliExtCommands { /// The scope where unlink file extension. #[arg(short = 't', long = "target", value_name = "TARGET", required = true, value_enum, default_value_t = RegScope::User)] target: RegScope, - // The file extensions used for this operation. Specify * for all file extension. + // The file extensions (without leading dot) used for this operation. Specify * for all file extension. #[arg(required = true, value_name = "EXTS", num_args = 1..)] exts: Vec, }, diff --git a/wfassoc-exec/src/runner.rs b/wfassoc-exec/src/runner.rs index 3af0322..fc3f896 100644 --- a/wfassoc-exec/src/runner.rs +++ b/wfassoc-exec/src/runner.rs @@ -1,7 +1,10 @@ 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 @@ -20,6 +23,19 @@ pub enum Error { /// 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("given extension name {0} is not presented in application")] + ExclusiveStarExtName(String), } /// Result type used in this module. @@ -27,13 +43,38 @@ type Result = std::result::Result; // endregion -// region: Utilities Functions +// region: Utilities fn stringified_exts_to_indices( program: &wfassoc::Program, exts: Vec, ) -> Result> { - todo!() + // 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 @@ -87,7 +128,31 @@ fn run_ext_list( view: wfassoc::View, style: cli::ExtListStyle, ) -> Result<()> { - todo!() + // 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 diff --git a/wfassoc/src/highlevel.rs b/wfassoc/src/highlevel.rs index 6e5d4dd..011a356 100644 --- a/wfassoc/src/highlevel.rs +++ b/wfassoc/src/highlevel.rs @@ -246,6 +246,7 @@ pub struct Program { behaviors: Vec>, ext_keys: Vec, + ext_keys_map: HashMap, } impl TryFrom for Program { @@ -386,7 +387,7 @@ impl Program { .map(|behavior| Self::resolve_index(&behavior, &behaviors, &behaviors_index_map)) .transpose()?; - // We build ProgIdKey and ExtKey list at the same time + // We build ext keys let mut ext_keys: Vec = Vec::with_capacity(schema.exts.len()); for (key, value) in &schema.exts { // Build ProgId first. @@ -403,7 +404,7 @@ impl Program { let ext = concept::Ext::new(key.as_str())?; let ext_key = lowlevel::ExtKey::new(ext); - // Create program Ext key struct + // Create program ProgId Ext key struct let progid_ext_key = ProgramProgIdExtKey { ext_key, progid_key, @@ -415,6 +416,12 @@ impl Program { // Add them into list ext_keys.push(progid_ext_key); } + // The build ext keys map + let ext_keys_map = ext_keys + .iter() + .enumerate() + .map(|(i, ext)| (ext.ext_key.inner().inner().to_string(), i)) + .collect(); // Everything is okey Ok(Self { @@ -429,10 +436,39 @@ impl Program { icons, behaviors, ext_keys, + ext_keys_map, }) } } +impl Program { + pub fn resolve_name(&self) -> Result { + todo!() + } + + pub fn resolve_icon(&self) -> Result { + todo!() + } + + pub fn get_ext_count(&self) -> usize { + self.ext_keys.len() + } + + pub fn get_ext(&self, index: usize) -> Result<&concept::Ext, ProgramError> { + match self.ext_keys.get(index) { + Some(program_key) => { + let ext_key = &program_key.ext_key; + Ok(ext_key.inner()) + } + None => Err(ProgramError::BadIndex), + } + } + + pub fn find_ext(&self, body: &str) -> Option { + self.ext_keys_map.get(body).copied() + } +} + impl Program { /// Register this application. /// @@ -577,6 +613,11 @@ impl Program { Some(program_key) => { let ext_key = &mut program_key.ext_key; let progid_key = &program_key.progid_key; + debug_println!( + "Linking ProgId \"{0}\" to extension \"{1}\"subkey...", + progid_key.inner().to_string(), + ext_key.inner().to_string() + ); // Before setting it, we must make sure this extension is existing ext_key.ensure(scope)?; @@ -591,6 +632,10 @@ impl Program { match self.ext_keys.get_mut(index) { Some(program_key) => { let ext_key = &mut program_key.ext_key; + debug_println!( + "Unlinking for extension \"{0}\"subkey...", + ext_key.inner().to_string() + ); // Before setting it, we must make sure this extension is existing ext_key.ensure(scope)?; @@ -609,6 +654,10 @@ impl Program { match self.ext_keys.get(index) { Some(program_key) => { let ext_key = &program_key.ext_key; + debug_println!( + "Querying for extension \"{0}\"subkey...", + ext_key.inner().to_string() + ); // If there is no such extension key, return None about this extension. if !ext_key.is_exist(view)? { @@ -656,7 +705,7 @@ impl Program { // region: Program Exposed Structs /// Exposed struct representing the default associated program of specific file extension. -/// +/// /// The data including the diaplay name and icon. pub struct ProgramExtStatus { name: String, @@ -669,7 +718,7 @@ impl ProgramExtStatus { } /// Get the display name of this program. - /// + /// /// The program provided display name will be used firstly. /// If this program has no display name, the stringified ProgId will be used instead. pub fn get_name(&self) -> &str { @@ -677,7 +726,7 @@ impl ProgramExtStatus { } /// Get the icon of this program. - /// + /// /// Due to the icon is optional, if there is no icon, return None. pub fn get_icon(&self) -> Option<&concept::IconRc> { self.icon.as_ref()