1use std::collections::BTreeMap;
5
6use saphyr::{Mapping, Scalar, Sequence, Yaml as SYaml, YamlEmitter};
7
8use super::{
9 Format, Grammar, RegexCache, TypeDef, TypeRegistry, format::lower_format, patch::merge_patch,
10 validate,
11};
12use crate::{Finding, Problem, RepoPath, Result, Spanned, Yaml, yaml};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum FormatSource {
17 BuiltIn,
19 Embedded,
21 Repo,
23 File(String),
26 Schema(String),
28}
29
30impl FormatSource {
31 pub fn label(&self) -> &str {
33 match self {
34 FormatSource::BuiltIn => "built-in",
35 FormatSource::Embedded => "embedded",
36 FormatSource::Repo => "repo",
37 FormatSource::File(path) => path,
38 FormatSource::Schema(id) => id,
39 }
40 }
41}
42
43#[derive(Debug, Clone, Default)]
47pub struct FormatRegistry {
48 pub entries: BTreeMap<String, (Format, FormatSource)>,
50 pub types: TypeRegistry,
52}
53impl FormatRegistry {
56 pub fn from_text(
61 text: &str,
62 built_ins: &[(&str, &str)],
63 grammar: &Grammar,
64 regexes: &RegexCache,
65 ) -> Result<Self> {
66 let fail = |m: String| crate::Error::Internal(format!("embedded formats: {m}"));
69 let base = FormatRegistry {
71 entries: BTreeMap::new(),
72 types: TypeRegistry::reserved(built_ins),
73 };
74 let root = yaml::parse(text, 1).map_err(|e| fail(e.to_string()))?;
75 let registry = root
76 .get("registry")
77 .ok_or_else(|| fail("no registry".into()))?;
78 let own = Self::from_yaml_at(
79 registry,
80 root.get("types"),
81 FormatSource::Embedded,
82 grammar,
83 regexes,
84 &RepoPath::root(),
85 Some(&base),
86 )
87 .map_err(|findings| {
88 fail(
89 findings
90 .iter()
91 .map(|f| f.message.clone())
92 .collect::<Vec<_>>()
93 .join("; "),
94 )
95 })?;
96 Ok(base.layered(&own))
97 }
98
99 pub fn from_yaml(
104 registry: &Spanned,
105 source: FormatSource,
106 grammar: &Grammar,
107 regexes: &RegexCache,
108 ) -> std::result::Result<Self, Vec<Finding>> {
109 Self::from_yaml_at(
110 registry,
111 None,
112 source,
113 grammar,
114 regexes,
115 &RepoPath::root(),
116 None,
117 )
118 }
119
120 pub fn from_yaml_at(
131 registry: &Spanned,
132 types: Option<&Spanned>,
133 source: FormatSource,
134 grammar: &Grammar,
135 regexes: &RegexCache,
136 path: &RepoPath,
137 base: Option<&FormatRegistry>,
138 ) -> std::result::Result<Self, Vec<Finding>> {
139 let (out, findings) = Self::layer(registry, types, source, grammar, regexes, path, base);
141 if findings.is_empty() {
142 Ok(out)
143 } else {
144 Err(findings)
145 }
146 }
147
148 pub fn layer(
157 registry: &Spanned,
158 types: Option<&Spanned>,
159 source: FormatSource,
160 grammar: &Grammar,
161 regexes: &RegexCache,
162 path: &RepoPath,
163 base: Option<&FormatRegistry>,
164 ) -> (Self, Vec<Finding>) {
165 let fail = |line: usize, problem: Problem| problem.error(path.clone(), Some(line));
167 let mut findings = Vec::new();
168 let below = base.map(|b| b.types.clone()).unwrap_or_default();
169 let own_types = match types {
170 Some(node) => {
171 let (mut own, problems) = TypeRegistry::from_yaml(node, &source, &below);
173 findings.extend(problems.into_iter().map(|(l, m)| fail(l, m)));
174 let failed = node
175 .as_map()
176 .unwrap_or(&[])
177 .iter()
178 .map(|(name, _)| &name.value)
179 .filter(|name| !own.entries.contains_key(*name) && !below.reserves(name));
180 for name in failed.collect::<Vec<_>>() {
181 own.entries
182 .insert(name.clone(), (TypeDef::Regex(".*?".into()), source.clone()));
183 }
184 own
185 }
186 None => TypeRegistry::default(),
187 };
188 let seen = TypeRegistry::built_ins()
189 .layered(&below)
190 .layered(&own_types);
191 let mut out = FormatRegistry {
194 entries: BTreeMap::new(),
195 types: own_types,
196 };
197 let Some(entries) = registry.as_map() else {
199 findings.push(fail(
200 registry.line,
201 Problem::new("TL_101", "the format registry must be a map of formats"),
202 ));
203 return (out, findings);
204 };
205 for (name, whole) in entries {
206 let merged;
212 let entry = if whole.get("patch").and_then(|p| p.as_bool()) == Some(true) {
213 match base.and_then(|b| b.entries.get(&name.value)) {
214 Some((f, _)) => {
215 merged = merge_patch(&f.raw, whole);
216 &merged
217 }
218 None => {
219 findings.push(fail(
220 whole.line,
221 Problem::new(
222 "TL_41",
223 format!("format `{}` patches no farther entry", name.value),
224 ),
225 ));
226 continue;
227 }
228 }
229 } else {
230 whole
231 };
232 let mut problems =
234 validate::validate_map(entry, "format", grammar, regexes, path, None);
235 if problems.is_empty() {
236 match lower_format(entry, &seen, regexes) {
237 Ok(format) => {
238 out.entries
239 .insert(name.value.clone(), (format, source.clone()));
240 continue;
241 }
242 Err(said) => problems.extend(said.into_iter().map(|(l, m)| fail(l, m))),
243 }
244 }
245 for p in &mut problems {
247 p.message = format!("format `{}`: {}", name.value, p.message);
248 }
249 findings.extend(problems);
250 }
251 (out, findings)
252 }
253
254 pub fn types_seen(&self) -> TypeRegistry {
257 TypeRegistry::built_ins().layered(&self.types)
258 }
259
260 pub fn layered(&self, over: &FormatRegistry) -> FormatRegistry {
271 let types = self.types.layered(&over.types);
273 let seen = TypeRegistry::built_ins().layered(&types);
274 let regexes = RegexCache::new();
275 let mut entries = self.entries.clone();
276 let redefined: Vec<&str> = over
280 .types
281 .entries
282 .keys()
283 .filter(|n| self.types.entries.contains_key(*n))
284 .map(String::as_str)
285 .collect();
286 if !redefined.is_empty() {
287 for (format, _) in entries.values_mut() {
288 if format.uses_any(&redefined)
289 && let Ok(again) = lower_format(&format.raw, &seen, ®exes)
290 {
291 *format = again;
292 }
293 }
294 }
295 for (name, entry) in &over.entries {
297 entries.insert(name.clone(), entry.clone());
298 }
299 FormatRegistry { entries, types }
300 }
301
302 pub fn get(&self, name: &str) -> Option<&Format> {
305 self.entries.get(name).map(|(f, _)| f)
307 }
308
309 pub fn to_yaml(&self, built_ins: bool) -> String {
313 let types = if built_ins {
314 self.types_seen()
315 } else {
316 self.types.clone()
317 };
318 let mut type_map = Mapping::new();
319 for (name, (def, source)) in &types.entries {
320 let value = match def {
321 TypeDef::Regex(r) => ystr(r),
322 TypeDef::Choices(c) => SYaml::Sequence(c.iter().map(ystr).collect::<Sequence>()),
323 };
324 let mut entry = Mapping::new();
325 entry.insert(ystr("match"), value);
326 entry.insert(ystr("source"), ystr(source.label()));
327 type_map.insert(ystr(name), SYaml::Mapping(entry));
328 }
329 let mut registry = Mapping::new();
330 for (name, (format, source)) in &self.entries {
331 let mut entry = match to_saphyr(&format.raw) {
332 SYaml::Mapping(m) => m,
333 _ => Mapping::new(),
334 };
335 entry.insert(ystr("source"), ystr(source.label()));
336 registry.insert(ystr(name), SYaml::Mapping(entry));
337 }
338 let mut root = Mapping::new();
339 if !type_map.is_empty() {
340 root.insert(ystr("types"), SYaml::Mapping(type_map));
341 }
342 root.insert(ystr("registry"), SYaml::Mapping(registry));
343 let mut out = String::new();
344 let mut emitter = YamlEmitter::new(&mut out);
345 emitter.multiline_strings(true);
346 emitter
347 .dump(&SYaml::Mapping(root))
348 .expect("emitting to a string cannot fail");
349 out.push('\n');
350 out.strip_prefix("---\n").map(str::to_string).unwrap_or(out)
351 }
352}
353
354fn to_saphyr(node: &Spanned) -> SYaml<'static> {
355 match &node.value {
356 Yaml::Null => SYaml::Value(Scalar::Null),
357 Yaml::Bool(b) => SYaml::Value(Scalar::Boolean(*b)),
358 Yaml::Int(i) => SYaml::Value(Scalar::Integer(*i)),
359 Yaml::Float(f) => SYaml::Value(Scalar::FloatingPoint((*f).into())),
360 Yaml::Str(s) => ystr(s),
361 Yaml::Seq(items) => SYaml::Sequence(items.iter().map(to_saphyr).collect::<Sequence>()),
362 Yaml::Map(entries) => {
363 let mut m = Mapping::new();
364 for (k, v) in entries {
365 m.insert(ystr(&k.value), to_saphyr(v));
366 }
367 SYaml::Mapping(m)
368 }
369 }
370}
371
372fn ystr(s: impl AsRef<str>) -> SYaml<'static> {
374 SYaml::Value(Scalar::String(s.as_ref().to_string().into()))
375}