mintyml/lib.rs
1//! This library exists to convert [MinTyML](https://youngspe.github.io/mintyml)
2//! (for <u>Min</u>imalist H<u>TML</u>) markup to its equivalent HTML.
3//!
4//! This should be considered the reference implementation for MinTyML.
5#![cfg_attr(not(feature = "std"), no_std)]
6
7extern crate alloc;
8extern crate derive_more;
9extern crate either;
10extern crate gramma;
11
12pub(crate) mod ast;
13pub(crate) mod config;
14pub(crate) mod document;
15pub mod error;
16pub(crate) mod escape;
17pub(crate) mod inference;
18pub(crate) mod output;
19pub(crate) mod transform;
20pub(crate) mod utils;
21
22use alloc::{borrow::Cow, string::String};
23use core::{borrow::Borrow, fmt};
24use error::{Errors, InternalError};
25
26use document::Document;
27use output::OutputError;
28
29pub use config::{MetadataConfig, OutputConfig, SpecialTagConfig};
30
31pub use error::ConvertError;
32#[deprecated]
33#[doc(hidden)]
34pub use error::{SyntaxError, SyntaxErrorKind};
35
36type Src<'src> = Cow<'src, str>;
37
38/// Converts the given MinTyML string `src` using `config` for configuration options.
39/// If successful, returns a string containing the converted HTML document.
40///
41/// # Example
42///
43/// ```
44/// # use mintyml::OutputConfig;
45/// let out = mintyml::convert(r#"
46/// {
47/// Hello there,
48/// world!
49/// img[src="./pic.png"]>
50///
51/// > Click <(a.example-link[href=www.example.com]> here )>
52/// for more.
53/// .empty>
54/// .foo#bar.baz> Goodbye
55/// }
56/// "#, OutputConfig::new()).unwrap();
57///
58/// assert_eq!(out, concat!(
59/// r#"<div>"#,
60/// r#"<p>Hello there, world!</p>"#,
61/// r#" <img src="./pic.png">"#,
62/// r#" <p>Click <a class="example-link" href="www.example.com">here</a> for more.</p>"#,
63/// r#" <p class="empty"></p>"#,
64/// r#" <p id="bar" class="foo baz">Goodbye</p>"#,
65/// r#"</div>"#,
66/// ));
67/// ```
68pub fn convert<'src>(
69 src: &'src str,
70 config: impl Borrow<OutputConfig<'src>>,
71) -> Result<String, ConvertError<'src>> {
72 let mut out = String::new();
73 convert_to_internal(src, config.borrow(), &mut out, false)?;
74 Ok(out)
75}
76
77/// Similar to [`convert`], but may return a best-effort conversion of an ill-formed document
78/// in the event of an error.
79///
80/// # Example
81///
82/// ```
83///
84/// # use mintyml::OutputConfig;
85/// let (out, _err) = mintyml::convert_forgiving(r#"
86/// table {
87/// {
88/// > Cell A
89/// > Cell B
90/// {
91/// > Cell C
92/// > Cell D
93/// }
94/// }
95/// "#, OutputConfig::new()).unwrap_err();
96///
97/// assert_eq!(out.unwrap(), concat!(
98/// r#"<table>"#,
99/// r#"<tr><td>Cell A</td> <td>Cell B</td>"#,
100/// r#" <td><p>Cell C</p> <p>Cell D</p></td></tr>"#,
101/// r#"</table>"#,
102/// ));
103/// ```
104pub fn convert_forgiving<'src>(
105 src: &'src str,
106 config: impl Borrow<OutputConfig<'src>>,
107) -> Result<String, (Option<String>, ConvertError<'src>)> {
108 let mut out = String::new();
109 match convert_to_internal(src, config.borrow(), &mut out, true) {
110 Ok(()) => Ok(out),
111 Err(err) if out.is_empty() => Err((None, err)),
112 Err(err) => Err((Some(out), err)),
113 }
114}
115
116/// Converts the given MinTyML string `src` using `config` for configuration options.
117/// The converted HTML document will be written to `out`.
118///
119/// # Example
120///
121/// ```
122/// # use mintyml::OutputConfig;
123/// let mut out = String::new();
124/// mintyml::convert_to(r#"
125/// {
126/// Hello there,
127/// world!
128/// img[src="./pic.png"]>
129///
130/// > Click <(a.example-link[href=www.example.com]> here )>
131/// for more.
132/// .empty>
133/// .foo#bar.baz> Goodbye
134/// }
135/// "#, OutputConfig::new(), &mut out).unwrap();
136///
137/// assert_eq!(out, concat!(
138/// r#"<div>"#,
139/// r#"<p>Hello there, world!</p>"#,
140/// r#" <img src="./pic.png">"#,
141/// r#" <p>Click <a class="example-link" href="www.example.com">here</a> for more.</p>"#,
142/// r#" <p class="empty"></p>"#,
143/// r#" <p id="bar" class="foo baz">Goodbye</p>"#,
144/// r#"</div>"#,
145/// ));
146/// ```
147pub fn convert_to<'src>(
148 src: &'src str,
149 config: impl Borrow<OutputConfig<'src>>,
150 out: &mut impl fmt::Write,
151) -> Result<(), ConvertError<'src>> {
152 convert_to_internal(src, config.borrow(), out, true)
153}
154
155fn convert_to_internal<'src>(
156 src: &'src str,
157 config: &OutputConfig<'src>,
158 out: &mut impl fmt::Write,
159 forgive: bool,
160) -> Result<(), ConvertError<'src>> {
161 let mut errors = Errors::new(config);
162
163 let (Ok(()) | Err(InternalError)) = (|| {
164 let mut document = Document::parse(src, &mut errors)?;
165 document = transform::transform_document(document, src, config, &mut errors)?;
166
167 if errors.is_empty() || forgive {
168 output::output_html_to(src, &document, out, config)
169 .map_err(|e| match e {
170 OutputError::WriteError(fmt::Error) => ConvertError::Unknown,
171 })
172 .or_else(|_| errors.unknown())?;
173 }
174
175 Ok(())
176 })();
177
178 errors.to_convert_error(src)
179}