nate_common/
lib.rs

1// Copyright (c) 2021-2022 René Kijewski <crates.io@k6i.de>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![no_std]
16#![cfg_attr(feature = "docsrs", feature(doc_cfg))]
17#![forbid(unsafe_code)]
18#![allow(unused_attributes)]
19#![warn(absolute_paths_not_starting_with_crate)]
20#![warn(elided_lifetimes_in_paths)]
21#![warn(explicit_outlives_requirements)]
22#![warn(meta_variable_misuse)]
23#![warn(missing_copy_implementations)]
24#![warn(missing_debug_implementations)]
25#![warn(missing_docs)]
26#![warn(non_ascii_idents)]
27#![warn(noop_method_call)]
28#![warn(single_use_lifetimes)]
29#![warn(trivial_casts)]
30#![warn(unreachable_pub)]
31#![warn(unused_crate_dependencies)]
32#![warn(unused_extern_crates)]
33#![warn(unused_lifetimes)]
34#![warn(unused_results)]
35#![no_implicit_prelude]
36
37//! [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/Kijewski/nate/CI)](https://github.com/Kijewski/nate/actions/workflows/ci.yml)
38//! [![Crates.io](https://img.shields.io/crates/v/nate-common)](https://crates.io/crates/nate-common)
39//! [![License](https://img.shields.io/crates/l/nate-common?color=informational)](/LICENSES)
40//!
41//! Helper library for [NaTE](https://crates.io/crates/nate).
42//!
43//! This libary is used during the runtime of the generated code.
44//!
45//! ## Feature flags
46#![cfg_attr(feature = "docsrs", doc = ::document_features::document_features!())]
47
48#[doc(hidden)]
49pub mod details;
50mod raw_marker;
51
52use details::alloc;
53use details::std::fmt::{self, Arguments, Write as _};
54use details::std::prelude::v1::*;
55use details::std::write;
56pub use raw_marker::{EscapeTag, RawMarker, RawTag};
57
58#[doc(hidden)]
59pub trait WriteAny {
60    fn write_fmt(&mut self, fmt: Arguments<'_>) -> fmt::Result;
61}
62
63/// Optimized trait methods to render a NaTE template
64///
65/// Every NaTE template implements this trait.
66pub trait RenderInto {
67    #[doc(hidden)]
68    fn render_into(&self, output: impl WriteAny) -> fmt::Result;
69
70    /// Render the output into an fmt::Write object
71    #[inline]
72    fn render_fmt(&self, output: impl fmt::Write) -> fmt::Result {
73        self.render_into(details::WriteFmt(output))
74    }
75
76    /// Render the output into an io::Write object
77    #[cfg(feature = "alloc")]
78    #[cfg_attr(feature = "docsrs", doc(cfg(any(feature = "alloc", feature = "std"))))]
79    #[inline]
80    fn render_io(&self, output: impl alloc::io::Write) -> fmt::Result {
81        self.render_into(details::WriteIo(output))
82    }
83
84    /// Render the output into a new string
85    #[cfg(feature = "alloc")]
86    #[cfg_attr(feature = "docsrs", doc(cfg(any(feature = "alloc", feature = "std"))))]
87    fn render_string(&self) -> Result<alloc::string::String, fmt::Error> {
88        let mut result = String::new();
89        self.render_fmt(&mut result)?;
90        Ok(result)
91    }
92
93    /// Render the output into a new vector
94    #[cfg(feature = "alloc")]
95    #[cfg_attr(feature = "docsrs", doc(cfg(any(feature = "alloc", feature = "std"))))]
96    fn render_bytes(&self) -> Result<alloc::vec::Vec<u8>, fmt::Error> {
97        let mut result = Vec::new();
98        self.render_io(&mut result)?;
99        Ok(result)
100    }
101}
102
103/// A wrapper around a [displayable][fmt::Display] type that makes it write out XML escaped.
104///
105/// All characters are written as is except `"`, `&`, `'`, `<`, and `>` which are printed as e.g.
106/// `&#34;`.
107pub struct XmlEscape<T: ?Sized>(pub T);
108
109impl<T: ?Sized + fmt::Display> fmt::Display for XmlEscape<T> {
110    #[inline]
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        write!(XmlEscapeWriter(f), "{}", &self.0)
113    }
114}
115
116impl<T: ?Sized + fmt::Debug> fmt::Debug for XmlEscape<T> {
117    #[inline]
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(XmlEscapeWriter(f), "{:?}", &self.0)
120    }
121}
122
123struct XmlEscapeWriter<'a, 'b>(&'a mut fmt::Formatter<'b>);
124
125impl fmt::Write for XmlEscapeWriter<'_, '_> {
126    fn write_str(&mut self, mut s: &str) -> fmt::Result {
127        loop {
128            let mut done = true;
129            for (i, c) in s.as_bytes().iter().enumerate() {
130                let c = match c {
131                    b'"' => "&#34;",
132                    b'&' => "&#38;",
133                    b'\'' => "&#39;",
134                    b'<' => "&#60;",
135                    b'>' => "&#62;",
136                    _ => continue,
137                };
138                write!(self.0, "{}{}", &s[..i], c)?;
139                s = &s[i + 1..];
140                done = false;
141                break;
142            }
143            if done {
144                if !s.is_empty() {
145                    self.0.write_str(s)?;
146                }
147                break Ok(());
148            }
149        }
150    }
151
152    fn write_char(&mut self, c: char) -> fmt::Result {
153        self.0.write_str(match c {
154            '"' => "&#34;",
155            '&' => "&#38;",
156            '\'' => "&#39;",
157            '<' => "&#60;",
158            '>' => "&#62;",
159            c => return self.0.write_char(c),
160        })
161    }
162}