#[cfg(feature = "web")]
pub mod web;
use proc_macro2::{Ident, Span};
use std::fmt;
use std::path::{Path, PathBuf};
use utils;
use utils::Filter;
use utils::Filter::*;
use utils::FilterListType;
use utils::FilterListType::*;
use walkdir::WalkDir;
use Pipeline;
#[cfg(feature = "web")]
pub use self::web::*;
struct AssetInfo {
path: String,
clean_path: String,
}
pub struct Assets {
ident: String,
prefix: String,
path: PathBuf,
filters: Vec<Filter>,
filter_list_type: FilterListType,
}
impl Assets {
pub fn new<S: Into<String>, P: Into<PathBuf>>(identifier: S, path: P) -> Self {
Assets {
ident: identifier.into(),
prefix: "/".to_string(),
path: path.into(),
filters: Vec::new(),
filter_list_type: Blacklist,
}
}
pub fn filter(mut self, filter: Filter) -> Self {
self.filters.push(filter);
self
}
pub fn prefix<S: Into<String>>(mut self, prefix: S) -> Self {
self.prefix = prefix.into();
self
}
pub fn set_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.path = path.into();
self
}
pub fn blacklist(mut self) -> Self {
self.filter_list_type = Blacklist;
self
}
pub fn whitelist(mut self) -> Self {
self.filter_list_type = Whitelist;
self
}
pub fn build(self) -> Box<Self> {
Box::new(self)
}
}
impl fmt::Display for Assets {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut entries = Vec::new();
for maybe_entry in WalkDir::new(&self.path) {
let entry = maybe_entry.expect("Couldn't read DirEntry");
if entry.file_type().is_dir() {
utils::watch_path(entry.path());
continue;
}
if self.filters.is_empty() {
match self.filter_list_type {
Whitelist => break,
Blacklist => {
utils::watch_path(entry.path());
entries.push(PathBuf::from(entry.path()));
continue;
}
}
}
let mut matched = true;
for filter in &self.filters {
if !filter.matches(entry.path()) {
continue;
}
if let Exclude(_) = filter {
matched = false;
}
break;
}
if matched {
utils::watch_path(entry.path());
entries.push(PathBuf::from(entry.path()));
}
}
let asset_info: Vec<AssetInfo> = entries
.iter()
.map(|p| AssetInfo {
path: utils::path_to_string(p),
clean_path: normalize_path(p, &self.path, &self.prefix),
}).collect();
if asset_info.is_empty() {
panic!("No assets were matched, something is wrong")
}
let code = generate_asset_const(&self.ident, asset_info);
write!(f, "{}", code)
}
}
impl Pipeline for Assets {}
fn normalize_path(path: &Path, dir: &Path, prefix: &str) -> String {
let relative = path.strip_prefix(&dir).expect("Couldn't strip path prefix");
let path = PathBuf::from("/").join(prefix).join(&relative);
utils::path_to_string(path)
}
fn generate_asset_const(ident_str: &str, raw_assets: Vec<AssetInfo>) -> String {
let len = raw_assets.len();
let mut structs = Vec::new();
for AssetInfo { path, clean_path } in raw_assets {
structs.push(quote! {
Asset {
uri: #clean_path,
data: include_bytes!(#path),
}
});
}
let ident = Ident::new(ident_str, Span::call_site());
let tokens = quote! {
const #ident: [Asset; #len] = [#(#structs),*];
};
format!("{}", tokens)
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(1 + 1, 2);
}
}