display_as/
lib.rs

1#![deny(missing_docs)]
2//! This template crate uses and defines a [`DisplayAs`] trait, which
3//! allows a type to be displayed in a particular format.
4//!
5//! # Overview
6//!
7//! This crate defines three things that you need be aware of in order
8//! to use it: the [`Format`] trait, which defines a markup language or
9//! other format, the [`DisplayAs`] trait which is implemented for any
10//! type that can be converted into some [`Format`], and finally the
11//! template language and macros which allow you to conveniently
12//! implement [`DisplayAs`] for your own types.  I will describe each of
13//! these concepts in order.  (**FIXME** I should also have a
14//! quick-start...)
15//!
16//! ## [`Format`]
17//!
18//! There are a number of predefined Formats (and I can easily add
19//! more if there are user requests), so the focus here will be on
20//! using these Formats, rather than on defining your own (which also
21//! isn't too hard).  A format is a zero-size type that has a rule for
22//! escaping strings and an associated MIME type.  The builtin formats
23//! include [`HTML`], [`LaTeX`], and [`Math`] (which is math-mode LaTeX).
24//!
25//! ## [`DisplayAs`]`<F>`
26//!
27//! The `[`DisplayAs`]<F: Format>` trait is entirely analogous to the [Display](std::fmt::Display) trait
28//! in the standard library, except that it is parametrized by a
29//! [`Format`] so you can have different representations for the same
30//! type in different formats.  This also makes it harder to
31//! accidentally include the wrong representation in your output.
32//!
33//! Most of the primitive types already have [`DisplayAs`] implemented
34//! for the included Formats.  If you encounter a type that you wish
35//! had [`DisplayAs`] implemented for a given format, just let me know.
36//! You can manually implement [`DisplayAs`] for any of your own types
37//! (it's not worse than implementing [Display](std::fmt::Display)) but that isn't how
38//! you are intended to do things (except perhaps in very simple
39//! cases, like a wrapper around an integer).  Instead you will want
40//! to use a template to implement [`DisplayAs`] for your own types.
41//!
42//! ## Templates!
43//!
44//! There are two template macros that you can use.  If you just want
45//! to get a string out of one or more [`DisplayAs`] objects, you will
46//! use something like `format_as!(HTML, "hello world" value).into_string()`.  If
47//! you want to implement [`DisplayAs`], you will use the attribute
48//! [`with_template!`].  In these examples I will use
49//! [`format_as!`] because that makes it easy to write testable
50//! documentation.  But in practice you will most likely primarily use
51//! the [with_template] attribute.
52//!
53//! ### String literals
54//!
55//! The first thing you can include in a template is a string literal,
56//! which is treated literally.
57//!
58//! ```
59//! use display_as::{HTML, format_as};
60//! assert_eq!(&format_as!(HTML, "Treat this literally <" ).into_string(),
61//!            "Treat this literally <");
62//! ```
63//!
64//! ### Expressions
65//!
66//! String literals are essential to representing some other [`Format`].
67//! To include your data in the output, you can include any expression
68//! that yields a type with [`DisplayAs`]`<F>` where `F` is your [`Format`].
69//! Each expression is delimited by string literals (or the other
70//! options below).  Note that since an expression is
71//!
72//! ```
73//! use display_as::{HTML, format_as};
74//! let s = "This is not a literal: <";
75//! assert_eq!(&format_as!(HTML, s ).into_string(),
76//!            "This is not a literal: &lt;");
77//! ```
78//!
79//! ### Blocks and conditionals
80//!
81//! You can use braces to enclose any template expression.  Any rust
82//! code before the braces is treated as literal rust.  This enables
83//! you to write conditionals, match expressions, and loops.
84//!
85//! ```
86//! use display_as::{HTML, format_as};
87//! assert_eq!(&format_as!(HTML,
88//!                        for i in 1..4 {
89//!                            "Counting " i "...\n"
90//!                        }
91//!                        "Blast off!").into_string(),
92//!            "Counting 1...\nCounting 2...\nCounting 3...\nBlast off!");
93//! ```
94//!
95//! ### Semicolons
96//!
97//! You may also play any rust statements you wish, if you end them
98//! with a semicolon.  This enables you to define local variables.
99//!
100//! ```
101//! use display_as::{HTML, format_as};
102//! assert_eq!(&format_as!(HTML, "I am counting " let count = 5;
103//!                              count " and again " count ).into_string(),
104//!            "I am counting 5 and again 5");
105//! ```
106//!
107//! ### Embedding a different format
108//!
109//! You can also embed in one format a representation from another
110//! type.  This can be helpful, for instance, if you want to use
111//! MathJax to handle LaTeX math embedded in an HTML file.
112//!
113//! ```
114//! use display_as::{HTML, Math, format_as};
115//! assert_eq!(&format_as!(HTML, "The number $" 1.2e12 as Math "$").into_string(),
116//!            r"The number $1.2\times10^{12}$");
117//! ```
118//!
119//! ### Saving a portion of a template for reuse
120//!
121//! You can also save a template expression using a let statement,
122//! provided the template expression is enclosed in braces.  This
123//! allows you to achieve goals similar to the base templates in
124//! Jinja2.  (Once we have an include feature... Example to come in
125//! the future.)
126//!
127//! ```
128//! use display_as::{HTML, format_as};
129//! assert_eq!(&format_as!(HTML,
130//!                        let x = 1;
131//!                        let announce = { "number " x };
132//!                        "The " announce " is silly " announce).into_string(),
133//!            "The number 1 is silly number 1");
134//! ```
135//!
136//! ## Differences when putting a template in a file
137//!
138//! You will most likely always put largish templates in a separate
139//! file.  This makes editing your template simpler and keeps things
140//! in general easier.  The template language for templates held in a
141//! distinct file has one difference from those shown above: the file
142//! always begins and ends with string literals, but their initial and
143//! final quotes respectively are omitted.  Furthermore, the first and
144//! last string literals must be "raw" literals with a number of #
145//! signs equal to the maximum used in the template.  I suggest using
146//! an equal number of # signs for all string literals in a given
147//! template.  Thus a template might look like:
148//!
149//! ```ignore
150//! <html>
151//!   <body>
152//!     "## self.title r##"
153//!   </body>
154//! </html>
155//! ```
156
157//! You can see that the quotes appear "inside out."  This is
158//! intentional, so that for most formats the quotes will appear to
159//! enclose the rust code rather than everything else, and as a result
160//! editors will hopefully be able to do the "right thing" for the
161//! template format (e.g. HTML in this case).
162
163//! ## Using `include!("...")` within a template
164//!
165//! Now I will demonstrate how you can include template files within
166//! other template files by using the `include!` macro within a
167//! template.  To demonstrate this, we will need a few template files.
168//!
169//! We will begin with a "base" template that describes how a page is
170//! laid out.
171//! #### `base.html`:
172//! ```ignore
173#![doc = include_str!("base.html")]
174//! ```
175//! We can have a template for how we will display students...
176//! #### `student.html`:
177//! ```ignore
178#![doc = include_str!("student.html")]
179//!```
180//! Finally, an actual web page describing a class!
181//! #### `class.html`:
182//! ```ignore
183#![doc = include_str!("class.html")]
184//! ```
185//! Now to put all this together, we'll need some rust code.
186//!
187//! ```
188//! use display_as::{DisplayAs, HTML, format_as, with_template};
189//! struct Student { name: &'static str };
190//! #[with_template("student.html")]
191//! impl DisplayAs<HTML> for Student {}
192//!
193//! struct Class { coursename: &'static str, coursenumber: usize, students: Vec<Student> };
194//! #[with_template("class.html")]
195//! impl DisplayAs<HTML> for Class {}
196//!
197//! let myclass = Class {
198//!       coursename: "Templates",
199//!       coursenumber: 365,
200//!       students: vec![Student {name: "David"}, Student {name: "Joel"}],
201//! };
202//! assert_eq!(&format_as!(HTML, myclass).into_string(), r#"<title>PH365: Templates</title>
203//! <html>
204//!   <ul><li><span class="student">Name: David</span>
205//!
206//! </li><li><span class="student">Name: Joel</span>
207//!
208//! </li></ul>
209//! </html>
210//!
211//!
212//! "#);
213//! ```
214
215extern crate display_as_proc_macro;
216extern crate mime;
217extern crate self as display_as;
218
219/// Use the given template to create a [`FormattedString`].
220///
221/// You can think of this as being kind of like [`format!`] on strange drugs.
222/// We return a [`FormattedString`] instaed of a [String] so that
223/// You can store the output and use it later in another template without
224/// having the contents escaped.
225///
226/// To obtain a [`String`], use the [`FormattedString::into_string`] method.
227pub use display_as_proc_macro::format_as;
228
229/// Write the given template to a file.
230///
231/// You can think of this as being kind of like [`write!`] on strange drugs.
232pub use display_as_proc_macro::write_as;
233
234/// Can I write doc here?
235pub use display_as_proc_macro::with_template;
236
237use std::fmt::{Display, Error, Formatter};
238
239#[macro_use]
240mod html;
241mod latex;
242mod mathlatex;
243mod rust;
244mod url;
245mod utf8;
246
247pub mod float;
248
249pub use crate::html::HTML;
250pub use crate::latex::LaTeX;
251pub use crate::mathlatex::Math;
252pub use crate::rust::Rust;
253pub use crate::url::URL;
254pub use crate::utf8::UTF8;
255
256/// Format is a format that we can use for displaying data.
257pub trait Format: Sync + Send + Copy + Eq + Ord + std::hash::Hash {
258    /// "Escape" the given string so it can be safely displayed in
259    /// this format.  The precise meaning of this may vary from format
260    /// to format, but the general sense is that this string does not
261    /// have any internal formatting, and must be displayed
262    /// appropriately.
263    fn escape(f: &mut Formatter, s: &str) -> Result<(), Error>;
264    /// The mime type of this format.
265    fn mime() -> mime::Mime;
266    /// Return an actual [`Format`] for use in [`As`] below.
267    fn this_format() -> Self;
268}
269
270/// This trait is analogous to [Display](std::fmt::Display), but will display the data in
271/// `F` [`Format`].
272pub trait DisplayAs<F: Format> {
273    /// Formats the value using the given formatter.
274    fn fmt(&self, f: &mut Formatter) -> Result<(), Error>;
275
276    /// Estimate the size of this when displayed
277    fn estimate_size(&self) -> usize {
278        4
279    }
280
281    /// Creates a display object
282    fn display<'a>(&'a self) -> As<'a, F, Self> {
283        As::from(self)
284    }
285}
286
287/// Create a Display object, which can be used with various web frameworks.
288pub fn display<'a, F: Format, T: DisplayAs<F>>(_f: F, x: &'a T) -> As<'a, F, T> {
289    x.display()
290}
291
292struct Closure<F: Format, C: Fn(&mut Formatter) -> Result<(), Error>> {
293    f: C,
294    _format: F,
295}
296/// Display the given closure as this format.
297///
298/// This is used internally in template handling.
299pub fn display_closure_as<F: Format>(
300    f: F,
301    c: impl Fn(&mut Formatter) -> Result<(), Error>,
302) -> impl DisplayAs<F> {
303    Closure { f: c, _format: f }
304}
305impl<F: Format, C: Fn(&mut Formatter) -> Result<(), Error>> DisplayAs<F> for Closure<F, C> {
306    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
307        (self.f)(f)
308    }
309}
310impl<F> PartialEq<FormattedString<F>> for str {
311    fn eq(&self, other: &FormattedString<F>) -> bool {
312        self == &other.inner
313    }
314}
315impl<F> PartialEq<str> for FormattedString<F> {
316    fn eq(&self, other: &str) -> bool {
317        &self.inner == other
318    }
319}
320#[test]
321fn test_closure() {
322    let x = |__f: &mut Formatter| -> Result<(), Error> {
323        __f.write_str("hello world")?;
324        Ok(())
325    };
326    assert_eq!(
327        "hello world",
328        &format_as!(HTML, display_closure_as(HTML, x))
329    );
330    assert_eq!(
331        &format_as!(HTML, display_closure_as(HTML, x)),
332        "hello world"
333    );
334}
335
336/// Choose to [Display](std::fmt::Display) this type using a particular [`Format`] `F`.
337pub struct As<'a, F: Format, T: DisplayAs<F> + ?Sized> {
338    inner: &'a T,
339    _format: F,
340}
341impl<'a, F: Format, T: DisplayAs<F> + ?Sized> From<&'a T> for As<'a, F, T> {
342    fn from(value: &'a T) -> Self {
343        As {
344            _format: F::this_format(),
345            inner: value,
346        }
347    }
348}
349impl<'a, F: Format, T: DisplayAs<F> + ?Sized> Display for As<'a, F, T> {
350    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
351        self.inner.fmt(f)
352    }
353}
354
355/// The `rouille` feature flag enables conversion of any `As<F,T>`
356/// type into a [rouille::Response].  Note that it is necessary to be
357/// explicit about the format because a given type `T` may be
358/// displayed in multiple different formats.
359#[cfg(feature = "rouille")]
360pub mod rouille {
361    extern crate rouille;
362    use super::{As, DisplayAs, Format};
363    impl<'a, F: Format, T: DisplayAs<F>> Into<rouille::Response> for As<'a, F, T> {
364        fn into(self) -> rouille::Response {
365            let s = format!("{}", &self);
366            rouille::Response::from_data(F::mime().as_ref().to_string(), s)
367        }
368    }
369}
370
371/// The `actix-web` feature flag makes any [`As`] type a
372/// [actix_web::Responder].
373#[cfg(feature = "actix-web")]
374pub mod actix {
375    extern crate actix_web;
376    use self::actix_web::{HttpRequest, HttpResponse, Responder};
377    use super::{As, DisplayAs, Format};
378    impl<'a, F: Format, T: 'a + DisplayAs<F>> Responder for As<'a, F, T> {
379        type Item = HttpResponse;
380        type Error = ::std::io::Error;
381        fn respond_to<S: 'static>(
382            self,
383            _req: &HttpRequest<S>,
384        ) -> Result<HttpResponse, Self::Error> {
385            Ok(HttpResponse::Ok()
386                .content_type(F::mime().as_ref().to_string())
387                .body(format!("{}", &self)))
388        }
389    }
390}
391
392/// The `gotham-web` feature flag makes any [`As`] type a
393/// [::gotham::handler::IntoResponse].
394#[cfg(feature = "gotham")]
395pub mod gotham {
396    use crate::{As, DisplayAs, Format};
397    use gotham::{
398        handler::IntoResponse,
399        hyper::{Body, Response, StatusCode},
400        state::State,
401    };
402
403    impl<'a, F: Format, T: 'a + DisplayAs<F>> IntoResponse for As<'a, F, T> {
404        fn into_response(self, state: &State) -> Response<Body> {
405            let s = format!("{}", self);
406            (StatusCode::OK, F::mime(), s).into_response(state)
407        }
408    }
409}
410
411/// The `warp` feature flag makes any [`DisplayAs`] type a [warp::Reply].
412#[cfg(feature = "warp")]
413pub mod warp {
414    use crate::{As, DisplayAs, Format};
415    impl<'a, F: Format, T: DisplayAs<F> + Sync> warp::Reply for As<'a, F, T> {
416        /// Convert into a [warp::Reply].
417        fn into_response(self) -> warp::reply::Response {
418            let s = format!("{}", self);
419            let m = F::mime().as_ref().to_string();
420            warp::http::Response::builder()
421                .header("Content-type", m.as_bytes())
422                .status(warp::http::StatusCode::OK)
423                .body(s)
424                .unwrap()
425                .map(warp::hyper::Body::from)
426        }
427    }
428
429    #[test]
430    fn test_warp() {
431        use crate::{display, HTML};
432        use warp::Reply;
433        // This sloppy test just verify that the code runs.
434        display(HTML, &"hello world".to_string()).into_response();
435    }
436}
437
438impl<F: Format> DisplayAs<F> for String {
439    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
440        F::escape(f, self)
441    }
442}
443impl<'a, F: Format> DisplayAs<F> for &'a String {
444    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
445        F::escape(f, self)
446    }
447}
448impl<F: Format> DisplayAs<F> for str {
449    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
450        F::escape(f, self)
451    }
452}
453impl<'a, F: Format> DisplayAs<F> for &'a str {
454    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
455        F::escape(f, self)
456    }
457}
458
459#[cfg(test)]
460mod tests {
461    use super::{format_as, HTML};
462    #[test]
463    fn html_escaping() {
464        assert_eq!(&format_as!(HTML, ("&")).into_string(), "&amp;");
465        assert_eq!(
466            &format_as!(HTML, ("hello &>this is cool")).into_string(),
467            "hello &amp;&gt;this is cool"
468        );
469        assert_eq!(
470            &format_as!(HTML, ("hello &>this is 'cool")).into_string(),
471            "hello &amp;&gt;this is &#x27;cool"
472        );
473    }
474}
475
476#[cfg(feature = "serde1")]
477use serde::{Deserialize, Serialize};
478
479/// A `String` that is formatted in `F`
480///
481/// The `serde1`` feature flag enables a [`FormattedString`] to be
482/// serialized and deserialized by [serde].
483#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
484#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
485#[cfg_attr(feature = "serde1", serde(transparent))]
486#[cfg_attr(feature = "serde1", serde(bound(deserialize = "F: Format")))]
487pub struct FormattedString<F> {
488    inner: String,
489    #[cfg_attr(feature = "serde1", serde(skip, default = "F::this_format"))]
490    _format: F,
491}
492
493impl<F: Format> FormattedString<F> {
494    /// Create a new `FormattedString` from an already-formatted `String`.
495    pub fn from_formatted<S: Into<String>>(s: S) -> Self {
496        FormattedString {
497            inner: s.into(),
498            _format: F::this_format(),
499        }
500    }
501    /// Convert back into a string
502    pub fn into_string(self) -> String {
503        self.inner
504    }
505    /// Reference a `&str` from this
506    pub fn as_str(&self) -> &str {
507        &self.inner
508    }
509}
510
511impl<F: Format> DisplayAs<F> for FormattedString<F> {
512    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
513        f.write_str(&self.inner)
514    }
515}
516impl<F: Format> std::fmt::Debug for FormattedString<F> {
517    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
518        write!(f, "{:?}", self.inner)
519    }
520}