changelog and docs
This commit is contained in:
parent
edf8a3121b
commit
23bf39b328
5 changed files with 109 additions and 32 deletions
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
## Added:
|
||||||
|
- `SHA-256`-based cache-buster
|
||||||
|
- runtime filemap loading
|
|
@ -17,6 +17,5 @@ fn main() {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
config.init().unwrap();
|
config.process().unwrap().to_env();
|
||||||
config.hash().unwrap().to_env();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,17 @@
|
||||||
* Use of this source code is governed by the Apache 2.0 and/or the MIT
|
* Use of this source code is governed by the Apache 2.0 and/or the MIT
|
||||||
* License.
|
* 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::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -12,13 +23,18 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
const ENV_VAR_NAME: &str = "CACHE_BUSTER_FILE_MAP";
|
const ENV_VAR_NAME: &str = "CACHE_BUSTER_FILE_MAP";
|
||||||
|
|
||||||
|
/// Filemap struct
|
||||||
|
///
|
||||||
|
/// maps original names to generated names
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
pub struct Files {
|
pub struct Files {
|
||||||
|
/// filemap<original-path, modified-path>
|
||||||
pub map: HashMap<String, String>,
|
pub map: HashMap<String, String>,
|
||||||
base_dir: String,
|
base_dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Files {
|
impl Files {
|
||||||
|
/// Initialize map
|
||||||
pub fn new(base_dir: &str) -> Self {
|
pub fn new(base_dir: &str) -> Self {
|
||||||
Files {
|
Files {
|
||||||
map: HashMap::default(),
|
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> {
|
pub fn get<'a>(&'a self, path: &'a str) -> Option<&'a str> {
|
||||||
if let Some(path) = self.map.get(path) {
|
if let Some(path) = self.map.get(path) {
|
||||||
Some(&path[self.base_dir.len()..])
|
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> {
|
pub fn get_full_path<'a>(&'a self, path: &'a str) -> Option<&'a String> {
|
||||||
self.map.get(path)
|
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> {
|
pub fn add(&mut self, k: String, v: String) -> Result<(), &'static str> {
|
||||||
if self.map.contains_key(&k) {
|
if self.map.contains_key(&k) {
|
||||||
Err("key exists")
|
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) {
|
pub fn to_env(&self) {
|
||||||
println!(
|
println!(
|
||||||
"cargo:rustc-env={}={}",
|
"cargo:rustc-env={}={}",
|
||||||
|
@ -61,6 +88,7 @@ impl Files {
|
||||||
env::set_var(ENV_VAR_NAME, serde_json::to_string(&self).unwrap());
|
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 {
|
pub fn load() -> Self {
|
||||||
let env = env::var(ENV_VAR_NAME)
|
let env = env::var(ENV_VAR_NAME)
|
||||||
.expect("unable to read env var, might be a bug in lib. Please report on GitHub");
|
.expect("unable to read env var, might be a bug in lib. Please report on GitHub");
|
||||||
|
@ -95,8 +123,7 @@ mod tests {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
config.init().unwrap();
|
let files = config.process().unwrap();
|
||||||
let files = config.hash().unwrap();
|
|
||||||
|
|
||||||
assert!(get_full_path_runner("./dist/log-out.svg", &files));
|
assert!(get_full_path_runner("./dist/log-out.svg", &files));
|
||||||
assert!(get_full_path_runner(
|
assert!(get_full_path_runner(
|
||||||
|
@ -138,8 +165,7 @@ mod tests {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
config.init().unwrap();
|
let files = config.process().unwrap();
|
||||||
let files = config.hash().unwrap();
|
|
||||||
|
|
||||||
files.to_env();
|
files.to_env();
|
||||||
|
|
||||||
|
@ -168,8 +194,7 @@ mod tests {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
config.init().unwrap();
|
let files = config.process().unwrap();
|
||||||
let files = config.hash().unwrap();
|
|
||||||
|
|
||||||
assert!(get_runner("./dist/log-out.svg", &files));
|
assert!(get_runner("./dist/log-out.svg", &files));
|
||||||
assert!(get_runner("./dist/a/b/c/d/s/d/svg/credit-card.svg", &files));
|
assert!(get_runner("./dist/a/b/c/d/s/d/svg/credit-card.svg", &files));
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
* Use of this source code is governed by the Apache 2.0 and/or the MIT
|
* Use of this source code is governed by the Apache 2.0 and/or the MIT
|
||||||
* License.
|
* License.
|
||||||
*/
|
*/
|
||||||
|
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
|
||||||
//! ```rust
|
//! ```no_run
|
||||||
//! use cache_buster::BusterBuilder;
|
//! use cache_buster::BusterBuilder;
|
||||||
//!
|
//!
|
||||||
//! fn main() {
|
//! fn main() {
|
||||||
//! // note: add error checking yourself.
|
//! // note: add error checking yourself.
|
||||||
//! // println!("cargo:rustc-env=GIT_HASH={}", git_hash);
|
//! // println!("cargo:rustc-env=GIT_process={}", git_process);
|
||||||
//! let types = vec![
|
//! let types = vec![
|
||||||
//! mime::IMAGE_PNG,
|
//! mime::IMAGE_PNG,
|
||||||
//! mime::IMAGE_SVG,
|
//! mime::IMAGE_SVG,
|
||||||
|
@ -27,8 +27,7 @@
|
||||||
//! .build()
|
//! .build()
|
||||||
//! .unwrap();
|
//! .unwrap();
|
||||||
//!
|
//!
|
||||||
//! config.init().unwrap();
|
//! config.process().unwrap();
|
||||||
//! config.hash().unwrap();
|
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,39 @@
|
||||||
* License.
|
* 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::io::Error;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
@ -14,23 +47,26 @@ use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::Files;
|
use crate::Files;
|
||||||
|
|
||||||
|
/// Configuration for setting up cache-busting
|
||||||
#[derive(Debug, Clone, Builder)]
|
#[derive(Debug, Clone, Builder)]
|
||||||
pub struct Buster {
|
pub struct Buster {
|
||||||
// source directory
|
/// source directory
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into))]
|
||||||
source: String,
|
source: String,
|
||||||
// mime_types for hashing
|
/// mime_types for hashing
|
||||||
mime_types: Vec<mime::Mime>,
|
mime_types: Vec<mime::Mime>,
|
||||||
// directory for writing results
|
/// directory for writing results
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into))]
|
||||||
result: String,
|
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,
|
copy: bool,
|
||||||
|
/// follow symlinks?
|
||||||
follow_links: bool,
|
follow_links: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buster {
|
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);
|
let res = Path::new(&self.result);
|
||||||
if res.exists() {
|
if res.exists() {
|
||||||
fs::remove_dir_all(&self.result).unwrap();
|
fs::remove_dir_all(&self.result).unwrap();
|
||||||
|
@ -49,11 +85,16 @@ impl Buster {
|
||||||
HEXUPPER.encode(&hasher.finalize())
|
HEXUPPER.encode(&hasher.finalize())
|
||||||
}
|
}
|
||||||
|
|
||||||
// if mime types are common, then you shoud be fine using this
|
/// Processes files.
|
||||||
// use [hash] when when they aren't
|
///
|
||||||
//
|
/// If MIME types are uncommon, then use this funtion
|
||||||
// doesn't process files for which mime is not resolved
|
/// as it won't panic when a weird MIM is encountered.
|
||||||
pub fn try_hash(&self) -> Result<Files, Error> {
|
///
|
||||||
|
/// Otherwise, use [process]
|
||||||
|
///
|
||||||
|
/// Note: it omits processing uncommon MIME types
|
||||||
|
pub fn try_process(&self) -> Result<Files, Error> {
|
||||||
|
self.init()?;
|
||||||
let mut file_map: Files = Files::new(&self.result);
|
let mut file_map: Files = Files::new(&self.result);
|
||||||
for entry in WalkDir::new(&self.source)
|
for entry in WalkDir::new(&self.source)
|
||||||
.follow_links(self.follow_links)
|
.follow_links(self.follow_links)
|
||||||
|
@ -89,9 +130,15 @@ impl Buster {
|
||||||
Ok(file_map)
|
Ok(file_map)
|
||||||
}
|
}
|
||||||
|
|
||||||
// panics when mimetypes are detected. This way you'll know which files are ignored
|
/// Processes files.
|
||||||
// from processing
|
///
|
||||||
pub fn hash(&self) -> Result<Files, Error> {
|
/// If MIME types are common, then use this funtion
|
||||||
|
/// as it will panic when a weird MIM is encountered.
|
||||||
|
pub fn process(&self) -> Result<Files, Error> {
|
||||||
|
// 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);
|
let mut file_map: Files = Files::new(&self.result);
|
||||||
|
|
||||||
for entry in WalkDir::new(&self.source)
|
for entry in WalkDir::new(&self.source)
|
||||||
|
@ -131,6 +178,7 @@ impl Buster {
|
||||||
Ok(file_map)
|
Ok(file_map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper fn to read file to string
|
||||||
fn read_to_string(path: &Path) -> Result<Vec<u8>, Error> {
|
fn read_to_string(path: &Path) -> Result<Vec<u8>, Error> {
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
@ -141,18 +189,21 @@ impl Buster {
|
||||||
Ok(file_content)
|
Ok(file_content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper fn to generate filemap
|
||||||
fn gen_map<'a>(&self, source: &'a Path, name: &str) -> (&'a Path, PathBuf) {
|
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 rel_location = source.strip_prefix(&self.source).unwrap().parent().unwrap();
|
||||||
let destination = Path::new(&self.result).join(rel_location).join(name);
|
let destination = Path::new(&self.result).join(rel_location).join(name);
|
||||||
(source, destination)
|
(source, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper fn to copy files
|
||||||
fn copy(&self, source: &Path, name: &str) {
|
fn copy(&self, source: &Path, name: &str) {
|
||||||
let rel_location = source.strip_prefix(&self.source).unwrap().parent().unwrap();
|
let rel_location = source.strip_prefix(&self.source).unwrap().parent().unwrap();
|
||||||
let destination = Path::new(&self.result).join(rel_location).join(name);
|
let destination = Path::new(&self.result).join(rel_location).join(name);
|
||||||
fs::copy(source, &destination).unwrap();
|
fs::copy(source, &destination).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper fn to create directory structure in self.base_dir
|
||||||
fn create_dir_structure(&self, path: &Path) -> Result<(), Error> {
|
fn create_dir_structure(&self, path: &Path) -> Result<(), Error> {
|
||||||
for entry in WalkDir::new(&path)
|
for entry in WalkDir::new(&path)
|
||||||
.follow_links(self.follow_links)
|
.follow_links(self.follow_links)
|
||||||
|
@ -193,15 +244,14 @@ pub mod tests {
|
||||||
|
|
||||||
let config = BusterBuilder::default()
|
let config = BusterBuilder::default()
|
||||||
.source("./dist")
|
.source("./dist")
|
||||||
.result("./prod")
|
.result("./prod56")
|
||||||
.mime_types(types)
|
.mime_types(types)
|
||||||
.copy(true)
|
.copy(true)
|
||||||
.follow_links(true)
|
.follow_links(true)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
config.init().unwrap();
|
let mut files = config.process().unwrap();
|
||||||
let mut files = config.hash().unwrap();
|
|
||||||
|
|
||||||
for (k, v) in files.map.drain() {
|
for (k, v) in files.map.drain() {
|
||||||
let src = Path::new(&k);
|
let src = Path::new(&k);
|
||||||
|
@ -213,7 +263,7 @@ pub mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn try_hash_works() {
|
fn try_process_works() {
|
||||||
let types = vec![
|
let types = vec![
|
||||||
mime::IMAGE_PNG,
|
mime::IMAGE_PNG,
|
||||||
mime::IMAGE_SVG,
|
mime::IMAGE_SVG,
|
||||||
|
@ -230,8 +280,7 @@ pub mod tests {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
config.init().unwrap();
|
let mut files = config.try_process().unwrap();
|
||||||
let mut files = config.try_hash().unwrap();
|
|
||||||
|
|
||||||
for (k, v) in files.map.drain() {
|
for (k, v) in files.map.drain() {
|
||||||
let src = Path::new(&k);
|
let src = Path::new(&k);
|
||||||
|
|
Loading…
Reference in a new issue