Skip to content

Commit

Permalink
Add ignored_static to config (#2209)
Browse files Browse the repository at this point in the history
* Add ignored_static to config

* Make  handle ignored static files correctly

* cargo fmt

* Match on relative path rather than incorrect target path

* path -> partial path for serve static ignore

* remove debug println

* copy static directory if there is no ignored globset

* Update docs

* Deduplicate code with additional Option argument

* cargo fmt
  • Loading branch information
Raymi306 authored and Keats committed Dec 18, 2023
1 parent 35aab56 commit 4430515
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 12 deletions.
90 changes: 80 additions & 10 deletions components/config/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ pub enum Mode {
Check,
}

fn build_ignore_glob_set(ignore: &Vec<String>, name: &str) -> Result<GlobSet> {
let mut glob_set_builder = GlobSetBuilder::new();
for pat in ignore {
let glob = match Glob::new(pat) {
Ok(g) => g,
Err(e) => bail!("Invalid ignored_{} glob pattern: {}, error = {}", name, pat, e),
};
glob_set_builder.add(glob);
}
Ok(glob_set_builder.build().expect(&format!("Bad ignored_{} in config file.", name)))
}

#[derive(Clone, Debug, Deserialize)]
#[serde(default)]
pub struct Config {
Expand Down Expand Up @@ -74,6 +86,11 @@ pub struct Config {
#[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are needed
pub ignored_content_globset: Option<GlobSet>,

/// A list of file glob patterns to ignore when processing the static folder. Defaults to none.
pub ignored_static: Vec<String>,
#[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are needed
pub ignored_static_globset: Option<GlobSet>,

/// The mode Zola is currently being ran on. Some logging/feature can differ depending on the
/// command being used.
#[serde(skip_serializing)]
Expand Down Expand Up @@ -140,16 +157,13 @@ impl Config {
// globset matcher to always exist (even though it has to be an inside an Option at the
// moment because of the TOML serializer); if the glob set is empty the `is_match` function
// of the globber always returns false.
let mut glob_set_builder = GlobSetBuilder::new();
for pat in &config.ignored_content {
let glob = match Glob::new(pat) {
Ok(g) => g,
Err(e) => bail!("Invalid ignored_content glob pattern: {}, error = {}", pat, e),
};
glob_set_builder.add(glob);
}
config.ignored_content_globset =
Some(glob_set_builder.build().expect("Bad ignored_content in config file."));
let glob_set = build_ignore_glob_set(&config.ignored_content, "content")?;
config.ignored_content_globset = Some(glob_set);
}

if !config.ignored_static.is_empty() {
let glob_set = build_ignore_glob_set(&config.ignored_static, "static")?;
config.ignored_static_globset = Some(glob_set);
}

Ok(config)
Expand Down Expand Up @@ -386,6 +400,8 @@ impl Default for Config {
build_search_index: false,
ignored_content: Vec::new(),
ignored_content_globset: None,
ignored_static: Vec::new(),
ignored_static_globset: None,
translations: HashMap::new(),
output_dir: "public".to_string(),
preserve_dotfiles_in_output: false,
Expand Down Expand Up @@ -648,6 +664,18 @@ base_url = "example.com"
assert!(config.ignored_content_globset.is_none());
}

#[test]
fn missing_ignored_static_results_in_empty_vector_and_empty_globset() {
let config_str = r#"
title = "My site"
base_url = "example.com"
"#;
let config = Config::parse(config_str).unwrap();
let v = config.ignored_static;
assert_eq!(v.len(), 0);
assert!(config.ignored_static_globset.is_none());
}

#[test]
fn empty_ignored_content_results_in_empty_vector_and_empty_globset() {
let config_str = r#"
Expand All @@ -661,6 +689,19 @@ ignored_content = []
assert!(config.ignored_content_globset.is_none());
}

#[test]
fn empty_ignored_static_results_in_empty_vector_and_empty_globset() {
let config_str = r#"
title = "My site"
base_url = "example.com"
ignored_static = []
"#;

let config = Config::parse(config_str).unwrap();
assert_eq!(config.ignored_static.len(), 0);
assert!(config.ignored_static_globset.is_none());
}

#[test]
fn non_empty_ignored_content_results_in_vector_of_patterns_and_configured_globset() {
let config_str = r#"
Expand Down Expand Up @@ -690,6 +731,35 @@ ignored_content = ["*.{graphml,iso}", "*.py?", "**/{target,temp_folder}"]
assert!(g.is_match("content/poetry/zen.py2"));
}

#[test]
fn non_empty_ignored_static_results_in_vector_of_patterns_and_configured_globset() {
let config_str = r#"
title = "My site"
base_url = "example.com"
ignored_static = ["*.{graphml,iso}", "*.py?", "**/{target,temp_folder}"]
"#;

let config = Config::parse(config_str).unwrap();
let v = config.ignored_static;
assert_eq!(v, vec!["*.{graphml,iso}", "*.py?", "**/{target,temp_folder}"]);

let g = config.ignored_static_globset.unwrap();
assert_eq!(g.len(), 3);
assert!(g.is_match("foo.graphml"));
assert!(g.is_match("foo/bar/foo.graphml"));
assert!(g.is_match("foo.iso"));
assert!(!g.is_match("foo.png"));
assert!(g.is_match("foo.py2"));
assert!(g.is_match("foo.py3"));
assert!(!g.is_match("foo.py"));
assert!(g.is_match("foo/bar/target"));
assert!(g.is_match("foo/bar/baz/temp_folder"));
assert!(g.is_match("foo/bar/baz/temp_folder/target"));
assert!(g.is_match("temp_folder"));
assert!(g.is_match("my/isos/foo.iso"));
assert!(g.is_match("content/poetry/zen.py2"));
}

#[test]
fn link_checker_skip_anchor_prefixes() {
let config_str = r#"
Expand Down
17 changes: 16 additions & 1 deletion components/site/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,11 +587,26 @@ impl Site {
&self.base_path.join("themes").join(theme).join("static"),
&self.output_path,
false,
None,
)?;
}
// We're fine with missing static folders
if self.static_path.exists() {
copy_directory(&self.static_path, &self.output_path, self.config.hard_link_static)?;
if let Some(gs) = &self.config.ignored_static_globset {
copy_directory(
&self.static_path,
&self.output_path,
self.config.hard_link_static,
Some(gs),
)?;
} else {
copy_directory(
&self.static_path,
&self.output_path,
self.config.hard_link_static,
None,
)?;
}
}

Ok(())
Expand Down
15 changes: 14 additions & 1 deletion components/utils/src/fs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use libs::filetime::{set_file_mtime, FileTime};
use libs::globset::GlobSet;
use libs::walkdir::WalkDir;
use std::fs::{copy, create_dir_all, metadata, remove_dir_all, remove_file, File};
use std::io::prelude::*;
Expand Down Expand Up @@ -115,11 +116,23 @@ pub fn copy_file_if_needed(src: &Path, dest: &Path, hard_link: bool) -> Result<(
Ok(())
}

pub fn copy_directory(src: &Path, dest: &Path, hard_link: bool) -> Result<()> {
pub fn copy_directory(
src: &Path,
dest: &Path,
hard_link: bool,
ignore_globset: Option<&GlobSet>,
) -> Result<()> {
for entry in
WalkDir::new(src).follow_links(true).into_iter().filter_map(std::result::Result::ok)
{
let relative_path = entry.path().strip_prefix(src).unwrap();

if let Some(gs) = ignore_globset {
if gs.is_match(&relative_path) {
continue;
}
}

let target_path = dest.join(relative_path);

if entry.path().is_dir() {
Expand Down
5 changes: 5 additions & 0 deletions docs/content/documentation/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ minify_html = false
# ignored_content = ["*.{graphml,xlsx}", "temp.*", "**/build_folder"]
ignored_content = []

# Similar to ignored_content, a list of glob patterns specifying asset files to
# ignore when the static directory is processed. Defaults to none, which means
# that all asset files are copied over to the `public` directory
ignored_static = []

# When set to "true", a feed is automatically generated.
generate_feed = false

Expand Down
6 changes: 6 additions & 0 deletions src/cmd/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,12 @@ pub fn serve(
};

let copy_static = |site: &Site, path: &Path, partial_path: &Path| {
// Do nothing if the file/dir is on the ignore list
if let Some(gs) = &site.config.ignored_static_globset {
if gs.is_match(partial_path) {
return;
}
}
// Do nothing if the file/dir was deleted
if !path.exists() {
return;
Expand Down

0 comments on commit 4430515

Please sign in to comment.