Uclicious is a flexible reduced boilerplate configuration framework.
Uclicious is built on top of libucl. If you ever wrote an nginx configurtion and though "Damn, I wish all configuration files were like this" this is the library for you. Internal parser supports both: nginx-like and json-like formats. JSON parser is a little bit more permissive than - every json file is a valid UCL file, but not other way around. It is much more complex than json or TOML, so I recommend reading documentaiton about it. Author of UCL did a great job documenting it. This library provides both: derive-driven and raw-api driven usage patterns.
Raw API involves interacting with libucl
parser via safe api:
```rust
use uclicious::*;
let mut parser = Parser::default();
let input = r#"
teststring = "no scope"
afloat = 3.14
aninteger = 69420
isitgood = yes
buffersize = 1KB
interval = 1s
"#;
parser.addchunkfull(input, Priority::default(), DEFAULTDUPLICATESTRATEGY).unwrap();
let result = parser.get_object().unwrap();
let lookupresult = result.lookup("teststring").unwrap().asstring().unwrap(); asserteq!(lookupresult.asstr(), "no scope");
let lookupresult = result.lookup("afloat").unwrap().asf64().unwrap(); asserteq!(lookup_result, 3.14f64);
let lookupresult = result.lookup("aninteger").unwrap().asi64().unwrap(); asserteq!(lookup_result, 69420i64);
let lookupresult = result.lookup("isitgood").unwrap().asbool().unwrap(); asserteq!(lookupresult, true);
let lookupresult = result.lookup("buffersize").unwrap().asi64().unwrap(); asserteq!(lookupresult, 1024); let lookupresult = result.lookup("interval").unwrap().astime().unwrap(); asserteq!(lookup_result, 1.0f64); ```
In order to get around rust rules library implemets its own trait FromObject for some basic types: ```rust use uclicious::*; let mut parser = Parser::default(); let input = r#" teststring = "no scope" afloat = 3.14 aninteger = 69420 isitgood = yes buffersize = 1KB "#; parser.addchunkfull(input, Priority::default(), DEFAULTDUPLICATESTRATEGY).unwrap(); let result = parser.get_object().unwrap();
let lookupresult = result.lookup("isitgood").unwrap();
let maybe: Option
On top of "raw" interface to libUCL, Uclicious provides an easy way to derive constructor for strucs: ```rust use uclicious::*; use std::path::PathBuf; use std::net::SocketAddr; use std::collections::HashMap; use std::time::Duration;
struct Connection {
#[ucl(default)]
enabled: bool,
host: String,
#[ucl(default = "420")]
port: i64,
buffer: u64,
#[ucl(path = "type")]
kind: String,
locations: Vec
struct Extra { enabled: bool } let mut builder = Connection::builder().unwrap();
let input = r#" enabled = yes host = "some.fake.url" buffer = 1mb type = $test locations = "/etc/" addr = "127.0.0.1:80" extra = { enabled = on } subsection { host = [host1, host2] } interval = 10ms gates { feature1 = on feature2 = off feature_3 = on }"#;
builder.addchunkfull(input, Priority::default(), DEFAULTDUPLICATESTRATEGY).unwrap(); let connection: Connection = builder.build().unwrap(); ```
If you choose to derive builder then ::builder()
method will be added to target struct.
Library supports running optional validators on values before building the resulting struct:
```rust use uclicious::*; mod validators { use uclicious::ObjectError; pub fn ispositive(lookuppath: &str, value: &i64) -> Result<(), ObjectError> { if *value > 0 { Ok(()) } else { Err(ObjectError::other(format!("{} is not a positive number", lookup_path))) } } }
struct Validated { #[ucl(default, validate="validators::is_positive")] number: i64 } let mut builder = Validated::builder().unwrap();
let input = "number = -1"; builder.addchunkfull(input, Priority::default(), DEFAULTDUPLICATESTRATEGY).unwrap(); assert!(builder.build().is_err()) ```
If your target structure has types that don't implement FromObject
you can use From
or TryFrom
via intermediate that does:
```rust use uclicious::*; use std::convert::{From,TryFrom};
enum Mode { On, Off, }
impl TryFrom
struct WrappedInt(i64);
impl From
struct Mapped { #[ucl(from="i64")] number: WrappedInt, #[ucl(try_from="String")] mode: Mode } let mut builder = Mapped::builder().unwrap();
let input = r#" number = -1, mode = "on" "#; builder.addchunkfull(input, Priority::default(), DEFAULTDUPLICATESTRATEGY).unwrap(); let actual = builder.build().unwrap(); let expected = Mapped { number: WrappedInt(-1), mode: Mode::On }; assert_eq!(expected, actual); ```
Additionally you can provide mapping to your type from ObjectRef: ```rust use uclicious::*;
pub enum Mode { On, Off, }
pub fn mapbool(src: ObjectRef) -> Result
struct Mapped { #[ucl(map="map_bool")] mode: Mode } let mut builder = Mapped::builder().unwrap();
let input = r#" mode = on "#; builder.addchunkfull(input, Priority::default(), DEFAULTDUPLICATESTRATEGY).unwrap(); let actual = builder.build().unwrap(); let expected = Mapped { mode: Mode::On }; ```
#[ucl(..)]
)skip_builder
parser(..)
flags
filevars(..)
set_filevars
on a parser.path
expand
pre_source_hook(...)
&mut Parser
as argument and return Result<(), Into<UclError>>
var(..)
name
$
part.value
include(..)
path
, chunk
or chunk_static
path = string
chunk = string
chunk_static = string
include_str!()
priority = u32
strategy = uclicious::DuplicateStrategy
All field level options are optional.
default
default = expression
path = string
validate = path::to_method
Fn(key: &str, value: &T) -> Result<(), E>
ObjectError
from = Type
ObjectRef
to Type
and then use std::convert::From
to convert into target typetry_from = Type
ObjectRef
to Type
and then use std::convert::TryFrom
to convert into target typeObjectError::Other
map = path::to_method
Fn(src: ObjectRef) -> Result<T, E>
From
or TryFrom
or when error is not convertable into ObjectError
PRs, feature requests, bug reports are welcome. I won't be adding CoC — be civilized.
raw
module