1mod ast;
2mod desugar;
3mod error;
4mod expr;
5mod fmt;
6mod icon;
7mod layout;
8mod lexer;
9mod lint;
10mod palette;
11mod render;
12mod resolve;
13mod serve;
14mod span;
15mod syntax;
16mod theme;
17
18pub use error::{Diagnostic, Error, Level};
19pub use fmt::format as format_source;
20
21pub fn desugar_source(src: &str) -> Result<String, Error> {
28 let tokens = lexer::lex(src)?;
29 let file = syntax::parser::parse(&tokens)?;
30 Ok(fmt::print_file(&desugar::desugar(&file)?))
31}
32pub use layout::{Rule, Severity, Violation};
33pub use serve::{ServeTarget, serve};
34pub use theme::{builtin_css, extract_lini_vars, list_themes, pair_css};
35
36#[derive(Clone, Debug, Default)]
40pub struct Options {
41 pub bake_vars: bool,
46 pub format: OutputFormat,
48 pub theme_css: Option<String>,
51}
52
53#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
54pub enum OutputFormat {
55 #[default]
56 Svg,
57 Html,
58}
59
60pub fn compile_str(src: &str) -> Result<String, Error> {
61 compile_str_with(src, &Options::default())
62}
63
64pub fn compile_str_with(src: &str, opts: &Options) -> Result<String, Error> {
65 let program = resolve_pipeline(src, opts)?;
66 let mut laid_out = layout::layout(&program)?;
67 render::lower_gradients(&mut laid_out);
68 Ok(finish_svg(&laid_out, opts))
69}
70
71pub fn compile_str_checked(src: &str, opts: &Options) -> Result<(String, Vec<Diagnostic>), Error> {
75 let program = resolve_pipeline(src, opts)?;
76 let mut laid_out = layout::layout(&program)?;
77 render::lower_gradients(&mut laid_out);
78 let diags = routing_diagnostics_of(layout::validate_routing(&laid_out));
79 Ok((finish_svg(&laid_out, opts), diags))
80}
81
82fn finish_svg(laid_out: &layout::LaidOut, opts: &Options) -> String {
83 let svg = render::render(laid_out, opts);
84 match opts.format {
85 OutputFormat::Svg => svg,
86 OutputFormat::Html => wrap_html(&svg),
87 }
88}
89
90pub fn check_parse(src: &str) -> Result<(), Error> {
93 let tokens = lexer::lex(src)?;
94 let _file = syntax::parser::parse(&tokens)?;
95 Ok(())
96}
97
98pub fn lint_str(src: &str) -> Result<Vec<Diagnostic>, Error> {
101 let tokens = lexer::lex(src)?;
102 let file = syntax::parser::parse(&tokens)?;
103 Ok(lint::lint(&file))
104}
105
106pub fn check(src: &str) -> Result<(), Error> {
109 check_with(src, &Options::default())
110}
111
112pub fn check_with(src: &str, opts: &Options) -> Result<(), Error> {
113 let _ = resolve_pipeline(src, opts)?;
114 Ok(())
115}
116
117pub fn validate_str(src: &str) -> Result<Vec<Violation>, Error> {
121 let program = resolve_pipeline(src, &Options::default())?;
122 let laid_out = layout::layout(&program)?;
123 Ok(layout::validate_routing(&laid_out))
124}
125
126fn routing_diagnostics_of(violations: Vec<Violation>) -> Vec<Diagnostic> {
131 violations
132 .into_iter()
133 .filter(|v| v.severity != Severity::Info)
134 .map(|v| {
135 Diagnostic::warn(
136 v.span,
137 format!("{} ({}): {}", v.rule.id(), v.links.join(", "), v.detail),
138 )
139 })
140 .collect()
141}
142
143fn resolve_pipeline(src: &str, opts: &Options) -> Result<resolve::Program, Error> {
144 let tokens = lexer::lex(src)?;
145 let file = syntax::parser::parse(&tokens)?;
146 let lowered = desugar::desugar(&file)?;
147 let theme = match &opts.theme_css {
148 Some(css) => theme::extract_lini_vars(css),
149 None => Vec::new(),
150 };
151 resolve::resolve_with_theme(&lowered, &theme)
152}
153
154fn wrap_html(svg: &str) -> String {
155 format!(
156 "<!doctype html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <title>lini</title>\n</head>\n<body>\n{}</body>\n</html>\n",
157 svg
158 )
159}
160
161#[doc(hidden)]
164pub mod testing {
165 use crate::Options;
166 use crate::layout;
167 use crate::resolve::ResolvedValue;
168
169 pub use crate::layout::LaidOut;
170
171 pub fn node_rect(laid: &LaidOut, path: &str) -> Option<(f64, f64, f64, f64)> {
173 layout::node_rect(&laid.nodes, path)
174 }
175
176 pub fn route_sample(src: &str, clearance: f64) -> LaidOut {
180 layout::layout(&forced(src, clearance)).expect("layout")
181 }
182
183 pub fn route_sample_raw(src: &str, clearance: f64) -> LaidOut {
188 layout::layout_raw(&forced(src, clearance)).expect("layout")
189 }
190
191 fn forced(src: &str, clearance: f64) -> crate::resolve::Program {
192 let mut prog = super::resolve_pipeline(src, &Options::default()).expect("resolve");
193 for w in &mut prog.links {
194 w.attrs
195 .insert("clearance", ResolvedValue::Number(clearance));
196 }
197 prog
198 }
199
200 pub fn declared_edges(src: &str) -> usize {
203 let prog = super::resolve_pipeline(src, &Options::default()).expect("resolve");
204 prog.links
205 .iter()
206 .map(|w| w.endpoints.len().saturating_sub(1))
207 .sum()
208 }
209
210 pub fn laws(laid: &LaidOut) -> Vec<crate::Violation> {
212 layout::validate_routing(laid)
213 }
214}