mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 07:20:55 +00:00
Merge pull request #546 from Kijewski/pr-relative-path
derive: track included files with relative path
This commit is contained in:
commit
d12afffc26
@ -5,8 +5,9 @@ mod node;
|
|||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::hash_map::HashMap;
|
use std::collections::hash_map::HashMap;
|
||||||
|
use std::env::current_dir;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ use parser::{
|
|||||||
use rustc_hash::FxBuildHasher;
|
use rustc_hash::FxBuildHasher;
|
||||||
|
|
||||||
use crate::ascii_str::{AsciiChar, AsciiStr};
|
use crate::ascii_str::{AsciiChar, AsciiStr};
|
||||||
|
use crate::generator::helpers::{clean_path, diff_paths};
|
||||||
use crate::heritage::{Context, Heritage};
|
use crate::heritage::{Context, Heritage};
|
||||||
use crate::html::write_escaped_str;
|
use crate::html::write_escaped_str;
|
||||||
use crate::input::{Source, TemplateInput};
|
use crate::input::{Source, TemplateInput};
|
||||||
@ -87,6 +89,14 @@ struct Generator<'a, 'h> {
|
|||||||
is_in_filter_block: usize,
|
is_in_filter_block: usize,
|
||||||
/// Set of called macros we are currently in. Used to prevent (indirect) recursions.
|
/// Set of called macros we are currently in. Used to prevent (indirect) recursions.
|
||||||
seen_callers: Vec<(&'a Macro<'a>, Option<FileInfo<'a>>)>,
|
seen_callers: Vec<(&'a Macro<'a>, Option<FileInfo<'a>>)>,
|
||||||
|
/// The directory path of the calling file.
|
||||||
|
caller_dir: CallerDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CallerDir {
|
||||||
|
Valid(PathBuf),
|
||||||
|
Invalid,
|
||||||
|
Unresolved,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'h> Generator<'a, 'h> {
|
impl<'a, 'h> Generator<'a, 'h> {
|
||||||
@ -112,6 +122,42 @@ impl<'a, 'h> Generator<'a, 'h> {
|
|||||||
},
|
},
|
||||||
is_in_filter_block,
|
is_in_filter_block,
|
||||||
seen_callers: Vec::new(),
|
seen_callers: Vec::new(),
|
||||||
|
caller_dir: CallerDir::Unresolved,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rel_path<'p>(&mut self, path: &'p Path) -> Cow<'p, Path> {
|
||||||
|
self.caller_dir()
|
||||||
|
.and_then(|caller_dir| diff_paths(path, caller_dir))
|
||||||
|
.map_or(Cow::Borrowed(path), Cow::Owned)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn caller_dir(&mut self) -> Option<&Path> {
|
||||||
|
match self.caller_dir {
|
||||||
|
CallerDir::Valid(ref caller_dir) => return Some(caller_dir.as_path()),
|
||||||
|
CallerDir::Invalid => return None,
|
||||||
|
CallerDir::Unresolved => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if proc_macro::is_available()
|
||||||
|
&& let Some(mut local_file) = proc_macro::Span::call_site().local_file()
|
||||||
|
{
|
||||||
|
local_file.pop();
|
||||||
|
if !local_file.is_absolute() {
|
||||||
|
local_file = current_dir()
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(Path::new("."))
|
||||||
|
.join(local_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.caller_dir = CallerDir::Valid(clean_path(&local_file));
|
||||||
|
match &self.caller_dir {
|
||||||
|
CallerDir::Valid(caller_dir) => Some(caller_dir.as_path()),
|
||||||
|
_ => None, // unreachable
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.caller_dir = CallerDir::Invalid;
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,8 +194,8 @@ impl<'a, 'h> Generator<'a, 'h> {
|
|||||||
if let Some(full_config_path) = &self.input.config.full_config_path {
|
if let Some(full_config_path) = &self.input.config.full_config_path {
|
||||||
buf.write(format_args!(
|
buf.write(format_args!(
|
||||||
"const _: &[askama::helpers::core::primitive::u8] =\
|
"const _: &[askama::helpers::core::primitive::u8] =\
|
||||||
askama::helpers::core::include_bytes!({:?});",
|
askama::helpers::core::include_bytes!({:?});",
|
||||||
full_config_path.display()
|
self.rel_path(full_config_path).display()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,8 +216,8 @@ impl<'a, 'h> Generator<'a, 'h> {
|
|||||||
if path_is_valid {
|
if path_is_valid {
|
||||||
buf.write(format_args!(
|
buf.write(format_args!(
|
||||||
"const _: &[askama::helpers::core::primitive::u8] =\
|
"const _: &[askama::helpers::core::primitive::u8] =\
|
||||||
askama::helpers::core::include_bytes!({:#?});",
|
askama::helpers::core::include_bytes!({:?});",
|
||||||
path.canonicalize().as_deref().unwrap_or(path),
|
self.rel_path(path).display()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
mod macro_invocation;
|
mod macro_invocation;
|
||||||
|
mod paths;
|
||||||
|
|
||||||
pub(crate) use macro_invocation::*;
|
pub(crate) use macro_invocation::MacroInvocation;
|
||||||
|
pub(crate) use paths::{clean as clean_path, diff_paths};
|
||||||
|
125
askama_derive/src/generator/helpers/paths.rs
Normal file
125
askama_derive/src/generator/helpers/paths.rs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
|
// The function [`clean()`] was copied from the project [`path_clean`] in version 1.0.1 (rev. [`d8948ae`]).
|
||||||
|
// License: MIT OR Apache-2.0.
|
||||||
|
// Authors: Dan Reeves <hey@danreev.es>, Alex Guerra <alex@heyimalex.com>, Adam Reichold
|
||||||
|
//
|
||||||
|
// The function [`diff_paths()`] was copied in from the project [`pathdiff`] in version 0.2.3 (rev. [`5180ff5`]).
|
||||||
|
// License: MIT OR Apache-2.0.
|
||||||
|
// Copyright 2012-2015 The Rust Project Developers.
|
||||||
|
//
|
||||||
|
// Please see their commit history for more information.
|
||||||
|
//
|
||||||
|
// [`path_clean`]: <https://github.com/danreeves/path-clean>
|
||||||
|
// [`pathdiff`]: <https://github.com/Manishearth/pathdiff>
|
||||||
|
// [`d8948ae`]: <https://github.com/danreeves/path-clean/blob/d8948ae69d349ec33dfe6d6b9c6a0fe30288a117/src/lib.rs#L50-L86>
|
||||||
|
// [`5180ff5`]: <https://github.com/Manishearth/pathdiff/blob/5180ff5b23d9d7eef0a14de13a3d814eb5d8d65c/src/lib.rs#L18-L86>
|
||||||
|
|
||||||
|
/// The core implementation. It performs the following, lexically:
|
||||||
|
/// 1. Reduce multiple slashes to a single slash.
|
||||||
|
/// 2. Eliminate `.` path name elements (the current directory).
|
||||||
|
/// 3. Eliminate `..` path name elements (the parent directory) and the non-`.` non-`..`, element that precedes them.
|
||||||
|
/// 4. Eliminate `..` elements that begin a rooted path, that is, replace `/..` by `/` at the beginning of a path.
|
||||||
|
/// 5. Leave intact `..` elements that begin a non-rooted path.
|
||||||
|
///
|
||||||
|
/// If the result of this process is an empty string, return the string `"."`, representing the current directory.
|
||||||
|
pub(crate) fn clean(path: &Path) -> PathBuf {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for comp in path.components() {
|
||||||
|
match comp {
|
||||||
|
Component::CurDir => (),
|
||||||
|
Component::ParentDir => match out.last() {
|
||||||
|
Some(Component::RootDir) => (),
|
||||||
|
Some(Component::Normal(_)) => {
|
||||||
|
out.pop();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
| Some(Component::CurDir)
|
||||||
|
| Some(Component::ParentDir)
|
||||||
|
| Some(Component::Prefix(_)) => out.push(comp),
|
||||||
|
},
|
||||||
|
comp => out.push(comp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !out.is_empty() {
|
||||||
|
out.iter().collect()
|
||||||
|
} else {
|
||||||
|
PathBuf::from(".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a relative path from a provided base directory path to the provided path.
|
||||||
|
pub(crate) fn diff_paths(path: &Path, base: &Path) -> Option<PathBuf> {
|
||||||
|
if path.is_absolute() != base.is_absolute() {
|
||||||
|
if path.is_absolute() {
|
||||||
|
Some(PathBuf::from(path))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut ita = path.components();
|
||||||
|
let mut itb = base.components();
|
||||||
|
let mut comps = vec![];
|
||||||
|
loop {
|
||||||
|
match (ita.next(), itb.next()) {
|
||||||
|
(None, None) => break,
|
||||||
|
(Some(a), None) => {
|
||||||
|
comps.push(a);
|
||||||
|
comps.extend(ita.by_ref());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(None, _) => comps.push(Component::ParentDir),
|
||||||
|
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
|
||||||
|
(Some(a), Some(Component::CurDir)) => comps.push(a),
|
||||||
|
(Some(_), Some(Component::ParentDir)) => return None,
|
||||||
|
(Some(a), Some(_)) => {
|
||||||
|
comps.push(Component::ParentDir);
|
||||||
|
for _ in itb {
|
||||||
|
comps.push(Component::ParentDir);
|
||||||
|
}
|
||||||
|
comps.push(a);
|
||||||
|
comps.extend(ita.by_ref());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(comps.iter().map(|c| c.as_os_str()).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diff_paths() {
|
||||||
|
assert_eq!(
|
||||||
|
diff_paths(Path::new("/foo/bar"), Path::new("/foo/bar/baz")),
|
||||||
|
Some("../".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
diff_paths(Path::new("/foo/bar/baz"), Path::new("/foo/bar")),
|
||||||
|
Some("baz".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
diff_paths(Path::new("/foo/bar/quux"), Path::new("/foo/bar/baz")),
|
||||||
|
Some("../quux".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
diff_paths(Path::new("/foo/bar/baz"), Path::new("/foo/bar/quux")),
|
||||||
|
Some("../baz".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
diff_paths(Path::new("/foo/bar"), Path::new("/foo/bar/quux")),
|
||||||
|
Some("../".into())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
diff_paths(Path::new("/foo/bar"), Path::new("baz")),
|
||||||
|
Some("/foo/bar".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
diff_paths(Path::new("/foo/bar"), Path::new("/baz")),
|
||||||
|
Some("../foo/bar".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
diff_paths(Path::new("foo"), Path::new("bar")),
|
||||||
|
Some("../foo".into())
|
||||||
|
);
|
||||||
|
}
|
@ -2,6 +2,8 @@
|
|||||||
#![deny(elided_lifetimes_in_paths)]
|
#![deny(elided_lifetimes_in_paths)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
|
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod generator;
|
mod generator;
|
||||||
mod heritage;
|
mod heritage;
|
||||||
@ -14,8 +16,6 @@ mod tests;
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[cfg(feature = "proc-macro")]
|
#[cfg(feature = "proc-macro")]
|
||||||
pub mod __macro_support {
|
pub mod __macro_support {
|
||||||
extern crate proc_macro;
|
|
||||||
|
|
||||||
pub use proc_macro::TokenStream as TokenStream1;
|
pub use proc_macro::TokenStream as TokenStream1;
|
||||||
pub use proc_macro2::TokenStream as TokenStream2;
|
pub use proc_macro2::TokenStream as TokenStream2;
|
||||||
pub use quote::quote;
|
pub use quote::quote;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user