Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ignored_static to config #2209

Merged
merged 10 commits into from
Aug 13, 2023
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
15 changes: 12 additions & 3 deletions components/site/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use libs::relative_path::RelativePathBuf;
use std::time::Instant;
use templates::{load_tera, render_redirect_template};
use utils::fs::{
clean_site_output_folder, copy_directory, copy_file_if_needed, create_directory, create_file,
ensure_directory_exists,
clean_site_output_folder, copy_directory, copy_directory_with_ignore_globset,
copy_file_if_needed, create_directory, create_file, ensure_directory_exists,
};
use utils::net::{get_available_port, is_external_link};
use utils::templates::{render_template, ShortcodeDefinition};
Expand Down Expand Up @@ -591,7 +591,16 @@ impl Site {
}
// 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_with_ignore_globset(
&self.static_path,
&self.output_path,
self.config.hard_link_static,
gs,
)?;
} else {
copy_directory(&self.static_path, &self.output_path, self.config.hard_link_static)?;
}
Keats marked this conversation as resolved.
Show resolved Hide resolved
}

Ok(())
Expand Down
34 changes: 34 additions & 0 deletions 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 @@ -140,6 +141,39 @@ pub fn copy_directory(src: &Path, dest: &Path, hard_link: bool) -> Result<()> {
Ok(())
}

pub fn copy_directory_with_ignore_globset(
src: &Path,
dest: &Path,
hard_link: bool,
gs: &GlobSet,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just pass an Option to copy_directory instead of copying the function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

) -> 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();
let target_path = dest.join(relative_path);

if gs.is_match(&relative_path) {
continue;
}
if entry.path().is_dir() {
if !target_path.exists() {
create_directory(&target_path)?;
}
} else {
copy_file(entry.path(), dest, src, hard_link).with_context(|| {
format!(
"Was not able to copy {} to {} (hard_link={})",
entry.path().display(),
dest.display(),
hard_link
)
})?;
}
}
Ok(())
}

pub fn get_file_time(path: &Path) -> Option<SystemTime> {
path.metadata().ok().and_then(|meta| {
Some(match (meta.created().ok(), meta.modified().ok()) {
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 @@ -475,6 +475,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