From f587ebc119ff64b0f12e6560c55b207c583a0166 Mon Sep 17 00:00:00 2001 From: sasakiyori Date: Thu, 26 Oct 2023 16:21:54 +0800 Subject: [PATCH] feat: support simple equal format --- Cargo.toml | 9 ++++++++ README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 116 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dd6edea..398619c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,16 @@ name = "merge-cfg" version = "0.1.0" edition = "2021" +authors = ["sasakiyori "] +license = "MIT" +description = "Merge or cover config based on priorities." +keywords = ["config"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +proc-macro = true + [dependencies] +syn = "2.0" +quote = "1.0" diff --git a/README.md b/README.md index 7651e85..9d7314c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,59 @@ # merge-cfg + Merge or cover config based on priorities. + +## Usage + +You can get your configuration from config file in any way at first. Then derive `MergeCfg` to merge your configuration from command line arguments. + +```rust +use merge_cfg::MergeCfg; + +#[derive(Debug, MergeCfg)] +struct Config { + id: u64, + name: String, +} + +fn main() { + let mut cfg = Config { + id: 1, + name: "abc".to_string(), + }; + cfg.merge_cfg(); + println!("{:?}", cfg); +} +``` + +Command line arguments format: `{field}={value}`, field name should be the same with what you defined in your structure: + +```shell +$ cargo run +Config { id: 1, name: "abc" } + +$ cargo run id=2 +Config { id: 2, name: "abc" } + +$ cargo run name=xyz +Config { id: 1, name: "xyz" } + +$ cargo run id=2 name=xyz +Config { id: 2, name: "xyz" } +``` + +## Roadmap + +- [ ] Support environment variables + - [ ] Merge priority + - [ ] Merge option (choose which to merge) +- [ ] More command line argument formats + - [x] Simple equal format: `{field}={value}` + - [ ] [getopt](https://en.wikipedia.org/wiki/Getopt) format: `-h --{field} {value}` +- [ ] Various use cases + - [ ] Non-panic function + - [ ] Immutable function +- [ ] Field Alias +- [ ] Support complex field type +- [ ] Document +- [ ] Readme +- [ ] ... diff --git a/src/lib.rs b/src/lib.rs index 7d12d9a..02e9f78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,54 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput, Fields}; -#[cfg(test)] -mod tests { - use super::*; +#[proc_macro_derive(MergeCfg)] +pub fn merge_cfg_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let struct_ident = &ast.ident; + + let mut merge_handle = quote! {}; + match ast.data { + Data::Struct(s) => match s.fields { + Fields::Named(fields) => { + for field in &fields.named { + let field_ident = field.ident.as_ref().unwrap(); + let field_name = field_ident.to_string(); + let field_type = field.ty.clone(); + // cast and set value + merge_handle = quote! { + #merge_handle + #field_name => { + self.#field_ident = kv[1].parse::<#field_type>().unwrap() + } + }; + } + } + _ => panic!("MergeCfg can only be derived on structs with named fields"), + }, + _ => panic!("MergeCfg can only be derived on structs"), + } - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + // final expanded code + quote! { + impl #struct_ident { + pub fn merge_cfg(&mut self) { + let args: Vec = ::std::env::args().collect(); + if args.len() > 1 { + for arg in args.iter().skip(1) { + let kv: Vec<&str> = arg.split('=').collect(); + if kv.len() == 2 { + match kv[0] { + #merge_handle + _ => {} + } + } + } + } + } + } } + .into() }