gsettings-macro-0.1.20/.cargo_vcs_info.json0000644000000001360000000000100142220ustar { "git": { "sha1": "f9ec1183426a7127b2d5145996dad819ee4406bd" }, "path_in_vcs": "" }gsettings-macro-0.1.20/.gitignore000064400000000000000000000000551046102023000150020ustar 00000000000000/target /Cargo.lock /tests/gschemas.compiled gsettings-macro-0.1.20/Cargo.toml0000644000000027010000000000100122200ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "gsettings-macro" version = "0.1.20" authors = ["Dave Patrick Caberto"] exclude = [ "/.github/*", "/tests/*", ] description = "Macro for typesafe GSettings key access" homepage = "https://github.com/SeaDve/gsettings-macro" documentation = "https://docs.rs/gsettings-macro" readme = "README.md" keywords = [ "gtk", "gio", "glib", "settings", "macro", ] categories = ["api-bindings"] license = "MPL-2.0" repository = "https://github.com/SeaDve/gsettings-macro" [lib] proc-macro = true [dependencies.deluxe] version = "0.5" [dependencies.heck] version = "0.4" [dependencies.proc-macro-error] version = "1.0" [dependencies.proc-macro2] version = "1.0" [dependencies.quick-xml] version = "0.31" features = ["serialize"] [dependencies.quote] version = "1.0" [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.syn] version = "2.0" [dev-dependencies.gio] version = "0.18" [dev-dependencies.serial_test] version = "2.0" gsettings-macro-0.1.20/Cargo.toml.orig000064400000000000000000000014261046102023000157040ustar 00000000000000[package] name = "gsettings-macro" version = "0.1.20" description = "Macro for typesafe GSettings key access" homepage = "https://github.com/SeaDve/gsettings-macro" repository = "https://github.com/SeaDve/gsettings-macro" documentation = "https://docs.rs/gsettings-macro" authors = ["Dave Patrick Caberto"] categories = ["api-bindings"] keywords = ["gtk", "gio", "glib", "settings", "macro"] edition = "2021" readme = "README.md" license = "MPL-2.0" exclude = ["/.github/*", "/tests/*"] [lib] proc-macro = true [dependencies] heck = "0.4" proc-macro2 = "1.0" proc-macro-error = "1.0" quote = "1.0" serde = { version = "1.0", features = ["derive"] } quick-xml = { version = "0.31", features = ["serialize"] } syn = "2.0" deluxe = "0.5" [dev-dependencies] gio = "0.18" serial_test = "2.0" gsettings-macro-0.1.20/LICENSE000064400000000000000000000405251046102023000140250ustar 00000000000000Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. gsettings-macro-0.1.20/README.md000064400000000000000000000057241046102023000143010ustar 00000000000000# GSettings Macro [![github](https://img.shields.io/badge/github-seadve/gsettings-macro)](https://github.com/SeaDve/gsettings-macro) [![crates.io](https://img.shields.io/crates/v/gsettings-macro)](https://crates.io/crates/gsettings-macro) [![docs](https://docs.rs/gsettings-macro/badge.svg)](https://docs.rs/gsettings-macro/) [![CI](https://github.com/SeaDve/gsettings-macro/actions/workflows/ci.yml/badge.svg)](https://github.com/SeaDve/gsettings-macro/actions/workflows/ci.yml) Macro for typesafe GSettings key access The macro's main purpose is to reduce the risk of mistyping a key, using the wrong method to access values, inputting incorrect values, and to reduce boilerplate. Additionally, the summary, description, and default value are included in the documentation of each generated method. This would be beneficial if you use tools like [`rust-analyzer`](https://rust-analyzer.github.io/). ## Example ```rust,ignore use gsettings_macro::gen_settings; use gio::glib; use std::path::{Path, PathBuf}; #[gen_settings( file = "./tests/io.github.seadve.test.gschema.xml", id = "io.github.seadve.test" )] #[gen_settings_define( key_name = "cache-dir", arg_type = "&Path", ret_type = "PathBuf" )] #[gen_settings_skip(signature = "(ss)")] pub struct ApplicationSettings; let settings = ApplicationSettings::default(); // `i` D-Bus type settings.set_window_width(100); assert_eq!(settings.window_width(), 100); // enums settings.set_alert_sound(AlertSound::Glass); assert_eq!(settings.alert_sound(), AlertSound::Glass); // bitflags settings.set_space_style(SpaceStyle::BEFORE_COLON | SpaceStyle::BEFORE_COMMA); assert_eq!( settings.space_style(), SpaceStyle::BEFORE_COLON | SpaceStyle::BEFORE_COMMA ); // customly defined settings.set_cache_dir(Path::new("/some_dir/")); assert_eq!(settings.cache_dir(), PathBuf::from("/some_dir/")); ``` For more examples and detailed information see the [documentation](https://seadve.github.io/gsettings-macro/gsettings_macro/attr.gen_settings.html). ## Generated methods The procedural macro generates the following [`gio::Settings`](https://docs.rs/gio/latest/gio/struct.Settings.html) methods for each key in the schema: * `set` -> `set_${key}`, which panics when writing in a readonly key, and `try_set_${key}`, which behaves the same as the original method. * `get` -> `${key}` * `connect_changed` -> `connect_${key}_changed` * `bind` -> `bind_${key}` * `create_action` -> `create_${key}_action` * `default_value` -> `${key}_default_value` * `reset` -> `reset_${key}` ## Known issues * Not updating when the gschema file is modified * Use hacks like `include_str!` * See [#73921](https://github.com/rust-lang/rust/issues/73921) or [#55904](https://github.com/rust-lang/rust/issues/55904) ## License Copyright 2023 Dave Patrick Caberto This software is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at [this site](http://mozilla.org/MPL/2.0/). gsettings-macro-0.1.20/src/generators/bitflag.rs000064400000000000000000000063571046102023000177430ustar 00000000000000use heck::{ToPascalCase, ToShoutySnakeCase}; use quote::quote; use syn::{spanned::Spanned, Ident}; use super::{Context, KeyGenerator, SchemaFlag, SchemaKey}; pub fn key_generator<'a>( key: &'a SchemaKey, flag: &SchemaFlag, aux_visibility: syn::Visibility, ) -> KeyGenerator<'a> { let flag_name = key.name.to_pascal_case(); KeyGenerator::new( key, Context::new_with_aux( &flag_name, bitflag_token_stream(&flag_name, flag, aux_visibility), ), ) } fn bitflag_token_stream( name: &str, flag: &SchemaFlag, visibility: syn::Visibility, ) -> proc_macro2::TokenStream { let value_idents = flag .values .iter() .map(|value| Ident::new(&value.nick.to_shouty_snake_case(), value.nick.span())) .collect::>(); let flags_arms = value_idents .iter() .zip(flag.values.iter()) .map(|(value_ident, value)| { let value = value.value; quote! { const #value_ident = #value; } }); let from_variant_arms = value_idents .iter() .zip(flag.values.iter()) .map(|(value_ident, value)| { let nick = &value.nick; quote! { #nick => this.insert(Self::#value_ident) } }); let to_variant_arms = value_idents .iter() .zip(flag.values.iter()) .map(|(value_ident, value)| { let nick = &value.nick; quote! { if self.contains(Self::#value_ident) { string_array.push(#nick) } } }); let name_pascal_case = name.to_pascal_case(); let ident = Ident::new(&name_pascal_case, name_pascal_case.span()); quote! { gio::glib::bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #visibility struct #ident: u32 { #(#flags_arms)* } } impl gio::glib::StaticVariantType for #ident { fn static_variant_type() -> std::borrow::Cow<'static, gio::glib::VariantTy> { std::borrow::Cow::Borrowed(gio::glib::VariantTy::STRING_ARRAY) } } impl gio::glib::FromVariant for #ident { fn from_variant(variant: &gio::glib::Variant) -> Option { let mut this = Self::empty(); for string in variant.get::>()? { match string.as_str() { #(#from_variant_arms),*, _ => return None, } } Some(this) } } impl gio::glib::ToVariant for #ident { fn to_variant(&self) -> gio::glib::Variant { let mut string_array = Vec::new(); #(#to_variant_arms)* gio::glib::ToVariant::to_variant(&string_array) } } impl std::convert::From<#ident> for gio::glib::Variant { fn from(this: #ident) -> gio::glib::Variant { gio::glib::ToVariant::to_variant(&this) } } } } gsettings-macro-0.1.20/src/generators/enumeration.rs000064400000000000000000000011421046102023000206440ustar 00000000000000use heck::ToPascalCase; use super::{Context, KeyGenerator, SchemaEnum, SchemaKey}; pub fn key_generator<'a>( key: &'a SchemaKey, enum_: &SchemaEnum, aux_visibility: syn::Visibility, ) -> KeyGenerator<'a> { let enum_name = key.name.to_pascal_case(); let enum_token_stream = super::new_variant_enum( &enum_name, &enum_ .values .iter() .map(|value| (value.nick.as_str(), Some(value.value))) .collect::>(), aux_visibility, ); KeyGenerator::new(key, Context::new_with_aux(&enum_name, enum_token_stream)) } gsettings-macro-0.1.20/src/generators/mod.rs000064400000000000000000000336401046102023000171050ustar 00000000000000mod bitflag; mod enumeration; mod string; use heck::ToSnakeCase; use proc_macro2::Span; use proc_macro_error::abort_call_site; use quote::{format_ident, quote}; use std::collections::{HashMap, HashSet}; use syn::Ident; use std::fmt::Write; use crate::schema::{ Enum as SchemaEnum, Flag as SchemaFlag, Key as SchemaKey, KeySignature as SchemaKeySignature, }; pub enum OverrideType { Define { arg_type: String, ret_type: String }, Skip, } pub enum GetResult<'a> { Some(KeyGenerator<'a>), Skip, Unknown, } pub struct KeyGenerators<'a> { signatures: HashMap, key_names: HashMap, enums: HashMap, flags: HashMap, signature_skips: HashSet, key_name_skips: HashSet, } impl<'a> KeyGenerators<'a> { pub fn with_defaults( enums: HashMap, flags: HashMap, ) -> Self { let mut this = Self { signatures: HashMap::new(), key_names: HashMap::new(), enums, flags, signature_skips: HashSet::new(), key_name_skips: HashSet::new(), }; // Built ins this.insert_type("b", Context::new("bool")); this.insert_type("i", Context::new("i32")); this.insert_type("u", Context::new("u32")); this.insert_type("x", Context::new("i64")); this.insert_type("t", Context::new("u64")); this.insert_type("d", Context::new("f64")); this.insert_type("(ii)", Context::new("(i32, i32)")); this.insert_type("as", Context::new_dissimilar("&[&str]", "Vec")); this } /// Add contexts that has higher priority than default, but lower than /// key_name overrides pub fn add_signature_overrides( &mut self, overrides: HashMap, ) { for (signature, item) in overrides { match item { OverrideType::Define { arg_type, ret_type } => { self.signatures .insert(signature, Context::new_dissimilar(&arg_type, &ret_type)); } OverrideType::Skip => { self.signature_skips.insert(signature); } } } } /// Add contexts that has higher priority than both default and signature overrides. pub fn add_key_name_overrides(&mut self, overrides: HashMap) { for (key_name, item) in overrides { match item { OverrideType::Define { arg_type, ret_type } => { self.key_names .insert(key_name, Context::new_dissimilar(&arg_type, &ret_type)); } OverrideType::Skip => { self.key_name_skips.insert(key_name); } } } } pub fn get( &'a self, key: &'a SchemaKey, aux_visibility: syn::Visibility, ) -> Option> { let key_signature = key.signature()?; if self.key_name_skips.contains(&key.name) { return Some(GetResult::Skip); } if self.signature_skips.contains(&key_signature) { return Some(GetResult::Skip); } if let Some(context) = self.key_names.get(&key.name) { return Some(GetResult::Some(KeyGenerator::new(key, context.clone()))); } if let Some(context) = self.signatures.get(&key_signature) { return Some(GetResult::Some(KeyGenerator::new(key, context.clone()))); } Some(match key_signature { SchemaKeySignature::Type(type_) => match type_.as_str() { "s" => GetResult::Some(string::key_generator(key, aux_visibility)), _ => GetResult::Unknown, }, SchemaKeySignature::Enum(ref enum_name) => GetResult::Some(enumeration::key_generator( key, self.enums.get(enum_name).unwrap_or_else(|| { abort_call_site!("expected an enum definition for `{}`", enum_name) }), aux_visibility, )), SchemaKeySignature::Flag(ref flag_name) => GetResult::Some(bitflag::key_generator( key, self.flags.get(flag_name).unwrap_or_else(|| { abort_call_site!("expected a flag definition for `{}`", flag_name) }), aux_visibility, )), }) } fn insert_type(&mut self, signature: &str, context: Context) { self.signatures .insert(SchemaKeySignature::Type(signature.to_string()), context); } } pub struct KeyGenerator<'a> { key: &'a SchemaKey, context: Context, } impl<'a> KeyGenerator<'a> { pub fn auxiliary(&self) -> Option { self.context.auxiliary.clone() } fn new(key: &'a SchemaKey, context: Context) -> Self { Self { key, context } } fn func_docs(&self) -> proc_macro2::TokenStream { let mut stream = proc_macro2::TokenStream::new(); let has_summary = self .key .summary .as_ref() .map_or(false, |summary| !summary.is_empty()); let has_description = self .key .description .as_ref() .map_or(false, |description| !description.is_empty()); if has_summary { let summary = self.key.summary.as_ref().unwrap(); stream.extend(quote! { #[doc = #summary] }); } if has_summary && has_description { stream.extend(quote! { #[doc = ""] }); } if has_description { let description = self.key.description.as_ref().unwrap(); stream.extend(quote! { #[doc = #description] }); } let default_docs = format!("default: {}", self.key.default); stream.extend(quote! { #[doc = ""] #[doc = #default_docs] }); // only needed for numerical types if let Some(ref range) = self.key.range { let has_min = range.min.as_ref().map_or(false, |min| !min.is_empty()); let has_max = range.max.as_ref().map_or(false, |max| !max.is_empty()); if has_min || has_max { stream.extend(quote! { #[doc = ""] }); } let mut range_docs = String::new(); if has_min { write!(range_docs, "min: {}", range.min.as_ref().unwrap()).unwrap(); } if has_min && has_max { range_docs.push(';'); range_docs.push(' '); } if has_max { write!(range_docs, "max: {}", range.max.as_ref().unwrap()).unwrap(); } stream.extend(quote! { #[doc = #range_docs] }) } stream } } impl quote::ToTokens for KeyGenerator<'_> { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let key_name = self.key.name.as_str(); let key_name_snake_case = key_name.to_snake_case(); let getter_func_ident = Ident::new(&key_name_snake_case, Span::call_site()); let connect_changed_func_ident = format_ident!("connect_{}_changed", getter_func_ident); let bind_func_ident = format_ident!("bind_{}", getter_func_ident); let create_action_func_ident = format_ident!("create_{}_action", getter_func_ident); let reset_func_ident = format_ident!("reset_{}", getter_func_ident); let func_docs = self.func_docs(); tokens.extend(quote! { #func_docs pub fn #connect_changed_func_ident(&self, f: impl Fn(&Self) + 'static) -> gio::glib::SignalHandlerId { gio::prelude::SettingsExt::connect_changed(&self.0, Some(#key_name), move |settings, _| { f(&Self(gio::Settings::clone(settings))) }) } #func_docs pub fn #bind_func_ident<'a>(&'a self, object: &'a impl gio::glib::object::IsA, property: &'a str) -> gio::BindingBuilder<'a> { gio::prelude::SettingsExtManual::bind(&self.0, #key_name, object, property) } #func_docs pub fn #create_action_func_ident(&self) -> gio::Action { gio::prelude::SettingsExt::create_action(&self.0, #key_name) } #func_docs pub fn #reset_func_ident(&self) { gio::prelude::SettingsExt::reset(&self.0, #key_name); } }); let setter_func_ident = format_ident!("set_{}", getter_func_ident); let try_setter_func_ident = format_ident!("try_set_{}", getter_func_ident); let default_value_func_ident = format_ident!("{}_default_value", getter_func_ident); let get_type = syn::parse_str::(&self.context.ret_type) .unwrap_or_else(|_| panic!("Invalid type `{}`", &self.context.ret_type)); let set_type = syn::parse_str::(&self.context.arg_type) .unwrap_or_else(|_| panic!("Invalid type `{}`", &self.context.arg_type)); tokens.extend(quote! { #func_docs pub fn #setter_func_ident(&self, value: #set_type) { self.#try_setter_func_ident(value).unwrap_or_else(|err| panic!("failed to set value for key `{}`: {:?}", #key_name, err)) } #func_docs pub fn #try_setter_func_ident(&self, value: #set_type) -> std::result::Result<(), gio::glib::BoolError> { gio::prelude::SettingsExtManual::set(&self.0, #key_name, &value) } #func_docs pub fn #getter_func_ident(&self) -> #get_type { gio::prelude::SettingsExtManual::get(&self.0, #key_name) } #func_docs pub fn #default_value_func_ident(&self) -> #get_type { gio::glib::Variant::get(&gio::prelude::SettingsExt::default_value(&self.0, #key_name).unwrap()).unwrap() } }); } } #[derive(Clone)] pub struct Context { arg_type: String, ret_type: String, auxiliary: Option, } impl Context { pub fn new(type_: &str) -> Self { Self::new_dissimilar(type_, type_) } pub fn new_dissimilar(arg_type: &str, ret_type: &str) -> Self { Self { arg_type: arg_type.to_string(), ret_type: ret_type.to_string(), auxiliary: None, } } pub fn new_with_aux(type_: &str, auxiliary: proc_macro2::TokenStream) -> Self { Self { arg_type: type_.to_string(), ret_type: type_.to_string(), auxiliary: Some(auxiliary), } } } /// Creates an enum with given name and (variant name, variant value) tuple. It implements /// [`FromVariant`](gio::glib::FromVariant), [`ToVariant`](gio::glib::ToVariant), /// and [`StaticVariantType`](gio::glib::StaticVariantType). /// /// The input names are converted to pascal case fn new_variant_enum( name: &str, variants: &[(&str, Option)], visibility: syn::Visibility, ) -> proc_macro2::TokenStream { use heck::ToPascalCase; use syn::spanned::Spanned; let variant_names = variants .iter() .map(|(variant_name, _)| variant_name) .collect::>(); let variant_idents = variant_names .iter() .map(|variant_name| Ident::new(&variant_name.to_pascal_case(), variant_name.span())) .collect::>(); let variant_arms = variants .iter() .zip(variant_idents.iter()) .map(|((_, variant_value), variant_ident)| { if let Some(variant_value) = variant_value { quote! { #variant_ident = #variant_value } } else { quote! { #variant_ident } } }); let from_variant_arms = variant_names .iter() .zip(variant_idents.iter()) .map(|(variant_name, variant_ident)| { quote! { #variant_name => Some(Self::#variant_ident) } }); let to_variant_arms = variant_names .iter() .zip(variant_idents.iter()) .map(|(variant_name, variant_ident)| { quote! { Self::#variant_ident => gio::glib::ToVariant::to_variant(#variant_name) } }); let name_pascal_case = name.to_pascal_case(); let ident = Ident::new(&name_pascal_case, name_pascal_case.span()); quote! { #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[repr(i32)] #visibility enum #ident { #(#variant_arms),* } impl gio::glib::StaticVariantType for #ident { fn static_variant_type() -> std::borrow::Cow<'static, gio::glib::VariantTy> { std::borrow::Cow::Borrowed(gio::glib::VariantTy::STRING) } } impl gio::glib::FromVariant for #ident { fn from_variant(variant: &gio::glib::Variant) -> Option { match variant.get::()?.as_str() { #(#from_variant_arms),*, _ => None, } } } impl gio::glib::ToVariant for #ident { fn to_variant(&self) -> gio::glib::Variant { match self { #(#to_variant_arms),* } } } impl std::convert::From<#ident> for gio::glib::Variant { fn from(this: #ident) -> gio::glib::Variant { gio::glib::ToVariant::to_variant(&this) } } } } gsettings-macro-0.1.20/src/generators/string.rs000064400000000000000000000014411046102023000176260ustar 00000000000000use heck::ToPascalCase; use super::{Context, KeyGenerator, SchemaKey}; pub fn key_generator(key: &SchemaKey, aux_visibility: syn::Visibility) -> KeyGenerator<'_> { if let Some(ref choices) = key.choices { let choice_enum_name = key.name.to_pascal_case(); let choice_enum_token_stream = super::new_variant_enum( &choice_enum_name, &choices .choices .iter() .map(|choice| (choice.value.as_str(), None)) .collect::>(), aux_visibility, ); KeyGenerator::new( key, Context::new_with_aux(&choice_enum_name, choice_enum_token_stream), ) } else { KeyGenerator::new(key, Context::new_dissimilar("&str", "String")) } } gsettings-macro-0.1.20/src/lib.rs000064400000000000000000000437301046102023000147240ustar 00000000000000#![warn(rust_2018_idioms)] #![deny(rustdoc::broken_intra_doc_links)] #![doc = include_str!("../README.md")] mod generators; mod schema; use deluxe::SpannedValue; use proc_macro_error::{abort, emit_call_site_error, emit_error, emit_warning, proc_macro_error}; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned, Token, }; use std::{collections::HashMap, fs::File, io::BufReader}; use crate::{ generators::{GetResult, KeyGenerators, OverrideType}, schema::{KeySignature as SchemaKeySignature, SchemaList}, }; // TODO: // * Use `quote_spanned` where applicable for better error propagation on generated code // * Remove serde and deluxe dependencies // * Improve enum generation (create enum based on its definition, instead of by key; also add doc alias for its id) // * Add way to map setter and getters value // * Add `bind_#key writable`, `user_#key_value`, `connect_#key_writable_changed` variants // * Add trybuild tests // * Support for multiple schema #[derive(deluxe::ParseMetaItem)] struct GenSettings { file: SpannedValue, id: Option>, } #[derive(deluxe::ParseAttributes)] struct GenSettingsDefine { signature: Option>, key_name: Option>, arg_type: SpannedValue, ret_type: SpannedValue, } #[derive(deluxe::ParseAttributes)] struct GenSettingsSkip { signature: Option>, key_name: Option>, } struct SettingsStruct { attrs: Vec, vis: syn::Visibility, struct_token: Token![struct], ident: syn::Ident, semi_token: Token![;], } impl Parse for SettingsStruct { fn parse(input: ParseStream<'_>) -> syn::parse::Result { Ok(Self { attrs: input.call(syn::Attribute::parse_outer)?, vis: input.parse()?, struct_token: input.parse()?, ident: input.parse()?, semi_token: input.parse()?, }) } } impl ToTokens for SettingsStruct { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { self.vis.to_tokens(tokens); self.struct_token.to_tokens(tokens); self.ident.to_tokens(tokens); let field: syn::FieldsUnnamed = syn::parse_quote!((gio::Settings)); field.to_tokens(tokens); self.semi_token.to_tokens(tokens); } } /// Macro for typesafe [`gio::Settings`] key access. /// /// The macro's main purpose is to reduce the risk of mistyping a key, /// using the wrong method to access values, inputting incorrect values, /// and to reduce boilerplate. Additionally, the summary, description, /// and default value are included in the documentation of each generated /// method. This would be beneficial if you use tools like /// [`rust-analyzer`](https://rust-analyzer.github.io/). /// /// **⚠️ IMPORTANT ⚠️** /// /// Both `gio` and `glib` need to be in scope, so unless they are direct crate /// dependencies, you need to import them because `gen_settings` is using /// them internally. For example: /// /// ```ignore /// use gtk::{gio, glib}; /// ``` /// /// ### Example /// /// ```ignore /// use gsettings_macro::gen_settings; /// /// #[gen_settings(file = "./tests/io.github.seadve.test.gschema.xml")] /// pub struct ApplicationSettings; /// /// let settings = ApplicationSettings::new("io.github.seadve.test"); /// /// // `i` D-Bus type /// settings.set_window_width(100); /// assert_eq!(settings.window_width(), 100); /// /// // enums /// settings.set_alert_sound(AlertSound::Glass); /// assert_eq!(settings.alert_sound(), AlertSound::Glass); /// /// // bitflags /// settings.set_space_style(SpaceStyle::BEFORE_COLON | SpaceStyle::BEFORE_COMMA); /// assert_eq!( /// settings.space_style(), /// SpaceStyle::BEFORE_COLON | SpaceStyle::BEFORE_COMMA /// ); /// ``` /// /// Note: The file path is relative to the project root or where the /// `Cargo.toml` file is located. /// /// ### Generated methods /// /// The procedural macro generates the following [`gio::Settings`] methods /// for each key in the schema: /// /// * `set` -> `set_${key}`, which panics when writing in a readonly /// key, and `try_set_${key}`, which behaves the same as the original method. /// * `get` -> `${key}` /// * `connect_changed` -> `connect_${key}_changed` /// * `bind` -> `bind_${key}` /// * `create_action` -> `create_${key}_action` /// * `default_value` -> `${key}_default_value` /// * `reset` -> `reset_${key}` /// /// ### Known D-Bus type signatures /// /// The setter and getter methods has the following parameter and /// return type, depending on the key's type signature. /// /// | Type Signature | Parameter Type | Return Type | /// | -------------- | -------------- | ------------- | /// | b | `bool` | `bool` | /// | i | `i32` | `i32` | /// | u | `u32` | `u32` | /// | x | `i64` | `i64` | /// | t | `u64` | `u64` | /// | d | `f64` | `f64` | /// | (ii) | `(i32, i32`) | `(i32, i32`) | /// | as | `&[&str]` | `Vec` | /// | s * | `&str` | `String` | /// /// \* If the key of type signature `s` has no `choice` attribute /// specified in the GSchema, the parameter and return types stated /// in the table would be applied. Otherwise, it will generate an /// enum, like described in the next section, and use it as the parameter /// and return types, instead of `&str` and `String` respectively. /// /// It will not compile if the type signature is not defined above. /// However, it is possible to explicitly skip generating methods /// for a specific key or type signature using the attribute /// `#[gen_settings_skip]`, or define a custom parameter and return /// types using `#[gen_settings_define]` attribute. The usage of /// the latter will be further explained in the following sections. /// /// ### Enums and Flags /// /// The macro will also automatically generate enums or flags. If it is /// an enum, it would generated a normal Rust enum with each nick /// specified in the GSchema converted to pascal case as an enum variant. /// The enum would implement both [`ToVariant`] and [`FromVariant`], [`Clone`], /// [`Hash`], [`PartialEq`], [`Eq`], [`PartialOrd`], and [`Ord`]. On /// the other hand, if it is a flag, it would generate bitflags /// same as the bitflags generated by the [`bitflags`] macro with each /// nick specified in the GSchema converted to screaming snake case as /// a const flag. /// /// The generated types, enum or bitflags, would have the same /// visibility and scope with the generated struct. /// /// ### Skipping methods generation /// /// This would be helpful if you want to have full control /// with the key without the macro intervening. For example: /// /// ```ignore /// use gsettings_macro::gen_settings; /// /// #[gen_settings( /// file = "./tests/io.github.seadve.test.gschema.xml", /// id = "io.github.seadve.test" /// )] /// // Skip generating methods for keys with type signature `(ss)` /// #[gen_settings_skip(signature = "(ss)")] /// // Skip generating methods for the key of name `some-key-name` /// #[gen_settings_skip(key_name = "some-key-name")] /// pub struct Settings; /// /// impl Settings { /// pub fn set_some_key_name(value: &std::path::Path) { /// ... /// } /// } /// ``` /// /// ### Defining custom types /// /// ```ignore /// use gsettings_macro::gen_settings; /// /// use std::path::{Path, PathBuf}; /// /// #[gen_settings(file = "./tests/io.github.seadve.test.gschema.xml")] /// // Define custom parameter and return types for keys with type `(ss)` /// #[gen_settings_define( /// signature = "(ss)", /// arg_type = "(&str, &str)", /// ret_type = "(String, String)" /// )] /// // Define custom parameter and return types for key with name `cache-dir` /// #[gen_settings_define(key_name = "cache-dir", arg_type = "&Path", ret_type = "PathBuf")] /// pub struct SomeAppSettings; /// /// let settings = SomeAppSettings::new("io.github.seadve.test"); /// /// settings.set_cache_dir(Path::new("/some_dir")); /// assert_eq!(settings.cache_dir(), PathBuf::from("/some_dir")); /// /// settings.set_string_tuple(("hi", "hi2")); /// assert_eq!(settings.string_tuple(), ("hi".into(), "hi2".into())); /// ``` /// /// The type specified in `arg_type` and `ret_type` has to be on scope or /// you can specify the full path. /// /// If you somehow do not want an enum parameter and return types for `s` /// type signature with choices. You can also use this to override that behavior. /// /// Note: The type has to implement both [`ToVariant`] and [`FromVariant`] or it /// would fail to compile. /// /// ### Default trait /// /// The schema id can be specified as an attribute, making it implement /// [`Default`] and create a `new` constructor without parameters. /// Otherwise, it will not implement [`Default`] and would require the /// schema id as an parameter in the the constructor or the `new` method. /// /// The following is an example of defining the `id` attribute in the macro: /// /// ```ignore /// use gsettings_macro::gen_settings; /// /// #[gen_settings( /// file = "./tests/io.github.seadve.test.gschema.xml", /// id = "io.github.seadve.test" /// )] /// pub struct ApplicationSettings; /// /// // The id is specified above so it is not needed /// // to specify it in the constructor. /// let settings = ApplicationSettings::new(); /// let another_instance = ApplicationSettings::default(); /// ``` /// /// [`gio::Settings`]: https://docs.rs/gio/latest/gio/struct.Settings.html /// [`ToVariant`]: https://docs.rs/glib/latest/glib/variant/trait.ToVariant.html /// [`FromVariant`]: https://docs.rs/glib/latest/glib/variant/trait.FromVariant.html /// [`bitflags`]: https://docs.rs/bitflags/latest/bitflags/macro.bitflags.html #[proc_macro_attribute] #[proc_macro_error] pub fn gen_settings( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let GenSettings { file: file_attr, id: id_attr, } = match deluxe::parse2(attr.into()) { Ok(gen_settings) => gen_settings, Err(err) => return err.to_compile_error().into(), }; let file_attr_span = file_attr.span(); let schema_file_path = SpannedValue::into_inner(file_attr); // Parse schema list let schema_file = File::open(&schema_file_path).unwrap_or_else(|err| { abort!(file_attr_span, "failed to open schema file: {}", err); }); let schema_list: SchemaList = quick_xml::de::from_reader(BufReader::new(schema_file)) .unwrap_or_else(|err| abort!(file_attr_span, "failed to parse schema file: {}", err)); // Get first schema let mut schemas = schema_list.schemas; if schemas.len() > 1 { emit_warning!(file_attr_span, "this macro only supports a single schema"); } let schema = schemas .pop() .unwrap_or_else(|| abort!(file_attr_span, "schema file must have a single schema")); // Get schema id let schema_id = if let Some(id_attr) = id_attr { let id_attr_span = id_attr.span(); let schema_id = SpannedValue::into_inner(id_attr); if schema.id != schema_id { emit_error!( id_attr_span, "id does not match the one specified in the schema file" ); } Some(schema_id) } else { None }; let settings_struct = syn::parse_macro_input!(item as SettingsStruct); // Parse overrides let known_signatures = schema .keys .iter() .map(|key| { key.signature().unwrap_or_else(|| { abort!(file_attr_span, "expected one of `type`, `enum` or `flags` specified attribute on key `{}` in the schema", key.name); }) }) .collect::>(); let known_key_names = schema .keys .iter() .map(|key| key.name.as_str()) .collect::>(); let mut signature_overrides = HashMap::new(); let mut key_name_overrides = HashMap::new(); for attr in &settings_struct.attrs { let (signature, key_name, override_type) = if attr.path().is_ident("gen_settings_define") { let GenSettingsDefine { signature, key_name, arg_type, ret_type, } = match deluxe::parse_attributes::<_, GenSettingsDefine>(attr) { Ok(gen_settings) => gen_settings, Err(err) => { emit_error!(attr.span(), err); continue; } }; ( signature, key_name, OverrideType::Define { arg_type: SpannedValue::into_inner(arg_type), ret_type: SpannedValue::into_inner(ret_type), }, ) } else if attr.path().is_ident("gen_settings_skip") { let GenSettingsSkip { signature, key_name, } = match deluxe::parse_attributes::<_, GenSettingsSkip>(attr) { Ok(gen_settings) => gen_settings, Err(err) => { emit_error!(attr.span(), err); continue; } }; (signature, key_name, OverrideType::Skip) } else { emit_error!( attr.span(), "expected `#[gen_settings_define( .. )]` or `#[gen_settings_skip( .. )]`" ); continue; }; match (signature, key_name) { (Some(_), Some(_)) => { emit_error!( attr.span(), "cannot specify both `signature` and `key_name`" ) } (None, None) => { emit_error!(attr.span(), "must specify either `signature` or `key_name`") } (Some(signature), None) => { let signature_span = signature.span(); let signature_str = SpannedValue::into_inner(signature); let signature_type = SchemaKeySignature::Type(signature_str); if !known_signatures.contains(&signature_type) { emit_error!(signature_span, "useless define for this signature"); } if signature_overrides.get(&signature_type).is_some() { emit_error!(signature_span, "duplicate override"); } signature_overrides.insert(signature_type, override_type); } (None, Some(key_name)) => { let key_name_span = key_name.span(); let key_name_str = SpannedValue::into_inner(key_name); if !known_key_names.contains(&key_name_str.as_str()) { emit_error!(key_name_span, "key_name not found in the schema"); } if key_name_overrides.get(&key_name_str).is_some() { emit_error!(key_name_span, "duplicate override"); } key_name_overrides.insert(key_name_str, override_type); } } } // Generate keys let enums = schema_list .enums .iter() .map(|enum_| (enum_.id.to_string(), enum_)) .collect::>(); let flags = schema_list .flags .iter() .map(|flag| (flag.id.to_string(), flag)) .collect::>(); let mut key_generators = KeyGenerators::with_defaults(enums, flags); key_generators.add_signature_overrides(signature_overrides); key_generators.add_key_name_overrides(key_name_overrides); // Generate code let mut aux_token_stream = proc_macro2::TokenStream::new(); let mut keys_token_stream = proc_macro2::TokenStream::new(); for key in &schema.keys { match key_generators .get(key, settings_struct.vis.clone()) .unwrap() { GetResult::Skip => (), GetResult::Some(generator) => { keys_token_stream.extend(generator.to_token_stream()); if let Some(aux) = generator.auxiliary() { aux_token_stream.extend(aux); } } GetResult::Unknown => { emit_call_site_error!( "unsupported {} signature used by key `{}`; consider using `#[gen_settings_define( .. )]` or skip it with `#[gen_settings_skip( .. )]`", &key.signature().unwrap(), &key.name, ) } } } let constructor_token_stream = if let Some(ref schema_id) = schema_id { quote! { pub fn new() -> Self { Self(gio::Settings::new(#schema_id)) } } } else { quote! { pub fn new(schema_id: &str) -> Self { Self(gio::Settings::new(schema_id)) } } }; let struct_ident = &settings_struct.ident; let mut expanded = quote! { #aux_token_stream #[derive(Clone, Hash, PartialEq, Eq, gio::glib::ValueDelegate)] #[value_delegate(nullable)] #settings_struct impl #struct_ident { #constructor_token_stream #keys_token_stream } impl std::ops::Deref for #struct_ident { type Target = gio::Settings; fn deref(&self) -> &Self::Target { &self.0 } } impl std::ops::DerefMut for #struct_ident { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl std::fmt::Debug for #struct_ident { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { std::fmt::Debug::fmt(&self.0, f) } } }; if schema_id.is_some() { expanded.extend(quote! { impl Default for #struct_ident { fn default() -> Self { Self::new() } } }); } expanded.into() } gsettings-macro-0.1.20/src/schema.rs000064400000000000000000000054041046102023000154120ustar 00000000000000use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct SchemaList { #[serde(rename = "enum", default)] pub enums: Vec, #[serde(default)] pub flags: Vec, #[serde(rename = "schema")] pub schemas: Vec, } #[derive(Debug, Deserialize)] pub struct Enum { #[serde(rename = "@id")] pub id: String, #[serde(rename = "value")] pub values: Vec, } #[derive(Debug, Deserialize)] pub struct EnumValues { #[serde(rename = "@nick")] pub nick: String, #[serde(rename = "@value")] pub value: i32, } #[derive(Debug, Deserialize)] pub struct Flag { #[serde(rename = "@id")] pub id: String, #[serde(rename = "value")] pub values: Vec, } #[derive(Debug, Deserialize)] pub struct FlagValues { #[serde(rename = "@nick")] pub nick: String, #[serde(rename = "@value")] pub value: u32, } #[derive(Debug, Deserialize)] pub struct Schema { #[serde(rename = "@id")] pub id: String, #[serde(rename = "key")] pub keys: Vec, } #[derive(Debug, Deserialize)] pub struct Key { #[serde(rename = "@name")] pub name: String, #[serde(rename = "@type")] type_: Option, #[serde(rename = "@enum")] enum_id: Option, #[serde(rename = "@flags")] flag_id: Option, pub default: String, pub summary: Option, pub description: Option, pub choices: Option, pub range: Option, } #[derive(PartialEq, Eq, Hash)] pub enum KeySignature { Type(String), Enum(String), Flag(String), } impl std::fmt::Display for KeySignature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { KeySignature::Type(type_) => write!(f, "`{}` type", type_), KeySignature::Enum(enum_) => write!(f, "`{}` enum", enum_), KeySignature::Flag(flag) => write!(f, "`{}` flag", flag), } } } impl Key { pub fn signature(&self) -> Option { match (&self.type_, &self.enum_id, &self.flag_id) { (Some(type_name), None, None) => Some(KeySignature::Type(type_name.to_string())), (None, Some(enum_id), None) => Some(KeySignature::Enum(enum_id.to_string())), (None, None, Some(flag_id)) => Some(KeySignature::Flag(flag_id.to_string())), _ => None, } } } #[derive(Debug, Deserialize)] pub struct Choice { #[serde(rename = "@value")] pub value: String, } #[derive(Debug, Deserialize)] pub struct Choices { #[serde(rename = "choice")] pub choices: Vec, } #[derive(Debug, Deserialize)] pub struct Range { #[serde(rename = "@max")] pub max: Option, #[serde(rename = "@min")] pub min: Option, }