use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::Path;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ImportMap {
#[serde(default)]
pub sources: Vec<SourceRule>,
#[serde(default)]
pub status: BTreeMap<String, String>,
#[serde(default)]
pub keys: BTreeMap<String, String>,
#[serde(default)]
pub stem_aliases: bool,
#[serde(default)]
pub strip_title_number: bool,
#[serde(default)]
pub status_patterns: Vec<StatusPattern>,
#[serde(default)]
pub body: BodyMap,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct StatusPattern {
pub pattern: String,
pub status: String,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct BodyMap {
pub status: Option<String>,
pub date: Option<String>,
#[serde(default)]
pub links: BTreeMap<String, String>,
#[serde(default)]
pub drop_status_section: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SourceRule {
pub glob: String,
#[serde(default)]
pub exclude: Vec<String>,
#[serde(rename = "type")]
pub ty: Option<String>,
#[serde(default = "auto")]
pub owner: String,
#[serde(default = "first_commit")]
pub key: String,
pub name: Option<String>,
pub default_status: Option<String>,
#[serde(default)]
pub status: BTreeMap<String, String>,
pub redirect: Option<String>,
#[serde(default)]
pub alias_fields: Vec<String>,
#[serde(default)]
pub sibling_dir: bool,
#[serde(default)]
pub wrap: bool,
#[serde(default)]
pub drop: bool,
}
fn auto() -> String {
"auto".into()
}
fn first_commit() -> String {
"first-commit".into()
}
impl ImportMap {
pub fn load(path: &Path) -> Result<Self> {
let text = std::fs::read_to_string(path).with_context(|| format!("cannot read {}", path.display()))?;
let map: Self = toml::from_str(&text).with_context(|| format!("invalid import map {}", path.display()))?;
for how in map.body.status.iter().chain(&map.body.date) {
if !super::body_meta::valid(how) {
anyhow::bail!(
"invalid import map {}: body '{how}' must be section:<heading>, bullet:<label> or line:<label>",
path.display()
);
}
}
for p in &map.status_patterns {
if let Err(e) = regex::Regex::new(&p.pattern) {
anyhow::bail!("invalid import map {}: status pattern '{}': {e}", path.display(), p.pattern);
}
}
Ok(map)
}
}