config/file/source/
file.rs1use std::env;
2use std::error::Error;
3use std::fs;
4use std::io;
5use std::path::PathBuf;
6
7use crate::file::{source::FileSourceResult, FileFormat, FileSource, FileStoredFormat, Format};
8
9#[derive(Clone, Debug)]
11pub struct FileSourceFile {
12 name: PathBuf,
14}
15
16impl FileSourceFile {
17 pub fn new(name: PathBuf) -> Self {
18 Self { name }
19 }
20
21 fn find_file<F>(
22 &self,
23 format_hint: Option<F>,
24 ) -> Result<(PathBuf, Box<dyn Format>), Box<dyn Error + Send + Sync>>
25 where
26 F: FileStoredFormat + Format + 'static,
27 {
28 let path = if self.name.is_absolute() {
29 self.name.clone()
30 } else {
31 env::current_dir()?.as_path().join(&self.name)
32 };
33
34 if path.is_file() {
36 if let Some(format) = format_hint {
37 return Ok((path, Box::new(format)));
38 } else {
39 let ext = path.extension().unwrap_or_default().to_string_lossy();
40 for format in FileFormat::all() {
41 if format.extensions().contains(&ext.as_ref()) {
42 return Ok((path, Box::new(*format)));
43 }
44 }
45 return Err(Box::new(io::Error::new(
46 io::ErrorKind::NotFound,
47 format!(
48 "configuration file \"{}\" is not of a supported file format",
49 path.to_string_lossy()
50 ),
51 )));
52 };
53 }
54
55 let mut path = path;
56 if path.extension().is_some() {
59 path.as_mut_os_string().push(".placeholder");
60 }
61 match format_hint {
62 Some(format) => {
63 for ext in format.file_extensions() {
64 path.set_extension(ext);
65
66 if path.is_file() {
67 return Ok((path, Box::new(format)));
68 }
69 }
70 }
71 None => {
72 for format in FileFormat::all() {
73 for ext in format.extensions() {
74 path.set_extension(ext);
75
76 if path.is_file() {
77 return Ok((path, Box::new(*format)));
78 }
79 }
80 }
81 }
82 }
83 Err(Box::new(io::Error::new(
84 io::ErrorKind::NotFound,
85 format!(
86 "configuration file \"{}\" not found",
87 self.name.to_string_lossy()
88 ),
89 )))
90 }
91}
92
93impl<F> FileSource<F> for FileSourceFile
94where
95 F: Format + FileStoredFormat + 'static,
96{
97 fn resolve(
98 &self,
99 format_hint: Option<F>,
100 ) -> Result<FileSourceResult, Box<dyn Error + Send + Sync>> {
101 let (filename, format) = self.find_file(format_hint)?;
103
104 let uri = env::current_dir()
106 .ok()
107 .and_then(|base| pathdiff::diff_paths(&filename, base))
108 .unwrap_or_else(|| filename.clone());
109
110 let buf = fs::read(filename)?;
112
113 let buf = if buf.len() >= 3 && &buf[0..3] == b"\xef\xbb\xbf" {
115 &buf[3..]
116 } else {
117 &buf
118 };
119
120 let c = String::from_utf8_lossy(buf);
121 let text = c.into_owned();
122
123 Ok(FileSourceResult {
124 uri: Some(uri.to_string_lossy().into_owned()),
125 content: text,
126 format,
127 })
128 }
129}