use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use serde::de::{self, DeserializeOwned};
use crate::value::{Map, Dict};
use crate::{Error, Profile, Provider, Metadata};
#[derive(Debug, Clone)]
enum Source {
File(Option<PathBuf>),
String(String)
}
#[derive(Debug, Clone)]
pub struct Data<F: Format> {
source: Source,
pub profile: Option<Profile>,
_format: PhantomData<F>,
}
impl<F: Format> Data<F> {
fn new(source: Source, profile: Option<Profile>) -> Self {
Data { source, profile, _format: PhantomData }
}
pub fn file<P: AsRef<Path>>(path: P) -> Self {
fn find(path: &Path) -> Option<PathBuf> {
if path.is_absolute() {
match path.is_file() {
true => return Some(path.to_path_buf()),
false => return None
}
}
let cwd = std::env::current_dir().ok()?;
let mut cwd = cwd.as_path();
loop {
let file_path = cwd.join(path);
if file_path.is_file() {
return Some(file_path);
}
cwd = cwd.parent()?;
}
}
Data::new(Source::File(find(path.as_ref())), Some(Profile::Default))
}
pub fn file_exact<P: AsRef<Path>>(path: P) -> Self {
Data::new(Source::File(Some(path.as_ref().to_owned())), Some(Profile::Default))
}
pub fn string(string: &str) -> Self {
Data::new(Source::String(string.into()), Some(Profile::Default))
}
pub fn nested(mut self) -> Self {
self.profile = None;
self
}
pub fn profile<P: Into<Profile>>(mut self, profile: P) -> Self {
self.profile = Some(profile.into());
self
}
}
impl<F: Format> Provider for Data<F> {
fn metadata(&self) -> Metadata {
use Source::*;
match &self.source {
String(_) => Metadata::named(format!("{} source string", F::NAME)),
File(None) => Metadata::named(format!("{} file", F::NAME)),
File(Some(p)) => Metadata::from(format!("{} file", F::NAME), &**p)
}
}
fn data(&self) -> Result<Map<Profile, Dict>, Error> {
use Source::*;
let map: Result<Map<Profile, Dict>, _> = match (&self.source, &self.profile) {
(File(None), _) => return Ok(Map::new()),
(File(Some(path)), None) => F::from_path(&path),
(String(s), None) => F::from_str(&s),
(File(Some(path)), Some(prof)) => F::from_path(&path).map(|v| prof.collect(v)),
(String(s), Some(prof)) => F::from_str(&s).map(|v| prof.collect(v)),
};
Ok(map.map_err(|e| e.to_string())?)
}
}
pub trait Format: Sized {
type Error: de::Error;
const NAME: &'static str;
fn file<P: AsRef<Path>>(path: P) -> Data<Self> {
Data::file(path)
}
fn file_exact<P: AsRef<Path>>(path: P) -> Data<Self> {
Data::file_exact(path)
}
fn string(string: &str) -> Data<Self> {
Data::string(string)
}
fn from_str<'de, T: DeserializeOwned>(string: &'de str) -> Result<T, Self::Error>;
fn from_path<T: DeserializeOwned>(path: &Path) -> Result<T, Self::Error> {
let source = std::fs::read_to_string(path).map_err(de::Error::custom)?;
Self::from_str(&source)
}
}
#[allow(unused_macros)]
macro_rules! impl_format {
($name:ident $NAME:literal/$string:literal: $func:expr, $E:ty, $doc:expr) => (
#[cfg(feature = $string)]
#[cfg_attr(nightly, doc(cfg(feature = $string)))]
#[doc = $doc]
pub struct $name;
#[cfg(feature = $string)]
impl Format for $name {
type Error = $E;
const NAME: &'static str = $NAME;
fn from_str<'de, T: DeserializeOwned>(s: &'de str) -> Result<T, $E> {
$func(s)
}
}
);
($name:ident $NAME:literal/$string:literal: $func:expr, $E:ty) => (
impl_format!($name $NAME/$string: $func, $E, concat!(
"A ", $NAME, " [`Format`] [`Data`] provider.",
"\n\n",
"Static constructor methods on `", stringify!($name), "` return a
[`Data`] value with a generic marker of [`", stringify!($name), "`].
Thus, further use occurs via methods on [`Data`].",
"\n```\n",
"use figment::providers::{Format, ", stringify!($name), "};",
"\n\n// Source directly from a source string...",
"\nlet provider = ", stringify!($name), r#"::string("source-string");"#,
"\n\n// Or read from a file on disk.",
"\nlet provider = ", stringify!($name), r#"::file("path-to-file");"#,
"\n\n// Or configured as nested (via Data::nested()):",
"\nlet provider = ", stringify!($name), r#"::file("path-to-file").nested();"#,
"\n```",
"\n\nSee also [`", stringify!($func), "`] for parsing details."
));
)
}
#[cfg(feature = "yaml")]
#[cfg_attr(nightly, doc(cfg(feature = "yaml")))]
impl YamlExtended {
pub fn from_str<'de, T: DeserializeOwned>(s: &'de str) -> serde_yaml::Result<T> {
let mut value: serde_yaml::Value = serde_yaml::from_str(s)?;
value.apply_merge()?;
T::deserialize(value)
}
}
impl_format!(Toml "TOML"/"toml": toml::from_str, toml::de::Error);
impl_format!(Yaml "YAML"/"yaml": serde_yaml::from_str, serde_yaml::Error);
impl_format!(Json "JSON"/"json": serde_json::from_str, serde_json::error::Error);
impl_format!(YamlExtended "YAML Extended"/"yaml": YamlExtended::from_str, serde_yaml::Error);