use crate::*;
use pad::{Alignment, PadStr};
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub trait AutoDeriveFromEnvironment: FromEnvironment {}
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
impl<P: AutoDeriveFromEnvironment> AutoDeriveFromEnvironment for Option<P> {}
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
#[allow(missing_debug_implementations)]
pub struct SalakDescContext<'a> {
pub(crate) key: &'a mut Key<'a>,
pub(crate) descs: &'a mut Vec<KeyDesc>,
pub(crate) current: KeyDesc,
}
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub trait DescFromEnvironment: FromEnvironment {
fn key_desc(env: &mut SalakDescContext<'_>);
}
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub trait PrefixedFromEnvironment: DescFromEnvironment {
fn prefix() -> &'static str;
}
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
impl<P: PrefixedFromEnvironment> PrefixedFromEnvironment for Option<P> {
#[inline]
fn prefix() -> &'static str {
P::prefix()
}
}
#[derive(Debug)]
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub(crate) struct KeyDesc {
key: String,
tp: &'static str,
pub(crate) required: Option<bool>,
def: Option<String>,
pub(crate) desc: Option<String>,
pub(crate) ignore: bool,
}
pub(crate) struct KeyDescs(pub(crate) Vec<KeyDesc>);
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
impl std::fmt::Display for KeyDescs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut l1 = 3;
let mut l2 = 8;
let mut l3 = 7;
let mut l4 = 11;
for desc in self.0.iter() {
l1 = l1.max(desc.key.len());
l2 = l2.max(desc.required.map(|_| 5).unwrap_or(0));
l3 = l3.max(desc.def.as_ref().map(|def| def.len()).unwrap_or(0));
l4 = l4.max(desc.desc.as_ref().map(|d| d.len()).unwrap_or(0));
}
f.write_fmt(format_args!(
" {} | {} | {} | {} \n",
"Key".pad_to_width_with_alignment(l1, Alignment::Middle),
"Required".pad_to_width_with_alignment(l2, Alignment::Middle),
"Default".pad_to_width_with_alignment(l3, Alignment::Middle),
"Description".pad_to_width_with_alignment(l4, Alignment::Middle)
))?;
f.write_fmt(format_args!(
"{}+{}+{}+{}\n",
"-".repeat(l1 + 2),
"-".repeat(l2 + 2),
"-".repeat(l3 + 2),
"-".repeat(l4 + 2),
))?;
for desc in self.0.iter() {
f.write_fmt(format_args!(
" {} | {} | {} | {} \n",
desc.key.pad_to_width_with_alignment(l1, Alignment::Left),
desc.required
.unwrap_or(true)
.to_string()
.pad_to_width_with_alignment(l2, Alignment::Middle),
desc.def
.as_ref()
.map(|f| f.as_ref())
.unwrap_or("")
.pad_to_width_with_alignment(l3, Alignment::Left),
desc.desc
.as_ref()
.map(|f| f.as_ref())
.unwrap_or("")
.pad_to_width_with_alignment(l4, Alignment::Left)
))?;
}
Ok(())
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
impl KeyDesc {
pub(crate) fn new(
key: String,
tp: &'static str,
required: Option<bool>,
def: Option<&str>,
desc: Option<String>,
) -> Self {
Self {
key,
tp,
required,
def: def.map(|c| c.to_string()),
desc,
ignore: true,
}
}
pub(crate) fn set_required(&mut self, required: bool) {
if self.required.is_none() {
self.required = Some(required);
}
}
}
impl Salak {
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub(crate) fn get_desc<T: PrefixedFromEnvironment + DescFromEnvironment>(
&self,
namespace: &'static str,
) -> Vec<KeyDesc> {
let mut key = Key::new();
let mut key_descs = vec![];
let mut context = SalakDescContext::new(&mut key, &mut key_descs);
if namespace.is_empty() {
context.add_key_desc::<T>(T::prefix(), None, None, None);
} else {
context.add_key_desc::<T>(&format!("{}.{}", T::prefix(), namespace), None, None, None);
};
key_descs
}
}
impl SalakBuilder {
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub fn configure_description<T: PrefixedFromEnvironment + DescFromEnvironment>(self) -> Self {
self.configure_description_by_namespace::<T>("")
}
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
pub fn configure_description_by_namespace<T: PrefixedFromEnvironment + DescFromEnvironment>(
mut self,
namespace: &'static str,
) -> Self {
self.app_desc
.push(Box::new(move |env| env.get_desc::<T>(namespace)));
self
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use lazy_static::__Deref;
use crate::wrapper::NonEmptyVec;
use crate::*;
#[derive(FromEnvironment, Debug)]
#[salak(prefix = "salak")]
struct Config {
#[salak(default = "world")]
hello: String,
world: Option<String>,
#[salak(name = "hello")]
hey: Option<String>,
#[salak(default = 123)]
num: u8,
arr: Vec<u8>,
#[salak(desc = "must at least have one")]
brr: NonEmptyVec<u8>,
#[salak(desc = "map desc")]
map: HashMap<String, u8>,
}
#[test]
fn config_test() {
let env = Salak::builder().set("salak.brr[0]", "1").build().unwrap();
let config = env.get::<Config>().unwrap();
assert_eq!("world", config.hello);
assert_eq!(None, config.world);
assert_eq!(None, config.hey);
assert_eq!(123, config.num);
let arr: Vec<u8> = vec![];
assert_eq!(arr, config.arr);
assert_eq!(&vec![1], config.brr.deref());
println!("{:?}", config);
for desc in env.get_desc::<Config>("") {
println!("{:?}", desc);
}
}
#[derive(FromEnvironment, Debug)]
enum Value {
Hello,
World,
}
#[test]
fn enum_test() {
let env = Salak::builder().set("hello", "world").build().unwrap();
println!("{:?}", env.require::<Value>("hello"))
}
#[test]
fn derive_fail_test() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/fail/*.rs");
}
}