Track config files

This commit is contained in:
Guillaume Gomez 2025-03-31 22:41:49 +02:00
parent aa5e200b10
commit d3f0947d11
5 changed files with 48 additions and 25 deletions

View File

@ -40,6 +40,7 @@ struct ConfigKey<'a> {
source: Cow<'a, str>,
config_path: Option<Cow<'a, str>>,
template_whitespace: Option<Whitespace>,
full_config_path: Option<PathBuf>,
}
impl ToOwned for ConfigKey<'_> {
@ -53,6 +54,7 @@ impl ToOwned for ConfigKey<'_> {
.as_ref()
.map(|s| Cow::Owned(s.as_ref().to_owned())),
template_whitespace: self.template_whitespace,
full_config_path: self.full_config_path.clone(),
};
OwnedConfigKey(Box::leak(Box::new(owned_key)))
}
@ -71,6 +73,7 @@ impl Config {
config_path: Option<&str>,
template_whitespace: Option<Whitespace>,
config_span: Option<Span>,
full_config_path: Option<PathBuf>,
) -> Result<&'static Config, CompileError> {
static CACHE: ManuallyDrop<OnceLock<OnceMap<OwnedConfigKey, &'static Config>>> =
ManuallyDrop::new(OnceLock::new());
@ -79,6 +82,7 @@ impl Config {
source: source.into(),
config_path: config_path.map(Cow::Borrowed),
template_whitespace,
full_config_path,
},
|key| {
let config = Config::new_uncached(key.to_owned(), config_span)?;
@ -88,6 +92,10 @@ impl Config {
|config| *config,
)
}
pub(crate) fn full_config_path(&self) -> Option<&Path> {
self._key.0.full_config_path.as_deref()
}
}
impl Config {
@ -332,7 +340,7 @@ struct RawEscaper<'a> {
pub(crate) fn read_config_file(
config_path: Option<&str>,
span: Option<Span>,
) -> Result<String, CompileError> {
) -> Result<(String, Option<PathBuf>), CompileError> {
let root = manifest_root();
let filename = match config_path {
Some(config_path) => root.join(config_path),
@ -340,19 +348,20 @@ pub(crate) fn read_config_file(
};
if filename.exists() {
fs::read_to_string(&filename).map_err(|err| {
let content = fs::read_to_string(&filename).map_err(|err| {
CompileError::no_file_info(
format_args!("unable to read {}: {err}", filename.display()),
span,
)
})
})?;
Ok((content, filename.canonicalize().ok()))
} else if config_path.is_some() {
Err(CompileError::no_file_info(
format_args!("`{}` does not exist", filename.display()),
span,
))
} else {
Ok(String::new())
Ok((String::new(), None))
}
}
@ -387,7 +396,7 @@ mod tests {
fn test_default_config() {
let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
root.push("templates");
let config = Config::new("", None, None, None).unwrap();
let config = Config::new("", None, None, None, None).unwrap();
assert_eq!(config.dirs, vec![root]);
}
@ -396,7 +405,7 @@ mod tests {
fn test_config_dirs() {
let mut root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
root.push("tpl");
let config = Config::new("[general]\ndirs = [\"tpl\"]", None, None, None).unwrap();
let config = Config::new("[general]\ndirs = [\"tpl\"]", None, None, None, None).unwrap();
assert_eq!(config.dirs, vec![root]);
}
@ -417,7 +426,7 @@ mod tests {
#[test]
fn find_absolute() {
let config = Config::new("", None, None, None).unwrap();
let config = Config::new("", None, None, None, None).unwrap();
let root = config.find_template("a.html", None, None).unwrap();
let path = config
.find_template("sub/b.html", Some(&root), None)
@ -428,14 +437,14 @@ mod tests {
#[test]
#[should_panic]
fn find_relative_nonexistent() {
let config = Config::new("", None, None, None).unwrap();
let config = Config::new("", None, None, None, None).unwrap();
let root = config.find_template("a.html", None, None).unwrap();
config.find_template("c.html", Some(&root), None).unwrap();
}
#[test]
fn find_relative() {
let config = Config::new("", None, None, None).unwrap();
let config = Config::new("", None, None, None, None).unwrap();
let root = config.find_template("sub/b.html", None, None).unwrap();
let path = config.find_template("c.html", Some(&root), None).unwrap();
assert_eq_rooted(&path, "sub/c.html");
@ -443,7 +452,7 @@ mod tests {
#[test]
fn find_relative_sub() {
let config = Config::new("", None, None, None).unwrap();
let config = Config::new("", None, None, None, None).unwrap();
let root = config.find_template("sub/b.html", None, None).unwrap();
let path = config
.find_template("sub1/d.html", Some(&root), None)
@ -468,7 +477,7 @@ mod tests {
"#;
let default_syntax = Syntax::default();
let config = Config::new(raw_config, None, None, None).unwrap();
let config = Config::new(raw_config, None, None, None, None).unwrap();
assert_eq!(config.default_syntax, "foo");
let foo = config.syntaxes.get("foo").unwrap();
@ -500,7 +509,7 @@ mod tests {
"#;
let default_syntax = Syntax::default();
let config = Config::new(raw_config, None, None, None).unwrap();
let config = Config::new(raw_config, None, None, None, None).unwrap();
assert_eq!(config.default_syntax, "foo");
let foo = config.syntaxes.get("foo").unwrap();
@ -537,7 +546,7 @@ mod tests {
default_syntax = "emoji"
"#;
let config = Config::new(raw_config, None, None, None).unwrap();
let config = Config::new(raw_config, None, None, None, None).unwrap();
assert_eq!(config.default_syntax, "emoji");
let foo = config.syntaxes.get("emoji").unwrap();
@ -565,7 +574,7 @@ mod tests {
name = "too_short"
block_start = "<"
"#;
let config = Config::new(raw_config, None, None, None);
let config = Config::new(raw_config, None, None, None, None);
assert_eq!(
expect_err(config).msg,
r#"delimiters must be at least two characters long. The opening block delimiter ("<") is too short"#,
@ -576,7 +585,7 @@ mod tests {
name = "contains_ws"
block_start = " {{ "
"#;
let config = Config::new(raw_config, None, None, None);
let config = Config::new(raw_config, None, None, None, None);
assert_eq!(
expect_err(config).msg,
r#"delimiters may not contain white spaces. The opening block delimiter (" {{ ") contains white spaces"#,
@ -589,7 +598,7 @@ mod tests {
expr_start = "{{$"
comment_start = "{{#"
"#;
let config = Config::new(raw_config, None, None, None);
let config = Config::new(raw_config, None, None, None, None);
assert_eq!(
expect_err(config).msg,
r#"an opening delimiter may not be the prefix of another delimiter. The block delimiter ("{{") clashes with the expression delimiter ("{{$")"#,
@ -604,7 +613,7 @@ mod tests {
syntax = [{ name = "default" }]
"#;
let _config = Config::new(raw_config, None, None, None).unwrap();
let _config = Config::new(raw_config, None, None, None, None).unwrap();
}
#[cfg(feature = "config")]
@ -616,7 +625,7 @@ mod tests {
{ name = "foo", block_start = "%%" } ]
"#;
let _config = Config::new(raw_config, None, None, None).unwrap();
let _config = Config::new(raw_config, None, None, None, None).unwrap();
}
#[cfg(feature = "config")]
@ -628,7 +637,7 @@ mod tests {
default_syntax = "foo"
"#;
let _config = Config::new(raw_config, None, None, None).unwrap();
let _config = Config::new(raw_config, None, None, None, None).unwrap();
}
#[cfg(feature = "config")]
@ -643,6 +652,7 @@ mod tests {
None,
None,
None,
None,
)
.unwrap();
assert_eq!(
@ -674,11 +684,12 @@ mod tests {
None,
None,
None,
None,
)
.unwrap();
assert_eq!(config.whitespace, Whitespace::Suppress);
let config = Config::new(r#""#, None, None, None).unwrap();
let config = Config::new(r#""#, None, None, None, None).unwrap();
assert_eq!(config.whitespace, Whitespace::Preserve);
let config = Config::new(
@ -689,6 +700,7 @@ mod tests {
None,
None,
None,
None,
)
.unwrap();
assert_eq!(config.whitespace, Whitespace::Preserve);
@ -701,6 +713,7 @@ mod tests {
None,
None,
None,
None,
)
.unwrap();
assert_eq!(config.whitespace, Whitespace::Minimize);
@ -720,11 +733,12 @@ mod tests {
None,
Some(Whitespace::Minimize),
None,
None,
)
.unwrap();
assert_eq!(config.whitespace, Whitespace::Minimize);
let config = Config::new(r#""#, None, Some(Whitespace::Minimize), None).unwrap();
let config = Config::new(r#""#, None, Some(Whitespace::Minimize), None, None).unwrap();
assert_eq!(config.whitespace, Whitespace::Minimize);
}
}

View File

@ -143,6 +143,14 @@ impl<'a, 'h> Generator<'a, 'h> {
};",
);
if let Some(ref full_config_path) = self.input.config.full_config_path() {
buf.write(format_args!(
"const _: &[askama::helpers::core::primitive::u8] =\
askama::helpers::core::include_bytes!({:?});",
full_config_path.display()
));
}
// Make sure the compiler understands that the generated code depends on the template files.
let mut paths = self
.contexts

View File

@ -1059,7 +1059,7 @@ const JINJA_EXTENSIONS: &[&str] = &["askama", "j2", "jinja", "jinja2", "rinja"];
#[test]
fn get_source() {
let path = Config::new("", None, None, None)
let path = Config::new("", None, None, None, None)
.and_then(|config| config.find_template("b.html", None, None))
.unwrap();
assert_eq!(get_template_source(&path, None).unwrap(), "bar".into());

View File

@ -260,7 +260,7 @@ pub fn derive_template(input: TokenStream12) -> TokenStream12 {
fn build_skeleton(buf: &mut Buffer, ast: &syn::DeriveInput) -> Result<usize, CompileError> {
let template_args = TemplateArgs::fallback();
let config = Config::new("", None, None, None)?;
let config = Config::new("", None, None, None, None)?;
let input = TemplateInput::new(ast, None, config, &template_args)?;
let mut contexts = HashMap::default();
let parsed = parser::Parsed::default();
@ -315,12 +315,13 @@ fn build_template_item(
tmpl_kind: TmplKind<'_>,
) -> Result<usize, CompileError> {
let config_path = template_args.config_path();
let s = read_config_file(config_path, template_args.config_span)?;
let (s, full_config_path) = read_config_file(config_path, template_args.config_span)?;
let config = Config::new(
&s,
config_path,
template_args.whitespace,
template_args.config_span,
full_config_path,
)?;
let input = TemplateInput::new(ast, enum_ast, config, template_args)?;

View File

@ -2,7 +2,7 @@ use askama::Template;
#[derive(Template)] // this will generate the code...
#[template(path = "hello.html")] // using the template in this path, relative
// to the templates dir in the crate root
// to the templates dir in the crate root
struct HelloTemplate<'a> {
// the name of the struct can be anything
name: &'a str, // the field name should match the variable name