1use std::io::{Read, Write};
23
24use std::io;
25use std::fs;
26use std::ffi::OsString;
27use std::collections::{HashMap};
28use std::collections::hash_map;
29use std::path::{Path};
30
31#[derive(Debug, PartialEq)]
33pub struct Dir {
34 items: HashMap<OsString, Entry>,
35}
36
37#[derive(Debug, PartialEq)]
39pub struct File {
40 bytes: Vec<u8>,
41}
42
43#[derive(Debug, PartialEq)]
45pub enum Entry {
46 File(File),
47 Dir(Dir),
48}
49
50impl Entry {
51 pub fn dump(&self, path: &Path) -> io::Result<()> {
52 match *self {
53 Entry::File(ref f) => f.dump(path),
54 Entry::Dir(ref d) => d.dump(path),
55 }
56 }
57}
58
59
60impl File {
61 pub fn new(bytes: Vec<u8>) -> File {
62 File {
63 bytes: bytes,
64 }
65 }
66
67 pub fn load(path: &Path) -> io::Result<File> {
68 let mut f = fs::File::open(path)?;
70 let mut bytes: Vec<u8> = Vec::new();
71 f.read_to_end(&mut bytes)?;
72 Ok(File::new(bytes))
73 }
74
75 pub fn bytes(&self) -> &[u8] {
76 &self.bytes
77 }
78
79 pub fn dump(&self, path: &Path) -> io::Result<()> {
80 let mut f = fs::File::create(path)?;
81 f.write_all(&self.bytes)
82 }
83
84}
85
86impl Dir {
87 pub fn new() -> Dir {
88 Dir {
89 items: HashMap::new(),
90 }
91 }
92
93 pub fn load(path: &Path) -> io::Result<Dir> {
94 let mut dir = Dir::new();
96 let read_dir = fs::read_dir(path)?;
97 for e in read_dir {
98 let entry = e?;
99 let ftype = entry.file_type()?;
100 if ftype.is_dir() {
101 let subdir = Dir::load(&entry.path())?;
102 dir.items.insert(entry.file_name(), Entry::Dir(subdir));
103 } else if ftype.is_file() {
104 let file = File::load(&entry.path())?;
105 dir.items.insert(entry.file_name(), Entry::File(file));
106 } else {
107 return Err(io::ErrorKind::Other.into());
108 }
109 }
110 Ok(dir)
111 }
112
113 pub fn dump(&self, path: &Path) -> io::Result<()> {
114 fs::create_dir(path)?;
115 for (name, entry) in self.items.iter() {
116 let epath = path.join(name);
117 entry.dump(&epath)?;
118 }
119 Ok(())
120 }
121
122 pub fn add_file(&mut self, name: OsString, file: File) -> io::Result<()> {
123 if self.items.contains_key(&name) {
124 return Err(io::ErrorKind::AlreadyExists.into());
125 }
126 self.items.insert(name, Entry::File(file));
127 Ok(())
128 }
129
130 pub fn add_dir(&mut self, name: OsString, dir: Dir) -> io::Result<()> {
131 if self.items.contains_key(&name) {
132 return Err(io::ErrorKind::AlreadyExists.into());
133 }
134 self.items.insert(name, Entry::Dir(dir));
135 Ok(())
136 }
137
138 pub fn entries(&self) -> hash_map::Iter<OsString, Entry> {
139 self.items.iter()
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use std::env;
147 use std::fs;
148 use std::str;
149 use std::path::PathBuf;
150
151 fn validate_dir(level: usize, dir: &Dir) -> Result<(), String> {
152 for (n, entry) in dir.entries() {
153 let name = n.to_str().unwrap();
154 let (name_prefix, last_c) = name.split_at(name.len()-1);
155 last_c.parse::<usize>().expect(&format!("last_c: {}, invalid name: {}",
156 last_c, name));
157 match *entry {
158 Entry::File(ref file) => {
159 if name_prefix != format!("file{}-", level) {
160 return Err(format!("lvl {} file {} has invalid name", level, name));
161 }
162 let data = str::from_utf8(file.bytes()).unwrap();
163 if data.trim() != name {
164 return Err(format!("lvl {} file {} has invalid data: \"{}\"",
165 level, name, data));
166 }
167 }
168 Entry::Dir(ref dir) => {
169 if name_prefix != format!("dir{}-", level) {
170 return Err(format!("invalid dir name at level {}: {}", level, name));
171 }
172 validate_dir(level + 1, dir)?;
174 }
175 }
176 }
177 Ok(())
178 }
179
180 fn count_entries(dir: &Dir) -> usize {
181 let mut count = 0;
182 for (_, entry) in dir.entries() {
183 count += 1;
184 if let &Entry::Dir(ref d) = entry {
185 count += count_entries(d);
186 }
187 }
188 count
189 }
190
191 #[test]
192 fn test() {
195 let cwd = env::current_dir().unwrap();
196 let test_dir: PathBuf = cwd.join(PathBuf::from(
197 file!()).parent().unwrap().to_path_buf());
198 let data_dir: PathBuf = test_dir.join(PathBuf::from("data"));
199
200 println!("loading data dir: {}", data_dir.display());
201 let dir = Dir::load(&data_dir).unwrap();
202 println!("validating data dir: {}", data_dir.display());
203 assert_eq!(count_entries(&dir), 10);
204 validate_dir(0, &dir).expect("data dir");
205 let tmp: PathBuf = test_dir.join(PathBuf::from("test_out_dir"));
206
207 println!("dumping to: {}", tmp.display());
208 dir.dump(&tmp).expect("couldn't dump dir");
209 let result = Dir::load(&tmp).unwrap();
210 assert_eq!(result, dir);
211 fs::remove_dir_all(tmp).expect("couldn't remove");
212 }
213}