diff --git a/CHANGELOG.md b/CHANGELOG.md index 15d40fbce7..6bc0aa6ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ This will error if 2 values are set - Enable locale date formatting for the Tera `date` filter - Cachebust fingerprint is now only 20 chars long - Add `text` alias for plain text highlighting (before, only `txt` was used) +- Adds a new built-in variable to shortcodes: `colocated_path` that points to the folder of the current file being rendered if it's a colocated folder. None otherwise. ## 0.16.1 (2022-08-14) diff --git a/components/content/src/file_info.rs b/components/content/src/file_info.rs index 8d910abaf4..37ffcf292c 100644 --- a/components/content/src/file_info.rs +++ b/components/content/src/file_info.rs @@ -37,6 +37,9 @@ pub struct FileInfo { pub name: String, /// The .md path, starting from the content directory, with `/` slashes pub relative: String, + /// The path from the content directory to the colocated directory. Ends with a `/` when set. + /// Only filled if it is a colocated directory, None otherwise. + pub colocated_path: Option, /// Path of the directory containing the .md file pub parent: PathBuf, /// Path of the grand parent directory for that file. Only used in sections to find subsections. @@ -63,11 +66,17 @@ impl FileInfo { } else { format!("{}.md", name) }; + let mut colocated_path = None; // If we have a folder with an asset, don't consider it as a component // Splitting on `.` as we might have a language so it isn't *only* index but also index.fr // etc if !components.is_empty() && name.split('.').collect::>()[0] == "index" { + colocated_path = Some({ + let mut val = components.join("/"); + val.push('/'); + val + }); components.pop(); // also set parent_path to grandparent instead parent = parent.parent().unwrap().to_path_buf(); @@ -83,6 +92,7 @@ impl FileInfo { name, components, relative, + colocated_path, } } @@ -108,6 +118,7 @@ impl FileInfo { name, components, relative, + colocated_path: None, } } @@ -171,6 +182,7 @@ mod tests { &PathBuf::new(), ); assert_eq!(file.components, ["posts".to_string(), "tutorials".to_string()]); + assert_eq!(file.colocated_path, Some("posts/tutorials/python/".to_string())); } #[test] @@ -211,6 +223,7 @@ mod tests { &PathBuf::new(), ); assert_eq!(file.components, ["posts".to_string(), "tutorials".to_string()]); + assert_eq!(file.colocated_path, Some("posts/tutorials/python/".to_string())); let res = file.find_language("en", &["fr"]); assert!(res.is_ok()); assert_eq!(res.unwrap(), "fr"); diff --git a/components/content/src/page.rs b/components/content/src/page.rs index f54d42c5d1..1b574988f4 100644 --- a/components/content/src/page.rs +++ b/components/content/src/page.rs @@ -228,6 +228,7 @@ impl Page { context.set_shortcode_definitions(shortcode_definitions); context.set_current_page_path(&self.file.relative); context.tera_context.insert("page", &SerializingPage::new(self, None, false)); + context.tera_context.insert("colocated_path", &self.file.colocated_path); let res = render_content(&self.raw_content, &context) .with_context(|| format!("Failed to render content of {}", self.file.path.display()))?; diff --git a/components/content/src/section.rs b/components/content/src/section.rs index f5695bad46..6a04ab0782 100644 --- a/components/content/src/section.rs +++ b/components/content/src/section.rs @@ -163,6 +163,7 @@ impl Section { context .tera_context .insert("section", &SerializingSection::new(self, SectionSerMode::ForMarkdown)); + context.tera_context.insert("colocated_path", &self.file.colocated_path); let res = render_content(&self.raw_content, &context) .with_context(|| format!("Failed to render content of {}", self.file.path.display()))?; diff --git a/components/imageproc/src/meta.rs b/components/imageproc/src/meta.rs index 8e23d2b54f..f00741cbab 100644 --- a/components/imageproc/src/meta.rs +++ b/components/imageproc/src/meta.rs @@ -47,7 +47,6 @@ impl ImageMetaResponse { impl From for ImageMetaResponse { fn from(im: ImageMeta) -> Self { - println!("{:?}", im); Self { width: im.size.0, height: im.size.1, diff --git a/components/imageproc/src/processor.rs b/components/imageproc/src/processor.rs index a1ab472dba..e78a28d1e3 100644 --- a/components/imageproc/src/processor.rs +++ b/components/imageproc/src/processor.rs @@ -158,7 +158,14 @@ impl Processor { // Only add it to the set of things to process if the file doesn't exist or the input file // is stale if !output_path.exists() || ufs::file_stale(&input_path, &output_path) { - self.img_ops.insert(ImageOp { input_path, output_path, instr, format }); + println!( + "Processing {:?}, output_exists? {}, is stale? {}", + input_path, + output_path.exists(), + ufs::file_stale(&input_path, &output_path) + ); + let img_op = ImageOp { input_path, output_path, instr, format }; + self.img_ops.insert(img_op); } Ok(enqueue_response) @@ -193,6 +200,7 @@ impl Processor { .iter() .map(|o| o.output_path.file_name().unwrap().to_string_lossy()) .collect(); + for entry in fs::read_dir(&self.output_dir)? { let entry_path = entry?.path(); if entry_path.is_file() { diff --git a/components/templates/src/global_fns/images.rs b/components/templates/src/global_fns/images.rs index d35b0a6278..641e26391c 100644 --- a/components/templates/src/global_fns/images.rs +++ b/components/templates/src/global_fns/images.rs @@ -60,7 +60,8 @@ impl TeraFn for ResizeImage { return Err("`resize_image`: `quality` must be in range 1-100".to_string().into()); } } - let resize_op = imageproc::ResizeOperation::from_args(&op, width, height).map_err(|e| format!("`resize_image`: {}", e))?; + let resize_op = imageproc::ResizeOperation::from_args(&op, width, height) + .map_err(|e| format!("`resize_image`: {}", e))?; let mut imageproc = self.imageproc.lock().unwrap(); let (file_path, unified_path) = match search_for_file(&self.base_path, &path, &self.theme, &self.output_path) @@ -72,7 +73,6 @@ impl TeraFn for ResizeImage { } }; - let response = imageproc .enqueue(resize_op, unified_path, file_path, &format, quality) .map_err(|e| format!("`resize_image`: {}", e))?; @@ -193,11 +193,13 @@ mod tests { assert_eq!( data["static_path"], - to_value(&format!("{}", static_path.join("gutenberg.da10f4be4f1c441e.jpg").display())).unwrap() + to_value(&format!("{}", static_path.join("gutenberg.da10f4be4f1c441e.jpg").display())) + .unwrap() ); assert_eq!( data["url"], - to_value("http://a-website.com/processed_images/gutenberg.da10f4be4f1c441e.jpg").unwrap() + to_value("http://a-website.com/processed_images/gutenberg.da10f4be4f1c441e.jpg") + .unwrap() ); // 2. resizing an image in content with a relative path @@ -205,11 +207,13 @@ mod tests { let data = static_fn.call(&args).unwrap().as_object().unwrap().clone(); assert_eq!( data["static_path"], - to_value(&format!("{}", static_path.join("gutenberg.3301b37eed389d2e.jpg").display())).unwrap() + to_value(&format!("{}", static_path.join("gutenberg.3301b37eed389d2e.jpg").display())) + .unwrap() ); assert_eq!( data["url"], - to_value("http://a-website.com/processed_images/gutenberg.3301b37eed389d2e.jpg").unwrap() + to_value("http://a-website.com/processed_images/gutenberg.3301b37eed389d2e.jpg") + .unwrap() ); // 3. resizing with an absolute path is the same as the above @@ -227,7 +231,8 @@ mod tests { let data = static_fn.call(&args).unwrap().as_object().unwrap().clone(); assert_eq!( data["static_path"], - to_value(&format!("{}", static_path.join("asset.d2fde9a750b68471.jpg").display())).unwrap() + to_value(&format!("{}", static_path.join("asset.d2fde9a750b68471.jpg").display())) + .unwrap() ); assert_eq!( data["url"], @@ -239,11 +244,13 @@ mod tests { let data = static_fn.call(&args).unwrap().as_object().unwrap().clone(); assert_eq!( data["static_path"], - to_value(&format!("{}", static_path.join("in-theme.9b0d29e07d588b60.jpg").display())).unwrap() + to_value(&format!("{}", static_path.join("in-theme.9b0d29e07d588b60.jpg").display())) + .unwrap() ); assert_eq!( data["url"], - to_value("http://a-website.com/processed_images/in-theme.9b0d29e07d588b60.jpg").unwrap() + to_value("http://a-website.com/processed_images/in-theme.9b0d29e07d588b60.jpg") + .unwrap() ); } diff --git a/components/utils/src/fs.rs b/components/utils/src/fs.rs index 1e719f0c7b..17fc5e746a 100644 --- a/components/utils/src/fs.rs +++ b/components/utils/src/fs.rs @@ -174,7 +174,6 @@ where path.as_ref().file_name().and_then(|s| s.to_str()).map(|s| s.starts_with('.')).unwrap_or(false) } - /// Returns whether the path we received corresponds to a temp file created /// by an editor or the OS pub fn is_temp_file(path: &Path) -> bool { diff --git a/docs/content/documentation/content/shortcodes.md b/docs/content/documentation/content/shortcodes.md index a6652323cd..179d2321f2 100644 --- a/docs/content/documentation/content/shortcodes.md +++ b/docs/content/documentation/content/shortcodes.md @@ -163,10 +163,11 @@ Every shortcode can access some variables, beyond what you explicitly passed as - invocation count (`nth`) - current language (`lang`), unless called from the `markdown` template filter (in which case it will always be the same value as `default_language` in configuration, or `en` when it is unset) +- `colocated_path` When one of these variables conflict with a variable passed as argument, the argument value will be used. -### Invocation Count +### `nth`: invocation count Every shortcode context is passed in a variable named `nth` that tracks how many times a particular shortcode has been invoked in the current Markdown file. Given a shortcode `true_statement.html` template: @@ -184,17 +185,30 @@ It could be used in our Markdown as follows: This is useful when implementing custom markup for features such as sidenotes or end notes. -### Current language +### `lang`: current language +**NOTE:** When calling a shortcode from within the `markdown` template filter, the `lang` variable will always be `en`. +If you feel like you need that, please consider using template macros instead. +If you really need that, you can rewrite your Markdown content to pass `lang` as argument to the shortcode. -**NOTE:** When calling a shortcode from within the `markdown` template filter, the `lang` variable will always be `en`. If you feel like you need that, please consider using template macros instead. If you really need that, you can rewrite your Markdown content to pass `lang` as argument to the shortcode. - -Every shortcode can access the current language in the `lang` variable in the context. This is useful for presenting/filtering information in a shortcode depending in a per-language manner. For example, to display a per-language book cover for the current page in a shortcode called `bookcover.md`: +Every shortcode can access the current language in the `lang` variable in the context. +This is useful for presenting/filtering information in a shortcode depending in a per-language manner. For example, to display a per-language book cover for the current page in a shortcode called `bookcover.md`: ```jinja2 ![Book cover in {{ lang }}](cover.{{ lang }}.png) ``` -You can then use it in your Markdown like so: `{{/* bookcover() */}}` +### `colocated_path` +This is used when you want to pass the name of some assets to shortcodes without repeating the full folders path. +Mostly useful when combined with `load_data` or `resize_image`. + +```jinja2 +{% set resized = resize_image(format="jpg", path=colocated_path ~ img_name, width=width, op="fit_width") %} +{{ alt }} +``` + +### `page` or `section` +You can access a slighty stripped down version of the equivalent variables in the normal templates. +The only things missing are translations, backlinks and pages for sections as we are still in the middle of processing. ## Examples diff --git a/src/cmd/serve.rs b/src/cmd/serve.rs index 9d0128cb2e..0ec4321dc7 100644 --- a/src/cmd/serve.rs +++ b/src/cmd/serve.rs @@ -36,10 +36,10 @@ use mime_guess::from_path as mimetype_from_path; use time::macros::format_description; use time::{OffsetDateTime, UtcOffset}; -use libs::percent_encoding; -use libs::serde_json; use libs::globset::GlobSet; +use libs::percent_encoding; use libs::relative_path::{RelativePath, RelativePathBuf}; +use libs::serde_json; use notify::{watcher, RecursiveMode, Watcher}; use ws::{Message, Sender, WebSocket}; @@ -652,7 +652,6 @@ fn is_ignored_file(ignored_content_globset: &Option, path: &Path) -> bo } } - /// Detect what changed from the given path so we have an idea what needs /// to be reloaded fn detect_change_kind(pwd: &Path, path: &Path, config_path: &Path) -> (ChangeKind, PathBuf) {