config_maint/file/source/
file.rs

1use std::error::Error;
2use std::result;
3use std::str::FromStr;
4
5use file::format::ALL_EXTENSIONS;
6use std::env;
7use std::fs;
8use std::io::{self, Read};
9use std::iter::Iterator;
10use std::path::{Path, PathBuf};
11
12use super::{FileFormat, FileSource};
13use source::Source;
14
15/// Describes a file sourced from a file
16#[derive(Clone, Debug)]
17pub struct FileSourceFile {
18    /// Path of configuration file
19    name: PathBuf,
20}
21
22impl FileSourceFile {
23    pub fn new(name: PathBuf) -> FileSourceFile {
24        FileSourceFile { name }
25    }
26
27    fn find_file(
28        &self,
29        format_hint: Option<FileFormat>,
30    ) -> Result<(PathBuf, FileFormat), Box<dyn Error + Send + Sync>> {
31        // First check for an _exact_ match
32        let mut filename = env::current_dir()?.as_path().join(self.name.clone());
33        if filename.is_file() {
34            return match format_hint {
35                Some(format) => Ok((filename, format)),
36                None => {
37                    for (format, extensions) in ALL_EXTENSIONS.iter() {
38                        if extensions.contains(
39                            &filename
40                                .extension()
41                                .unwrap_or_default()
42                                .to_string_lossy()
43                                .as_ref(),
44                        ) {
45                            return Ok((filename, *format));
46                        }
47                    }
48
49                    Err(Box::new(io::Error::new(
50                        io::ErrorKind::NotFound,
51                        format!(
52                            "configuration file \"{}\" is not of a registered file format",
53                            filename.to_string_lossy()
54                        ),
55                    )))
56                }
57            };
58        }
59
60        match format_hint {
61            Some(format) => {
62                for ext in format.extensions() {
63                    filename.set_extension(ext);
64
65                    if filename.is_file() {
66                        return Ok((filename, format));
67                    }
68                }
69            }
70
71            None => {
72                for (format, extensions) in ALL_EXTENSIONS.iter() {
73                    for ext in format.extensions() {
74                        filename.set_extension(ext);
75
76                        if filename.is_file() {
77                            return Ok((filename, *format));
78                        }
79                    }
80                }
81            }
82        }
83
84        Err(Box::new(io::Error::new(
85            io::ErrorKind::NotFound,
86            format!(
87                "configuration file \"{}\" not found",
88                self.name.to_string_lossy()
89            ),
90        )))
91    }
92}
93
94impl FileSource for FileSourceFile {
95    fn resolve(
96        &self,
97        format_hint: Option<FileFormat>,
98    ) -> Result<(Option<String>, String, FileFormat), Box<dyn Error + Send + Sync>> {
99        // Find file
100        let (filename, format) = self.find_file(format_hint)?;
101
102        // Attempt to use a relative path for the URI
103        let base = env::current_dir()?;
104        let uri = match path_relative_from(&filename, &base) {
105            Some(value) => value,
106            None => filename.clone(),
107        };
108
109        // Read contents from file
110        let mut file = fs::File::open(filename)?;
111        let mut text = String::new();
112        file.read_to_string(&mut text)?;
113
114        Ok((Some(uri.to_string_lossy().into_owned()), text, format))
115    }
116}
117
118// TODO: This should probably be a crate
119// https://github.com/rust-lang/rust/blob/master/src/librustc_trans/back/rpath.rs#L128
120fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
121    use std::path::Component;
122
123    if path.is_absolute() != base.is_absolute() {
124        if path.is_absolute() {
125            Some(PathBuf::from(path))
126        } else {
127            None
128        }
129    } else {
130        let mut ita = path.components();
131        let mut itb = base.components();
132        let mut comps: Vec<Component> = vec![];
133        loop {
134            match (ita.next(), itb.next()) {
135                (None, None) => break,
136                (Some(a), None) => {
137                    comps.push(a);
138                    comps.extend(ita.by_ref());
139                    break;
140                }
141                (None, _) => comps.push(Component::ParentDir),
142                (Some(a), Some(b)) if comps.is_empty() && a == b => (),
143                (Some(a), Some(b)) if b == Component::CurDir => comps.push(a),
144                (Some(_), Some(b)) if b == Component::ParentDir => return None,
145                (Some(a), Some(_)) => {
146                    comps.push(Component::ParentDir);
147                    for _ in itb {
148                        comps.push(Component::ParentDir);
149                    }
150                    comps.push(a);
151                    comps.extend(ita.by_ref());
152                    break;
153                }
154            }
155        }
156        Some(comps.iter().map(|c| c.as_os_str()).collect())
157    }
158}