Skip to content

Commit ee5481f

Browse files
authored
Merge pull request #59 from jonathan-s/filter-refactor
Filter refactor
2 parents 5c20ff9 + 684269f commit ee5481f

File tree

6 files changed

+478
-308
lines changed

6 files changed

+478
-308
lines changed

src/error.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use miette::{Diagnostic, SourceSpan};
2+
use pyo3::prelude::*;
3+
use thiserror::Error;
4+
5+
#[derive(Error, Debug)]
6+
pub enum PyRenderError {
7+
#[error(transparent)]
8+
PyErr(#[from] PyErr),
9+
#[error(transparent)]
10+
RenderError(#[from] RenderError),
11+
}
12+
13+
impl PyRenderError {
14+
pub fn try_into_render_error(self) -> Result<RenderError, PyErr> {
15+
match self {
16+
Self::RenderError(err) => Ok(err),
17+
Self::PyErr(err) => Err(err),
18+
}
19+
}
20+
}
21+
22+
#[derive(Error, Debug, Diagnostic, PartialEq, Eq)]
23+
pub enum RenderError {
24+
#[error("Failed lookup for key [{key}] in {object}")]
25+
VariableDoesNotExist {
26+
key: String,
27+
object: String,
28+
#[label("key")]
29+
key_at: SourceSpan,
30+
#[label("{object}")]
31+
object_at: Option<SourceSpan>,
32+
},
33+
}

src/filters.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
use crate::render::{Context, IntoBorrowedContent, IntoOwnedContent, Render, TemplateResult};
2+
use crate::types::Argument;
3+
use crate::{render::Content, types::TemplateString};
4+
use pyo3::prelude::*;
5+
6+
#[derive(Debug)]
7+
pub enum FilterType {
8+
Add(AddFilter),
9+
AddSlashes(AddSlashesFilter),
10+
Capfirst(CapfirstFilter),
11+
Default(DefaultFilter),
12+
External(ExternalFilter),
13+
Lower(LowerFilter),
14+
}
15+
16+
pub trait ResolveFilter {
17+
fn resolve<'t, 'py>(
18+
&self,
19+
variable: Option<Content<'t, 'py>>,
20+
py: Python<'py>,
21+
template: TemplateString<'t>,
22+
context: &mut Context,
23+
) -> TemplateResult<'t, 'py>;
24+
}
25+
26+
#[derive(Debug)]
27+
pub struct AddSlashesFilter;
28+
29+
impl ResolveFilter for AddSlashesFilter {
30+
fn resolve<'t, 'py>(
31+
&self,
32+
variable: Option<Content<'t, 'py>>,
33+
_py: Python<'py>,
34+
_template: TemplateString<'t>,
35+
context: &mut Context,
36+
) -> TemplateResult<'t, 'py> {
37+
let content = match variable {
38+
Some(content) => content
39+
.render(context)?
40+
.replace(r"\", r"\\")
41+
.replace("\"", "\\\"")
42+
.replace("'", r"\'")
43+
.into_content(),
44+
None => "".into_content(),
45+
};
46+
Ok(content)
47+
}
48+
}
49+
50+
#[derive(Debug)]
51+
pub struct AddFilter {
52+
pub argument: Argument,
53+
}
54+
55+
impl AddFilter {
56+
pub fn new(argument: Argument) -> Self {
57+
Self { argument: argument }
58+
}
59+
}
60+
61+
impl ResolveFilter for AddFilter {
62+
fn resolve<'t, 'py>(
63+
&self,
64+
variable: Option<Content<'t, 'py>>,
65+
py: Python<'py>,
66+
template: TemplateString<'t>,
67+
context: &mut Context,
68+
) -> TemplateResult<'t, 'py> {
69+
let variable = match variable {
70+
Some(left) => left,
71+
None => return Ok(None),
72+
};
73+
let right = self
74+
.argument
75+
.resolve(py, template, context)?
76+
.expect("missing argument in context should already have raised");
77+
match (variable.to_bigint(), right.to_bigint()) {
78+
(Some(variable), Some(right)) => return Ok(Some(Content::Int(variable + right))),
79+
_ => {
80+
let variable = variable.to_py(py);
81+
let right = right.to_py(py);
82+
match variable.add(right) {
83+
Ok(sum) => return Ok(Some(Content::Py(sum))),
84+
Err(_) => return Ok(None),
85+
}
86+
}
87+
}
88+
}
89+
}
90+
91+
#[derive(Debug)]
92+
pub struct CapfirstFilter;
93+
94+
impl ResolveFilter for CapfirstFilter {
95+
fn resolve<'t, 'py>(
96+
&self,
97+
variable: Option<Content<'t, 'py>>,
98+
_py: Python<'py>,
99+
_template: TemplateString<'t>,
100+
context: &mut Context,
101+
) -> TemplateResult<'t, 'py> {
102+
let content = match variable {
103+
Some(content) => {
104+
let content_string = content.render(context)?.into_owned();
105+
let mut chars = content_string.chars();
106+
let first_char = match chars.next() {
107+
Some(c) => c.to_uppercase(),
108+
None => return Ok("".into_content()),
109+
};
110+
let string: String = first_char.chain(chars).collect();
111+
string.into_content()
112+
}
113+
None => "".into_content(),
114+
};
115+
Ok(content)
116+
}
117+
}
118+
119+
#[derive(Debug)]
120+
pub struct DefaultFilter {
121+
pub argument: Argument,
122+
}
123+
124+
impl DefaultFilter {
125+
pub fn new(argument: Argument) -> Self {
126+
Self { argument: argument }
127+
}
128+
}
129+
130+
impl ResolveFilter for DefaultFilter {
131+
fn resolve<'t, 'py>(
132+
&self,
133+
variable: Option<Content<'t, 'py>>,
134+
py: Python<'py>,
135+
template: TemplateString<'t>,
136+
context: &mut Context,
137+
) -> TemplateResult<'t, 'py> {
138+
let content = match variable {
139+
Some(left) => Some(left),
140+
None => self.argument.resolve(py, template, context)?,
141+
};
142+
Ok(content)
143+
}
144+
}
145+
146+
#[derive(Debug)]
147+
pub struct ExternalFilter {
148+
pub filter: Py<PyAny>,
149+
pub argument: Option<Argument>,
150+
}
151+
152+
impl ExternalFilter {
153+
pub fn new(filter: Py<PyAny>, argument: Option<Argument>) -> Self {
154+
Self {
155+
filter: filter,
156+
argument: argument,
157+
}
158+
}
159+
}
160+
161+
impl ResolveFilter for ExternalFilter {
162+
fn resolve<'t, 'py>(
163+
&self,
164+
variable: Option<Content<'t, 'py>>,
165+
py: Python<'py>,
166+
template: TemplateString<'t>,
167+
context: &mut Context,
168+
) -> TemplateResult<'t, 'py> {
169+
let arg = match &self.argument {
170+
Some(arg) => arg.resolve(py, template, context)?,
171+
None => None,
172+
};
173+
let filter = self.filter.bind(py);
174+
let value = match arg {
175+
Some(arg) => filter.call1((variable, arg))?,
176+
None => filter.call1((variable,))?,
177+
};
178+
Ok(Some(Content::Py(value)))
179+
}
180+
}
181+
182+
#[derive(Debug)]
183+
pub struct LowerFilter;
184+
185+
impl ResolveFilter for LowerFilter {
186+
fn resolve<'t, 'py>(
187+
&self,
188+
variable: Option<Content<'t, 'py>>,
189+
_py: Python<'py>,
190+
_template: TemplateString<'t>,
191+
context: &mut Context,
192+
) -> TemplateResult<'t, 'py> {
193+
let content = match variable {
194+
Some(content) => content.render(context)?.to_lowercase().into_content(),
195+
None => "".into_content(),
196+
};
197+
Ok(content)
198+
}
199+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
mod error;
2+
mod filters;
13
mod lex;
24
mod loaders;
35
mod parse;

0 commit comments

Comments
 (0)