ocl_include/source/
fs.rs

1use std::{
2    cell::Cell,
3    collections::hash_map::{Entry, HashMap},
4    fs, io,
5    path::{Path, PathBuf},
6};
7
8use super::Source;
9
10/// Source for reading files from filesystem.
11pub struct Fs {
12    inc_dirs: Vec<PathBuf>,
13    cache: Cell<Option<HashMap<PathBuf, String>>>,
14}
15
16impl Default for Fs {
17    fn default() -> Self {
18        Fs {
19            inc_dirs: Vec::new(),
20            cache: Cell::new(Some(HashMap::new())),
21        }
22    }
23}
24
25impl Fs {
26    pub fn new() -> Self {
27        Self::default()
28    }
29
30    pub fn builder() -> FsBuilder {
31        FsBuilder {
32            source: Self::new(),
33        }
34    }
35
36    pub fn include_dir(&mut self, dir: &Path) -> io::Result<()> {
37        self.check_dir(dir)?;
38        self.inc_dirs.push(dir.to_path_buf());
39        Ok(())
40    }
41
42    fn check_dir(&self, dir: &Path) -> io::Result<()> {
43        let meta = fs::metadata(dir)?;
44        if !meta.is_dir() {
45            Err(io::Error::new(
46                io::ErrorKind::InvalidData,
47                format!("{:?} is not a directory", dir),
48            ))
49        } else {
50            Ok(())
51        }
52    }
53
54    fn check_file(&self, path: &Path) -> io::Result<()> {
55        let map = self.cache.take().unwrap();
56        let contains = map.contains_key(path);
57        self.cache.set(Some(map));
58        if contains {
59            return Ok(());
60        }
61
62        match fs::metadata(path) {
63            Ok(meta) => {
64                if !meta.is_file() {
65                    Err(io::Error::new(
66                        io::ErrorKind::InvalidData,
67                        format!("{:?} is not a file", path),
68                    ))
69                } else {
70                    Ok(())
71                }
72            }
73            Err(e) => Err(e),
74        }
75    }
76
77    fn find_in_dir(&self, dir: &Path, name: &Path) -> io::Result<Option<PathBuf>> {
78        let path = dir.join(name);
79        match self.check_file(&path) {
80            Ok(()) => Ok(Some(path)),
81            Err(e) => match e.kind() {
82                io::ErrorKind::NotFound => Ok(None),
83                _ => Err(e),
84            },
85        }
86    }
87
88    fn find_file(&self, dir: Option<&Path>, name: &Path) -> io::Result<PathBuf> {
89        if name.is_absolute() {
90            return Ok(name.to_path_buf());
91        }
92
93        if let Some(dir) = dir {
94            if let Some(path) = self.find_in_dir(dir, name)? {
95                return Ok(path);
96            }
97        }
98
99        for dir in self.inc_dirs.iter() {
100            if let Some(path) = self.find_in_dir(dir, name)? {
101                return Ok(path);
102            }
103        }
104
105        Err(io::Error::new(
106            io::ErrorKind::NotFound,
107            name.to_string_lossy(),
108        ))
109    }
110}
111
112pub struct FsBuilder {
113    source: Fs,
114}
115
116impl FsBuilder {
117    pub fn include_dir<P: AsRef<Path>>(mut self, dir: P) -> io::Result<Self> {
118        self.source.include_dir(dir.as_ref()).map(|()| self)
119    }
120
121    pub fn build(self) -> Fs {
122        self.source
123    }
124}
125
126impl Source for Fs {
127    fn read(&self, path: &Path, dir: Option<&Path>) -> io::Result<(PathBuf, String)> {
128        self.find_file(dir, path).and_then(|path| {
129            let mut map = self.cache.take().unwrap();
130
131            let res = match map.entry(path.clone()) {
132                Entry::Occupied(v) => Ok(v.get().clone()),
133                Entry::Vacant(v) => fs::read_to_string(&path).map(|data| {
134                    v.insert(data.clone());
135                    data
136                }),
137            }
138            .map(|data| (path, data));
139
140            self.cache.set(Some(map));
141            res
142        })
143    }
144}