Skip to content

Commit 7d080a5

Browse files
committed
use globs for rule matches.
1 parent e224c16 commit 7d080a5

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed

java-spaghetti-gen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ quote = "1.0.40"
1919
proc-macro2 = "1.0.95"
2020
anyhow = "1.0.98"
2121
serde_yaml = "0.9.34"
22+
glob = "0.3"
2223

2324
[dev-dependencies]
2425
jni-sys = "0.4.0"

java-spaghetti-gen/src/config.rs

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,27 +99,44 @@ pub enum ClassMatch {
9999

100100
impl Default for ClassMatch {
101101
fn default() -> Self {
102-
Self::One("".to_string())
102+
Self::One("**".to_string())
103103
}
104104
}
105105
impl ClassMatch {
106106
fn matches(&self, class: &str) -> bool {
107+
let options = glob::MatchOptions {
108+
case_sensitive: true,
109+
require_literal_separator: true,
110+
require_literal_leading_dot: false,
111+
};
112+
107113
match self {
108-
Self::One(p) => class.starts_with(p),
109-
Self::Many(pp) => pp.iter().any(|p| class.starts_with(p)),
114+
Self::One(p) => {
115+
let pattern = glob::Pattern::new(p).unwrap_or_else(|e| panic!("Invalid glob pattern '{}': {}", p, e));
116+
pattern.matches_with(class, options)
117+
}
118+
Self::Many(pp) => pp.iter().any(|p| {
119+
let pattern = glob::Pattern::new(p).unwrap_or_else(|e| panic!("Invalid glob pattern '{}': {}", p, e));
120+
pattern.matches_with(class, options)
121+
}),
110122
}
111123
}
112124
}
113125

114126
#[derive(Debug, Clone, Deserialize, Default)]
115127
pub struct Rule {
116-
/// What java class(es) to match against. This takes the form of a simple prefix to a JNI path with no wildcards.
128+
/// What java class(es) to match against. This takes the form of a glob pattern matching JNI paths.
117129
///
118-
/// | To Match: | Use a JNI Prefix: |
130+
/// Glob patterns are case-sensitive and require literal path separators (/ cannot be matched by *).
131+
/// Use ** to match across directory boundaries.
132+
///
133+
/// | To Match: | Use a glob pattern: |
119134
/// | ------------------------- | ------------------------------------- |
120-
/// | * | jni_prefix = ""
121-
/// | java.lang.* | jni_prefix = "java/lang/"
122-
/// | name.spaces.OuterClass.* | jni_prefix = "name/spaces/OuterClass$"
135+
/// | * | "*"
136+
/// | java.lang.* | "java/lang/**"
137+
/// | name.spaces.OuterClass.* | "name/spaces/OuterClass$*"
138+
/// | Specific class | "com/example/MyClass"
139+
/// | Multiple specific classes | ["com/example/Class1", "com/example/Class2"]
123140
#[serde(rename = "match")]
124141
pub matches: ClassMatch,
125142

@@ -386,4 +403,57 @@ mod tests {
386403
fn test_expand_vars_panic_on_missing_var_dollar() {
387404
expand_vars("$NONEXISTENT");
388405
}
406+
407+
#[test]
408+
fn test_class_match_glob_patterns() {
409+
// Test exact match
410+
let match_exact = ClassMatch::One("com/example/MyClass".to_string());
411+
assert!(match_exact.matches("com/example/MyClass"));
412+
assert!(!match_exact.matches("com/example/MyOtherClass"));
413+
414+
// Test wildcard patterns
415+
let match_wildcard = ClassMatch::One("com/example/*".to_string());
416+
assert!(match_wildcard.matches("com/example/MyClass"));
417+
assert!(match_wildcard.matches("com/example/MyOtherClass"));
418+
assert!(!match_wildcard.matches("com/other/MyClass"));
419+
420+
// Test question mark pattern
421+
let match_question = ClassMatch::One("com/example/MyClass?".to_string());
422+
assert!(match_question.matches("com/example/MyClass1"));
423+
assert!(match_question.matches("com/example/MyClassA"));
424+
assert!(!match_question.matches("com/example/MyClass"));
425+
assert!(!match_question.matches("com/example/MyClass12"));
426+
427+
// Test multiple patterns
428+
let match_many = ClassMatch::Many(vec!["com/example/*".to_string(), "org/test/specific/Class".to_string()]);
429+
assert!(match_many.matches("com/example/MyClass"));
430+
assert!(match_many.matches("org/test/specific/Class"));
431+
assert!(!match_many.matches("org/other/MyClass"));
432+
}
433+
434+
#[test]
435+
#[should_panic(expected = "Invalid glob pattern")]
436+
fn test_class_match_invalid_pattern_panics() {
437+
let match_invalid = ClassMatch::One("[invalid".to_string());
438+
match_invalid.matches("any_class");
439+
}
440+
#[test]
441+
fn test_class_match_literal_separator() {
442+
// Test that require_literal_separator: true prevents * from matching /
443+
let match_pattern = ClassMatch::One("com/example*".to_string());
444+
assert!(match_pattern.matches("com/example"));
445+
assert!(match_pattern.matches("com/exampleClass"));
446+
assert!(!match_pattern.matches("com/example/SubClass")); // * should not match /
447+
448+
// Test that we can use ** to match across directories
449+
let match_recursive = ClassMatch::One("com/**/MyClass".to_string());
450+
assert!(match_recursive.matches("com/example/MyClass"));
451+
assert!(match_recursive.matches("com/deep/nested/path/MyClass"));
452+
assert!(match_recursive.matches("com/MyClass")); // ** can match zero directories too
453+
454+
// Test that * within a directory works
455+
let match_single_dir = ClassMatch::One("com/*/MyClass".to_string());
456+
assert!(match_single_dir.matches("com/example/MyClass"));
457+
assert!(!match_single_dir.matches("com/deep/nested/MyClass")); // single * doesn't cross /
458+
}
389459
}

0 commit comments

Comments
 (0)