diff --git a/build/syntax_mapping.rs b/build/syntax_mapping.rs index f17b1cf5..17722f67 100644 --- a/build/syntax_mapping.rs +++ b/build/syntax_mapping.rs @@ -47,16 +47,34 @@ impl ToTokens for MappingTarget { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash, DeserializeFromStr)] +/// Whether a glob pattern should be matched case-sensitively or case-insensitively. +/// +/// Mirrors the runtime `Case` type in `src/syntax_mapping.rs`. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +enum Case { + Sensitive, + Insensitive, +} +impl ToTokens for Case { + fn to_tokens(&self, tokens: &mut TokenStream) { + let t = match self { + Self::Sensitive => quote! { Case::Sensitive }, + Self::Insensitive => quote! { Case::Insensitive }, + }; + tokens.append_all(t); + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] /// A single matcher. /// /// Codegen converts this into a `Lazy>`. struct Matcher { segments: Vec, - /// Whether the glob pattern should be matched case-insensitively. + /// Whether the glob pattern should be matched case-sensitively. /// - /// Defaults to `true` (case-insensitive) for backwards compatibility. - case_insensitive: bool, + /// Defaults to `Case::Insensitive` for backwards compatibility. + case: Case, } /// Parse a matcher. /// @@ -124,21 +142,53 @@ impl FromStr for Matcher { Ok(Self { segments: non_empty_segments, - case_insensitive: true, + case: Case::Insensitive, }) } } + +/// Helper type for deserializing a `Matcher` from either a plain string or a +/// `{ glob = "...", case_sensitive = true }` struct. +#[derive(Deserialize)] +#[serde(untagged)] +enum RawMatcher { + Simple(String), + Full { + glob: String, + #[serde(default)] + case_sensitive: bool, + }, +} + +impl<'de> serde::Deserialize<'de> for Matcher { + fn deserialize>(deserializer: D) -> Result { + let raw = RawMatcher::deserialize(deserializer)?; + match raw { + RawMatcher::Simple(s) => Matcher::from_str(&s).map_err(serde::de::Error::custom), + RawMatcher::Full { glob, case_sensitive } => { + let mut matcher = + Matcher::from_str(&glob).map_err(serde::de::Error::custom)?; + matcher.case = if case_sensitive { + Case::Sensitive + } else { + Case::Insensitive + }; + Ok(matcher) + } + } + } +} impl ToTokens for Matcher { fn to_tokens(&self, tokens: &mut TokenStream) { - let case_insensitive = self.case_insensitive; + let case = &self.case; let t = match self.segments.as_slice() { [] => unreachable!("0-length matcher should never be created"), [MatcherSegment::Text(text)] => { - quote! { Lazy::new(|| Some(build_matcher_fixed(#text, #case_insensitive))) } + quote! { Lazy::new(|| Some(build_matcher_fixed(#text, #case))) } } // parser logic ensures that this case can only happen when there are dynamic segments segs @ [_, ..] => { - quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ], #case_insensitive)) } + quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ], #case)) } } }; tokens.append_all(t); @@ -189,10 +239,6 @@ impl MatcherSegment { struct MappingDefModel { #[serde(default)] mappings: IndexMap>, - /// Case-sensitive mappings. Unlike `mappings`, these glob patterns are - /// matched case-sensitively. - #[serde(default)] - case_sensitive_mappings: IndexMap>, } impl MappingDefModel { fn into_mapping_list(self) -> MappingList { @@ -205,19 +251,6 @@ impl MappingDefModel { .map(|matcher| (matcher, target.clone())) .collect::>() }) - .chain( - self.case_sensitive_mappings - .into_iter() - .flat_map(|(target, matchers)| { - matchers - .into_iter() - .map(|mut matcher| { - matcher.case_insensitive = false; - (matcher, target.clone()) - }) - .collect::>() - }), - ) .collect(); MappingList(list) } diff --git a/src/syntax_mapping.rs b/src/syntax_mapping.rs index 8fc4008a..584a5cb6 100644 --- a/src/syntax_mapping.rs +++ b/src/syntax_mapping.rs @@ -17,9 +17,16 @@ use ignored_suffixes::IgnoredSuffixes; mod builtin; pub mod ignored_suffixes; -fn make_glob_matcher(from: &str, case_insensitive: bool) -> Result { +/// Whether a glob pattern should be matched case-sensitively or case-insensitively. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Case { + Sensitive, + Insensitive, +} + +fn make_glob_matcher(from: &str, case: Case) -> Result { let matcher = GlobBuilder::new(from) - .case_insensitive(case_insensitive) + .case_insensitive(matches!(case, Case::Insensitive)) .literal_separator(true) .build()? .compile_matcher(); @@ -97,14 +104,14 @@ impl<'a> SyntaxMapping<'a> { } pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> { - let matcher = make_glob_matcher(from, true)?; + let matcher = make_glob_matcher(from, Case::Insensitive)?; self.custom_mappings.push((matcher, to)); Ok(()) } /// Like [`Self::insert`], but the glob pattern is matched case-sensitively. pub fn insert_case_sensitive(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> { - let matcher = make_glob_matcher(from, false)?; + let matcher = make_glob_matcher(from, Case::Sensitive)?; self.custom_mappings.push((matcher, to)); Ok(()) } diff --git a/src/syntax_mapping/builtin.rs b/src/syntax_mapping/builtin.rs index 52f1d47b..41007be5 100644 --- a/src/syntax_mapping/builtin.rs +++ b/src/syntax_mapping/builtin.rs @@ -3,7 +3,7 @@ use std::env; use globset::GlobMatcher; use once_cell::sync::Lazy; -use crate::syntax_mapping::{make_glob_matcher, MappingTarget}; +use crate::syntax_mapping::{make_glob_matcher, Case, MappingTarget}; // Static syntax mappings generated from /src/syntax_mapping/builtins/ by the // build script (/build/syntax_mapping.rs). @@ -53,8 +53,8 @@ include!(concat!( /// A failure to compile is a fatal error. /// /// Used internally by `Lazy>`'s lazy evaluation closure. -fn build_matcher_fixed(from: &str, case_insensitive: bool) -> GlobMatcher { - make_glob_matcher(from, case_insensitive) +fn build_matcher_fixed(from: &str, case: Case) -> GlobMatcher { + make_glob_matcher(from, case) .expect("A builtin fixed glob matcher failed to compile") } @@ -65,7 +65,7 @@ fn build_matcher_fixed(from: &str, case_insensitive: bool) -> GlobMatcher { /// to compile. /// /// Used internally by `Lazy>`'s lazy evaluation closure. -fn build_matcher_dynamic(segs: &[MatcherSegment], case_insensitive: bool) -> Option { +fn build_matcher_dynamic(segs: &[MatcherSegment], case: Case) -> Option { // join segments let mut buf = String::new(); for seg in segs { @@ -78,7 +78,7 @@ fn build_matcher_dynamic(segs: &[MatcherSegment], case_insensitive: bool) -> Opt } } // compile glob matcher - let matcher = make_glob_matcher(&buf, case_insensitive).ok()?; + let matcher = make_glob_matcher(&buf, case).ok()?; Some(matcher) } diff --git a/src/syntax_mapping/builtins/common/50-bazel.toml b/src/syntax_mapping/builtins/common/50-bazel.toml index 2b0e17e2..2ced1399 100644 --- a/src/syntax_mapping/builtins/common/50-bazel.toml +++ b/src/syntax_mapping/builtins/common/50-bazel.toml @@ -1,2 +1,2 @@ -[case_sensitive_mappings] -"Python" = ["BUILD"] +[mappings] +"Python" = [{ glob = "BUILD", case_sensitive = true }]