//! Resources for subplot. //! //! This module encapsulates a mechanism for subplot to find resource files. //! Resource files come from a number of locations, such as embedded into the //! binary, from template paths given on the CLI, or from paths built into the //! binary and present on disk. use std::io::{self, Cursor, Read}; use std::path::{Path, PathBuf}; use std::sync::Mutex; use structopt::StructOpt; #[derive(Debug, StructOpt)] /// Options which relate to resource management /// /// To use this, include them *flat* in your options struct, and then after /// parsing, call the [ResourceOpts::handle()] function. pub struct ResourceOpts { #[structopt( long, number_of_values = 1, help = "Look for code templates and other resources in DIR", name = "DIR" )] resources: Vec, } impl ResourceOpts { /// Handle any supplied resource related arguments pub fn handle(&self) { for rpath in &self.resources { add_search_path(rpath); } } } use lazy_static::lazy_static; lazy_static! { static ref SEARCH_PATHS: Mutex> = { let ret = Vec::new(); Mutex::new(ret) }; static ref TEMPLATE_NAME: Mutex> = Mutex::new(None); } static EMBEDDED_FILES: &[(&str, &[u8])] = include!(concat!(env!("OUT_DIR"), "/embedded_files.inc")); /// Put a path at the back of the queue to search in pub fn add_search_path>(path: P) { SEARCH_PATHS .lock() .expect("Unable to lock SEARCH_PATHS") .push(path.as_ref().into()); } /// Set the template name, for use in searching for content... pub fn set_template(template: &str) { *TEMPLATE_NAME.lock().expect("Unable to lock TEMPLATE_NAME") = Some(template.to_string()); } /// Open a file for reading, honouring search paths established during /// startup, and falling back to potentially embedded file content pub fn open>(subpath: P) -> io::Result> { let subpath = subpath.as_ref(); match internal_open(subpath) { Ok(r) => Ok(r), Err(e) => { let template = TEMPLATE_NAME.lock().expect("Unable to lock TEMPLATE_NAME"); if let Some(templ) = template.as_deref() { let subpath = Path::new(templ).join(subpath); match internal_open(&subpath) { Ok(r) => Ok(r), Err(sub_e) => { if sub_e.kind() != io::ErrorKind::NotFound && e.kind() == io::ErrorKind::NotFound { Err(sub_e) } else { Err(e) } } } } else { Err(e) } } } } fn internal_fallback_path() -> Option<&'static Path> { match env!("FALLBACK_PATH") { "" => None, s => Some(Path::new(s)), } } fn internal_open(subpath: &Path) -> io::Result> { let search_paths = SEARCH_PATHS.lock().expect("Unable to lock SEARCH_PATHS"); let search_paths = search_paths.iter().map(|p| p.as_path()); let search_paths = std::iter::empty() .chain(Some(Path::new("."))) .chain(search_paths) .chain(internal_fallback_path()); let mut ret = Err(io::Error::new( io::ErrorKind::NotFound, format!("Unable to find {} in resource paths", subpath.display()), )); for basepath in search_paths { let full_path = basepath.join(subpath); ret = std::fs::File::open(full_path); if ret.is_ok() { break; } } match ret { Ok(ret) => Ok(Box::new(ret)), Err(e) => { if let Some(data) = EMBEDDED_FILES.iter().find(|e| Path::new(e.0) == subpath) { Ok(Box::new(Cursor::new(data.1))) } else { Err(e) } } } } /// Read a file, honouring search paths established during startup, and /// falling back to potentially embedded file content pub fn read_as_string>(subpath: P) -> io::Result { let mut f = open(subpath)?; let mut ret = String::with_capacity(8192); f.read_to_string(&mut ret)?; Ok(ret) }