diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cd6e24c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +## 0.1.0 + +## Added: +- `SHA-256`-based cache-buster +- runtime filemap loading diff --git a/actix-example/build.rs b/actix-example/build.rs index 91aad76..4d73bcc 100644 --- a/actix-example/build.rs +++ b/actix-example/build.rs @@ -17,6 +17,5 @@ fn main() { .build() .unwrap(); - config.init().unwrap(); - config.hash().unwrap().to_env(); + config.process().unwrap().to_env(); } diff --git a/src/filemap.rs b/src/filemap.rs index f5daeb8..e290b42 100644 --- a/src/filemap.rs +++ b/src/filemap.rs @@ -4,6 +4,17 @@ * Use of this source code is governed by the Apache 2.0 and/or the MIT * License. */ +//! Module describing runtime compoenet for fetching modified filenames +//! +//! Add the following tou your program to load the filemap during compiletime: +//! +//! ```no_run +//! use cache_buster::Files; +//! +//! fn main(){ +//! let files = Files::load(); +//! } +//! ``` use std::collections::HashMap; use std::env; @@ -12,13 +23,18 @@ use serde::{Deserialize, Serialize}; const ENV_VAR_NAME: &str = "CACHE_BUSTER_FILE_MAP"; +/// Filemap struct +/// +/// maps original names to generated names #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Files { + /// filemap pub map: HashMap, base_dir: String, } impl Files { + /// Initialize map pub fn new(base_dir: &str) -> Self { Files { map: HashMap::default(), @@ -26,6 +42,10 @@ impl Files { } } + /// Get relative file path + /// + /// If the modified filename path is './prod/test.randomhash.svg`, it will + /// output `/test.randomhash.svg`. For full path, see [get_full_path] pub fn get<'a>(&'a self, path: &'a str) -> Option<&'a str> { if let Some(path) = self.map.get(path) { Some(&path[self.base_dir.len()..]) @@ -34,10 +54,15 @@ impl Files { } } + /// Get file path + /// + /// If the modified filename path is './prod/test.randomhash.svg`, it will + /// output `/prod/test.randomhash.svg`. For relative path, see [get] pub fn get_full_path<'a>(&'a self, path: &'a str) -> Option<&'a String> { self.map.get(path) } + /// Create file map: map original path to modified paths pub fn add(&mut self, k: String, v: String) -> Result<(), &'static str> { if self.map.contains_key(&k) { Err("key exists") @@ -47,6 +72,8 @@ impl Files { } } + /// This crate uses compile-time environment variables to transfer + /// data to the main program. This funtction sets that variable pub fn to_env(&self) { println!( "cargo:rustc-env={}={}", @@ -61,6 +88,7 @@ impl Files { env::set_var(ENV_VAR_NAME, serde_json::to_string(&self).unwrap()); } + /// Load filemap in main program. Should be called from main program pub fn load() -> Self { let env = env::var(ENV_VAR_NAME) .expect("unable to read env var, might be a bug in lib. Please report on GitHub"); @@ -95,8 +123,7 @@ mod tests { .build() .unwrap(); - config.init().unwrap(); - let files = config.hash().unwrap(); + let files = config.process().unwrap(); assert!(get_full_path_runner("./dist/log-out.svg", &files)); assert!(get_full_path_runner( @@ -138,8 +165,7 @@ mod tests { .build() .unwrap(); - config.init().unwrap(); - let files = config.hash().unwrap(); + let files = config.process().unwrap(); files.to_env(); @@ -168,8 +194,7 @@ mod tests { .build() .unwrap(); - config.init().unwrap(); - let files = config.hash().unwrap(); + let files = config.process().unwrap(); assert!(get_runner("./dist/log-out.svg", &files)); assert!(get_runner("./dist/a/b/c/d/s/d/svg/credit-card.svg", &files)); diff --git a/src/lib.rs b/src/lib.rs index a467842..5129c92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,13 @@ * Use of this source code is governed by the Apache 2.0 and/or the MIT * License. */ - -//! ```rust +#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] +//! ```no_run //! use cache_buster::BusterBuilder; //! //! fn main() { //! // note: add error checking yourself. -//! // println!("cargo:rustc-env=GIT_HASH={}", git_hash); +//! // println!("cargo:rustc-env=GIT_process={}", git_process); //! let types = vec![ //! mime::IMAGE_PNG, //! mime::IMAGE_SVG, @@ -27,8 +27,7 @@ //! .build() //! .unwrap(); //! -//! config.init().unwrap(); -//! config.hash().unwrap(); +//! config.process().unwrap(); //! } //! ``` diff --git a/src/processor.rs b/src/processor.rs index 6c83d9e..2848c39 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -5,6 +5,39 @@ * License. */ +//! Module describing file processor that changes filenames to setup cache-busting +//! +//! Run the following during build using `build.rs`: +//! +//! ```rust +//! use cache_buster::BusterBuilder; +//! +//! fn main() { +//! // note: add error checking yourself. +//! // println!("cargo:rustc-env=GIT_process={}", git_process); +//! let types = vec![ +//! mime::IMAGE_PNG, +//! mime::IMAGE_SVG, +//! mime::IMAGE_JPEG, +//! mime::IMAGE_GIF, +//! ]; +//! +//! let config = BusterBuilder::default() +//! .source("./dist") +//! .result("./prod") +//! .mime_types(types) +//! .copy(true) +//! .follow_links(true) +//! .build() +//! .unwrap(); +//! +//! config.process().unwrap(); +//! } +//! ``` +//! +//! There's a runtime component to this library which will let you read modified +//! filenames from within your program. See [Files] + use std::io::Error; use std::path::Path; use std::{fs, path::PathBuf}; @@ -14,23 +47,26 @@ use walkdir::WalkDir; use crate::Files; +/// Configuration for setting up cache-busting #[derive(Debug, Clone, Builder)] pub struct Buster { - // source directory + /// source directory #[builder(setter(into))] source: String, - // mime_types for hashing + /// mime_types for hashing mime_types: Vec, - // directory for writing results + /// directory for writing results #[builder(setter(into))] result: String, - // copy other non-hashed files from source dire to result dir? + /// copy other non-hashed files from source dire to result dir? copy: bool, + /// follow symlinks? follow_links: bool, } impl Buster { - pub fn init(&self) -> Result<(), Error> { + // creates base_dir to output files to + fn init(&self) -> Result<(), Error> { let res = Path::new(&self.result); if res.exists() { fs::remove_dir_all(&self.result).unwrap(); @@ -49,11 +85,16 @@ impl Buster { HEXUPPER.encode(&hasher.finalize()) } - // if mime types are common, then you shoud be fine using this - // use [hash] when when they aren't - // - // doesn't process files for which mime is not resolved - pub fn try_hash(&self) -> Result { + /// Processes files. + /// + /// If MIME types are uncommon, then use this funtion + /// as it won't panic when a weird MIM is encountered. + /// + /// Otherwise, use [process] + /// + /// Note: it omits processing uncommon MIME types + pub fn try_process(&self) -> Result { + self.init()?; let mut file_map: Files = Files::new(&self.result); for entry in WalkDir::new(&self.source) .follow_links(self.follow_links) @@ -89,9 +130,15 @@ impl Buster { Ok(file_map) } - // panics when mimetypes are detected. This way you'll know which files are ignored - // from processing - pub fn hash(&self) -> Result { + /// Processes files. + /// + /// If MIME types are common, then use this funtion + /// as it will panic when a weird MIM is encountered. + pub fn process(&self) -> Result { + // panics when mimetypes are detected. This way you'll know which files are ignored + // from processing + + self.init()?; let mut file_map: Files = Files::new(&self.result); for entry in WalkDir::new(&self.source) @@ -131,6 +178,7 @@ impl Buster { Ok(file_map) } + // helper fn to read file to string fn read_to_string(path: &Path) -> Result, Error> { use std::fs::File; use std::io::Read; @@ -141,18 +189,21 @@ impl Buster { Ok(file_content) } + // helper fn to generate filemap fn gen_map<'a>(&self, source: &'a Path, name: &str) -> (&'a Path, PathBuf) { let rel_location = source.strip_prefix(&self.source).unwrap().parent().unwrap(); let destination = Path::new(&self.result).join(rel_location).join(name); (source, destination) } + // helper fn to copy files fn copy(&self, source: &Path, name: &str) { let rel_location = source.strip_prefix(&self.source).unwrap().parent().unwrap(); let destination = Path::new(&self.result).join(rel_location).join(name); fs::copy(source, &destination).unwrap(); } + // helper fn to create directory structure in self.base_dir fn create_dir_structure(&self, path: &Path) -> Result<(), Error> { for entry in WalkDir::new(&path) .follow_links(self.follow_links) @@ -193,15 +244,14 @@ pub mod tests { let config = BusterBuilder::default() .source("./dist") - .result("./prod") + .result("./prod56") .mime_types(types) .copy(true) .follow_links(true) .build() .unwrap(); - config.init().unwrap(); - let mut files = config.hash().unwrap(); + let mut files = config.process().unwrap(); for (k, v) in files.map.drain() { let src = Path::new(&k); @@ -213,7 +263,7 @@ pub mod tests { } #[test] - fn try_hash_works() { + fn try_process_works() { let types = vec![ mime::IMAGE_PNG, mime::IMAGE_SVG, @@ -230,8 +280,7 @@ pub mod tests { .build() .unwrap(); - config.init().unwrap(); - let mut files = config.try_hash().unwrap(); + let mut files = config.try_process().unwrap(); for (k, v) in files.map.drain() { let src = Path::new(&k);