1pub mod apply_node_policy;
7pub mod apply_resolved_publish_urls;
8pub mod flatten_svg;
9pub mod rasterize_svg_to_data_uri;
10pub mod rasterize_svg_to_local_asset;
11pub mod resolve_internal_links;
12mod shared;
13pub mod validate_document;
14pub mod walk;
15
16use anyhow::Result;
17use serde_json::Value as JsonValue;
18use std::collections::BTreeMap;
19
20use typub_ir::Document;
21
22#[derive(Debug, Clone, Default, PartialEq)]
24pub struct PassCtx {
25 pub diagnostics: Vec<Diagnostic>,
27 pub sidecar: BTreeMap<String, JsonValue>,
29}
30
31impl PassCtx {
32 pub fn push_diagnostic(&mut self, diagnostic: Diagnostic) {
33 self.diagnostics.push(diagnostic);
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum DiagnosticLevel {
39 Info,
40 Warning,
41 Error,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct Diagnostic {
46 pub pass: &'static str,
47 pub level: DiagnosticLevel,
48 pub message: String,
49 pub location: Option<String>,
50}
51
52impl Diagnostic {
53 pub fn error(pass: &'static str, message: String, location: Option<String>) -> Self {
54 Self {
55 pass,
56 level: DiagnosticLevel::Error,
57 message,
58 location,
59 }
60 }
61}
62
63pub trait Pass {
65 fn name(&self) -> &'static str;
66 fn run(&mut self, doc: &mut Document, ctx: &mut PassCtx) -> Result<()>;
67}
68
69pub use apply_node_policy::ApplyNodePolicyPass;
70pub use apply_resolved_publish_urls::ApplyResolvedPublishUrlsPass;
71pub use flatten_svg::FlattenSvgPass;
72pub use rasterize_svg_to_data_uri::RasterizeSvgToDataUriPass;
73pub use rasterize_svg_to_local_asset::{
74 RasterizeSvgToLocalAssetPass, SIDECAR_GENERATED_RENDER_ASSETS,
75};
76pub use resolve_internal_links::ResolveInternalLinksPass;
77pub use validate_document::ValidateDocumentPass;
78
79pub fn run_passes(
81 doc: &mut Document,
82 ctx: &mut PassCtx,
83 passes: &mut [&mut dyn Pass],
84) -> Result<()> {
85 for pass in passes {
86 pass.run(doc, ctx)?;
87 }
88 Ok(())
89}
90
91#[cfg(test)]
92mod tests {
93 #![allow(clippy::expect_used)]
94
95 use super::*;
96 use typub_ir::DocMeta;
97
98 struct MarkPass(&'static str);
99
100 impl Pass for MarkPass {
101 fn name(&self) -> &'static str {
102 self.0
103 }
104
105 fn run(&mut self, _doc: &mut Document, ctx: &mut PassCtx) -> Result<()> {
106 ctx.push_diagnostic(Diagnostic {
107 pass: self.name(),
108 level: DiagnosticLevel::Info,
109 message: "ran".to_string(),
110 location: None,
111 });
112 Ok(())
113 }
114 }
115
116 #[test]
117 fn run_passes_executes_in_order() {
118 let mut doc = Document {
119 blocks: Vec::new(),
120 footnotes: Default::default(),
121 assets: Default::default(),
122 meta: DocMeta::default(),
123 };
124 let mut ctx = PassCtx::default();
125 let mut pass_a = MarkPass("a");
126 let mut pass_b = MarkPass("b");
127 run_passes(&mut doc, &mut ctx, &mut [&mut pass_a, &mut pass_b]).expect("run passes");
128
129 assert_eq!(ctx.diagnostics.len(), 2);
130 assert_eq!(ctx.diagnostics[0].pass, "a");
131 assert_eq!(ctx.diagnostics[1].pass, "b");
132 }
133}