diff options
-rw-r--r-- | codegen/Cargo.toml (renamed from Cargo.toml) | 5 | ||||
-rw-r--r-- | codegen/src/lib.rs | 147 | ||||
-rw-r--r-- | example/Cargo.toml | 17 | ||||
-rw-r--r-- | example/build.rs | 10 | ||||
-rw-r--r-- | example/data/empty (renamed from examples/foo/data/empty) | 0 | ||||
-rw-r--r-- | example/data/foo (renamed from examples/foo/data/foo) | 0 | ||||
-rw-r--r-- | example/data/inner/boom (renamed from examples/foo/data/inner/boom) | 0 | ||||
-rw-r--r-- | example/src/main.rs | 11 | ||||
-rw-r--r-- | examples/foo/Cargo.toml | 13 | ||||
-rw-r--r-- | examples/foo/build.rs | 5 | ||||
-rw-r--r-- | examples/foo/src/main.rs | 9 | ||||
-rw-r--r-- | lib/Cargo.toml | 17 | ||||
-rw-r--r-- | lib/src/lib.rs | 65 | ||||
-rw-r--r-- | src/lib.rs | 52 |
14 files changed, 270 insertions, 81 deletions
diff --git a/Cargo.toml b/codegen/Cargo.toml index b6bbd4c..1fb1cad 100644 --- a/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "includedir" -description = "Include a whole directory tree at compile time!" +name = "includedir_codegen" +description = "Include a whole directory tree at compile time! - Compile time part" keywords = ["include", "tree", "directory", "build", "static"] version = "0.1.1" authors = ["Till Höppner <till@hoeppner.ws>"] @@ -11,3 +11,4 @@ repository = "https://github.com/tilpner/includedir" [dependencies] walkdir = "0.1.5" phf_codegen = "0.7.12" +flate2 = "0.2.13" diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 0000000..e8a6475 --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,147 @@ +extern crate walkdir; +extern crate phf_codegen; +extern crate flate2; + +use std::{fmt, env, io}; +use std::borrow::{Borrow, Cow}; +use std::collections::HashMap; +use std::fs::{self, File}; +use std::io::{BufReader, BufWriter, Write}; +use std::path::{Path, PathBuf}; + +use walkdir::WalkDir; + +use flate2::FlateWriteExt; + +#[derive(Copy, Clone, Debug)] +pub enum Compression { + None, + Gzip, +} + +impl fmt::Display for Compression { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + Compression::None => fmt.write_str("None"), + Compression::Gzip => fmt.write_str("Gzip"), + } + } +} + +pub struct IncludeDir { + files: HashMap<String, (Compression, PathBuf)>, + name: String, +} + +pub fn start(static_name: &str) -> IncludeDir { + IncludeDir { + files: HashMap::new(), + name: static_name.to_owned(), + } +} + +#[cfg(windows)] +fn as_key(path: &str) -> Cow<str> { + Cow::Owned(path.replace("\\", "/")) +} + +#[cfg(not(windows))] +fn as_key(path: &str) -> Cow<str> { + Cow::Borrowed(path) +} + +impl IncludeDir { + /// Add a single file to the binary. + /// With Gzip compression, the file will be encoded to OUT_DIR first. + /// For chaining, it's not sensible to return a Result. If any to-be-included + /// files can't be found, or encoded, this function will panic!. + pub fn file<P: AsRef<Path>>(&mut self, path: P, comp: Compression) -> &mut IncludeDir { + self.add_file(path, comp).unwrap(); + self + } + + /// ## Panics + /// + /// This function panics when CARGO_MANIFEST_DIR or OUT_DIR are not defined. + pub fn add_file<P: AsRef<Path>>(&mut self, path: P, comp: Compression) -> io::Result<()> { + let key = path.as_ref().to_string_lossy(); + match comp { + Compression::None => { + self.files.insert(as_key(key.borrow()).into_owned(), + (comp, path.as_ref().clone().to_owned())); + } + Compression::Gzip => { + // gzip encode file to OUT_DIR + let in_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(&path); + let mut in_file = BufReader::new(try!(File::open(&in_path))); + + let out_path = Path::new(&env::var("OUT_DIR").unwrap()).join(&path); + try!(fs::create_dir_all(&out_path.parent().unwrap())); + let out_file = BufWriter::new(try!(File::create(&out_path))); + let mut encoder = out_file.gz_encode(flate2::Compression::Default); + + try!(io::copy(&mut in_file, &mut encoder)); + + self.files.insert(as_key(key.borrow()).into_owned(), + (comp, out_path.to_owned())); + } + } + Ok(()) + } + + /// Add a whole directory recursively to the binary. + /// This function calls `file`, and therefore will panic! on missing files. + pub fn dir<P: AsRef<Path>>(&mut self, path: P, comp: Compression) -> &mut IncludeDir { + self.add_dir(path, comp).unwrap(); + self + } + + /// ## Panics + /// + /// This function panics when CARGO_MANIFEST_DIR or OUT_DIR are not defined. + pub fn add_dir<P: AsRef<Path>>(&mut self, path: P, comp: Compression) -> io::Result<()> { + for entry in WalkDir::new(path).follow_links(true).into_iter() { + match entry { + Ok(ref e) if !e.file_type().is_dir() => { + try!(self.add_file(e.path(), comp)); + } + _ => (), + } + } + Ok(()) + } + + pub fn build(&self, out_name: &str) -> io::Result<()> { + let base_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).to_owned(); + let out_path = Path::new(&env::var("OUT_DIR").unwrap()).join(out_name); + let mut out_file = BufWriter::new(try!(File::create(&out_path))); + + try!(write!(&mut out_file, + "use includedir::*;\n\ + pub static {}: Files = Files {{\n\ + \tfiles: ", + self.name)); + + let entries: Vec<_> = self.files //accumulate_entries() + .iter() + .map(|(name, &(ref comp, ref path))| { + let include_path = format!("{}", + base_path.join(path).display()); + let code = format!("(Compression::{}, \ + include_bytes!(\"{}\") as &'static \ + [u8])", + comp, + as_key(&include_path)); + (as_key(&name).into_owned(), code) + }) + .collect(); + let mut map: phf_codegen::Map<&str> = phf_codegen::Map::new(); + for &(ref name, ref code) in &entries { + map.entry(&name, &code); + } + try!(map.build(&mut out_file)); + + try!(write!(&mut out_file, "\n}};\n")); + Ok(()) + } +} diff --git a/example/Cargo.toml b/example/Cargo.toml new file mode 100644 index 0000000..678f8fb --- /dev/null +++ b/example/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "foo" +version = "0.1.0" +authors = ["Till Höppner <till@hoeppner.ws>"] +build = "build.rs" +include = ["data", "data/*", "data/**"] +publish = false + +[dependencies] +phf = "0.7.12" +# includedir = "0.1.1" +includedir = { path = "../lib" } + +[build-dependencies] +# includedir = "0.1.1" +includedir = { path = "../lib" } +includedir_codegen = { path = "../codegen" } diff --git a/example/build.rs b/example/build.rs new file mode 100644 index 0000000..fb23001 --- /dev/null +++ b/example/build.rs @@ -0,0 +1,10 @@ +extern crate includedir_codegen; + +use includedir_codegen::Compression; + +fn main() { + includedir_codegen::start("FILES") + .dir("data", Compression::Gzip) + .build("data.rs") + .unwrap(); +} diff --git a/examples/foo/data/empty b/example/data/empty index e69de29..e69de29 100644 --- a/examples/foo/data/empty +++ b/example/data/empty diff --git a/examples/foo/data/foo b/example/data/foo index 257cc56..257cc56 100644 --- a/examples/foo/data/foo +++ b/example/data/foo diff --git a/examples/foo/data/inner/boom b/example/data/inner/boom index 9e2ba7e..9e2ba7e 100644 --- a/examples/foo/data/inner/boom +++ b/example/data/inner/boom diff --git a/example/src/main.rs b/example/src/main.rs new file mode 100644 index 0000000..9ca782c --- /dev/null +++ b/example/src/main.rs @@ -0,0 +1,11 @@ +extern crate includedir; +extern crate phf; + +include!(concat!(env!("OUT_DIR"), "/data.rs")); + +fn main() { + println!("{:?}", FILES.get("data/foo")) + // for (k, v) in FILES.entries() { + // println!("{}: {} bytes", k, v.len()); + // } +} diff --git a/examples/foo/Cargo.toml b/examples/foo/Cargo.toml deleted file mode 100644 index 93c06c8..0000000 --- a/examples/foo/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "foo" -version = "0.1.0" -authors = ["Till Höppner <till@hoeppner.ws>"] -build = "build.rs" -include = ["data"] -publish = false - -[dependencies] -phf = "0.7.12" - -[build-dependencies] -includedir = "0.1.1" diff --git a/examples/foo/build.rs b/examples/foo/build.rs deleted file mode 100644 index 08c0a34..0000000 --- a/examples/foo/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate includedir; - -fn main() { - includedir::build("data").unwrap(); -} diff --git a/examples/foo/src/main.rs b/examples/foo/src/main.rs deleted file mode 100644 index 8270aec..0000000 --- a/examples/foo/src/main.rs +++ /dev/null @@ -1,9 +0,0 @@ -extern crate phf; - -include!(concat!(env!("OUT_DIR"), "/dir_data.rs")); - -fn main() { - for (k, v) in FILES.entries() { - println!("{}: {} bytes", k, v.len()); - } -} diff --git a/lib/Cargo.toml b/lib/Cargo.toml new file mode 100644 index 0000000..aa82ad8 --- /dev/null +++ b/lib/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "includedir" +description = "Include a whole directory tree at compile time! - Runtime part" +keywords = ["include", "tree", "directory", "build", "static"] +version = "0.1.1" +authors = ["Till Höppner <till@hoeppner.ws>"] +license = "MIT" +homepage = "https://github.com/tilpner/includedir" +repository = "https://github.com/tilpner/includedir" + +[features] +default = ["flate2"] + +[dependencies] +phf = "0.7.12" + +flate2 = { version = "*", optional = true } diff --git a/lib/src/lib.rs b/lib/src/lib.rs new file mode 100644 index 0000000..666c727 --- /dev/null +++ b/lib/src/lib.rs @@ -0,0 +1,65 @@ +extern crate phf; +extern crate flate2; + +use std::borrow::{Borrow, Cow}; +use std::io::{self, Cursor, Error, ErrorKind, Read}; + +use flate2::FlateReadExt; + +pub enum Compression { + None, + Gzip, +} + +/// Runtime access to the included files +pub struct Files { + /// **Do not access this field, it is only public to allow for code generation!** + pub files: phf::Map<&'static str, (Compression, &'static [u8])>, +} + +#[cfg(windows)] +fn as_key(path: &str) -> Cow<str> { + Cow::Owned(path.replace("\\", "/")) +} + +#[cfg(not(windows))] +fn as_key(path: &str) -> Cow<str> { + Cow::Borrowed(path) +} + +impl Files { + pub fn available(&self, path: &str) -> bool { + self.files.contains_key(path) + } + + pub fn get(&self, path: &str) -> io::Result<Cow<'static, [u8]>> { + let key = as_key(path); + match self.files.get(key.borrow() as &str) { + Some(b) => { + match b.0 { + Compression::None => Ok(Cow::Borrowed(b.1)), + Compression::Gzip => { + let mut r = try!(Cursor::new(b.1).gz_decode()); + let mut v = Vec::new(); + try!(r.read_to_end(&mut v)); + Ok(Cow::Owned(v)) + } + } + } + None => Err(Error::new(ErrorKind::NotFound, "Key not found")), + } + } + + pub fn read(&self, path: &str) -> io::Result<Box<Read>> { + let key = as_key(path); + match self.files.get(key.borrow() as &str) { + Some(b) => { + match b.0 { + Compression::None => Ok(Box::new(Cursor::new(b.1))), + Compression::Gzip => Ok(Box::new(try!(Cursor::new(b.1).gz_decode()))), + } + } + None => Err(Error::new(ErrorKind::NotFound, "Key not found")), + } + } +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 939b958..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,52 +0,0 @@ -extern crate walkdir; -extern crate phf_codegen; - -use std::{env, io}; -use std::fs::File; -use std::io::{BufWriter, Write}; -use std::path::Path; - -use walkdir::WalkDir; - -pub fn build<P: AsRef<Path>>(dir: P) -> io::Result<()> { - let dir = dir.as_ref(); - - let base_path = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).to_owned(); - - let dir_name = dir.file_name().expect("Invalid directory name").to_string_lossy(); - let out_name = format!("dir_{}.rs", dir_name); - let out_path = Path::new(&env::var("OUT_DIR").unwrap()).join(out_name); - let mut out_file = BufWriter::new(File::create(&out_path).unwrap()); - - write!(&mut out_file, - "static FILES: phf::Map<&'static str, &'static [u8]> = ") - .unwrap(); - - // FIXME: rustfmt mangles this - let entries: Vec<(String, String)> = WalkDir::new(dir) - .into_iter() - .filter(|e| { - !e.as_ref() - .ok() - .map_or(true, |e| e.file_type().is_dir()) - }) - .map(|e| { - let entry = e.expect("Invalid dir entry."); - let name = format!("{}", entry.path().display()); - let code = format!("include_bytes!(\"{}\") \ - as &'static [u8]", - base_path.join(entry.path()) - .display()); - (name, code) - }) - .collect(); - - let mut map: phf_codegen::Map<&str> = phf_codegen::Map::new(); - for &(ref name, ref code) in &entries { - map.entry(name, code); - } - map.build(&mut out_file).unwrap(); - - write!(&mut out_file, ";\n").unwrap(); - Ok(()) -} |