ascii-1.1.0/.cargo_vcs_info.json0000644000000001360000000000100121230ustar { "git": { "sha1": "8605175df3a9bd1c5e36a300fa68eb5b4a181e6f" }, "path_in_vcs": "" }ascii-1.1.0/.github/workflows/ci.yml000064400000000000000000000027371046102023000154370ustar 00000000000000name: CI on: pull_request: push: branches: - master jobs: test: name: Test with Rust ${{ matrix.rust }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: rust: [1.41.1, stable, beta, nightly] steps: - uses: actions/checkout@v2 - uses: hecrj/setup-rust-action@v1 with: rust-version: ${{ matrix.rust }} - run: cargo test --verbose --all-features - run: cargo test --verbose --no-default-features --features alloc - run: cargo test --verbose --no-default-features clippy: name: Lint with Clippy runs-on: ubuntu-latest env: RUSTFLAGS: -Dwarnings steps: - uses: actions/checkout@v2 - uses: hecrj/setup-rust-action@v1 with: components: clippy - run: cargo clippy --all-targets --verbose --no-default-features - run: cargo clippy --all-targets --verbose --all-features test-minimal: name: Test minimal dependency version with Rust nightly runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: hecrj/setup-rust-action@v1 with: rust-version: nightly - run: cargo test -Zminimal-versions --verbose --all-features miri: name: Run tests under `miri` to check for UB runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@nightly with: components: miri - run: cargo miri test --all-features ascii-1.1.0/.gitignore000064400000000000000000000000241046102023000126770ustar 00000000000000/target /Cargo.lock ascii-1.1.0/Cargo.toml0000644000000021100000000000100101130ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "ascii" version = "1.1.0" authors = [ "Thomas Bahn ", "Torbjørn Birch Moltu ", "Simon Sapin ", ] description = "ASCII-only equivalents to `char`, `str` and `String`." documentation = "https://docs.rs/ascii" readme = "README.md" license = "Apache-2.0 OR MIT" repository = "https://github.com/tomprogrammer/rust-ascii" [[test]] name = "tests" path = "tests.rs" [dependencies.serde] version = "1.0.25" optional = true [dependencies.serde_test] version = "1.0" optional = true [features] alloc = [] default = ["std"] std = ["alloc"] ascii-1.1.0/Cargo.toml.orig000064400000000000000000000011441046102023000136020ustar 00000000000000[package] authors = ["Thomas Bahn ", "Torbjørn Birch Moltu ", "Simon Sapin "] description = "ASCII-only equivalents to `char`, `str` and `String`." documentation = "https://docs.rs/ascii" license = "Apache-2.0 OR MIT" name = "ascii" readme = "README.md" repository = "https://github.com/tomprogrammer/rust-ascii" version = "1.1.0" [dependencies] serde = { version = "1.0.25", optional = true } serde_test = { version = "1.0", optional = true } [features] default = ["std"] std = ["alloc"] alloc = [] [[test]] name = "tests" path = "tests.rs" ascii-1.1.0/LICENSE-APACHE000064400000000000000000000260761046102023000126520ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ascii-1.1.0/LICENSE-MIT000064400000000000000000000021541046102023000123510ustar 00000000000000MIT License Copyright (c) 2017 Thomas Bahn and contributors Copyright (c) 2014 The Rust Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ascii-1.1.0/README.md000064400000000000000000000044131046102023000121740ustar 00000000000000# ascii A library that provides ASCII-only string and character types, equivalent to the `char`, `str` and `String` types in the standard library. Types and conversion traits are described in the [Documentation](https://docs.rs/ascii). You can include this crate in your cargo project by adding it to the dependencies section in `Cargo.toml`: ```toml [dependencies] ascii = "1.1" ``` ## Using ascii without libstd Most of `AsciiChar` and `AsciiStr` can be used without `std` by disabling the default features. The owned string type `AsciiString` and the conversion trait `IntoAsciiString` as well as all methods referring to these types can be re-enabled by enabling the `alloc` feature. Methods referring to `CStr` and `CString` are also unavailable. The `Error` trait also only exists in `std`, but `description()` is made available as an inherent method for `ToAsciiCharError` and `AsAsciiStrError` in `#![no_std]`-mode. To use the `ascii` crate in `#![no_std]` mode in your cargo project, just add the following dependency declaration in `Cargo.toml`: ```toml [dependencies] ascii = { version = "1.1", default-features = false, features = ["alloc"] } ``` ## Minimum supported Rust version The minimum Rust version for 1.1.\* releases is 1.41.1. Later 1.y.0 releases might require newer Rust versions, but the three most recent stable releases at the time of publishing will always be supported. For example this means that if the current stable Rust version is 1.70 when ascii 1.2.0 is released, then ascii 1.2.\* will not require a newer Rust version than 1.68. ## History This package included the Ascii types that were removed from the Rust standard library by the 2014-12 [reform of the `std::ascii` module](https://github.com/rust-lang/rfcs/pull/486). The API changed significantly since then. ## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ascii-1.1.0/RELEASES.md000064400000000000000000000203011046102023000124340ustar 00000000000000Version 1.1.0 (2022-09-18) ========================== * Add alloc feature. This enables `AsciiString` and methods that take or return `Box<[AsciiStr]>` in `!#[no_std]`-mode. * Add `AsciiStr::into_ascii_string()`, `AsciiString::into_boxed_ascii_str()` and `AsciiString::insert_str()`. * Implement `From>` and `From` for `AsciiString`. * Implement `From` for `Box`, `Rc`, `Arc` and `Vec`. * Make `AsciiString::new()`, `AsciiStr::len()` and `AsciiStr::is_empty()` `const fn`. * Require Rust 1.44.1. Version 1.0.0 (2019-08-26) ========================== Breaking changes: * Change `AsciiChar.is_whitespace()` to also return true for '\0xb' (vertical tab) and '\0xc' (form feed). * Remove quickcheck feature. * Remove `AsciiStr::new()`. * Rename `AsciiChar::from()` and `AsciiChar::from_unchecked()` to `from_ascii()` and `from_ascii_unchecked()`. * Rename several `AsciiChar.is_xxx()` methods to `is_ascii_xxx()` (for comsistency with std). * Rename `AsciiChar::Null` to `Nul` (for consistency with eg. `CStr::from_bytes_with_nul()`). * Rename `AsciiStr.trim_left()` and `AsciiStr.trim_right()` to `trim_start()` and `trim_end()`. * Remove impls of the deprecated `std::ascii::AsciiExt` trait. * Change iterators `Chars`, `CharsMut` and `CharsRef` from type aliases to newtypes. * Return `impl Trait` from `AsciiStr.lines()` and `AsciiStr.split()`, and remove iterator types `Lines` and `Split`. * Add `slice_ascii_str()`, `get_ascii()` and `unwrap_ascii()` to the `AsAsciiStr` trait. * Add `slice_mut_ascii_str()` and `unwrap_ascii_mut()` to the `AsMutAsciiStr` trait. * Require Rust 1.33.0 for 1.0.\*, and allow later semver-compatible 1.y.0 releases to increase it. Additions: * Add `const fn` `AsciiChar::new()` which panicks on invalid values. * Make most `AsciiChar` methods `const fn`. * Add multiple `AsciiChar::is_[ascii_]xxx()` methods. * Implement `AsRef` for `AsciiChar`. * Make `AsciiString`'s `Extend` and `FromIterator` impl generic over all `AsRef`. * Implement inclusive range indexing for `AsciiStr` (and thereby `AsciiString`). * Mark `AsciiStr` and `AsciiString` `#[repr(transparent)]` (to `[AsciiChar]` and `Vec` respectively). Version 0.9.3 (2019-08-26) ========================== Soundness fix: **Remove** [unsound](https://github.com/tomprogrammer/rust-ascii/issues/64) impls of `From<&mut AsciiStr>` for `&mut [u8]` and `&mut str`. This is a breaking change, but theese impls can lead to undefined behavior in safe code. If you use this impl and know that non-ASCII values are never inserted into the `[u8]` or `str`, you can pin ascii to 0.9.2. Other changes: * Make quickcheck `Arbitrary` impl sometimes produce `AsciiChar::DEL`. * Implement `Clone`, `Copy` and `Eq` for `ToAsciiCharError`. * Implement `ToAsciiChar` for `u16`, `u32` and `i8`. Version 0.9.2 (2019-07-07) ========================== * Implement the `IntoAsciiString` trait for `std::ffi::CStr` and `std::ffi::CString` types, and implemented the `AsAsciiStr` trait for `std::ffi::CStr` type. * Implement the `IntoAsciiString` for `std::borrow::Cow`, where the inner types themselves implement `IntoAsciiString`. * Implement conversions between `AsciiString` and `Cow<'a, AsciiStr>`. * Implement the `std::ops::AddAssign` trait for `AsciiString`. * Implement `BorrowMut`, `AsRef<[AsciiChar]>`, `AsRef`, `AsMut<[AsciiChar]>` for `AsciiString`. * Implement `PartialEq<[u8]>` and `PartialEq<[AsciiChar]>` for `AsciiStr`. * Add `AsciiStr::first()`, `AsciiStr::last()` and `AsciiStr::split()` methods. * Implement `DoubleEndedIterator` for `AsciiStr::lines()`. * Implement `AsRef` and `AsMut for AsciiString`. Version 0.8.4 (2017-04-18) ========================== * Fix the tests when running without std. Version 0.8.3 (2017-04-18) ========================== * Bugfix: `::to_ascii_lowercase` did erroneously convert to uppercase. Version 0.8.2 (2017-04-17) ========================== * Implement `IntoAsciiString` for `&'a str` and `&'a [u8]`. * Implement the `quickcheck::Arbitrary` trait for `AsciiChar` and `AsciiString`. The implementation is enabled by the `quickcheck` feature. Version 0.8.1 (2017-02-11) ========================== * Add `Chars`, `CharsMut` and `Lines` iterators. * Implement `std::fmt::Write` for `AsciiString`. Version 0.8.0 (2017-01-02) ========================== Breaking changes: * Return `FromAsciiError` instead of the input when `AsciiString::from_ascii()` or `into_ascii_string()` fails. * Replace the `no_std` feature with the additive `std` feature, which is part of the default features. (Issue #29) * `AsciiChar::is_*()` and `::as_{byte,char}()` take `self` by value instead of by reference. Additions: * Make `AsciiChar` comparable with `char` and `u8`. * Add `AsciiChar::as_printable_char()` and the free functions `caret_encode()` and `caret_decode()`. * Implement some methods from `AsciiExt` and `Error` (which are not in libcore) directly in `core` mode: * `Ascii{Char,Str}::eq_ignore_ascii_case()` * `AsciiChar::to_ascii_{upper,lower}case()` * `AsciiStr::make_ascii_{upper,lower}case()` * `{ToAsciiChar,AsAsciiStr}Error::description()` Version 0.7.1 (2016-08-15) ========================== * Fix the implementation of `AsciiExt::to_ascii_lowercase()` for `AsciiChar` converting to uppercase. (introduced in 0.7.0) Version 0.7.0 (2016-06-25) ========================== * Rename `Ascii` to `AsciiChar` and convert it into an enum. (with a variant for every ASCII character) * Replace `OwnedAsciiCast` with `IntoAsciiString`. * Replace `AsciiCast` with `As[Mut]AsciiStr` and `IntoAsciiChar`. * Add *from[_ascii]_unchecked* methods. * Replace *from_bytes* with *from_ascii* in method names. * Return `std::error::Error`-implementing types instead of `()` and `None` when conversion to `AsciiStr` or `AsciiChar` fails. * Implement `AsciiExt` without the `unstable` Cargo feature flag, which is removed. * Require Rust 1.9 or later. * Add `#[no_std]` support in a Cargo feature. * Implement `From<{&,&mut,Box<}AsciiStr>` for `[Ascii]`, `[u8]` and `str` * Implement `From<{&,&mut,Box<}[Ascii]>`, `As{Ref,Mut}<[Ascii]>` and Default for `AsciiStr` * Implement `From>` for `AsciiString`. * Implement `AsMut` for `AsciiString`. * Stop some `Ascii::is_xxx()` methods from panicking. * Add `Ascii::is_whitespace()`. * Add `AsciiString::as_mut_slice()`. * Add raw pointer methods on `AsciiString`: * `from_raw_parts` * `as_ptr` * `as_mut_ptr` Version 0.6.0 (2015-12-30) ========================== * Add `Ascii::from_byte()` * Add `AsciiStr::trim[_{left,right}]()` Version 0.5.4 (2015-07-29) ========================== Implement `IndexMut` for AsciiStr and AsciiString. Version 0.5.1 (2015-06-13) ========================== * Add `Ascii::from()`. * Implement `Index` for `AsciiStr` and `AsciiString`. * Implement `Default`,`FromIterator`,`Extend` and `Add` for `AsciiString` * Added inherent methods on `AsciiString`: * `with_capacity` * `push_str` * `capacity` * `reserve` * `reserve_exact` * `shrink_to_fit` * `push` * `truncate` * `pop` * `remove` * `insert` * `len` * `is_empty` * `clear` Version 0.5.0 (2015-05-05) ========================== First release compatible with Rust 1.0.0. ascii-1.1.0/src/ascii_char.rs000064400000000000000000001024011046102023000141330ustar 00000000000000use core::cmp::Ordering; use core::mem; use core::{char, fmt}; #[cfg(feature = "std")] use std::error::Error; #[allow(non_camel_case_types)] /// An ASCII character. It wraps a `u8`, with the highest bit always zero. #[derive(Clone, PartialEq, PartialOrd, Ord, Eq, Hash, Copy)] #[repr(u8)] pub enum AsciiChar { /// `'\0'` Null = 0, /// [Start Of Heading](http://en.wikipedia.org/wiki/Start_of_Heading) SOH = 1, /// [Start Of teXt](http://en.wikipedia.org/wiki/Start_of_Text) SOX = 2, /// [End of TeXt](http://en.wikipedia.org/wiki/End-of-Text_character) ETX = 3, /// [End Of Transmission](http://en.wikipedia.org/wiki/End-of-Transmission_character) EOT = 4, /// [Enquiry](http://en.wikipedia.org/wiki/Enquiry_character) ENQ = 5, /// [Acknowledgement](http://en.wikipedia.org/wiki/Acknowledge_character) ACK = 6, /// [bell / alarm / audible](http://en.wikipedia.org/wiki/Bell_character) /// /// `'\a'` is not recognized by Rust. Bell = 7, /// [Backspace](http://en.wikipedia.org/wiki/Backspace) /// /// `'\b'` is not recognized by Rust. BackSpace = 8, /// `'\t'` Tab = 9, /// `'\n'` LineFeed = 10, /// [Vertical tab](http://en.wikipedia.org/wiki/Vertical_Tab) /// /// `'\v'` is not recognized by Rust. VT = 11, /// [Form Feed](http://en.wikipedia.org/wiki/Form_Feed) /// /// `'\f'` is not recognized by Rust. FF = 12, /// `'\r'` CarriageReturn = 13, /// [Shift In](http://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters) SI = 14, /// [Shift Out](http://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters) SO = 15, /// [Data Link Escape](http://en.wikipedia.org/wiki/Data_Link_Escape) DLE = 16, /// [Device control 1, often XON](http://en.wikipedia.org/wiki/Device_Control_1) DC1 = 17, /// Device control 2 DC2 = 18, /// Device control 3, Often XOFF DC3 = 19, /// Device control 4 DC4 = 20, /// [Negative AcKnowledgement](http://en.wikipedia.org/wiki/Negative-acknowledge_character) NAK = 21, /// [Synchronous idle](http://en.wikipedia.org/wiki/Synchronous_Idle) SYN = 22, /// [End of Transmission Block](http://en.wikipedia.org/wiki/End-of-Transmission-Block_character) ETB = 23, /// [Cancel](http://en.wikipedia.org/wiki/Cancel_character) CAN = 24, /// [End of Medium](http://en.wikipedia.org/wiki/End_of_Medium) EM = 25, /// [Substitute](http://en.wikipedia.org/wiki/Substitute_character) SUB = 26, /// [Escape](http://en.wikipedia.org/wiki/Escape_character) /// /// `'\e'` is not recognized by Rust. ESC = 27, /// [File Separator](http://en.wikipedia.org/wiki/File_separator) FS = 28, /// [Group Separator](http://en.wikipedia.org/wiki/Group_separator) GS = 29, /// [Record Separator](http://en.wikipedia.org/wiki/Record_separator) RS = 30, /// [Unit Separator](http://en.wikipedia.org/wiki/Unit_separator) US = 31, /// `' '` Space = 32, /// `'!'` Exclamation = 33, /// `'"'` Quotation = 34, /// `'#'` Hash = 35, /// `'$'` Dollar = 36, /// `'%'` Percent = 37, /// `'&'` Ampersand = 38, /// `'\''` Apostrophe = 39, /// `'('` ParenOpen = 40, /// `')'` ParenClose = 41, /// `'*'` Asterisk = 42, /// `'+'` Plus = 43, /// `','` Comma = 44, /// `'-'` Minus = 45, /// `'.'` Dot = 46, /// `'/'` Slash = 47, /// `'0'` _0 = 48, /// `'1'` _1 = 49, /// `'2'` _2 = 50, /// `'3'` _3 = 51, /// `'4'` _4 = 52, /// `'5'` _5 = 53, /// `'6'` _6 = 54, /// `'7'` _7 = 55, /// `'8'` _8 = 56, /// `'9'` _9 = 57, /// `':'` Colon = 58, /// `';'` Semicolon = 59, /// `'<'` LessThan = 60, /// `'='` Equal = 61, /// `'>'` GreaterThan = 62, /// `'?'` Question = 63, /// `'@'` At = 64, /// `'A'` A = 65, /// `'B'` B = 66, /// `'C'` C = 67, /// `'D'` D = 68, /// `'E'` E = 69, /// `'F'` F = 70, /// `'G'` G = 71, /// `'H'` H = 72, /// `'I'` I = 73, /// `'J'` J = 74, /// `'K'` K = 75, /// `'L'` L = 76, /// `'M'` M = 77, /// `'N'` N = 78, /// `'O'` O = 79, /// `'P'` P = 80, /// `'Q'` Q = 81, /// `'R'` R = 82, /// `'S'` S = 83, /// `'T'` T = 84, /// `'U'` U = 85, /// `'V'` V = 86, /// `'W'` W = 87, /// `'X'` X = 88, /// `'Y'` Y = 89, /// `'Z'` Z = 90, /// `'['` BracketOpen = 91, /// `'\'` BackSlash = 92, /// `']'` BracketClose = 93, /// `'^'` Caret = 94, /// `'_'` UnderScore = 95, /// `'`'` Grave = 96, /// `'a'` a = 97, /// `'b'` b = 98, /// `'c'` c = 99, /// `'d'` d = 100, /// `'e'` e = 101, /// `'f'` f = 102, /// `'g'` g = 103, /// `'h'` h = 104, /// `'i'` i = 105, /// `'j'` j = 106, /// `'k'` k = 107, /// `'l'` l = 108, /// `'m'` m = 109, /// `'n'` n = 110, /// `'o'` o = 111, /// `'p'` p = 112, /// `'q'` q = 113, /// `'r'` r = 114, /// `'s'` s = 115, /// `'t'` t = 116, /// `'u'` u = 117, /// `'v'` v = 118, /// `'w'` w = 119, /// `'x'` x = 120, /// `'y'` y = 121, /// `'z'` z = 122, /// `'{'` CurlyBraceOpen = 123, /// `'|'` VerticalBar = 124, /// `'}'` CurlyBraceClose = 125, /// `'~'` Tilde = 126, /// [Delete](http://en.wikipedia.org/wiki/Delete_character) DEL = 127, } impl AsciiChar { /// Constructs an ASCII character from a `u8`, `char` or other character type. /// /// # Errors /// Returns `Err(())` if the character can't be ASCII encoded. /// /// # Example /// ``` /// # use ascii::AsciiChar; /// let a = AsciiChar::from_ascii('g').unwrap(); /// assert_eq!(a.as_char(), 'g'); /// ``` #[inline] pub fn from_ascii(ch: C) -> Result { ch.to_ascii_char() } /// Create an `AsciiChar` from a `char`, panicking if it's not ASCII. /// /// This function is intended for creating `AsciiChar` values from /// hardcoded known-good character literals such as `'K'`, `'-'` or `'\0'`, /// and for use in `const` contexts. /// Use [`from_ascii()`](#method.from_ascii) instead when you're not /// certain the character is ASCII. /// /// # Examples /// /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('@'), AsciiChar::At); /// assert_eq!(AsciiChar::new('C').as_char(), 'C'); /// ``` /// /// In a constant: /// ``` /// # use ascii::AsciiChar; /// const SPLIT_ON: AsciiChar = AsciiChar::new(','); /// ``` /// /// This will not compile: /// ```compile_fail /// # use ascii::AsciiChar; /// const BAD: AsciiChar = AsciiChar::new('Ø'); /// ``` /// /// # Panics /// /// This function will panic if passed a non-ASCII character. /// /// The panic message might not be the most descriptive due to the /// current limitations of `const fn`. #[must_use] pub const fn new(ch: char) -> AsciiChar { // It's restricted to this function, and without it // we'd need to specify `AsciiChar::` or `Self::` 128 times. #[allow(clippy::enum_glob_use)] use AsciiChar::*; #[rustfmt::skip] const ALL: [AsciiChar; 128] = [ Null, SOH, SOX, ETX, EOT, ENQ, ACK, Bell, BackSpace, Tab, LineFeed, VT, FF, CarriageReturn, SI, SO, DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EM, SUB, ESC, FS, GS, RS, US, Space, Exclamation, Quotation, Hash, Dollar, Percent, Ampersand, Apostrophe, ParenOpen, ParenClose, Asterisk, Plus, Comma, Minus, Dot, Slash, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, Colon, Semicolon, LessThan, Equal, GreaterThan, Question, At, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, BracketOpen, BackSlash, BracketClose, Caret, UnderScore, Grave, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, CurlyBraceOpen, VerticalBar, CurlyBraceClose, Tilde, DEL, ]; // We want to slice here and detect `const_err` from rustc if the slice is invalid #[allow(clippy::indexing_slicing)] ALL[ch as usize] } /// Constructs an ASCII character from a `u8`, `char` or other character /// type without any checks. /// /// # Safety /// /// This function is very unsafe as it can create invalid enum /// discriminants, which instantly creates undefined behavior. /// (`let _ = AsciiChar::from_ascii_unchecked(200);` alone is UB). /// /// The undefined behavior is not just theoretical either: /// For example, `[0; 128][AsciiChar::from_ascii_unchecked(255) as u8 as usize] = 0` /// might not panic, creating a buffer overflow, /// and `Some(AsciiChar::from_ascii_unchecked(128))` might be `None`. #[inline] #[must_use] pub unsafe fn from_ascii_unchecked(ch: u8) -> Self { // SAFETY: Caller guarantees `ch` is within bounds of ascii. unsafe { ch.to_ascii_char_unchecked() } } /// Converts an ASCII character into a `u8`. #[inline] #[must_use] pub const fn as_byte(self) -> u8 { self as u8 } /// Converts an ASCII character into a `char`. #[inline] #[must_use] pub const fn as_char(self) -> char { self as u8 as char } // the following methods are like ctype, and the implementation is inspired by musl. // The ascii_ methods take self by reference for maximum compatibility // with the corresponding methods on u8 and char. // It is bad for both usability and performance, but marking those // that doesn't have a non-ascii sibling #[inline] should // make the compiler optimize away the indirection. /// Turns uppercase into lowercase, but also modifies '@' and '<'..='_' #[must_use] const fn to_not_upper(self) -> u8 { self as u8 | 0b010_0000 } /// Check if the character is a letter (a-z, A-Z) #[inline] #[must_use] pub const fn is_alphabetic(self) -> bool { (self.to_not_upper() >= b'a') & (self.to_not_upper() <= b'z') } /// Check if the character is a letter (a-z, A-Z). /// /// This method is identical to [`is_alphabetic()`](#method.is_alphabetic) #[inline] #[must_use] pub const fn is_ascii_alphabetic(&self) -> bool { self.is_alphabetic() } /// Check if the character is a digit in the given radix. /// /// If the radix is always 10 or 16, /// [`is_ascii_digit()`](#method.is_ascii_digit) and /// [`is_ascii_hexdigit()`](#method.is_ascii_hexdigit()) will be faster. /// /// # Panics /// /// Radixes greater than 36 are not supported and will result in a panic. #[must_use] pub fn is_digit(self, radix: u32) -> bool { match (self as u8, radix) { (b'0'..=b'9', 0..=36) => u32::from(self as u8 - b'0') < radix, (b'a'..=b'z', 11..=36) => u32::from(self as u8 - b'a') < radix - 10, (b'A'..=b'Z', 11..=36) => u32::from(self as u8 - b'A') < radix - 10, (_, 0..=36) => false, (_, _) => panic!("radixes greater than 36 are not supported"), } } /// Check if the character is a number (0-9) /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('0').is_ascii_digit(), true); /// assert_eq!(AsciiChar::new('9').is_ascii_digit(), true); /// assert_eq!(AsciiChar::new('a').is_ascii_digit(), false); /// assert_eq!(AsciiChar::new('A').is_ascii_digit(), false); /// assert_eq!(AsciiChar::new('/').is_ascii_digit(), false); /// ``` #[inline] #[must_use] pub const fn is_ascii_digit(&self) -> bool { (*self as u8 >= b'0') & (*self as u8 <= b'9') } /// Check if the character is a letter or number #[inline] #[must_use] pub const fn is_alphanumeric(self) -> bool { self.is_alphabetic() | self.is_ascii_digit() } /// Check if the character is a letter or number /// /// This method is identical to [`is_alphanumeric()`](#method.is_alphanumeric) #[inline] #[must_use] pub const fn is_ascii_alphanumeric(&self) -> bool { self.is_alphanumeric() } /// Check if the character is a space or horizontal tab /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert!(AsciiChar::Space.is_ascii_blank()); /// assert!(AsciiChar::Tab.is_ascii_blank()); /// assert!(!AsciiChar::VT.is_ascii_blank()); /// assert!(!AsciiChar::LineFeed.is_ascii_blank()); /// assert!(!AsciiChar::CarriageReturn.is_ascii_blank()); /// assert!(!AsciiChar::FF.is_ascii_blank()); /// ``` #[inline] #[must_use] pub const fn is_ascii_blank(&self) -> bool { (*self as u8 == b' ') | (*self as u8 == b'\t') } /// Check if the character one of ' ', '\t', '\n', '\r', /// '\0xb' (vertical tab) or '\0xc' (form feed). #[inline] #[must_use] pub const fn is_whitespace(self) -> bool { let b = self as u8; self.is_ascii_blank() | (b == b'\n') | (b == b'\r') | (b == 0x0b) | (b == 0x0c) } /// Check if the character is a ' ', '\t', '\n', '\r' or '\0xc' (form feed). /// /// This method is NOT identical to `is_whitespace()`. #[inline] #[must_use] pub const fn is_ascii_whitespace(&self) -> bool { self.is_ascii_blank() | (*self as u8 == b'\n') | (*self as u8 == b'\r') | (*self as u8 == 0x0c/*form feed*/) } /// Check if the character is a control character /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('\0').is_ascii_control(), true); /// assert_eq!(AsciiChar::new('n').is_ascii_control(), false); /// assert_eq!(AsciiChar::new(' ').is_ascii_control(), false); /// assert_eq!(AsciiChar::new('\n').is_ascii_control(), true); /// assert_eq!(AsciiChar::new('\t').is_ascii_control(), true); /// assert_eq!(AsciiChar::EOT.is_ascii_control(), true); /// ``` #[inline] #[must_use] pub const fn is_ascii_control(&self) -> bool { ((*self as u8) < b' ') | (*self as u8 == 127) } /// Checks if the character is printable (except space) /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('n').is_ascii_graphic(), true); /// assert_eq!(AsciiChar::new(' ').is_ascii_graphic(), false); /// assert_eq!(AsciiChar::new('\n').is_ascii_graphic(), false); /// ``` #[inline] #[must_use] pub const fn is_ascii_graphic(&self) -> bool { self.as_byte().wrapping_sub(b' ' + 1) < 0x5E } /// Checks if the character is printable (including space) /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('n').is_ascii_printable(), true); /// assert_eq!(AsciiChar::new(' ').is_ascii_printable(), true); /// assert_eq!(AsciiChar::new('\n').is_ascii_printable(), false); /// ``` #[inline] #[must_use] pub const fn is_ascii_printable(&self) -> bool { self.as_byte().wrapping_sub(b' ') < 0x5F } /// Checks if the character is alphabetic and lowercase (a-z). /// /// # Examples /// ``` /// use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('a').is_lowercase(), true); /// assert_eq!(AsciiChar::new('A').is_lowercase(), false); /// assert_eq!(AsciiChar::new('@').is_lowercase(), false); /// ``` #[inline] #[must_use] pub const fn is_lowercase(self) -> bool { self.as_byte().wrapping_sub(b'a') < 26 } /// Checks if the character is alphabetic and lowercase (a-z). /// /// This method is identical to [`is_lowercase()`](#method.is_lowercase) #[inline] #[must_use] pub const fn is_ascii_lowercase(&self) -> bool { self.is_lowercase() } /// Checks if the character is alphabetic and uppercase (A-Z). /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('A').is_uppercase(), true); /// assert_eq!(AsciiChar::new('a').is_uppercase(), false); /// assert_eq!(AsciiChar::new('@').is_uppercase(), false); /// ``` #[inline] #[must_use] pub const fn is_uppercase(self) -> bool { self.as_byte().wrapping_sub(b'A') < 26 } /// Checks if the character is alphabetic and uppercase (A-Z). /// /// This method is identical to [`is_uppercase()`](#method.is_uppercase) #[inline] #[must_use] pub const fn is_ascii_uppercase(&self) -> bool { self.is_uppercase() } /// Checks if the character is punctuation /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('n').is_ascii_punctuation(), false); /// assert_eq!(AsciiChar::new(' ').is_ascii_punctuation(), false); /// assert_eq!(AsciiChar::new('_').is_ascii_punctuation(), true); /// assert_eq!(AsciiChar::new('~').is_ascii_punctuation(), true); /// ``` #[inline] #[must_use] pub const fn is_ascii_punctuation(&self) -> bool { self.is_ascii_graphic() & !self.is_alphanumeric() } /// Checks if the character is a valid hex digit /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('5').is_ascii_hexdigit(), true); /// assert_eq!(AsciiChar::new('a').is_ascii_hexdigit(), true); /// assert_eq!(AsciiChar::new('F').is_ascii_hexdigit(), true); /// assert_eq!(AsciiChar::new('G').is_ascii_hexdigit(), false); /// assert_eq!(AsciiChar::new(' ').is_ascii_hexdigit(), false); /// ``` #[inline] #[must_use] pub const fn is_ascii_hexdigit(&self) -> bool { self.is_ascii_digit() | ((*self as u8 | 0x20_u8).wrapping_sub(b'a') < 6) } /// Unicode has printable versions of the ASCII control codes, like '␛'. /// /// This function is identical with `.as_char()` /// for all values `.is_printable()` returns true for, /// but replaces the control codes with those unicodes printable versions. /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('\0').as_printable_char(), '␀'); /// assert_eq!(AsciiChar::new('\n').as_printable_char(), '␊'); /// assert_eq!(AsciiChar::new(' ').as_printable_char(), ' '); /// assert_eq!(AsciiChar::new('p').as_printable_char(), 'p'); /// ``` #[must_use] pub fn as_printable_char(self) -> char { match self as u8 { // Non printable characters // SAFETY: From codepoint 0x2400 ('␀') to 0x241f (`␟`), there are characters representing // the unprintable characters from 0x0 to 0x1f, ordered correctly. // As `b` is guaranteed to be within 0x0 to 0x1f, the conversion represents a // valid character. b @ 0x0..=0x1f => unsafe { char::from_u32_unchecked(u32::from('␀') + u32::from(b)) }, // 0x7f (delete) has it's own character at codepoint 0x2420, not 0x247f, so it is special // cased to return it's character 0x7f => '␡', // All other characters are printable, and per function contract use `Self::as_char` _ => self.as_char(), } } /// Replaces letters `a` to `z` with `A` to `Z` pub fn make_ascii_uppercase(&mut self) { *self = self.to_ascii_uppercase(); } /// Replaces letters `A` to `Z` with `a` to `z` pub fn make_ascii_lowercase(&mut self) { *self = self.to_ascii_lowercase(); } /// Maps letters a-z to A-Z and returns any other character unchanged. /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('u').to_ascii_uppercase().as_char(), 'U'); /// assert_eq!(AsciiChar::new('U').to_ascii_uppercase().as_char(), 'U'); /// assert_eq!(AsciiChar::new('2').to_ascii_uppercase().as_char(), '2'); /// assert_eq!(AsciiChar::new('=').to_ascii_uppercase().as_char(), '='); /// assert_eq!(AsciiChar::new('[').to_ascii_uppercase().as_char(), '['); /// ``` #[inline] #[must_use] #[allow(clippy::indexing_slicing)] // We're sure it'll either access one or the other, as `bool` is either `0` or `1` pub const fn to_ascii_uppercase(&self) -> Self { [*self, AsciiChar::new((*self as u8 & 0b101_1111) as char)][self.is_lowercase() as usize] } /// Maps letters A-Z to a-z and returns any other character unchanged. /// /// # Examples /// ``` /// # use ascii::AsciiChar; /// assert_eq!(AsciiChar::new('U').to_ascii_lowercase().as_char(), 'u'); /// assert_eq!(AsciiChar::new('u').to_ascii_lowercase().as_char(), 'u'); /// assert_eq!(AsciiChar::new('2').to_ascii_lowercase().as_char(), '2'); /// assert_eq!(AsciiChar::new('^').to_ascii_lowercase().as_char(), '^'); /// assert_eq!(AsciiChar::new('\x7f').to_ascii_lowercase().as_char(), '\x7f'); /// ``` #[inline] #[must_use] #[allow(clippy::indexing_slicing)] // We're sure it'll either access one or the other, as `bool` is either `0` or `1` pub const fn to_ascii_lowercase(&self) -> Self { [*self, AsciiChar::new(self.to_not_upper() as char)][self.is_uppercase() as usize] } /// Compares two characters case-insensitively. #[inline] #[must_use] pub const fn eq_ignore_ascii_case(&self, other: &Self) -> bool { (self.as_byte() == other.as_byte()) | (self.is_alphabetic() & (self.to_not_upper() == other.to_not_upper())) } } impl fmt::Display for AsciiChar { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.as_char().fmt(f) } } impl fmt::Debug for AsciiChar { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.as_char().fmt(f) } } impl Default for AsciiChar { fn default() -> AsciiChar { AsciiChar::Null } } macro_rules! impl_into_partial_eq_ord { ($wider:ty, $to_wider:expr) => { impl From for $wider { #[inline] fn from(a: AsciiChar) -> $wider { $to_wider(a) } } impl PartialEq<$wider> for AsciiChar { #[inline] fn eq(&self, rhs: &$wider) -> bool { $to_wider(*self) == *rhs } } impl PartialEq for $wider { #[inline] fn eq(&self, rhs: &AsciiChar) -> bool { *self == $to_wider(*rhs) } } impl PartialOrd<$wider> for AsciiChar { #[inline] fn partial_cmp(&self, rhs: &$wider) -> Option { $to_wider(*self).partial_cmp(rhs) } } impl PartialOrd for $wider { #[inline] fn partial_cmp(&self, rhs: &AsciiChar) -> Option { self.partial_cmp(&$to_wider(*rhs)) } } }; } impl_into_partial_eq_ord! {u8, AsciiChar::as_byte} impl_into_partial_eq_ord! {char, AsciiChar::as_char} /// Error returned by `ToAsciiChar`. #[derive(Clone, Copy, PartialEq, Eq)] pub struct ToAsciiCharError(()); const ERRORMSG_CHAR: &str = "not an ASCII character"; #[cfg(not(feature = "std"))] impl ToAsciiCharError { /// Returns a description for this error, like `std::error::Error::description`. #[inline] #[must_use] #[allow(clippy::unused_self)] pub const fn description(&self) -> &'static str { ERRORMSG_CHAR } } impl fmt::Debug for ToAsciiCharError { fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { write!(fmtr, "{}", ERRORMSG_CHAR) } } impl fmt::Display for ToAsciiCharError { fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { write!(fmtr, "{}", ERRORMSG_CHAR) } } #[cfg(feature = "std")] impl Error for ToAsciiCharError { #[inline] fn description(&self) -> &'static str { ERRORMSG_CHAR } } /// Convert `char`, `u8` and other character types to `AsciiChar`. pub trait ToAsciiChar { /// Convert to `AsciiChar`. /// /// # Errors /// If `self` is outside the valid ascii range, this returns `Err` fn to_ascii_char(self) -> Result; /// Convert to `AsciiChar` without checking that it is an ASCII character. /// /// # Safety /// Calling this function with a value outside of the ascii range, `0x0` to `0x7f` inclusive, /// is undefined behavior. // TODO: Make sure this is the contract we want to express in this function. // It is ambigous if numbers such as `0xffffff20_u32` are valid ascii characters, // as this function returns `Ascii::Space` due to the cast to `u8`, even though // `to_ascii_char` returns `Err()`. unsafe fn to_ascii_char_unchecked(self) -> AsciiChar; } impl ToAsciiChar for AsciiChar { #[inline] fn to_ascii_char(self) -> Result { Ok(self) } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { self } } impl ToAsciiChar for u8 { #[inline] fn to_ascii_char(self) -> Result { u32::from(self).to_ascii_char() } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { // SAFETY: Caller guarantees `self` is within bounds of the enum // variants, so this cast successfully produces a valid ascii // variant unsafe { mem::transmute::(self) } } } // Note: Casts to `u8` here does not cause problems, as the negative // range is mapped outside of ascii bounds and we don't mind losing // the sign, as long as negative numbers are mapped outside ascii range. #[allow(clippy::cast_sign_loss)] impl ToAsciiChar for i8 { #[inline] fn to_ascii_char(self) -> Result { u32::from(self as u8).to_ascii_char() } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { // SAFETY: Caller guarantees `self` is within bounds of the enum // variants, so this cast successfully produces a valid ascii // variant unsafe { mem::transmute::(self as u8) } } } impl ToAsciiChar for char { #[inline] fn to_ascii_char(self) -> Result { u32::from(self).to_ascii_char() } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { // SAFETY: Caller guarantees we're within ascii range. unsafe { u32::from(self).to_ascii_char_unchecked() } } } impl ToAsciiChar for u32 { fn to_ascii_char(self) -> Result { match self { // SAFETY: We're within the valid ascii range in this branch. 0x0..=0x7f => Ok(unsafe { self.to_ascii_char_unchecked() }), _ => Err(ToAsciiCharError(())), } } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { // Note: This cast discards the top bytes, this may cause problems, see // the TODO on this method's documentation in the trait. // SAFETY: Caller guarantees we're within ascii range. #[allow(clippy::cast_possible_truncation)] // We want to truncate it unsafe { (self as u8).to_ascii_char_unchecked() } } } impl ToAsciiChar for u16 { fn to_ascii_char(self) -> Result { u32::from(self).to_ascii_char() } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { // Note: This cast discards the top bytes, this may cause problems, see // the TODO on this method's documentation in the trait. // SAFETY: Caller guarantees we're within ascii range. #[allow(clippy::cast_possible_truncation)] // We want to truncate it unsafe { (self as u8).to_ascii_char_unchecked() } } } #[cfg(test)] mod tests { use super::{AsciiChar, ToAsciiChar, ToAsciiCharError}; #[test] fn to_ascii_char() { fn generic(ch: C) -> Result { ch.to_ascii_char() } assert_eq!(generic(AsciiChar::A), Ok(AsciiChar::A)); assert_eq!(generic(b'A'), Ok(AsciiChar::A)); assert_eq!(generic('A'), Ok(AsciiChar::A)); assert!(generic(200_u16).is_err()); assert!(generic('λ').is_err()); } #[test] fn as_byte_and_char() { assert_eq!(AsciiChar::A.as_byte(), b'A'); assert_eq!(AsciiChar::A.as_char(), 'A'); } #[test] fn new_array_is_correct() { for byte in 0..128_u8 { assert_eq!(AsciiChar::new(byte as char).as_byte(), byte); } } #[test] fn is_all() { #![allow(clippy::is_digit_ascii_radix)] // testing it for byte in 0..128_u8 { let ch = byte as char; let ascii = AsciiChar::new(ch); assert_eq!(ascii.is_alphabetic(), ch.is_alphabetic()); assert_eq!(ascii.is_ascii_alphabetic(), ch.is_ascii_alphabetic()); assert_eq!(ascii.is_alphanumeric(), ch.is_alphanumeric()); assert_eq!(ascii.is_ascii_alphanumeric(), ch.is_ascii_alphanumeric()); assert_eq!(ascii.is_digit(8), ch.is_digit(8), "is_digit(8) {:?}", ch); assert_eq!(ascii.is_digit(10), ch.is_digit(10), "is_digit(10) {:?}", ch); assert_eq!(ascii.is_digit(16), ch.is_digit(16), "is_digit(16) {:?}", ch); assert_eq!(ascii.is_digit(36), ch.is_digit(36), "is_digit(36) {:?}", ch); assert_eq!(ascii.is_ascii_digit(), ch.is_ascii_digit()); assert_eq!(ascii.is_ascii_hexdigit(), ch.is_ascii_hexdigit()); assert_eq!(ascii.is_ascii_control(), ch.is_ascii_control()); assert_eq!(ascii.is_ascii_graphic(), ch.is_ascii_graphic()); assert_eq!(ascii.is_ascii_punctuation(), ch.is_ascii_punctuation()); assert_eq!( ascii.is_whitespace(), ch.is_whitespace(), "{:?} ({:#04x})", ch, byte ); assert_eq!( ascii.is_ascii_whitespace(), ch.is_ascii_whitespace(), "{:?} ({:#04x})", ch, byte ); assert_eq!(ascii.is_uppercase(), ch.is_uppercase()); assert_eq!(ascii.is_ascii_uppercase(), ch.is_ascii_uppercase()); assert_eq!(ascii.is_lowercase(), ch.is_lowercase()); assert_eq!(ascii.is_ascii_lowercase(), ch.is_ascii_lowercase()); assert_eq!(ascii.to_ascii_uppercase(), ch.to_ascii_uppercase()); assert_eq!(ascii.to_ascii_lowercase(), ch.to_ascii_lowercase()); } } #[test] fn is_digit_strange_radixes() { assert_eq!(AsciiChar::_0.is_digit(0), '0'.is_digit(0)); assert_eq!(AsciiChar::_0.is_digit(1), '0'.is_digit(1)); assert_eq!(AsciiChar::_5.is_digit(5), '5'.is_digit(5)); assert_eq!(AsciiChar::z.is_digit(35), 'z'.is_digit(35)); } #[test] #[should_panic] fn is_digit_bad_radix() { let _ = AsciiChar::_7.is_digit(37); } #[test] fn cmp_wider() { assert_eq!(AsciiChar::A, 'A'); assert_eq!(b'b', AsciiChar::b); assert!(AsciiChar::a < 'z'); } #[test] fn ascii_case() { assert_eq!(AsciiChar::At.to_ascii_lowercase(), AsciiChar::At); assert_eq!(AsciiChar::At.to_ascii_uppercase(), AsciiChar::At); assert_eq!(AsciiChar::A.to_ascii_lowercase(), AsciiChar::a); assert_eq!(AsciiChar::A.to_ascii_uppercase(), AsciiChar::A); assert_eq!(AsciiChar::a.to_ascii_lowercase(), AsciiChar::a); assert_eq!(AsciiChar::a.to_ascii_uppercase(), AsciiChar::A); let mut mutable = (AsciiChar::A, AsciiChar::a); mutable.0.make_ascii_lowercase(); mutable.1.make_ascii_uppercase(); assert_eq!(mutable.0, AsciiChar::a); assert_eq!(mutable.1, AsciiChar::A); assert!(AsciiChar::LineFeed.eq_ignore_ascii_case(&AsciiChar::LineFeed)); assert!(!AsciiChar::LineFeed.eq_ignore_ascii_case(&AsciiChar::CarriageReturn)); assert!(AsciiChar::z.eq_ignore_ascii_case(&AsciiChar::Z)); assert!(AsciiChar::Z.eq_ignore_ascii_case(&AsciiChar::z)); assert!(AsciiChar::A.eq_ignore_ascii_case(&AsciiChar::a)); assert!(!AsciiChar::K.eq_ignore_ascii_case(&AsciiChar::C)); assert!(!AsciiChar::Z.eq_ignore_ascii_case(&AsciiChar::DEL)); assert!(!AsciiChar::BracketOpen.eq_ignore_ascii_case(&AsciiChar::CurlyBraceOpen)); assert!(!AsciiChar::Grave.eq_ignore_ascii_case(&AsciiChar::At)); assert!(!AsciiChar::Grave.eq_ignore_ascii_case(&AsciiChar::DEL)); } #[test] #[cfg(feature = "std")] fn fmt_ascii() { assert_eq!(format!("{}", AsciiChar::t), "t"); assert_eq!(format!("{:?}", AsciiChar::t), "'t'"); assert_eq!(format!("{}", AsciiChar::LineFeed), "\n"); assert_eq!(format!("{:?}", AsciiChar::LineFeed), "'\\n'"); } } ascii-1.1.0/src/ascii_str.rs000064400000000000000000001471521046102023000140420ustar 00000000000000#[cfg(feature = "alloc")] use alloc::borrow::ToOwned; #[cfg(feature = "alloc")] use alloc::boxed::Box; use core::fmt; use core::ops::{Index, IndexMut}; use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; use core::slice::{self, Iter, IterMut, SliceIndex}; #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "std")] use std::ffi::CStr; use ascii_char::AsciiChar; #[cfg(feature = "alloc")] use ascii_string::AsciiString; /// [`AsciiStr`] represents a byte or string slice that only contains ASCII characters. /// /// It wraps an `[AsciiChar]` and implements many of `str`s methods and traits. /// /// It can be created by a checked conversion from a `str` or `[u8]`, or borrowed from an /// `AsciiString`. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct AsciiStr { slice: [AsciiChar], } impl AsciiStr { /// Converts `&self` to a `&str` slice. #[inline] #[must_use] pub fn as_str(&self) -> &str { // SAFETY: All variants of `AsciiChar` are valid bytes for a `str`. unsafe { &*(self as *const AsciiStr as *const str) } } /// Converts `&self` into a byte slice. #[inline] #[must_use] pub fn as_bytes(&self) -> &[u8] { // SAFETY: All variants of `AsciiChar` are valid `u8`, given they're `repr(u8)`. unsafe { &*(self as *const AsciiStr as *const [u8]) } } /// Returns the entire string as slice of `AsciiChar`s. #[inline] #[must_use] pub const fn as_slice(&self) -> &[AsciiChar] { &self.slice } /// Returns the entire string as mutable slice of `AsciiChar`s. #[inline] #[must_use] pub fn as_mut_slice(&mut self) -> &mut [AsciiChar] { &mut self.slice } /// Returns a raw pointer to the `AsciiStr`'s buffer. /// /// The caller must ensure that the slice outlives the pointer this function returns, or else it /// will end up pointing to garbage. Modifying the `AsciiStr` may cause it's buffer to be /// reallocated, which would also make any pointers to it invalid. #[inline] #[must_use] pub const fn as_ptr(&self) -> *const AsciiChar { self.as_slice().as_ptr() } /// Returns an unsafe mutable pointer to the `AsciiStr`'s buffer. /// /// The caller must ensure that the slice outlives the pointer this function returns, or else it /// will end up pointing to garbage. Modifying the `AsciiStr` may cause it's buffer to be /// reallocated, which would also make any pointers to it invalid. #[inline] #[must_use] pub fn as_mut_ptr(&mut self) -> *mut AsciiChar { self.as_mut_slice().as_mut_ptr() } /// Copies the content of this `AsciiStr` into an owned `AsciiString`. #[cfg(feature = "alloc")] #[must_use] pub fn to_ascii_string(&self) -> AsciiString { AsciiString::from(self.slice.to_vec()) } /// Converts anything that can represent a byte slice into an `AsciiStr`. /// /// # Errors /// If `bytes` contains a non-ascii byte, `Err` will be returned /// /// # Examples /// ``` /// # use ascii::AsciiStr; /// let foo = AsciiStr::from_ascii(b"foo"); /// let err = AsciiStr::from_ascii("Ŋ"); /// assert_eq!(foo.unwrap().as_str(), "foo"); /// assert_eq!(err.unwrap_err().valid_up_to(), 0); /// ``` #[inline] pub fn from_ascii(bytes: &B) -> Result<&AsciiStr, AsAsciiStrError> where B: AsRef<[u8]> + ?Sized, { bytes.as_ref().as_ascii_str() } /// Converts anything that can be represented as a byte slice to an `AsciiStr` without checking /// for non-ASCII characters.. /// /// # Safety /// If any of the bytes in `bytes` do not represent valid ascii characters, calling /// this function is undefined behavior. /// /// # Examples /// ``` /// # use ascii::AsciiStr; /// let foo = unsafe { AsciiStr::from_ascii_unchecked(&b"foo"[..]) }; /// assert_eq!(foo.as_str(), "foo"); /// ``` #[inline] #[must_use] pub unsafe fn from_ascii_unchecked(bytes: &[u8]) -> &AsciiStr { // SAFETY: Caller guarantees all bytes in `bytes` are valid // ascii characters. unsafe { bytes.as_ascii_str_unchecked() } } /// Returns the number of characters / bytes in this ASCII sequence. /// /// # Examples /// ``` /// # use ascii::AsciiStr; /// let s = AsciiStr::from_ascii("foo").unwrap(); /// assert_eq!(s.len(), 3); /// ``` #[inline] #[must_use] pub const fn len(&self) -> usize { self.slice.len() } /// Returns true if the ASCII slice contains zero bytes. /// /// # Examples /// ``` /// # use ascii::AsciiStr; /// let mut empty = AsciiStr::from_ascii("").unwrap(); /// let mut full = AsciiStr::from_ascii("foo").unwrap(); /// assert!(empty.is_empty()); /// assert!(!full.is_empty()); /// ``` #[inline] #[must_use] pub const fn is_empty(&self) -> bool { self.len() == 0 } /// Returns an iterator over the characters of the `AsciiStr`. #[inline] #[must_use] pub fn chars(&self) -> Chars { Chars(self.slice.iter()) } /// Returns an iterator over the characters of the `AsciiStr` which allows you to modify the /// value of each `AsciiChar`. #[inline] #[must_use] pub fn chars_mut(&mut self) -> CharsMut { CharsMut(self.slice.iter_mut()) } /// Returns an iterator over parts of the `AsciiStr` separated by a character. /// /// # Examples /// ``` /// # use ascii::{AsciiStr, AsciiChar}; /// let words = AsciiStr::from_ascii("apple banana lemon").unwrap() /// .split(AsciiChar::Space) /// .map(|a| a.as_str()) /// .collect::>(); /// assert_eq!(words, ["apple", "banana", "lemon"]); /// ``` #[must_use] pub fn split(&self, on: AsciiChar) -> impl DoubleEndedIterator { Split { on, ended: false, chars: self.chars(), } } /// Returns an iterator over the lines of the `AsciiStr`, which are themselves `AsciiStr`s. /// /// Lines are ended with either `LineFeed` (`\n`), or `CarriageReturn` then `LineFeed` (`\r\n`). /// /// The final line ending is optional. #[inline] #[must_use] pub fn lines(&self) -> impl DoubleEndedIterator { Lines { string: self } } /// Returns an ASCII string slice with leading and trailing whitespace removed. /// /// # Examples /// ``` /// # use ascii::AsciiStr; /// let example = AsciiStr::from_ascii(" \twhite \tspace \t").unwrap(); /// assert_eq!("white \tspace", example.trim()); /// ``` #[must_use] pub fn trim(&self) -> &Self { self.trim_start().trim_end() } /// Returns an ASCII string slice with leading whitespace removed. /// /// # Examples /// ``` /// # use ascii::AsciiStr; /// let example = AsciiStr::from_ascii(" \twhite \tspace \t").unwrap(); /// assert_eq!("white \tspace \t", example.trim_start()); /// ``` #[must_use] pub fn trim_start(&self) -> &Self { let whitespace_len = self .chars() .position(|ch| !ch.is_whitespace()) .unwrap_or_else(|| self.len()); // SAFETY: `whitespace_len` is `0..=len`, which is at most `len`, which is a valid empty slice. unsafe { self.as_slice().get_unchecked(whitespace_len..).into() } } /// Returns an ASCII string slice with trailing whitespace removed. /// /// # Examples /// ``` /// # use ascii::AsciiStr; /// let example = AsciiStr::from_ascii(" \twhite \tspace \t").unwrap(); /// assert_eq!(" \twhite \tspace", example.trim_end()); /// ``` #[must_use] pub fn trim_end(&self) -> &Self { // Number of whitespace characters counting from the end let whitespace_len = self .chars() .rev() .position(|ch| !ch.is_whitespace()) .unwrap_or_else(|| self.len()); // SAFETY: `whitespace_len` is `0..=len`, which is at most `len`, which is a valid empty slice, and at least `0`, which is the whole slice. unsafe { self.as_slice() .get_unchecked(..self.len() - whitespace_len) .into() } } /// Compares two strings case-insensitively. #[must_use] pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { self.len() == other.len() && self .chars() .zip(other.chars()) .all(|(ch, other_ch)| ch.eq_ignore_ascii_case(&other_ch)) } /// Replaces lowercase letters with their uppercase equivalent. pub fn make_ascii_uppercase(&mut self) { for ch in self.chars_mut() { *ch = ch.to_ascii_uppercase(); } } /// Replaces uppercase letters with their lowercase equivalent. pub fn make_ascii_lowercase(&mut self) { for ch in self.chars_mut() { *ch = ch.to_ascii_lowercase(); } } /// Returns a copy of this string where letters 'a' to 'z' are mapped to 'A' to 'Z'. #[cfg(feature = "alloc")] #[must_use] pub fn to_ascii_uppercase(&self) -> AsciiString { let mut ascii_string = self.to_ascii_string(); ascii_string.make_ascii_uppercase(); ascii_string } /// Returns a copy of this string where letters 'A' to 'Z' are mapped to 'a' to 'z'. #[cfg(feature = "alloc")] #[must_use] pub fn to_ascii_lowercase(&self) -> AsciiString { let mut ascii_string = self.to_ascii_string(); ascii_string.make_ascii_lowercase(); ascii_string } /// Returns the first character if the string is not empty. #[inline] #[must_use] pub fn first(&self) -> Option { self.slice.first().copied() } /// Returns the last character if the string is not empty. #[inline] #[must_use] pub fn last(&self) -> Option { self.slice.last().copied() } /// Converts a [`Box`] into a [`AsciiString`] without copying or allocating. #[cfg(feature = "alloc")] #[inline] #[must_use] pub fn into_ascii_string(self: Box) -> AsciiString { let slice = Box::<[AsciiChar]>::from(self); AsciiString::from(slice.into_vec()) } } macro_rules! impl_partial_eq { ($wider: ty) => { impl PartialEq<$wider> for AsciiStr { #[inline] fn eq(&self, other: &$wider) -> bool { >::as_ref(self) == other } } impl PartialEq for $wider { #[inline] fn eq(&self, other: &AsciiStr) -> bool { self == >::as_ref(other) } } }; } impl_partial_eq! {str} impl_partial_eq! {[u8]} impl_partial_eq! {[AsciiChar]} #[cfg(feature = "alloc")] impl ToOwned for AsciiStr { type Owned = AsciiString; #[inline] fn to_owned(&self) -> AsciiString { self.to_ascii_string() } } impl AsRef<[u8]> for AsciiStr { #[inline] fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl AsRef for AsciiStr { #[inline] fn as_ref(&self) -> &str { self.as_str() } } impl AsRef<[AsciiChar]> for AsciiStr { #[inline] fn as_ref(&self) -> &[AsciiChar] { &self.slice } } impl AsMut<[AsciiChar]> for AsciiStr { #[inline] fn as_mut(&mut self) -> &mut [AsciiChar] { &mut self.slice } } impl Default for &'static AsciiStr { #[inline] fn default() -> &'static AsciiStr { From::from(&[] as &[AsciiChar]) } } impl<'a> From<&'a [AsciiChar]> for &'a AsciiStr { #[inline] fn from(slice: &[AsciiChar]) -> &AsciiStr { let ptr = slice as *const [AsciiChar] as *const AsciiStr; unsafe { &*ptr } } } impl<'a> From<&'a mut [AsciiChar]> for &'a mut AsciiStr { #[inline] fn from(slice: &mut [AsciiChar]) -> &mut AsciiStr { let ptr = slice as *mut [AsciiChar] as *mut AsciiStr; unsafe { &mut *ptr } } } #[cfg(feature = "alloc")] impl From> for Box { #[inline] fn from(owned: Box<[AsciiChar]>) -> Box { let ptr = Box::into_raw(owned) as *mut AsciiStr; unsafe { Box::from_raw(ptr) } } } impl AsRef for AsciiStr { #[inline] fn as_ref(&self) -> &AsciiStr { self } } impl AsMut for AsciiStr { #[inline] fn as_mut(&mut self) -> &mut AsciiStr { self } } impl AsRef for [AsciiChar] { #[inline] fn as_ref(&self) -> &AsciiStr { self.into() } } impl AsMut for [AsciiChar] { #[inline] fn as_mut(&mut self) -> &mut AsciiStr { self.into() } } impl<'a> From<&'a AsciiStr> for &'a [AsciiChar] { #[inline] fn from(astr: &AsciiStr) -> &[AsciiChar] { &astr.slice } } impl<'a> From<&'a mut AsciiStr> for &'a mut [AsciiChar] { #[inline] fn from(astr: &mut AsciiStr) -> &mut [AsciiChar] { &mut astr.slice } } impl<'a> From<&'a AsciiStr> for &'a [u8] { #[inline] fn from(astr: &AsciiStr) -> &[u8] { astr.as_bytes() } } impl<'a> From<&'a AsciiStr> for &'a str { #[inline] fn from(astr: &AsciiStr) -> &str { astr.as_str() } } macro_rules! widen_box { ($wider: ty) => { #[cfg(feature = "alloc")] impl From> for Box<$wider> { #[inline] fn from(owned: Box) -> Box<$wider> { let ptr = Box::into_raw(owned) as *mut $wider; unsafe { Box::from_raw(ptr) } } } }; } widen_box! {[AsciiChar]} widen_box! {[u8]} widen_box! {str} // allows &AsciiChar to be used by generic AsciiString Extend and FromIterator impl AsRef for AsciiChar { fn as_ref(&self) -> &AsciiStr { slice::from_ref(self).into() } } impl fmt::Display for AsciiStr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } impl fmt::Debug for AsciiStr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self.as_str(), f) } } macro_rules! impl_index { ($idx:ty) => { #[allow(clippy::indexing_slicing)] // In `Index`, if it's out of bounds, panic is the default impl Index<$idx> for AsciiStr { type Output = AsciiStr; #[inline] fn index(&self, index: $idx) -> &AsciiStr { self.slice[index].as_ref() } } #[allow(clippy::indexing_slicing)] // In `IndexMut`, if it's out of bounds, panic is the default impl IndexMut<$idx> for AsciiStr { #[inline] fn index_mut(&mut self, index: $idx) -> &mut AsciiStr { self.slice[index].as_mut() } } }; } impl_index! { Range } impl_index! { RangeTo } impl_index! { RangeFrom } impl_index! { RangeFull } impl_index! { RangeInclusive } impl_index! { RangeToInclusive } #[allow(clippy::indexing_slicing)] // In `Index`, if it's out of bounds, panic is the default impl Index for AsciiStr { type Output = AsciiChar; #[inline] fn index(&self, index: usize) -> &AsciiChar { &self.slice[index] } } #[allow(clippy::indexing_slicing)] // In `IndexMut`, if it's out of bounds, panic is the default impl IndexMut for AsciiStr { #[inline] fn index_mut(&mut self, index: usize) -> &mut AsciiChar { &mut self.slice[index] } } /// Produces references for compatibility with `[u8]`. /// /// (`str` doesn't implement `IntoIterator` for its references, /// so there is no compatibility to lose.) impl<'a> IntoIterator for &'a AsciiStr { type Item = &'a AsciiChar; type IntoIter = CharsRef<'a>; #[inline] fn into_iter(self) -> Self::IntoIter { CharsRef(self.as_slice().iter()) } } impl<'a> IntoIterator for &'a mut AsciiStr { type Item = &'a mut AsciiChar; type IntoIter = CharsMut<'a>; #[inline] fn into_iter(self) -> Self::IntoIter { self.chars_mut() } } /// A copying iterator over the characters of an `AsciiStr`. #[derive(Clone, Debug)] pub struct Chars<'a>(Iter<'a, AsciiChar>); impl<'a> Chars<'a> { /// Returns the ascii string slice with the remaining characters. #[must_use] pub fn as_str(&self) -> &'a AsciiStr { self.0.as_slice().into() } } impl<'a> Iterator for Chars<'a> { type Item = AsciiChar; #[inline] fn next(&mut self) -> Option { self.0.next().copied() } fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } } impl<'a> DoubleEndedIterator for Chars<'a> { #[inline] fn next_back(&mut self) -> Option { self.0.next_back().copied() } } impl<'a> ExactSizeIterator for Chars<'a> { fn len(&self) -> usize { self.0.len() } } /// A mutable iterator over the characters of an `AsciiStr`. #[derive(Debug)] pub struct CharsMut<'a>(IterMut<'a, AsciiChar>); impl<'a> CharsMut<'a> { /// Returns the ascii string slice with the remaining characters. #[must_use] pub fn into_str(self) -> &'a mut AsciiStr { self.0.into_slice().into() } } impl<'a> Iterator for CharsMut<'a> { type Item = &'a mut AsciiChar; #[inline] fn next(&mut self) -> Option<&'a mut AsciiChar> { self.0.next() } fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } } impl<'a> DoubleEndedIterator for CharsMut<'a> { #[inline] fn next_back(&mut self) -> Option<&'a mut AsciiChar> { self.0.next_back() } } impl<'a> ExactSizeIterator for CharsMut<'a> { fn len(&self) -> usize { self.0.len() } } /// An immutable iterator over the characters of an `AsciiStr`. #[derive(Clone, Debug)] pub struct CharsRef<'a>(Iter<'a, AsciiChar>); impl<'a> CharsRef<'a> { /// Returns the ascii string slice with the remaining characters. #[must_use] pub fn as_str(&self) -> &'a AsciiStr { self.0.as_slice().into() } } impl<'a> Iterator for CharsRef<'a> { type Item = &'a AsciiChar; #[inline] fn next(&mut self) -> Option<&'a AsciiChar> { self.0.next() } fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } } impl<'a> DoubleEndedIterator for CharsRef<'a> { #[inline] fn next_back(&mut self) -> Option<&'a AsciiChar> { self.0.next_back() } } /// An iterator over parts of an `AsciiStr` separated by an `AsciiChar`. /// /// This type is created by [`AsciiChar::split()`](struct.AsciiChar.html#method.split). #[derive(Clone, Debug)] struct Split<'a> { on: AsciiChar, ended: bool, chars: Chars<'a>, } impl<'a> Iterator for Split<'a> { type Item = &'a AsciiStr; fn next(&mut self) -> Option<&'a AsciiStr> { if !self.ended { let start: &AsciiStr = self.chars.as_str(); let split_on = self.on; if let Some(at) = self.chars.position(|ch| ch == split_on) { // SAFETY: `at` is guaranteed to be in bounds, as `position` returns `Ok(0..len)`. Some(unsafe { start.as_slice().get_unchecked(..at).into() }) } else { self.ended = true; Some(start) } } else { None } } } impl<'a> DoubleEndedIterator for Split<'a> { fn next_back(&mut self) -> Option<&'a AsciiStr> { if !self.ended { let start: &AsciiStr = self.chars.as_str(); let split_on = self.on; if let Some(at) = self.chars.rposition(|ch| ch == split_on) { // SAFETY: `at` is guaranteed to be in bounds, as `rposition` returns `Ok(0..len)`, and slices `1..`, `2..`, etc... until `len..` inclusive, are valid. Some(unsafe { start.as_slice().get_unchecked(at + 1..).into() }) } else { self.ended = true; Some(start) } } else { None } } } /// An iterator over the lines of the internal character array. #[derive(Clone, Debug)] struct Lines<'a> { string: &'a AsciiStr, } impl<'a> Iterator for Lines<'a> { type Item = &'a AsciiStr; fn next(&mut self) -> Option<&'a AsciiStr> { if let Some(idx) = self .string .chars() .position(|chr| chr == AsciiChar::LineFeed) { // SAFETY: `idx` is guaranteed to be `1..len`, as we get it from `position` as `0..len` and make sure it's not `0`. let line = if idx > 0 && *unsafe { self.string.as_slice().get_unchecked(idx - 1) } == AsciiChar::CarriageReturn { // SAFETY: As per above, `idx` is guaranteed to be `1..len` unsafe { self.string.as_slice().get_unchecked(..idx - 1).into() } } else { // SAFETY: As per above, `idx` is guaranteed to be `0..len` unsafe { self.string.as_slice().get_unchecked(..idx).into() } }; // SAFETY: As per above, `idx` is guaranteed to be `0..len`, so at the extreme, slicing `len..` is a valid empty slice. self.string = unsafe { self.string.as_slice().get_unchecked(idx + 1..).into() }; Some(line) } else if self.string.is_empty() { None } else { let line = self.string; // SAFETY: An empty string is a valid string. self.string = unsafe { AsciiStr::from_ascii_unchecked(b"") }; Some(line) } } } impl<'a> DoubleEndedIterator for Lines<'a> { fn next_back(&mut self) -> Option<&'a AsciiStr> { if self.string.is_empty() { return None; } // If we end with `LF` / `CR/LF`, remove them if let Some(AsciiChar::LineFeed) = self.string.last() { // SAFETY: `last()` returned `Some`, so our len is at least 1. self.string = unsafe { self.string .as_slice() .get_unchecked(..self.string.len() - 1) .into() }; if let Some(AsciiChar::CarriageReturn) = self.string.last() { // SAFETY: `last()` returned `Some`, so our len is at least 1. self.string = unsafe { self.string .as_slice() .get_unchecked(..self.string.len() - 1) .into() }; } } // Get the position of the first `LF` from the end. let lf_rev_pos = self .string .chars() .rev() .position(|ch| ch == AsciiChar::LineFeed) .unwrap_or_else(|| self.string.len()); // SAFETY: `lf_rev_pos` will be in range `0..=len`, so `len - lf_rev_pos` // will be within `0..=len`, making it correct as a start and end // point for the strings. let line = unsafe { self.string .as_slice() .get_unchecked(self.string.len() - lf_rev_pos..) .into() }; self.string = unsafe { self.string .as_slice() .get_unchecked(..self.string.len() - lf_rev_pos) .into() }; Some(line) } } /// Error that is returned when a sequence of `u8` are not all ASCII. /// /// Is used by `As[Mut]AsciiStr` and the `from_ascii` method on `AsciiStr` and `AsciiString`. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct AsAsciiStrError(usize); const ERRORMSG_STR: &str = "one or more bytes are not ASCII"; impl AsAsciiStrError { /// Returns the index of the first non-ASCII byte. /// /// It is the maximum index such that `from_ascii(input[..index])` would return `Ok(_)`. #[inline] #[must_use] pub const fn valid_up_to(self) -> usize { self.0 } #[cfg(not(feature = "std"))] /// Returns a description for this error, like `std::error::Error::description`. #[inline] #[must_use] #[allow(clippy::unused_self)] pub const fn description(&self) -> &'static str { ERRORMSG_STR } } impl fmt::Display for AsAsciiStrError { fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { write!(fmtr, "the byte at index {} is not ASCII", self.0) } } #[cfg(feature = "std")] impl Error for AsAsciiStrError { #[inline] fn description(&self) -> &'static str { ERRORMSG_STR } } /// Convert slices of bytes or [`AsciiChar`] to [`AsciiStr`]. // Could nearly replace this trait with SliceIndex, but its methods isn't even // on a path for stabilization. pub trait AsAsciiStr { /// Used to constrain `SliceIndex` #[doc(hidden)] type Inner; /// Convert a subslice to an ASCII slice. /// /// # Errors /// Returns `Err` if the range is out of bounds or if not all bytes in the /// slice are ASCII. The value in the error will be the index of the first /// non-ASCII byte or the end of the slice. /// /// # Examples /// ``` /// use ascii::AsAsciiStr; /// assert!("'zoä'".slice_ascii(..3).is_ok()); /// assert!("'zoä'".slice_ascii(0..4).is_err()); /// assert!("'zoä'".slice_ascii(5..=5).is_ok()); /// assert!("'zoä'".slice_ascii(4..).is_err()); /// assert!(b"\r\n".slice_ascii(..).is_ok()); /// ``` fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> where R: SliceIndex<[Self::Inner], Output = [Self::Inner]>; /// Convert to an ASCII slice. /// /// # Errors /// Returns `Err` if not all bytes are valid ascii values. /// /// # Example /// ``` /// use ascii::{AsAsciiStr, AsciiChar}; /// assert!("ASCII".as_ascii_str().is_ok()); /// assert!(b"\r\n".as_ascii_str().is_ok()); /// assert!("'zoä'".as_ascii_str().is_err()); /// assert!(b"\xff".as_ascii_str().is_err()); /// assert!([AsciiChar::C][..].as_ascii_str().is_ok()); // infallible /// ``` fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { self.slice_ascii(..) } /// Get a single ASCII character from the slice. /// /// Returns `None` if the index is out of bounds or the byte is not ASCII. /// /// # Examples /// ``` /// use ascii::{AsAsciiStr, AsciiChar}; /// assert_eq!("'zoä'".get_ascii(4), None); /// assert_eq!("'zoä'".get_ascii(5), Some(AsciiChar::Apostrophe)); /// assert_eq!("'zoä'".get_ascii(6), None); /// ``` fn get_ascii(&self, index: usize) -> Option { self.slice_ascii(index..=index) .ok() .and_then(AsciiStr::first) } /// Convert to an ASCII slice without checking for non-ASCII characters. /// /// # Safety /// Calling this function when `self` contains non-ascii characters is /// undefined behavior. /// /// # Examples /// unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr; } /// Convert mutable slices of bytes or [`AsciiChar`] to [`AsciiStr`]. pub trait AsMutAsciiStr: AsAsciiStr { /// Convert a subslice to an ASCII slice. /// /// # Errors /// This function returns `Err` if range is out of bounds, or if /// `self` contains non-ascii values fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> where R: SliceIndex<[Self::Inner], Output = [Self::Inner]>; /// Convert to a mutable ASCII slice. /// /// # Errors /// This function returns `Err` if `self` contains non-ascii values fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { self.slice_ascii_mut(..) } /// Convert to a mutable ASCII slice without checking for non-ASCII characters. /// /// # Safety /// Calling this function when `self` contains non-ascii characters is /// undefined behavior. unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr; } // These generic implementations mirror the generic implementations for AsRef in core. impl<'a, T> AsAsciiStr for &'a T where T: AsAsciiStr + ?Sized, { type Inner = ::Inner; fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> where R: SliceIndex<[Self::Inner], Output = [Self::Inner]>, { ::slice_ascii(*self, range) } unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { // SAFETY: Caller guarantees `self` does not contain non-ascii characters unsafe { ::as_ascii_str_unchecked(*self) } } } impl<'a, T> AsAsciiStr for &'a mut T where T: AsAsciiStr + ?Sized, { type Inner = ::Inner; fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> where R: SliceIndex<[Self::Inner], Output = [Self::Inner]>, { ::slice_ascii(*self, range) } unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { // SAFETY: Caller guarantees `self` does not contain non-ascii characters unsafe { ::as_ascii_str_unchecked(*self) } } } impl<'a, T> AsMutAsciiStr for &'a mut T where T: AsMutAsciiStr + ?Sized, { fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> where R: SliceIndex<[Self::Inner], Output = [Self::Inner]>, { ::slice_ascii_mut(*self, range) } unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { // SAFETY: Caller guarantees `self` does not contain non-ascii characters unsafe { ::as_mut_ascii_str_unchecked(*self) } } } impl AsAsciiStr for AsciiStr { type Inner = AsciiChar; fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> where R: SliceIndex<[AsciiChar], Output = [AsciiChar]>, { self.slice.slice_ascii(range) } #[inline] fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { Ok(self) } #[inline] unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { self } #[inline] fn get_ascii(&self, index: usize) -> Option { self.slice.get_ascii(index) } } impl AsMutAsciiStr for AsciiStr { fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> where R: SliceIndex<[AsciiChar], Output = [AsciiChar]>, { self.slice.slice_ascii_mut(range) } #[inline] unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { self } } impl AsAsciiStr for [AsciiChar] { type Inner = AsciiChar; fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> where R: SliceIndex<[AsciiChar], Output = [AsciiChar]>, { match self.get(range) { Some(slice) => Ok(slice.into()), None => Err(AsAsciiStrError(self.len())), } } #[inline] fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { Ok(self.into()) } #[inline] unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { <&AsciiStr>::from(self) } #[inline] fn get_ascii(&self, index: usize) -> Option { self.get(index).copied() } } impl AsMutAsciiStr for [AsciiChar] { fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> where R: SliceIndex<[AsciiChar], Output = [AsciiChar]>, { let len = self.len(); match self.get_mut(range) { Some(slice) => Ok(slice.into()), None => Err(AsAsciiStrError(len)), } } #[inline] unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { <&mut AsciiStr>::from(self) } } impl AsAsciiStr for [u8] { type Inner = u8; fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> where R: SliceIndex<[u8], Output = [u8]>, { if let Some(slice) = self.get(range) { slice.as_ascii_str().map_err(|AsAsciiStrError(not_ascii)| { let offset = slice.as_ptr() as usize - self.as_ptr() as usize; AsAsciiStrError(offset + not_ascii) }) } else { Err(AsAsciiStrError(self.len())) } } fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { // is_ascii is likely optimized if self.is_ascii() { // SAFETY: `is_ascii` guarantees all bytes are within ascii range. unsafe { Ok(self.as_ascii_str_unchecked()) } } else { Err(AsAsciiStrError( self.iter().take_while(|&b| b.is_ascii()).count(), )) } } #[inline] unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { // SAFETY: Caller guarantees `self` does not contain non-ascii characters unsafe { &*(self as *const [u8] as *const AsciiStr) } } } impl AsMutAsciiStr for [u8] { fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> where R: SliceIndex<[u8], Output = [u8]>, { let (ptr, len) = (self.as_ptr(), self.len()); if let Some(slice) = self.get_mut(range) { let slice_ptr = slice.as_ptr(); slice .as_mut_ascii_str() .map_err(|AsAsciiStrError(not_ascii)| { let offset = slice_ptr as usize - ptr as usize; AsAsciiStrError(offset + not_ascii) }) } else { Err(AsAsciiStrError(len)) } } fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { // is_ascii() is likely optimized if self.is_ascii() { // SAFETY: `is_ascii` guarantees all bytes are within ascii range. unsafe { Ok(self.as_mut_ascii_str_unchecked()) } } else { Err(AsAsciiStrError( self.iter().take_while(|&b| b.is_ascii()).count(), )) } } #[inline] unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { // SAFETY: Caller guarantees `self` does not contain non-ascii characters unsafe { &mut *(self as *mut [u8] as *mut AsciiStr) } } } impl AsAsciiStr for str { type Inner = u8; fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> where R: SliceIndex<[u8], Output = [u8]>, { self.as_bytes().slice_ascii(range) } fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { self.as_bytes().as_ascii_str() } #[inline] unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { // SAFETY: Caller guarantees `self` does not contain non-ascii characters unsafe { self.as_bytes().as_ascii_str_unchecked() } } } impl AsMutAsciiStr for str { fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> where R: SliceIndex<[u8], Output = [u8]>, { // SAFETY: We don't modify the reference in this function, and the caller may // only modify it to include valid ascii characters. let bytes = unsafe { self.as_bytes_mut() }; match bytes.get_mut(range) { // Valid ascii slice Some(slice) if slice.is_ascii() => { // SAFETY: All bytes are ascii, so this cast is valid let ptr = slice.as_mut_ptr().cast::(); let len = slice.len(); // SAFETY: The pointer is valid for `len` elements, as it came // from a slice. unsafe { let slice = core::slice::from_raw_parts_mut(ptr, len); Ok(<&mut AsciiStr>::from(slice)) } } Some(slice) => { let not_ascii_len = slice.iter().copied().take_while(u8::is_ascii).count(); let offset = slice.as_ptr() as usize - self.as_ptr() as usize; Err(AsAsciiStrError(offset + not_ascii_len)) } None => Err(AsAsciiStrError(self.len())), } } fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { match self.bytes().position(|b| !b.is_ascii()) { Some(index) => Err(AsAsciiStrError(index)), // SAFETY: All bytes were iterated, and all were ascii None => unsafe { Ok(self.as_mut_ascii_str_unchecked()) }, } } #[inline] unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { // SAFETY: Caller guarantees `self` does not contain non-ascii characters &mut *(self as *mut str as *mut AsciiStr) } } /// Note that the trailing null byte will be removed in the conversion. #[cfg(feature = "std")] impl AsAsciiStr for CStr { type Inner = u8; fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> where R: SliceIndex<[u8], Output = [u8]>, { self.to_bytes().slice_ascii(range) } #[inline] fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { self.to_bytes().as_ascii_str() } #[inline] unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { // SAFETY: Caller guarantees `self` does not contain non-ascii characters unsafe { self.to_bytes().as_ascii_str_unchecked() } } } #[cfg(test)] mod tests { use super::{AsAsciiStr, AsAsciiStrError, AsMutAsciiStr, AsciiStr}; #[cfg(feature = "alloc")] use alloc::string::{String, ToString}; #[cfg(feature = "alloc")] use alloc::vec::Vec; use AsciiChar; /// Ensures that common types, `str`, `[u8]`, `AsciiStr` and their /// references, shared and mutable implement `AsAsciiStr`. #[test] fn generic_as_ascii_str() { // Generic function to ensure `C` implements `AsAsciiStr` fn generic(c: &C) -> Result<&AsciiStr, AsAsciiStrError> { c.as_ascii_str() } let arr = [AsciiChar::A]; let ascii_str = arr.as_ref().into(); let mut mut_arr = arr; // Note: We need a second copy to prevent overlapping mutable borrows. let mut_ascii_str = mut_arr.as_mut().into(); let mut_arr_mut_ref: &mut [AsciiChar] = &mut [AsciiChar::A]; let mut string_bytes = [b'A']; let string_mut = unsafe { core::str::from_utf8_unchecked_mut(&mut string_bytes) }; // SAFETY: 'A' is a valid string. let string_mut_bytes: &mut [u8] = &mut [b'A']; // Note: This is a trick because `rustfmt` doesn't support // attributes on blocks yet. #[rustfmt::skip] let _ = [ assert_eq!(generic::("A" ), Ok(ascii_str)), assert_eq!(generic::<[u8] >(&b"A"[..] ), Ok(ascii_str)), assert_eq!(generic::(ascii_str ), Ok(ascii_str)), assert_eq!(generic::<[AsciiChar] >(&arr ), Ok(ascii_str)), assert_eq!(generic::<&str >(&"A" ), Ok(ascii_str)), assert_eq!(generic::<&[u8] >(&&b"A"[..] ), Ok(ascii_str)), assert_eq!(generic::<&AsciiStr >(&ascii_str ), Ok(ascii_str)), assert_eq!(generic::<&[AsciiChar] >(&&arr[..] ), Ok(ascii_str)), assert_eq!(generic::<&mut str >(&string_mut ), Ok(ascii_str)), assert_eq!(generic::<&mut [u8] >(&string_mut_bytes), Ok(ascii_str)), assert_eq!(generic::<&mut AsciiStr >(&mut_ascii_str ), Ok(ascii_str)), assert_eq!(generic::<&mut [AsciiChar]>(&mut_arr_mut_ref ), Ok(ascii_str)), ]; } #[cfg(feature = "std")] #[test] fn cstring_as_ascii_str() { use std::ffi::CString; fn generic(c: &C) -> Result<&AsciiStr, AsAsciiStrError> { c.as_ascii_str() } let arr = [AsciiChar::A]; let ascii_str: &AsciiStr = arr.as_ref().into(); let cstr = CString::new("A").unwrap(); assert_eq!(generic(&*cstr), Ok(ascii_str)); } #[test] fn generic_as_mut_ascii_str() { fn generic_mut( c: &mut C, ) -> Result<&mut AsciiStr, AsAsciiStrError> { c.as_mut_ascii_str() } let mut arr_mut = [AsciiChar::B]; let mut ascii_str_mut: &mut AsciiStr = arr_mut.as_mut().into(); // Need a second reference to prevent overlapping mutable borrows let mut arr_mut_2 = [AsciiChar::B]; let ascii_str_mut_2: &mut AsciiStr = arr_mut_2.as_mut().into(); assert_eq!(generic_mut(&mut ascii_str_mut), Ok(&mut *ascii_str_mut_2)); assert_eq!(generic_mut(ascii_str_mut), Ok(&mut *ascii_str_mut_2)); } #[test] fn as_ascii_str() { macro_rules! err {{$i:expr} => {Err(AsAsciiStrError($i))}} let s = "abčd"; let b = s.as_bytes(); assert_eq!(s.as_ascii_str(), err!(2)); assert_eq!(b.as_ascii_str(), err!(2)); let a: &AsciiStr = [AsciiChar::a, AsciiChar::b][..].as_ref(); assert_eq!(s[..2].as_ascii_str(), Ok(a)); assert_eq!(b[..2].as_ascii_str(), Ok(a)); assert_eq!(s.slice_ascii(..2), Ok(a)); assert_eq!(b.slice_ascii(..2), Ok(a)); assert_eq!(s.slice_ascii(..=2), err!(2)); assert_eq!(b.slice_ascii(..=2), err!(2)); assert_eq!(s.get_ascii(4), Some(AsciiChar::d)); assert_eq!(b.get_ascii(4), Some(AsciiChar::d)); assert_eq!(s.get_ascii(3), None); assert_eq!(b.get_ascii(3), None); assert_eq!(s.get_ascii(b.len()), None); assert_eq!(b.get_ascii(b.len()), None); assert_eq!(a.get_ascii(0), Some(AsciiChar::a)); assert_eq!(a.get_ascii(a.len()), None); } #[test] #[cfg(feature = "std")] fn cstr_as_ascii_str() { use std::ffi::CStr; macro_rules! err {{$i:expr} => {Err(AsAsciiStrError($i))}} let cstr = CStr::from_bytes_with_nul(b"a\xbbcde\xffg\0").unwrap(); assert_eq!(cstr.as_ascii_str(), err!(1)); assert_eq!(cstr.slice_ascii(2..), err!(5)); assert_eq!(cstr.get_ascii(5), None); assert_eq!(cstr.get_ascii(6), Some(AsciiChar::g)); assert_eq!(cstr.get_ascii(7), None); let ascii_slice = &[AsciiChar::X, AsciiChar::Y, AsciiChar::Z, AsciiChar::Null][..]; let ascii_str: &AsciiStr = ascii_slice.as_ref(); let cstr = CStr::from_bytes_with_nul(ascii_str.as_bytes()).unwrap(); assert_eq!(cstr.slice_ascii(..2), Ok(&ascii_str[..2])); assert_eq!(cstr.as_ascii_str(), Ok(&ascii_str[..3])); } #[test] #[cfg(feature = "alloc")] fn as_mut_ascii_str() { macro_rules! err {{$i:expr} => {Err(AsAsciiStrError($i))}} let mut s: String = "abčd".to_string(); let mut b: Vec = s.clone().into(); let mut first = [AsciiChar::a, AsciiChar::b]; let mut second = [AsciiChar::d]; assert_eq!(s.as_mut_ascii_str(), err!(2)); assert_eq!(b.as_mut_ascii_str(), err!(2)); assert_eq!(s.slice_ascii_mut(..), err!(2)); assert_eq!(b.slice_ascii_mut(..), err!(2)); assert_eq!(s[..2].as_mut_ascii_str(), Ok((&mut first[..]).into())); assert_eq!(b[..2].as_mut_ascii_str(), Ok((&mut first[..]).into())); assert_eq!(s.slice_ascii_mut(0..2), Ok((&mut first[..]).into())); assert_eq!(b.slice_ascii_mut(0..2), Ok((&mut first[..]).into())); assert_eq!(s.slice_ascii_mut(4..), Ok((&mut second[..]).into())); assert_eq!(b.slice_ascii_mut(4..), Ok((&mut second[..]).into())); assert_eq!(s.slice_ascii_mut(4..=10), err!(5)); assert_eq!(b.slice_ascii_mut(4..=10), err!(5)); } #[test] fn default() { let default: &'static AsciiStr = Default::default(); assert!(default.is_empty()); } #[test] #[allow(clippy::redundant_slicing)] fn index() { let mut arr = [AsciiChar::A, AsciiChar::B, AsciiChar::C, AsciiChar::D]; { let a: &AsciiStr = arr[..].into(); assert_eq!(a[..].as_slice(), &a.as_slice()[..]); assert_eq!(a[..4].as_slice(), &a.as_slice()[..4]); assert_eq!(a[4..].as_slice(), &a.as_slice()[4..]); assert_eq!(a[2..3].as_slice(), &a.as_slice()[2..3]); assert_eq!(a[..=3].as_slice(), &a.as_slice()[..=3]); assert_eq!(a[1..=1].as_slice(), &a.as_slice()[1..=1]); } let mut copy = arr; let a_mut: &mut AsciiStr = { &mut arr[..] }.into(); assert_eq!(a_mut[..].as_mut_slice(), &mut copy[..]); assert_eq!(a_mut[..2].as_mut_slice(), &mut copy[..2]); assert_eq!(a_mut[3..].as_mut_slice(), &mut copy[3..]); assert_eq!(a_mut[4..4].as_mut_slice(), &mut copy[4..4]); assert_eq!(a_mut[..=0].as_mut_slice(), &mut copy[..=0]); assert_eq!(a_mut[0..=2].as_mut_slice(), &mut copy[0..=2]); } #[test] fn as_str() { let b = b"( ;"; let v = AsciiStr::from_ascii(b).unwrap(); assert_eq!(v.as_str(), "( ;"); assert_eq!(AsRef::::as_ref(v), "( ;"); } #[test] fn as_bytes() { let b = b"( ;"; let v = AsciiStr::from_ascii(b).unwrap(); assert_eq!(v.as_bytes(), b"( ;"); assert_eq!(AsRef::<[u8]>::as_ref(v), b"( ;"); } #[test] fn make_ascii_case() { let mut bytes = ([b'a', b'@', b'A'], [b'A', b'@', b'a']); let a = bytes.0.as_mut_ascii_str().unwrap(); let b = bytes.1.as_mut_ascii_str().unwrap(); assert!(a.eq_ignore_ascii_case(b)); assert!(b.eq_ignore_ascii_case(a)); a.make_ascii_lowercase(); b.make_ascii_uppercase(); assert_eq!(a, "a@a"); assert_eq!(b, "A@A"); } #[test] #[cfg(feature = "alloc")] fn to_ascii_case() { let bytes = ([b'a', b'@', b'A'], [b'A', b'@', b'a']); let a = bytes.0.as_ascii_str().unwrap(); let b = bytes.1.as_ascii_str().unwrap(); assert_eq!(a.to_ascii_lowercase().as_str(), "a@a"); assert_eq!(a.to_ascii_uppercase().as_str(), "A@A"); assert_eq!(b.to_ascii_lowercase().as_str(), "a@a"); assert_eq!(b.to_ascii_uppercase().as_str(), "A@A"); } #[test] fn chars_iter() { let chars = &[ b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', b'\0', ]; let ascii = AsciiStr::from_ascii(chars).unwrap(); for (achar, byte) in ascii.chars().zip(chars.iter().copied()) { assert_eq!(achar, byte); } } #[test] fn chars_iter_mut() { let chars = &mut [ b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', b'\0', ]; let ascii = chars.as_mut_ascii_str().unwrap(); *ascii.chars_mut().next().unwrap() = AsciiChar::H; assert_eq!(ascii[0], b'H'); } #[test] fn lines_iter() { use core::iter::Iterator; let lines: [&str; 4] = ["foo", "bar", "", "baz"]; let joined = "foo\r\nbar\n\nbaz\n"; let ascii = AsciiStr::from_ascii(joined.as_bytes()).unwrap(); for (asciiline, line) in ascii.lines().zip(&lines) { assert_eq!(asciiline, *line); } assert_eq!(ascii.lines().count(), lines.len()); let lines: [&str; 4] = ["foo", "bar", "", "baz"]; let joined = "foo\r\nbar\n\nbaz"; let ascii = AsciiStr::from_ascii(joined.as_bytes()).unwrap(); for (asciiline, line) in ascii.lines().zip(&lines) { assert_eq!(asciiline, *line); } assert_eq!(ascii.lines().count(), lines.len()); let trailing_line_break = b"\n"; let ascii = AsciiStr::from_ascii(&trailing_line_break).unwrap(); let mut line_iter = ascii.lines(); assert_eq!(line_iter.next(), Some(AsciiStr::from_ascii("").unwrap())); assert_eq!(line_iter.next(), None); let empty_lines = b"\n\r\n\n\r\n"; let mut iter_count = 0; let ascii = AsciiStr::from_ascii(&empty_lines).unwrap(); for line in ascii.lines() { iter_count += 1; assert!(line.is_empty()); } assert_eq!(4, iter_count); } #[test] fn lines_iter_rev() { let joined = "foo\r\nbar\n\nbaz\n"; let ascii = AsciiStr::from_ascii(joined.as_bytes()).unwrap(); assert_eq!(ascii.lines().rev().count(), 4); assert_eq!(ascii.lines().rev().count(), joined.lines().rev().count()); for (asciiline, line) in ascii.lines().rev().zip(joined.lines().rev()) { assert_eq!(asciiline, line); } let mut iter = ascii.lines(); assert_eq!(iter.next(), Some("foo".as_ascii_str().unwrap())); assert_eq!(iter.next_back(), Some("baz".as_ascii_str().unwrap())); assert_eq!(iter.next_back(), Some("".as_ascii_str().unwrap())); assert_eq!(iter.next(), Some("bar".as_ascii_str().unwrap())); let empty_lines = b"\n\r\n\n\r\n"; let mut iter_count = 0; let ascii = AsciiStr::from_ascii(&empty_lines).unwrap(); for line in ascii.lines().rev() { iter_count += 1; assert!(line.is_empty()); } assert_eq!(4, iter_count); } #[test] fn lines_iter_empty() { assert_eq!("".as_ascii_str().unwrap().lines().next(), None); assert_eq!("".as_ascii_str().unwrap().lines().next_back(), None); assert_eq!("".lines().next(), None); } #[test] fn split_str() { fn split_equals_str(haystack: &str, needle: char) { let mut strs = haystack.split(needle); let mut asciis = haystack .as_ascii_str() .unwrap() .split(AsciiChar::from_ascii(needle).unwrap()) .map(AsciiStr::as_str); loop { assert_eq!(asciis.size_hint(), strs.size_hint()); let (a, s) = (asciis.next(), strs.next()); assert_eq!(a, s); if a == None { break; } } // test fusedness if str's version is fused if strs.next() == None { assert_eq!(asciis.next(), None); } } split_equals_str("", '='); split_equals_str("1,2,3", ','); split_equals_str("foo;bar;baz;", ';'); split_equals_str("|||", '|'); split_equals_str(" a b c ", ' '); } #[test] fn split_str_rev() { let words = " foo bar baz "; let ascii = words.as_ascii_str().unwrap(); for (word, asciiword) in words .split(' ') .rev() .zip(ascii.split(AsciiChar::Space).rev()) { assert_eq!(asciiword, word); } let mut iter = ascii.split(AsciiChar::Space); assert_eq!(iter.next(), Some("".as_ascii_str().unwrap())); assert_eq!(iter.next_back(), Some("".as_ascii_str().unwrap())); assert_eq!(iter.next(), Some("foo".as_ascii_str().unwrap())); assert_eq!(iter.next_back(), Some("baz".as_ascii_str().unwrap())); assert_eq!(iter.next_back(), Some("bar".as_ascii_str().unwrap())); assert_eq!(iter.next(), Some("".as_ascii_str().unwrap())); assert_eq!(iter.next_back(), None); } #[test] fn split_str_empty() { let empty = <&AsciiStr>::default(); let mut iter = empty.split(AsciiChar::NAK); assert_eq!(iter.next(), Some(empty)); assert_eq!(iter.next(), None); let mut iter = empty.split(AsciiChar::NAK); assert_eq!(iter.next_back(), Some(empty)); assert_eq!(iter.next_back(), None); assert_eq!("".split('s').next(), Some("")); // str.split() also produces one element } #[test] #[cfg(feature = "std")] fn fmt_ascii_str() { let s = "abc".as_ascii_str().unwrap(); assert_eq!(format!("{}", s), "abc".to_string()); assert_eq!(format!("{:?}", s), "\"abc\"".to_string()); } } ascii-1.1.0/src/ascii_string.rs000064400000000000000000000752451046102023000145430ustar 00000000000000use alloc::borrow::{Borrow, BorrowMut, Cow, ToOwned}; use alloc::fmt; use alloc::string::String; use alloc::vec::Vec; use alloc::boxed::Box; use alloc::rc::Rc; use alloc::sync::Arc; #[cfg(feature = "std")] use core::any::Any; use core::iter::FromIterator; use core::mem; use core::ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut}; use core::str::FromStr; #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "std")] use std::ffi::{CStr, CString}; use ascii_char::AsciiChar; use ascii_str::{AsAsciiStr, AsAsciiStrError, AsciiStr}; /// A growable string stored as an ASCII encoded buffer. #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct AsciiString { vec: Vec, } impl AsciiString { /// Creates a new, empty ASCII string buffer without allocating. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let mut s = AsciiString::new(); /// ``` #[inline] #[must_use] pub const fn new() -> Self { AsciiString { vec: Vec::new() } } /// Creates a new ASCII string buffer with the given capacity. /// The string will be able to hold exactly `capacity` bytes without reallocating. /// If `capacity` is 0, the ASCII string will not allocate. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let mut s = AsciiString::with_capacity(10); /// ``` #[inline] #[must_use] pub fn with_capacity(capacity: usize) -> Self { AsciiString { vec: Vec::with_capacity(capacity), } } /// Creates a new `AsciiString` from a length, capacity and pointer. /// /// # Safety /// /// This is highly unsafe, due to the number of invariants that aren't checked: /// /// * The memory at `buf` need to have been previously allocated by the same allocator this /// library uses, with an alignment of 1. /// * `length` needs to be less than or equal to `capacity`. /// * `capacity` needs to be the correct value. /// * `buf` must have `length` valid ascii elements and contain a total of `capacity` total, /// possibly, uninitialized, elements. /// * Nothing else must be using the memory `buf` points to. /// /// Violating these may cause problems like corrupting the allocator's internal data structures. /// /// # Examples /// /// Basic usage: /// /// ``` /// # use ascii::AsciiString; /// use std::mem; /// /// unsafe { /// let mut s = AsciiString::from_ascii("hello").unwrap(); /// let ptr = s.as_mut_ptr(); /// let len = s.len(); /// let capacity = s.capacity(); /// /// mem::forget(s); /// /// let s = AsciiString::from_raw_parts(ptr, len, capacity); /// /// assert_eq!(AsciiString::from_ascii("hello").unwrap(), s); /// } /// ``` #[inline] #[must_use] pub unsafe fn from_raw_parts(buf: *mut AsciiChar, length: usize, capacity: usize) -> Self { AsciiString { // SAFETY: Caller guarantees that `buf` was previously allocated by this library, // that `buf` contains `length` valid ascii elements and has a total capacity // of `capacity` elements, and that nothing else is using the momory. vec: unsafe { Vec::from_raw_parts(buf, length, capacity) }, } } /// Converts a vector of bytes to an `AsciiString` without checking for non-ASCII characters. /// /// # Safety /// This function is unsafe because it does not check that the bytes passed to it are valid /// ASCII characters. If this constraint is violated, it may cause memory unsafety issues with /// future of the `AsciiString`, as the rest of this library assumes that `AsciiString`s are /// ASCII encoded. #[inline] #[must_use] pub unsafe fn from_ascii_unchecked(bytes: B) -> Self where B: Into>, { let mut bytes = bytes.into(); // SAFETY: The caller guarantees all bytes are valid ascii bytes. let ptr = bytes.as_mut_ptr().cast::(); let length = bytes.len(); let capacity = bytes.capacity(); mem::forget(bytes); // SAFETY: We guarantee all invariants, as we got the // pointer, length and capacity from a `Vec`, // and we also guarantee the pointer is valid per // the `SAFETY` notice above. let vec = Vec::from_raw_parts(ptr, length, capacity); Self { vec } } /// Converts anything that can represent a byte buffer into an `AsciiString`. /// /// # Errors /// Returns the byte buffer if not all of the bytes are ASCII characters. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let foo = AsciiString::from_ascii("foo".to_string()).unwrap(); /// let err = AsciiString::from_ascii("Ŋ".to_string()).unwrap_err(); /// assert_eq!(foo.as_str(), "foo"); /// assert_eq!(err.into_source(), "Ŋ"); /// ``` pub fn from_ascii(bytes: B) -> Result> where B: Into> + AsRef<[u8]>, { match bytes.as_ref().as_ascii_str() { // SAFETY: `as_ascii_str` guarantees all bytes are valid ascii bytes. Ok(_) => Ok(unsafe { AsciiString::from_ascii_unchecked(bytes) }), Err(e) => Err(FromAsciiError { error: e, owner: bytes, }), } } /// Pushes the given ASCII string onto this ASCII string buffer. /// /// # Examples /// ``` /// # use ascii::{AsciiString, AsAsciiStr}; /// use std::str::FromStr; /// let mut s = AsciiString::from_str("foo").unwrap(); /// s.push_str("bar".as_ascii_str().unwrap()); /// assert_eq!(s, "foobar".as_ascii_str().unwrap()); /// ``` #[inline] pub fn push_str(&mut self, string: &AsciiStr) { self.vec.extend(string.chars()); } /// Inserts the given ASCII string at the given place in this ASCII string buffer. /// /// # Panics /// /// Panics if `idx` is larger than the `AsciiString`'s length. /// /// # Examples /// ``` /// # use ascii::{AsciiString, AsAsciiStr}; /// use std::str::FromStr; /// let mut s = AsciiString::from_str("abc").unwrap(); /// s.insert_str(1, "def".as_ascii_str().unwrap()); /// assert_eq!(&*s, "adefbc"); #[inline] pub fn insert_str(&mut self, idx: usize, string: &AsciiStr) { self.vec.reserve(string.len()); self.vec.splice(idx..idx, string.into_iter().copied()); } /// Returns the number of bytes that this ASCII string buffer can hold without reallocating. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let s = String::with_capacity(10); /// assert!(s.capacity() >= 10); /// ``` #[inline] #[must_use] pub fn capacity(&self) -> usize { self.vec.capacity() } /// Reserves capacity for at least `additional` more bytes to be inserted in the given /// `AsciiString`. The collection may reserve more space to avoid frequent reallocations. /// /// # Panics /// Panics if the new capacity overflows `usize`. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let mut s = AsciiString::new(); /// s.reserve(10); /// assert!(s.capacity() >= 10); /// ``` #[inline] pub fn reserve(&mut self, additional: usize) { self.vec.reserve(additional); } /// Reserves the minimum capacity for exactly `additional` more bytes to be inserted in the /// given `AsciiString`. Does nothing if the capacity is already sufficient. /// /// Note that the allocator may give the collection more space than it requests. Therefore /// capacity can not be relied upon to be precisely minimal. Prefer `reserve` if future /// insertions are expected. /// /// # Panics /// Panics if the new capacity overflows `usize`. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let mut s = AsciiString::new(); /// s.reserve_exact(10); /// assert!(s.capacity() >= 10); /// ``` #[inline] pub fn reserve_exact(&mut self, additional: usize) { self.vec.reserve_exact(additional); } /// Shrinks the capacity of this ASCII string buffer to match it's length. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// use std::str::FromStr; /// let mut s = AsciiString::from_str("foo").unwrap(); /// s.reserve(100); /// assert!(s.capacity() >= 100); /// s.shrink_to_fit(); /// assert_eq!(s.capacity(), 3); /// ``` #[inline] pub fn shrink_to_fit(&mut self) { self.vec.shrink_to_fit(); } /// Adds the given ASCII character to the end of the ASCII string. /// /// # Examples /// ``` /// # use ascii::{ AsciiChar, AsciiString}; /// let mut s = AsciiString::from_ascii("abc").unwrap(); /// s.push(AsciiChar::from_ascii('1').unwrap()); /// s.push(AsciiChar::from_ascii('2').unwrap()); /// s.push(AsciiChar::from_ascii('3').unwrap()); /// assert_eq!(s, "abc123"); /// ``` #[inline] pub fn push(&mut self, ch: AsciiChar) { self.vec.push(ch); } /// Shortens a ASCII string to the specified length. /// /// # Panics /// Panics if `new_len` > current length. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let mut s = AsciiString::from_ascii("hello").unwrap(); /// s.truncate(2); /// assert_eq!(s, "he"); /// ``` #[inline] pub fn truncate(&mut self, new_len: usize) { self.vec.truncate(new_len); } /// Removes the last character from the ASCII string buffer and returns it. /// Returns `None` if this string buffer is empty. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let mut s = AsciiString::from_ascii("foo").unwrap(); /// assert_eq!(s.pop().map(|c| c.as_char()), Some('o')); /// assert_eq!(s.pop().map(|c| c.as_char()), Some('o')); /// assert_eq!(s.pop().map(|c| c.as_char()), Some('f')); /// assert_eq!(s.pop(), None); /// ``` #[inline] #[must_use] pub fn pop(&mut self) -> Option { self.vec.pop() } /// Removes the ASCII character at position `idx` from the buffer and returns it. /// /// # Warning /// This is an O(n) operation as it requires copying every element in the buffer. /// /// # Panics /// If `idx` is out of bounds this function will panic. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let mut s = AsciiString::from_ascii("foo").unwrap(); /// assert_eq!(s.remove(0).as_char(), 'f'); /// assert_eq!(s.remove(1).as_char(), 'o'); /// assert_eq!(s.remove(0).as_char(), 'o'); /// ``` #[inline] #[must_use] pub fn remove(&mut self, idx: usize) -> AsciiChar { self.vec.remove(idx) } /// Inserts an ASCII character into the buffer at position `idx`. /// /// # Warning /// This is an O(n) operation as it requires copying every element in the buffer. /// /// # Panics /// If `idx` is out of bounds this function will panic. /// /// # Examples /// ``` /// # use ascii::{AsciiString,AsciiChar}; /// let mut s = AsciiString::from_ascii("foo").unwrap(); /// s.insert(2, AsciiChar::b); /// assert_eq!(s, "fobo"); /// ``` #[inline] pub fn insert(&mut self, idx: usize, ch: AsciiChar) { self.vec.insert(idx, ch); } /// Returns the number of bytes in this ASCII string. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let s = AsciiString::from_ascii("foo").unwrap(); /// assert_eq!(s.len(), 3); /// ``` #[inline] #[must_use] pub fn len(&self) -> usize { self.vec.len() } /// Returns true if the ASCII string contains zero bytes. /// /// # Examples /// ``` /// # use ascii::{AsciiChar, AsciiString}; /// let mut s = AsciiString::new(); /// assert!(s.is_empty()); /// s.push(AsciiChar::from_ascii('a').unwrap()); /// assert!(!s.is_empty()); /// ``` #[inline] #[must_use] pub fn is_empty(&self) -> bool { self.len() == 0 } /// Truncates the ASCII string, setting length (but not capacity) to zero. /// /// # Examples /// ``` /// # use ascii::AsciiString; /// let mut s = AsciiString::from_ascii("foo").unwrap(); /// s.clear(); /// assert!(s.is_empty()); /// ``` #[inline] pub fn clear(&mut self) { self.vec.clear(); } /// Converts this [`AsciiString`] into a [`Box`]`<`[`AsciiStr`]`>`. /// /// This will drop any excess capacity #[inline] #[must_use] pub fn into_boxed_ascii_str(self) -> Box { let slice = self.vec.into_boxed_slice(); Box::from(slice) } } impl Deref for AsciiString { type Target = AsciiStr; #[inline] fn deref(&self) -> &AsciiStr { self.vec.as_slice().as_ref() } } impl DerefMut for AsciiString { #[inline] fn deref_mut(&mut self) -> &mut AsciiStr { self.vec.as_mut_slice().as_mut() } } impl PartialEq for AsciiString { #[inline] fn eq(&self, other: &str) -> bool { **self == *other } } impl PartialEq for str { #[inline] fn eq(&self, other: &AsciiString) -> bool { **other == *self } } macro_rules! impl_eq { ($lhs:ty, $rhs:ty) => { impl PartialEq<$rhs> for $lhs { #[inline] fn eq(&self, other: &$rhs) -> bool { PartialEq::eq(&**self, &**other) } } }; } impl_eq! { AsciiString, String } impl_eq! { String, AsciiString } impl_eq! { &AsciiStr, String } impl_eq! { String, &AsciiStr } impl_eq! { &AsciiStr, AsciiString } impl_eq! { AsciiString, &AsciiStr } impl_eq! { &str, AsciiString } impl_eq! { AsciiString, &str } impl Borrow for AsciiString { #[inline] fn borrow(&self) -> &AsciiStr { &**self } } impl BorrowMut for AsciiString { #[inline] fn borrow_mut(&mut self) -> &mut AsciiStr { &mut **self } } impl From> for AsciiString { #[inline] fn from(vec: Vec) -> Self { AsciiString { vec } } } impl From for AsciiString { #[inline] fn from(ch: AsciiChar) -> Self { AsciiString { vec: vec![ch] } } } impl From for Vec { fn from(mut s: AsciiString) -> Vec { // SAFETY: All ascii bytes are valid `u8`, as we are `repr(u8)`. // Note: We forget `self` to avoid `self.vec` from being deallocated. let ptr = s.vec.as_mut_ptr().cast::(); let length = s.vec.len(); let capacity = s.vec.capacity(); mem::forget(s); // SAFETY: We guarantee all invariants due to getting `ptr`, `length` // and `capacity` from a `Vec`. We also guarantee `ptr` is valid // due to the `SAFETY` block above. unsafe { Vec::from_raw_parts(ptr, length, capacity) } } } impl From for Vec { fn from(s: AsciiString) -> Vec { s.vec } } impl<'a> From<&'a AsciiStr> for AsciiString { #[inline] fn from(s: &'a AsciiStr) -> Self { s.to_ascii_string() } } impl<'a> From<&'a [AsciiChar]> for AsciiString { #[inline] fn from(s: &'a [AsciiChar]) -> AsciiString { s.iter().copied().collect() } } impl From for String { #[inline] fn from(s: AsciiString) -> String { // SAFETY: All ascii bytes are `utf8`. unsafe { String::from_utf8_unchecked(s.into()) } } } impl From> for AsciiString { #[inline] fn from(boxed: Box) -> Self { boxed.into_ascii_string() } } impl From for Box { #[inline] fn from(string: AsciiString) -> Self { string.into_boxed_ascii_str() } } impl From for Rc { fn from(s: AsciiString) -> Rc { let var: Rc<[AsciiChar]> = s.vec.into(); // SAFETY: AsciiStr is repr(transparent) and thus has the same layout as [AsciiChar] unsafe { Rc::from_raw(Rc::into_raw(var) as *const AsciiStr) } } } impl From for Arc { fn from(s: AsciiString) -> Arc { let var: Arc<[AsciiChar]> = s.vec.into(); // SAFETY: AsciiStr is repr(transparent) and thus has the same layout as [AsciiChar] unsafe { Arc::from_raw(Arc::into_raw(var) as *const AsciiStr) } } } impl<'a> From> for AsciiString { fn from(cow: Cow<'a, AsciiStr>) -> AsciiString { cow.into_owned() } } impl From for Cow<'static, AsciiStr> { fn from(string: AsciiString) -> Cow<'static, AsciiStr> { Cow::Owned(string) } } impl<'a> From<&'a AsciiStr> for Cow<'a, AsciiStr> { fn from(s: &'a AsciiStr) -> Cow<'a, AsciiStr> { Cow::Borrowed(s) } } impl AsRef for AsciiString { #[inline] fn as_ref(&self) -> &AsciiStr { &**self } } impl AsRef<[AsciiChar]> for AsciiString { #[inline] fn as_ref(&self) -> &[AsciiChar] { &self.vec } } impl AsRef<[u8]> for AsciiString { #[inline] fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl AsRef for AsciiString { #[inline] fn as_ref(&self) -> &str { self.as_str() } } impl AsMut for AsciiString { #[inline] fn as_mut(&mut self) -> &mut AsciiStr { &mut *self } } impl AsMut<[AsciiChar]> for AsciiString { #[inline] fn as_mut(&mut self) -> &mut [AsciiChar] { &mut self.vec } } impl FromStr for AsciiString { type Err = AsAsciiStrError; fn from_str(s: &str) -> Result { s.as_ascii_str().map(AsciiStr::to_ascii_string) } } impl fmt::Display for AsciiString { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&**self, f) } } impl fmt::Debug for AsciiString { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&**self, f) } } /// Please note that the `std::fmt::Result` returned by these methods does not support /// transmission of an error other than that an error occurred. impl fmt::Write for AsciiString { fn write_str(&mut self, s: &str) -> fmt::Result { if let Ok(astr) = AsciiStr::from_ascii(s) { self.push_str(astr); Ok(()) } else { Err(fmt::Error) } } fn write_char(&mut self, c: char) -> fmt::Result { if let Ok(achar) = AsciiChar::from_ascii(c) { self.push(achar); Ok(()) } else { Err(fmt::Error) } } } impl> FromIterator for AsciiString { fn from_iter>(iter: I) -> AsciiString { let mut buf = AsciiString::new(); buf.extend(iter); buf } } impl> Extend for AsciiString { fn extend>(&mut self, iterable: I) { let iterator = iterable.into_iter(); let (lower_bound, _) = iterator.size_hint(); self.reserve(lower_bound); for item in iterator { self.push_str(item.as_ref()); } } } impl<'a> Add<&'a AsciiStr> for AsciiString { type Output = AsciiString; #[inline] fn add(mut self, other: &AsciiStr) -> AsciiString { self.push_str(other); self } } impl<'a> AddAssign<&'a AsciiStr> for AsciiString { #[inline] fn add_assign(&mut self, other: &AsciiStr) { self.push_str(other); } } #[allow(clippy::indexing_slicing)] // In `Index`, if it's out of bounds, panic is the default impl Index for AsciiString where AsciiStr: Index, { type Output = >::Output; #[inline] fn index(&self, index: T) -> &>::Output { &(**self)[index] } } #[allow(clippy::indexing_slicing)] // In `IndexMut`, if it's out of bounds, panic is the default impl IndexMut for AsciiString where AsciiStr: IndexMut, { #[inline] fn index_mut(&mut self, index: T) -> &mut >::Output { &mut (**self)[index] } } /// A possible error value when converting an `AsciiString` from a byte vector or string. /// It wraps an `AsAsciiStrError` which you can get through the `ascii_error()` method. /// /// This is the error type for `AsciiString::from_ascii()` and /// `IntoAsciiString::into_ascii_string()`. They will never clone or touch the content of the /// original type; It can be extracted by the `into_source` method. /// /// #Examples /// ``` /// # use ascii::IntoAsciiString; /// let err = "bø!".to_string().into_ascii_string().unwrap_err(); /// assert_eq!(err.ascii_error().valid_up_to(), 1); /// assert_eq!(err.into_source(), "bø!".to_string()); /// ``` #[derive(Clone, Copy, PartialEq, Eq)] pub struct FromAsciiError { error: AsAsciiStrError, owner: O, } impl FromAsciiError { /// Get the position of the first non-ASCII byte or character. #[inline] #[must_use] pub fn ascii_error(&self) -> AsAsciiStrError { self.error } /// Get back the original, unmodified type. #[inline] #[must_use] pub fn into_source(self) -> O { self.owner } } impl fmt::Debug for FromAsciiError { #[inline] fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.error, fmtr) } } impl fmt::Display for FromAsciiError { #[inline] fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.error, fmtr) } } #[cfg(feature = "std")] impl Error for FromAsciiError { #[inline] #[allow(deprecated)] // TODO: Remove deprecation once the earliest version we support deprecates this method. fn description(&self) -> &str { self.error.description() } /// Always returns an `AsAsciiStrError` fn cause(&self) -> Option<&dyn Error> { Some(&self.error as &dyn Error) } } /// Convert vectors into `AsciiString`. pub trait IntoAsciiString: Sized { /// Convert to `AsciiString` without checking for non-ASCII characters. /// /// # Safety /// If `self` contains non-ascii characters, calling this function is /// undefined behavior. unsafe fn into_ascii_string_unchecked(self) -> AsciiString; /// Convert to `AsciiString`. /// /// # Errors /// If `self` contains non-ascii characters, this will return `Err` fn into_ascii_string(self) -> Result>; } impl IntoAsciiString for Vec { #[inline] unsafe fn into_ascii_string_unchecked(self) -> AsciiString { AsciiString::from(self) } #[inline] fn into_ascii_string(self) -> Result> { Ok(AsciiString::from(self)) } } impl<'a> IntoAsciiString for &'a [AsciiChar] { #[inline] unsafe fn into_ascii_string_unchecked(self) -> AsciiString { AsciiString::from(self) } #[inline] fn into_ascii_string(self) -> Result> { Ok(AsciiString::from(self)) } } impl<'a> IntoAsciiString for &'a AsciiStr { #[inline] unsafe fn into_ascii_string_unchecked(self) -> AsciiString { AsciiString::from(self) } #[inline] fn into_ascii_string(self) -> Result> { Ok(AsciiString::from(self)) } } macro_rules! impl_into_ascii_string { ('a, $wider:ty) => { impl<'a> IntoAsciiString for $wider { #[inline] unsafe fn into_ascii_string_unchecked(self) -> AsciiString { // SAFETY: Caller guarantees `self` only has valid ascii bytes unsafe { AsciiString::from_ascii_unchecked(self) } } #[inline] fn into_ascii_string(self) -> Result> { AsciiString::from_ascii(self) } } }; ($wider:ty) => { impl IntoAsciiString for $wider { #[inline] unsafe fn into_ascii_string_unchecked(self) -> AsciiString { // SAFETY: Caller guarantees `self` only has valid ascii bytes unsafe { AsciiString::from_ascii_unchecked(self) } } #[inline] fn into_ascii_string(self) -> Result> { AsciiString::from_ascii(self) } } }; } impl_into_ascii_string! {AsciiString} impl_into_ascii_string! {Vec} impl_into_ascii_string! {'a, &'a [u8]} impl_into_ascii_string! {String} impl_into_ascii_string! {'a, &'a str} /// # Notes /// The trailing null byte `CString` has will be removed during this conversion. #[cfg(feature = "std")] impl IntoAsciiString for CString { #[inline] unsafe fn into_ascii_string_unchecked(self) -> AsciiString { // SAFETY: Caller guarantees `self` only has valid ascii bytes unsafe { AsciiString::from_ascii_unchecked(self.into_bytes()) } } fn into_ascii_string(self) -> Result> { AsciiString::from_ascii(self.into_bytes_with_nul()) .map_err(|FromAsciiError { error, owner }| { FromAsciiError { // SAFETY: We don't discard the NULL byte from the original // string, so we ensure that it's null terminated owner: unsafe { CString::from_vec_unchecked(owner) }, error, } }) .map(|mut s| { let nul = s.pop(); debug_assert_eq!(nul, Some(AsciiChar::Null)); s }) } } /// Note that the trailing null byte will be removed in the conversion. #[cfg(feature = "std")] impl<'a> IntoAsciiString for &'a CStr { #[inline] unsafe fn into_ascii_string_unchecked(self) -> AsciiString { // SAFETY: Caller guarantees `self` only has valid ascii bytes unsafe { AsciiString::from_ascii_unchecked(self.to_bytes()) } } fn into_ascii_string(self) -> Result> { AsciiString::from_ascii(self.to_bytes_with_nul()) .map_err(|FromAsciiError { error, owner }| FromAsciiError { // SAFETY: We don't discard the NULL byte from the original // string, so we ensure that it's null terminated owner: unsafe { CStr::from_ptr(owner.as_ptr().cast()) }, error, }) .map(|mut s| { let nul = s.pop(); debug_assert_eq!(nul, Some(AsciiChar::Null)); s }) } } impl<'a, B> IntoAsciiString for Cow<'a, B> where B: 'a + ToOwned + ?Sized, &'a B: IntoAsciiString, ::Owned: IntoAsciiString, { #[inline] unsafe fn into_ascii_string_unchecked(self) -> AsciiString { // SAFETY: Caller guarantees `self` only has valid ascii bytes unsafe { IntoAsciiString::into_ascii_string_unchecked(self.into_owned()) } } fn into_ascii_string(self) -> Result> { match self { Cow::Owned(b) => { IntoAsciiString::into_ascii_string(b).map_err(|FromAsciiError { error, owner }| { FromAsciiError { owner: Cow::Owned(owner), error, } }) } Cow::Borrowed(b) => { IntoAsciiString::into_ascii_string(b).map_err(|FromAsciiError { error, owner }| { FromAsciiError { owner: Cow::Borrowed(owner), error, } }) } } } } #[cfg(test)] mod tests { use super::{AsciiString, IntoAsciiString}; use alloc::str::FromStr; use alloc::string::{String, ToString}; use alloc::vec::Vec; use alloc::boxed::Box; #[cfg(feature = "std")] use std::ffi::CString; use {AsciiChar, AsciiStr}; #[test] fn into_string() { let v = AsciiString::from_ascii(&[40_u8, 32, 59][..]).unwrap(); assert_eq!(Into::::into(v), "( ;".to_string()); } #[test] fn into_bytes() { let v = AsciiString::from_ascii(&[40_u8, 32, 59][..]).unwrap(); assert_eq!(Into::>::into(v), vec![40_u8, 32, 59]); } #[test] fn from_ascii_vec() { let vec = vec![ AsciiChar::from_ascii('A').unwrap(), AsciiChar::from_ascii('B').unwrap(), ]; assert_eq!(AsciiString::from(vec), AsciiString::from_str("AB").unwrap()); } #[test] #[cfg(feature = "std")] fn from_cstring() { let cstring = CString::new("baz").unwrap(); let ascii_str = cstring.clone().into_ascii_string().unwrap(); let expected_chars = &[AsciiChar::b, AsciiChar::a, AsciiChar::z]; assert_eq!(ascii_str.len(), 3); assert_eq!(ascii_str.as_slice(), expected_chars); // SAFETY: "baz" only contains valid ascii characters. let ascii_str_unchecked = unsafe { cstring.into_ascii_string_unchecked() }; assert_eq!(ascii_str_unchecked.len(), 3); assert_eq!(ascii_str_unchecked.as_slice(), expected_chars); let sparkle_heart_bytes = vec![240_u8, 159, 146, 150]; let cstring = CString::new(sparkle_heart_bytes).unwrap(); let cstr = &*cstring; let ascii_err = cstr.into_ascii_string().unwrap_err(); assert_eq!(ascii_err.into_source(), &*cstring); } #[test] #[cfg(feature = "std")] fn fmt_ascii_string() { let s = "abc".to_string().into_ascii_string().unwrap(); assert_eq!(format!("{}", s), "abc".to_string()); assert_eq!(format!("{:?}", s), "\"abc\"".to_string()); } #[test] fn write_fmt() { use alloc::{fmt, str}; let mut s0 = AsciiString::new(); fmt::write(&mut s0, format_args!("Hello World")).unwrap(); assert_eq!(s0, "Hello World"); let mut s1 = AsciiString::new(); fmt::write(&mut s1, format_args!("{}", 9)).unwrap(); assert_eq!(s1, "9"); let mut s2 = AsciiString::new(); let sparkle_heart_bytes = [240, 159, 146, 150]; let sparkle_heart = str::from_utf8(&sparkle_heart_bytes).unwrap(); assert!(fmt::write(&mut s2, format_args!("{}", sparkle_heart)).is_err()); } #[test] fn to_and_from_box() { let string = "abc".into_ascii_string().unwrap(); let converted: Box = Box::from(string.clone()); let converted: AsciiString = converted.into(); assert_eq!(string, converted); } } ascii-1.1.0/src/free_functions.rs000064400000000000000000000043221046102023000150620ustar 00000000000000use ascii_char::{AsciiChar, ToAsciiChar}; /// Terminals use [caret notation](https://en.wikipedia.org/wiki/Caret_notation) /// to display some typed control codes, such as ^D for EOT and ^Z for SUB. /// /// This function returns the caret notation letter for control codes, /// or `None` for printable characters. /// /// # Examples /// ``` /// # use ascii::{AsciiChar, caret_encode}; /// assert_eq!(caret_encode(b'\0'), Some(AsciiChar::At)); /// assert_eq!(caret_encode(AsciiChar::DEL), Some(AsciiChar::Question)); /// assert_eq!(caret_encode(b'E'), None); /// assert_eq!(caret_encode(b'\n'), Some(AsciiChar::J)); /// ``` pub fn caret_encode>(c: C) -> Option { // The formula is explained in the Wikipedia article. let c = c.into() ^ 0b0100_0000; if (b'?'..=b'_').contains(&c) { // SAFETY: All bytes between '?' (0x3F) and '_' (0x5f) are valid ascii characters. Some(unsafe { c.to_ascii_char_unchecked() }) } else { None } } /// Returns the control code represented by a [caret notation](https://en.wikipedia.org/wiki/Caret_notation) /// letter, or `None` if the letter is not used in caret notation. /// /// This function is the inverse of `caret_encode()`. /// /// # Examples /// /// Basic usage: /// /// ``` /// # use ascii::{AsciiChar, caret_decode}; /// assert_eq!(caret_decode(b'?'), Some(AsciiChar::DEL)); /// assert_eq!(caret_decode(AsciiChar::D), Some(AsciiChar::EOT)); /// assert_eq!(caret_decode(b'\0'), None); /// ``` /// /// Symmetry: /// /// ``` /// # use ascii::{AsciiChar, caret_encode, caret_decode}; /// assert_eq!(caret_encode(AsciiChar::US).and_then(caret_decode), Some(AsciiChar::US)); /// assert_eq!(caret_decode(b'@').and_then(caret_encode), Some(AsciiChar::At)); /// ``` pub fn caret_decode>(c: C) -> Option { // The formula is explained in the Wikipedia article. match c.into() { // SAFETY: All bytes between '?' (0x3F) and '_' (0x5f) after `xoring` with `0b0100_0000` are // valid bytes, as they represent characters between '␀' (0x0) and '␠' (0x1f) + '␡' (0x7f) b'?'..=b'_' => Some(unsafe { AsciiChar::from_ascii_unchecked(c.into() ^ 0b0100_0000) }), _ => None, } } ascii-1.1.0/src/lib.rs000064400000000000000000000063321046102023000126220ustar 00000000000000// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! A library that provides ASCII-only string and character types, equivalent to the `char`, `str` //! and `String` types in the standard library. //! //! Please refer to the readme file to learn about the different feature modes of this crate. //! //! # Minimum supported Rust version //! //! The minimum Rust version for 1.1.\* releases is 1.41.1. //! Later 1.y.0 releases might require newer Rust versions, but the three most //! recent stable releases at the time of publishing will always be supported. //! For example this means that if the current stable Rust version is 1.70 when //! ascii 1.2.0 is released, then ascii 1.2.\* will not require a newer //! Rust version than 1.68. //! //! # History //! //! This package included the Ascii types that were removed from the Rust standard library by the //! 2014-12 [reform of the `std::ascii` module](https://github.com/rust-lang/rfcs/pull/486). The //! API changed significantly since then. #![cfg_attr(not(feature = "std"), no_std)] // Clippy lints #![warn( clippy::pedantic, clippy::decimal_literal_representation, clippy::get_unwrap, clippy::indexing_slicing )] // Naming conventions sometimes go against this lint #![allow(clippy::module_name_repetitions)] // We need to get literal non-asciis for tests #![allow(clippy::non_ascii_literal)] // Sometimes it looks better to invert the order, such as when the `else` block is small #![allow(clippy::if_not_else)] // Shadowing is common and doesn't affect understanding // TODO: Consider removing `shadow_unrelated`, as it can show some actual logic errors #![allow(clippy::shadow_unrelated, clippy::shadow_reuse, clippy::shadow_same)] // A `if let` / `else` sometimes looks better than using iterator adaptors #![allow(clippy::option_if_let_else)] // In tests, we're fine with indexing, since a panic is a failure. #![cfg_attr(test, allow(clippy::indexing_slicing))] // for compatibility with methods on char and u8 #![allow(clippy::trivially_copy_pass_by_ref)] // In preparation for feature `unsafe_block_in_unsafe_fn` (https://github.com/rust-lang/rust/issues/71668) #![allow(unused_unsafe)] #[cfg(feature = "alloc")] #[macro_use] extern crate alloc; #[cfg(feature = "std")] extern crate core; #[cfg(feature = "serde")] extern crate serde; #[cfg(all(test, feature = "serde_test"))] extern crate serde_test; mod ascii_char; mod ascii_str; #[cfg(feature = "alloc")] mod ascii_string; mod free_functions; #[cfg(feature = "serde")] mod serialization; pub use ascii_char::{AsciiChar, ToAsciiChar, ToAsciiCharError}; pub use ascii_str::{AsAsciiStr, AsAsciiStrError, AsMutAsciiStr, AsciiStr}; pub use ascii_str::{Chars, CharsMut, CharsRef}; #[cfg(feature = "alloc")] pub use ascii_string::{AsciiString, FromAsciiError, IntoAsciiString}; pub use free_functions::{caret_decode, caret_encode}; ascii-1.1.0/src/serialization/ascii_char.rs000064400000000000000000000050401046102023000170110ustar 00000000000000use std::fmt; use serde::de::{Error, Unexpected, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use ascii_char::AsciiChar; impl Serialize for AsciiChar { #[inline] fn serialize(&self, serializer: S) -> Result { serializer.serialize_char(self.as_char()) } } struct AsciiCharVisitor; impl<'de> Visitor<'de> for AsciiCharVisitor { type Value = AsciiChar; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("an ascii character") } #[inline] fn visit_char(self, v: char) -> Result { AsciiChar::from_ascii(v).map_err(|_| Error::invalid_value(Unexpected::Char(v), &self)) } #[inline] fn visit_str(self, v: &str) -> Result { if v.len() == 1 { let c = v.chars().next().unwrap(); self.visit_char(c) } else { Err(Error::invalid_value(Unexpected::Str(v), &self)) } } } impl<'de> Deserialize<'de> for AsciiChar { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_char(AsciiCharVisitor) } } #[cfg(test)] mod tests { use super::*; #[cfg(feature = "serde_test")] const ASCII_CHAR: char = 'e'; #[cfg(feature = "serde_test")] const ASCII_STR: &str = "e"; #[cfg(feature = "serde_test")] const UNICODE_CHAR: char = 'é'; #[test] fn basic() { fn assert_serialize() {} fn assert_deserialize<'de, T: Deserialize<'de>>() {} assert_serialize::(); assert_deserialize::(); } #[test] #[cfg(feature = "serde_test")] fn serialize() { use serde_test::{assert_tokens, Token}; let ascii_char = AsciiChar::from_ascii(ASCII_CHAR).unwrap(); assert_tokens(&ascii_char, &[Token::Char(ASCII_CHAR)]); } #[test] #[cfg(feature = "serde_test")] fn deserialize() { use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; let ascii_char = AsciiChar::from_ascii(ASCII_CHAR).unwrap(); assert_de_tokens(&ascii_char, &[Token::String(ASCII_STR)]); assert_de_tokens(&ascii_char, &[Token::Str(ASCII_STR)]); assert_de_tokens(&ascii_char, &[Token::BorrowedStr(ASCII_STR)]); assert_de_tokens_error::( &[Token::Char(UNICODE_CHAR)], "invalid value: character `é`, expected an ascii character", ); } } ascii-1.1.0/src/serialization/ascii_str.rs000064400000000000000000000044251046102023000167120ustar 00000000000000use std::fmt; use serde::de::{Error, Unexpected, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use ascii_str::AsciiStr; impl Serialize for AsciiStr { #[inline] fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(self.as_str()) } } struct AsciiStrVisitor; impl<'a> Visitor<'a> for AsciiStrVisitor { type Value = &'a AsciiStr; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("a borrowed ascii string") } fn visit_borrowed_str(self, v: &'a str) -> Result { AsciiStr::from_ascii(v.as_bytes()) .map_err(|_| Error::invalid_value(Unexpected::Str(v), &self)) } fn visit_borrowed_bytes(self, v: &'a [u8]) -> Result { AsciiStr::from_ascii(v).map_err(|_| Error::invalid_value(Unexpected::Bytes(v), &self)) } } impl<'de: 'a, 'a> Deserialize<'de> for &'a AsciiStr { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(AsciiStrVisitor) } } #[cfg(test)] mod tests { use super::*; #[cfg(feature = "serde_test")] const ASCII: &str = "Francais"; #[cfg(feature = "serde_test")] const UNICODE: &str = "Français"; #[test] fn basic() { fn assert_serialize() {} fn assert_deserialize<'de, T: Deserialize<'de>>() {} assert_serialize::<&AsciiStr>(); assert_deserialize::<&AsciiStr>(); } #[test] #[cfg(feature = "serde_test")] fn serialize() { use serde_test::{assert_tokens, Token}; let ascii_str = AsciiStr::from_ascii(ASCII).unwrap(); assert_tokens(&ascii_str, &[Token::BorrowedStr(ASCII)]); } #[test] #[cfg(feature = "serde_test")] fn deserialize() { use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; let ascii_str = AsciiStr::from_ascii(ASCII).unwrap(); assert_de_tokens(&ascii_str, &[Token::BorrowedBytes(ASCII.as_bytes())]); assert_de_tokens_error::<&AsciiStr>( &[Token::BorrowedStr(UNICODE)], "invalid value: string \"Français\", expected a borrowed ascii string", ); } } ascii-1.1.0/src/serialization/ascii_string.rs000064400000000000000000000114461046102023000174110ustar 00000000000000use std::fmt; use serde::de::{Error, Unexpected, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use ascii_str::AsciiStr; use ascii_string::AsciiString; impl Serialize for AsciiString { #[inline] fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(self.as_str()) } } struct AsciiStringVisitor; impl<'de> Visitor<'de> for AsciiStringVisitor { type Value = AsciiString; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("an ascii string") } fn visit_str(self, v: &str) -> Result { AsciiString::from_ascii(v).map_err(|_| Error::invalid_value(Unexpected::Str(v), &self)) } fn visit_string(self, v: String) -> Result { AsciiString::from_ascii(v.as_bytes()) .map_err(|_| Error::invalid_value(Unexpected::Str(&v), &self)) } fn visit_bytes(self, v: &[u8]) -> Result { AsciiString::from_ascii(v).map_err(|_| Error::invalid_value(Unexpected::Bytes(v), &self)) } fn visit_byte_buf(self, v: Vec) -> Result { AsciiString::from_ascii(v.as_slice()) .map_err(|_| Error::invalid_value(Unexpected::Bytes(&v), &self)) } } struct AsciiStringInPlaceVisitor<'a>(&'a mut AsciiString); impl<'a, 'de> Visitor<'de> for AsciiStringInPlaceVisitor<'a> { type Value = (); fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("an ascii string") } fn visit_str(self, v: &str) -> Result { let ascii_str = match AsciiStr::from_ascii(v.as_bytes()) { Ok(ascii_str) => ascii_str, Err(_) => return Err(Error::invalid_value(Unexpected::Str(v), &self)), }; self.0.clear(); self.0.push_str(ascii_str); Ok(()) } fn visit_string(self, v: String) -> Result { let ascii_string = match AsciiString::from_ascii(v.as_bytes()) { Ok(ascii_string) => ascii_string, Err(_) => return Err(Error::invalid_value(Unexpected::Str(&v), &self)), }; *self.0 = ascii_string; Ok(()) } fn visit_bytes(self, v: &[u8]) -> Result { let ascii_str = match AsciiStr::from_ascii(v) { Ok(ascii_str) => ascii_str, Err(_) => return Err(Error::invalid_value(Unexpected::Bytes(v), &self)), }; self.0.clear(); self.0.push_str(ascii_str); Ok(()) } fn visit_byte_buf(self, v: Vec) -> Result { let ascii_string = match AsciiString::from_ascii(v.as_slice()) { Ok(ascii_string) => ascii_string, Err(_) => return Err(Error::invalid_value(Unexpected::Bytes(&v), &self)), }; *self.0 = ascii_string; Ok(()) } } impl<'de> Deserialize<'de> for AsciiString { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_string(AsciiStringVisitor) } fn deserialize_in_place(deserializer: D, place: &mut Self) -> Result<(), D::Error> where D: Deserializer<'de>, { deserializer.deserialize_string(AsciiStringInPlaceVisitor(place)) } } #[cfg(test)] mod tests { use super::*; #[cfg(feature = "serde_test")] const ASCII: &str = "Francais"; #[cfg(feature = "serde_test")] const UNICODE: &str = "Français"; #[test] fn basic() { fn assert_serialize() {} fn assert_deserialize<'de, T: Deserialize<'de>>() {} assert_serialize::(); assert_deserialize::(); } #[test] #[cfg(feature = "serde_test")] fn serialize() { use serde_test::{assert_tokens, Token}; let ascii_string = AsciiString::from_ascii(ASCII).unwrap(); assert_tokens(&ascii_string, &[Token::String(ASCII)]); assert_tokens(&ascii_string, &[Token::Str(ASCII)]); assert_tokens(&ascii_string, &[Token::BorrowedStr(ASCII)]); } #[test] #[cfg(feature = "serde_test")] fn deserialize() { use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; let ascii_string = AsciiString::from_ascii(ASCII).unwrap(); assert_de_tokens(&ascii_string, &[Token::Bytes(ASCII.as_bytes())]); assert_de_tokens(&ascii_string, &[Token::BorrowedBytes(ASCII.as_bytes())]); assert_de_tokens(&ascii_string, &[Token::ByteBuf(ASCII.as_bytes())]); assert_de_tokens_error::( &[Token::String(UNICODE)], "invalid value: string \"Français\", expected an ascii string", ); } } ascii-1.1.0/src/serialization/mod.rs000064400000000000000000000000611046102023000155010ustar 00000000000000mod ascii_char; mod ascii_str; mod ascii_string; ascii-1.1.0/tests.rs000064400000000000000000000101661046102023000124270ustar 00000000000000extern crate ascii; use ascii::{AsAsciiStr, AsciiChar, AsciiStr}; #[cfg(feature = "std")] use ascii::{AsciiString, IntoAsciiString}; #[test] #[cfg(feature = "std")] fn ascii_vec() { let test = b"( ;"; let a = AsciiStr::from_ascii(test).unwrap(); assert_eq!(test.as_ascii_str(), Ok(a)); assert_eq!("( ;".as_ascii_str(), Ok(a)); let v = test.to_vec(); assert_eq!(v.as_ascii_str(), Ok(a)); assert_eq!("( ;".to_string().as_ascii_str(), Ok(a)); } #[test] fn to_ascii() { assert!("zoä华".as_ascii_str().is_err()); assert!([127_u8, 128, 255].as_ascii_str().is_err()); let arr = [AsciiChar::ParenOpen, AsciiChar::Space, AsciiChar::Semicolon]; let a: &AsciiStr = (&arr[..]).into(); assert_eq!(b"( ;".as_ascii_str(), Ok(a)); assert_eq!("( ;".as_ascii_str(), Ok(a)); } #[test] #[cfg(feature = "std")] fn into_ascii() { let arr = [AsciiChar::ParenOpen, AsciiChar::Space, AsciiChar::Semicolon]; let v = AsciiString::from(arr.to_vec()); assert_eq!(b"( ;".to_vec().into_ascii_string(), Ok(v.clone())); assert_eq!("( ;".to_string().into_ascii_string(), Ok(v.clone())); assert_eq!(b"( ;", AsRef::<[u8]>::as_ref(&v)); let err = "zoä华".to_string().into_ascii_string().unwrap_err(); assert_eq!(Err(err.ascii_error()), "zoä华".as_ascii_str()); assert_eq!(err.into_source(), "zoä华"); let err = vec![127, 128, 255].into_ascii_string().unwrap_err(); assert_eq!(Err(err.ascii_error()), [127, 128, 255].as_ascii_str()); assert_eq!(err.into_source(), &[127, 128, 255]); } #[test] #[cfg(feature = "std")] fn compare_ascii_string_ascii_str() { let v = b"abc"; let ascii_string = AsciiString::from_ascii(&v[..]).unwrap(); let ascii_str = AsciiStr::from_ascii(v).unwrap(); assert!(ascii_string == ascii_str); assert!(ascii_str == ascii_string); } #[test] #[cfg(feature = "std")] fn compare_ascii_string_string() { let v = b"abc"; let string = String::from_utf8(v.to_vec()).unwrap(); let ascii_string = AsciiString::from_ascii(&v[..]).unwrap(); assert!(string == ascii_string); assert!(ascii_string == string); } #[test] #[cfg(feature = "std")] fn compare_ascii_str_string() { let v = b"abc"; let string = String::from_utf8(v.to_vec()).unwrap(); let ascii_str = AsciiStr::from_ascii(&v[..]).unwrap(); assert!(string == ascii_str); assert!(ascii_str == string); } #[test] #[cfg(feature = "std")] fn compare_ascii_string_str() { let v = b"abc"; let sstr = ::std::str::from_utf8(v).unwrap(); let ascii_string = AsciiString::from_ascii(&v[..]).unwrap(); assert!(sstr == ascii_string); assert!(ascii_string == sstr); } #[test] fn compare_ascii_str_str() { let v = b"abc"; let sstr = ::std::str::from_utf8(v).unwrap(); let ascii_str = AsciiStr::from_ascii(v).unwrap(); assert!(sstr == ascii_str); assert!(ascii_str == sstr); } #[test] #[allow(clippy::redundant_slicing)] fn compare_ascii_str_slice() { let b = b"abc".as_ascii_str().unwrap(); let c = b"ab".as_ascii_str().unwrap(); assert_eq!(&b[..2], &c[..]); assert_eq!(c[1].as_char(), 'b'); } #[test] #[cfg(feature = "std")] fn compare_ascii_string_slice() { let b = AsciiString::from_ascii("abc").unwrap(); let c = AsciiString::from_ascii("ab").unwrap(); assert_eq!(&b[..2], &c[..]); assert_eq!(c[1].as_char(), 'b'); } #[test] #[cfg(feature = "std")] fn extend_from_iterator() { use std::borrow::Cow; let abc = "abc".as_ascii_str().unwrap(); let mut s = abc.chars().collect::(); assert_eq!(s, abc); s.extend(abc); assert_eq!(s, "abcabc"); let lines = "one\ntwo\nthree".as_ascii_str().unwrap().lines(); s.extend(lines); assert_eq!(s, "abcabconetwothree"); let cows = "ASCII Ascii ascii" .as_ascii_str() .unwrap() .split(AsciiChar::Space) .map(|case| { if case.chars().all(AsciiChar::is_uppercase) { Cow::from(case) } else { Cow::from(case.to_ascii_uppercase()) } }); s.extend(cows); s.extend(&[AsciiChar::LineFeed]); assert_eq!(s, "abcabconetwothreeASCIIASCIIASCII\n"); }