1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use std::ascii::AsciiExt;
use std::env;
use std::io;
use std::io::Write;
use std::fs;
use std::fs::File;
use std::iter::IntoIterator;
use std::path::{Component, Path};

pub fn package<I>(directories: I, remove_extensions: bool) -> io::Result<()>
                  where I: IntoIterator, I::Item: AsRef<Path>
{
    let mut enum_output = format!(r#"
        #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
        pub enum Resource {{
            "#);

    let mut if_clause = format!("");
    let mut load_clause = format!("");

    for directory in directories.into_iter() {
        try!(visit_dirs(&directory, &mut |original_entry| {
            let original_entry = original_entry.path();
            let entry = relative_from(&original_entry, &directory).unwrap();

            let res_name = path_to_resource_name(entry, remove_extensions);
            let enum_variant = path_to_enum_variant(entry, remove_extensions);

            enum_output.push_str(&format!(r#"
                /// {}
                {},
            "#, res_name, enum_variant));

            if_clause.push_str(&format!(r##"
                if name == r#"{}"# {{
                    Some(Resource::{})
                }} else 
            "##, res_name, enum_variant));

            load_clause.push_str(&format!(r##"
                &Resource::{} => include_bytes!(r#"{}"#),
            "##, enum_variant, env::current_dir().unwrap().join(&original_entry).display()));
        }));
    }

    enum_output.push_str("}");

    let file_path = env::var("OUT_DIR").unwrap();
    let file_path = Path::new(&file_path).join("pocket-resources.rs");
    let mut file = File::create(&file_path).unwrap();
    try!(writeln!(file.by_ref(), r#"
        {en}

        impl Resource {{
            pub fn from_name(name: &str) -> Option<Resource> {{
                {if_clause} {{
                    None
                }}
            }}

            pub fn load(&self) -> &'static [u8] {{
                match self {{
                    {load_clause}
                }}
            }}
        }}
    "#, en = enum_output, if_clause = if_clause, load_clause = load_clause));

    Ok(())
}

fn visit_dirs<P, C>(dir: P, mut cb: &mut C) -> io::Result<()>
                    where P: AsRef<Path>, C: FnMut(fs::DirEntry)
{
    let dir = dir.as_ref();

    if try!(fs::metadata(dir)).is_dir() {
        for entry in try!(fs::read_dir(dir)) {
            let entry = try!(entry);
            if try!(fs::metadata(entry.path())).is_dir() {
                try!(visit_dirs(&entry.path(), cb));
            } else {
                cb(entry);
            }
        }
    }

    Ok(())
}

/// Turns a path into a variant name for the enumeration of resources.
fn path_to_enum_variant<P>(path: P, remove_extensions: bool) -> String where P: AsRef<Path> {
    let path = path.as_ref();

    let components = path.parent().into_iter().flat_map(|p| p.iter())
                         .chain(if remove_extensions {
                             path.file_stem().into_iter()
                         } else {
                             path.file_name().into_iter()
                         })
                         .map(|val| {
                             let val = val.to_str().expect("Cannot process non-UTF8 path");
                             let val = val.chars().filter(|c| c.is_alphanumeric()).collect::<String>();
                             format!("{}{}", val[..1].to_ascii_uppercase(), val[1..].to_ascii_lowercase())
                         }).collect::<Vec<_>>();

    components.concat()
}

/// Turns a path into a resource name usable by the program.
fn path_to_resource_name<P>(path: P, remove_extensions: bool) -> String where P: AsRef<Path> {
    let path = path.as_ref();

    path.parent()
        .into_iter()
        .flat_map(|p| p.components().map(|component| {
            match component {
                Component::Prefix(_) => unreachable!(),
                Component::RootDir => unreachable!(),
                Component::CurDir => unreachable!(),
                Component::ParentDir => unreachable!(),
                Component::Normal(s) => s.to_str().expect("Cannot process non-UTF8 path"),
            }
        }))
        .chain(if remove_extensions {
            path.file_stem().map(|v| v.to_str().unwrap()).into_iter()
        } else {
            path.file_name().map(|v| v.to_str().unwrap()).into_iter()
        })
        .collect::<Vec<_>>()
        .connect("/")
}

pub fn relative_from<'a, P: ?Sized + AsRef<Path>>(path: &'a Path, base: &'a P) -> Option<&'a Path>
{
    fn iter_after<A, I, J>(mut iter: I, mut prefix: J) -> Option<I> where
        I: Iterator<Item=A> + Clone, J: Iterator<Item=A>, A: PartialEq
    {
        loop {
            let mut iter_next = iter.clone();
            match (iter_next.next(), prefix.next()) {
                (Some(x), Some(y)) => {
                    if x != y { return None }
                }
                (Some(_), None) => return Some(iter),
                (None, None) => return Some(iter),
                (None, Some(_)) => return None,
            }
            iter = iter_next;
        }
    }

    iter_after(path.components(), base.as_ref().components()).map(|c| c.as_path())
}