Skip to main content

config/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4mod builder;
5mod cfg;
6pub(crate) mod context;
7mod error;
8mod file;
9mod merge;
10mod provider;
11mod reloadable;
12mod section;
13mod settings;
14
15/// Contains chained configuration support.
16#[cfg(feature = "chained")]
17pub mod chained;
18
19/// Contains command line configuration support.
20#[cfg(feature = "cmd")]
21pub mod cmd;
22
23/// Contains strongly-typed configuration deserialization support.
24pub mod de;
25
26/// Contains environment variable configuration support.
27#[cfg(feature = "env")]
28pub mod env;
29
30/// Contains `*.ini` file configuration support.
31#[cfg(feature = "ini")]
32pub mod ini;
33
34/// Contains `*.json` file configuration support.
35#[cfg(feature = "json")]
36pub mod json;
37
38/// Contains in-memory configuration support.
39#[cfg(feature = "mem")]
40pub mod mem;
41
42/// Contains configuration serialization support.
43pub mod ser;
44
45/// Contains strongly-typed configuration support.
46#[cfg(feature = "typed")]
47pub mod typed;
48
49/// Provides configuration path utilities.
50pub mod path;
51
52/// Contains library prelude.
53pub mod prelude;
54
55/// Contains `*.xml` file configuration support.
56#[cfg(feature = "xml")]
57pub mod xml;
58
59/// Contains `*.yaml` and `*.yml` file configuration support.
60#[cfg(feature = "yaml")]
61pub mod yaml;
62
63pub use builder::Builder;
64pub use cfg::{Configuration, ReloadableConfiguration};
65pub use error::Error;
66pub use file::{FileSource, FileSourceBuilder};
67pub use merge::Merge;
68pub use provider::Provider;
69pub use reloadable::Reloadable;
70pub use section::{OwnedSection, Section};
71pub use settings::Settings;
72
73#[cfg(feature = "derive")]
74#[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
75pub use config_derive::Deserialize;
76
77/// Represents a configuration result.
78pub type Result<T = ()> = std::result::Result<T, Error>;
79
80/// Creates and returns a new [configuration builder](Builder)
81#[inline]
82pub fn builder() -> Builder {
83    Builder::default()
84}
85
86/// Converts the specified text into Pascal Case.
87///
88/// # Arguments
89///
90/// * `text` - the input text to convert
91///
92/// # Remarks
93///
94/// This function supports converting the following input forms:
95///
96/// - Pascal Case (`HelloWorld → HelloWorld`)
97/// - Camel Case (`helloWorld → HelloWorld`)
98/// - Snake Case (`hello_world → HelloWorld`)
99/// - Screaming Snake Case (`HELLO_WORLD → HelloWorld`)
100/// - Kebab Case (`hello-world → HelloWorld`)
101/// - Screaming Kebab Case (`HELLO-WORLD → HelloWorld`)
102///
103/// The characters `' '`, `'_'`, and `'-'` are considered word boundaries. Alphabetic characters following these
104/// characters will be capitalized.
105///
106/// # Remarks
107///
108/// This function does not handle Unicode word boundaries.
109pub fn pascal_case(text: &str) -> String {
110    let mut converted = String::with_capacity(text.len());
111    let mut next_is_upper = true;
112    let mut last_was_lower = false;
113
114    for ch in text.chars() {
115        if ch == ' ' || ch == '_' || ch == '-' || ch == ':' {
116            next_is_upper = true;
117            last_was_lower = false;
118
119            if ch == ':' {
120                converted.push(ch);
121            }
122        } else if ch.is_alphabetic() && (next_is_upper || (last_was_lower && ch.is_ascii_uppercase())) {
123            converted.push(ch.to_ascii_uppercase());
124            next_is_upper = false;
125            last_was_lower = ch.is_ascii_lowercase();
126        } else if ch.is_alphabetic() {
127            converted.push(ch.to_ascii_lowercase());
128            last_was_lower = ch.is_ascii_lowercase();
129        } else {
130            converted.push(ch);
131            next_is_upper = true;
132            last_was_lower = false;
133        }
134    }
135
136    converted
137}
138
139// this function is intentionally located here for tracing::trace! to capture the desired location info
140#[inline(never)]
141fn overridden(mut id: u8, names: &[String], providers: u8, key: &str, old: &str, new: &str) {
142    use tracing::trace;
143
144    const UNKNOWN: &str = "Unknown";
145
146    let mut i = (id as u32).saturating_sub(1);
147    let current = if i < u8::BITS && (i as usize) < names.len() {
148        &names[i as usize]
149    } else {
150        UNKNOWN
151    };
152    let last = loop {
153        if id > 0 {
154            id >>= 1;
155            i = (id as u32).saturating_sub(1);
156
157            if providers & id != 0 {
158                if i < u8::BITS && (i as usize) < names.len() {
159                    break names[i as usize].as_str();
160                } else {
161                    break UNKNOWN;
162                }
163            }
164        } else {
165            break UNKNOWN;
166        }
167    };
168
169    trace!("key '{key}' with value '{old}' ({last}) has been overridden with value '{new}' ({current})");
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use test_case::test_case;
176
177    #[test_case(""; "if empty")]
178    #[test_case("HelloWorld"; "in pascal case")]
179    #[test_case("Hello.World"; "with a period")]
180    #[test_case("Hello:World"; "with a colon")]
181    fn pascal_case_should_not_change_text(expected: &str) {
182        // arrange
183
184        // act
185        let actual = pascal_case(expected);
186
187        // assert
188        assert_eq!(actual, expected);
189    }
190
191    #[test_case("hello world"; "from lower title case")]
192    #[test_case("Hello World"; "from upper title case")]
193    #[test_case("helloWorld"; "from camel case")]
194    #[test_case("hello_world"; "from snake case")]
195    #[test_case("HELLO_WORLD"; "from screaming snake case")]
196    #[test_case("hello-world"; "from kebab case")]
197    #[test_case("HELLO-WORLD"; "from screaming kebab case")]
198    fn pascal_case_should_convert_text(text: &str) {
199        // arrange
200
201        // act
202        let actual = pascal_case(text);
203
204        // assert
205        assert_eq!(actual, "HelloWorld");
206    }
207
208    #[test]
209    fn pascal_case_should_convert_capitalized_with_colon() {
210        // arrange
211        let expected = "Hello:World";
212
213        // act
214        let actual = pascal_case("HELLO:WORLD");
215
216        // assert
217        assert_eq!(actual, expected);
218    }
219}