diff --git a/Cargo.lock b/Cargo.lock index 9327b9a..f391f36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "clap", "env_logger", "log", + "regex", "semver", "serde", "tera", @@ -657,9 +658,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 8ae2233..292ce6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ chrono = "0.4.35" clap = { version = "4.5.2", features = ["derive"] } env_logger = "0.11.3" log = "0.4.21" +regex = "1.10.4" semver = { version = "1.0.22", features = ["serde"] } serde = { version = "1.0.197", features = ["derive"] } tera = { version = "1.19.1", features = ["builtins"] } diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index dd914fd..26828a0 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -59,6 +59,18 @@ This should be relative path of configuration file. Search target of file. This accepts multi-line text and using templating. +``files[].regex`` +------------------ + +:Required: No +:Default: ``false`` + +Flag to use regular expression (regex) when searching target. + +If it is ``true``, age search target using regex and replace text with captured text. + +.. note:: See it: https://docs.rs/regex/1.10.4/regex/ + ``files[].replace`` ------------------- diff --git a/src/config.rs b/src/config.rs index cf538b1..2f0c49b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -22,6 +22,13 @@ pub struct FileConfig { pub path: PathBuf, pub search: String, pub replace: String, + pub regex: Option, +} + +impl FileConfig { + pub fn regex(&self) -> bool { + self.regex.is_some() && self.regex.unwrap() + } } pub trait ParseAvailable { diff --git a/src/workspace.rs b/src/workspace.rs index 99304d0..90c3d33 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -53,7 +53,7 @@ impl Workspace { fn init_writer(&self, ctx: &Context) -> Writer { let mut writer = Writer::new(&ctx.for_tera()); for f in &self.config.files { - writer.add_target(&f.path, &f.search, &f.replace); + writer.add_target(f); } writer } diff --git a/src/writer.rs b/src/writer.rs index 4a250d2..68180eb 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -4,8 +4,11 @@ use std::io::prelude::*; use std::path::{Path, PathBuf}; use anyhow::Result; +use regex::Regex; use tera::{Context, Tera}; +use crate::config::FileConfig; + /** * File writer. */ @@ -34,6 +37,7 @@ pub struct WriteRule { pub search: String, /** Replacement content that is rendered by Tera. */ pub replace: String, + pub regex: bool, } impl Writer { @@ -44,21 +48,23 @@ impl Writer { } } - pub fn add_target(&mut self, path: &Path, search: &str, replace: &str) { - let path_key = path.display().to_string(); + pub fn add_target(&mut self, config: &FileConfig) { + let path_key = config.path.display().to_string(); if !self.targets.contains_key(&path_key) { self.targets - .insert(path_key.clone(), WriteTarget::new(path)); + .insert(path_key.clone(), WriteTarget::new(&config.path)); } let target = self.targets.get_mut(&path_key).unwrap(); - target.add_rule( - Tera::one_off(search, &self.context, true) + let rule = WriteRule { + search: Tera::one_off(&config.search, &self.context, true) .unwrap() .to_string(), - Tera::one_off(replace, &self.context, true) + replace: Tera::one_off(&config.replace, &self.context, true) .unwrap() .to_string(), - ); + regex: config.regex(), + }; + target.add_rule(rule); } pub fn update_all(&self) -> Result<()> { @@ -87,13 +93,18 @@ impl WriteTarget { Ok(()) } - pub fn add_rule(&mut self, search: String, replace: String) { - self.rules.push(WriteRule { search, replace }); + pub fn add_rule(&mut self, rule: WriteRule) { + self.rules.push(rule); } } impl WriteRule { fn update(&self, target: String) -> String { + // If regex is enabled, func runs using Regex directly. + if self.regex { + let search = Regex::new(&self.search).unwrap(); + return search.replace(&target, &self.replace).to_string(); + } let lines = self.search.split('\n').count(); let mut buf: VecDeque = VecDeque::new(); let mut output: Vec = Vec::new(); @@ -137,16 +148,18 @@ mod tests { ctx.insert("new_version", &Version::new(0, 2, 0)); let mut writer = Writer::new(&ctx); let filepath = PathBuf::from("dummy.txt"); - writer.add_target( - &filepath, - &String::from("target-1"), - &String::from("replace-2"), - ); - writer.add_target( - &filepath, - &String::from("target-2"), - &String::from("replace-2"), - ); + writer.add_target(&FileConfig { + path: filepath.clone(), + search: String::from("target-1"), + replace: String::from("replace-2"), + regex: Some(false), + }); + writer.add_target(&FileConfig { + path: filepath.clone(), + search: String::from("target-2"), + replace: String::from("replace-2"), + regex: Some(false), + }); assert_eq!(writer.targets.len(), 1); } } diff --git a/tests/return-0/multi-line-regex-captured/after/.age.toml b/tests/return-0/multi-line-regex-captured/after/.age.toml new file mode 100644 index 0000000..016162b --- /dev/null +++ b/tests/return-0/multi-line-regex-captured/after/.age.toml @@ -0,0 +1,14 @@ +current_version = "0.2.0" + +[[files]] +path = "example.txt" +regex = true +search = """ +version = '{{current_version}}' +hello (?.+) +""" +replace = """ +version = '{{new_version}}' +hello +from $name +""" diff --git a/tests/return-0/multi-line-regex-captured/after/example.txt b/tests/return-0/multi-line-regex-captured/after/example.txt new file mode 100644 index 0000000..9aa8c59 --- /dev/null +++ b/tests/return-0/multi-line-regex-captured/after/example.txt @@ -0,0 +1,5 @@ +version = '0.2.0' +hello +from world + +This line is kept. diff --git a/tests/return-0/multi-line-regex-captured/before/.age.toml b/tests/return-0/multi-line-regex-captured/before/.age.toml new file mode 100644 index 0000000..ce32286 --- /dev/null +++ b/tests/return-0/multi-line-regex-captured/before/.age.toml @@ -0,0 +1,14 @@ +current_version = "0.1.0" + +[[files]] +path = "example.txt" +regex = true +search = """ +version = '{{current_version}}' +hello (?.+) +""" +replace = """ +version = '{{new_version}}' +hello +from $name +""" diff --git a/tests/return-0/multi-line-regex-captured/before/example.txt b/tests/return-0/multi-line-regex-captured/before/example.txt new file mode 100644 index 0000000..2fddeb3 --- /dev/null +++ b/tests/return-0/multi-line-regex-captured/before/example.txt @@ -0,0 +1,4 @@ +version = '0.1.0' +hello world + +This line is kept. diff --git a/tests/return-0/multi-line-regex/after/.age.toml b/tests/return-0/multi-line-regex/after/.age.toml new file mode 100644 index 0000000..f069c07 --- /dev/null +++ b/tests/return-0/multi-line-regex/after/.age.toml @@ -0,0 +1,13 @@ +current_version = "0.2.0" + +[[files]] +path = "example.txt" +regex = true +search = """ +version = '{{current_version}}' +(.+) +""" +replace = """ +version = '{{new_version}}' +world +""" diff --git a/tests/return-0/multi-line-regex/after/example.txt b/tests/return-0/multi-line-regex/after/example.txt new file mode 100644 index 0000000..6d379b6 --- /dev/null +++ b/tests/return-0/multi-line-regex/after/example.txt @@ -0,0 +1,4 @@ +version = '0.2.0' +world + +This line is kept. diff --git a/tests/return-0/multi-line-regex/before/.age.toml b/tests/return-0/multi-line-regex/before/.age.toml new file mode 100644 index 0000000..0c708ac --- /dev/null +++ b/tests/return-0/multi-line-regex/before/.age.toml @@ -0,0 +1,13 @@ +current_version = "0.1.0" + +[[files]] +path = "example.txt" +regex = true +search = """ +version = '{{current_version}}' +(.+) +""" +replace = """ +version = '{{new_version}}' +world +""" diff --git a/tests/return-0/multi-line-regex/before/example.txt b/tests/return-0/multi-line-regex/before/example.txt new file mode 100644 index 0000000..648a597 --- /dev/null +++ b/tests/return-0/multi-line-regex/before/example.txt @@ -0,0 +1,4 @@ +version = '0.1.0' +hello + +This line is kept. diff --git a/tests/return-0/single-line-no-regex/after/.age.toml b/tests/return-0/single-line-no-regex/after/.age.toml new file mode 100644 index 0000000..f0fb08a --- /dev/null +++ b/tests/return-0/single-line-no-regex/after/.age.toml @@ -0,0 +1,7 @@ +current_version = "0.2.0" + +[[files]] +regex = false +path = "example.txt" +search = "version = '{{current_version}}'" +replace = "version = '{{new_version}}'" diff --git a/tests/return-0/single-line-no-regex/after/example.txt b/tests/return-0/single-line-no-regex/after/example.txt new file mode 100644 index 0000000..60fb6a5 --- /dev/null +++ b/tests/return-0/single-line-no-regex/after/example.txt @@ -0,0 +1 @@ +version = '0.2.0' diff --git a/tests/return-0/single-line-no-regex/before/.age.toml b/tests/return-0/single-line-no-regex/before/.age.toml new file mode 100644 index 0000000..b42c7f9 --- /dev/null +++ b/tests/return-0/single-line-no-regex/before/.age.toml @@ -0,0 +1,7 @@ +current_version = "0.1.0" + +[[files]] +regex = false +path = "example.txt" +search = "version = '{{current_version}}'" +replace = "version = '{{new_version}}'" diff --git a/tests/return-0/single-line-no-regex/before/example.txt b/tests/return-0/single-line-no-regex/before/example.txt new file mode 100644 index 0000000..75b8a57 --- /dev/null +++ b/tests/return-0/single-line-no-regex/before/example.txt @@ -0,0 +1 @@ +version = '0.1.0'