Leon template library (#766)

* leon: first implementation

* Update crates/leon/src/values.rs

Co-authored-by: Jiahao XU <Jiahao_XU@outlook.com>

* Workaround orphan rules to make API more intuitive

* Fmt

* Clippy

* Use CoW

* Use cow for items too

* Test that const construction works

* leon: Initial attempt at O(n) parser

* leon: finish parser (except escapes)

* leon: Improve ergonomics of compile-time templates

* Document helpers

* leon: Docs tweaks

* leon: Use macro to minimise parser tests

* leon: add escapes to parser

* leon: test escapes preceding keys

* leon: add multibyte tests

* leon: test escapes following keys

* Format

* Debug

* leon: Don't actually need to keep track of the key

* leon: Parse to vec first

* leon: there's actually no need for string cows

* leon: reorganise and redo macro now that there's no coww

* Well that was silly

* leon: Adjust text end when pushing

* leon: catch unbalanced keys

* Add error tests

* leon: Catch unfinished escape

* Comment out debugging

* leon: fuzz

* Clippy

* leon: Box parse error

* leon: &dyn instead of impl

* Can't impl FromStr, so rename to parse

* Add Vec<> to values

* leon: Add benches for ways to supply values

* leon: Add bench comparing to std and tt

* Fix fuzz

* Fmt

* Split ParseError and RenderError

* Make miette optional

* Remove RenderError lifetime

* Simplify ParseError type schema

* Write concrete Values types instead of generics

* Add license files

* Reduce criterion deps

* Make default a cow

* Add a CLI leon tool

* Fix tests

* Clippy

* Disable cli by default

* Avoid failing the build when cli is off

* Add to ci

* Update crates/leon/src/main.rs

Co-authored-by: Jiahao XU <Jiahao_XU@outlook.com>

* Update crates/leon/Cargo.toml

Co-authored-by: Jiahao XU <Jiahao_XU@outlook.com>

* Bump version

* Error not transparent

* Diagnostic can do forwarding

* Simplify error type

* Expand doc examples

* Generic Values for Hash and BTree maps

* One more borrowed

* Forward implementations

* More generics

* Add has_keys

* Lock stdout in leon tool

* No more debug comments in parser

* Even more generics

* Macros to reduce bench duplication

* Further simplify error

* Fix leon main

* Stable support

* Clippy

---------

Co-authored-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Félix Saparelli 2023-03-21 14:36:02 +13:00 committed by GitHub
parent daf8cdd010
commit 2227d363f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 2382 additions and 162 deletions

85
crates/leon/src/error.rs Normal file
View file

@ -0,0 +1,85 @@
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "miette", derive(miette::Diagnostic))]
pub enum RenderError {
/// A key was missing from the provided values.
#[error("missing key `{0}`")]
MissingKey(String),
/// An I/O error passed through from [`Template::render_into`].
#[error("write failed: {0}")]
Io(#[from] std::io::Error),
}
/// An error that can occur when parsing a template.
///
/// When the `miette` feature is enabled, this is a rich miette-powered error
/// which will highlight the source of the error in the template when output
/// (with miette's `fancy` feature). With `miette` disabled, this is opaque.
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
#[cfg_attr(feature = "miette", derive(miette::Diagnostic))]
#[cfg_attr(feature = "miette", diagnostic(transparent))]
#[error(transparent)]
pub struct ParseError(Box<InnerParseError>);
/// The inner (unboxed) type of [`ParseError`].
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
#[cfg_attr(feature = "miette", derive(miette::Diagnostic))]
#[error("template parse failed")]
struct InnerParseError {
#[cfg_attr(feature = "miette", source_code)]
src: String,
#[cfg_attr(feature = "miette", label("This bracket is not opening or closing anything. Try removing it, or escaping it with a backslash."))]
unbalanced: Option<(usize, usize)>,
#[cfg_attr(feature = "miette", label("This escape is malformed."))]
escape: Option<(usize, usize)>,
#[cfg_attr(feature = "miette", label("A key cannot be empty."))]
key_empty: Option<(usize, usize)>,
#[cfg_attr(feature = "miette", label("Escapes are not allowed in keys."))]
key_escape: Option<(usize, usize)>,
}
impl ParseError {
pub(crate) fn unbalanced(src: &str, start: usize, end: usize) -> Self {
Self(Box::new(InnerParseError {
src: String::from(src),
unbalanced: Some((start, end.saturating_sub(start) + 1)),
escape: None,
key_empty: None,
key_escape: None,
}))
}
pub(crate) fn escape(src: &str, start: usize, end: usize) -> Self {
Self(Box::new(InnerParseError {
src: String::from(src),
unbalanced: None,
escape: Some((start, end.saturating_sub(start) + 1)),
key_empty: None,
key_escape: None,
}))
}
pub(crate) fn key_empty(src: &str, start: usize, end: usize) -> Self {
Self(Box::new(InnerParseError {
src: String::from(src),
unbalanced: None,
escape: None,
key_empty: Some((start, end.saturating_sub(start) + 1)),
key_escape: None,
}))
}
pub(crate) fn key_escape(src: &str, start: usize, end: usize) -> Self {
Self(Box::new(InnerParseError {
src: String::from(src),
unbalanced: None,
escape: None,
key_empty: None,
key_escape: Some((start, end.saturating_sub(start) + 1)),
}))
}
}