Skip to main content

specfold_core/import/
key.rs

1//! Choosing the key and slug of an imported document.
2
3use super::map::SourceRule;
4use crate::model::doctype::{KeyKind, TypeDef};
5use crate::model::stamp;
6use crate::workspace::vcs;
7use regex::Captures;
8use std::path::Path;
9
10/// Turns `20260101`, `20260101-120000` or `20260101120000` into a 14-digit stamp.
11pub fn normalize_stamp(s: &str) -> Option<String> {
12    let digits: String = s.chars().filter(|c| c.is_ascii_digit()).collect();
13    let full = match digits.len() {
14        8 => format!("{digits}000000"),
15        12 => format!("{digits}00"),
16        14 => digits,
17        _ => return None,
18    };
19    stamp::is_stamp(&full).then_some(full)
20}
21
22/// What is known about a source when its key and slug are chosen.
23pub struct Source<'a> {
24    pub path: &'a str,
25    pub caps: Option<&'a Captures<'a>>,
26    pub created: Option<&'a str>,
27    pub title: &'a str,
28    pub name_stem: &'a str,
29}
30
31pub fn choose(root: &Path, rule: &SourceRule, ty: &TypeDef, src: &Source) -> (String, String) {
32    let Source { path, caps, created, title, name_stem } = *src;
33    let cap = |g: &str| caps.and_then(|c| c.name(g)).map(|m| m.as_str().to_string());
34    let slug = cap("slug")
35        .map(|s| stamp::slugify(&s))
36        .filter(|s| stamp::is_slug(s))
37        .or_else(|| Some(stamp::slugify(title)).filter(|s| stamp::is_slug(s)))
38        .or_else(|| Some(stamp::slugify(name_stem)).filter(|s| stamp::is_slug(s)))
39        .unwrap_or_else(|| "untitled".into());
40    if ty.key == KeyKind::Slug {
41        return (slug.clone(), slug);
42    }
43    let from_name = || cap("key").and_then(|k| normalize_stamp(&k));
44    let from_created = || created.and_then(normalize_stamp);
45    let from_commit = || vcs::first_commit_stamp(root, path);
46    let key = match rule.key.as_str() {
47        "name" => from_name().or_else(from_commit).or_else(from_created),
48        "created" => from_created().or_else(from_commit),
49        _ => from_commit().or_else(from_name).or_else(from_created),
50    }
51    .unwrap_or_else(stamp::now_stamp);
52    (key, slug)
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn stamps() {
61        assert_eq!(normalize_stamp("20260101").as_deref(), Some("20260101000000"));
62        assert_eq!(normalize_stamp("20260101-120000").as_deref(), Some("20260101120000"));
63        assert_eq!(normalize_stamp("2026-01-01").as_deref(), Some("20260101000000"));
64        assert!(normalize_stamp("ADR-001").is_none());
65    }
66}