diff --git a/.gitignore b/.gitignore index 6e858b2..e46892d 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,6 @@ Cargo.lock *.pdb # End of https://www.toptal.com/developers/gitignore/api/rust,clion + +run/ +src/templates/ diff --git a/Cargo.toml b/Cargo.toml index 56bad3e..4f44608 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "mrpc" version = "0.1.0" edition = "2021" +[build-dependencies] +ructe = { path = "ructe-0.17.0" } + [dependencies] codespan-reporting = "0.11.1" once_cell = "1.18.0" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e13f6a2 --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() -> ructe::Result<()> { + ructe::Ructe::new("src/templates".into())?.compile_templates("templates") +} diff --git a/ructe-0.17.0/.gitignore b/ructe-0.17.0/.gitignore new file mode 100644 index 0000000..ea0625d --- /dev/null +++ b/ructe-0.17.0/.gitignore @@ -0,0 +1,4 @@ +*~ + +target +Cargo.lock diff --git a/ructe-0.17.0/CHANGELOG.md b/ructe-0.17.0/CHANGELOG.md new file mode 100644 index 0000000..f5fabb2 --- /dev/null +++ b/ructe-0.17.0/CHANGELOG.md @@ -0,0 +1,556 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on +[Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this +project adheres to +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## Release 0.17.0 -- 2023-07-22 + +* Added a check that no more than one of the http-types, mime02, or + mime03 features are enabled (PR #124). Thanks @rustafarian-dev. +* Changed the writer type from `W: &mut Write` to just `W: Write` (PR #125). + Thanks @kornelski! +* Fixed handling of `MULTI_WORD_CONSTANTS` in templates (Issue #129, PR #130). + Thanks @wezm! +* More ways to create a working rust symbol name from a "strange" + static file name. Illegal characters are replaced by `_`, and if + the file name starts with a number it is prefixed with `n` (Issue + #82, PR #132). Thanks @Aedius for reporting! +* Fixed more clippy lints (PR #123, #127). Thanks @vbrandl! +* Updated `rsass` to 0.28.0 and `itertools` to 0.11.0. + + +## Release 0.16.1 -- 2023-01-28 + +* Msrv is 1.58.1, so let ructe itself use rust edition 2021. +* Use format strings with inline captures (in ructe itself and + generated code). + + +## Release 0.16.0 -- 2023-01-22 + +* Removed backwards compatible aliases for template functions. + In ructe 0.7.2 and earlier, a template file `page.rs.html` resulted + in a rust function `templates::page(...)`. + In 0.7.2, that was changed to `templates::page_html(...)` and the + old name was kept as a deprecated alias. + However, since the template functions are usually defined within the + same crate that defines them, the deprecation warning has usually + not been shown, and this removal may still be a surprise to some + users (it was even used in examples up to this change). +* Allow more lifetime arguments to templates in template arguments (PR + #122, fixes #121). Thanks to @wezm! +* Added axum example (PR #118). Thanks to @vbrandl! +* Updated rsass to 0.27.0 and base64 to 0.21.0. +* Updated dependencies in examples: actix-web 4.2.1, axum 0.6.2, + env_logger 0.10.0, +* Dropped support for rust edition 2015 in crates that directly uses + ructe. + + +## Releaase 0.15.0 + +* Breaking change: Most methods of `StaticFiles` now supports method + chaining, by returning `Result<&mut Self>`, making typical build scripts + nicer (PR #115). +* Update (optional) rsass to 0.26 (PR #116). +* Some doc improvements. + + +## Release 0.14.2 - 2022-08-20 + +* Improve error reporting. The debug output for `RucteError` is now the + same as display, and the standard `Error::source` is implemented. +* Fix clippy lint clippy::get-first (PR #114). +* Update optional rsass to 0.25.0. + +Thanks to @vbrandl for PR #114. + + +## Release 0.14.0 - 2022-02-06 + +* Breaking change: The generated template functions have a simpler + signature. +* Allow litetimes in template argument types. Issue #106, PR #110. +* Improve error handling in optional warp support, PR #109. +* Current stable rust is 1.57, MSRV is now 1.46.0. +* Update nom dependency to 7.1.0. +* Update optional rsass to 0.23.0. +* Update env_logger to 0.9 and gotham to 0.7.1 in examples +* Dropped support for warp 0.2 (the warp02 feature and example). + +Thanks to @JojiiOfficial for reporting #106. + + +## Release 0.13.4 - 2021-06-25 + +* Allow `else if` after an `@if` block in templates. PR #104, fixes #81. +* Add a missing `}` in doc example. PR #102. +* Update optional rsass to 0.22.0. +* Updated gotham example to 0.6.0. + +Thanks @bearfrieze for #102 and @Aunmag for #81. + +Tested with rustc 1.53.0, 1.48.0, 1.46.0, 1.44.1, 1.54.0-beta.1, +and 1.55.0-nightly (7c3872e6b 2021-06-24). + + +## Release 0.13.2 - 2021-03-14 + +* Improve formatting of README, PR #100. +* Update nom to 6.1.0, which raises the MSRV to 0.44 +* Update base64 to 0.13 and itertools to 0.10. +* Update optional rsass to 0.19.0. +* Add warp 0.3 feature and example. +* Add tide 0.16 feaure and update example. +* Testing is now done with github actions rather than Travis CI. +* Minor clippy fixes, PR #99. + +Thanks to @ibraheemdev for PR #100. + +Tested with rustc 1.50.0 (cb75ad5db 2021-02-10), +1.48.0 (7eac88abb 2020-11-16), +1.46.0 (04488afe3 2020-08-24), +1.44.1 (c7087fe00 2020-06-17), +1.51.0-beta.6 (6a1835ad7 2021-03-12), +1.52.0-nightly (acca81892 2021-03-13) + + +## Release 0.13.0 - 2020-11-15 + +* Try to improve incremental compile times of projects using ructe by + only writing fils if their contents actually changed. Also some code + cleanup. PR #97. +* Update ructe itself to use edition 2018 (it is still useable for + projects using both editios). PR #98. +* Fix `StaticFiles::add_files_as` for empty `to` argument and add some + more documentation for it. Fixes issue #96. +* Update optional rsass dependency to 0.16.0. +* Add optional support for tide 0.14 and 0.15. +* Update gotham to 0.5 and axtix-web to 3.2 in examples. + +Tested with rustc 1.47.0 (18bf6b4f0 2020-10-07), +1.42.0 (b8cedc004 2020-03-09), 1.40.0 (73528e339 2019-12-16), +1.48.0-beta.8 (121901459 2020-11-08), and +1.50.0-nightly (98d66340d 2020-11-14) + + +## Release 0.12.0 - 2020-08-14 + +* PR #80 and #94: Support Tide framework by a feature and an example. +* PR #91: Update basic examples to edition 2018. +* Issue #68, PR #90: Don't eat whitespace after a for loop. +* Issue #66, PR #89: Fix parse error for nested braces in expressions. +* PR #84: Use std::ascii::escape_default. +* PR #87: Provide ToHtml::to_buffer() +* Forbid unsafe and undocumented code. +* The build is on https://travis-ci.com/kaj/ructe now. +* Internal cleanup. + +## Release 0.11.4 - 2020-04-25 + +* Improve `@match` parsing. + +## Release 0.11.2 - 2020-04-22 + +* Bugfix: Allow space before laste brace in `@match`. + +## Release 0.11.0 - 2020-04-21 + +* PR #73, Issue #38: Add support for `@match` statements. + +Thanks to @vivlim for the issue. + +## Release 0.10.0 - 2020-04-19 + +* Update rsass to 0.13.0 and improve sass error handling. +* Drop the warp01 feature. +* PR #72 from @kornelski: Avoid clobbering variable name. +* Update itertools to 0.9.0 and base64 to 0.12.0. + +Thanks to @kornelski for suggestions and bug reports. + + +## Release 0.9.2 - 2020-01-25 + +* PR #70, Issue #63: Add feature warp02, supportig warp 0.2.x, and add + a name alias warp01 for the old warp 0.1.x feature. Same in + examples. +* PR #69, Issue #67: Anyting that is allowed in a string in Rust + should be allowed in a string in ructe. +* Fix clippy complaints re statics in generated code. +* Update actix-web example to 2.0. +* Fix doctest with mime03 feature. + +Thanks to @nocduro and @Aunmag for suggestions and bug reports. + + +## Release 0.9.0 - 2019-12-25 + +* PR #65, Issue #64: An expression starting with paren ends on close. + BREAKING: Before this change, calling a function on the result of + some subexpression could be written as `@(a - b).abs()`. After this + change, that should be changed to `@((a - b).abs())` unless the + intent is to have the result of (a - b) followed by the template + string `.abs()`. +* RucteError now implements std::error::Error. +* Specify which references in examples are `dyn` or `impl`. +* Remove a useless string clone. +* Update rsass to 0.12.0. + +Thanks to @Aunmag. + + +## Release 0.8.0 - 2019-11-06 + +* Issue #62: New version number due to a semver-breaking change, + reported by @kornelski. + +Otherwise same as 0.7.4: + +* PR #55 from kornelski: Improve benchmarks. +* Part of issue #20: Allow template source files to be named *.rs.svg + or *.rs.xml as well as *.rs.html. The generated template functions + will simlilarly be suffixed _svg, _xml or _html (any template_html + will get a template alias, for backwards compatibility. +* PR #61 from Eroc33: Improve parsing for tuple and generic type + expressions. +* Fix old doc link in readme. +* Update dependencies in ructe and examples. + +Thaks to @kornelski and @Eroc33. + + +## Redacted: Relase 0.7.4 - 2019-11-02 + +* PR #55 from kornelski: Improve benchmarks. +* Part of issue #20: Allow template source files to be named + `*.rs.svg` or `*.rs.xml` as well as `*.rs.html`. The generated + template functions will simlilarly be suffixed `_svg`, `_xml` or + `_html` (any `template_html` will get a `template` alias, for + backwards compatibility. +* PR #61 from @Eroc33: Improve parsing for tuple and generic type + expressions. +* Fix old doc link in readme. +* Update dependencies in ructe and examples. + +Thaks to @kornelski and @Eroc33. + + +## Release 0.7.2 - 2019-08-28 + +* Issue #53, PR #60: Allow empty strings everywhere quoted strings are + allowed. +* Issue #57, PR #59: Accept explicit impl and dyn in types. +* Relax over-strict whitespace requirements, fix a regression in 0.7.0. +* PR #56: Require buf reference to implement Write, not buf itself +* PR #58: Fix warnings in generated code. +* Remove no-longer-used imports. + +Thanks to @kornelski for multiple contributions. + + +## Release 0.7.0 - 2019-07-18 + +* PR #52: Upgrade nom to 5.0 +* Update rsass to 0.11.0 (which also uses nom 5.0) +* Improve template declaration parsing and diagnostics. +* PR #50 and #51 from @dkotrada: Fix typos in actix example. +* Remove deprecated functions. + + +## Release 0.6.4 - 2019-06-23 + +* Added more modern rust compiler versions (and dropped 1.26). +* PR #49: Add an actix example. +* PR #48 from @Noughmad: Use `impl Write` or generic argument instead + of dynamic traits. Fixes a warning for each template when using + edition 2018 in nightly rust. +* Clearer doc about escaping special characters. +* PR #46 from @kornelski: Add missing crates keyword + + +## Release 0.6.2 - 2019-03-16 + +* Improved documentation and examples. + All public items now have documentation. +* Improve build-time error handling. + If there is an error involving an environment variable, include the + variable name in the message. +* Take more Path build-time arguements AsRef. + Make it possible to simpl use a string literal as a path in more places. + + +## Release 0.6.0 - 2019-03-14 + +* PR #45: Provide a warp feature. + All my warp + ructe projects use the same RenderRucte extension trait + to make calling the templates on generating responses a bit clearer. + Provide that trait here as an optional feature. +* PR #43: Make the build scripts nicer. + Provide a struct Ructe with methods to handle the red tape from build + scripts. Make the remaining parts of the build scripts shorter and + more to the point. +* Use edition 2018 in warp example. +* Fix examples lang attribute. + A whole bunch of examples had the html lang attibute set to sv when + the content is actually in English. + + +## Release 0.5.10 - 2019-02-22 + +* Convert more file names to rust names (a file name might contain + dashes and dots that needs to be converted to something else + (underscore) to work in a rust name). +* Find new files in static dirs (add a cargo:rerun-if-changed line + for the directory itself). + + +## Release 0.5.8 - 2019-02-16 + +* Adapt to rsass 0.9.8 (the sass feature now requires a compiler that + supports edition 2018). +* More compact static data, using byte strings instead of numbers. + (i.e. b"\xef\xbb\xbfabc" rather than [239, 187, 191, 65, 66, 67]). +* Minor internal cleanup. +* Update bytecount dependency. + + +## Release 0.5.6 - 2019-01-05 + +* PR #41: Benchmark and improve performance of html-escaping. +* PR #39: Silence a clippy warning about old syntax in silencing + another warning. +* Update itertools to 0.8 (and env_logger in warp example) + +Thanks to @kornelski for PRs #39 and #41. + + +## Release 0.5.4 - 2018-11-30 + +* Support struct unpacking in `@if` and `@for` expressions. + + +## Release 0.5.2 - 2018-11-04 + +* Special case for empty sub-templates, mainly to avoid a warning when + compiling generated code. +* Update md5 to 0.6. +* Update gotham in example to 0.3.0. +* Use mime 0.3 in static example, and remove mime03 example. + + +## Release 0.5.0 - 2018-11-03 + +* Support multiple Content arguments. + Impl Trait is used to make sub-templates as arguments less magic. + This way we can also support more than one Content argument to the + same template. +* PR #36 / Issue #35: Test and fix support for edition=2018. + Module paths used by generated code are now compatible with the 2018 + edition. Also, some code in examples and documentation use more + 2018-friendly module paths. +* PR 34: Use bytecount rather than simple counting, elide one lifetime. +* Update nom to 4.1.1, base64 to 0.10.0, bytecount to 0.4, and md5 to 0.5. +* Update iron to 0.6 and warp to 0.1.9 in examples. +* Minor cleanup in nickel example. + +Thanks to @KlossPeter for PR #34 and @matthewpflueger for issue #35. + + +## Release 0.4.6 - 2018-10-07 + +* Lock nom version at 4.0, since it seems the 4.1 release is + incompatible with the error handling in ructe. + + +## Release 0.4.4 - 2018-09-06 + +* Test and fix #33, unduplicate curly brackets. +* Add `@@` escape, producing a single `@` sign. Suggested in #33. +* Some more mime types for static files. +* Update dependencies: nom 4.0, rsass 0.9.0 +* Add a warp example, and link to kaj/warp-diesel-ructe-sample + +Thanks to @dermetfan for reporting issue #33. + + +## Release 0.4.2 - 2018-08-01 + +* Test and fix issue #31, comments in body. + +Thanks to @jo-so for reporting the issue, and for the test + + +## Release 0.4.0 - 2018-07-05 + +* Template syntax: + - Allow local ranges (i.e. `2..7`) in loop expressions. + - Allow underscore rust names. There is use for unused variables in + templates, so allow names starting with underscore. + - Issue #24 / PR #28: Allow logic operators in `@if ...` expressions. + - Issue #25 / PR #27: Allow much more in parentehsis expressions. + +* Improved examples: + - A new design for the framework examples web page, using svg graphics. + - Improve code and inline documentation of iron and nickel examples. + - Add a similar example with the Gotham framework. + +* Recognize `.svg` static files. +* Allocate much fewer strings when parsing expressions in templates. +* PR #26: use `write_all` rather than the `write!` macro in generated + code, contributed by @kornelski +* Fix `application/octet-stream` MIME type. Contributed by @kornelski. +* Use `write_str`/`write_all` when generating output. Contributed by + @kornelski. + + +## Release 0.3.16 - 2018-04-08 + +Changes since 0.3.14 is mainly some internal cleanup, a link fix in +README and the optional rsass dependency is updated to 0.8.0. + + +## Release 0.3.14 - 2018-03-11 + +* Make the space after a comma in list expressions optional. +* Allow enum variants (and module names) in expressions. +* Some cleanup in parser code. + + +## Release 0.3.12 - 2018-02-10 + +* Add a way to add static files without hashnames. + A static file can be added and mapped as an arbitrary name, or a + directory can be recursively added with an arbitrary prefix. + + +## Release 0.3.10 - 2017-12-30 + +* Allow `*` at start of expressions (and subexpressions). +* Updated (optional) rsass to ^0.7.0. +* Updated base64 to ^0.9.0. + + +## Release 0.3.8 - 2017-12-07 + +* Make clippy happy with the code genarated for templates. +* Updated lazy_static to 1.0. +* Updated base64 to 0.8. +* Updated (optional) rsass to 0.6. + + +## Relese 0.3.6 - 2017-11-05 + +* Update nom dependency to version 3.2. +* Update optional rsass dependency to version 0.5.0. +* Update base64 dependency to 0.7.0. +* A documentation typo fixed, by @jo-so. +* Minor internal cleanup. + + +## Release 0.3.4 - 2017-07-10 + +* PR #15, issue #14: Allow destructure in loops, thanks to @nubis. +* PR #16 Allow complex argument types to templates +* PR #17 Write a doc chapter about static content. + + +## Release 0.3.2 - 2017-06-23 + +* Fix a bug in ordering (and therefor findability) of static files. +* Improved documentation. +* PR #13: Provide mime type for static file data. +* Fix file paths for `@import` in scss (using sass feature). +* Code cleanup (use `?` operator rather than `try!` macro). +* Use include_bytes for static files to improve compile times. + + +## Release 0.3.0 - 2017-05-07 + +* Issue #10: Watch template directories for changes, to build new + templates when they are created. +* PR #12: Integrate sass, including a function to reference static + files from a scss document. +* Some documentation improvements and internal code cleanup. + + +## Release 0.2.6 - 2017-03-23 + +* #8 / PR #11: Much improved error reporting. +* #9 allow comparison operators in if statements. +* Improved documentation, including a chapter on template structure in + the docs (based on what was in the README.md). + +This release is tested with rust versions 1.14.0, 1.15.1, 1.16.0 +(stable), 1.17.0-beta.2 (b7c276653 2017-03-20), and 1.17.0-nightly +(8c4f2c64c 2017-03-22). + + +## Release 0.2.4 - 2017-02-05 + +* Test: expression may be in string, such as `...`. + This should work for all expressions. +* Handle escaped quotes in strings. +* PR #7: Allow slices in templates. +* Stop using the nom macro chain!, which is deprecated. + +This release is tested with rust versions 1.14.0, 1.15.0 (stable), +1.16.0-beta.1 (beta), and 1.17.0-nightly (0648517fa 2017-02-03). + + +## Release 0.2.2 - 2017-01-29 + +* PR #3: Add convenient handling of static files +* Add documentation for utilities. + + +## Release 0.2.0 - 2017-01-28 + +* PR #6, Issue #5: Template directory structure. Finds templates in + all subdirectories of the template dir rather than only the template + dir itself. Template functions are created in a module structure + that mirrors the directory structure. + Thanks to @mrLSD for suggestion. +* Update `nom` to 2.0 +* Use `base64` instead of entire `rustc_serialize` for just base64 coding. +* Issue #4: Mention curly brackets escaping in docs. Thanks to @dermetfan. +* Cleanup, more tests and documentation. + + +## Release 0.1.2 - 2016-11-20 + +* Allow expressions to start with boolean not. +* DRYer test code. +* Write the generated code for each template into a separate file. + As suggested by Jethro Beekman in + [a comment on my blog](https://rasmus.krats.se/2016/ructe.en#c2427). +* More calling templates from templates doc. + + +## Release 0.1.1 - 2016-10-03 + +* Support calling templates with body arguments. Usefull for + "base-page" templates. +* Provide `Html` trait to template code. +* Some testing and cleanup. + + +## Version 0.1.0 - 2016-09-24 + +* First version published on crates.io. +* Support for `@if` and `@for` blocks. +* Improved expression parsing with chaining, square and curly brakets, + and string literals. +* Compile all found templates. +* More tests and documentation. + + +## Initial commit - 2016-09-14 + +Very siple templates, with arguments, worked, and text was +html-escaped as needed. diff --git a/ructe-0.17.0/Cargo.toml b/ructe-0.17.0/Cargo.toml new file mode 100644 index 0000000..be0f348 --- /dev/null +++ b/ructe-0.17.0/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "ructe" +version = "0.17.0" +authors = ["Rasmus Kaj "] +description = "Rust Compiled Templates, efficient type-safe web page templates." +documentation = "https://docs.rs/ructe" +repository = "https://github.com/kaj/ructe" +readme = "README.md" +keywords = ["web", "templating", "template", "html"] +categories = ["template-engine", "web-programming"] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.58.1" + +[features] +sass = ["rsass"] +mime02 = [] +mime03 = ["mime"] +warp03 = ["mime03"] +http-types = [] +tide016 = ["tide013"] +tide015 = ["tide013"] +tide014 = ["tide013"] +tide013 = ["http-types"] + +[dependencies] +base64 = "0.21.0" +bytecount = "0.6.0" +itertools = "0.11.0" +md5 = "0.7" +nom = "7.1.0" + +rsass = { version = "0.28.0", optional = true } +mime = { version = "0.3", optional = true } + +[badges] +travis-ci = { repository = "kaj/ructe" } +maintenance = { status = "actively-developed" } diff --git a/ructe-0.17.0/README.md b/ructe-0.17.0/README.md new file mode 100644 index 0000000..1e4dcde --- /dev/null +++ b/ructe-0.17.0/README.md @@ -0,0 +1,78 @@ +# Rust Compiled Templates — ructe + +This is my attempt at writing a HTML template system for Rust. +Some inspiration comes from the scala template system used in play 2, +as well as plain old jsp. + +[![Crate](https://img.shields.io/crates/v/ructe.svg)](https://crates.io/crates/ructe) +[![docs](https://docs.rs/ructe/badge.svg)](https://docs.rs/ructe) +[![CI](https://github.com/kaj/ructe/workflows/CI/badge.svg)](https://github.com/kaj/ructe/actions) + + +## Design criteria + +* As many errors as possible should be caught at compile-time. +* A compiled binary should include all the template code it needs, + no need to read template files at runtime. +* Compilation may take time, running should be fast. +* Writing templates should be almost as easy as writing html. +* The template language should be as expressive as possible. +* It should be possible to write templates for any text-like format, + not only html. +* Any value that implements the `Display` trait should be outputable. +* By default, all values should be html-escaped. There should be an + easy but explicit way to output preformatted html. + +## Current status + +Ructes is in a rather early stage, but does work; +templates can be transpiled to rust functions, which are then compiled +and can be called from rust code. + +### Template format + +A template consists of three basic parts: +First a preamble of `use` statements, each prepended by an `@` sign. +Secondly a declaration of the parameters the template takes. +And third, the template body. + +The full syntax is described [in the documentation](https://docs.rs/ructe/). +Some examples can be seen in +[examples/simple/templates](examples/simple/templates). +A template may look something like this: + +```html +@use any::rust::Type; +@use super::statics::style_css; + +@(name: &str, items: &[Type]) + + + + @name + + + +

@name

+
+ @for item in items { +
@item.title()
+
@item.description()
+ } +
+ + +``` + +## How to use ructe + +Ructe compiles your templates to rust code that should be compiled with +your other rust code, so it needs to be called before compiling, +as described [in the documentation](https://docs.rs/ructe/). +There are also [examples](examples), +both for ructe itself and its futures and for using it with the web +frameworks [actix-web](examples/actix), [gotham](examples/gotham), +[iron](examples/iron), [nickel](examples/nickel), [tide](examples/tide), +and [warp](examples/warp03). +There is also [a separate example of using ructe with warp and +diesel](https://github.com/kaj/warp-diesel-ructe-sample). diff --git a/ructe-0.17.0/rustfmt.toml b/ructe-0.17.0/rustfmt.toml new file mode 100644 index 0000000..e6ff97e --- /dev/null +++ b/ructe-0.17.0/rustfmt.toml @@ -0,0 +1,3 @@ +max_width = 78 + +reorder_imports = true diff --git a/ructe-0.17.0/src/Template_syntax.rs b/ructe-0.17.0/src/Template_syntax.rs new file mode 100644 index 0000000..90fdf70 --- /dev/null +++ b/ructe-0.17.0/src/Template_syntax.rs @@ -0,0 +1,304 @@ +// This module is only a chapter of the documentation. +//! This module describes the template syntax used by ructe. +//! +//! The syntax is inspired by +//! [Twirl](https://github.com/playframework/twirl), the Scala-based +//! template engine in +//! [Play framework](https://www.playframework.com/), +//! but of course with rust types expressions instead of scala. +//! +//! A template consists of three basic parts: +//! First a preamble of `use` statements, each prepended by an @ sign. +//! Secondly a declaration of the parameters the template takes. +//! And third, the template body. +//! +//! ```html +//! @(name: &str, value: &u32) +//! +//! +//! @name +//! +//!

The value of @name is @value.

+//! +//! +//! ``` +//! +//! As seen above, string slices and integers can easily be outputed +//! in the template body, using `@name` where `name` is a parameter of +//! the template. +//! Actually, more complex expressions can be outputed in the same +//! way, as long as the resulting value implements [`ToHtml`]. +//! Rust types that implements [`Display`] automatically implements +//! [`ToHtml`] in such a way that contents are safely escaped for +//! html. +//! +//! ```html +//! @use any::rust::Type; +//! +//! @(name: &str, items: &[Type]) +//! +//! +//! @name +//! +//! @if items.is_empty() { +//!

There are no items.

+//! } else { +//!

There are @items.len() items.

+//! +//! +//! +//! ``` +//! +//! The curly brackets, `{` and `}`, is used for blocks (see Loops, +//! Conditionals, and Calling other templates below). +//! +//! To use verbatim curly brackets in the template body, they must be +//! escaped as `@{` and `@}`, the same goes for the `@` sign, that +//! precedes expressions and special blocks; verbtim `@` signs must be +//! escaped as `@@`. +//! +//! [`ToHtml`]: crate::templates::ToHtml +//! [`Display`]: std::fmt::Display +#![allow(non_snake_case)] + +pub mod a_Value_expressions { + //! A value expression can be as simple as `@name` to get the value of + //! a parameter, but more complicated expressions, including function + //! calls, are also allowed. + //! + //! # Value expressions + //! + //! A parameter can be used in an expression preceded by an @ sign. + //! + //! ```text + //!

@name

+ //! ``` + //! + //! If a parameter is a struct or a trait object, its fields or methods can + //! be used, and if it is a callable, it can be called. + //! + //! ```text + //!

The user @user.name has email @user.get_email().

+ //!

A function result is @function(with, three, arguments).

+ //! ``` + //! + //! Standard function and macros can also be used, e.g. for specific + //! formatting needs: + //! + //! ```text + //!

The value is @format!("{:.1}", float_value).

+ //! ``` + //! + //! If more complex expressions are needed, they can be put in + //! parenthesis. + //! + //! ```text + //!

The sum @a+3 is @(a+3).

+ //! ``` + //! If `a` is 2, this exapands to: + //! ```text + //!

The sum 2+3 is 5.

+ //! ``` + //! + //! Anything is allowed in parenthesis, as long as parenthesis, + //! brackets and string quotes are balanced. + //! Note that this also applies to the parenthesis of a function + //! call or the brackets of an index, so complex things like the + //! following are allowed: + //! + //! ```text + //!

Index: @myvec[t.map(|s| s.length()).unwrap_or(0)].

+ //!

Argument: @call(a + 3, |t| t.something()).

+ //! ``` + //! + //! An expression ends when parenthesis and brackets are matched + //! and it is followed by something not allowed in an expression. + //! This includes whitespace and e.g. the `<` and `@` characters. + //! If an expression starts with an open parenthesis, the + //! expression ends when that parentheis is closed. + //! That is usefull if an expression is to be emmediatley followed + //! by something that would be allowed in an expression. + //! + //! ```text + //!

@arg

+ //!

@arg.

+ //!

@arg.@arg

+ //!

@arg.len()

+ //!

@(arg).len()

+ //!

@((2_i8 - 3).abs())

@* Note extra parens needed here *@ + //! ``` + //! With `arg = "name"`, the above renders as: + //! ```text + //!

name

+ //!

name.

+ //!

name.name

+ //!

4

+ //!

name.len()

+ //!

1

+ //! ``` +} + +pub mod b_Loops { + //! A ructe `@for` loop works just as a rust `for` loop, + //! iterating over anything that implements `std::iter::IntoIterator`, + //! such as a `Vec` or a slice. + //! + //! # Loops + //! + //! Rust-like loops are supported like this: + //! + //! ```text + //! + //! ``` + //! + //! Note that the thing to loop over (items, in the example) is a rust + //! expression, while the contents of the block is template code. + //! + //! If items is a slice of tuples (or really, anything that is + //! iterable yielding tuples), it is possible to deconstruct the + //! tuples into separate values directly: + //! + //! ```text + //! @for (n, item) in items.iter().enumerate() { + //!

@n: @item

+ //! } + //! ``` + //! + //! It is also possible to loop over a literal array (which may be + //! an array of tuples), as long as you do it by reference: + //! + //! ```text + //! @for &(name, age) in &[("Rasmus", 44), ("Mike", 36)] { + //!

@name is @age years old.

+ //! } + //! ``` +} + +pub mod c_Conditionals { + //! Both `@if` statements with boolean expressions, `@if let` guard + //! statements, and `@match` statements are supported. + //! + //! # Conditionals + //! + //! Rust-like conditionals are supported in a style similar to the loops: + //! + //! ```text + //! @if items.is_empty() { + //!

There are no items.

+ //! } + //! ``` + //! + //! Pattern matching let expressions are also supported, as well as an + //! optional else part. + //! + //! ```text + //! @if let Some(foo) = foo { + //!

Foo is @foo.

+ //! } else { + //!

There is no foo.

+ //! } + //! ``` + //! + //! The condition or let expression should allow anything that would be + //! allowed in the same place in plain rust. + //! As with loops, the things in the curly brackets are ructe template + //! code. + //! + //! ## match + //! + //! Pattern matching using `match` statements are also supported. + //! + //! ```text + //! @match answer { + //! Ok(value) => { + //!

The answer is @value.

+ //! } + //! Err(_) => { + //!

I don't know the answer.

+ //! } + //! } + //! ``` + //! + //! The let expression and patterns should allow anything that would be + //! allowed in the same place in plain rust. + //! As above, the things in the curly brackets are ructe template code. +} + +pub mod d_Calling_other_templates { + //! The ability to call other templates for from a template makes + //! both "tag libraries" and "base templates" possible with the + //! same syntax. + //! + //! # Calling other templates + //! + //! While rust methods can be called as a simple expression, there is a + //! special syntax for calling other templates: + //! `@:template_name(template_arguments)`. + //! Also, before calling a template, it has to be imported by a `use` + //! statement. + //! Templates are declared in a `templates` module. + //! + //! So, given something like this in `header.rs.html`: + //! + //! ```text + //! @(title: &str) + //! + //! + //! @title + //! + //! + //! ``` + //! + //! It can be used like this: + //! + //! ```text + //! @use super::header_html; + //! + //! @() + //! + //! + //! @:header_html("Example") + //! + //!

Example

+ //!

page content ...

+ //! + //! + //! ``` + //! + //! It is also possible to send template blocks as parameters to templates. + //! A structure similar to the above can be created by having something like + //! this in `base_page.rs.html`: + //! + //! ```text + //! @(title: &str, body: Content) + //! + //! + //! + //! @title + //! + //! + //! + //!

@title

+ //! @:body() + //! + //! + //! ``` + //! + //! And use it like this: + //! + //! ```text + //! @use super::base_page_html; + //! + //! @() + //! + //! @:base_page_html("Example", { + //!

page content ...

+ //! }) + //! ``` +} diff --git a/ructe-0.17.0/src/expression.rs b/ructe-0.17.0/src/expression.rs new file mode 100644 index 0000000..a8f6a49 --- /dev/null +++ b/ructe-0.17.0/src/expression.rs @@ -0,0 +1,275 @@ +use crate::parseresult::PResult; +use nom::branch::alt; +use nom::bytes::complete::{escaped, is_a, is_not, tag}; +use nom::character::complete::{alpha1, char, digit1, none_of, one_of}; +use nom::combinator::{map, map_res, not, opt, recognize, value}; +use nom::error::context; //, VerboseError}; +use nom::multi::{fold_many0, many0, separated_list0}; +use nom::sequence::{delimited, pair, preceded, terminated, tuple}; +use std::str::{from_utf8, Utf8Error}; + +pub fn expression(input: &[u8]) -> PResult<&str> { + map_res( + recognize(context( + "Expected rust expression", + tuple(( + map_res(alt((tag("&"), tag("*"), tag(""))), input_to_str), + alt(( + rust_name, + map_res(digit1, input_to_str), + quoted_string, + expr_in_parens, + expr_in_brackets, + )), + fold_many0( + alt(( + preceded(context("separator", tag(".")), expression), + preceded(tag("::"), expression), + expr_in_parens, + expr_in_braces, + expr_in_brackets, + preceded(tag("!"), expr_in_parens), + preceded(tag("!"), expr_in_brackets), + )), + || (), + |_, _| (), + ), + )), + )), + input_to_str, + )(input) +} + +pub fn input_to_str(s: &[u8]) -> Result<&str, Utf8Error> { + from_utf8(s) +} + +pub fn comma_expressions(input: &[u8]) -> PResult { + map( + separated_list0(preceded(tag(","), many0(tag(" "))), expression), + |list: Vec<_>| list.join(", "), + )(input) +} + +pub fn rust_name(input: &[u8]) -> PResult<&str> { + map_res( + recognize(pair( + alt((tag("_"), alpha1)), + opt(is_a("_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")), + )), + input_to_str, + )(input) +} + +fn expr_in_parens(input: &[u8]) -> PResult<&str> { + map_res( + recognize(delimited(tag("("), expr_inside_parens, tag(")"))), + input_to_str, + )(input) +} + +fn expr_in_brackets(input: &[u8]) -> PResult<&str> { + map_res( + recognize(delimited( + tag("["), + many0(alt(( + value((), is_not("[]()\"/")), + value((), expr_in_brackets), + value((), expr_in_braces), + value((), expr_in_parens), + value((), quoted_string), + value((), rust_comment), + value((), terminated(tag("/"), none_of("*"))), + ))), + tag("]"), + )), + input_to_str, + )(input) +} + +pub fn expr_in_braces(input: &[u8]) -> PResult<&str> { + map_res( + recognize(delimited( + tag("{"), + many0(alt(( + value((), is_not("{}[]()\"/")), + value((), expr_in_brackets), + value((), expr_in_braces), + value((), expr_in_parens), + value((), quoted_string), + value((), rust_comment), + value((), terminated(tag("/"), none_of("*"))), + ))), + tag("}"), + )), + input_to_str, + )(input) +} + +pub fn expr_inside_parens(input: &[u8]) -> PResult<&str> { + map_res( + recognize(many0(alt(( + value((), is_not("{}[]()\"/")), + value((), expr_in_braces), + value((), expr_in_brackets), + value((), expr_in_parens), + value((), quoted_string), + value((), rust_comment), + value((), terminated(tag("/"), none_of("*"))), + )))), + input_to_str, + )(input) +} + +pub fn quoted_string(input: &[u8]) -> PResult<&str> { + map_res( + recognize(delimited( + char('"'), + opt(escaped(is_not("\"\\"), '\\', one_of("'\"\\nrt0xu"))), + char('"'), + )), + input_to_str, + )(input) +} + +pub fn rust_comment(input: &[u8]) -> PResult<&[u8]> { + delimited( + tag("/*"), + recognize(many0(alt(( + is_not("*"), + terminated(tag("*"), not(tag("/"))), + )))), + tag("*/"), + )(input) +} + +#[cfg(test)] +mod test { + use super::expression; + + #[test] + fn expression_1() { + check_expr("foo"); + } + #[test] + fn expression_2() { + check_expr("x15"); + } + #[test] + fn expression_3() { + check_expr("a_b_c"); + } + #[test] + fn expression_4() { + check_expr("foo.bar"); + } + #[test] + fn expression_5() { + check_expr("foo.bar.baz"); + } + #[test] + fn expression_6() { + check_expr("(!foo.is_empty())"); + } + #[test] + fn expression_7() { + check_expr("foo(x, a.b.c(), d)"); + } + #[test] + fn expression_8() { + check_expr("foo(&\"x\").bar"); + } + #[test] + fn expression_9() { + check_expr("foo().bar(x).baz"); + } + #[test] + fn expression_str() { + check_expr("\"foo\""); + } + #[test] + fn expression_str_paren() { + check_expr("(\")\")"); + } + #[test] + fn expression_str_quoted() { + check_expr("\"line 1\\nline\\t2\""); + } + #[test] + fn expression_str_quoted_unicode() { + check_expr("\"Snowman: \\u{2603}\""); + } + #[test] + fn expression_enum_variant() { + check_expr("MyEnum::Variant.method()"); + } + #[test] + fn expression_str_with_escaped_quotes() { + check_expr("\"Hello \\\"world\\\"\""); + } + #[test] + fn expression_slice() { + check_expr("&[foo, bar]"); + } + #[test] + fn expression_slice_empty() { + check_expr("&[]"); + } + #[test] + fn expression_number() { + check_expr("42"); + } + #[test] + fn expression_with_comment() { + check_expr("(42 /* truly important number */)"); + } + #[test] + fn expression_with_comment_a() { + check_expr("(42 /* \" */)"); + } + #[test] + fn expression_with_comment_b() { + check_expr("(42 /* ) */)"); + } + #[test] + fn expression_arithemtic_in_parens() { + check_expr("(2 + 3*4 - 5/2)"); + } + + fn check_expr(expr: &str) { + assert_eq!(expression(expr.as_bytes()), Ok((&b""[..], expr))); + } + + #[test] + fn non_expression_a() { + assert_eq!( + expression_error_message(b".foo"), + ": 1:.foo\n\ + : ^ Expected rust expression\n" + ); + } + #[test] + fn non_expression_b() { + assert_eq!( + expression_error_message(b" foo"), + ": 1: foo\n\ + : ^ Expected rust expression\n" + ); + } + #[test] + fn non_expression_c() { + assert_eq!( + expression_error_message(b"(missing end"), + ": 1:(missing end\n\ + : ^ Expected rust expression\n" + ); + } + fn expression_error_message(input: &[u8]) -> String { + use crate::parseresult::show_errors; + let mut buf = Vec::new(); + if let Err(error) = expression(input) { + show_errors(&mut buf, input, &error, ":"); + } + String::from_utf8(buf).unwrap() + } +} diff --git a/ructe-0.17.0/src/lib.rs b/ructe-0.17.0/src/lib.rs new file mode 100644 index 0000000..1f35963 --- /dev/null +++ b/ructe-0.17.0/src/lib.rs @@ -0,0 +1,454 @@ +//! Rust Compiled Templates is a HTML template system for Rust. +//! +//! Ructe works by converting your templates (and static files) to +//! rust source code, which is then compiled with your project. +//! This has the benefits that: +//! +//! 1. Many syntactical and logical errors in templates are caught +//! compile-time, rather than in a running server. +//! 2. No extra latency on the first request, since the templates are +//! fully compiled before starting the program. +//! 3. The template files does not have to be distributed / installed. +//! Templates (and static assets) are included in the compiled +//! program, which can be a single binary. +//! +//! The template syntax, which is inspired by [Twirl], the Scala-based +//! template engine in [Play framework], is documented in +//! the [Template_syntax] module. +//! A sample template may look like this: +//! +//! ```html +//! @use any::rust::Type; +//! +//! @(name: &str, items: &[Type]) +//! +//! +//! @name +//! +//! @if items.is_empty() { +//!

There are no items.

+//! } else { +//!

There are @items.len() items.

+//! +//! } +//! +//! +//! ``` +//! +//! There are some [examples in the repository]. +//! There is also a separate example of +//! [using ructe with warp and diesel]. +//! +//! [Twirl]: https://github.com/playframework/twirl +//! [Play framework]: https://www.playframework.com/ +//! [examples in the repository]: https://github.com/kaj/ructe/tree/master/examples +//! [using ructe with warp and diesel]: https://github.com/kaj/warp-diesel-ructe-sample +//! +//! To be able to use this template in your rust code, you need a +//! `build.rs` that transpiles the template to rust code. +//! A minimal such build script looks like the following. +//! See the [`Ructe`] struct documentation for details. +//! +//! ```rust,no_run +//! use ructe::{Result, Ructe}; +//! +//! fn main() -> Result<()> { +//! Ructe::from_env()?.compile_templates("templates") +//! } +//! ``` +//! +//! When calling a template, the arguments declared in the template will be +//! prepended by a `Write` argument to write the output to. +//! It can be a `Vec` as a buffer or for testing, or an actual output +//! destination. +//! The return value of a template is `std::io::Result<()>`, which should be +//! `Ok(())` unless writing to the destination fails. +//! +//! ``` +//! #[test] +//! fn test_hello() { +//! let mut buf = Vec::new(); +//! templates::hello_html(&mut buf, "World").unwrap(); +//! assert_eq!(buf, b"

Hello World!

\n"); +//! } +//! ``` +//! +//! # Optional features +//! +//! Ructe has some options that can be enabled from `Cargo.toml`. +//! +//! * `sass` -- Compile sass and include the compiled css as static assets. +//! * `mime03` -- Static files know their mime types, compatible with +//! version 0.3.x of the [mime] crate. +//! * `mime02` -- Static files know their mime types, compatible with +//! version 0.2.x of the [mime] crate. +//! * `warp03` -- Provide an extension to `Response::Builder` of the [warp] +//! framework (versions 0.3.x) to simplify template rendering. +//! * `http-types` -- Static files know their mime types, compatible with +//! the [http-types] crate. +//! * `tide013`, `tide014`, `tide015`, `tide016` -- Support for the +//! [tide] framework version 0.13.x through 0.16.x. Implies the +//! `http-types` feature (but does not require a direct http-types +//! requirement, as that is reexported by tide). +//! (these versions of tide is compatible enough that the features +//! are actually just aliases for the first one, but a future tide +//! version may require a modified feature.) +//! +//! [mime]: https://crates.rs/crates/mime +//! [warp]: https://crates.rs/crates/warp +//! [tide]: https://crates.rs/crates/tide +//! [http-types]: https://crates.rs/crates/http-types +//! +//! The `mime02`, `mime03`, and `http-types` features are mutually +//! exclusive and requires a dependency on a matching version of +//! `mime` or `http-types`. +//! Any of them can be combined with the `sass` feature. +//! +//! ```toml +//! build = "src/build.rs" +//! +//! [build-dependencies] +//! ructe = { version = "0.6.0", features = ["sass", "mime03"] } +//! +//! [dependencies] +//! mime = "0.3.13" +//! ``` +#![forbid(unsafe_code, missing_docs)] +#![allow(clippy::manual_strip)] // Until MSR is 1.45.0 + +pub mod Template_syntax; +mod expression; +mod parseresult; +mod spacelike; +mod staticfiles; +mod template; +mod templateexpression; + +use parseresult::show_errors; +use std::env; +use std::error::Error; +use std::fmt::{self, Debug, Display}; +use std::fs::{create_dir_all, read_dir, File}; +use std::io::{self, Read, Write}; +use std::path::{Path, PathBuf}; +use template::template; + +pub use staticfiles::StaticFiles; + +/// The main build-time interface of ructe. +/// +/// Your build script should create an instance of `Ructe` and use it +/// to compile templates and possibly get access to the static files +/// handler. +/// +/// Ructe compiles your templates to rust code that should be compiled +/// with your other rust code, so it needs to be called before +/// compiling. +/// Assuming you use [cargo], it can be done like this: +/// +/// First, specify a build script and ructe as a build dependency in +/// `Cargo.toml`: +/// +/// ```toml +/// build = "src/build.rs" +/// +/// [build-dependencies] +/// ructe = "0.6.0" +/// ``` +/// +/// Then, in `build.rs`, compile all templates found in the templates +/// directory and put the output where cargo tells it to: +/// +/// ```rust,no_run +/// use ructe::{Result, Ructe}; +/// +/// fn main() -> Result<()> { +/// Ructe::from_env()?.compile_templates("templates") +/// } +/// ``` +/// +/// And finally, include and use the generated code in your code. +/// The file `templates.rs` will contain `mod templates { ... }`, +/// so I just include it in my `main.rs`: +/// +/// ```rust,ignore +/// include!(concat!(env!("OUT_DIR"), "/templates.rs")); +/// ``` +/// +/// +/// When creating a `Ructe` it will create a file called +/// `templates.rs` in your `$OUT_DIR` (which is normally created and +/// specified by `cargo`). +/// The methods will add content, and when the `Ructe` goes of of +/// scope, the file will be completed. +/// +/// [cargo]: https://doc.rust-lang.org/cargo/ +pub struct Ructe { + f: Vec, + outdir: PathBuf, +} + +impl Ructe { + /// Create a Ructe instance suitable for a [cargo]-built project. + /// + /// A file called `templates.rs` (and a directory called + /// `templates` containing sub-modules) will be created in the + /// directory that cargo specifies with the `OUT_DIR` environment + /// variable. + /// + /// [cargo]: https://doc.rust-lang.org/cargo/ + pub fn from_env() -> Result { + Ructe::new(PathBuf::from(get_env("OUT_DIR")?)) + } + + /// Create a ructe instance writing to a given directory. + /// + /// The `out_dir` path is assumed to be a directory that exists + /// and is writable. + /// A file called `templates.rs` (and a directory called + /// `templates` containing sub-modules) will be created in + /// `out_dir`. + /// + /// If you are using Ructe in a project that uses [cargo], + /// you should probably use [`Ructe::from_env`] instead. + /// + /// [cargo]: https://doc.rust-lang.org/cargo/ + pub fn new(outdir: PathBuf) -> Result { + let mut f = Vec::with_capacity(512); + create_dir_all(&outdir)?; + //f.write_all(b"pub mod templates {\n")?; + write_if_changed( + &outdir.join("_utils.rs"), + include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/templates/utils.rs" + )), + )?; + f.write_all( + b"#[doc(hidden)]\nmod _utils;\n\ + #[doc(inline)]\npub use self::_utils::*;\n\n", + )?; + if cfg!(feature = "warp03") { + write_if_changed( + &outdir.join("_utils_warp03.rs"), + include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/src/templates/utils_warp03.rs" + )), + )?; + f.write_all( + b"#[doc(hidden)]\nmod _utils_warp03;\n\ + #[doc(inline)]\npub use self::_utils_warp03::*;\n\n", + )?; + } + Ok(Ructe { f, outdir }) + } + + /// Create a `templates` module in `outdir` containing rust code for + /// all templates found in `indir`. + /// + /// If indir is a relative path, it should be relative to the main + /// directory of your crate, i.e. the directory containing your + /// `Cargo.toml` file. + /// + /// Files with suffix `.rs.html`, `.rs.svg`, or `.rs.xml` are + /// considered templates. + /// A templete file called `template.rs.html`, `template.rs.svg`, + /// etc, will result in a callable function named `template_html`, + /// `template_svg`, etc. + /// The `template_html` function will get a `template` alias for + /// backwards compatibility, but that will be removed in a future + /// release. + pub fn compile_templates

(&mut self, indir: P) -> Result<()> + where + P: AsRef, + { + handle_entries(&mut self.f, indir.as_ref(), &self.outdir) + } + + /// Create a [`StaticFiles`] handler for this Ructe instance. + /// + /// This will create a `statics` module inside the generated + /// `templates` module. + /// + /// # Examples + /// + /// This code goes into the `build.rs`: + /// + /// ```no_run + /// # use ructe::{Ructe, RucteError}; + /// # fn main() -> Result<(), RucteError> { + /// let mut ructe = Ructe::from_env()?; + /// ructe.statics()?.add_files("static")?; + /// Ok(()) + /// # } + /// ``` + /// + /// Assuming your project have a directory named `static` that + /// contains e.g. a file called `logo.svg` and you have included + /// the generated `templates.rs`, you can now use + /// `templates::statics::logo_png` as a [`StaticFile`] in your + /// project. + /// + /// [`StaticFile`]: templates::StaticFile + pub fn statics(&mut self) -> Result { + self.f.write_all(b"pub mod statics;")?; + StaticFiles::for_template_dir( + &self.outdir, + &PathBuf::from(get_env("CARGO_MANIFEST_DIR")?), + ) + } +} + +impl Drop for Ructe { + fn drop(&mut self) { + let _ = self.f.write_all(b"\n"); + let _ = + write_if_changed(&self.outdir.join("mod.rs"), &self.f); + } +} + +fn write_if_changed(path: &Path, content: &[u8]) -> io::Result<()> { + use std::fs::{read, write}; + if let Ok(old) = read(path) { + if old == content { + return Ok(()); + } + } + write(path, content) +} + +fn handle_entries( + f: &mut impl Write, + indir: &Path, + outdir: &Path, +) -> Result<()> { + println!("cargo:rerun-if-changed={}", indir.display()); + for entry in read_dir(indir)? { + let entry = entry?; + let path = entry.path(); + if entry.file_type()?.is_dir() { + if let Some(filename) = entry.file_name().to_str() { + let outdir = outdir.join(filename); + create_dir_all(&outdir)?; + let mut modrs = Vec::with_capacity(512); + modrs.write_all( + b"#[allow(renamed_and_removed_lints)]\n\ + #[cfg_attr(feature=\"cargo-clippy\", \ + allow(useless_attribute))]\n\ + #[allow(unused)]\n\ + use super::{Html,ToHtml};\n", + )?; + handle_entries(&mut modrs, &path, &outdir)?; + write_if_changed(&outdir.join("mod.rs"), &modrs)?; + writeln!(f, "pub mod {filename};\n")?; + } + } else if let Some(filename) = entry.file_name().to_str() { + if filename.contains(".rs.") { + println!("cargo:rerun-if-changed={}", path.display()); + let (prename, suffix) = filename.rsplit_once(".rs.").unwrap(); + let name = format!("{prename}_{suffix}"); + if handle_template(&name, &path, outdir)? { + writeln!( + f, + "#[doc(hidden)]\n\ + mod template_{name};\n\ + #[doc(inline)]\n\ + pub use self::template_{name}::{name};\n", + )?; + } + } + } + } + Ok(()) +} + +fn handle_template( + name: &str, + path: &Path, + outdir: &Path, +) -> io::Result { + let mut input = File::open(path)?; + let mut buf = Vec::new(); + input.read_to_end(&mut buf)?; + match template(&buf) { + Ok((_, t)) => { + let mut data = Vec::new(); + t.write_rust(&mut data, name)?; + write_if_changed( + &outdir.join(format!("template_{name}.rs")), + &data, + )?; + Ok(true) + } + Err(error) => { + println!("cargo:warning=Template parse error in {path:?}:"); + show_errors(&mut io::stdout(), &buf, &error, "cargo:warning="); + Ok(false) + } + } +} + +pub mod templates; + +fn get_env(name: &str) -> Result { + env::var(name).map_err(|e| RucteError::Env(name.into(), e)) +} + +/// The build-time error type for Ructe. +pub enum RucteError { + /// A build-time IO error in Ructe + Io(io::Error), + /// Error resolving a given environment variable. + Env(String, env::VarError), + /// Error bundling a sass stylesheet as css. + #[cfg(feature = "sass")] + Sass(rsass::Error), +} + +impl Error for RucteError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match &self { + RucteError::Io(e) => Some(e), + RucteError::Env(_, e) => Some(e), + #[cfg(feature = "sass")] + RucteError::Sass(e) => Some(e), + } + } +} + +impl Display for RucteError { + fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { + write!(out, "Error: {self:?}") + } +} +impl Debug for RucteError { + fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { + match self { + RucteError::Io(err) => Display::fmt(err, out), + RucteError::Env(var, err) => write!(out, "{var:?}: {err}"), + #[cfg(feature = "sass")] + RucteError::Sass(err) => Debug::fmt(err, out), + } + } +} + +impl From for RucteError { + fn from(e: io::Error) -> RucteError { + RucteError::Io(e) + } +} + +#[cfg(feature = "sass")] +impl From for RucteError { + fn from(e: rsass::Error) -> RucteError { + RucteError::Sass(e) + } +} + +/// A result where the error type is a [`RucteError`]. +pub type Result = std::result::Result; diff --git a/ructe-0.17.0/src/parseresult.rs b/ructe-0.17.0/src/parseresult.rs new file mode 100644 index 0000000..cd358f9 --- /dev/null +++ b/ructe-0.17.0/src/parseresult.rs @@ -0,0 +1,70 @@ +use nom::error::{VerboseError, VerboseErrorKind}; +use nom::{Err, IResult}; +use std::io::Write; +use std::str::from_utf8; + +/// Parser result, with verbose error. +pub type PResult<'a, O> = IResult<&'a [u8], O, VerboseError<&'a [u8]>>; + +pub fn show_errors( + out: &mut impl Write, + buf: &[u8], + error: &Err>, + prefix: &str, +) { + match error { + Err::Failure(VerboseError { ref errors }) + | Err::Error(VerboseError { ref errors }) => { + for (rest, err) in errors.iter().rev() { + if let Some(message) = get_message(err) { + let pos = buf.len() - rest.len(); + show_error(out, buf, pos, &message, prefix); + } + } + } + Err::Incomplete(needed) => { + let msg = format!("Incomplete: {needed:?}"); + show_error(out, buf, 0, &msg, prefix); + } + } +} + +fn get_message(err: &VerboseErrorKind) -> Option { + match err { + VerboseErrorKind::Context(msg) => Some((*msg).into()), + VerboseErrorKind::Char(ch) => Some(format!("Expected {ch:?}")), + VerboseErrorKind::Nom(_err) => None, + } +} + +fn show_error( + out: &mut impl Write, + buf: &[u8], + pos: usize, + msg: &str, + prefix: &str, +) { + let mut line_start = buf[0..pos].rsplitn(2, |c| *c == b'\n'); + let _ = line_start.next(); + let line_start = line_start.next().map_or(0, |bytes| bytes.len() + 1); + let line = buf[line_start..] + .splitn(2, |c| *c == b'\n') + .next() + .and_then(|s| from_utf8(s).ok()) + .unwrap_or("(Failed to display line)"); + let line_no = bytecount::count(&buf[..line_start], b'\n') + 1; + let pos_in_line = + from_utf8(&buf[line_start..pos]).unwrap().chars().count() + 1; + writeln!( + out, + "{prefix}{:>4}:{}\n\ + {prefix} {:>pos$} {}", + line_no, + line, + "^", + msg, + pos = pos_in_line, + prefix = prefix, + ) + .unwrap(); +} diff --git a/ructe-0.17.0/src/spacelike.rs b/ructe-0.17.0/src/spacelike.rs new file mode 100644 index 0000000..630a3a4 --- /dev/null +++ b/ructe-0.17.0/src/spacelike.rs @@ -0,0 +1,99 @@ +use crate::parseresult::PResult; +use nom::branch::alt; +use nom::bytes::complete::{is_not, tag}; +use nom::character::complete::{multispace1, none_of}; +use nom::combinator::{map, value}; +use nom::multi::many0; +use nom::sequence::preceded; + +pub fn spacelike(input: &[u8]) -> PResult<()> { + map(many0(alt((comment, map(multispace1, |_| ())))), |_| ())(input) +} + +pub fn comment(input: &[u8]) -> PResult<()> { + preceded(tag("@*"), comment_tail)(input) +} + +pub fn comment_tail(input: &[u8]) -> PResult<()> { + preceded( + many0(alt(( + value((), is_not("*")), + value((), preceded(tag("*"), none_of("@"))), + ))), + value((), tag("*@")), + )(input) +} + +#[cfg(test)] +mod test { + use super::{comment, spacelike}; + use nom::error::{ErrorKind, VerboseError, VerboseErrorKind}; + use nom::Err; + + #[test] + fn comment1() { + assert_eq!(comment(b"@* a simple comment *@"), Ok((&b""[..], ()))); + } + #[test] + fn comment2() { + let space_before = b" @* comment *@"; + assert_eq!( + comment(space_before), + Err(Err::Error(VerboseError { + errors: vec![( + &space_before[..], + VerboseErrorKind::Nom(ErrorKind::Tag), + )], + })), + ) + } + #[test] + fn comment3() { + assert_eq!( + comment(b"@* comment *@ & stuff"), + Ok((&b" & stuff"[..], ())) + ); + } + #[test] + fn comment4() { + assert_eq!( + comment(b"@* comment *@ and @* another *@"), + Ok((&b" and @* another *@"[..], ())) + ); + } + #[test] + fn comment5() { + assert_eq!( + comment(b"@* comment containing * and @ *@"), + Ok((&b""[..], ())) + ); + } + #[test] + fn comment6() { + assert_eq!( + comment(b"@*** peculiar comment ***@***"), + Ok((&b"***"[..], ())) + ); + } + + #[test] + fn spacelike_empty() { + assert_eq!(spacelike(b""), Ok((&b""[..], ()))); + } + #[test] + fn spacelike_simple() { + assert_eq!(spacelike(b" "), Ok((&b""[..], ()))); + } + #[test] + fn spacelike_long() { + assert_eq!( + spacelike( + b"\n\ + @* a comment on a line by itself *@\n\ + \t\t \n\n\r\n\ + @*another comment*@ something else" + ), + Ok((&b"something else"[..], ())) + ); + } +} diff --git a/ructe-0.17.0/src/staticfiles.rs b/ructe-0.17.0/src/staticfiles.rs new file mode 100644 index 0000000..71518d3 --- /dev/null +++ b/ructe-0.17.0/src/staticfiles.rs @@ -0,0 +1,708 @@ +use super::Result; +use itertools::Itertools; +use std::ascii::escape_default; +use std::collections::BTreeMap; +use std::fmt::{self, Display}; +use std::fs::{read_dir, File}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; + +/// Handler for static files. +/// +/// Apart from handling templates for dynamic content, ructe also +/// helps with constants for static content. +/// +/// Most sites that need HTML templates also needs some static resources. +/// Maybe one or several CSS files, some javascript, and / or pictures. +/// A good way to reduce network round-trips is to use a far expires +/// header to tell the browser it can cache those files and don't need +/// to check if they have changed. +/// But what if the files do change? +/// Then pretty much the only way to make sure the browser gets the +/// updated file is to change the URL to the file as well. +/// +/// Ructe can create content-dependent file names for static files. +/// If you have an `image.png`, ructe may call it `image-SomeHash.png` +/// where `SomeHash` is 8 url-safe base64 characters encoding 48 bits +/// of a md5 sum of the file. +/// +/// Each static file will be available as a +/// [`StaticFile`](templates/statics/index.html) struct instance in +/// your `templates::statics` module. +/// Also, the const `STATICS` array in the same module will contain a +/// reference to each of those instances. +/// +/// Actually serving the file is a job for a web framework like +/// [iron](https://github.com/iron/iron), +/// [nickel](https://github.com/nickel-org/nickel.rs) or +/// [rocket](https://rocket.rs/), but ructe helps by packing the file +/// contents into a constant struct that you can access from rust +/// code. +/// +/// # Overview +/// +/// This section describes how to set up your project to serve +/// static content using ructe. +/// +/// To do this, the first step is to add a line in `build.rs` telling +/// ructe to find and transpile your static files: +/// +/// ```no_run +/// # use ructe::{Ructe, RucteError}; +/// # fn main() -> Result<(), RucteError> { +/// let mut ructe = Ructe::from_env()?; +/// ructe.statics()?.add_files("static")?; +/// # Ok(()) +/// # } +/// ``` +/// +/// Then you need to link to the encoded file. +/// For an image, you probably want to link it from an `` tag in +/// a template. That can be done like this: +/// +/// ```html +/// @use super::statics::image_png; +/// @() +/// Something +/// ``` +/// +/// So, what has happened here? +/// First, assuming the `static` directory in your +/// `$CARGO_MANIFEST_DIR` contained a file name `image.png`, your +/// `templates::statics` module (which is reachable as `super::statics` +/// from inside a template) will contain a +/// `pub static image_png: StaticFile` which can be imported and used +/// in both templates and rust code. +/// A `StaticFile` has a field named `name` which is a `&'static str` +/// containing the name with the generated hash, `image-SomeHash.png`. +/// +/// The next step is that a browser actually sends a request for +/// `/static/image-SomeHash.png` and your server needs to deliver it. +/// Here, things depend on your web framework, so we start with some +/// pseudo code. +/// Full examples for [warp], [gotham], [nickel], and [iron] is +/// available [in the ructe repository]. +/// +/// [warp]: https://crates.rs/crates/warp +/// [gotham]: https://crates.rs/crates/gotham +/// [nickel]: https://crates.rs/crates/nickel +/// [iron]: https://crates.rs/crates/iron +/// [in the ructe repository]: https://github.com/kaj/ructe/tree/master/examples +/// +/// ```ignore +/// /// A hypothetical web framework calls this each /static/... request, +/// /// with the name component of the URL as the name argument. +/// fn serve_static(name: &str) -> Response { +/// if let Some(data) = StaticFile::get(name) { +/// Response::Ok(data.content) +/// } else { +/// Response::NotFound +/// } +/// } +/// ``` +/// +/// The `StaticFile::get` function returns the `&'static StaticFile` +/// for a given file name if the file exists. +/// This is a reference to the same struct that we used by the name +/// `image_png` in the template. +/// Besides the `name` field (which will be equal to the argument, or +/// `get` would not have returned this `StaticFile`), there is a +/// `content: &'static [u8]` field which contains the actual file +/// data. +/// +/// # Content-types +/// +/// How to get the content type of static files. +/// +/// Ructe has support for making the content-type of each static +/// file availiable using the +/// [mime](https://crates.io/crates/mime) crate. +/// Since mime version 0.3.0 was a breaking change of how the +/// `mime::Mime` type was implemented, and both Nickel and Iron +/// currently require the old version (0.2.x), ructe provides +/// support for both mime 0.2.x and mime 0.3.x with separate +/// feature flags. +/// +/// # Mime 0.2.x +/// +/// To use the mime 0.2.x support, enable the `mime02` feature and +/// add mime 0.2.x as a dependency: +/// +/// ```toml +/// [build-dependencies] +/// ructe = { version = "^0.3.2", features = ["mime02"] } +/// +/// [dependencies] +/// mime = "~0.2" +/// ``` +/// +/// A `Mime` as implemented in `mime` version 0.2.x cannot be +/// created statically, so instead a `StaticFile` provides +/// `pub fn mime(&self) -> Mime`. +/// +/// ``` +/// # // Test and doc even without the feature, so mock functionality. +/// # pub mod templates { pub mod statics { +/// # pub struct FakeFile; +/// # impl FakeFile { pub fn mime(&self) -> &'static str { "image/png" } } +/// # pub static image_png: FakeFile = FakeFile; +/// # }} +/// use templates::statics::image_png; +/// +/// # fn main() { +/// assert_eq!(format!("Type is {}", image_png.mime()), +/// "Type is image/png"); +/// # } +/// ``` +/// +/// # Mime 0.3.x +/// +/// To use the mime 0.3.x support, enable the `mime3` feature and +/// add mime 0.3.x as a dependency: +/// +/// ```toml +/// [build-dependencies] +/// ructe = { version = "^0.3.2", features = ["mime03"] } +/// +/// [dependencies] +/// mime = "~0.3" +/// ``` +/// +/// From version 0.3, the `mime` crates supports creating const +/// static `Mime` objects, so with this feature, a `StaticFile` +/// simply has a `pub mime: &'static Mime` field. +/// +/// ``` +/// # // Test and doc even without the feature, so mock functionality. +/// # pub mod templates { pub mod statics { +/// # pub struct FakeFile { pub mime: &'static str } +/// # pub static image_png: FakeFile = FakeFile { mime: "image/png", }; +/// # }} +/// use templates::statics::image_png; +/// +/// # fn main() { +/// assert_eq!(format!("Type is {}", image_png.mime), +/// "Type is image/png"); +/// # } +/// ``` +pub struct StaticFiles { + /// Rust source file `statics.rs` beeing written. + src: Vec, + /// Path for writing the file `statics.rs`. + src_path: PathBuf, + /// Base path for finding static files with relative paths + base_path: PathBuf, + /// Maps rust names to public names (foo_jpg -> foo-abc123.jpg) + names: BTreeMap, + /// Maps public names to rust names (foo-abc123.jpg -> foo_jpg) + names_r: BTreeMap, +} + +impl StaticFiles { + pub(crate) fn for_template_dir( + outdir: &Path, + base_path: &Path, + ) -> Result { + let mut src = Vec::with_capacity(512); + if cfg!(feature = "mime03") { + src.write_all(b"use mime::Mime;\n\n")?; + } + if cfg!(feature = "tide013") { + src.write_all(b"use tide::http::mime::{self, Mime};\n\n")?; + } else if cfg!(feature = "http-types") { + src.write_all(b"use http_types::mime::{self, Mime};\n\n")?; + } + src.write_all( +b"/// A static file has a name (so its url can be recognized) and the +/// actual file contents. +/// +/// The name includes a short (48 bits as 8 base64 characters) hash of +/// the content, to enable long-time caching of static resourses in +/// the clients. +#[allow(dead_code)] +pub struct StaticFile { + pub content: &'static [u8], + pub name: &'static str, +")?; + if cfg!(feature = "mime02") { + src.write_all(b" _mime: &'static str,\n")?; + } + if cfg!(feature = "mime03") { + src.write_all(b" pub mime: &'static Mime,\n")?; + } + if cfg!(feature = "http-types") { + src.write_all(b" pub mime: &'static Mime,\n")?; + } + src.write_all( + b"} +#[allow(dead_code)] +impl StaticFile { + /// Get a single `StaticFile` by name, if it exists. + #[must_use] + pub fn get(name: &str) -> Option<&'static Self> { + if let Ok(pos) = STATICS.binary_search_by_key(&name, |s| s.name) { + Some(STATICS[pos]) + } else {None} + } +} +", + )?; + if cfg!(feature = "mime02") { + src.write_all( + b"use mime::Mime; +impl StaticFile { + /// Get the mime type of this static file. + /// + /// Currently, this method parses a (static) string every time. + /// A future release of `mime` may support statically created + /// `Mime` structs, which will make this nicer. + #[allow(unused)] + pub fn mime(&self) -> Mime { + self._mime.parse().unwrap() + } +} +", + )?; + } + Ok(StaticFiles { + src, + src_path: outdir.join("statics.rs"), + base_path: base_path.into(), + names: BTreeMap::new(), + names_r: BTreeMap::new(), + }) + } + + // Should the return type be some kind of cow path? + fn path_for(&self, path: impl AsRef) -> PathBuf { + let path = path.as_ref(); + if path.is_relative() { + self.base_path.join(path) + } else { + path.into() + } + } + + /// Add all files from a specific directory, `indir`, as static files. + pub fn add_files( + &mut self, + indir: impl AsRef, + ) -> Result<&mut Self> { + let indir = self.path_for(indir); + println!("cargo:rerun-if-changed={}", indir.display()); + for entry in read_dir(indir)? { + let entry = entry?; + if entry.file_type()?.is_file() { + self.add_file(&entry.path())?; + } + } + Ok(self) + } + + /// Add all files from a specific directory, `indir`, as static files. + /// + /// The `to` string is used as a directory path of the resulting + /// urls, the file names are taken as is, without adding any hash. + /// This is usefull for resources used by preexisting javascript + /// packages, where it might be hard to change the used urls. + /// + /// Note that some way of changing the url when the content + /// changes is still needed if you serve the files with far + /// expire, and using this method makes that your responsibility + /// rather than ructes. + /// Either the file may have hashed names as is, or you may use + /// the version number of a 3:rd party package as part of the `to` + /// parameter. + /// + /// The `to` parameter may be an empty string. + /// In that case, no extra slash is added. + pub fn add_files_as( + &mut self, + indir: impl AsRef, + to: &str, + ) -> Result<&mut Self> { + for entry in read_dir(self.path_for(indir))? { + let entry = entry?; + let file_type = entry.file_type()?; + let to = if to.is_empty() { + entry.file_name().to_string_lossy().to_string() + } else { + format!("{}/{}", to, entry.file_name().to_string_lossy()) + }; + if file_type.is_file() { + self.add_file_as(&entry.path(), &to)?; + } else if file_type.is_dir() { + self.add_files_as(&entry.path(), &to)?; + } + } + Ok(self) + } + + /// Add one specific file as a static file. + /// + /// Create a name to use in the url like `name-hash.ext` where + /// name and ext are the name and extension from `path` and has is + /// a few url-friendly bytes from a hash of the file content. + /// + pub fn add_file(&mut self, path: impl AsRef) -> Result<&mut Self> { + let path = self.path_for(path); + if let Some((name, ext)) = name_and_ext(&path) { + println!("cargo:rerun-if-changed={}", path.display()); + let mut input = File::open(&path)?; + let mut buf = Vec::new(); + input.read_to_end(&mut buf)?; + let rust_name = format!("{name}_{ext}"); + let url_name = format!("{name}-{}.{ext}", checksum_slug(&buf)); + self.add_static( + &path, + &rust_name, + &url_name, + &FileContent(&path), + ext, + )?; + } + Ok(self) + } + + /// Add one specific file as a static file. + /// + /// Use `url_name` in the url without adding any hash characters. + pub fn add_file_as( + &mut self, + path: impl AsRef, + url_name: &str, + ) -> Result<&mut Self> { + let path = &self.path_for(path); + let ext = name_and_ext(path).map_or("", |(_, e)| e); + println!("cargo:rerun-if-changed={}", path.display()); + self.add_static(path, url_name, url_name, &FileContent(path), ext)?; + Ok(self) + } + + /// Add a resource by its name and content, without reading an actual file. + /// + /// The `path` parameter is used only to create a file name, the actual + /// content of the static file will be the `data` parameter. + /// A hash will be added to the file name, just as for + /// file-sourced statics. + /// + /// # Examples + /// + /// With the folloing code in `build.rs`: + /// ```` + /// # use ructe::{Result, Ructe, StaticFiles}; + /// # use std::fs::create_dir_all; + /// # use std::path::PathBuf; + /// # use std::vec::Vec; + /// # fn main() -> Result<()> { + /// # let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target").join("test-tmp").join("add-file"); + /// # create_dir_all(&p); + /// # let mut ructe = Ructe::new(p)?; + /// let mut statics = ructe.statics()?; + /// statics.add_file_data("black.css", b"body{color:black}\n"); + /// # Ok(()) + /// # } + /// ```` + /// + /// A `StaticFile` named `black_css` will be defined in the + /// `templates::statics` module of your crate: + /// + /// ```` + /// # mod statics { + /// # use ructe::templates::StaticFile; + /// # pub static black_css: StaticFile = StaticFile { + /// # content: b"body{color:black}\n", + /// # name: "black-r3rltVhW.css", + /// # #[cfg(feature = "mime03")] + /// # mime: &mime::TEXT_CSS, + /// # }; + /// # } + /// assert_eq!(statics::black_css.name, "black-r3rltVhW.css"); + /// ```` + pub fn add_file_data

( + &mut self, + path: P, + data: &[u8], + ) -> Result<&mut Self> + where + P: AsRef, + { + let path = &self.path_for(path); + if let Some((name, ext)) = name_and_ext(path) { + let rust_name = format!("{name}_{ext}"); + let url_name = format!("{name}-{}.{ext}", checksum_slug(data)); + self.add_static( + path, + &rust_name, + &url_name, + &ByteString(data), + ext, + )?; + } + Ok(self) + } + + /// Compile a sass file and add the resulting css. + /// + /// If `src` is `"somefile.sass"`, then that file will be copiled + /// with [rsass] (using the `Comressed` output style). + /// The result will be added as if if was an existing + /// `"somefile.css"` file. + /// + /// While handling the scss input, rsass is extended with a + /// `static_name` sass function that takes a file name as given to + /// [`add_file()`][Self::add_file] (or simliar) and returns the + /// `name-hash.ext` filename that ructe creates for it. + /// Note that only files that are added to the `StaticFiles` + /// _before_ the call to `add_sass_files` are supported by the + /// `static_name` function. + /// + /// This method is only available when ructe is built with the + /// "sass" feature. + #[cfg(feature = "sass")] + pub fn add_sass_file

(&mut self, src: P) -> Result<&mut Self> + where + P: AsRef, + { + use rsass::css::CssString; + use rsass::input::CargoContext; + use rsass::output::{Format, Style}; + use rsass::sass::{CallError, FormalArgs}; + use rsass::value::Quotes; + use rsass::*; + use std::sync::Arc; + let format = Format { + style: Style::Compressed, + precision: 4, + }; + + let src = self.path_for(src); + let (context, scss) = + CargoContext::for_path(&src).map_err(rsass::Error::from)?; + let mut context = context.with_format(format); + let existing_statics = self.get_names().clone(); + context.get_scope().define_function( + "static_name".into(), + sass::Function::builtin( + "", + &"static_name".into(), + FormalArgs::new(vec![("name".into(), None)]), + Arc::new(move |s| { + let name: String = s.get("name".into())?; + let rname = name.replace('-', "_").replace('.', "_"); + existing_statics + .iter() + .find(|(n, _v)| *n == &rname) + .map(|(_n, v)| { + CssString::new(v.into(), Quotes::Double).into() + }) + .ok_or_else(|| { + CallError::msg(format!( + "Static file {name:?} not found", + )) + }) + }), + ), + ); + + let css = context.transform(scss)?; + self.add_file_data(&src.with_extension("css"), &css) + } + + fn add_static( + &mut self, + path: &Path, + rust_name: &str, + url_name: &str, + content: &impl Display, + suffix: &str, + ) -> Result<&mut Self> { + let mut rust_name = + rust_name.replace(|c: char| !c.is_alphanumeric(), "_"); + if rust_name + .as_bytes() + .first() + .map(|c| c.is_ascii_digit()) + .unwrap_or(true) + { + rust_name.insert(0, 'n'); + } + writeln!( + self.src, + "\n/// From {path:?}\ + \n#[allow(non_upper_case_globals)]\ + \npub static {rust_name}: StaticFile = StaticFile {{\ + \n content: {content},\ + \n name: \"{url_name}\",\ + \n{mime}\ + }};", + path = path, + rust_name = rust_name, + url_name = url_name, + content = content, + mime = mime_arg(suffix), + )?; + self.names.insert(rust_name.clone(), url_name.into()); + self.names_r.insert(url_name.into(), rust_name); + Ok(self) + } + + /// Get a mapping of names, from without hash to with. + /// + /// ```` + /// # use ructe::{Result, Ructe, StaticFiles}; + /// # use std::fs::create_dir_all; + /// # use std::path::PathBuf; + /// # use std::vec::Vec; + /// # fn main() -> Result<()> { + /// # let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target").join("test-tmp").join("get-names"); + /// # create_dir_all(&p); + /// # let mut ructe = Ructe::new(p)?; + /// let mut statics = ructe.statics()?; + /// statics.add_file_data("black.css", b"body{color:black}\n"); + /// statics.add_file_data("blue.css", b"body{color:blue}\n"); + /// assert_eq!( + /// statics.get_names().iter() + /// .map(|(a, b)| format!("{} -> {}", a, b)) + /// .collect::>(), + /// vec!["black_css -> black-r3rltVhW.css".to_string(), + /// "blue_css -> blue-GZGxfXag.css".to_string()], + /// ); + /// # Ok(()) + /// # } + /// ```` + pub fn get_names(&self) -> &BTreeMap { + &self.names + } +} + +impl Drop for StaticFiles { + /// Write the ending of the statics source code, declaring the + /// `STATICS` variable. + fn drop(&mut self) { + // Ignore a possible write failure, rather than a panic in drop. + let _ = writeln!( + self.src, + "\npub static STATICS: &[&StaticFile] \ + = &[{}];", + self.names_r + .iter() + .map(|s| format!("&{}", s.1)) + .format(", "), + ); + let _ = super::write_if_changed(&self.src_path, &self.src); + } +} + +struct FileContent<'a>(&'a Path); + +impl<'a> Display for FileContent<'a> { + fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { + write!(out, "include_bytes!({:?})", self.0) + } +} + +struct ByteString<'a>(&'a [u8]); + +impl<'a> Display for ByteString<'a> { + fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { + out.write_str("b\"")?; + for byte in self.0 { + escape_default(*byte).fmt(out)?; + } + out.write_str("\"") + } +} + +fn name_and_ext(path: &Path) -> Option<(&str, &str)> { + if let (Some(name), Some(ext)) = (path.file_name(), path.extension()) { + if let (Some(name), Some(ext)) = (name.to_str(), ext.to_str()) { + return Some((&name[..name.len() - ext.len() - 1], ext)); + } + } + None +} + +#[cfg(any( + all(feature = "mime03", feature = "http-types"), + all(feature = "mime02", feature = "http-types"), + all(feature = "mime02", feature = "mime03"), +))] +compile_error!( + r#"Only one of these features "http-types", "mime02" or "mime03" must be enabled at a time."# +); + +/// A short and url-safe checksum string from string data. +fn checksum_slug(data: &[u8]) -> String { + use base64::prelude::{Engine, BASE64_URL_SAFE_NO_PAD}; + BASE64_URL_SAFE_NO_PAD.encode(&md5::compute(data)[..6]) +} +#[cfg(not(feature = "mime02"))] +#[cfg(not(feature = "mime03"))] +#[cfg(not(feature = "http-types"))] +fn mime_arg(_: &str) -> String { + "".to_string() +} +#[cfg(feature = "mime02")] +fn mime_arg(suffix: &str) -> String { + format!(" _mime: {:?},\n", mime_from_suffix(suffix)) +} + +#[cfg(feature = "mime02")] +fn mime_from_suffix(suffix: &str) -> &'static str { + match suffix.to_lowercase().as_ref() { + "bmp" => "image/bmp", + "css" => "text/css", + "eot" => "application/vnd.ms-fontobject", + "gif" => "image/gif", + "jpg" | "jpeg" => "image/jpeg", + "js" | "jsonp" => "application/javascript", + "json" => "application/json", + "png" => "image/png", + "svg" => "image/svg+xml", + "woff" => "font/woff", + "woff2" => "font/woff2", + _ => "application/octet-stream", + } +} + +#[cfg(any(feature = "mime03", feature = "http-types"))] +fn mime_arg(suffix: &str) -> String { + format!(" mime: &mime::{},\n", mime_from_suffix(suffix)) +} + +#[cfg(feature = "mime03")] +fn mime_from_suffix(suffix: &str) -> &'static str { + // This is limited to the constants that is defined in mime 0.3. + match suffix.to_lowercase().as_ref() { + "bmp" => "IMAGE_BMP", + "css" => "TEXT_CSS", + "gif" => "IMAGE_GIF", + "jpg" | "jpeg" => "IMAGE_JPEG", + "js" | "jsonp" => "TEXT_JAVASCRIPT", + "json" => "APPLICATION_JSON", + "png" => "IMAGE_PNG", + "svg" => "IMAGE_SVG", + "woff" => "FONT_WOFF", + "woff2" => "FONT_WOFF", + _ => "APPLICATION_OCTET_STREAM", + } +} + +#[cfg(feature = "http-types")] +fn mime_from_suffix(suffix: &str) -> &'static str { + match suffix.to_lowercase().as_ref() { + "css" => "CSS", + "html" | "htm" => "CSS", + "ico" => "ICO", + "jpg" | "jpeg" => "JPEG", + "js" | "jsonp" => "JAVASCRIPT", + "json" => "JSON", + "png" => "PNG", + "svg" => "SVG", + "txt" => "PLAIN", + "wasm" => "WASM", + "xml" => "XML", + _ => "mime::BYTE_STREAM", + } +} diff --git a/ructe-0.17.0/src/template.rs b/ructe-0.17.0/src/template.rs new file mode 100644 index 0000000..0b08501 --- /dev/null +++ b/ructe-0.17.0/src/template.rs @@ -0,0 +1,253 @@ +use crate::expression::{input_to_str, rust_name}; +use crate::parseresult::PResult; +use crate::spacelike::spacelike; +use crate::templateexpression::{template_expression, TemplateExpression}; +use itertools::Itertools; +use nom::branch::alt; +use nom::bytes::complete::is_not; +use nom::bytes::complete::tag; +use nom::character::complete::{char, multispace0}; +use nom::combinator::{map, map_res, opt, recognize}; +use nom::error::context; +use nom::multi::{many0, many_till, separated_list0, separated_list1}; +use nom::sequence::{delimited, preceded, terminated, tuple}; +use std::io::{self, Write}; + +#[derive(Debug, PartialEq, Eq)] +pub struct Template { + preamble: Vec, + type_args: String, + args: Vec, + body: Vec, +} + +impl Template { + pub fn write_rust( + &self, + out: &mut impl Write, + name: &str, + ) -> io::Result<()> { + out.write_all( + b"use std::io::{self, Write};\n\ + #[allow(renamed_and_removed_lints)]\n\ + #[cfg_attr(feature=\"cargo-clippy\", \ + allow(useless_attribute))]\n\ + #[allow(unused)]\n\ + use super::{Html,ToHtml};\n", + )?; + for line in &self.preamble { + writeln!(out, "{line};")?; + } + writeln!( + out, + "\n\ + #[allow(clippy::used_underscore_binding)]\n + pub fn {name}<{ta}{ta_sep}W>(#[allow(unused_mut)] mut _ructe_out_: W{args}) -> io::Result<()>\n\ + where W: Write {{\n\ + {body}\ + Ok(())\n\ + }}", + name = name, + ta = self.type_args, + ta_sep = if self.type_args.is_empty() { "" } else { ", " }, + args = + self.args.iter().format_with("", |arg, f| f(&format_args!( + ", {}", + arg.replace( + " Content", + " impl FnOnce(&mut W) -> io::Result<()>" + ) + ))), + body = self.body.iter().map(|b| b.code()).format(""), + ) + } +} + +pub fn template(input: &[u8]) -> PResult