├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASES.md ├── src ├── ascii_char.rs ├── ascii_str.rs ├── ascii_string.rs ├── free_functions.rs ├── lib.rs └── serialization │ ├── ascii_char.rs │ ├── ascii_str.rs │ ├── ascii_string.rs │ └── mod.rs └── tests.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | test: 11 | name: Test with Rust ${{ matrix.rust }} 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | rust: [1.56.1, stable, beta, nightly] 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: hecrj/setup-rust-action@v1 20 | with: 21 | rust-version: ${{ matrix.rust }} 22 | - run: cargo test --verbose --all-features 23 | - run: cargo test --verbose --no-default-features --features alloc 24 | - run: cargo test --verbose --no-default-features 25 | 26 | clippy: 27 | name: Lint with Clippy 28 | runs-on: ubuntu-latest 29 | env: 30 | RUSTFLAGS: -Dwarnings 31 | steps: 32 | - uses: actions/checkout@v2 33 | - uses: hecrj/setup-rust-action@v1 34 | with: 35 | components: clippy 36 | - run: cargo clippy --all-targets --verbose --no-default-features 37 | - run: cargo clippy --all-targets --verbose --all-features 38 | 39 | test-minimal: 40 | name: Test minimal dependency version with Rust nightly 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: hecrj/setup-rust-action@v1 45 | with: 46 | rust-version: nightly 47 | - run: cargo test -Zminimal-versions --verbose --all-features 48 | 49 | miri: 50 | name: Run tests under `miri` to check for UB 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v3 54 | - uses: dtolnay/rust-toolchain@nightly 55 | with: 56 | components: miri 57 | - run: cargo miri test --all-features 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Thomas Bahn ", "Torbjørn Birch Moltu ", "Simon Sapin "] 3 | description = "ASCII-only equivalents to `char`, `str` and `String`." 4 | documentation = "https://docs.rs/ascii" 5 | license = "Apache-2.0 OR MIT" 6 | name = "ascii" 7 | readme = "README.md" 8 | repository = "https://github.com/tomprogrammer/rust-ascii" 9 | version = "1.1.0" 10 | 11 | [dependencies] 12 | serde = { version = "1.0.25", optional = true } 13 | serde_test = { version = "1.0", optional = true } 14 | 15 | [features] 16 | default = ["std"] 17 | std = ["alloc"] 18 | alloc = [] 19 | 20 | [[test]] 21 | name = "tests" 22 | path = "tests.rs" 23 | -------------------------------------------------------------------------------- /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) 2017 Thomas Bahn and contributors 4 | Copyright (c) 2014 The Rust Project Developers 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ascii 2 | 3 | A library that provides ASCII-only string and character types, equivalent to the 4 | `char`, `str` and `String` types in the standard library. 5 | 6 | Types and conversion traits are described in the [Documentation](https://docs.rs/ascii). 7 | 8 | You can include this crate in your cargo project by adding it to the 9 | dependencies section in `Cargo.toml`: 10 | 11 | ```toml 12 | [dependencies] 13 | ascii = "1.1" 14 | ``` 15 | 16 | ## Using ascii without libstd 17 | 18 | Most of `AsciiChar` and `AsciiStr` can be used without `std` by disabling the 19 | default features. The owned string type `AsciiString` and the conversion trait 20 | `IntoAsciiString` as well as all methods referring to these types can be 21 | re-enabled by enabling the `alloc` feature. 22 | 23 | Methods referring to `CStr` and `CString` are also unavailable. 24 | The `Error` trait also only exists in `std`, but `description()` is made 25 | available as an inherent method for `ToAsciiCharError` and `AsAsciiStrError` 26 | in `#![no_std]`-mode. 27 | 28 | To use the `ascii` crate in `#![no_std]` mode in your cargo project, 29 | just add the following dependency declaration in `Cargo.toml`: 30 | 31 | ```toml 32 | [dependencies] 33 | ascii = { version = "1.1", default-features = false, features = ["alloc"] } 34 | ``` 35 | 36 | ## Minimum supported Rust version 37 | 38 | The minimum Rust version for 1.2.\* releases is 1.56.1. 39 | Later 1.y.0 releases might require newer Rust versions, but the three most 40 | recent stable releases at the time of publishing will always be supported. 41 | For example this means that if the current stable Rust version is 1.70 when 42 | ascii 1.3.0 is released, then ascii 1.3.\* will not require a newer 43 | Rust version than 1.68. 44 | 45 | ## History 46 | 47 | This package included the Ascii types that were removed from the Rust standard 48 | library by the 2014-12 [reform of the `std::ascii` module](https://github.com/rust-lang/rfcs/pull/486). 49 | The API changed significantly since then. 50 | 51 | ## License 52 | 53 | Licensed under either of 54 | 55 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 56 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 57 | 58 | at your option. 59 | 60 | ### Contribution 61 | 62 | Unless you explicitly state otherwise, any contribution intentionally submitted 63 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 64 | additional terms or conditions. 65 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | Version 1.1.0 (2022-09-18) 2 | ========================== 3 | * Add alloc feature. 4 | This enables `AsciiString` and methods that take or return `Box<[AsciiStr]>` in `!#[no_std]`-mode. 5 | * Add `AsciiStr::into_ascii_string()`, `AsciiString::into_boxed_ascii_str()` and `AsciiString::insert_str()`. 6 | * Implement `From>` and `From` for `AsciiString`. 7 | * Implement `From` for `Box`, `Rc`, `Arc` and `Vec`. 8 | * Make `AsciiString::new()`, `AsciiStr::len()` and `AsciiStr::is_empty()` `const fn`. 9 | * Require Rust 1.44.1. 10 | 11 | Version 1.0.0 (2019-08-26) 12 | ========================== 13 | 14 | Breaking changes: 15 | 16 | * Change `AsciiChar.is_whitespace()` to also return true for '\0xb' (vertical tab) and '\0xc' (form feed). 17 | * Remove quickcheck feature. 18 | * Remove `AsciiStr::new()`. 19 | * Rename `AsciiChar::from()` and `AsciiChar::from_unchecked()` to `from_ascii()` and `from_ascii_unchecked()`. 20 | * Rename several `AsciiChar.is_xxx()` methods to `is_ascii_xxx()` (for comsistency with std). 21 | * Rename `AsciiChar::Null` to `Nul` (for consistency with eg. `CStr::from_bytes_with_nul()`). 22 | * Rename `AsciiStr.trim_left()` and `AsciiStr.trim_right()` to `trim_start()` and `trim_end()`. 23 | * Remove impls of the deprecated `std::ascii::AsciiExt` trait. 24 | * Change iterators `Chars`, `CharsMut` and `CharsRef` from type aliases to newtypes. 25 | * Return `impl Trait` from `AsciiStr.lines()` and `AsciiStr.split()`, and remove iterator types `Lines` and `Split`. 26 | * Add `slice_ascii_str()`, `get_ascii()` and `unwrap_ascii()` to the `AsAsciiStr` trait. 27 | * Add `slice_mut_ascii_str()` and `unwrap_ascii_mut()` to the `AsMutAsciiStr` trait. 28 | * Require Rust 1.33.0 for 1.0.\*, and allow later semver-compatible 1.y.0 releases to increase it. 29 | 30 | Additions: 31 | 32 | * Add `const fn` `AsciiChar::new()` which panicks on invalid values. 33 | * Make most `AsciiChar` methods `const fn`. 34 | * Add multiple `AsciiChar::is_[ascii_]xxx()` methods. 35 | * Implement `AsRef` for `AsciiChar`. 36 | * Make `AsciiString`'s `Extend` and `FromIterator` impl generic over all `AsRef`. 37 | * Implement inclusive range indexing for `AsciiStr` (and thereby `AsciiString`). 38 | * Mark `AsciiStr` and `AsciiString` `#[repr(transparent)]` (to `[AsciiChar]` and `Vec` respectively). 39 | 40 | Version 0.9.3 (2019-08-26) 41 | ========================== 42 | 43 | Soundness fix: 44 | 45 | **Remove** [unsound](https://github.com/tomprogrammer/rust-ascii/issues/64) impls of `From<&mut AsciiStr>` for `&mut [u8]` and `&mut str`. 46 | This is a breaking change, but theese impls can lead to undefined behavior in safe code. 47 | 48 | If you use this impl and know that non-ASCII values are never inserted into the `[u8]` or `str`, 49 | you can pin ascii to 0.9.2. 50 | 51 | Other changes: 52 | 53 | * Make quickcheck `Arbitrary` impl sometimes produce `AsciiChar::DEL`. 54 | * Implement `Clone`, `Copy` and `Eq` for `ToAsciiCharError`. 55 | * Implement `ToAsciiChar` for `u16`, `u32` and `i8`. 56 | 57 | Version 0.9.2 (2019-07-07) 58 | ========================== 59 | * Implement the `IntoAsciiString` trait for `std::ffi::CStr` and `std::ffi::CString` types, 60 | and implemented the `AsAsciiStr` trait for `std::ffi::CStr` type. 61 | * Implement the `IntoAsciiString` for `std::borrow::Cow`, where the inner types themselves 62 | implement `IntoAsciiString`. 63 | * Implement conversions between `AsciiString` and `Cow<'a, AsciiStr>`. 64 | * Implement the `std::ops::AddAssign` trait for `AsciiString`. 65 | * Implement `BorrowMut`, `AsRef<[AsciiChar]>`, `AsRef`, `AsMut<[AsciiChar]>` for `AsciiString`. 66 | * Implement `PartialEq<[u8]>` and `PartialEq<[AsciiChar]>` for `AsciiStr`. 67 | * Add `AsciiStr::first()`, `AsciiStr::last()` and `AsciiStr::split()` methods. 68 | * Implement `DoubleEndedIterator` for `AsciiStr::lines()`. 69 | * Implement `AsRef` and `AsMut for AsciiString`. 93 | 94 | Version 0.8.4 (2017-04-18) 95 | ========================== 96 | * Fix the tests when running without std. 97 | 98 | Version 0.8.3 (2017-04-18) 99 | ========================== 100 | * Bugfix: `::to_ascii_lowercase` did erroneously convert to uppercase. 101 | 102 | Version 0.8.2 (2017-04-17) 103 | ========================== 104 | * Implement `IntoAsciiString` for `&'a str` and `&'a [u8]`. 105 | * Implement the `quickcheck::Arbitrary` trait for `AsciiChar` and `AsciiString`. 106 | The implementation is enabled by the `quickcheck` feature. 107 | 108 | Version 0.8.1 (2017-02-11) 109 | ========================== 110 | * Add `Chars`, `CharsMut` and `Lines` iterators. 111 | * Implement `std::fmt::Write` for `AsciiString`. 112 | 113 | Version 0.8.0 (2017-01-02) 114 | ========================== 115 | 116 | Breaking changes: 117 | 118 | * Return `FromAsciiError` instead of the input when `AsciiString::from_ascii()` or `into_ascii_string()` fails. 119 | * Replace the `no_std` feature with the additive `std` feature, which is part of the default features. (Issue #29) 120 | * `AsciiChar::is_*()` and `::as_{byte,char}()` take `self` by value instead of by reference. 121 | 122 | Additions: 123 | 124 | * Make `AsciiChar` comparable with `char` and `u8`. 125 | * Add `AsciiChar::as_printable_char()` and the free functions `caret_encode()` and `caret_decode()`. 126 | * Implement some methods from `AsciiExt` and `Error` (which are not in libcore) directly in `core` mode: 127 | * `Ascii{Char,Str}::eq_ignore_ascii_case()` 128 | * `AsciiChar::to_ascii_{upper,lower}case()` 129 | * `AsciiStr::make_ascii_{upper,lower}case()` 130 | * `{ToAsciiChar,AsAsciiStr}Error::description()` 131 | 132 | Version 0.7.1 (2016-08-15) 133 | ========================== 134 | * Fix the implementation of `AsciiExt::to_ascii_lowercase()` for `AsciiChar` converting to uppercase. (introduced in 0.7.0) 135 | 136 | Version 0.7.0 (2016-06-25) 137 | ========================== 138 | * Rename `Ascii` to `AsciiChar` and convert it into an enum. 139 | (with a variant for every ASCII character) 140 | * Replace `OwnedAsciiCast` with `IntoAsciiString`. 141 | * Replace `AsciiCast` with `As[Mut]AsciiStr` and `IntoAsciiChar`. 142 | * Add *from[_ascii]_unchecked* methods. 143 | * Replace *from_bytes* with *from_ascii* in method names. 144 | * Return `std::error::Error`-implementing types instead of `()` and `None` when 145 | conversion to `AsciiStr` or `AsciiChar` fails. 146 | * Implement `AsciiExt` without the `unstable` Cargo feature flag, which is removed. 147 | * Require Rust 1.9 or later. 148 | * Add `#[no_std]` support in a Cargo feature. 149 | * Implement `From<{&,&mut,Box<}AsciiStr>` for `[Ascii]`, `[u8]` and `str` 150 | * Implement `From<{&,&mut,Box<}[Ascii]>`, `As{Ref,Mut}<[Ascii]>` and Default for `AsciiStr` 151 | * Implement `From>` for `AsciiString`. 152 | * Implement `AsMut` for `AsciiString`. 153 | * Stop some `Ascii::is_xxx()` methods from panicking. 154 | * Add `Ascii::is_whitespace()`. 155 | * Add `AsciiString::as_mut_slice()`. 156 | * Add raw pointer methods on `AsciiString`: 157 | * `from_raw_parts` 158 | * `as_ptr` 159 | * `as_mut_ptr` 160 | 161 | Version 0.6.0 (2015-12-30) 162 | ========================== 163 | * Add `Ascii::from_byte()` 164 | * Add `AsciiStr::trim[_{left,right}]()` 165 | 166 | Version 0.5.4 (2015-07-29) 167 | ========================== 168 | Implement `IndexMut` for AsciiStr and AsciiString. 169 | 170 | Version 0.5.1 (2015-06-13) 171 | ========================== 172 | * Add `Ascii::from()`. 173 | * Implement `Index` for `AsciiStr` and `AsciiString`. 174 | * Implement `Default`,`FromIterator`,`Extend` and `Add` for `AsciiString` 175 | * Added inherent methods on `AsciiString`: 176 | * `with_capacity` 177 | * `push_str` 178 | * `capacity` 179 | * `reserve` 180 | * `reserve_exact` 181 | * `shrink_to_fit` 182 | * `push` 183 | * `truncate` 184 | * `pop` 185 | * `remove` 186 | * `insert` 187 | * `len` 188 | * `is_empty` 189 | * `clear` 190 | 191 | Version 0.5.0 (2015-05-05) 192 | ========================== 193 | First release compatible with Rust 1.0.0. 194 | -------------------------------------------------------------------------------- /src/ascii_char.rs: -------------------------------------------------------------------------------- 1 | use core::cmp::Ordering; 2 | use core::mem; 3 | use core::{char, fmt}; 4 | #[cfg(feature = "std")] 5 | use std::error::Error; 6 | 7 | #[allow(non_camel_case_types)] 8 | /// An ASCII character. It wraps a `u8`, with the highest bit always zero. 9 | #[derive(Clone, PartialEq, PartialOrd, Ord, Eq, Hash, Copy)] 10 | #[repr(u8)] 11 | pub enum AsciiChar { 12 | /// `'\0'` 13 | Null = 0, 14 | /// [Start Of Heading](http://en.wikipedia.org/wiki/Start_of_Heading) 15 | SOH = 1, 16 | #[allow(clippy::doc_markdown)] 17 | /// [Start of TeXt](http://en.wikipedia.org/wiki/Start_of_Text) 18 | #[doc(hidden)] 19 | #[deprecated(since="1.2.0", note="Replaced with AsciiChar::STX which is the correct name for this variant.")] 20 | SOX = 2, 21 | /// [End of TeXt](http://en.wikipedia.org/wiki/End-of-Text_character) 22 | ETX = 3, 23 | /// [End Of Transmission](http://en.wikipedia.org/wiki/End-of-Transmission_character) 24 | EOT = 4, 25 | /// [Enquiry](http://en.wikipedia.org/wiki/Enquiry_character) 26 | ENQ = 5, 27 | /// [Acknowledgement](http://en.wikipedia.org/wiki/Acknowledge_character) 28 | ACK = 6, 29 | /// [bell / alarm / audible](http://en.wikipedia.org/wiki/Bell_character) 30 | /// 31 | /// `'\a'` is not recognized by Rust. 32 | Bell = 7, 33 | /// [Backspace](http://en.wikipedia.org/wiki/Backspace) 34 | /// 35 | /// `'\b'` is not recognized by Rust. 36 | BackSpace = 8, 37 | /// `'\t'` 38 | Tab = 9, 39 | /// `'\n'` 40 | LineFeed = 10, 41 | /// [Vertical tab](http://en.wikipedia.org/wiki/Vertical_Tab) 42 | /// 43 | /// `'\v'` is not recognized by Rust. 44 | VT = 11, 45 | /// [Form Feed](http://en.wikipedia.org/wiki/Form_Feed) 46 | /// 47 | /// `'\f'` is not recognized by Rust. 48 | FF = 12, 49 | /// `'\r'` 50 | CarriageReturn = 13, 51 | /// [Shift In](http://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters) 52 | SI = 14, 53 | /// [Shift Out](http://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters) 54 | SO = 15, 55 | /// [Data Link Escape](http://en.wikipedia.org/wiki/Data_Link_Escape) 56 | DLE = 16, 57 | /// [Device control 1, often XON](http://en.wikipedia.org/wiki/Device_Control_1) 58 | DC1 = 17, 59 | /// Device control 2 60 | DC2 = 18, 61 | /// Device control 3, Often XOFF 62 | DC3 = 19, 63 | /// Device control 4 64 | DC4 = 20, 65 | /// [Negative AcKnowledgement](http://en.wikipedia.org/wiki/Negative-acknowledge_character) 66 | NAK = 21, 67 | /// [Synchronous idle](http://en.wikipedia.org/wiki/Synchronous_Idle) 68 | SYN = 22, 69 | /// [End of Transmission Block](http://en.wikipedia.org/wiki/End-of-Transmission-Block_character) 70 | ETB = 23, 71 | /// [Cancel](http://en.wikipedia.org/wiki/Cancel_character) 72 | CAN = 24, 73 | /// [End of Medium](http://en.wikipedia.org/wiki/End_of_Medium) 74 | EM = 25, 75 | /// [Substitute](http://en.wikipedia.org/wiki/Substitute_character) 76 | SUB = 26, 77 | /// [Escape](http://en.wikipedia.org/wiki/Escape_character) 78 | /// 79 | /// `'\e'` is not recognized by Rust. 80 | ESC = 27, 81 | /// [File Separator](http://en.wikipedia.org/wiki/File_separator) 82 | FS = 28, 83 | /// [Group Separator](http://en.wikipedia.org/wiki/Group_separator) 84 | GS = 29, 85 | /// [Record Separator](http://en.wikipedia.org/wiki/Record_separator) 86 | RS = 30, 87 | /// [Unit Separator](http://en.wikipedia.org/wiki/Unit_separator) 88 | US = 31, 89 | /// `' '` 90 | Space = 32, 91 | /// `'!'` 92 | Exclamation = 33, 93 | /// `'"'` 94 | Quotation = 34, 95 | /// `'#'` 96 | Hash = 35, 97 | /// `'$'` 98 | Dollar = 36, 99 | /// `'%'` 100 | Percent = 37, 101 | /// `'&'` 102 | Ampersand = 38, 103 | /// `'\''` 104 | Apostrophe = 39, 105 | /// `'('` 106 | ParenOpen = 40, 107 | /// `')'` 108 | ParenClose = 41, 109 | /// `'*'` 110 | Asterisk = 42, 111 | /// `'+'` 112 | Plus = 43, 113 | /// `','` 114 | Comma = 44, 115 | /// `'-'` 116 | Minus = 45, 117 | /// `'.'` 118 | Dot = 46, 119 | /// `'/'` 120 | Slash = 47, 121 | /// `'0'` 122 | _0 = 48, 123 | /// `'1'` 124 | _1 = 49, 125 | /// `'2'` 126 | _2 = 50, 127 | /// `'3'` 128 | _3 = 51, 129 | /// `'4'` 130 | _4 = 52, 131 | /// `'5'` 132 | _5 = 53, 133 | /// `'6'` 134 | _6 = 54, 135 | /// `'7'` 136 | _7 = 55, 137 | /// `'8'` 138 | _8 = 56, 139 | /// `'9'` 140 | _9 = 57, 141 | /// `':'` 142 | Colon = 58, 143 | /// `';'` 144 | Semicolon = 59, 145 | /// `'<'` 146 | LessThan = 60, 147 | /// `'='` 148 | Equal = 61, 149 | /// `'>'` 150 | GreaterThan = 62, 151 | /// `'?'` 152 | Question = 63, 153 | /// `'@'` 154 | At = 64, 155 | /// `'A'` 156 | A = 65, 157 | /// `'B'` 158 | B = 66, 159 | /// `'C'` 160 | C = 67, 161 | /// `'D'` 162 | D = 68, 163 | /// `'E'` 164 | E = 69, 165 | /// `'F'` 166 | F = 70, 167 | /// `'G'` 168 | G = 71, 169 | /// `'H'` 170 | H = 72, 171 | /// `'I'` 172 | I = 73, 173 | /// `'J'` 174 | J = 74, 175 | /// `'K'` 176 | K = 75, 177 | /// `'L'` 178 | L = 76, 179 | /// `'M'` 180 | M = 77, 181 | /// `'N'` 182 | N = 78, 183 | /// `'O'` 184 | O = 79, 185 | /// `'P'` 186 | P = 80, 187 | /// `'Q'` 188 | Q = 81, 189 | /// `'R'` 190 | R = 82, 191 | /// `'S'` 192 | S = 83, 193 | /// `'T'` 194 | T = 84, 195 | /// `'U'` 196 | U = 85, 197 | /// `'V'` 198 | V = 86, 199 | /// `'W'` 200 | W = 87, 201 | /// `'X'` 202 | X = 88, 203 | /// `'Y'` 204 | Y = 89, 205 | /// `'Z'` 206 | Z = 90, 207 | /// `'['` 208 | BracketOpen = 91, 209 | /// `'\'` 210 | BackSlash = 92, 211 | /// `']'` 212 | BracketClose = 93, 213 | /// `'^'` 214 | Caret = 94, 215 | /// `'_'` 216 | UnderScore = 95, 217 | /// `'`'` 218 | Grave = 96, 219 | /// `'a'` 220 | a = 97, 221 | /// `'b'` 222 | b = 98, 223 | /// `'c'` 224 | c = 99, 225 | /// `'d'` 226 | d = 100, 227 | /// `'e'` 228 | e = 101, 229 | /// `'f'` 230 | f = 102, 231 | /// `'g'` 232 | g = 103, 233 | /// `'h'` 234 | h = 104, 235 | /// `'i'` 236 | i = 105, 237 | /// `'j'` 238 | j = 106, 239 | /// `'k'` 240 | k = 107, 241 | /// `'l'` 242 | l = 108, 243 | /// `'m'` 244 | m = 109, 245 | /// `'n'` 246 | n = 110, 247 | /// `'o'` 248 | o = 111, 249 | /// `'p'` 250 | p = 112, 251 | /// `'q'` 252 | q = 113, 253 | /// `'r'` 254 | r = 114, 255 | /// `'s'` 256 | s = 115, 257 | /// `'t'` 258 | t = 116, 259 | /// `'u'` 260 | u = 117, 261 | /// `'v'` 262 | v = 118, 263 | /// `'w'` 264 | w = 119, 265 | /// `'x'` 266 | x = 120, 267 | /// `'y'` 268 | y = 121, 269 | /// `'z'` 270 | z = 122, 271 | /// `'{'` 272 | CurlyBraceOpen = 123, 273 | /// `'|'` 274 | VerticalBar = 124, 275 | /// `'}'` 276 | CurlyBraceClose = 125, 277 | /// `'~'` 278 | Tilde = 126, 279 | /// [Delete](http://en.wikipedia.org/wiki/Delete_character) 280 | DEL = 127, 281 | } 282 | 283 | impl AsciiChar { 284 | /// [Start of TeXt](http://en.wikipedia.org/wiki/Start_of_Text) 285 | /// 286 | /// (It's an associated constant instead of a variant because 287 | /// the variant for it has an incorrect name.) 288 | #[allow(deprecated, clippy::doc_markdown)] 289 | pub const STX: AsciiChar = AsciiChar::SOX; 290 | 291 | /// Constructs an ASCII character from a `u8`, `char` or other character type. 292 | /// 293 | /// # Errors 294 | /// Returns `Err(())` if the character can't be ASCII encoded. 295 | /// 296 | /// # Example 297 | /// ``` 298 | /// # #![allow(bindings_with_variant_name)] 299 | /// # use ascii::AsciiChar; 300 | /// let a = AsciiChar::from_ascii('g').unwrap(); 301 | /// assert_eq!(a.as_char(), 'g'); 302 | /// ``` 303 | #[inline] 304 | pub fn from_ascii(ch: C) -> Result { 305 | ch.to_ascii_char() 306 | } 307 | 308 | /// Create an `AsciiChar` from a `char`, panicking if it's not ASCII. 309 | /// 310 | /// This function is intended for creating `AsciiChar` values from 311 | /// hardcoded known-good character literals such as `'K'`, `'-'` or `'\0'`, 312 | /// and for use in `const` contexts. 313 | /// Use [`from_ascii()`](#method.from_ascii) instead when you're not 314 | /// certain the character is ASCII. 315 | /// 316 | /// # Examples 317 | /// 318 | /// ``` 319 | /// # use ascii::AsciiChar; 320 | /// assert_eq!(AsciiChar::new('@'), AsciiChar::At); 321 | /// assert_eq!(AsciiChar::new('C').as_char(), 'C'); 322 | /// ``` 323 | /// 324 | /// In a constant: 325 | /// ``` 326 | /// # use ascii::AsciiChar; 327 | /// const SPLIT_ON: AsciiChar = AsciiChar::new(','); 328 | /// ``` 329 | /// 330 | /// This will not compile: 331 | /// ```compile_fail 332 | /// # use ascii::AsciiChar; 333 | /// const BAD: AsciiChar = AsciiChar::new('Ø'); 334 | /// ``` 335 | /// 336 | /// # Panics 337 | /// 338 | /// This function will panic if passed a non-ASCII character. 339 | /// 340 | /// The panic message might not be the most descriptive due to the 341 | /// current limitations of `const fn`. 342 | #[must_use] 343 | pub const fn new(ch: char) -> AsciiChar { 344 | // It's restricted to this function, and without it 345 | // we'd need to specify `AsciiChar::` or `Self::` 128 times. 346 | #[allow(clippy::enum_glob_use)] 347 | use AsciiChar::*; 348 | 349 | #[rustfmt::skip] 350 | const ALL: [AsciiChar; 128] = [ 351 | Null, SOH, AsciiChar::STX, ETX, EOT, ENQ, ACK, Bell, 352 | BackSpace, Tab, LineFeed, VT, FF, CarriageReturn, SI, SO, 353 | DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, 354 | CAN, EM, SUB, ESC, FS, GS, RS, US, 355 | Space, Exclamation, Quotation, Hash, Dollar, Percent, Ampersand, Apostrophe, 356 | ParenOpen, ParenClose, Asterisk, Plus, Comma, Minus, Dot, Slash, 357 | _0, _1, _2, _3, _4, _5, _6, _7, 358 | _8, _9, Colon, Semicolon, LessThan, Equal, GreaterThan, Question, 359 | At, A, B, C, D, E, F, G, 360 | H, I, J, K, L, M, N, O, 361 | P, Q, R, S, T, U, V, W, 362 | X, Y, Z, BracketOpen, BackSlash, BracketClose, Caret, UnderScore, 363 | Grave, a, b, c, d, e, f, g, 364 | h, i, j, k, l, m, n, o, 365 | p, q, r, s, t, u, v, w, 366 | x, y, z, CurlyBraceOpen, VerticalBar, CurlyBraceClose, Tilde, DEL, 367 | ]; 368 | 369 | // We want to slice here and detect `const_err` from rustc if the slice is invalid 370 | #[allow(clippy::indexing_slicing)] 371 | ALL[ch as usize] 372 | } 373 | 374 | /// Create an `AsciiChar` from a `char`, in a `const fn` way. 375 | /// 376 | /// Within non-`const fn` functions the more general 377 | /// [`from_ascii()`](#method.from_ascii) should be used instead. 378 | /// 379 | /// # Examples 380 | /// ``` 381 | /// # use ascii::AsciiChar; 382 | /// assert!(AsciiChar::try_new('-').is_ok()); 383 | /// assert!(AsciiChar::try_new('—').is_err()); 384 | /// assert_eq!(AsciiChar::try_new('\x7f'), Ok(AsciiChar::DEL)); 385 | /// ``` 386 | /// 387 | /// # Errors 388 | /// 389 | /// Fails for non-ASCII characters. 390 | #[inline] 391 | pub const fn try_new(ch: char) -> Result { 392 | unsafe { 393 | match ch as u32 { 394 | 0..=127 => Ok(mem::transmute(ch as u8)), 395 | _ => Err(ToAsciiCharError(())), 396 | } 397 | } 398 | } 399 | 400 | /// Constructs an ASCII character from a `u8`, `char` or other character 401 | /// type without any checks. 402 | /// 403 | /// # Safety 404 | /// 405 | /// This function is very unsafe as it can create invalid enum 406 | /// discriminants, which instantly creates undefined behavior. 407 | /// (`let _ = AsciiChar::from_ascii_unchecked(200);` alone is UB). 408 | /// 409 | /// The undefined behavior is not just theoretical either: 410 | /// For example, `[0; 128][AsciiChar::from_ascii_unchecked(255) as u8 as usize] = 0` 411 | /// might not panic, creating a buffer overflow, 412 | /// and `Some(AsciiChar::from_ascii_unchecked(128))` might be `None`. 413 | #[inline] 414 | #[must_use] 415 | pub const unsafe fn from_ascii_unchecked(ch: u8) -> Self { 416 | // SAFETY: Caller guarantees `ch` is within bounds of ascii. 417 | unsafe { mem::transmute(ch) } 418 | } 419 | 420 | /// Converts an ASCII character into a `u8`. 421 | #[inline] 422 | #[must_use] 423 | pub const fn as_byte(self) -> u8 { 424 | self as u8 425 | } 426 | 427 | /// Converts an ASCII character into a `char`. 428 | #[inline] 429 | #[must_use] 430 | pub const fn as_char(self) -> char { 431 | self as u8 as char 432 | } 433 | 434 | // the following methods are like ctype, and the implementation is inspired by musl. 435 | // The ascii_ methods take self by reference for maximum compatibility 436 | // with the corresponding methods on u8 and char. 437 | // It is bad for both usability and performance, but marking those 438 | // that doesn't have a non-ascii sibling #[inline] should 439 | // make the compiler optimize away the indirection. 440 | 441 | /// Turns uppercase into lowercase, but also modifies '@' and '<'..='_' 442 | #[must_use] 443 | const fn to_not_upper(self) -> u8 { 444 | self as u8 | 0b010_0000 445 | } 446 | 447 | /// Check if the character is a letter (a-z, A-Z) 448 | #[inline] 449 | #[must_use] 450 | pub const fn is_alphabetic(self) -> bool { 451 | (self.to_not_upper() >= b'a') && (self.to_not_upper() <= b'z') 452 | } 453 | 454 | /// Check if the character is a letter (a-z, A-Z). 455 | /// 456 | /// This method is identical to [`is_alphabetic()`](#method.is_alphabetic) 457 | #[inline] 458 | #[must_use] 459 | pub const fn is_ascii_alphabetic(&self) -> bool { 460 | self.is_alphabetic() 461 | } 462 | 463 | /// Check if the character is a digit in the given radix. 464 | /// 465 | /// If the radix is always 10 or 16, 466 | /// [`is_ascii_digit()`](#method.is_ascii_digit) and 467 | /// [`is_ascii_hexdigit()`](#method.is_ascii_hexdigit()) will be faster. 468 | /// 469 | /// # Panics 470 | /// 471 | /// Radixes greater than 36 are not supported and will result in a panic. 472 | #[must_use] 473 | pub fn is_digit(self, radix: u32) -> bool { 474 | match (self as u8, radix) { 475 | (b'0'..=b'9', 0..=36) => u32::from(self as u8 - b'0') < radix, 476 | (b'a'..=b'z', 11..=36) => u32::from(self as u8 - b'a') < radix - 10, 477 | (b'A'..=b'Z', 11..=36) => u32::from(self as u8 - b'A') < radix - 10, 478 | (_, 0..=36) => false, 479 | (_, _) => panic!("radixes greater than 36 are not supported"), 480 | } 481 | } 482 | 483 | /// Check if the character is a number (0-9) 484 | /// 485 | /// # Examples 486 | /// ``` 487 | /// # use ascii::AsciiChar; 488 | /// assert_eq!(AsciiChar::new('0').is_ascii_digit(), true); 489 | /// assert_eq!(AsciiChar::new('9').is_ascii_digit(), true); 490 | /// assert_eq!(AsciiChar::new('a').is_ascii_digit(), false); 491 | /// assert_eq!(AsciiChar::new('A').is_ascii_digit(), false); 492 | /// assert_eq!(AsciiChar::new('/').is_ascii_digit(), false); 493 | /// ``` 494 | #[inline] 495 | #[must_use] 496 | pub const fn is_ascii_digit(&self) -> bool { 497 | (*self as u8 >= b'0') && (*self as u8 <= b'9') 498 | } 499 | 500 | /// Check if the character is a letter or number 501 | #[inline] 502 | #[must_use] 503 | pub const fn is_alphanumeric(self) -> bool { 504 | self.is_alphabetic() || self.is_ascii_digit() 505 | } 506 | 507 | /// Check if the character is a letter or number 508 | /// 509 | /// This method is identical to [`is_alphanumeric()`](#method.is_alphanumeric) 510 | #[inline] 511 | #[must_use] 512 | pub const fn is_ascii_alphanumeric(&self) -> bool { 513 | self.is_alphanumeric() 514 | } 515 | 516 | /// Check if the character is a space or horizontal tab 517 | /// 518 | /// # Examples 519 | /// ``` 520 | /// # use ascii::AsciiChar; 521 | /// assert!(AsciiChar::Space.is_ascii_blank()); 522 | /// assert!(AsciiChar::Tab.is_ascii_blank()); 523 | /// assert!(!AsciiChar::VT.is_ascii_blank()); 524 | /// assert!(!AsciiChar::LineFeed.is_ascii_blank()); 525 | /// assert!(!AsciiChar::CarriageReturn.is_ascii_blank()); 526 | /// assert!(!AsciiChar::FF.is_ascii_blank()); 527 | /// ``` 528 | #[inline] 529 | #[must_use] 530 | pub const fn is_ascii_blank(&self) -> bool { 531 | (*self as u8 == b' ') || (*self as u8 == b'\t') 532 | } 533 | 534 | /// Check if the character one of ' ', '\t', '\n', '\r', 535 | /// '\0xb' (vertical tab) or '\0xc' (form feed). 536 | #[inline] 537 | #[must_use] 538 | pub const fn is_whitespace(self) -> bool { 539 | let b = self as u8; 540 | self.is_ascii_blank() || (b == b'\n') || (b == b'\r') || (b == 0x0b) || (b == 0x0c) 541 | } 542 | 543 | /// Check if the character is a ' ', '\t', '\n', '\r' or '\0xc' (form feed). 544 | /// 545 | /// This method is NOT identical to `is_whitespace()`. 546 | #[inline] 547 | #[must_use] 548 | pub const fn is_ascii_whitespace(&self) -> bool { 549 | self.is_ascii_blank() 550 | || (*self as u8 == b'\n') 551 | || (*self as u8 == b'\r') 552 | || (*self as u8 == 0x0c/*form feed*/) 553 | } 554 | 555 | /// Check if the character is a control character 556 | /// 557 | /// # Examples 558 | /// ``` 559 | /// # use ascii::AsciiChar; 560 | /// assert_eq!(AsciiChar::new('\0').is_ascii_control(), true); 561 | /// assert_eq!(AsciiChar::new('n').is_ascii_control(), false); 562 | /// assert_eq!(AsciiChar::new(' ').is_ascii_control(), false); 563 | /// assert_eq!(AsciiChar::new('\n').is_ascii_control(), true); 564 | /// assert_eq!(AsciiChar::new('\t').is_ascii_control(), true); 565 | /// assert_eq!(AsciiChar::EOT.is_ascii_control(), true); 566 | /// ``` 567 | #[inline] 568 | #[must_use] 569 | pub const fn is_ascii_control(&self) -> bool { 570 | ((*self as u8) < b' ') || (*self as u8 == 127) 571 | } 572 | 573 | /// Checks if the character is printable (except space) 574 | /// 575 | /// # Examples 576 | /// ``` 577 | /// # use ascii::AsciiChar; 578 | /// assert_eq!(AsciiChar::new('n').is_ascii_graphic(), true); 579 | /// assert_eq!(AsciiChar::new(' ').is_ascii_graphic(), false); 580 | /// assert_eq!(AsciiChar::new('\n').is_ascii_graphic(), false); 581 | /// ``` 582 | #[inline] 583 | #[must_use] 584 | pub const fn is_ascii_graphic(&self) -> bool { 585 | self.as_byte().wrapping_sub(b' ' + 1) < 0x5E 586 | } 587 | 588 | /// Checks if the character is printable (including space) 589 | /// 590 | /// # Examples 591 | /// ``` 592 | /// # use ascii::AsciiChar; 593 | /// assert_eq!(AsciiChar::new('n').is_ascii_printable(), true); 594 | /// assert_eq!(AsciiChar::new(' ').is_ascii_printable(), true); 595 | /// assert_eq!(AsciiChar::new('\n').is_ascii_printable(), false); 596 | /// ``` 597 | #[inline] 598 | #[must_use] 599 | pub const fn is_ascii_printable(&self) -> bool { 600 | self.as_byte().wrapping_sub(b' ') < 0x5F 601 | } 602 | 603 | /// Checks if the character is alphabetic and lowercase (a-z). 604 | /// 605 | /// # Examples 606 | /// ``` 607 | /// use ascii::AsciiChar; 608 | /// assert_eq!(AsciiChar::new('a').is_lowercase(), true); 609 | /// assert_eq!(AsciiChar::new('A').is_lowercase(), false); 610 | /// assert_eq!(AsciiChar::new('@').is_lowercase(), false); 611 | /// ``` 612 | #[inline] 613 | #[must_use] 614 | pub const fn is_lowercase(self) -> bool { 615 | self.as_byte().wrapping_sub(b'a') < 26 616 | } 617 | 618 | /// Checks if the character is alphabetic and lowercase (a-z). 619 | /// 620 | /// This method is identical to [`is_lowercase()`](#method.is_lowercase) 621 | #[inline] 622 | #[must_use] 623 | pub const fn is_ascii_lowercase(&self) -> bool { 624 | self.is_lowercase() 625 | } 626 | 627 | /// Checks if the character is alphabetic and uppercase (A-Z). 628 | /// 629 | /// # Examples 630 | /// ``` 631 | /// # use ascii::AsciiChar; 632 | /// assert_eq!(AsciiChar::new('A').is_uppercase(), true); 633 | /// assert_eq!(AsciiChar::new('a').is_uppercase(), false); 634 | /// assert_eq!(AsciiChar::new('@').is_uppercase(), false); 635 | /// ``` 636 | #[inline] 637 | #[must_use] 638 | pub const fn is_uppercase(self) -> bool { 639 | self.as_byte().wrapping_sub(b'A') < 26 640 | } 641 | 642 | /// Checks if the character is alphabetic and uppercase (A-Z). 643 | /// 644 | /// This method is identical to [`is_uppercase()`](#method.is_uppercase) 645 | #[inline] 646 | #[must_use] 647 | pub const fn is_ascii_uppercase(&self) -> bool { 648 | self.is_uppercase() 649 | } 650 | 651 | /// Checks if the character is punctuation 652 | /// 653 | /// # Examples 654 | /// ``` 655 | /// # use ascii::AsciiChar; 656 | /// assert_eq!(AsciiChar::new('n').is_ascii_punctuation(), false); 657 | /// assert_eq!(AsciiChar::new(' ').is_ascii_punctuation(), false); 658 | /// assert_eq!(AsciiChar::new('_').is_ascii_punctuation(), true); 659 | /// assert_eq!(AsciiChar::new('~').is_ascii_punctuation(), true); 660 | /// ``` 661 | #[inline] 662 | #[must_use] 663 | pub const fn is_ascii_punctuation(&self) -> bool { 664 | self.is_ascii_graphic() && !self.is_alphanumeric() 665 | } 666 | 667 | /// Checks if the character is a valid hex digit 668 | /// 669 | /// # Examples 670 | /// ``` 671 | /// # use ascii::AsciiChar; 672 | /// assert_eq!(AsciiChar::new('5').is_ascii_hexdigit(), true); 673 | /// assert_eq!(AsciiChar::new('a').is_ascii_hexdigit(), true); 674 | /// assert_eq!(AsciiChar::new('F').is_ascii_hexdigit(), true); 675 | /// assert_eq!(AsciiChar::new('G').is_ascii_hexdigit(), false); 676 | /// assert_eq!(AsciiChar::new(' ').is_ascii_hexdigit(), false); 677 | /// ``` 678 | #[inline] 679 | #[must_use] 680 | pub const fn is_ascii_hexdigit(&self) -> bool { 681 | self.is_ascii_digit() || ((*self as u8 | 0x20u8).wrapping_sub(b'a') < 6) 682 | } 683 | 684 | /// Unicode has printable versions of the ASCII control codes, like '␛'. 685 | /// 686 | /// This function is identical with `.as_char()` 687 | /// for all values `.is_printable()` returns true for, 688 | /// but replaces the control codes with those unicodes printable versions. 689 | /// 690 | /// # Examples 691 | /// ``` 692 | /// # use ascii::AsciiChar; 693 | /// assert_eq!(AsciiChar::new('\0').as_printable_char(), '␀'); 694 | /// assert_eq!(AsciiChar::new('\n').as_printable_char(), '␊'); 695 | /// assert_eq!(AsciiChar::new(' ').as_printable_char(), ' '); 696 | /// assert_eq!(AsciiChar::new('p').as_printable_char(), 'p'); 697 | /// ``` 698 | #[must_use] 699 | pub const fn as_printable_char(self) -> char { 700 | #![allow(clippy::transmute_int_to_char)] // from_utf32_unchecked() is not const fn yet. 701 | match self as u8 { 702 | // Non printable characters 703 | // SAFETY: From codepoint 0x2400 ('␀') to 0x241f (`␟`), there are characters representing 704 | // the unprintable characters from 0x0 to 0x1f, ordered correctly. 705 | // As `b` is guaranteed to be within 0x0 to 0x1f, the conversion represents a 706 | // valid character. 707 | b @ 0x0..=0x1f => unsafe { mem::transmute('␀' as u32 + b as u32) }, 708 | 709 | // 0x7f (delete) has it's own character at codepoint 0x2420, not 0x247f, so it is special 710 | // cased to return it's character 711 | 0x7f => '␡', 712 | 713 | // All other characters are printable, and per function contract use `Self::as_char` 714 | _ => self.as_char(), 715 | } 716 | } 717 | 718 | /// Replaces letters `a` to `z` with `A` to `Z` 719 | pub fn make_ascii_uppercase(&mut self) { 720 | *self = self.to_ascii_uppercase(); 721 | } 722 | 723 | /// Replaces letters `A` to `Z` with `a` to `z` 724 | pub fn make_ascii_lowercase(&mut self) { 725 | *self = self.to_ascii_lowercase(); 726 | } 727 | 728 | /// Maps letters a-z to A-Z and returns any other character unchanged. 729 | /// 730 | /// # Examples 731 | /// ``` 732 | /// # use ascii::AsciiChar; 733 | /// assert_eq!(AsciiChar::new('u').to_ascii_uppercase().as_char(), 'U'); 734 | /// assert_eq!(AsciiChar::new('U').to_ascii_uppercase().as_char(), 'U'); 735 | /// assert_eq!(AsciiChar::new('2').to_ascii_uppercase().as_char(), '2'); 736 | /// assert_eq!(AsciiChar::new('=').to_ascii_uppercase().as_char(), '='); 737 | /// assert_eq!(AsciiChar::new('[').to_ascii_uppercase().as_char(), '['); 738 | /// ``` 739 | #[inline] 740 | #[must_use] 741 | #[allow(clippy::indexing_slicing)] // We're sure it'll either access one or the other, as `bool` is either `0` or `1` 742 | pub const fn to_ascii_uppercase(&self) -> Self { 743 | [*self, AsciiChar::new((*self as u8 & 0b101_1111) as char)][self.is_lowercase() as usize] 744 | } 745 | 746 | /// Maps letters A-Z to a-z and returns any other character unchanged. 747 | /// 748 | /// # Examples 749 | /// ``` 750 | /// # use ascii::AsciiChar; 751 | /// assert_eq!(AsciiChar::new('U').to_ascii_lowercase().as_char(), 'u'); 752 | /// assert_eq!(AsciiChar::new('u').to_ascii_lowercase().as_char(), 'u'); 753 | /// assert_eq!(AsciiChar::new('2').to_ascii_lowercase().as_char(), '2'); 754 | /// assert_eq!(AsciiChar::new('^').to_ascii_lowercase().as_char(), '^'); 755 | /// assert_eq!(AsciiChar::new('\x7f').to_ascii_lowercase().as_char(), '\x7f'); 756 | /// ``` 757 | #[inline] 758 | #[must_use] 759 | #[allow(clippy::indexing_slicing)] // We're sure it'll either access one or the other, as `bool` is either `0` or `1` 760 | pub const fn to_ascii_lowercase(&self) -> Self { 761 | [*self, AsciiChar::new(self.to_not_upper() as char)][self.is_uppercase() as usize] 762 | } 763 | 764 | /// Compares two characters case-insensitively. 765 | #[inline] 766 | #[must_use] 767 | pub const fn eq_ignore_ascii_case(&self, other: &Self) -> bool { 768 | (self.as_byte() == other.as_byte()) 769 | || (self.is_alphabetic() && (self.to_not_upper() == other.to_not_upper())) 770 | } 771 | } 772 | 773 | impl fmt::Display for AsciiChar { 774 | #[inline] 775 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 776 | self.as_char().fmt(f) 777 | } 778 | } 779 | 780 | impl fmt::Debug for AsciiChar { 781 | #[inline] 782 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 783 | self.as_char().fmt(f) 784 | } 785 | } 786 | 787 | impl Default for AsciiChar { 788 | fn default() -> AsciiChar { 789 | AsciiChar::Null 790 | } 791 | } 792 | 793 | macro_rules! impl_into_partial_eq_ord { 794 | ($wider:ty, $to_wider:expr) => { 795 | impl From for $wider { 796 | #[inline] 797 | fn from(a: AsciiChar) -> $wider { 798 | $to_wider(a) 799 | } 800 | } 801 | impl PartialEq<$wider> for AsciiChar { 802 | #[inline] 803 | fn eq(&self, rhs: &$wider) -> bool { 804 | $to_wider(*self) == *rhs 805 | } 806 | } 807 | impl PartialEq for $wider { 808 | #[inline] 809 | fn eq(&self, rhs: &AsciiChar) -> bool { 810 | *self == $to_wider(*rhs) 811 | } 812 | } 813 | impl PartialOrd<$wider> for AsciiChar { 814 | #[inline] 815 | fn partial_cmp(&self, rhs: &$wider) -> Option { 816 | $to_wider(*self).partial_cmp(rhs) 817 | } 818 | } 819 | impl PartialOrd for $wider { 820 | #[inline] 821 | fn partial_cmp(&self, rhs: &AsciiChar) -> Option { 822 | self.partial_cmp(&$to_wider(*rhs)) 823 | } 824 | } 825 | }; 826 | } 827 | impl_into_partial_eq_ord! {u8, AsciiChar::as_byte} 828 | impl_into_partial_eq_ord! {char, AsciiChar::as_char} 829 | 830 | /// Error returned by `ToAsciiChar`. 831 | #[derive(Clone, Copy, PartialEq, Eq)] 832 | pub struct ToAsciiCharError(()); 833 | 834 | const ERRORMSG_CHAR: &str = "not an ASCII character"; 835 | 836 | #[cfg(not(feature = "std"))] 837 | impl ToAsciiCharError { 838 | /// Returns a description for this error, like `std::error::Error::description`. 839 | #[inline] 840 | #[must_use] 841 | #[allow(clippy::unused_self)] 842 | pub const fn description(&self) -> &'static str { 843 | ERRORMSG_CHAR 844 | } 845 | } 846 | 847 | impl fmt::Debug for ToAsciiCharError { 848 | fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { 849 | write!(fmtr, "{}", ERRORMSG_CHAR) 850 | } 851 | } 852 | 853 | impl fmt::Display for ToAsciiCharError { 854 | fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { 855 | write!(fmtr, "{}", ERRORMSG_CHAR) 856 | } 857 | } 858 | 859 | #[cfg(feature = "std")] 860 | impl Error for ToAsciiCharError { 861 | #[inline] 862 | fn description(&self) -> &'static str { 863 | ERRORMSG_CHAR 864 | } 865 | } 866 | 867 | /// Convert `char`, `u8` and other character types to `AsciiChar`. 868 | pub trait ToAsciiChar { 869 | /// Convert to `AsciiChar`. 870 | /// 871 | /// # Errors 872 | /// If `self` is outside the valid ascii range, this returns `Err` 873 | fn to_ascii_char(self) -> Result; 874 | 875 | /// Convert to `AsciiChar` without checking that it is an ASCII character. 876 | /// 877 | /// # Safety 878 | /// Calling this function with a value outside of the ascii range, `0x0` to `0x7f` inclusive, 879 | /// is undefined behavior. 880 | // TODO: Make sure this is the contract we want to express in this function. 881 | // It is ambigous if numbers such as `0xffffff20_u32` are valid ascii characters, 882 | // as this function returns `Ascii::Space` due to the cast to `u8`, even though 883 | // `to_ascii_char` returns `Err()`. 884 | unsafe fn to_ascii_char_unchecked(self) -> AsciiChar; 885 | } 886 | 887 | impl ToAsciiChar for AsciiChar { 888 | #[inline] 889 | fn to_ascii_char(self) -> Result { 890 | Ok(self) 891 | } 892 | 893 | #[inline] 894 | unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { 895 | self 896 | } 897 | } 898 | 899 | impl ToAsciiChar for u8 { 900 | #[inline] 901 | fn to_ascii_char(self) -> Result { 902 | u32::from(self).to_ascii_char() 903 | } 904 | #[inline] 905 | unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { 906 | // SAFETY: Caller guarantees `self` is within bounds of the enum 907 | // variants, so this cast successfully produces a valid ascii 908 | // variant 909 | unsafe { mem::transmute::(self) } 910 | } 911 | } 912 | 913 | // Note: Casts to `u8` here does not cause problems, as the negative 914 | // range is mapped outside of ascii bounds and we don't mind losing 915 | // the sign, as long as negative numbers are mapped outside ascii range. 916 | #[allow(clippy::cast_sign_loss)] 917 | impl ToAsciiChar for i8 { 918 | #[inline] 919 | fn to_ascii_char(self) -> Result { 920 | u32::from(self as u8).to_ascii_char() 921 | } 922 | #[inline] 923 | unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { 924 | // SAFETY: Caller guarantees `self` is within bounds of the enum 925 | // variants, so this cast successfully produces a valid ascii 926 | // variant 927 | unsafe { mem::transmute::(self as u8) } 928 | } 929 | } 930 | 931 | impl ToAsciiChar for char { 932 | #[inline] 933 | fn to_ascii_char(self) -> Result { 934 | u32::from(self).to_ascii_char() 935 | } 936 | #[inline] 937 | unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { 938 | // SAFETY: Caller guarantees we're within ascii range. 939 | unsafe { u32::from(self).to_ascii_char_unchecked() } 940 | } 941 | } 942 | 943 | impl ToAsciiChar for u32 { 944 | fn to_ascii_char(self) -> Result { 945 | match self { 946 | // SAFETY: We're within the valid ascii range in this branch. 947 | 0x0..=0x7f => Ok(unsafe { self.to_ascii_char_unchecked() }), 948 | _ => Err(ToAsciiCharError(())), 949 | } 950 | } 951 | 952 | #[inline] 953 | unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { 954 | // Note: This cast discards the top bytes, this may cause problems, see 955 | // the TODO on this method's documentation in the trait. 956 | // SAFETY: Caller guarantees we're within ascii range. 957 | #[allow(clippy::cast_possible_truncation)] // We want to truncate it 958 | unsafe { 959 | (self as u8).to_ascii_char_unchecked() 960 | } 961 | } 962 | } 963 | 964 | impl ToAsciiChar for u16 { 965 | fn to_ascii_char(self) -> Result { 966 | u32::from(self).to_ascii_char() 967 | } 968 | #[inline] 969 | unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { 970 | // Note: This cast discards the top bytes, this may cause problems, see 971 | // the TODO on this method's documentation in the trait. 972 | // SAFETY: Caller guarantees we're within ascii range. 973 | #[allow(clippy::cast_possible_truncation)] // We want to truncate it 974 | unsafe { 975 | (self as u8).to_ascii_char_unchecked() 976 | } 977 | } 978 | } 979 | 980 | #[cfg(test)] 981 | mod tests { 982 | use super::{AsciiChar, ToAsciiChar, ToAsciiCharError}; 983 | 984 | #[test] 985 | fn to_ascii_char() { 986 | fn generic(ch: C) -> Result { 987 | ch.to_ascii_char() 988 | } 989 | assert_eq!(generic(AsciiChar::A), Ok(AsciiChar::A)); 990 | assert_eq!(generic(b'A'), Ok(AsciiChar::A)); 991 | assert_eq!(generic('A'), Ok(AsciiChar::A)); 992 | assert!(generic(200_u16).is_err()); 993 | assert!(generic('λ').is_err()); 994 | } 995 | 996 | #[test] 997 | fn as_byte_and_char() { 998 | assert_eq!(AsciiChar::A.as_byte(), b'A'); 999 | assert_eq!(AsciiChar::A.as_char(), 'A'); 1000 | } 1001 | 1002 | #[test] 1003 | fn new_array_is_correct() { 1004 | for byte in 0..128_u8 { 1005 | assert_eq!(AsciiChar::new(byte as char).as_byte(), byte); 1006 | } 1007 | } 1008 | 1009 | #[test] 1010 | fn is_all() { 1011 | #![allow(clippy::is_digit_ascii_radix)] // testing it 1012 | for byte in 0..128_u8 { 1013 | let ch = byte as char; 1014 | let ascii = AsciiChar::new(ch); 1015 | assert_eq!(ascii.is_alphabetic(), ch.is_alphabetic()); 1016 | assert_eq!(ascii.is_ascii_alphabetic(), ch.is_ascii_alphabetic()); 1017 | assert_eq!(ascii.is_alphanumeric(), ch.is_alphanumeric()); 1018 | assert_eq!(ascii.is_ascii_alphanumeric(), ch.is_ascii_alphanumeric()); 1019 | assert_eq!(ascii.is_digit(8), ch.is_digit(8), "is_digit(8) {:?}", ch); 1020 | assert_eq!(ascii.is_digit(10), ch.is_digit(10), "is_digit(10) {:?}", ch); 1021 | assert_eq!(ascii.is_digit(16), ch.is_digit(16), "is_digit(16) {:?}", ch); 1022 | assert_eq!(ascii.is_digit(36), ch.is_digit(36), "is_digit(36) {:?}", ch); 1023 | assert_eq!(ascii.is_ascii_digit(), ch.is_ascii_digit()); 1024 | assert_eq!(ascii.is_ascii_hexdigit(), ch.is_ascii_hexdigit()); 1025 | assert_eq!(ascii.is_ascii_control(), ch.is_ascii_control()); 1026 | assert_eq!(ascii.is_ascii_graphic(), ch.is_ascii_graphic()); 1027 | assert_eq!(ascii.is_ascii_punctuation(), ch.is_ascii_punctuation()); 1028 | assert_eq!( 1029 | ascii.is_whitespace(), 1030 | ch.is_whitespace(), 1031 | "{:?} ({:#04x})", 1032 | ch, 1033 | byte 1034 | ); 1035 | assert_eq!( 1036 | ascii.is_ascii_whitespace(), 1037 | ch.is_ascii_whitespace(), 1038 | "{:?} ({:#04x})", 1039 | ch, 1040 | byte 1041 | ); 1042 | assert_eq!(ascii.is_uppercase(), ch.is_uppercase()); 1043 | assert_eq!(ascii.is_ascii_uppercase(), ch.is_ascii_uppercase()); 1044 | assert_eq!(ascii.is_lowercase(), ch.is_lowercase()); 1045 | assert_eq!(ascii.is_ascii_lowercase(), ch.is_ascii_lowercase()); 1046 | assert_eq!(ascii.to_ascii_uppercase(), ch.to_ascii_uppercase()); 1047 | assert_eq!(ascii.to_ascii_lowercase(), ch.to_ascii_lowercase()); 1048 | } 1049 | } 1050 | 1051 | #[test] 1052 | fn is_digit_strange_radixes() { 1053 | assert_eq!(AsciiChar::_0.is_digit(0), '0'.is_digit(0)); 1054 | assert_eq!(AsciiChar::_0.is_digit(1), '0'.is_digit(1)); 1055 | assert_eq!(AsciiChar::_5.is_digit(5), '5'.is_digit(5)); 1056 | assert_eq!(AsciiChar::z.is_digit(35), 'z'.is_digit(35)); 1057 | } 1058 | 1059 | #[test] 1060 | #[should_panic] 1061 | fn is_digit_bad_radix() { 1062 | let _ = AsciiChar::_7.is_digit(37); 1063 | } 1064 | 1065 | #[test] 1066 | fn cmp_wider() { 1067 | assert_eq!(AsciiChar::A, 'A'); 1068 | assert_eq!(b'b', AsciiChar::b); 1069 | assert!(AsciiChar::a < 'z'); 1070 | } 1071 | 1072 | #[test] 1073 | fn ascii_case() { 1074 | assert_eq!(AsciiChar::At.to_ascii_lowercase(), AsciiChar::At); 1075 | assert_eq!(AsciiChar::At.to_ascii_uppercase(), AsciiChar::At); 1076 | assert_eq!(AsciiChar::A.to_ascii_lowercase(), AsciiChar::a); 1077 | assert_eq!(AsciiChar::A.to_ascii_uppercase(), AsciiChar::A); 1078 | assert_eq!(AsciiChar::a.to_ascii_lowercase(), AsciiChar::a); 1079 | assert_eq!(AsciiChar::a.to_ascii_uppercase(), AsciiChar::A); 1080 | 1081 | let mut mutable = (AsciiChar::A, AsciiChar::a); 1082 | mutable.0.make_ascii_lowercase(); 1083 | mutable.1.make_ascii_uppercase(); 1084 | assert_eq!(mutable.0, AsciiChar::a); 1085 | assert_eq!(mutable.1, AsciiChar::A); 1086 | 1087 | assert!(AsciiChar::LineFeed.eq_ignore_ascii_case(&AsciiChar::LineFeed)); 1088 | assert!(!AsciiChar::LineFeed.eq_ignore_ascii_case(&AsciiChar::CarriageReturn)); 1089 | assert!(AsciiChar::z.eq_ignore_ascii_case(&AsciiChar::Z)); 1090 | assert!(AsciiChar::Z.eq_ignore_ascii_case(&AsciiChar::z)); 1091 | assert!(AsciiChar::A.eq_ignore_ascii_case(&AsciiChar::a)); 1092 | assert!(!AsciiChar::K.eq_ignore_ascii_case(&AsciiChar::C)); 1093 | assert!(!AsciiChar::Z.eq_ignore_ascii_case(&AsciiChar::DEL)); 1094 | assert!(!AsciiChar::BracketOpen.eq_ignore_ascii_case(&AsciiChar::CurlyBraceOpen)); 1095 | assert!(!AsciiChar::Grave.eq_ignore_ascii_case(&AsciiChar::At)); 1096 | assert!(!AsciiChar::Grave.eq_ignore_ascii_case(&AsciiChar::DEL)); 1097 | } 1098 | 1099 | #[test] 1100 | #[cfg(feature = "std")] 1101 | fn fmt_ascii() { 1102 | assert_eq!(format!("{}", AsciiChar::t), "t"); 1103 | assert_eq!(format!("{:?}", AsciiChar::t), "'t'"); 1104 | assert_eq!(format!("{}", AsciiChar::LineFeed), "\n"); 1105 | assert_eq!(format!("{:?}", AsciiChar::LineFeed), "'\\n'"); 1106 | } 1107 | } 1108 | -------------------------------------------------------------------------------- /src/ascii_str.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "alloc")] 2 | use alloc::borrow::ToOwned; 3 | #[cfg(feature = "alloc")] 4 | use alloc::boxed::Box; 5 | use core::{fmt, mem}; 6 | use core::ops::{Index, IndexMut}; 7 | use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; 8 | use core::slice::{self, Iter, IterMut, SliceIndex}; 9 | #[cfg(feature = "std")] 10 | use std::error::Error; 11 | #[cfg(feature = "std")] 12 | use std::ffi::CStr; 13 | 14 | use ascii_char::AsciiChar; 15 | #[cfg(feature = "alloc")] 16 | use ascii_string::AsciiString; 17 | 18 | /// [`AsciiStr`] represents a byte or string slice that only contains ASCII characters. 19 | /// 20 | /// It wraps an `[AsciiChar]` and implements many of `str`s methods and traits. 21 | /// 22 | /// It can be created by a checked conversion from a `str` or `[u8]`, or borrowed from an 23 | /// `AsciiString`. 24 | #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] 25 | #[repr(transparent)] 26 | pub struct AsciiStr { 27 | slice: [AsciiChar], 28 | } 29 | 30 | impl AsciiStr { 31 | /// Coerces into an `AsciiStr` slice. 32 | /// 33 | /// # Examples 34 | /// ``` 35 | /// # use ascii::{AsciiChar, AsciiStr}; 36 | /// const HELLO: &AsciiStr = AsciiStr::new( 37 | /// &[AsciiChar::H, AsciiChar::e, AsciiChar::l, AsciiChar::l, AsciiChar::o] 38 | /// ); 39 | /// 40 | /// assert_eq!(HELLO.as_str(), "Hello"); 41 | /// ``` 42 | #[inline] 43 | #[must_use] 44 | pub const fn new(s: &[AsciiChar]) -> &Self { 45 | unsafe { mem::transmute(s) } 46 | } 47 | 48 | /// Converts `&self` to a `&str` slice. 49 | #[inline] 50 | #[must_use] 51 | pub const fn as_str(&self) -> &str { 52 | // SAFETY: All variants of `AsciiChar` are valid bytes for a `str`. 53 | unsafe { mem::transmute(self) } 54 | } 55 | 56 | /// Converts `&self` into a byte slice. 57 | #[inline] 58 | #[must_use] 59 | pub const fn as_bytes(&self) -> &[u8] { 60 | // SAFETY: All variants of `AsciiChar` are valid `u8`, given they're `repr(u8)`. 61 | unsafe { mem::transmute(self) } 62 | } 63 | 64 | /// Returns the entire string as slice of `AsciiChar`s. 65 | #[inline] 66 | #[must_use] 67 | pub const fn as_slice(&self) -> &[AsciiChar] { 68 | &self.slice 69 | } 70 | 71 | /// Returns the entire string as mutable slice of `AsciiChar`s. 72 | #[inline] 73 | #[must_use] 74 | pub fn as_mut_slice(&mut self) -> &mut [AsciiChar] { 75 | &mut self.slice 76 | } 77 | 78 | /// Returns a raw pointer to the `AsciiStr`'s buffer. 79 | /// 80 | /// The caller must ensure that the slice outlives the pointer this function returns, or else it 81 | /// will end up pointing to garbage. Modifying the `AsciiStr` may cause it's buffer to be 82 | /// reallocated, which would also make any pointers to it invalid. 83 | #[inline] 84 | #[must_use] 85 | pub const fn as_ptr(&self) -> *const AsciiChar { 86 | self.as_slice().as_ptr() 87 | } 88 | 89 | /// Returns an unsafe mutable pointer to the `AsciiStr`'s buffer. 90 | /// 91 | /// The caller must ensure that the slice outlives the pointer this function returns, or else it 92 | /// will end up pointing to garbage. Modifying the `AsciiStr` may cause it's buffer to be 93 | /// reallocated, which would also make any pointers to it invalid. 94 | #[inline] 95 | #[must_use] 96 | pub fn as_mut_ptr(&mut self) -> *mut AsciiChar { 97 | self.as_mut_slice().as_mut_ptr() 98 | } 99 | 100 | /// Copies the content of this `AsciiStr` into an owned `AsciiString`. 101 | #[cfg(feature = "alloc")] 102 | #[must_use] 103 | pub fn to_ascii_string(&self) -> AsciiString { 104 | AsciiString::from(self.slice.to_vec()) 105 | } 106 | 107 | /// Converts anything that can represent a byte slice into an `AsciiStr`. 108 | /// 109 | /// # Errors 110 | /// If `bytes` contains a non-ascii byte, `Err` will be returned 111 | /// 112 | /// # Examples 113 | /// ``` 114 | /// # use ascii::AsciiStr; 115 | /// let foo = AsciiStr::from_ascii(b"foo"); 116 | /// let err = AsciiStr::from_ascii("Ŋ"); 117 | /// assert_eq!(foo.unwrap().as_str(), "foo"); 118 | /// assert_eq!(err.unwrap_err().valid_up_to(), 0); 119 | /// ``` 120 | #[inline] 121 | pub fn from_ascii(bytes: &B) -> Result<&AsciiStr, AsAsciiStrError> 122 | where 123 | B: AsRef<[u8]> + ?Sized, 124 | { 125 | bytes.as_ref().as_ascii_str() 126 | } 127 | 128 | /// Convert a byte slice innto an `AsciiStr`. 129 | /// 130 | /// [`from_ascii()`](#method.from_ascii) should be preferred outside of `const` contexts 131 | /// as it might be faster due to using functions that are not `const fn`. 132 | /// 133 | /// # Errors 134 | /// Returns `Err` if not all bytes are valid ASCII values. 135 | /// 136 | /// # Examples 137 | /// ``` 138 | /// # use ascii::AsciiStr; 139 | /// assert!(AsciiStr::from_ascii_bytes(b"\x00\x22\x44").is_ok()); 140 | /// assert!(AsciiStr::from_ascii_bytes(b"\x66\x77\x88").is_err()); 141 | /// ``` 142 | pub const fn from_ascii_bytes(b: &[u8]) -> Result<&Self, AsAsciiStrError> { 143 | #![allow(clippy::indexing_slicing)] // .get() is not const yes (as of Rust 1.61) 144 | let mut valid = 0; 145 | loop { 146 | if valid == b.len() { 147 | // SAFETY: `is_ascii` having returned true for all bytes guarantees all bytes are within ascii range. 148 | return unsafe { Ok(mem::transmute(b)) }; 149 | } else if b[valid].is_ascii() { 150 | valid += 1; 151 | } else { 152 | return Err(AsAsciiStrError(valid)); 153 | } 154 | } 155 | } 156 | 157 | /// Convert a `str` innto an `AsciiStr`. 158 | /// 159 | /// [`from_ascii()`](#method.from_ascii) should be preferred outside of `const` contexts 160 | /// as it might be faster due to using functions that are not `const fn`. 161 | /// 162 | /// # Errors 163 | /// Returns `Err` if it contains non-ASCII codepoints. 164 | /// 165 | /// # Examples 166 | /// ``` 167 | /// # use ascii::AsciiStr; 168 | /// assert!(AsciiStr::from_ascii_str("25 C").is_ok()); 169 | /// assert!(AsciiStr::from_ascii_str("35°C").is_err()); 170 | /// ``` 171 | pub const fn from_ascii_str(s: &str) -> Result<&Self, AsAsciiStrError> { 172 | Self::from_ascii_bytes(s.as_bytes()) 173 | } 174 | 175 | /// Converts anything that can be represented as a byte slice to an `AsciiStr` without checking 176 | /// for non-ASCII characters.. 177 | /// 178 | /// # Safety 179 | /// If any of the bytes in `bytes` do not represent valid ascii characters, calling 180 | /// this function is undefined behavior. 181 | /// 182 | /// # Examples 183 | /// ``` 184 | /// # use ascii::AsciiStr; 185 | /// let foo = unsafe { AsciiStr::from_ascii_unchecked(&b"foo"[..]) }; 186 | /// assert_eq!(foo.as_str(), "foo"); 187 | /// ``` 188 | #[inline] 189 | #[must_use] 190 | pub unsafe fn from_ascii_unchecked(bytes: &[u8]) -> &AsciiStr { 191 | // SAFETY: Caller guarantees all bytes in `bytes` are valid 192 | // ascii characters. 193 | unsafe { bytes.as_ascii_str_unchecked() } 194 | } 195 | 196 | /// Returns the number of characters / bytes in this ASCII sequence. 197 | /// 198 | /// # Examples 199 | /// ``` 200 | /// # use ascii::AsciiStr; 201 | /// let s = AsciiStr::from_ascii("foo").unwrap(); 202 | /// assert_eq!(s.len(), 3); 203 | /// ``` 204 | #[inline] 205 | #[must_use] 206 | pub const fn len(&self) -> usize { 207 | self.slice.len() 208 | } 209 | 210 | /// Returns true if the ASCII slice contains zero bytes. 211 | /// 212 | /// # Examples 213 | /// ``` 214 | /// # use ascii::AsciiStr; 215 | /// let mut empty = AsciiStr::from_ascii("").unwrap(); 216 | /// let mut full = AsciiStr::from_ascii("foo").unwrap(); 217 | /// assert!(empty.is_empty()); 218 | /// assert!(!full.is_empty()); 219 | /// ``` 220 | #[inline] 221 | #[must_use] 222 | pub const fn is_empty(&self) -> bool { 223 | self.len() == 0 224 | } 225 | 226 | /// Returns an iterator over the characters of the `AsciiStr`. 227 | #[inline] 228 | #[must_use] 229 | pub fn chars(&self) -> Chars { 230 | Chars(self.slice.iter()) 231 | } 232 | 233 | /// Returns an iterator over the characters of the `AsciiStr` which allows you to modify the 234 | /// value of each `AsciiChar`. 235 | #[inline] 236 | #[must_use] 237 | pub fn chars_mut(&mut self) -> CharsMut { 238 | CharsMut(self.slice.iter_mut()) 239 | } 240 | 241 | /// Returns an iterator over parts of the `AsciiStr` separated by a character. 242 | /// 243 | /// # Examples 244 | /// ``` 245 | /// # use ascii::{AsciiStr, AsciiChar}; 246 | /// let words = AsciiStr::from_ascii("apple banana lemon").unwrap() 247 | /// .split(AsciiChar::Space) 248 | /// .map(|a| a.as_str()) 249 | /// .collect::>(); 250 | /// assert_eq!(words, ["apple", "banana", "lemon"]); 251 | /// ``` 252 | #[must_use] 253 | pub fn split(&self, on: AsciiChar) -> impl DoubleEndedIterator { 254 | Split { 255 | on, 256 | ended: false, 257 | chars: self.chars(), 258 | } 259 | } 260 | 261 | /// Returns an iterator over the lines of the `AsciiStr`, which are themselves `AsciiStr`s. 262 | /// 263 | /// Lines are ended with either `LineFeed` (`\n`), or `CarriageReturn` then `LineFeed` (`\r\n`). 264 | /// 265 | /// The final line ending is optional. 266 | #[inline] 267 | #[must_use] 268 | pub fn lines(&self) -> impl DoubleEndedIterator { 269 | Lines { string: self } 270 | } 271 | 272 | /// Returns an ASCII string slice with leading and trailing whitespace removed. 273 | /// 274 | /// # Examples 275 | /// ``` 276 | /// # use ascii::AsciiStr; 277 | /// let example = AsciiStr::from_ascii(" \twhite \tspace \t").unwrap(); 278 | /// assert_eq!("white \tspace", example.trim()); 279 | /// ``` 280 | #[must_use] 281 | pub const fn trim(&self) -> &Self { 282 | self.trim_start().trim_end() 283 | } 284 | 285 | /// Returns an ASCII string slice with leading whitespace removed. 286 | /// 287 | /// # Examples 288 | /// ``` 289 | /// # use ascii::AsciiStr; 290 | /// let example = AsciiStr::from_ascii(" \twhite \tspace \t").unwrap(); 291 | /// assert_eq!("white \tspace \t", example.trim_start()); 292 | /// ``` 293 | #[must_use] 294 | pub const fn trim_start(&self) -> &Self { 295 | let mut trimmed = &self.slice; 296 | while let Some((first, rest)) = trimmed.split_first() { 297 | if first.is_whitespace() { 298 | trimmed = rest; 299 | } else { 300 | break; 301 | } 302 | } 303 | AsciiStr::new(trimmed) 304 | } 305 | 306 | /// Returns an ASCII string slice with trailing whitespace removed. 307 | /// 308 | /// # Examples 309 | /// ``` 310 | /// # use ascii::AsciiStr; 311 | /// let example = AsciiStr::from_ascii(" \twhite \tspace \t").unwrap(); 312 | /// assert_eq!(" \twhite \tspace", example.trim_end()); 313 | /// ``` 314 | #[must_use] 315 | pub const fn trim_end(&self) -> &Self { 316 | let mut trimmed = &self.slice; 317 | while let Some((last, rest)) = trimmed.split_last() { 318 | if last.is_whitespace() { 319 | trimmed = rest; 320 | } else { 321 | break; 322 | } 323 | } 324 | AsciiStr::new(trimmed) 325 | } 326 | 327 | /// Compares two strings case-insensitively. 328 | #[must_use] 329 | pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { 330 | self.len() == other.len() 331 | && self 332 | .chars() 333 | .zip(other.chars()) 334 | .all(|(ch, other_ch)| ch.eq_ignore_ascii_case(&other_ch)) 335 | } 336 | 337 | /// Replaces lowercase letters with their uppercase equivalent. 338 | pub fn make_ascii_uppercase(&mut self) { 339 | for ch in self.chars_mut() { 340 | *ch = ch.to_ascii_uppercase(); 341 | } 342 | } 343 | 344 | /// Replaces uppercase letters with their lowercase equivalent. 345 | pub fn make_ascii_lowercase(&mut self) { 346 | for ch in self.chars_mut() { 347 | *ch = ch.to_ascii_lowercase(); 348 | } 349 | } 350 | 351 | /// Returns a copy of this string where letters 'a' to 'z' are mapped to 'A' to 'Z'. 352 | #[cfg(feature = "alloc")] 353 | #[must_use] 354 | pub fn to_ascii_uppercase(&self) -> AsciiString { 355 | let mut ascii_string = self.to_ascii_string(); 356 | ascii_string.make_ascii_uppercase(); 357 | ascii_string 358 | } 359 | 360 | /// Returns a copy of this string where letters 'A' to 'Z' are mapped to 'a' to 'z'. 361 | #[cfg(feature = "alloc")] 362 | #[must_use] 363 | pub fn to_ascii_lowercase(&self) -> AsciiString { 364 | let mut ascii_string = self.to_ascii_string(); 365 | ascii_string.make_ascii_lowercase(); 366 | ascii_string 367 | } 368 | 369 | /// Returns the first character if the string is not empty. 370 | #[inline] 371 | #[must_use] 372 | pub fn first(&self) -> Option { 373 | self.slice.first().copied() 374 | } 375 | 376 | /// Returns the last character if the string is not empty. 377 | #[inline] 378 | #[must_use] 379 | pub fn last(&self) -> Option { 380 | self.slice.last().copied() 381 | } 382 | 383 | /// Converts a [`Box`] into a [`AsciiString`] without copying or allocating. 384 | #[cfg(feature = "alloc")] 385 | #[inline] 386 | #[must_use] 387 | pub fn into_ascii_string(self: Box) -> AsciiString { 388 | let slice = Box::<[AsciiChar]>::from(self); 389 | AsciiString::from(slice.into_vec()) 390 | } 391 | } 392 | 393 | macro_rules! impl_partial_eq { 394 | ($wider: ty) => { 395 | impl PartialEq<$wider> for AsciiStr { 396 | #[inline] 397 | fn eq(&self, other: &$wider) -> bool { 398 | >::as_ref(self) == other 399 | } 400 | } 401 | impl PartialEq for $wider { 402 | #[inline] 403 | fn eq(&self, other: &AsciiStr) -> bool { 404 | self == >::as_ref(other) 405 | } 406 | } 407 | }; 408 | } 409 | 410 | impl_partial_eq! {str} 411 | impl_partial_eq! {[u8]} 412 | impl_partial_eq! {[AsciiChar]} 413 | 414 | #[cfg(feature = "alloc")] 415 | impl ToOwned for AsciiStr { 416 | type Owned = AsciiString; 417 | 418 | #[inline] 419 | fn to_owned(&self) -> AsciiString { 420 | self.to_ascii_string() 421 | } 422 | } 423 | 424 | impl AsRef<[u8]> for AsciiStr { 425 | #[inline] 426 | fn as_ref(&self) -> &[u8] { 427 | self.as_bytes() 428 | } 429 | } 430 | impl AsRef for AsciiStr { 431 | #[inline] 432 | fn as_ref(&self) -> &str { 433 | self.as_str() 434 | } 435 | } 436 | impl AsRef<[AsciiChar]> for AsciiStr { 437 | #[inline] 438 | fn as_ref(&self) -> &[AsciiChar] { 439 | &self.slice 440 | } 441 | } 442 | impl AsMut<[AsciiChar]> for AsciiStr { 443 | #[inline] 444 | fn as_mut(&mut self) -> &mut [AsciiChar] { 445 | &mut self.slice 446 | } 447 | } 448 | 449 | impl Default for &'static AsciiStr { 450 | #[inline] 451 | fn default() -> &'static AsciiStr { 452 | From::from(&[] as &[AsciiChar]) 453 | } 454 | } 455 | impl<'a> From<&'a [AsciiChar]> for &'a AsciiStr { 456 | #[inline] 457 | fn from(slice: &[AsciiChar]) -> &AsciiStr { 458 | let ptr = slice as *const [AsciiChar] as *const AsciiStr; 459 | unsafe { &*ptr } 460 | } 461 | } 462 | impl<'a> From<&'a mut [AsciiChar]> for &'a mut AsciiStr { 463 | #[inline] 464 | fn from(slice: &mut [AsciiChar]) -> &mut AsciiStr { 465 | let ptr = slice as *mut [AsciiChar] as *mut AsciiStr; 466 | unsafe { &mut *ptr } 467 | } 468 | } 469 | #[cfg(feature = "alloc")] 470 | impl From> for Box { 471 | #[inline] 472 | fn from(owned: Box<[AsciiChar]>) -> Box { 473 | let ptr = Box::into_raw(owned) as *mut AsciiStr; 474 | unsafe { Box::from_raw(ptr) } 475 | } 476 | } 477 | 478 | impl AsRef for AsciiStr { 479 | #[inline] 480 | fn as_ref(&self) -> &AsciiStr { 481 | self 482 | } 483 | } 484 | impl AsMut for AsciiStr { 485 | #[inline] 486 | fn as_mut(&mut self) -> &mut AsciiStr { 487 | self 488 | } 489 | } 490 | impl AsRef for [AsciiChar] { 491 | #[inline] 492 | fn as_ref(&self) -> &AsciiStr { 493 | self.into() 494 | } 495 | } 496 | impl AsMut for [AsciiChar] { 497 | #[inline] 498 | fn as_mut(&mut self) -> &mut AsciiStr { 499 | self.into() 500 | } 501 | } 502 | 503 | impl<'a> From<&'a AsciiStr> for &'a [AsciiChar] { 504 | #[inline] 505 | fn from(astr: &AsciiStr) -> &[AsciiChar] { 506 | &astr.slice 507 | } 508 | } 509 | impl<'a> From<&'a mut AsciiStr> for &'a mut [AsciiChar] { 510 | #[inline] 511 | fn from(astr: &mut AsciiStr) -> &mut [AsciiChar] { 512 | &mut astr.slice 513 | } 514 | } 515 | impl<'a> From<&'a AsciiStr> for &'a [u8] { 516 | #[inline] 517 | fn from(astr: &AsciiStr) -> &[u8] { 518 | astr.as_bytes() 519 | } 520 | } 521 | impl<'a> From<&'a AsciiStr> for &'a str { 522 | #[inline] 523 | fn from(astr: &AsciiStr) -> &str { 524 | astr.as_str() 525 | } 526 | } 527 | macro_rules! widen_box { 528 | ($wider: ty) => { 529 | #[cfg(feature = "alloc")] 530 | impl From> for Box<$wider> { 531 | #[inline] 532 | fn from(owned: Box) -> Box<$wider> { 533 | let ptr = Box::into_raw(owned) as *mut $wider; 534 | unsafe { Box::from_raw(ptr) } 535 | } 536 | } 537 | }; 538 | } 539 | widen_box! {[AsciiChar]} 540 | widen_box! {[u8]} 541 | widen_box! {str} 542 | 543 | // allows &AsciiChar to be used by generic AsciiString Extend and FromIterator 544 | impl AsRef for AsciiChar { 545 | fn as_ref(&self) -> &AsciiStr { 546 | slice::from_ref(self).into() 547 | } 548 | } 549 | 550 | impl fmt::Display for AsciiStr { 551 | #[inline] 552 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 553 | fmt::Display::fmt(self.as_str(), f) 554 | } 555 | } 556 | 557 | impl fmt::Debug for AsciiStr { 558 | #[inline] 559 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 560 | fmt::Debug::fmt(self.as_str(), f) 561 | } 562 | } 563 | 564 | macro_rules! impl_index { 565 | ($idx:ty) => { 566 | #[allow(clippy::indexing_slicing)] // In `Index`, if it's out of bounds, panic is the default 567 | impl Index<$idx> for AsciiStr { 568 | type Output = AsciiStr; 569 | 570 | #[inline] 571 | fn index(&self, index: $idx) -> &AsciiStr { 572 | self.slice[index].as_ref() 573 | } 574 | } 575 | 576 | #[allow(clippy::indexing_slicing)] // In `IndexMut`, if it's out of bounds, panic is the default 577 | impl IndexMut<$idx> for AsciiStr { 578 | #[inline] 579 | fn index_mut(&mut self, index: $idx) -> &mut AsciiStr { 580 | self.slice[index].as_mut() 581 | } 582 | } 583 | }; 584 | } 585 | 586 | impl_index! { Range } 587 | impl_index! { RangeTo } 588 | impl_index! { RangeFrom } 589 | impl_index! { RangeFull } 590 | impl_index! { RangeInclusive } 591 | impl_index! { RangeToInclusive } 592 | 593 | #[allow(clippy::indexing_slicing)] // In `Index`, if it's out of bounds, panic is the default 594 | impl Index for AsciiStr { 595 | type Output = AsciiChar; 596 | 597 | #[inline] 598 | fn index(&self, index: usize) -> &AsciiChar { 599 | &self.slice[index] 600 | } 601 | } 602 | 603 | #[allow(clippy::indexing_slicing)] // In `IndexMut`, if it's out of bounds, panic is the default 604 | impl IndexMut for AsciiStr { 605 | #[inline] 606 | fn index_mut(&mut self, index: usize) -> &mut AsciiChar { 607 | &mut self.slice[index] 608 | } 609 | } 610 | 611 | /// Produces references for compatibility with `[u8]`. 612 | /// 613 | /// (`str` doesn't implement `IntoIterator` for its references, 614 | /// so there is no compatibility to lose.) 615 | impl<'a> IntoIterator for &'a AsciiStr { 616 | type Item = &'a AsciiChar; 617 | type IntoIter = CharsRef<'a>; 618 | #[inline] 619 | fn into_iter(self) -> Self::IntoIter { 620 | CharsRef(self.as_slice().iter()) 621 | } 622 | } 623 | 624 | impl<'a> IntoIterator for &'a mut AsciiStr { 625 | type Item = &'a mut AsciiChar; 626 | type IntoIter = CharsMut<'a>; 627 | #[inline] 628 | fn into_iter(self) -> Self::IntoIter { 629 | self.chars_mut() 630 | } 631 | } 632 | 633 | /// A copying iterator over the characters of an `AsciiStr`. 634 | #[derive(Clone, Debug)] 635 | pub struct Chars<'a>(Iter<'a, AsciiChar>); 636 | impl<'a> Chars<'a> { 637 | /// Returns the ascii string slice with the remaining characters. 638 | #[must_use] 639 | pub fn as_str(&self) -> &'a AsciiStr { 640 | self.0.as_slice().into() 641 | } 642 | } 643 | impl<'a> Iterator for Chars<'a> { 644 | type Item = AsciiChar; 645 | #[inline] 646 | fn next(&mut self) -> Option { 647 | self.0.next().copied() 648 | } 649 | fn size_hint(&self) -> (usize, Option) { 650 | self.0.size_hint() 651 | } 652 | } 653 | impl<'a> DoubleEndedIterator for Chars<'a> { 654 | #[inline] 655 | fn next_back(&mut self) -> Option { 656 | self.0.next_back().copied() 657 | } 658 | } 659 | impl<'a> ExactSizeIterator for Chars<'a> { 660 | fn len(&self) -> usize { 661 | self.0.len() 662 | } 663 | } 664 | 665 | /// A mutable iterator over the characters of an `AsciiStr`. 666 | #[derive(Debug)] 667 | pub struct CharsMut<'a>(IterMut<'a, AsciiChar>); 668 | impl<'a> CharsMut<'a> { 669 | /// Returns the ascii string slice with the remaining characters. 670 | #[must_use] 671 | pub fn into_str(self) -> &'a mut AsciiStr { 672 | self.0.into_slice().into() 673 | } 674 | } 675 | impl<'a> Iterator for CharsMut<'a> { 676 | type Item = &'a mut AsciiChar; 677 | #[inline] 678 | fn next(&mut self) -> Option<&'a mut AsciiChar> { 679 | self.0.next() 680 | } 681 | fn size_hint(&self) -> (usize, Option) { 682 | self.0.size_hint() 683 | } 684 | } 685 | impl<'a> DoubleEndedIterator for CharsMut<'a> { 686 | #[inline] 687 | fn next_back(&mut self) -> Option<&'a mut AsciiChar> { 688 | self.0.next_back() 689 | } 690 | } 691 | impl<'a> ExactSizeIterator for CharsMut<'a> { 692 | fn len(&self) -> usize { 693 | self.0.len() 694 | } 695 | } 696 | 697 | /// An immutable iterator over the characters of an `AsciiStr`. 698 | #[derive(Clone, Debug)] 699 | pub struct CharsRef<'a>(Iter<'a, AsciiChar>); 700 | impl<'a> CharsRef<'a> { 701 | /// Returns the ascii string slice with the remaining characters. 702 | #[must_use] 703 | pub fn as_str(&self) -> &'a AsciiStr { 704 | self.0.as_slice().into() 705 | } 706 | } 707 | impl<'a> Iterator for CharsRef<'a> { 708 | type Item = &'a AsciiChar; 709 | #[inline] 710 | fn next(&mut self) -> Option<&'a AsciiChar> { 711 | self.0.next() 712 | } 713 | fn size_hint(&self) -> (usize, Option) { 714 | self.0.size_hint() 715 | } 716 | } 717 | impl<'a> DoubleEndedIterator for CharsRef<'a> { 718 | #[inline] 719 | fn next_back(&mut self) -> Option<&'a AsciiChar> { 720 | self.0.next_back() 721 | } 722 | } 723 | 724 | /// An iterator over parts of an `AsciiStr` separated by an `AsciiChar`. 725 | /// 726 | /// This type is created by [`AsciiChar::split()`](struct.AsciiChar.html#method.split). 727 | #[derive(Clone, Debug)] 728 | struct Split<'a> { 729 | on: AsciiChar, 730 | ended: bool, 731 | chars: Chars<'a>, 732 | } 733 | impl<'a> Iterator for Split<'a> { 734 | type Item = &'a AsciiStr; 735 | 736 | fn next(&mut self) -> Option<&'a AsciiStr> { 737 | if !self.ended { 738 | let start: &AsciiStr = self.chars.as_str(); 739 | let split_on = self.on; 740 | 741 | if let Some(at) = self.chars.position(|ch| ch == split_on) { 742 | // SAFETY: `at` is guaranteed to be in bounds, as `position` returns `Ok(0..len)`. 743 | Some(unsafe { start.as_slice().get_unchecked(..at).into() }) 744 | } else { 745 | self.ended = true; 746 | Some(start) 747 | } 748 | } else { 749 | None 750 | } 751 | } 752 | } 753 | impl<'a> DoubleEndedIterator for Split<'a> { 754 | fn next_back(&mut self) -> Option<&'a AsciiStr> { 755 | if !self.ended { 756 | let start: &AsciiStr = self.chars.as_str(); 757 | let split_on = self.on; 758 | 759 | if let Some(at) = self.chars.rposition(|ch| ch == split_on) { 760 | // SAFETY: `at` is guaranteed to be in bounds, as `rposition` returns `Ok(0..len)`, and slices `1..`, `2..`, etc... until `len..` inclusive, are valid. 761 | Some(unsafe { start.as_slice().get_unchecked(at + 1..).into() }) 762 | } else { 763 | self.ended = true; 764 | Some(start) 765 | } 766 | } else { 767 | None 768 | } 769 | } 770 | } 771 | 772 | /// An iterator over the lines of the internal character array. 773 | #[derive(Clone, Debug)] 774 | struct Lines<'a> { 775 | string: &'a AsciiStr, 776 | } 777 | impl<'a> Iterator for Lines<'a> { 778 | type Item = &'a AsciiStr; 779 | 780 | fn next(&mut self) -> Option<&'a AsciiStr> { 781 | if let Some(idx) = self 782 | .string 783 | .chars() 784 | .position(|chr| chr == AsciiChar::LineFeed) 785 | { 786 | // SAFETY: `idx` is guaranteed to be `1..len`, as we get it from `position` as `0..len` and make sure it's not `0`. 787 | let line = if idx > 0 788 | && *unsafe { self.string.as_slice().get_unchecked(idx - 1) } 789 | == AsciiChar::CarriageReturn 790 | { 791 | // SAFETY: As per above, `idx` is guaranteed to be `1..len` 792 | unsafe { self.string.as_slice().get_unchecked(..idx - 1).into() } 793 | } else { 794 | // SAFETY: As per above, `idx` is guaranteed to be `0..len` 795 | unsafe { self.string.as_slice().get_unchecked(..idx).into() } 796 | }; 797 | // SAFETY: As per above, `idx` is guaranteed to be `0..len`, so at the extreme, slicing `len..` is a valid empty slice. 798 | self.string = unsafe { self.string.as_slice().get_unchecked(idx + 1..).into() }; 799 | Some(line) 800 | } else if self.string.is_empty() { 801 | None 802 | } else { 803 | let line = self.string; 804 | // SAFETY: An empty string is a valid string. 805 | self.string = unsafe { AsciiStr::from_ascii_unchecked(b"") }; 806 | Some(line) 807 | } 808 | } 809 | } 810 | 811 | impl<'a> DoubleEndedIterator for Lines<'a> { 812 | fn next_back(&mut self) -> Option<&'a AsciiStr> { 813 | if self.string.is_empty() { 814 | return None; 815 | } 816 | 817 | // If we end with `LF` / `CR/LF`, remove them 818 | if let Some(AsciiChar::LineFeed) = self.string.last() { 819 | // SAFETY: `last()` returned `Some`, so our len is at least 1. 820 | self.string = unsafe { 821 | self.string 822 | .as_slice() 823 | .get_unchecked(..self.string.len() - 1) 824 | .into() 825 | }; 826 | 827 | if let Some(AsciiChar::CarriageReturn) = self.string.last() { 828 | // SAFETY: `last()` returned `Some`, so our len is at least 1. 829 | self.string = unsafe { 830 | self.string 831 | .as_slice() 832 | .get_unchecked(..self.string.len() - 1) 833 | .into() 834 | }; 835 | } 836 | } 837 | 838 | // Get the position of the first `LF` from the end. 839 | let lf_rev_pos = self 840 | .string 841 | .chars() 842 | .rev() 843 | .position(|ch| ch == AsciiChar::LineFeed) 844 | .unwrap_or_else(|| self.string.len()); 845 | 846 | // SAFETY: `lf_rev_pos` will be in range `0..=len`, so `len - lf_rev_pos` 847 | // will be within `0..=len`, making it correct as a start and end 848 | // point for the strings. 849 | let line = unsafe { 850 | self.string 851 | .as_slice() 852 | .get_unchecked(self.string.len() - lf_rev_pos..) 853 | .into() 854 | }; 855 | self.string = unsafe { 856 | self.string 857 | .as_slice() 858 | .get_unchecked(..self.string.len() - lf_rev_pos) 859 | .into() 860 | }; 861 | Some(line) 862 | } 863 | } 864 | 865 | /// Error that is returned when a sequence of `u8` are not all ASCII. 866 | /// 867 | /// Is used by `As[Mut]AsciiStr` and the `from_ascii` method on `AsciiStr` and `AsciiString`. 868 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 869 | pub struct AsAsciiStrError(usize); 870 | 871 | const ERRORMSG_STR: &str = "one or more bytes are not ASCII"; 872 | 873 | impl AsAsciiStrError { 874 | /// Returns the index of the first non-ASCII byte. 875 | /// 876 | /// It is the maximum index such that `from_ascii(input[..index])` would return `Ok(_)`. 877 | #[inline] 878 | #[must_use] 879 | pub const fn valid_up_to(self) -> usize { 880 | self.0 881 | } 882 | #[cfg(not(feature = "std"))] 883 | /// Returns a description for this error, like `std::error::Error::description`. 884 | #[inline] 885 | #[must_use] 886 | #[allow(clippy::unused_self)] 887 | pub const fn description(&self) -> &'static str { 888 | ERRORMSG_STR 889 | } 890 | } 891 | impl fmt::Display for AsAsciiStrError { 892 | fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { 893 | write!(fmtr, "the byte at index {} is not ASCII", self.0) 894 | } 895 | } 896 | #[cfg(feature = "std")] 897 | impl Error for AsAsciiStrError { 898 | #[inline] 899 | fn description(&self) -> &'static str { 900 | ERRORMSG_STR 901 | } 902 | } 903 | 904 | /// Convert slices of bytes or [`AsciiChar`] to [`AsciiStr`]. 905 | // Could nearly replace this trait with SliceIndex, but its methods isn't even 906 | // on a path for stabilization. 907 | pub trait AsAsciiStr { 908 | /// Used to constrain `SliceIndex` 909 | #[doc(hidden)] 910 | type Inner; 911 | /// Convert a subslice to an ASCII slice. 912 | /// 913 | /// # Errors 914 | /// Returns `Err` if the range is out of bounds or if not all bytes in the 915 | /// slice are ASCII. The value in the error will be the index of the first 916 | /// non-ASCII byte or the end of the slice. 917 | /// 918 | /// # Examples 919 | /// ``` 920 | /// use ascii::AsAsciiStr; 921 | /// assert!("'zoä'".slice_ascii(..3).is_ok()); 922 | /// assert!("'zoä'".slice_ascii(0..4).is_err()); 923 | /// assert!("'zoä'".slice_ascii(5..=5).is_ok()); 924 | /// assert!("'zoä'".slice_ascii(4..).is_err()); 925 | /// assert!(b"\r\n".slice_ascii(..).is_ok()); 926 | /// ``` 927 | fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> 928 | where 929 | R: SliceIndex<[Self::Inner], Output = [Self::Inner]>; 930 | /// Convert to an ASCII slice. 931 | /// 932 | /// # Errors 933 | /// Returns `Err` if not all bytes are valid ascii values. 934 | /// 935 | /// # Example 936 | /// ``` 937 | /// use ascii::{AsAsciiStr, AsciiChar}; 938 | /// assert!("ASCII".as_ascii_str().is_ok()); 939 | /// assert!(b"\r\n".as_ascii_str().is_ok()); 940 | /// assert!("'zoä'".as_ascii_str().is_err()); 941 | /// assert!(b"\xff".as_ascii_str().is_err()); 942 | /// assert!([AsciiChar::C][..].as_ascii_str().is_ok()); // infallible 943 | /// ``` 944 | fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { 945 | self.slice_ascii(..) 946 | } 947 | /// Get a single ASCII character from the slice. 948 | /// 949 | /// Returns `None` if the index is out of bounds or the byte is not ASCII. 950 | /// 951 | /// # Examples 952 | /// ``` 953 | /// use ascii::{AsAsciiStr, AsciiChar}; 954 | /// assert_eq!("'zoä'".get_ascii(4), None); 955 | /// assert_eq!("'zoä'".get_ascii(5), Some(AsciiChar::Apostrophe)); 956 | /// assert_eq!("'zoä'".get_ascii(6), None); 957 | /// ``` 958 | fn get_ascii(&self, index: usize) -> Option { 959 | self.slice_ascii(index..=index) 960 | .ok() 961 | .and_then(AsciiStr::first) 962 | } 963 | /// Convert to an ASCII slice without checking for non-ASCII characters. 964 | /// 965 | /// # Safety 966 | /// Calling this function when `self` contains non-ascii characters is 967 | /// undefined behavior. 968 | /// 969 | /// # Examples 970 | /// 971 | unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr; 972 | } 973 | 974 | /// Convert mutable slices of bytes or [`AsciiChar`] to [`AsciiStr`]. 975 | pub trait AsMutAsciiStr: AsAsciiStr { 976 | /// Convert a subslice to an ASCII slice. 977 | /// 978 | /// # Errors 979 | /// This function returns `Err` if range is out of bounds, or if 980 | /// `self` contains non-ascii values 981 | fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> 982 | where 983 | R: SliceIndex<[Self::Inner], Output = [Self::Inner]>; 984 | 985 | /// Convert to a mutable ASCII slice. 986 | /// 987 | /// # Errors 988 | /// This function returns `Err` if `self` contains non-ascii values 989 | fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { 990 | self.slice_ascii_mut(..) 991 | } 992 | 993 | /// Convert to a mutable ASCII slice without checking for non-ASCII characters. 994 | /// 995 | /// # Safety 996 | /// Calling this function when `self` contains non-ascii characters is 997 | /// undefined behavior. 998 | unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr; 999 | } 1000 | 1001 | // These generic implementations mirror the generic implementations for AsRef in core. 1002 | impl<'a, T> AsAsciiStr for &'a T 1003 | where 1004 | T: AsAsciiStr + ?Sized, 1005 | { 1006 | type Inner = ::Inner; 1007 | fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> 1008 | where 1009 | R: SliceIndex<[Self::Inner], Output = [Self::Inner]>, 1010 | { 1011 | ::slice_ascii(*self, range) 1012 | } 1013 | 1014 | unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { 1015 | // SAFETY: Caller guarantees `self` does not contain non-ascii characters 1016 | unsafe { ::as_ascii_str_unchecked(*self) } 1017 | } 1018 | } 1019 | 1020 | impl<'a, T> AsAsciiStr for &'a mut T 1021 | where 1022 | T: AsAsciiStr + ?Sized, 1023 | { 1024 | type Inner = ::Inner; 1025 | fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> 1026 | where 1027 | R: SliceIndex<[Self::Inner], Output = [Self::Inner]>, 1028 | { 1029 | ::slice_ascii(*self, range) 1030 | } 1031 | 1032 | unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { 1033 | // SAFETY: Caller guarantees `self` does not contain non-ascii characters 1034 | unsafe { ::as_ascii_str_unchecked(*self) } 1035 | } 1036 | } 1037 | 1038 | impl<'a, T> AsMutAsciiStr for &'a mut T 1039 | where 1040 | T: AsMutAsciiStr + ?Sized, 1041 | { 1042 | fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> 1043 | where 1044 | R: SliceIndex<[Self::Inner], Output = [Self::Inner]>, 1045 | { 1046 | ::slice_ascii_mut(*self, range) 1047 | } 1048 | 1049 | unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { 1050 | // SAFETY: Caller guarantees `self` does not contain non-ascii characters 1051 | unsafe { ::as_mut_ascii_str_unchecked(*self) } 1052 | } 1053 | } 1054 | 1055 | impl AsAsciiStr for AsciiStr { 1056 | type Inner = AsciiChar; 1057 | 1058 | fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> 1059 | where 1060 | R: SliceIndex<[AsciiChar], Output = [AsciiChar]>, 1061 | { 1062 | self.slice.slice_ascii(range) 1063 | } 1064 | 1065 | #[inline] 1066 | fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { 1067 | Ok(self) 1068 | } 1069 | 1070 | #[inline] 1071 | unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { 1072 | self 1073 | } 1074 | 1075 | #[inline] 1076 | fn get_ascii(&self, index: usize) -> Option { 1077 | self.slice.get_ascii(index) 1078 | } 1079 | } 1080 | impl AsMutAsciiStr for AsciiStr { 1081 | fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> 1082 | where 1083 | R: SliceIndex<[AsciiChar], Output = [AsciiChar]>, 1084 | { 1085 | self.slice.slice_ascii_mut(range) 1086 | } 1087 | 1088 | #[inline] 1089 | unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { 1090 | self 1091 | } 1092 | } 1093 | 1094 | impl AsAsciiStr for [AsciiChar] { 1095 | type Inner = AsciiChar; 1096 | fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> 1097 | where 1098 | R: SliceIndex<[AsciiChar], Output = [AsciiChar]>, 1099 | { 1100 | match self.get(range) { 1101 | Some(slice) => Ok(slice.into()), 1102 | None => Err(AsAsciiStrError(self.len())), 1103 | } 1104 | } 1105 | 1106 | #[inline] 1107 | fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { 1108 | Ok(self.into()) 1109 | } 1110 | 1111 | #[inline] 1112 | unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { 1113 | <&AsciiStr>::from(self) 1114 | } 1115 | 1116 | #[inline] 1117 | fn get_ascii(&self, index: usize) -> Option { 1118 | self.get(index).copied() 1119 | } 1120 | } 1121 | impl AsMutAsciiStr for [AsciiChar] { 1122 | fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> 1123 | where 1124 | R: SliceIndex<[AsciiChar], Output = [AsciiChar]>, 1125 | { 1126 | let len = self.len(); 1127 | match self.get_mut(range) { 1128 | Some(slice) => Ok(slice.into()), 1129 | None => Err(AsAsciiStrError(len)), 1130 | } 1131 | } 1132 | #[inline] 1133 | unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { 1134 | <&mut AsciiStr>::from(self) 1135 | } 1136 | } 1137 | 1138 | impl AsAsciiStr for [u8] { 1139 | type Inner = u8; 1140 | 1141 | fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> 1142 | where 1143 | R: SliceIndex<[u8], Output = [u8]>, 1144 | { 1145 | if let Some(slice) = self.get(range) { 1146 | slice.as_ascii_str().map_err(|AsAsciiStrError(not_ascii)| { 1147 | let offset = slice.as_ptr() as usize - self.as_ptr() as usize; 1148 | AsAsciiStrError(offset + not_ascii) 1149 | }) 1150 | } else { 1151 | Err(AsAsciiStrError(self.len())) 1152 | } 1153 | } 1154 | 1155 | fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { 1156 | // is_ascii is likely optimized 1157 | if self.is_ascii() { 1158 | // SAFETY: `is_ascii` guarantees all bytes are within ascii range. 1159 | unsafe { Ok(self.as_ascii_str_unchecked()) } 1160 | } else { 1161 | Err(AsAsciiStrError( 1162 | self.iter().take_while(|&b| b.is_ascii()).count(), 1163 | )) 1164 | } 1165 | } 1166 | 1167 | #[inline] 1168 | unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { 1169 | // SAFETY: Caller guarantees `self` does not contain non-ascii characters 1170 | unsafe { &*(self as *const [u8] as *const AsciiStr) } 1171 | } 1172 | } 1173 | impl AsMutAsciiStr for [u8] { 1174 | fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> 1175 | where 1176 | R: SliceIndex<[u8], Output = [u8]>, 1177 | { 1178 | let (ptr, len) = (self.as_ptr(), self.len()); 1179 | if let Some(slice) = self.get_mut(range) { 1180 | let slice_ptr = slice.as_ptr(); 1181 | slice 1182 | .as_mut_ascii_str() 1183 | .map_err(|AsAsciiStrError(not_ascii)| { 1184 | let offset = slice_ptr as usize - ptr as usize; 1185 | AsAsciiStrError(offset + not_ascii) 1186 | }) 1187 | } else { 1188 | Err(AsAsciiStrError(len)) 1189 | } 1190 | } 1191 | 1192 | fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { 1193 | // is_ascii() is likely optimized 1194 | if self.is_ascii() { 1195 | // SAFETY: `is_ascii` guarantees all bytes are within ascii range. 1196 | unsafe { Ok(self.as_mut_ascii_str_unchecked()) } 1197 | } else { 1198 | Err(AsAsciiStrError( 1199 | self.iter().take_while(|&b| b.is_ascii()).count(), 1200 | )) 1201 | } 1202 | } 1203 | 1204 | #[inline] 1205 | unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { 1206 | // SAFETY: Caller guarantees `self` does not contain non-ascii characters 1207 | unsafe { &mut *(self as *mut [u8] as *mut AsciiStr) } 1208 | } 1209 | } 1210 | 1211 | impl AsAsciiStr for str { 1212 | type Inner = u8; 1213 | fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> 1214 | where 1215 | R: SliceIndex<[u8], Output = [u8]>, 1216 | { 1217 | self.as_bytes().slice_ascii(range) 1218 | } 1219 | fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { 1220 | self.as_bytes().as_ascii_str() 1221 | } 1222 | #[inline] 1223 | unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { 1224 | // SAFETY: Caller guarantees `self` does not contain non-ascii characters 1225 | unsafe { self.as_bytes().as_ascii_str_unchecked() } 1226 | } 1227 | } 1228 | impl AsMutAsciiStr for str { 1229 | fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> 1230 | where 1231 | R: SliceIndex<[u8], Output = [u8]>, 1232 | { 1233 | // SAFETY: We don't modify the reference in this function, and the caller may 1234 | // only modify it to include valid ascii characters. 1235 | let bytes = unsafe { self.as_bytes_mut() }; 1236 | match bytes.get_mut(range) { 1237 | // Valid ascii slice 1238 | Some(slice) if slice.is_ascii() => { 1239 | // SAFETY: All bytes are ascii, so this cast is valid 1240 | let ptr = slice.as_mut_ptr().cast::(); 1241 | let len = slice.len(); 1242 | 1243 | // SAFETY: The pointer is valid for `len` elements, as it came 1244 | // from a slice. 1245 | unsafe { 1246 | let slice = core::slice::from_raw_parts_mut(ptr, len); 1247 | Ok(<&mut AsciiStr>::from(slice)) 1248 | } 1249 | } 1250 | Some(slice) => { 1251 | let not_ascii_len = slice.iter().copied().take_while(u8::is_ascii).count(); 1252 | let offset = slice.as_ptr() as usize - self.as_ptr() as usize; 1253 | 1254 | Err(AsAsciiStrError(offset + not_ascii_len)) 1255 | } 1256 | None => Err(AsAsciiStrError(self.len())), 1257 | } 1258 | } 1259 | fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { 1260 | match self.bytes().position(|b| !b.is_ascii()) { 1261 | Some(index) => Err(AsAsciiStrError(index)), 1262 | // SAFETY: All bytes were iterated, and all were ascii 1263 | None => unsafe { Ok(self.as_mut_ascii_str_unchecked()) }, 1264 | } 1265 | } 1266 | #[inline] 1267 | unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { 1268 | // SAFETY: Caller guarantees `self` does not contain non-ascii characters 1269 | &mut *(self as *mut str as *mut AsciiStr) 1270 | } 1271 | } 1272 | 1273 | /// Note that the trailing null byte will be removed in the conversion. 1274 | #[cfg(feature = "std")] 1275 | impl AsAsciiStr for CStr { 1276 | type Inner = u8; 1277 | fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> 1278 | where 1279 | R: SliceIndex<[u8], Output = [u8]>, 1280 | { 1281 | self.to_bytes().slice_ascii(range) 1282 | } 1283 | #[inline] 1284 | fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { 1285 | self.to_bytes().as_ascii_str() 1286 | } 1287 | #[inline] 1288 | unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { 1289 | // SAFETY: Caller guarantees `self` does not contain non-ascii characters 1290 | unsafe { self.to_bytes().as_ascii_str_unchecked() } 1291 | } 1292 | } 1293 | 1294 | #[cfg(test)] 1295 | mod tests { 1296 | use super::{AsAsciiStr, AsAsciiStrError, AsMutAsciiStr, AsciiStr}; 1297 | #[cfg(feature = "alloc")] 1298 | use alloc::string::{String, ToString}; 1299 | #[cfg(feature = "alloc")] 1300 | use alloc::vec::Vec; 1301 | use AsciiChar; 1302 | 1303 | /// Ensures that common types, `str`, `[u8]`, `AsciiStr` and their 1304 | /// references, shared and mutable implement `AsAsciiStr`. 1305 | #[test] 1306 | fn generic_as_ascii_str() { 1307 | // Generic function to ensure `C` implements `AsAsciiStr` 1308 | fn generic(c: &C) -> Result<&AsciiStr, AsAsciiStrError> { 1309 | c.as_ascii_str() 1310 | } 1311 | 1312 | let arr = [AsciiChar::A]; 1313 | let ascii_str = arr.as_ref().into(); 1314 | let mut mut_arr = arr; // Note: We need a second copy to prevent overlapping mutable borrows. 1315 | let mut_ascii_str = mut_arr.as_mut().into(); 1316 | let mut_arr_mut_ref: &mut [AsciiChar] = &mut [AsciiChar::A]; 1317 | let mut string_bytes = [b'A']; 1318 | let string_mut = unsafe { core::str::from_utf8_unchecked_mut(&mut string_bytes) }; // SAFETY: 'A' is a valid string. 1319 | let string_mut_bytes: &mut [u8] = &mut [b'A']; 1320 | 1321 | // Note: This is a trick because `rustfmt` doesn't support 1322 | // attributes on blocks yet. 1323 | #[rustfmt::skip] 1324 | let _ = [ 1325 | assert_eq!(generic::("A" ), Ok(ascii_str)), 1326 | assert_eq!(generic::<[u8] >(&b"A"[..] ), Ok(ascii_str)), 1327 | assert_eq!(generic::(ascii_str ), Ok(ascii_str)), 1328 | assert_eq!(generic::<[AsciiChar] >(&arr ), Ok(ascii_str)), 1329 | assert_eq!(generic::<&str >(&"A" ), Ok(ascii_str)), 1330 | assert_eq!(generic::<&[u8] >(&&b"A"[..] ), Ok(ascii_str)), 1331 | assert_eq!(generic::<&AsciiStr >(&ascii_str ), Ok(ascii_str)), 1332 | assert_eq!(generic::<&[AsciiChar] >(&&arr[..] ), Ok(ascii_str)), 1333 | assert_eq!(generic::<&mut str >(&string_mut ), Ok(ascii_str)), 1334 | assert_eq!(generic::<&mut [u8] >(&string_mut_bytes), Ok(ascii_str)), 1335 | assert_eq!(generic::<&mut AsciiStr >(&mut_ascii_str ), Ok(ascii_str)), 1336 | assert_eq!(generic::<&mut [AsciiChar]>(&mut_arr_mut_ref ), Ok(ascii_str)), 1337 | ]; 1338 | } 1339 | 1340 | #[cfg(feature = "std")] 1341 | #[test] 1342 | fn cstring_as_ascii_str() { 1343 | use std::ffi::CString; 1344 | fn generic(c: &C) -> Result<&AsciiStr, AsAsciiStrError> { 1345 | c.as_ascii_str() 1346 | } 1347 | let arr = [AsciiChar::A]; 1348 | let ascii_str: &AsciiStr = arr.as_ref().into(); 1349 | let cstr = CString::new("A").unwrap(); 1350 | assert_eq!(generic(&*cstr), Ok(ascii_str)); 1351 | } 1352 | 1353 | #[test] 1354 | fn generic_as_mut_ascii_str() { 1355 | fn generic_mut( 1356 | c: &mut C, 1357 | ) -> Result<&mut AsciiStr, AsAsciiStrError> { 1358 | c.as_mut_ascii_str() 1359 | } 1360 | 1361 | let mut arr_mut = [AsciiChar::B]; 1362 | let mut ascii_str_mut: &mut AsciiStr = arr_mut.as_mut().into(); 1363 | // Need a second reference to prevent overlapping mutable borrows 1364 | let mut arr_mut_2 = [AsciiChar::B]; 1365 | let ascii_str_mut_2: &mut AsciiStr = arr_mut_2.as_mut().into(); 1366 | assert_eq!(generic_mut(&mut ascii_str_mut), Ok(&mut *ascii_str_mut_2)); 1367 | assert_eq!(generic_mut(ascii_str_mut), Ok(&mut *ascii_str_mut_2)); 1368 | } 1369 | 1370 | #[test] 1371 | fn as_ascii_str() { 1372 | macro_rules! err {{$i:expr} => {Err(AsAsciiStrError($i))}} 1373 | let s = "abčd"; 1374 | let b = s.as_bytes(); 1375 | assert_eq!(s.as_ascii_str(), err!(2)); 1376 | assert_eq!(b.as_ascii_str(), err!(2)); 1377 | let a: &AsciiStr = [AsciiChar::a, AsciiChar::b][..].as_ref(); 1378 | assert_eq!(s[..2].as_ascii_str(), Ok(a)); 1379 | assert_eq!(b[..2].as_ascii_str(), Ok(a)); 1380 | assert_eq!(s.slice_ascii(..2), Ok(a)); 1381 | assert_eq!(b.slice_ascii(..2), Ok(a)); 1382 | assert_eq!(s.slice_ascii(..=2), err!(2)); 1383 | assert_eq!(b.slice_ascii(..=2), err!(2)); 1384 | assert_eq!(s.get_ascii(4), Some(AsciiChar::d)); 1385 | assert_eq!(b.get_ascii(4), Some(AsciiChar::d)); 1386 | assert_eq!(s.get_ascii(3), None); 1387 | assert_eq!(b.get_ascii(3), None); 1388 | assert_eq!(s.get_ascii(b.len()), None); 1389 | assert_eq!(b.get_ascii(b.len()), None); 1390 | assert_eq!(a.get_ascii(0), Some(AsciiChar::a)); 1391 | assert_eq!(a.get_ascii(a.len()), None); 1392 | } 1393 | 1394 | #[test] 1395 | #[cfg(feature = "std")] 1396 | fn cstr_as_ascii_str() { 1397 | use std::ffi::CStr; 1398 | macro_rules! err {{$i:expr} => {Err(AsAsciiStrError($i))}} 1399 | let cstr = CStr::from_bytes_with_nul(b"a\xbbcde\xffg\0").unwrap(); 1400 | assert_eq!(cstr.as_ascii_str(), err!(1)); 1401 | assert_eq!(cstr.slice_ascii(2..), err!(5)); 1402 | assert_eq!(cstr.get_ascii(5), None); 1403 | assert_eq!(cstr.get_ascii(6), Some(AsciiChar::g)); 1404 | assert_eq!(cstr.get_ascii(7), None); 1405 | let ascii_slice = &[AsciiChar::X, AsciiChar::Y, AsciiChar::Z, AsciiChar::Null][..]; 1406 | let ascii_str: &AsciiStr = ascii_slice.as_ref(); 1407 | let cstr = CStr::from_bytes_with_nul(ascii_str.as_bytes()).unwrap(); 1408 | assert_eq!(cstr.slice_ascii(..2), Ok(&ascii_str[..2])); 1409 | assert_eq!(cstr.as_ascii_str(), Ok(&ascii_str[..3])); 1410 | } 1411 | 1412 | #[test] 1413 | #[cfg(feature = "alloc")] 1414 | fn as_mut_ascii_str() { 1415 | macro_rules! err {{$i:expr} => {Err(AsAsciiStrError($i))}} 1416 | let mut s: String = "abčd".to_string(); 1417 | let mut b: Vec = s.clone().into(); 1418 | let mut first = [AsciiChar::a, AsciiChar::b]; 1419 | let mut second = [AsciiChar::d]; 1420 | assert_eq!(s.as_mut_ascii_str(), err!(2)); 1421 | assert_eq!(b.as_mut_ascii_str(), err!(2)); 1422 | assert_eq!(s.slice_ascii_mut(..), err!(2)); 1423 | assert_eq!(b.slice_ascii_mut(..), err!(2)); 1424 | assert_eq!(s[..2].as_mut_ascii_str(), Ok((&mut first[..]).into())); 1425 | assert_eq!(b[..2].as_mut_ascii_str(), Ok((&mut first[..]).into())); 1426 | assert_eq!(s.slice_ascii_mut(0..2), Ok((&mut first[..]).into())); 1427 | assert_eq!(b.slice_ascii_mut(0..2), Ok((&mut first[..]).into())); 1428 | assert_eq!(s.slice_ascii_mut(4..), Ok((&mut second[..]).into())); 1429 | assert_eq!(b.slice_ascii_mut(4..), Ok((&mut second[..]).into())); 1430 | assert_eq!(s.slice_ascii_mut(4..=10), err!(5)); 1431 | assert_eq!(b.slice_ascii_mut(4..=10), err!(5)); 1432 | } 1433 | 1434 | #[test] 1435 | fn default() { 1436 | let default: &'static AsciiStr = Default::default(); 1437 | assert!(default.is_empty()); 1438 | } 1439 | 1440 | #[test] 1441 | #[allow(clippy::redundant_slicing)] 1442 | fn index() { 1443 | let mut arr = [AsciiChar::A, AsciiChar::B, AsciiChar::C, AsciiChar::D]; 1444 | { 1445 | let a: &AsciiStr = arr[..].into(); 1446 | assert_eq!(a[..].as_slice(), &a.as_slice()[..]); 1447 | assert_eq!(a[..4].as_slice(), &a.as_slice()[..4]); 1448 | assert_eq!(a[4..].as_slice(), &a.as_slice()[4..]); 1449 | assert_eq!(a[2..3].as_slice(), &a.as_slice()[2..3]); 1450 | assert_eq!(a[..=3].as_slice(), &a.as_slice()[..=3]); 1451 | assert_eq!(a[1..=1].as_slice(), &a.as_slice()[1..=1]); 1452 | } 1453 | let mut copy = arr; 1454 | let a_mut: &mut AsciiStr = { &mut arr[..] }.into(); 1455 | assert_eq!(a_mut[..].as_mut_slice(), &mut copy[..]); 1456 | assert_eq!(a_mut[..2].as_mut_slice(), &mut copy[..2]); 1457 | assert_eq!(a_mut[3..].as_mut_slice(), &mut copy[3..]); 1458 | assert_eq!(a_mut[4..4].as_mut_slice(), &mut copy[4..4]); 1459 | assert_eq!(a_mut[..=0].as_mut_slice(), &mut copy[..=0]); 1460 | assert_eq!(a_mut[0..=2].as_mut_slice(), &mut copy[0..=2]); 1461 | } 1462 | 1463 | #[test] 1464 | fn as_str() { 1465 | let b = b"( ;"; 1466 | let v = AsciiStr::from_ascii(b).unwrap(); 1467 | assert_eq!(v.as_str(), "( ;"); 1468 | assert_eq!(AsRef::::as_ref(v), "( ;"); 1469 | } 1470 | 1471 | #[test] 1472 | fn as_bytes() { 1473 | let b = b"( ;"; 1474 | let v = AsciiStr::from_ascii(b).unwrap(); 1475 | assert_eq!(v.as_bytes(), b"( ;"); 1476 | assert_eq!(AsRef::<[u8]>::as_ref(v), b"( ;"); 1477 | } 1478 | 1479 | #[test] 1480 | fn make_ascii_case() { 1481 | let mut bytes = ([b'a', b'@', b'A'], [b'A', b'@', b'a']); 1482 | let a = bytes.0.as_mut_ascii_str().unwrap(); 1483 | let b = bytes.1.as_mut_ascii_str().unwrap(); 1484 | assert!(a.eq_ignore_ascii_case(b)); 1485 | assert!(b.eq_ignore_ascii_case(a)); 1486 | a.make_ascii_lowercase(); 1487 | b.make_ascii_uppercase(); 1488 | assert_eq!(a, "a@a"); 1489 | assert_eq!(b, "A@A"); 1490 | } 1491 | 1492 | #[test] 1493 | #[cfg(feature = "alloc")] 1494 | fn to_ascii_case() { 1495 | let bytes = ([b'a', b'@', b'A'], [b'A', b'@', b'a']); 1496 | let a = bytes.0.as_ascii_str().unwrap(); 1497 | let b = bytes.1.as_ascii_str().unwrap(); 1498 | assert_eq!(a.to_ascii_lowercase().as_str(), "a@a"); 1499 | assert_eq!(a.to_ascii_uppercase().as_str(), "A@A"); 1500 | assert_eq!(b.to_ascii_lowercase().as_str(), "a@a"); 1501 | assert_eq!(b.to_ascii_uppercase().as_str(), "A@A"); 1502 | } 1503 | 1504 | #[test] 1505 | fn chars_iter() { 1506 | let chars = &[ 1507 | b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', b'\0', 1508 | ]; 1509 | let ascii = AsciiStr::from_ascii(chars).unwrap(); 1510 | for (achar, byte) in ascii.chars().zip(chars.iter().copied()) { 1511 | assert_eq!(achar, byte); 1512 | } 1513 | } 1514 | 1515 | #[test] 1516 | fn chars_iter_mut() { 1517 | let chars = &mut [ 1518 | b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', b'\0', 1519 | ]; 1520 | let ascii = chars.as_mut_ascii_str().unwrap(); 1521 | *ascii.chars_mut().next().unwrap() = AsciiChar::H; 1522 | assert_eq!(ascii[0], b'H'); 1523 | } 1524 | 1525 | #[test] 1526 | fn lines_iter() { 1527 | use core::iter::Iterator; 1528 | 1529 | let lines: [&str; 4] = ["foo", "bar", "", "baz"]; 1530 | let joined = "foo\r\nbar\n\nbaz\n"; 1531 | let ascii = AsciiStr::from_ascii(joined.as_bytes()).unwrap(); 1532 | for (asciiline, line) in ascii.lines().zip(&lines) { 1533 | assert_eq!(asciiline, *line); 1534 | } 1535 | assert_eq!(ascii.lines().count(), lines.len()); 1536 | 1537 | let lines: [&str; 4] = ["foo", "bar", "", "baz"]; 1538 | let joined = "foo\r\nbar\n\nbaz"; 1539 | let ascii = AsciiStr::from_ascii(joined.as_bytes()).unwrap(); 1540 | for (asciiline, line) in ascii.lines().zip(&lines) { 1541 | assert_eq!(asciiline, *line); 1542 | } 1543 | assert_eq!(ascii.lines().count(), lines.len()); 1544 | 1545 | let trailing_line_break = b"\n"; 1546 | let ascii = AsciiStr::from_ascii(&trailing_line_break).unwrap(); 1547 | let mut line_iter = ascii.lines(); 1548 | assert_eq!(line_iter.next(), Some(AsciiStr::from_ascii("").unwrap())); 1549 | assert_eq!(line_iter.next(), None); 1550 | 1551 | let empty_lines = b"\n\r\n\n\r\n"; 1552 | let mut iter_count = 0; 1553 | let ascii = AsciiStr::from_ascii(&empty_lines).unwrap(); 1554 | for line in ascii.lines() { 1555 | iter_count += 1; 1556 | assert!(line.is_empty()); 1557 | } 1558 | assert_eq!(4, iter_count); 1559 | } 1560 | 1561 | #[test] 1562 | fn lines_iter_rev() { 1563 | let joined = "foo\r\nbar\n\nbaz\n"; 1564 | let ascii = AsciiStr::from_ascii(joined.as_bytes()).unwrap(); 1565 | assert_eq!(ascii.lines().rev().count(), 4); 1566 | assert_eq!(ascii.lines().rev().count(), joined.lines().rev().count()); 1567 | for (asciiline, line) in ascii.lines().rev().zip(joined.lines().rev()) { 1568 | assert_eq!(asciiline, line); 1569 | } 1570 | let mut iter = ascii.lines(); 1571 | assert_eq!(iter.next(), Some("foo".as_ascii_str().unwrap())); 1572 | assert_eq!(iter.next_back(), Some("baz".as_ascii_str().unwrap())); 1573 | assert_eq!(iter.next_back(), Some("".as_ascii_str().unwrap())); 1574 | assert_eq!(iter.next(), Some("bar".as_ascii_str().unwrap())); 1575 | 1576 | let empty_lines = b"\n\r\n\n\r\n"; 1577 | let mut iter_count = 0; 1578 | let ascii = AsciiStr::from_ascii(&empty_lines).unwrap(); 1579 | for line in ascii.lines().rev() { 1580 | iter_count += 1; 1581 | assert!(line.is_empty()); 1582 | } 1583 | assert_eq!(4, iter_count); 1584 | } 1585 | 1586 | #[test] 1587 | fn lines_iter_empty() { 1588 | assert_eq!("".as_ascii_str().unwrap().lines().next(), None); 1589 | assert_eq!("".as_ascii_str().unwrap().lines().next_back(), None); 1590 | assert_eq!("".lines().next(), None); 1591 | } 1592 | 1593 | #[test] 1594 | fn split_str() { 1595 | fn split_equals_str(haystack: &str, needle: char) { 1596 | let mut strs = haystack.split(needle); 1597 | let mut asciis = haystack 1598 | .as_ascii_str() 1599 | .unwrap() 1600 | .split(AsciiChar::from_ascii(needle).unwrap()) 1601 | .map(AsciiStr::as_str); 1602 | loop { 1603 | assert_eq!(asciis.size_hint(), strs.size_hint()); 1604 | let (a, s) = (asciis.next(), strs.next()); 1605 | assert_eq!(a, s); 1606 | if a == None { 1607 | break; 1608 | } 1609 | } 1610 | // test fusedness if str's version is fused 1611 | if strs.next() == None { 1612 | assert_eq!(asciis.next(), None); 1613 | } 1614 | } 1615 | split_equals_str("", '='); 1616 | split_equals_str("1,2,3", ','); 1617 | split_equals_str("foo;bar;baz;", ';'); 1618 | split_equals_str("|||", '|'); 1619 | split_equals_str(" a b c ", ' '); 1620 | } 1621 | 1622 | #[test] 1623 | fn split_str_rev() { 1624 | let words = " foo bar baz "; 1625 | let ascii = words.as_ascii_str().unwrap(); 1626 | for (word, asciiword) in words 1627 | .split(' ') 1628 | .rev() 1629 | .zip(ascii.split(AsciiChar::Space).rev()) 1630 | { 1631 | assert_eq!(asciiword, word); 1632 | } 1633 | let mut iter = ascii.split(AsciiChar::Space); 1634 | assert_eq!(iter.next(), Some("".as_ascii_str().unwrap())); 1635 | assert_eq!(iter.next_back(), Some("".as_ascii_str().unwrap())); 1636 | assert_eq!(iter.next(), Some("foo".as_ascii_str().unwrap())); 1637 | assert_eq!(iter.next_back(), Some("baz".as_ascii_str().unwrap())); 1638 | assert_eq!(iter.next_back(), Some("bar".as_ascii_str().unwrap())); 1639 | assert_eq!(iter.next(), Some("".as_ascii_str().unwrap())); 1640 | assert_eq!(iter.next_back(), None); 1641 | } 1642 | 1643 | #[test] 1644 | fn split_str_empty() { 1645 | let empty = <&AsciiStr>::default(); 1646 | let mut iter = empty.split(AsciiChar::NAK); 1647 | assert_eq!(iter.next(), Some(empty)); 1648 | assert_eq!(iter.next(), None); 1649 | let mut iter = empty.split(AsciiChar::NAK); 1650 | assert_eq!(iter.next_back(), Some(empty)); 1651 | assert_eq!(iter.next_back(), None); 1652 | assert_eq!("".split('s').next(), Some("")); // str.split() also produces one element 1653 | } 1654 | 1655 | #[test] 1656 | #[cfg(feature = "std")] 1657 | fn fmt_ascii_str() { 1658 | let s = "abc".as_ascii_str().unwrap(); 1659 | assert_eq!(format!("{}", s), "abc".to_string()); 1660 | assert_eq!(format!("{:?}", s), "\"abc\"".to_string()); 1661 | } 1662 | } 1663 | -------------------------------------------------------------------------------- /src/ascii_string.rs: -------------------------------------------------------------------------------- 1 | use alloc::borrow::{Borrow, BorrowMut, Cow, ToOwned}; 2 | use alloc::fmt; 3 | use alloc::string::String; 4 | use alloc::vec::Vec; 5 | use alloc::boxed::Box; 6 | use alloc::rc::Rc; 7 | use alloc::sync::Arc; 8 | #[cfg(feature = "std")] 9 | use core::any::Any; 10 | use core::iter::FromIterator; 11 | use core::mem; 12 | use core::ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut}; 13 | use core::str::FromStr; 14 | #[cfg(feature = "std")] 15 | use std::error::Error; 16 | #[cfg(feature = "std")] 17 | use std::ffi::{CStr, CString}; 18 | 19 | use ascii_char::AsciiChar; 20 | use ascii_str::{AsAsciiStr, AsAsciiStrError, AsciiStr}; 21 | 22 | /// A growable string stored as an ASCII encoded buffer. 23 | #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] 24 | #[repr(transparent)] 25 | pub struct AsciiString { 26 | vec: Vec, 27 | } 28 | 29 | impl AsciiString { 30 | /// Creates a new, empty ASCII string buffer without allocating. 31 | /// 32 | /// # Examples 33 | /// ``` 34 | /// # use ascii::AsciiString; 35 | /// let mut s = AsciiString::new(); 36 | /// ``` 37 | #[inline] 38 | #[must_use] 39 | pub const fn new() -> Self { 40 | AsciiString { vec: Vec::new() } 41 | } 42 | 43 | /// Creates a new ASCII string buffer with the given capacity. 44 | /// The string will be able to hold exactly `capacity` bytes without reallocating. 45 | /// If `capacity` is 0, the ASCII string will not allocate. 46 | /// 47 | /// # Examples 48 | /// ``` 49 | /// # use ascii::AsciiString; 50 | /// let mut s = AsciiString::with_capacity(10); 51 | /// ``` 52 | #[inline] 53 | #[must_use] 54 | pub fn with_capacity(capacity: usize) -> Self { 55 | AsciiString { 56 | vec: Vec::with_capacity(capacity), 57 | } 58 | } 59 | 60 | /// Creates a new `AsciiString` from a length, capacity and pointer. 61 | /// 62 | /// # Safety 63 | /// 64 | /// This is highly unsafe, due to the number of invariants that aren't checked: 65 | /// 66 | /// * The memory at `buf` need to have been previously allocated by the same allocator this 67 | /// library uses, with an alignment of 1. 68 | /// * `length` needs to be less than or equal to `capacity`. 69 | /// * `capacity` needs to be the correct value. 70 | /// * `buf` must have `length` valid ascii elements and contain a total of `capacity` total, 71 | /// possibly, uninitialized, elements. 72 | /// * Nothing else must be using the memory `buf` points to. 73 | /// 74 | /// Violating these may cause problems like corrupting the allocator's internal data structures. 75 | /// 76 | /// # Examples 77 | /// 78 | /// Basic usage: 79 | /// 80 | /// ``` 81 | /// # use ascii::AsciiString; 82 | /// use std::mem; 83 | /// 84 | /// unsafe { 85 | /// let mut s = AsciiString::from_ascii("hello").unwrap(); 86 | /// let ptr = s.as_mut_ptr(); 87 | /// let len = s.len(); 88 | /// let capacity = s.capacity(); 89 | /// 90 | /// mem::forget(s); 91 | /// 92 | /// let s = AsciiString::from_raw_parts(ptr, len, capacity); 93 | /// 94 | /// assert_eq!(AsciiString::from_ascii("hello").unwrap(), s); 95 | /// } 96 | /// ``` 97 | #[inline] 98 | #[must_use] 99 | pub unsafe fn from_raw_parts(buf: *mut AsciiChar, length: usize, capacity: usize) -> Self { 100 | AsciiString { 101 | // SAFETY: Caller guarantees that `buf` was previously allocated by this library, 102 | // that `buf` contains `length` valid ascii elements and has a total capacity 103 | // of `capacity` elements, and that nothing else is using the momory. 104 | vec: unsafe { Vec::from_raw_parts(buf, length, capacity) }, 105 | } 106 | } 107 | 108 | /// Converts a vector of bytes to an `AsciiString` without checking for non-ASCII characters. 109 | /// 110 | /// # Safety 111 | /// This function is unsafe because it does not check that the bytes passed to it are valid 112 | /// ASCII characters. If this constraint is violated, it may cause memory unsafety issues with 113 | /// future of the `AsciiString`, as the rest of this library assumes that `AsciiString`s are 114 | /// ASCII encoded. 115 | #[inline] 116 | #[must_use] 117 | pub unsafe fn from_ascii_unchecked(bytes: B) -> Self 118 | where 119 | B: Into>, 120 | { 121 | let mut bytes = bytes.into(); 122 | // SAFETY: The caller guarantees all bytes are valid ascii bytes. 123 | let ptr = bytes.as_mut_ptr().cast::(); 124 | let length = bytes.len(); 125 | let capacity = bytes.capacity(); 126 | mem::forget(bytes); 127 | 128 | // SAFETY: We guarantee all invariants, as we got the 129 | // pointer, length and capacity from a `Vec`, 130 | // and we also guarantee the pointer is valid per 131 | // the `SAFETY` notice above. 132 | let vec = Vec::from_raw_parts(ptr, length, capacity); 133 | 134 | Self { vec } 135 | } 136 | 137 | /// Converts anything that can represent a byte buffer into an `AsciiString`. 138 | /// 139 | /// # Errors 140 | /// Returns the byte buffer if not all of the bytes are ASCII characters. 141 | /// 142 | /// # Examples 143 | /// ``` 144 | /// # use ascii::AsciiString; 145 | /// let foo = AsciiString::from_ascii("foo".to_string()).unwrap(); 146 | /// let err = AsciiString::from_ascii("Ŋ".to_string()).unwrap_err(); 147 | /// assert_eq!(foo.as_str(), "foo"); 148 | /// assert_eq!(err.into_source(), "Ŋ"); 149 | /// ``` 150 | pub fn from_ascii(bytes: B) -> Result> 151 | where 152 | B: Into> + AsRef<[u8]>, 153 | { 154 | match bytes.as_ref().as_ascii_str() { 155 | // SAFETY: `as_ascii_str` guarantees all bytes are valid ascii bytes. 156 | Ok(_) => Ok(unsafe { AsciiString::from_ascii_unchecked(bytes) }), 157 | Err(e) => Err(FromAsciiError { 158 | error: e, 159 | owner: bytes, 160 | }), 161 | } 162 | } 163 | 164 | /// Pushes the given ASCII string onto this ASCII string buffer. 165 | /// 166 | /// # Examples 167 | /// ``` 168 | /// # use ascii::{AsciiString, AsAsciiStr}; 169 | /// use std::str::FromStr; 170 | /// let mut s = AsciiString::from_str("foo").unwrap(); 171 | /// s.push_str("bar".as_ascii_str().unwrap()); 172 | /// assert_eq!(s, "foobar".as_ascii_str().unwrap()); 173 | /// ``` 174 | #[inline] 175 | pub fn push_str(&mut self, string: &AsciiStr) { 176 | self.vec.extend(string.chars()); 177 | } 178 | 179 | /// Inserts the given ASCII string at the given place in this ASCII string buffer. 180 | /// 181 | /// # Panics 182 | /// 183 | /// Panics if `idx` is larger than the `AsciiString`'s length. 184 | /// 185 | /// # Examples 186 | /// ``` 187 | /// # use ascii::{AsciiString, AsAsciiStr}; 188 | /// use std::str::FromStr; 189 | /// let mut s = AsciiString::from_str("abc").unwrap(); 190 | /// s.insert_str(1, "def".as_ascii_str().unwrap()); 191 | /// assert_eq!(&*s, "adefbc"); 192 | #[inline] 193 | pub fn insert_str(&mut self, idx: usize, string: &AsciiStr) { 194 | self.vec.reserve(string.len()); 195 | self.vec.splice(idx..idx, string.into_iter().copied()); 196 | } 197 | 198 | /// Returns the number of bytes that this ASCII string buffer can hold without reallocating. 199 | /// 200 | /// # Examples 201 | /// ``` 202 | /// # use ascii::AsciiString; 203 | /// let s = String::with_capacity(10); 204 | /// assert!(s.capacity() >= 10); 205 | /// ``` 206 | #[inline] 207 | #[must_use] 208 | pub fn capacity(&self) -> usize { 209 | self.vec.capacity() 210 | } 211 | 212 | /// Reserves capacity for at least `additional` more bytes to be inserted in the given 213 | /// `AsciiString`. The collection may reserve more space to avoid frequent reallocations. 214 | /// 215 | /// # Panics 216 | /// Panics if the new capacity overflows `usize`. 217 | /// 218 | /// # Examples 219 | /// ``` 220 | /// # use ascii::AsciiString; 221 | /// let mut s = AsciiString::new(); 222 | /// s.reserve(10); 223 | /// assert!(s.capacity() >= 10); 224 | /// ``` 225 | #[inline] 226 | pub fn reserve(&mut self, additional: usize) { 227 | self.vec.reserve(additional); 228 | } 229 | 230 | /// Reserves the minimum capacity for exactly `additional` more bytes to be inserted in the 231 | /// given `AsciiString`. Does nothing if the capacity is already sufficient. 232 | /// 233 | /// Note that the allocator may give the collection more space than it requests. Therefore 234 | /// capacity can not be relied upon to be precisely minimal. Prefer `reserve` if future 235 | /// insertions are expected. 236 | /// 237 | /// # Panics 238 | /// Panics if the new capacity overflows `usize`. 239 | /// 240 | /// # Examples 241 | /// ``` 242 | /// # use ascii::AsciiString; 243 | /// let mut s = AsciiString::new(); 244 | /// s.reserve_exact(10); 245 | /// assert!(s.capacity() >= 10); 246 | /// ``` 247 | #[inline] 248 | 249 | pub fn reserve_exact(&mut self, additional: usize) { 250 | self.vec.reserve_exact(additional); 251 | } 252 | 253 | /// Shrinks the capacity of this ASCII string buffer to match it's length. 254 | /// 255 | /// # Examples 256 | /// ``` 257 | /// # use ascii::AsciiString; 258 | /// use std::str::FromStr; 259 | /// let mut s = AsciiString::from_str("foo").unwrap(); 260 | /// s.reserve(100); 261 | /// assert!(s.capacity() >= 100); 262 | /// s.shrink_to_fit(); 263 | /// assert_eq!(s.capacity(), 3); 264 | /// ``` 265 | #[inline] 266 | 267 | pub fn shrink_to_fit(&mut self) { 268 | self.vec.shrink_to_fit(); 269 | } 270 | 271 | /// Adds the given ASCII character to the end of the ASCII string. 272 | /// 273 | /// # Examples 274 | /// ``` 275 | /// # use ascii::{ AsciiChar, AsciiString}; 276 | /// let mut s = AsciiString::from_ascii("abc").unwrap(); 277 | /// s.push(AsciiChar::from_ascii('1').unwrap()); 278 | /// s.push(AsciiChar::from_ascii('2').unwrap()); 279 | /// s.push(AsciiChar::from_ascii('3').unwrap()); 280 | /// assert_eq!(s, "abc123"); 281 | /// ``` 282 | #[inline] 283 | 284 | pub fn push(&mut self, ch: AsciiChar) { 285 | self.vec.push(ch); 286 | } 287 | 288 | /// Shortens a ASCII string to the specified length. 289 | /// 290 | /// # Panics 291 | /// Panics if `new_len` > current length. 292 | /// 293 | /// # Examples 294 | /// ``` 295 | /// # use ascii::AsciiString; 296 | /// let mut s = AsciiString::from_ascii("hello").unwrap(); 297 | /// s.truncate(2); 298 | /// assert_eq!(s, "he"); 299 | /// ``` 300 | #[inline] 301 | 302 | pub fn truncate(&mut self, new_len: usize) { 303 | self.vec.truncate(new_len); 304 | } 305 | 306 | /// Removes the last character from the ASCII string buffer and returns it. 307 | /// Returns `None` if this string buffer is empty. 308 | /// 309 | /// # Examples 310 | /// ``` 311 | /// # #![allow(bindings_with_variant_name)] 312 | /// # use ascii::AsciiString; 313 | /// let mut s = AsciiString::from_ascii("foo").unwrap(); 314 | /// assert_eq!(s.pop().map(|c| c.as_char()), Some('o')); 315 | /// assert_eq!(s.pop().map(|c| c.as_char()), Some('o')); 316 | /// assert_eq!(s.pop().map(|c| c.as_char()), Some('f')); 317 | /// assert_eq!(s.pop(), None); 318 | /// ``` 319 | #[inline] 320 | #[must_use] 321 | pub fn pop(&mut self) -> Option { 322 | self.vec.pop() 323 | } 324 | 325 | /// Removes the ASCII character at position `idx` from the buffer and returns it. 326 | /// 327 | /// # Warning 328 | /// This is an O(n) operation as it requires copying every element in the buffer. 329 | /// 330 | /// # Panics 331 | /// If `idx` is out of bounds this function will panic. 332 | /// 333 | /// # Examples 334 | /// ``` 335 | /// # use ascii::AsciiString; 336 | /// let mut s = AsciiString::from_ascii("foo").unwrap(); 337 | /// assert_eq!(s.remove(0).as_char(), 'f'); 338 | /// assert_eq!(s.remove(1).as_char(), 'o'); 339 | /// assert_eq!(s.remove(0).as_char(), 'o'); 340 | /// ``` 341 | #[inline] 342 | #[must_use] 343 | pub fn remove(&mut self, idx: usize) -> AsciiChar { 344 | self.vec.remove(idx) 345 | } 346 | 347 | /// Inserts an ASCII character into the buffer at position `idx`. 348 | /// 349 | /// # Warning 350 | /// This is an O(n) operation as it requires copying every element in the buffer. 351 | /// 352 | /// # Panics 353 | /// If `idx` is out of bounds this function will panic. 354 | /// 355 | /// # Examples 356 | /// ``` 357 | /// # use ascii::{AsciiString,AsciiChar}; 358 | /// let mut s = AsciiString::from_ascii("foo").unwrap(); 359 | /// s.insert(2, AsciiChar::b); 360 | /// assert_eq!(s, "fobo"); 361 | /// ``` 362 | #[inline] 363 | 364 | pub fn insert(&mut self, idx: usize, ch: AsciiChar) { 365 | self.vec.insert(idx, ch); 366 | } 367 | 368 | /// Returns the number of bytes in this ASCII string. 369 | /// 370 | /// # Examples 371 | /// ``` 372 | /// # use ascii::AsciiString; 373 | /// let s = AsciiString::from_ascii("foo").unwrap(); 374 | /// assert_eq!(s.len(), 3); 375 | /// ``` 376 | #[inline] 377 | #[must_use] 378 | pub fn len(&self) -> usize { 379 | self.vec.len() 380 | } 381 | 382 | /// Returns true if the ASCII string contains zero bytes. 383 | /// 384 | /// # Examples 385 | /// ``` 386 | /// # use ascii::{AsciiChar, AsciiString}; 387 | /// let mut s = AsciiString::new(); 388 | /// assert!(s.is_empty()); 389 | /// s.push(AsciiChar::from_ascii('a').unwrap()); 390 | /// assert!(!s.is_empty()); 391 | /// ``` 392 | #[inline] 393 | #[must_use] 394 | pub fn is_empty(&self) -> bool { 395 | self.len() == 0 396 | } 397 | 398 | /// Truncates the ASCII string, setting length (but not capacity) to zero. 399 | /// 400 | /// # Examples 401 | /// ``` 402 | /// # use ascii::AsciiString; 403 | /// let mut s = AsciiString::from_ascii("foo").unwrap(); 404 | /// s.clear(); 405 | /// assert!(s.is_empty()); 406 | /// ``` 407 | #[inline] 408 | 409 | pub fn clear(&mut self) { 410 | self.vec.clear(); 411 | } 412 | 413 | /// Converts this [`AsciiString`] into a [`Box`]`<`[`AsciiStr`]`>`. 414 | /// 415 | /// This will drop any excess capacity 416 | #[inline] 417 | #[must_use] 418 | pub fn into_boxed_ascii_str(self) -> Box { 419 | let slice = self.vec.into_boxed_slice(); 420 | Box::from(slice) 421 | } 422 | } 423 | 424 | impl Deref for AsciiString { 425 | type Target = AsciiStr; 426 | 427 | #[inline] 428 | fn deref(&self) -> &AsciiStr { 429 | self.vec.as_slice().as_ref() 430 | } 431 | } 432 | 433 | impl DerefMut for AsciiString { 434 | #[inline] 435 | fn deref_mut(&mut self) -> &mut AsciiStr { 436 | self.vec.as_mut_slice().as_mut() 437 | } 438 | } 439 | 440 | impl PartialEq for AsciiString { 441 | #[inline] 442 | fn eq(&self, other: &str) -> bool { 443 | **self == *other 444 | } 445 | } 446 | 447 | impl PartialEq for str { 448 | #[inline] 449 | fn eq(&self, other: &AsciiString) -> bool { 450 | **other == *self 451 | } 452 | } 453 | 454 | macro_rules! impl_eq { 455 | ($lhs:ty, $rhs:ty) => { 456 | impl PartialEq<$rhs> for $lhs { 457 | #[inline] 458 | fn eq(&self, other: &$rhs) -> bool { 459 | PartialEq::eq(&**self, &**other) 460 | } 461 | } 462 | }; 463 | } 464 | 465 | impl_eq! { AsciiString, String } 466 | impl_eq! { String, AsciiString } 467 | impl_eq! { &AsciiStr, String } 468 | impl_eq! { String, &AsciiStr } 469 | impl_eq! { &AsciiStr, AsciiString } 470 | impl_eq! { AsciiString, &AsciiStr } 471 | impl_eq! { &str, AsciiString } 472 | impl_eq! { AsciiString, &str } 473 | 474 | impl Borrow for AsciiString { 475 | #[inline] 476 | fn borrow(&self) -> &AsciiStr { 477 | &**self 478 | } 479 | } 480 | 481 | impl BorrowMut for AsciiString { 482 | #[inline] 483 | fn borrow_mut(&mut self) -> &mut AsciiStr { 484 | &mut **self 485 | } 486 | } 487 | 488 | impl From> for AsciiString { 489 | #[inline] 490 | fn from(vec: Vec) -> Self { 491 | AsciiString { vec } 492 | } 493 | } 494 | 495 | impl From for AsciiString { 496 | #[inline] 497 | fn from(ch: AsciiChar) -> Self { 498 | AsciiString { vec: vec![ch] } 499 | } 500 | } 501 | 502 | impl From for Vec { 503 | fn from(mut s: AsciiString) -> Vec { 504 | // SAFETY: All ascii bytes are valid `u8`, as we are `repr(u8)`. 505 | // Note: We forget `self` to avoid `self.vec` from being deallocated. 506 | let ptr = s.vec.as_mut_ptr().cast::(); 507 | let length = s.vec.len(); 508 | let capacity = s.vec.capacity(); 509 | mem::forget(s); 510 | 511 | // SAFETY: We guarantee all invariants due to getting `ptr`, `length` 512 | // and `capacity` from a `Vec`. We also guarantee `ptr` is valid 513 | // due to the `SAFETY` block above. 514 | unsafe { Vec::from_raw_parts(ptr, length, capacity) } 515 | } 516 | } 517 | 518 | impl From for Vec { 519 | fn from(s: AsciiString) -> Vec { 520 | s.vec 521 | } 522 | } 523 | 524 | impl<'a> From<&'a AsciiStr> for AsciiString { 525 | #[inline] 526 | fn from(s: &'a AsciiStr) -> Self { 527 | s.to_ascii_string() 528 | } 529 | } 530 | 531 | impl<'a> From<&'a [AsciiChar]> for AsciiString { 532 | #[inline] 533 | fn from(s: &'a [AsciiChar]) -> AsciiString { 534 | s.iter().copied().collect() 535 | } 536 | } 537 | 538 | impl From for String { 539 | #[inline] 540 | fn from(s: AsciiString) -> String { 541 | // SAFETY: All ascii bytes are `utf8`. 542 | unsafe { String::from_utf8_unchecked(s.into()) } 543 | } 544 | } 545 | 546 | impl From> for AsciiString { 547 | #[inline] 548 | fn from(boxed: Box) -> Self { 549 | boxed.into_ascii_string() 550 | } 551 | } 552 | 553 | impl From for Box { 554 | #[inline] 555 | fn from(string: AsciiString) -> Self { 556 | string.into_boxed_ascii_str() 557 | } 558 | } 559 | 560 | impl From for Rc { 561 | fn from(s: AsciiString) -> Rc { 562 | let var: Rc<[AsciiChar]> = s.vec.into(); 563 | // SAFETY: AsciiStr is repr(transparent) and thus has the same layout as [AsciiChar] 564 | unsafe { Rc::from_raw(Rc::into_raw(var) as *const AsciiStr) } 565 | } 566 | } 567 | 568 | impl From for Arc { 569 | fn from(s: AsciiString) -> Arc { 570 | let var: Arc<[AsciiChar]> = s.vec.into(); 571 | // SAFETY: AsciiStr is repr(transparent) and thus has the same layout as [AsciiChar] 572 | unsafe { Arc::from_raw(Arc::into_raw(var) as *const AsciiStr) } 573 | } 574 | } 575 | 576 | impl<'a> From> for AsciiString { 577 | fn from(cow: Cow<'a, AsciiStr>) -> AsciiString { 578 | cow.into_owned() 579 | } 580 | } 581 | 582 | impl From for Cow<'static, AsciiStr> { 583 | fn from(string: AsciiString) -> Cow<'static, AsciiStr> { 584 | Cow::Owned(string) 585 | } 586 | } 587 | 588 | impl<'a> From<&'a AsciiStr> for Cow<'a, AsciiStr> { 589 | fn from(s: &'a AsciiStr) -> Cow<'a, AsciiStr> { 590 | Cow::Borrowed(s) 591 | } 592 | } 593 | 594 | impl AsRef for AsciiString { 595 | #[inline] 596 | fn as_ref(&self) -> &AsciiStr { 597 | &**self 598 | } 599 | } 600 | 601 | impl AsRef<[AsciiChar]> for AsciiString { 602 | #[inline] 603 | fn as_ref(&self) -> &[AsciiChar] { 604 | &self.vec 605 | } 606 | } 607 | 608 | impl AsRef<[u8]> for AsciiString { 609 | #[inline] 610 | fn as_ref(&self) -> &[u8] { 611 | self.as_bytes() 612 | } 613 | } 614 | 615 | impl AsRef for AsciiString { 616 | #[inline] 617 | fn as_ref(&self) -> &str { 618 | self.as_str() 619 | } 620 | } 621 | 622 | impl AsMut for AsciiString { 623 | #[inline] 624 | fn as_mut(&mut self) -> &mut AsciiStr { 625 | &mut *self 626 | } 627 | } 628 | 629 | impl AsMut<[AsciiChar]> for AsciiString { 630 | #[inline] 631 | fn as_mut(&mut self) -> &mut [AsciiChar] { 632 | &mut self.vec 633 | } 634 | } 635 | 636 | impl FromStr for AsciiString { 637 | type Err = AsAsciiStrError; 638 | 639 | fn from_str(s: &str) -> Result { 640 | s.as_ascii_str().map(AsciiStr::to_ascii_string) 641 | } 642 | } 643 | 644 | impl fmt::Display for AsciiString { 645 | #[inline] 646 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 647 | fmt::Display::fmt(&**self, f) 648 | } 649 | } 650 | 651 | impl fmt::Debug for AsciiString { 652 | #[inline] 653 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 654 | fmt::Debug::fmt(&**self, f) 655 | } 656 | } 657 | 658 | /// Please note that the `std::fmt::Result` returned by these methods does not support 659 | /// transmission of an error other than that an error occurred. 660 | impl fmt::Write for AsciiString { 661 | fn write_str(&mut self, s: &str) -> fmt::Result { 662 | if let Ok(astr) = AsciiStr::from_ascii(s) { 663 | self.push_str(astr); 664 | Ok(()) 665 | } else { 666 | Err(fmt::Error) 667 | } 668 | } 669 | 670 | fn write_char(&mut self, c: char) -> fmt::Result { 671 | if let Ok(achar) = AsciiChar::from_ascii(c) { 672 | self.push(achar); 673 | Ok(()) 674 | } else { 675 | Err(fmt::Error) 676 | } 677 | } 678 | } 679 | 680 | impl> FromIterator for AsciiString { 681 | fn from_iter>(iter: I) -> AsciiString { 682 | let mut buf = AsciiString::new(); 683 | buf.extend(iter); 684 | buf 685 | } 686 | } 687 | 688 | impl> Extend for AsciiString { 689 | fn extend>(&mut self, iterable: I) { 690 | let iterator = iterable.into_iter(); 691 | let (lower_bound, _) = iterator.size_hint(); 692 | self.reserve(lower_bound); 693 | for item in iterator { 694 | self.push_str(item.as_ref()); 695 | } 696 | } 697 | } 698 | 699 | impl<'a> Add<&'a AsciiStr> for AsciiString { 700 | type Output = AsciiString; 701 | 702 | #[inline] 703 | fn add(mut self, other: &AsciiStr) -> AsciiString { 704 | self.push_str(other); 705 | self 706 | } 707 | } 708 | 709 | impl<'a> AddAssign<&'a AsciiStr> for AsciiString { 710 | #[inline] 711 | fn add_assign(&mut self, other: &AsciiStr) { 712 | self.push_str(other); 713 | } 714 | } 715 | 716 | #[allow(clippy::indexing_slicing)] // In `Index`, if it's out of bounds, panic is the default 717 | impl Index for AsciiString 718 | where 719 | AsciiStr: Index, 720 | { 721 | type Output = >::Output; 722 | 723 | #[inline] 724 | fn index(&self, index: T) -> &>::Output { 725 | &(**self)[index] 726 | } 727 | } 728 | 729 | #[allow(clippy::indexing_slicing)] // In `IndexMut`, if it's out of bounds, panic is the default 730 | impl IndexMut for AsciiString 731 | where 732 | AsciiStr: IndexMut, 733 | { 734 | #[inline] 735 | fn index_mut(&mut self, index: T) -> &mut >::Output { 736 | &mut (**self)[index] 737 | } 738 | } 739 | 740 | /// A possible error value when converting an `AsciiString` from a byte vector or string. 741 | /// It wraps an `AsAsciiStrError` which you can get through the `ascii_error()` method. 742 | /// 743 | /// This is the error type for `AsciiString::from_ascii()` and 744 | /// `IntoAsciiString::into_ascii_string()`. They will never clone or touch the content of the 745 | /// original type; It can be extracted by the `into_source` method. 746 | /// 747 | /// #Examples 748 | /// ``` 749 | /// # use ascii::IntoAsciiString; 750 | /// let err = "bø!".to_string().into_ascii_string().unwrap_err(); 751 | /// assert_eq!(err.ascii_error().valid_up_to(), 1); 752 | /// assert_eq!(err.into_source(), "bø!".to_string()); 753 | /// ``` 754 | #[derive(Clone, Copy, PartialEq, Eq)] 755 | pub struct FromAsciiError { 756 | error: AsAsciiStrError, 757 | owner: O, 758 | } 759 | impl FromAsciiError { 760 | /// Get the position of the first non-ASCII byte or character. 761 | #[inline] 762 | #[must_use] 763 | pub fn ascii_error(&self) -> AsAsciiStrError { 764 | self.error 765 | } 766 | /// Get back the original, unmodified type. 767 | #[inline] 768 | #[must_use] 769 | pub fn into_source(self) -> O { 770 | self.owner 771 | } 772 | } 773 | impl fmt::Debug for FromAsciiError { 774 | #[inline] 775 | fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { 776 | fmt::Debug::fmt(&self.error, fmtr) 777 | } 778 | } 779 | impl fmt::Display for FromAsciiError { 780 | #[inline] 781 | fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { 782 | fmt::Display::fmt(&self.error, fmtr) 783 | } 784 | } 785 | #[cfg(feature = "std")] 786 | impl Error for FromAsciiError { 787 | #[inline] 788 | #[allow(deprecated)] // TODO: Remove deprecation once the earliest version we support deprecates this method. 789 | fn description(&self) -> &str { 790 | self.error.description() 791 | } 792 | /// Always returns an `AsAsciiStrError` 793 | fn cause(&self) -> Option<&dyn Error> { 794 | Some(&self.error as &dyn Error) 795 | } 796 | } 797 | 798 | /// Convert vectors into `AsciiString`. 799 | pub trait IntoAsciiString: Sized { 800 | /// Convert to `AsciiString` without checking for non-ASCII characters. 801 | /// 802 | /// # Safety 803 | /// If `self` contains non-ascii characters, calling this function is 804 | /// undefined behavior. 805 | unsafe fn into_ascii_string_unchecked(self) -> AsciiString; 806 | 807 | /// Convert to `AsciiString`. 808 | /// 809 | /// # Errors 810 | /// If `self` contains non-ascii characters, this will return `Err` 811 | fn into_ascii_string(self) -> Result>; 812 | } 813 | 814 | impl IntoAsciiString for Vec { 815 | #[inline] 816 | unsafe fn into_ascii_string_unchecked(self) -> AsciiString { 817 | AsciiString::from(self) 818 | } 819 | #[inline] 820 | fn into_ascii_string(self) -> Result> { 821 | Ok(AsciiString::from(self)) 822 | } 823 | } 824 | 825 | impl<'a> IntoAsciiString for &'a [AsciiChar] { 826 | #[inline] 827 | unsafe fn into_ascii_string_unchecked(self) -> AsciiString { 828 | AsciiString::from(self) 829 | } 830 | #[inline] 831 | fn into_ascii_string(self) -> Result> { 832 | Ok(AsciiString::from(self)) 833 | } 834 | } 835 | 836 | impl<'a> IntoAsciiString for &'a AsciiStr { 837 | #[inline] 838 | unsafe fn into_ascii_string_unchecked(self) -> AsciiString { 839 | AsciiString::from(self) 840 | } 841 | #[inline] 842 | fn into_ascii_string(self) -> Result> { 843 | Ok(AsciiString::from(self)) 844 | } 845 | } 846 | 847 | macro_rules! impl_into_ascii_string { 848 | ('a, $wider:ty) => { 849 | impl<'a> IntoAsciiString for $wider { 850 | #[inline] 851 | unsafe fn into_ascii_string_unchecked(self) -> AsciiString { 852 | // SAFETY: Caller guarantees `self` only has valid ascii bytes 853 | unsafe { AsciiString::from_ascii_unchecked(self) } 854 | } 855 | 856 | #[inline] 857 | fn into_ascii_string(self) -> Result> { 858 | AsciiString::from_ascii(self) 859 | } 860 | } 861 | }; 862 | 863 | ($wider:ty) => { 864 | impl IntoAsciiString for $wider { 865 | #[inline] 866 | unsafe fn into_ascii_string_unchecked(self) -> AsciiString { 867 | // SAFETY: Caller guarantees `self` only has valid ascii bytes 868 | unsafe { AsciiString::from_ascii_unchecked(self) } 869 | } 870 | 871 | #[inline] 872 | fn into_ascii_string(self) -> Result> { 873 | AsciiString::from_ascii(self) 874 | } 875 | } 876 | }; 877 | } 878 | 879 | impl_into_ascii_string! {AsciiString} 880 | impl_into_ascii_string! {Vec} 881 | impl_into_ascii_string! {'a, &'a [u8]} 882 | impl_into_ascii_string! {String} 883 | impl_into_ascii_string! {'a, &'a str} 884 | 885 | /// # Notes 886 | /// The trailing null byte `CString` has will be removed during this conversion. 887 | #[cfg(feature = "std")] 888 | impl IntoAsciiString for CString { 889 | #[inline] 890 | unsafe fn into_ascii_string_unchecked(self) -> AsciiString { 891 | // SAFETY: Caller guarantees `self` only has valid ascii bytes 892 | unsafe { AsciiString::from_ascii_unchecked(self.into_bytes()) } 893 | } 894 | 895 | fn into_ascii_string(self) -> Result> { 896 | AsciiString::from_ascii(self.into_bytes_with_nul()) 897 | .map_err(|FromAsciiError { error, owner }| { 898 | FromAsciiError { 899 | // SAFETY: We don't discard the NULL byte from the original 900 | // string, so we ensure that it's null terminated 901 | owner: unsafe { CString::from_vec_unchecked(owner) }, 902 | error, 903 | } 904 | }) 905 | .map(|mut s| { 906 | let nul = s.pop(); 907 | debug_assert_eq!(nul, Some(AsciiChar::Null)); 908 | s 909 | }) 910 | } 911 | } 912 | 913 | /// Note that the trailing null byte will be removed in the conversion. 914 | #[cfg(feature = "std")] 915 | impl<'a> IntoAsciiString for &'a CStr { 916 | #[inline] 917 | unsafe fn into_ascii_string_unchecked(self) -> AsciiString { 918 | // SAFETY: Caller guarantees `self` only has valid ascii bytes 919 | unsafe { AsciiString::from_ascii_unchecked(self.to_bytes()) } 920 | } 921 | 922 | fn into_ascii_string(self) -> Result> { 923 | AsciiString::from_ascii(self.to_bytes_with_nul()) 924 | .map_err(|FromAsciiError { error, owner }| FromAsciiError { 925 | // SAFETY: We don't discard the NULL byte from the original 926 | // string, so we ensure that it's null terminated 927 | owner: unsafe { CStr::from_ptr(owner.as_ptr().cast()) }, 928 | error, 929 | }) 930 | .map(|mut s| { 931 | let nul = s.pop(); 932 | debug_assert_eq!(nul, Some(AsciiChar::Null)); 933 | s 934 | }) 935 | } 936 | } 937 | 938 | impl<'a, B> IntoAsciiString for Cow<'a, B> 939 | where 940 | B: 'a + ToOwned + ?Sized, 941 | &'a B: IntoAsciiString, 942 | ::Owned: IntoAsciiString, 943 | { 944 | #[inline] 945 | unsafe fn into_ascii_string_unchecked(self) -> AsciiString { 946 | // SAFETY: Caller guarantees `self` only has valid ascii bytes 947 | unsafe { IntoAsciiString::into_ascii_string_unchecked(self.into_owned()) } 948 | } 949 | 950 | fn into_ascii_string(self) -> Result> { 951 | match self { 952 | Cow::Owned(b) => { 953 | IntoAsciiString::into_ascii_string(b).map_err(|FromAsciiError { error, owner }| { 954 | FromAsciiError { 955 | owner: Cow::Owned(owner), 956 | error, 957 | } 958 | }) 959 | } 960 | Cow::Borrowed(b) => { 961 | IntoAsciiString::into_ascii_string(b).map_err(|FromAsciiError { error, owner }| { 962 | FromAsciiError { 963 | owner: Cow::Borrowed(owner), 964 | error, 965 | } 966 | }) 967 | } 968 | } 969 | } 970 | } 971 | 972 | #[cfg(test)] 973 | mod tests { 974 | use super::{AsciiString, IntoAsciiString}; 975 | use alloc::str::FromStr; 976 | use alloc::string::{String, ToString}; 977 | use alloc::vec::Vec; 978 | use alloc::boxed::Box; 979 | #[cfg(feature = "std")] 980 | use std::ffi::CString; 981 | use {AsciiChar, AsciiStr}; 982 | 983 | #[test] 984 | fn into_string() { 985 | let v = AsciiString::from_ascii(&[40_u8, 32, 59][..]).unwrap(); 986 | assert_eq!(Into::::into(v), "( ;".to_string()); 987 | } 988 | 989 | #[test] 990 | fn into_bytes() { 991 | let v = AsciiString::from_ascii(&[40_u8, 32, 59][..]).unwrap(); 992 | assert_eq!(Into::>::into(v), vec![40_u8, 32, 59]); 993 | } 994 | 995 | #[test] 996 | fn from_ascii_vec() { 997 | let vec = vec![ 998 | AsciiChar::from_ascii('A').unwrap(), 999 | AsciiChar::from_ascii('B').unwrap(), 1000 | ]; 1001 | assert_eq!(AsciiString::from(vec), AsciiString::from_str("AB").unwrap()); 1002 | } 1003 | 1004 | #[test] 1005 | #[cfg(feature = "std")] 1006 | fn from_cstring() { 1007 | let cstring = CString::new("baz").unwrap(); 1008 | let ascii_str = cstring.clone().into_ascii_string().unwrap(); 1009 | let expected_chars = &[AsciiChar::b, AsciiChar::a, AsciiChar::z]; 1010 | assert_eq!(ascii_str.len(), 3); 1011 | assert_eq!(ascii_str.as_slice(), expected_chars); 1012 | 1013 | // SAFETY: "baz" only contains valid ascii characters. 1014 | let ascii_str_unchecked = unsafe { cstring.into_ascii_string_unchecked() }; 1015 | assert_eq!(ascii_str_unchecked.len(), 3); 1016 | assert_eq!(ascii_str_unchecked.as_slice(), expected_chars); 1017 | 1018 | let sparkle_heart_bytes = vec![240_u8, 159, 146, 150]; 1019 | let cstring = CString::new(sparkle_heart_bytes).unwrap(); 1020 | let cstr = &*cstring; 1021 | let ascii_err = cstr.into_ascii_string().unwrap_err(); 1022 | assert_eq!(ascii_err.into_source(), &*cstring); 1023 | } 1024 | 1025 | #[test] 1026 | #[cfg(feature = "std")] 1027 | fn fmt_ascii_string() { 1028 | let s = "abc".to_string().into_ascii_string().unwrap(); 1029 | assert_eq!(format!("{}", s), "abc".to_string()); 1030 | assert_eq!(format!("{:?}", s), "\"abc\"".to_string()); 1031 | } 1032 | 1033 | #[test] 1034 | fn write_fmt() { 1035 | use alloc::{fmt, str}; 1036 | 1037 | let mut s0 = AsciiString::new(); 1038 | fmt::write(&mut s0, format_args!("Hello World")).unwrap(); 1039 | assert_eq!(s0, "Hello World"); 1040 | 1041 | let mut s1 = AsciiString::new(); 1042 | fmt::write(&mut s1, format_args!("{}", 9)).unwrap(); 1043 | assert_eq!(s1, "9"); 1044 | 1045 | let mut s2 = AsciiString::new(); 1046 | let sparkle_heart_bytes = [240, 159, 146, 150]; 1047 | let sparkle_heart = str::from_utf8(&sparkle_heart_bytes).unwrap(); 1048 | assert!(fmt::write(&mut s2, format_args!("{}", sparkle_heart)).is_err()); 1049 | } 1050 | 1051 | #[test] 1052 | fn to_and_from_box() { 1053 | let string = "abc".into_ascii_string().unwrap(); 1054 | let converted: Box = Box::from(string.clone()); 1055 | let converted: AsciiString = converted.into(); 1056 | assert_eq!(string, converted); 1057 | } 1058 | } 1059 | -------------------------------------------------------------------------------- /src/free_functions.rs: -------------------------------------------------------------------------------- 1 | use ascii_char::{AsciiChar, ToAsciiChar}; 2 | 3 | /// Terminals use [caret notation](https://en.wikipedia.org/wiki/Caret_notation) 4 | /// to display some typed control codes, such as ^D for EOT and ^Z for SUB. 5 | /// 6 | /// This function returns the caret notation letter for control codes, 7 | /// or `None` for printable characters. 8 | /// 9 | /// # Examples 10 | /// ``` 11 | /// # use ascii::{AsciiChar, caret_encode}; 12 | /// assert_eq!(caret_encode(b'\0'), Some(AsciiChar::At)); 13 | /// assert_eq!(caret_encode(AsciiChar::DEL), Some(AsciiChar::Question)); 14 | /// assert_eq!(caret_encode(b'E'), None); 15 | /// assert_eq!(caret_encode(b'\n'), Some(AsciiChar::J)); 16 | /// ``` 17 | pub fn caret_encode>(c: C) -> Option { 18 | // The formula is explained in the Wikipedia article. 19 | let c = c.into() ^ 0b0100_0000; 20 | if (b'?'..=b'_').contains(&c) { 21 | // SAFETY: All bytes between '?' (0x3F) and '_' (0x5f) are valid ascii characters. 22 | Some(unsafe { c.to_ascii_char_unchecked() }) 23 | } else { 24 | None 25 | } 26 | } 27 | 28 | /// Returns the control code represented by a [caret notation](https://en.wikipedia.org/wiki/Caret_notation) 29 | /// letter, or `None` if the letter is not used in caret notation. 30 | /// 31 | /// This function is the inverse of `caret_encode()`. 32 | /// 33 | /// # Examples 34 | /// 35 | /// Basic usage: 36 | /// 37 | /// ``` 38 | /// # use ascii::{AsciiChar, caret_decode}; 39 | /// assert_eq!(caret_decode(b'?'), Some(AsciiChar::DEL)); 40 | /// assert_eq!(caret_decode(AsciiChar::D), Some(AsciiChar::EOT)); 41 | /// assert_eq!(caret_decode(b'\0'), None); 42 | /// ``` 43 | /// 44 | /// Symmetry: 45 | /// 46 | /// ``` 47 | /// # use ascii::{AsciiChar, caret_encode, caret_decode}; 48 | /// assert_eq!(caret_encode(AsciiChar::US).and_then(caret_decode), Some(AsciiChar::US)); 49 | /// assert_eq!(caret_decode(b'@').and_then(caret_encode), Some(AsciiChar::At)); 50 | /// ``` 51 | pub fn caret_decode>(c: C) -> Option { 52 | // The formula is explained in the Wikipedia article. 53 | match c.into() { 54 | // SAFETY: All bytes between '?' (0x3F) and '_' (0x5f) after `xoring` with `0b0100_0000` are 55 | // valid bytes, as they represent characters between '␀' (0x0) and '␠' (0x1f) + '␡' (0x7f) 56 | b'?'..=b'_' => Some(unsafe { AsciiChar::from_ascii_unchecked(c.into() ^ 0b0100_0000) }), 57 | _ => None, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! A library that provides ASCII-only string and character types, equivalent to the `char`, `str` 12 | //! and `String` types in the standard library. 13 | //! 14 | //! Please refer to the readme file to learn about the different feature modes of this crate. 15 | //! 16 | //! # Minimum supported Rust version 17 | //! 18 | //! The minimum Rust version for 1.2.\* releases is 1.56.1. 19 | //! Later 1.y.0 releases might require newer Rust versions, but the three most 20 | //! recent stable releases at the time of publishing will always be supported. 21 | //! For example this means that if the current stable Rust version is 1.70 when 22 | //! ascii 1.3.0 is released, then ascii 1.3.\* will not require a newer 23 | //! Rust version than 1.68. 24 | //! 25 | //! # History 26 | //! 27 | //! This package included the Ascii types that were removed from the Rust standard library by the 28 | //! 2014-12 [reform of the `std::ascii` module](https://github.com/rust-lang/rfcs/pull/486). The 29 | //! API changed significantly since then. 30 | 31 | #![cfg_attr(not(feature = "std"), no_std)] 32 | // Clippy lints 33 | #![warn( 34 | clippy::pedantic, 35 | clippy::decimal_literal_representation, 36 | clippy::get_unwrap, 37 | clippy::indexing_slicing 38 | )] 39 | // Naming conventions sometimes go against this lint 40 | #![allow(clippy::module_name_repetitions)] 41 | // We need to get literal non-asciis for tests 42 | #![allow(clippy::non_ascii_literal)] 43 | // Sometimes it looks better to invert the order, such as when the `else` block is small 44 | #![allow(clippy::if_not_else)] 45 | // Shadowing is common and doesn't affect understanding 46 | // TODO: Consider removing `shadow_unrelated`, as it can show some actual logic errors 47 | #![allow(clippy::shadow_unrelated, clippy::shadow_reuse, clippy::shadow_same)] 48 | // A `if let` / `else` sometimes looks better than using iterator adaptors 49 | #![allow(clippy::option_if_let_else)] 50 | // In tests, we're fine with indexing, since a panic is a failure. 51 | #![cfg_attr(test, allow(clippy::indexing_slicing))] 52 | // for compatibility with methods on char and u8 53 | #![allow(clippy::trivially_copy_pass_by_ref)] 54 | // In preparation for feature `unsafe_block_in_unsafe_fn` (https://github.com/rust-lang/rust/issues/71668) 55 | #![allow(unused_unsafe)] 56 | 57 | #[cfg(feature = "alloc")] 58 | #[macro_use] 59 | extern crate alloc; 60 | #[cfg(feature = "std")] 61 | extern crate core; 62 | 63 | #[cfg(feature = "serde")] 64 | extern crate serde; 65 | 66 | #[cfg(all(test, feature = "serde_test"))] 67 | extern crate serde_test; 68 | 69 | mod ascii_char; 70 | mod ascii_str; 71 | #[cfg(feature = "alloc")] 72 | mod ascii_string; 73 | mod free_functions; 74 | #[cfg(feature = "serde")] 75 | mod serialization; 76 | 77 | pub use ascii_char::{AsciiChar, ToAsciiChar, ToAsciiCharError}; 78 | pub use ascii_str::{AsAsciiStr, AsAsciiStrError, AsMutAsciiStr, AsciiStr}; 79 | pub use ascii_str::{Chars, CharsMut, CharsRef}; 80 | #[cfg(feature = "alloc")] 81 | pub use ascii_string::{AsciiString, FromAsciiError, IntoAsciiString}; 82 | pub use free_functions::{caret_decode, caret_encode}; 83 | -------------------------------------------------------------------------------- /src/serialization/ascii_char.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use serde::de::{Error, Unexpected, Visitor}; 4 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 5 | 6 | use ascii_char::AsciiChar; 7 | 8 | impl Serialize for AsciiChar { 9 | #[inline] 10 | fn serialize(&self, serializer: S) -> Result { 11 | serializer.serialize_char(self.as_char()) 12 | } 13 | } 14 | 15 | struct AsciiCharVisitor; 16 | 17 | impl<'de> Visitor<'de> for AsciiCharVisitor { 18 | type Value = AsciiChar; 19 | 20 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | f.write_str("an ascii character") 22 | } 23 | 24 | #[inline] 25 | fn visit_char(self, v: char) -> Result { 26 | AsciiChar::from_ascii(v).map_err(|_| Error::invalid_value(Unexpected::Char(v), &self)) 27 | } 28 | 29 | #[inline] 30 | fn visit_str(self, v: &str) -> Result { 31 | if v.len() == 1 { 32 | let c = v.chars().next().unwrap(); 33 | self.visit_char(c) 34 | } else { 35 | Err(Error::invalid_value(Unexpected::Str(v), &self)) 36 | } 37 | } 38 | } 39 | 40 | impl<'de> Deserialize<'de> for AsciiChar { 41 | fn deserialize(deserializer: D) -> Result 42 | where 43 | D: Deserializer<'de>, 44 | { 45 | deserializer.deserialize_char(AsciiCharVisitor) 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | 53 | #[cfg(feature = "serde_test")] 54 | const ASCII_CHAR: char = 'e'; 55 | #[cfg(feature = "serde_test")] 56 | const ASCII_STR: &str = "e"; 57 | #[cfg(feature = "serde_test")] 58 | const UNICODE_CHAR: char = 'é'; 59 | 60 | #[test] 61 | fn basic() { 62 | fn assert_serialize() {} 63 | fn assert_deserialize<'de, T: Deserialize<'de>>() {} 64 | assert_serialize::(); 65 | assert_deserialize::(); 66 | } 67 | 68 | #[test] 69 | #[cfg(feature = "serde_test")] 70 | fn serialize() { 71 | use serde_test::{assert_tokens, Token}; 72 | let ascii_char = AsciiChar::from_ascii(ASCII_CHAR).unwrap(); 73 | assert_tokens(&ascii_char, &[Token::Char(ASCII_CHAR)]); 74 | } 75 | 76 | #[test] 77 | #[cfg(feature = "serde_test")] 78 | fn deserialize() { 79 | use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; 80 | let ascii_char = AsciiChar::from_ascii(ASCII_CHAR).unwrap(); 81 | assert_de_tokens(&ascii_char, &[Token::String(ASCII_STR)]); 82 | assert_de_tokens(&ascii_char, &[Token::Str(ASCII_STR)]); 83 | assert_de_tokens(&ascii_char, &[Token::BorrowedStr(ASCII_STR)]); 84 | assert_de_tokens_error::( 85 | &[Token::Char(UNICODE_CHAR)], 86 | "invalid value: character `é`, expected an ascii character", 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/serialization/ascii_str.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use serde::de::{Error, Unexpected, Visitor}; 4 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 5 | 6 | use ascii_str::AsciiStr; 7 | 8 | impl Serialize for AsciiStr { 9 | #[inline] 10 | fn serialize(&self, serializer: S) -> Result { 11 | serializer.serialize_str(self.as_str()) 12 | } 13 | } 14 | 15 | struct AsciiStrVisitor; 16 | 17 | impl<'a> Visitor<'a> for AsciiStrVisitor { 18 | type Value = &'a AsciiStr; 19 | 20 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | f.write_str("a borrowed ascii string") 22 | } 23 | 24 | fn visit_borrowed_str(self, v: &'a str) -> Result { 25 | AsciiStr::from_ascii(v.as_bytes()) 26 | .map_err(|_| Error::invalid_value(Unexpected::Str(v), &self)) 27 | } 28 | 29 | fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result { 30 | AsciiStr::from_ascii(v).map_err(|_| Error::invalid_value(Unexpected::Bytes(v), &self)) 31 | } 32 | } 33 | 34 | impl<'de: 'a, 'a> Deserialize<'de> for &'a AsciiStr { 35 | fn deserialize(deserializer: D) -> Result 36 | where 37 | D: Deserializer<'de>, 38 | { 39 | deserializer.deserialize_str(AsciiStrVisitor) 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | 47 | #[cfg(feature = "serde_test")] 48 | const ASCII: &str = "Francais"; 49 | #[cfg(feature = "serde_test")] 50 | const UNICODE: &str = "Français"; 51 | 52 | #[test] 53 | fn basic() { 54 | fn assert_serialize() {} 55 | fn assert_deserialize<'de, T: Deserialize<'de>>() {} 56 | assert_serialize::<&AsciiStr>(); 57 | assert_deserialize::<&AsciiStr>(); 58 | } 59 | 60 | #[test] 61 | #[cfg(feature = "serde_test")] 62 | fn serialize() { 63 | use serde_test::{assert_tokens, Token}; 64 | let ascii_str = AsciiStr::from_ascii(ASCII).unwrap(); 65 | assert_tokens(&ascii_str, &[Token::BorrowedStr(ASCII)]); 66 | } 67 | 68 | #[test] 69 | #[cfg(feature = "serde_test")] 70 | fn deserialize() { 71 | use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; 72 | let ascii_str = AsciiStr::from_ascii(ASCII).unwrap(); 73 | assert_de_tokens(&ascii_str, &[Token::BorrowedBytes(ASCII.as_bytes())]); 74 | assert_de_tokens_error::<&AsciiStr>( 75 | &[Token::BorrowedStr(UNICODE)], 76 | "invalid value: string \"Français\", expected a borrowed ascii string", 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/serialization/ascii_string.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use serde::de::{Error, Unexpected, Visitor}; 4 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 5 | 6 | use ascii_str::AsciiStr; 7 | use ascii_string::AsciiString; 8 | 9 | impl Serialize for AsciiString { 10 | #[inline] 11 | fn serialize(&self, serializer: S) -> Result { 12 | serializer.serialize_str(self.as_str()) 13 | } 14 | } 15 | 16 | struct AsciiStringVisitor; 17 | 18 | impl<'de> Visitor<'de> for AsciiStringVisitor { 19 | type Value = AsciiString; 20 | 21 | fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | f.write_str("an ascii string") 23 | } 24 | 25 | fn visit_str(self, v: &str) -> Result { 26 | AsciiString::from_ascii(v).map_err(|_| Error::invalid_value(Unexpected::Str(v), &self)) 27 | } 28 | 29 | fn visit_string(self, v: String) -> Result { 30 | AsciiString::from_ascii(v.as_bytes()) 31 | .map_err(|_| Error::invalid_value(Unexpected::Str(&v), &self)) 32 | } 33 | 34 | fn visit_bytes(self, v: &[u8]) -> Result { 35 | AsciiString::from_ascii(v).map_err(|_| Error::invalid_value(Unexpected::Bytes(v), &self)) 36 | } 37 | 38 | fn visit_byte_buf(self, v: Vec) -> Result { 39 | AsciiString::from_ascii(v.as_slice()) 40 | .map_err(|_| Error::invalid_value(Unexpected::Bytes(&v), &self)) 41 | } 42 | } 43 | 44 | struct AsciiStringInPlaceVisitor<'a>(&'a mut AsciiString); 45 | 46 | impl<'a, 'de> Visitor<'de> for AsciiStringInPlaceVisitor<'a> { 47 | type Value = (); 48 | 49 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 50 | formatter.write_str("an ascii string") 51 | } 52 | 53 | fn visit_str(self, v: &str) -> Result { 54 | let ascii_str = match AsciiStr::from_ascii(v.as_bytes()) { 55 | Ok(ascii_str) => ascii_str, 56 | Err(_) => return Err(Error::invalid_value(Unexpected::Str(v), &self)), 57 | }; 58 | self.0.clear(); 59 | self.0.push_str(ascii_str); 60 | Ok(()) 61 | } 62 | 63 | fn visit_string(self, v: String) -> Result { 64 | let ascii_string = match AsciiString::from_ascii(v.as_bytes()) { 65 | Ok(ascii_string) => ascii_string, 66 | Err(_) => return Err(Error::invalid_value(Unexpected::Str(&v), &self)), 67 | }; 68 | *self.0 = ascii_string; 69 | Ok(()) 70 | } 71 | 72 | fn visit_bytes(self, v: &[u8]) -> Result { 73 | let ascii_str = match AsciiStr::from_ascii(v) { 74 | Ok(ascii_str) => ascii_str, 75 | Err(_) => return Err(Error::invalid_value(Unexpected::Bytes(v), &self)), 76 | }; 77 | self.0.clear(); 78 | self.0.push_str(ascii_str); 79 | Ok(()) 80 | } 81 | 82 | fn visit_byte_buf(self, v: Vec) -> Result { 83 | let ascii_string = match AsciiString::from_ascii(v.as_slice()) { 84 | Ok(ascii_string) => ascii_string, 85 | Err(_) => return Err(Error::invalid_value(Unexpected::Bytes(&v), &self)), 86 | }; 87 | *self.0 = ascii_string; 88 | Ok(()) 89 | } 90 | } 91 | 92 | impl<'de> Deserialize<'de> for AsciiString { 93 | fn deserialize(deserializer: D) -> Result 94 | where 95 | D: Deserializer<'de>, 96 | { 97 | deserializer.deserialize_string(AsciiStringVisitor) 98 | } 99 | 100 | fn deserialize_in_place(deserializer: D, place: &mut Self) -> Result<(), D::Error> 101 | where 102 | D: Deserializer<'de>, 103 | { 104 | deserializer.deserialize_string(AsciiStringInPlaceVisitor(place)) 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod tests { 110 | use super::*; 111 | 112 | #[cfg(feature = "serde_test")] 113 | const ASCII: &str = "Francais"; 114 | #[cfg(feature = "serde_test")] 115 | const UNICODE: &str = "Français"; 116 | 117 | #[test] 118 | fn basic() { 119 | fn assert_serialize() {} 120 | fn assert_deserialize<'de, T: Deserialize<'de>>() {} 121 | assert_serialize::(); 122 | assert_deserialize::(); 123 | } 124 | 125 | #[test] 126 | #[cfg(feature = "serde_test")] 127 | fn serialize() { 128 | use serde_test::{assert_tokens, Token}; 129 | 130 | let ascii_string = AsciiString::from_ascii(ASCII).unwrap(); 131 | assert_tokens(&ascii_string, &[Token::String(ASCII)]); 132 | assert_tokens(&ascii_string, &[Token::Str(ASCII)]); 133 | assert_tokens(&ascii_string, &[Token::BorrowedStr(ASCII)]); 134 | } 135 | 136 | #[test] 137 | #[cfg(feature = "serde_test")] 138 | fn deserialize() { 139 | use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; 140 | let ascii_string = AsciiString::from_ascii(ASCII).unwrap(); 141 | assert_de_tokens(&ascii_string, &[Token::Bytes(ASCII.as_bytes())]); 142 | assert_de_tokens(&ascii_string, &[Token::BorrowedBytes(ASCII.as_bytes())]); 143 | assert_de_tokens(&ascii_string, &[Token::ByteBuf(ASCII.as_bytes())]); 144 | assert_de_tokens_error::( 145 | &[Token::String(UNICODE)], 146 | "invalid value: string \"Français\", expected an ascii string", 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/serialization/mod.rs: -------------------------------------------------------------------------------- 1 | mod ascii_char; 2 | mod ascii_str; 3 | mod ascii_string; 4 | -------------------------------------------------------------------------------- /tests.rs: -------------------------------------------------------------------------------- 1 | extern crate ascii; 2 | 3 | use ascii::{AsAsciiStr, AsciiChar, AsciiStr}; 4 | #[cfg(feature = "std")] 5 | use ascii::{AsciiString, IntoAsciiString}; 6 | 7 | #[test] 8 | #[cfg(feature = "std")] 9 | fn ascii_vec() { 10 | let test = b"( ;"; 11 | let a = AsciiStr::from_ascii(test).unwrap(); 12 | assert_eq!(test.as_ascii_str(), Ok(a)); 13 | assert_eq!("( ;".as_ascii_str(), Ok(a)); 14 | let v = test.to_vec(); 15 | assert_eq!(v.as_ascii_str(), Ok(a)); 16 | assert_eq!("( ;".to_string().as_ascii_str(), Ok(a)); 17 | } 18 | 19 | #[test] 20 | fn to_ascii() { 21 | assert!("zoä华".as_ascii_str().is_err()); 22 | assert!([127_u8, 128, 255].as_ascii_str().is_err()); 23 | 24 | let arr = [AsciiChar::ParenOpen, AsciiChar::Space, AsciiChar::Semicolon]; 25 | let a: &AsciiStr = (&arr[..]).into(); 26 | assert_eq!(b"( ;".as_ascii_str(), Ok(a)); 27 | assert_eq!("( ;".as_ascii_str(), Ok(a)); 28 | } 29 | 30 | #[test] 31 | fn deprecated_variant() { 32 | #![allow(deprecated)] 33 | use AsciiChar::*; 34 | assert_eq!(AsciiChar::STX, SOX); 35 | } 36 | 37 | #[test] 38 | #[cfg(feature = "std")] 39 | fn into_ascii() { 40 | let arr = [AsciiChar::ParenOpen, AsciiChar::Space, AsciiChar::Semicolon]; 41 | let v = AsciiString::from(arr.to_vec()); 42 | assert_eq!(b"( ;".to_vec().into_ascii_string(), Ok(v.clone())); 43 | assert_eq!("( ;".to_string().into_ascii_string(), Ok(v.clone())); 44 | assert_eq!(b"( ;", AsRef::<[u8]>::as_ref(&v)); 45 | 46 | let err = "zoä华".to_string().into_ascii_string().unwrap_err(); 47 | assert_eq!(Err(err.ascii_error()), "zoä华".as_ascii_str()); 48 | assert_eq!(err.into_source(), "zoä华"); 49 | let err = vec![127, 128, 255].into_ascii_string().unwrap_err(); 50 | assert_eq!(Err(err.ascii_error()), [127, 128, 255].as_ascii_str()); 51 | assert_eq!(err.into_source(), &[127, 128, 255]); 52 | } 53 | 54 | #[test] 55 | #[cfg(feature = "std")] 56 | fn compare_ascii_string_ascii_str() { 57 | let v = b"abc"; 58 | let ascii_string = AsciiString::from_ascii(&v[..]).unwrap(); 59 | let ascii_str = AsciiStr::from_ascii(v).unwrap(); 60 | assert!(ascii_string == ascii_str); 61 | assert!(ascii_str == ascii_string); 62 | } 63 | 64 | #[test] 65 | #[cfg(feature = "std")] 66 | fn compare_ascii_string_string() { 67 | let v = b"abc"; 68 | let string = String::from_utf8(v.to_vec()).unwrap(); 69 | let ascii_string = AsciiString::from_ascii(&v[..]).unwrap(); 70 | assert!(string == ascii_string); 71 | assert!(ascii_string == string); 72 | } 73 | 74 | #[test] 75 | #[cfg(feature = "std")] 76 | fn compare_ascii_str_string() { 77 | let v = b"abc"; 78 | let string = String::from_utf8(v.to_vec()).unwrap(); 79 | let ascii_str = AsciiStr::from_ascii(&v[..]).unwrap(); 80 | assert!(string == ascii_str); 81 | assert!(ascii_str == string); 82 | } 83 | 84 | #[test] 85 | #[cfg(feature = "std")] 86 | fn compare_ascii_string_str() { 87 | let v = b"abc"; 88 | let sstr = ::std::str::from_utf8(v).unwrap(); 89 | let ascii_string = AsciiString::from_ascii(&v[..]).unwrap(); 90 | assert!(sstr == ascii_string); 91 | assert!(ascii_string == sstr); 92 | } 93 | 94 | #[test] 95 | fn compare_ascii_str_str() { 96 | let v = b"abc"; 97 | let sstr = ::std::str::from_utf8(v).unwrap(); 98 | let ascii_str = AsciiStr::from_ascii(v).unwrap(); 99 | assert!(sstr == ascii_str); 100 | assert!(ascii_str == sstr); 101 | } 102 | 103 | #[test] 104 | #[allow(clippy::redundant_slicing)] 105 | fn compare_ascii_str_slice() { 106 | let b = b"abc".as_ascii_str().unwrap(); 107 | let c = b"ab".as_ascii_str().unwrap(); 108 | assert_eq!(&b[..2], &c[..]); 109 | assert_eq!(c[1].as_char(), 'b'); 110 | } 111 | 112 | #[test] 113 | #[cfg(feature = "std")] 114 | fn compare_ascii_string_slice() { 115 | let b = AsciiString::from_ascii("abc").unwrap(); 116 | let c = AsciiString::from_ascii("ab").unwrap(); 117 | assert_eq!(&b[..2], &c[..]); 118 | assert_eq!(c[1].as_char(), 'b'); 119 | } 120 | 121 | #[test] 122 | #[cfg(feature = "std")] 123 | fn extend_from_iterator() { 124 | use std::borrow::Cow; 125 | 126 | let abc = "abc".as_ascii_str().unwrap(); 127 | let mut s = abc.chars().collect::(); 128 | assert_eq!(s, abc); 129 | s.extend(abc); 130 | assert_eq!(s, "abcabc"); 131 | 132 | let lines = "one\ntwo\nthree".as_ascii_str().unwrap().lines(); 133 | s.extend(lines); 134 | assert_eq!(s, "abcabconetwothree"); 135 | 136 | let cows = "ASCII Ascii ascii" 137 | .as_ascii_str() 138 | .unwrap() 139 | .split(AsciiChar::Space) 140 | .map(|case| { 141 | if case.chars().all(AsciiChar::is_uppercase) { 142 | Cow::from(case) 143 | } else { 144 | Cow::from(case.to_ascii_uppercase()) 145 | } 146 | }); 147 | s.extend(cows); 148 | s.extend(&[AsciiChar::LineFeed]); 149 | assert_eq!(s, "abcabconetwothreeASCIIASCIIASCII\n"); 150 | } 151 | --------------------------------------------------------------------------------