mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-30 22:41:13 +00:00
Add support for prefixes for string literals
This commit is contained in:
parent
1a24ace17b
commit
71f49a8fc2
@ -9,7 +9,7 @@ use std::{cmp, hash, mem, str};
|
||||
use parser::node::{
|
||||
Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Match, Whitespace, Ws,
|
||||
};
|
||||
use parser::{CharLit, CharPrefix, Expr, Filter, Node, Target, WithSpan};
|
||||
use parser::{CharLit, CharPrefix, Expr, Filter, Node, StrLit, StrPrefix, Target, WithSpan};
|
||||
use quote::quote;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
|
||||
@ -1271,7 +1271,9 @@ impl<'a> Generator<'a> {
|
||||
|
||||
// for now, we only escape strings and chars at compile time
|
||||
let (lit, escape_prefix) = match &**s {
|
||||
Expr::StrLit(input) => (InputKind::StrLit(input), None),
|
||||
Expr::StrLit(StrLit { prefix, content }) => {
|
||||
(InputKind::StrLit(content), prefix.map(|p| p.to_char()))
|
||||
}
|
||||
Expr::CharLit(CharLit { prefix, content }) => (
|
||||
InputKind::CharLit(content),
|
||||
if *prefix == Some(CharPrefix::Binary) {
|
||||
@ -1488,7 +1490,7 @@ impl<'a> Generator<'a> {
|
||||
Ok(match **expr {
|
||||
Expr::BoolLit(s) => self.visit_bool_lit(buf, s),
|
||||
Expr::NumLit(s) => self.visit_num_lit(buf, s),
|
||||
Expr::StrLit(s) => self.visit_str_lit(buf, s),
|
||||
Expr::StrLit(ref s) => self.visit_str_lit(buf, s),
|
||||
Expr::CharLit(ref s) => self.visit_char_lit(buf, s),
|
||||
Expr::Var(s) => self.visit_var(buf, s),
|
||||
Expr::Path(ref path) => self.visit_path(buf, path),
|
||||
@ -1709,7 +1711,22 @@ impl<'a> Generator<'a> {
|
||||
return Err(ctx.generate_error("only two arguments allowed to escape filter", node));
|
||||
}
|
||||
let opt_escaper = match args.get(1).map(|expr| &**expr) {
|
||||
Some(Expr::StrLit(name)) => Some(*name),
|
||||
Some(Expr::StrLit(StrLit { prefix, content })) => {
|
||||
if let Some(prefix) = prefix {
|
||||
let kind = if *prefix == StrPrefix::Binary {
|
||||
"slice"
|
||||
} else {
|
||||
"CStr"
|
||||
};
|
||||
return Err(ctx.generate_error(
|
||||
&format!(
|
||||
"invalid escaper `b{content:?}`. Expected a string, found a {kind}"
|
||||
),
|
||||
&args[1],
|
||||
));
|
||||
}
|
||||
Some(content)
|
||||
}
|
||||
Some(_) => {
|
||||
return Err(ctx.generate_error("invalid escaper type for escape filter", node));
|
||||
}
|
||||
@ -1751,7 +1768,7 @@ impl<'a> Generator<'a> {
|
||||
node: &WithSpan<'_, T>,
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
if !args.is_empty() {
|
||||
if let Expr::StrLit(fmt) = *args[0] {
|
||||
if let Expr::StrLit(ref fmt) = *args[0] {
|
||||
buf.write("::std::format!(");
|
||||
self.visit_str_lit(buf, fmt);
|
||||
if args.len() > 1 {
|
||||
@ -1773,7 +1790,7 @@ impl<'a> Generator<'a> {
|
||||
node: &WithSpan<'_, T>,
|
||||
) -> Result<DisplayWrap, CompileError> {
|
||||
if let [_, arg2] = args {
|
||||
if let Expr::StrLit(fmt) = **arg2 {
|
||||
if let Expr::StrLit(ref fmt) = **arg2 {
|
||||
buf.write("::std::format!(");
|
||||
self.visit_str_lit(buf, fmt);
|
||||
buf.write(',');
|
||||
@ -2087,8 +2104,11 @@ impl<'a> Generator<'a> {
|
||||
DisplayWrap::Unwrapped
|
||||
}
|
||||
|
||||
fn visit_str_lit(&mut self, buf: &mut Buffer, s: &str) -> DisplayWrap {
|
||||
buf.write(format_args!("\"{s}\""));
|
||||
fn visit_str_lit(&mut self, buf: &mut Buffer, s: &StrLit<'_>) -> DisplayWrap {
|
||||
if let Some(prefix) = s.prefix {
|
||||
buf.write(prefix.to_char());
|
||||
}
|
||||
buf.write(format_args!("\"{}\"", s.content));
|
||||
DisplayWrap::Unwrapped
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ use nom::sequence::{pair, preceded, terminated, tuple};
|
||||
|
||||
use crate::{
|
||||
char_lit, filter, identifier, keyword, not_ws, num_lit, path_or_identifier, str_lit, ws,
|
||||
CharLit, ErrorContext, Level, ParseResult, PathOrIdentifier, WithSpan,
|
||||
CharLit, ErrorContext, Level, ParseResult, PathOrIdentifier, StrLit, WithSpan,
|
||||
};
|
||||
|
||||
macro_rules! expr_prec_layer {
|
||||
@ -36,7 +36,7 @@ macro_rules! expr_prec_layer {
|
||||
pub enum Expr<'a> {
|
||||
BoolLit(bool),
|
||||
NumLit(&'a str),
|
||||
StrLit(&'a str),
|
||||
StrLit(StrLit<'a>),
|
||||
CharLit(CharLit<'a>),
|
||||
Var(&'a str),
|
||||
Path(Vec<&'a str>),
|
||||
|
@ -416,7 +416,36 @@ fn separated_digits(radix: u32, start: bool) -> impl Fn(&str) -> ParseResult<'_>
|
||||
}
|
||||
}
|
||||
|
||||
fn str_lit(i: &str) -> ParseResult<'_> {
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum StrPrefix {
|
||||
Binary,
|
||||
CLike,
|
||||
}
|
||||
|
||||
impl StrPrefix {
|
||||
pub fn to_char(self) -> char {
|
||||
match self {
|
||||
Self::Binary => 'b',
|
||||
Self::CLike => 'c',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for StrPrefix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use std::fmt::Write;
|
||||
|
||||
f.write_char(self.to_char())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct StrLit<'a> {
|
||||
pub prefix: Option<StrPrefix>,
|
||||
pub content: &'a str,
|
||||
}
|
||||
|
||||
fn str_lit_without_prefix(i: &str) -> ParseResult<'_> {
|
||||
let (i, s) = delimited(
|
||||
char('"'),
|
||||
opt(escaped(is_not("\\\""), '\\', anychar)),
|
||||
@ -425,6 +454,17 @@ fn str_lit(i: &str) -> ParseResult<'_> {
|
||||
Ok((i, s.unwrap_or_default()))
|
||||
}
|
||||
|
||||
fn str_lit(i: &str) -> Result<(&str, StrLit<'_>), ParseErr<'_>> {
|
||||
let (i, (prefix, content)) =
|
||||
tuple((opt(alt((char('b'), char('c')))), str_lit_without_prefix))(i)?;
|
||||
let prefix = match prefix {
|
||||
Some('b') => Some(StrPrefix::Binary),
|
||||
Some('c') => Some(StrPrefix::CLike),
|
||||
_ => None,
|
||||
};
|
||||
Ok((i, StrLit { prefix, content }))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum CharPrefix {
|
||||
Binary,
|
||||
@ -803,7 +843,7 @@ const PRIMITIVE_TYPES: &[&str] = &{
|
||||
mod test {
|
||||
use std::path::Path;
|
||||
|
||||
use super::{char_lit, num_lit, strip_common};
|
||||
use super::{char_lit, num_lit, str_lit, strip_common, StrLit, StrPrefix};
|
||||
|
||||
#[test]
|
||||
fn test_strip_common() {
|
||||
@ -896,4 +936,29 @@ mod test {
|
||||
assert!(char_lit("'\\u{}'").is_err());
|
||||
assert!(char_lit("'\\u{110000}'").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_str_lit() {
|
||||
assert_eq!(
|
||||
str_lit(r#"b"hello""#).unwrap(),
|
||||
(
|
||||
"",
|
||||
StrLit {
|
||||
prefix: Some(StrPrefix::Binary),
|
||||
content: "hello"
|
||||
}
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
str_lit(r#"c"hello""#).unwrap(),
|
||||
(
|
||||
"",
|
||||
StrLit {
|
||||
prefix: Some(StrPrefix::CLike),
|
||||
content: "hello"
|
||||
}
|
||||
)
|
||||
);
|
||||
assert!(str_lit(r#"d"hello""#).is_err());
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ use nom::sequence::{delimited, pair, preceded, tuple};
|
||||
|
||||
use crate::memchr_splitter::{Splitter1, Splitter2, Splitter3};
|
||||
use crate::{
|
||||
filter, identifier, is_ws, keyword, not_ws, skip_till, str_lit, ws, ErrorContext, Expr, Filter,
|
||||
ParseResult, State, Target, WithSpan,
|
||||
filter, identifier, is_ws, keyword, not_ws, skip_till, str_lit_without_prefix, ws,
|
||||
ErrorContext, Expr, Filter, ParseResult, State, Target, WithSpan,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@ -562,7 +562,7 @@ impl<'a> Import<'a> {
|
||||
opt(Whitespace::parse),
|
||||
ws(keyword("import")),
|
||||
cut(tuple((
|
||||
ws(str_lit),
|
||||
ws(str_lit_without_prefix),
|
||||
ws(keyword("as")),
|
||||
cut(pair(ws(identifier), opt(Whitespace::parse))),
|
||||
))),
|
||||
@ -938,7 +938,7 @@ impl<'a> Include<'a> {
|
||||
let mut p = tuple((
|
||||
opt(Whitespace::parse),
|
||||
ws(keyword("include")),
|
||||
cut(pair(ws(str_lit), opt(Whitespace::parse))),
|
||||
cut(pair(ws(str_lit_without_prefix), opt(Whitespace::parse))),
|
||||
));
|
||||
let (i, (pws, _, (path, nws))) = p(i)?;
|
||||
Ok((
|
||||
@ -966,7 +966,7 @@ impl<'a> Extends<'a> {
|
||||
let (i, (pws, _, (path, nws))) = tuple((
|
||||
opt(Whitespace::parse),
|
||||
ws(keyword("extends")),
|
||||
cut(pair(ws(str_lit), opt(Whitespace::parse))),
|
||||
cut(pair(ws(str_lit_without_prefix), opt(Whitespace::parse))),
|
||||
))(i)?;
|
||||
match (pws, nws) {
|
||||
(None, None) => Ok((i, WithSpan::new(Self { path }, start))),
|
||||
|
@ -7,7 +7,7 @@ use nom::sequence::{pair, preceded, tuple};
|
||||
|
||||
use crate::{
|
||||
bool_lit, char_lit, identifier, keyword, num_lit, path_or_identifier, str_lit, ws, CharLit,
|
||||
ErrorContext, ParseErr, ParseResult, PathOrIdentifier, State, WithSpan,
|
||||
ErrorContext, ParseErr, ParseResult, PathOrIdentifier, State, StrLit, WithSpan,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -17,7 +17,7 @@ pub enum Target<'a> {
|
||||
Array(Vec<&'a str>, Vec<Target<'a>>),
|
||||
Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>),
|
||||
NumLit(&'a str),
|
||||
StrLit(&'a str),
|
||||
StrLit(StrLit<'a>),
|
||||
CharLit(CharLit<'a>),
|
||||
BoolLit(&'a str),
|
||||
Path(Vec<&'a str>),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::node::{Lit, Whitespace, Ws};
|
||||
use super::{Ast, Expr, Filter, Node, Syntax, WithSpan};
|
||||
use crate::node::{Lit, Whitespace, Ws};
|
||||
use crate::{Ast, Expr, Filter, Node, StrLit, Syntax, WithSpan};
|
||||
|
||||
impl<T> WithSpan<'static, T> {
|
||||
fn no_span(inner: T) -> Self {
|
||||
@ -218,7 +218,10 @@ fn test_parse_var_call() {
|
||||
WithSpan::no_span(Expr::Call(
|
||||
Box::new(WithSpan::no_span(Expr::Var("function"))),
|
||||
vec![
|
||||
WithSpan::no_span(Expr::StrLit("123")),
|
||||
WithSpan::no_span(Expr::StrLit(StrLit {
|
||||
content: "123",
|
||||
prefix: None,
|
||||
})),
|
||||
WithSpan::no_span(Expr::NumLit("3"))
|
||||
]
|
||||
)),
|
||||
@ -259,7 +262,10 @@ fn test_parse_path_call() {
|
||||
WithSpan::no_span(Expr::Call(
|
||||
Box::new(WithSpan::no_span(Expr::Path(vec!["self", "function"]))),
|
||||
vec![
|
||||
WithSpan::no_span(Expr::StrLit("123")),
|
||||
WithSpan::no_span(Expr::StrLit(StrLit {
|
||||
content: "123",
|
||||
prefix: None,
|
||||
})),
|
||||
WithSpan::no_span(Expr::NumLit("3"))
|
||||
],
|
||||
),)
|
||||
|
@ -27,3 +27,35 @@ fn test_prefix_char_literal_in_target() {
|
||||
let t = Target { data: b"hi" };
|
||||
assert_eq!(t.render().unwrap(), "bc hoy");
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = r#"{% if x == b"hi".as_slice() %}bc{% endif %}
|
||||
{%- if c"a".to_bytes_with_nul() == b"a\0" %} hoy{% endif %}"#,
|
||||
ext = "txt"
|
||||
)]
|
||||
struct ExprStr {
|
||||
x: &'static [u8],
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_str_literal_in_expr() {
|
||||
let t = ExprStr { x: b"hi" };
|
||||
assert_eq!(t.render().unwrap(), "bc hoy");
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(
|
||||
source = r#"{% if let Some(b"hi") = Some(data) %}bc{% endif %}
|
||||
{%- if let x = c"hi" %} hoy{% endif %}"#,
|
||||
ext = "txt"
|
||||
)]
|
||||
struct TargetStr {
|
||||
data: [u8; 2],
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_str_literal_in_target() {
|
||||
let t = TargetStr { data: *b"hi" };
|
||||
assert_eq!(t.render().unwrap(), "bc hoy");
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user