diff --git a/Cargo.lock b/Cargo.lock index 66a7711..1ad201f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ dependencies = [ [[package]] name = "dream-cli" -version = "0.1.0" +version = "0.1.1" dependencies = [ "clap", "dream-core", @@ -291,7 +291,7 @@ dependencies = [ [[package]] name = "dream-core" -version = "0.1.0" +version = "0.1.1" dependencies = [ "chrono", "dream-gate", @@ -307,7 +307,7 @@ dependencies = [ [[package]] name = "dream-gate" -version = "0.1.0" +version = "0.1.1" dependencies = [ "chrono", "failure-fingerprint", diff --git a/Cargo.toml b/Cargo.toml index 312fded..b6ba8d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,4 @@ rust-version = "1.89" license = "MIT OR Apache-2.0" repository = "https://github.com/DreamLab-AI/dream-engine" authors = ["DreamLab AI"] -version = "0.1.0" +version = "0.1.1" diff --git a/crates/dream-cli/Cargo.toml b/crates/dream-cli/Cargo.toml index e5050f4..84834bb 100644 --- a/crates/dream-cli/Cargo.toml +++ b/crates/dream-cli/Cargo.toml @@ -21,8 +21,8 @@ name = "dream_cli" path = "src/lib.rs" [dependencies] -dream-gate = { path = "../dream-gate", version = "0.1.0" } -dream-core = { path = "../dream-core", version = "0.1.0" } +dream-gate = { path = "../dream-gate", version = "0.1.1" } +dream-core = { path = "../dream-core", version = "0.1.1" } clap = { version = "4", features = ["derive"] } serde_json = "1" thiserror = "2" diff --git a/crates/dream-core/Cargo.toml b/crates/dream-core/Cargo.toml index 198ae84..8f34c27 100644 --- a/crates/dream-core/Cargo.toml +++ b/crates/dream-core/Cargo.toml @@ -14,7 +14,7 @@ categories = ["development-tools", "development-tools::testing"] authors.workspace = true [dependencies] -dream-gate = { path = "../dream-gate", version = "0.1.0" } +dream-gate = { path = "../dream-gate", version = "0.1.1" } failure-fingerprint = { path = "../failure-fingerprint", version = "0.1.0" } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/crates/dream-core/src/compile.rs b/crates/dream-core/src/compile.rs index e3f3272..62ee8db 100644 --- a/crates/dream-core/src/compile.rs +++ b/crates/dream-core/src/compile.rs @@ -211,7 +211,7 @@ pub fn compile( .collect::>() .join("\n"); - let adr_example = format!("ADR-{:0>width$}", 1, width = cfg.adr_convention.pad()); + let adr_example = cfg.adr_convention.example(1); let labels = if cfg.labels.is_empty() { "none".to_string() } else { diff --git a/crates/dream-gate/src/config.rs b/crates/dream-gate/src/config.rs index 9de96d5..6449ef5 100644 --- a/crates/dream-gate/src/config.rs +++ b/crates/dream-gate/src/config.rs @@ -111,6 +111,25 @@ pub enum AdrConvention { }, } +/// Byte offset and length of the first longest run of `N` in a template. +fn longest_n_run(t: &str) -> Option<(usize, usize)> { + let (mut best, mut run_start, mut run) = (None::<(usize, usize)>, 0usize, 0usize); + for (i, c) in t.char_indices() { + if c == 'N' { + if run == 0 { + run_start = i; + } + run += 1; + if best.is_none_or(|(_, l)| run > l) { + best = Some((run_start, run)); + } + } else { + run = 0; + } + } + best +} + /// Width of the longest run of `N` in a numbering template, or `None` when the /// text has no `N` at all and so is not a template. fn template_pad(t: &str) -> Option { @@ -143,6 +162,30 @@ impl AdrConvention { } } + /// The identifier for ADR number `n` under this convention. + /// + /// A free-form template keeps everything around its numbering run, so + /// `ADR-OM-NNN` gives `ADR-OM-006` for 6; the other forms give `ADR-` plus + /// the zero-padded number. + /// + /// ``` + /// use dream_gate::config::AdrConvention; + /// let t: AdrConvention = serde_json::from_str("\"ADR-OM-NNN\"").unwrap(); + /// assert_eq!(t.example(6), "ADR-OM-006"); + /// assert_eq!(AdrConvention::ThreeDigit.example(1), "ADR-001"); + /// ``` + pub fn example(&self, n: u32) -> String { + let width = self.pad(); + let number = format!("{n:0>width$}"); + match self { + AdrConvention::Template(t) => match longest_n_run(t) { + Some((start, len)) => format!("{}{number}{}", &t[..start], &t[start + len..]), + None => format!("ADR-{number}"), + }, + _ => format!("ADR-{number}"), + } + } + /// The configured directory, for the custom form only. pub fn dir(&self) -> Option<&str> { match self { diff --git a/crates/dream-gate/tests/adr_template.rs b/crates/dream-gate/tests/adr_template.rs index f195790..de06e7c 100644 --- a/crates/dream-gate/tests/adr_template.rs +++ b/crates/dream-gate/tests/adr_template.rs @@ -37,3 +37,18 @@ fn named_forms_are_unchanged() { assert_eq!(parse("\"3-digit\"").unwrap(), AdrConvention::ThreeDigit); assert_eq!(parse("\"4-digit\"").unwrap(), AdrConvention::FourDigit); } + +#[test] +fn example_keeps_the_template_prefix() { + assert_eq!(parse("\"ADR-OM-NNN\"").unwrap().example(6), "ADR-OM-006"); + assert_eq!(parse("\"ADR-NNNNN\"").unwrap().example(1), "ADR-00001"); + assert_eq!(parse("\"N-ADR-NNNN\"").unwrap().example(12), "N-ADR-0012"); + assert_eq!(parse("\"ADR-NN-NNN\"").unwrap().example(2), "ADR-NN-002"); +} + +#[test] +fn example_for_named_forms_is_plain_adr() { + assert_eq!(parse("\"3-digit\"").unwrap().example(1), "ADR-001"); + assert_eq!(parse("\"4-digit\"").unwrap().example(1), "ADR-0001"); + assert_eq!(AdrConvention::Unset.example(7), "ADR-0007"); +}