specfold_core/import/map.rs
1//! The import map: how files from another layout become typed documents. A project writes it
2//! once for its migration; nothing in it is specific to specfold's own layout.
3
4use anyhow::{Context, Result};
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7use std::path::Path;
8
9#[derive(Debug, Clone, Deserialize, Serialize)]
10#[serde(deny_unknown_fields)]
11pub struct ImportMap {
12 #[serde(default)]
13 pub sources: Vec<SourceRule>,
14 /// Old status → new status, for every source.
15 #[serde(default)]
16 pub status: BTreeMap<String, String>,
17 /// Old frontmatter key → new key, or "-" to drop it. `rel.<kind>` moves a value into rel.
18 /// A trailing `?` (`check = "verify?"`) sets the key only when no other old key filled it.
19 #[serde(default)]
20 pub keys: BTreeMap<String, String>,
21 /// Also record each source's bare name (file name without `.md`, or directory name) as an
22 /// alias, for layouts whose documents referred to each other by name.
23 #[serde(default)]
24 pub stem_aliases: bool,
25}
26
27#[derive(Debug, Clone, Deserialize, Serialize)]
28#[serde(deny_unknown_fields)]
29pub struct SourceRule {
30 /// Files to import; a pattern ending in `/` matches directories, imported as bundles.
31 pub glob: String,
32 #[serde(default)]
33 pub exclude: Vec<String>,
34 /// The document type. Required unless `redirect` is set.
35 #[serde(rename = "type")]
36 pub ty: Option<String>,
37 /// `auto` (from governs, then from the code paths the text mentions) or a fixed owner id.
38 #[serde(default = "auto")]
39 pub owner: String,
40 /// How the key is chosen for timestamp types: `name` (from `name`'s `key` group),
41 /// `first-commit` (the file's first commit time) or `created` (frontmatter created date).
42 #[serde(default = "first_commit")]
43 pub key: String,
44 /// A regex over the file or directory name with optional named groups `key`, `slug`, `alias`.
45 pub name: Option<String>,
46 /// Status for documents without one.
47 pub default_status: Option<String>,
48 /// Status overrides for this source only.
49 #[serde(default)]
50 pub status: BTreeMap<String, String>,
51 /// The file is a "moved" stub: a regex with a `target` group naming the new path. Stubs become
52 /// alias rows and are removed.
53 pub redirect: Option<String>,
54 /// Extra frontmatter fields whose values become aliases (old ids).
55 #[serde(default)]
56 pub alias_fields: Vec<String>,
57 /// For files: also take the sibling directory `<stem>/` as the bundle.
58 #[serde(default)]
59 pub sibling_dir: bool,
60 /// Files with no Markdown (raw output, data, boards) become attachments of a generated
61 /// document titled after them: one per matched file, or one per matched directory.
62 #[serde(default)]
63 pub wrap: bool,
64 /// The matched files belong to the old layout only (indexes, generated graphs, retired rule
65 /// pages) and are removed without becoming documents.
66 #[serde(default)]
67 pub drop: bool,
68}
69
70fn auto() -> String {
71 "auto".into()
72}
73
74fn first_commit() -> String {
75 "first-commit".into()
76}
77
78impl ImportMap {
79 pub fn load(path: &Path) -> Result<Self> {
80 let text = std::fs::read_to_string(path).with_context(|| format!("cannot read {}", path.display()))?;
81 toml::from_str(&text).with_context(|| format!("invalid import map {}", path.display()))
82 }
83}