├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── docs ├── app_keys.md ├── arg_groups.md └── arg_keys.md └── src ├── de ├── app │ ├── appsettings.rs │ ├── color.rs │ └── mod.rs ├── arg │ ├── arg_action.rs │ ├── mod.rs │ ├── value_hint.rs │ └── value_parser.rs ├── group.rs ├── macros.rs └── mod.rs ├── documents.rs ├── lib.rs ├── tests.rs └── yaml.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .vscode/ 4 | cargo_expand_result.rs 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | # 0.5.1 4 | 5 | ## Fixes 6 | - Relax requirement of deserizaler for `override-arg`. 7 | - Fix env feature flag. 8 | 9 | # 0.5.0 10 | 11 | ## Features 12 | - Bump Clap version to 3.2 . 13 | 14 | # 0.4.0 15 | 16 | ## Breaking Changes 17 | 18 | - Change schema for Argumets(`args`) and SubCommands(`subcommands`) from Hash to Array of Hash. Old behavior can be used by `args_map` and, `subcommands_map`. 19 | - Deprecated yaml feature. Use `serde-yaml >= 0.9` instead. 20 | 21 | ## Features 22 | 23 | - Support Clap 3.1 24 | - Can override arguments by using `DeserizalizeSeed for CommandWrap`. 25 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "clap-serde" 3 | version = "0.5.1" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "Provides a wrapper to deserialize clap app using serde." 7 | repository = "https://github.com/aobatact/clap-serde" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | [package.metadata.docs.rs] 11 | features = ["docsrs"] 12 | 13 | [features] 14 | default = ["snake-case-key", "allow-deprecated"] 15 | env = ["clap/env"] 16 | pascal-case-key = ["convert_case"] 17 | kebab-case-key = ["convert_case"] 18 | snake-case-key = [] 19 | yaml = ["yaml-rust"] 20 | color = ["clap/color"] 21 | docsrs = ["snake-case-key", "yaml", "env", "color"] 22 | allow-deprecated = [] 23 | override-arg = [] 24 | 25 | [dependencies] 26 | clap = { version = "3.2.16", default-features = false, features = ["std"]} 27 | serde = { version = "1", features = ["derive"]} 28 | yaml-rust = { version = "0.4.5", default-features = false, optional = true } 29 | convert_case = { version = "0.6.0", optional = true } 30 | 31 | [dev-dependencies] 32 | serde_json = { version = "1.0.75" } 33 | toml = { version = "0.5.8" } 34 | serde_yaml = { version = "0.9.2" } 35 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 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 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clap-serde 2 | Provides a wrapper to deserialize [clap](https://crates.io/crates/clap) app using [serde](https://crates.io/crates/serde). 3 | 4 | [![Crates.io](https://img.shields.io/crates/v/clap-serde?style=flat-square)](https://crates.io/crates/clap-serde) 5 | [![License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](https://github.com/aobatact/clap-serde/blob/main/LICENSE-APACHE) 6 | [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](https://github.com/aobatact/clap-serde/blob/main/LICENSE-MIT) 7 | [![API Reference](https://img.shields.io/docsrs/clap-serde?style=flat-square)](https://docs.rs/clap-serde) 8 | 9 | ## toml 10 | 11 | ```rust 12 | const CLAP_TOML: &'static str = r#" 13 | name = "app_clap_serde" 14 | version = "1.0" 15 | author = "toml_tester" 16 | about = "test-clap-serde" 17 | [subcommands] 18 | sub1 = { about = "subcommand_1" } 19 | [subcommands.sub2] 20 | about = "subcommand_2" 21 | [args] 22 | apple = { short = "a" } 23 | banana = { short = "b", long = "banana", aliases = ["musa_spp"] } 24 | [groups] 25 | fruit = { args = ["apple", "banana"] } 26 | "#; 27 | 28 | let app: clap::App = toml::from_str::(CLAP_TOML) 29 | .expect("parse failed") 30 | .into(); 31 | assert_eq!(app.get_name(), "app_clap_serde"); 32 | assert_eq!(app.get_about(), Some("test-clap-serde")); 33 | ``` 34 | 35 | ## json 36 | ```rust 37 | const CLAP_JSON: &'static str = r#"{ 38 | "name" : "app_clap_serde", 39 | "version" : "1.0" , 40 | "author" : "json_tester", 41 | "about" : "test-clap-serde", 42 | "subcommands" : [ 43 | { "sub1" : {"about" : "subcommand_1"}}, 44 | { "sub2" : {"about" : "subcommand_2"}} 45 | ], 46 | "args" : [ 47 | { "apple" : {"short" : "a" } }, 48 | { "banana" : {"short" : "b", "long" : "banana", "aliases" : [ "musa_spp" ]} } 49 | ], 50 | "groups" : { 51 | "fruit" : { "args" : ["apple", "banana"] } 52 | } 53 | }"#; 54 | 55 | let app: clap::App = serde_json::from_str::(CLAP_JSON) 56 | .expect("parse failed") 57 | .into(); 58 | assert_eq!(app.get_name(), "app_clap_serde"); 59 | assert_eq!(app.get_about(), Some("test-clap-serde")); 60 | ``` 61 | 62 | ## yaml 63 | ```rust 64 | const CLAP_YAML: &'static str = r#" 65 | name: app_clap_serde 66 | version : "1.0" 67 | about : yaml_support! 68 | author : yaml_supporter 69 | 70 | args: 71 | - apple : 72 | short: a 73 | - banana: 74 | short: b 75 | long: banana 76 | aliases : 77 | - musa_spp 78 | 79 | subcommands: 80 | - sub1: 81 | about : subcommand_1 82 | - sub2: 83 | about : subcommand_2 84 | 85 | "#; 86 | let app: clap_serde::CommandWrap = serde_yaml::from_str(CLAP_YAML).expect("fail to make yaml"); 87 | assert_eq!(app.get_name(), "app_clap_serde"); 88 | ``` 89 | 90 | # features 91 | ## env 92 | Enables env feature in clap. 93 | ## yaml (deprecated, use serde-yaml instead) 94 | Enables to use yaml. 95 | ## color 96 | Enablse color feature in clap. 97 | 98 | ## (key case settings) 99 | Settings names format for keys and [`AppSettings`](`clap::AppSettings`). 100 | #### snake-case-key 101 | snake_case. Enabled by default. 102 | #### pascal-case-key 103 | PascalCase. Same as variants name in enum at `AppSettings`. 104 | #### kebab-case-key 105 | kebab-case. 106 | 107 | ## allow-deprecated 108 | Allow deprecated keys, settings. Enabled by default. 109 | 110 | ## override-args 111 | 112 | Override a `Arg` with `DeserializeSeed`. 113 | 114 | ```rust 115 | # #[cfg(feature = "override-arg")] 116 | # { 117 | # use clap::{Command, Arg}; 118 | use serde::de::DeserializeSeed; 119 | 120 | const CLAP_TOML: &str = r#" 121 | name = "app_clap_serde" 122 | version = "1.0" 123 | author = "aobat" 124 | about = "test-clap-serde" 125 | [args] 126 | apple = { short = "a" } 127 | "#; 128 | let app = Command::new("app").arg(Arg::new("apple").default_value("aaa")); 129 | let wrap = clap_serde::CommandWrap::from(app); 130 | let mut de = toml::Deserializer::new(CLAP_TOML); 131 | let wrap2 = wrap.deserialize(&mut de).unwrap(); 132 | let apple = wrap2 133 | .get_arguments() 134 | .find(|a| a.get_id() == "apple") 135 | .unwrap(); 136 | assert!(apple.get_short() == Some('a')); 137 | assert!(apple.get_default_values() == ["aaa"]); 138 | # } 139 | ``` 140 | -------------------------------------------------------------------------------- /docs/app_keys.md: -------------------------------------------------------------------------------- 1 | # App Key List 2 | - clap : 3.0.10 3 | - clap-serde : 0.3.0 4 | 5 | (Keys can be changed by the case-key features) 6 | 7 | | key | type | feature | 8 | | - | - | - | 9 | | about| `&str`| 10 | | after_help| `&str`| 11 | | after_long_help| `&str`| 12 | | alias| `&str`| 13 | | aliases| `Vec<&str>`| 14 | | (arg) | not supported single arg (now)| 15 | | args | [Args](`crate::documents::arg_keys`) | 16 | | author| `&str`| 17 | | before_help| `&str`| 18 | | before_long_help| `&str`| 19 | | bin_name| `&str`| 20 | | color | [`ColorChoice`](#colorchoice)| color | 21 | | display_order| `usize`| 22 | | global_setting | [`AppSettings`](#appsettings) | 23 | | global_settings | `Vec<`[`AppSettings`](#appsettings)`>` | 24 | | (group) | not supported single group (now)| 25 | | groups | [ArgGroup](`crate::documents::arg_groups_keys`) | 26 | | help_heading| `Option<&str>`| 27 | | help_template| `&str`| 28 | | long_about| `&str`| 29 | | long_flag| `&str`| 30 | | long_flag_alias| `&str`| 31 | | long_flag_aliases| `Vec<&str>`| 32 | | long_version| `&str`| 33 | | max_term_width| `usize`| 34 | | name| `&str`| 35 | | override_help| `&str`| 36 | | override_usage| `&str`| 37 | | setting | [`AppSettings`](#appsettings) | 38 | | settings | `Vec<`[`AppSettings`](#appsettings)`>` | 39 | | short_flag|`char`| 40 | | short_flag_alias|`char`| 41 | | short_flag_aliases| `Vec`| 42 | | (subcommand) | not supported single subcommand (now)| 43 | | subcommands | `Map<&str, App>`| 44 | | term_width| `usize`| 45 | | version| `&str`| 46 | | visible_alias| `&str`| 47 | | visible_aliases| `Vec<&str>`| 48 | | visible_long_flag_alias| `&str`| 49 | | visible_long_flag_aliases| `Vec<&str>`| 50 | | visible_short_flag_alias|`char`| 51 | | visible_short_flag_aliases| `Vec`| 52 | 53 | 54 | ## AppSettings 55 | For setting, settings, global_setting, global_settings, 56 | 57 | - ignore_errors 58 | - wait_on_error 59 | - allow_hyphen_values 60 | - allow_negative_numbers 61 | - all_args_override_self 62 | - allow_missing_positional 63 | - trailing_var_arg 64 | - dont_delimit_trailing_values 65 | - infer_long_args 66 | - infer_subcommands 67 | - subcommand_required 68 | - subcommand_required_else_help 69 | - allow_external_subcommands 70 | - multicall 71 | - allow_invalid_utf_8_for_external_subcommands 72 | - use_long_format_for_help_subcommand 73 | - subcommands_negate_reqs 74 | - args_negate_subcommands 75 | - subcommand_precedence_over_arg 76 | - arg_required_else_help 77 | - derive_display_order 78 | - dont_collapse_args_in_usage 79 | - next_line_help 80 | - disable_colored_help 81 | - disable_help_flag 82 | - disable_help_subcommand 83 | - disable_version_flag 84 | - propagate_version 85 | - hidden 86 | - hide_possible_values 87 | - help_expected 88 | - no_binary_name 89 | - no_auto_help 90 | - no_auto_version 91 | 92 | # ColorChoice 93 | - auto 94 | - always 95 | - never 96 | -------------------------------------------------------------------------------- /docs/arg_groups.md: -------------------------------------------------------------------------------- 1 | # ArgGropus Key List 2 | 3 | - clap : 3.0.10 4 | - clap-serde : 0.3.0 5 | 6 | (Keys can be changed by the case-key features) 7 | 8 | |key| type| 9 | |-|-| 10 | |arg| `&str` | 11 | |args| `Vec<&str>` | 12 | |conflicts_with| `&str` | 13 | |conflicts_with_all| `Vec<&str>` | 14 | |multiple| `bool`| 15 | |name| `&str`| 16 | |required| `bool`| 17 | |requires| `&str`| 18 | |requires_all| `Vec<&str>`| 19 | -------------------------------------------------------------------------------- /docs/arg_keys.md: -------------------------------------------------------------------------------- 1 | # Arg Key List 2 | - clap | 3.0.10 3 | - clap-serde | 0.3.0 4 | 5 | (Keys can be changed by the case-key features) 6 | 7 | |key | type|feature| 8 | |-|-|-| 9 | |alias|`&str`|| 10 | |aliases|`Vec<&str>`|| 11 | |allow_hyphen_values|`bool`|| 12 | |allow_invalid_utf8|`bool`|| 13 | |conflicts_with|`&str`|| 14 | |conflicts_with_all|`Vec<&str>`|| 15 | |default_missing_value|`&str`|| 16 | |default_missing_values|`Vec<&str>`|| 17 | |default_value|`&str`|| 18 | |default_value_if | `(&str,Option<&str>,Option<&str>)` || 19 | |default_value_ifs|`Vec<(&str,Option<&str>,Option<&str>)>` || 20 | |display_order|`usize`|| 21 | |env | `&str`|env| 22 | |exclusive|`bool`|| 23 | |forbid_empty_values|`bool`|| 24 | |global|`bool`|| 25 | |group|`&str`|| 26 | |groups|`Vec<&str>`|| 27 | |help|`&str`|| 28 | |help_heading|`&str`|| 29 | |hide|`bool`|| 30 | |hide_default_value|`bool`|| 31 | |hide_env | `bool`|env| 32 | |hide_env_values | `bool`|env| 33 | |hide_long_help|`bool`|| 34 | |hide_possible_values|`bool`|| 35 | |hide_short_help|`bool`|| 36 | |ignore_case|`bool`|| 37 | |index|`usize`|| 38 | |last|`bool`|| 39 | |long|`&str`|| 40 | |long_help|`&str`|| 41 | |max_occurrences|`usize`|| 42 | |max_values|`usize`|| 43 | |min_values|`usize`|| 44 | |multiple_occurrences|`bool`|| 45 | |multiple_values|`bool`|| 46 | |name|`&str`|| 47 | |next_line_help|`bool`|| 48 | |number_of_values|`usize`|| 49 | |overrides_with|`&str`|| 50 | |overrides_with_all|`Vec<&str>`|| 51 | |possible_value|`&str`|| 52 | |possible_values|`Vec<&str>`|| 53 | |raw|`bool`|| 54 | |require_delimiter|`bool`|| 55 | |require_equals|`bool`|| 56 | |required|`bool`|| 57 | |required_if_eq| `(&str,&str)`|| 58 | |required_if_eq_all|`Vec<(&str,&str)>`|| 59 | |required_if_eq_any|`Vec<(&str,&str)>`|| 60 | |required_unless_present|`&str`|| 61 | |required_unless_present_any|`Vec<&str>`|| 62 | |required_unless_present_all|`Vec<&str>`|| 63 | |requires|`&str`|| 64 | |requires_all|`Vec<&str>`|| 65 | |requires_if | `(&str,&str)` || 66 | |requires_ifs|`Vec<(&str,&str)>`|| 67 | |short|`char`|| 68 | |short_alias|`char`|| 69 | |short_aliases|`Vec`|| 70 | |takes_value|`bool`|| 71 | |use_delimiter|`bool`|| 72 | |(validator_regex) | not supported yet|| 73 | |value_hint | [`ValueHint`](#valuehint)|| 74 | |value_delimiter|`char`|| 75 | |value_name|`&str`|| 76 | |value_names|`Vec<&str>`|| 77 | |value_terminator|`&str`|| 78 | |visible_alias|`&str`|| 79 | |visible_aliases|`Vec<&str>`|| 80 | |visible_short_alias|`char`|| 81 | |visible_short_aliases|`Vec`|| 82 | 83 | ## ValueHint 84 | 85 | - unknown 86 | - other 87 | - any_path 88 | - file_path 89 | - dir_path 90 | - executable_path 91 | - command_name 92 | - command_string 93 | - command_with_arguments 94 | - username 95 | - hostname 96 | - url 97 | - email_address 98 | -------------------------------------------------------------------------------- /src/de/app/appsettings.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | use clap::{AppFlags, AppSettings}; 3 | use serde::{de::DeserializeSeed, Deserialize}; 4 | 5 | enum_de!(AppSettings,AppSetting1, 6 | #[derive(Deserialize, Clone, Copy)] 7 | #[cfg_attr(feature = "kebab-case-key" ,serde(rename_all = "kebab-case"))] 8 | #[cfg_attr(feature = "snake-case-key" ,serde(rename_all = "snake_case"))] 9 | { 10 | #[cfg(feature="allow-deprecated")] 11 | IgnoreErrors, 12 | #[cfg(feature="allow-deprecated")] 13 | WaitOnError, 14 | #[cfg(feature="allow-deprecated")] 15 | AllowHyphenValues, 16 | #[cfg(feature="allow-deprecated")] 17 | AllowNegativeNumbers, 18 | #[cfg(feature="allow-deprecated")] 19 | AllArgsOverrideSelf, 20 | #[cfg(feature="allow-deprecated")] 21 | AllowMissingPositional, 22 | #[cfg(feature="allow-deprecated")] 23 | TrailingVarArg, 24 | #[cfg(feature="allow-deprecated")] 25 | DontDelimitTrailingValues, 26 | #[cfg(feature="allow-deprecated")] 27 | InferLongArgs, 28 | #[cfg(feature="allow-deprecated")] 29 | InferSubcommands, 30 | #[cfg(feature="allow-deprecated")] 31 | SubcommandRequired, 32 | #[cfg(feature="allow-deprecated")] 33 | SubcommandRequiredElseHelp, 34 | #[cfg(feature="allow-deprecated")] 35 | AllowExternalSubcommands, 36 | #[cfg(all(feature = "unstable-multicall", feature="allow-depreacted"))] 37 | Multicall, 38 | #[cfg(feature="allow-deprecated")] 39 | AllowInvalidUtf8ForExternalSubcommands, 40 | #[cfg(feature="allow-deprecated")] 41 | UseLongFormatForHelpSubcommand, 42 | #[cfg(feature="allow-deprecated")] 43 | SubcommandsNegateReqs, 44 | #[cfg(feature="allow-deprecated")] 45 | ArgsNegateSubcommands, 46 | #[cfg(feature="allow-deprecated")] 47 | SubcommandPrecedenceOverArg, 48 | #[cfg(feature="allow-deprecated")] 49 | ArgRequiredElseHelp, 50 | DeriveDisplayOrder, 51 | #[cfg(feature="allow-deprecated")] 52 | DontCollapseArgsInUsage, 53 | #[cfg(feature="allow-deprecated")] 54 | NextLineHelp, 55 | #[cfg(feature="allow-deprecated")] 56 | DisableColoredHelp, 57 | #[cfg(feature="allow-deprecated")] 58 | DisableHelpFlag, 59 | #[cfg(feature="allow-deprecated")] 60 | DisableHelpSubcommand, 61 | #[cfg(feature="allow-deprecated")] 62 | DisableVersionFlag, 63 | #[cfg(feature="allow-deprecated")] 64 | PropagateVersion, 65 | #[cfg(feature="allow-deprecated")] 66 | Hidden, 67 | #[cfg(feature="allow-deprecated")] 68 | HidePossibleValues, 69 | #[cfg(feature="allow-deprecated")] 70 | HelpExpected, 71 | #[cfg(feature="allow-deprecated")] 72 | NoBinaryName, 73 | NoAutoHelp, 74 | NoAutoVersion,} 75 | ); 76 | 77 | pub(crate) struct AppSettingSeed; 78 | impl<'de> DeserializeSeed<'de> for AppSettingSeed { 79 | type Value = AppSettings; 80 | 81 | fn deserialize(self, deserializer: D) -> Result 82 | where 83 | D: serde::Deserializer<'de>, 84 | { 85 | AppSetting1::deserialize(deserializer).map(|s| s.into()) 86 | } 87 | } 88 | 89 | pub(crate) struct AppSettingsSeed; 90 | impl<'de> DeserializeSeed<'de> for AppSettingsSeed { 91 | type Value = AppFlags; 92 | 93 | fn deserialize(self, deserializer: D) -> Result 94 | where 95 | D: serde::Deserializer<'de>, 96 | { 97 | Vec::::deserialize(deserializer).map(|s| { 98 | s.into_iter() 99 | .fold(AppFlags::default(), |a, b| a | AppSettings::from(b)) 100 | }) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/de/app/color.rs: -------------------------------------------------------------------------------- 1 | use clap::ColorChoice; 2 | use serde::{de::DeserializeSeed, Deserialize}; 3 | 4 | enum_de!(ColorChoice,ColorChoice1, 5 | #[derive(Deserialize, Clone, Copy)] 6 | #[cfg_attr(feature = "kebab-case-key" ,serde(rename_all = "kebab-case"))] 7 | #[cfg_attr(feature = "snake-case-key" ,serde(rename_all = "snake_case"))] 8 | { 9 | Auto, 10 | Always, 11 | Never, 12 | }); 13 | 14 | pub struct ColorChoiceSeed; 15 | impl<'de> DeserializeSeed<'de> for ColorChoiceSeed { 16 | type Value = ColorChoice; 17 | 18 | fn deserialize(self, deserializer: D) -> Result 19 | where 20 | D: serde::Deserializer<'de>, 21 | { 22 | ColorChoice1::deserialize(deserializer).map(|c| c.into()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/de/app/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::CommandWrap; 2 | use appsettings::*; 3 | use clap::Command; 4 | use serde::{ 5 | de::{DeserializeSeed, Error, Visitor}, 6 | Deserialize, 7 | }; 8 | 9 | mod appsettings; 10 | #[cfg(feature = "color")] 11 | mod color; 12 | 13 | const TMP_APP_NAME: &str = "__tmp__deserialize__name__"; 14 | impl<'de> Deserialize<'de> for CommandWrap<'de> { 15 | fn deserialize(deserializer: D) -> Result 16 | where 17 | D: serde::Deserializer<'de>, 18 | { 19 | deserializer 20 | .deserialize_map(CommandVisitor(Command::new(TMP_APP_NAME))) 21 | //check the name so as not to expose the tmp name. 22 | .and_then(|r| { 23 | if r.app.get_name() != TMP_APP_NAME { 24 | Ok(r) 25 | } else { 26 | Err(::missing_field("name")) 27 | } 28 | }) 29 | } 30 | } 31 | 32 | struct CommandVisitor<'a>(Command<'a>); 33 | 34 | impl<'a> Visitor<'a> for CommandVisitor<'a> { 35 | type Value = CommandWrap<'a>; 36 | 37 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 38 | formatter.write_str("Command Map") 39 | } 40 | 41 | fn visit_map(self, mut map: A) -> Result 42 | where 43 | A: serde::de::MapAccess<'a>, 44 | { 45 | let mut app = self.0; 46 | //TODO: check the first key to get name from the input? 47 | //currently the name change in `Clap::Command::name` doesn't change the `Clap::Command::id` so might cause problems? 48 | while let Some(key) = map.next_key::<&str>()? { 49 | app = parse_value!(key, app, map, Command, { 50 | (about, &str), 51 | (after_help, &str), 52 | (after_long_help, &str), 53 | (alias, &str), 54 | ref (aliases, Vec<&str>), 55 | (allow_external_subcommands, bool), 56 | (allow_hyphen_values, bool), 57 | (allow_invalid_utf8_for_external_subcommands, bool), 58 | (allow_missing_positional, bool), 59 | (allow_negative_numbers, bool), 60 | //arg : not supported single arg(now) 61 | //args : specialized 62 | (arg_required_else_help, bool), 63 | (args_conflicts_with_subcommands, bool), 64 | (args_override_self, bool), 65 | (author, &str), 66 | (before_help, &str), 67 | (before_long_help, &str), 68 | (bin_name, &str), 69 | // color : specialized 70 | (disable_colored_help, bool), 71 | (disable_help_flag, bool), 72 | (disable_help_subcommand, bool), 73 | (disable_version_flag, bool), 74 | (display_name, &str), 75 | (display_order, usize), 76 | (dont_collapse_args_in_usage, bool), 77 | (dont_delimit_trailing_values, bool), 78 | // global_setting : specialized 79 | // global_settings : specialized (though the original method is deprecated) 80 | // group : not supported single group 81 | // groups : specialized 82 | (help_expected, bool), 83 | (help_template, &str), 84 | (hide, bool), 85 | (hide_possible_values, bool), 86 | (ignore_errors, bool), 87 | (infer_long_args, bool), 88 | (infer_subcommands, bool), 89 | (long_about, &str), 90 | (long_flag, &str), 91 | (long_flag_alias, &str), 92 | ref (long_flag_aliases, Vec<&str>), 93 | (long_version, &str), 94 | (max_term_width, usize), 95 | (multicall, bool), 96 | (name, &str), 97 | (next_display_order, Option), 98 | (next_help_heading, Option<&str>), 99 | (next_line_help, bool), 100 | (no_binary_name, bool), 101 | (override_help, &str), 102 | (override_usage, &str), 103 | (propagate_version, bool), 104 | // setting : specialized 105 | // settings : specialized (though the original method is deprecated) 106 | (short_flag, char), 107 | (short_flag_alias, char), 108 | ref (short_flag_aliases, Vec), 109 | // subcommand : not supported single subcommand(now) 110 | // subcommands : specialized 111 | (subcommand_help_heading, &str), 112 | (subcommand_negates_reqs, bool), 113 | (subcommand_required, bool), 114 | (subcommand_value_name, &str), 115 | (propagate_version, bool), 116 | (term_width, usize), 117 | (trailing_var_arg, bool), 118 | (version, &str), 119 | (visible_alias, &str), 120 | ref (visible_aliases, Vec<&str>), 121 | (visible_long_flag_alias, &str), 122 | ref (visible_long_flag_aliases, Vec<&str>), 123 | (visible_short_flag_alias, char), 124 | ref (visible_short_flag_aliases, Vec), 125 | }, 126 | deprecated: [ 127 | "help_message", 128 | "version_message", 129 | ]{ 130 | "help_heading" => "next_help_heading", 131 | }, 132 | not_supported: { 133 | "arg" => "Use args instead", 134 | "group" => "Use groups instead", 135 | }, 136 | specialize: 137 | [ 138 | "args" => map.next_value_seed(super::arg::Args::(app))? 139 | "args_map" => map.next_value_seed(super::arg::Args::(app))? 140 | "color" => { 141 | #[cfg(color)] { 142 | app.color(map.next_value_seed(ColorChoiceSeed)?) 143 | } 144 | #[cfg(not(color))] { return Err(Error::custom("color feature disabled"))}} 145 | "subcommands" => map.next_value_seed(SubCommands::(app))? 146 | "subcommands_map" => map.next_value_seed(SubCommands::(app))? 147 | "groups" => map.next_value_seed(super::group::Groups(app))? 148 | "setting" => app.setting(map.next_value_seed(AppSettingSeed)?) 149 | "settings" => app.setting(map.next_value_seed(AppSettingsSeed)?) 150 | "global_setting" => app.global_setting(map.next_value_seed(AppSettingSeed)?) 151 | "global_settings" => { 152 | let sets = map.next_value::>()?.into_iter().map(|s|s.into()); 153 | for s in sets{ 154 | app = app.global_setting(s); 155 | } 156 | app 157 | } 158 | ]); 159 | } 160 | 161 | Ok(CommandWrap { app }) 162 | } 163 | } 164 | 165 | pub struct NameSeed<'a>(&'a str); 166 | 167 | impl<'de> DeserializeSeed<'de> for NameSeed<'de> { 168 | type Value = CommandWrap<'de>; 169 | 170 | fn deserialize(self, deserializer: D) -> Result 171 | where 172 | D: serde::Deserializer<'de>, 173 | { 174 | deserializer.deserialize_map(CommandVisitor(Command::new(self.0))) 175 | } 176 | } 177 | 178 | impl<'de> DeserializeSeed<'de> for CommandWrap<'de> { 179 | type Value = CommandWrap<'de>; 180 | 181 | fn deserialize(self, deserializer: D) -> Result 182 | where 183 | D: serde::Deserializer<'de>, 184 | { 185 | deserializer.deserialize_map(CommandVisitor(self.app)) 186 | } 187 | } 188 | 189 | struct SubCommands<'a, const KV_ARRAY: bool>(Command<'a>); 190 | impl<'de, const KV_ARRAY: bool> DeserializeSeed<'de> for SubCommands<'de, KV_ARRAY> { 191 | type Value = Command<'de>; 192 | 193 | fn deserialize(self, deserializer: D) -> Result 194 | where 195 | D: serde::Deserializer<'de>, 196 | { 197 | if KV_ARRAY { 198 | deserializer.deserialize_seq(self) 199 | } else { 200 | deserializer.deserialize_map(self) 201 | } 202 | } 203 | } 204 | 205 | impl<'de, const KV_ARRAY: bool> Visitor<'de> for SubCommands<'de, KV_ARRAY> { 206 | type Value = Command<'de>; 207 | 208 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 209 | formatter.write_str("Subcommand") 210 | } 211 | 212 | fn visit_map(self, mut map: A) -> Result 213 | where 214 | A: serde::de::MapAccess<'de>, 215 | { 216 | let mut app = self.0; 217 | while let Some(name) = map.next_key::<&str>()? { 218 | let sub = map.next_value_seed(NameSeed(name))?; 219 | app = app.subcommand(sub); 220 | } 221 | Ok(app) 222 | } 223 | 224 | fn visit_seq(self, mut seq: A) -> Result 225 | where 226 | A: serde::de::SeqAccess<'de>, 227 | { 228 | let mut app = self.0; 229 | while let Some(sub) = seq.next_element_seed(InnerSubCommand)? { 230 | app = app.subcommand(sub) 231 | } 232 | Ok(app) 233 | } 234 | } 235 | 236 | pub struct InnerSubCommand; 237 | impl<'de> Visitor<'de> for InnerSubCommand { 238 | type Value = Command<'de>; 239 | 240 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 241 | formatter.write_str("Subcommand Inner") 242 | } 243 | 244 | fn visit_map(self, mut map: A) -> Result 245 | where 246 | A: serde::de::MapAccess<'de>, 247 | { 248 | let k = map 249 | .next_key()? 250 | .ok_or_else(|| A::Error::invalid_length(0, &"missing command in subcommand"))?; 251 | let com = map.next_value_seed(NameSeed(k))?; 252 | Ok(com.into()) 253 | } 254 | } 255 | 256 | impl<'de> DeserializeSeed<'de> for InnerSubCommand { 257 | type Value = Command<'de>; 258 | 259 | fn deserialize(self, deserializer: D) -> Result 260 | where 261 | D: serde::Deserializer<'de>, 262 | { 263 | deserializer.deserialize_map(self) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/de/arg/arg_action.rs: -------------------------------------------------------------------------------- 1 | use clap::ArgAction as AA; 2 | use serde::Deserialize; 3 | 4 | enum_de!(AA, ArgAction, 5 | #[derive(Deserialize, Clone, Copy)] 6 | #[cfg_attr(feature = "kebab-case-key" ,serde(rename_all = "kebab-case"))] 7 | #[cfg_attr(feature = "snake-case-key" ,serde(rename_all = "snake_case"))] 8 | { 9 | Set, 10 | Append, 11 | StoreValue, 12 | IncOccurrence, 13 | SetTrue, 14 | SetFalse, 15 | Count, 16 | Help, 17 | Version, 18 | } 19 | ); 20 | -------------------------------------------------------------------------------- /src/de/arg/mod.rs: -------------------------------------------------------------------------------- 1 | use self::{arg_action::ArgAction, value_hint::ValueHint, value_parser::ValueParser}; 2 | use crate::ArgWrap; 3 | use clap::{Arg, Command}; 4 | use serde::de::{DeserializeSeed, Error, Visitor}; 5 | use std::marker::PhantomData; 6 | 7 | mod arg_action; 8 | mod value_hint; 9 | mod value_parser; 10 | 11 | #[cfg(feature = "override-arg")] 12 | struct ArgKVO<'a>(Option>); 13 | 14 | #[cfg(feature = "override-arg")] 15 | impl<'de> Visitor<'de> for &mut ArgKVO<'de> { 16 | type Value = (); 17 | 18 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 19 | formatter.write_str("kv argument") 20 | } 21 | 22 | fn visit_map(self, mut map: A) -> Result 23 | where 24 | A: serde::de::MapAccess<'de>, 25 | { 26 | let name: &str = map 27 | .next_key()? 28 | .ok_or_else(|| A::Error::missing_field("argument"))?; 29 | let mut status = Ok(()); 30 | let app = unsafe { self.0.take().unwrap_unchecked() }; 31 | let next = app.mut_arg(name, |a| match map.next_value_seed(ArgVisitor(a)) { 32 | Ok(a) => a.into(), 33 | Err(e) => { 34 | status = Err(e); 35 | Arg::new(name) 36 | } 37 | }); 38 | self.0.replace(next); 39 | status 40 | } 41 | } 42 | 43 | #[cfg(feature = "override-arg")] 44 | impl<'de> DeserializeSeed<'de> for &mut ArgKVO<'de> { 45 | type Value = (); 46 | 47 | fn deserialize(self, deserializer: D) -> Result 48 | where 49 | D: serde::Deserializer<'de>, 50 | { 51 | deserializer.deserialize_map(self) 52 | } 53 | } 54 | 55 | #[derive(Clone, Copy)] 56 | struct ArgKV<'de>(PhantomData<&'de ()>); 57 | 58 | impl<'de> Visitor<'de> for ArgKV<'de> { 59 | type Value = ArgWrap<'de>; 60 | 61 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 62 | formatter.write_str("kv argument") 63 | } 64 | 65 | fn visit_map(self, mut map: A) -> Result 66 | where 67 | A: serde::de::MapAccess<'de>, 68 | { 69 | let name: &str = map 70 | .next_key()? 71 | .ok_or_else(|| A::Error::missing_field("argument"))?; 72 | map.next_value_seed(ArgVisitor::new_str(name)) 73 | } 74 | } 75 | 76 | impl<'de> DeserializeSeed<'de> for ArgKV<'de> { 77 | type Value = ArgWrap<'de>; 78 | 79 | fn deserialize(self, deserializer: D) -> Result 80 | where 81 | D: serde::Deserializer<'de>, 82 | { 83 | deserializer.deserialize_map(self) 84 | } 85 | } 86 | 87 | struct ArgVisitor<'a>(Arg<'a>); 88 | 89 | impl<'a> ArgVisitor<'a> { 90 | fn new_str(v: &'a str) -> Self { 91 | Self(Arg::new(v)) 92 | } 93 | } 94 | 95 | impl<'a> Visitor<'a> for ArgVisitor<'a> { 96 | type Value = ArgWrap<'a>; 97 | 98 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 99 | formatter.write_str("Arg Map") 100 | } 101 | 102 | fn visit_map(self, mut map: A) -> Result 103 | where 104 | A: serde::de::MapAccess<'a>, 105 | { 106 | let mut arg = self.0; 107 | 108 | while let Some(key) = map.next_key::<&str>()? { 109 | arg = parse_value!(key, arg, map, Arg, { 110 | // action : specailized 111 | (alias, &str), 112 | ref (aliases, Vec<&str>), 113 | (allow_hyphen_values, bool), 114 | (allow_invalid_utf8, bool), 115 | (conflicts_with, &str), 116 | ref (conflicts_with_all, Vec<&str>), 117 | (default_missing_value, &str), 118 | ref (default_missing_values, Vec<&str>), 119 | // (default_missing_value_os, &OsStr), // need Deseriaze to OsStr 120 | // ref (default_missing_values_os, Vec<&OsStr>), 121 | (default_value, &str), 122 | // default_value_if : tuple3 123 | ref (default_value_ifs, Vec<(&str, Option<&str>, Option<&str>)> ), 124 | (display_order, usize), 125 | // env : specialized 126 | // env_os // not supported yet 127 | (exclusive, bool), 128 | (forbid_empty_values, bool), 129 | (global, bool), 130 | (group, &str), 131 | ref (groups, Vec<&str>), 132 | (help, &str), 133 | (help_heading, &str), 134 | (hide, bool), 135 | (hide_default_value, bool), 136 | // hide_env : specialized 137 | // hide_env_values : specialized 138 | (hide_long_help, bool), 139 | (hide_possible_values, bool), 140 | (hide_short_help, bool), 141 | (id, &str), 142 | (ignore_case, bool), 143 | (index, usize), 144 | (last, bool), 145 | (long, &str), 146 | (long_help, &str), 147 | (max_occurrences, usize), 148 | (max_values, usize), 149 | (min_values, usize), 150 | (multiple_occurrences, bool), 151 | (multiple_values, bool), 152 | (id, &str), 153 | (next_line_help, bool), 154 | (number_of_values, usize), 155 | (overrides_with, &str), 156 | ref (overrides_with_all, Vec<&str>), 157 | (possible_value, &str), 158 | (possible_values, Vec<&str>), 159 | (raw, bool), 160 | (require_value_delimiter, bool), 161 | (require_equals, bool), 162 | (required, bool), 163 | // required_if_eq: tuple2 164 | ref (required_if_eq_all, Vec<(&str, &str)>), 165 | ref (required_if_eq_any, Vec<(&str, &str)>), 166 | (required_unless_present, &str), 167 | ref (required_unless_present_any, Vec<&str>), 168 | ref (required_unless_present_all, Vec<&str>), 169 | (requires, &str), 170 | ref (requires_all, Vec<&str>), 171 | // requires_if: tuple2 172 | ref (requires_ifs, Vec<(&str, &str)>), 173 | (short, char), 174 | (short_alias, char), 175 | ref (short_aliases, Vec), 176 | (takes_value, bool), 177 | (use_value_delimiter, bool), 178 | // validator_regex : todo 179 | // value_hint : specialized 180 | (value_delimiter, char), 181 | (value_name, &str), 182 | ref (value_names, Vec<&str>), 183 | // value_parser : specialized 184 | (value_terminator, &str), 185 | (visible_alias, &str), 186 | ref (visible_aliases, Vec<&str>), 187 | (visible_short_alias, char), 188 | ref (visible_short_aliases, Vec), 189 | }, 190 | tuple2: { 191 | (required_if_eq, (&str, &str)), 192 | (requires_if, (&str, &str)), 193 | }, 194 | tuple3: { 195 | (default_value_if, (&str, Option<&str>, Option<&str>)), 196 | }, 197 | deprecated: 198 | [ 199 | "case_insensitive", 200 | "empty_values", 201 | "from_usage", 202 | "hidden", 203 | "hidden_long_help", 204 | "hidden_short_help", 205 | "multiple", 206 | "required_if", 207 | "required_ifs", 208 | "required_unless", 209 | "required_unless_all", 210 | "required_unless_one", 211 | "set", 212 | "setting", 213 | "settings", 214 | "validator_regex", 215 | "with_name", 216 | ]{ 217 | //3.1 218 | "name" => "id", 219 | "require_delimiter" => "require_value_delimiter", 220 | "use_delimiter" => "use_value_delimiter", 221 | }, 222 | // not_supported: { 223 | // }, 224 | specialize:[ 225 | "arg_action" => { 226 | arg.action(map.next_value::()?.into()) 227 | } 228 | "env" => { 229 | #[cfg(feature = "env")] { parse_value_inner!(arg, map, Arg, &str, env) } 230 | #[cfg(not(feature = "env"))] { return Err(Error::custom("env feature disabled"))}} 231 | "hide_env" => { 232 | #[cfg(feature = "env")] { parse_value_inner!(arg, map, Arg, bool, hide_env) } 233 | #[cfg(not(feature = "env"))] { return Err(Error::custom("env feature disabled"))}} 234 | "hide_env_values" => { 235 | #[cfg(feature = "env")] { parse_value_inner!(arg, map, Arg, bool, hide_env_values) } 236 | #[cfg(not(feature = "env"))] { return Err(Error::custom("env feature disabled"))}} 237 | "value_hint" => { 238 | arg.value_hint(map.next_value::()?.into()) 239 | } 240 | "value_parser" => { 241 | arg.value_parser(map.next_value::()?) 242 | } 243 | ] 244 | ); 245 | } 246 | Ok(ArgWrap { arg }) 247 | } 248 | } 249 | 250 | impl<'de> DeserializeSeed<'de> for ArgVisitor<'de> { 251 | type Value = ArgWrap<'de>; 252 | 253 | fn deserialize(self, deserializer: D) -> Result 254 | where 255 | D: serde::Deserializer<'de>, 256 | { 257 | deserializer.deserialize_map(self) 258 | } 259 | } 260 | 261 | impl<'de> DeserializeSeed<'de> for ArgWrap<'de> { 262 | type Value = ArgWrap<'de>; 263 | 264 | fn deserialize(self, deserializer: D) -> Result 265 | where 266 | D: serde::Deserializer<'de>, 267 | { 268 | deserializer.deserialize_map(ArgVisitor(self.arg)) 269 | } 270 | } 271 | 272 | pub(crate) struct Args<'a, const USE_ARRAY: bool>(pub(crate) Command<'a>); 273 | impl<'de, const USE_ARRAY: bool> DeserializeSeed<'de> for Args<'de, USE_ARRAY> { 274 | type Value = Command<'de>; 275 | 276 | fn deserialize(self, deserializer: D) -> Result 277 | where 278 | D: serde::Deserializer<'de>, 279 | { 280 | if USE_ARRAY { 281 | deserializer.deserialize_seq(self) 282 | } else { 283 | deserializer.deserialize_map(self) 284 | } 285 | } 286 | } 287 | 288 | impl<'de, const USE_ARRAY: bool> Visitor<'de> for Args<'de, USE_ARRAY> { 289 | type Value = Command<'de>; 290 | 291 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 292 | formatter.write_str("args") 293 | } 294 | 295 | #[cfg(feature = "override-arg")] 296 | fn visit_seq(self, mut seq: A) -> Result 297 | where 298 | A: serde::de::SeqAccess<'de>, 299 | { 300 | let mut argkvo = ArgKVO(Some(self.0)); 301 | 302 | while (seq.next_element_seed(&mut argkvo)?).is_some() {} 303 | Ok(unsafe { argkvo.0.unwrap_unchecked() }) 304 | } 305 | 306 | #[cfg(not(feature = "override-arg"))] 307 | fn visit_seq(self, mut seq: A) -> Result 308 | where 309 | A: serde::de::SeqAccess<'de>, 310 | { 311 | let x = ArgKV(PhantomData); 312 | let mut com = self.0; 313 | while let Some(a) = seq.next_element_seed(x)? { 314 | com = com.arg(a); 315 | } 316 | Ok(com) 317 | } 318 | 319 | fn visit_map(self, mut map: A) -> Result 320 | where 321 | A: serde::de::MapAccess<'de>, 322 | { 323 | let mut app = self.0; 324 | while let Some(name) = map.next_key::<&str>()? { 325 | #[cfg(feature = "override-arg")] 326 | { 327 | let mut error = None; 328 | app = app.mut_arg(name, |a| match map.next_value_seed(ArgVisitor(a)) { 329 | Ok(a) => a.into(), 330 | Err(e) => { 331 | error = Some(e); 332 | Arg::new(name) 333 | } 334 | }); 335 | if let Some(error) = error { 336 | return Err(error); 337 | } 338 | } 339 | #[cfg(not(feature = "override-arg"))] 340 | { 341 | app = app.arg(map.next_value_seed(ArgVisitor::new_str(name))?); 342 | } 343 | } 344 | Ok(app) 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/de/arg/value_hint.rs: -------------------------------------------------------------------------------- 1 | use clap::ValueHint as VH; 2 | use serde::Deserialize; 3 | 4 | enum_de!(VH,ValueHint, 5 | #[derive(Deserialize, Clone, Copy)] 6 | #[cfg_attr(feature = "kebab-case-key" ,serde(rename_all = "kebab-case"))] 7 | #[cfg_attr(feature = "snake-case-key" ,serde(rename_all = "snake_case"))] 8 | { 9 | Unknown, 10 | Other, 11 | AnyPath, 12 | FilePath, 13 | DirPath, 14 | ExecutablePath, 15 | CommandName, 16 | CommandString, 17 | CommandWithArguments, 18 | Username, 19 | Hostname, 20 | Url, 21 | EmailAddress, 22 | }); 23 | -------------------------------------------------------------------------------- /src/de/arg/value_parser.rs: -------------------------------------------------------------------------------- 1 | use clap::builder::ValueParser as VP; 2 | use serde::Deserialize; 3 | 4 | macro_rules! enum_de_value { 5 | ($basety : ident, $newty :ident, 6 | $(#[$derive_meta:meta])* 7 | { 8 | $( $( 9 | #[ $cfg_meta_ex:meta ] )? 10 | $var_ex: ident $( { $( $(#[$cfg_v:meta])* $vx: ident : $vt: ty ),* } )? 11 | => $to_ex: expr 12 | ,)* 13 | } 14 | { 15 | $(($pty: ty, $pty_upper : tt $(, $ty_as: ty)?)),* 16 | } 17 | ) => { 18 | enum_de!($basety, $newty, 19 | $(#[$derive_meta])* {} 20 | { 21 | $( $( 22 | #[ $cfg_meta_ex ] )? 23 | $var_ex $( { $( $(#[$cfg_v])* $vx : $vt ),* } )? 24 | => $to_ex 25 | ,)* 26 | $( 27 | $pty_upper { 28 | #[serde(skip_serializing_if = "Option::is_none")] 29 | min: Option<$pty>, 30 | #[serde(skip_serializing_if = "Option::is_none")] 31 | max: Option<$pty>, 32 | #[serde(default = "get_true")] 33 | max_inclusive: bool 34 | } => { 35 | match (min, max, max_inclusive) { 36 | (Some(s), Some(e), false) => clap::value_parser!($pty).range((s $(as $ty_as)*) ..(e $(as $ty_as)*)).into(), 37 | (Some(s), Some(e), true) => clap::value_parser!($pty).range((s $(as $ty_as)*) ..=(e $(as $ty_as)*)).into(), 38 | (Some(s), None, _) => clap::value_parser!($pty).range((s $(as $ty_as)*)..).into(), 39 | (None, Some(e), false) => clap::value_parser!($pty).range(..(e $(as $ty_as)*)).into(), 40 | (None, Some(e), true) => clap::value_parser!($pty).range(..=(e $(as $ty_as)*)).into(), 41 | (None, None, _) => clap::value_parser!($pty).into(), 42 | } 43 | },)* 44 | } 45 | ); 46 | }; 47 | } 48 | 49 | const fn get_true() -> bool { 50 | true 51 | } 52 | 53 | enum_de_value!(VP, ValueParser1, 54 | #[derive(Deserialize, Clone, Copy)] 55 | #[serde(tag = "type")] 56 | #[cfg_attr(feature = "kebab-case-key" ,serde(rename_all = "kebab-case"))] 57 | #[cfg_attr(feature = "snake-case-key" ,serde(rename_all = "snake_case"))] 58 | { 59 | Bool => { 60 | VP::bool() 61 | }, 62 | String => { 63 | VP::string() 64 | }, 65 | OsString => { 66 | VP::os_string() 67 | }, 68 | PathBuf => { 69 | VP::path_buf() 70 | }, 71 | Boolish => { 72 | clap::builder::BoolishValueParser::new().into() 73 | }, 74 | Falsey => { 75 | clap::builder::FalseyValueParser::new().into() 76 | }, 77 | NonEmptyString => { 78 | clap::builder::NonEmptyStringValueParser::new().into() 79 | }, 80 | } 81 | { 82 | (i64, I64), 83 | (i32, I32, i64), 84 | (i16, I16, i64), 85 | (i8 , I8, i64), 86 | (u64, U64), 87 | (u32, U32, i64), 88 | (u16, U16, i64), 89 | (u8 , U8, i64) 90 | } 91 | ); 92 | 93 | enum_de!(VP, ValueParser2, 94 | #[derive(Deserialize, Clone, Copy)] 95 | #[cfg_attr(feature = "kebab-case-key" ,serde(rename_all = "kebab-case"))] 96 | #[cfg_attr(feature = "snake-case-key" ,serde(rename_all = "snake_case"))] 97 | {} 98 | { 99 | Bool => { 100 | VP::bool() 101 | }, 102 | String => { 103 | VP::string() 104 | }, 105 | OsString => { 106 | VP::os_string() 107 | }, 108 | PathBuf => { 109 | VP::path_buf() 110 | }, 111 | Boolish => { 112 | clap::builder::BoolishValueParser::new().into() 113 | }, 114 | Falsey => { 115 | clap::builder::FalseyValueParser::new().into() 116 | }, 117 | NonEmptyString => { 118 | clap::builder::NonEmptyStringValueParser::new().into() 119 | }, 120 | I64 => { 121 | clap::value_parser!(i64).into() 122 | }, 123 | I32 => { 124 | clap::value_parser!(i32).into() 125 | }, 126 | I16 => { 127 | clap::value_parser!(i16).into() 128 | }, 129 | I8 => { 130 | clap::value_parser!(i8).into() 131 | }, 132 | U64 => { 133 | clap::value_parser!(u64).into() 134 | }, 135 | U32 => { 136 | clap::value_parser!(u32).into() 137 | }, 138 | U16 => { 139 | clap::value_parser!(u16).into() 140 | }, 141 | U8 => { 142 | clap::value_parser!(u8).into() 143 | }, 144 | } 145 | ); 146 | 147 | #[derive(Deserialize)] 148 | #[serde(untagged)] 149 | pub(crate) enum ValueParser { 150 | Value(ValueParser2), 151 | Tagged(ValueParser1), 152 | } 153 | 154 | impl From for VP { 155 | fn from(v: ValueParser) -> Self { 156 | match v { 157 | ValueParser::Value(v) => v.into(), 158 | ValueParser::Tagged(t) => t.into(), 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/de/group.rs: -------------------------------------------------------------------------------- 1 | use crate::ArgGroupWrap; 2 | use clap::{ArgGroup, Command}; 3 | use serde::de::{DeserializeSeed, Error, Visitor}; 4 | 5 | struct GroupVisitor<'a>(&'a str); 6 | 7 | impl<'de> Visitor<'de> for GroupVisitor<'de> { 8 | type Value = ArgGroupWrap<'de>; 9 | 10 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 11 | formatter.write_str("arg group map") 12 | } 13 | 14 | fn visit_map(self, mut map: A) -> Result 15 | where 16 | A: serde::de::MapAccess<'de>, 17 | { 18 | let mut group = ArgGroup::new(self.0); 19 | while let Some(key) = map.next_key::<&str>()? { 20 | group = parse_value!(key, group, map, ArgGroup, { 21 | (arg, &str), 22 | ref (args, Vec<&str>), 23 | (conflicts_with, &str), 24 | ref (conflicts_with_all, Vec<&str>), 25 | (id, &str), 26 | (multiple, bool), 27 | (required, bool), 28 | (requires, &str), 29 | ref (requires_all, Vec<&str>), 30 | }, deprecated:{ 31 | "name" => "id", 32 | }); 33 | } 34 | 35 | Ok(ArgGroupWrap { group }) 36 | } 37 | } 38 | 39 | impl<'de> DeserializeSeed<'de> for GroupVisitor<'de> { 40 | type Value = ArgGroupWrap<'de>; 41 | 42 | fn deserialize(self, deserializer: D) -> Result 43 | where 44 | D: serde::Deserializer<'de>, 45 | { 46 | deserializer.deserialize_map(self) 47 | } 48 | } 49 | 50 | pub(crate) struct Groups<'a>(pub(crate) Command<'a>); 51 | impl<'de> DeserializeSeed<'de> for Groups<'de> { 52 | type Value = Command<'de>; 53 | 54 | fn deserialize(self, deserializer: D) -> Result 55 | where 56 | D: serde::Deserializer<'de>, 57 | { 58 | deserializer.deserialize_map(self) 59 | } 60 | } 61 | 62 | impl<'de> Visitor<'de> for Groups<'de> { 63 | type Value = Command<'de>; 64 | 65 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 66 | formatter.write_str("arg groups") 67 | } 68 | 69 | fn visit_map(self, mut map: A) -> Result 70 | where 71 | A: serde::de::MapAccess<'de>, 72 | { 73 | let mut app = self.0; 74 | while let Some(name) = map.next_key::<&str>()? { 75 | app = app.group(map.next_value_seed(GroupVisitor(name))?); 76 | } 77 | Ok(app) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/de/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! parse_value_inner { 2 | ( $app : ident, $map : ident, $target_type:ty, $value_type:ty, $register : ident) => { 3 | <$target_type>::$register($app, $map.next_value::<$value_type>()?) 4 | }; 5 | ( $app : ident, $map : ident, $target_type:ty, ref $value_type:ty, $register : ident) => { 6 | <$target_type>::$register($app, &$map.next_value::<$value_type>()?) 7 | }; 8 | } 9 | 10 | macro_rules! parse_value { 11 | ($key : ident, $app : ident, $map : ident, $target_type:ty, 12 | { $( 13 | $( ( $register : ident, $value_type:ty), ),+ 14 | $( ref ( $register_r : ident, $value_type_r:ty) ),* $(,)? 15 | )* } 16 | $(, tuple2:{$(( $register_t : ident, ( $value_type_t0:ty, $value_type_t1:ty)),)*})? 17 | $(, tuple3:{$(( $register_3t : ident, ( $value_type_3t0:ty, $value_type_3t1:ty, $value_type_3t2:ty)),)*})? 18 | $(, deprecated:$([$($dep:pat ,)*])?$({$($dep_s:pat => $dep_d:expr,)*})?)? 19 | $(, not_supported:{$($ns:pat => $ns_r:stmt ,)*})? 20 | $(, specialize:[$( $sp_pat : pat => $sp_exp : expr )+ ])? ) => {{ 21 | #[allow(unused_mut)] 22 | let mut key; 23 | convert_case_to!($key, key); 24 | 25 | #[allow(unused_labels)] 26 | 'parse_value_jmp_loop: loop { 27 | break 'parse_value_jmp_loop match key { 28 | $( 29 | $( stringify!($register) => parse_value_inner!($app, $map, $target_type, $value_type, $register), )* 30 | $( stringify!($register_r) => parse_value_inner!($app, $map, $target_type, ref $value_type_r, $register_r), )* 31 | )* 32 | $($( 33 | stringify!($register_t) => { 34 | let (v0, v1) = $map.next_value::<($value_type_t0, $value_type_t1)>()?; 35 | <$target_type>::$register_t($app, v0, v1) 36 | } 37 | )*)* 38 | $($( 39 | stringify!($register_3t) => { 40 | let (v0, v1, v2) = $map.next_value::<($value_type_3t0, $value_type_3t1, $value_type_3t2)>()?; 41 | <$target_type>::$register_3t($app, v0, v1, v2) 42 | } 43 | )*)* 44 | $($($sp_pat => {$sp_exp})*)* 45 | $($(depr @ ($($dep )|* ) => {return Err(Error::custom(format_args!("deprecated key: {}", depr)))})*)* 46 | $($($( 47 | #[cfg(feature="allow-deprecated")] 48 | $dep_s => { 49 | const N_KEY : &str = stringify!($dep_d); 50 | convert_case_to!(N_KEY, key); 51 | continue 'parse_value_jmp_loop; 52 | }, 53 | #[cfg(not(feature="allow-deprecated"))] 54 | $dep_s => { 55 | return Err(Error::custom(format_args!("deprecated key: {}, use {} insted", stringify!($depr_s), $dep_d))) 56 | }, 57 | )*)*)* 58 | $($( 59 | $ns => { 60 | return Err(Error::custom(format_args!("not supported key : {}, {} ", stringify!($ns), stringify!($ns_r)))) 61 | } 62 | )*)* 63 | unknown => return Err(Error::unknown_field(unknown, &[ 64 | $( $( stringify!($register),)* 65 | $( stringify!($register_r),)* )* 66 | $($(stringify!($sp_pat),)*)* ])) 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | #[cfg(feature = "snake-case-key")] 74 | macro_rules! convert_case_to { 75 | ($key:ident, $target: ident) => {{ 76 | $target = $key; 77 | }}; 78 | } 79 | 80 | // if const heap is stabilized, should convert the target keys instead. 81 | 82 | #[cfg(not(feature = "snake-case-key"))] 83 | macro_rules! convert_case_to { 84 | ($key:ident, $target: ident) => { 85 | let cc_xk = 86 | (<&str as convert_case::Casing<&str>>::to_case(&$key, convert_case::Case::Snake)); 87 | $target = cc_xk.as_str(); 88 | }; 89 | } 90 | 91 | macro_rules! enum_de { 92 | ($basety : ident, $newty :ident, 93 | $(#[$derive_meta:meta])* { 94 | $( 95 | $( #[ $cfg_meta:meta ] )? 96 | $var: ident 97 | ,)* 98 | } 99 | $({ 100 | $( $( 101 | #[ $cfg_meta_ex:meta ] )? 102 | $var_ex: ident $( { $( $(#[$cfg_v:meta])* $vx: ident : $vt: ty ),* } )? 103 | => $to_ex: expr 104 | ,)* 105 | } )? 106 | ) => { 107 | 108 | $(#[$derive_meta])* 109 | pub(crate) enum $newty { 110 | $( $(#[$cfg_meta])* $var , )* 111 | $($( $(#[$cfg_meta_ex])* $var_ex $( { $( $( #[$cfg_v] )* $vx : $vt ,)* } )* , )*)* 112 | } 113 | 114 | impl From<$newty> for $basety { 115 | fn from(s : $newty) -> $basety { 116 | match s { 117 | $( 118 | $(#[$cfg_meta])* 119 | $newty::$var => $basety::$var, 120 | )* 121 | $($( 122 | $(#[$cfg_meta_ex])* 123 | $newty::$var_ex$({$( $vx ,)*})* => { $to_ex }, 124 | )*)* 125 | } 126 | } 127 | } 128 | }; 129 | } 130 | -------------------------------------------------------------------------------- /src/de/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod macros; 3 | 4 | mod app; 5 | mod arg; 6 | mod group; 7 | -------------------------------------------------------------------------------- /src/documents.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | Documents of keys. 3 | */ 4 | 5 | #[doc = include_str!("../docs/app_keys.md")] 6 | pub mod app_keys {} 7 | 8 | #[doc = include_str!("../docs/arg_keys.md")] 9 | pub mod arg_keys {} 10 | 11 | #[doc = include_str!("../docs/arg_groups.md")] 12 | pub mod arg_groups_keys {} 13 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 2 | #![doc = include_str!("../README.md")] 3 | 4 | use clap::{Arg, ArgGroup, Command}; 5 | use serde::Deserializer; 6 | use std::ops::Deref; 7 | 8 | #[cfg(not(any( 9 | feature = "kebab-case-key", 10 | feature = "snake-case-key", 11 | feature = "pascal-case-key" 12 | )))] 13 | compile_error!("Case setting feature is missing. Either one should be set."); 14 | 15 | #[cfg(any( 16 | all(feature = "kebab-case-key", feature = "snake-case-key"), 17 | all(feature = "kebab-case-key", feature = "pascal-case-key"), 18 | all(feature = "pascal-case-key", feature = "snake-case-key"), 19 | ))] 20 | compile_error!("Case setting feature is conflicting. Only one should be set."); 21 | 22 | #[macro_use] 23 | mod de; 24 | #[cfg(feature = "docsrs")] 25 | pub mod documents; 26 | #[cfg(feature = "yaml")] 27 | #[deprecated(since = "0.4", note = "use serde-yaml instead")] 28 | mod yaml; 29 | 30 | #[cfg(all(test, feature = "snake-case-key"))] 31 | mod tests; 32 | 33 | #[cfg(feature = "yaml")] 34 | pub use yaml::{yaml_to_app, YamlWrap}; 35 | 36 | /** 37 | Deserialize [`Command`] from [`Deserializer`]. 38 | ``` 39 | const CLAP_TOML: &'static str = r#" 40 | name = "app_clap_serde" 41 | version = "1.0" 42 | author = "tester" 43 | about = "test-clap-serde" 44 | "#; 45 | let app = clap_serde::load(&mut toml::Deserializer::new(CLAP_TOML)) 46 | .expect("parse failed"); 47 | assert_eq!(app.get_name(), "app_clap_serde"); 48 | assert_eq!(app.get_about(), Some("test-clap-serde")); 49 | ``` 50 | */ 51 | pub fn load<'de, D>(de: D) -> Result, D::Error> 52 | where 53 | D: Deserializer<'de>, 54 | { 55 | use serde::Deserialize; 56 | CommandWrap::deserialize(de).map(|a| a.into()) 57 | } 58 | 59 | /** 60 | Wrapper of [`Command`] to deserialize. 61 | ``` 62 | const CLAP_TOML: &'static str = r#" 63 | name = "app_clap_serde" 64 | version = "1.0" 65 | author = "tester" 66 | about = "test-clap-serde" 67 | "#; 68 | let app: clap::Command = toml::from_str::(CLAP_TOML) 69 | .expect("parse failed") 70 | .into(); 71 | assert_eq!(app.get_name(), "app_clap_serde"); 72 | assert_eq!(app.get_about(), Some("test-clap-serde")); 73 | ``` 74 | */ 75 | #[derive(Debug, Clone)] 76 | pub struct CommandWrap<'a> { 77 | app: Command<'a>, 78 | } 79 | 80 | #[deprecated] 81 | pub type AppWrap<'a> = CommandWrap<'a>; 82 | 83 | impl<'a> From> for Command<'a> { 84 | fn from(a: CommandWrap<'a>) -> Self { 85 | a.app 86 | } 87 | } 88 | 89 | impl<'a> From> for CommandWrap<'a> { 90 | fn from(app: Command<'a>) -> Self { 91 | CommandWrap { app } 92 | } 93 | } 94 | 95 | impl<'a> Deref for CommandWrap<'a> { 96 | type Target = Command<'a>; 97 | 98 | fn deref(&self) -> &Self::Target { 99 | &self.app 100 | } 101 | } 102 | 103 | /// Wrapper of [`Arg`] to deserialize with [`DeserializeSeed`](`serde::de::DeserializeSeed`). 104 | #[derive(Debug, Clone)] 105 | pub struct ArgWrap<'a> { 106 | arg: Arg<'a>, 107 | } 108 | 109 | impl<'a> From> for Arg<'a> { 110 | fn from(arg: ArgWrap<'a>) -> Self { 111 | arg.arg 112 | } 113 | } 114 | 115 | impl<'a> From> for ArgWrap<'a> { 116 | fn from(arg: Arg<'a>) -> Self { 117 | ArgWrap { arg } 118 | } 119 | } 120 | 121 | impl<'a> Deref for ArgWrap<'a> { 122 | type Target = Arg<'a>; 123 | 124 | fn deref(&self) -> &Self::Target { 125 | &self.arg 126 | } 127 | } 128 | 129 | pub(crate) struct ArgGroupWrap<'a> { 130 | group: ArgGroup<'a>, 131 | } 132 | 133 | impl<'a> From> for ArgGroup<'a> { 134 | fn from(group: ArgGroupWrap<'a>) -> Self { 135 | group.group 136 | } 137 | } 138 | 139 | impl<'a> From> for ArgGroupWrap<'a> { 140 | fn from(group: ArgGroup<'a>) -> Self { 141 | ArgGroupWrap { group } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::CommandWrap; 2 | use clap::{builder::ValueParser, Command}; 3 | 4 | #[test] 5 | fn name_yaml() { 6 | const NAME_YAML: &str = "name: app_clap_serde\n"; 7 | let app: CommandWrap = serde_yaml::from_str(NAME_YAML).expect("fail to make yaml"); 8 | assert_eq!(app.get_name(), "app_clap_serde"); 9 | } 10 | 11 | #[test] 12 | fn test_yaml() { 13 | const CLAP_YAML: &str = r#" 14 | name: app_clap_serde 15 | version : "1.0" 16 | about : yaml_support! 17 | author : yaml_supporter 18 | 19 | args: 20 | - apple : 21 | short: a 22 | - banana: 23 | short: b 24 | long: banana 25 | aliases : 26 | - musa_spp 27 | 28 | subcommands: 29 | - sub1: 30 | about : subcommand_1 31 | - sub2: 32 | about : subcommand_2 33 | 34 | setting: trailing_var_arg 35 | 36 | settings: 37 | - help_expected 38 | "#; 39 | 40 | let app: CommandWrap = serde_yaml::from_str(CLAP_YAML).expect("fail to make yaml"); 41 | assert_eq!(app.get_name(), "app_clap_serde"); 42 | let subs = app.get_subcommands().collect::>(); 43 | assert!(subs 44 | .iter() 45 | .any(|x| x.get_name() == "sub1" && x.get_about() == Some("subcommand_1"))); 46 | assert!(subs 47 | .iter() 48 | .any(|x| x.get_name() == "sub2" && x.get_about() == Some("subcommand_2"))); 49 | let args = app.get_arguments().collect::>(); 50 | assert!(args 51 | .iter() 52 | .any(|x| x.get_id() == "apple" && x.get_short() == Some('a'))); 53 | assert!(args.iter().any(|x| x.get_id() == "banana" 54 | && x.get_short() == Some('b') 55 | && x.get_long() == Some("banana"))); 56 | } 57 | 58 | #[test] 59 | fn test_yaml_value_parser() { 60 | const CLAP_YAML: &str = r#" 61 | name: app_clap_serde 62 | version : "1.0" 63 | about : yaml_support! 64 | author : yaml_supporter 65 | 66 | args: 67 | - apple : 68 | short: a 69 | value_parser: 70 | type: i64 71 | min: -100 72 | max: 100 73 | max_inclusive: true 74 | - banana: 75 | short: b 76 | long: banana 77 | value_parser: non_empty_string 78 | "#; 79 | 80 | let app: CommandWrap = serde_yaml::from_str(CLAP_YAML).expect("fail to make yaml"); 81 | assert_eq!(app.get_name(), "app_clap_serde"); 82 | 83 | let args = app.get_arguments().collect::>(); 84 | let vp_i: ValueParser = clap::value_parser!(i64).into(); 85 | let vp_s: ValueParser = clap::builder::NonEmptyStringValueParser::default().into(); 86 | assert!(args.iter().any(|x| x.get_id() == "apple" 87 | && x.get_short() == Some('a') 88 | && x.get_value_parser().type_id() == vp_i.type_id())); 89 | assert!(args.iter().any(|x| x.get_id() == "banana" 90 | && x.get_short() == Some('b') 91 | && x.get_value_parser().type_id() == vp_s.type_id())); 92 | } 93 | 94 | #[test] 95 | fn name_json() { 96 | const CLAP_JSON: &str = "{ \"name\" : \"app_clap_serde\" }"; 97 | let app: Command = serde_json::from_str::(CLAP_JSON) 98 | .expect("parse failed") 99 | .into(); 100 | assert_eq!(app.get_name(), "app_clap_serde"); 101 | } 102 | 103 | #[test] 104 | fn name_toml() { 105 | const CLAP_TOML: &str = "name = \"app_clap_serde\""; 106 | let app: Command = toml::from_str::(CLAP_TOML) 107 | .expect("parse failed") 108 | .into(); 109 | assert_eq!(app.get_name(), "app_clap_serde"); 110 | } 111 | 112 | #[test] 113 | fn infos_json() { 114 | const NAME_JSON: &str = r#"{ "name" : "app_clap_serde", "version" : "1.0" , "author" : "aobat", "about" : "test-clap-serde" }"#; 115 | let app: Command = serde_json::from_str::(NAME_JSON) 116 | .expect("parse failed") 117 | .into(); 118 | assert_eq!(app.get_name(), "app_clap_serde"); 119 | assert_eq!(app.get_about(), Some("test-clap-serde")); 120 | } 121 | 122 | #[test] 123 | fn infos_toml() { 124 | const CLAP_TOML: &str = r#" 125 | name = "app_clap_serde" 126 | version = "1.0" 127 | author = "aobat" 128 | about = "test-clap-serde" 129 | "#; 130 | let app: Command = toml::from_str::(CLAP_TOML) 131 | .expect("parse failed") 132 | .into(); 133 | assert_eq!(app.get_name(), "app_clap_serde"); 134 | assert_eq!(app.get_about(), Some("test-clap-serde")); 135 | } 136 | 137 | #[test] 138 | fn subcommands_json() { 139 | const CLAP_JSON: &str = r#"{ 140 | "name" : "app_clap_serde", 141 | "version" : "1.0" , 142 | "author" : "aobat", 143 | "about" : "test-clap-serde", 144 | "subcommands" : [ 145 | {"sub1" : {"about" : "subcommand_1"}}, 146 | {"sub2" : {"about" : "subcommand_2"}} 147 | ]}"#; 148 | let app: Command = serde_json::from_str::(CLAP_JSON) 149 | .expect("parse failed") 150 | .into(); 151 | assert_eq!(app.get_name(), "app_clap_serde"); 152 | assert_eq!(app.get_about(), Some("test-clap-serde")); 153 | let subs = app.get_subcommands().collect::>(); 154 | assert!(subs 155 | .iter() 156 | .any(|x| x.get_name() == "sub1" && x.get_about() == Some("subcommand_1"))); 157 | assert!(subs 158 | .iter() 159 | .any(|x| x.get_name() == "sub2" && x.get_about() == Some("subcommand_2"))); 160 | } 161 | 162 | #[test] 163 | fn subcommands_toml() { 164 | const CLAP_TOML: &str = r#" 165 | name = "app_clap_serde" 166 | version = "1.0" 167 | author = "aobat" 168 | about = "test-clap-serde" 169 | [subcommands] 170 | sub1 = { about = "subcommand_1" } 171 | [subcommands.sub2] 172 | about = "subcommand_2" 173 | "#; 174 | let app: Command = toml::from_str::(CLAP_TOML) 175 | .expect("parse failed") 176 | .into(); 177 | assert_eq!(app.get_name(), "app_clap_serde"); 178 | assert_eq!(app.get_about(), Some("test-clap-serde")); 179 | let subs = app.get_subcommands().collect::>(); 180 | assert!(subs 181 | .iter() 182 | .any(|x| x.get_name() == "sub1" && x.get_about() == Some("subcommand_1"))); 183 | assert!(subs 184 | .iter() 185 | .any(|x| x.get_name() == "sub2" && x.get_about() == Some("subcommand_2"))); 186 | } 187 | 188 | #[test] 189 | fn args_map_json() { 190 | const ARGS_JSON: &str = r#"{ 191 | "name" : "app_clap_serde", 192 | "args_map" : { 193 | "apple" : {"short" : "a" }, 194 | "banana" : {"short" : "b", "long" : "banana", "aliases" : [ "musa_spp" ]} 195 | } 196 | }"#; 197 | let app: Command = serde_json::from_str::(ARGS_JSON) 198 | .expect("parse failed") 199 | .into(); 200 | assert_eq!(app.get_name(), "app_clap_serde"); 201 | let args = app.get_arguments().collect::>(); 202 | assert!(args 203 | .iter() 204 | .any(|x| x.get_id() == "apple" && x.get_short() == Some('a'))); 205 | assert!(args.iter().any(|x| x.get_id() == "banana" 206 | && x.get_short() == Some('b') 207 | && x.get_long() == Some("banana"))); 208 | } 209 | 210 | #[test] 211 | fn args_json() { 212 | const ARGS_JSON: &str = r#"{ 213 | "name" : "app_clap_serde", 214 | "args" : [ 215 | { "apple" : {"short" : "a" } }, 216 | { "banana" : {"short" : "b", "long" : "banana", "aliases" : [ "musa_spp" ]}} 217 | ] 218 | }"#; 219 | let app: Command = serde_json::from_str::(ARGS_JSON) 220 | .expect("parse failed") 221 | .into(); 222 | assert_eq!(app.get_name(), "app_clap_serde"); 223 | let args = app.get_arguments().collect::>(); 224 | assert!(args 225 | .iter() 226 | .any(|x| x.get_id() == "apple" && x.get_short() == Some('a'))); 227 | assert!(args.iter().any(|x| x.get_id() == "banana" 228 | && x.get_short() == Some('b') 229 | && x.get_long() == Some("banana"))); 230 | } 231 | 232 | #[test] 233 | fn args_toml() { 234 | const CLAP_TOML: &str = r#" 235 | name = "app_clap_serde" 236 | version = "1.0" 237 | author = "aobat" 238 | about = "test-clap-serde" 239 | [subcommands] 240 | sub1 = { about = "subcommand_1" } 241 | [subcommands.sub2] 242 | about = "subcommand_2" 243 | [args] 244 | apple = { short = "a" } 245 | banana = { short = "b", long = "banana", aliases = ["musa_spp"] } 246 | "#; 247 | let app: Command = toml::from_str::(CLAP_TOML) 248 | .expect("parse failed") 249 | .into(); 250 | assert_eq!(app.get_name(), "app_clap_serde"); 251 | assert_eq!(app.get_about(), Some("test-clap-serde")); 252 | let subs = app.get_subcommands().collect::>(); 253 | assert!(subs 254 | .iter() 255 | .any(|x| x.get_name() == "sub1" && x.get_about() == Some("subcommand_1"))); 256 | assert!(subs 257 | .iter() 258 | .any(|x| x.get_name() == "sub2" && x.get_about() == Some("subcommand_2"))); 259 | let args = app.get_arguments().collect::>(); 260 | assert!(args 261 | .iter() 262 | .any(|x| x.get_id() == "apple" && x.get_short() == Some('a'))); 263 | assert!(args.iter().any(|x| x.get_id() == "banana" 264 | && x.get_short() == Some('b') 265 | && x.get_long() == Some("banana"))); 266 | } 267 | 268 | #[test] 269 | fn groups_toml() { 270 | const CLAP_TOML: &str = r#" 271 | name = "app_clap_serde" 272 | version = "1.0" 273 | author = "aobat" 274 | about = "test-clap-serde" 275 | [subcommands] 276 | sub1 = { about = "subcommand_1" } 277 | [subcommands.sub2] 278 | about = "subcommand_2" 279 | [args] 280 | apple = { short = "a" } 281 | banana = { short = "b", long = "banana", aliases = ["musa_spp"] } 282 | [groups] 283 | fruit = { args = ["apple", "banana"] } 284 | "#; 285 | let app: Command = toml::from_str::(CLAP_TOML) 286 | .expect("parse failed") 287 | .into(); 288 | assert_eq!(app.get_name(), "app_clap_serde"); 289 | assert_eq!(app.get_about(), Some("test-clap-serde")); 290 | } 291 | 292 | #[test] 293 | fn arg_fail() { 294 | use clap::{Arg, Command}; 295 | use serde::de::DeserializeSeed; 296 | 297 | const CLAP_TOML: &str = r#" 298 | name = "app_clap_serde" 299 | version = "1.0" 300 | author = "aobat" 301 | about = "test-clap-serde" 302 | [args] 303 | apple = { short = } 304 | "#; 305 | let app = Command::new("app").arg(Arg::new("apple").default_value("aaa")); 306 | let wrap = CommandWrap::from(app); 307 | let mut de = toml::Deserializer::new(CLAP_TOML); 308 | assert!(wrap.deserialize(&mut de).is_err()); 309 | } 310 | -------------------------------------------------------------------------------- /src/yaml.rs: -------------------------------------------------------------------------------- 1 | use serde::{ 2 | de::{ 3 | value::{MapDeserializer, SeqDeserializer}, 4 | Error as _, IntoDeserializer, Unexpected, 5 | }, 6 | Deserializer, 7 | }; 8 | use yaml_rust::Yaml; 9 | 10 | /** 11 | Deserializing from [`Yaml`] 12 | ``` 13 | const YAML_STR: &'static str = r#" 14 | name: app_clap_serde 15 | version : "1.0" 16 | about : yaml_support! 17 | author : yaml_supporter 18 | 19 | args: 20 | - apple : 21 | - short: a 22 | - banana: 23 | - short: b 24 | - long: banana 25 | - aliases : 26 | - musa_spp 27 | 28 | subcommands: 29 | - sub1: 30 | - about : subcommand_1 31 | - sub2: 32 | - about : subcommand_2 33 | 34 | "#; 35 | let yaml = yaml_rust::Yaml::Array(yaml_rust::YamlLoader::load_from_str(YAML_STR).expect("not a yaml")); 36 | let app = clap_serde::yaml_to_app(&yaml).expect("parse failed from yaml"); 37 | assert_eq!(app.get_name(), "app_clap_serde"); 38 | ``` 39 | */ 40 | pub fn yaml_to_app(yaml: &Yaml) -> Result, Error> { 41 | let wrap = YamlWrap { yaml }; 42 | use serde::Deserialize; 43 | crate::CommandWrap::deserialize(wrap).map(|x| x.into()) 44 | } 45 | 46 | /// Wrapper to use [`Yaml`] as [`Deserializer`]. 47 | /// 48 | /// Currently this implement functions in [`Deserializer`] that is only needed in deserializing into `Command`. 49 | /// Recommend to use [`yaml_to_app`] instead. 50 | pub struct YamlWrap<'a> { 51 | yaml: &'a yaml_rust::Yaml, 52 | } 53 | 54 | impl<'a> YamlWrap<'a> { 55 | pub fn new(yaml: &'a yaml_rust::Yaml) -> Self { 56 | Self { yaml } 57 | } 58 | } 59 | 60 | #[derive(Debug, Clone)] 61 | pub enum Error { 62 | Custom(String), 63 | } 64 | 65 | impl std::fmt::Display for Error { 66 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 67 | std::fmt::Debug::fmt(self, f) 68 | } 69 | } 70 | impl std::error::Error for Error {} 71 | 72 | impl serde::de::Error for Error { 73 | fn custom(msg: T) -> Self 74 | where 75 | T: std::fmt::Display, 76 | { 77 | Self::Custom(msg.to_string()) 78 | } 79 | } 80 | 81 | macro_rules! de_num { 82 | ($sig : ident, $sig_v : ident) => { 83 | fn $sig(self, visitor: V) -> Result 84 | where 85 | V: serde::de::Visitor<'de>, 86 | { 87 | visitor.$sig_v(match self.yaml.as_i64().map(|i| i.try_into()) { 88 | Some(Ok(i)) => i, 89 | _ => return Err(as_invalid(self.yaml, "Intger")), 90 | }) 91 | } 92 | }; 93 | } 94 | 95 | fn as_invalid(y: &Yaml, expected: &str) -> Error { 96 | Error::invalid_type( 97 | match y { 98 | Yaml::Real(r) => r 99 | .parse() 100 | .map(Unexpected::Float) 101 | .unwrap_or(Unexpected::Other(r)), 102 | Yaml::Integer(i) => Unexpected::Signed(*i), 103 | Yaml::String(s) => Unexpected::Str(s), 104 | Yaml::Boolean(b) => Unexpected::Bool(*b), 105 | Yaml::Array(_) => Unexpected::Seq, 106 | Yaml::Hash(_) => Unexpected::Map, 107 | Yaml::Alias(_) => todo!(), 108 | Yaml::Null => Unexpected::Unit, 109 | Yaml::BadValue => Unexpected::Other("BadValue"), 110 | }, 111 | &expected, 112 | ) 113 | } 114 | 115 | impl<'de> IntoDeserializer<'de, Error> for YamlWrap<'de> { 116 | type Deserializer = Self; 117 | 118 | fn into_deserializer(self) -> Self::Deserializer { 119 | self 120 | } 121 | } 122 | 123 | impl<'de> Deserializer<'de> for YamlWrap<'de> { 124 | type Error = Error; 125 | 126 | fn deserialize_any(self, visitor: V) -> Result 127 | where 128 | V: serde::de::Visitor<'de>, 129 | { 130 | match self.yaml { 131 | yaml_rust::Yaml::Real(s) => { 132 | visitor.visit_f64(s.parse::().map_err(|e| Error::Custom(e.to_string()))?) 133 | } 134 | yaml_rust::Yaml::Integer(i) => visitor.visit_i64(*i), 135 | yaml_rust::Yaml::String(s) => visitor.visit_str(s), 136 | yaml_rust::Yaml::Boolean(b) => visitor.visit_bool(*b), 137 | yaml_rust::Yaml::Array(_) => self.deserialize_seq(visitor), //visitor.visit_seq(a), 138 | yaml_rust::Yaml::Hash(_) => self.deserialize_map(visitor), 139 | yaml_rust::Yaml::Alias(_) => todo!(), 140 | yaml_rust::Yaml::Null => visitor.visit_none(), 141 | yaml_rust::Yaml::BadValue => Err(as_invalid(self.yaml, "any")), 142 | } 143 | } 144 | 145 | fn deserialize_bool(self, visitor: V) -> Result 146 | where 147 | V: serde::de::Visitor<'de>, 148 | { 149 | visitor.visit_bool( 150 | self.yaml 151 | .as_bool() 152 | .ok_or_else(|| as_invalid(self.yaml, "bool"))?, 153 | ) 154 | } 155 | 156 | de_num!(deserialize_i8, visit_i8); 157 | de_num!(deserialize_i16, visit_i16); 158 | de_num!(deserialize_i32, visit_i32); 159 | de_num!(deserialize_i64, visit_i64); 160 | de_num!(deserialize_u8, visit_i8); 161 | de_num!(deserialize_u16, visit_u16); 162 | de_num!(deserialize_u32, visit_u32); 163 | de_num!(deserialize_u64, visit_u64); 164 | 165 | fn deserialize_f32(self, visitor: V) -> Result 166 | where 167 | V: serde::de::Visitor<'de>, 168 | { 169 | visitor.visit_f32( 170 | self.yaml 171 | .as_f64() 172 | .ok_or_else(|| as_invalid(self.yaml, "f32"))? as f32, 173 | ) 174 | } 175 | 176 | fn deserialize_f64(self, visitor: V) -> Result 177 | where 178 | V: serde::de::Visitor<'de>, 179 | { 180 | visitor.visit_f64( 181 | self.yaml 182 | .as_f64() 183 | .ok_or_else(|| as_invalid(self.yaml, "f64"))?, 184 | ) 185 | } 186 | 187 | fn deserialize_char(self, visitor: V) -> Result 188 | where 189 | V: serde::de::Visitor<'de>, 190 | { 191 | visitor.visit_char( 192 | self.yaml 193 | .as_str() 194 | .ok_or_else(|| as_invalid(self.yaml, "char"))? 195 | .chars() 196 | .next() 197 | .ok_or_else(|| as_invalid(self.yaml, "char"))?, 198 | ) 199 | } 200 | 201 | fn deserialize_str(self, visitor: V) -> Result 202 | where 203 | V: serde::de::Visitor<'de>, 204 | { 205 | let s = self.yaml.as_str(); 206 | visitor.visit_borrowed_str(s.ok_or_else(|| as_invalid(self.yaml, "str"))?) 207 | } 208 | 209 | fn deserialize_string(self, visitor: V) -> Result 210 | where 211 | V: serde::de::Visitor<'de>, 212 | { 213 | visitor.visit_string( 214 | self.yaml 215 | .as_str() 216 | .ok_or_else(|| as_invalid(self.yaml, "string"))? 217 | .to_string(), 218 | ) 219 | } 220 | 221 | ///not supported 222 | fn deserialize_bytes(self, _visitor: V) -> Result 223 | where 224 | V: serde::de::Visitor<'de>, 225 | { 226 | unimplemented!() 227 | } 228 | 229 | ///not supported 230 | fn deserialize_byte_buf(self, _visitor: V) -> Result 231 | where 232 | V: serde::de::Visitor<'de>, 233 | { 234 | unimplemented!() 235 | } 236 | 237 | fn deserialize_option(self, visitor: V) -> Result 238 | where 239 | V: serde::de::Visitor<'de>, 240 | { 241 | if matches!(self.yaml, yaml_rust::Yaml::Null) { 242 | visitor.visit_none() 243 | } else { 244 | visitor.visit_some(self) 245 | } 246 | } 247 | 248 | fn deserialize_unit(self, visitor: V) -> Result 249 | where 250 | V: serde::de::Visitor<'de>, 251 | { 252 | if matches!(self.yaml, yaml_rust::Yaml::Null) { 253 | visitor.visit_unit() 254 | } else { 255 | Err(as_invalid(self.yaml, "unit")) 256 | } 257 | } 258 | 259 | ///unimplemented 260 | fn deserialize_unit_struct( 261 | self, 262 | _name: &'static str, 263 | _visitor: V, 264 | ) -> Result 265 | where 266 | V: serde::de::Visitor<'de>, 267 | { 268 | todo!() 269 | } 270 | 271 | ///unimplemented 272 | fn deserialize_newtype_struct( 273 | self, 274 | _name: &'static str, 275 | _visitor: V, 276 | ) -> Result 277 | where 278 | V: serde::de::Visitor<'de>, 279 | { 280 | todo!() 281 | } 282 | 283 | ///unimplemented 284 | fn deserialize_seq(self, visitor: V) -> Result 285 | where 286 | V: serde::de::Visitor<'de>, 287 | { 288 | if let Some(n) = self.yaml.as_vec() { 289 | let seq = SeqDeserializer::new(n.iter().map(|y| YamlWrap { yaml: y })); 290 | visitor.visit_seq(seq) 291 | } else { 292 | Err(as_invalid(self.yaml, "seq")) 293 | } 294 | } 295 | 296 | ///unimplemented 297 | fn deserialize_tuple(self, _len: usize, _visitor: V) -> Result 298 | where 299 | V: serde::de::Visitor<'de>, 300 | { 301 | todo!() 302 | } 303 | 304 | ///unimplemented 305 | fn deserialize_tuple_struct( 306 | self, 307 | _name: &'static str, 308 | _len: usize, 309 | _visitor: V, 310 | ) -> Result 311 | where 312 | V: serde::de::Visitor<'de>, 313 | { 314 | todo!() 315 | } 316 | 317 | fn deserialize_map(self, visitor: V) -> Result 318 | where 319 | V: serde::de::Visitor<'de>, 320 | { 321 | match self.yaml { 322 | Yaml::Hash(h) => { 323 | let m = MapDeserializer::new( 324 | h.iter() 325 | .map(|(k, v)| (YamlWrap { yaml: k }, YamlWrap { yaml: v })), 326 | ); 327 | visitor.visit_map(m) 328 | } 329 | Yaml::Array(a) => { 330 | let x = a 331 | .iter() 332 | .map(|y| y.as_hash().ok_or_else(|| as_invalid(self.yaml, "map"))) 333 | .collect::, _>>()?; 334 | let m = MapDeserializer::new( 335 | x.into_iter() 336 | .flat_map(|x| x.iter()) 337 | .map(|(k, v)| (YamlWrap { yaml: k }, YamlWrap { yaml: v })), 338 | ); 339 | visitor.visit_map(m) 340 | } 341 | _ => Err(as_invalid(self.yaml, "map")), 342 | } 343 | } 344 | 345 | ///unimplemented 346 | fn deserialize_struct( 347 | self, 348 | _name: &'static str, 349 | _fields: &'static [&'static str], 350 | _visitor: V, 351 | ) -> Result 352 | where 353 | V: serde::de::Visitor<'de>, 354 | { 355 | todo!() 356 | } 357 | 358 | fn deserialize_enum( 359 | self, 360 | _name: &'static str, 361 | _variants: &'static [&'static str], 362 | visitor: V, 363 | ) -> Result 364 | where 365 | V: serde::de::Visitor<'de>, 366 | { 367 | if let Some(s) = self.yaml.as_str() { 368 | visitor.visit_enum(s.into_deserializer()) 369 | } else { 370 | Err(as_invalid(self.yaml, "enum")) 371 | } 372 | } 373 | 374 | fn deserialize_identifier(self, visitor: V) -> Result 375 | where 376 | V: serde::de::Visitor<'de>, 377 | { 378 | self.deserialize_str(visitor) 379 | } 380 | 381 | fn deserialize_ignored_any(self, visitor: V) -> Result 382 | where 383 | V: serde::de::Visitor<'de>, 384 | { 385 | self.deserialize_any(visitor) 386 | } 387 | } 388 | --------------------------------------------------------------------------------