use {
super::project_path,
crate::compression::{BrotliParams, Compression},
anyhow::Context,
glob::glob,
serde::Deserialize,
std::{
collections::HashSet,
path::{Path, PathBuf},
},
};
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
pub struct Content {
compression: Option<CompressionType>,
#[serde(rename = "buffer-size")]
buffer_size: Option<usize>,
quality: Option<u32>,
#[serde(rename = "window-size")]
window_size: Option<u32>,
#[serde(default, rename = "group")]
groups: Box<[Group]>,
}
impl Content {
#[allow(unused)]
pub fn groups(&self) -> impl Iterator<Item = &Group> {
self.groups.iter()
}
pub(crate) fn selected_asset_paths(
&self,
asset_root: impl AsRef<Path>,
) -> anyhow::Result<Vec<PathBuf>> {
let asset_root = asset_root.as_ref();
let enabled_groups = || self.groups().filter(|group| group.enabled());
let mut excluded_assets = HashSet::new();
for pattern in enabled_groups().flat_map(|group| group.exclude_globs()) {
for path in glob(project_path(asset_root, pattern).to_string_lossy().as_ref())? {
excluded_assets.insert(path?);
}
}
let mut asset_paths = Vec::new();
for pattern in enabled_groups().flat_map(|group| group.asset_globs()) {
for path in glob(project_path(asset_root, pattern).to_string_lossy().as_ref())
.context("Unable to glob source directory")?
{
let path = path?;
if !excluded_assets.contains(&path) {
asset_paths.push(path);
}
}
}
Ok(asset_paths)
}
pub(crate) fn compression(&self) -> Option<Compression> {
self.compression.map(|compression| match compression {
CompressionType::Brotli => Compression::Brotli(BrotliParams {
buffer_size: self
.buffer_size
.unwrap_or_else(|| BrotliParams::default().buffer_size),
quality: self
.quality
.unwrap_or_else(|| BrotliParams::default().quality),
window_size: self
.window_size
.unwrap_or_else(|| BrotliParams::default().window_size),
}),
CompressionType::Snap => Compression::Snap,
})
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq)]
pub enum CompressionType {
#[serde(rename = "brotli")]
Brotli,
#[serde(rename = "snap")]
Snap,
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
pub struct Group {
#[serde(default)]
assets: Vec<String>,
#[serde(default = "Group::default_enabled")]
enabled: bool,
#[serde(default)]
exclude: Vec<String>,
}
impl Group {
const DEFAULT_ENABLED: bool = true;
#[allow(unused)]
pub fn asset_globs(&self) -> impl Iterator<Item = &String> {
self.assets.iter()
}
const fn default_enabled() -> bool {
Self::DEFAULT_ENABLED
}
#[allow(unused)]
pub fn enabled(&self) -> bool {
self.enabled
}
#[allow(unused)]
pub fn exclude_globs(&self) -> impl Iterator<Item = &String> {
self.exclude.iter()
}
}
#[cfg(test)]
mod test {
use super::Content;
#[test]
fn content_deserializes_without_groups() {
let content = toml::from_str::<Content>("compression = 'snap'")
.expect("content groups should be optional");
assert_eq!(content.groups().count(), 0);
}
}