Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions askama_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ fn build_template(ast: &syn::DeriveInput) -> Result<String, CompileError> {
let config = Config::new(&config_toml)?;
let input = TemplateInput::new(ast, &config)?;
let source: String = match input.source {
Source::Source(ref s) => s.clone(),
Source::Path(_) => get_template_source(&input.path)?,
Source::Source(ref s) => input.strip.apply(s),
Source::Path(_) => get_template_source(&input.path, input.strip)?,
};

let mut sources = HashMap::new();
Expand Down Expand Up @@ -96,12 +96,12 @@ fn find_used_templates(
.into());
}
dependency_graph.push(dependency_path);
let source = get_template_source(&extends)?;
let source = get_template_source(&extends, input.strip)?;
check.push((extends, source));
}
Node::Import(_, import, _) => {
let import = input.config.find_template(import, Some(&path))?;
let source = get_template_source(&import)?;
let source = get_template_source(&import, input.strip)?;
check.push((import, source));
}
_ => {}
Expand Down
6 changes: 2 additions & 4 deletions askama_shared/src/generator.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use super::{get_template_source, CompileError, Integrations};
use crate::filters;
use crate::heritage::{Context, Heritage};
use crate::input::{Source, TemplateInput};
use crate::parser::{parse, Cond, CondTest, Expr, Loop, Node, Target, When, Ws};
use crate::{filters, get_template_source, CompileError, Integrations};

use proc_macro2::Span;

use quote::{quote, ToTokens};

use std::collections::HashMap;
Expand Down Expand Up @@ -765,7 +763,7 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
.input
.config
.find_template(path, Some(&self.input.path))?;
let src = get_template_source(&path)?;
let src = get_template_source(&path, self.input.strip)?;
let nodes = parse(&src, self.input.syntax)?;

// Make sure the compiler understands that the generated code depends on the template file.
Expand Down
11 changes: 10 additions & 1 deletion askama_shared/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{CompileError, Config, Syntax};
use crate::{CompileError, Config, Strip, Syntax};

use std::path::{Path, PathBuf};
use std::str::FromStr;
Expand All @@ -17,6 +17,7 @@ pub struct TemplateInput<'a> {
pub mime_type: String,
pub parent: Option<&'a syn::Type>,
pub path: PathBuf,
pub strip: Strip,
}

impl TemplateInput<'_> {
Expand Down Expand Up @@ -62,6 +63,7 @@ impl TemplateInput<'_> {
let mut escaping = None;
let mut ext = None;
let mut syntax = None;
let mut strip = config.strip;
for item in template_args {
let pair = match item {
syn::NestedMeta::Meta(syn::Meta::NameValue(ref pair)) => pair,
Expand Down Expand Up @@ -120,6 +122,12 @@ impl TemplateInput<'_> {
} else {
return Err("syntax value must be string literal".into());
}
} else if ident == "strip" {
if let syn::Lit::Str(ref s) = pair.lit {
strip = s.value().parse()?;
} else {
return Err("syntax strip must be string literal".into());
}
} else {
return Err(format!("unsupported attribute key {:?} found", ident).into());
}
Expand Down Expand Up @@ -205,6 +213,7 @@ impl TemplateInput<'_> {
mime_type,
parent,
path,
strip,
})
}

Expand Down
19 changes: 11 additions & 8 deletions askama_shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::{env, fmt, fs};
use serde::Deserialize;

pub use crate::input::extension_to_mime_type;
pub use crate::strip::Strip;
pub use askama_escape::MarkupDisplay;

mod error;
Expand All @@ -27,13 +28,15 @@ pub mod heritage;
pub mod input;
#[doc(hidden)]
pub mod parser;
mod strip;

#[derive(Debug)]
pub struct Config<'a> {
pub dirs: Vec<PathBuf>,
pub syntaxes: BTreeMap<String, Syntax<'a>>,
pub default_syntax: &'a str,
pub escapers: Vec<(HashSet<String>, String)>,
pub strip: Strip,
}

impl Config<'_> {
Expand Down Expand Up @@ -102,6 +105,7 @@ impl Config<'_> {
syntaxes,
default_syntax,
escapers,
strip: raw.strip.unwrap_or_default(),
})
}

Expand Down Expand Up @@ -200,6 +204,7 @@ struct RawConfig<'d> {
general: Option<General<'d>>,
syntax: Option<Vec<RawSyntax<'d>>>,
escaper: Option<Vec<RawEscaper<'d>>>,
strip: Option<Strip>,
}

impl RawConfig<'_> {
Expand Down Expand Up @@ -257,19 +262,17 @@ where
}

#[allow(clippy::match_wild_err_arm)]
pub fn get_template_source(tpl_path: &Path) -> std::result::Result<String, CompileError> {
pub fn get_template_source(
tpl_path: &Path,
strip: Strip,
) -> std::result::Result<String, CompileError> {
match fs::read_to_string(tpl_path) {
Err(_) => Err(format!(
"unable to open template file '{}'",
tpl_path.to_str().unwrap()
)
.into()),
Ok(mut source) => {
if source.ends_with('\n') {
let _ = source.pop();
}
Ok(source)
}
Ok(source) => Ok(strip.apply(source)),
}
}

Expand Down Expand Up @@ -328,7 +331,7 @@ mod tests {
let path = Config::new("")
.and_then(|config| config.find_template("b.html", None))
.unwrap();
assert_eq!(get_template_source(&path).unwrap(), "bar");
assert_eq!(get_template_source(&path, Strip::Tail).unwrap(), "bar");
}

#[test]
Expand Down
117 changes: 117 additions & 0 deletions askama_shared/src/strip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use crate::CompileError;

use std::borrow::Cow;
use std::convert::{TryFrom, TryInto};
use std::str::FromStr;

/// Whitespace handling of the input source.
///
/// The automatic stripping does not try to understand the input at all. It
/// does not know what `xml:space="preserve"`, `<pre>` or `<textarea>`
/// means.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Strip {
/// "none": Don't strip any spaces in the input
None,
/// "tail": Remove a single single newline at the end of the input. This is the default
Tail,
/// "trim-lines": Remove all whitespaces at the front and back of all lines, and remove empty
/// lines
TrimLines,
/// "eager": Like "trim", but also replace runs of whitespaces with a single space.
Eager,
}

impl Default for Strip {
fn default() -> Self {
Strip::Tail
}
}

impl TryFrom<&str> for Strip {
type Error = String;

fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"none" => Ok(Strip::None),
"tail" => Ok(Strip::Tail),
"trim-lines" => Ok(Strip::TrimLines),
"eager" => Ok(Strip::Eager),
v => return Err(format!("invalid value for strip: {:?}", v)),
}
}
}

impl FromStr for Strip {
type Err = CompileError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into().map_err(Into::into)
}
}

#[cfg(feature = "serde")]
const _: () = {
use std::fmt;

struct StripVisitor;

impl<'de> serde::de::Visitor<'de> for StripVisitor {
type Value = Strip;

fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r#"the string "none", "tail", "trim-lines", or "eager""#)
}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
s.try_into().map_err(E::custom)
}
}

impl<'de> serde::Deserialize<'de> for Strip {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(StripVisitor)
}
}
};

impl Strip {
pub fn apply<'a, S: Into<Cow<'a, str>>>(self, src: S) -> String {
let src = src.into();
match self {
Strip::None => src.into_owned(),
Strip::Tail => {
let mut s = src.into_owned();
if s.ends_with('\n') {
s.pop();
}
s
}
Strip::TrimLines | Strip::Eager => {
let mut stripped = String::with_capacity(src.len());
for line in src.lines().map(|s| s.trim()).filter(|&s| !s.is_empty()) {
if !stripped.is_empty() {
stripped.push('\n');
}
if self == Strip::Eager {
for (index, word) in line.split_ascii_whitespace().enumerate() {
if index > 0 {
stripped.push(' ');
}
stripped.push_str(word);
}
} else {
stripped.push_str(line);
}
}
stripped
}
}
}
}
10 changes: 10 additions & 0 deletions testing/templates/whitespace_trimming.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

<!DOCTYPE html>

<html>
<body>
<p>
. . .
</p>
</body>
</html>
Loading