├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── lib.rs ├── main.rs └── pug.pest /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "arrayref" 5 | version = "0.3.5" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "block-buffer" 10 | version = "0.3.3" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 14 | "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "byte-tools" 19 | version = "0.2.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | 22 | [[package]] 23 | name = "cfg-if" 24 | version = "0.1.6" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | 27 | [[package]] 28 | name = "digest" 29 | version = "0.7.6" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | dependencies = [ 32 | "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 33 | ] 34 | 35 | [[package]] 36 | name = "fake-simd" 37 | version = "0.1.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | 40 | [[package]] 41 | name = "generic-array" 42 | version = "0.9.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | dependencies = [ 45 | "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 46 | ] 47 | 48 | [[package]] 49 | name = "lazy_static" 50 | version = "1.2.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | 53 | [[package]] 54 | name = "log" 55 | version = "0.4.6" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | dependencies = [ 58 | "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 59 | ] 60 | 61 | [[package]] 62 | name = "maplit" 63 | version = "1.0.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | 66 | [[package]] 67 | name = "pest" 68 | version = "2.1.0" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | dependencies = [ 71 | "ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 72 | ] 73 | 74 | [[package]] 75 | name = "pest_derive" 76 | version = "2.1.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | dependencies = [ 79 | "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 81 | ] 82 | 83 | [[package]] 84 | name = "pest_generator" 85 | version = "2.1.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | dependencies = [ 88 | "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "pest_meta 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", 93 | ] 94 | 95 | [[package]] 96 | name = "pest_meta" 97 | version = "2.1.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | dependencies = [ 100 | "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 101 | "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "proc-macro2" 107 | version = "0.4.24" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | dependencies = [ 110 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 111 | ] 112 | 113 | [[package]] 114 | name = "pug" 115 | version = "0.2.1" 116 | dependencies = [ 117 | "pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "wasm-bindgen 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 120 | ] 121 | 122 | [[package]] 123 | name = "quote" 124 | version = "0.6.10" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | dependencies = [ 127 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", 128 | ] 129 | 130 | [[package]] 131 | name = "sha-1" 132 | version = "0.7.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | dependencies = [ 135 | "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 139 | ] 140 | 141 | [[package]] 142 | name = "syn" 143 | version = "0.15.24" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | dependencies = [ 146 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", 147 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 149 | ] 150 | 151 | [[package]] 152 | name = "typenum" 153 | version = "1.10.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | 156 | [[package]] 157 | name = "ucd-trie" 158 | version = "0.1.1" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | 161 | [[package]] 162 | name = "unicode-xid" 163 | version = "0.1.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | 166 | [[package]] 167 | name = "wasm-bindgen" 168 | version = "0.2.31" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | dependencies = [ 171 | "wasm-bindgen-macro 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 172 | ] 173 | 174 | [[package]] 175 | name = "wasm-bindgen-backend" 176 | version = "0.2.31" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | dependencies = [ 179 | "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 180 | "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 181 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 183 | "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", 184 | "wasm-bindgen-shared 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 185 | ] 186 | 187 | [[package]] 188 | name = "wasm-bindgen-macro" 189 | version = "0.2.31" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | dependencies = [ 192 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 193 | "wasm-bindgen-macro-support 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 194 | ] 195 | 196 | [[package]] 197 | name = "wasm-bindgen-macro-support" 198 | version = "0.2.31" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | dependencies = [ 201 | "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "wasm-bindgen-backend 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 205 | "wasm-bindgen-shared 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", 206 | ] 207 | 208 | [[package]] 209 | name = "wasm-bindgen-shared" 210 | version = "0.2.31" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | 213 | [metadata] 214 | "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" 215 | "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" 216 | "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" 217 | "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" 218 | "checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" 219 | "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 220 | "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" 221 | "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" 222 | "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 223 | "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" 224 | "checksum pest 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "54f0c72a98d8ab3c99560bfd16df8059cc10e1f9a8e83e6e3b97718dd766e9c3" 225 | "checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 226 | "checksum pest_generator 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63120576c4efd69615b5537d3d052257328a4ca82876771d6944424ccfd9f646" 227 | "checksum pest_meta 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5a3492a4ed208ffc247adcdcc7ba2a95be3104f58877d0d02f0df39bf3efb5e" 228 | "checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09" 229 | "checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c" 230 | "checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded" 231 | "checksum syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)" = "734ecc29cd36e8123850d9bf21dfd62ef8300aaa8f879aabaa899721808be37c" 232 | "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" 233 | "checksum ucd-trie 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "71a9c5b1fe77426cf144cc30e49e955270f5086e31a6441dfa8b32efc09b9d77" 234 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 235 | "checksum wasm-bindgen 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "f45ea87af0efdd3b142836cf2ea233c938fbd0ead3a9da0fd240abd5303d1f5c" 236 | "checksum wasm-bindgen-backend 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "65aafeecd4dcb2385505cbd33479c08c961902eae2d19f8cdc09a27c3cd50e09" 237 | "checksum wasm-bindgen-macro 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "e3723ce26569facc66e1b53982a1035224d5c0d89a766eb3a45e56153e59b5e1" 238 | "checksum wasm-bindgen-macro-support 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "51ff31a6a49abf9743b2cac233bf83a33f24b154878ffdce5a41c8d11f91b3d9" 239 | "checksum wasm-bindgen-shared 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "98c697f76b3ab6a0e8abd4489ba81c1ad2eded87f8a871ea5593bb45323400c8" 240 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pug" 3 | version = "0.2.1" 4 | authors = ["Arvid E. Picciani "] 5 | edition = '2018' 6 | license = "MIT" 7 | description = "pug.js reimplemented in rust for speed" 8 | repository = "https://github.com/aep/pug-rs" 9 | readme = "README.md" 10 | keywords = ["pug", "jade", "template"] 11 | categories = ["command-line-utilities", "parsing", "web-programming"] 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [dependencies] 17 | pest = "2.1" 18 | pest_derive = "2.1" 19 | 20 | [target.'cfg(target_arch = "wasm32")'.dependencies] 21 | wasm-bindgen = "0.2" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DED 2 | ==================== 3 | 4 | since i no longer work with rust, this crate is canceled as well. 5 | it is fairly simple anyway and if i ever work with pug/jade again, i'll probably reimplement it in zetz. 6 | 7 | 8 | pug (jade) templates 9 | --------------------- 10 | 11 | reimplemented in rust for performance reasons. 12 | 13 | | pug.js | pug-rs | 14 | |--------|--------| 15 | | 780ms | 29ms | 16 | 17 | 18 | usage: 19 | ------- 20 | 21 | ``` 22 | $ cargo install pug 23 | $ pug < thing.pug > thing.html 24 | ``` 25 | 26 | 27 | with webpack: 28 | ------------ 29 | 30 | 31 | pug_loader.js: 32 | ```javascript 33 | const spawnSync = require('child_process').spawnSync; 34 | module.exports = function(source) { 35 | var proc = spawnSync("pug", { 36 | input: source 37 | }); 38 | if (proc.status != 0) { 39 | throw proc.error; 40 | } 41 | return proc.stdout.toString(); 42 | } 43 | ``` 44 | 45 | ``` 46 | module: { 47 | rules: [ 48 | { 49 | test: /\.pug$/, 50 | use: [require.resolve('./pug_loader.js')] 51 | }, 52 | 53 | ``` 54 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate pest_derive; 3 | 4 | use std::fmt::Debug; 5 | pub use pest::RuleType; 6 | use pest::Parser; 7 | use std::io::Write; 8 | 9 | #[derive(Debug)] 10 | pub enum Error { 11 | Parser(pest::error::Error), 12 | Include(E), 13 | Io(std::io::Error), 14 | } 15 | 16 | impl From> for Error { 17 | fn from(e : pest::error::Error) -> Self { 18 | Error::Parser(e) 19 | } 20 | } 21 | 22 | impl From for Error { 23 | fn from(e : std::io::Error) -> Self { 24 | Error::Io(e) 25 | } 26 | } 27 | 28 | #[cfg(target_arch = "wasm32")] 29 | use wasm_bindgen::prelude::*; 30 | 31 | #[derive(Parser)] 32 | #[grammar = "pug.pest"] 33 | pub struct PugParser; 34 | 35 | 36 | #[derive(Default, Debug)] 37 | pub struct Ast { 38 | pub children: Vec, 39 | pub id: Option, 40 | pub element: String, 41 | pub class: Vec, 42 | pub attrs: Vec, 43 | } 44 | 45 | impl Ast { 46 | pub fn special, B: Into>(element: A, id: B) -> Self { 47 | Self { 48 | element: element.into(), 49 | id: Some(id.into()), 50 | .. Default::default() 51 | } 52 | } 53 | 54 | pub fn expand(mut self, mut inc: F) -> Result> 55 | where E: Debug, 56 | F: Clone + FnMut(String) -> Result, 57 | { 58 | match self.element.as_ref() { 59 | ":include" => { 60 | return inc(self.id.as_ref().unwrap().to_string()).map_err(Error::Include)?.expand(inc.clone()); 61 | } 62 | _ => { 63 | for child in std::mem::replace(&mut self.children, Vec::new()) { 64 | eprintln!("{}", child.element); 65 | self.children.push(child.expand(inc.clone())?); 66 | }; 67 | Ok(self) 68 | } 69 | } 70 | } 71 | 72 | pub fn to_html(&self, w: &mut W) -> Result<(), std::io::Error> 73 | where W: Write, 74 | 75 | { 76 | self.to_html_i(w, &mut false) 77 | } 78 | 79 | fn to_html_i(&self, w: &mut W, previous_was_text: &mut bool) -> Result<(), std::io::Error> 80 | where W: Write, 81 | 82 | { 83 | match self.element.as_ref() { 84 | ":include" => { 85 | panic!("include cannot be written to html. forgot to call expand?"); 86 | } 87 | ":document" => { 88 | *previous_was_text = false; 89 | for child in &self.children { 90 | child.to_html_i(w, previous_was_text)?; 91 | } 92 | return Ok(()); 93 | } 94 | ":text" => { 95 | if *previous_was_text { 96 | w.write_all(b"\n")?; 97 | } 98 | *previous_was_text = true; 99 | w.write_all(self.id.as_ref().unwrap().as_bytes())?; 100 | return Ok(()); 101 | } 102 | ":doctype" => { 103 | *previous_was_text = false; 104 | w.write_all(b"")?; 107 | return Ok(()); 108 | } 109 | _ => { 110 | *previous_was_text = false; 111 | w.write_all(b"<")?; 112 | w.write_all(self.element.as_bytes())?; 113 | if !self.class.is_empty() { 114 | w.write_all(b" class=\"")?; 115 | w.write_all(self.class.join(" ").as_bytes())?; 116 | w.write_all(b"\"")?; 117 | } 118 | if let Some(ref id) = self.id { 119 | w.write_all(b" id=\"")?; 120 | w.write_all(id.as_bytes())?; 121 | w.write_all(b"\"")?; 122 | } 123 | for attr in &self.attrs { 124 | w.write_all(b" ")?; 125 | w.write_all(attr.as_bytes())?; 126 | } 127 | match self.element.as_ref() { 128 | "area"|"base"|"br"|"col"|"command"|"embed"|"hr"|"img"|"input"|"keygen"|"link"|"meta"|"param"|"source"|"track"|"wbr" => { 129 | w.write_all(b">")?; 130 | return Ok(()); 131 | }, 132 | _ => (), 133 | }; 134 | w.write_all(b">")?; 135 | } 136 | } 137 | 138 | for child in &self.children { 139 | child.to_html_i(w, previous_was_text)?; 140 | } 141 | 142 | w.write_all(b"")?; 145 | 146 | Ok(()) 147 | } 148 | } 149 | 150 | fn parse_impl(file: &str) -> Result> { 151 | let mut file = PugParser::parse(Rule::file, file)?; 152 | 153 | let mut comment = None; 154 | let mut indent = 0; 155 | 156 | let mut cur = Ast::default(); 157 | cur.element = ":document".into(); 158 | let mut stack : Vec<(usize, Ast)> = Vec::new(); 159 | 160 | for decl in file.next().unwrap().into_inner() { 161 | match decl.as_rule() { 162 | Rule::indent => { 163 | indent = decl.as_str().len(); 164 | if let Some(ind) = comment { 165 | if indent > ind { 166 | continue; 167 | } else { 168 | comment = None; 169 | } 170 | } 171 | 172 | while let Some((ind, mut ast)) = stack.pop() { 173 | if ind >= indent { 174 | ast.children.push(std::mem::replace(&mut cur, Ast::default())); 175 | cur = ast; 176 | } else { 177 | stack.push((ind,ast)); 178 | break; 179 | } 180 | } 181 | } 182 | Rule::include => { 183 | cur.children.push(Ast::special(":include", decl.into_inner().as_str())); 184 | } 185 | Rule::doctype => { 186 | cur.children.push(Ast::special(":doctype", decl.into_inner().as_str())); 187 | } 188 | Rule::tag => { 189 | eprintln!("tag: {}", decl.as_str()); 190 | if comment.is_some() { 191 | continue; 192 | } 193 | 194 | let parent = std::mem::replace(&mut cur, Ast::default()); 195 | stack.push((indent, parent)); 196 | 197 | cur.element = "div".into(); 198 | for e in decl.into_inner() { 199 | match e.as_rule() { 200 | Rule::element => { 201 | cur.element = e.as_str().to_string(); 202 | } 203 | Rule::class => { 204 | cur.class.push(e.into_inner().next().unwrap().as_str().to_string()); 205 | } 206 | Rule::id => { 207 | cur.id = Some(e.into_inner().next().unwrap().as_str().to_string()); 208 | } 209 | Rule::attrs => { 210 | for e in e.into_inner() { 211 | let mut e = e.into_inner(); 212 | let key = e.next().unwrap().as_str(); 213 | let value = e.next().unwrap(); 214 | if key == "id" { 215 | cur.id = Some( 216 | value.into_inner().next().unwrap().as_str().to_string(), 217 | ); 218 | } else if key == "class" { 219 | cur.class.push( 220 | value.into_inner().next().unwrap().as_str().to_string(), 221 | ); 222 | } else { 223 | cur.attrs.push(format!("{}={}", key, value.as_str())); 224 | } 225 | } 226 | } 227 | _ => unreachable!(), 228 | } 229 | } 230 | 231 | } 232 | Rule::comment => { 233 | if comment.is_some() { 234 | continue; 235 | } 236 | comment = Some(indent); 237 | } 238 | Rule::text => { 239 | if comment.is_some() { 240 | continue; 241 | } 242 | let text = decl.as_str().to_string(); 243 | cur.children.push(Ast::special(":text", text)); 244 | } 245 | Rule::EOI => { 246 | for (_, mut ast) in stack.drain(..).rev() { 247 | ast.children.push(std::mem::replace(&mut cur, Ast::default())); 248 | cur = ast; 249 | } 250 | } 251 | any => panic!(println!("parser bug. did not expect: {:?}", any)), 252 | } 253 | } 254 | 255 | Ok(cur) 256 | } 257 | 258 | /// parse a Pug template into an abstract syntax tree 259 | pub fn parse>(file: S) -> Result> { 260 | let mut file = file.into(); 261 | file.push('\n'); 262 | parse_impl(&file) 263 | } 264 | 265 | #[test] 266 | pub fn valid_identitifer_characters() { 267 | let mut html = Vec::new(); 268 | parse( 269 | r#"a(a="b",a-:.b.="c" 270 | x="y")"# 271 | ).unwrap().to_html(&mut html).unwrap(); 272 | assert_eq!(html, br#""#); 273 | } 274 | 275 | #[test] 276 | pub fn emptyline() { 277 | let mut html = Vec::new(); 278 | parse( 279 | r#" 280 | a 281 | b 282 | 283 | c 284 | 285 | "# 286 | ).unwrap().to_html(&mut html).unwrap(); 287 | assert_eq!(html, br#""#); 288 | } 289 | 290 | #[test] 291 | pub fn dupclass() { 292 | let mut html = Vec::new(); 293 | parse(r#"a#x.b(id="v" class="c")"#).unwrap().to_html(&mut html).unwrap(); 294 | assert_eq!( 295 | String::from_utf8_lossy(&html), 296 | r#""# 297 | ); 298 | } 299 | 300 | #[test] 301 | pub fn preserve_newline_in_multiline_text() { 302 | let mut html = Vec::new(); 303 | parse( 304 | r#"pre 305 | | The pipe always goes at the beginning of its own line, 306 | | not counting indentation. 307 | | lol look at me 308 | | getting all getho indent 309 | | watt"# 310 | ).unwrap().to_html(&mut html).unwrap(); 311 | 312 | 313 | assert_eq!( 314 | String::from_utf8_lossy(&html), 315 | r#"
The pipe always goes at the beginning of its own line,
316 | not counting indentation.
317 |   lol look at me
318 |   getting all getho indent
319 |     watt
"# 320 | ); 321 | } 322 | 323 | #[test] 324 | pub fn eoi() { 325 | let mut html = Vec::new(); 326 | parse( 327 | r#"body#blorp.herp.derp 328 | a(href="google.de") 329 | derp 330 | yorlo jaja"# 331 | ).unwrap().to_html(&mut html).unwrap(); 332 | 333 | assert_eq!( 334 | String::from_utf8_lossy(&html), 335 | r#"jaja"# 336 | ); 337 | 338 | let mut html = Vec::new(); 339 | parse( 340 | r#"body#blorp.herp.derp 341 | a(href="google.de") 342 | derp 343 | yorlo jaja 344 | "# 345 | ).unwrap().to_html(&mut html).unwrap(); 346 | 347 | assert_eq!( 348 | String::from_utf8_lossy(&html), 349 | r#"jaja"# 350 | ); 351 | 352 | let mut html = Vec::new(); 353 | parse( 354 | r#"body#blorp.herp.derp 355 | a(href="google.de") 356 | derp 357 | yorlo jaja 358 | 359 | 360 | 361 | "# 362 | ).unwrap().to_html(&mut html).unwrap(); 363 | assert_eq!( 364 | String::from_utf8_lossy(&html), 365 | r#"jaja"# 366 | ); 367 | } 368 | 369 | #[test] 370 | pub fn doctype() { 371 | let mut html = Vec::new(); 372 | parse( 373 | r#"doctype html 374 | html 375 | body 376 | "# 377 | ).unwrap().to_html(&mut html).unwrap(); 378 | assert_eq!( 379 | String::from_utf8_lossy(&html), 380 | r#""# 381 | ); 382 | } 383 | 384 | #[test] 385 | pub fn voidelements() { 386 | let mut html = Vec::new(); 387 | parse( 388 | r#" 389 | doctype html 390 | html 391 | head(lang="en") 392 | meta(charset="utf-8") 393 | title n1's personal site 394 | link(rel="stylesheet", href="normalize.css") 395 | link(rel="stylesheet", href="style.css") 396 | 397 | body 398 | .container 399 | "# 400 | ).unwrap().to_html(&mut html).unwrap(); 401 | 402 | assert_eq!( 403 | String::from_utf8_lossy(&html), 404 | r#"n1's personal site
"# 405 | ); 406 | } 407 | 408 | 409 | #[test] 410 | pub fn include_p() { 411 | let ast = parse("include ./a").unwrap(); 412 | assert_eq!( 413 | ast.children.len(), 414 | 1 415 | ); 416 | assert_eq!( 417 | ast.children[0].element, 418 | ":include" 419 | ); 420 | } 421 | 422 | 423 | #[test] 424 | pub fn include () { 425 | let f = |i:String| match i.as_ref() { 426 | "/a/1" => parse("include a"), 427 | _ => parse("| tomato"), 428 | }; 429 | let mut html = Vec::new(); 430 | parse( 431 | r#" 432 | doctype html 433 | kebab 434 | include /a/1 435 | "# 436 | ).unwrap().expand(f).unwrap().to_html(&mut html).unwrap(); 437 | assert_eq!( 438 | String::from_utf8_lossy(&html), 439 | r#"tomato"# 440 | ); 441 | } 442 | 443 | 444 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use pug::parse; 2 | use std::io::{self, Read}; 3 | 4 | fn main() { 5 | let mut buffer = String::new(); 6 | io::stdin().read_to_string(&mut buffer).unwrap(); 7 | parse(buffer).unwrap().to_html(&mut io::stdout()).unwrap(); 8 | } 9 | -------------------------------------------------------------------------------- /src/pug.pest: -------------------------------------------------------------------------------- 1 | ws1 = _{ (" " | "\t") } 2 | ws = _{ ws1 * } 3 | wsnl1 = _{ ws1 | "\n" } 4 | wsnl = _{ wsnl1* } 5 | identifier = { 6 | (ASCII_ALPHANUMERIC) ~ 7 | (ASCII_ALPHANUMERIC | "_" | "/" | "-" | ":" ) * 8 | } 9 | indent = { " " * } 10 | class = { "." ~ identifier } 11 | id = { "#" ~ identifier } 12 | element = { identifier } 13 | value = { string1 | string2 } 14 | string1 = _{ "'" ~ inner1 ~ "'" } 15 | inner1 = { char1* } 16 | char1 = { 17 | !("'" | "\\") ~ ANY 18 | | "\\" ~ ("'" | "\\" | "/" | "b" | "f" | "n" | "r" | "t") 19 | | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4}) 20 | } 21 | string2 = _{ "\"" ~ inner2 ~ "\"" } 22 | inner2 = { char2* } 23 | char2 = _{ 24 | !("\"" | "\\") ~ ANY 25 | | "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t") 26 | | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4}) 27 | } 28 | attr_name = { 29 | (ASCII_ALPHANUMERIC) ~ 30 | (ASCII_ALPHANUMERIC | ":" | "." | "_" | "/" | "-" ) * 31 | } 32 | attr = { attr_name ~ ws ~ "=" ~ ws ~ value ~ wsnl ~ ","? ~ wsnl} 33 | attrs = { ( "(" ~ wsnl ~ attr * ~ wsnl ~ ")" ) } 34 | anystr = {(ASCII_ALPHANUMERIC | ":" | "." | "_" | "/" | "-" ) *} 35 | doctype = {"doctype" ~ ws ~ anystr} 36 | include = {"include" ~ ws ~ anystr } 37 | tag = { 38 | (class | id | element ) ~ 39 | (class | id ) * ~ 40 | attrs ? 41 | } 42 | text = { (!("\n") ~ ANY)+ } 43 | plaintext = _{ "|" ~ ws1? ~ text } 44 | comment = { "//" ~ text } 45 | decl = _{ 46 | indent ~ 47 | ( doctype | 48 | include | 49 | comment | 50 | plaintext | 51 | tag ~ ( ws1+ ~ text?)? ) ~ 52 | ("\n") 53 | } 54 | emptyline = _{ ws1* ~ "\n" } 55 | file = { SOI ~ (decl | emptyline )* ~ EOI } 56 | --------------------------------------------------------------------------------