use std::collections::BTreeMap;
use std::path::Path;
use crate::authoring::parse::{
kind_from_source_path, module_name_from_path, module_source_from_node_path,
parse_admission_expression, parse_flow_expression, rule_syntax_for_source,
source_path_from_file,
};
use crate::authoring::validation::authoring_namespace;
use crate::{
NodeId, ParentSyntax, RuntimeCheckSpec, parse_face as parse_face_syntax, root_node_id,
};
use super::super::FaceManifest;
pub(crate) fn source(path: &Path) -> Result<FaceManifest, String> {
parse_face_macro_impl(
path,
&std::fs::read_to_string(path).map_err(|error| error.to_string())?,
)
}
fn parse_face_macro_impl(path: &Path, text: &str) -> Result<FaceManifest, String> {
let face = parse_face_syntax(text)
.map_err(|error| format!("cannot parse {}: {error}", path.display()))?
.ok_or_else(|| "this module was not generated by NichLink".to_owned())?;
let mut values = BTreeMap::new();
let source = source_path_from_file(path);
values.insert("source".to_owned(), source);
values.insert("module".to_owned(), module_name_from_path(path));
let declared_parent_registry = face
.macro_name
.strip_suffix("_object")
.filter(|name| !matches!(*name, "control" | "external"));
if let Some(parent_registry) = declared_parent_registry {
values.insert(
"parent_registry_name".to_owned(),
parent_registry.to_owned(),
);
}
for key in ["kind", "preset", "parts"] {
if let Some(value) = face.path(key) {
values.insert(key.to_owned(), value);
}
}
for key in ["registry_rule_path"] {
if let Some(value) = face.string(key) {
values.insert(key.to_owned(), value);
}
}
if let Some(value) = face.boolean("needs_registry") {
values.insert("needs_registry".to_owned(), value.to_string());
}
let kind = values.get("kind").cloned().unwrap_or_default();
values.insert(
"stable_name".to_owned(),
face.string("stable_name").unwrap_or_default(),
);
values.insert(
"name_zh".to_owned(),
face.localized("name", "zh").unwrap_or_else(|| kind.clone()),
);
values.insert(
"name_en".to_owned(),
face.localized("name", "en").unwrap_or_else(|| kind.clone()),
);
values.insert(
"summary_zh".to_owned(),
face.localized("summary", "zh").unwrap_or_default(),
);
values.insert(
"summary_en".to_owned(),
face.localized("summary", "en").unwrap_or_default(),
);
values.insert(
"exports".to_owned(),
face.string_list("exports").unwrap_or_default().join(","),
);
values.insert(
"parts".to_owned(),
values
.get("parts")
.cloned()
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "NoParts".to_owned()),
);
values.insert(
"preset".to_owned(),
values
.get("preset")
.cloned()
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "NoPreset".to_owned()),
);
values.insert(
"handle".to_owned(),
values
.get("handle")
.cloned()
.filter(|value| !value.is_empty())
.unwrap_or_else(|| kind.clone()),
);
if let Some(plugin) = face.field(nichlink::lexicon::FACE_FIELD_PLUGIN) {
values.insert(nichlink::lexicon::FACE_FIELD_PLUGIN.to_owned(), plugin);
}
values.insert(
"getting_from_other_registry".to_owned(),
face.option_string("getting_from_other_registry")
.flatten()
.unwrap_or_default(),
);
values.insert(
"needs_registry".to_owned(),
values
.get("needs_registry")
.cloned()
.unwrap_or_else(|| "false".to_owned()),
);
values.insert(
"admission".to_owned(),
parse_admission_expression(face.field("admission").as_deref().unwrap_or(""))?,
);
values.insert(
"handle_traits".to_owned(),
face.string_list("handle_traits")
.unwrap_or_default()
.join(","),
);
values.insert(
"handle_contracts".to_owned(),
face.path_list("handle_contracts")
.unwrap_or_default()
.join(","),
);
values.insert(
"part_contracts".to_owned(),
face.path_list("part_contracts")
.unwrap_or_default()
.join(","),
);
values.insert(
"part_traits".to_owned(),
face.string_list("part_traits")
.unwrap_or_default()
.join(","),
);
values.insert(
"requires".to_owned(),
face.requirements("requires")
.unwrap_or_default()
.into_iter()
.map(|(capability, provider)| format!("{capability}=>{provider}"))
.collect::<Vec<_>>()
.join(","),
);
values.insert(
"provides".to_owned(),
face.string_list("provides").unwrap_or_default().join(","),
);
values.insert(
"runtime_checks".to_owned(),
RuntimeCheckSpec::parse_list(face.field("runtime_checks").as_deref().unwrap_or(""))?
.into_iter()
.map(RuntimeCheckSpec::expression)
.collect::<Vec<_>>()
.join(","),
);
values.insert(
"flow".to_owned(),
parse_flow_expression(face.field("flow").as_deref().unwrap_or(""))?,
);
values.insert(
"flow_provider".to_owned(),
face.path("flow_provider").unwrap_or_default(),
);
values.insert(
"declaration_line".to_owned(),
face.location.line.to_string(),
);
let parent_syntax = face.parent();
if declared_parent_registry.is_some() && parent_syntax.is_none() {
return Err("parent-specific registration macro requires an explicit parent".to_owned());
}
let parent_syntax = parent_syntax.or(Some(ParentSyntax::Root));
if let (Some(declared), Some(parent)) = (declared_parent_registry, parent_syntax.as_ref()) {
let expected = match parent {
ParentSyntax::Root => "root".to_owned(),
ParentSyntax::FromPath { source, .. } => Path::new(source)
.file_stem()
.and_then(|stem| stem.to_str())
.unwrap_or("root")
.to_owned(),
ParentSyntax::NodePath(module) => module
.rsplit("::")
.find(|segment| !segment.is_empty())
.unwrap_or("root")
.to_owned(),
};
if declared != expected {
return Err(format!(
"registration macro `{}_object!` does not match parent `{expected}`",
declared
));
}
}
match parent_syntax {
Some(ParentSyntax::Root) => {
let namespace = authoring_namespace();
values.insert("namespace".to_owned(), namespace.clone());
values.insert(
"parent_node".to_owned(),
root_node_id(&namespace).to_string(),
);
values.insert("parent_source".to_owned(), "<root>".to_owned());
values.insert("parent_kind".to_owned(), "root".to_owned());
}
Some(ParentSyntax::FromPath { source, kind }) => {
values.insert(
"parent_node".to_owned(),
NodeId::from_namespaced_path(&authoring_namespace(), &source, &kind).to_string(),
);
values.insert("parent_source".to_owned(), source);
values.insert("parent_kind".to_owned(), kind);
}
Some(ParentSyntax::NodePath(module)) => {
let source = module_source_from_node_path(&module)
.ok_or_else(|| "generated face has an invalid parent path".to_owned())?;
let kind = kind_from_source_path(&source)
.ok_or_else(|| "generated face has an invalid parent path".to_owned())?;
values.insert("parent_source".to_owned(), source.clone());
values.insert("parent_kind".to_owned(), kind.clone());
values.insert(
"parent_node".to_owned(),
NodeId::from_namespaced_path(&authoring_namespace(), &source, &kind).to_string(),
);
}
None => return Err("generated face has an invalid parent path".to_owned()),
}
values.insert(
"registration_rule".to_owned(),
rule_syntax_for_source(path)?,
);
Ok(FaceManifest { values })
}