1use crate::*;
2use pad::{Alignment, PadStr};
3
4#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
6pub trait AutoDeriveFromEnvironment: FromEnvironment {}
7
8#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
9impl<P: AutoDeriveFromEnvironment> AutoDeriveFromEnvironment for Option<P> {}
10
11#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
12#[allow(missing_debug_implementations)]
14pub struct SalakDescContext<'a> {
15 pub(crate) key: &'a mut Key<'a>,
16 pub(crate) descs: &'a mut Vec<KeyDesc>,
17 pub(crate) current: KeyDesc,
18}
19
20#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
21pub trait DescFromEnvironment: FromEnvironment {
23 fn key_desc(env: &mut SalakDescContext<'_>);
26}
27
28#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
29pub trait PrefixedFromEnvironment: DescFromEnvironment {
32 fn prefix() -> &'static str;
34}
35
36#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
37impl<P: PrefixedFromEnvironment> PrefixedFromEnvironment for Option<P> {
38 #[inline]
39 fn prefix() -> &'static str {
40 P::prefix()
41 }
42}
43#[derive(Debug)]
45#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
46pub(crate) struct KeyDesc {
47 key: String,
48 tp: &'static str,
49 pub(crate) required: Option<bool>,
50 def: Option<String>,
51 pub(crate) desc: Option<String>,
52 pub(crate) ignore: bool,
53}
54
55pub(crate) struct KeyDescs(pub(crate) Vec<KeyDesc>);
56
57#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
58impl std::fmt::Display for KeyDescs {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 let mut l1 = 3;
61 let mut l2 = 8;
62 let mut l3 = 7;
63 let mut l4 = 11;
64 for desc in self.0.iter() {
66 l1 = l1.max(desc.key.len());
67 l2 = l2.max(desc.required.map(|_| 5).unwrap_or(0));
69 l3 = l3.max(desc.def.as_ref().map(|def| def.len()).unwrap_or(0));
70 l4 = l4.max(desc.desc.as_ref().map(|d| d.len()).unwrap_or(0));
71 }
72
73 f.write_fmt(format_args!(
74 " {} | {} | {} | {} \n",
75 "Key".pad_to_width_with_alignment(l1, Alignment::Middle),
76 "Required".pad_to_width_with_alignment(l2, Alignment::Middle),
78 "Default".pad_to_width_with_alignment(l3, Alignment::Middle),
79 "Description".pad_to_width_with_alignment(l4, Alignment::Middle)
80 ))?;
81 f.write_fmt(format_args!(
82 "{}+{}+{}+{}\n",
83 "-".repeat(l1 + 2),
84 "-".repeat(l2 + 2),
86 "-".repeat(l3 + 2),
87 "-".repeat(l4 + 2),
88 ))?;
89
90 for desc in self.0.iter() {
91 f.write_fmt(format_args!(
92 " {} | {} | {} | {} \n",
93 desc.key.pad_to_width_with_alignment(l1, Alignment::Left),
94 desc.required
96 .unwrap_or(true)
97 .to_string()
98 .pad_to_width_with_alignment(l2, Alignment::Middle),
99 desc.def
100 .as_ref()
101 .map(|f| f.as_ref())
102 .unwrap_or("")
103 .pad_to_width_with_alignment(l3, Alignment::Left),
104 desc.desc
105 .as_ref()
106 .map(|f| f.as_ref())
107 .unwrap_or("")
108 .pad_to_width_with_alignment(l4, Alignment::Left)
109 ))?;
110 }
111 Ok(())
112 }
113}
114
115#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
116impl KeyDesc {
117 pub(crate) fn new(
118 key: String,
119 tp: &'static str,
120 required: Option<bool>,
121 def: Option<&str>,
122 desc: Option<String>,
123 ) -> Self {
124 Self {
125 key,
126 tp,
127 required,
128 def: def.map(|c| c.to_string()),
129 desc,
130 ignore: true,
131 }
132 }
133
134 pub(crate) fn set_required(&mut self, required: bool) {
135 if self.required.is_none() {
136 self.required = Some(required);
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143
144 use std::collections::HashMap;
145
146 use lazy_static::__Deref;
147
148 use crate::wrapper::NonEmptyVec;
149 use crate::*;
150
151 #[derive(FromEnvironment, Debug)]
152 #[salak(prefix = "salak")]
153 struct Config {
154 #[salak(default = "world")]
155 hello: String,
156 world: Option<String>,
157 #[salak(name = "hello")]
158 hey: Option<String>,
159 #[salak(default = 123)]
160 num: u8,
161 arr: Vec<u8>,
162 #[salak(desc = "must at least have one")]
163 brr: NonEmptyVec<u8>,
164 #[salak(desc = "map desc")]
165 map: HashMap<String, u8>,
166 }
167
168 #[test]
169 fn config_test() {
170 let env = Salak::builder().set("salak.brr[0]", "1").build().unwrap();
171
172 let config = env.get::<Config>().unwrap();
173
174 assert_eq!("world", config.hello);
175 assert_eq!(None, config.world);
176 assert_eq!(None, config.hey);
177 assert_eq!(123, config.num);
178 let arr: Vec<u8> = vec![];
179 assert_eq!(arr, config.arr);
180 assert_eq!(&vec![1], config.brr.deref());
181
182 println!("{:?}", config);
183
184 for desc in env.get_desc::<Config>("") {
185 println!("{:?}", desc);
186 }
187 }
188
189 #[derive(FromEnvironment, Debug)]
190 enum Value {
191 Hello,
192 World,
193 }
194
195 #[test]
196 fn enum_test() {
197 let env = Salak::builder().set("hello", "world").build().unwrap();
198 println!("{:?}", env.require::<Value>("hello"))
199 }
200
201 #[test]
202 fn derive_fail_test() {
203 let t = trybuild::TestCases::new();
204 t.compile_fail("tests/fail/*.rs");
205 }
206}