use crate::expanded::Inputs;
use crate::load;
use crate::resolve::{DocumentId, Resolved};
use crate::settings::{Names, Settings};
use crate::tree::DeclaredType;
use serde::Serialize;
use serde_json::{Map, Value};
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Frontmatter {
pub doc: Map<String, Value>,
pub settings: DocumentSettings,
pub data: Vec<DataImport>,
pub inputs: Inputs,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct DocumentSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub lang: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tz: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub import: Vec<ComponentImport>,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct ComponentImport {
pub from: String,
pub names: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct DataImport {
pub name: String,
pub from: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub schema: Vec<(String, DeclaredType)>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fallback: Option<String>,
pub value: Value,
}
pub(crate) fn of(resolved: &Resolved, id: DocumentId, loaded: &load::Loaded) -> Frontmatter {
let Settings {
doc,
imports,
data,
version,
lang,
tz,
..
} = &resolved.document(id).settings;
let values = loaded.data(id).to_json();
Frontmatter {
doc: doc.clone(),
settings: DocumentSettings {
version: version.clone(),
lang: lang.clone(),
tz: tz.clone(),
import: imports
.iter()
.map(|import| ComponentImport {
from: import.from.value.clone(),
names: match &import.names {
Names::All(_) => vec!["*".into()],
Names::Listed(entries) => entries
.iter()
.map(|entry| {
if entry.name == entry.renamed {
entry.name.clone()
} else {
format!("{} as {}", entry.name, entry.renamed)
}
})
.collect(),
},
})
.collect(),
},
data: data
.iter()
.map(|import| DataImport {
name: import.name.clone(),
from: import.from.value.clone(),
schema: import
.schema
.iter()
.flatten()
.map(|field| {
(
field.declaration.name.clone(),
field.declaration.declared.clone(),
)
})
.collect(),
fallback: import.fallback.clone(),
value: values.get(&import.name).cloned().unwrap_or(Value::Null),
})
.collect(),
inputs: loaded.inputs.clone(),
}
}
#[cfg(test)]
mod tests {
use crate::context::RenderContext;
use crate::expand::Options;
use crate::{Ambient, ParseOptions, Processor};
use serde_json::json;
use std::collections::HashMap;
use std::rc::Rc;
#[test]
fn gives_settings_imports_data_and_inputs() {
let text = "---\nmd:\n version: 1\n lang: de\n tz: UTC\n import:\n - from: ./ui.comp.md\n names: \"*\"\n - from: ./more.comp.md\n names: [card, {name: badge, as: tag}]\n data:\n - from: ./gone.csv\n as: gone\n fallback: ./kept.csv\n schema: {n: number}\n inputs:\n region: eu | us = eu\n---\n";
let files = HashMap::from([
("ui.comp.md".to_string(), String::new()),
("more.comp.md".to_string(), String::new()),
]);
let context = RenderContext {
now: 0,
tz: "UTC".into(),
time: Rc::new(()),
inputs: serde_json::Map::default(),
};
let frontmatter = Processor::new("a.comp.md", text)
.parse(&ParseOptions::default())
.resolve(files, (), &Ambient::default())
.validate(&[])
.load(context, (), ())
.expand(&Options::default())
.frontmatter();
let json = serde_json::to_value(&frontmatter).expect("JSON");
assert_eq!(
json["settings"],
json!({
"version": "1",
"lang": "de",
"tz": "UTC",
"import": [
{ "from": "./ui.comp.md", "names": ["*"] },
{ "from": "./more.comp.md", "names": ["card", "badge as tag"] }
]
})
);
assert_eq!(
json["data"],
json!([{ "name": "gone", "from": "./gone.csv", "schema": [["n", "number"]], "fallback": "./kept.csv", "value": null }])
);
assert_eq!(json["inputs"]["region"]["value"], "eu");
assert_eq!(json["doc"], json!({}));
}
}