dir_obj/
lib.rs

1/*  dir-obj-rs: the requirements tracking tool made for developers
2    Copyright (C) 2017  Garrett Berg <@vitiral, vitiral@gmail.com>
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the Lesser GNU General Public License as published 
6    by the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the Lesser GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16*/
17//! Simple package to mimick a directory structure in ram
18//! Provides bindings (through feature flags) for conversion to/from
19//! various libraries
20
21// traits
22use 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/// representation of a directory
32#[derive(Debug, PartialEq)]
33pub struct Dir {
34    items: HashMap<OsString, Entry>,
35}
36
37/// representation of a file
38#[derive(Debug, PartialEq)]
39pub struct File {
40    bytes: Vec<u8>,
41}
42
43/// possible entries in a directory
44#[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        //println!("loading file: {}", path.display());
69        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        //println!("loading dir : {}", path.display());
95        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                    //println!("validating dir name: {}", name);
173                    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    /// really basic test to just load a directory, validate it, 
193    /// save it to a new folder and reload + validate it
194    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}