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}