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
160fn 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}