├── docs └── introduction.md ├── i18n ├── zh-hans │ └── introduction.md └── zh-hant │ └── introduction.md ├── .gitignore ├── splash.png ├── src ├── utils │ ├── mod.rs │ └── flatten.rs ├── template │ ├── mod.rs │ ├── auto_structs.rs │ ├── structs.rs │ ├── auto_enums.rs │ └── enums.rs ├── tools │ ├── derive_struct.rs │ ├── derive_enum.rs │ ├── mod.rs │ ├── derive_macros_token.rs │ ├── derive_enum_items.rs │ ├── auto_macros.rs │ └── derive_struct_items.rs └── lib.rs ├── tests ├── across_crate_lib │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── across_crate_entry │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── single_level_struct.rs ├── multi_level_struct.rs ├── reference_type_struct.rs ├── visibility_test.rs ├── array_type_struct.rs ├── option_type_struct.rs ├── default_struct.rs ├── enum_type_struct.rs ├── anonymous_struct.rs ├── auto_macros.rs └── extra_derive_struct.rs ├── .github └── workflows │ ├── fmt.yml │ ├── publish.yml │ ├── clippy.yml │ └── test.yml ├── Cargo.toml ├── examples ├── language_pack.rs └── server_router.rs ├── README.md └── LICENSE /docs/introduction.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /i18n/zh-hans/introduction.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /i18n/zh-hant/introduction.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/celestia-island/yuuka/HEAD/splash.png -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | mod flatten; 2 | 3 | pub(crate) use flatten::flatten; 4 | -------------------------------------------------------------------------------- /tests/across_crate_lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "across_crate_lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | private = false 6 | 7 | [lib] 8 | crate-type = ["rlib", "dylib"] 9 | 10 | [dependencies] 11 | yuuka = { path = "../.." } 12 | -------------------------------------------------------------------------------- /tests/across_crate_entry/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "across_crate_entry" 3 | version = "0.1.0" 4 | edition = "2021" 5 | private = false 6 | 7 | [dependencies] 8 | across_crate_lib = { path = "../across_crate_lib", package = "across_crate_lib" } 9 | 10 | yuuka = { path = "../.." } 11 | -------------------------------------------------------------------------------- /src/template/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod auto_enums; 2 | pub(crate) mod auto_structs; 3 | pub(crate) mod enums; 4 | pub(crate) mod structs; 5 | 6 | pub(crate) use auto_enums::generate_enums_auto_macros; 7 | pub(crate) use auto_structs::generate_structs_auto_macros; 8 | pub(crate) use enums::generate_enums_quote; 9 | pub(crate) use structs::generate_structs_quote; 10 | -------------------------------------------------------------------------------- /tests/single_level_struct.rs: -------------------------------------------------------------------------------- 1 | use yuuka::{derive_enum, derive_struct}; 2 | 3 | #[test] 4 | fn single_level_struct() { 5 | derive_struct!(Root { a: String, b: i32 }); 6 | 7 | let _ = Root { 8 | a: "Hello".to_string(), 9 | b: 42, 10 | }; 11 | } 12 | 13 | #[test] 14 | fn single_level_enum() { 15 | derive_enum!( 16 | enum Root { 17 | A, 18 | B, 19 | } 20 | ); 21 | 22 | let _ = Root::A; 23 | } 24 | -------------------------------------------------------------------------------- /tests/multi_level_struct.rs: -------------------------------------------------------------------------------- 1 | use yuuka::derive_struct; 2 | 3 | #[test] 4 | fn multi_level_struct() { 5 | derive_struct!(Root { 6 | a: String, 7 | b: i32, 8 | c: C { 9 | d: f64, 10 | e: E { f: bool }, 11 | }, 12 | }); 13 | 14 | let _ = Root { 15 | a: "Hello".to_string(), 16 | b: 42, 17 | c: C { 18 | d: std::f64::consts::PI, 19 | e: E { f: true }, 20 | }, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /tests/reference_type_struct.rs: -------------------------------------------------------------------------------- 1 | use yuuka::derive_struct; 2 | 3 | #[derive(Debug, Clone, PartialEq, Default)] 4 | struct C { 5 | d: f64, 6 | } 7 | 8 | #[test] 9 | fn reference_type_struct() { 10 | derive_struct!(Root { 11 | a_b: String, 12 | B: i32, 13 | c: super::C, 14 | }); 15 | 16 | let _ = Root { 17 | a_b: "Hello".to_string(), 18 | B: 42, 19 | c: C { 20 | d: std::f64::consts::PI, 21 | }, 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: Format 2 | on: 3 | pull_request: 4 | paths: 5 | - '.github/workflows/clippy.yml' 6 | - 'src/**/*' 7 | - 'tests/**/*' 8 | - 'Cargo.toml' 9 | push: 10 | branches: [master] 11 | 12 | jobs: 13 | format: 14 | name: Format 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Setup toolchain 21 | uses: dtolnay/rust-toolchain@master 22 | with: 23 | toolchain: nightly 24 | components: rustfmt 25 | 26 | - name: Run fmt 27 | run: cargo +nightly fmt --all -- --check --unstable-features 28 | -------------------------------------------------------------------------------- /tests/visibility_test.rs: -------------------------------------------------------------------------------- 1 | use yuuka::{derive_enum, derive_struct}; 2 | 3 | #[derive(Debug, Clone, PartialEq, Default)] 4 | struct C { 5 | d: f64, 6 | } 7 | 8 | derive_struct!(pub Root { 9 | a_b: String, 10 | B: i32, 11 | c: C, 12 | }); 13 | 14 | #[test] 15 | fn pub_type_struct() { 16 | let _ = Root { 17 | a_b: "Hello".to_string(), 18 | B: 42, 19 | c: C { 20 | d: std::f64::consts::PI, 21 | }, 22 | }; 23 | } 24 | 25 | derive_enum!( 26 | pub enum Root2 { 27 | A, 28 | B, 29 | C, 30 | } 31 | ); 32 | 33 | #[test] 34 | fn pub_type_enum() { 35 | let _ = Root2::A; 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish crates.io 2 | on: 3 | push: 4 | tags: [v*] 5 | 6 | jobs: 7 | publish: 8 | name: Publish to crates.io 9 | runs-on: ubuntu-latest 10 | timeout-minutes: 10 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: dtolnay/rust-toolchain@stable 15 | 16 | - uses: Swatinem/rust-cache@v2 17 | - run: cargo install cargo-release 18 | 19 | - run: cargo login ${{ secrets.CRATES_IO_API_TOKEN }} 20 | - run: |- 21 | cargo release \ 22 | publish \ 23 | --all-features \ 24 | --allow-branch HEAD \ 25 | --no-confirm \ 26 | --no-verify \ 27 | --execute 28 | -------------------------------------------------------------------------------- /tests/across_crate_lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | use yuuka::{derive_enum, derive_struct}; 2 | 3 | derive_struct!( 4 | #[derive(PartialEq)] 5 | #[macro_export] // After the derive macros. 6 | pub TestStruct { 7 | a: i32, 8 | b: String, 9 | c: { 10 | d: i32, 11 | e: String, 12 | } 13 | } 14 | ); 15 | 16 | derive_enum!( 17 | #[macro_export] // Behind the derive macros. 18 | #[derive(PartialEq)] 19 | pub enum TestEnum { 20 | A(i32), 21 | B(String), 22 | C(enum C { 23 | D(i32), 24 | E(String), 25 | F(enum F { 26 | G(i32), 27 | H(String), 28 | }), 29 | }) 30 | } 31 | ); 32 | -------------------------------------------------------------------------------- /tests/across_crate_entry/src/main.rs: -------------------------------------------------------------------------------- 1 | use yuuka::auto; 2 | 3 | use across_crate_lib::*; 4 | 5 | fn main() { 6 | let test_struct = auto!(TestStruct { 7 | a: 1, 8 | b: "Hello".to_string(), 9 | c: { 10 | d: 2, 11 | e: "World".to_string(), 12 | } 13 | }); 14 | assert_eq!( 15 | test_struct, 16 | TestStruct { 17 | a: 1, 18 | b: "Hello".to_string(), 19 | c: _TestStruct_0_anonymous { 20 | d: 2, 21 | e: "World".to_string(), 22 | } 23 | } 24 | ); 25 | 26 | let test_enum = auto!(TestEnum::C::F::H("Hello".to_string())); 27 | assert_eq!(test_enum, TestEnum::C(C::F(F::H("Hello".to_string())))); 28 | } 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yuuka" 3 | version = "0.6.2" 4 | edition = "2021" 5 | 6 | publish = true 7 | authors = ["langyo "] 8 | description = "A helper library to generate complex and nested structures by a simple macro" 9 | license = "Apache-2.0" 10 | repository = "https://github.com/celestia-island/yuuka" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | anyhow = "^1" 17 | async-trait = "^0.1" 18 | derive_more = { version = "^1", features = ["full"] } 19 | 20 | serde = { version = "^1", features = ["derive"] } 21 | serde_json = "^1" 22 | strum = { version = "^0.26", features = ["derive"] } 23 | 24 | syn = { version = "^2", features = ["full"] } 25 | proc-macro2 = { version = "^1", features = ["span-locations"] } 26 | quote = "^1" 27 | -------------------------------------------------------------------------------- /tests/array_type_struct.rs: -------------------------------------------------------------------------------- 1 | use yuuka::derive_struct; 2 | 3 | #[test] 4 | fn array_type_struct() { 5 | derive_struct!(Root { 6 | a: [A { b: String }] 7 | }); 8 | 9 | let _ = Root { 10 | a: vec![A { 11 | b: "hello".to_string(), 12 | }], 13 | }; 14 | } 15 | 16 | #[test] 17 | fn array_type_struct_with_enum() { 18 | derive_struct!(Root { 19 | a: [A { b: String }], 20 | b: [enum AttackType { 21 | Momoi, 22 | Midori, 23 | Yuzu, 24 | Arisu, 25 | }], 26 | }); 27 | 28 | let _ = Root { 29 | a: vec![A { 30 | b: "hello".to_string(), 31 | }], 32 | b: vec![ 33 | AttackType::Momoi, 34 | AttackType::Midori, 35 | AttackType::Yuzu, 36 | AttackType::Arisu, 37 | ], 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Clippy 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/workflows/clippy.yml' 7 | - 'src/**/*' 8 | - 'tests/**/*' 9 | - 'Cargo.toml' 10 | push: 11 | branches: [master] 12 | 13 | jobs: 14 | clippy: 15 | name: Clippy 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | toolchain: 21 | - stable 22 | - nightly 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Setup toolchain 28 | uses: dtolnay/rust-toolchain@master 29 | with: 30 | toolchain: ${{ matrix.toolchain }} 31 | components: clippy 32 | 33 | - uses: Swatinem/rust-cache@v2 34 | 35 | - name: Run clippy 36 | run: | 37 | cargo clippy \ 38 | --all-targets \ 39 | --all-features \ 40 | -- -D warnings 41 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Clippy 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/workflows/clippy.yml' 7 | - 'src/**/*' 8 | - 'tests/**/*' 9 | - 'Cargo.toml' 10 | push: 11 | branches: [master] 12 | 13 | jobs: 14 | clippy: 15 | name: Test 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | toolchain: 21 | - stable 22 | - nightly 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Setup toolchain 28 | uses: dtolnay/rust-toolchain@master 29 | with: 30 | toolchain: ${{ matrix.toolchain }} 31 | 32 | - uses: Swatinem/rust-cache@v2 33 | 34 | - name: Run unit test 35 | run: | 36 | cargo test \ 37 | --all-targets \ 38 | --all-features \ 39 | --no-fail-fast 40 | 41 | - name: Run across crate example test 42 | run: | 43 | cargo run \ 44 | --manifest-path \ 45 | ./tests/across_crate_entry/Cargo.toml 46 | -------------------------------------------------------------------------------- /tests/option_type_struct.rs: -------------------------------------------------------------------------------- 1 | use yuuka::derive_struct; 2 | 3 | #[test] 4 | fn option_type_basic() { 5 | derive_struct!(Root { 6 | a?: String 7 | }); 8 | 9 | let _ = Root { 10 | a: Some("hello".to_string()), 11 | }; 12 | } 13 | 14 | #[test] 15 | fn option_type_struct() { 16 | derive_struct!(Root { 17 | a?: A { b: String } 18 | }); 19 | 20 | let _ = Root { 21 | a: Some(A { 22 | b: "hello".to_string(), 23 | }), 24 | }; 25 | } 26 | 27 | #[test] 28 | fn option_type_struct_with_enum() { 29 | derive_struct!(Root { 30 | a?: A { b: String }, 31 | b?: enum AttackType { 32 | Momoi, 33 | Midori, 34 | Yuzu, 35 | Arisu, 36 | }, 37 | }); 38 | 39 | let _ = Root { 40 | a: None, 41 | b: Some(AttackType::Momoi), 42 | }; 43 | } 44 | 45 | #[test] 46 | fn option_type_struct_anonymous() { 47 | derive_struct!(Root { 48 | a?: { 49 | b: String 50 | }, 51 | }); 52 | 53 | let _ = Root { 54 | a: Some(__Root::_Root_0_anonymous { 55 | b: "hello".to_string(), 56 | }), 57 | }; 58 | } 59 | 60 | #[test] 61 | fn option_type_enum_with_keys() { 62 | derive_struct!(Root { 63 | a?: enum AttackType { 64 | Momoi, 65 | Midori { b?: String }, 66 | Yuzu, 67 | Arisu, 68 | }, 69 | }); 70 | 71 | let _ = Root { 72 | a: Some(AttackType::Midori { 73 | b: Some("hello".to_string()), 74 | }), 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /src/template/auto_structs.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::Ident; 4 | 5 | use crate::tools::{DeriveAutoMacrosVisibility, StructsFlatten}; 6 | 7 | pub(crate) fn generate_structs_auto_macros( 8 | structs: StructsFlatten, 9 | macros_visibility: DeriveAutoMacrosVisibility, 10 | ) -> Vec { 11 | structs 12 | .iter() 13 | .map(|(ident, v, _extra_macros)| { 14 | let rules = v 15 | .iter() 16 | .map(|(name, ty, _, _)| { 17 | quote! { 18 | (#name { $($val:tt)+ }) => { 19 | ::yuuka::auto!(#ty { $($val)+ }) 20 | }; 21 | } 22 | }) 23 | .collect::>(); 24 | let rules = quote! { 25 | #(#rules)* 26 | }; 27 | 28 | let ident = Ident::new(format!("__auto_{}", ident).as_str(), ident.span()); 29 | if macros_visibility == DeriveAutoMacrosVisibility::Public { 30 | quote! { 31 | #[doc(hidden)] 32 | #[macro_export] 33 | macro_rules! #ident { 34 | () => {}; 35 | 36 | #rules 37 | 38 | ($name:ident $val:expr) => { 39 | $val 40 | }; 41 | } 42 | } 43 | } else { 44 | quote! { 45 | #[doc(hidden)] 46 | macro_rules! #ident { 47 | () => {}; 48 | 49 | #rules 50 | 51 | ($name:ident $val:expr) => { 52 | $val 53 | }; 54 | } 55 | } 56 | } 57 | }) 58 | .collect::>() 59 | } 60 | -------------------------------------------------------------------------------- /tests/default_struct.rs: -------------------------------------------------------------------------------- 1 | use yuuka::derive_struct; 2 | 3 | #[test] 4 | fn default_struct() { 5 | derive_struct!(Root { 6 | a: String = "hello".to_string(), 7 | b: i32 = 42, 8 | c: String, 9 | }); 10 | 11 | let val = Root::default(); 12 | assert_eq!(val.a, "hello".to_string()); 13 | assert_eq!(val.b, 42); 14 | assert_eq!(val.c, String::default()); 15 | } 16 | 17 | #[test] 18 | fn empty_default_array() { 19 | derive_struct!(Root { 20 | a: [Item { 21 | b: String = "hello".to_string() 22 | }] 23 | }); 24 | 25 | let val = Root::default(); 26 | assert_eq!(val.a.len(), 0); 27 | } 28 | 29 | #[test] 30 | fn default_array() { 31 | derive_struct!(Root { 32 | a: [Item { 33 | b: String = "hello".to_string() 34 | }] = vec![Item { 35 | b: "world".to_string() 36 | }] 37 | }); 38 | 39 | let mut val = Root::default(); 40 | assert_eq!(val.a.len(), 1); 41 | assert_eq!(val.a[0].b, "world"); 42 | val.a.push(Default::default()); 43 | assert_eq!(val.a.len(), 2); 44 | assert_eq!(val.a[1].b, "hello"); 45 | } 46 | 47 | #[test] 48 | fn default_enum() { 49 | derive_struct!( 50 | #[derive(PartialEq)] 51 | Root { 52 | a: enum Member { 53 | Momoi, 54 | Midori, 55 | Yuzu, 56 | Arisu, 57 | } = Midori 58 | } 59 | ); 60 | 61 | let val = Root::default(); 62 | assert_eq!(val.a, Member::Midori); 63 | } 64 | 65 | #[test] 66 | fn default_enum_array() { 67 | derive_struct!( 68 | #[derive(PartialEq)] 69 | Root { 70 | a: [enum Member { 71 | Momoi, 72 | Midori, 73 | Yuzu, 74 | Arisu, 75 | } = Midori] = vec![Member::Arisu] 76 | } 77 | ); 78 | 79 | let mut val = Root::default(); 80 | assert_eq!(val.a.len(), 1); 81 | val.a.push(Default::default()); 82 | assert_eq!(val.a.len(), 2); 83 | assert_eq!(val.a[0], Member::Arisu); 84 | assert_eq!(val.a[1], Member::Midori); 85 | } 86 | -------------------------------------------------------------------------------- /examples/language_pack.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use serde::{Deserialize, Serialize}; 3 | use yuuka::{auto, derive_struct}; 4 | 5 | fn main() -> Result<()> { 6 | derive_struct!( 7 | #[derive(PartialEq, Serialize, Deserialize)] 8 | LanguagePack { 9 | 是: String, 10 | 否: String, 11 | 确认: String, 12 | 取消: String, 13 | 保存: String, 14 | 主页: { 15 | 启动: String, 16 | 设置: String, 17 | }, 18 | 设置: { 19 | 虚拟机路径: String, 20 | 程序本体路径: String, 21 | 网络配置: { 22 | 网络配置: String, 23 | 是否启用代理: String, 24 | 代理地址: String, 25 | 是否启用IPV6: String, 26 | } 27 | } 28 | } 29 | ); 30 | 31 | let config = auto!(LanguagePack { 32 | 是: "Yes".to_string(), 33 | 否: "No".to_string(), 34 | 确认: "Confirm".to_string(), 35 | 取消: "Cancel".to_string(), 36 | 保存: "Save".to_string(), 37 | 主页: { 38 | 启动: "Start".to_string(), 39 | 设置: "Settings".to_string(), 40 | }, 41 | 设置: { 42 | 虚拟机路径: "VM Path".to_string(), 43 | 程序本体路径: "Program Path".to_string(), 44 | 网络配置: { 45 | 网络配置: "Network Config".to_string(), 46 | 是否启用代理: "Enable Proxy".to_string(), 47 | 代理地址: "Proxy Address".to_string(), 48 | 是否启用IPV6: "Enable IPV6".to_string(), 49 | } 50 | } 51 | }); 52 | 53 | let json_raw = r#" 54 | { 55 | "是": "Yes", 56 | "否": "No", 57 | "确认": "Confirm", 58 | "取消": "Cancel", 59 | "保存": "Save", 60 | "主页": { 61 | "启动": "Start", 62 | "设置": "Settings" 63 | }, 64 | "设置": { 65 | "虚拟机路径": "VM Path", 66 | "程序本体路径": "Program Path", 67 | "网络配置": { 68 | "网络配置": "Network Config", 69 | "是否启用代理": "Enable Proxy", 70 | "代理地址": "Proxy Address", 71 | "是否启用IPV6": "Enable IPV6" 72 | } 73 | } 74 | } 75 | "#; 76 | 77 | let config_from_json = serde_json::from_str::(json_raw)?; 78 | assert_eq!(config, config_from_json); 79 | assert_eq!(config.设置.网络配置.代理地址, "Proxy Address"); 80 | Ok(()) 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | yuuka 2 | 3 | ![Crates.io License](https://img.shields.io/crates/l/yuuka) 4 | [![Crates.io Version](https://img.shields.io/crates/v/yuuka)](https://docs.rs/yuuka) 5 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/celestia-island/yuuka/test.yml) 6 | 7 | ## Introduction 8 | 9 | This is a helper library to generate complex and nested structures by a simple macro. It is based on the `serde` library that is used to serialize and deserialize data in Rust. 10 | 11 | The name `yuuka` comes from the character [Yuuka](https://bluearchive.wiki/wiki/Yuuka) in the game [Blue Archive](https://bluearchive.jp/). 12 | 13 | > Still in development, the API may change in the future. 14 | 15 | ## Quick Start 16 | 17 | ```rust 18 | use serde::{Serialize, Deserialize}; 19 | use yuuka::derive_struct; 20 | 21 | derive_struct!( 22 | #[derive(Serialize, Deserialize)] 23 | GameDevelopment { 24 | description: String, 25 | members: Members { 26 | script_writer: String, 27 | illustrator: String, 28 | programmer: String, 29 | tester: Vec, 30 | }, 31 | projects: [Project { 32 | project_name: String, 33 | engine: String, 34 | }], 35 | } 36 | ); 37 | 38 | let config = auto!(GameDevelopment { 39 | description: "A game development team".to_string(), 40 | members: { 41 | script_writer: "Momoi".to_string(), 42 | illustrator: "Midori".to_string(), 43 | programmer: "Yuzu".to_string(), 44 | tester: vec!["Arisu".to_string(), "Key".to_string()], 45 | }, 46 | projects: vec![ 47 | Project { 48 | project_name: "777 Game Launcher".to_string(), 49 | engine: "Tauri".to_string(), 50 | }, 51 | Project { 52 | project_name: "Blue Archive".to_string(), 53 | engine: "Unity".to_string(), 54 | }, 55 | ] 56 | }); 57 | ``` 58 | 59 | More information can be found in the documentation ([English](https://github.com/celestia-island/yuuka/tree/master/docs/introduction.md) | [简体中文](https://github.com/celestia-island/yuuka/tree/master/i18n/zh-hans/introduction.md) | [繁體中文](https://github.com/celestia-island/yuuka/tree/master/i18n/zh-hant/introduction.md)). 60 | -------------------------------------------------------------------------------- /src/tools/derive_struct.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use std::{cell::RefCell, rc::Rc}; 3 | use syn::{ 4 | braced, 5 | parse::{Parse, ParseStream}, 6 | Ident, Token, TypePath, 7 | }; 8 | 9 | use super::{DeriveStructItems, DeriveVisibility, ExtraMacros, StructMembers, StructName}; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct DeriveStruct { 13 | pub visibility: DeriveVisibility, 14 | pub ident: StructName, 15 | pub items: StructMembers, 16 | pub extra_macros: ExtraMacros, 17 | } 18 | 19 | impl DeriveStruct { 20 | pub fn pin_unique_id(&self, root_name: String, id: Rc>) -> Self { 21 | let mut ret = self.clone(); 22 | ret.ident = ret.ident.pin_unique_id(root_name, *id.borrow()); 23 | *id.borrow_mut() += 1; 24 | ret 25 | } 26 | 27 | pub fn extend_derive_macros(&self, extra_macros: Vec) -> Self { 28 | let mut ret = self.clone(); 29 | ret.extra_macros.extend_derive_macros(extra_macros); 30 | ret 31 | } 32 | 33 | pub fn extend_attr_macros(&self, extra_macros: Vec) -> Self { 34 | let mut ret = self.clone(); 35 | ret.extra_macros.extend_attr_macros(extra_macros); 36 | ret 37 | } 38 | 39 | pub fn extend_attr_macros_recursive(&self, extra_macros: Vec) -> Self { 40 | let mut ret = self.clone(); 41 | ret.extra_macros.extend_attr_macros_recursive(extra_macros); 42 | ret 43 | } 44 | } 45 | 46 | impl Parse for DeriveStruct { 47 | fn parse(input: ParseStream) -> syn::Result { 48 | let extra_macros = if input.peek(Token![#]) { 49 | input.parse::()? 50 | } else { 51 | Default::default() 52 | }; 53 | 54 | let visibility = if input.peek(Token![pub]) { 55 | input.parse::()?; 56 | DeriveVisibility::Public 57 | } else { 58 | DeriveVisibility::PublicOnCrate 59 | }; 60 | 61 | let ident: StructName = if input.peek(Ident) { 62 | StructName::Named(input.parse()?) 63 | } else { 64 | StructName::Unnamed(None) 65 | }; 66 | 67 | let content; 68 | braced!(content in input); 69 | let content: DeriveStructItems = content.parse()?; 70 | 71 | Ok(DeriveStruct { 72 | visibility, 73 | ident, 74 | items: content.items, 75 | extra_macros, 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/server_router.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use serde::{Deserialize, Serialize}; 3 | use yuuka::{auto, derive_struct}; 4 | 5 | fn main() -> Result<()> { 6 | derive_struct!( 7 | #[derive(PartialEq, Serialize, Deserialize)] 8 | Config { 9 | port: u16, 10 | services: [Service { 11 | domain: Vec, 12 | rules: [Rule { 13 | pattern: String, 14 | method: enum Method { 15 | Redirect { url: String }, 16 | Proxy { host: String }, 17 | StaticFile { path: String }, 18 | StaticDir { path: String }, 19 | } 20 | }] 21 | }] 22 | } 23 | ); 24 | 25 | let config = auto!(Config { 26 | port: 8080, 27 | services: vec![Service { 28 | domain: vec!["example.com".to_string()], 29 | rules: vec![ 30 | Rule { 31 | pattern: "^/$".to_string(), 32 | method: Method::Redirect { 33 | url: "https://example.com/index.html".to_string() 34 | } 35 | }, 36 | Rule { 37 | pattern: "^/api".to_string(), 38 | method: Method::Proxy { 39 | host: "http://localhost:8081".to_string() 40 | } 41 | }, 42 | Rule { 43 | pattern: "^/static".to_string(), 44 | method: Method::StaticDir { 45 | path: "/var/www/static".to_string() 46 | } 47 | } 48 | ] 49 | }] 50 | }); 51 | 52 | let json_raw = r#" 53 | { 54 | "port": 8080, 55 | "services": [ 56 | { 57 | "domain": ["example.com"], 58 | "rules": [ 59 | { 60 | "pattern": "^/$", 61 | "method": { 62 | "Redirect": { 63 | "url": "https://example.com/index.html" 64 | } 65 | } 66 | }, 67 | { 68 | "pattern": "^/api", 69 | "method": { 70 | "Proxy": { 71 | "host": "http://localhost:8081" 72 | } 73 | } 74 | }, 75 | { 76 | "pattern": "^/static", 77 | "method": { 78 | "StaticDir": { 79 | "path": "/var/www/static" 80 | } 81 | } 82 | } 83 | ] 84 | } 85 | ] 86 | } 87 | "#; 88 | 89 | let config_from_json = serde_json::from_str::(json_raw)?; 90 | assert_eq!(config, config_from_json); 91 | Ok(()) 92 | } 93 | -------------------------------------------------------------------------------- /src/tools/derive_enum.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use std::{cell::RefCell, rc::Rc}; 3 | use syn::{ 4 | braced, 5 | parse::{Parse, ParseStream}, 6 | Expr, Ident, Token, TypePath, 7 | }; 8 | 9 | use super::{DeriveEnumItems, DeriveVisibility, EnumMembers, ExtraMacros, StructName}; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct DeriveEnum { 13 | pub visibility: DeriveVisibility, 14 | pub ident: StructName, 15 | pub items: EnumMembers, 16 | pub default_value: Option, 17 | pub extra_macros: ExtraMacros, 18 | } 19 | 20 | impl DeriveEnum { 21 | pub fn pin_unique_id(&self, root_name: String, id: Rc>) -> Self { 22 | let mut ret = self.clone(); 23 | ret.ident = ret.ident.pin_unique_id(root_name, *id.borrow()); 24 | *id.borrow_mut() += 1; 25 | ret 26 | } 27 | 28 | pub fn extend_derive_macros(&self, extra_macros: Vec) -> Self { 29 | let mut ret = self.clone(); 30 | ret.extra_macros.extend_derive_macros(extra_macros); 31 | ret 32 | } 33 | 34 | pub fn extend_attr_macros(&self, extra_macros: Vec) -> Self { 35 | let mut ret = self.clone(); 36 | ret.extra_macros.extend_attr_macros(extra_macros); 37 | ret 38 | } 39 | 40 | pub fn extend_attr_macros_recursive(&self, extra_macros: Vec) -> Self { 41 | let mut ret = self.clone(); 42 | ret.extra_macros.extend_attr_macros_recursive(extra_macros); 43 | ret 44 | } 45 | } 46 | 47 | impl Parse for DeriveEnum { 48 | fn parse(input: ParseStream) -> syn::Result { 49 | let extra_macros = if input.peek(Token![#]) { 50 | input.parse::()? 51 | } else { 52 | Default::default() 53 | }; 54 | 55 | let visibility = if input.peek(Token![pub]) { 56 | input.parse::()?; 57 | DeriveVisibility::Public 58 | } else { 59 | DeriveVisibility::PublicOnCrate 60 | }; 61 | 62 | input.parse::()?; 63 | let ident: StructName = if input.peek(Ident) { 64 | StructName::Named(input.parse()?) 65 | } else { 66 | StructName::Unnamed(None) 67 | }; 68 | let content; 69 | braced!(content in input); 70 | let content: DeriveEnumItems = content.parse()?; 71 | 72 | if input.peek(Token![=]) { 73 | input.parse::()?; 74 | let default_value = input.parse::()?; 75 | 76 | Ok(DeriveEnum { 77 | visibility, 78 | ident, 79 | items: content.items, 80 | default_value: Some(default_value), 81 | extra_macros, 82 | }) 83 | } else { 84 | Ok(DeriveEnum { 85 | visibility, 86 | ident, 87 | items: content.items, 88 | default_value: None, 89 | extra_macros, 90 | }) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/template/structs.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::tools::{DefaultValue, StructsFlatten}; 5 | 6 | pub(crate) fn generate_structs_quote(structs: StructsFlatten) -> Vec { 7 | structs 8 | .iter() 9 | .map(|(ident, v, extra_macros)| { 10 | let keys = v 11 | .iter() 12 | .map(|(key, ty, _default_value, extra_macros)| { 13 | let extra_macros = extra_macros 14 | .iter() 15 | .map(|content| { 16 | quote! { 17 | #[#content] 18 | } 19 | }) 20 | .collect::>(); 21 | 22 | quote! { 23 | #(#extra_macros)* 24 | pub #key: #ty, 25 | } 26 | }) 27 | .collect::>(); 28 | 29 | let derive_macros = extra_macros.derive_macros.clone(); 30 | let attr_macros = extra_macros.attr_macros.clone(); 31 | 32 | let derive_macros = if derive_macros.is_empty() { 33 | quote! {} 34 | } else { 35 | quote! { 36 | #[derive(#(#derive_macros),*)] 37 | } 38 | }; 39 | let attr_macros = if attr_macros.is_empty() { 40 | quote! {} 41 | } else { 42 | let list = attr_macros 43 | .iter() 44 | .map(|content| { 45 | quote! { 46 | #[#content] 47 | } 48 | }) 49 | .collect::>(); 50 | quote! { 51 | #(#list)* 52 | } 53 | }; 54 | 55 | if v.iter() 56 | .all(|(_, _, default_value, _)| default_value == &DefaultValue::None) 57 | { 58 | quote! { 59 | #[derive(Debug, Clone, Default)] 60 | #derive_macros 61 | #attr_macros 62 | pub struct #ident { 63 | #( #keys )* 64 | } 65 | } 66 | } else { 67 | let default_values = v 68 | .iter() 69 | .map(|(key, _, default_value, _)| match default_value { 70 | DefaultValue::None => quote! { 71 | #key: Default::default(), 72 | }, 73 | DefaultValue::Single(v) => quote! { 74 | #key: #v, 75 | }, 76 | DefaultValue::Array(v) => quote! { 77 | #key: vec![#(#v),*], 78 | }, 79 | }) 80 | .collect::>(); 81 | 82 | quote! { 83 | #[derive(Debug, Clone)] 84 | #derive_macros 85 | #attr_macros 86 | pub struct #ident { 87 | #( #keys )* 88 | } 89 | 90 | impl Default for #ident { 91 | fn default() -> Self { 92 | Self { 93 | #( #default_values )* 94 | } 95 | } 96 | } 97 | } 98 | } 99 | }) 100 | .collect::>() 101 | } 102 | -------------------------------------------------------------------------------- /src/template/auto_enums.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::Ident; 4 | 5 | use crate::tools::{DeriveAutoMacrosVisibility, EnumValueFlatten, EnumsFlatten}; 6 | 7 | pub(crate) fn generate_enums_auto_macros( 8 | enums: EnumsFlatten, 9 | macros_visibility: DeriveAutoMacrosVisibility, 10 | ) -> Vec { 11 | enums 12 | .iter() 13 | .map(|(ident, v, _default_value, _extra_macros)| { 14 | let rules = v 15 | .iter() 16 | .map(|(name, ty, _)| match ty { 17 | EnumValueFlatten::Empty => { 18 | quote! {} 19 | } 20 | EnumValueFlatten::Struct(items) => { 21 | let list = items 22 | .iter() 23 | .map(|(key, ty, _default_value, _extra_macros)| { 24 | quote! { 25 | (#name #key { $($val:tt)+ }) => { 26 | ::yuuka::auto!(#ty { $($val)+ }) 27 | }; 28 | } 29 | }) 30 | .collect::>(); 31 | quote! { 32 | #(#list)* 33 | } 34 | } 35 | EnumValueFlatten::Tuple(items) => { 36 | let list = items 37 | .iter() 38 | .enumerate() 39 | .map(|(i, ty)| { 40 | let i = syn::Index::from(i); 41 | quote! { 42 | (#name #i $($val:tt)+) => { 43 | ::yuuka::auto!(#ty::$($val)+) 44 | }; 45 | 46 | (#name #i { $($val:tt)+ }) => { 47 | ::yuuka::auto!(#ty { $($val)+ }) 48 | }; 49 | } 50 | }) 51 | .collect::>(); 52 | quote! { 53 | #(#list)* 54 | } 55 | } 56 | }) 57 | .collect::>(); 58 | let rules = quote! { 59 | #(#rules)* 60 | }; 61 | 62 | let ident = Ident::new(format!("__auto_{}", ident).as_str(), ident.span()); 63 | if macros_visibility == DeriveAutoMacrosVisibility::Public { 64 | quote! { 65 | #[doc(hidden)] 66 | #[macro_export] 67 | macro_rules! #ident { 68 | () => {}; 69 | 70 | #rules 71 | 72 | ($name:ident $key:ident $val:expr) => { 73 | $val 74 | }; 75 | ($name:ident $val:expr) => { 76 | $val 77 | }; 78 | } 79 | } 80 | } else { 81 | quote! { 82 | #[doc(hidden)] 83 | macro_rules! #ident { 84 | () => {}; 85 | 86 | #rules 87 | 88 | ($name:ident $key:ident $val:expr) => { 89 | $val 90 | }; 91 | ($name:ident $val:expr) => { 92 | $val 93 | }; 94 | } 95 | } 96 | } 97 | }) 98 | .collect::>() 99 | } 100 | -------------------------------------------------------------------------------- /src/tools/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use proc_macro2::TokenStream; 3 | use syn::{Expr, Ident, TypePath}; 4 | 5 | pub(crate) mod auto_macros; 6 | pub(crate) mod derive_enum; 7 | pub(crate) mod derive_enum_items; 8 | pub(crate) mod derive_macros_token; 9 | pub(crate) mod derive_struct; 10 | pub(crate) mod derive_struct_items; 11 | 12 | pub(crate) use auto_macros::AutoMacros; 13 | pub(crate) use derive_enum::DeriveEnum; 14 | pub(crate) use derive_enum_items::DeriveEnumItems; 15 | pub(crate) use derive_macros_token::ExtraMacros; 16 | pub(crate) use derive_struct::DeriveStruct; 17 | pub(crate) use derive_struct_items::DeriveStructItems; 18 | 19 | #[derive(Debug, Clone, Copy, PartialEq, Default)] 20 | pub enum DeriveVisibility { 21 | Public, 22 | #[default] 23 | PublicOnCrate, 24 | } 25 | 26 | #[derive(Debug, Clone, Copy, PartialEq, Default)] 27 | pub enum DeriveAutoMacrosVisibility { 28 | Public, 29 | #[default] 30 | PublicOnCrate, 31 | } 32 | 33 | #[derive(Debug, Clone, PartialEq)] 34 | pub(crate) enum DefaultValue { 35 | None, 36 | Single(Box), 37 | Array(Vec), 38 | } 39 | 40 | #[derive(Debug, Clone, PartialEq)] 41 | pub(crate) enum StructName { 42 | Named(Ident), 43 | Unnamed(Option<(String, usize)>), 44 | } 45 | 46 | impl StructName { 47 | pub(crate) fn to_ident(&self) -> Result { 48 | Ok(match self { 49 | StructName::Named(v) => v.clone(), 50 | StructName::Unnamed(Some((root_name, v))) => Ident::new( 51 | &format!("_{}_{}_anonymous", root_name, v), 52 | proc_macro2::Span::call_site(), 53 | ), 54 | _ => { 55 | return Err(syn::Error::new( 56 | proc_macro2::Span::call_site(), 57 | "Unnamed struct is not supported", 58 | )) 59 | } 60 | }) 61 | } 62 | 63 | pub(crate) fn pin_unique_id(&self, root_name: String, unique_id: usize) -> Self { 64 | match self { 65 | StructName::Named(v) => StructName::Named(v.clone()), 66 | StructName::Unnamed(v) => { 67 | if let Some(v) = v { 68 | StructName::Unnamed(Some(v.clone())) 69 | } else { 70 | StructName::Unnamed(Some((root_name, unique_id))) 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | #[derive(Debug, Clone)] 78 | pub(crate) enum StructType { 79 | Static(TypePath), 80 | InlineStruct(Box), 81 | InlineEnum(Box), 82 | } 83 | 84 | #[derive(Debug, Clone)] 85 | pub(crate) enum EnumValue { 86 | Empty, 87 | Tuple(Vec<(StructType, ExtraTypeWrapper)>), 88 | Struct(StructMembers), 89 | } 90 | 91 | #[derive(Debug, Clone, Copy, PartialEq)] 92 | pub(crate) enum ExtraTypeWrapper { 93 | Default, 94 | Vec, 95 | Option, 96 | OptionVec, 97 | } 98 | 99 | #[derive(Debug, Clone)] 100 | pub(crate) struct ExtraMacrosFlatten { 101 | pub(crate) derive_macros: Vec, 102 | pub(crate) attr_macros: Vec, 103 | } 104 | 105 | pub(crate) type StructMembers = Vec<( 106 | Ident, 107 | StructType, 108 | ExtraTypeWrapper, 109 | DefaultValue, 110 | ExtraMacros, 111 | )>; 112 | pub(crate) type EnumMembers = Vec<(Ident, EnumValue, ExtraMacros)>; 113 | 114 | #[derive(Debug, Clone)] 115 | pub(crate) enum EnumValueFlatten { 116 | Empty, 117 | Tuple(Vec), 118 | Struct(Vec<(Ident, TypePath, DefaultValue, Vec)>), 119 | } 120 | pub(crate) type StructsFlatten = Vec<( 121 | Ident, 122 | Vec<(Ident, TypePath, DefaultValue, Vec)>, 123 | ExtraMacrosFlatten, 124 | )>; 125 | pub(crate) type EnumsFlatten = Vec<( 126 | Ident, 127 | Vec<(Ident, EnumValueFlatten, Vec)>, 128 | DefaultValue, 129 | ExtraMacrosFlatten, 130 | )>; 131 | 132 | #[derive(Debug, Clone)] 133 | pub(crate) enum DeriveBox { 134 | Struct(Box), 135 | Enum(Box), 136 | } 137 | -------------------------------------------------------------------------------- /tests/enum_type_struct.rs: -------------------------------------------------------------------------------- 1 | use yuuka::derive_struct; 2 | 3 | #[test] 4 | fn enum_type_struct() { 5 | derive_struct!(Root { 6 | a: enum Member { 7 | Momoi, 8 | Midori, 9 | Yuzu, 10 | Arisu, 11 | }, 12 | }); 13 | 14 | let _ = Root { a: Member::Momoi }; 15 | } 16 | 17 | #[test] 18 | fn enum_type_struct_with_braces() { 19 | derive_struct!(Root { 20 | a: enum Member { 21 | Momoi { 22 | skill: Skill { 23 | name: String 24 | } 25 | }, 26 | Midori { skills: Vec, level: usize }, 27 | Yuzu { 28 | skill: SkillYuzu { 29 | name: String 30 | }, 31 | level: usize 32 | }, 33 | Arisu { level: usize }, 34 | }, 35 | }); 36 | 37 | let _ = Root { 38 | a: Member::Midori { 39 | skills: vec!["hello".to_string()], 40 | level: 1, 41 | }, 42 | }; 43 | } 44 | 45 | #[test] 46 | fn enum_type_struct_with_parentheses() { 47 | derive_struct!(Root { 48 | a: enum Member { 49 | Momoi (Skill { 50 | name: String 51 | }), 52 | Midori (Vec, usize), 53 | Yuzu ( 54 | SkillYuzu { 55 | name: String 56 | }, 57 | usize 58 | ), 59 | Arisu (usize), 60 | }, 61 | }); 62 | 63 | let _ = Root { 64 | a: Member::Midori(vec!["hello".to_string()], 1), 65 | }; 66 | } 67 | 68 | #[test] 69 | fn enum_type_struct_array_with_parentheses() { 70 | derive_struct!(Root { 71 | a: [enum Member { 72 | Momoi (Skill { 73 | name: String 74 | }), 75 | Midori (Vec, usize), 76 | Yuzu ( 77 | SkillYuzu { 78 | name: String 79 | }, 80 | usize 81 | ), 82 | Arisu (usize), 83 | }], 84 | }); 85 | 86 | let _ = Root { 87 | a: vec![Member::Midori(vec!["hello".to_string()], 1)], 88 | }; 89 | } 90 | 91 | #[test] 92 | fn enum_type_struct_with_enum_in_braces() { 93 | derive_struct!(Root { 94 | a: enum Member { 95 | Momoi, 96 | Midori, 97 | Yuzu, 98 | Arisu { ty: enum ArisuType { 99 | Arisu, 100 | Key 101 | } }, 102 | }, 103 | }); 104 | 105 | let _ = Root { 106 | a: Member::Arisu { ty: ArisuType::Key }, 107 | }; 108 | } 109 | 110 | #[test] 111 | fn enum_type_struct_with_enum_array_in_braces() { 112 | derive_struct!(Root { 113 | a: enum Member { 114 | Momoi, 115 | Midori, 116 | Yuzu, 117 | Arisu { ty: [enum ArisuType { 118 | Arisu, 119 | Key 120 | }] }, 121 | }, 122 | }); 123 | 124 | let _ = Root { 125 | a: Member::Arisu { 126 | ty: vec![ArisuType::Key], 127 | }, 128 | }; 129 | } 130 | 131 | #[test] 132 | fn enum_type_struct_with_enum_in_parentheses() { 133 | derive_struct!(Root { 134 | a: enum Member { 135 | Momoi, 136 | Midori, 137 | Yuzu, 138 | Arisu(enum ArisuType { 139 | Arisu, 140 | Key 141 | }), 142 | }, 143 | }); 144 | 145 | let _ = Root { 146 | a: Member::Arisu(ArisuType::Key), 147 | }; 148 | } 149 | 150 | #[test] 151 | fn enum_type_struct_with_enum_array_in_parentheses() { 152 | derive_struct!(Root { 153 | a: enum Member { 154 | Momoi, 155 | Midori, 156 | Yuzu, 157 | Arisu([enum ArisuType { 158 | Arisu, 159 | Key 160 | }]), 161 | }, 162 | }); 163 | 164 | let _ = Root { 165 | a: Member::Arisu(vec![ArisuType::Key]), 166 | }; 167 | } 168 | -------------------------------------------------------------------------------- /tests/anonymous_struct.rs: -------------------------------------------------------------------------------- 1 | use yuuka::derive_struct; 2 | 3 | #[test] 4 | fn derive_struct_anonymously() { 5 | derive_struct!(Root { 6 | a: { 7 | b: String 8 | } 9 | }); 10 | } 11 | 12 | #[test] 13 | fn derive_struct_anonymously_multiple() { 14 | derive_struct!(Root { 15 | a: { 16 | b: String 17 | }, 18 | c: { 19 | d: f64 20 | } 21 | }); 22 | } 23 | 24 | #[test] 25 | fn derive_enum_anonymously() { 26 | derive_struct!(Root { 27 | a: enum { 28 | Momoi, 29 | Midori, 30 | Yuzu, 31 | Arisu, 32 | } 33 | }); 34 | } 35 | 36 | #[test] 37 | fn derive_enum_anonymously_multiple() { 38 | derive_struct!(Root { 39 | a: enum { 40 | Momoi, 41 | Midori, 42 | Yuzu, 43 | Arisu, 44 | }, 45 | b: enum { 46 | Apple, 47 | Pen, 48 | Pineapple, 49 | ApplePen, 50 | } 51 | }); 52 | } 53 | 54 | #[test] 55 | fn derive_struct_anonymously_with_braces() { 56 | derive_struct!(Root { 57 | a: { 58 | b: String, 59 | c: { 60 | d: f64 = std::f64::consts::PI, 61 | e: { 62 | f: bool = false, 63 | }, 64 | }, 65 | g: { 66 | h: i32 = -114514, 67 | } 68 | }, 69 | i: { 70 | j: String = "いいよ,こいよ".to_string(), 71 | } 72 | }); 73 | 74 | let root = Root::default(); 75 | assert_eq!(root.a.b, String::default()); 76 | assert_eq!(root.a.c.d, std::f64::consts::PI); 77 | assert!(!root.a.c.e.f); 78 | assert_eq!(root.a.g.h, -114514); 79 | assert_eq!(root.i.j, "いいよ,こいよ".to_string()); 80 | } 81 | 82 | #[test] 83 | fn derive_struct_anonymously_with_array() { 84 | derive_struct!(Root { 85 | a: [{ 86 | b: String, 87 | }] 88 | }); 89 | } 90 | 91 | #[test] 92 | fn derive_struct_anonymously_with_enum_array() { 93 | derive_struct!(Root { 94 | a: [enum { 95 | Momoi, 96 | Midori, 97 | Yuzu, 98 | Arisu, 99 | }], 100 | }); 101 | } 102 | 103 | #[test] 104 | fn derive_struct_anonymously_with_default_value() { 105 | derive_struct!(Root { 106 | a: { 107 | b: String = "Hello".to_string() 108 | }, 109 | }); 110 | } 111 | 112 | #[test] 113 | fn derive_struct_anonymously_with_default_array() { 114 | derive_struct!(Root { 115 | a: Vec = vec!["Hello".to_string()], 116 | }); 117 | } 118 | 119 | #[test] 120 | fn derive_struct_anonymously_with_default_enum() { 121 | derive_struct!(Root { 122 | a: enum { 123 | Momoi, 124 | Midori, 125 | Yuzu, 126 | Arisu, 127 | } = Midori, 128 | }); 129 | derive_struct!(Root2 { 130 | a: [enum { 131 | Momoi, 132 | Midori, 133 | Yuzu, 134 | Arisu, 135 | } = Midori], 136 | }); 137 | } 138 | 139 | #[test] 140 | fn derive_struct_anonymously_with_default_enum_array() { 141 | derive_struct!( 142 | #[derive(PartialEq)] 143 | Root { 144 | a: [ 145 | enum { 146 | Momoi, 147 | Midori, 148 | Yuzu, 149 | Arisu, 150 | } = Midori 151 | ], 152 | } 153 | ); 154 | derive_struct!( 155 | #[derive(PartialEq)] 156 | Root2 { 157 | a: [enum { 158 | Momoi, 159 | Midori, 160 | Yuzu, 161 | Arisu(usize), 162 | } = Arisu(233)], 163 | } 164 | ); 165 | 166 | let mut root = Root::default(); 167 | root.a.push(Default::default()); 168 | assert_eq!(root.a, vec![__Root::_Root_0_anonymous::Midori]); 169 | let mut root2 = Root2::default(); 170 | root2.a.push(Default::default()); 171 | assert_eq!(root2.a, vec![__Root2::_Root2_0_anonymous::Arisu(233)]); 172 | } 173 | -------------------------------------------------------------------------------- /src/template/enums.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::tools::{DefaultValue, EnumValueFlatten, EnumsFlatten}; 5 | 6 | pub(crate) fn generate_enums_quote(enums: EnumsFlatten) -> Vec { 7 | enums 8 | .iter() 9 | .map(|(k, v, default_value, extra_macros)| { 10 | let keys = v 11 | .iter() 12 | .map(|(key, ty, extra_macros)| { 13 | let extra_macros = extra_macros 14 | .iter() 15 | .map(|content| { 16 | quote! { 17 | #[#content] 18 | } 19 | }) 20 | .collect::>(); 21 | 22 | match ty { 23 | EnumValueFlatten::Empty => quote! { 24 | #(#extra_macros)* 25 | #key, 26 | }, 27 | EnumValueFlatten::Tuple(v) => quote! { 28 | #(#extra_macros)* 29 | #key(#(#v),*), 30 | }, 31 | EnumValueFlatten::Struct(v) => { 32 | let keys = v 33 | .iter() 34 | .map(|(key, ty, _default_value, extra_macros)| { 35 | let extra_macros = extra_macros 36 | .iter() 37 | .map(|content| { 38 | quote! { 39 | #[#content] 40 | } 41 | }) 42 | .collect::>(); 43 | 44 | quote! { 45 | #(#extra_macros)* 46 | #key: #ty, 47 | } 48 | }) 49 | .collect::>(); 50 | 51 | quote! { 52 | #(#extra_macros)* 53 | #key { 54 | #( #keys )* 55 | }, 56 | } 57 | } 58 | } 59 | }) 60 | .collect::>(); 61 | let default_value = if let DefaultValue::Single(default_value) = default_value { 62 | quote! { 63 | impl Default for #k { 64 | fn default() -> Self { 65 | #default_value 66 | } 67 | } 68 | } 69 | } else { 70 | quote! { 71 | impl Default for #k { 72 | fn default() -> Self { 73 | unimplemented!("Default value for enum is not implemented"); 74 | } 75 | } 76 | } 77 | }; 78 | 79 | let derive_macros = extra_macros.derive_macros.clone(); 80 | let attr_macros = extra_macros.attr_macros.clone(); 81 | 82 | let derive_macros = if derive_macros.is_empty() { 83 | quote! {} 84 | } else { 85 | quote! { 86 | #[derive(#(#derive_macros),*)] 87 | } 88 | }; 89 | let attr_macros = if attr_macros.is_empty() { 90 | quote! {} 91 | } else { 92 | let list = attr_macros 93 | .iter() 94 | .map(|content| { 95 | quote! { 96 | #[#content] 97 | } 98 | }) 99 | .collect::>(); 100 | quote! { 101 | #(#list)* 102 | } 103 | }; 104 | 105 | quote! { 106 | #[derive(Debug, Clone)] 107 | #derive_macros 108 | #attr_macros 109 | pub enum #k { 110 | #( #keys )* 111 | } 112 | 113 | #default_value 114 | } 115 | }) 116 | .collect::>() 117 | } 118 | -------------------------------------------------------------------------------- /src/tools/derive_macros_token.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{ 4 | bracketed, parenthesized, 5 | parse::{Parse, ParseStream}, 6 | token, Ident, Token, TypePath, 7 | }; 8 | 9 | use super::DeriveAutoMacrosVisibility; 10 | 11 | #[derive(Debug, Clone, Default)] 12 | pub struct ExtraDeriveMacros { 13 | pub derive_macros: Vec, 14 | pub attr_macros: Vec, 15 | pub attr_macros_recursive: Vec, 16 | } 17 | 18 | #[derive(Debug, Clone, Default)] 19 | pub struct ExtraMacros { 20 | pub attr_macros: Vec, 21 | pub derive_macros: Option, 22 | pub macros_visibility: DeriveAutoMacrosVisibility, 23 | } 24 | 25 | impl ExtraMacros { 26 | pub fn extend_derive_macros(&mut self, other: Vec) { 27 | if let Some(derive_macros) = &mut self.derive_macros { 28 | derive_macros.derive_macros.extend(other); 29 | } else { 30 | self.derive_macros = Some(ExtraDeriveMacros { 31 | derive_macros: other, 32 | attr_macros: vec![], 33 | attr_macros_recursive: vec![], 34 | }); 35 | } 36 | } 37 | 38 | pub fn extend_attr_macros(&mut self, other: Vec) { 39 | if let Some(derive_macros) = &mut self.derive_macros { 40 | derive_macros.attr_macros.extend(other); 41 | } else { 42 | self.derive_macros = Some(ExtraDeriveMacros { 43 | derive_macros: vec![], 44 | attr_macros: other, 45 | attr_macros_recursive: vec![], 46 | }); 47 | } 48 | } 49 | 50 | pub fn extend_attr_macros_recursive(&mut self, other: Vec) { 51 | if let Some(derive_macros) = &mut self.derive_macros { 52 | derive_macros.attr_macros_recursive.extend(other); 53 | } else { 54 | self.derive_macros = Some(ExtraDeriveMacros { 55 | derive_macros: vec![], 56 | attr_macros: vec![], 57 | attr_macros_recursive: other, 58 | }); 59 | } 60 | } 61 | } 62 | 63 | impl Parse for ExtraMacros { 64 | fn parse(input: ParseStream) -> syn::Result { 65 | let mut attr_macros_before_derive = vec![]; 66 | let mut derive_macros = vec![]; 67 | let mut attr_macros_after_derive = vec![]; 68 | let mut attr_macros_after_derive_recursive = vec![]; 69 | 70 | let mut has_export_macro = false; 71 | let mut has_parsed_derive = false; 72 | 73 | while input.peek(Token![#]) { 74 | input.parse::()?; 75 | let bracked_content; 76 | bracketed!(bracked_content in input); 77 | 78 | let head_ident = bracked_content.parse::()?; 79 | if head_ident == "derive" { 80 | if bracked_content.peek(token::Paren) { 81 | let content; 82 | parenthesized!(content in bracked_content); 83 | 84 | while !content.is_empty() { 85 | let item = content.parse::()?; 86 | derive_macros.push(item); 87 | 88 | if content.is_empty() { 89 | break; 90 | } 91 | content.parse::()?; 92 | } 93 | } 94 | 95 | has_parsed_derive = true; 96 | } else if head_ident == "macros_recursive" && has_parsed_derive { 97 | let content; 98 | parenthesized!(content in bracked_content); 99 | 100 | let token_stream = content.parse::()?; 101 | attr_macros_after_derive_recursive.push(token_stream); 102 | } else if head_ident == "macro_export" { 103 | has_export_macro = true; 104 | } else if !has_parsed_derive { 105 | let token_stream = bracked_content.parse::()?; 106 | let token_stream = quote! { 107 | #head_ident #token_stream 108 | }; 109 | attr_macros_before_derive.push(token_stream); 110 | } else { 111 | let token_stream = bracked_content.parse::()?; 112 | let token_stream = quote! { 113 | #head_ident #token_stream 114 | }; 115 | attr_macros_after_derive.push(token_stream); 116 | } 117 | } 118 | 119 | if !has_parsed_derive { 120 | Ok(Self { 121 | attr_macros: attr_macros_before_derive, 122 | derive_macros: None, 123 | macros_visibility: if has_export_macro { 124 | DeriveAutoMacrosVisibility::Public 125 | } else { 126 | DeriveAutoMacrosVisibility::PublicOnCrate 127 | }, 128 | }) 129 | } else { 130 | Ok(Self { 131 | attr_macros: attr_macros_before_derive, 132 | derive_macros: Some(ExtraDeriveMacros { 133 | derive_macros, 134 | attr_macros: attr_macros_after_derive, 135 | attr_macros_recursive: attr_macros_after_derive_recursive, 136 | }), 137 | macros_visibility: if has_export_macro { 138 | DeriveAutoMacrosVisibility::Public 139 | } else { 140 | DeriveAutoMacrosVisibility::PublicOnCrate 141 | }, 142 | }) 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/auto_macros.rs: -------------------------------------------------------------------------------- 1 | use yuuka::{auto, derive_enum, derive_struct}; 2 | 3 | #[test] 4 | fn basic_struct() { 5 | derive_struct!(Root { 6 | a: String, 7 | b: i32, 8 | c: f64, 9 | d: { 10 | e: String = "world".to_string(), 11 | f: i32, 12 | } 13 | }); 14 | 15 | let obj = auto!(Root { 16 | a: "hello".to_string(), 17 | b: 42, 18 | c: std::f64::consts::PI, 19 | d: { 20 | f: 24, 21 | ..Default::default() 22 | } 23 | }); 24 | assert_eq!(obj.a, "hello"); 25 | assert_eq!(obj.b, 42); 26 | assert_eq!(obj.c, std::f64::consts::PI); 27 | assert_eq!(obj.d.e, "world"); 28 | assert_eq!(obj.d.f, 24); 29 | } 30 | 31 | #[test] 32 | fn basic_enum() { 33 | derive_enum!( 34 | #[derive(PartialEq)] 35 | enum Root { 36 | A, 37 | B(i32), 38 | C { 39 | a: String, 40 | b: i32, 41 | }, 42 | D(enum { 43 | E, 44 | F(i32), 45 | G { 46 | a: String, 47 | b: i32, 48 | }, 49 | }) 50 | } 51 | ); 52 | 53 | assert_eq!(auto!(Root::A), Root::A); 54 | assert_eq!(auto!(Root::B(42)), Root::B(42)); 55 | assert_eq!( 56 | auto!(Root::C { 57 | a: "hello".to_string(), 58 | b: 42 59 | }), 60 | Root::C { 61 | a: "hello".to_string(), 62 | b: 42 63 | } 64 | ); 65 | assert_eq!(auto!(Root::D::E), Root::D(__Root::_Root_0_anonymous::E)); 66 | assert_eq!( 67 | auto!(Root::D::F(42)), 68 | Root::D(__Root::_Root_0_anonymous::F(42)) 69 | ); 70 | assert_eq!( 71 | auto!(Root::D::G { 72 | a: "hello".to_string(), 73 | b: 42 74 | }), 75 | Root::D(__Root::_Root_0_anonymous::G { 76 | a: "hello".to_string(), 77 | b: 42 78 | }) 79 | ); 80 | } 81 | 82 | #[test] 83 | fn multi_level_enum() { 84 | derive_enum!( 85 | #[derive(PartialEq)] 86 | enum A { 87 | B(enum { 88 | C(enum { 89 | D(enum { 90 | E(enum { 91 | F, 92 | G(String), 93 | }) 94 | }) 95 | }) 96 | }) 97 | } 98 | ); 99 | 100 | assert_eq!( 101 | auto!(A::B::C::D::E::F), 102 | A::B(_A_0_anonymous::C(_A_1_anonymous::D(_A_2_anonymous::E( 103 | _A_3_anonymous::F 104 | )))) 105 | ); 106 | assert_eq!( 107 | auto!(A::B::C::D::E::G("いいよ!こいよ!".to_string())), 108 | A::B(_A_0_anonymous::C(_A_1_anonymous::D(_A_2_anonymous::E( 109 | _A_3_anonymous::G("いいよ!こいよ!".to_string()) 110 | )))) 111 | ); 112 | } 113 | 114 | #[test] 115 | fn mixed_auto() { 116 | derive_struct!( 117 | #[derive(PartialEq)] 118 | Root { 119 | outer: { 120 | a: enum B { 121 | C { 122 | c: i32, 123 | d: f64, 124 | } 125 | } 126 | } 127 | } 128 | ); 129 | 130 | assert_eq!( 131 | auto!(Root { 132 | outer: { 133 | a: auto!(B::C { c: 42, d: std::f64::consts::PI }) 134 | } 135 | }), 136 | Root { 137 | outer: _Root_0_anonymous { 138 | a: B::C { 139 | c: 42, 140 | d: std::f64::consts::PI 141 | } 142 | } 143 | } 144 | ); 145 | } 146 | 147 | #[test] 148 | fn default_struct_auto() { 149 | derive_struct!(Root { 150 | a: String, 151 | b: i32, 152 | c: f64, 153 | d: { 154 | e: String = "world".to_string(), 155 | f: i32, 156 | } 157 | }); 158 | 159 | let obj = auto!(Root { 160 | a: "hello".to_string(), 161 | b: 42, 162 | c: std::f64::consts::PI, 163 | d: { 164 | f: 24, 165 | ..Default::default() 166 | } 167 | }); 168 | assert_eq!(obj.a, "hello"); 169 | assert_eq!(obj.b, 42); 170 | assert_eq!(obj.c, std::f64::consts::PI); 171 | assert_eq!(obj.d.e, "world"); 172 | assert_eq!(obj.d.f, 24); 173 | } 174 | 175 | #[test] 176 | fn across_mod_auto() { 177 | #[macro_use] 178 | mod mod_a { 179 | use yuuka::derive_struct; 180 | 181 | derive_struct!( 182 | #[derive(PartialEq)] 183 | pub Root { a: String, b: i32 } 184 | ); 185 | 186 | #[macro_use] 187 | pub mod mod_b { 188 | use yuuka::derive_enum; 189 | 190 | derive_enum!( 191 | #[derive(PartialEq)] 192 | pub enum Root2 { 193 | A, 194 | B(i32), 195 | } 196 | ); 197 | } 198 | } 199 | 200 | use yuuka::auto; 201 | 202 | use mod_a::mod_b::*; 203 | use mod_a::*; 204 | 205 | assert_eq!( 206 | auto!(Root { 207 | a: "hello".to_string(), 208 | b: 42 209 | }), 210 | Root { 211 | a: "hello".to_string(), 212 | b: 42 213 | } 214 | ); 215 | assert_eq!(auto!(Root2::A), Root2::A); 216 | } 217 | 218 | #[macro_use] 219 | mod across_mod_1 { 220 | use yuuka::derive_struct; 221 | 222 | derive_struct!(Root { 223 | a: { 224 | b: String 225 | } 226 | }); 227 | } 228 | 229 | mod across_mod_2 { 230 | use yuuka::auto; 231 | 232 | use super::across_mod_1::*; 233 | 234 | #[test] 235 | fn test() { 236 | let val = auto!(Root { 237 | a: { 238 | b: "hello".to_string() 239 | } 240 | }); 241 | assert_eq!(val.a.b, "hello"); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/tools/derive_enum_items.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | braced, bracketed, parenthesized, 3 | parse::{Parse, ParseStream}, 4 | token, Ident, Token, TypePath, 5 | }; 6 | 7 | use crate::tools::ExtraTypeWrapper; 8 | 9 | use super::{ 10 | DeriveEnum, DeriveStruct, DeriveStructItems, EnumMembers, EnumValue, ExtraMacros, StructType, 11 | }; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct DeriveEnumItems { 15 | pub items: EnumMembers, 16 | } 17 | 18 | impl Parse for DeriveEnumItems { 19 | fn parse(input: ParseStream) -> syn::Result { 20 | let mut own_enum: EnumMembers = Vec::new(); 21 | 22 | while !input.is_empty() { 23 | let extra_macros = if input.peek(Token![#]) { 24 | input.parse::()? 25 | } else { 26 | Default::default() 27 | }; 28 | 29 | let key = input.parse::()?; 30 | 31 | let value = if input.peek(token::Brace) { 32 | // Ident { ... }, 33 | let sub_content; 34 | braced!(sub_content in input); 35 | let content: DeriveStructItems = sub_content.parse()?; 36 | 37 | EnumValue::Struct(content.items) 38 | } else if input.peek(token::Paren) { 39 | // Ident(...), 40 | let sub_content; 41 | parenthesized!(sub_content in input); 42 | let mut tuple: Vec<(StructType, ExtraTypeWrapper)> = vec![]; 43 | 44 | while !sub_content.is_empty() { 45 | if sub_content.peek(token::Bracket) { 46 | // Ident([...], ...), 47 | let bracket_level_content; 48 | bracketed!(bracket_level_content in sub_content); 49 | 50 | if bracket_level_content.peek(Token![enum]) { 51 | // Ident([enum Ident { ... }], ...), 52 | // Ident([enum { ... }], ...), 53 | let content: DeriveEnum = bracket_level_content.parse()?; 54 | let content = 55 | if let Some(derive_macros) = extra_macros.derive_macros.clone() { 56 | content 57 | .extend_derive_macros(derive_macros.derive_macros) 58 | .extend_attr_macros_recursive( 59 | derive_macros.attr_macros_recursive, 60 | ) 61 | } else { 62 | content 63 | }; 64 | 65 | tuple.push(( 66 | StructType::InlineEnum(Box::new(content)), 67 | ExtraTypeWrapper::Vec, 68 | )); 69 | } else { 70 | // Ident([Ident { ... }], ...), 71 | // Ident([{ ... }], ...), 72 | let content: DeriveStruct = bracket_level_content.parse()?; 73 | let content = 74 | if let Some(derive_macros) = extra_macros.derive_macros.clone() { 75 | content 76 | .extend_derive_macros(derive_macros.derive_macros) 77 | .extend_attr_macros_recursive( 78 | derive_macros.attr_macros_recursive, 79 | ) 80 | } else { 81 | content 82 | }; 83 | 84 | tuple.push(( 85 | StructType::InlineStruct(Box::new(content)), 86 | ExtraTypeWrapper::Vec, 87 | )); 88 | } 89 | } else if sub_content.peek(Token![enum]) { 90 | // Ident(enum Ident { ... }, ...), 91 | // Ident(enum { ... }, ...), 92 | let content: DeriveEnum = sub_content.parse()?; 93 | let content = if let Some(derive_macros) = 94 | extra_macros.derive_macros.clone() 95 | { 96 | content 97 | .extend_derive_macros(derive_macros.derive_macros) 98 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 99 | } else { 100 | content 101 | }; 102 | 103 | tuple.push(( 104 | StructType::InlineEnum(Box::new(content)), 105 | ExtraTypeWrapper::Default, 106 | )); 107 | } else if sub_content.peek2(token::Brace) { 108 | // Ident(Ident { ... }, ...), 109 | // Ident({ ... }, ...), 110 | let content: DeriveStruct = sub_content.parse()?; 111 | let content = if let Some(derive_macros) = 112 | extra_macros.derive_macros.clone() 113 | { 114 | content 115 | .extend_derive_macros(derive_macros.derive_macros) 116 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 117 | } else { 118 | content 119 | }; 120 | 121 | tuple.push(( 122 | StructType::InlineStruct(Box::new(content)), 123 | ExtraTypeWrapper::Default, 124 | )); 125 | } else { 126 | // Ident (TypePath, ...), 127 | let ty: TypePath = sub_content.parse()?; 128 | tuple.push((StructType::Static(ty), ExtraTypeWrapper::Default)); 129 | } 130 | 131 | if sub_content.peek(Token![,]) { 132 | sub_content.parse::()?; 133 | } 134 | } 135 | 136 | EnumValue::Tuple(tuple) 137 | } else { 138 | // Ident, 139 | EnumValue::Empty 140 | }; 141 | 142 | own_enum.push((key, value, extra_macros)); 143 | 144 | if input.peek(Token![,]) { 145 | input.parse::()?; 146 | } 147 | } 148 | 149 | Ok(DeriveEnumItems { items: own_enum }) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /tests/extra_derive_struct.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use yuuka::{derive_enum, derive_struct}; 3 | 4 | #[test] 5 | fn extra_derive_struct() { 6 | derive_struct!( 7 | #[derive(Serialize, Deserialize)] 8 | #[serde(rename_all = "camelCase")] 9 | Root { 10 | nick_name: String, 11 | live_in: String, 12 | } 13 | ); 14 | 15 | let ret = Root { 16 | nick_name: "langyo".to_string(), 17 | live_in: "China".to_string(), 18 | }; 19 | assert_eq!( 20 | serde_json::to_string(&ret).unwrap(), 21 | r#"{"nickName":"langyo","liveIn":"China"}"# 22 | ); 23 | } 24 | 25 | #[test] 26 | fn extra_derive_enum() { 27 | derive_enum!( 28 | #[derive(Serialize, Deserialize)] 29 | #[serde(rename_all = "snake_case")] 30 | enum Root { 31 | SaibaMomoi, 32 | SaibaMidori, 33 | HanaokaYuzu, 34 | TendouAris, 35 | } = SaibaMidori 36 | ); 37 | 38 | let ret = Root::default(); 39 | assert_eq!(serde_json::to_string(&ret).unwrap(), r#""saiba_midori""#); 40 | } 41 | 42 | #[test] 43 | fn extra_derive_struct_with_multi_level() { 44 | derive_struct!( 45 | #[derive(Serialize, Deserialize)] 46 | #[macros_recursive(serde(rename_all = "camelCase"))] 47 | Root { 48 | nick_name: { 49 | chinese: { 50 | simplified_chinese: { 51 | first_name: { 52 | origin: String = "早濑".to_string(), 53 | meme: String = "旱濑".to_string(), 54 | }, 55 | last_name: String = "优香".to_string(), 56 | }, 57 | traditional_chinese: { 58 | first_name: String = "早瀨".to_string(), 59 | last_name: String = "優香".to_string(), 60 | }, 61 | } 62 | japanese: { 63 | first_name: String = "早瀬".to_string(), 64 | last_name: String = "ユウカ".to_string(), 65 | }, 66 | korean: { 67 | first_name: String = "하야세".to_string(), 68 | last_name: String = "유우카".to_string(), 69 | }, 70 | english: { 71 | first_name: String = "Hayase".to_string(), 72 | last_name: String = "Yuuka".to_string(), 73 | } 74 | }, 75 | } 76 | ); 77 | 78 | let ret = Root::default(); 79 | assert_eq!( 80 | serde_json::to_string(&ret).unwrap(), 81 | r#"{"nickName":{"chinese":{"simplifiedChinese":{"firstName":{"origin":"早濑","meme":"旱濑"},"lastName":"优香"},"traditionalChinese":{"firstName":"早瀨","lastName":"優香"}},"japanese":{"firstName":"早瀬","lastName":"ユウカ"},"korean":{"firstName":"하야세","lastName":"유우카"},"english":{"firstName":"Hayase","lastName":"Yuuka"}}}"# 82 | ); 83 | } 84 | 85 | #[test] 86 | fn extra_derive_struct_for_keys() { 87 | derive_struct!( 88 | #[derive(Serialize, Deserialize)] 89 | #[serde(rename_all = "camelCase")] 90 | Root { 91 | nick_name: String, 92 | #[serde(rename = "location")] 93 | live_in: String, 94 | } 95 | ); 96 | 97 | let ret = Root { 98 | nick_name: "langyo".to_string(), 99 | live_in: "China".to_string(), 100 | }; 101 | assert_eq!( 102 | serde_json::to_string(&ret).unwrap(), 103 | r#"{"nickName":"langyo","location":"China"}"# 104 | ); 105 | } 106 | 107 | #[test] 108 | fn extra_derive_enum_for_keys() { 109 | derive_struct!( 110 | #[derive(Serialize, Deserialize)] 111 | #[serde(rename_all = "snake_case")] 112 | Root { 113 | nick_name: enum { 114 | SaibaMomoi, 115 | SaibaMidori, 116 | #[serde(rename = "yuzu")] 117 | HanaokaYuzu, 118 | TendouAris, 119 | } = HanaokaYuzu 120 | } 121 | ); 122 | 123 | let ret = Root::default(); 124 | assert_eq!( 125 | serde_json::to_string(&ret).unwrap(), 126 | r#"{"nick_name":"yuzu"}"# 127 | ); 128 | } 129 | 130 | #[test] 131 | fn extra_derive_derive_for_typed_keys() { 132 | derive_struct!( 133 | #[derive(Serialize, Deserialize)] 134 | #[serde(deny_unknown_fields)] 135 | Root { 136 | nick_name: String, 137 | #[serde(rename = "position")] 138 | #[derive(PartialEq)] 139 | #[serde(rename_all = "UPPERCASE")] 140 | location: Location { 141 | country: String, 142 | address: String, 143 | }, 144 | } 145 | ); 146 | 147 | assert_eq!( 148 | serde_json::to_string(&Root { 149 | nick_name: "arisu".to_string(), 150 | location: Location { 151 | country: "kivotos".to_string(), 152 | address: "777".to_string(), 153 | }, 154 | }) 155 | .unwrap(), 156 | r#"{"nick_name":"arisu","position":{"COUNTRY":"kivotos","ADDRESS":"777"}}"# 157 | ); 158 | } 159 | 160 | #[test] 161 | fn extra_derive_derive_for_anonymous_keys() { 162 | derive_struct!( 163 | #[derive(Serialize, Deserialize)] 164 | #[serde(deny_unknown_fields)] 165 | Root { 166 | nick_name: String, 167 | #[serde(rename = "position")] 168 | #[derive] 169 | #[serde(rename_all = "UPPERCASE")] 170 | location: { 171 | country: String = "kivotos".to_string(), 172 | address: String = "777".to_string(), 173 | }, 174 | } 175 | ); 176 | 177 | assert_eq!( 178 | serde_json::to_string(&Root { 179 | nick_name: "arisu".to_string(), 180 | location: Default::default(), 181 | }) 182 | .unwrap(), 183 | r#"{"nick_name":"arisu","position":{"COUNTRY":"kivotos","ADDRESS":"777"}}"# 184 | ); 185 | } 186 | 187 | #[test] 188 | fn extra_derive_enum_for_typed_keys() { 189 | derive_enum!( 190 | #[derive(Serialize, Deserialize)] 191 | #[serde(deny_unknown_fields)] 192 | enum Group { 193 | #[serde(rename = "777")] 194 | #[derive(PartialEq)] 195 | #[serde(rename_all = "UPPERCASE")] 196 | Millennium(enum Millennium { 197 | GameDevelopment(enum GameDevelopment { 198 | Momoi, 199 | Midori, 200 | Yuzu, 201 | Arisu, 202 | }), 203 | #[serde(rename = "C&C")] 204 | CAndC, 205 | Veritasu, 206 | }) 207 | } 208 | ); 209 | 210 | assert_eq!( 211 | serde_json::to_string(&Group::Millennium(Millennium::GameDevelopment( 212 | GameDevelopment::Yuzu 213 | ))) 214 | .unwrap(), 215 | r#"{"777":{"GAMEDEVELOPMENT":"Yuzu"}}"# 216 | ); 217 | assert_eq!( 218 | serde_json::to_string(&Group::Millennium(Millennium::CAndC)).unwrap(), 219 | r#"{"777":"C&C"}"# 220 | ); 221 | } 222 | 223 | #[test] 224 | fn extra_derive_enum_for_anonymous_keys() { 225 | derive_enum!( 226 | #[derive(Serialize, Deserialize)] 227 | #[serde(deny_unknown_fields)] 228 | enum Group { 229 | #[serde(rename = "777")] 230 | #[derive] 231 | #[serde(rename_all = "UPPERCASE")] 232 | Millennium(enum { 233 | GameDevelopment(enum GameDevelopment { 234 | Momoi, 235 | Midori, 236 | Yuzu, 237 | Arisu, 238 | } = Yuzu), 239 | #[serde(rename = "C&C")] 240 | CAndC, 241 | Veritasu, 242 | } = GameDevelopment(Default::default())) 243 | } = Millennium(Default::default()) 244 | ); 245 | 246 | assert_eq!( 247 | serde_json::to_string(&Group::Millennium(_Group_0_anonymous::GameDevelopment( 248 | GameDevelopment::Yuzu 249 | ))) 250 | .unwrap(), 251 | r#"{"777":{"GAMEDEVELOPMENT":"Yuzu"}}"# 252 | ); 253 | assert_eq!( 254 | serde_json::to_string(&Group::Millennium(_Group_0_anonymous::CAndC)).unwrap(), 255 | r#"{"777":"C&C"}"# 256 | ); 257 | } 258 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use std::{cell::RefCell, rc::Rc}; 4 | use syn::{parse_macro_input, Ident}; 5 | 6 | mod template; 7 | mod tools; 8 | mod utils; 9 | 10 | use template::{ 11 | generate_enums_auto_macros, generate_enums_quote, generate_structs_auto_macros, 12 | generate_structs_quote, 13 | }; 14 | use tools::{ 15 | auto_macros::AutoMacrosType, AutoMacros, DeriveBox, DeriveEnum, DeriveStruct, DeriveVisibility, 16 | StructName, 17 | }; 18 | use utils::flatten; 19 | 20 | #[proc_macro] 21 | pub fn derive_struct(input: TokenStream) -> TokenStream { 22 | let input = parse_macro_input!(input as DeriveStruct); 23 | 24 | let is_public = input.visibility == DeriveVisibility::Public; 25 | let macro_visibility = input.extra_macros.macros_visibility; 26 | let root_ident = match input.ident.clone() { 27 | StructName::Named(v) => v, 28 | StructName::Unnamed(_) => { 29 | panic!("Unnamed root struct is not supported"); 30 | } 31 | }; 32 | let mod_ident = syn::Ident::new(&format!("__{}", root_ident), root_ident.span()); 33 | let (structs, enums) = flatten( 34 | root_ident.to_string(), 35 | Rc::new(RefCell::new(0)), 36 | DeriveBox::Struct(Box::new(input.clone())), 37 | ) 38 | .expect("Failed to flatten"); 39 | 40 | let structs_auto_macros = generate_structs_auto_macros(structs.clone(), macro_visibility); 41 | let enums_auto_macros = generate_enums_auto_macros(enums.clone(), macro_visibility); 42 | 43 | let structs = generate_structs_quote(structs); 44 | let enums = generate_enums_quote(enums); 45 | 46 | let ret = if is_public { 47 | quote! { 48 | #[macro_use] 49 | #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)] 50 | pub mod #mod_ident { 51 | use super::*; 52 | 53 | #( #structs )* 54 | #( #enums )* 55 | 56 | #( #structs_auto_macros )* 57 | #( #enums_auto_macros )* 58 | } 59 | 60 | pub use #mod_ident::*; 61 | } 62 | } else { 63 | quote! { 64 | #[macro_use] 65 | #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)] 66 | pub(crate) mod #mod_ident { 67 | use super::*; 68 | 69 | #( #structs )* 70 | #( #enums )* 71 | 72 | #( #structs_auto_macros )* 73 | #( #enums_auto_macros )* 74 | } 75 | 76 | pub(crate) use #mod_ident::*; 77 | } 78 | }; 79 | 80 | ret.into() 81 | } 82 | 83 | #[proc_macro] 84 | pub fn derive_enum(input: TokenStream) -> TokenStream { 85 | let input = parse_macro_input!(input as DeriveEnum); 86 | 87 | let is_public = input.visibility == DeriveVisibility::Public; 88 | let macro_visibility = input.extra_macros.macros_visibility; 89 | let root_ident = match input.ident.clone() { 90 | StructName::Named(v) => v, 91 | StructName::Unnamed(_) => { 92 | panic!("Unnamed root struct is not supported"); 93 | } 94 | }; 95 | let mod_ident = syn::Ident::new(&format!("__{}", root_ident), root_ident.span()); 96 | let (structs, enums) = flatten( 97 | root_ident.to_string(), 98 | Rc::new(RefCell::new(0)), 99 | DeriveBox::Enum(Box::new(input.clone())), 100 | ) 101 | .expect("Failed to flatten"); 102 | 103 | let structs_auto_macros = generate_structs_auto_macros(structs.clone(), macro_visibility); 104 | let enums_auto_macros = generate_enums_auto_macros(enums.clone(), macro_visibility); 105 | 106 | let structs = generate_structs_quote(structs); 107 | let enums = generate_enums_quote(enums); 108 | 109 | let ret = if is_public { 110 | quote! { 111 | #[macro_use] 112 | #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)] 113 | pub mod #mod_ident { 114 | use super::*; 115 | 116 | #( #structs )* 117 | #( #enums )* 118 | 119 | #( #structs_auto_macros )* 120 | #( #enums_auto_macros )* 121 | } 122 | 123 | pub use #mod_ident::*; 124 | } 125 | } else { 126 | quote! { 127 | #[macro_use] 128 | #[allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)] 129 | pub(crate) mod #mod_ident { 130 | use super::*; 131 | 132 | #( #structs )* 133 | #( #enums )* 134 | 135 | #( #structs_auto_macros )* 136 | #( #enums_auto_macros )* 137 | } 138 | 139 | pub(crate) use #mod_ident::*; 140 | } 141 | }; 142 | 143 | ret.into() 144 | } 145 | 146 | #[proc_macro] 147 | pub fn auto(input: TokenStream) -> TokenStream { 148 | let input = parse_macro_input!(input as AutoMacros); 149 | let ident = input.ident.clone(); 150 | let body = input.body; 151 | 152 | let macro_ident = Ident::new(&format!("__auto_{}", input.ident), input.ident.span()); 153 | match body { 154 | AutoMacrosType::Struct { 155 | items, 156 | expand_exprs, 157 | } => { 158 | let list = items 159 | .iter() 160 | .map(|(key, value)| { 161 | quote! { 162 | #key: #macro_ident!(#key #value) 163 | } 164 | }) 165 | .collect::>(); 166 | 167 | if let Some(expand_exprs) = expand_exprs { 168 | quote! { 169 | #ident { 170 | #( #list ),*, 171 | ..#expand_exprs 172 | } 173 | } 174 | .into() 175 | } else { 176 | quote! { 177 | #ident { 178 | #( #list ),* 179 | } 180 | } 181 | .into() 182 | } 183 | } 184 | 185 | AutoMacrosType::EnumEmpty { key } => quote! { 186 | #ident::#key 187 | } 188 | .into(), 189 | AutoMacrosType::EnumStruct { 190 | key, 191 | items, 192 | expand_exprs, 193 | } => { 194 | let list = items 195 | .iter() 196 | .map(|(item_key, value)| { 197 | quote! { 198 | #item_key: #macro_ident!(#key #item_key #value) 199 | } 200 | }) 201 | .collect::>(); 202 | 203 | if let Some(expand_exprs) = expand_exprs { 204 | quote! { 205 | #ident::#key { 206 | #( #list ),*, 207 | ..#expand_exprs 208 | } 209 | } 210 | .into() 211 | } else { 212 | quote! { 213 | #ident::#key { 214 | #( #list ),* 215 | } 216 | } 217 | .into() 218 | } 219 | } 220 | AutoMacrosType::EnumTuple { key, items } => { 221 | if items.len() == 1 { 222 | let first_item = items.first().expect("Failed to get first item"); 223 | quote! { 224 | #ident::#key(#macro_ident!(#key #first_item)) 225 | } 226 | .into() 227 | } else { 228 | let list = items 229 | .iter() 230 | .enumerate() 231 | .map(|(index, item)| { 232 | quote! { 233 | #macro_ident!(#key #index #item) 234 | } 235 | }) 236 | .collect::>(); 237 | 238 | quote! { 239 | #ident::#key(#( #list ),*) 240 | } 241 | .into() 242 | } 243 | } 244 | AutoMacrosType::EnumSinglePath { key, next_key } => quote! { 245 | #ident::#key(#macro_ident!(#key 0 #next_key)) 246 | } 247 | .into(), 248 | 249 | AutoMacrosType::Value { items } => { 250 | if items.len() == 1 { 251 | let first_item = items.first().expect("Failed to get first item"); 252 | quote! { 253 | #first_item 254 | } 255 | .into() 256 | } else { 257 | let list = items 258 | .iter() 259 | .enumerate() 260 | .map(|(index, item)| { 261 | quote! { 262 | #macro_ident!(#index #item) 263 | } 264 | }) 265 | .collect::>(); 266 | 267 | quote! { 268 | (#( #list ),*) 269 | } 270 | .into() 271 | } 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/tools/auto_macros.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{ 4 | braced, bracketed, parenthesized, 5 | parse::{Parse, ParseStream}, 6 | token, Expr, Ident, Token, 7 | }; 8 | 9 | #[derive(Debug, Clone)] 10 | pub enum AutoMacrosType { 11 | Struct { 12 | items: Vec<(Ident, TokenStream)>, 13 | expand_exprs: Option, 14 | }, 15 | EnumEmpty { 16 | key: Ident, 17 | }, 18 | EnumStruct { 19 | key: Ident, 20 | items: Vec<(Ident, TokenStream)>, 21 | expand_exprs: Option, 22 | }, 23 | EnumTuple { 24 | key: Ident, 25 | items: Vec, 26 | }, 27 | EnumSinglePath { 28 | key: Ident, 29 | next_key: TokenStream, 30 | }, 31 | Value { 32 | items: Vec, 33 | }, 34 | } 35 | 36 | #[derive(Debug, Clone)] 37 | pub struct AutoMacros { 38 | pub ident: Ident, 39 | pub body: AutoMacrosType, 40 | } 41 | 42 | impl Parse for AutoMacros { 43 | fn parse(input: ParseStream) -> syn::Result { 44 | let ident = input.parse()?; 45 | 46 | if input.peek(token::Brace) { 47 | // Str { key: ..., ... } 48 | let content; 49 | braced!(content in input); 50 | 51 | let mut items = vec![]; 52 | let mut expand_exprs = None; 53 | 54 | while !content.is_empty() { 55 | if content.peek(Token![..]) { 56 | if expand_exprs.is_some() { 57 | return Err(content.error("Expand expression is already set")); 58 | } 59 | 60 | content.parse::()?; 61 | let value: Expr = content.parse()?; 62 | expand_exprs = Some(value); 63 | 64 | if !content.is_empty() { 65 | return Err(content.error("Expand expression should be the last")); 66 | } 67 | break; 68 | } 69 | 70 | let key: Ident = content.parse()?; 71 | content.parse::()?; 72 | 73 | if content.peek(token::Brace) { 74 | let inner_content; 75 | braced!(inner_content in content); 76 | let inner_content: TokenStream = inner_content.parse()?; 77 | items.push(( 78 | key, 79 | quote! { 80 | { #inner_content } 81 | }, 82 | )); 83 | } else if content.peek(token::Bracket) { 84 | let inner_content; 85 | bracketed!(inner_content in content); 86 | let inner_content: TokenStream = inner_content.parse()?; 87 | items.push(( 88 | key, 89 | quote! { 90 | [ #inner_content ] 91 | }, 92 | )); 93 | } else if content.peek(token::Paren) { 94 | let inner_content; 95 | parenthesized!(inner_content in content); 96 | let inner_content: TokenStream = inner_content.parse()?; 97 | items.push(( 98 | key, 99 | quote! { 100 | ( #inner_content ) 101 | }, 102 | )); 103 | } else { 104 | let value: Expr = content.parse()?; 105 | items.push(( 106 | key, 107 | quote! { 108 | #value 109 | }, 110 | )); 111 | } 112 | 113 | if content.peek(Token![,]) { 114 | content.parse::()?; 115 | } 116 | } 117 | 118 | Ok(AutoMacros { 119 | ident, 120 | body: AutoMacrosType::Struct { 121 | items, 122 | expand_exprs, 123 | }, 124 | }) 125 | } else if input.peek(Token![::]) { 126 | // Sth::... 127 | 128 | input.parse::()?; 129 | let key: Ident = input.parse()?; 130 | 131 | if input.peek(token::Brace) { 132 | // Sth::Sth { key: ..., ... } 133 | 134 | let content; 135 | braced!(content in input); 136 | 137 | let mut items = vec![]; 138 | let mut expand_exprs = None; 139 | 140 | while !content.is_empty() { 141 | if content.peek(Token![..]) { 142 | if expand_exprs.is_some() { 143 | return Err(content.error("Expand expression is already set")); 144 | } 145 | 146 | content.parse::()?; 147 | let value: Expr = content.parse()?; 148 | expand_exprs = Some(value); 149 | 150 | if !content.is_empty() { 151 | return Err(content.error("Expand expression should be the last")); 152 | } 153 | break; 154 | } 155 | 156 | let key: Ident = content.parse()?; 157 | content.parse::()?; 158 | 159 | if content.peek(token::Brace) { 160 | let inner_content; 161 | braced!(inner_content in content); 162 | let inner_content: TokenStream = inner_content.parse()?; 163 | items.push(( 164 | key, 165 | quote! { 166 | { #inner_content } 167 | }, 168 | )); 169 | } else if content.peek(token::Bracket) { 170 | let inner_content; 171 | bracketed!(inner_content in content); 172 | let inner_content: TokenStream = inner_content.parse()?; 173 | items.push(( 174 | key, 175 | quote! { 176 | [ #inner_content ] 177 | }, 178 | )); 179 | } else if content.peek(token::Paren) { 180 | let inner_content; 181 | parenthesized!(inner_content in content); 182 | let inner_content: TokenStream = inner_content.parse()?; 183 | items.push(( 184 | key, 185 | quote! { 186 | ( #inner_content ) 187 | }, 188 | )); 189 | } else { 190 | let value: Expr = content.parse()?; 191 | items.push(( 192 | key, 193 | quote! { 194 | #value 195 | }, 196 | )); 197 | } 198 | 199 | if content.peek(Token![,]) { 200 | content.parse::()?; 201 | } 202 | } 203 | 204 | Ok(AutoMacros { 205 | ident, 206 | body: AutoMacrosType::EnumStruct { 207 | key, 208 | items, 209 | expand_exprs, 210 | }, 211 | }) 212 | } else if input.peek(token::Paren) { 213 | // Sth::Sth(key, ...) 214 | 215 | let content; 216 | parenthesized!(content in input); 217 | 218 | let mut items = vec![]; 219 | while !content.is_empty() { 220 | let value: Expr = content.parse()?; 221 | items.push(quote! { 222 | #value 223 | }); 224 | 225 | if content.peek(Token![,]) { 226 | content.parse::()?; 227 | } 228 | } 229 | 230 | Ok(AutoMacros { 231 | ident, 232 | body: AutoMacrosType::EnumTuple { key, items }, 233 | }) 234 | } else if input.peek(Token![::]) { 235 | // Sth::Sth::Sth 236 | 237 | input.parse::()?; 238 | let next_key: TokenStream = input.parse()?; 239 | 240 | Ok(AutoMacros { 241 | ident, 242 | body: AutoMacrosType::EnumSinglePath { key, next_key }, 243 | }) 244 | } else { 245 | // Sth::Sth 246 | 247 | Ok(AutoMacros { 248 | ident, 249 | body: AutoMacrosType::EnumEmpty { key }, 250 | }) 251 | } 252 | } else { 253 | // Sth(...) 254 | 255 | let content; 256 | parenthesized!(content in input); 257 | 258 | let mut items = vec![]; 259 | while !content.is_empty() { 260 | let value: Expr = content.parse()?; 261 | items.push(value); 262 | 263 | if content.peek(Token![,]) { 264 | content.parse::()?; 265 | } 266 | } 267 | 268 | Ok(AutoMacros { 269 | ident, 270 | body: AutoMacrosType::Value { items }, 271 | }) 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/tools/derive_struct_items.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | bracketed, 3 | parse::{Parse, ParseStream}, 4 | token, Expr, Ident, Token, TypePath, 5 | }; 6 | 7 | use crate::tools::ExtraTypeWrapper; 8 | 9 | use super::{DefaultValue, DeriveEnum, DeriveStruct, ExtraMacros, StructMembers, StructType}; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct DeriveStructItems { 13 | pub items: StructMembers, 14 | } 15 | 16 | impl Parse for DeriveStructItems { 17 | fn parse(input: ParseStream) -> syn::Result { 18 | let mut own_struct: StructMembers = Vec::new(); 19 | 20 | while !input.is_empty() { 21 | let extra_macros = if input.peek(Token![#]) { 22 | input.parse::()? 23 | } else { 24 | Default::default() 25 | }; 26 | 27 | let key = input.parse::()?; 28 | let optional = if input.peek(Token![?]) { 29 | input.parse::()?; 30 | true 31 | } else { 32 | false 33 | }; 34 | input.parse::()?; 35 | 36 | if input.peek(token::Bracket) { 37 | // sth: [...], 38 | 39 | let bracket_level_content; 40 | bracketed!(bracket_level_content in input); 41 | 42 | if bracket_level_content.peek(Token![enum]) { 43 | // sth: [enum Ident { ... }], 44 | // sth: [enum { ... }], 45 | let content: DeriveEnum = bracket_level_content.parse()?; 46 | let content = if let Some(derive_macros) = extra_macros.derive_macros.clone() { 47 | content 48 | .extend_derive_macros(derive_macros.derive_macros) 49 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 50 | } else { 51 | content 52 | }; 53 | 54 | own_struct.push(( 55 | key, 56 | StructType::InlineEnum(Box::new(content)), 57 | if optional { 58 | ExtraTypeWrapper::OptionVec 59 | } else { 60 | ExtraTypeWrapper::Vec 61 | }, 62 | { 63 | if input.peek(Token![=]) { 64 | input.parse::()?; 65 | let default_value = input.parse::()?; 66 | 67 | if input.peek(token::Brace) { 68 | // sth: [enum Ident { ... } = { ... }], 69 | // sth: [enum { ... } = { ... }], 70 | 71 | let sub_content; 72 | bracketed!(sub_content in input); 73 | 74 | let mut default_values = Vec::new(); 75 | while !sub_content.is_empty() { 76 | default_values.push(sub_content.parse::()?); 77 | if sub_content.peek(Token![,]) { 78 | sub_content.parse::()?; 79 | } 80 | } 81 | 82 | DefaultValue::Array(default_values) 83 | } else { 84 | // sth: [enum Ident { ... } = ...], 85 | // sth: [enum { ... } = ...], 86 | DefaultValue::Single(Box::new(default_value)) 87 | } 88 | } else { 89 | DefaultValue::None 90 | } 91 | }, 92 | extra_macros, 93 | )); 94 | } else { 95 | // sth: [Ident { ... }], 96 | // sth: [{ ... }], 97 | let content: DeriveStruct = bracket_level_content.parse()?; 98 | let content = if let Some(derive_macros) = extra_macros.derive_macros.clone() { 99 | content 100 | .extend_derive_macros(derive_macros.derive_macros) 101 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 102 | } else { 103 | content 104 | }; 105 | 106 | own_struct.push(( 107 | key, 108 | StructType::InlineStruct(Box::new(content)), 109 | if optional { 110 | ExtraTypeWrapper::OptionVec 111 | } else { 112 | ExtraTypeWrapper::Vec 113 | }, 114 | { 115 | if input.peek(Token![=]) { 116 | input.parse::()?; 117 | let default_value = input.parse::()?; 118 | 119 | if input.peek(token::Brace) { 120 | // sth: [Ident { ... } = { ... }], 121 | // sth: [{ ... } = { ... }], 122 | 123 | let sub_content; 124 | bracketed!(sub_content in input); 125 | 126 | let mut default_values = Vec::new(); 127 | while !sub_content.is_empty() { 128 | default_values.push(sub_content.parse::()?); 129 | if sub_content.peek(Token![,]) { 130 | sub_content.parse::()?; 131 | } 132 | } 133 | 134 | DefaultValue::Array(default_values) 135 | } else { 136 | // sth: [Ident { ... } = ...], 137 | // sth: [{ ... } = ...], 138 | DefaultValue::Single(Box::new(default_value)) 139 | } 140 | } else { 141 | DefaultValue::None 142 | } 143 | }, 144 | extra_macros, 145 | )); 146 | }; 147 | } else if input.peek(Token![enum]) { 148 | // sth: enum Ident { ... }, 149 | // sth: enum { ... }, 150 | let content: DeriveEnum = input.parse()?; 151 | let content = if let Some(derive_macros) = extra_macros.derive_macros.clone() { 152 | content 153 | .extend_derive_macros(derive_macros.derive_macros) 154 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 155 | } else { 156 | content 157 | }; 158 | 159 | own_struct.push(( 160 | key.clone(), 161 | StructType::InlineEnum(Box::new(content)), 162 | if optional { 163 | ExtraTypeWrapper::Option 164 | } else { 165 | ExtraTypeWrapper::Default 166 | }, 167 | { 168 | if input.peek(Token![=]) { 169 | input.parse::()?; 170 | let default_value = input.parse::()?; 171 | DefaultValue::Single(Box::new(default_value)) 172 | } else { 173 | DefaultValue::None 174 | } 175 | }, 176 | extra_macros, 177 | )); 178 | } else if input.peek(token::Brace) || input.peek2(token::Brace) { 179 | // sth: Ident { ... }, 180 | // sth: { ... }, 181 | let content: DeriveStruct = input.parse()?; 182 | let content = if let Some(derive_macros) = extra_macros.derive_macros.clone() { 183 | content 184 | .extend_derive_macros(derive_macros.derive_macros) 185 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 186 | } else { 187 | content 188 | }; 189 | 190 | own_struct.push(( 191 | key.clone(), 192 | StructType::InlineStruct(Box::new(content)), 193 | if optional { 194 | ExtraTypeWrapper::Option 195 | } else { 196 | ExtraTypeWrapper::Default 197 | }, 198 | DefaultValue::None, 199 | extra_macros, 200 | )); 201 | } else { 202 | // sth: TypePath, 203 | let ty: TypePath = input.parse()?; 204 | 205 | own_struct.push(( 206 | key, 207 | StructType::Static(ty), 208 | if optional { 209 | ExtraTypeWrapper::Option 210 | } else { 211 | ExtraTypeWrapper::Default 212 | }, 213 | { 214 | if input.peek(Token![=]) { 215 | input.parse::()?; 216 | let default_value = input.parse::()?; 217 | 218 | DefaultValue::Single(Box::new(default_value)) 219 | } else { 220 | DefaultValue::None 221 | } 222 | }, 223 | extra_macros, 224 | )); 225 | } 226 | 227 | if input.peek(Token![,]) { 228 | input.parse::()?; 229 | } 230 | } 231 | 232 | Ok(DeriveStructItems { items: own_struct }) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /src/utils/flatten.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::{cell::RefCell, rc::Rc}; 3 | use syn::parse_quote; 4 | 5 | use crate::tools::{ 6 | DefaultValue, DeriveBox, EnumValue, EnumValueFlatten, EnumsFlatten, ExtraMacrosFlatten, 7 | ExtraTypeWrapper, StructType, StructsFlatten, 8 | }; 9 | 10 | pub(crate) fn flatten( 11 | root_name: String, 12 | unique_id_count: Rc>, 13 | parent: DeriveBox, 14 | ) -> Result<(StructsFlatten, EnumsFlatten)> { 15 | match parent { 16 | DeriveBox::Struct(parent) => { 17 | let mut structs = vec![]; 18 | let mut enums = vec![]; 19 | 20 | let mut items = vec![]; 21 | for (key, ty, extra_type_wrapper, default_value, extra_macros) in parent.items.iter() { 22 | match ty { 23 | StructType::Static(v) => { 24 | items.push(( 25 | key.clone(), 26 | match extra_type_wrapper { 27 | ExtraTypeWrapper::Option => parse_quote! { Option<#v> }, 28 | ExtraTypeWrapper::OptionVec => parse_quote! { Option<#v> }, 29 | _ => v.clone(), 30 | }, 31 | default_value.clone(), 32 | extra_macros.attr_macros.clone(), 33 | )); 34 | } 35 | StructType::InlineStruct(v) => { 36 | let v = v 37 | .clone() 38 | .pin_unique_id(root_name.clone(), unique_id_count.clone()); 39 | let v = if let Some(derive_macros) = extra_macros.derive_macros.clone() { 40 | v.extend_attr_macros(derive_macros.attr_macros) 41 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 42 | } else { 43 | v 44 | }; 45 | let v = if let Some(derive_macros) = 46 | parent.extra_macros.derive_macros.clone() 47 | { 48 | v.extend_derive_macros(derive_macros.derive_macros) 49 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 50 | } else { 51 | v 52 | }; 53 | 54 | let (sub_structs, sub_enums) = flatten( 55 | root_name.clone(), 56 | unique_id_count.clone(), 57 | DeriveBox::Struct(Box::new(v.clone())), 58 | )?; 59 | 60 | structs.extend(sub_structs); 61 | enums.extend(sub_enums); 62 | 63 | let ty = v.ident.to_ident()?; 64 | items.push(( 65 | key.clone(), 66 | match extra_type_wrapper { 67 | ExtraTypeWrapper::Default => parse_quote! { #ty }, 68 | ExtraTypeWrapper::Vec => parse_quote! { Vec<#ty> }, 69 | ExtraTypeWrapper::Option => parse_quote! { Option<#ty> }, 70 | ExtraTypeWrapper::OptionVec => parse_quote! { Option> }, 71 | }, 72 | default_value.clone(), 73 | extra_macros.attr_macros.clone(), 74 | )); 75 | } 76 | StructType::InlineEnum(v) => { 77 | let v = v 78 | .clone() 79 | .pin_unique_id(root_name.clone(), unique_id_count.clone()); 80 | let v = if let Some(derive_macros) = extra_macros.derive_macros.clone() { 81 | v.extend_attr_macros(derive_macros.attr_macros) 82 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 83 | } else { 84 | v 85 | }; 86 | let v = if let Some(derive_macros) = 87 | parent.extra_macros.derive_macros.clone() 88 | { 89 | v.extend_derive_macros(derive_macros.derive_macros) 90 | .extend_attr_macros_recursive(derive_macros.attr_macros_recursive) 91 | } else { 92 | v 93 | }; 94 | 95 | let (sub_structs, sub_enums) = flatten( 96 | root_name.clone(), 97 | unique_id_count.clone(), 98 | DeriveBox::Enum(Box::new(v.clone())), 99 | )?; 100 | 101 | structs.extend(sub_structs); 102 | enums.extend(sub_enums); 103 | 104 | let ty = v.ident.to_ident()?; 105 | items.push(( 106 | key.clone(), 107 | match extra_type_wrapper { 108 | ExtraTypeWrapper::Default => parse_quote! { #ty }, 109 | ExtraTypeWrapper::Vec => parse_quote! { Vec<#ty> }, 110 | ExtraTypeWrapper::Option => parse_quote! { Option<#ty> }, 111 | ExtraTypeWrapper::OptionVec => parse_quote! { Option> }, 112 | }, 113 | default_value.clone(), 114 | extra_macros.attr_macros.clone(), 115 | )); 116 | } 117 | } 118 | } 119 | 120 | let ty = parent.ident.to_ident()?; 121 | structs.push(( 122 | ty, 123 | items, 124 | ExtraMacrosFlatten { 125 | derive_macros: parent 126 | .extra_macros 127 | .derive_macros 128 | .clone() 129 | .map(|derive_macros| derive_macros.derive_macros) 130 | .unwrap_or_default(), 131 | attr_macros: parent 132 | .extra_macros 133 | .derive_macros 134 | .map(|derive_macros| { 135 | [ 136 | derive_macros.attr_macros.clone(), 137 | derive_macros.attr_macros_recursive.clone(), 138 | ] 139 | .concat() 140 | }) 141 | .unwrap_or_default(), 142 | }, 143 | )); 144 | 145 | Ok((structs, enums)) 146 | } 147 | DeriveBox::Enum(parent) => { 148 | let mut structs = vec![]; 149 | let mut enums = vec![]; 150 | 151 | let mut items = vec![]; 152 | for (key, value, extra_macros) in parent.items.iter() { 153 | match value { 154 | EnumValue::Empty => { 155 | items.push(( 156 | key.clone(), 157 | EnumValueFlatten::Empty, 158 | extra_macros.attr_macros.clone(), 159 | )); 160 | } 161 | EnumValue::Tuple(v) => { 162 | let mut tuple = vec![]; 163 | for (ty, extra_type_wrapper) in v.iter() { 164 | match ty { 165 | StructType::Static(v) => { 166 | tuple.push(match extra_type_wrapper { 167 | ExtraTypeWrapper::Option => parse_quote! { Option<#v> }, 168 | ExtraTypeWrapper::OptionVec => parse_quote! { Option<#v> }, 169 | _ => v.clone(), 170 | }); 171 | } 172 | StructType::InlineStruct(v) => { 173 | let v = v 174 | .clone() 175 | .pin_unique_id(root_name.clone(), unique_id_count.clone()); 176 | let v = if let Some(derive_macros) = 177 | extra_macros.derive_macros.clone() 178 | { 179 | v.extend_attr_macros(derive_macros.attr_macros) 180 | .extend_attr_macros_recursive( 181 | derive_macros.attr_macros_recursive, 182 | ) 183 | } else { 184 | v 185 | }; 186 | let v = if let Some(derive_macros) = 187 | parent.extra_macros.derive_macros.clone() 188 | { 189 | v.extend_derive_macros(derive_macros.derive_macros) 190 | .extend_attr_macros_recursive( 191 | derive_macros.attr_macros_recursive, 192 | ) 193 | } else { 194 | v 195 | }; 196 | 197 | let (sub_structs, sub_enums) = flatten( 198 | root_name.clone(), 199 | unique_id_count.clone(), 200 | DeriveBox::Struct(Box::new(v.clone())), 201 | )?; 202 | 203 | structs.extend(sub_structs); 204 | enums.extend(sub_enums); 205 | 206 | let ty = v.ident.to_ident()?; 207 | tuple.push(match extra_type_wrapper { 208 | ExtraTypeWrapper::Default => parse_quote! { #ty }, 209 | ExtraTypeWrapper::Vec => parse_quote! { Vec<#ty> }, 210 | ExtraTypeWrapper::Option => parse_quote! { Option<#ty> }, 211 | ExtraTypeWrapper::OptionVec => { 212 | parse_quote! { Option> } 213 | } 214 | }); 215 | } 216 | StructType::InlineEnum(v) => { 217 | let v = v 218 | .clone() 219 | .pin_unique_id(root_name.clone(), unique_id_count.clone()); 220 | let v = if let Some(derive_macros) = 221 | extra_macros.derive_macros.clone() 222 | { 223 | v.extend_attr_macros(derive_macros.attr_macros) 224 | .extend_attr_macros_recursive( 225 | derive_macros.attr_macros_recursive, 226 | ) 227 | } else { 228 | v 229 | }; 230 | let v = if let Some(derive_macros) = 231 | parent.extra_macros.derive_macros.clone() 232 | { 233 | v.extend_derive_macros(derive_macros.derive_macros) 234 | .extend_attr_macros_recursive( 235 | derive_macros.attr_macros_recursive, 236 | ) 237 | } else { 238 | v 239 | }; 240 | 241 | let (sub_structs, sub_enums) = flatten( 242 | root_name.clone(), 243 | unique_id_count.clone(), 244 | DeriveBox::Enum(Box::new(v.clone())), 245 | )?; 246 | 247 | structs.extend(sub_structs); 248 | enums.extend(sub_enums); 249 | 250 | let ty = v.ident.to_ident()?; 251 | tuple.push(match extra_type_wrapper { 252 | ExtraTypeWrapper::Default => parse_quote! { #ty }, 253 | ExtraTypeWrapper::Vec => parse_quote! { Vec<#ty> }, 254 | ExtraTypeWrapper::Option => parse_quote! { Option<#ty> }, 255 | ExtraTypeWrapper::OptionVec => { 256 | parse_quote! { Option> } 257 | } 258 | }); 259 | } 260 | } 261 | } 262 | items.push(( 263 | key.clone(), 264 | EnumValueFlatten::Tuple(tuple), 265 | extra_macros.attr_macros.clone(), 266 | )); 267 | } 268 | EnumValue::Struct(v) => { 269 | let mut sub_items = vec![]; 270 | for (key, ty, extra_type_wrapper, default_value, extra_macros) in v.iter() { 271 | match ty { 272 | StructType::Static(v) => { 273 | sub_items.push(( 274 | key.clone(), 275 | match extra_type_wrapper { 276 | ExtraTypeWrapper::Option => parse_quote! { Option<#v> }, 277 | ExtraTypeWrapper::OptionVec => { 278 | parse_quote! { Option<#v> } 279 | } 280 | _ => v.clone(), 281 | }, 282 | default_value.clone(), 283 | extra_macros.attr_macros.clone(), 284 | )); 285 | } 286 | StructType::InlineStruct(v) => { 287 | let v = v 288 | .clone() 289 | .pin_unique_id(root_name.clone(), unique_id_count.clone()); 290 | let v = if let Some(derive_macros) = 291 | extra_macros.derive_macros.clone() 292 | { 293 | v.extend_attr_macros(derive_macros.attr_macros) 294 | .extend_attr_macros_recursive( 295 | derive_macros.attr_macros_recursive, 296 | ) 297 | } else { 298 | v 299 | }; 300 | let v = if let Some(derive_macros) = 301 | parent.extra_macros.derive_macros.clone() 302 | { 303 | v.extend_derive_macros(derive_macros.derive_macros) 304 | .extend_attr_macros_recursive( 305 | derive_macros.attr_macros_recursive, 306 | ) 307 | } else { 308 | v 309 | }; 310 | 311 | let (sub_structs, sub_enums) = flatten( 312 | root_name.clone(), 313 | unique_id_count.clone(), 314 | DeriveBox::Struct(Box::new(v.clone())), 315 | )?; 316 | 317 | structs.extend(sub_structs); 318 | enums.extend(sub_enums); 319 | 320 | let ty = v.ident.to_ident()?; 321 | sub_items.push(( 322 | key.clone(), 323 | match extra_type_wrapper { 324 | ExtraTypeWrapper::Default => parse_quote! { #ty }, 325 | ExtraTypeWrapper::Vec => parse_quote! { Vec<#ty> }, 326 | ExtraTypeWrapper::Option => { 327 | parse_quote! { Option<#ty> } 328 | } 329 | ExtraTypeWrapper::OptionVec => { 330 | parse_quote! { Option> } 331 | } 332 | }, 333 | default_value.clone(), 334 | extra_macros.attr_macros.clone(), 335 | )); 336 | } 337 | StructType::InlineEnum(v) => { 338 | let v = v 339 | .clone() 340 | .pin_unique_id(root_name.clone(), unique_id_count.clone()); 341 | let v = if let Some(derive_macros) = 342 | extra_macros.derive_macros.clone() 343 | { 344 | v.extend_attr_macros(derive_macros.attr_macros) 345 | .extend_attr_macros_recursive( 346 | derive_macros.attr_macros_recursive, 347 | ) 348 | } else { 349 | v 350 | }; 351 | let v = if let Some(derive_macros) = 352 | parent.extra_macros.derive_macros.clone() 353 | { 354 | v.extend_derive_macros(derive_macros.derive_macros) 355 | .extend_attr_macros_recursive( 356 | derive_macros.attr_macros_recursive, 357 | ) 358 | } else { 359 | v 360 | }; 361 | 362 | let (sub_structs, sub_enums) = flatten( 363 | root_name.clone(), 364 | unique_id_count.clone(), 365 | DeriveBox::Enum(Box::new(v.clone())), 366 | )?; 367 | 368 | structs.extend(sub_structs); 369 | enums.extend(sub_enums); 370 | 371 | let ty = v.ident.to_ident()?; 372 | sub_items.push(( 373 | key.clone(), 374 | match extra_type_wrapper { 375 | ExtraTypeWrapper::Default => parse_quote! { #ty }, 376 | ExtraTypeWrapper::Vec => parse_quote! { Vec<#ty> }, 377 | ExtraTypeWrapper::Option => { 378 | parse_quote! { Option<#ty> } 379 | } 380 | ExtraTypeWrapper::OptionVec => { 381 | parse_quote! { Option> } 382 | } 383 | }, 384 | default_value.clone(), 385 | extra_macros.attr_macros.clone(), 386 | )); 387 | } 388 | } 389 | } 390 | 391 | items.push(( 392 | key.clone(), 393 | EnumValueFlatten::Struct(sub_items), 394 | extra_macros.attr_macros.clone(), 395 | )); 396 | } 397 | } 398 | } 399 | 400 | let ty = parent.ident.to_ident()?; 401 | enums.push(( 402 | ty, 403 | items, 404 | if let Some(value) = parent.default_value { 405 | DefaultValue::Single(parse_quote! { Self::#value }) 406 | } else { 407 | DefaultValue::None 408 | }, 409 | ExtraMacrosFlatten { 410 | derive_macros: parent 411 | .extra_macros 412 | .derive_macros 413 | .clone() 414 | .map(|derive_macros| derive_macros.derive_macros) 415 | .unwrap_or_default(), 416 | attr_macros: parent 417 | .extra_macros 418 | .derive_macros 419 | .map(|derive_macros| { 420 | [ 421 | derive_macros.attr_macros.clone(), 422 | derive_macros.attr_macros_recursive.clone(), 423 | ] 424 | .concat() 425 | }) 426 | .unwrap_or_default(), 427 | }, 428 | )); 429 | 430 | Ok((structs, enums)) 431 | } 432 | } 433 | } 434 | --------------------------------------------------------------------------------