salak/
env.rs

1use parking_lot::Mutex;
2#[cfg(feature = "app")]
3use std::any::Any;
4use std::collections::HashMap;
5
6#[cfg(feature = "args")]
7use crate::AppInfo;
8
9use crate::{
10    raw_ioref::IORefT, source_raw::PropertyRegistryInternal, Environment, FromEnvironment,
11    PropertySource, Res,
12};
13#[cfg(feature = "app")]
14use crate::{Resource, ResourceBuilder, ResourceRegistry};
15
16#[allow(unused_imports)]
17use crate::source_raw::FileConfig;
18#[cfg(feature = "derive")]
19use crate::{DescFromEnvironment, Key, KeyDesc, PrefixedFromEnvironment, SalakDescContext};
20
21/// A builder which can configure for how to build a salak env.
22#[allow(missing_debug_implementations)]
23pub struct SalakBuilder {
24    args: HashMap<String, String>,
25    #[cfg(any(feature = "toml", feature = "yaml"))]
26    disable_file: bool,
27    #[cfg(feature = "rand")]
28    disable_random: bool,
29    registry: PropertyRegistryInternal<'static>,
30    #[cfg(any(feature = "args", feature = "derive"))]
31    pub(crate) app_desc: Vec<Box<dyn Fn(&mut Salak) -> Vec<KeyDesc>>>,
32    #[cfg(feature = "args")]
33    app_info: Option<AppInfo<'static>>,
34    iorefs: Mutex<Vec<Box<dyn IORefT + Send>>>,
35    #[cfg(feature = "app")]
36    resource: ResourceRegistry,
37}
38
39#[allow(dead_code)]
40pub(crate) const PREFIX: &str = "salak.app";
41
42impl SalakBuilder {
43    /// Set custom arguments properties.
44    #[inline]
45    pub fn set_args(mut self, args: HashMap<String, String>) -> Self {
46        self.args.extend(args);
47        self
48    }
49
50    /// Set custom property.
51    pub fn set<K: Into<String>, V: Into<String>>(mut self, k: K, v: V) -> Self {
52        self.args.insert(k.into(), v.into());
53        self
54    }
55
56    #[cfg(any(feature = "toml", feature = "yaml"))]
57    #[cfg_attr(docsrs, doc(cfg(any(feature = "toml", feature = "yaml"))))]
58    /// Configure file source.
59    pub fn configure_files(mut self, enabled: bool) -> Self {
60        self.disable_file = !enabled;
61        self
62    }
63
64    #[cfg(feature = "rand")]
65    #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
66    /// Configure random source.
67    pub fn configure_random(mut self, enabled: bool) -> Self {
68        self.disable_random = !enabled;
69        self
70    }
71
72    #[cfg(feature = "args")]
73    #[cfg_attr(docsrs, doc(cfg(feature = "args")))]
74    /// Configure predefined arguments.
75    pub fn configure_args(mut self, info: AppInfo<'static>) -> Self {
76        self.app_info = Some(info);
77        self
78    }
79
80    /// Build salak.
81    #[allow(unused_mut)]
82    pub fn build(mut self) -> Res<Salak> {
83        #[cfg(feature = "derive")]
84        let mut _desc: Vec<KeyDesc> = vec![];
85        #[cfg(feature = "derive")]
86        #[cfg(any(feature = "toml", feature = "yaml"))]
87        {
88            self.app_desc
89                .insert(0, Box::new(|env| env.get_desc::<FileConfig>("")));
90        }
91        let mut env = self.registry;
92
93        #[cfg(feature = "rand")]
94        if !self.disable_random {
95            env.register_by_ref(Box::new(crate::source_rand::Random));
96        }
97        let mut salak = Salak {
98            reg: env,
99            ior: self.iorefs,
100            #[cfg(feature = "app")]
101            res: self.resource,
102        };
103
104        #[cfg(feature = "args")]
105        if let Some(app) = self.app_info {
106            self.args
107                .insert(format!("{}.name", PREFIX), app.name.into());
108            self.args
109                .insert(format!("{}.version", PREFIX), app.version.into());
110
111            #[cfg(feature = "derive")]
112            {
113                _desc.push(KeyDesc::new(
114                    format!("{}.name", PREFIX),
115                    "String",
116                    Some(false),
117                    Some(app.name),
118                    None,
119                ));
120                _desc.push(KeyDesc::new(
121                    format!("{}.version", PREFIX),
122                    "String",
123                    Some(false),
124                    Some(app.version),
125                    None,
126                ));
127
128                for x in self.app_desc {
129                    _desc.extend((x)(&mut salak));
130                }
131            }
132
133            self.args.extend(crate::source::from_args(_desc, app)?);
134        }
135
136        salak.reg = salak
137            .reg
138            .register(crate::source::HashMapSource::new("Arguments").set_all(self.args))
139            .register(crate::source::system_environment());
140
141        #[cfg(any(feature = "toml", feature = "yaml"))]
142        if !self.disable_file {
143            let mut fc = FileConfig::new(&salak.reg, &salak.ior)?;
144            #[cfg(feature = "toml")]
145            {
146                fc.build("toml", crate::source_toml::Toml::new)?;
147            }
148            #[cfg(feature = "yaml")]
149            {
150                fc.build("yaml", crate::source_yaml::YamlValue::new)?;
151            }
152            fc.register_to_env(&mut salak.reg);
153        }
154
155        #[cfg(feature = "app")]
156        salak.res.initialize(&salak)?;
157        Ok(salak)
158    }
159
160    #[inline]
161    #[cfg(feature = "app")]
162    #[cfg_attr(docsrs, doc(cfg(feature = "app")))]
163    /// Register [`Resource`] with default builder.
164    pub fn register_default_resource<R: Resource + Send + Sync + Any>(self) -> Res<Self> {
165        self.register_resource::<R>(ResourceBuilder::default())
166    }
167
168    #[inline]
169    #[cfg(feature = "app")]
170    #[cfg_attr(docsrs, doc(cfg(feature = "app")))]
171    /// Register [`Resource`] by [`ResourceBuilder`].
172    pub fn register_resource<R: Resource + Send + Sync + Any>(
173        self,
174        builder: ResourceBuilder<R>,
175    ) -> Res<Self> {
176        let mut env = self.configure_resource_description_by_builder(&builder);
177        env.resource.register(builder)?;
178        Ok(env)
179    }
180
181    #[inline]
182    #[cfg(feature = "app")]
183    /// Configure resource description.
184    #[cfg_attr(docsrs, doc(cfg(feature = "app")))]
185    pub(crate) fn configure_resource_description_by_builder<R: Resource>(
186        self,
187        builder: &ResourceBuilder<R>,
188    ) -> Self {
189        self.configure_description_by_namespace::<R::Config>(builder.namespace)
190    }
191
192    #[cfg(feature = "derive")]
193    #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
194    /// Configure description parsing.
195    pub fn configure_description<T: PrefixedFromEnvironment + DescFromEnvironment>(self) -> Self {
196        self.configure_description_by_namespace::<T>("")
197    }
198
199    #[cfg(feature = "derive")]
200    #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
201    /// Configure description parsing.
202    pub fn configure_description_by_namespace<T: PrefixedFromEnvironment + DescFromEnvironment>(
203        mut self,
204        namespace: &'static str,
205    ) -> Self {
206        self.app_desc
207            .push(Box::new(move |env| env.get_desc::<T>(namespace)));
208        self
209    }
210}
211
212/// Salak is a wrapper for salak env, all functions that this crate provides will be implemented on it.
213/// * Provides a group of sources that have predefined orders.
214/// * Provides custom source registration.
215///
216#[allow(missing_debug_implementations)]
217pub struct Salak {
218    reg: PropertyRegistryInternal<'static>,
219    ior: Mutex<Vec<Box<dyn IORefT + Send>>>,
220    #[cfg(feature = "app")]
221    pub(crate) res: ResourceRegistry,
222}
223
224impl Salak {
225    /// Create a builder for configure salak env.
226    pub fn builder() -> SalakBuilder {
227        SalakBuilder {
228            args: HashMap::new(),
229            #[cfg(any(feature = "toml", feature = "yaml"))]
230            disable_file: false,
231            #[cfg(feature = "rand")]
232            disable_random: false,
233            registry: PropertyRegistryInternal::new("registry"),
234            #[cfg(any(feature = "args", feature = "derive"))]
235            app_desc: vec![],
236            #[cfg(feature = "args")]
237            app_info: None,
238            iorefs: Mutex::new(vec![]),
239            #[cfg(feature = "app")]
240            resource: ResourceRegistry::new(),
241        }
242    }
243
244    /// Create a new salak env.
245    pub fn new() -> Res<Self> {
246        Self::builder().build()
247    }
248
249    /// Register source to registry, source that register earlier that higher priority for
250    /// configuration.
251    pub fn register<P: PropertySource + 'static>(&mut self, provider: P) {
252        self.reg.register_by_ref(Box::new(provider))
253    }
254
255    #[cfg(feature = "derive")]
256    /// Get key description.
257    #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
258    pub(crate) fn get_desc<T: PrefixedFromEnvironment + DescFromEnvironment>(
259        &self,
260        namespace: &'static str,
261    ) -> Vec<KeyDesc> {
262        let mut key = Key::new();
263        let mut key_descs = vec![];
264        let mut context = SalakDescContext::new(&mut key, &mut key_descs);
265        if namespace.is_empty() {
266            context.add_key_desc::<T>(T::prefix(), None, None, None);
267        } else {
268            context.add_key_desc::<T>(&format!("{}.{}", T::prefix(), namespace), None, None, None);
269        };
270        key_descs
271    }
272}
273
274impl Environment for Salak {
275    #[inline]
276    fn reload(&self) -> Res<bool> {
277        self.reg.reload(&self.ior)
278    }
279
280    #[inline]
281    fn require<T: FromEnvironment>(&self, key: &str) -> Res<T> {
282        self.reg.require(key, &self.ior)
283    }
284}