{"id":13621804,"url":"https://github.com/andoriyu/uclicious","last_synced_at":"2025-04-15T01:34:08.772Z","repository":{"id":46162818,"uuid":"246129535","full_name":"andoriyu/uclicious","owner":"andoriyu","description":"Uclicious is a flexible reduced boilerplate configuration framework.","archived":false,"fork":false,"pushed_at":"2023-04-25T19:09:51.000Z","size":188,"stargazers_count":17,"open_issues_count":3,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-08-10T10:25:35.233Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andoriyu.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"andoriyu"}},"created_at":"2020-03-09T19:57:45.000Z","updated_at":"2024-04-12T06:19:32.000Z","dependencies_parsed_at":"2024-08-01T21:54:20.821Z","dependency_job_id":null,"html_url":"https://github.com/andoriyu/uclicious","commit_stats":{"total_commits":118,"total_committers":3,"mean_commits":"39.333333333333336","dds":"0.17796610169491522","last_synced_commit":"d234f6894d622ca63b45d56e8a13ae063971e4aa"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andoriyu%2Fuclicious","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andoriyu%2Fuclicious/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andoriyu%2Fuclicious/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andoriyu%2Fuclicious/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andoriyu","download_url":"https://codeload.github.com/andoriyu/uclicious/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223654986,"owners_count":17180626,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T21:01:10.715Z","updated_at":"2025-04-15T01:34:08.761Z","avatar_url":"https://github.com/andoriyu.png","language":"Rust","funding_links":["https://github.com/sponsors/andoriyu"],"categories":["Libraries","库 Libraries"],"sub_categories":["Configuration","配置 Configuration"],"readme":"\u003c!-- cargo-sync-readme start --\u003e\n\n# Uclicious [![Build Status](https://dev.azure.com/andoriyu/personal/_apis/build/status/andoriyu.uclicious?branchName=master)](https://dev.azure.com/andoriyu/personal/_build/latest?definitionId=7\u0026branchName=master) [![codecov](https://codecov.io/gh/andoriyu/uclicious/branch/master/graph/badge.svg)](https://codecov.io/gh/andoriyu/uclicious) [![docs.rs](https://docs.rs/uclicious/badge.svg)](https://docs.rs/uclicious) [![Crates.io](https://img.shields.io/crates/v/uclicious.svg)](https://crates.io/crates/uclicious)\n\n  * [What is Uclicious](#what-is-uclicious)\n  * [Usage](#usage)\n    + [Raw API](#raw-api)\n    + [Derive-driven](#derive-driven)\n      - [Validators](#validators)\n      - [Type Mapping](#type-mapping)\n    + [Supported attributes (`#[ucl(..)]`)](#supported-attributes-%23ucl)\n      - [Structure level](#structure-level)\n      - [Field level](#field-level)\n    + [Additional notes](#additional-notes)\n  * [Contributing](#contributing)\n      - [Particular Contributions of Interest](#particular-contributions-of-interest)\n  * [Goals](#goals)\n    + [Not Goals](#not-goals)\n  * [Special thanks](#special-thanks)\n  * [LICENSE](#license)\n## What is Uclicious\n\nUclicious is a flexible reduced boilerplate configuration framework.\n\nUclicious is built on top of [libucl](https://github.com/vstakhov/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.\nIt 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.\n\n## Usage\n### Raw API\n\nRaw API involves interacting with `libucl` parser via safe api:\n```rust\nuse uclicious::*;\nlet mut parser = Parser::default();\nlet input = r#\"\ntest_string = \"no scope\"\na_float = 3.14\nan_integer = 69420\nis_it_good = yes\nbuffer_size = 1KB\ninterval = 1s\n\"#;\nparser.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();\nlet result = parser.get_object().unwrap();\n\nlet lookup_result = result.lookup(\"test_string\").unwrap().as_string().unwrap();\nassert_eq!(lookup_result.as_str(), \"no scope\");\n\nlet lookup_result = result.lookup(\"a_float\").unwrap().as_f64().unwrap();\nassert_eq!(lookup_result, 3.14f64);\n\nlet lookup_result = result.lookup(\"an_integer\").unwrap().as_i64().unwrap();\nassert_eq!(lookup_result, 69420i64);\n\nlet lookup_result = result.lookup(\"is_it_good\").unwrap().as_bool().unwrap();\nassert_eq!(lookup_result, true);\n\nlet lookup_result = result.lookup(\"buffer_size\").unwrap().as_i64().unwrap();\nassert_eq!(lookup_result, 1024);\nlet lookup_result = result.lookup(\"interval\").unwrap().as_time().unwrap();\nassert_eq!(lookup_result, 1.0f64);\n```\n\nIn order to get around rust rules library implemets its own trait FromObject for some basic types:\n```rust\nuse uclicious::*;\nlet mut parser = Parser::default();\nlet input = r#\"\ntest_string = \"no scope\"\na_float = 3.14\nan_integer = 69420\nis_it_good = yes\nbuffer_size = 1KB\n\"#;\nparser.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();\nlet result = parser.get_object().unwrap();\n\nlet lookup_result = result.lookup(\"is_it_good\").unwrap();\nlet maybe: Option\u003cbool\u003e = FromObject::try_from(lookup_result).unwrap();\nassert_eq!(Some(true), maybe);\n```\n### Derive-driven\n\nOn top of \"raw\" interface to libUCL, Uclicious provides an easy way to derive constructor for strucs:\n```rust\nuse uclicious::*;\nuse std::path::PathBuf;\nuse std::net::SocketAddr;\nuse std::collections::HashMap;\nuse std::time::Duration;\n\n#[derive(Debug,Uclicious)]\n#[ucl(var(name = \"test\", value = \"works\"))]\nstruct Connection {\n   #[ucl(default)]\n   enabled: bool,\n   host: String,\n   #[ucl(default = \"420\")]\n   port: i64,\n   buffer: u64,\n   #[ucl(path = \"type\")]\n   kind: String,\n   locations: Vec\u003cPathBuf\u003e,\n   addr: SocketAddr,\n   extra: Extra,\n   #[ucl(path = \"subsection.host\")]\n   hosts: Vec\u003cString\u003e,\n   #[ucl(default)]\n   option: Option\u003cString\u003e,\n   gates: HashMap\u003cString, bool\u003e,\n   interval: Duration,\n}\n\n#[derive(Debug,Uclicious)]\n#[ucl(skip_builder)]\nstruct Extra {\n   enabled: bool\n}\nlet mut builder = Connection::builder().unwrap();\n\nlet input = r#\"\n    enabled = yes\n    host = \"some.fake.url\"\n    buffer = 1mb\n    type = $test\n    locations = \"/etc/\"\n    addr = \"127.0.0.1:80\"\n    extra = {\n       enabled = on\n   }\n    subsection {\n       host = [host1, host2]\n   }\n   interval = 10ms\n   gates {\n        feature_1 = on\n        feature_2 = off\n        feature_3 = on\n   }\"#;\n\nbuilder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();\nlet connection: Connection = builder.build().unwrap();\n```\n\nIf you choose to derive builder then `::builder()` method will be added to target struct.\n\n#### Validators\n\nLibrary supports running optional validators on values before building the resulting struct:\n\n```rust\nuse uclicious::*;\nmod validators {\n   use uclicious::ObjectError;\n    pub fn is_positive(lookup_path: \u0026str, value: \u0026i64) -\u003e Result\u003c(), ObjectError\u003e {\n        if *value \u003e 0 {\n            Ok(())\n        } else {\n            Err(ObjectError::other(format!(\"{} is not a positive number\", lookup_path)))\n        }\n    }\n}\n#[derive(Debug,Uclicious)]\nstruct Validated {\n   #[ucl(default, validate=\"validators::is_positive\")]\n    number: i64\n}\nlet mut builder = Validated::builder().unwrap();\n\nlet input = \"number = -1\";\nbuilder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();\nassert!(builder.build().is_err())\n```\n#### Type Mapping\n\nIf your target structure has types that don't implement `FromObject` you can use `From` or `TryFrom`\nvia intermediate that does:\n\n```rust\nuse uclicious::*;\nuse std::convert::{From,TryFrom};\n\n#[derive(Debug, Eq, PartialEq)]\nenum Mode {\n    On,\n    Off,\n}\n\nimpl TryFrom\u003cString\u003e for Mode {\n    type Error = ObjectError;\n    fn try_from(src: String) -\u003e Result\u003cMode, ObjectError\u003e {\n        match src.to_lowercase().as_str() {\n            \"on\" =\u003e Ok(Mode::On),\n            \"off\" =\u003e Ok(Mode::Off),\n            _   =\u003e Err(ObjectError::other(format!(\"{} is not supported value\", src)))\n        }\n    }\n}\n\n#[derive(Debug, Eq, PartialEq)]\nstruct WrappedInt(i64);\n\nimpl From\u003ci64\u003e for WrappedInt {\n    fn from(src: i64) -\u003e WrappedInt {\n        WrappedInt(src)\n    }\n}\n\n#[derive(Debug,Uclicious, Eq, PartialEq)]\nstruct Mapped {\n   #[ucl(from=\"i64\")]\n    number: WrappedInt,\n   #[ucl(try_from=\"String\")]\n    mode: Mode\n}\nlet mut builder = Mapped::builder().unwrap();\n\nlet input = r#\"\n    number = -1,\n    mode = \"on\"\n\"#;\nbuilder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();\nlet actual = builder.build().unwrap();\nlet expected = Mapped {\nnumber: WrappedInt(-1),\nmode: Mode::On\n};\nassert_eq!(expected, actual);\n```\n\nAdditionally you can provide mapping to your type from ObjectRef:\n```rust\nuse uclicious::*;\n\n#[derive(Debug, Eq, PartialEq)]\npub enum Mode {\n    On,\n    Off,\n}\n\npub fn map_bool(src: ObjectRef) -\u003e Result\u003cMode, ObjectError\u003e {\n    let bool: bool = src.try_into()?;\n    if bool {\n        Ok(Mode::On)\n    } else {\n        Ok(Mode::Off)\n    }\n}\n#[derive(Debug,Uclicious, Eq, PartialEq)]\nstruct Mapped {\n   #[ucl(map=\"map_bool\")]\n    mode: Mode\n}\nlet mut builder = Mapped::builder().unwrap();\n\nlet input = r#\"\n    mode = on\n\"#;\nbuilder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();\nlet actual = builder.build().unwrap();\nlet expected = Mapped {\n    mode: Mode::On\n};\n```\n### Supported attributes (`#[ucl(..)]`)\n\n#### Structure level\n\n - `skip_builder`\n    - if set, then builder and builder methods won't be generated.\n - `parser(..)`\n    - Optional attribute to configure inner parser.\n    - Has following nested attributes:\n        - `flags`\n            - a path to function that returns flags.\n        - `filevars(..)`\n            - call `set_filevars` on a parser.\n            - Has following nested attributes:\n                - `path`\n                    - a string representation of filepath.\n                - `expand`\n                    - (optional) if set, then variables would be expanded to absolute.\n - `pre_source_hook(...)`\n    - Optional attribute to run a function before sources are added\n    - Can be used to register vars handler\n    - Must take `\u0026mut Parser` as argument and return `Result\u003c(), Into\u003cUclError\u003e\u003e`\n - `var(..)`\n    - Optional attribute to register string variables with the parser.\n    - Has following nested attributes:\n        - `name`\n            - A name of the variable without `$` part.\n        - `value`\n            - A string values for the variable.\n            - Onlt string variables are supported by libUCL.\n - `include(..)`\n    - Used to add files into the parser.\n    - If file doesn't exist or failed to parse, then error will be returned in a constructor.\n    - Must specify exactly one of following sources: `path`, `chunk` or `chunk_static`\n    - Has following nested attirbutes:\n        - (semi-optional) `path = string`\n            - File path. Can be absolute or relative to CWD.\n        - (semi-optional) `chunk = string`\n            - A string that will be added to parser as a chunk.\n        - (semi-optional) `chunk_static = string`\n            - A path to a file that will be included into binary with [`include_str!()`](https://doc.rust-lang.org/std/macro.include_str.html)\n        - (optional) `priority = u32`\n            - 0-15 priority for the source. Consult the libUCL documentation for more information.\n        - (optional) `strategy = uclicious::DuplicateStrategy`\n            - Strategy to use for duplicate keys. Consult the libUCL documentation for more information.\n\n#### Field level\n All field level options are optional.\n\n - `default`\n    - Use Default::default if key not found in object.\n - `default = expression`\n    - Use this _expression_ as value if key not found.\n    - Could be a value or a function call.\n - `path = string`\n    - By default field name is used as path.\n    - If set that would be used as a key.\n    - dot notation for key is supported.\n - `validate = path::to_method`\n    - `Fn(key: \u0026str, value: \u0026T) -\u003e Result\u003c(), E\u003e`\n    - Error needs to be convertable into `ObjectError`\n - `from = Type`\n    - Try to convert `ObjectRef` to `Type` and then use `std::convert::From` to convert into target type\n - `try_from = Type`\n    - Try to convert `ObjectRef` to `Type` and then use `std::convert::TryFrom` to convert into target type\n    - Error will be converted into `ObjectError::Other`\n - `from_str`\n    - Try to convert `ObjectRef` to `String` and then use `std::str::FromStr` to convert into target type\n    - Error will be converted into `ObjectError::Other`\n - `map = path::to_method`\n    - `Fn(src: ObjectRef) -\u003e Result\u003cT, E\u003e`\n    - A way to map foreign objects that can't implement `From` or `TryFrom` or when error is not convertable into `ObjectError`\n\n### Additional notes\n - If target type is an array, but key is a single value — an implicit list is created.\n - Automatic derive on enums is not supported, but you can implement it yourself.\n - I have a few more features I want to implement before publishing this crate:\n    - Ability to add variables.\n    - Ability to add macross handlers.\n    - (maybe) configure parser that us used for derived builder with atrributes.\n    - (done) add sources to parser with attributes.\n\n## Contributing\n\nPRs, feature requests, bug reports are welcome. I won't be adding CoC  — be civilized.\n\n#### Particular Contributions of Interest\n\n - Optimize derive code.\n - Improve documentation — I often write late and night and some it might look like a word soup.\n - Better tests\n - Glob support in derive parser section\n - Variable handler\n\n\n## Goals\n - Provider safe and convient configuration library\n - Automatic derive, so you don't have to think about parser object\n\n### Not Goals\n - Providing UCL Object generation tools is not a goal for this project\n - 1:1 interface to libUCL\n - sugar inside `raw` module\n\n## Special thanks\n - [draft6](https://github.com/draft6) and [hauleth](https://github.com/hauleth)\n    - libucl-rs was a good starting point\n    - Type wrappers pretty much copied from there\n - [colin-kiegel](https://github.com/colin-kiegel)\n    - Rust-derive-builder was used as a starting point for uclicious-derive\n    - Very well documented proc_macro crate, do recommend\n\n## LICENSE\n\n[BSD-2-Clause](https://github.com/andoriyu/uclicious/blob/master/LICENSE).\n\n\u003c!-- cargo-sync-readme end --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandoriyu%2Fuclicious","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandoriyu%2Fuclicious","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandoriyu%2Fuclicious/lists"}