diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 06c6a58..e651d02 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -12,7 +12,7 @@ jobs: strategy: max-parallel: 2 matrix: - rust: [stable, beta, nightly, 1.46.0, 1.51.0] + rust: [stable, beta, nightly, 1.46.0, 1.51.0, 1.57.0] steps: - uses: actions/checkout@v2 @@ -25,7 +25,7 @@ jobs: cargo test cd "${{github.workspace}}/const_format/" - cargo test --features "testing" + cargo test --features "__test" - uses: actions/checkout@v2 - name: ci-stable @@ -34,7 +34,17 @@ jobs: cargo update cd "${{github.workspace}}/const_format/" - cargo test --features "testing const_generics" + cargo test --features "__test const_generics" + + - uses: actions/checkout@v2 + - name: ci-stable + if: ${{ matrix.rust == '1.57.0' }} + run: | + cargo update + + cd "${{github.workspace}}/const_format/" + cargo test --features "__test const_generics" + cargo test --features "__test assertcp" - uses: actions/checkout@v2 @@ -50,14 +60,16 @@ jobs: cd "${{github.workspace}}/const_format/" - cargo test --features "testing" - cargo test --features "testing assertcp" - cargo test --features "testing fmt" - cargo test --features "testing assertc" - cargo test --features "testing derive" - cargo test --features "testing constant_time_as_str" - cargo test --features "testing derive constant_time_as_str" - cargo test --features "testing derive constant_time_as_str assertc" + cargo test --features "__test" + cargo test --features "__test more_str_macros" + cargo test --features "__test assertcp" + cargo test --features "__test fmt" + cargo test --features "__test assertc" + cargo test --features "__test derive" + cargo test --features "__test constant_time_as_str" + cargo test --features "__test derive constant_time_as_str" + cargo test --features "__test derive constant_time_as_str assertc" + cargo test --features "__test derive constant_time_as_str assertc more_str_macros" MIRI_NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri) MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-check-number-validity -Zmiri-symbolic-alignment-check" @@ -71,5 +83,5 @@ jobs: cargo clean - cargo miri test --tests --features "testing derive fmt assertc" - cargo miri test --features "testing derive fmt constant_time_as_str assertc" + cargo miri test --tests --features "__test derive fmt assertc" + cargo miri test --features "__test derive fmt constant_time_as_str assertc" diff --git a/Changelog.md b/Changelog.md index 9f3df3d..df6a002 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,12 +2,20 @@ This is the changelog,summarising changes in each version(some minor changes may # 0.2 +### 0.2.26 + +Added `"more_str_macros"` crate feature. + +Added `str_split` macro, conditional on the `"more_str_macros"` feature. + +Added `char` pattern support to `str_replace`. + ### 0.2.25 Fixed the `clippy::double_parens` (false positive) warning by encoding the `&'static str` type annotation some other way. -Made `SplicedStr`, `Formatting`, `NumberFormatting`, +Made `SplicedStr`, `Formatting`, and `NumberFormatting` derive `Eq` . ### 0.2.24 diff --git a/README.md b/README.md index e3915c0..6de0680 100644 --- a/README.md +++ b/README.md @@ -289,6 +289,9 @@ Enables the macros listed in the [Rust 1.51.0](#rust-1510) section. Also changes the the implementation of the [`concatcp`] and [`formatcp`] macros to use const generics. +- "more_str_macros": Requires Rust nightly, implies the "const_generics" feature. +Enables the [`str_split`] macro. + # No-std support `const_format` is `#![no_std]`, it can be used anywhere Rust can be used. @@ -355,4 +358,6 @@ need to be explicitly enabled with cargo features. [`str_replace`]: https://docs.rs/const_format/0.2.*/const_format/macro.str_replace.html +[`str_split`]: https://docs.rs/const_format/0.2.*/const_format/macro.str_split.html + [`str::replace`]: https://doc.rust-lang.org/std/primitive.str.html#method.replace diff --git a/const_format/Cargo.toml b/const_format/Cargo.toml index f8e4647..e0ef83f 100644 --- a/const_format/Cargo.toml +++ b/const_format/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "const_format" -version = "0.2.25" +version = "0.2.26" authors = ["rodrimati1992 "] edition = "2018" license = "Zlib" @@ -31,22 +31,40 @@ assert = ["assertc"] assertc = ["fmt", "assertcp"] assertcp = ["const_generics"] constant_time_as_str = ["fmt"] +more_str_macros = ["konst", "konst/constant_time_slice", "const_generics"] -# "private" features -debug = ["const_format_proc_macros/debug"] -testing = [] -only_new_tests = ["testing"] -docsrs = [] -all = ["fmt", "derive", "constant_time_as_str", "nightly_const_generics", "assert", "docsrs"] +# enables all the features, requires (potentially) the latest nightly +all = [ + "fmt", + "derive", + "constant_time_as_str", + "more_str_macros", + "nightly_const_generics", + "assert", +] + +############## +### "private" features + +# +__debug = ["const_format_proc_macros/debug"] +__test = [] +__only_new_tests = ["__test"] +__docsrs = [] [dependencies.const_format_proc_macros] version = "=0.2.22" path = "../const_format_proc_macros" +[dependencies.konst] +version = "0.2.13" +default-features = false +optional = true + [dev-dependencies] fastrand = {version = "1.3.5", default_features = false} arrayvec = {version = "0.5.1", default_features = false} [package.metadata.docs.rs] -features = ["all"] +features = ["all", "__docsrs"] diff --git a/const_format/src/__str_methods.rs b/const_format/src/__str_methods.rs index 810419a..ff50323 100644 --- a/const_format/src/__str_methods.rs +++ b/const_format/src/__str_methods.rs @@ -13,6 +13,18 @@ pub use str_splice::{DecomposedString, SplicedStr, StrSplceArgsConv, StrSpliceAr mod str_indexing; pub use str_indexing::{IndexValidity, StrIndexArgs, StrIndexArgsConv}; +#[cfg(feature = "more_str_macros")] +mod str_split; + +#[cfg(feature = "more_str_macros")] +pub use str_split::{SplitInput, SplitInputConv}; + +#[cfg(feature = "const_generics")] +mod pattern; + +#[cfg(feature = "const_generics")] +use pattern::{Pattern, PatternCtor, PatternNorm}; + #[cfg(feature = "const_generics")] mod ascii_byte { #[derive(Copy, Clone)] diff --git a/const_format/src/__str_methods/pattern.rs b/const_format/src/__str_methods/pattern.rs new file mode 100644 index 0000000..1b79edd --- /dev/null +++ b/const_format/src/__str_methods/pattern.rs @@ -0,0 +1,52 @@ +use super::AsciiByte; + +pub(crate) struct PatternCtor(pub(crate) T); + +impl PatternCtor { + pub(crate) const fn conv(self) -> Pattern { + Pattern::AsciiByte(AsciiByte::new(self.0)) + } +} + +impl PatternCtor<&'static str> { + pub(crate) const fn conv(self) -> Pattern { + if let [b @ 0..=127] = *self.0.as_bytes() { + Pattern::AsciiByte(AsciiByte::new(b)) + } else { + Pattern::Str(self.0) + } + } +} + +impl PatternCtor { + pub(crate) const fn conv(self) -> Pattern { + let code = self.0 as u32; + if let c @ 0..=127 = code { + Pattern::AsciiByte(AsciiByte::new(c as u8)) + } else { + Pattern::Char(crate::char_encoding::char_to_display(self.0)) + } + } +} + +#[derive(Copy, Clone)] +pub(crate) enum Pattern { + AsciiByte(AsciiByte), + Str(&'static str), + Char(crate::char_encoding::FmtChar), +} + +pub(crate) enum PatternNorm<'a> { + AsciiByte(AsciiByte), + Str(&'a [u8]), +} + +impl Pattern { + pub(crate) const fn normalize(&self) -> PatternNorm<'_> { + match self { + Pattern::AsciiByte(ab) => PatternNorm::AsciiByte(*ab), + Pattern::Str(str) => PatternNorm::Str(str.as_bytes()), + Pattern::Char(char) => PatternNorm::Str(char.as_bytes()), + } + } +} diff --git a/const_format/src/__str_methods/str_replace.rs b/const_format/src/__str_methods/str_replace.rs index 3858ce0..dbd75e8 100644 --- a/const_format/src/__str_methods/str_replace.rs +++ b/const_format/src/__str_methods/str_replace.rs @@ -1,39 +1,31 @@ -use super::{bytes_find, AsciiByte}; +use super::{bytes_find, Pattern, PatternCtor, PatternNorm}; pub struct ReplaceInputConv(pub &'static str, pub T, pub &'static str); -impl ReplaceInputConv { - pub const fn conv(self) -> ReplaceInput { - ReplaceInput { - str: self.0, - pattern: ReplacePattern::AsciiByte(AsciiByte::new(self.1)), - replaced_with: self.2, +macro_rules! ctor { + ($ty:ty) => { + impl ReplaceInputConv<$ty> { + pub const fn conv(self) -> ReplaceInput { + ReplaceInput { + str: self.0, + pattern: PatternCtor(self.1).conv(), + replaced_with: self.2, + } + } } - } + }; } -impl ReplaceInputConv<&'static str> { - pub const fn conv(self) -> ReplaceInput { - ReplaceInput { - str: self.0, - pattern: ReplacePattern::Str(self.1), - replaced_with: self.2, - } - } -} +ctor! {u8} +ctor! {&'static str} +ctor! {char} pub struct ReplaceInput { str: &'static str, - pattern: ReplacePattern, + pattern: Pattern, replaced_with: &'static str, } -#[derive(Copy, Clone)] -pub enum ReplacePattern { - AsciiByte(AsciiByte), - Str(&'static str), -} - impl ReplaceInput { pub const fn replace_length(&self) -> usize { str_replace_length(self.str, self.pattern, self.replaced_with) @@ -43,24 +35,23 @@ impl ReplaceInput { } } -const fn str_replace_length(inp: &str, r: ReplacePattern, replaced_with: &str) -> usize { +const fn str_replace_length(inp: &str, r: Pattern, replaced_with: &str) -> usize { let inp = inp.as_bytes(); let replaced_len = replaced_with.len(); let mut out_len = 0; - match r { - ReplacePattern::AsciiByte(byte) => { + match r.normalize() { + PatternNorm::AsciiByte(byte) => { let byte = byte.get(); iter_copy_slice! {b in inp => out_len += if b == byte { replaced_len } else { 1 }; } } - ReplacePattern::Str(str) => { + PatternNorm::Str(str) => { if str.is_empty() { return inp.len(); } - let str = str.as_bytes(); let str_len = str.len(); let mut i = 0; while let Some(next_match) = bytes_find(inp, str, i) { @@ -74,7 +65,7 @@ const fn str_replace_length(inp: &str, r: ReplacePattern, replaced_with: &str) - out_len } -const fn str_replace(inp: &str, r: ReplacePattern, replaced_with: &str) -> [u8; L] { +const fn str_replace(inp: &str, r: Pattern, replaced_with: &str) -> [u8; L] { let inp = inp.as_bytes(); let replaced_with_bytes = replaced_with.as_bytes(); @@ -96,8 +87,8 @@ const fn str_replace(inp: &str, r: ReplacePattern, replaced_with }; } - match r { - ReplacePattern::AsciiByte(byte) => { + match r.normalize() { + PatternNorm::AsciiByte(byte) => { let byte = byte.get(); iter_copy_slice! {b in inp => if b == byte { @@ -107,14 +98,13 @@ const fn str_replace(inp: &str, r: ReplacePattern, replaced_with } } } - ReplacePattern::Str(str) => { + PatternNorm::Str(str) => { if str.is_empty() { iter_copy_slice! {b in inp => write_byte!(b); } return out; } - let str = str.as_bytes(); let str_len = str.len(); let mut i = 0; while let Some(next_match) = bytes_find(inp, str, i) { diff --git a/const_format/src/__str_methods/str_split.rs b/const_format/src/__str_methods/str_split.rs new file mode 100644 index 0000000..6ce76c5 --- /dev/null +++ b/const_format/src/__str_methods/str_split.rs @@ -0,0 +1,161 @@ +use super::{Pattern, PatternCtor, PatternNorm}; + +use konst::slice::{bytes_find, bytes_find_skip}; + +pub struct SplitInputConv(pub &'static str, pub T); + +macro_rules! ctor { + ($ty:ty) => { + impl SplitInputConv<$ty> { + pub const fn conv(self) -> SplitInput { + SplitInput { + str: self.0, + pattern: PatternCtor(self.1).conv(), + length: usize::MAX, + } + .compute_length() + } + } + }; +} + +ctor! {u8} +ctor! {&'static str} +ctor! {char} + +#[derive(Copy, Clone)] +pub struct SplitInput { + str: &'static str, + pattern: Pattern, + length: usize, +} + +impl SplitInput { + const fn compute_length(mut self) -> Self { + self.length = count_splits(self); + self + } + + pub const fn split_it(self) -> [&'static str; LEN] { + split_it(self) + } + + pub const fn length(&self) -> usize { + self.length + } +} + +pub const fn count_splits( + SplitInput { + mut str, pattern, .. + }: SplitInput, +) -> usize { + let mut count = 1; + + match pattern.normalize() { + PatternNorm::AsciiByte(ascii_c) => { + let mut bytes = str.as_bytes(); + let ascii_c = ascii_c.get(); + + while let [byte, rem @ ..] = bytes { + bytes = rem; + + if *byte == ascii_c { + count += 1; + } + } + } + PatternNorm::Str(str_pat) => { + if str_pat.is_empty() { + let mut char_i = 0; + count += 1; + while let Some(next) = find_next_char_boundary(str, char_i) { + char_i = next; + count += 1; + } + } else { + let mut str = str.as_bytes(); + while let Some(next) = bytes_find_skip(str, str_pat) { + str = next; + count += 1; + } + } + } + } + + count +} + +const fn find_u8(mut slice: &[u8], byte: u8) -> Option { + let mut i = 0; + + while let [b, ref rem @ ..] = *slice { + if byte == b { + return Some(i); + } + slice = rem; + i += 1; + } + None +} + +const fn find_next_char_boundary(str: &str, mut index: usize) -> Option { + if index == str.len() { + None + } else { + loop { + index += 1; + if index == str.len() || (str.as_bytes()[index] as i8) >= -0x40 { + break Some(index); + } + } + } +} + +pub const fn split_it(args: SplitInput) -> [&'static str; LEN] { + let SplitInput { + mut str, + pattern, + length: _, + } = args; + + let mut out = [""; LEN]; + let mut out_i = 0; + + macro_rules! write_out { + ($string:expr) => { + out[out_i] = $string; + out_i += 1; + }; + } + + match pattern.normalize() { + PatternNorm::AsciiByte(ascii_c) => { + let ascii_c = ascii_c.get(); + + while let Some(found_at) = find_u8(str.as_bytes(), ascii_c) { + write_out! {konst::string::str_up_to(str, found_at)} + str = konst::string::str_from(str, found_at + 1); + } + } + PatternNorm::Str(str_pat) => { + if str_pat.is_empty() { + out_i += 1; + while let Some(next) = find_next_char_boundary(str, 0) { + write_out! {konst::string::str_up_to(str, next)} + str = konst::string::str_from(str, next); + } + } else { + while let Some(found_at) = bytes_find(str.as_bytes(), str_pat, 0) { + write_out! {konst::string::str_up_to(str, found_at)} + str = konst::string::str_from(str, found_at + str_pat.len()); + } + } + } + } + + write_out! {str} + + assert!(out_i == LEN); + out +} diff --git a/const_format/src/char_encoding.rs b/const_format/src/char_encoding.rs index 0d0115d..fe0d381 100644 --- a/const_format/src/char_encoding.rs +++ b/const_format/src/char_encoding.rs @@ -97,9 +97,39 @@ impl FmtChar { self.len as usize } - #[cfg(test)] - fn as_bytes(&self) -> &[u8] { - &self.encoded[..self.len()] + pub(crate) const fn as_bytes(&self) -> &[u8] { + #[cfg(not(feature = "more_str_macros"))] + { + match self.len() { + 1 => { + let [ret @ .., _, _, _, _, _] = &self.encoded; + ret + } + 2 => { + let [ret @ .., _, _, _, _] = &self.encoded; + ret + } + 3 => { + let [ret @ .., _, _, _] = &self.encoded; + ret + } + 4 => { + let [ret @ .., _, _] = &self.encoded; + ret + } + 5 => { + let [ret @ .., _] = &self.encoded; + ret + } + 6 => &self.encoded, + x => [/*bug WTF*/][x], + } + } + + #[cfg(feature = "more_str_macros")] + { + ::konst::slice::slice_up_to(&self.encoded, self.len()) + } } } diff --git a/const_format/src/const_debug_derive.rs b/const_format/src/const_debug_derive.rs index ec2e32f..b70883d 100644 --- a/const_format/src/const_debug_derive.rs +++ b/const_format/src/const_debug_derive.rs @@ -354,6 +354,6 @@ /// # } /// ``` /// -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "derive")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "derive")))] #[cfg(feature = "derive")] pub use const_format_proc_macros::ConstDebug; diff --git a/const_format/src/lib.rs b/const_format/src/lib.rs index 3ecb045..389cfe5 100644 --- a/const_format/src/lib.rs +++ b/const_format/src/lib.rs @@ -277,6 +277,8 @@ //! Also changes the the implementation of the [`concatcp`] and [`formatcp`] //! macros to use const generics. //! +//! - "more_str_macros": Requires Rust nightly, implies the "const_generics" feature. +//! Enables the [`str_split`] macro. //! //! //! # No-std support @@ -346,12 +348,14 @@ //! //! [`str_replace`]: ./macro.str_replace.html //! +//! [`str_split`]: ./macro.str_split.html +//! //! [`str::replace`]: https://doc.rust-lang.org/std/primitive.str.html#method.replace //! #![no_std] #![cfg_attr(feature = "fmt", feature(const_mut_refs))] #![cfg_attr(feature = "constant_time_as_str", feature(const_slice_from_raw_parts))] -#![cfg_attr(feature = "docsrs", feature(doc_cfg))] +#![cfg_attr(feature = "__docsrs", feature(doc_cfg))] #![deny(rust_2018_idioms)] // This lint is silly #![allow(clippy::blacklisted_name)] @@ -387,7 +391,7 @@ mod pargument; #[cfg(feature = "const_generics")] mod const_generic_concatcp; -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] pub mod utils; @@ -398,22 +402,22 @@ mod slice_cmp; #[doc(hidden)] pub mod __hidden_utils; -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] pub mod for_examples; -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] pub mod marker_traits; -#[cfg(feature = "testing")] +#[cfg(feature = "__test")] pub mod test_utils; -#[cfg(feature = "testing")] +#[cfg(feature = "__test")] #[allow(missing_docs)] pub mod doctests; -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] pub mod fmt; @@ -421,7 +425,7 @@ pub mod fmt; #[doc(hidden)] pub mod msg; -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg_attr(not(feature = "fmt"), doc(hidden))] pub mod wrapper_types; @@ -434,7 +438,7 @@ pub mod __str_methods; pub use __str_methods::SplicedStr; -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "const_generics")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "const_generics")))] #[cfg(feature = "const_generics")] pub use __ascii_case_conv::Case; @@ -509,5 +513,5 @@ pub mod pmr { } } -#[cfg(all(test, not(feature = "testing")))] +#[cfg(all(test, not(feature = "__test")))] compile_error! { "tests must be run with the \"testing\" feature" } diff --git a/const_format/src/macros.rs b/const_format/src/macros.rs index 28f2133..cc156f6 100644 --- a/const_format/src/macros.rs +++ b/const_format/src/macros.rs @@ -45,7 +45,7 @@ mod str_methods; /// /// # Ok::<(), Error>(()) /// ``` -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] #[macro_export] macro_rules! try_ { @@ -81,7 +81,7 @@ macro_rules! try_ { /// assert_eq!(TEXT, "foo bar baz") /// /// ``` -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] #[macro_export] macro_rules! unwrap { @@ -130,7 +130,7 @@ macro_rules! unwrap { /// assert_eq!(BAR.as_str(), "bar"); /// /// ``` -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] #[macro_export] macro_rules! unwrap_or_else { @@ -205,7 +205,7 @@ macro_rules! unwrap_or_else { /// ``` /// /// [`PWrapper`]: ./struct.PWrapper.html -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] #[macro_export] macro_rules! coerce_to_fmt { @@ -266,7 +266,7 @@ macro_rules! coerce_to_fmt { /// } /// ``` /// -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] #[deprecated(since = "0.2.19", note = "Use `StrWriter::as_str_alt` instead")] #[macro_export] @@ -280,7 +280,7 @@ macro_rules! strwriter_as_str { }; } -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] macro_rules! conditionally_const { ( @@ -302,7 +302,7 @@ macro_rules! conditionally_const { ) } -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] macro_rules! std_kind_impl { ( diff --git a/const_format/src/macros/assertions/assertc_macros.rs b/const_format/src/macros/assertions/assertc_macros.rs index 81cdbe0..9d9b894 100644 --- a/const_format/src/macros/assertions/assertc_macros.rs +++ b/const_format/src/macros/assertions/assertc_macros.rs @@ -105,7 +105,7 @@ with_shared_docs! { /// /// ``` /// - #[cfg_attr(feature = "docsrs", doc(cfg(feature = "assertc")))] + #[cfg_attr(feature = "__docsrs", doc(cfg(feature = "assertc")))] #[macro_export] macro_rules! assertc { ($($parameters:tt)*) => ( @@ -261,7 +261,7 @@ assert_eq_docs! { /// /// ``` /// - #[cfg_attr(feature = "docsrs", doc(cfg(feature = "assertc")))] + #[cfg_attr(feature = "__docsrs", doc(cfg(feature = "assertc")))] #[macro_export] macro_rules! assertc_eq { ($($parameters:tt)*) => ( @@ -389,7 +389,7 @@ assert_eq_docs! { /// /// ``` /// - #[cfg_attr(feature = "docsrs", doc(cfg(feature = "assertc")))] + #[cfg_attr(feature = "__docsrs", doc(cfg(feature = "assertc")))] #[macro_export] macro_rules! assertc_ne { ($($parameters:tt)*) => ( diff --git a/const_format/src/macros/assertions/assertcp_macros.rs b/const_format/src/macros/assertions/assertcp_macros.rs index 3803b7c..d4c6e8a 100644 --- a/const_format/src/macros/assertions/assertcp_macros.rs +++ b/const_format/src/macros/assertions/assertcp_macros.rs @@ -102,7 +102,7 @@ with_shared_docs! { /// /// ``` /// - #[cfg_attr(feature = "docsrs", doc(cfg(feature = "assertcp")))] + #[cfg_attr(feature = "__docsrs", doc(cfg(feature = "assertcp")))] #[macro_export] macro_rules! assertcp { ($($parameters:tt)*) => ( @@ -222,7 +222,7 @@ with_shared_docs! { /// /// ``` /// - #[cfg_attr(feature = "docsrs", doc(cfg(feature = "assertcp")))] + #[cfg_attr(feature = "__docsrs", doc(cfg(feature = "assertcp")))] #[macro_export] macro_rules! assertcp_eq { ($($parameters:tt)*) => ( @@ -283,7 +283,7 @@ with_shared_docs! { /// ', src/macros/assertions/assertcp_macros.rs:7:14 /// ``` /// - #[cfg_attr(feature = "docsrs", doc(cfg(feature = "assertcp")))] + #[cfg_attr(feature = "__docsrs", doc(cfg(feature = "assertcp")))] #[macro_export] macro_rules! assertcp_ne { ($($parameters:tt)*) => ( diff --git a/const_format/src/macros/call_debug_fmt.rs b/const_format/src/macros/call_debug_fmt.rs index 839315c..af9215f 100644 --- a/const_format/src/macros/call_debug_fmt.rs +++ b/const_format/src/macros/call_debug_fmt.rs @@ -125,7 +125,7 @@ /// # Ok::<(), const_format::Error>(()) /// ``` /// -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[macro_export] macro_rules! call_debug_fmt { (array, $expr:expr, $formatter:expr $(,)* ) => {{ diff --git a/const_format/src/macros/constructors.rs b/const_format/src/macros/constructors.rs index ed33dc6..be33d7d 100644 --- a/const_format/src/macros/constructors.rs +++ b/const_format/src/macros/constructors.rs @@ -31,7 +31,7 @@ /// /// [`AsciiStr`]: ./struct.AsciiStr.html /// -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] #[macro_export] macro_rules! ascii_str { diff --git a/const_format/src/macros/fmt_macros.rs b/const_format/src/macros/fmt_macros.rs index 13b9743..0dd4379 100644 --- a/const_format/src/macros/fmt_macros.rs +++ b/const_format/src/macros/fmt_macros.rs @@ -324,7 +324,7 @@ macro_rules! formatcp { /// /// [`FormatMarker`]: ./marker_traits/trait.FormatMarker.html /// -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] #[macro_export] macro_rules! concatc { @@ -496,7 +496,7 @@ macro_rules! __concatc_inner { /// /// #[macro_export] -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] macro_rules! formatc { ($format_string:expr $( $(, $expr:expr )+ )? $(,)? ) => ( @@ -696,7 +696,7 @@ macro_rules! formatc { /// /// #[macro_export] -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[cfg(feature = "fmt")] macro_rules! writec { ( $writer:expr, $format_string:expr $( $(, $expr:expr )+ )? $(,)? ) => ({ diff --git a/const_format/src/macros/impl_fmt.rs b/const_format/src/macros/impl_fmt.rs index 9bcc9b6..a3fbda1 100644 --- a/const_format/src/macros/impl_fmt.rs +++ b/const_format/src/macros/impl_fmt.rs @@ -125,7 +125,7 @@ /// /// [`FormatMarker`]: ./marker_traits/trait.FormatMarker.html /// -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[macro_export] macro_rules! impl_fmt { ( diff --git a/const_format/src/macros/map_ascii_case.rs b/const_format/src/macros/map_ascii_case.rs index e0dc112..e5495db 100644 --- a/const_format/src/macros/map_ascii_case.rs +++ b/const_format/src/macros/map_ascii_case.rs @@ -73,7 +73,7 @@ /// /// /// ``` -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "const_generics")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "const_generics")))] #[macro_export] macro_rules! map_ascii_case { ($case:expr, $str:expr) => {{ diff --git a/const_format/src/macros/str_methods.rs b/const_format/src/macros/str_methods.rs index ce1c2d5..f8825da 100644 --- a/const_format/src/macros/str_methods.rs +++ b/const_format/src/macros/str_methods.rs @@ -17,6 +17,8 @@ /// /// - `&'static str` /// +/// - `char` +/// /// - `u8`: required to be ascii (`0` up to `127` inclusive). /// /// # Example @@ -31,6 +33,12 @@ /// "The eeencredeeeble shreeenkeeeng man.", /// ); /// +/// // Passing a char pattern +/// assert_eq!( +/// str_replace!("The incredible shrinking man.", ' ', "---"), +/// "The---incredible---shrinking---man.", +/// ); +/// /// // Passing an ascii u8 pattern. /// assert_eq!( /// str_replace!("The incredible shrinking man.", b'i', "eee"), @@ -55,7 +63,7 @@ /// [`str::replace`]: https://doc.rust-lang.org/std/primitive.str.html#method.replace #[macro_export] #[cfg(feature = "const_generics")] -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "const_generics")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "const_generics")))] macro_rules! str_replace { ($input:expr, $pattern:expr, $replace_with:expr $(,)*) => {{ const ARGS_OSRCTFL4A: $crate::__str_methods::ReplaceInput = @@ -102,7 +110,7 @@ macro_rules! str_replace { /// ``` /// #[cfg_attr( - feature = "testing", + feature = "__test", doc = r##" ```rust const_format::str_repeat!("hello", usize::MAX.wrapping_add(4)); @@ -202,7 +210,7 @@ macro_rules! str_repeat { /// const_format::str_splice!("foo", 0..10, ""); /// ``` #[cfg_attr( - feature = "testing", + feature = "__test", doc = r#" ```rust const_format::str_splice!("foo", 0..3, ""); @@ -341,7 +349,7 @@ macro_rules! str_splice { /// const_format::str_index!("foo", 0..10); /// ``` #[cfg_attr( - feature = "testing", + feature = "__test", doc = r#" ```rust assert_eq!(const_format::str_index!("効率的", 3..6), "率"); @@ -442,7 +450,7 @@ macro_rules! str_index { /// /// ``` #[cfg_attr( - feature = "testing", + feature = "__test", doc = r#" ```rust assert_eq!(const_format::str_get!("効率的", 3..6), Some("率")); @@ -484,3 +492,68 @@ macro_rules! str_get { } }}; } + +/// Splits `$string` (a `&'static str` constant) with `$splitter`, +/// returning an array of `&'static str`s. +/// +/// # Signature +/// +/// This macro acts like a function of this signature: +/// ```rust +/// # const LEN: usize = 0; +/// # trait Splitter {} +/// fn str_split(string: &'static str, splitter: impl Splitter) -> [&'static str; LEN] +/// # { [] } +/// ``` +/// +/// `impl Splitter` is any of these types: +/// +/// - `&'static str` +/// +/// - `char` +/// +/// - `u8`: only ascii values (0 up to 127 inclusive) are allowed +/// +/// The value of `LEN` depends on the `string` and `splitter` arguments. +/// +/// +/// # Example +/// +/// ```rust +/// use const_format::str_split; +/// +/// assert_eq!(str_split!("this is nice", ' '), ["this", "is", "nice"]); +/// +/// assert_eq!(str_split!("Hello, world!", ", "), ["Hello", "world!"]); +/// +/// // A `""` splitter outputs all chars individually (`str::split` does the same) +/// assert_eq!(str_split!("🧡BAR🧠", ""), ["", "🧡", "B", "A", "R", "🧠", ""]); +/// +/// // Splitting the string with an ascii byte +/// assert_eq!(str_split!("dash-separated-string", b'-'), ["dash", "separated", "string"]); +/// +/// { +/// const STR: &str = "foo bar baz"; +/// const SPLITTER: &str = " "; +/// +/// // both arguments to the `str_aplit` macro can be non-literal constants +/// const SPLIT: [&str; 3] = str_split!(STR, SPLITTER); +/// +/// assert_eq!(SPLIT, ["foo", "bar", "baz"]); +/// } +/// +/// ``` +#[macro_export] +#[cfg(feature = "more_str_macros")] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "more_str_macros")))] +macro_rules! str_split { + ($string:expr, $splitter:expr $(,)?) => {{ + const ARGS_OSRCTFL4A: $crate::__str_methods::SplitInput = + $crate::__str_methods::SplitInputConv($string, $splitter).conv(); + + { + const OB: [&$crate::pmr::str; ARGS_OSRCTFL4A.length()] = ARGS_OSRCTFL4A.split_it(); + OB + } + }}; +} diff --git a/const_format/src/wrapper_types/ascii_str.rs b/const_format/src/wrapper_types/ascii_str.rs index 6189910..1e4c0bf 100644 --- a/const_format/src/wrapper_types/ascii_str.rs +++ b/const_format/src/wrapper_types/ascii_str.rs @@ -36,7 +36,7 @@ use core::fmt::{self, Display}; /// /// [`ascii_str`]: ./macro.ascii_str.html /// -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct AsciiStr<'a>(&'a [u8]); diff --git a/const_format/src/wrapper_types/pwrapper.rs b/const_format/src/wrapper_types/pwrapper.rs index e3107c2..c4200e6 100644 --- a/const_format/src/wrapper_types/pwrapper.rs +++ b/const_format/src/wrapper_types/pwrapper.rs @@ -73,7 +73,7 @@ mod tests; /// [`call_debug_fmt`]: ./macro.call_debug_fmt.html /// [`writec`]: ./macro.writec.html /// -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] #[derive(Copy, Clone)] pub struct PWrapper(pub T); diff --git a/const_format/src/wrapper_types/sliced.rs b/const_format/src/wrapper_types/sliced.rs index c7414bf..915dddc 100644 --- a/const_format/src/wrapper_types/sliced.rs +++ b/const_format/src/wrapper_types/sliced.rs @@ -27,7 +27,7 @@ use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInc /// assert_eq!(formatc!("{}t", Sliced(SRC, 4..7)), "bart"); /// /// ``` -#[cfg_attr(feature = "docsrs", doc(cfg(feature = "fmt")))] +#[cfg_attr(feature = "__docsrs", doc(cfg(feature = "fmt")))] pub struct Sliced(pub T, pub R); impl_fmt! { diff --git a/const_format/tests/fmt_tests_modules.rs b/const_format/tests/fmt_tests_modules.rs index 9c682e9..93f804a 100644 --- a/const_format/tests/fmt_tests_modules.rs +++ b/const_format/tests/fmt_tests_modules.rs @@ -12,21 +12,21 @@ pub const _ASSERT_NOT_CF: [(); 13] = [(); const_format::NOT_CF]; cfmt_a::__declare_rng_ext! {} mod fmt_tests { - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod display_formatting; - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod formatted_writing; - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod formatter_methods; - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod std_impl_tests; - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod str_writer_methods; - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod str_writer_mut; } diff --git a/const_format/tests/misc_tests_modules.rs b/const_format/tests/misc_tests_modules.rs index 89bdc7d..e5226eb 100644 --- a/const_format/tests/misc_tests_modules.rs +++ b/const_format/tests/misc_tests_modules.rs @@ -9,7 +9,7 @@ pub const _ASSERT_NOT_CF: [(); 13] = [(); const_format::NOT_CF]; mod misc_tests { #[cfg(feature = "assertc")] - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod assertc_tests; mod clippy_warnings; @@ -18,36 +18,36 @@ mod misc_tests { mod assertcp_tests; #[cfg(feature = "fmt")] - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod call_debug_fmt_macro; #[cfg(feature = "fmt")] - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod concatc_macro_tests; #[cfg(feature = "derive")] - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod derive_tests; #[cfg(feature = "assertc")] - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod equality_tests; - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod formatc_macros; #[cfg(feature = "fmt")] - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod impl_fmt_macro_tests; - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod shared_cp_macro_tests; #[cfg(feature = "fmt")] - #[cfg(not(feature = "only_new_tests"))] + #[cfg(not(feature = "__only_new_tests"))] mod type_kind_coercion_macro_tests; #[cfg(feature = "fmt")] - //#[cfg(not(feature = "only_new_tests"))] + //#[cfg(not(feature = "__only_new_tests"))] mod writec_macro; } diff --git a/const_format/tests/str_methods.rs b/const_format/tests/str_methods.rs index 6f552f4..148e734 100644 --- a/const_format/tests/str_methods.rs +++ b/const_format/tests/str_methods.rs @@ -6,4 +6,7 @@ mod str_methods_modules { mod str_replace; mod str_splice; + + #[cfg(feature = "more_str_macros")] + mod str_split_tests; } diff --git a/const_format/tests/str_methods_modules/str_replace.rs b/const_format/tests/str_methods_modules/str_replace.rs index ec7dd8f..cfbf2ad 100644 --- a/const_format/tests/str_methods_modules/str_replace.rs +++ b/const_format/tests/str_methods_modules/str_replace.rs @@ -31,6 +31,46 @@ fn test_small_pattern() { assert_case! {"hequx", "qu", "XYZ", "heXYZx"} } +#[test] +fn test_char_pattern() { + { + const C: char = 'q'; + assert_eq!(C.len_utf8(), 1); + + assert_case! {"hequ", C, "XY", "heXYu"} + assert_case! {"hequx", C, "XYZ", "heXYZux"} + assert_case! {"hequq", C, "XY", "heXYuXY"} + assert_case! {"hequxq", C, "XYZ", "heXYZuxXYZ"} + } + { + const C: char = 'ñ'; + assert_eq!(C.len_utf8(), 2); + + assert_case! {"heñu", C, "XY", "heXYu"} + assert_case! {"heñux", C, "XYZ", "heXYZux"} + assert_case! {"heñuñ", C, "XY", "heXYuXY"} + assert_case! {"heñuxñ", C, "XYZ", "heXYZuxXYZ"} + } + { + const C: char = '₀'; + assert_eq!(C.len_utf8(), 3); + + assert_case! {"he₀u", C, "XY", "heXYu"} + assert_case! {"he₀ux", C, "XYZ", "heXYZux"} + assert_case! {"he₀u₀", C, "XY", "heXYuXY"} + assert_case! {"he₀ux₀", C, "XYZ", "heXYZuxXYZ"} + } + { + const C: char = '🧡'; + assert_eq!(C.len_utf8(), 4); + + assert_case! {"he🧡u", C, "XY", "heXYu"} + assert_case! {"he🧡ux", C, "XYZ", "heXYZux"} + assert_case! {"he🧡u🧡", C, "XY", "heXYuXY"} + assert_case! {"he🧡ux🧡", C, "XYZ", "heXYZuxXYZ"} + } +} + #[test] fn test_replace_overlapping() { assert_case! {"helololololol", "lol", "XY", "heXYoXYoXY"} diff --git a/const_format/tests/str_methods_modules/str_split_tests.rs b/const_format/tests/str_methods_modules/str_split_tests.rs new file mode 100644 index 0000000..69be15e --- /dev/null +++ b/const_format/tests/str_methods_modules/str_split_tests.rs @@ -0,0 +1,86 @@ +use const_format::str_split; + +#[test] +fn test_str_split_with_empty_str_arg() { + assert_eq!(str_split!("", ""), ["", ""]); + assert_eq!(str_split!("f", ""), ["", "f", ""]); + assert_eq!(str_split!("fo", ""), ["", "f", "o", ""]); + assert_eq!(str_split!("fob", ""), ["", "f", "o", "b", ""]); + + assert_eq!( + str_split!("!Aq¡€🧡🧠₀₁oñ个", ""), + ["", "!", "A", "q", "¡", "", "€", "🧡", "🧠", "₀", "₁", "o", "ñ", "个", ""], + ); +} + +#[test] +fn test_str_split_with_space_str_arg() { + assert_eq!(str_split!("fob", " "), ["fob"]); + assert_eq!(str_split!(" fob", " "), ["", "fob"]); + assert_eq!(str_split!(" fob ", " "), ["", "fob", ""]); + assert_eq!(str_split!("foo bar baz", " "), ["foo", "bar", "baz"]); + assert_eq!(str_split!("foo bar baz", " "), ["foo", "", "bar", "baz"]); +} + +#[test] +fn test_str_split_with_dash_str_arg() { + assert_eq!(str_split!("fob", "-"), ["fob"]); + assert_eq!(str_split!("-fob", "-"), ["", "fob"]); + assert_eq!(str_split!("-fob-", "-"), ["", "fob", ""]); + assert_eq!(str_split!("foo-bar-baz", "-"), ["foo", "bar", "baz"]); + assert_eq!(str_split!("foo--bar-baz", "-"), ["foo", "", "bar", "baz"]); +} + +#[test] +fn test_str_split_with_word_arg() { + assert_eq!(str_split!("fob", "XY"), ["fob"]); + assert_eq!(str_split!("XYfob", "XY"), ["", "fob"]); + assert_eq!(str_split!("XYfobXY", "XY"), ["", "fob", ""]); + assert_eq!(str_split!("fooXYbarXYbaz", "XY"), ["foo", "bar", "baz"]); + assert_eq!(str_split!("fooXY bar XYbaz", "XY"), ["foo", " bar ", "baz"]); +} + +#[test] +fn test_str_split_with_ascii_char_arg() { + assert_eq!(str_split!("fob", '-'), ["fob"]); + assert_eq!(str_split!("-fob", '-'), ["", "fob"]); + assert_eq!(str_split!("-fob-", '-'), ["", "fob", ""]); + assert_eq!(str_split!("foo-bar-baz", '-'), ["foo", "bar", "baz"]); + assert_eq!(str_split!("foo- bar -baz", '-'), ["foo", " bar ", "baz"]); +} + +#[test] +fn test_str_split_with_non_ascii_char_arg() { + { + assert_eq!(''.len_utf8(), 1); + assert_eq!(str_split!("fob", ''), ["fob"]); + assert_eq!(str_split!("fob", ''), ["", "fob"]); + assert_eq!(str_split!("fob", ''), ["", "fob", ""]); + assert_eq!(str_split!("foobarbaz", ''), ["foo", "bar", "baz"]); + assert_eq!(str_split!("foo bar baz", ''), ["foo", " bar ", "baz"]); + } + { + assert_eq!('ñ'.len_utf8(), 2); + assert_eq!(str_split!("fob", 'ñ'), ["fob"]); + assert_eq!(str_split!("ñfob", 'ñ'), ["", "fob"]); + assert_eq!(str_split!("ñfobñ", 'ñ'), ["", "fob", ""]); + assert_eq!(str_split!("fooñbarñbaz", 'ñ'), ["foo", "bar", "baz"]); + assert_eq!(str_split!("fooñ bar ñbaz", 'ñ'), ["foo", " bar ", "baz"]); + } + { + assert_eq!('₀'.len_utf8(), 3); + assert_eq!(str_split!("fob", '₀'), ["fob"]); + assert_eq!(str_split!("₀fob", '₀'), ["", "fob"]); + assert_eq!(str_split!("₀fob₀", '₀'), ["", "fob", ""]); + assert_eq!(str_split!("foo₀bar₀baz", '₀'), ["foo", "bar", "baz"]); + assert_eq!(str_split!("foo₀ bar ₀baz", '₀'), ["foo", " bar ", "baz"]); + } + { + assert_eq!('🧡'.len_utf8(), 4); + assert_eq!(str_split!("fob", '🧡'), ["fob"]); + assert_eq!(str_split!("🧡fob", '🧡'), ["", "fob"]); + assert_eq!(str_split!("🧡fob🧡", '🧡'), ["", "fob", ""]); + assert_eq!(str_split!("foo🧡bar🧡baz", '🧡'), ["foo", "bar", "baz"]); + assert_eq!(str_split!("foo🧡 bar 🧡baz", '🧡'), ["foo", " bar ", "baz"]); + } +}