config_maint/file/source/
file.rs1use 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#[derive(Clone, Debug)]
17pub struct FileSourceFile {
18 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 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 let (filename, format) = self.find_file(format_hint)?;
101
102 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 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
118fn 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}