Cleanup of get_url and get_file_hash
This commit is contained in:
parent
7154e90542
commit
9e0cac6d4d
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
- Newlines are now required after the closing `+++` of front-matter
|
- Newlines are now required after the closing `+++` of front-matter
|
||||||
- `resize_image` now returns a map: `{url, static_path}` instead of just the URL so you can follow up with other functions
|
- `resize_image` now returns a map: `{url, static_path}` instead of just the URL so you can follow up with other functions
|
||||||
|
- `get_file_hash` now has the `base64` option set to `true` by default (from `false`) since it's mainly used for integrity hashes which are base64
|
||||||
|
- `get_url` does not automatically strip leading `/` from paths anymore
|
||||||
- i18n rework: languages now have their sections in `config.toml` to set up all their options
|
- i18n rework: languages now have their sections in `config.toml` to set up all their options
|
||||||
1. taxonomies don't have a `lang` anymore in the config, you need to declare them in their respective language section
|
1. taxonomies don't have a `lang` anymore in the config, you need to declare them in their respective language section
|
||||||
2. the `config` variable in templates has been changed and is now a stripped down language aware version of the previous `config`
|
2. the `config` variable in templates has been changed and is now a stripped down language aware version of the previous `config`
|
||||||
|
|
|
@ -16,9 +16,9 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> {
|
||||||
site.tera.register_function(
|
site.tera.register_function(
|
||||||
"get_url",
|
"get_url",
|
||||||
global_fns::GetUrl::new(
|
global_fns::GetUrl::new(
|
||||||
|
site.base_path.clone(),
|
||||||
site.config.clone(),
|
site.config.clone(),
|
||||||
site.permalinks.clone(),
|
site.permalinks.clone(),
|
||||||
vec![site.static_path.clone(), site.output_path.clone(), site.content_path.clone()],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
site.tera.register_function(
|
site.tera.register_function(
|
||||||
|
@ -39,14 +39,8 @@ pub fn register_early_global_fns(site: &mut Site) -> TeraResult<()> {
|
||||||
site.config.slugify.taxonomies,
|
site.config.slugify.taxonomies,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
site.tera.register_function(
|
site.tera
|
||||||
"get_file_hash",
|
.register_function("get_file_hash", global_fns::GetFileHash::new(site.base_path.clone()));
|
||||||
global_fns::GetFileHash::new(vec![
|
|
||||||
site.static_path.clone(),
|
|
||||||
site.output_path.clone(),
|
|
||||||
site.content_path.clone(),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
435
components/templates/src/global_fns/files.rs
Normal file
435
components/templates/src/global_fns/files.rs
Normal file
|
@ -0,0 +1,435 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{fs, io, result};
|
||||||
|
|
||||||
|
use base64::encode as encode_b64;
|
||||||
|
use sha2::{digest, Sha256, Sha384, Sha512};
|
||||||
|
use tera::{from_value, to_value, Function as TeraFn, Result, Value};
|
||||||
|
use config::Config;
|
||||||
|
use utils::site::resolve_internal_link;
|
||||||
|
use crate::global_fns::helpers::search_for_file;
|
||||||
|
|
||||||
|
|
||||||
|
fn compute_file_hash<D: digest::Digest>(
|
||||||
|
mut file: fs::File,
|
||||||
|
as_base64: bool,
|
||||||
|
) -> result::Result<String, io::Error>
|
||||||
|
where
|
||||||
|
digest::Output<D>: core::fmt::LowerHex,
|
||||||
|
D: std::io::Write,
|
||||||
|
{
|
||||||
|
let mut hasher = D::new();
|
||||||
|
io::copy(&mut file, &mut hasher)?;
|
||||||
|
println!("base64: {}", as_base64);
|
||||||
|
if as_base64 {
|
||||||
|
Ok(encode_b64(hasher.finalize()))
|
||||||
|
} else {
|
||||||
|
Ok(format!("{:x}", hasher.finalize()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GetUrl {
|
||||||
|
base_path: PathBuf,
|
||||||
|
config: Config,
|
||||||
|
permalinks: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetUrl {
|
||||||
|
pub fn new(base_path: PathBuf, config: Config, permalinks: HashMap<String, String>) -> Self {
|
||||||
|
Self { base_path, config, permalinks }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_path_with_lang(path: String, lang: &str, config: &Config) -> Result<String> {
|
||||||
|
if lang == config.default_language {
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.other_languages().contains_key(lang) {
|
||||||
|
return Err(
|
||||||
|
format!("`{}` is not an authorized language (check config.languages).", lang).into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut split_path: Vec<String> = path.split('.').map(String::from).collect();
|
||||||
|
let ilast = split_path.len() - 1;
|
||||||
|
split_path[ilast] = format!("{}.{}", lang, split_path[ilast]);
|
||||||
|
Ok(split_path.join("."))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TeraFn for GetUrl {
|
||||||
|
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
|
||||||
|
let path = required_arg!(
|
||||||
|
String,
|
||||||
|
args.get("path"),
|
||||||
|
"`get_url` requires a `path` argument with a string value"
|
||||||
|
);
|
||||||
|
let cachebust = optional_arg!(
|
||||||
|
bool,
|
||||||
|
args.get("cachebust"),
|
||||||
|
"`get_url`: `cachebust` must be a boolean (true or false)"
|
||||||
|
)
|
||||||
|
.unwrap_or(false);
|
||||||
|
let trailing_slash = optional_arg!(
|
||||||
|
bool,
|
||||||
|
args.get("trailing_slash"),
|
||||||
|
"`get_url`: `trailing_slash` must be a boolean (true or false)"
|
||||||
|
)
|
||||||
|
.unwrap_or(false);
|
||||||
|
let lang = optional_arg!(String, args.get("lang"), "`get_url`: `lang` must be a string.")
|
||||||
|
.unwrap_or_else(|| self.config.default_language.clone());
|
||||||
|
|
||||||
|
// TODO: handle rss files with langs
|
||||||
|
// https://zola.discourse.group/t/rss-and-languages-do-not-work/878
|
||||||
|
// TODO: clean up everything
|
||||||
|
// if it starts with @/, resolve it as an internal link
|
||||||
|
if path.starts_with("@/") {
|
||||||
|
let path_with_lang = match make_path_with_lang(path, &lang, &self.config) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
match resolve_internal_link(&path_with_lang, &self.permalinks) {
|
||||||
|
Ok(resolved) => Ok(to_value(resolved.permalink).unwrap()),
|
||||||
|
Err(_) => {
|
||||||
|
Err(format!("Could not resolve URL for link `{}` not found.", path_with_lang)
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// anything else
|
||||||
|
let mut segments = vec![];
|
||||||
|
|
||||||
|
if lang != self.config.default_language {
|
||||||
|
segments.push(lang);
|
||||||
|
};
|
||||||
|
|
||||||
|
segments.push(path);
|
||||||
|
|
||||||
|
let path_with_lang = segments.join("/");
|
||||||
|
|
||||||
|
let mut permalink = self.config.make_permalink(&path_with_lang);
|
||||||
|
if !trailing_slash && permalink.ends_with('/') {
|
||||||
|
permalink.pop(); // Removes the slash
|
||||||
|
}
|
||||||
|
|
||||||
|
if cachebust {
|
||||||
|
match search_for_file(&self.base_path, &path_with_lang)
|
||||||
|
.and_then(|p| fs::File::open(&p).ok())
|
||||||
|
.and_then(|f| compute_file_hash::<Sha256>(f, false).ok())
|
||||||
|
{
|
||||||
|
Some(hash) => {
|
||||||
|
permalink = format!("{}?h={}", permalink, hash);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(format!("Could not find or open file {}", path_with_lang).into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(to_value(permalink).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GetFileHash {
|
||||||
|
base_path: PathBuf,
|
||||||
|
}
|
||||||
|
impl GetFileHash {
|
||||||
|
pub fn new(base_path: PathBuf) -> Self {
|
||||||
|
Self { base_path }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TeraFn for GetFileHash {
|
||||||
|
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
|
||||||
|
let path = required_arg!(
|
||||||
|
String,
|
||||||
|
args.get("path"),
|
||||||
|
"`get_file_hash` requires a `path` argument with a string value"
|
||||||
|
);
|
||||||
|
let sha_type = optional_arg!(
|
||||||
|
u16,
|
||||||
|
args.get("sha_type"),
|
||||||
|
"`get_file_hash`: `sha_type` must be 256, 384 or 512"
|
||||||
|
)
|
||||||
|
.unwrap_or(384);
|
||||||
|
let base64 = optional_arg!(
|
||||||
|
bool,
|
||||||
|
args.get("base64"),
|
||||||
|
"`get_file_hash`: `base64` must be true or false"
|
||||||
|
)
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
let file_path = match search_for_file(&self.base_path, &path) {
|
||||||
|
Some(f) => f,
|
||||||
|
None => {
|
||||||
|
return Err(format!("`get_file_hash`: Cannot find file: {}", path).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let f = match std::fs::File::open(file_path) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(format!("File {} could not be open: {}", path, e).into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let hash = match sha_type {
|
||||||
|
256 => compute_file_hash::<Sha256>(f, base64),
|
||||||
|
384 => compute_file_hash::<Sha384>(f, base64),
|
||||||
|
512 => compute_file_hash::<Sha512>(f, base64),
|
||||||
|
_ => return Err("`get_file_hash`: Invalid sha value".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match hash {
|
||||||
|
Ok(digest) => Ok(to_value(digest).unwrap()),
|
||||||
|
Err(_) => Err("`get_file_hash`: could no compute hash".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{GetFileHash, GetUrl};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use tempfile::{tempdir, TempDir};
|
||||||
|
use tera::{to_value, Function};
|
||||||
|
|
||||||
|
use config::Config;
|
||||||
|
use utils::fs::create_file;
|
||||||
|
|
||||||
|
fn create_temp_dir() -> TempDir {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
create_file(&dir.path().join("app.css"), "// Hello world!").expect("Failed to create file");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONFIG_DATA: &str = r#"
|
||||||
|
base_url = "https://remplace-par-ton-url.fr"
|
||||||
|
default_language = "fr"
|
||||||
|
|
||||||
|
[translations]
|
||||||
|
title = "Un titre"
|
||||||
|
|
||||||
|
[languages.en]
|
||||||
|
[languages.en.translations]
|
||||||
|
title = "A title"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_cachebust_to_url() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetUrl::new(dir.path().to_path_buf(), Config::default(), HashMap::new());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
args.insert("cachebust".to_string(), to_value(true).unwrap());
|
||||||
|
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css?h=572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_trailing_slashes() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetUrl::new(dir.path().to_path_buf(), Config::default(), HashMap::new());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
args.insert("trailing_slash".to_string(), to_value(true).unwrap());
|
||||||
|
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css/");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_add_slashes_and_cachebust() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetUrl::new(dir.path().to_path_buf(), Config::default(), HashMap::new());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
args.insert("trailing_slash".to_string(), to_value(true).unwrap());
|
||||||
|
args.insert("cachebust".to_string(), to_value(true).unwrap());
|
||||||
|
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css/?h=572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_link_to_some_static_file() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetUrl::new(dir.path().to_path_buf(), Config::default(), HashMap::new());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_when_language_not_available() {
|
||||||
|
let config = Config::parse(CONFIG_DATA).unwrap();
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetUrl::new(dir.path().to_path_buf(), config, HashMap::new());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap());
|
||||||
|
args.insert("lang".to_string(), to_value("it").unwrap());
|
||||||
|
let err = static_fn.call(&args).unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
"`it` is not an authorized language (check config.languages).",
|
||||||
|
format!("{}", err)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_url_with_default_language() {
|
||||||
|
let mut permalinks = HashMap::new();
|
||||||
|
permalinks.insert(
|
||||||
|
"a_section/a_page.md".to_string(),
|
||||||
|
"https://remplace-par-ton-url.fr/a_section/a_page/".to_string(),
|
||||||
|
);
|
||||||
|
permalinks.insert(
|
||||||
|
"a_section/a_page.en.md".to_string(),
|
||||||
|
"https://remplace-par-ton-url.fr/en/a_section/a_page/".to_string(),
|
||||||
|
);
|
||||||
|
let config = Config::parse(CONFIG_DATA).unwrap();
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetUrl::new(dir.path().to_path_buf(), config, permalinks);
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap());
|
||||||
|
args.insert("lang".to_string(), to_value("fr").unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"https://remplace-par-ton-url.fr/a_section/a_page/"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_url_with_other_language() {
|
||||||
|
let config = Config::parse(CONFIG_DATA).unwrap();
|
||||||
|
let mut permalinks = HashMap::new();
|
||||||
|
permalinks.insert(
|
||||||
|
"a_section/a_page.md".to_string(),
|
||||||
|
"https://remplace-par-ton-url.fr/a_section/a_page/".to_string(),
|
||||||
|
);
|
||||||
|
permalinks.insert(
|
||||||
|
"a_section/a_page.en.md".to_string(),
|
||||||
|
"https://remplace-par-ton-url.fr/en/a_section/a_page/".to_string(),
|
||||||
|
);
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetUrl::new(dir.path().to_path_buf(), config, permalinks);
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap());
|
||||||
|
args.insert("lang".to_string(), to_value("en").unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"https://remplace-par-ton-url.fr/en/a_section/a_page/"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_feed_url_with_default_language() {
|
||||||
|
let config = Config::parse(CONFIG_DATA).unwrap();
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetUrl::new(dir.path().to_path_buf(), config.clone(), HashMap::new());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value(config.feed_filename).unwrap());
|
||||||
|
args.insert("lang".to_string(), to_value("fr").unwrap());
|
||||||
|
assert_eq!(static_fn.call(&args).unwrap(), "https://remplace-par-ton-url.fr/atom.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_feed_url_with_other_language() {
|
||||||
|
let config = Config::parse(CONFIG_DATA).unwrap();
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetUrl::new(dir.path().to_path_buf(), config.clone(), HashMap::new());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value(config.feed_filename).unwrap());
|
||||||
|
args.insert("lang".to_string(), to_value("en").unwrap());
|
||||||
|
assert_eq!(static_fn.call(&args).unwrap(), "https://remplace-par-ton-url.fr/en/atom.xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_file_hash_sha256_no_base64() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetFileHash::new(dir.into_path());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
args.insert("sha_type".to_string(), to_value(256).unwrap());
|
||||||
|
args.insert("base64".to_string(), to_value(false).unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_file_hash_sha256_base64() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetFileHash::new(dir.into_path());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
args.insert("sha_type".to_string(), to_value(256).unwrap());
|
||||||
|
args.insert("base64".to_string(), to_value(true).unwrap());
|
||||||
|
assert_eq!(static_fn.call(&args).unwrap(), "Vy5pHcaMP81lOuRjJhvbOPNdxvAXFdnOaHmTGd0ViEA=");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_file_hash_sha384_no_base64() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetFileHash::new(dir.into_path());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
args.insert("base64".to_string(), to_value(false).unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"141c09bd28899773b772bbe064d8b718fa1d6f2852b7eafd5ed6689d26b74883b79e2e814cd69d5b52ab476aa284c414"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_file_hash_sha384() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetFileHash::new(dir.into_path());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"FBwJvSiJl3O3crvgZNi3GPodbyhSt+r9XtZonSa3SIO3ni6BTNadW1KrR2qihMQU"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_file_hash_sha512_no_base64() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetFileHash::new(dir.into_path());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
||||||
|
args.insert("base64".to_string(), to_value(false).unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"379dfab35123b9159d9e4e92dc90e2be44cf3c2f7f09b2e2df80a1b219b461de3556c93e1a9ceb3008e999e2d6a54b4f1d65ee9be9be63fa45ec88931623372f"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_get_file_hash_sha512() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetFileHash::new(dir.into_path());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("app.css").unwrap());
|
||||||
|
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
static_fn.call(&args).unwrap(),
|
||||||
|
"N536s1EjuRWdnk6S3JDivkTPPC9/CbLi34Chshm0Yd41Vsk+GpzrMAjpmeLWpUtPHWXum+m+Y/pF7IiTFiM3Lw=="
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_when_file_not_found_for_hash() {
|
||||||
|
let dir = create_temp_dir();
|
||||||
|
let static_fn = GetFileHash::new(dir.into_path());
|
||||||
|
let mut args = HashMap::new();
|
||||||
|
args.insert("path".to_string(), to_value("doesnt-exist").unwrap());
|
||||||
|
let err = format!("{}", static_fn.call(&args).unwrap_err());
|
||||||
|
println!("{:?}", err);
|
||||||
|
|
||||||
|
assert!(err.contains("Cannot find file"));
|
||||||
|
}
|
||||||
|
}
|
36
components/templates/src/global_fns/helpers.rs
Normal file
36
components/templates/src/global_fns/helpers.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// This is used by a few Tera functions to search for files on the filesystem.
|
||||||
|
/// This does try to find the file in 3 different spots:
|
||||||
|
/// 1. base_path + path
|
||||||
|
/// 2. base_path + static + path
|
||||||
|
/// 3. base_path + content + path
|
||||||
|
pub fn search_for_file(base_path: &Path, path: &str) -> Option<PathBuf> {
|
||||||
|
let search_paths = [base_path.join("static"), base_path.join("content")];
|
||||||
|
let actual_path = if path.starts_with("@/") {
|
||||||
|
Cow::Owned(path.replace("@/", "content/"))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(path)
|
||||||
|
};
|
||||||
|
let mut file_path = base_path.join(&*actual_path);
|
||||||
|
let mut file_exists = file_path.exists();
|
||||||
|
|
||||||
|
if !file_exists {
|
||||||
|
// we need to search in both search folders now
|
||||||
|
for dir in &search_paths {
|
||||||
|
let p = dir.join(&*actual_path);
|
||||||
|
if p.exists() {
|
||||||
|
file_path = p;
|
||||||
|
file_exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if file_exists {
|
||||||
|
Some(file_path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,13 @@ use std::ffi::OsStr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::global_fns::search_for_file;
|
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use svg_metadata as svg;
|
use svg_metadata as svg;
|
||||||
use tera::{from_value, to_value, Error, Function as TeraFn, Result, Value};
|
use tera::{from_value, to_value, Error, Function as TeraFn, Result, Value};
|
||||||
|
|
||||||
|
use crate::global_fns::helpers::search_for_file;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct ResizeImageResponse {
|
struct ResizeImageResponse {
|
||||||
/// The final URL for that asset
|
/// The final URL for that asset
|
||||||
|
@ -69,7 +70,7 @@ impl TeraFn for ResizeImage {
|
||||||
let file_path = match search_for_file(&self.base_path, &path) {
|
let file_path = match search_for_file(&self.base_path, &path) {
|
||||||
Some(f) => f,
|
Some(f) => f,
|
||||||
None => {
|
None => {
|
||||||
return Err(format!("`resize_image`: Cannot find path: {}", path).into());
|
return Err(format!("`resize_image`: Cannot find file: {}", path).into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use csv::Reader;
|
||||||
|
use reqwest::header::{HeaderValue, CONTENT_TYPE};
|
||||||
|
use reqwest::{blocking::Client, header};
|
||||||
|
use tera::{from_value, to_value, Error, Function as TeraFn, Map, Result, Value};
|
||||||
|
use url::Url;
|
||||||
use utils::de::fix_toml_dates;
|
use utils::de::fix_toml_dates;
|
||||||
use utils::fs::{get_file_time, is_path_in_directory, read_file};
|
use utils::fs::{get_file_time, is_path_in_directory, read_file};
|
||||||
|
|
||||||
use reqwest::header::{HeaderValue, CONTENT_TYPE};
|
use crate::global_fns::helpers::search_for_file;
|
||||||
use reqwest::{blocking::Client, header};
|
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use crate::global_fns::search_for_file;
|
|
||||||
use csv::Reader;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use tera::{from_value, to_value, Error, Function as TeraFn, Map, Result, Value};
|
|
||||||
|
|
||||||
static GET_DATA_ARGUMENT_ERROR_MESSAGE: &str =
|
static GET_DATA_ARGUMENT_ERROR_MESSAGE: &str =
|
||||||
"`load_data`: requires EITHER a `path` or `url` argument";
|
"`load_data`: requires EITHER a `path` or `url` argument";
|
||||||
|
@ -73,7 +72,7 @@ impl OutputFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug)]
|
||||||
enum DataSource {
|
enum DataSource {
|
||||||
Url(Url),
|
Url(Url),
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
|
@ -88,7 +87,7 @@ impl DataSource {
|
||||||
fn from_args(
|
fn from_args(
|
||||||
path_arg: Option<String>,
|
path_arg: Option<String>,
|
||||||
url_arg: Option<String>,
|
url_arg: Option<String>,
|
||||||
base_path: &PathBuf,
|
base_path: &Path,
|
||||||
) -> Result<Option<Self>> {
|
) -> Result<Option<Self>> {
|
||||||
if path_arg.is_some() && url_arg.is_some() {
|
if path_arg.is_some() && url_arg.is_some() {
|
||||||
return Err(GET_DATA_ARGUMENT_ERROR_MESSAGE.into());
|
return Err(GET_DATA_ARGUMENT_ERROR_MESSAGE.into());
|
||||||
|
@ -104,7 +103,7 @@ impl DataSource {
|
||||||
if let Some(url) = url_arg {
|
if let Some(url) = url_arg {
|
||||||
return Url::parse(&url)
|
return Url::parse(&url)
|
||||||
.map(DataSource::Url)
|
.map(DataSource::Url)
|
||||||
.map(|x| Some(x))
|
.map(Some)
|
||||||
.map_err(|e| format!("Failed to parse {} as url: {}", url, e).into());
|
.map_err(|e| format!("Failed to parse {} as url: {}", url, e).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +139,7 @@ impl Hash for DataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_local_data_file(base_path: &PathBuf, full_path: PathBuf) -> Result<String> {
|
fn read_local_data_file(base_path: &Path, full_path: PathBuf) -> Result<String> {
|
||||||
if !is_path_in_directory(&base_path, &full_path)
|
if !is_path_in_directory(&base_path, &full_path)
|
||||||
.map_err(|e| format!("Failed to read data file {}: {}", full_path.display(), e))?
|
.map_err(|e| format!("Failed to read data file {}: {}", full_path.display(), e))?
|
||||||
{
|
{
|
||||||
|
@ -274,8 +273,8 @@ impl TeraFn for LoadData {
|
||||||
let mut resp = response_client
|
let mut resp = response_client
|
||||||
.post(url.as_str())
|
.post(url.as_str())
|
||||||
.header(header::ACCEPT, file_format.as_accept_header());
|
.header(header::ACCEPT, file_format.as_accept_header());
|
||||||
match post_content_type {
|
if let Some(content_type) = post_content_type {
|
||||||
Some(content_type) => match HeaderValue::from_str(&content_type) {
|
match HeaderValue::from_str(&content_type) {
|
||||||
Ok(c) => {
|
Ok(c) => {
|
||||||
resp = resp.header(CONTENT_TYPE, c);
|
resp = resp.header(CONTENT_TYPE, c);
|
||||||
}
|
}
|
||||||
|
@ -286,9 +285,8 @@ impl TeraFn for LoadData {
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {}
|
}
|
||||||
};
|
|
||||||
if let Some(body) = post_body_arg {
|
if let Some(body) = post_body_arg {
|
||||||
resp = resp.body(body);
|
resp = resp.body(body);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,482 +1,15 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::{fs, io, result};
|
|
||||||
|
|
||||||
use base64::encode as encode_b64;
|
|
||||||
use sha2::{digest, Sha256, Sha384, Sha512};
|
|
||||||
use tera::{from_value, to_value, Function as TeraFn, Result, Value};
|
|
||||||
|
|
||||||
use config::Config;
|
|
||||||
use utils::site::resolve_internal_link;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
mod content;
|
mod content;
|
||||||
|
mod files;
|
||||||
|
mod helpers;
|
||||||
mod i18n;
|
mod i18n;
|
||||||
mod images;
|
mod images;
|
||||||
mod load_data;
|
mod load_data;
|
||||||
|
|
||||||
pub use self::content::{GetPage, GetSection, GetTaxonomy, GetTaxonomyUrl};
|
pub use self::content::{GetPage, GetSection, GetTaxonomy, GetTaxonomyUrl};
|
||||||
|
pub use self::files::{GetFileHash, GetUrl};
|
||||||
pub use self::i18n::Trans;
|
pub use self::i18n::Trans;
|
||||||
pub use self::images::{GetImageMetadata, ResizeImage};
|
pub use self::images::{GetImageMetadata, ResizeImage};
|
||||||
pub use self::load_data::LoadData;
|
pub use self::load_data::LoadData;
|
||||||
|
|
||||||
/// This is used by a few Tera functions to search for files on the filesystem.
|
|
||||||
/// This does try to find the file in 3 different spots:
|
|
||||||
/// 1. base_path + path
|
|
||||||
/// 2. base_path + static + path
|
|
||||||
/// 3. base_path + content + path
|
|
||||||
pub fn search_for_file(base_path: &Path, path: &str) -> Option<PathBuf> {
|
|
||||||
let search_paths = [base_path.join("static"), base_path.join("content")];
|
|
||||||
let actual_path = if path.starts_with("@/") {
|
|
||||||
Cow::Owned(path.replace("@/", "content/"))
|
|
||||||
} else {
|
|
||||||
Cow::Borrowed(path)
|
|
||||||
};
|
|
||||||
let mut file_path = base_path.join(&*actual_path);
|
|
||||||
let mut file_exists = file_path.exists();
|
|
||||||
|
|
||||||
if !file_exists {
|
|
||||||
// we need to search in both search folders now
|
|
||||||
for dir in &search_paths {
|
|
||||||
let p = dir.join(&*actual_path);
|
|
||||||
if p.exists() {
|
|
||||||
file_path = p;
|
|
||||||
file_exists = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if file_exists {
|
|
||||||
Some(file_path)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct GetUrl {
|
|
||||||
config: Config,
|
|
||||||
permalinks: HashMap<String, String>,
|
|
||||||
search_paths: Vec<PathBuf>,
|
|
||||||
}
|
|
||||||
impl GetUrl {
|
|
||||||
pub fn new(
|
|
||||||
config: Config,
|
|
||||||
permalinks: HashMap<String, String>,
|
|
||||||
search_paths: Vec<PathBuf>,
|
|
||||||
) -> Self {
|
|
||||||
Self { config, permalinks, search_paths }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_path_with_lang(path: String, lang: &str, config: &Config) -> Result<String> {
|
|
||||||
if lang == config.default_language {
|
|
||||||
return Ok(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !config.other_languages().contains_key(lang) {
|
|
||||||
return Err(
|
|
||||||
format!("`{}` is not an authorized language (check config.languages).", lang).into()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut splitted_path: Vec<String> = path.split('.').map(String::from).collect();
|
|
||||||
let ilast = splitted_path.len() - 1;
|
|
||||||
splitted_path[ilast] = format!("{}.{}", lang, splitted_path[ilast]);
|
|
||||||
Ok(splitted_path.join("."))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_file(search_paths: &[PathBuf], url: &str) -> result::Result<fs::File, io::Error> {
|
|
||||||
let cleaned_url = url.trim_start_matches("@/").trim_start_matches('/');
|
|
||||||
for base_path in search_paths {
|
|
||||||
match fs::File::open(base_path.join(cleaned_url)) {
|
|
||||||
Ok(f) => return Ok(f),
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Err(io::Error::from(io::ErrorKind::NotFound))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_file_hash<D: digest::Digest>(
|
|
||||||
mut file: fs::File,
|
|
||||||
base64: bool,
|
|
||||||
) -> result::Result<String, io::Error>
|
|
||||||
where
|
|
||||||
digest::Output<D>: core::fmt::LowerHex,
|
|
||||||
D: std::io::Write,
|
|
||||||
{
|
|
||||||
let mut hasher = D::new();
|
|
||||||
io::copy(&mut file, &mut hasher)?;
|
|
||||||
if base64 {
|
|
||||||
Ok(encode_b64(hasher.finalize()))
|
|
||||||
} else {
|
|
||||||
Ok(format!("{:x}", hasher.finalize()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_not_found_err(search_paths: &[PathBuf], url: &str) -> Result<Value> {
|
|
||||||
Err(format!(
|
|
||||||
"file `{}` not found; searched in{}",
|
|
||||||
url,
|
|
||||||
search_paths.iter().fold(String::new(), |acc, arg| acc + " " + arg.to_str().unwrap())
|
|
||||||
)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TeraFn for GetUrl {
|
|
||||||
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
|
|
||||||
let cachebust =
|
|
||||||
args.get("cachebust").map_or(false, |c| from_value::<bool>(c.clone()).unwrap_or(false));
|
|
||||||
|
|
||||||
let trailing_slash = args
|
|
||||||
.get("trailing_slash")
|
|
||||||
.map_or(false, |c| from_value::<bool>(c.clone()).unwrap_or(false));
|
|
||||||
|
|
||||||
let path = required_arg!(
|
|
||||||
String,
|
|
||||||
args.get("path"),
|
|
||||||
"`get_url` requires a `path` argument with a string value"
|
|
||||||
);
|
|
||||||
|
|
||||||
let lang = optional_arg!(String, args.get("lang"), "`get_url`: `lang` must be a string.")
|
|
||||||
.unwrap_or_else(|| self.config.default_language.clone());
|
|
||||||
|
|
||||||
if path.starts_with("@/") {
|
|
||||||
let path_with_lang = match make_path_with_lang(path, &lang, &self.config) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
match resolve_internal_link(&path_with_lang, &self.permalinks) {
|
|
||||||
Ok(resolved) => Ok(to_value(resolved.permalink).unwrap()),
|
|
||||||
Err(_) => {
|
|
||||||
Err(format!("Could not resolve URL for link `{}` not found.", path_with_lang)
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// anything else
|
|
||||||
let mut segments = vec![];
|
|
||||||
|
|
||||||
if lang != self.config.default_language {
|
|
||||||
segments.push(lang);
|
|
||||||
};
|
|
||||||
|
|
||||||
segments.push(path);
|
|
||||||
|
|
||||||
let path_with_lang = segments.join("/");
|
|
||||||
|
|
||||||
let mut permalink = self.config.make_permalink(&path_with_lang);
|
|
||||||
if !trailing_slash && permalink.ends_with('/') {
|
|
||||||
permalink.pop(); // Removes the slash
|
|
||||||
}
|
|
||||||
|
|
||||||
if cachebust {
|
|
||||||
match open_file(&self.search_paths, &path_with_lang)
|
|
||||||
.and_then(|f| compute_file_hash::<Sha256>(f, false))
|
|
||||||
{
|
|
||||||
Ok(hash) => {
|
|
||||||
permalink = format!("{}?h={}", permalink, hash);
|
|
||||||
}
|
|
||||||
Err(_) => return file_not_found_err(&self.search_paths, &path_with_lang),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(to_value(permalink).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct GetFileHash {
|
|
||||||
search_paths: Vec<PathBuf>,
|
|
||||||
}
|
|
||||||
impl GetFileHash {
|
|
||||||
pub fn new(search_paths: Vec<PathBuf>) -> Self {
|
|
||||||
Self { search_paths }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_SHA_TYPE: u16 = 384;
|
|
||||||
const DEFAULT_BASE64: bool = false;
|
|
||||||
|
|
||||||
impl TeraFn for GetFileHash {
|
|
||||||
fn call(&self, args: &HashMap<String, Value>) -> Result<Value> {
|
|
||||||
let path = required_arg!(
|
|
||||||
String,
|
|
||||||
args.get("path"),
|
|
||||||
"`get_file_hash` requires a `path` argument with a string value"
|
|
||||||
);
|
|
||||||
let sha_type = optional_arg!(
|
|
||||||
u16,
|
|
||||||
args.get("sha_type"),
|
|
||||||
"`get_file_hash`: `sha_type` must be 256, 384 or 512"
|
|
||||||
)
|
|
||||||
.unwrap_or(DEFAULT_SHA_TYPE);
|
|
||||||
let base64 = optional_arg!(
|
|
||||||
bool,
|
|
||||||
args.get("base64"),
|
|
||||||
"`get_file_hash`: `base64` must be true or false"
|
|
||||||
)
|
|
||||||
.unwrap_or(DEFAULT_BASE64);
|
|
||||||
|
|
||||||
let f = match open_file(&self.search_paths, &path) {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format!(
|
|
||||||
"File {} could not be open: {} (searched in {:?})",
|
|
||||||
path, e, self.search_paths
|
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let hash = match sha_type {
|
|
||||||
256 => compute_file_hash::<Sha256>(f, base64),
|
|
||||||
384 => compute_file_hash::<Sha384>(f, base64),
|
|
||||||
512 => compute_file_hash::<Sha512>(f, base64),
|
|
||||||
_ => return Err("`get_file_hash`: Invalid sha value".into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
match hash {
|
|
||||||
Ok(digest) => Ok(to_value(digest).unwrap()),
|
|
||||||
Err(_) => file_not_found_err(&self.search_paths, &path),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::{GetFileHash, GetUrl};
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use tempfile::{tempdir, TempDir};
|
|
||||||
use tera::{to_value, Function};
|
|
||||||
|
|
||||||
use config::Config;
|
|
||||||
use utils::fs::create_file;
|
|
||||||
|
|
||||||
fn create_temp_dir() -> TempDir {
|
|
||||||
let dir = tempdir().unwrap();
|
|
||||||
create_file(&dir.path().join("app.css"), "// Hello world!").expect("Failed to create file");
|
|
||||||
dir
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONFIG_DATA: &str = r#"
|
|
||||||
base_url = "https://remplace-par-ton-url.fr"
|
|
||||||
default_language = "fr"
|
|
||||||
|
|
||||||
[translations]
|
|
||||||
title = "Un titre"
|
|
||||||
|
|
||||||
[languages.en]
|
|
||||||
[languages.en.translations]
|
|
||||||
title = "A title"
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_add_cachebust_to_url() {
|
|
||||||
let config = Config::default();
|
|
||||||
let static_fn = GetUrl::new(config, HashMap::new(), vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
args.insert("cachebust".to_string(), to_value(true).unwrap());
|
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css?h=572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_add_trailing_slashes() {
|
|
||||||
let config = Config::default();
|
|
||||||
let static_fn = GetUrl::new(config, HashMap::new(), vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
args.insert("trailing_slash".to_string(), to_value(true).unwrap());
|
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css/");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_add_slashes_and_cachebust() {
|
|
||||||
let config = Config::default();
|
|
||||||
let static_fn = GetUrl::new(config, HashMap::new(), vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
args.insert("trailing_slash".to_string(), to_value(true).unwrap());
|
|
||||||
args.insert("cachebust".to_string(), to_value(true).unwrap());
|
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css/?h=572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_link_to_some_static_file() {
|
|
||||||
let config = Config::default();
|
|
||||||
let static_fn = GetUrl::new(config, HashMap::new(), vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "http://a-website.com/app.css");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_when_language_not_available() {
|
|
||||||
let config = Config::parse(CONFIG_DATA).unwrap();
|
|
||||||
let static_fn = GetUrl::new(config, HashMap::new(), vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap());
|
|
||||||
args.insert("lang".to_string(), to_value("it").unwrap());
|
|
||||||
let err = static_fn.call(&args).unwrap_err();
|
|
||||||
assert_eq!(
|
|
||||||
"`it` is not an authorized language (check config.languages).",
|
|
||||||
format!("{}", err)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_url_with_default_language() {
|
|
||||||
let config = Config::parse(CONFIG_DATA).unwrap();
|
|
||||||
let mut permalinks = HashMap::new();
|
|
||||||
permalinks.insert(
|
|
||||||
"a_section/a_page.md".to_string(),
|
|
||||||
"https://remplace-par-ton-url.fr/a_section/a_page/".to_string(),
|
|
||||||
);
|
|
||||||
permalinks.insert(
|
|
||||||
"a_section/a_page.en.md".to_string(),
|
|
||||||
"https://remplace-par-ton-url.fr/en/a_section/a_page/".to_string(),
|
|
||||||
);
|
|
||||||
let static_fn = GetUrl::new(config, permalinks, vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap());
|
|
||||||
args.insert("lang".to_string(), to_value("fr").unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
static_fn.call(&args).unwrap(),
|
|
||||||
"https://remplace-par-ton-url.fr/a_section/a_page/"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_url_with_other_language() {
|
|
||||||
let config = Config::parse(CONFIG_DATA).unwrap();
|
|
||||||
let mut permalinks = HashMap::new();
|
|
||||||
permalinks.insert(
|
|
||||||
"a_section/a_page.md".to_string(),
|
|
||||||
"https://remplace-par-ton-url.fr/a_section/a_page/".to_string(),
|
|
||||||
);
|
|
||||||
permalinks.insert(
|
|
||||||
"a_section/a_page.en.md".to_string(),
|
|
||||||
"https://remplace-par-ton-url.fr/en/a_section/a_page/".to_string(),
|
|
||||||
);
|
|
||||||
let static_fn = GetUrl::new(config, permalinks, vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("@/a_section/a_page.md").unwrap());
|
|
||||||
args.insert("lang".to_string(), to_value("en").unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
static_fn.call(&args).unwrap(),
|
|
||||||
"https://remplace-par-ton-url.fr/en/a_section/a_page/"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_feed_url_with_default_language() {
|
|
||||||
let config = Config::parse(CONFIG_DATA).unwrap();
|
|
||||||
let static_fn =
|
|
||||||
GetUrl::new(config.clone(), HashMap::new(), vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value(config.feed_filename).unwrap());
|
|
||||||
args.insert("lang".to_string(), to_value("fr").unwrap());
|
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "https://remplace-par-ton-url.fr/atom.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_feed_url_with_other_language() {
|
|
||||||
let config = Config::parse(CONFIG_DATA).unwrap();
|
|
||||||
let static_fn =
|
|
||||||
GetUrl::new(config.clone(), HashMap::new(), vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value(config.feed_filename).unwrap());
|
|
||||||
args.insert("lang".to_string(), to_value("en").unwrap());
|
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "https://remplace-par-ton-url.fr/en/atom.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_file_hash_sha256() {
|
|
||||||
let static_fn = GetFileHash::new(vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
args.insert("sha_type".to_string(), to_value(256).unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
static_fn.call(&args).unwrap(),
|
|
||||||
"572e691dc68c3fcd653ae463261bdb38f35dc6f01715d9ce68799319dd158840"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_file_hash_sha256_base64() {
|
|
||||||
let static_fn = GetFileHash::new(vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
args.insert("sha_type".to_string(), to_value(256).unwrap());
|
|
||||||
args.insert("base64".to_string(), to_value(true).unwrap());
|
|
||||||
assert_eq!(static_fn.call(&args).unwrap(), "Vy5pHcaMP81lOuRjJhvbOPNdxvAXFdnOaHmTGd0ViEA=");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_file_hash_sha384() {
|
|
||||||
let static_fn = GetFileHash::new(vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
static_fn.call(&args).unwrap(),
|
|
||||||
"141c09bd28899773b772bbe064d8b718fa1d6f2852b7eafd5ed6689d26b74883b79e2e814cd69d5b52ab476aa284c414"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_file_hash_sha384_base64() {
|
|
||||||
let static_fn = GetFileHash::new(vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
args.insert("base64".to_string(), to_value(true).unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
static_fn.call(&args).unwrap(),
|
|
||||||
"FBwJvSiJl3O3crvgZNi3GPodbyhSt+r9XtZonSa3SIO3ni6BTNadW1KrR2qihMQU"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_file_hash_sha512() {
|
|
||||||
let static_fn = GetFileHash::new(vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
static_fn.call(&args).unwrap(),
|
|
||||||
"379dfab35123b9159d9e4e92dc90e2be44cf3c2f7f09b2e2df80a1b219b461de3556c93e1a9ceb3008e999e2d6a54b4f1d65ee9be9be63fa45ec88931623372f"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_get_file_hash_sha512_base64() {
|
|
||||||
let static_fn = GetFileHash::new(vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("app.css").unwrap());
|
|
||||||
args.insert("sha_type".to_string(), to_value(512).unwrap());
|
|
||||||
args.insert("base64".to_string(), to_value(true).unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
static_fn.call(&args).unwrap(),
|
|
||||||
"N536s1EjuRWdnk6S3JDivkTPPC9/CbLi34Chshm0Yd41Vsk+GpzrMAjpmeLWpUtPHWXum+m+Y/pF7IiTFiM3Lw=="
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn error_when_file_not_found_for_hash() {
|
|
||||||
let static_fn = GetFileHash::new(vec![create_temp_dir().into_path()]);
|
|
||||||
let mut args = HashMap::new();
|
|
||||||
args.insert("path".to_string(), to_value("doesnt-exist").unwrap());
|
|
||||||
let err = format!("{}", static_fn.call(&args).unwrap_err());
|
|
||||||
println!("{:?}", err);
|
|
||||||
|
|
||||||
assert!(err.contains("File doesnt-exist could not be open"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<meta name="description" content="{{ config.description }}">
|
<meta name="description" content="{{ config.description }}">
|
||||||
<meta name="author" content="{{ config.extra.author.name }}">
|
<meta name="author" content="{{ config.extra.author.name }}">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Fira+Mono|Fira+Sans|Merriweather" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Fira+Mono|Fira+Sans|Merriweather" rel="stylesheet">
|
||||||
<link href="{{ get_url(path="/site.css", cachebust=true) | safe }}" rel="stylesheet">
|
<link href="{{ get_url(path="site.css", cachebust=true) | safe }}" rel="stylesheet">
|
||||||
<title>{{ config.title }}</title>
|
<title>{{ config.title }}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue