use std::env; use std::error::Error; use std::fs; use std::io; use std::path::PathBuf; use crate::file::{ format::ALL_EXTENSIONS, source::FileSourceResult, FileSource, FileStoredFormat, Format, }; /// Describes a file sourced from a file #[derive(Clone, Debug)] pub struct FileSourceFile { /// Path of configuration file name: PathBuf, } impl FileSourceFile { pub fn new(name: PathBuf) -> Self { Self { name } } fn find_file( &self, format_hint: Option, ) -> Result<(PathBuf, Box), Box> where F: FileStoredFormat + Format + 'static, { let filename = if self.name.is_absolute() { self.name.clone() } else { env::current_dir()?.as_path().join(&self.name) }; // First check for an _exact_ match if filename.is_file() { return if let Some(format) = format_hint { Ok((filename, Box::new(format))) } else { for (format, extensions) in ALL_EXTENSIONS.iter() { if extensions.contains( &filename .extension() .unwrap_or_default() .to_string_lossy() .as_ref(), ) { return Ok((filename, Box::new(*format))); } } Err(Box::new(io::Error::new( io::ErrorKind::NotFound, format!( "configuration file \"{}\" is not of a registered file format", filename.to_string_lossy() ), ))) }; } // Adding a dummy extension will make sure we will not override secondary extensions, i.e. "file.local" // This will make the following set_extension function calls to append the extension. let mut filename = add_dummy_extension(filename); match format_hint { Some(format) => { for ext in format.file_extensions() { filename.set_extension(ext); if filename.is_file() { return Ok((filename, Box::new(format))); } } } None => { for format in ALL_EXTENSIONS.keys() { for ext in format.extensions() { filename.set_extension(ext); if filename.is_file() { return Ok((filename, Box::new(*format))); } } } } } Err(Box::new(io::Error::new( io::ErrorKind::NotFound, format!( "configuration file \"{}\" not found", self.name.to_string_lossy() ), ))) } } impl FileSource for FileSourceFile where F: Format + FileStoredFormat + 'static, { fn resolve( &self, format_hint: Option, ) -> Result> { // Find file let (filename, format) = self.find_file(format_hint)?; // Attempt to use a relative path for the URI let uri = env::current_dir() .ok() .and_then(|base| pathdiff::diff_paths(&filename, base)) .unwrap_or_else(|| filename.clone()); // Read contents from file let text = fs::read_to_string(filename)?; Ok(FileSourceResult { uri: Some(uri.to_string_lossy().into_owned()), content: text, format, }) } } fn add_dummy_extension(mut filename: PathBuf) -> PathBuf { match filename.extension() { Some(extension) => { let mut ext = extension.to_os_string(); ext.push("."); ext.push("dummy"); filename.set_extension(ext); } None => { filename.set_extension("dummy"); } } filename }