salak/
derive.rs

1use crate::*;
2use pad::{Alignment, PadStr};
3
4/// This trait is automatically derived.
5#[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/// Context for implementing description.
13#[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")))]
21/// Generate description for this object.
22pub trait DescFromEnvironment: FromEnvironment {
23    /// Generate key description from [`SalakDescContext`].
24    /// * `env` - Describable context.
25    fn key_desc(env: &mut SalakDescContext<'_>);
26}
27
28#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
29/// [`FromEnvironment`] with a configuration prefix, which is required by [`Environment::get()`].
30/// Attribute `#[salak(prefix = "salak.app")]` will implement this trait.
31pub trait PrefixedFromEnvironment: DescFromEnvironment {
32    /// Set configuration prefix.
33    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/// Key Description
44#[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        // let mut l5 = 0;
65        for desc in self.0.iter() {
66            l1 = l1.max(desc.key.len());
67            // l5 = l5.max(desc.tp.len());
68            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            // "Type".pad_to_width_with_alignment(l5, Alignment::Middle),
77            "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(l5 + 2),
85            "-".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.tp.pad_to_width_with_alignment(l5, Alignment::Middle),
95                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}