Add proper impl for templating
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
1ee7611981
commit
4470af3926
5 changed files with 85 additions and 27 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
use thiserror::Error;
|
||||||
use winnow::Parser;
|
use winnow::Parser;
|
||||||
use winnow::RecoverableParser;
|
use winnow::RecoverableParser;
|
||||||
use winnow::combinator::alt;
|
use winnow::combinator::alt;
|
||||||
|
|
@ -113,9 +114,15 @@ impl ParserError<Input<'_>> for AstError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub struct AstFailure {}
|
pub struct AstFailure {}
|
||||||
|
|
||||||
|
impl std::fmt::Display for AstFailure {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("TODO")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AstFailure {
|
impl AstFailure {
|
||||||
fn from_errors(_errors: Vec<AstError>, _input: &[TemplateToken]) -> AstFailure {
|
fn from_errors(_errors: Vec<AstError>, _input: &[TemplateToken]) -> AstFailure {
|
||||||
AstFailure {}
|
AstFailure {}
|
||||||
|
|
@ -217,7 +224,7 @@ mod tests {
|
||||||
fn check_only_content() {
|
fn check_only_content() {
|
||||||
let input = "Hello World";
|
let input = "Hello World";
|
||||||
|
|
||||||
let parsed = crate::parser::parse(input).unwrap();
|
let parsed = crate::parser::parse(input.into()).unwrap();
|
||||||
|
|
||||||
let ast = parse(parsed.tokens()).unwrap();
|
let ast = parse(parsed.tokens()).unwrap();
|
||||||
|
|
||||||
|
|
@ -239,7 +246,7 @@ mod tests {
|
||||||
fn check_simple_variable_interpolation() {
|
fn check_simple_variable_interpolation() {
|
||||||
let input = "Hello {{= world }}";
|
let input = "Hello {{= world }}";
|
||||||
|
|
||||||
let parsed = crate::parser::parse(input).unwrap();
|
let parsed = crate::parser::parse(input.into()).unwrap();
|
||||||
|
|
||||||
let ast = parse(parsed.tokens()).unwrap();
|
let ast = parse(parsed.tokens()).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ mod tests {
|
||||||
fn check_simple_variable_interpolation() {
|
fn check_simple_variable_interpolation() {
|
||||||
let input = "Hello {{= world }}";
|
let input = "Hello {{= world }}";
|
||||||
|
|
||||||
let parsed = crate::parser::parse(input).unwrap();
|
let parsed = crate::parser::parse(input.into()).unwrap();
|
||||||
|
|
||||||
let ast = crate::ast::parse(parsed.tokens()).unwrap();
|
let ast = crate::ast::parse(parsed.tokens()).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,14 @@ use crate::emit::Instruction;
|
||||||
use crate::input::TempleInput;
|
use crate::input::TempleInput;
|
||||||
|
|
||||||
#[derive(Debug, Error, Display)]
|
#[derive(Debug, Error, Display)]
|
||||||
enum EvalError {
|
pub enum EvaluationError {
|
||||||
/// An unknown variable was encountered: .0
|
/// An unknown variable was encountered: .0
|
||||||
UnknownVariable(TempleInput),
|
UnknownVariable(TempleInput),
|
||||||
/// An explicit abort was requested
|
/// An explicit abort was requested
|
||||||
ExplicitAbort,
|
ExplicitAbort,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(instructions: &[Instruction], global_context: &Context) -> Result<String, EvalError> {
|
pub fn execute(instructions: &[Instruction], global_context: &Context) -> Result<String, EvaluationError> {
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
|
|
||||||
let mut scopes: HashMap<crate::emit::VariableSlot, serde_json::Value> = HashMap::new();
|
let mut scopes: HashMap<crate::emit::VariableSlot, serde_json::Value> = HashMap::new();
|
||||||
|
|
@ -27,7 +27,7 @@ fn execute(instructions: &[Instruction], global_context: &Context) -> Result<Str
|
||||||
let value = global_context
|
let value = global_context
|
||||||
.values
|
.values
|
||||||
.get(name.as_str())
|
.get(name.as_str())
|
||||||
.ok_or(EvalError::UnknownVariable(name.clone()))?;
|
.ok_or(EvaluationError::UnknownVariable(name.clone()))?;
|
||||||
|
|
||||||
scopes.insert(*slot, value.clone());
|
scopes.insert(*slot, value.clone());
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +36,7 @@ fn execute(instructions: &[Instruction], global_context: &Context) -> Result<Str
|
||||||
output.push_str(value);
|
output.push_str(value);
|
||||||
}
|
}
|
||||||
Instruction::PushScope { inherit_parent: _ } => todo!(),
|
Instruction::PushScope { inherit_parent: _ } => todo!(),
|
||||||
Instruction::Abort => return Err(EvalError::ExplicitAbort),
|
Instruction::Abort => return Err(EvaluationError::ExplicitAbort),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ mod tests {
|
||||||
fn check_simple_variable_interpolation() {
|
fn check_simple_variable_interpolation() {
|
||||||
let input = "Hello {{= world }}";
|
let input = "Hello {{= world }}";
|
||||||
|
|
||||||
let parsed = crate::parser::parse(input).unwrap();
|
let parsed = crate::parser::parse(input.into()).unwrap();
|
||||||
|
|
||||||
let ast = crate::ast::parse(parsed.tokens()).unwrap();
|
let ast = crate::ast::parse(parsed.tokens()).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
58
src/lib.rs
58
src/lib.rs
|
|
@ -5,16 +5,36 @@ use displaydoc::Display;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::emit::Instruction;
|
||||||
|
use crate::input::TempleInput;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod emit;
|
pub mod emit;
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod parser;
|
|
||||||
mod input;
|
mod input;
|
||||||
|
pub mod parser;
|
||||||
|
|
||||||
#[derive(Debug, Error, Display)]
|
#[derive(Debug, Error, Display)]
|
||||||
pub enum TempleError {
|
pub enum TempleError {
|
||||||
/// Could not parse the given template
|
/// Could not parse the given template
|
||||||
ParseError {},
|
ParseError {
|
||||||
|
#[from]
|
||||||
|
source: parser::ParseFailure,
|
||||||
|
},
|
||||||
|
/// Invalid Template
|
||||||
|
AstError {
|
||||||
|
#[from]
|
||||||
|
source: ast::AstFailure,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// An error occurred while evaluating
|
||||||
|
EvaluationError {
|
||||||
|
#[from]
|
||||||
|
source: eval::EvaluationError,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The template '{0}' could not be found
|
||||||
|
UnknownTemplate(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Temple {
|
pub struct Temple {
|
||||||
|
|
@ -37,17 +57,41 @@ impl Temple {
|
||||||
pub fn add_template(
|
pub fn add_template(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
value: impl AsRef<str>,
|
value: impl Into<TempleInput>,
|
||||||
) -> Result<(), TempleError> {
|
) -> Result<(), TempleError> {
|
||||||
|
let source = value.into();
|
||||||
|
let parse = parser::parse(source.clone())?;
|
||||||
|
let ast = ast::parse(parse.tokens())?;
|
||||||
|
|
||||||
|
let instructions = emit::emit_machine(ast);
|
||||||
|
|
||||||
|
self.templates.insert(
|
||||||
|
name.into(),
|
||||||
|
Template {
|
||||||
|
source,
|
||||||
|
instructions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&self, arg: &str, ctx: &Context) -> Result<String, TempleError> {
|
pub fn render(&self, name: &str, ctx: &Context) -> Result<String, TempleError> {
|
||||||
Ok(String::new())
|
let template = self
|
||||||
|
.templates
|
||||||
|
.get(name)
|
||||||
|
.ok_or_else(|| TempleError::UnknownTemplate(name.to_string()))?;
|
||||||
|
|
||||||
|
let res = eval::execute(&template.instructions, ctx)?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Template {}
|
struct Template {
|
||||||
|
source: TempleInput,
|
||||||
|
instructions: Vec<Instruction>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
values: BTreeMap<String, serde_json::Value>,
|
values: BTreeMap<String, serde_json::Value>,
|
||||||
|
|
@ -155,6 +199,6 @@ mod tests {
|
||||||
|
|
||||||
let rendered = temp.render("base", &ctx).unwrap();
|
let rendered = temp.render("base", &ctx).unwrap();
|
||||||
|
|
||||||
insta::assert_snapshot!(rendered, @"")
|
insta::assert_snapshot!(rendered, @"Hello World")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use annotate_snippets::AnnotationKind;
|
||||||
use annotate_snippets::Level;
|
use annotate_snippets::Level;
|
||||||
use annotate_snippets::Renderer;
|
use annotate_snippets::Renderer;
|
||||||
use annotate_snippets::Snippet;
|
use annotate_snippets::Snippet;
|
||||||
|
use thiserror::Error;
|
||||||
use winnow::LocatingSlice;
|
use winnow::LocatingSlice;
|
||||||
use winnow::Parser;
|
use winnow::Parser;
|
||||||
use winnow::RecoverableParser;
|
use winnow::RecoverableParser;
|
||||||
|
|
@ -37,16 +38,22 @@ use crate::resume_after_cut;
|
||||||
type Input<'input> = Recoverable<LocatingSlice<TempleInput>, ParseError>;
|
type Input<'input> = Recoverable<LocatingSlice<TempleInput>, ParseError>;
|
||||||
type PResult<'input, T> = Result<T, ParseError>;
|
type PResult<'input, T> = Result<T, ParseError>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Error)]
|
||||||
pub struct ParseFailure {
|
pub struct ParseFailure {
|
||||||
source: Arc<str>,
|
input: Arc<str>,
|
||||||
errors: Vec<ParseError>,
|
errors: Vec<ParseError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ParseFailure {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.to_report())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ParseFailure {
|
impl ParseFailure {
|
||||||
fn from_errors(errors: Vec<ParseError>, input: &str) -> ParseFailure {
|
fn from_errors(errors: Vec<ParseError>, input: TempleInput) -> ParseFailure {
|
||||||
ParseFailure {
|
ParseFailure {
|
||||||
source: Arc::from(input.to_string()),
|
input: Arc::from(input.to_string()),
|
||||||
errors,
|
errors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +71,7 @@ impl ParseFailure {
|
||||||
.unwrap_or("An error occurred while parsing"),
|
.unwrap_or("An error occurred while parsing"),
|
||||||
)
|
)
|
||||||
.element(
|
.element(
|
||||||
Snippet::source(self.source.as_ref()).annotation(
|
Snippet::source(self.input.as_ref()).annotation(
|
||||||
AnnotationKind::Primary
|
AnnotationKind::Primary
|
||||||
.span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)),
|
.span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)),
|
||||||
),
|
),
|
||||||
|
|
@ -294,9 +301,9 @@ impl TemplateToken {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(input: &str) -> Result<ParsedTemplate, ParseFailure> {
|
pub fn parse(input: TempleInput) -> Result<ParsedTemplate, ParseFailure> {
|
||||||
let (_remaining, val, errors) =
|
let (_remaining, val, errors) =
|
||||||
parse_tokens.recoverable_parse(LocatingSlice::new(TempleInput::from(input)));
|
parse_tokens.recoverable_parse(LocatingSlice::new(input.clone()));
|
||||||
|
|
||||||
if errors.is_empty()
|
if errors.is_empty()
|
||||||
&& let Some(val) = val
|
&& let Some(val) = val
|
||||||
|
|
@ -418,7 +425,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_simple() {
|
fn parse_simple() {
|
||||||
let input = "Hello There";
|
let input = "Hello There";
|
||||||
let output = parse(input);
|
let output = parse(input.into());
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(output, @r#"
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
Ok(
|
Ok(
|
||||||
|
|
@ -437,7 +444,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_interpolate() {
|
fn parse_interpolate() {
|
||||||
let input = "Hello {{ there }}";
|
let input = "Hello {{ there }}";
|
||||||
let output = parse(input);
|
let output = parse(input.into());
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(output, @r#"
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
Ok(
|
Ok(
|
||||||
|
|
@ -480,12 +487,12 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_interpolate_bad() {
|
fn parse_interpolate_bad() {
|
||||||
let input = "Hello {{ the2re }} {{ the@re }}";
|
let input = "Hello {{ the2re }} {{ the@re }}";
|
||||||
let output = parse(input);
|
let output = parse(input.into());
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(output, @r#"
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
Err(
|
Err(
|
||||||
ParseFailure {
|
ParseFailure {
|
||||||
source: "Hello {{ the2re }} {{ the@re }}",
|
input: "Hello {{ the2re }} {{ the@re }}",
|
||||||
errors: [
|
errors: [
|
||||||
ParseError {
|
ParseError {
|
||||||
message: Some(
|
message: Some(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue