1 use std::env;
2 use std::error::Error;
3 use std::fs;
4 use std::io;
5 use std::path::PathBuf;
6 
7 use crate::file::{
8     format::ALL_EXTENSIONS, source::FileSourceResult, FileSource, FileStoredFormat, Format,
9 };
10 
11 /// Describes a file sourced from a file
12 #[derive(Clone, Debug)]
13 pub struct FileSourceFile {
14     /// Path of configuration file
15     name: PathBuf,
16 }
17 
18 impl FileSourceFile {
new(name: PathBuf) -> Self19     pub fn new(name: PathBuf) -> Self {
20         Self { name }
21     }
22 
find_file<F>( &self, format_hint: Option<F>, ) -> Result<(PathBuf, Box<dyn Format>), Box<dyn Error + Send + Sync>> where F: FileStoredFormat + Format + 'static,23     fn find_file<F>(
24         &self,
25         format_hint: Option<F>,
26     ) -> Result<(PathBuf, Box<dyn Format>), Box<dyn Error + Send + Sync>>
27     where
28         F: FileStoredFormat + Format + 'static,
29     {
30         let filename = if self.name.is_absolute() {
31             self.name.clone()
32         } else {
33             env::current_dir()?.as_path().join(&self.name)
34         };
35 
36         // First check for an _exact_ match
37         if filename.is_file() {
38             return if let Some(format) = format_hint {
39                 Ok((filename, Box::new(format)))
40             } else {
41                 for (format, extensions) in ALL_EXTENSIONS.iter() {
42                     if extensions.contains(
43                         &filename
44                             .extension()
45                             .unwrap_or_default()
46                             .to_string_lossy()
47                             .as_ref(),
48                     ) {
49                         return Ok((filename, Box::new(*format)));
50                     }
51                 }
52 
53                 Err(Box::new(io::Error::new(
54                     io::ErrorKind::NotFound,
55                     format!(
56                         "configuration file \"{}\" is not of a registered file format",
57                         filename.to_string_lossy()
58                     ),
59                 )))
60             };
61         }
62         // Adding a dummy extension will make sure we will not override secondary extensions, i.e. "file.local"
63         // This will make the following set_extension function calls to append the extension.
64         let mut filename = add_dummy_extension(filename);
65 
66         match format_hint {
67             Some(format) => {
68                 for ext in format.file_extensions() {
69                     filename.set_extension(ext);
70 
71                     if filename.is_file() {
72                         return Ok((filename, Box::new(format)));
73                     }
74                 }
75             }
76 
77             None => {
78                 for format in ALL_EXTENSIONS.keys() {
79                     for ext in format.extensions() {
80                         filename.set_extension(ext);
81 
82                         if filename.is_file() {
83                             return Ok((filename, Box::new(*format)));
84                         }
85                     }
86                 }
87             }
88         }
89 
90         Err(Box::new(io::Error::new(
91             io::ErrorKind::NotFound,
92             format!(
93                 "configuration file \"{}\" not found",
94                 self.name.to_string_lossy()
95             ),
96         )))
97     }
98 }
99 
100 impl<F> FileSource<F> for FileSourceFile
101 where
102     F: Format + FileStoredFormat + 'static,
103 {
resolve( &self, format_hint: Option<F>, ) -> Result<FileSourceResult, Box<dyn Error + Send + Sync>>104     fn resolve(
105         &self,
106         format_hint: Option<F>,
107     ) -> Result<FileSourceResult, Box<dyn Error + Send + Sync>> {
108         // Find file
109         let (filename, format) = self.find_file(format_hint)?;
110 
111         // Attempt to use a relative path for the URI
112         let uri = env::current_dir()
113             .ok()
114             .and_then(|base| pathdiff::diff_paths(&filename, base))
115             .unwrap_or_else(|| filename.clone());
116 
117         // Read contents from file
118         let text = fs::read_to_string(filename)?;
119 
120         Ok(FileSourceResult {
121             uri: Some(uri.to_string_lossy().into_owned()),
122             content: text,
123             format,
124         })
125     }
126 }
127 
add_dummy_extension(mut filename: PathBuf) -> PathBuf128 fn add_dummy_extension(mut filename: PathBuf) -> PathBuf {
129     match filename.extension() {
130         Some(extension) => {
131             let mut ext = extension.to_os_string();
132             ext.push(".");
133             ext.push("dummy");
134             filename.set_extension(ext);
135         }
136         None => {
137             filename.set_extension("dummy");
138         }
139     }
140     filename
141 }
142