8 个版本
0.1.8 | 2023年4月25日 |
---|---|
0.1.7 | 2021年11月11日 |
0.1.5 | 2020年4月20日 |
0.1.2 | 2020年3月22日 |
#34 in 配置
110KB
2K SLoC
Uclicious
什么是 Uclicious
Uclicious 是一个灵活的减少样板代码的配置框架。
Uclicious 建立在 libucl 之上。如果你曾经编写过 nginx 配置,并想“天啊,我希望所有配置文件都像这样”,那么这个库就是为你准备的。内部解析器支持两种格式:nginx-like 和 json-like。JSON 解析器比 JSON 更宽容,即每个 JSON 文件都是有效的 UCL 文件,但反之则不然。它比 JSON 或 TOML 更复杂,因此我建议阅读有关它的文档。UCL 的作者对其进行了很好的文档记录。此库提供 derive-driven 和 raw-api 驱动的使用模式。
用法
原始 API
原始 API 涉及通过安全 API 与 libucl
解析器交互
use uclicious::*;
let mut parser = Parser::default();
let input = r#"
test_string = "no scope"
a_float = 3.14
an_integer = 69420
is_it_good = yes
buffer_size = 1KB
interval = 1s
"#;
parser.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let result = parser.get_object().unwrap();
let lookup_result = result.lookup("test_string").unwrap().as_string().unwrap();
assert_eq!(lookup_result.as_str(), "no scope");
let lookup_result = result.lookup("a_float").unwrap().as_f64().unwrap();
assert_eq!(lookup_result, 3.14f64);
let lookup_result = result.lookup("an_integer").unwrap().as_i64().unwrap();
assert_eq!(lookup_result, 69420i64);
let lookup_result = result.lookup("is_it_good").unwrap().as_bool().unwrap();
assert_eq!(lookup_result, true);
let lookup_result = result.lookup("buffer_size").unwrap().as_i64().unwrap();
assert_eq!(lookup_result, 1024);
let lookup_result = result.lookup("interval").unwrap().as_time().unwrap();
assert_eq!(lookup_result, 1.0f64);
为了绕过 Rust 规则,库实现了自己的 trait FromObject 以支持一些基本类型
use uclicious::*;
let mut parser = Parser::default();
let input = r#"
test_string = "no scope"
a_float = 3.14
an_integer = 69420
is_it_good = yes
buffer_size = 1KB
"#;
parser.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let result = parser.get_object().unwrap();
let lookup_result = result.lookup("is_it_good").unwrap();
let maybe: Option<bool> = FromObject::try_from(lookup_result).unwrap();
assert_eq!(Some(true), maybe);
派生驱动
在 libUCL 的“原始”接口之上,Uclicious 提供了一种轻松的方式来自定义结构体的构造函数
use uclicious::*;
use std::path::PathBuf;
use std::net::SocketAddr;
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug,Uclicious)]
#[ucl(var(name = "test", value = "works"))]
struct Connection {
#[ucl(default)]
enabled: bool,
host: String,
#[ucl(default = "420")]
port: i64,
buffer: u64,
#[ucl(path = "type")]
kind: String,
locations: Vec<PathBuf>,
addr: SocketAddr,
extra: Extra,
#[ucl(path = "subsection.host")]
hosts: Vec<String>,
#[ucl(default)]
option: Option<String>,
gates: HashMap<String, bool>,
interval: Duration,
}
#[derive(Debug,Uclicious)]
#[ucl(skip_builder)]
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 {
feature_1 = on
feature_2 = off
feature_3 = on
}"#;
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let connection: Connection = builder.build().unwrap();
如果你选择派生构建器,则 ::builder()
方法将被添加到目标结构体中。
验证器
库支持在构建结果结构体之前运行可选验证器
use uclicious::*;
mod validators {
use uclicious::ObjectError;
pub fn is_positive(lookup_path: &str, value: &i64) -> Result<(), ObjectError> {
if *value > 0 {
Ok(())
} else {
Err(ObjectError::other(format!("{} is not a positive number", lookup_path)))
}
}
}
#[derive(Debug,Uclicious)]
struct Validated {
#[ucl(default, validate="validators::is_positive")]
number: i64
}
let mut builder = Validated::builder().unwrap();
let input = "number = -1";
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
assert!(builder.build().is_err())
类型映射
如果目标结构包含未实现 FromObject
的类型,您可以使用 From
或 TryFrom
通过中间实现。
use uclicious::*;
use std::convert::{From,TryFrom};
#[derive(Debug, Eq, PartialEq)]
enum Mode {
On,
Off,
}
impl TryFrom<String> for Mode {
type Error = ObjectError;
fn try_from(src: String) -> Result<Mode, ObjectError> {
match src.to_lowercase().as_str() {
"on" => Ok(Mode::On),
"off" => Ok(Mode::Off),
_ => Err(ObjectError::other(format!("{} is not supported value", src)))
}
}
}
#[derive(Debug, Eq, PartialEq)]
struct WrappedInt(i64);
impl From<i64> for WrappedInt {
fn from(src: i64) -> WrappedInt {
WrappedInt(src)
}
}
#[derive(Debug,Uclicious, Eq, PartialEq)]
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.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let actual = builder.build().unwrap();
let expected = Mapped {
number: WrappedInt(-1),
mode: Mode::On
};
assert_eq!(expected, actual);
此外,您还可以提供从 ObjectRef 到您类型的映射。
use uclicious::*;
#[derive(Debug, Eq, PartialEq)]
pub enum Mode {
On,
Off,
}
pub fn map_bool(src: ObjectRef) -> Result<Mode, ObjectError> {
let bool: bool = src.try_into()?;
if bool {
Ok(Mode::On)
} else {
Ok(Mode::Off)
}
}
#[derive(Debug,Uclicious, Eq, PartialEq)]
struct Mapped {
#[ucl(map="map_bool")]
mode: Mode
}
let mut builder = Mapped::builder().unwrap();
let input = r#"
mode = on
"#;
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let actual = builder.build().unwrap();
let expected = Mapped {
mode: Mode::On
};
支持属性 (#[ucl(..)]
)
结构级别
skip_builder
- 如果设置,则不会生成 builder 和 builder 方法。
parser(..)
- 配置内部解析器的可选属性。
- 具有以下嵌套属性
flags
- 函数返回标志的路径。
filevars(..)
- 在解析器上调用
set_filevars
。 - 具有以下嵌套属性
path
- 文件路径的字符串表示。
expand
- (可选)如果设置,则变量会被展开到绝对路径。
- 在解析器上调用
pre_source_hook(...)
- 在添加源之前运行函数的可选属性。
- 可用于注册变量处理器
- 必须接受
&mut Parser
作为参数,并返回Result<(), Into<UclError>>
var(..)
- 将字符串变量注册到解析器的可选属性。
- 具有以下嵌套属性
name
- 变量名,不带
$
部分。
- 变量名,不带
value
- 变量的字符串值。
- libUCL 仅支持字符串变量。
include(..)
- 用于将文件添加到解析器。
- 如果文件不存在或解析失败,则构造函数中会返回错误。
- 必须指定以下之一:
path
、chunk
或chunk_static
- 具有以下嵌套属性
- (半可选)
path = string
- 文件路径。可以是绝对路径或相对于当前工作目录的相对路径。
- (半可选)
chunk = string
- 将作为块添加到解析器中的字符串。
- (半可选)
chunk_static = string
- 包含到二进制文件中的文件的路径,使用
include_str!()
- 包含到二进制文件中的文件的路径,使用
- (可选)
priority = u32
- 0-15 优先级。有关更多信息,请参阅 libUCL 文档。
- (可选)
strategy = uclicious::DuplicateStrategy
- 用于重复键的策略。有关更多信息,请参阅 libUCL 文档。
- (半可选)
字段级别
所有字段级选项都是可选的。
default
- 如果对象中未找到键,则使用 Default::default。
default=expression
- 如果未找到键,则使用此 表达式 作为值。
- 可以是值或函数调用。
path=string
- 默认情况下,字段名称用作路径。
- 如果设置,则将用作键。
- 支持键的点表示法。
validate= path::to_method
Fn(key: &str,value: &T) -> Result<(), E>
- 错误需要可转换为
ObjectError
from= Type
- 尝试将
ObjectRef
转换为Type
,然后使用std::convert::From
转换为目标类型
- 尝试将
try_from=Type
- 尝试将
ObjectRef
转换为Type
,然后使用std::convert::TryFrom
转换为目标类型 - 错误将被转换为
ObjectError::Other
- 尝试将
from_str
- 尝试将
ObjectRef
转换为String
,然后使用std::str::FromStr
转换为目标类型 - 错误将被转换为
ObjectError::Other
- 尝试将
map= path::to_method
Fn(src:ObjectRef) -> Result<T, E>
- 一种将无法实现
From
或TryFrom
的外部对象进行映射的方法,或者当错误不能转换为ObjectError
时
附加说明
- 如果目标类型是数组,但键是单个值,则创建一个隐式列表。
- 枚举的自动推导不支持,但您可以自己实现。
- 在发布此crate之前,我还有几个功能要实现。
- 能够添加变量。
- 能够添加宏处理器。
- (可能)配置用于派生构建器的解析器,带有属性。
- (完成)将带有属性的源添加到解析器中。
贡献
欢迎提交PR、功能请求和错误报告。我不会添加CoC——要文明。
感兴趣的特殊贡献
- 优化派生代码。
- 改进文档——我经常熬夜写,有些可能看起来像字面汤。
- 更好的测试。
- 在派生解析器部分支持glob。
- 变量处理器
目标
- 提供安全便捷的配置库
- 自动派生,因此您无需考虑解析器对象
非目标
- 提供UCL对象生成工具不是此项目的目标
- 与libUCL的1:1接口
- 在
raw
模块中的糖
特别感谢
- draft6 和 hauleth
- libucl-rs 是一个好的起点
- 类型包装器基本上是从那里复制的
- colin-kiegel
- Rust-derive-builder 被用作 uclicious-derive 的起点
- 非常详尽的 proc_macro crate,强烈推荐
许可协议
依赖项
~2.5MB
~30K SLoC