Files
nomina/crates/nomina-core/src/rules/regex.rs
lashman 9dca2bedfa initial project scaffold
Rust workspace with nomina-core (rename engine) and nomina-app (Tauri v2 shell).
React/TypeScript frontend with tabbed rule panels, virtual-scrolled file list,
and Zustand state management. All 9 rule types implemented with 25 passing tests.
2026-03-13 23:49:29 +02:00

101 lines
2.5 KiB
Rust

use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::{NominaError, RenameContext, RenameRule};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegexRule {
pub pattern: String,
pub replace_with: String,
pub case_insensitive: bool,
pub enabled: bool,
}
impl RegexRule {
pub fn new() -> Self {
Self {
pattern: String::new(),
replace_with: String::new(),
case_insensitive: false,
enabled: true,
}
}
fn build_regex(&self) -> std::result::Result<Regex, NominaError> {
let pat = if self.case_insensitive {
format!("(?i){}", self.pattern)
} else {
self.pattern.clone()
};
Regex::new(&pat).map_err(|e| NominaError::InvalidRegex {
pattern: self.pattern.clone(),
reason: e.to_string(),
})
}
}
impl RenameRule for RegexRule {
fn apply(&self, filename: &str, _context: &RenameContext) -> String {
if self.pattern.is_empty() {
return filename.to_string();
}
match self.build_regex() {
Ok(re) => re.replace_all(filename, self.replace_with.as_str()).into_owned(),
Err(_) => filename.to_string(),
}
}
fn display_name(&self) -> &str {
"Regex"
}
fn rule_type(&self) -> &str {
"regex"
}
fn is_enabled(&self) -> bool {
self.enabled
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_regex() {
let rule = RegexRule {
pattern: r"(\d+)".into(),
replace_with: "NUM".into(),
case_insensitive: false,
enabled: true,
};
let ctx = RenameContext::dummy(0);
assert_eq!(rule.apply("file123", &ctx), "fileNUM");
}
#[test]
fn capture_groups() {
let rule = RegexRule {
pattern: r"([a-z]+)-([a-z]+)".into(),
replace_with: "${2}_${1}".into(),
case_insensitive: false,
enabled: true,
};
let ctx = RenameContext::dummy(0);
assert_eq!(rule.apply("hello-world", &ctx), "world_hello");
}
#[test]
fn empty_pattern() {
let rule = RegexRule {
pattern: String::new(),
replace_with: "x".into(),
case_insensitive: false,
enabled: true,
};
let ctx = RenameContext::dummy(0);
assert_eq!(rule.apply("test", &ctx), "test");
}
}