From f7d92243c9af527688868fb086703edbdf31d02c Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Fri, 10 Oct 2025 14:23:01 +0800 Subject: [PATCH] add decl for ProgId --- wfassoc/src/lib.rs | 242 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 193 insertions(+), 49 deletions(-) diff --git a/wfassoc/src/lib.rs b/wfassoc/src/lib.rs index a2eafb2..d8b9873 100644 --- a/wfassoc/src/lib.rs +++ b/wfassoc/src/lib.rs @@ -24,6 +24,8 @@ pub enum Error { BadFileExt(#[from] ParseFileExtError), #[error("{0}")] BadExecRc(#[from] ParseExecRcError), + #[error("{0}")] + BadProgId(#[from] ParseProgIdError), } // endregion @@ -192,7 +194,11 @@ impl FileExtAssoc { let default = thisext.get_value("").unwrap_or(String::new()); let open_with_progids = if let Ok(progids) = thisext.open_subkey_with_flags("OpenWithProdIds", KEY_READ) { - progids.enum_keys().map(|x| x.unwrap()).filter(|k| !k.is_empty()).collect() + progids + .enum_keys() + .map(|x| x.unwrap()) + .filter(|k| !k.is_empty()) + .collect() } else { Vec::new() }; @@ -220,71 +226,209 @@ impl FileExtAssoc { // region: Executable Resource -/// The struct representing an Windows executable resources path like -/// `path_to_file.exe,1`. -pub struct ExecRc { - /// The path to binary for finding resources. - binary: PathBuf, - /// The inner index of resources. - index: u32, +// /// The struct representing an Windows executable resources path like +// /// `path_to_file.exe,1`. +// pub struct ExecRc { +// /// The path to binary for finding resources. +// binary: PathBuf, +// /// The inner index of resources. +// index: u32, +// } + +// impl ExecRc { +// pub fn new(res_str: &str) -> Result { +// static RE: LazyLock = LazyLock::new(|| Regex::new(r"^([^,]+),([0-9]+)$").unwrap()); +// let caps = RE.captures(res_str); +// if let Some(caps) = caps { +// let binary = PathBuf::from_str(&caps[1])?; +// let index = caps[2].parse::()?; +// Ok(Self { binary, index }) +// } else { +// Err(ParseExecRcError::NoCapture) +// } +// } +// } + +// /// The error occurs when try parsing string into ExecRc. +// #[derive(Debug, TeError)] +// #[error("given string is not a valid executable resource string")] +// pub enum ParseExecRcError { +// /// Given string is not matched with format. +// NoCapture, +// /// Fail to convert executable part into path. +// BadBinaryPath(#[from] std::convert::Infallible), +// /// Fail to convert index part into valid number. +// BadIndex(#[from] std::num::ParseIntError), +// } + +// impl FromStr for ExecRc { +// type Err = ParseExecRcError; + +// fn from_str(s: &str) -> Result { +// ExecRc::new(s) +// } +// } + +// impl Display for ExecRc { +// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// write!(f, "{},{}", self.binary.to_str().unwrap(), self.index) +// } +// } + +// endregion + +// region: Programmatic Identifiers (ProgId) + +/// The struct representing Programmatic Identifiers (ProgId). +/// +/// Because there is optional part in ProgId, and not all software developer +/// are willing to following Microsoft standard, there is no strict constaint for ProgId. +/// So this struct is actually an enum which holding any possible ProgId format. +pub enum ProgId { + Plain(String), + Loose(LosseProgId), + Strict(StrictProgId), } -impl ExecRc { - pub fn new(res_str: &str) -> Result { - static RE: LazyLock = LazyLock::new(|| Regex::new(r"^([^,]+),([0-9]+)$").unwrap()); - let caps = RE.captures(res_str); - if let Some(caps) = caps { - let binary = PathBuf::from_str(&caps[1])?; - let index = caps[2].parse::()?; - Ok(Self { binary, index }) - } else { - Err(ParseExecRcError::NoCapture) +impl FromStr for ProgId { + type Err = ParseProgIdError; + + fn from_str(s: &str) -> Result { + // match it for strict ProgId first + if let Ok(v) = StrictProgId::from_str(s) { + return Ok(Self::Strict(v)); + } + // then match for loose ProgId + if let Ok(v) = LosseProgId::from_str(s) { + return Ok(Self::Loose(v)); + } + // fallback with plain + Ok(Self::Plain(s.to_string())) + } +} + +impl Display for ProgId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProgId::Plain(v) => v.fmt(f), + ProgId::Loose(v) => v.fmt(f), + ProgId::Strict(v) => v.fmt(f), } } } -/// The error occurs when try parsing string into ExecRc. +/// The error occurs when parsing ProgId. #[derive(Debug, TeError)] -#[error("given string is not a valid executable resource string")] -pub enum ParseExecRcError { - /// Given string is not matched with format. - NoCapture, - /// Fail to convert executable part into path. - BadBinaryPath(#[from] std::convert::Infallible), - /// Fail to convert index part into valid number. - BadIndex(#[from] std::num::ParseIntError), -} +#[error("given ProgId string is invalid")] +pub struct ParseProgIdError {} -impl FromStr for ExecRc { - type Err = ParseExecRcError; - - fn from_str(s: &str) -> Result { - ExecRc::new(s) +impl ParseProgIdError { + fn new() -> Self { + Self {} } } -impl Display for ExecRc { +/// The ProgId similar with strict ProgId, but no version part. +pub struct LosseProgId { + vendor: String, + component: String, +} + +impl LosseProgId { + pub fn new(vendor: &str, component: &str) -> Self { + Self { + vendor: vendor.to_string(), + component: component.to_string(), + } + } + + pub fn get_vendor(&self) -> &str { + &self.vendor + } + + pub fn get_component(&self) -> &str { + &self.component + } +} + +impl FromStr for LosseProgId { + type Err = ParseProgIdError; + + fn from_str(s: &str) -> Result { + static RE: LazyLock = + LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)$").unwrap()); + let caps = RE.captures(s); + if let Some(caps) = caps { + let vendor = &caps[1]; + let component = &caps[2]; + Ok(Self::new(vendor, component)) + } else { + Err(ParseProgIdError::new()) + } + } +} + +impl Display for LosseProgId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{},{}", self.binary.to_str().unwrap(), self.index) + write!(f, "{}.{}", self.vendor, self.component) + } +} + +/// The ProgId exactly follows `[Vendor or Application].[Component].[Version]` format. +pub struct StrictProgId { + vendor: String, + component: String, + version: u32, +} + +impl StrictProgId { + pub fn new(vendor: &str, component: &str, version: u32) -> Self { + Self { + vendor: vendor.to_string(), + component: component.to_string(), + version, + } + } + + pub fn get_vendor(&self) -> &str { + &self.vendor + } + + pub fn get_component(&self) -> &str { + &self.component + } + + pub fn get_version(&self) -> u32 { + self.version + } +} + +impl FromStr for StrictProgId { + type Err = ParseProgIdError; + + fn from_str(s: &str) -> Result { + static RE: LazyLock = + LazyLock::new(|| Regex::new(r"^([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)\.([0-9]+)$").unwrap()); + let caps = RE.captures(s); + if let Some(caps) = caps { + let vendor = &caps[1]; + let component = &caps[2]; + let version = caps[3].parse::().map_err(|_| ParseProgIdError::new())?; + Ok(Self::new(vendor, component, version)) + } else { + Err(ParseProgIdError::new()) + } + } +} + +impl Display for StrictProgId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}.{}", self.vendor, self.component, self.version) } } // endregion -// /// The struct representing an Windows acceptable Prgram ID, -// /// which looks like `Program.Document.2` -// pub struct ProgId { -// inner: String, -// } - -// impl ProgId { -// pub fn new(prog_id: &str) -> Self { -// Self { -// inner: prog_id.to_string(), -// } -// } -// } - // region: Program // /// The struct representing a complete Win32 program.