From 5e47e77ac5848bdd8fa8fcef8ac57bf5a81c449b Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 22 Mar 2025 08:48:39 +0000 Subject: [PATCH] Fix some more translation bugs --- src/metadata/error.rs | 6 +++++ src/metadata/message.rs | 42 +++++++++++++++++++++++++++++ src/plugin/mod.rs | 58 ++++++++++++++++++++--------------------- src/sorting/plugins.rs | 32 +++++++---------------- 4 files changed, 86 insertions(+), 52 deletions(-) diff --git a/src/metadata/error.rs b/src/metadata/error.rs index 871cf381..50b33752 100644 --- a/src/metadata/error.rs +++ b/src/metadata/error.rs @@ -129,6 +129,7 @@ pub(super) enum MetadataParsingErrorReason { UnexpectedType(ExpectedType, YamlObjectType), UnexpectedValueType(&'static str, ExpectedType, YamlObjectType), MissingPlaceholder(String, usize), + MissingSubstitution(String), NonU32Number(i64), DuplicateEntry(String, YamlObjectType), Other(Box), @@ -164,6 +165,11 @@ impl std::fmt::Display for MetadataParsingErrorReason { "failed to substitute \"{}\" into message, no placeholder {{{}}} was found", sub, placeholder_index ), + Self::MissingSubstitution(placeholder) => write!( + f, + "failed to substitute a value into message, no substitution was given for the placeholder \"{}\"", + placeholder + ), Self::NonU32Number(i) => { write!(f, "{} is not valid as a 32-bit unsigned integer", i) } diff --git a/src/metadata/message.rs b/src/metadata/message.rs index 6f3d4d0f..dedda515 100644 --- a/src/metadata/message.rs +++ b/src/metadata/message.rs @@ -1,5 +1,10 @@ +use std::{borrow::Cow, sync::LazyLock}; + +use fancy_regex::{Captures, Regex}; use saphyr::{MarkedYaml, YamlData}; +use crate::logging; + use super::{ error::{ ExpectedType, MetadataParsingErrorReason, MultilingualMessageContentsError, @@ -308,9 +313,39 @@ impl TryFrom<&MarkedYaml> for Message { let subs = get_strings_vec_value(hash, "subs", YamlObjectType::Message)?; if !subs.is_empty() { + static FMT_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"{(\d+)}").expect("hardcoded fmt placeholder regex should be valid") + }); + for mc in &mut content { + if mc.text.contains("%1%") { + static BOOST_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"%(\d+)%") + .expect("hardcoded Boost placeholder regex should be valid") + }); + + let result = BOOST_REGEX.replace_all(&mc.text, |captures: &Captures| { + match captures[1].parse::() { + Ok(i) if i > 0 => format!("{{{}}}", i - 1), + Ok(_) => { + logging::warn!("Found zero-indexed placeholder using Boost syntax in string \"{}\"", mc.text); + captures[0].to_string() + }, + Err(e) => { + logging::error!("Unexpected failure to parse Boost placeholder index \"{}\": {}", &captures[1], e); + captures[0].to_string() + } + } + }); + + if let Cow::Owned(text) = result { + mc.text = text; + } + } + for (index, sub) in subs.iter().enumerate() { let placeholder = format!("{{{}}}", index); + if !mc.text.contains(&placeholder) { return Err(ParseMetadataError::new( value.span.start, @@ -320,6 +355,13 @@ impl TryFrom<&MarkedYaml> for Message { mc.text = mc.text.replace(&placeholder, sub); } + + if let Ok(Some(m)) = FMT_REGEX.find(&mc.text) { + return Err(ParseMetadataError::new( + value.span.start, + MetadataParsingErrorReason::MissingSubstitution(m.as_str().to_string()), + )); + } } } diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index b50847ea..4ecfbc4d 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -24,35 +24,6 @@ use error::{ PluginValidationErrorReason, }; -static VERSION_REGEXES: LazyLock> = LazyLock::new(|| { - // The string below matches the range of version strings supported by - // Pseudosem v1.0.1, excluding space separators, as they make version - // extraction from inside sentences very tricky and have not been seen "in - // the wild". The second non-capturing group prevents version numbers - // followed by a comma from matching. - let pseudosem_regex_str = r"(\d+(?:\.\d+)+(?:[-._:]?[A-Za-z0-9]+)*)(?!,)"; - - /* There are a few different version formats that can appear in strings - together, and in order to extract the correct one, they must be searched - for in order of priority. */ - Box::new([ - /* The string below matches timestamps that use forwardslashes for date - separators. However, Pseudosem v1.0.1 will only compare the first - two digits as it does not recognise forwardslashes as separators. */ - Regex::new(r"(?i)(\d{1,2}/\d{1,2}/\d{1,4} \d{1,2}:\d{1,2}:\d{1,2})") - .expect("Hardcoded version timestamp regex should be valid"), - Regex::new(&format!(r"(?i)version:?\s{}", pseudosem_regex_str)) - .expect("Hardcoded version-prefixed pseudosem version regex should be valid"), - Regex::new(&format!(r"(?i)(?:^|v|\s){}", pseudosem_regex_str)) - .expect("Hardcoded pseudosem version regex should be valid"), - /* The string below matches a number containing one or more - digits found at the start of the search string or preceded by - 'v' or 'version:. */ - Regex::new(r"(?i)(?:^|v|version:\s*)(\d+)") - .expect("Hardcoded prefixed version number regex should be valid"), - ]) -}); - #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub(crate) enum LoadScope { HeaderOnly, @@ -462,6 +433,35 @@ fn extract_bash_tags(description: &str) -> Vec { } fn extract_version(description: &str) -> Result, Box> { + static VERSION_REGEXES: LazyLock> = LazyLock::new(|| { + // The string below matches the range of version strings supported by + // Pseudosem v1.0.1, excluding space separators, as they make version + // extraction from inside sentences very tricky and have not been seen "in + // the wild". The second non-capturing group prevents version numbers + // followed by a comma from matching. + let pseudosem_regex_str = r"(\d+(?:\.\d+)+(?:[-._:]?[A-Za-z0-9]+)*)(?!,)"; + + /* There are a few different version formats that can appear in strings + together, and in order to extract the correct one, they must be searched + for in order of priority. */ + Box::new([ + /* The string below matches timestamps that use forwardslashes for date + separators. However, Pseudosem v1.0.1 will only compare the first + two digits as it does not recognise forwardslashes as separators. */ + Regex::new(r"(?i)(\d{1,2}/\d{1,2}/\d{1,4} \d{1,2}:\d{1,2}:\d{1,2})") + .expect("Hardcoded version timestamp regex should be valid"), + Regex::new(&format!(r"(?i)version:?\s{}", pseudosem_regex_str)) + .expect("Hardcoded version-prefixed pseudosem version regex should be valid"), + Regex::new(&format!(r"(?i)(?:^|v|\s){}", pseudosem_regex_str)) + .expect("Hardcoded pseudosem version regex should be valid"), + /* The string below matches a number containing one or more + digits found at the start of the search string or preceded by + 'v' or 'version:. */ + Regex::new(r"(?i)(?:^|v|version:\s*)(\d+)") + .expect("Hardcoded prefixed version number regex should be valid"), + ]) + }); + for regex in &*VERSION_REGEXES { let version = regex .captures(description)? diff --git a/src/sorting/plugins.rs b/src/sorting/plugins.rs index 0caefd49..3f4a8439 100644 --- a/src/sorting/plugins.rs +++ b/src/sorting/plugins.rs @@ -260,43 +260,29 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> { return; } - let mut early_loading_plugin_indices: HashMap<&str, NodeIndex> = HashMap::new(); + let mut early_loader_indices = Vec::new(); let mut other_plugin_indices = Vec::new(); for node_index in self.node_indices() { let plugin = &self[node_index]; - if let Some(p) = early_loading_plugins + if let Some(i) = early_loading_plugins .iter() - .find(|e| unicase::eq(e.as_str(), plugin.name())) + .position(|e| unicase::eq(e.as_str(), plugin.name())) { - early_loading_plugin_indices.insert(p.as_str(), node_index); + early_loader_indices.push((i, node_index)); } else { other_plugin_indices.push(node_index); } } - if early_loading_plugin_indices.is_empty() { - return; - } + early_loader_indices.sort_by_key(|e| e.0); - let mut last_early_loading_plugin_index = None; - for window in early_loading_plugins.windows(2) { - if let [from, to] = window { - let from_index = early_loading_plugin_indices.get(from.as_str()); - let to_index = early_loading_plugin_indices.get(to.as_str()); - - if to_index.is_some() { - last_early_loading_plugin_index = to_index; - } else if from_index.is_some() { - last_early_loading_plugin_index = from_index; - } - - if let (Some(from_index), Some(to_index)) = (from_index, to_index) { - self.add_edge(*from_index, *to_index, EdgeType::Hardcoded); - } + for window in early_loader_indices.windows(2) { + if let [(_, from_index), (_, to_index)] = window { + self.add_edge(*from_index, *to_index, EdgeType::Hardcoded); } } - if let Some(from_index) = last_early_loading_plugin_index { + if let Some((_, from_index)) = early_loader_indices.last() { for to_index in other_plugin_indices { self.add_edge(*from_index, to_index, EdgeType::Hardcoded); }