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#[cfg(feature = "chained")]
17pub mod chained;
18
19#[cfg(feature = "cmd")]
21pub mod cmd;
22
23pub mod de;
25
26#[cfg(feature = "env")]
28pub mod env;
29
30#[cfg(feature = "ini")]
32pub mod ini;
33
34#[cfg(feature = "json")]
36pub mod json;
37
38#[cfg(feature = "mem")]
40pub mod mem;
41
42pub mod ser;
44
45#[cfg(feature = "typed")]
47pub mod typed;
48
49pub mod path;
51
52pub mod prelude;
54
55#[cfg(feature = "xml")]
57pub mod xml;
58
59#[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
77pub type Result<T = ()> = std::result::Result<T, Error>;
79
80#[inline]
82pub fn builder() -> Builder {
83 Builder::default()
84}
85
86pub 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#[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 let actual = pascal_case(expected);
186
187 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 let actual = pascal_case(text);
203
204 assert_eq!(actual, "HelloWorld");
206 }
207
208 #[test]
209 fn pascal_case_should_convert_capitalized_with_colon() {
210 let expected = "Hello:World";
212
213 let actual = pascal_case("HELLO:WORLD");
215
216 assert_eq!(actual, expected);
218 }
219}