seam/assemble/
mod.rs

1use crate::{impl_clone_box, CloneBox, parse::tokens::Site};
2use std::{convert, error::Error, fmt::{self, Debug}};
3
4use colored::*;
5use unicode_width::UnicodeWidthStr;
6
7/// Error type for specific errors with generating
8/// each type of markup.
9#[derive(Debug, Clone)]
10pub struct GenerationError<'a> {
11    pub markup: &'static str,
12    pub message: String,
13    pub site: Site<'a>,
14}
15
16impl<'a> GenerationError<'a> {
17    /// Create a new error given the ML, the message, and the site.
18    pub fn new(ml: &'static str, msg: &str, site: &Site<'a>) -> Self {
19        Self {
20            markup: ml,
21            message: msg.to_owned(),
22            site: site.to_owned(),
23        }
24    }
25}
26
27/// Implement fmt::Display for user-facing error output.
28impl<'a> fmt::Display for GenerationError<'a> {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        let line_prefix = format!("  {} |", self.site.line);
31        let line_view = self.site.line_slice();
32        writeln!(f, "{} {}", line_prefix, line_view)?;
33        writeln!(f, "{:>prefix_offset$} {:~>text_offset$}{:^>length$}", "|", "", "",
34            prefix_offset=UnicodeWidthStr::width(line_prefix.as_str()),
35            text_offset=self.site.line_column() - 1,
36            length=self.site.width())?;
37        write!(f, "{}: {}",
38            format!("[{}] Error Generating {} ({}:{}:{})",
39                "**".red().bold(),
40                self.markup.bold(),
41                self.site.source,
42                self.site.line,
43                self.site.line_column(),
44            ).black(),
45            self.message)
46    }
47}
48
49/// Implements std::error::Error.
50impl<'a> Error for GenerationError<'a> { }
51
52/// Convert from an io::Error to a generation error.
53impl<'a> From<std::io::Error> for GenerationError<'a> {
54    fn from(e: std::io::Error) -> Self {
55        Self {
56            markup: "<markup>",
57            message: format!("IO error: {}", e),
58            site: Site::unknown(),
59        }
60    }
61}
62
63/// An fmt::Error can be cast to an equally horribly
64/// ambiguous GenerationError.
65impl<'a> convert::From<fmt::Error> for GenerationError<'a> {
66    fn from(e: fmt::Error) -> Self {
67        Self {
68            markup: "<markup>",
69            message: format!("Format buffer error: {}", e),
70            site: Site::unknown(),
71        }
72    }
73}
74
75pub type Formatter<'a> = &'a mut dyn fmt::Write;
76
77/// Trait for all structs that can generate specific markup
78/// for the s-expression tree.
79pub trait MarkupFormatter: Debug + CloneBox {
80    // Required definitions:
81    /// Similar to fmt in Display/Debug traits, takes in a
82    /// mutable writable buffer, returns success or a specifc
83    /// error while generating the markup.
84    fn generate(&self, buf: Formatter) -> Result<(), GenerationError>;
85    /// Documentises the input, that's to say, it adds any
86    /// extra meta-information to the generated markup, if
87    /// the s-expressions your wrote ommited it.
88    /// e.g. All XML gets a `<?xml ... ?>` tag added to it.
89    fn document(&self) -> Result<String, GenerationError>;
90    // Default definitions:
91    /// Directly converts the s-expressions into a string
92    /// containing the markup, unless there was an error.
93    fn display(&self) -> Result<String, GenerationError> {
94        let mut buf = String::new();
95        self.generate(&mut buf)?;
96        Ok(buf)
97    }
98}
99
100impl_clone_box! { 'a; dyn MarkupFormatter + 'a}
101
102/// Automatically implement fmt::Display as a wrapper around
103/// MarkupFormatter::generate, but throws away the useful error message.
104impl fmt::Display for dyn MarkupFormatter {
105    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106        self.generate(f).map_err(|_| fmt::Error)
107    }
108}
109
110/// Performs the following escapes:
111/// - `<` → `&lt;`
112/// - `>` → `&gt;`
113/// - `"` → `&quot;`
114/// - `'` → `&apos;`
115/// - `&` → `&amp;`
116pub fn escape_xml(string: &str) -> String {
117    let mut bytes = string.bytes();
118    let mut byte_builder: Vec<u8> = Vec::with_capacity(bytes.len());
119    while let Some(byte) = bytes.next() {
120        match byte {
121            b'<'  => byte_builder.extend(b"&lt;"),
122            b'>'  => byte_builder.extend(b"&gt;"),
123            b'"'  => byte_builder.extend(b"&quot;"),
124            b'\'' => byte_builder.extend(b"&apos;"),
125            b'&'  => byte_builder.extend(b"&amp;"),
126            _ => byte_builder.push(byte)
127        }
128    }
129    unsafe {
130        String::from_utf8_unchecked(byte_builder)
131    }
132}
133
134/// Re-constitute original S-expressions.
135pub mod sexp;
136/// Converts source into expanded plain-text.
137pub mod text;
138/// XML generation.
139pub mod xml;
140/// HTML5 CSS generation.
141pub mod css;
142/// HTML5 HTML generation.
143pub mod html;