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