pocket_resources/
lib.rs

1use std::ascii::AsciiExt;
2use std::env;
3use std::io;
4use std::io::Write;
5use std::fs::File;
6use std::iter::IntoIterator;
7use std::path::Path;
8use std::collections::HashSet;
9
10#[derive(Debug)]
11struct Entry {
12    path: Vec<String>,
13    struct_name: String,
14    struct_name_no_ext: String,
15    resource_str_name: String,
16    resource_str_name_no_ext: String,
17    file_path: String,
18    enum_name: String,
19}
20
21pub fn package<'a, I, P1, P2>(files: I) -> io::Result<()>
22    where I: IntoIterator<Item = &'a (P1, P2)>, P1: AsRef<Path> + 'a, P2: AsRef<Path> + 'a
23{
24    let entries = files.into_iter().map(|&(ref base_dir, ref file)| {
25        let base_dir = base_dir.as_ref();
26        let file = file.as_ref();
27
28        println!("cargo:rerun-if-changed={}/{}", base_dir.display(), file.display());
29
30        Entry {
31            path: file.parent().into_iter().flat_map(|p| p.iter()).map(|val| {
32                let val = val.to_str().expect("Cannot process non-UTF8 path");
33                val.chars().filter(|c| c.is_alphanumeric()).collect::<String>()
34            }).collect(),
35            struct_name: {
36                let val = file.file_name().unwrap();
37                let val = val.to_os_string().into_string().unwrap();
38                let val = val.chars().filter_map(|c| if c.is_alphanumeric() || c == '_' { Some(c) } else if c == '.' { Some('_') } else { None }).collect::<String>();
39                val.to_ascii_uppercase()
40            },
41            struct_name_no_ext: {
42                let val = file.file_stem().unwrap();
43                let val = val.to_os_string().into_string().unwrap();
44                let val = val.chars().filter_map(|c| if c.is_alphanumeric() || c == '_' { Some(c) } else if c == '.' { Some('_') } else { None }).collect::<String>();
45                val.to_ascii_uppercase()
46            },
47            resource_str_name: file.display().to_string(),
48            resource_str_name_no_ext: if file.iter().count() == 1 {
49                file.file_stem().unwrap().to_os_string().into_string().unwrap()
50            } else {
51                file.parent().unwrap().display().to_string() + "/" + &file.file_stem().unwrap().to_os_string().into_string().unwrap()
52            },
53            file_path: base_dir.join(file).display().to_string(),
54            enum_name: path_to_enum_variant(file),
55        }
56    }).collect::<Vec<_>>();
57
58    let file_path = env::var("OUT_DIR").unwrap();
59    let file_path = Path::new(&file_path).join("pocket-resources.rs");
60    let mut file = File::create(&file_path).unwrap();
61
62    try!(writeln!(file, r#"#[derive(Debug, Clone, Hash, PartialEq, Eq)]"#));
63    try!(writeln!(file, r#"pub enum ResourceId {{"#));
64    for entry in &entries { try!(writeln!(file, r"{},", entry.enum_name)); }
65    try!(writeln!(file, r#"}}"#));
66
67    try!(writeln!(file, r#"impl ResourceId {{"#));
68    try!(writeln!(file, r#"    #[inline]"#));
69    try!(writeln!(file, r#"    pub fn load(&self) -> &'static [u8] {{"#));
70    try!(writeln!(file, r#"        match self {{"#));
71    for entry in &entries {
72        try!(writeln!(file, r##"
73                &ResourceId::{} => &include_bytes!(r#"{}/{}"#)[..],
74            "##, entry.enum_name, env::var("CARGO_MANIFEST_DIR").unwrap(), entry.file_path));
75    }
76    try!(writeln!(file, r#"        }}"#));
77    try!(writeln!(file, r#"    }}"#));
78    try!(writeln!(file, r#"    #[inline]"#));
79    try!(writeln!(file, r#"    pub fn name_ext(&self) -> &str {{"#));
80    try!(writeln!(file, r#"        match *self {{"#));
81    for entry in &entries {
82        try!(writeln!(file, r##"
83                ResourceId::{} => r#"{}"#,
84            "##, entry.enum_name, entry.resource_str_name));
85    }
86    try!(writeln!(file, r#"        }}"#));
87    try!(writeln!(file, r#"    }}"#));
88    try!(writeln!(file, r#"    #[inline]"#));
89    try!(writeln!(file, r#"    pub fn from_name(name: &str) -> Option<ResourceId> {{"#));
90    for entry in &entries {
91        try!(writeln!(file, r##"
92                if name == r#"{}"# {{ return Some(ResourceId::{}); }}
93            "##, entry.resource_str_name, entry.enum_name));
94
95        if entry.resource_str_name != entry.resource_str_name_no_ext {
96            if entries.iter().filter(|e| e.resource_str_name_no_ext == entry.resource_str_name_no_ext || e.resource_str_name == entry.resource_str_name_no_ext).count() == 1 {
97            try!(writeln!(file, r##"
98                    if name == r#"{}"# {{ return Some(ResourceId::{}); }}
99                "##, entry.resource_str_name_no_ext, entry.enum_name));
100            }
101        }
102    }
103    try!(writeln!(file, r#"        None"#));
104    try!(writeln!(file, r#"    }}"#));
105    try!(writeln!(file, r#"}}"#));
106
107    try!(write(&entries, &[], &mut file));
108    Ok(())
109}
110
111fn write<W>(entries: &[Entry], base: &[String], output: &mut W) -> io::Result<()>
112    where W: Write
113{
114    let mut sub_paths = HashSet::new();
115
116    for entry in entries {
117        if entry.path.len() > base.len() && &entry.path[..base.len()] == base {
118            sub_paths.insert(&entry.path[base.len()]);
119        }
120
121        if entry.path != base {
122            continue;
123        }
124
125        try!(write!(output, "#[allow(missing_docs)] pub const {}: ", entry.struct_name));
126        for _ in 0 .. base.len() { try!(write!(output, r"super::")); }
127        try!(write!(output, "ResourceId = "));
128        for _ in 0 .. base.len() { try!(write!(output, r"super::")); }
129        try!(writeln!(output, r"ResourceId::{};", entry.enum_name));
130
131        if entry.struct_name != entry.struct_name_no_ext {
132            if entries.iter().filter(|e| e.struct_name_no_ext == entry.struct_name_no_ext || e.struct_name == entry.struct_name_no_ext).count() == 1 {
133                try!(write!(output, "#[allow(missing_docs)] pub const {}: ", entry.struct_name_no_ext));
134                for _ in 0 .. base.len() { try!(write!(output, r"super::")); }
135                try!(write!(output, "ResourceId = "));
136                for _ in 0 .. base.len() { try!(write!(output, r"super::")); }
137                try!(writeln!(output, r"ResourceId::{};", entry.enum_name));
138            }
139        }
140    }
141
142    for sub_path in sub_paths.iter() {
143        try!(writeln!(output, r#"
144            #[allow(missing_docs)]
145            pub mod {} {{
146        "#, sub_path));
147
148        let mut base = base.to_vec();
149        base.push(sub_path.to_string());
150        try!(write(entries, &base, output));
151
152        try!(writeln!(output, r#"
153            }}
154        "#));
155    }
156    
157    Ok(())
158}
159
160/// Turns a path into a variant name for the enumeration of resources.
161fn path_to_enum_variant<P>(path: P) -> String where P: AsRef<Path> {
162    let path = path.as_ref();
163
164    let components = path.iter()
165                         .map(|val| {
166                             let val = val.to_str().expect("Cannot process non-UTF8 path");
167                             let val = val.chars().filter(|c| c.is_alphanumeric()).collect::<String>();
168                             format!("{}{}", val[..1].to_ascii_uppercase(), val[1..].to_ascii_lowercase())
169                         }).collect::<Vec<_>>();
170
171    components.concat()
172}