├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vscode ├── settings.json └── tasks.json ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── parse-display-benches ├── Cargo.toml ├── benches │ ├── display.rs │ └── from_str.rs ├── rust-toolchain └── src │ └── lib.rs ├── parse-display-derive ├── Cargo.toml └── src │ ├── bound.rs │ ├── format_syntax.rs │ ├── lib.rs │ ├── parser_builder.rs │ ├── regex_utils.rs │ └── syn_utils.rs ├── parse-display-with ├── Cargo.toml ├── README.md ├── src │ ├── formats.rs │ ├── lib.rs │ └── tests │ │ ├── readme_parse_display.rs │ │ └── readme_parse_display_with.rs └── tests │ ├── display_formats.rs │ └── from_str_formats.rs └── parse-display ├── Cargo.toml ├── src ├── display.md ├── from_str_regex.rs ├── helpers.rs ├── helpers_std.rs ├── lib.rs └── tests.rs └── tests ├── both.rs ├── clippy_self.rs ├── compile_fail.rs ├── compile_fail ├── display │ ├── invalid_format.rs │ ├── invalid_format.stderr │ ├── invalid_format_spec.rs │ ├── invalid_format_spec.stderr │ ├── invalid_with_enum.rs │ ├── invalid_with_enum.stderr │ ├── invalid_with_struct.rs │ ├── invalid_with_struct.stderr │ ├── struct_empty_field_ident.rs │ ├── struct_empty_field_ident.stderr │ ├── struct_no_display_field.rs │ ├── struct_no_display_field.stderr │ ├── struct_no_display_field_format.rs │ ├── struct_no_display_field_format.stderr │ ├── struct_no_pointer_field.rs │ └── struct_no_pointer_field.stderr └── from_str │ ├── enum_default.rs │ ├── enum_default.stderr │ ├── invalid_with.rs │ ├── invalid_with.stderr │ ├── no_field_in_format.rs │ ├── no_field_in_format.stderr │ ├── no_field_in_regex.rs │ ├── no_field_in_regex.stderr │ ├── opt_with_not_option.rs │ ├── opt_with_not_option.stderr │ ├── struct_empty_capture_name.rs │ └── struct_empty_capture_name.stderr ├── display.rs ├── display_std.rs ├── from_str.rs ├── from_str_regex.rs └── lib.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: [cron: "20 5 * * *"] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Rustup update stable 15 | run: rustup update stable 16 | - name: Show cargo version 17 | run: cargo --version 18 | - name: Build 19 | run: cargo build --verbose 20 | - name: Build tests 21 | run: cargo test --verbose --no-run 22 | - name: Run tests 23 | run: cargo test --verbose 24 | 25 | - name: Build no-std 26 | run: cargo build --verbose --target-dir=target/no-std --no-default-features 27 | - name: Build tests no-std 28 | run: cargo test --verbose --target-dir=target/no-std --no-default-features --lib --tests --no-run 29 | - name: Run tests no-std 30 | run: cargo test --verbose --target-dir=target/no-std --no-default-features --lib --tests 31 | 32 | - name: Clippy 33 | run: cargo clippy --features std --tests --lib -- -W clippy::all 34 | env: 35 | RUSTFLAGS: -D warnings 36 | - name: Rustup toolchain install nightly 37 | run: rustup toolchain install nightly 38 | - name: Set minimal versions 39 | run: cargo +nightly update -Z minimal-versions 40 | - name: Build tests (minimal versions) 41 | run: cargo +stable test --verbose --no-run 42 | - name: Run tests (minimal versions) 43 | run: cargo +stable test --verbose 44 | - uses: taiki-e/install-action@cargo-hack 45 | - name: Check msrv 46 | run: cargo hack test --rust-version --workspace --all-targets --ignore-private 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /parse-display-benches/target 3 | /parse-display-tests/src/main.rs 4 | **/*.rs.bk 5 | Cargo.lock 6 | /.idea 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "varb", 4 | "VARC" 5 | ], 6 | "markdownlint.config": { 7 | "MD024": false, 8 | "MD041": false, 9 | }, 10 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "shell", 8 | "label": "cargo build", 9 | "command": "cargo", 10 | "args": [ 11 | "build" 12 | ], 13 | "problemMatcher": [ 14 | "$rustc" 15 | ], 16 | "presentation": { 17 | "panel": "dedicated", 18 | "clear": true 19 | } 20 | // "options": { 21 | // "env": { 22 | // "RUST_BACKTRACE": "1", 23 | // } 24 | // } 25 | }, 26 | { 27 | "type": "shell", 28 | "label": "cargo build release", 29 | "command": "cargo", 30 | "args": [ 31 | "build", 32 | "--release" 33 | ], 34 | "problemMatcher": [ 35 | "$rustc" 36 | ], 37 | "presentation": { 38 | "panel": "dedicated", 39 | "clear": true 40 | } 41 | }, 42 | { 43 | "type": "shell", 44 | "label": "cargo test", 45 | "command": "cargo", 46 | "args": [ 47 | "test" 48 | ], 49 | "problemMatcher": [ 50 | "$rustc" 51 | ], 52 | "group": { 53 | "kind": "build", 54 | "isDefault": true 55 | }, 56 | "presentation": { 57 | "panel": "dedicated", 58 | "clear": true 59 | }, 60 | "dependsOn": [ 61 | "rustdoc-include" 62 | ], 63 | }, 64 | { 65 | "type": "shell", 66 | "label": "cargo test no-std", 67 | "command": "cargo", 68 | "args": [ 69 | "test", 70 | "--target-dir=target/no-std", 71 | "--no-default-features", 72 | "--lib", 73 | "--tests", 74 | ], 75 | "problemMatcher": [ 76 | "$rustc" 77 | ], 78 | "group": { 79 | "kind": "test", 80 | "isDefault": true 81 | }, 82 | "presentation": { 83 | "panel": "dedicated", 84 | "clear": true 85 | }, 86 | "dependsOn": [ 87 | "rustdoc-include" 88 | ], 89 | }, 90 | { 91 | "type": "shell", 92 | "label": "cargo run exmaple", 93 | "command": "cargo", 94 | "args": [ 95 | "run", 96 | "--example", 97 | "${fileBasenameNoExtension}" 98 | ], 99 | "problemMatcher": [ 100 | "$rustc" 101 | ], 102 | "presentation": { 103 | "panel": "dedicated", 104 | "clear": true 105 | } 106 | }, 107 | { 108 | "type": "shell", 109 | "label": "cargo doc open", 110 | "command": "cargo", 111 | "args": [ 112 | "+nightly", 113 | "doc", 114 | "--open", 115 | "--no-deps", 116 | "--all-features" 117 | ], 118 | "problemMatcher": [ 119 | "$rustc" 120 | ], 121 | "presentation": { 122 | "panel": "dedicated", 123 | "clear": true 124 | }, 125 | "dependsOn": [ 126 | "rustdoc-include" 127 | ], 128 | }, 129 | { 130 | "type": "shell", 131 | "label": "cargo expand test", 132 | "command": "cargo", 133 | "args": [ 134 | "expand", 135 | "--test", 136 | "lib" 137 | ], 138 | "options": { 139 | "cwd": "${workspaceFolder}/parse-display-tests" 140 | }, 141 | "problemMatcher": [ 142 | "$rustc" 143 | ], 144 | "presentation": { 145 | "panel": "dedicated", 146 | "clear": true 147 | } 148 | }, 149 | { 150 | "type": "shell", 151 | "label": "cargo clippy", 152 | "command": "cargo", 153 | "args": [ 154 | "clippy", 155 | "--tests", 156 | "--lib", 157 | "--", 158 | "-W", 159 | "clippy::all" 160 | ], 161 | "problemMatcher": [ 162 | { 163 | "base": "$rustc", 164 | "owner": "clippy", 165 | } 166 | ], 167 | "presentation": { 168 | "panel": "dedicated", 169 | "clear": true 170 | } 171 | }, 172 | { 173 | "type": "shell", 174 | "label": "cargo fix & fmt", 175 | "command": "cargo fix && cargo clippy --fix --allow-dirty && cargo fmt", 176 | "problemMatcher": [ 177 | "$rustc" 178 | ], 179 | "presentation": { 180 | "panel": "dedicated", 181 | "clear": true, 182 | } 183 | }, 184 | { 185 | "type": "shell", 186 | "label": "cargo bench", 187 | "command": "cargo", 188 | "args": [ 189 | "+nightly", 190 | "bench", 191 | "--target-dir", 192 | "target/bench", 193 | ], 194 | "options": { 195 | "cwd": "${workspaceFolder}/parse-display-benches" 196 | }, 197 | "problemMatcher": [ 198 | "$rustc" 199 | ], 200 | "presentation": { 201 | "panel": "dedicated", 202 | "clear": true 203 | } 204 | }, 205 | { 206 | "type": "shell", 207 | "label": "cargo update minimal-versions", 208 | "command": "cargo", 209 | "args": [ 210 | "+nightly", 211 | "update", 212 | "-Z", 213 | "minimal-versions" 214 | ], 215 | "problemMatcher": [ 216 | "$rustc" 217 | ], 218 | "presentation": { 219 | "panel": "dedicated", 220 | "clear": true 221 | } 222 | }, 223 | { 224 | "type": "shell", 225 | "label": "update compile error", 226 | "command": "cargo", 227 | "args": [ 228 | "test", 229 | "--test", 230 | "compile_fail", 231 | "--", 232 | "--ignored" 233 | ], 234 | "problemMatcher": [ 235 | "$rustc" 236 | ], 237 | "presentation": { 238 | "panel": "dedicated", 239 | "clear": true 240 | }, 241 | "options": { 242 | "env": { 243 | "TRYBUILD": "overwrite" 244 | } 245 | } 246 | }, 247 | { 248 | "type": "shell", 249 | "label": "rustdoc-include", 250 | "command": "rustdoc-include", 251 | "args": [ 252 | "--root", 253 | "${workspaceFolder}" 254 | ], 255 | "problemMatcher": [ 256 | { 257 | "owner": "rustdoc-include", 258 | "fileLocation": [ 259 | "relative", 260 | "${workspaceFolder}" 261 | ], 262 | "pattern": [ 263 | { 264 | "regexp": "^(error): (.*)$", 265 | "severity": 1, 266 | "message": 2, 267 | }, 268 | { 269 | "regexp": "^--> (.*):(\\d+)\\s*$", 270 | "file": 1, 271 | "line": 2, 272 | "loop": true, 273 | }, 274 | ] 275 | }, 276 | ], 277 | "presentation": { 278 | "panel": "dedicated", 279 | "clear": true 280 | } 281 | }, 282 | ] 283 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ### Added 6 | 7 | - Add `FromStrRegex` trait. 8 | - Support `#[from_str(regex_infer)]`. 9 | - Support `#[display(opt)]`. 10 | 11 | ### Changed 12 | 13 | - Use edition 2024. 14 | - Set `rust-version` to 1.85.0. 15 | - Deprecate `FromStrFormat::regex` and add `FromStrFormat::regex_pattern` instead. 16 | 17 | ### Deprecated 18 | 19 | ### Removed 20 | 21 | ### Fixed 22 | 23 | ### Performance 24 | 25 | ### Security 26 | 27 | ## [0.10.0] - 2024-08-04 28 | 29 | ### Changed 30 | 31 | - Set `rust-version` to 1.80.0. 32 | - In debug mode, it will panic if the result of `FromStrFormat::regex` varies for the same field depending on the type parameters. 33 | - Change the behavior when both `#[display("...")]` and `#[from_str("...")]` are specified for a field. ([dc14a2b]) 34 | 35 | [dc14a2b]: https://github.com/frozenlib/parse-display/commit/dc14a2b78a0b547f4911d2cf45d2f8b96aa723e2 36 | 37 | ### Fixed 38 | 39 | - Fix `#[from_str]` to not affect `Display`. 40 | 41 | ## [0.9.1] - 2024-05-31 42 | 43 | ### Changed 44 | 45 | - Set `rust-version` to 1.70.0. [#42](https://github.com/frozenlib/parse-display/issues/42) 46 | 47 | ### Fixed 48 | 49 | - Ensure `Pointer` format is formatted correctly. 50 | 51 | ### Performance 52 | 53 | - Optimizing runtime performance for the literal string case. [#39](https://github.com/frozenlib/parse-display/issues/39) 54 | 55 | ## [0.9.0] - 2024-02-04 56 | 57 | ### Added 58 | 59 | - Support `#[display(with = ...)]`. [#36](https://github.com/frozenlib/parse-display/issues/36) 60 | - Support for use of format traits other than `Display` for self. [#35](https://github.com/frozenlib/parse-display/issues/35) 61 | - Allow DST fields with `#[derive(Display)]`. 62 | 63 | ### Changed 64 | 65 | - Use [`std::sync::OnceLock`] in generated code and remove [`once_cell`] dependency. 66 | 67 | [`std::sync::OnceLock`]: https://doc.rust-lang.org/std/sync/struct.OnceLock.html 68 | [`once_cell`]: https://crates.io/crates/once_cell 69 | 70 | ## [0.8.2] - 2023-07-16 71 | 72 | ### Added 73 | 74 | - Enabled `(?.*)` usage in regex alongside `(?P.*)`. 75 | 76 | ### Changed 77 | 78 | - Update to `regex-syntax` 0.7. 79 | 80 | ### Fixed 81 | 82 | - Fix handling of regex that resemble, but aren't, captures (e.g. `(\(?.*)`) 83 | 84 | ## [0.8.1] - 2023-06-10 85 | 86 | ### Added 87 | 88 | - Support `#[display(crate = ...)]`. 89 | 90 | ### Changed 91 | 92 | - Update to `syn` 2.0. 93 | 94 | ## [0.8.0] - 2022-12-21 95 | 96 | ### Fixed 97 | 98 | - Fixed a problem where strings containing newlines could not be parsed [#27](https://github.com/frozenlib/parse-display/issues/27) 99 | 100 | ## [0.7.0] - 2022-12-05 101 | 102 | ### Fixed 103 | 104 | - Use result with full path in the generated code [#26](https://github.com/frozenlib/parse-display/pull/26) 105 | 106 | ## [0.6.0] - 2022-09-01 107 | 108 | ### Added 109 | 110 | - Support `#[from_str(ignore)]` for variant. 111 | 112 | [unreleased]: https://github.com/frozenlib/parse-display/compare/v0.10.0...HEAD 113 | [0.10.0]: https://github.com/frozenlib/parse-display/compare/v0.9.1...v0.10.0 114 | [0.9.1]: https://github.com/frozenlib/parse-display/compare/v0.9.0...v0.9.1 115 | [0.9.0]: https://github.com/frozenlib/parse-display/compare/v0.8.2...v0.9.0 116 | [0.8.2]: https://github.com/frozenlib/parse-display/compare/v0.8.1...v0.8.2 117 | [0.8.1]: https://github.com/frozenlib/parse-display/compare/v0.8.0...v0.8.1 118 | [0.8.0]: https://github.com/frozenlib/parse-display/compare/v0.7.0...v0.8.0 119 | [0.7.0]: https://github.com/frozenlib/parse-display/compare/v0.6.0...v0.7.0 120 | [0.6.0]: https://github.com/frozenlib/parse-display/compare/v0.5.5...v0.6.0 121 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "3" 3 | members = ["parse-display", "parse-display-derive", "parse-display-with"] 4 | 5 | [workspace.dependencies] 6 | regex = "1.11.1" 7 | regex-syntax = "0.8.5" 8 | -------------------------------------------------------------------------------- /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 | MIT License 2 | 3 | Copyright (c) 2019 FrozenLib 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parse-display 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/parse-display.svg)](https://crates.io/crates/parse-display) 4 | [![Docs.rs](https://docs.rs/parse-display/badge.svg)](https://docs.rs/parse-display/) 5 | [![Actions Status](https://github.com/frozenlib/parse-display/workflows/CI/badge.svg)](https://github.com/frozenlib/parse-display/actions) 6 | 7 | This crate provides derive macro `Display` and `FromStr`. 8 | These macros use common helper attributes to specify the format. 9 | 10 | ## Install 11 | 12 | Add this to your Cargo.toml: 13 | 14 | ```toml 15 | [dependencies] 16 | parse-display = "0.10.0" 17 | ``` 18 | 19 | ## Documentation 20 | 21 | See [`#[derive(Display)]`](https://docs.rs/parse-display/latest/parse_display/derive.Display.html) documentation for details. 22 | 23 | ## Example 24 | 25 | ```rust 26 | use parse_display::{Display, FromStr}; 27 | 28 | #[derive(Display, FromStr, PartialEq, Debug)] 29 | #[display("{a}-{b}")] 30 | struct X { 31 | a: u32, 32 | b: u32, 33 | } 34 | assert_eq!(X { a:10, b:20 }.to_string(), "10-20"); 35 | assert_eq!("10-20".parse(), Ok(X { a:10, b:20 })); 36 | 37 | 38 | #[derive(Display, FromStr, PartialEq, Debug)] 39 | #[display(style = "snake_case")] 40 | enum Y { 41 | VarA, 42 | VarB, 43 | } 44 | assert_eq!(Y::VarA.to_string(), "var_a"); 45 | assert_eq!("var_a".parse(), Ok(Y::VarA)); 46 | ``` 47 | 48 | ## License 49 | 50 | This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-\* files for details. 51 | 52 | ## Contribution 53 | 54 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 55 | -------------------------------------------------------------------------------- /parse-display-benches/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parse-display-benches" 3 | version = "0.1.0" 4 | authors = ["frozenlib "] 5 | edition = "2024" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [workspace] 11 | 12 | [dependencies] 13 | parse-display = { path = "../parse-display" } 14 | -------------------------------------------------------------------------------- /parse-display-benches/benches/display.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | use std::{ 6 | fmt::{self, Display, Write}, 7 | hint::black_box, 8 | }; 9 | 10 | use parse_display::{Display, FromStr}; 11 | 12 | #[bench] 13 | fn no_placeholder_derive(b: &mut test::Bencher) { 14 | #[derive(Display, FromStr)] 15 | #[display("a")] 16 | struct X; 17 | 18 | bench_write(b, X); 19 | } 20 | 21 | #[bench] 22 | fn no_placeholder_by_hand_write_str(b: &mut test::Bencher) { 23 | struct X; 24 | impl Display for X { 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | f.write_str("a") 27 | } 28 | } 29 | bench_write(b, X); 30 | } 31 | fn bench_write(b: &mut test::Bencher, value: T) { 32 | let mut buffer = String::new(); 33 | b.iter(|| { 34 | buffer.clear(); 35 | write!(&mut buffer, "{}", value).unwrap(); 36 | black_box(&buffer); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /parse-display-benches/benches/from_str.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | use parse_display::{Display, FromStr, ParseError}; 6 | use std::{hint::black_box, str::FromStr}; 7 | 8 | #[bench] 9 | fn parse_simple_enum_derive(b: &mut test::Bencher) { 10 | #[derive(FromStr)] 11 | enum SimpleEnumDerive { 12 | ItemA, 13 | ItemB, 14 | ItemC, 15 | ItemD, 16 | } 17 | 18 | let inputs = ["ItemA", "ItemB", "ItemC", "ItemD"]; 19 | b.iter(|| { 20 | for &input in &inputs { 21 | black_box(input.parse::().unwrap()); 22 | } 23 | }); 24 | } 25 | #[bench] 26 | fn parse_simple_enum_by_hand(b: &mut test::Bencher) { 27 | enum SimpleEnumByHand { 28 | ItemA, 29 | ItemB, 30 | ItemC, 31 | ItemD, 32 | } 33 | 34 | impl FromStr for SimpleEnumByHand { 35 | type Err = ParseError; 36 | 37 | fn from_str(s: &str) -> Result { 38 | match s { 39 | "ItemA" => Ok(Self::ItemA), 40 | "ItemB" => Ok(Self::ItemB), 41 | "ItemC" => Ok(Self::ItemC), 42 | "ItemD" => Ok(Self::ItemD), 43 | _ => Err(ParseError::new()), 44 | } 45 | } 46 | } 47 | 48 | let inputs = ["ItemA", "ItemB", "ItemC", "ItemD"]; 49 | b.iter(|| { 50 | for &input in &inputs { 51 | black_box(input.parse::().unwrap()); 52 | } 53 | }); 54 | } 55 | 56 | #[bench] 57 | fn parse_non_regex_format_struct_derive(b: &mut test::Bencher) { 58 | #[derive(Display, FromStr)] 59 | #[display("{a},{b},{c}")] 60 | struct TestInput { 61 | a: u32, 62 | b: u32, 63 | c: u32, 64 | } 65 | 66 | let input = TestInput { 67 | a: 10, 68 | b: 20, 69 | c: 30, 70 | } 71 | .to_string(); 72 | 73 | // The first run is excluded from the benchmark because of the time required to initialize the regex. 74 | let _ = input.parse::().unwrap(); 75 | 76 | b.iter(|| { 77 | black_box(input.parse::().unwrap()); 78 | }); 79 | } 80 | 81 | #[bench] 82 | fn parse_non_regex_format_struct_by_hand(b: &mut test::Bencher) { 83 | #[derive(Display)] 84 | #[display("{a},{b},{c}")] 85 | struct TestInput { 86 | a: u32, 87 | b: u32, 88 | c: u32, 89 | } 90 | impl FromStr for TestInput { 91 | type Err = ParseError; 92 | 93 | fn from_str(s: &str) -> Result { 94 | let idx1 = s.find(",").ok_or_else(ParseError::new)?; 95 | let idx2 = idx1 + 1 + s[idx1 + 1..].find(",").ok_or_else(ParseError::new)?; 96 | let a = s[0..idx1].parse().map_err(|_| ParseError::new())?; 97 | let b = s[idx1 + 1..idx2].parse().map_err(|_| ParseError::new())?; 98 | let c = s[idx2 + 1..].parse().map_err(|_| ParseError::new())?; 99 | Ok(Self { a, b, c }) 100 | } 101 | } 102 | 103 | let input = TestInput { 104 | a: 10, 105 | b: 20, 106 | c: 30, 107 | } 108 | .to_string(); 109 | 110 | b.iter(|| { 111 | black_box(input.parse::().unwrap()); 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /parse-display-benches/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly -------------------------------------------------------------------------------- /parse-display-benches/src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /parse-display-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parse-display-derive" 3 | version = "0.10.0" 4 | authors = ["frozenlib"] 5 | license = "MIT OR Apache-2.0" 6 | readme = "../README.md" 7 | repository = "https://github.com/frozenlib/parse-display" 8 | documentation = "https://docs.rs/parse-display/" 9 | keywords = ["derive", "enum", "from_str", "display", "regex"] 10 | categories = ["parsing"] 11 | description = "Procedural macro to implement Display and FromStr using common settings." 12 | edition = "2024" 13 | rust-version = "1.85.0" 14 | 15 | [lib] 16 | proc-macro = true 17 | 18 | [features] 19 | default = [] 20 | std = [] 21 | 22 | 23 | [dependencies] 24 | syn = { version = "2.0.72", features = ["visit"] } 25 | quote = "1.0.36" 26 | proc-macro2 = "1.0.86" 27 | regex = { workspace = true } 28 | regex-syntax = { workspace = true } 29 | structmeta = "0.3.0" 30 | -------------------------------------------------------------------------------- /parse-display-derive/src/bound.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | use structmeta::ToTokens; 3 | use syn::{ 4 | parse::{discouraged::Speculative, Parse, ParseStream}, 5 | parse_quote, Path, Result, Token, Type, WherePredicate, 6 | }; 7 | 8 | #[derive(Clone, ToTokens)] 9 | pub enum Bound { 10 | Type(Type), 11 | Pred(WherePredicate), 12 | Default(Token![..]), 13 | } 14 | 15 | impl Parse for Bound { 16 | fn parse(input: ParseStream) -> Result { 17 | if input.peek(Token![..]) { 18 | return Ok(Self::Default(input.parse()?)); 19 | } 20 | let fork = input.fork(); 21 | match fork.parse() { 22 | Ok(p) => { 23 | input.advance_to(&fork); 24 | Ok(Self::Pred(p)) 25 | } 26 | Err(e) => { 27 | if let Ok(ty) = input.parse() { 28 | Ok(Self::Type(ty)) 29 | } else { 30 | Err(e) 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | pub struct Bounds { 38 | pub ty: Vec, 39 | pub pred: Vec, 40 | pub can_extend: bool, 41 | } 42 | impl Bounds { 43 | fn new(can_extend: bool) -> Self { 44 | Bounds { 45 | ty: Vec::new(), 46 | pred: Vec::new(), 47 | can_extend, 48 | } 49 | } 50 | pub fn from_data(bound: Option>) -> Self { 51 | if let Some(bound) = bound { 52 | let mut bs = Self::new(false); 53 | for b in bound { 54 | bs.push(b); 55 | } 56 | bs 57 | } else { 58 | Self::new(true) 59 | } 60 | } 61 | fn push(&mut self, bound: Bound) { 62 | match bound { 63 | Bound::Type(ty) => self.ty.push(ty), 64 | Bound::Pred(pred) => self.pred.push(pred), 65 | Bound::Default(_) => self.can_extend = true, 66 | } 67 | } 68 | pub fn child(&mut self, bounds: Option>) -> BoundsChild { 69 | let bounds = if self.can_extend { 70 | Self::from_data(bounds) 71 | } else { 72 | Self::new(false) 73 | }; 74 | BoundsChild { 75 | owner: self, 76 | bounds, 77 | } 78 | } 79 | pub fn build_wheres(self, trait_path: &Path) -> Vec { 80 | let mut pred = self.pred; 81 | for ty in self.ty { 82 | pred.push(parse_quote!(#ty : #trait_path)); 83 | } 84 | pred 85 | } 86 | } 87 | pub struct BoundsChild<'a> { 88 | owner: &'a mut Bounds, 89 | bounds: Bounds, 90 | } 91 | impl Deref for BoundsChild<'_> { 92 | type Target = Bounds; 93 | 94 | fn deref(&self) -> &Self::Target { 95 | &self.bounds 96 | } 97 | } 98 | impl DerefMut for BoundsChild<'_> { 99 | fn deref_mut(&mut self) -> &mut Self::Target { 100 | &mut self.bounds 101 | } 102 | } 103 | impl Drop for BoundsChild<'_> { 104 | fn drop(&mut self) { 105 | if self.owner.can_extend { 106 | self.owner.ty.append(&mut self.bounds.ty); 107 | self.owner.pred.append(&mut self.bounds.pred); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /parse-display-derive/src/format_syntax.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::str::FromStr; 3 | 4 | use proc_macro2::Span; 5 | 6 | #[derive(Debug, PartialEq, Eq)] 7 | pub enum Sign { 8 | Plus, 9 | Minus, 10 | } 11 | 12 | #[derive(Debug, PartialEq, Eq)] 13 | pub enum Align { 14 | Left, 15 | Right, 16 | Center, 17 | } 18 | 19 | #[derive(Debug, PartialEq, Eq, Default)] 20 | pub struct FormatSpec<'a> { 21 | pub fill: Option, 22 | pub align: Option, 23 | pub sign: Option, 24 | pub is_alternate: bool, 25 | pub is_zero: bool, 26 | pub width: Option>, 27 | pub precision: Option>, 28 | pub format_type: FormatType, 29 | } 30 | 31 | #[derive(Debug, PartialEq, Eq)] 32 | pub enum SubArg<'a, T> { 33 | Value(T), 34 | Index(usize), 35 | Name(&'a str), 36 | Input, 37 | } 38 | 39 | #[derive(Debug, PartialEq, Eq, Default)] 40 | pub enum FormatType { 41 | #[default] 42 | Display, 43 | Debug, 44 | DebugUpperHex, 45 | DebugLowerHex, 46 | Octal, 47 | LowerHex, 48 | UpperHex, 49 | Pointer, 50 | Binary, 51 | LowerExp, 52 | UpperExp, 53 | } 54 | impl FormatType { 55 | pub fn trait_name(&self) -> &str { 56 | match self { 57 | FormatType::Display => "Display", 58 | FormatType::Debug | FormatType::DebugUpperHex | FormatType::DebugLowerHex => "Debug", 59 | FormatType::Octal => "Octal", 60 | FormatType::LowerHex => "LowerHex", 61 | FormatType::UpperHex => "UpperHex", 62 | FormatType::Pointer => "Pointer", 63 | FormatType::Binary => "Binary", 64 | FormatType::LowerExp => "LowerExp", 65 | FormatType::UpperExp => "UpperExp", 66 | } 67 | } 68 | } 69 | 70 | #[derive(Debug, Eq, PartialEq)] 71 | pub struct FormatParseError; 72 | 73 | impl FormatParseError { 74 | const ERROR_MESSAGE: &'static str = "FormatSpec parse failed."; 75 | } 76 | 77 | impl std::error::Error for FormatParseError { 78 | fn description(&self) -> &str { 79 | FormatParseError::ERROR_MESSAGE 80 | } 81 | } 82 | impl Display for FormatParseError { 83 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { 84 | write!(f, "{}", FormatParseError::ERROR_MESSAGE) 85 | } 86 | } 87 | 88 | impl<'a> FormatSpec<'a> { 89 | pub fn parse_with_span(s: &'a str, span: Span) -> syn::Result { 90 | match Self::parse(s) { 91 | Ok(ps) => Ok(ps), 92 | Err(_) => bail!(span, "unknown format trait `{s}`"), // This message is the same as the one output by the compiler 93 | } 94 | } 95 | 96 | pub fn parse(s: &'a str) -> std::result::Result { 97 | let re = regex!( 98 | "^\ 99 | ((?.)?\ 100 | (?[<>^]))??\ 101 | (?[+-])?\ 102 | (?#)?\ 103 | (?0)?\ 104 | (\ 105 | (?[0-9]+)|\ 106 | ((?[a-zA-Z0-9_]+)\\$)\ 107 | )?\ 108 | (\\.(\ 109 | (?\\*)|\ 110 | (?[0-9]+)|\ 111 | ((?[a-zA-Z0-9_]+)\\$)\ 112 | ))?\ 113 | (?[a-zA-Z0-9_]*\\??)\ 114 | $" 115 | ); 116 | 117 | let c = re.captures(s).ok_or(FormatParseError)?; 118 | let fill = c.name("fill").map(|m| m.as_str().chars().next().unwrap()); 119 | let align = c.name("align").map(|m| m.as_str().parse().unwrap()); 120 | let sign = c.name("sign").map(|m| match m.as_str() { 121 | "+" => Sign::Plus, 122 | "-" => Sign::Minus, 123 | _ => unreachable!(), 124 | }); 125 | let is_alternate = c.name("is_alternate").is_some(); 126 | let is_zero = c.name("is_zero").is_some(); 127 | let width = if let Some(m) = c.name("width_integer") { 128 | let value = m.as_str().parse().map_err(|_| FormatParseError)?; 129 | Some(SubArg::Value(value)) 130 | } else if let Some(m) = c.name("width_arg") { 131 | let s = m.as_str(); 132 | Some(if let Ok(idx) = s.parse() { 133 | SubArg::Index(idx) 134 | } else { 135 | SubArg::Name(s) 136 | }) 137 | } else { 138 | None 139 | }; 140 | 141 | let precision = if let Some(m) = c.name("precision_integer") { 142 | let value = m.as_str().parse().map_err(|_| FormatParseError)?; 143 | Some(SubArg::Value(value)) 144 | } else if let Some(m) = c.name("precision_arg") { 145 | let s = m.as_str(); 146 | Some(if let Ok(idx) = s.parse() { 147 | SubArg::Index(idx) 148 | } else { 149 | SubArg::Name(s) 150 | }) 151 | } else if c.name("precision_input").is_some() { 152 | Some(SubArg::Input) 153 | } else { 154 | None 155 | }; 156 | let format_type = c.name("format_type").unwrap().as_str().parse()?; 157 | 158 | Ok(FormatSpec { 159 | fill, 160 | align, 161 | sign, 162 | is_alternate, 163 | is_zero, 164 | width, 165 | precision, 166 | format_type, 167 | }) 168 | } 169 | } 170 | 171 | impl FromStr for Align { 172 | type Err = FormatParseError; 173 | fn from_str(s: &str) -> std::result::Result { 174 | Ok(match s { 175 | "<" => Align::Left, 176 | ">" => Align::Right, 177 | "^" => Align::Center, 178 | _ => return Err(FormatParseError), 179 | }) 180 | } 181 | } 182 | 183 | impl FromStr for FormatType { 184 | type Err = FormatParseError; 185 | fn from_str(s: &str) -> std::result::Result { 186 | Ok(match s { 187 | "" => FormatType::Display, 188 | "?" => FormatType::Debug, 189 | "x?" => FormatType::DebugLowerHex, 190 | "X?" => FormatType::DebugUpperHex, 191 | "o" => FormatType::Octal, 192 | "x" => FormatType::LowerHex, 193 | "X" => FormatType::UpperHex, 194 | "p" => FormatType::Pointer, 195 | "b" => FormatType::Binary, 196 | "e" => FormatType::LowerExp, 197 | "E" => FormatType::UpperExp, 198 | _ => return Err(FormatParseError), 199 | }) 200 | } 201 | } 202 | impl Display for FormatType { 203 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { 204 | match self { 205 | FormatType::Display => write!(f, ""), 206 | FormatType::Debug => write!(f, "?"), 207 | FormatType::DebugLowerHex => write!(f, "x?"), 208 | FormatType::DebugUpperHex => write!(f, "X?"), 209 | FormatType::Octal => write!(f, "o"), 210 | FormatType::LowerHex => write!(f, "x"), 211 | FormatType::UpperHex => write!(f, "X"), 212 | FormatType::Pointer => write!(f, "p"), 213 | FormatType::Binary => write!(f, "b"), 214 | FormatType::LowerExp => write!(f, "e"), 215 | FormatType::UpperExp => write!(f, "E"), 216 | } 217 | } 218 | } 219 | 220 | #[cfg(test)] 221 | mod tests { 222 | use super::*; 223 | 224 | #[test] 225 | fn align() { 226 | assert_ps( 227 | "<", 228 | FormatSpec { 229 | align: Some(Align::Left), 230 | ..Default::default() 231 | }, 232 | ); 233 | assert_ps( 234 | "^", 235 | FormatSpec { 236 | align: Some(Align::Center), 237 | ..Default::default() 238 | }, 239 | ); 240 | assert_ps( 241 | ">", 242 | FormatSpec { 243 | align: Some(Align::Right), 244 | ..Default::default() 245 | }, 246 | ); 247 | } 248 | 249 | #[test] 250 | fn fill_align() { 251 | assert_ps( 252 | "x<", 253 | FormatSpec { 254 | fill: Some('x'), 255 | align: Some(Align::Left), 256 | ..Default::default() 257 | }, 258 | ); 259 | assert_ps( 260 | "0>", 261 | FormatSpec { 262 | fill: Some('0'), 263 | align: Some(Align::Right), 264 | ..Default::default() 265 | }, 266 | ); 267 | } 268 | 269 | #[test] 270 | fn sign() { 271 | assert_ps( 272 | "+", 273 | FormatSpec { 274 | sign: Some(Sign::Plus), 275 | ..Default::default() 276 | }, 277 | ); 278 | assert_ps( 279 | "-", 280 | FormatSpec { 281 | sign: Some(Sign::Minus), 282 | ..Default::default() 283 | }, 284 | ); 285 | } 286 | #[test] 287 | fn alternate() { 288 | assert_ps( 289 | "#", 290 | FormatSpec { 291 | is_alternate: true, 292 | ..Default::default() 293 | }, 294 | ); 295 | } 296 | 297 | #[test] 298 | fn zero() { 299 | assert_ps( 300 | "0", 301 | FormatSpec { 302 | is_zero: true, 303 | ..Default::default() 304 | }, 305 | ); 306 | } 307 | 308 | #[test] 309 | fn width_value() { 310 | assert_ps( 311 | "5", 312 | FormatSpec { 313 | width: Some(SubArg::Value(5)), 314 | ..Default::default() 315 | }, 316 | ); 317 | } 318 | 319 | #[test] 320 | fn width_arg_index() { 321 | assert_ps( 322 | "5$", 323 | FormatSpec { 324 | width: Some(SubArg::Index(5)), 325 | ..Default::default() 326 | }, 327 | ); 328 | } 329 | 330 | #[test] 331 | fn width_arg_name() { 332 | assert_ps( 333 | "field$", 334 | FormatSpec { 335 | width: Some(SubArg::Name("field")), 336 | ..Default::default() 337 | }, 338 | ); 339 | } 340 | 341 | #[test] 342 | fn zero_width() { 343 | assert_ps( 344 | "05", 345 | FormatSpec { 346 | is_zero: true, 347 | width: Some(SubArg::Value(5)), 348 | ..Default::default() 349 | }, 350 | ); 351 | } 352 | 353 | #[test] 354 | fn precision_value() { 355 | assert_ps( 356 | ".5", 357 | FormatSpec { 358 | precision: Some(SubArg::Value(5)), 359 | ..Default::default() 360 | }, 361 | ); 362 | } 363 | 364 | #[test] 365 | fn precision_arg_index() { 366 | assert_ps( 367 | ".5$", 368 | FormatSpec { 369 | precision: Some(SubArg::Index(5)), 370 | ..Default::default() 371 | }, 372 | ); 373 | } 374 | 375 | #[test] 376 | fn precision_arg_name() { 377 | assert_ps( 378 | ".field$", 379 | FormatSpec { 380 | precision: Some(SubArg::Name("field")), 381 | ..Default::default() 382 | }, 383 | ); 384 | } 385 | 386 | #[test] 387 | fn precision_arg_input() { 388 | assert_ps( 389 | ".*", 390 | FormatSpec { 391 | precision: Some(SubArg::Input), 392 | ..Default::default() 393 | }, 394 | ); 395 | } 396 | 397 | #[test] 398 | fn format_type() { 399 | assert_ps( 400 | "?", 401 | FormatSpec { 402 | format_type: FormatType::Debug, 403 | ..Default::default() 404 | }, 405 | ); 406 | assert_ps( 407 | "x?", 408 | FormatSpec { 409 | format_type: FormatType::DebugLowerHex, 410 | ..Default::default() 411 | }, 412 | ); 413 | assert_ps( 414 | "x", 415 | FormatSpec { 416 | format_type: FormatType::LowerHex, 417 | ..Default::default() 418 | }, 419 | ); 420 | assert_ps( 421 | "X", 422 | FormatSpec { 423 | format_type: FormatType::UpperHex, 424 | ..Default::default() 425 | }, 426 | ); 427 | assert_ps( 428 | "b", 429 | FormatSpec { 430 | format_type: FormatType::Binary, 431 | ..Default::default() 432 | }, 433 | ); 434 | } 435 | 436 | #[test] 437 | fn all() { 438 | assert_ps( 439 | "_>+#05$.name$x?", 440 | FormatSpec { 441 | fill: Some('_'), 442 | align: Some(Align::Right), 443 | sign: Some(Sign::Plus), 444 | is_alternate: true, 445 | is_zero: true, 446 | width: Some(SubArg::Index(5)), 447 | precision: Some(SubArg::Name("name")), 448 | format_type: FormatType::DebugLowerHex, 449 | }, 450 | ); 451 | } 452 | 453 | fn assert_ps<'a>(s: &'a str, ps: FormatSpec<'a>) { 454 | assert_eq!(FormatSpec::parse(s), Ok(ps), "input : {s}"); 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /parse-display-derive/src/parser_builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | field_map, get_option_element, join, regex_utils::*, set_span, syn_utils::*, Bounds, 3 | DisplayFormat, DisplayFormatPart, DisplayStyle, FieldKey, HelperAttributes, VarBase, With, 4 | }; 5 | use proc_macro2::{Span, TokenStream}; 6 | use quote::{format_ident, quote, quote_spanned}; 7 | use regex::{Captures, Regex}; 8 | use regex_syntax::{ 9 | escape, 10 | hir::{Hir, Repetition}, 11 | }; 12 | use std::{ 13 | collections::{BTreeMap, HashMap}, 14 | mem, 15 | }; 16 | use syn::{ 17 | parse_quote, spanned::Spanned, DataStruct, Expr, Field, Fields, Ident, LitStr, Path, Result, 18 | Variant, 19 | }; 20 | 21 | pub(crate) struct ParserBuilder<'a> { 22 | capture_next: usize, 23 | parse_format: ParseFormat, 24 | fields: BTreeMap>, 25 | with: Vec, 26 | source: &'a Fields, 27 | use_default: bool, 28 | span: Span, 29 | new_expr: Option, 30 | crate_path: &'a Path, 31 | } 32 | 33 | impl<'a> ParserBuilder<'a> { 34 | fn new(source: &'a Fields, regex_infer: bool, crate_path: &'a Path) -> Result { 35 | let mut fields = BTreeMap::new(); 36 | for (key, field) in field_map(source) { 37 | fields.insert(key, FieldEntry::new(field, regex_infer, crate_path)?); 38 | } 39 | Ok(Self { 40 | source, 41 | capture_next: 1, 42 | parse_format: ParseFormat::new(), 43 | fields, 44 | with: Vec::new(), 45 | use_default: false, 46 | span: Span::call_site(), 47 | new_expr: None, 48 | crate_path, 49 | }) 50 | } 51 | pub fn from_struct(hattrs: &'a HelperAttributes, data: &'a DataStruct) -> Result { 52 | let mut s = Self::new(&data.fields, hattrs.regex_infer, &hattrs.crate_path)?; 53 | let vb = VarBase::Struct { data }; 54 | s.new_expr.clone_from(&hattrs.new_expr); 55 | s.apply_attrs(hattrs)?; 56 | s.push_attrs(hattrs, &vb)?; 57 | Ok(s) 58 | } 59 | pub fn from_variant( 60 | hattrs_variant: &HelperAttributes, 61 | hattrs_enum: &'a HelperAttributes, 62 | variant: &'a Variant, 63 | ) -> Result { 64 | let mut s = Self::new( 65 | &variant.fields, 66 | hattrs_enum.regex_infer || hattrs_variant.regex_infer, 67 | &hattrs_enum.crate_path, 68 | )?; 69 | let vb = VarBase::Variant { 70 | variant, 71 | style: DisplayStyle::from_helper_attributes(hattrs_enum, hattrs_variant), 72 | }; 73 | s.new_expr.clone_from(&hattrs_variant.new_expr); 74 | s.apply_attrs(hattrs_enum)?; 75 | s.apply_attrs(hattrs_variant)?; 76 | if !s.try_push_attrs(hattrs_variant, &vb)? { 77 | s.push_attrs(hattrs_enum, &vb)?; 78 | } 79 | Ok(s) 80 | } 81 | fn apply_attrs(&mut self, hattrs: &HelperAttributes) -> Result<()> { 82 | if hattrs.default_self.is_some() { 83 | self.use_default = true; 84 | } 85 | for field in &hattrs.default_fields { 86 | let key = FieldKey::from_member(&field.0); 87 | let span = field.span(); 88 | self.field(&key, span)?.use_default = true; 89 | } 90 | if let Some(span) = hattrs.span_of_from_str_format() { 91 | self.span = span; 92 | } 93 | Ok(()) 94 | } 95 | fn field(&mut self, key: &FieldKey, span: Span) -> Result<&mut FieldEntry<'a>> { 96 | field_of(&mut self.fields, key, span) 97 | } 98 | fn set_capture(&mut self, vb: &VarBase, keys: &[FieldKey], span: Span) -> Result { 99 | let field_key; 100 | let sub_keys; 101 | if let VarBase::Field { key, .. } = vb { 102 | field_key = *key; 103 | sub_keys = keys; 104 | } else { 105 | if keys.is_empty() { 106 | return Ok(CAPTURE_NAME_EMPTY.into()); 107 | } 108 | field_key = &keys[0]; 109 | sub_keys = &keys[1..]; 110 | } 111 | let field = field_of(&mut self.fields, field_key, span)?; 112 | Ok(field.set_capture(sub_keys, &mut self.capture_next)) 113 | } 114 | 115 | fn push_regex( 116 | &mut self, 117 | s: &LitStr, 118 | vb: &VarBase, 119 | format: &Option, 120 | ) -> Result<()> { 121 | const IDX_ESC: usize = 1; 122 | const IDX_P: usize = 2; 123 | const IDX_KEY: usize = 3; 124 | fn is_escaped(s: &str) -> bool { 125 | s.len() % 2 == 1 126 | } 127 | 128 | let regex_number = regex!("^[0-9]+$"); 129 | let regex_capture = regex!(r"(?\\*)\(\?(?

P?)<(?[_0-9a-zA-Z.]*)>"); 130 | 131 | let text = s.value(); 132 | let text_debug = regex_capture.replace_all(&text, |c: &Captures| { 133 | let esc = &c[IDX_ESC]; 134 | if is_escaped(esc) { 135 | return c[0].to_owned(); 136 | } 137 | let key = &c[IDX_KEY]; 138 | let key = if key.is_empty() { 139 | "self".into() 140 | } else { 141 | key.replace('.', "_") 142 | }; 143 | let key = regex_number.replace(&key, "_$0"); 144 | format!("{esc}(?<{key}>") 145 | }); 146 | if let Err(e) = regex_syntax::ast::parse::Parser::new().parse(&text_debug) { 147 | bail!(s.span(), "{e}") 148 | } 149 | 150 | let mut has_capture = false; 151 | let mut has_capture_empty = false; 152 | let mut p = ""; 153 | let mut text = try_replace_all(regex_capture, &text, |c: &Captures| -> Result { 154 | let esc = &c[IDX_ESC]; 155 | if is_escaped(esc) { 156 | return Ok(c[0].to_owned()); 157 | } 158 | has_capture = true; 159 | let cp = &c[IDX_P]; 160 | let keys = FieldKey::from_str_deep(&c[IDX_KEY]); 161 | let name = self.set_capture(vb, &keys, s.span())?; 162 | if name == CAPTURE_NAME_EMPTY { 163 | if !cp.is_empty() { 164 | p = "P"; 165 | } 166 | has_capture_empty = true; 167 | } 168 | Ok(format!("{esc}(?<{name}>")) 169 | })?; 170 | 171 | if has_capture_empty { 172 | if let VarBase::Variant { variant, style, .. } = vb { 173 | let value = style.apply(&variant.ident); 174 | self.parse_format 175 | .push_hir(to_hir_with_expand(&text, CAPTURE_NAME_EMPTY, &value)); 176 | return Ok(()); 177 | } 178 | bail!( 179 | s.span(), 180 | "`(?{p}<>)` (empty capture name) is not allowed in struct's regex." 181 | ); 182 | } 183 | if let VarBase::Field { .. } = vb { 184 | if !has_capture { 185 | if let Some(format) = format { 186 | return self.push_format(format, vb, None, Some(&text)); 187 | } 188 | let name = self.set_capture(vb, &[], s.span())?; 189 | text = format!("(?<{name}>{text})"); 190 | } 191 | } 192 | self.parse_format.push_hir(to_hir(&text)); 193 | Ok(()) 194 | } 195 | fn push_format( 196 | &mut self, 197 | format: &DisplayFormat, 198 | vb: &VarBase, 199 | with: Option<&Expr>, 200 | regex: Option<&str>, 201 | ) -> Result<()> { 202 | for p in &format.parts { 203 | match p { 204 | DisplayFormatPart::Str(s) => self.push_str(s), 205 | DisplayFormatPart::EscapedBeginBracket => self.push_str("{"), 206 | DisplayFormatPart::EscapedEndBracket => self.push_str("}"), 207 | DisplayFormatPart::Var { arg, .. } => { 208 | let keys = FieldKey::from_str_deep(arg); 209 | if let VarBase::Variant { variant, style, .. } = vb { 210 | if keys.is_empty() { 211 | self.push_str(&style.apply(&variant.ident)); 212 | continue; 213 | } 214 | } 215 | if keys.len() == 1 { 216 | self.push_field(vb, &keys[0], format.span)?; 217 | continue; 218 | } 219 | let c = self.set_capture(vb, &keys, format.span)?; 220 | let mut f = format!("(?<{c}>(?s:.*?))"); 221 | if keys.is_empty() { 222 | if let Some(regex) = regex { 223 | f = format!("(?<{c}>(?s:{regex}))"); 224 | } 225 | if let Some(with_expr) = with { 226 | match vb { 227 | VarBase::Struct { .. } => {} 228 | VarBase::Variant { .. } => {} 229 | VarBase::Field { field, key, .. } => { 230 | self.with.push(With::new(c, key, with_expr, &field.ty)); 231 | } 232 | VarBase::FieldSome { key, ty } => { 233 | self.with.push(With::new(c, key, with_expr, ty)); 234 | } 235 | } 236 | } 237 | } 238 | self.parse_format.push_hir(to_hir(&f)); 239 | } 240 | } 241 | } 242 | Ok(()) 243 | } 244 | fn push_str(&mut self, string: &str) { 245 | self.parse_format.push_str(string); 246 | } 247 | fn push_field(&mut self, vb: &VarBase, key: &FieldKey, span: Span) -> Result<()> { 248 | let e = self.field(key, span)?; 249 | let hattrs = e.hattrs.clone(); 250 | let parent = vb; 251 | let field = e.source; 252 | if e.hattrs.opt.value() { 253 | let mut hirs = mem::take(&mut self.parse_format).into_hirs(); 254 | self.push_attrs(&hattrs, &VarBase::Field { parent, key, field })?; 255 | let hirs_child = mem::take(&mut self.parse_format).into_hirs(); 256 | let hir = Hir::repetition(Repetition { 257 | min: 0, 258 | max: Some(1), 259 | greedy: false, 260 | sub: Box::new(Hir::concat(hirs_child)), 261 | }); 262 | hirs.push(hir); 263 | self.parse_format = ParseFormat::Hirs(hirs); 264 | Ok(()) 265 | } else { 266 | self.push_attrs(&hattrs, &VarBase::Field { parent, key, field }) 267 | } 268 | } 269 | fn push_attrs(&mut self, hattrs: &HelperAttributes, vb: &VarBase) -> Result<()> { 270 | if !self.try_push_attrs(hattrs, vb)? { 271 | self.push_format( 272 | &vb.default_from_str_format()?, 273 | vb, 274 | hattrs.with.as_ref(), 275 | None, 276 | )?; 277 | } 278 | Ok(()) 279 | } 280 | fn try_push_attrs(&mut self, hattrs: &HelperAttributes, vb: &VarBase) -> Result { 281 | Ok(if let Some(regex) = &hattrs.regex { 282 | self.push_regex(regex, vb, &hattrs.format)?; 283 | true 284 | } else if let Some(format) = &hattrs.format { 285 | self.push_format(format, vb, hattrs.with.as_ref(), None)?; 286 | true 287 | } else { 288 | false 289 | }) 290 | } 291 | 292 | pub fn build_from_str_body(&self, constructor: Path) -> Result { 293 | let code = self.build_parse_code(constructor)?; 294 | let crate_path = self.crate_path; 295 | Ok(quote! { 296 | #code 297 | ::core::result::Result::Err(#crate_path::ParseError::new()) 298 | }) 299 | } 300 | pub fn build_from_str_regex_body(&self) -> Result { 301 | match &self.parse_format { 302 | ParseFormat::Hirs(hirs) => { 303 | let expr = self.build_parser_init(hirs)?.expr; 304 | Ok(quote! { (#expr).re_str }) 305 | } 306 | ParseFormat::String(s) => { 307 | let s = escape(s); 308 | Ok(quote! { #s.into() }) 309 | } 310 | } 311 | } 312 | 313 | pub fn build_parse_variant_code(&self, constructor: Path) -> Result { 314 | match &self.parse_format { 315 | ParseFormat::Hirs(_) => { 316 | let fn_ident: Ident = format_ident!("parse_variant"); 317 | let crate_path = self.crate_path; 318 | let code = self.build_from_str_body(constructor)?; 319 | let code = quote! { 320 | let #fn_ident = |s: &str| -> ::core::result::Result { 321 | #code 322 | }; 323 | if let ::core::result::Result::Ok(value) = #fn_ident(s) { 324 | return ::core::result::Result::Ok(value); 325 | } 326 | }; 327 | Ok(ParseVariantCode::Statement(code)) 328 | } 329 | ParseFormat::String(s) => { 330 | let code = self.build_construct_code(constructor)?; 331 | let code = quote! { #s => { #code }}; 332 | Ok(ParseVariantCode::MatchArm(code)) 333 | } 334 | } 335 | } 336 | 337 | fn build_construct_code(&self, constructor: Path) -> Result { 338 | let mut names = HashMap::new(); 339 | let re; 340 | match &self.parse_format { 341 | ParseFormat::Hirs(hirs) => { 342 | re = Regex::new(&to_regex_string(hirs)).unwrap(); 343 | for (index, name) in re.capture_names().enumerate() { 344 | if let Some(name) = name { 345 | names.insert(name, index); 346 | } 347 | } 348 | } 349 | ParseFormat::String(_) => {} 350 | } 351 | 352 | let code = if let Some(new_expr) = &self.new_expr { 353 | let mut code = TokenStream::new(); 354 | for (key, field) in &self.fields { 355 | let expr = field.build_field_init_expr(&names, key, self.span)?; 356 | let var = key.new_arg_var(); 357 | code.extend(quote! { let #var = #expr; }); 358 | } 359 | let crate_path = self.crate_path; 360 | code.extend(quote! { 361 | if let ::core::result::Result::Ok(value) = #crate_path::IntoResult::into_result(#new_expr) { 362 | return ::core::result::Result::Ok(value); 363 | } 364 | }); 365 | code 366 | } else if self.use_default { 367 | let mut setters = Vec::new(); 368 | for (key, field) in &self.fields { 369 | let left_expr = quote! { value . #key }; 370 | setters.push(field.build_setters(&names, key, left_expr, true)?); 371 | } 372 | quote! { 373 | let mut value = ::default(); 374 | #(#setters)* 375 | return ::core::result::Result::Ok(value); 376 | } 377 | } else { 378 | let ps = match &self.source { 379 | Fields::Named(..) => { 380 | let mut fields_code = Vec::new(); 381 | for (key, field) in &self.fields { 382 | let expr = field.build_field_init_expr(&names, key, self.span)?; 383 | fields_code.push(quote! { #key : #expr }); 384 | } 385 | quote! { { #(#fields_code,)* } } 386 | } 387 | Fields::Unnamed(..) => { 388 | let mut fields_code = Vec::new(); 389 | for (key, field) in &self.fields { 390 | fields_code.push(field.build_field_init_expr(&names, key, self.span)?); 391 | } 392 | quote! { ( #(#fields_code,)* ) } 393 | } 394 | Fields::Unit => quote! {}, 395 | }; 396 | quote! { return ::core::result::Result::Ok(#constructor #ps); } 397 | }; 398 | Ok(code) 399 | } 400 | fn build_parser_init(&self, hirs: &[Hir]) -> Result { 401 | let regex = to_regex_string(hirs); 402 | let crate_path = self.crate_path; 403 | let mut with = Vec::new(); 404 | let helpers = quote!( #crate_path::helpers ); 405 | let mut debug_asserts = Vec::new(); 406 | for ( 407 | index, 408 | With { 409 | capture, 410 | key, 411 | ty, 412 | expr, 413 | }, 414 | ) in self.with.iter().enumerate() 415 | { 416 | with.push(quote_spanned! {expr.span()=> 417 | (#capture, #helpers::to_ast::<#ty, _>(&#expr)) 418 | }); 419 | let msg = 420 | format!("The regex for the field `{key}` varies depending on the type parameter."); 421 | debug_asserts.push(quote_spanned! {expr.span()=> 422 | ::core::debug_assert_eq!(&p.ss[#index], &#helpers::to_regex::<#ty, _>(&#expr), #msg); 423 | }); 424 | } 425 | Ok(ParserInit { 426 | expr: quote!(#helpers::Parser::new(#regex, &mut [#(#with,)*])), 427 | debug_asserts, 428 | }) 429 | } 430 | fn build_parse_code(&self, constructor: Path) -> Result { 431 | let code = self.build_construct_code(constructor)?; 432 | Ok(match &self.parse_format { 433 | ParseFormat::Hirs(hirs) => { 434 | let ParserInit { 435 | expr, 436 | debug_asserts, 437 | } = self.build_parser_init(&hirs_with_start_end(hirs))?; 438 | let crate_path = self.crate_path; 439 | quote! { 440 | static PARSER: ::std::sync::OnceLock<#crate_path::helpers::Parser> = ::std::sync::OnceLock::new(); 441 | #[allow(clippy::trivial_regex)] 442 | let p = PARSER.get_or_init(|| #expr); 443 | #(#debug_asserts)* 444 | if let ::core::option::Option::Some(c) = p.re.captures(&s) { 445 | #code 446 | } 447 | } 448 | } 449 | ParseFormat::String(s) => quote! { 450 | if s == #s { 451 | #code 452 | } 453 | }, 454 | }) 455 | } 456 | pub fn build_regex_fmts_args( 457 | &self, 458 | fmts: &mut Vec>, 459 | args: &mut Vec, 460 | ) -> Result<()> { 461 | match &self.parse_format { 462 | ParseFormat::Hirs(hirs) => { 463 | fmts.push(None); 464 | let expr = self.build_parser_init(hirs)?.expr; 465 | args.push(quote!((#expr).re_str)); 466 | } 467 | ParseFormat::String(s) => { 468 | fmts.push(Some(s.clone())); 469 | } 470 | } 471 | Ok(()) 472 | } 473 | 474 | pub fn build_bounds(&self, generics: &GenericParamSet, bounds: &mut Bounds) -> Result<()> { 475 | if !bounds.can_extend { 476 | return Ok(()); 477 | } 478 | for field in self.fields.values() { 479 | let mut bounds = bounds.child(field.hattrs.bound_from_str_resolved()); 480 | if bounds.can_extend && field.capture.is_some() && field.hattrs.with.is_none() { 481 | let mut ty = &field.source.ty; 482 | if field.hattrs.opt.value() { 483 | if let Some(opt_ty) = get_option_element(ty) { 484 | ty = opt_ty; 485 | } else { 486 | let key = FieldKey::from_field(field.source); 487 | bail!(ty.span(), "field `{key}` is not a option type."); 488 | } 489 | } 490 | if generics.contains_in_type(ty) { 491 | bounds.ty.push(ty.clone()); 492 | } 493 | } 494 | } 495 | Ok(()) 496 | } 497 | } 498 | impl<'a> FieldEntry<'a> { 499 | fn new(source: &'a Field, regex_infer: bool, crate_path: &'a Path) -> Result { 500 | let mut hattrs = HelperAttributes::from(&source.attrs, true)?; 501 | if (regex_infer || hattrs.regex_infer) && hattrs.with.is_none() { 502 | hattrs.with = Some(parse_quote!(#crate_path::helpers::RegexInfer)); 503 | }; 504 | let use_default = hattrs.default_self.is_some(); 505 | Ok(Self { 506 | hattrs, 507 | deep_captures: BTreeMap::new(), 508 | capture: None, 509 | use_default, 510 | source, 511 | crate_path, 512 | }) 513 | } 514 | #[allow(clippy::collapsible_else_if)] 515 | fn set_capture(&mut self, keys: &[FieldKey], capture_next: &mut usize) -> String { 516 | let idx = if keys.is_empty() { 517 | if let Some(idx) = self.capture { 518 | idx 519 | } else { 520 | let idx = *capture_next; 521 | self.capture = Some(idx); 522 | *capture_next += 1; 523 | idx 524 | } 525 | } else { 526 | if let Some(&idx) = self.deep_captures.get(keys) { 527 | idx 528 | } else { 529 | let idx = *capture_next; 530 | self.deep_captures.insert(keys.to_vec(), idx); 531 | *capture_next += 1; 532 | idx 533 | } 534 | }; 535 | capture_name(idx) 536 | } 537 | fn capture_index(&self, names: &HashMap<&str, usize>) -> Option { 538 | Some(capture_index(self.capture?, names)) 539 | } 540 | fn build_expr( 541 | &self, 542 | names: &HashMap<&str, usize>, 543 | key: &FieldKey, 544 | ) -> Result> { 545 | if let Some(capture_index) = self.capture_index(names) { 546 | Ok(Some(build_parse_capture_expr( 547 | &key.to_string(), 548 | capture_index, 549 | Some(self), 550 | self.crate_path, 551 | )?)) 552 | } else if self.use_default { 553 | Ok(Some(quote! { ::core::default::Default::default() })) 554 | } else { 555 | Ok(None) 556 | } 557 | } 558 | fn build_setters( 559 | &self, 560 | names: &HashMap<&str, usize>, 561 | key: &FieldKey, 562 | left_expr: TokenStream, 563 | include_self: bool, 564 | ) -> Result { 565 | let mut setters = Vec::new(); 566 | if include_self { 567 | if let Some(expr) = self.build_expr(names, key)? { 568 | setters.push(quote! { #left_expr = #expr; }); 569 | } 570 | } 571 | for (keys, idx) in &self.deep_captures { 572 | let field_name = key.to_string() + &join(keys, "."); 573 | let expr = build_parse_capture_expr( 574 | &field_name, 575 | capture_index(*idx, names), 576 | None, 577 | self.crate_path, 578 | )?; 579 | setters.push(quote! { #left_expr #(.#keys)* = #expr; }); 580 | } 581 | Ok(quote! { #(#setters)* }) 582 | } 583 | 584 | fn build_field_init_expr( 585 | &self, 586 | names: &HashMap<&str, usize>, 587 | key: &FieldKey, 588 | span: Span, 589 | ) -> Result { 590 | if let Some(mut expr) = self.build_expr(names, key)? { 591 | if !self.deep_captures.is_empty() { 592 | let setters = self.build_setters(names, key, quote!(field_value), false)?; 593 | let ty = &self.source.ty; 594 | expr = quote! { 595 | { 596 | let mut field_value : #ty = #expr; 597 | #setters 598 | field_value 599 | } 600 | }; 601 | } 602 | return Ok(expr); 603 | } 604 | bail!(span, "field `{key}` is not appear in format."); 605 | } 606 | } 607 | 608 | pub enum ParseVariantCode { 609 | MatchArm(TokenStream), 610 | Statement(TokenStream), 611 | } 612 | 613 | enum ParseFormat { 614 | Hirs(Vec), 615 | String(String), 616 | } 617 | impl ParseFormat { 618 | fn new() -> Self { 619 | Self::String(String::new()) 620 | } 621 | fn as_hirs(&mut self) -> &mut Vec { 622 | match self { 623 | Self::Hirs(_) => {} 624 | Self::String(s) => { 625 | let mut hirs = vec![]; 626 | push_str(&mut hirs, s); 627 | std::mem::swap(self, &mut Self::Hirs(hirs)); 628 | } 629 | } 630 | if let Self::Hirs(hirs) = self { 631 | hirs 632 | } else { 633 | unreachable!() 634 | } 635 | } 636 | fn into_hirs(mut self) -> Vec { 637 | self.as_hirs(); 638 | match self { 639 | Self::Hirs(hirs) => hirs, 640 | Self::String(_) => unreachable!(), 641 | } 642 | } 643 | 644 | fn push_str(&mut self, string: &str) { 645 | match self { 646 | Self::Hirs(hirs) => push_str(hirs, string), 647 | Self::String(s) => s.push_str(string), 648 | } 649 | } 650 | fn push_hir(&mut self, hir: Hir) { 651 | self.as_hirs().push(hir); 652 | } 653 | } 654 | impl Default for ParseFormat { 655 | fn default() -> Self { 656 | Self::new() 657 | } 658 | } 659 | 660 | struct ParserInit { 661 | expr: TokenStream, 662 | debug_asserts: Vec, 663 | } 664 | 665 | fn build_parse_capture_expr( 666 | field_name: &str, 667 | capture_index: usize, 668 | field: Option<&FieldEntry>, 669 | crate_path: &Path, 670 | ) -> Result { 671 | let msg = format!("field `{field_name}` parse failed."); 672 | let e = if let Some(field) = field { 673 | if field.hattrs.opt.value() { 674 | let e = str_expr_to_parse_capture_expr(quote!(s), field, crate_path); 675 | quote! { 676 | c.get(#capture_index).map(|m| m.as_str()).map(|s| #e).transpose() 677 | } 678 | } else { 679 | str_expr_to_parse_capture_expr( 680 | quote!(c.get(#capture_index).map_or("", |m| m.as_str())), 681 | field, 682 | crate_path, 683 | ) 684 | } 685 | } else { 686 | quote!(c.get(#capture_index).map_or("", |m| m.as_str()).parse()) 687 | }; 688 | Ok(quote! { 689 | #e.map_err(|e| #crate_path::ParseError::with_message(#msg))? 690 | }) 691 | } 692 | fn str_expr_to_parse_capture_expr( 693 | str_expr: TokenStream, 694 | field: &FieldEntry, 695 | crate_path: &Path, 696 | ) -> TokenStream { 697 | if let Some(with) = &field.hattrs.with { 698 | let ty = &field.source.ty; 699 | let expr = quote! { 700 | #crate_path::helpers::parse_with::<#ty, _>(#with, #str_expr) 701 | }; 702 | set_span(expr, with.span()) 703 | } else { 704 | quote!(#str_expr.parse()) 705 | } 706 | } 707 | 708 | const CAPTURE_NAME_EMPTY: &str = "empty"; 709 | fn capture_name(idx: usize) -> String { 710 | format!("value_{idx}") 711 | } 712 | fn capture_index(idx: usize, names: &HashMap<&str, usize>) -> usize { 713 | names[capture_name(idx).as_str()] 714 | } 715 | 716 | struct FieldEntry<'a> { 717 | hattrs: HelperAttributes, 718 | deep_captures: BTreeMap, usize>, 719 | source: &'a Field, 720 | capture: Option, 721 | use_default: bool, 722 | crate_path: &'a Path, 723 | } 724 | 725 | fn field_of<'a, 'b>( 726 | fields: &'a mut BTreeMap>, 727 | key: &FieldKey, 728 | span: Span, 729 | ) -> Result<&'a mut FieldEntry<'b>> { 730 | if let Some(f) = fields.get_mut(key) { 731 | Ok(f) 732 | } else { 733 | bail!(span, "field `{key}` not found."); 734 | } 735 | } 736 | -------------------------------------------------------------------------------- /parse-display-derive/src/regex_utils.rs: -------------------------------------------------------------------------------- 1 | use regex::{Captures, Regex}; 2 | use regex_syntax::ast::Ast; 3 | use regex_syntax::hir::Hir; 4 | 5 | pub fn to_hir(s: &str) -> Hir { 6 | regex_syntax::Parser::new().parse(s).unwrap() 7 | } 8 | pub fn to_hir_with_expand(s: &str, name: &str, value: &str) -> Hir { 9 | let mut ast = to_ast(s); 10 | expand_capture(&mut ast, |group_name| { 11 | if group_name == name { 12 | Some(to_ast(®ex_syntax::escape(value))) 13 | } else { 14 | None 15 | } 16 | }); 17 | let s = format!("{ast}"); 18 | 19 | regex_syntax::hir::translate::Translator::new() 20 | .translate(&s, &ast) 21 | .unwrap() 22 | } 23 | 24 | fn to_ast(s: &str) -> Ast { 25 | regex_syntax::ast::parse::Parser::new().parse(s).unwrap() 26 | } 27 | 28 | pub fn push_str(hirs: &mut Vec, s: &str) { 29 | hirs.push(Hir::literal(s.as_bytes())); 30 | } 31 | pub fn hirs_with_start_end(hirs: &[Hir]) -> Vec { 32 | let mut hs = vec![Hir::look(regex_syntax::hir::Look::Start)]; 33 | hs.extend(hirs.iter().cloned()); 34 | hs.push(Hir::look(regex_syntax::hir::Look::End)); 35 | hs 36 | } 37 | pub fn to_regex_string(hirs: &[Hir]) -> String { 38 | Hir::concat(hirs.to_vec()).to_string() 39 | } 40 | 41 | fn replace_asts(asts: &mut Vec, f: &mut impl FnMut(&mut Ast) -> bool) { 42 | for ast in asts { 43 | replace_ast(ast, f); 44 | } 45 | } 46 | 47 | fn replace_ast(ast: &mut Ast, f: &mut impl FnMut(&mut Ast) -> bool) { 48 | if !f(ast) { 49 | return; 50 | } 51 | match ast { 52 | Ast::Empty(..) 53 | | Ast::Flags(..) 54 | | Ast::Literal(..) 55 | | Ast::Dot(..) 56 | | Ast::Assertion(..) 57 | | Ast::ClassUnicode(..) 58 | | Ast::ClassPerl(..) 59 | | Ast::ClassBracketed(..) => {} 60 | Ast::Repetition(rep) => replace_ast(&mut rep.ast, f), 61 | Ast::Group(g) => replace_ast(&mut g.ast, f), 62 | Ast::Alternation(alt) => replace_asts(&mut alt.asts, f), 63 | Ast::Concat(c) => replace_asts(&mut c.asts, f), 64 | } 65 | } 66 | 67 | fn expand_capture(ast: &mut Ast, mut f: impl FnMut(&str) -> Option) { 68 | let f = &mut f; 69 | replace_ast(ast, &mut |ast| { 70 | use regex_syntax::ast::GroupKind; 71 | if let Ast::Group(g) = &ast { 72 | if let GroupKind::CaptureName { name, .. } = &g.kind { 73 | if let Some(ast_new) = f(&name.name) { 74 | *ast = ast_new; 75 | return false; 76 | } 77 | } 78 | } 79 | true 80 | }); 81 | } 82 | pub fn try_replace_all, E>( 83 | regex: &Regex, 84 | text: &str, 85 | mut replacer: impl FnMut(&Captures) -> Result, 86 | ) -> Result { 87 | let mut s = String::new(); 88 | let mut last_end = 0; 89 | for c in regex.captures_iter(text) { 90 | let m = c.get(0).unwrap(); 91 | s.push_str(&text[last_end..m.start()]); 92 | s.push_str(replacer(&c)?.as_ref()); 93 | last_end = m.end(); 94 | } 95 | s.push_str(&text[last_end..]); 96 | Ok(s) 97 | } 98 | 99 | macro_rules! regex { 100 | ($s:expr) => {{ 101 | static RE: ::std::sync::LazyLock = 102 | ::std::sync::LazyLock::new(|| ::regex::Regex::new($s).unwrap()); 103 | &RE 104 | }}; 105 | } 106 | -------------------------------------------------------------------------------- /parse-display-derive/src/syn_utils.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, quote_spanned, ToTokens}; 3 | use std::collections::HashSet; 4 | use syn::{ 5 | ext::IdentExt, 6 | parse::{discouraged::Speculative, Parse, ParseStream}, 7 | parse2, parse_str, 8 | punctuated::Punctuated, 9 | visit::{visit_path, Visit}, 10 | DeriveInput, GenericParam, Generics, Ident, LitStr, Path, PathArguments, PathSegment, Result, 11 | Token, Type, WherePredicate, 12 | }; 13 | 14 | macro_rules! bail { 15 | (_, $($arg:tt)*) => { 16 | bail!(::proc_macro2::Span::call_site(), $($arg)*) 17 | }; 18 | ($span:expr, $fmt:literal $(,)?) => { 19 | return ::std::result::Result::Err(::syn::Error::new($span, ::std::format!($fmt))) 20 | }; 21 | ($span:expr, $fmt:literal, $($arg:tt)*) => { 22 | return ::std::result::Result::Err(::syn::Error::new($span, ::std::format!($fmt, $($arg)*))) 23 | }; 24 | } 25 | 26 | pub fn into_macro_output(input: Result) -> proc_macro::TokenStream { 27 | match input { 28 | Ok(s) => s, 29 | Err(e) => e.to_compile_error(), 30 | } 31 | .into() 32 | } 33 | 34 | pub struct GenericParamSet { 35 | idents: HashSet, 36 | } 37 | 38 | impl GenericParamSet { 39 | pub fn new(generics: &Generics) -> Self { 40 | let mut idents = HashSet::new(); 41 | for p in &generics.params { 42 | match p { 43 | GenericParam::Type(t) => { 44 | idents.insert(t.ident.unraw()); 45 | } 46 | GenericParam::Const(t) => { 47 | idents.insert(t.ident.unraw()); 48 | } 49 | GenericParam::Lifetime(_) => {} 50 | } 51 | } 52 | Self { idents } 53 | } 54 | fn contains(&self, ident: &Ident) -> bool { 55 | self.idents.contains(&ident.unraw()) 56 | } 57 | 58 | pub fn contains_in_type(&self, ty: &Type) -> bool { 59 | struct Visitor<'a> { 60 | generics: &'a GenericParamSet, 61 | result: bool, 62 | } 63 | impl<'ast> Visit<'ast> for Visitor<'_> { 64 | fn visit_path(&mut self, i: &'ast syn::Path) { 65 | if i.leading_colon.is_none() { 66 | if let Some(s) = i.segments.iter().next() { 67 | if self.generics.contains(&s.ident) { 68 | self.result = true; 69 | } 70 | } 71 | } 72 | visit_path(self, i); 73 | } 74 | } 75 | let mut visitor = Visitor { 76 | generics: self, 77 | result: false, 78 | }; 79 | visitor.visit_type(ty); 80 | visitor.result 81 | } 82 | } 83 | 84 | pub enum Quotable { 85 | Direct(T), 86 | Quoted { s: LitStr, args: ArgsOf }, 87 | } 88 | impl Parse for Quotable { 89 | fn parse(input: ParseStream) -> Result { 90 | let fork = input.fork(); 91 | if let Ok(s) = fork.parse::() { 92 | input.advance_to(&fork); 93 | let token: TokenStream = parse_str(&s.value())?; 94 | let tokens = quote_spanned!(s.span()=> #token); 95 | let args = parse2(tokens)?; 96 | Ok(Quotable::Quoted { s, args }) 97 | } else { 98 | Ok(Quotable::Direct(input.parse()?)) 99 | } 100 | } 101 | } 102 | impl ToTokens for Quotable { 103 | fn to_tokens(&self, tokens: &mut TokenStream) { 104 | match self { 105 | Self::Direct(value) => value.to_tokens(tokens), 106 | Self::Quoted { s, .. } => s.to_tokens(tokens), 107 | } 108 | } 109 | } 110 | 111 | impl Quotable { 112 | pub fn into_iter(self) -> impl IntoIterator { 113 | match self { 114 | Self::Direct(item) => vec![item], 115 | Self::Quoted { args, .. } => args.into_iter().collect(), 116 | } 117 | .into_iter() 118 | } 119 | } 120 | 121 | pub struct ArgsOf(Punctuated); 122 | 123 | impl Parse for ArgsOf { 124 | fn parse(input: ParseStream) -> Result { 125 | Ok(Self(Punctuated::parse_terminated(input)?)) 126 | } 127 | } 128 | impl ToTokens for ArgsOf { 129 | fn to_tokens(&self, tokens: &mut TokenStream) { 130 | self.0.to_tokens(tokens); 131 | } 132 | } 133 | 134 | impl ArgsOf { 135 | pub fn into_iter(self) -> impl Iterator { 136 | self.0.into_iter() 137 | } 138 | } 139 | 140 | pub fn impl_trait( 141 | input: &DeriveInput, 142 | trait_path: &Path, 143 | wheres: &[WherePredicate], 144 | contents: TokenStream, 145 | ) -> TokenStream { 146 | let ty = &input.ident; 147 | let (impl_g, ty_g, where_clause) = input.generics.split_for_impl(); 148 | let mut wheres = wheres.to_vec(); 149 | if let Some(where_clause) = where_clause { 150 | wheres.extend(where_clause.predicates.iter().cloned()); 151 | } 152 | let where_clause = if wheres.is_empty() { 153 | quote! {} 154 | } else { 155 | quote! { where #(#wheres,)*} 156 | }; 157 | quote! { 158 | #[automatically_derived] 159 | impl #impl_g #trait_path for #ty #ty_g #where_clause { 160 | #contents 161 | } 162 | } 163 | } 164 | pub fn impl_trait_result( 165 | input: &DeriveInput, 166 | trait_path: &Path, 167 | wheres: &[WherePredicate], 168 | contents: TokenStream, 169 | dump: bool, 170 | ) -> Result { 171 | let ts = impl_trait(input, trait_path, wheres, contents); 172 | dump_if(dump, &ts); 173 | Ok(ts) 174 | } 175 | pub fn dump_if(dump: bool, ts: &TokenStream) { 176 | if dump { 177 | panic!("macro output:\n{ts}"); 178 | } 179 | } 180 | 181 | pub fn get_arguments_of<'a>(ty: &'a Type, ns: &[&[&str]], name: &str) -> Option<&'a PathArguments> { 182 | if let Type::Path(ty) = ty { 183 | if ty.qself.is_some() { 184 | return None; 185 | } 186 | let ss = &ty.path.segments; 187 | if let Some(last) = ty.path.segments.last() { 188 | if last.ident != name { 189 | return None; 190 | } 191 | return if ns.iter().any(|ns| is_match_ns(ss, ns)) { 192 | Some(&last.arguments) 193 | } else { 194 | None 195 | }; 196 | } 197 | } 198 | None 199 | } 200 | pub fn is_match_ns(ss: &Punctuated, ns: &[&str]) -> bool { 201 | let mut i_ss = ss.len() - 1; 202 | let mut i_ns = ns.len(); 203 | while i_ss > 0 && i_ns > 0 { 204 | i_ns -= 1; 205 | i_ss -= 1; 206 | let s = &ss[i_ss]; 207 | if s.ident != ns[i_ns] || !s.arguments.is_empty() { 208 | return false; 209 | } 210 | } 211 | i_ss == 0 212 | } 213 | -------------------------------------------------------------------------------- /parse-display-with/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parse-display-with" 3 | version = "0.0.2" 4 | edition = "2024" 5 | authors = ["frozenlib"] 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/frozenlib/parse-display" 9 | documentation = "https://docs.rs/parse-display-with/" 10 | keywords = ["parse-display"] 11 | categories = ["parsing"] 12 | description = "Custom formatting/parsing utilities for parse-display." 13 | rust-version = "1.85.0" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [features] 18 | default = ["std"] 19 | std = ["parse-display/std"] 20 | 21 | [dependencies] 22 | parse-display = { version = "0.10.0", path = "../parse-display", default-features = false } 23 | -------------------------------------------------------------------------------- /parse-display-with/README.md: -------------------------------------------------------------------------------- 1 | # parse-display-with 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/parse-display-with.svg)](https://crates.io/crates/parse-display-with) 4 | [![Docs.rs](https://docs.rs/parse-display-with/badge.svg)](https://docs.rs/parse-display-with/) 5 | [![Actions Status](https://github.com/frozenlib/parse-display/workflows/CI/badge.svg)](https://github.com/frozenlib/parse-display/actions) 6 | 7 | Custom formatting/parsing utilities for [parse-display](https://crates.io/crates/parse-display). 8 | 9 | ## License 10 | 11 | This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-\* files for details. 12 | 13 | ## Contribution 14 | 15 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 16 | -------------------------------------------------------------------------------- /parse-display-with/src/formats.rs: -------------------------------------------------------------------------------- 1 | use core::{ 2 | fmt::{self, Display, Formatter}, 3 | marker::PhantomData, 4 | result::Result, 5 | str::FromStr, 6 | }; 7 | 8 | use parse_display::{DisplayFormat, FromStrFormat}; 9 | 10 | pub struct Fmt; 11 | 12 | impl DisplayFormat for Fmt { 13 | fn write(&self, f: &mut Formatter, value: &T) -> fmt::Result { 14 | write!(f, "{value}") 15 | } 16 | } 17 | impl FromStrFormat for Fmt { 18 | type Err = T::Err; 19 | fn parse(&self, s: &str) -> Result { 20 | s.parse() 21 | } 22 | } 23 | 24 | pub fn fmt() -> Fmt { 25 | Fmt 26 | } 27 | 28 | pub fn fmt_display( 29 | f: impl Fn(&mut Formatter, &T) -> fmt::Result, 30 | ) -> impl DisplayFormat { 31 | struct FnFmtDisplay(F); 32 | impl DisplayFormat for FnFmtDisplay 33 | where 34 | T: ?Sized, 35 | F: Fn(&mut Formatter, &T) -> fmt::Result, 36 | { 37 | fn write(&self, f: &mut Formatter, t: &T) -> fmt::Result { 38 | (self.0)(f, t) 39 | } 40 | } 41 | FnFmtDisplay(f) 42 | } 43 | 44 | pub fn fmt_from_str(f: impl Fn(&str) -> Result) -> impl FromStrFormat { 45 | struct FnFmtFromStr(F); 46 | impl FromStrFormat for FnFmtFromStr 47 | where 48 | F: Fn(&str) -> Result, 49 | { 50 | type Err = E; 51 | fn parse(&self, s: &str) -> Result { 52 | (self.0)(s) 53 | } 54 | } 55 | FnFmtFromStr(f) 56 | } 57 | 58 | pub struct Join<'a, I: ?Sized, F = Fmt> { 59 | item_format: F, 60 | delimiter: &'a str, 61 | _phantom: PhantomData, 62 | } 63 | 64 | impl DisplayFormat for Join<'_, I, F> 65 | where 66 | T: ?Sized, 67 | for<'a> &'a T: IntoIterator, 68 | F: DisplayFormat, 69 | { 70 | fn write(&self, f: &mut Formatter, value: &T) -> fmt::Result { 71 | let mut iter = value.into_iter(); 72 | if let Some(first) = iter.next() { 73 | self.item_format.write(f, first)?; 74 | for item in iter { 75 | write!(f, "{}", self.delimiter)?; 76 | self.item_format.write(f, item)?; 77 | } 78 | } 79 | Ok(()) 80 | } 81 | } 82 | impl FromStrFormat for Join<'_, I, F> 83 | where 84 | F: FromStrFormat, 85 | T: FromIterator, 86 | { 87 | type Err = F::Err; 88 | fn parse(&self, s: &str) -> Result { 89 | s.split(self.delimiter) 90 | .map(|item| self.item_format.parse(item)) 91 | .collect() 92 | } 93 | } 94 | 95 | pub fn join(item_format: F, delimiter: &str) -> Join { 96 | Join { 97 | item_format, 98 | delimiter, 99 | _phantom: PhantomData, 100 | } 101 | } 102 | 103 | pub fn delimiter(delimiter: &str) -> Join { 104 | join(fmt(), delimiter) 105 | } 106 | -------------------------------------------------------------------------------- /parse-display-with/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | 3 | pub mod formats; 4 | 5 | #[cfg(doctest)] 6 | mod tests { 7 | mod readme_parse_display; 8 | mod readme_parse_display_with; 9 | } 10 | -------------------------------------------------------------------------------- /parse-display-with/src/tests/readme_parse_display.rs: -------------------------------------------------------------------------------- 1 | // #![include_doc("../../../README.md", start)] 2 | //! # parse-display 3 | //! 4 | //! [![Crates.io](https://img.shields.io/crates/v/parse-display.svg)](https://crates.io/crates/parse-display) 5 | //! [![Docs.rs](https://docs.rs/parse-display/badge.svg)](https://docs.rs/parse-display/) 6 | //! [![Actions Status](https://github.com/frozenlib/parse-display/workflows/CI/badge.svg)](https://github.com/frozenlib/parse-display/actions) 7 | //! 8 | //! This crate provides derive macro `Display` and `FromStr`. 9 | //! These macros use common helper attributes to specify the format. 10 | //! 11 | //! ## Install 12 | //! 13 | //! Add this to your Cargo.toml: 14 | //! 15 | //! ```toml 16 | //! [dependencies] 17 | //! parse-display = "0.10.0" 18 | //! ``` 19 | //! 20 | //! ## Documentation 21 | //! 22 | //! See [`#[derive(Display)]`](https://docs.rs/parse-display/latest/parse_display/derive.Display.html) documentation for details. 23 | //! 24 | //! ## Example 25 | //! 26 | //! ```rust 27 | //! use parse_display::{Display, FromStr}; 28 | //! 29 | //! #[derive(Display, FromStr, PartialEq, Debug)] 30 | //! #[display("{a}-{b}")] 31 | //! struct X { 32 | //! a: u32, 33 | //! b: u32, 34 | //! } 35 | //! assert_eq!(X { a:10, b:20 }.to_string(), "10-20"); 36 | //! assert_eq!("10-20".parse(), Ok(X { a:10, b:20 })); 37 | //! 38 | //! 39 | //! #[derive(Display, FromStr, PartialEq, Debug)] 40 | //! #[display(style = "snake_case")] 41 | //! enum Y { 42 | //! VarA, 43 | //! VarB, 44 | //! } 45 | //! assert_eq!(Y::VarA.to_string(), "var_a"); 46 | //! assert_eq!("var_a".parse(), Ok(Y::VarA)); 47 | //! ``` 48 | //! 49 | //! ## License 50 | //! 51 | //! This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-\* files for details. 52 | //! 53 | //! ## Contribution 54 | //! 55 | //! Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 56 | // #![include_doc("../../../README.md", end)] 57 | -------------------------------------------------------------------------------- /parse-display-with/src/tests/readme_parse_display_with.rs: -------------------------------------------------------------------------------- 1 | // #![include_doc("../../README.md", start)] 2 | //! # parse-display-with 3 | //! 4 | //! [![Crates.io](https://img.shields.io/crates/v/parse-display-with.svg)](https://crates.io/crates/parse-display-with) 5 | //! [![Docs.rs](https://docs.rs/parse-display-with/badge.svg)](https://docs.rs/parse-display-with/) 6 | //! [![Actions Status](https://github.com/frozenlib/parse-display/workflows/CI/badge.svg)](https://github.com/frozenlib/parse-display/actions) 7 | //! 8 | //! Custom formatting/parsing utilities for [parse-display](https://crates.io/crates/parse-display). 9 | //! 10 | //! ## License 11 | //! 12 | //! This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-\* files for details. 13 | //! 14 | //! ## Contribution 15 | //! 16 | //! Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 17 | // #![include_doc("../../README.md", end)] 18 | -------------------------------------------------------------------------------- /parse-display-with/tests/display_formats.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | extern crate alloc; 3 | 4 | use core::mem::transmute; 5 | 6 | use alloc::{format, vec::Vec}; 7 | use parse_display::Display; 8 | use parse_display_with::formats::delimiter; 9 | 10 | #[test] 11 | fn delimiter_struct() { 12 | #[derive(Display)] 13 | #[display("{0}")] 14 | struct X(#[display(with = delimiter(", "))] Vec); 15 | 16 | assert_display(X(alloc::vec![10, 20, 30]), "10, 20, 30"); 17 | } 18 | 19 | #[test] 20 | fn delimiter_enum() { 21 | #[derive(Display)] 22 | enum X { 23 | #[display("a : {0}")] 24 | A(#[display(with = delimiter(", "))] Vec), 25 | 26 | #[display("b : {0}")] 27 | B(#[display(with = delimiter(", "))] Vec), 28 | } 29 | 30 | assert_display(X::A(alloc::vec![10, 20, 30]), "a : 10, 20, 30"); 31 | assert_display(X::B(alloc::vec![10, 20, 30]), "b : 10, 20, 30"); 32 | } 33 | 34 | #[test] 35 | fn delimiter_field_vec() { 36 | #[derive(Display)] 37 | #[display("{0}")] 38 | struct X(#[display(with = delimiter(", "))] Vec); 39 | 40 | assert_display(X(alloc::vec![10, 20, 30]), "10, 20, 30"); 41 | } 42 | 43 | #[test] 44 | fn delimiter_field_array() { 45 | #[derive(Display)] 46 | #[display("{0}")] 47 | struct X(#[display(with = delimiter(", "))] [u32; 3]); 48 | 49 | assert_display(X([10, 20, 30]), "10, 20, 30"); 50 | } 51 | 52 | #[test] 53 | fn delimiter_field_slice() { 54 | #[derive(Display)] 55 | #[display("{0}")] 56 | struct X<'a>(#[display(with = delimiter(", "))] &'a [u32]); 57 | 58 | assert_display(X(&[10, 20, 30]), "10, 20, 30"); 59 | } 60 | 61 | #[test] 62 | fn delimiter_field_dst() { 63 | #[repr(transparent)] 64 | #[derive(Display)] 65 | #[display("{0}")] 66 | struct X(#[display(with = delimiter(", "))] [u32]); 67 | 68 | let x: &[u32] = &[10, 20, 30]; 69 | let x: &X = unsafe { transmute(x) }; 70 | 71 | assert_display(x, "10, 20, 30"); 72 | } 73 | 74 | #[test] 75 | fn with_and_default_bound() { 76 | #[derive(Display, Debug, Eq, PartialEq)] 77 | struct X(#[display(with = delimiter(", "))] Vec); 78 | 79 | assert_display(X(alloc::vec![10, 20, 30]), "10, 20, 30"); 80 | } 81 | 82 | fn assert_display(value: T, display: &str) { 83 | let value_display = format!("{value}"); 84 | assert_eq!(value_display, display); 85 | } 86 | -------------------------------------------------------------------------------- /parse-display-with/tests/from_str_formats.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "std")] 2 | 3 | use core::fmt::{Debug, Display}; 4 | use core::str::FromStr; 5 | 6 | use parse_display::FromStr; 7 | use parse_display_with::formats::delimiter; 8 | 9 | #[test] 10 | fn delimiter_struct() { 11 | #[derive(FromStr, Debug, Eq, PartialEq)] 12 | #[display("{0}")] 13 | struct X(#[display(with = delimiter(", "))] Vec); 14 | 15 | assert_from_str("10, 20, 30", X(vec![10, 20, 30])); 16 | } 17 | 18 | #[test] 19 | fn delimiter_enum() { 20 | #[derive(FromStr, Debug, Eq, PartialEq)] 21 | 22 | enum X { 23 | #[display("a : {0}")] 24 | A(#[display(with = delimiter(", "))] Vec), 25 | 26 | #[display("b : {0}")] 27 | B(#[display(with = delimiter(", "))] Vec), 28 | } 29 | 30 | assert_from_str("a : 10, 20, 30", X::A(vec![10, 20, 30])); 31 | assert_from_str("b : 10, 20, 30", X::B(vec![10, 20, 30])); 32 | } 33 | 34 | #[test] 35 | fn with_and_default_bound() { 36 | #[derive(FromStr, Debug, Eq, PartialEq)] 37 | struct X(#[from_str(with = delimiter(", "))] Vec); 38 | 39 | assert_from_str("10, 20, 30", X(vec![10, 20, 30])); 40 | } 41 | 42 | fn assert_from_str(s: &str, value: T) 43 | where 44 | ::Err: Display, 45 | { 46 | match s.parse::() { 47 | Ok(a) => assert_eq!(a, value, "input = \"{s}\""), 48 | Err(e) => panic!("\"{s}\" parse failed. ({e})"), 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /parse-display/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parse-display" 3 | version = "0.10.0" 4 | edition = "2024" 5 | authors = ["frozenlib"] 6 | license = "MIT OR Apache-2.0" 7 | readme = "../README.md" 8 | repository = "https://github.com/frozenlib/parse-display" 9 | documentation = "https://docs.rs/parse-display/" 10 | keywords = ["derive", "enum", "from_str", "display", "regex"] 11 | categories = ["parsing"] 12 | description = "Procedural macro to implement Display and FromStr using common settings." 13 | rust-version = "1.85.0" 14 | 15 | [features] 16 | default = ["std"] 17 | std = ["regex", "regex-syntax", "parse-display-derive/std"] 18 | docs = [] 19 | 20 | [dependencies] 21 | parse-display-derive = { version = "=0.10.0", path = "../parse-display-derive" } 22 | regex = { workspace = true, optional = true } 23 | regex-syntax = { workspace = true, optional = true } 24 | 25 | [dev-dependencies] 26 | trybuild = "1.0.99" 27 | 28 | [package.metadata.docs.rs] 29 | features = ["docs"] 30 | -------------------------------------------------------------------------------- /parse-display/src/display.md: -------------------------------------------------------------------------------- 1 | Derive [`Display`]. 2 | 3 | ## Helper attributes 4 | 5 | `#[derive(Display)]` and `#[derive(FromStr)]` use common helper attributes. 6 | 7 | - `#[derive(Display)]` use `#[display]`. 8 | - `#[derive(FromStr)]` use both `#[display]` and `#[from_str]`, with `#[from_str]` having priority. 9 | 10 | Helper attributes can be written in the following positions. 11 | 12 | | attribute | `#[display]` | `#[from_str]` | struct | enum | variant | field | 13 | | ------------------------------------------------------------- | ------------ | ------------- | ------ | ---- | ------- | ----- | 14 | | [`#[display("...")]`](#display) | ✔ | | ✔ | ✔ | ✔ | ✔ | 15 | | [`#[display(style = "...")]`](#displaystyle--) | ✔ | | | ✔ | ✔ | | 16 | | [`#[display(with = ...)]`](#displaywith---from_strwith--) | ✔ | ✔ | | | | ✔ | 17 | | [`#[display(opt)]`](#displayopt) | | | | | | ✔ | 18 | | [`#[display(bound(...))]`](#displaybound-from_strbound) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | 19 | | [`#[display(crate = ...)]`](#displaycrate--) | ✔ | | ✔ | ✔ | | | 20 | | [`#[display(dump)]`](#displaydump-from_strdump) | ✔ | ✔ | ✔ | ✔ | | | 21 | | [`#[from_str(regex = "...")]`](#from_strregex--) | | ✔ | ✔ | ✔ | ✔ | ✔ | 22 | | [`#[from_str(regex_infer)]`](#from_strregex_infer) | | ✔ | ✔ | ✔ | ✔ | ✔ | 23 | | [`#[from_str(new = ...)]`](#from_strnew--) | | ✔ | ✔ | | ✔ | | 24 | | [`#[from_str(ignore)]`](#from_strignore) | | ✔ | | | ✔ | | 25 | | [`#[from_str(default)]`](#from_strdefault) | | ✔ | ✔ | | | ✔ | 26 | | [`#[from_str(default_fields(...))]`](#from_strdefault_fields) | | ✔ | ✔ | ✔ | ✔ | | 27 | 28 | ## `#[display("...")]` 29 | 30 | Specifies the format using a syntax similar to [`std::format!()`]. 31 | 32 | However, unlike `std::format!()`, `{}` has the following meaning. 33 | 34 | | format | struct | enum | variant | field | description | 35 | | --------------------- | ------ | ---- | ------- | ----- | ----------------------------------------------------------------------------------- | 36 | | [`{a}`, `{b}`, `{1}`] | ✔ | ✔ | ✔ | ✔ | Use a field with the specified name. | 37 | | [`{}`] | | ✔ | ✔ | | Use a variant name of enum. | 38 | | [`{}`,`{:x}`, `{:?}`] | | | | ✔ | Use the field itself. | 39 | | [`{:x}`, `{:?}`] | ✔ | ✔ | | | Use format traits other than [`Display`] for `self`. (e.g. [`LowerHex`], [`Debug`]) | 40 | | [`{a.b.c}`] | ✔ | ✔ | ✔ | ✔ | Use a nested field. | 41 | 42 | [`LowerHex`]: std::fmt::LowerHex 43 | [`{a}`, `{b}`, `{1}`]: #struct-format 44 | [`{}`]: #variant-name 45 | [`{}`,`{:x}`, `{:?}`]: #field-format 46 | [`{:x}`, `{:?}`]: #format-parameter 47 | [`{a.b.c}`]: #nested-field 48 | 49 | ### Struct format 50 | 51 | By writing `#[display("..")]`, you can specify the format used by `Display` and `FromStr`. 52 | 53 | ```rust 54 | use parse_display::{Display, FromStr}; 55 | 56 | #[derive(Display, FromStr, PartialEq, Debug)] 57 | #[display("{a}-{b}")] 58 | struct MyStruct { 59 | a: u32, 60 | b: u32, 61 | } 62 | assert_eq!(MyStruct { a:10, b:20 }.to_string(), "10-20"); 63 | assert_eq!("10-20".parse(), Ok(MyStruct { a:10, b:20 })); 64 | 65 | #[derive(Display, FromStr, PartialEq, Debug)] 66 | #[display("{0}+{1}")] 67 | struct MyTuple(u32, u32); 68 | assert_eq!(MyTuple(10, 20).to_string(), "10+20"); 69 | assert_eq!("10+20".parse(), Ok(MyTuple(10, 20))); 70 | ``` 71 | 72 | ### Newtype pattern 73 | 74 | If the struct has only one field, the format can be omitted. 75 | In this case, the only field is used. 76 | 77 | ```rust 78 | use parse_display::{Display, FromStr}; 79 | 80 | #[derive(Display, FromStr, PartialEq, Debug)] 81 | struct NewType(u32); 82 | assert_eq!(NewType(10).to_string(), "10"); 83 | assert_eq!("10".parse(), Ok(NewType(10))); 84 | ``` 85 | 86 | ### Enum format 87 | 88 | In enum, you can specify the format for each variant. 89 | 90 | ```rust 91 | use parse_display::{Display, FromStr}; 92 | 93 | #[derive(Display, FromStr, PartialEq, Debug)] 94 | enum MyEnum { 95 | #[display("aaa")] 96 | VarA, 97 | #[display("bbb")] 98 | VarB, 99 | } 100 | assert_eq!(MyEnum::VarA.to_string(), "aaa"); 101 | assert_eq!(MyEnum::VarB.to_string(), "bbb"); 102 | assert_eq!("aaa".parse(), Ok(MyEnum::VarA)); 103 | assert_eq!("bbb".parse(), Ok(MyEnum::VarB)); 104 | ``` 105 | 106 | In enum format, `{}` means variant name. 107 | Variant name style (e.g. `snake_case`, `camelCase`, ...) can be specified by [`#[from_str(style = "...")]`](#displaystyle--). 108 | 109 | ```rust 110 | use parse_display::{Display, FromStr}; 111 | 112 | #[derive(Display, FromStr, PartialEq, Debug)] 113 | enum MyEnum { 114 | #[display("aaa-{}")] 115 | VarA, 116 | #[display("bbb-{}")] 117 | VarB, 118 | } 119 | assert_eq!(MyEnum::VarA.to_string(), "aaa-VarA"); 120 | assert_eq!(MyEnum::VarB.to_string(), "bbb-VarB"); 121 | assert_eq!("aaa-VarA".parse(), Ok(MyEnum::VarA)); 122 | assert_eq!("bbb-VarB".parse(), Ok(MyEnum::VarB)); 123 | 124 | #[derive(Display, FromStr, PartialEq, Debug)] 125 | #[display(style = "snake_case")] 126 | enum MyEnumSnake { 127 | #[display("{}")] 128 | VarA, 129 | } 130 | assert_eq!(MyEnumSnake::VarA.to_string(), "var_a"); 131 | assert_eq!("var_a".parse(), Ok(MyEnumSnake::VarA)); 132 | ``` 133 | 134 | By writing a format on enum instead of variant, you can specify the format common to multiple variants. 135 | 136 | ```rust 137 | use parse_display::{Display, FromStr}; 138 | 139 | #[derive(Display, FromStr, PartialEq, Debug)] 140 | #[display("xxx-{}")] 141 | enum MyEnum { 142 | VarA, 143 | VarB, 144 | } 145 | assert_eq!(MyEnum::VarA.to_string(), "xxx-VarA"); 146 | assert_eq!(MyEnum::VarB.to_string(), "xxx-VarB"); 147 | assert_eq!("xxx-VarA".parse(), Ok(MyEnum::VarA)); 148 | assert_eq!("xxx-VarB".parse(), Ok(MyEnum::VarB)); 149 | ``` 150 | 151 | ### Unit variants 152 | 153 | If all variants has no field, format can be omitted. 154 | In this case, variant name is used. 155 | 156 | ```rust 157 | use parse_display::{Display, FromStr}; 158 | 159 | #[derive(Display, FromStr, PartialEq, Debug)] 160 | enum MyEnum { 161 | VarA, 162 | VarB, 163 | } 164 | assert_eq!(MyEnum::VarA.to_string(), "VarA"); 165 | assert_eq!(MyEnum::VarB.to_string(), "VarB"); 166 | assert_eq!("VarA".parse(), Ok(MyEnum::VarA)); 167 | assert_eq!("VarB".parse(), Ok(MyEnum::VarB)); 168 | ``` 169 | 170 | ### Field format 171 | 172 | You can specify the format of the field. 173 | In field format, `{}` means the field itself. 174 | 175 | ```rust 176 | use parse_display::{Display, FromStr}; 177 | 178 | #[derive(Display, FromStr, PartialEq, Debug)] 179 | #[display("{a}, {b}")] 180 | struct MyStruct { 181 | #[display("a is {}")] 182 | a: u32, 183 | #[display("b is {}")] 184 | b: u32, 185 | } 186 | assert_eq!(MyStruct { a:10, b:20 }.to_string(), "a is 10, b is 20"); 187 | assert_eq!("a is 10, b is 20".parse(), Ok(MyStruct { a:10, b:20 })); 188 | 189 | #[derive(Display, FromStr, PartialEq, Debug)] 190 | #[display("{0}, {1}")] 191 | struct MyTuple(#[display("first is {}")] u32, #[display("next is {}")] u32); 192 | assert_eq!(MyTuple(10, 20).to_string(), "first is 10, next is 20"); 193 | assert_eq!("first is 10, next is 20".parse(), Ok(MyTuple(10, 20))); 194 | 195 | #[derive(Display, FromStr, PartialEq, Debug)] 196 | enum MyEnum { 197 | #[display("this is A {0}")] 198 | VarA(#[display("___{}___")] u32), 199 | } 200 | assert_eq!(MyEnum::VarA(10).to_string(), "this is A ___10___"); 201 | assert_eq!("this is A ___10___".parse(), Ok(MyEnum::VarA(10))); 202 | ``` 203 | 204 | ### Format parameter 205 | 206 | Like `std::format!()`, format parameter can be specified. 207 | 208 | ```rust 209 | use parse_display::{Display, FromStr}; 210 | 211 | #[derive(Display, PartialEq, Debug)] 212 | #[display("{a:>04}")] 213 | struct WithFormatParameter { 214 | a: u32, 215 | } 216 | assert_eq!(WithFormatParameter { a:5 }.to_string(), "0005"); 217 | ``` 218 | 219 | When `{}` is used within `#[display("...")]` set for an enum, and if a format trait is added to `{}` such as `{:?}`, the meaning changes from "variant name" to "a string using a trait other than Display for self." 220 | 221 | ```rust 222 | use parse_display::Display; 223 | 224 | #[derive(Display, PartialEq, Debug)] 225 | #[display("{}")] 226 | enum X { 227 | A, 228 | } 229 | assert_eq!(X::A.to_string(), "A"); 230 | 231 | #[derive(Display, PartialEq)] 232 | #[display("{:?}")] 233 | enum Y { 234 | A, 235 | } 236 | impl std::fmt::Debug for Y { 237 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 238 | write!(f, "Debug Y") 239 | } 240 | } 241 | assert_eq!(Y::A.to_string(), "Debug Y"); 242 | ``` 243 | 244 | ### Nested field 245 | 246 | You can use nested field, e.g. `{x.a}` . 247 | 248 | ```rust 249 | use parse_display::{Display, FromStr}; 250 | 251 | #[derive(PartialEq, Debug, Default)] 252 | struct X { 253 | a: u32, 254 | b: u32, 255 | } 256 | 257 | #[derive(FromStr, Display, PartialEq, Debug)] 258 | #[display("{x.a}")] 259 | struct Y { 260 | #[from_str(default)] 261 | x: X, 262 | } 263 | assert_eq!(Y { x: X { a: 10, b: 20 } }.to_string(), "10"); 264 | assert_eq!("10".parse(), Ok(Y { x: X { a: 10, b: 0 } })); 265 | ``` 266 | 267 | When using nested field, you need to use [`#[from_str(default)]`](#from_strdefault) to implement `FromStr`. 268 | 269 | ## `#[display(style = "...")]` 270 | 271 | By writing `#[display(style = "...")]`, you can specify the variant name style. 272 | The following styles are available. 273 | 274 | - `none` 275 | - `lowercase` 276 | - `UPPERCASE` 277 | - `snake_case` 278 | - `SNAKE_CASE` 279 | - `camelCase` 280 | - `CamelCase` 281 | - `kebab-case` 282 | - `KEBAB-CASE` 283 | - `Title Case` 284 | - `Title case` 285 | - `title case` 286 | - `TITLE CASE` 287 | 288 | ```rust 289 | use parse_display::{Display, FromStr}; 290 | 291 | #[derive(Display, FromStr, PartialEq, Debug)] 292 | #[display(style = "snake_case")] 293 | enum MyEnum { 294 | VarA, 295 | VarB, 296 | } 297 | assert_eq!(MyEnum::VarA.to_string(), "var_a"); 298 | assert_eq!("var_a".parse(), Ok(MyEnum::VarA)); 299 | 300 | #[derive(Display, FromStr, PartialEq, Debug)] 301 | enum StyleExample { 302 | #[display(style = "none")] 303 | VarA1, 304 | #[display(style = "none")] 305 | varA2, 306 | #[display(style = "lowercase")] 307 | VarB, 308 | #[display(style = "UPPERCASE")] 309 | VarC, 310 | #[display(style = "snake_case")] 311 | VarD, 312 | #[display(style = "SNAKE_CASE")] 313 | VarE, 314 | #[display(style = "camelCase")] 315 | VarF, 316 | #[display(style = "CamelCase")] 317 | VarG1, 318 | #[display(style = "CamelCase")] 319 | varG2, 320 | #[display(style = "kebab-case")] 321 | VarH, 322 | #[display(style = "KEBAB-CASE")] 323 | VarI, 324 | #[display(style = "Title Case")] 325 | VarJ, 326 | #[display(style = "Title case")] 327 | VarK, 328 | #[display(style = "title case")] 329 | VarL, 330 | #[display(style = "TITLE CASE")] 331 | VarM, 332 | } 333 | assert_eq!(StyleExample::VarA1.to_string(), "VarA1"); 334 | assert_eq!(StyleExample::varA2.to_string(), "varA2"); 335 | assert_eq!(StyleExample::VarB.to_string(), "varb"); 336 | assert_eq!(StyleExample::VarC.to_string(), "VARC"); 337 | assert_eq!(StyleExample::VarD.to_string(), "var_d"); 338 | assert_eq!(StyleExample::VarE.to_string(), "VAR_E"); 339 | assert_eq!(StyleExample::VarF.to_string(), "varF"); 340 | assert_eq!(StyleExample::VarG1.to_string(), "VarG1"); 341 | assert_eq!(StyleExample::varG2.to_string(), "VarG2"); 342 | assert_eq!(StyleExample::VarH.to_string(), "var-h"); 343 | assert_eq!(StyleExample::VarI.to_string(), "VAR-I"); 344 | assert_eq!(StyleExample::VarJ.to_string(), "Var J"); 345 | assert_eq!(StyleExample::VarK.to_string(), "Var k"); 346 | assert_eq!(StyleExample::VarL.to_string(), "var l"); 347 | assert_eq!(StyleExample::VarM.to_string(), "VAR M"); 348 | ``` 349 | 350 | ## `#[display(opt)]` 351 | 352 | When applied to an `Option` field, this attribute makes the field display an empty string for `None` and use `T`'s trait implementations directly (not `Option`'s, but `T`'s `Display`, `FromStr`, etc.) for `Some(T)`. 353 | 354 | ```rust 355 | use parse_display::{Display, FromStr}; 356 | 357 | #[derive(Display, FromStr, PartialEq, Debug)] 358 | struct X { 359 | #[display("a={}", opt)] 360 | a: Option, 361 | } 362 | assert_eq!(X { a: Some(10) }.to_string(), "a=10"); 363 | assert_eq!(X { a: None:: }.to_string(), ""); 364 | ``` 365 | 366 | When the field is `None`, not just the placeholder but the entire format string for that field is omitted from the output. In the example above, when `a` is `None`, the output is `""` rather than `"a="`. 367 | 368 | ## `#[display(with = "...")]`, `#[from_str(with = "...")]` 369 | 370 | You can customize [`Display`] and [`FromStr`] processing for a field by specifying the values that implements [`DisplayFormat`] and [`FromStrFormat`]. 371 | 372 | ```rust 373 | use parse_display::{Display, DisplayFormat, FromStr, FromStrFormat}; 374 | 375 | #[derive(Display, FromStr, PartialEq, Debug)] 376 | pub struct X { 377 | #[display(with = Plus1)] 378 | a: i32, 379 | } 380 | 381 | struct Plus1; 382 | 383 | impl DisplayFormat for Plus1 { 384 | fn write(&self, f: &mut std::fmt::Formatter, value: &i32) -> std::fmt::Result { 385 | write!(f, "{}", value + 1) 386 | } 387 | } 388 | impl FromStrFormat for Plus1 { 389 | type Err = ::Err; 390 | fn parse(&self, s: &str) -> std::result::Result { 391 | Ok(s.parse::()? - 1) 392 | } 393 | } 394 | 395 | assert_eq!(X { a: 1 }.to_string(), "2"); 396 | assert_eq!("2".parse(), Ok(X { a: 1 })); 397 | ``` 398 | 399 | The expression specified for `with = ...` must be lightweight because it is called each time when formatting and parsing. 400 | 401 | ## `#[display(bound(...))]`, `#[from_str(bound(...))]` 402 | 403 | By default, the type of field used in the format is added to the trait bound. 404 | 405 | In Rust prior to 1.59, this behavior causes a compile error if you use fields of non public type in public struct. 406 | 407 | ```rust 408 | #![deny(private_in_public)] 409 | use parse_display::Display; 410 | 411 | // private type `Inner` in public interface (error E0446) 412 | #[derive(Display)] 413 | pub struct Outer(Inner); 414 | 415 | #[derive(Display)] 416 | struct Inner(T); 417 | ``` 418 | 419 | By writing `#[display(bound(...))]`, you can override the default behavior. 420 | 421 | ### Specify trait bound type 422 | 423 | By specifying the type, you can specify the type that need to implement `Display` and `FromStr`. 424 | 425 | ```rust 426 | use parse_display::{Display, FromStr}; 427 | 428 | #[derive(Display, FromStr, PartialEq, Debug)] 429 | #[display(bound(T))] 430 | pub struct Outer(Inner); 431 | 432 | #[derive(Display, FromStr, PartialEq, Debug)] 433 | struct Inner(T); 434 | 435 | assert_eq!(Outer(Inner(10)).to_string(), "10"); 436 | assert_eq!("10".parse(), Ok(Outer(Inner(10)))); 437 | ``` 438 | 439 | ### Specify where predicate 440 | 441 | You can also specify the where predicate. 442 | 443 | ```rust 444 | use parse_display::Display; 445 | 446 | #[derive(Display)] 447 | #[display(bound(T : std::fmt::Debug))] 448 | pub struct Outer(Inner); 449 | 450 | #[derive(Display)] 451 | #[display("{0:?}")] 452 | struct Inner(T); 453 | 454 | assert_eq!(Outer(Inner(10)).to_string(), "10"); 455 | ``` 456 | 457 | ### No trait bounds 458 | 459 | You can also remove all trait bounds. 460 | 461 | ```rust 462 | use parse_display::Display; 463 | 464 | #[derive(Display)] 465 | #[display(bound())] 466 | pub struct Outer(Inner); 467 | 468 | #[derive(Display)] 469 | #[display("ABC")] 470 | struct Inner(T); 471 | 472 | assert_eq!(Outer(Inner(10)).to_string(), "ABC"); 473 | ``` 474 | 475 | ### Default trait bounds 476 | 477 | `..` means default (automatically generated) trait bounds. 478 | 479 | The following example specifies `T1` as a trait bound in addition to the default trait bound `T2`. 480 | 481 | ```rust 482 | use parse_display::Display; 483 | 484 | pub struct Inner(T); 485 | 486 | #[derive(Display)] 487 | #[display("{0.0}, {1}", bound(T1, ..))] 488 | pub struct Outer(Inner, T2); 489 | 490 | assert_eq!(Outer(Inner(10), 20).to_string(), "10, 20"); 491 | ``` 492 | 493 | You can use a different trait bound for `Display` and `FromStr` by specifying both `#[display(bound(...))]` and `#[from_str(bound(...))]`. 494 | 495 | ```rust 496 | use parse_display::*; 497 | use std::{fmt::Display, str::FromStr}; 498 | 499 | #[derive(Display, FromStr, PartialEq, Debug)] 500 | #[display(bound("T : Display"))] 501 | #[from_str(bound("T : FromStr"))] 502 | pub struct Outer(Inner); 503 | 504 | #[derive(Display, FromStr, PartialEq, Debug)] 505 | struct Inner(T); 506 | 507 | assert_eq!(Outer(Inner(10)).to_string(), "10"); 508 | assert_eq!("10".parse(), Ok(Outer(Inner(10)))); 509 | ``` 510 | 511 | ## `#[display(crate = ...)]` 512 | 513 | Specify a path to the `parse-display` crate instance. 514 | 515 | Used when `::parse_display` is not an instance of `parse-display`, such as when a macro is re-exported or used from another macro. 516 | 517 | ## `#[display(dump)]`, `#[from_str(dump)]` 518 | 519 | Outputs the generated code as a compile error. 520 | 521 | ## `#[from_str(regex = "...")]` 522 | 523 | Specify the format of the string to be input with `FromStr`. 524 | `#[display("...")]` is ignored, when this attribute is specified. 525 | 526 | ### Capture name 527 | 528 | The capture name corresponds to the field name. 529 | 530 | ```rust 531 | use parse_display::FromStr; 532 | 533 | #[derive(FromStr, PartialEq, Debug)] 534 | #[from_str(regex = "(?[0-9]+)__(?[0-9]+)")] 535 | struct MyStruct { 536 | a: u8, 537 | b: u8, 538 | } 539 | 540 | assert_eq!("10__20".parse(), Ok(MyStruct { a: 10, b: 20 })); 541 | ``` 542 | 543 | ### Field regex 544 | 545 | Set `#[display("...")]` to struct and set `#[from_str(regex = "...")]` to field, regex is used in the position where field name is specified in `#[display("...")]`. 546 | 547 | ```rust 548 | use parse_display::FromStr; 549 | 550 | #[derive(FromStr, PartialEq, Debug)] 551 | #[display("{a}__{b}")] 552 | struct MyStruct { 553 | #[from_str(regex = "[0-9]+")] 554 | a: u8, 555 | 556 | #[from_str(regex = "[0-9]+")] 557 | b: u8, 558 | } 559 | assert_eq!("10__20".parse(), Ok(MyStruct { a: 10, b: 20 })); 560 | ``` 561 | 562 | If `#[from_str(regex = "...")]` is not set to field , 563 | it operates in the same way as when `#[from_str(regex = "(?s:.*?)")]` is set. 564 | 565 | ```rust 566 | use parse_display::FromStr; 567 | 568 | #[derive(FromStr, PartialEq, Debug)] 569 | #[display("{a}{b}")] 570 | struct MyStruct { 571 | a: String, 572 | b: String, 573 | } 574 | assert_eq!("abcdef".parse(), Ok(MyStruct { a:"".into(), b:"abcdef".into() })); 575 | ``` 576 | 577 | ### Field regex with capture 578 | 579 | Using a named capture group with an empty name in the field's regex will convert only the string within that group to the field's value. 580 | 581 | ```rust 582 | use parse_display::FromStr; 583 | 584 | #[derive(FromStr, PartialEq, Debug)] 585 | struct MyStruct { 586 | #[from_str(regex = "a = (?<>[0-9]+)")] 587 | a: u8, 588 | } 589 | assert_eq!("a = 10".parse(), Ok(MyStruct { a: 10 })); 590 | ``` 591 | 592 | ### Field regex with display format 593 | 594 | If both `#[display("...")]` and `#[from_str(regex = "...")]` are specified for a field and the regex does not contain named capture groups, the pattern within the `{}` part of the format specified by `#[display("...")]` will be determined by `#[from_str(regex = "...")]`. 595 | 596 | ```rust 597 | use parse_display::FromStr; 598 | 599 | #[derive(FromStr, PartialEq, Debug)] 600 | struct X { 601 | #[display("a = {}")] 602 | #[from_str(regex = "[0-9]+")] 603 | a: u8, 604 | } 605 | assert_eq!("a = 10".parse(), Ok(X { a: 10 })); 606 | ``` 607 | 608 | If the regex does not contain named capture groups, `#[display("...")]` is ignored. 609 | 610 | ```rust 611 | use parse_display::FromStr; 612 | 613 | #[derive(FromStr, PartialEq, Debug)] 614 | struct Y { 615 | #[display("a = {}")] 616 | #[from_str(regex = "a = (?<>[0-9]+)")] 617 | a: u8, 618 | } 619 | assert_eq!("a = 10".parse(), Ok(Y { a: 10 })); 620 | assert!("a = a = 10".parse::().is_err()); 621 | ``` 622 | 623 | ### Variant name 624 | 625 | In the regex specified for enum or variant, empty name capture means variant name. 626 | 627 | ```rust 628 | use parse_display::FromStr; 629 | 630 | #[derive(FromStr, PartialEq, Debug)] 631 | #[from_str(regex = "___(?<>)___")] 632 | enum MyEnum { 633 | VarA, 634 | 635 | #[from_str(regex = "xxx(?<>)xxx")] 636 | VarB, 637 | } 638 | assert_eq!("___VarA___".parse(), Ok(MyEnum::VarA)); 639 | assert_eq!("xxxVarBxxx".parse(), Ok(MyEnum::VarB)); 640 | ``` 641 | 642 | ### Regex nested field 643 | 644 | You can use nested field in regex. 645 | 646 | ```rust 647 | use parse_display::FromStr; 648 | 649 | #[derive(PartialEq, Debug, Default)] 650 | struct X { 651 | a: u32, 652 | } 653 | 654 | #[derive(FromStr, PartialEq, Debug)] 655 | #[from_str(regex = "___(?[0-9]+)")] 656 | struct Y { 657 | #[from_str(default)] 658 | x: X, 659 | } 660 | assert_eq!("___10".parse(), Ok(Y { x: X { a: 10 } })); 661 | ``` 662 | 663 | When using nested field, you need to use [`#[from_str(default)]`](#from_strdefault). 664 | 665 | ### Regex priority 666 | 667 | In addition to `#[from_str(regex = "...")]`, 668 | you can also specify `#[from_str(with = ...)]`, `#[display(with = ...)]`, or `#[from_str(regex_infer)]` to change the regular expression. 669 | If you specify multiple attributes in the same field, the regular expression that is applied is determined by the following priority. 670 | 671 | - [`#[from_str(regex = "...")]`](#from_strregex--) 672 | - [`#[from_str(with = ...)]`, `#[display(with = ...)]`)](#displaywith---from_strwith--) 673 | - [`#[from_str(regex_infer)]`](#from_strregex_infer) 674 | 675 | ## `#[from_str(regex_infer)]` 676 | 677 | By default, fields are matched using the regular expression `(?s:.*?)`. 678 | 679 | If you specify `#[from_str(regex_infer)]`, 680 | this behavior is changed and the pattern obtained from the field type's [`FromStrRegex`] is used for matching. 681 | 682 | ```rust 683 | use parse_display::FromStr; 684 | 685 | #[derive(FromStr, PartialEq, Debug)] 686 | #[display("{a}{b}")] 687 | struct X { 688 | a: u32, 689 | b: String, 690 | } 691 | 692 | // `a` matches "" and `b` matches "1a", so it fails to convert to `Y`. 693 | assert!("1a".parse::().is_err()); 694 | 695 | #[derive(FromStr, PartialEq, Debug)] 696 | #[display("{a}{b}")] 697 | struct Y { 698 | #[from_str(regex_infer)] 699 | a: u32, 700 | b: String, 701 | } 702 | 703 | // `a` matches "1" and `b` matches "a", so it can be converted to `Y`. 704 | assert_eq!("1a".parse(), Ok(Y { a: 1, b: "a".into() })); 705 | ``` 706 | 707 | If `#[from_str(regex_infer)]` is specified for a type or variant rather than a field, this attribute is applied to all fields. 708 | 709 | ## `#[from_str(new = ...)]` 710 | 711 | If `#[from_str(new = ...)]` is specified, the value will be initialized with the specified expression instead of the constructor. 712 | 713 | The expression must return a value that implement [`IntoResult`] (e.g. `Self`, `Option`, `Result`). 714 | 715 | In the expression, you can use a variable with the same name as the field name. 716 | 717 | ```rust 718 | use parse_display::FromStr; 719 | #[derive(FromStr, Debug, PartialEq)] 720 | #[from_str(new = Self::new(value))] 721 | struct MyNonZeroUSize { 722 | value: usize, 723 | } 724 | 725 | impl MyNonZeroUSize { 726 | fn new(value: usize) -> Option { 727 | if value == 0 { 728 | None 729 | } else { 730 | Some(Self { value }) 731 | } 732 | } 733 | } 734 | 735 | assert_eq!("1".parse(), Ok(MyNonZeroUSize { value: 1 })); 736 | assert_eq!("0".parse::().is_err(), true); 737 | ``` 738 | 739 | In tuple struct, variables are named with a leading underscore and their index. (e.g. `_0`, `_1`). 740 | 741 | ```rust 742 | use parse_display::FromStr; 743 | #[derive(FromStr, Debug, PartialEq)] 744 | #[from_str(new = Self::new(_0))] 745 | struct MyNonZeroUSize(usize); 746 | 747 | impl MyNonZeroUSize { 748 | fn new(value: usize) -> Option { 749 | if value == 0 { 750 | None 751 | } else { 752 | Some(Self(value)) 753 | } 754 | } 755 | } 756 | 757 | assert_eq!("1".parse(), Ok(MyNonZeroUSize(1))); 758 | assert_eq!("0".parse::().is_err(), true); 759 | ``` 760 | 761 | ## `#[from_str(ignore)]` 762 | 763 | Specifying this attribute for a variant will not generate `FromStr` implementation for that variant. 764 | 765 | ```rust 766 | use parse_display::FromStr; 767 | 768 | #[derive(Debug, Eq, PartialEq)] 769 | struct CanNotFromStr; 770 | 771 | #[derive(FromStr, Debug, Eq, PartialEq)] 772 | #[allow(dead_code)] 773 | enum HasIgnore { 774 | #[from_str(ignore)] 775 | A(CanNotFromStr), 776 | #[display("{0}")] 777 | B(u32), 778 | } 779 | 780 | assert_eq!("1".parse(), Ok(HasIgnore::B(1))); 781 | ``` 782 | 783 | ## `#[from_str(default)]` 784 | 785 | If this attribute is specified, the default value is used for fields not included in the input. 786 | 787 | If an attribute is specified for struct, the struct's default value is used. 788 | 789 | ```rust 790 | use parse_display::FromStr; 791 | 792 | #[derive(FromStr, PartialEq, Debug)] 793 | #[display("{b}")] 794 | #[from_str(default)] 795 | struct MyStruct { 796 | a: u32, 797 | b: u32, 798 | } 799 | 800 | impl Default for MyStruct { 801 | fn default() -> Self { 802 | Self { a:99, b:99 } 803 | } 804 | } 805 | assert_eq!("10".parse(), Ok(MyStruct { a:99, b:10 })); 806 | ``` 807 | 808 | If an attribute is specified for field, the field type's default value is used. 809 | 810 | ```rust 811 | use parse_display::FromStr; 812 | 813 | #[derive(FromStr, PartialEq, Debug)] 814 | #[display("{b}")] 815 | struct MyStruct { 816 | #[from_str(default)] 817 | a: u32, 818 | b: u32, 819 | } 820 | 821 | impl Default for MyStruct { 822 | fn default() -> Self { 823 | Self { a:99, b:99 } 824 | } 825 | } 826 | assert_eq!("10".parse(), Ok(MyStruct { a:0, b:10 })); 827 | ``` 828 | 829 | ## `#[from_str(default_fields(...))]` 830 | 831 | You can use `#[from_str(default_fields(...))]` if you want to set default values for the same-named fields of multiple variants. 832 | 833 | ```rust 834 | use parse_display::FromStr; 835 | 836 | #[derive(FromStr, PartialEq, Debug)] 837 | #[display("{}-{a}")] 838 | #[from_str(default_fields("b", "c"))] 839 | enum MyEnum { 840 | VarA { a:u8, b:u8, c:u8 }, 841 | VarB { a:u8, b:u8, c:u8 }, 842 | } 843 | 844 | assert_eq!("VarA-10".parse(), Ok(MyEnum::VarA { a:10, b:0, c:0 })); 845 | assert_eq!("VarB-10".parse(), Ok(MyEnum::VarB { a:10, b:0, c:0 })); 846 | ``` 847 | -------------------------------------------------------------------------------- /parse-display/src/from_str_regex.rs: -------------------------------------------------------------------------------- 1 | use core::num::NonZero; 2 | use std::{ffi::OsString, path::PathBuf}; 3 | 4 | use crate::ANY_REGEX; 5 | 6 | /// A trait for getting regex patterns that match strings parseable by [`FromStr`](core::str::FromStr). 7 | /// 8 | /// When using [`#[derive(FromStr)]`](derive@crate::FromStr) with the [`#[from_str(regex_infer)]`](derive@crate::Display#from_strregex_infer) attribute, 9 | /// the regex pattern is obtained from the `FromStrRegex` implementation of the field's type. 10 | pub trait FromStrRegex: core::str::FromStr { 11 | /// Returns a regex pattern for strings that might be parseable by [`FromStr`](core::str::FromStr). 12 | /// 13 | /// Note: Matching this pattern does not guarantee that the string can be parsed successfully. 14 | fn from_str_regex() -> String; 15 | } 16 | 17 | impl FromStrRegex for char { 18 | fn from_str_regex() -> String { 19 | r"(?s:.)".into() 20 | } 21 | } 22 | 23 | fn regex_any() -> String { 24 | ANY_REGEX.into() 25 | } 26 | 27 | impl FromStrRegex for String { 28 | fn from_str_regex() -> String { 29 | regex_any() 30 | } 31 | } 32 | impl FromStrRegex for OsString { 33 | fn from_str_regex() -> String { 34 | regex_any() 35 | } 36 | } 37 | impl FromStrRegex for PathBuf { 38 | fn from_str_regex() -> String { 39 | regex_any() 40 | } 41 | } 42 | 43 | impl FromStrRegex for bool { 44 | fn from_str_regex() -> String { 45 | r"true|false".into() 46 | } 47 | } 48 | 49 | fn regex_uint() -> String { 50 | r"[0-9]+".into() 51 | } 52 | impl FromStrRegex for u8 { 53 | fn from_str_regex() -> String { 54 | regex_uint() 55 | } 56 | } 57 | impl FromStrRegex for NonZero { 58 | fn from_str_regex() -> String { 59 | regex_uint() 60 | } 61 | } 62 | 63 | impl FromStrRegex for u16 { 64 | fn from_str_regex() -> String { 65 | regex_uint() 66 | } 67 | } 68 | impl FromStrRegex for NonZero { 69 | fn from_str_regex() -> String { 70 | regex_uint() 71 | } 72 | } 73 | 74 | impl FromStrRegex for u32 { 75 | fn from_str_regex() -> String { 76 | regex_uint() 77 | } 78 | } 79 | impl FromStrRegex for NonZero { 80 | fn from_str_regex() -> String { 81 | regex_uint() 82 | } 83 | } 84 | 85 | impl FromStrRegex for u64 { 86 | fn from_str_regex() -> String { 87 | regex_uint() 88 | } 89 | } 90 | impl FromStrRegex for NonZero { 91 | fn from_str_regex() -> String { 92 | regex_uint() 93 | } 94 | } 95 | 96 | impl FromStrRegex for u128 { 97 | fn from_str_regex() -> String { 98 | regex_uint() 99 | } 100 | } 101 | impl FromStrRegex for NonZero { 102 | fn from_str_regex() -> String { 103 | regex_uint() 104 | } 105 | } 106 | 107 | impl FromStrRegex for usize { 108 | fn from_str_regex() -> String { 109 | regex_uint() 110 | } 111 | } 112 | impl FromStrRegex for NonZero { 113 | fn from_str_regex() -> String { 114 | regex_uint() 115 | } 116 | } 117 | 118 | fn regex_sint() -> String { 119 | r"-?[0-9]+".into() 120 | } 121 | 122 | impl FromStrRegex for i8 { 123 | fn from_str_regex() -> String { 124 | regex_sint() 125 | } 126 | } 127 | impl FromStrRegex for NonZero { 128 | fn from_str_regex() -> String { 129 | regex_sint() 130 | } 131 | } 132 | 133 | impl FromStrRegex for i16 { 134 | fn from_str_regex() -> String { 135 | regex_sint() 136 | } 137 | } 138 | impl FromStrRegex for NonZero { 139 | fn from_str_regex() -> String { 140 | regex_sint() 141 | } 142 | } 143 | 144 | impl FromStrRegex for i32 { 145 | fn from_str_regex() -> String { 146 | regex_sint() 147 | } 148 | } 149 | impl FromStrRegex for NonZero { 150 | fn from_str_regex() -> String { 151 | regex_sint() 152 | } 153 | } 154 | 155 | impl FromStrRegex for i64 { 156 | fn from_str_regex() -> String { 157 | regex_sint() 158 | } 159 | } 160 | impl FromStrRegex for NonZero { 161 | fn from_str_regex() -> String { 162 | regex_sint() 163 | } 164 | } 165 | impl FromStrRegex for i128 { 166 | fn from_str_regex() -> String { 167 | regex_sint() 168 | } 169 | } 170 | impl FromStrRegex for NonZero { 171 | fn from_str_regex() -> String { 172 | regex_sint() 173 | } 174 | } 175 | 176 | impl FromStrRegex for isize { 177 | fn from_str_regex() -> String { 178 | regex_sint() 179 | } 180 | } 181 | impl FromStrRegex for NonZero { 182 | fn from_str_regex() -> String { 183 | regex_sint() 184 | } 185 | } 186 | 187 | fn regex_f() -> String { 188 | r"(?i:[+-]?([0-9]+\.?|[0-9]*\.[0-9]+)(e[+-]?[0-9]+)?|[+-]?inf|nan)".into() 189 | } 190 | impl FromStrRegex for f32 { 191 | fn from_str_regex() -> String { 192 | regex_f() 193 | } 194 | } 195 | impl FromStrRegex for f64 { 196 | fn from_str_regex() -> String { 197 | regex_f() 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /parse-display/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use ::core::{ 2 | fmt::{self, Display, Formatter}, 3 | ops::Fn, 4 | }; 5 | 6 | #[cfg(feature = "std")] 7 | pub use super::helpers_std::*; 8 | 9 | use crate::{DisplayFormat, FromStrFormat}; 10 | 11 | pub struct Formatted<'a, T: ?Sized, F: DisplayFormat> { 12 | pub value: &'a T, 13 | pub format: F, 14 | } 15 | impl> fmt::Display for Formatted<'_, T, F> { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | self.format.write(f, self.value) 18 | } 19 | } 20 | 21 | pub fn parse_with(fmt: F, s: &str) -> Result 22 | where 23 | F: FromStrFormat, 24 | { 25 | fmt.parse(s) 26 | } 27 | 28 | struct FmtPointer<'a, T: ?Sized + fmt::Pointer>(&'a T); 29 | 30 | impl fmt::Pointer for FmtPointer<'_, T> { 31 | #[inline] 32 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 33 | fmt::Pointer::fmt(self.0, f) 34 | } 35 | } 36 | 37 | pub fn fmt_pointer(value: &T) -> impl fmt::Pointer + '_ { 38 | FmtPointer(value) 39 | } 40 | 41 | pub struct OptionFormatHelper<'a, T, F> { 42 | pub value: &'a Option, 43 | pub f: F, 44 | pub none_value: &'a str, 45 | } 46 | impl<'a, T, F> Display for OptionFormatHelper<'a, T, F> 47 | where 48 | F: Fn(&'a T, &mut Formatter) -> fmt::Result, 49 | { 50 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 51 | if let Some(value) = self.value { 52 | (self.f)(value, f) 53 | } else { 54 | Formatter::write_str(f, self.none_value) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /parse-display/src/helpers_std.rs: -------------------------------------------------------------------------------- 1 | use core::mem; 2 | use std::collections::HashMap; 3 | use std::{borrow::Cow, fmt}; 4 | 5 | use regex::Regex; 6 | use regex_syntax::ast::{Ast, Flags, GroupKind}; 7 | 8 | use crate::{ANY_REGEX, DisplayFormat, FromStrFormat, FromStrRegex}; 9 | 10 | pub use regex; 11 | 12 | #[track_caller] 13 | pub fn to_regex(format: &dyn FromStrFormat) -> Option { 14 | let s = format.regex_pattern(); 15 | if s == ANY_REGEX { None } else { Some(s) } 16 | } 17 | 18 | #[track_caller] 19 | pub fn to_ast(format: &dyn FromStrFormat) -> Option<(String, Ast)> { 20 | let s = format.regex_pattern(); 21 | if s == ANY_REGEX { 22 | None 23 | } else { 24 | let ast = ast_from_str(&s); 25 | Some((s, ast)) 26 | } 27 | } 28 | 29 | #[track_caller] 30 | fn ast_from_str(s: &str) -> Ast { 31 | let Ok(mut ast) = regex_syntax::ast::parse::Parser::new().parse(s) else { 32 | panic!("invalid regex: {s}") 33 | }; 34 | let e = replace_ast(&mut ast, &mut |ast| { 35 | if let Ast::Group(g) = ast { 36 | match &g.kind { 37 | GroupKind::CaptureIndex(_) => { 38 | g.kind = GroupKind::NonCapturing(Flags { 39 | span: g.span, 40 | items: vec![], 41 | }); 42 | } 43 | GroupKind::CaptureName { name, .. } => { 44 | return Err(format!( 45 | "named capture group is not supported: `{}`", 46 | name.name 47 | )); 48 | } 49 | GroupKind::NonCapturing(_) => {} 50 | } 51 | } 52 | Ok(true) 53 | }); 54 | if let Err(e) = e { 55 | panic!("{e}"); 56 | } 57 | ast 58 | } 59 | 60 | pub struct Parser { 61 | pub re: Regex, 62 | pub re_str: String, 63 | pub ss: Vec>, 64 | } 65 | impl Parser { 66 | #[track_caller] 67 | pub fn new(s: &str, with: &mut [(&str, Option<(String, Ast)>)]) -> Self { 68 | let mut asts: HashMap<&str, &Ast> = HashMap::new(); 69 | let mut ss = Vec::new(); 70 | for (capture_name, item) in with { 71 | if let Some((item_s, item_ast)) = item { 72 | asts.insert(capture_name, item_ast); 73 | ss.push(Some(mem::take(item_s))); 74 | } else { 75 | ss.push(None); 76 | } 77 | } 78 | let mut ast = regex_syntax::ast::parse::Parser::new().parse(s).unwrap(); 79 | replace_ast(&mut ast, &mut |ast| { 80 | if let Ast::Group(g) = ast { 81 | if let GroupKind::CaptureName { name, .. } = &g.kind { 82 | if let Some(ast) = asts.get(name.name.as_str()) { 83 | g.ast = Box::new((*ast).clone()); 84 | return Ok(false); 85 | } 86 | } 87 | } 88 | Ok(true) 89 | }) 90 | .unwrap(); 91 | let re = Regex::new(&ast.to_string()).unwrap(); 92 | replace_ast(&mut ast, &mut |ast| { 93 | if let Ast::Group(g) = ast { 94 | if let GroupKind::CaptureName { .. } = &g.kind { 95 | g.kind = GroupKind::NonCapturing(Flags { 96 | span: g.span, 97 | items: vec![], 98 | }); 99 | } 100 | } 101 | Ok(true) 102 | }) 103 | .unwrap(); 104 | let re_str = ast.to_string(); 105 | Self { re, re_str, ss } 106 | } 107 | } 108 | 109 | #[track_caller] 110 | pub fn build_regex(s: &str, with: &[(&str, Option)]) -> Regex { 111 | let with: HashMap<&str, &Ast> = with 112 | .iter() 113 | .filter_map(|(name, ast)| Some((*name, ast.as_ref()?))) 114 | .collect(); 115 | let re = if with.is_empty() { 116 | Cow::Borrowed(s) 117 | } else { 118 | let mut ast = regex_syntax::ast::parse::Parser::new().parse(s).unwrap(); 119 | let e = replace_ast(&mut ast, &mut |ast| { 120 | if let Ast::Group(g) = ast { 121 | if let GroupKind::CaptureName { name, .. } = &g.kind { 122 | if let Some(ast) = with.get(name.name.as_str()) { 123 | g.ast = Box::new((*ast).clone()); 124 | return Ok(false); 125 | } 126 | } 127 | } 128 | Ok(true) 129 | }); 130 | if let Err(e) = e { 131 | panic!("{e}"); 132 | } 133 | Cow::Owned(ast.to_string()) 134 | }; 135 | Regex::new(&re).unwrap() 136 | } 137 | 138 | fn replace_asts( 139 | asts: &mut Vec, 140 | f: &mut impl FnMut(&mut Ast) -> ReplaceAstResult, 141 | ) -> ReplaceAstResult { 142 | for ast in asts { 143 | replace_ast(ast, f)?; 144 | } 145 | Ok(()) 146 | } 147 | 148 | fn replace_ast( 149 | ast: &mut Ast, 150 | f: &mut impl FnMut(&mut Ast) -> ReplaceAstResult, 151 | ) -> ReplaceAstResult { 152 | if !f(ast)? { 153 | return Ok(()); 154 | } 155 | match ast { 156 | Ast::Empty(..) 157 | | Ast::Flags(..) 158 | | Ast::Literal(..) 159 | | Ast::Dot(..) 160 | | Ast::Assertion(..) 161 | | Ast::ClassUnicode(..) 162 | | Ast::ClassPerl(..) 163 | | Ast::ClassBracketed(..) => Ok(()), 164 | Ast::Repetition(rep) => replace_ast(&mut rep.ast, f), 165 | Ast::Group(g) => replace_ast(&mut g.ast, f), 166 | Ast::Alternation(alt) => replace_asts(&mut alt.asts, f), 167 | Ast::Concat(c) => replace_asts(&mut c.asts, f), 168 | } 169 | } 170 | 171 | type ReplaceAstResult = Result; 172 | 173 | pub struct RegexInfer; 174 | impl DisplayFormat for RegexInfer { 175 | fn write(&self, f: &mut fmt::Formatter, value: &T) -> fmt::Result { 176 | T::fmt(value, f) 177 | } 178 | } 179 | impl FromStrFormat for RegexInfer { 180 | type Err = T::Err; 181 | fn parse(&self, s: &str) -> core::result::Result { 182 | s.parse() 183 | } 184 | fn regex(&self) -> Option { 185 | Some(T::from_str_regex()) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /parse-display/src/tests.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | #[allow(dead_code)] 3 | fn test_crate() { 4 | #[derive(crate::Display)] 5 | #[display(crate = crate)] 6 | struct TestDisplay(u32); 7 | 8 | #[cfg(feature = "std")] 9 | #[derive(crate::FromStr)] 10 | #[display(crate = crate)] 11 | struct TestFromStr(u32); 12 | } 13 | 14 | mod my_mod { 15 | #[allow(unused_imports)] 16 | pub use crate as my_crate; 17 | } 18 | 19 | #[test] 20 | #[allow(dead_code)] 21 | fn test_crate_mod() { 22 | #[derive(crate::Display)] 23 | #[display(crate = my_mod::my_crate)] 24 | struct TestDisplay(u32); 25 | 26 | #[cfg(feature = "std")] 27 | #[derive(crate::FromStr)] 28 | #[display(crate = my_mod::my_crate)] 29 | struct TestFromStr(u32); 30 | } 31 | -------------------------------------------------------------------------------- /parse-display/tests/both.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "std")] 2 | 3 | use parse_display::*; 4 | use std::fmt::Debug; 5 | use std::fmt::Display; 6 | use std::str::FromStr; 7 | 8 | #[test] 9 | fn both_newtype() { 10 | #[derive(Display, FromStr, Debug, PartialEq)] 11 | struct TestStruct(u32); 12 | 13 | assert_both("12", TestStruct(12)); 14 | } 15 | 16 | #[test] 17 | fn both_struct_format() { 18 | #[derive(Display, FromStr, Debug, PartialEq)] 19 | #[display("{a},{b}")] 20 | struct TestStruct { 21 | a: u32, 22 | b: u32, 23 | } 24 | assert_both("12,50", TestStruct { a: 12, b: 50 }); 25 | } 26 | 27 | fn assert_both(s: &str, value: T) 28 | where 29 | ::Err: Display, 30 | { 31 | let value_display = format!("{value}"); 32 | assert_eq!(value_display, s); 33 | 34 | match s.parse::() { 35 | Ok(a) => assert_eq!(a, value, "input = \"{s}\""), 36 | Err(e) => panic!("\"{s}\" parse failed. ({e})"), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /parse-display/tests/clippy_self.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::use_self)] 2 | 3 | use parse_display::*; 4 | 5 | #[test] 6 | fn clippy_use_self() { 7 | #[derive(FromStr)] 8 | enum Foo { 9 | Bar, 10 | Baz, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | #[ignore] 3 | fn compile_fail() { 4 | trybuild::TestCases::new().compile_fail("tests/compile_fail/*/*.rs") 5 | } 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/invalid_format.rs: -------------------------------------------------------------------------------- 1 | use parse_display::Display; 2 | #[derive(Display)] 3 | #[display("{x")] 4 | struct TestStruct; 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/invalid_format.stderr: -------------------------------------------------------------------------------- 1 | error: invalid display format. 2 | --> tests/compile_fail/display/invalid_format.rs:3:11 3 | | 4 | 3 | #[display("{x")] 5 | | ^^^^ 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/invalid_format_spec.rs: -------------------------------------------------------------------------------- 1 | use parse_display::Display; 2 | #[derive(Display)] 3 | #[display("{0:y}")] 4 | struct TestStruct(u32); 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/invalid_format_spec.stderr: -------------------------------------------------------------------------------- 1 | error: unknown format trait `y` 2 | --> tests/compile_fail/display/invalid_format_spec.rs:3:11 3 | | 4 | 3 | #[display("{0:y}")] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/invalid_with_enum.rs: -------------------------------------------------------------------------------- 1 | use parse_display::Display; 2 | 3 | #[derive(Display)] 4 | enum X { 5 | #[display("{0}")] 6 | A(#[display(with = unknown)] u32), 7 | B, 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/invalid_with_enum.stderr: -------------------------------------------------------------------------------- 1 | error[E0425]: cannot find value `unknown` in this scope 2 | --> tests/compile_fail/display/invalid_with_enum.rs:6:24 3 | | 4 | 6 | A(#[display(with = unknown)] u32), 5 | | ^^^^^^^ not found in this scope 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/invalid_with_struct.rs: -------------------------------------------------------------------------------- 1 | use parse_display::Display; 2 | 3 | #[derive(Display)] 4 | struct X { 5 | #[display(with = unknown)] 6 | a: u32, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/invalid_with_struct.stderr: -------------------------------------------------------------------------------- 1 | error[E0425]: cannot find value `unknown` in this scope 2 | --> tests/compile_fail/display/invalid_with_struct.rs:5:22 3 | | 4 | 5 | #[display(with = unknown)] 5 | | ^^^^^^^ not found in this scope 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/struct_empty_field_ident.rs: -------------------------------------------------------------------------------- 1 | use parse_display::Display; 2 | 3 | #[derive(Display)] 4 | #[display("{}")] 5 | struct TestStruct; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/struct_empty_field_ident.stderr: -------------------------------------------------------------------------------- 1 | error: {} is not allowed in struct format. 2 | --> tests/compile_fail/display/struct_empty_field_ident.rs:4:11 3 | | 4 | 4 | #[display("{}")] 5 | | ^^^^ 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/struct_no_display_field.rs: -------------------------------------------------------------------------------- 1 | use parse_display::Display; 2 | 3 | #[derive(Display)] 4 | struct TestStruct { 5 | x: NoDisplay, 6 | } 7 | struct NoDisplay; 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/struct_no_display_field.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: `NoDisplay` doesn't implement `std::fmt::Display` 2 | --> tests/compile_fail/display/struct_no_display_field.rs:3:10 3 | | 4 | 3 | #[derive(Display)] 5 | | ^^^^^^^ `NoDisplay` cannot be formatted with the default formatter 6 | | 7 | = help: the trait `std::fmt::Display` is not implemented for `NoDisplay` 8 | = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead 9 | = note: this error originates in the macro `$crate::format_args` which comes from the expansion of the derive macro `Display` (in Nightly builds, run with -Z macro-backtrace for more info) 10 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/struct_no_display_field_format.rs: -------------------------------------------------------------------------------- 1 | use parse_display::Display; 2 | 3 | struct NoDisplay; 4 | 5 | #[derive(Display)] 6 | #[display("{x}")] 7 | struct NoDisplayFieldInFormat { 8 | x: NoDisplay, 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/struct_no_display_field_format.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: `NoDisplay` doesn't implement `std::fmt::Display` 2 | --> tests/compile_fail/display/struct_no_display_field_format.rs:6:11 3 | | 4 | 5 | #[derive(Display)] 5 | | ------- in this derive macro expansion 6 | 6 | #[display("{x}")] 7 | | ^^^^^ `NoDisplay` cannot be formatted with the default formatter 8 | | 9 | = help: the trait `std::fmt::Display` is not implemented for `NoDisplay` 10 | = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead 11 | = note: this error originates in the macro `$crate::format_args` which comes from the expansion of the derive macro `Display` (in Nightly builds, run with -Z macro-backtrace for more info) 12 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/struct_no_pointer_field.rs: -------------------------------------------------------------------------------- 1 | use parse_display::Display; 2 | 3 | #[derive(Display)] 4 | #[display("{x:p}")] 5 | struct TestStruct { 6 | x: u32, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/display/struct_no_pointer_field.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `u32: Pointer` is not satisfied 2 | --> tests/compile_fail/display/struct_no_pointer_field.rs:3:10 3 | | 4 | 3 | #[derive(Display)] 5 | | ^^^^^^^ the trait `Pointer` is not implemented for `u32` 6 | 4 | #[display("{x:p}")] 7 | | ------- required by a bound introduced by this call 8 | | 9 | note: required by a bound in `fmt_pointer` 10 | --> src/helpers.rs 11 | | 12 | | pub fn fmt_pointer(value: &T) -> impl fmt::Pointer + '_ { 13 | | ^^^^^^^^^^^^ required by this bound in `fmt_pointer` 14 | help: consider borrowing here 15 | | 16 | 3 | #[derive(&Display)] 17 | | + 18 | 3 | #[derive(&mut Display)] 19 | | ++++ 20 | 21 | error[E0277]: the trait bound `u32: Pointer` is not satisfied 22 | --> tests/compile_fail/display/struct_no_pointer_field.rs:4:11 23 | | 24 | 4 | #[display("{x:p}")] 25 | | ^^^^^^^ the trait `Pointer` is not implemented for `u32` 26 | | 27 | note: required by a bound in `fmt_pointer` 28 | --> src/helpers.rs 29 | | 30 | | pub fn fmt_pointer(value: &T) -> impl fmt::Pointer + '_ { 31 | | ^^^^^^^^^^^^ required by this bound in `fmt_pointer` 32 | 33 | error[E0277]: the trait bound `u32: Pointer` is not satisfied 34 | --> tests/compile_fail/display/struct_no_pointer_field.rs:4:11 35 | | 36 | 3 | #[derive(Display)] 37 | | ------- in this derive macro expansion 38 | 4 | #[display("{x:p}")] 39 | | ^^^^^^^ the trait `Pointer` is not implemented for `u32` 40 | | 41 | note: required by a bound in `fmt_pointer` 42 | --> src/helpers.rs 43 | | 44 | | pub fn fmt_pointer(value: &T) -> impl fmt::Pointer + '_ { 45 | | ^^^^^^^^^^^^ required by this bound in `fmt_pointer` 46 | = note: this error originates in the macro `$crate::format_args` which comes from the expansion of the derive macro `Display` (in Nightly builds, run with -Z macro-backtrace for more info) 47 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/enum_default.rs: -------------------------------------------------------------------------------- 1 | use parse_display::FromStr; 2 | 3 | #[derive(FromStr)] 4 | #[from_str(default)] 5 | enum TestEnum {} 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/enum_default.stderr: -------------------------------------------------------------------------------- 1 | error: `#[from_str(default)]` cannot be specified for enum. 2 | --> tests/compile_fail/from_str/enum_default.rs:4:12 3 | | 4 | 4 | #[from_str(default)] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/invalid_with.rs: -------------------------------------------------------------------------------- 1 | use parse_display::FromStr; 2 | 3 | #[derive(FromStr, Debug, PartialEq)] 4 | struct X { 5 | #[from_str(with = "not impl FromStrFormat")] 6 | x: u8, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/invalid_with.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `&'static str: FromStrFormat` is not satisfied 2 | --> tests/compile_fail/from_str/invalid_with.rs:5:23 3 | | 4 | 5 | #[from_str(with = "not impl FromStrFormat")] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromStrFormat` is not implemented for `&'static str` 6 | | 7 | = help: the trait `FromStrFormat` is implemented for `RegexInfer` 8 | = note: required for the cast from `&&'static str` to `&dyn FromStrFormat` 9 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/no_field_in_format.rs: -------------------------------------------------------------------------------- 1 | use parse_display::FromStr; 2 | 3 | #[derive(FromStr)] 4 | #[display("abc")] 5 | struct TestStruct { 6 | x: u8, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/no_field_in_format.stderr: -------------------------------------------------------------------------------- 1 | error: field `x` is not appear in format. 2 | --> tests/compile_fail/from_str/no_field_in_format.rs:4:11 3 | | 4 | 4 | #[display("abc")] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/no_field_in_regex.rs: -------------------------------------------------------------------------------- 1 | use parse_display::FromStr; 2 | 3 | #[derive(FromStr)] 4 | #[from_str(regex = "abc")] 5 | struct TestStruct { 6 | x: u8, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/no_field_in_regex.stderr: -------------------------------------------------------------------------------- 1 | error: field `x` is not appear in format. 2 | --> tests/compile_fail/from_str/no_field_in_regex.rs:4:20 3 | | 4 | 4 | #[from_str(regex = "abc")] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/opt_with_not_option.rs: -------------------------------------------------------------------------------- 1 | use parse_display::FromStr; 2 | 3 | #[derive(FromStr, PartialEq, Debug)] 4 | struct X { 5 | #[display("a={}", opt)] 6 | a: u32, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/opt_with_not_option.stderr: -------------------------------------------------------------------------------- 1 | error: field `a` is not a option type. 2 | --> tests/compile_fail/from_str/opt_with_not_option.rs:6:8 3 | | 4 | 6 | a: u32, 5 | | ^^^ 6 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/struct_empty_capture_name.rs: -------------------------------------------------------------------------------- 1 | use parse_display::FromStr; 2 | 3 | #[derive(FromStr)] 4 | #[from_str(regex = "(?P<>)")] 5 | struct TestStruct; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /parse-display/tests/compile_fail/from_str/struct_empty_capture_name.stderr: -------------------------------------------------------------------------------- 1 | error: `(?P<>)` (empty capture name) is not allowed in struct's regex. 2 | --> tests/compile_fail/from_str/struct_empty_capture_name.rs:4:20 3 | | 4 | 4 | #[from_str(regex = "(?P<>)")] 5 | | ^^^^^^^^ 6 | -------------------------------------------------------------------------------- /parse-display/tests/display.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::pattern_type_mismatch)] 2 | #![no_std] 3 | extern crate alloc; 4 | use core::{fmt::LowerHex, mem::transmute}; 5 | 6 | use alloc::format; 7 | use parse_display::*; 8 | 9 | #[test] 10 | fn display_newtype() { 11 | #[derive(Display)] 12 | struct TestStruct(u8); 13 | 14 | assert_display(TestStruct(10), "10"); 15 | } 16 | 17 | #[test] 18 | fn display_str() { 19 | #[derive(Display)] 20 | #[display("abcde")] 21 | struct TestStruct; 22 | 23 | assert_display(TestStruct, "abcde"); 24 | } 25 | 26 | #[test] 27 | fn display_struct_field() { 28 | #[derive(Display)] 29 | #[display("{a} --- {b}")] 30 | struct TestStruct { 31 | a: u32, 32 | b: u32, 33 | } 34 | 35 | assert_display(TestStruct { a: 1, b: 2 }, "1 --- 2"); 36 | } 37 | 38 | #[test] 39 | fn display_struct_field_raw() { 40 | #[derive(Display)] 41 | #[display("{a},{b}")] 42 | struct TestStruct { 43 | r#a: u32, 44 | b: u32, 45 | } 46 | 47 | assert_display(TestStruct { a: 1, b: 2 }, "1,2"); 48 | } 49 | 50 | #[test] 51 | fn display_struct_field_raw_keyword() { 52 | #[derive(Display)] 53 | #[display("{fn},{b}")] 54 | struct TestStruct { 55 | r#fn: u32, 56 | b: u32, 57 | } 58 | 59 | assert_display(TestStruct { r#fn: 1, b: 2 }, "1,2"); 60 | } 61 | 62 | #[test] 63 | fn display_struct_field_with_parameter() { 64 | #[derive(Display)] 65 | #[display("{a:<4},{b}")] 66 | struct TestStruct { 67 | a: u32, 68 | b: u32, 69 | } 70 | 71 | assert_display(TestStruct { a: 1, b: 2 }, "1 ,2"); 72 | } 73 | 74 | #[test] 75 | fn display_struct_nested_field() { 76 | #[derive(Display)] 77 | #[display("{a.y},{b.x}")] 78 | struct TestStruct { 79 | a: TestStruct2, 80 | b: TestStruct2, 81 | } 82 | 83 | struct TestStruct2 { 84 | x: u32, 85 | y: u32, 86 | } 87 | 88 | let value = TestStruct { 89 | a: TestStruct2 { x: 1, y: 2 }, 90 | b: TestStruct2 { x: 3, y: 4 }, 91 | }; 92 | 93 | assert_display(value, "2,3"); 94 | } 95 | 96 | #[test] 97 | fn display_struct_nested_field_raw_keyword() { 98 | #[derive(Display)] 99 | #[display("{fn.fn},{b.y}")] 100 | struct TestStruct { 101 | r#fn: TestStruct2, 102 | b: TestStruct2, 103 | } 104 | 105 | struct TestStruct2 { 106 | r#fn: u32, 107 | y: u32, 108 | } 109 | 110 | let value = TestStruct { 111 | r#fn: TestStruct2 { r#fn: 1, y: 2 }, 112 | b: TestStruct2 { r#fn: 3, y: 4 }, 113 | }; 114 | 115 | assert_display(value, "1,4"); 116 | } 117 | 118 | #[test] 119 | fn display_tuple_struct_field() { 120 | #[derive(Display)] 121 | #[display("{1},{0}")] 122 | struct TestStruct(u32, u32); 123 | 124 | assert_display(TestStruct(10, 20), "20,10"); 125 | } 126 | 127 | #[test] 128 | #[allow(dead_code)] 129 | fn display_struct_field_attribute() { 130 | #[derive(Display)] 131 | #[display("{a},{b}")] 132 | struct TestStruct { 133 | #[display("AAAA")] 134 | a: u32, 135 | b: u32, 136 | } 137 | assert_display(TestStruct { a: 1, b: 2 }, "AAAA,2"); 138 | } 139 | 140 | #[test] 141 | fn display_struct_field_attribute_var() { 142 | #[derive(Display)] 143 | #[display("{a},{b}")] 144 | struct TestStruct { 145 | #[display("{x}+{y}")] 146 | a: TestStruct2, 147 | #[display("{x}-{y}")] 148 | b: TestStruct2, 149 | } 150 | 151 | struct TestStruct2 { 152 | x: u32, 153 | y: u32, 154 | } 155 | 156 | let value = TestStruct { 157 | a: TestStruct2 { x: 1, y: 2 }, 158 | b: TestStruct2 { x: 3, y: 4 }, 159 | }; 160 | 161 | assert_display(value, "1+2,3-4"); 162 | } 163 | 164 | #[test] 165 | fn display_struct_field_attribute_var_nested() { 166 | #[derive(Display)] 167 | #[display("__{a}")] 168 | struct TestStruct { 169 | #[display("{x.l}+{x.m}")] 170 | a: TestStruct2, 171 | } 172 | 173 | struct TestStruct2 { 174 | x: TestStruct3, 175 | } 176 | struct TestStruct3 { 177 | l: u32, 178 | m: u32, 179 | } 180 | 181 | let value = TestStruct { 182 | a: TestStruct2 { 183 | x: TestStruct3 { l: 10, m: 20 }, 184 | }, 185 | }; 186 | 187 | assert_display(value, "__10+20"); 188 | } 189 | 190 | #[test] 191 | #[allow(dead_code)] 192 | fn display_struct_field_attribute_self() { 193 | #[derive(Display)] 194 | #[display("{a},{b}")] 195 | struct TestStruct { 196 | #[display("_{}_")] 197 | a: u32, 198 | b: u32, 199 | } 200 | assert_display(TestStruct { a: 1, b: 2 }, "_1_,2"); 201 | } 202 | 203 | #[test] 204 | fn display_struct_field_attribute_self_hex() { 205 | #[derive(Display)] 206 | #[display("{a},{b}")] 207 | struct TestStruct { 208 | #[display("_{:X}_")] 209 | a: u32, 210 | b: u32, 211 | } 212 | assert_display(TestStruct { a: 10, b: 2 }, "_A_,2"); 213 | } 214 | 215 | #[test] 216 | fn display_struct_field_another_attribute() { 217 | #[derive(Display)] 218 | #[display("{a},{b}")] 219 | struct TestStruct { 220 | #[allow(dead_code)] 221 | a: u32, 222 | b: u32, 223 | } 224 | assert_display(TestStruct { a: 1, b: 2 }, "1,2"); 225 | } 226 | 227 | #[test] 228 | fn display_tuple_struct_nested_field() { 229 | #[derive(Display)] 230 | #[display("{1.1},{1.0},{0}")] 231 | struct TestStruct(u32, (u32, u32)); 232 | 233 | assert_display(TestStruct(10, (20, 30)), "30,20,10"); 234 | } 235 | 236 | #[test] 237 | fn display_tuple_struct_attribute() { 238 | #[derive(Display)] 239 | #[display("{0},{1}")] 240 | struct TestStruct(#[display("AAA-{}")] u32, u32); 241 | 242 | assert_display(TestStruct(10, 20), "AAA-10,20"); 243 | } 244 | 245 | #[test] 246 | fn display_enum() { 247 | #[derive(Display)] 248 | enum TestEnum { 249 | AbcDef, 250 | XyzXyz, 251 | } 252 | assert_display(TestEnum::AbcDef, "AbcDef"); 253 | assert_display(TestEnum::XyzXyz, "XyzXyz"); 254 | } 255 | 256 | #[test] 257 | fn display_enum_lower_snake_case() { 258 | #[derive(Display)] 259 | #[display(style = "snake_case")] 260 | enum TestEnum { 261 | AbcDef, 262 | XyzXyz, 263 | Abc1, 264 | Abc1Abc2, 265 | Xxx1xxx, 266 | _Xxx, 267 | } 268 | assert_display(TestEnum::AbcDef, "abc_def"); 269 | assert_display(TestEnum::XyzXyz, "xyz_xyz"); 270 | assert_display(TestEnum::Abc1, "abc1"); 271 | assert_display(TestEnum::Abc1Abc2, "abc1_abc2"); 272 | assert_display(TestEnum::Xxx1xxx, "xxx1xxx"); 273 | assert_display(TestEnum::_Xxx, "xxx"); 274 | } 275 | 276 | #[test] 277 | fn display_enum_upper_snake_case() { 278 | #[derive(Display)] 279 | #[display(style = "SNAKE_CASE")] 280 | enum TestEnum { 281 | AbcDef, 282 | XyzXyz, 283 | Abc1, 284 | Abc1Abc2, 285 | Xxx1xxx, 286 | _Xxx, 287 | } 288 | assert_display(TestEnum::AbcDef, "ABC_DEF"); 289 | assert_display(TestEnum::XyzXyz, "XYZ_XYZ"); 290 | assert_display(TestEnum::Abc1, "ABC1"); 291 | assert_display(TestEnum::Abc1Abc2, "ABC1_ABC2"); 292 | assert_display(TestEnum::Xxx1xxx, "XXX1XXX"); 293 | assert_display(TestEnum::_Xxx, "XXX"); 294 | } 295 | 296 | #[test] 297 | fn display_enum_lower_camel_case() { 298 | #[derive(Display)] 299 | #[display(style = "camelCase")] 300 | enum TestEnum { 301 | AbcDef, 302 | XyzXyz, 303 | Abc1, 304 | Abc1Abc2, 305 | Xxx1xxx, 306 | _Xxx, 307 | } 308 | assert_display(TestEnum::AbcDef, "abcDef"); 309 | assert_display(TestEnum::XyzXyz, "xyzXyz"); 310 | assert_display(TestEnum::Abc1, "abc1"); 311 | assert_display(TestEnum::Abc1Abc2, "abc1Abc2"); 312 | assert_display(TestEnum::Xxx1xxx, "xxx1xxx"); 313 | assert_display(TestEnum::_Xxx, "xxx"); 314 | } 315 | 316 | #[test] 317 | fn display_enum_upper_camel_case() { 318 | #[derive(Display)] 319 | #[display(style = "CamelCase")] 320 | enum TestEnum { 321 | AbcDef, 322 | XyzXyz, 323 | Abc1, 324 | Abc1Abc2, 325 | Xxx1xxx, 326 | _Xxx, 327 | } 328 | assert_display(TestEnum::AbcDef, "AbcDef"); 329 | assert_display(TestEnum::XyzXyz, "XyzXyz"); 330 | assert_display(TestEnum::Abc1, "Abc1"); 331 | assert_display(TestEnum::Abc1Abc2, "Abc1Abc2"); 332 | assert_display(TestEnum::Xxx1xxx, "Xxx1xxx"); 333 | assert_display(TestEnum::_Xxx, "Xxx"); 334 | } 335 | 336 | #[test] 337 | fn display_enum_lower_kebab_case() { 338 | #[derive(Display)] 339 | #[display(style = "kebab-case")] 340 | enum TestEnum { 341 | AbcDef, 342 | XyzXyz, 343 | Abc1, 344 | Abc1Abc2, 345 | Xxx1xxx, 346 | _Xxx, 347 | } 348 | assert_display(TestEnum::AbcDef, "abc-def"); 349 | assert_display(TestEnum::XyzXyz, "xyz-xyz"); 350 | assert_display(TestEnum::Abc1, "abc1"); 351 | assert_display(TestEnum::Abc1Abc2, "abc1-abc2"); 352 | assert_display(TestEnum::Xxx1xxx, "xxx1xxx"); 353 | assert_display(TestEnum::_Xxx, "xxx"); 354 | } 355 | 356 | #[test] 357 | fn display_enum_upper_kebab_case() { 358 | #[derive(Display)] 359 | #[display(style = "KEBAB-CASE")] 360 | enum TestEnum { 361 | AbcDef, 362 | XyzXyz, 363 | Abc1, 364 | Abc1Abc2, 365 | Xxx1xxx, 366 | _Xxx, 367 | } 368 | assert_display(TestEnum::AbcDef, "ABC-DEF"); 369 | assert_display(TestEnum::XyzXyz, "XYZ-XYZ"); 370 | assert_display(TestEnum::Abc1, "ABC1"); 371 | assert_display(TestEnum::Abc1Abc2, "ABC1-ABC2"); 372 | assert_display(TestEnum::Xxx1xxx, "XXX1XXX"); 373 | assert_display(TestEnum::_Xxx, "XXX"); 374 | } 375 | 376 | #[test] 377 | fn display_enum_upper_title_case() { 378 | #[derive(Display)] 379 | #[display(style = "Title Case")] 380 | enum TestEnum { 381 | AbcDef, 382 | XyzXyz, 383 | Abc1, 384 | Abc1Abc2, 385 | Xxx1xxx, 386 | _Xxx, 387 | } 388 | assert_display(TestEnum::AbcDef, "Abc Def"); 389 | assert_display(TestEnum::XyzXyz, "Xyz Xyz"); 390 | assert_display(TestEnum::Abc1, "Abc1"); 391 | assert_display(TestEnum::Abc1Abc2, "Abc1 Abc2"); 392 | assert_display(TestEnum::Xxx1xxx, "Xxx1xxx"); 393 | assert_display(TestEnum::_Xxx, "Xxx"); 394 | } 395 | 396 | #[test] 397 | fn display_enum_upper_title_case_upper() { 398 | #[derive(Display)] 399 | #[display(style = "TITLE CASE")] 400 | enum TestEnum { 401 | AbcDef, 402 | XyzXyz, 403 | Abc1, 404 | Abc1Abc2, 405 | Xxx1xxx, 406 | _Xxx, 407 | } 408 | assert_display(TestEnum::AbcDef, "ABC DEF"); 409 | assert_display(TestEnum::XyzXyz, "XYZ XYZ"); 410 | assert_display(TestEnum::Abc1, "ABC1"); 411 | assert_display(TestEnum::Abc1Abc2, "ABC1 ABC2"); 412 | assert_display(TestEnum::Xxx1xxx, "XXX1XXX"); 413 | assert_display(TestEnum::_Xxx, "XXX"); 414 | } 415 | 416 | #[test] 417 | fn display_enum_upper_title_case_lower() { 418 | #[derive(Display)] 419 | #[display(style = "title case")] 420 | enum TestEnum { 421 | AbcDef, 422 | XyzXyz, 423 | Abc1, 424 | Abc1Abc2, 425 | Xxx1xxx, 426 | _Xxx, 427 | } 428 | assert_display(TestEnum::AbcDef, "abc def"); 429 | assert_display(TestEnum::XyzXyz, "xyz xyz"); 430 | assert_display(TestEnum::Abc1, "abc1"); 431 | assert_display(TestEnum::Abc1Abc2, "abc1 abc2"); 432 | assert_display(TestEnum::Xxx1xxx, "xxx1xxx"); 433 | assert_display(TestEnum::_Xxx, "xxx"); 434 | } 435 | 436 | #[test] 437 | fn display_enum_upper_title_case_head() { 438 | #[derive(Display)] 439 | #[display(style = "Title case")] 440 | enum TestEnum { 441 | AbcDef, 442 | XyzXyz, 443 | Abc1, 444 | Abc1Abc2, 445 | Xxx1xxx, 446 | _Xxx, 447 | } 448 | assert_display(TestEnum::AbcDef, "Abc def"); 449 | assert_display(TestEnum::XyzXyz, "Xyz xyz"); 450 | assert_display(TestEnum::Abc1, "Abc1"); 451 | assert_display(TestEnum::Abc1Abc2, "Abc1 abc2"); 452 | assert_display(TestEnum::Xxx1xxx, "Xxx1xxx"); 453 | assert_display(TestEnum::_Xxx, "Xxx"); 454 | } 455 | 456 | #[test] 457 | fn display_enum_lower_case() { 458 | #[derive(Display)] 459 | #[display(style = "lowercase")] 460 | enum TestEnum { 461 | AbcDef, 462 | XyzXyz, 463 | Abc1, 464 | Abc1Abc2, 465 | Xxx1xxx, 466 | _Xxx, 467 | } 468 | assert_display(TestEnum::AbcDef, "abcdef"); 469 | assert_display(TestEnum::XyzXyz, "xyzxyz"); 470 | assert_display(TestEnum::Abc1, "abc1"); 471 | assert_display(TestEnum::Abc1Abc2, "abc1abc2"); 472 | assert_display(TestEnum::Xxx1xxx, "xxx1xxx"); 473 | assert_display(TestEnum::_Xxx, "xxx"); 474 | } 475 | 476 | #[test] 477 | fn display_enum_upper_case() { 478 | #[derive(Display)] 479 | #[display(style = "UPPERCASE")] 480 | enum TestEnum { 481 | AbcDef, 482 | XyzXyz, 483 | Abc1, 484 | Abc1Abc2, 485 | Xxx1xxx, 486 | _Xxx, 487 | } 488 | assert_display(TestEnum::AbcDef, "ABCDEF"); 489 | assert_display(TestEnum::XyzXyz, "XYZXYZ"); 490 | assert_display(TestEnum::Abc1, "ABC1"); 491 | assert_display(TestEnum::Abc1Abc2, "ABC1ABC2"); 492 | assert_display(TestEnum::Xxx1xxx, "XXX1XXX"); 493 | assert_display(TestEnum::_Xxx, "XXX"); 494 | } 495 | 496 | #[test] 497 | fn display_enum_none() { 498 | #[derive(Display)] 499 | #[display(style = "none")] 500 | enum TestEnum { 501 | AbcDef, 502 | XyzXyz, 503 | Abc1, 504 | Abc1Abc2, 505 | Xxx1xxx, 506 | _Xxx, 507 | } 508 | assert_display(TestEnum::AbcDef, "AbcDef"); 509 | assert_display(TestEnum::XyzXyz, "XyzXyz"); 510 | assert_display(TestEnum::Abc1, "Abc1"); 511 | assert_display(TestEnum::Abc1Abc2, "Abc1Abc2"); 512 | assert_display(TestEnum::Xxx1xxx, "Xxx1xxx"); 513 | assert_display(TestEnum::_Xxx, "_Xxx"); 514 | } 515 | 516 | #[test] 517 | fn display_enum_common_format() { 518 | #[derive(Display)] 519 | #[display("{0}")] 520 | enum TestEnum { 521 | A(u32), 522 | B(bool), 523 | } 524 | 525 | assert_display(TestEnum::A(10), "10"); 526 | assert_display(TestEnum::B(true), "true"); 527 | } 528 | 529 | #[test] 530 | fn display_enum_common_format_variant_name() { 531 | #[derive(Display)] 532 | #[display("{}-{0}")] 533 | enum TestEnum { 534 | A(u32), 535 | B(bool), 536 | } 537 | 538 | assert_display(TestEnum::A(10), "A-10"); 539 | assert_display(TestEnum::B(false), "B-false"); 540 | } 541 | 542 | #[test] 543 | fn display_enum_variant_format() { 544 | #[derive(Display)] 545 | enum TestEnum { 546 | #[display("AAA")] 547 | A(u32), 548 | 549 | #[display("BBB")] 550 | B(bool), 551 | } 552 | 553 | assert_display(TestEnum::A(10), "AAA"); 554 | assert_display(TestEnum::B(false), "BBB"); 555 | } 556 | 557 | #[test] 558 | fn display_enum_variant_format_tuple_var() { 559 | #[derive(Display)] 560 | enum TestEnum { 561 | #[display("AAA-{0}")] 562 | A(u32), 563 | 564 | #[display("BBB+{0}")] 565 | B(bool), 566 | } 567 | 568 | assert_display(TestEnum::A(10), "AAA-10"); 569 | assert_display(TestEnum::B(true), "BBB+true"); 570 | } 571 | 572 | #[test] 573 | fn display_enum_variant_format_record_var() { 574 | #[derive(Display)] 575 | enum TestEnum { 576 | #[display("x={x},y={y}")] 577 | A { x: u32, y: u32 }, 578 | } 579 | assert_display(TestEnum::A { x: 10, y: 20 }, "x=10,y=20"); 580 | } 581 | 582 | #[test] 583 | fn display_enum_variant_format_record_var_f() { 584 | #[derive(Display)] 585 | enum TestEnum { 586 | #[display("f={f}")] 587 | A { f: u32 }, 588 | } 589 | assert_display(TestEnum::A { f: 10 }, "f=10"); 590 | } 591 | 592 | #[test] 593 | fn display_enum_variant_format_record_var_keyword() { 594 | #[derive(Display)] 595 | enum TestEnum { 596 | #[display("fn={fn}")] 597 | A { r#fn: u32 }, 598 | } 599 | assert_display(TestEnum::A { r#fn: 10 }, "fn=10"); 600 | } 601 | 602 | #[test] 603 | fn display_enum_field_format() { 604 | #[derive(Display)] 605 | enum TestEnum { 606 | #[display("{} = {x}")] 607 | A { 608 | #[display("---{}")] 609 | x: u32, 610 | }, 611 | } 612 | assert_display(TestEnum::A { x: 10 }, "A = ---10"); 613 | } 614 | 615 | #[test] 616 | fn display_enum_field_format_deep() { 617 | #[derive(Display)] 618 | enum TestEnum { 619 | #[display("{} = {x}")] 620 | A { 621 | #[display("---{l}")] 622 | x: TestStruct, 623 | }, 624 | } 625 | 626 | struct TestStruct { 627 | l: u32, 628 | } 629 | 630 | assert_display( 631 | TestEnum::A { 632 | x: TestStruct { l: 20 }, 633 | }, 634 | "A = ---20", 635 | ); 636 | } 637 | 638 | #[test] 639 | fn display_enum_field_format_deep_noncopy() { 640 | #[derive(Display)] 641 | enum TestEnum { 642 | #[display("{} = {x}")] 643 | A { 644 | #[display("---{l}")] 645 | x: TestStruct, 646 | }, 647 | } 648 | 649 | struct TestStruct { 650 | l: bool, 651 | } 652 | assert_display( 653 | TestEnum::A { 654 | x: TestStruct { l: true }, 655 | }, 656 | "A = ---true", 657 | ); 658 | } 659 | 660 | #[test] 661 | fn auto_bound_newtype() { 662 | #[derive(Display)] 663 | struct TestNewType(T); 664 | assert_display(TestNewType(10), "10"); 665 | } 666 | 667 | #[test] 668 | fn auto_bound_newtype_debug() { 669 | #[derive(Display)] 670 | #[display("{0:?}")] 671 | struct TestNewType(T); 672 | assert_display(TestNewType(10), "10"); 673 | } 674 | 675 | #[test] 676 | fn auto_bound_newtype_lower_hex() { 677 | #[derive(Display)] 678 | #[display("{0:#x}")] 679 | struct TestNewType(T); 680 | assert_display(TestNewType(10), "0xa"); 681 | } 682 | 683 | #[test] 684 | fn auto_bound_newtype_upper_hex() { 685 | #[derive(Display)] 686 | #[display("{0:#X}")] 687 | struct TestNewType(T); 688 | assert_display(TestNewType(10), "0xA"); 689 | } 690 | 691 | #[test] 692 | fn auto_bound_newtype_binary() { 693 | #[derive(Display)] 694 | #[display("{0:#b}")] 695 | struct TestNewType(T); 696 | assert_display(TestNewType(10), "0b1010"); 697 | } 698 | 699 | #[test] 700 | fn auto_bound_field() { 701 | #[derive(Display)] 702 | #[display("{a}")] 703 | struct TestStruct { 704 | #[display("___{}___")] 705 | a: T, 706 | } 707 | assert_display(TestStruct { a: 10 }, "___10___"); 708 | } 709 | 710 | #[test] 711 | fn auto_bound_enum() { 712 | #[derive(Display)] 713 | #[display("{0}")] 714 | enum TestEnum { 715 | VarA(T), 716 | } 717 | assert_display(TestEnum::VarA(10), "10"); 718 | } 719 | 720 | #[test] 721 | fn private_in_public_non_generic() { 722 | assert_display(TestStructPrivateInPublic(TestStructPrivate(5)), "5"); 723 | } 724 | 725 | #[derive(Display)] 726 | pub struct TestStructPrivateInPublic(TestStructPrivate); 727 | 728 | #[derive(Display)] 729 | struct TestStructPrivate(u8); 730 | 731 | #[test] 732 | fn private_in_public_generic() { 733 | assert_display( 734 | TestStructPrivateInPublicGeneric(TestStructPrivateGeneric(5)), 735 | "5", 736 | ); 737 | } 738 | 739 | #[derive(Display)] 740 | #[display(bound(T))] 741 | pub struct TestStructPrivateInPublicGeneric(TestStructPrivateGeneric); 742 | 743 | #[derive(Display)] 744 | struct TestStructPrivateGeneric(T); 745 | 746 | #[test] 747 | fn bound_predicate_struct() { 748 | #[derive(Display)] 749 | #[display(bound(T : Copy))] 750 | pub struct TestStructBoundPredicate(DisplayIfCopy); 751 | 752 | struct DisplayIfCopy(T); 753 | 754 | impl core::fmt::Display for DisplayIfCopy { 755 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 756 | write!(f, "this is display") 757 | } 758 | } 759 | assert_display( 760 | TestStructBoundPredicate(DisplayIfCopy(10)), 761 | "this is display", 762 | ); 763 | } 764 | 765 | #[test] 766 | fn bound_predicate_struct_x2() { 767 | #[derive(Display)] 768 | #[display("{a},{b}", bound(T1 : Copy, T2 : Copy))] 769 | pub struct TestStructBoundPredicate { 770 | a: DisplayIfCopy, 771 | b: DisplayIfCopy, 772 | } 773 | 774 | struct DisplayIfCopy(T); 775 | 776 | impl core::fmt::Display for DisplayIfCopy { 777 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 778 | write!(f, "this is display") 779 | } 780 | } 781 | assert_display( 782 | TestStructBoundPredicate { 783 | a: DisplayIfCopy(10), 784 | b: DisplayIfCopy(20), 785 | }, 786 | "this is display,this is display", 787 | ); 788 | } 789 | 790 | #[test] 791 | fn bound_predicate_struct_str() { 792 | #[derive(Display)] 793 | #[display(bound("T : Copy"))] 794 | pub struct TestStructBoundPredicate(DisplayIfCopy); 795 | 796 | struct DisplayIfCopy(T); 797 | 798 | impl core::fmt::Display for DisplayIfCopy { 799 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 800 | write!(f, "this is display") 801 | } 802 | } 803 | assert_display( 804 | TestStructBoundPredicate(DisplayIfCopy(10)), 805 | "this is display", 806 | ); 807 | } 808 | 809 | #[test] 810 | fn bound_type_enum() { 811 | assert_display(Outer::A(Inner(10)), "10"); 812 | #[derive(Display)] 813 | #[display("{0.0}", bound(T))] 814 | enum Outer { 815 | A(Inner), 816 | } 817 | struct Inner(T); 818 | } 819 | 820 | #[test] 821 | fn bound_type_generic() { 822 | assert_display(Outer(Inner(5)), "5"); 823 | 824 | #[derive(Display)] 825 | #[display(bound(Inner))] 826 | struct Outer(Inner); 827 | 828 | #[derive(Display)] 829 | struct Inner(T); 830 | } 831 | 832 | #[test] 833 | fn bound_type_generic_x2() { 834 | assert_display(Outer(Inner(5), Inner(10)), "5,10"); 835 | 836 | #[derive(Display)] 837 | #[display("{0},{1}",bound(Inner, Inner))] 838 | struct Outer(Inner, Inner); 839 | 840 | #[derive(Display)] 841 | struct Inner(T); 842 | } 843 | 844 | #[test] 845 | #[allow(dead_code)] 846 | fn bound_type_array() { 847 | #[derive(Display)] 848 | #[display(bound([T; 1]))] 849 | struct TestStruct { 850 | x: [T; 1], 851 | } 852 | } 853 | 854 | #[test] 855 | fn auto_bound_unused_field() { 856 | #[derive(Display)] 857 | #[display("{val_u8}")] 858 | #[allow(dead_code)] 859 | struct TestStruct { 860 | val_eq: T, 861 | val_u8: u8, 862 | } 863 | assert_display( 864 | TestStruct { 865 | val_eq: 0, 866 | val_u8: 1, 867 | }, 868 | "1", 869 | ) 870 | } 871 | 872 | #[test] 873 | fn bound_by_hand_with_auto() { 874 | pub struct Inner(T); 875 | 876 | #[derive(Display)] 877 | #[display("{0.0},{1}", bound(T1, ..))] 878 | pub struct Outer(Inner, T2); 879 | 880 | assert_display(Outer(Inner(10), 20), "10,20"); 881 | } 882 | 883 | #[deny(private_bounds)] 884 | #[test] 885 | fn bound_struct_field() { 886 | #[derive(Display)] 887 | struct Inner(T); 888 | #[derive(Display)] 889 | pub struct Outer(#[display(bound(T))] Inner); 890 | } 891 | #[allow(dead_code)] 892 | #[test] 893 | fn bound_enum_variant() { 894 | #[derive(Display)] 895 | #[display(bound(T : core::fmt::Display + Copy ))] 896 | pub struct Inner(T); 897 | #[derive(Display)] 898 | pub enum Outer { 899 | #[display("{0}", bound(T : core::fmt::Display + Copy))] 900 | A(Inner), 901 | } 902 | } 903 | 904 | #[allow(dead_code)] 905 | #[test] 906 | fn bound_enum_field() { 907 | #[derive(Display)] 908 | #[display(bound(T : core::fmt::Display + Copy ))] 909 | pub struct Inner(T); 910 | #[derive(Display)] 911 | pub enum Outer { 912 | #[display("{0}")] 913 | A(#[display(bound(T : core::fmt::Display + Copy))] Inner), 914 | } 915 | } 916 | 917 | #[test] 918 | fn doc_comment_struct() { 919 | /// doc 920 | #[derive(Display)] 921 | struct TestStruct { 922 | a: u8, 923 | } 924 | assert_display(TestStruct { a: 10 }, "10"); 925 | } 926 | 927 | #[test] 928 | fn doc_comment_struct_field() { 929 | #[derive(Display)] 930 | pub struct TestStruct { 931 | /// doc 932 | a: u8, 933 | } 934 | assert_display(TestStruct { a: 10 }, "10"); 935 | } 936 | 937 | #[test] 938 | fn doc_comment_enum() { 939 | /// doc 940 | #[derive(Display)] 941 | enum TestEnum { 942 | A, 943 | } 944 | assert_display(TestEnum::A, "A"); 945 | } 946 | 947 | #[test] 948 | fn doc_comment_variant() { 949 | #[derive(Display)] 950 | enum TestEnum { 951 | /// doc 952 | A, 953 | } 954 | assert_display(TestEnum::A, "A"); 955 | } 956 | 957 | #[test] 958 | fn attr_enum() { 959 | #[derive(Display)] 960 | #[non_exhaustive] 961 | enum TestEnum { 962 | A, 963 | } 964 | assert_display(TestEnum::A, "A"); 965 | } 966 | 967 | macro_rules! macro_rule_hygiene_test { 968 | () => { 969 | #[derive(Display)] 970 | struct HygieneTestType { 971 | x: $crate::U8Alias, 972 | } 973 | }; 974 | } 975 | 976 | type U8Alias = u8; 977 | #[test] 978 | fn macro_rule_hygiene() { 979 | macro_rule_hygiene_test!(); 980 | assert_display(HygieneTestType { x: 5 }, "5"); 981 | } 982 | 983 | #[test] 984 | fn format_spec_is_empty() { 985 | #[derive(Display)] 986 | #[display("{0}>")] 987 | struct TestStruct(u32); 988 | assert_display(TestStruct(10), "10>"); 989 | } 990 | 991 | #[test] 992 | fn dst_field() { 993 | #[derive(Display)] 994 | #[display("{0}")] 995 | #[repr(transparent)] 996 | struct DstField(str); 997 | 998 | let x: &DstField = unsafe { transmute("abc") }; 999 | assert_display(x, "abc"); 1000 | } 1001 | 1002 | #[test] 1003 | fn by_hex() { 1004 | #[derive(Display)] 1005 | #[display("{:#x}")] 1006 | struct TestStruct(u32); 1007 | 1008 | impl LowerHex for TestStruct { 1009 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 1010 | LowerHex::fmt(&self.0, f) 1011 | } 1012 | } 1013 | assert_display(TestStruct(10), "0xa"); 1014 | } 1015 | 1016 | #[test] 1017 | fn by_debug() { 1018 | #[derive(Display, Debug)] 1019 | #[display("{:?}")] 1020 | enum E { 1021 | A, 1022 | B(u8), 1023 | } 1024 | assert_display(E::A, &format!("{:?}", E::A)); 1025 | assert_display(E::B(10), &format!("{:?}", E::B(10))); 1026 | } 1027 | 1028 | #[test] 1029 | fn escape() { 1030 | #[derive(Display)] 1031 | #[display("{{")] 1032 | struct X; 1033 | assert_display(X, "{"); 1034 | 1035 | #[derive(Display)] 1036 | #[display("}}")] 1037 | struct Y; 1038 | assert_display(Y, "}"); 1039 | } 1040 | 1041 | #[test] 1042 | fn struct_field_pointer() { 1043 | #[derive(Display)] 1044 | #[display("{0:p}")] 1045 | #[allow(unused)] 1046 | struct X(*const u32); 1047 | let p: *const u32 = &0; 1048 | assert_display(X(p), &format!("{p:p}")); 1049 | } 1050 | 1051 | #[test] 1052 | fn struct_field_pointer_field_format() { 1053 | #[derive(Display)] 1054 | #[display("{0}")] 1055 | #[allow(unused)] 1056 | struct X(#[display("{:p}")] *const u32); 1057 | let p: *const u32 = &0; 1058 | assert_display(X(p), &format!("{p:p}")); 1059 | } 1060 | 1061 | #[test] 1062 | fn struct_field_pointer_deep() { 1063 | #[derive(Display)] 1064 | #[display("{0.0:p}")] 1065 | #[allow(unused)] 1066 | struct X(Y); 1067 | 1068 | struct Y(*const u32); 1069 | let p: *const u32 = &0; 1070 | assert_display(X(Y(p)), &format!("{p:p}")); 1071 | } 1072 | 1073 | #[test] 1074 | fn enum_field_pointer() { 1075 | #[derive(Display)] 1076 | #[allow(unused)] 1077 | enum X { 1078 | #[display("{0:p}")] 1079 | A(*const u32), 1080 | } 1081 | let p: *const u32 = &0; 1082 | assert_display(X::A(p), &format!("{p:p}")); 1083 | } 1084 | 1085 | #[test] 1086 | fn enum_field_pointer_field_format() { 1087 | #[derive(Display)] 1088 | #[allow(unused)] 1089 | enum X { 1090 | #[display("{0}")] 1091 | A(#[display("{:p}")] *const u32), 1092 | } 1093 | let p: *const u32 = &0; 1094 | assert_display(X::A(p), &format!("{p:p}")); 1095 | } 1096 | 1097 | #[test] 1098 | fn enum_field_pointer_deep() { 1099 | #[derive(Display)] 1100 | #[allow(unused)] 1101 | enum X { 1102 | #[display("{0.0:p}")] 1103 | A(Y), 1104 | } 1105 | struct Y(*const u32); 1106 | 1107 | let p: *const u32 = &0; 1108 | assert_display(X::A(Y(p)), &format!("{p:p}")); 1109 | } 1110 | 1111 | #[test] 1112 | fn with_through_formatting_parameters() { 1113 | struct F; 1114 | impl parse_display::DisplayFormat for F { 1115 | fn write(&self, f: &mut core::fmt::Formatter, value: &i32) -> core::fmt::Result { 1116 | core::fmt::Display::fmt(value, f) 1117 | } 1118 | } 1119 | #[derive(Display, Debug, PartialEq)] 1120 | #[display("{0:5}")] 1121 | struct X(#[display(with = F)] i32); 1122 | assert_display(X(10), &format!("{:5}", 10)); 1123 | } 1124 | 1125 | #[test] 1126 | fn use_type_parameter_in_with() { 1127 | struct Fmt { 1128 | _marker: core::marker::PhantomData, 1129 | } 1130 | impl Fmt { 1131 | fn new() -> Self { 1132 | Self { 1133 | _marker: core::marker::PhantomData, 1134 | } 1135 | } 1136 | } 1137 | impl parse_display::DisplayFormat for Fmt { 1138 | fn write(&self, f: &mut core::fmt::Formatter, value: &T) -> core::fmt::Result { 1139 | write!(f, "--{value}--") 1140 | } 1141 | } 1142 | 1143 | #[derive(Display, Debug, PartialEq)] 1144 | #[display("{0}")] 1145 | struct X(#[display(with = Fmt::new())] T); 1146 | assert_display(X(10), "--10--"); 1147 | } 1148 | 1149 | #[test] 1150 | fn opt() { 1151 | #[derive(Display)] 1152 | #[display("{a}")] 1153 | struct X { 1154 | #[display(opt)] 1155 | a: Option, 1156 | } 1157 | assert_display(X { a: Some(10) }, "10"); 1158 | assert_display(X { a: None }, ""); 1159 | } 1160 | 1161 | #[test] 1162 | fn opt_with_format() { 1163 | #[derive(Display)] 1164 | #[display("{a}")] 1165 | struct X { 1166 | #[display("a={}", opt)] 1167 | a: Option, 1168 | } 1169 | 1170 | assert_display(X { a: Some(10) }, "a=10"); 1171 | assert_display(X { a: None }, ""); 1172 | } 1173 | 1174 | #[test] 1175 | fn opt_member() { 1176 | struct A { 1177 | u: u8, 1178 | } 1179 | 1180 | #[derive(Display)] 1181 | #[display("{a}")] 1182 | struct X { 1183 | #[display("a={u}", opt)] 1184 | a: Option, 1185 | } 1186 | assert_display( 1187 | X { 1188 | a: Some(A { u: 10 }), 1189 | }, 1190 | "a=10", 1191 | ); 1192 | assert_display(X { a: None }, ""); 1193 | } 1194 | 1195 | #[test] 1196 | fn opt_generics() { 1197 | #[derive(Display)] 1198 | #[display("{a}")] 1199 | struct X { 1200 | #[display(opt)] 1201 | a: Option, 1202 | } 1203 | assert_display(X { a: Some(10) }, "10"); 1204 | assert_display(X { a: None:: }, ""); 1205 | } 1206 | 1207 | #[track_caller] 1208 | fn assert_display(value: T, display: &str) { 1209 | let value_display = format!("{value}"); 1210 | assert_eq!(value_display, display); 1211 | } 1212 | -------------------------------------------------------------------------------- /parse-display/tests/display_std.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{self, Formatter}, 3 | path::{Path, PathBuf}, 4 | }; 5 | 6 | use parse_display::{Display, DisplayFormat}; 7 | 8 | struct PathFormat; 9 | 10 | impl> DisplayFormat for PathFormat { 11 | fn write(&self, f: &mut Formatter, value: &T) -> fmt::Result { 12 | write!(f, "{}", value.as_ref().display()) 13 | } 14 | } 15 | fn path() -> PathFormat { 16 | PathFormat 17 | } 18 | 19 | #[test] 20 | fn with_path() { 21 | #[derive(Display)] 22 | #[display("{0}")] 23 | struct X<'a>(#[display(with = path())] &'a Path); 24 | assert_display(X(Path::new("/tmp")), "/tmp"); 25 | } 26 | 27 | #[test] 28 | fn with_path_buf() { 29 | #[derive(Display)] 30 | #[display("{0}")] 31 | struct X(#[display(with = path())] PathBuf); 32 | assert_display(X(PathBuf::from("/tmp")), "/tmp"); 33 | } 34 | 35 | fn assert_display(value: T, display: &str) { 36 | let value_display = format!("{value}"); 37 | assert_eq!(value_display, display); 38 | } 39 | -------------------------------------------------------------------------------- /parse-display/tests/from_str_regex.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "std")] 2 | 3 | use core::f32; 4 | use std::fmt::Display; 5 | 6 | use parse_display::FromStrRegex; 7 | use regex::Regex; 8 | 9 | #[track_caller] 10 | fn assert_match_str(s: &str, should_match: bool) { 11 | let re = T::from_str_regex(); 12 | let msg = format!( 13 | "type = {}, from_str_regex = {}", 14 | std::any::type_name::(), 15 | re 16 | ); 17 | assert_eq!(Regex::new(&re).unwrap().is_match(s), should_match, "{msg}"); 18 | 19 | let is_match = s.parse::().is_ok(); 20 | assert_eq!(is_match, should_match, "{msg}"); 21 | } 22 | 23 | #[track_caller] 24 | fn assert_match(value: T) { 25 | assert_match_str::(&value.to_string(), true); 26 | } 27 | 28 | #[test] 29 | fn test_bool() { 30 | assert_match(true); 31 | assert_match(false); 32 | 33 | assert_match_str::("TRUE", false); 34 | assert_match_str::("False", false); 35 | assert_match_str::("1", false); 36 | assert_match_str::("0", false); 37 | assert_match_str::("yes", false); 38 | assert_match_str::("", false); 39 | } 40 | 41 | #[test] 42 | fn test_uint() { 43 | assert_match(0u8); 44 | assert_match(u8::MIN); 45 | assert_match(u8::MAX); 46 | assert_match_str::("a", false); 47 | assert_match_str::("", false); 48 | 49 | assert_match(0u16); 50 | assert_match(u16::MIN); 51 | assert_match(u16::MAX); 52 | assert_match_str::("a", false); 53 | assert_match_str::("", false); 54 | 55 | assert_match(0u32); 56 | assert_match(u32::MIN); 57 | assert_match(u32::MAX); 58 | assert_match_str::("a", false); 59 | assert_match_str::("", false); 60 | 61 | assert_match(0u64); 62 | assert_match(u64::MIN); 63 | assert_match(u64::MAX); 64 | assert_match_str::("a", false); 65 | assert_match_str::("", false); 66 | 67 | assert_match(0u128); 68 | assert_match(u128::MIN); 69 | assert_match(u128::MAX); 70 | assert_match_str::("a", false); 71 | assert_match_str::("", false); 72 | } 73 | 74 | #[test] 75 | fn test_sint() { 76 | assert_match(0i8); 77 | assert_match(i8::MIN); 78 | assert_match(i8::MAX); 79 | assert_match_str::("a", false); 80 | assert_match_str::("", false); 81 | 82 | assert_match(0i16); 83 | assert_match(i16::MIN); 84 | assert_match(i16::MAX); 85 | assert_match_str::("a", false); 86 | assert_match_str::("", false); 87 | 88 | assert_match(0i32); 89 | assert_match(i32::MIN); 90 | assert_match(i32::MAX); 91 | assert_match_str::("a", false); 92 | assert_match_str::("", false); 93 | 94 | assert_match(0i64); 95 | assert_match(i64::MIN); 96 | assert_match(i64::MAX); 97 | assert_match_str::("a", false); 98 | assert_match_str::("", false); 99 | 100 | assert_match(0i128); 101 | assert_match(i128::MIN); 102 | assert_match(i128::MAX); 103 | assert_match_str::("a", false); 104 | assert_match_str::("", false); 105 | } 106 | 107 | #[test] 108 | fn test_f() { 109 | assert_match(0.0f32); 110 | assert_match(-0.0f32); 111 | assert_match(f32::MIN); 112 | assert_match(f32::MAX); 113 | assert_match(f32::MIN_POSITIVE); 114 | assert_match(f32::EPSILON); 115 | assert_match(f32::INFINITY); 116 | assert_match(f32::NEG_INFINITY); 117 | assert_match(f32::NAN); 118 | assert_match_str::("1.0e12", true); 119 | assert_match_str::("1.0E12", true); 120 | assert_match_str::("Inf", true); 121 | assert_match_str::("inf", true); 122 | assert_match_str::("NAN", true); 123 | assert_match_str::("NaN", true); 124 | assert_match_str::("nan", true); 125 | assert_match_str::("a", false); 126 | assert_match_str::("", false); 127 | 128 | assert_match(0.0f64); 129 | assert_match(-0.0f64); 130 | assert_match(f64::MIN); 131 | assert_match(f64::MAX); 132 | assert_match(f64::MIN_POSITIVE); 133 | assert_match(f64::EPSILON); 134 | assert_match(f64::INFINITY); 135 | assert_match(f64::NEG_INFINITY); 136 | assert_match(f64::NAN); 137 | assert_match_str::("1.0e12", true); 138 | assert_match_str::("1.0E12", true); 139 | assert_match_str::("Inf", true); 140 | assert_match_str::("inf", true); 141 | assert_match_str::("NAN", true); 142 | assert_match_str::("NaN", true); 143 | assert_match_str::("nan", true); 144 | assert_match_str::("a", false); 145 | assert_match_str::("", false); 146 | } 147 | 148 | #[test] 149 | fn test_string() { 150 | assert_match(String::new()); 151 | assert_match(String::from("abc")); 152 | assert_match(String::from(" ")); 153 | assert_match(String::from("\n\t\r")); 154 | } 155 | -------------------------------------------------------------------------------- /parse-display/tests/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------