diff --git a/.travis.yml b/.travis.yml index e190a34..93cbc23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,8 @@ script: printf 'Checking for tabs in %s\n' "$TRAVIS_COMMIT_RANGE" ! git diff --name-only --diff-filter=ACMR "$TRAVIS_COMMIT_RANGE" | xargs grep $'\t' fi + - ( cd maud_htmlescape && cargo test --all-features ) + - if command -v cargo-clippy > /dev/null; then ( cd maud_htmlescape && cargo clippy -- -D warnings ); fi - ( cd maud && cargo test --all-features ) - if command -v cargo-clippy > /dev/null; then ( cd maud && cargo clippy -- -D warnings ); fi - ( cd maud_macros && cargo test --all-features ) diff --git a/Cargo.toml b/Cargo.toml index 123dc41..87876cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ - "maud", + "maud_htmlescape", "maud_macros", + "maud", "maud_extras", ] diff --git a/maud/Cargo.toml b/maud/Cargo.toml index bfcb7bf..3001991 100644 --- a/maud/Cargo.toml +++ b/maud/Cargo.toml @@ -13,6 +13,7 @@ description = "Compile-time HTML templates." categories = ["template-engine"] [dependencies] +maud_htmlescape = { version = "0.17.0", path = "../maud_htmlescape" } maud_macros = { version = "0.16.3", path = "../maud_macros" } iron = { version = "0.5.1", optional = true } rocket = { version = "0.3", optional = true } diff --git a/maud/src/lib.rs b/maud/src/lib.rs index 95b3d71..115d7df 100644 --- a/maud/src/lib.rs +++ b/maud/src/lib.rs @@ -13,6 +13,7 @@ #[cfg(feature = "iron")] extern crate iron; #[cfg(feature = "rocket")] extern crate rocket; +extern crate maud_htmlescape; extern crate maud_macros; use std::fmt::{self, Write}; @@ -121,52 +122,7 @@ impl<T: AsRef<str> + Into<String>> Into<String> for PreEscaped<T> { } } -/// An adapter that escapes HTML special characters. -/// -/// The following characters are escaped: -/// -/// * `&` is escaped as `&` -/// * `<` is escaped as `<` -/// * `>` is escaped as `>` -/// * `"` is escaped as `"` -/// -/// All other characters are passed through unchanged. -/// -/// **Note:** In versions prior to 0.13, the single quote (`'`) was -/// escaped as well. -/// -/// # Example -/// -/// ``` -/// # use maud::Escaper; -/// use std::fmt::Write; -/// let mut s = String::new(); -/// write!(Escaper::new(&mut s), "<script>launchMissiles()</script>").unwrap(); -/// assert_eq!(s, "<script>launchMissiles()</script>"); -/// ``` -pub struct Escaper<'a>(&'a mut String); - -impl<'a> Escaper<'a> { - /// Creates an `Escaper` from a `String`. - pub fn new(buffer: &'a mut String) -> Escaper<'a> { - Escaper(buffer) - } -} - -impl<'a> fmt::Write for Escaper<'a> { - fn write_str(&mut self, s: &str) -> fmt::Result { - for b in s.bytes() { - match b { - b'&' => self.0.push_str("&"), - b'<' => self.0.push_str("<"), - b'>' => self.0.push_str(">"), - b'"' => self.0.push_str("""), - _ => unsafe { self.0.as_mut_vec().push(b) }, - } - } - Ok(()) - } -} +pub use maud_htmlescape::Escaper; /// The literal string `<!DOCTYPE html>`. /// diff --git a/maud_htmlescape/Cargo.toml b/maud_htmlescape/Cargo.toml new file mode 100644 index 0000000..a5caae0 --- /dev/null +++ b/maud_htmlescape/Cargo.toml @@ -0,0 +1,15 @@ +[package] + +name = "maud_htmlescape" +# When releasing a new version, please update html_root_url in lib.rs +version = "0.17.0" +authors = ["Chris Wong <lambda.fairy@gmail.com>"] + +license = "MIT/Apache-2.0" +documentation = "https://docs.rs/maud_htmlescape/" +homepage = "https://maud.lambda.xyz/" +repository = "https://github.com/lfairy/maud" +description = "Internal support code used by Maud." + +[lib] +path = "lib.rs" diff --git a/maud_htmlescape/lib.rs b/maud_htmlescape/lib.rs new file mode 100644 index 0000000..873df13 --- /dev/null +++ b/maud_htmlescape/lib.rs @@ -0,0 +1,68 @@ +//! Internal support code used by the [Maud] template engine. +//! +//! You should not need to depend on this crate directly. +//! +//! [Maud]: https://maud.lambda.xyz + +#![doc(html_root_url = "https://docs.rs/maud_htmlescape/0.17.0")] + +use std::fmt; + +/// An adapter that escapes HTML special characters. +/// +/// The following characters are escaped: +/// +/// * `&` is escaped as `&` +/// * `<` is escaped as `<` +/// * `>` is escaped as `>` +/// * `"` is escaped as `"` +/// +/// All other characters are passed through unchanged. +/// +/// **Note:** In versions prior to 0.13, the single quote (`'`) was +/// escaped as well. +/// +/// # Example +/// +/// ```rust,ignore +/// use std::fmt::Write; +/// let mut s = String::new(); +/// write!(Escaper::new(&mut s), "<script>launchMissiles()</script>").unwrap(); +/// assert_eq!(s, "<script>launchMissiles()</script>"); +/// ``` +pub struct Escaper<'a>(&'a mut String); + +impl<'a> Escaper<'a> { + /// Creates an `Escaper` from a `String`. + pub fn new(buffer: &'a mut String) -> Escaper<'a> { + Escaper(buffer) + } +} + +impl<'a> fmt::Write for Escaper<'a> { + fn write_str(&mut self, s: &str) -> fmt::Result { + for b in s.bytes() { + match b { + b'&' => self.0.push_str("&"), + b'<' => self.0.push_str("<"), + b'>' => self.0.push_str(">"), + b'"' => self.0.push_str("""), + _ => unsafe { self.0.as_mut_vec().push(b) }, + } + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use std::fmt::Write; + use Escaper; + + #[test] + fn it_works() { + let mut s = String::new(); + write!(Escaper::new(&mut s), "<script>launchMissiles()</script>").unwrap(); + assert_eq!(s, "<script>launchMissiles()</script>"); + } +} diff --git a/maud_macros/Cargo.toml b/maud_macros/Cargo.toml index 5765624..40907eb 100644 --- a/maud_macros/Cargo.toml +++ b/maud_macros/Cargo.toml @@ -13,6 +13,7 @@ description = "Compile-time HTML templates." [dependencies] literalext = { version = "0.1", default-features = false, features = ["proc-macro"] } +maud_htmlescape = { version = "0.17.0", path = "../maud_htmlescape" } [lib] name = "maud_macros" diff --git a/maud_macros/src/lib.rs b/maud_macros/src/lib.rs index cbdbb18..409c7ab 100644 --- a/maud_macros/src/lib.rs +++ b/maud_macros/src/lib.rs @@ -4,6 +4,7 @@ #![doc(html_root_url = "https://docs.rs/maud_macros/0.16.3")] extern crate literalext; +extern crate maud_htmlescape; extern crate proc_macro; // TODO move lints into their own `maud_lints` crate diff --git a/maud_macros/src/render.rs b/maud_macros/src/render.rs index 04b5100..b286502 100644 --- a/maud_macros/src/render.rs +++ b/maud_macros/src/render.rs @@ -1,6 +1,7 @@ use proc_macro::{Literal, Term, TokenNode, TokenStream}; use proc_macro::quote; -use std::fmt; + +use maud_htmlescape::Escaper; pub struct Renderer { output: TokenNode, @@ -166,28 +167,3 @@ fn html_escape(s: &str) -> String { Escaper::new(&mut buffer).write_str(s).unwrap(); buffer } - -// TODO move this into a common `maud_htmlescape` crate -struct Escaper<'a>(&'a mut String); - -impl<'a> Escaper<'a> { - /// Creates an `Escaper` from a `String`. - pub fn new(buffer: &'a mut String) -> Escaper<'a> { - Escaper(buffer) - } -} - -impl<'a> fmt::Write for Escaper<'a> { - fn write_str(&mut self, s: &str) -> fmt::Result { - for b in s.bytes() { - match b { - b'&' => self.0.push_str("&"), - b'<' => self.0.push_str("<"), - b'>' => self.0.push_str(">"), - b'"' => self.0.push_str("""), - _ => unsafe { self.0.as_mut_vec().push(b) }, - } - } - Ok(()) - } -}