New:Socket for Asana Is Now Available.Learn more
Sign In

writeable

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

writeable - cargo Package Compare versions

Comparing version
0.6.3
to
0.6.4
+321
src/replace.rs
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
use crate::{impl_display_with_writeable, LengthHint, Writeable};
use core::fmt;
/// A [`Writeable`] adapter that replaces occurrences of a needle with a replacement.
///
/// This adapter performs the replacement in a streaming fashion during `write_to`,
/// requiring zero allocations.
///
/// # Examples
///
/// ```
/// use writeable::adapters::Replace;
/// use writeable::assert_writeable_eq;
/// use writeable::concat_writeable;
///
/// let source = concat_writeable!("I 💖 🦀", " and 🦀 loves me!");
/// let replace = Replace {
/// source,
/// needle: "🦀",
/// replacement: "Rust",
/// };
///
/// assert_writeable_eq!(replace, "I 💖 Rust and Rust loves me!");
/// ```
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)] // designed for nesting
pub struct Replace<A, B, C> {
/// The source writeable.
pub source: A,
/// The needle to search for.
pub needle: B,
/// The replacement writeable.
pub replacement: C,
}
// Computes the Knuth-Morris-Pratt (KMP) prefix function (failure function) value
// for the character prefix ending at byte index `matched_bytes` in `needle`.
//
// Returns the byte length of the longest proper prefix of `needle[0..matched_bytes]`
// that is also a suffix of `needle[0..matched_bytes]`.
//
// This is computed on the fly without allocation by iterating over char boundaries.
fn get_pi_bytes(needle: &str, matched_bytes: usize) -> usize {
let s = match needle.get(0..matched_bytes) {
Some(s) => s,
None => return 0,
};
// char_indices() gives us the byte offsets of character starts.
// These offsets correspond to the byte lengths of all possible prefixes.
// We want to iterate them in reverse order, excluding the first one (0)
// because we want proper prefixes.
for k in s
.char_indices()
.map(|(idx, _)| idx)
.rev()
.filter(|&idx| idx > 0)
{
// Compare the prefix of length `k` with the suffix of length `k`.
if let Some(suffix) = s.as_bytes().get(s.len() - k..) {
if s.as_bytes().starts_with(suffix) {
return k;
}
}
}
0
}
// A writer wrapper that performs streaming replacement.
// It intercepts characters written to it, matches them against `needle` using KMP
// (tracking progress by storing the remaining unmatched suffix of the needle),
// and writes `replacement` when a full match is found, or the original characters otherwise.
struct ReplaceWriter<'a, W: ?Sized, C> {
// The underlying sink to write to.
sink: &'a mut W,
// The needle we are searching for.
needle: &'a str,
// The replacement to write when the needle is matched.
replacement: &'a C,
// The remaining unmatched suffix of the needle.
// This is always a suffix of `needle` starting at a character boundary.
remaining_needle: &'a str,
}
impl<'a, W, C> ReplaceWriter<'a, W, C>
where
W: fmt::Write + ?Sized,
C: Writeable,
{
fn new(sink: &'a mut W, needle: &'a str, replacement: &'a C) -> Self {
Self {
sink,
needle,
replacement,
remaining_needle: needle,
}
}
// Helper to get the length of the prefix matched so far.
fn matched_len(&self) -> usize {
self.needle.len() - self.remaining_needle.len()
}
// Finalizes the writer, flushing any partially matched prefix to the sink.
fn finalize(&mut self) -> fmt::Result {
let matched = self.matched_len();
if matched > 0 {
let slice = self.needle.get(0..matched).ok_or(fmt::Error)?;
self.sink.write_str(slice)?;
self.remaining_needle = self.needle;
}
Ok(())
}
}
impl<'a, W, C> fmt::Write for ReplaceWriter<'a, W, C>
where
W: fmt::Write + ?Sized,
C: Writeable,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
for c in s.chars() {
self.write_char(c)?;
}
Ok(())
}
fn write_char(&mut self, c: char) -> fmt::Result {
// If the needle is empty, we just pass through the characters.
if self.needle.is_empty() {
return self.sink.write_char(c);
}
let mut matched = self.matched_len();
// KMP State Transition:
// While we have a mismatch and we are not at the start of the needle,
// backtrack using the prefix function.
while matched > 0 && !self.remaining_needle.starts_with(c) {
let old_j = matched;
matched = get_pi_bytes(self.needle, old_j);
// Since we backtracked, the prefix of length `old_j - j` is no longer
// part of the potential match. We write it to the sink as a single slice.
let slice = self.needle.get(0..(old_j - matched)).ok_or(fmt::Error)?;
self.sink.write_str(slice)?;
// Update remaining_needle to reflect the new matched length.
self.remaining_needle = self.needle.get(matched..).ok_or(fmt::Error)?;
}
// If the character matches the next character in the needle, advance the match state.
if self.remaining_needle.starts_with(c) {
// Advance remaining_needle by the matched character.
self.remaining_needle = self
.remaining_needle
.get(c.len_utf8()..)
.ok_or(fmt::Error)?;
if self.remaining_needle.is_empty() {
// Full match found! Write the replacement instead of the needle.
self.replacement.write_to(self.sink)?;
// Reset match state.
self.remaining_needle = self.needle;
}
} else {
// Mismatch at the very beginning of the needle. Write the character as is.
self.sink.write_char(c)?;
}
Ok(())
}
}
impl<A, C> Writeable for Replace<A, &str, C>
where
A: Writeable,
C: Writeable,
{
// We do not implement writeable_borrow because it is meant to be a constant-time O(1)
// operation, but determining if a replacement occurred would require O(N) scanning.
fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
let mut writer = ReplaceWriter::new(sink, self.needle, &self.replacement);
self.source.write_to(&mut writer)?;
writer.finalize()
}
fn writeable_length_hint(&self) -> LengthHint {
let source_hint = self.source.writeable_length_hint();
let needle_len = self.needle.len();
let replacement_hint = self.replacement.writeable_length_hint();
// If needle and replacement have same exact length, length is unchanged.
if let Some(r_upper) = replacement_hint.1 {
if replacement_hint.0 == r_upper && needle_len == r_upper {
return source_hint;
}
}
let mut lower = 0;
let mut upper = None;
// If replacement is always larger than or equal to needle:
// New length is at least the source length.
if replacement_hint.0 >= needle_len {
lower = source_hint.0;
}
// If replacement is always smaller than or equal to needle:
// New length is at most the source length.
if let Some(r_upper) = replacement_hint.1 {
if r_upper <= needle_len {
upper = source_hint.1;
}
}
LengthHint(lower, upper)
}
}
impl_display_with_writeable!(Replace<A, &'a str, C>, #[cfg(feature = "alloc")], where 'a, A: Writeable, C: Writeable);
#[test]
fn test_replace() {
use crate::assert_writeable_eq;
use crate::concat::Concat;
// Basic replacement
let replace1 = Replace {
source: Concat("Hello", " 10 22 1101 33"),
needle: "10",
replacement: Concat("4", "4"),
};
assert_writeable_eq!(replace1, "Hello 44 22 1441 33");
// Empty needle (should just write source)
let replace2 = Replace {
source: "Hello World",
needle: "",
replacement: "X",
};
assert_writeable_eq!(replace2, "Hello World");
// Empty replacement
let replace3 = Replace {
source: "Hello 10 World 10",
needle: "10",
replacement: "",
};
assert_writeable_eq!(replace3, "Hello World ");
// Needle not found
let replace4 = Replace {
source: "Hello World",
needle: "10",
replacement: "X",
};
assert_writeable_eq!(replace4, "Hello World");
// Needle at the beginning
let replace5 = Replace {
source: "10 Hello World",
needle: "10",
replacement: "X",
};
assert_writeable_eq!(replace5, "X Hello World");
// Needle at the end
let replace6 = Replace {
source: "Hello World 10",
needle: "10",
replacement: "X",
};
assert_writeable_eq!(replace6, "Hello World X");
// Overlapping needles (should consume and not match again)
let replace7 = Replace {
source: "ababa",
needle: "aba",
replacement: "X",
};
assert_writeable_eq!(replace7, "Xba");
// Self-overlap but no match
let replace8 = Replace {
source: "aab",
needle: "aac",
replacement: "X",
};
assert_writeable_eq!(replace8, "aab");
// Multi-byte UTF-8
let replace9 = Replace {
source: "🚀 🛸 🚀🚀 🚁",
needle: "🚀",
replacement: "星",
};
assert_writeable_eq!(replace9, "星 🛸 星星 🚁");
// Multi-byte UTF-8 with partial match
let replace10 = Replace {
source: "🚀🚁",
needle: "🚀🛸",
replacement: "星",
};
assert_writeable_eq!(replace10, "🚀🚁");
// Multi-byte UTF-8 with backtracking (no match)
let replace11 = Replace {
source: "🚀🚀🚁",
needle: "🚀🚀🛸",
replacement: "星",
};
assert_writeable_eq!(replace11, "🚀🚀🚁");
// Multi-byte UTF-8 with backtracking (match)
let replace12 = Replace {
source: "🚀🚀🚀🛸",
needle: "🚀🚀🛸",
replacement: "星",
};
assert_writeable_eq!(replace12, "🚀星");
}
+1
-1
{
"git": {
"sha1": "e8d5b7561cf6cc4bee35286f5d5e569100dfa79d"
"sha1": "a14f2dad852be26bad277ae704abf27a15cbfed1"
},
"path_in_vcs": "utils/writeable"
}

@@ -7,5 +7,5 @@ # This file is automatically @generated by Cargo.

name = "aho-corasick"
version = "1.1.4"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
dependencies = [

@@ -29,11 +29,11 @@ "memchr",

name = "autocfg"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bumpalo"
version = "3.20.2"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"

@@ -142,5 +142,5 @@ [[package]]

name = "crossbeam-deque"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
dependencies = [

@@ -153,5 +153,5 @@ "crossbeam-epoch",

name = "crossbeam-epoch"
version = "0.9.18"
version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [

@@ -163,5 +163,5 @@ "crossbeam-utils",

name = "crossbeam-utils"
version = "0.8.21"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"

@@ -176,5 +176,5 @@ [[package]]

name = "either"
version = "1.15.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"

@@ -237,5 +237,5 @@ [[package]]

name = "js-sys"
version = "0.3.91"
version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c"
checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
dependencies = [

@@ -248,11 +248,11 @@ "once_cell",

name = "libc"
version = "0.2.183"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "memchr"
version = "2.8.0"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"

@@ -319,5 +319,5 @@ [[package]]

name = "proc-macro2"
version = "1.0.106"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [

@@ -329,5 +329,5 @@ "unicode-ident",

name = "quote"
version = "1.0.45"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [

@@ -345,5 +345,5 @@ "proc-macro2",

name = "rand"
version = "0.9.2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
dependencies = [

@@ -395,5 +395,5 @@ "rand_chacha",

name = "regex"
version = "1.12.3"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [

@@ -408,5 +408,5 @@ "aho-corasick",

name = "regex-automata"
version = "0.4.14"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [

@@ -420,11 +420,11 @@ "aho-corasick",

name = "regex-syntax"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "rustversion"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"

@@ -442,5 +442,5 @@ [[package]]

name = "serde"
version = "1.0.228"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [

@@ -453,5 +453,5 @@ "serde_core",

name = "serde_core"
version = "1.0.228"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [

@@ -463,9 +463,9 @@ "serde_derive",

name = "serde_derive"
version = "1.0.228"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 3.0.3",
]

@@ -475,5 +475,5 @@

name = "serde_json"
version = "1.0.149"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [

@@ -489,5 +489,5 @@ "itoa",

name = "syn"
version = "2.0.117"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [

@@ -500,2 +500,13 @@ "proc-macro2",

[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tinytemplate"

@@ -537,5 +548,5 @@ version = "1.2.1"

name = "wasm-bindgen"
version = "0.2.114"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e"
checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
dependencies = [

@@ -551,5 +562,5 @@ "cfg-if",

name = "wasm-bindgen-macro"
version = "0.2.114"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6"
checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
dependencies = [

@@ -562,5 +573,5 @@ "quote",

name = "wasm-bindgen-macro-support"
version = "0.2.114"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3"
checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
dependencies = [

@@ -570,3 +581,3 @@ "bumpalo",

"quote",
"syn",
"syn 2.0.119",
"wasm-bindgen-shared",

@@ -577,5 +588,5 @@ ]

name = "wasm-bindgen-shared"
version = "0.2.114"
version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16"
checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
dependencies = [

@@ -587,5 +598,5 @@ "unicode-ident",

name = "web-sys"
version = "0.3.91"
version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9"
checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a"
dependencies = [

@@ -628,3 +639,3 @@ "js-sys",

name = "writeable"
version = "0.6.3"
version = "0.6.4"
dependencies = [

@@ -638,5 +649,5 @@ "criterion",

name = "zerocopy"
version = "0.8.47"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87"
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
dependencies = [

@@ -648,9 +659,9 @@ "zerocopy-derive",

name = "zerocopy-derive"
version = "0.8.47"
version = "0.8.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89"
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.119",
]

@@ -660,4 +671,4 @@

name = "zmij"
version = "1.0.21"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"

@@ -16,3 +16,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO

name = "writeable"
version = "0.6.3"
version = "0.6.4"
authors = ["The ICU4X Project Developers"]

@@ -89,4 +89,5 @@ build = false

[lints.clippy]
alloc-instead-of-core = "warn"
branches-sharing-code = "warn"
alloc_instead_of_core = "warn"
bool_assert_comparison = "allow"
branches_sharing_code = "warn"
collection_is_never_read = "warn"

@@ -106,3 +107,3 @@ crosspointer_transmute = "warn"

negative_feature_names = "warn"
or-fun-call = "warn"
or_fun_call = "warn"
same_functions_in_if_condition = "warn"

@@ -118,3 +119,3 @@ todo = "warn"

trivially_copy_pass_by_ref = "deny"
unnecessary-wraps = "warn"
unnecessary_wraps = "warn"
useless_transmute = "warn"

@@ -136,4 +137,5 @@ wildcard_dependencies = "warn"

"cfg(needs_alloc_error_handler)",
"cfg(icu4x_nightly_tests)",
"cfg(icu4x_run_size_tests)",
"cfg(icu4x_unstable_fast_trie_only)",
]

@@ -50,1 +50,49 @@ // This file is part of ICU4X. For terms of use, please see the file

}
/// A [`TryWriteable`] impl that delegates to one type or another type.
impl<W0, W1> TryWriteable for Either<W0, W1>
where
W0: TryWriteable,
W1: TryWriteable,
{
type Error = Either<W0::Error, W1::Error>;
fn try_write_to<W: fmt::Write + ?Sized>(
&self,
sink: &mut W,
) -> Result<Result<(), Self::Error>, fmt::Error> {
match self {
Either::Left(w) => w.try_write_to(sink).map(|r| r.map_err(Either::Left)),
Either::Right(w) => w.try_write_to(sink).map(|r| r.map_err(Either::Right)),
}
}
fn try_write_to_parts<S: PartsWrite + ?Sized>(
&self,
sink: &mut S,
) -> Result<Result<(), Self::Error>, fmt::Error> {
match self {
Either::Left(w) => w.try_write_to_parts(sink).map(|r| r.map_err(Either::Left)),
Either::Right(w) => w.try_write_to_parts(sink).map(|r| r.map_err(Either::Right)),
}
}
fn writeable_length_hint(&self) -> LengthHint {
match self {
Either::Left(w) => w.writeable_length_hint(),
Either::Right(w) => w.writeable_length_hint(),
}
}
#[cfg(feature = "alloc")]
fn try_write_to_string(&self) -> Result<Cow<'_, str>, (Self::Error, Cow<'_, str>)> {
match self {
Either::Left(w) => w
.try_write_to_string()
.map_err(|(e, s)| (Either::Left(e), s)),
Either::Right(w) => w
.try_write_to_string()
.map_err(|(e, s)| (Either::Right(e), s)),
}
}
}

@@ -116,19 +116,4 @@ // This file is part of ICU4X. For terms of use, please see the file

#[cfg(feature = "alloc")]
impl Writeable for String {
#[inline]
fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
sink.write_str(self)
}
crate::impl_writeable_delegate!(String, |&self| self.as_str());
#[inline]
fn writeable_length_hint(&self) -> LengthHint {
LengthHint::exact(self.len())
}
#[inline]
fn writeable_borrow(&self) -> Option<&str> {
Some(self)
}
}
impl Writeable for char {

@@ -154,30 +139,4 @@ #[inline]

impl<T: Writeable + ?Sized> Writeable for &T {
#[inline]
fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
(*self).write_to(sink)
}
crate::impl_writeable_delegate!(&T, |&self| *self, #[cfg(feature = "alloc")] fn write_to_string, where T: Writeable + ?Sized);
#[inline]
fn write_to_parts<W: PartsWrite + ?Sized>(&self, sink: &mut W) -> fmt::Result {
(*self).write_to_parts(sink)
}
#[inline]
fn writeable_length_hint(&self) -> LengthHint {
(*self).writeable_length_hint()
}
#[inline]
fn writeable_borrow(&self) -> Option<&str> {
(*self).writeable_borrow()
}
#[inline]
#[cfg(feature = "alloc")]
fn write_to_string(&self) -> Cow<'_, str> {
(*self).write_to_string()
}
}
#[cfg(feature = "alloc")]

@@ -184,0 +143,0 @@ macro_rules! impl_write_smart_pointer {

+132
-13

@@ -86,2 +86,3 @@ // This file is part of ICU4X. For terms of use, please see the file

mod parts_write_adapter;
mod replace;
#[cfg(feature = "alloc")]

@@ -113,2 +114,3 @@ mod testing;

pub use parts_write_adapter::WithPart;
pub use replace::Replace;
pub use try_writeable::TryWriteableInfallibleAsWriteable;

@@ -119,3 +121,4 @@ pub use try_writeable::WriteableAsTryWriteableInfallible;

/// and ignores any errors.
#[derive(Debug)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
#[allow(clippy::exhaustive_structs)] // newtype

@@ -125,2 +128,3 @@ pub struct LossyWrap<T>(pub T);

impl<T: TryWriteable> Writeable for LossyWrap<T> {
#[inline]
fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {

@@ -131,13 +135,32 @@ let _ = self.0.try_write_to(sink)?;

#[inline]
fn write_to_parts<S: PartsWrite + ?Sized>(&self, sink: &mut S) -> fmt::Result {
let _ = self.0.try_write_to_parts(sink)?;
Ok(())
}
#[inline]
fn writeable_length_hint(&self) -> LengthHint {
self.0.writeable_length_hint()
}
}
impl<T: TryWriteable> fmt::Display for LossyWrap<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let _ = self.0.try_write_to(f)?;
Ok(())
#[inline]
fn writeable_borrow(&self) -> Option<&str> {
match self.0.try_writeable_borrow()? {
Ok(s) => Some(s),
Err((_err, s)) => Some(s),
}
}
#[inline]
#[cfg(feature = "alloc")]
fn write_to_string(&self) -> Cow<'_, str> {
match self.0.try_write_to_string() {
Ok(s) => s,
Err((_err, s)) => s,
}
}
}
impl_display_with_writeable!(LossyWrap<T>, #[cfg(feature = "alloc")], where T: TryWriteable);
}

@@ -152,2 +175,4 @@

#[cfg(feature = "alloc")]
pub use alloc::borrow::Cow;
#[cfg(feature = "alloc")]
pub use alloc::string::String;

@@ -373,2 +398,72 @@ }

/// Macro to implement [`Writeable`] by delegating to another `Writeable`.
///
/// Useful for wrapper types.
///
/// # Examples
///
/// ```
/// struct MyStruct(String);
/// writeable::impl_writeable_delegate!(MyStruct, |&self| &self.0);
/// writeable::impl_display_with_writeable!(MyStruct);
///
/// writeable::assert_writeable_eq!(MyStruct("hello".to_string()), "hello");
/// ```
///
/// With a cfg on fn `write_to_string`:
///
/// ```
/// struct MyStruct(String);
/// writeable::impl_writeable_delegate!(MyStruct, |&self| &self.0, #[cfg(feature = "alloc")] fn write_to_string);
/// writeable::impl_display_with_writeable!(MyStruct, #[cfg(feature = "alloc")]);
///
/// writeable::assert_writeable_eq!(
/// MyStruct("hello".to_string()),
/// "hello"
/// );
/// ```
///
/// With generics:
///
/// ```
/// use writeable::Writeable;
///
/// struct MyStruct<T>(T);
/// writeable::impl_writeable_delegate!(MyStruct<T>, |&self| &self.0, where T: Writeable);
/// writeable::impl_display_with_writeable!(MyStruct<T>, where T: Writeable);
///
/// writeable::assert_writeable_eq!(
/// MyStruct("hello"),
/// "hello"
/// );
/// ```
#[macro_export]
macro_rules! impl_writeable_delegate {
($ty:ty, |&$self:ident| $delegate:expr $(, #[$alloc_feature:meta] fn write_to_string)? $(, where $($generics:tt)*)?) => {
impl $(<$($generics)*>)? $crate::Writeable for $ty {
#[inline]
fn write_to<W: core::fmt::Write + ?Sized>(&$self, sink: &mut W) -> core::fmt::Result {
($delegate).write_to(sink)
}
#[inline]
fn write_to_parts<S: $crate::PartsWrite + ?Sized>(&$self, sink: &mut S) -> core::fmt::Result {
($delegate).write_to_parts(sink)
}
#[inline]
fn writeable_length_hint(&$self) -> $crate::LengthHint {
($delegate).writeable_length_hint()
}
#[inline]
fn writeable_borrow(&$self) -> Option<&str> {
($delegate).writeable_borrow()
}
#[inline]
$(#[$alloc_feature])?
fn write_to_string(&$self) -> $crate::_internal::Cow<'_, str> {
($delegate).write_to_string()
}
}
};
}
/// Implements [`Display`](core::fmt::Display) for types that implement [`Writeable`].

@@ -383,8 +478,31 @@ ///

/// To add only `Display`, use the `@display` macro variant.
///
/// If your type has generics, list them in a `where` clause in the macro invocation.
///
/// # Examples
///
/// ```
/// use writeable::Writeable;
/// use std::fmt;
///
/// struct Message<T>(T);
///
/// impl<T> Writeable for Message<T> where T: Writeable {
/// fn write_to<W: fmt::Write + ?Sized>(&self, sink: &mut W) -> fmt::Result {
/// sink.write_str("Message: ")?;
/// self.0.write_to(sink)
/// }
/// // ...
/// }
///
/// writeable::impl_display_with_writeable!(Message<T>, where T: Writeable);
///
/// writeable::assert_writeable_eq!(Message("hello"), "Message: hello");
/// ```
#[macro_export]
macro_rules! impl_display_with_writeable {
(@display, $type:ty) => {
(@display, $type:ty $(, where $($generics:tt)*)?) => {
/// This trait is implemented for compatibility with [`fmt!`](core::fmt).
/// To create a string, [`Writeable::write_to_string`] is usually more efficient.
impl core::fmt::Display for $type {
impl $(<$($generics)*>)? core::fmt::Display for $type {
#[inline]

@@ -396,11 +514,12 @@ fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {

};
($type:ty $(, #[$alloc_feature:meta])? ) => {
$crate::impl_display_with_writeable!(@display, $type);
($type:ty $(, #[$alloc_feature:meta])? $(, where $($generics:tt)*)?) => {
$crate::impl_display_with_writeable!(@display, $type $(, where $($generics)*)?);
$(#[$alloc_feature])?
impl $type {
impl $(<$($generics)*>)? $type {
/// Converts the given value to a `String`.
///
/// Under the hood, this uses an efficient [`Writeable`] implementation.
/// However, in order to avoid allocating a string, it is more efficient
/// to use [`Writeable`] directly.
///
/// If you don't need an allocated [`String`], but e.g. need to write this
/// to some sink, it is more efficient to use [`Writeable`] directly.
pub fn to_string(&self) -> $crate::_internal::String {

@@ -407,0 +526,0 @@ $crate::Writeable::write_to_string(self).into_owned()

@@ -117,7 +117,2 @@ // This file is part of ICU4X. For terms of use, please see the file

impl<T: Writeable + ?Sized> fmt::Display for WithPart<T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Writeable::write_to(&self, f)
}
}
crate::impl_display_with_writeable!(@display, WithPart<T>, where T: Writeable + ?Sized);

@@ -178,2 +178,9 @@ // This file is part of ICU4X. For terms of use, please see the file

/// Returns a `&str` that matches the output of `try_write_to`, if possible.
///
/// This method is used to avoid materializing a [`String`] in `write_to_string`.
fn try_writeable_borrow(&self) -> Option<Result<&str, (Self::Error, &str)>> {
None
}
/// Writes the content of this writeable to a string.

@@ -183,4 +190,8 @@ ///

///
/// Examples
/// # Note to implementors
///
/// See the note in [`Writeable::write_to_string`].
///
/// # Examples
///
/// ```

@@ -200,2 +211,7 @@ /// # use std::borrow::Cow;

fn try_write_to_string(&self) -> Result<Cow<'_, str>, (Self::Error, Cow<'_, str>)> {
if let Some(borrow) = self.try_writeable_borrow() {
return borrow
.map(Cow::Borrowed)
.map_err(|(e, s)| (e, Cow::Borrowed(s)));
}
let hint = self.writeable_length_hint();

@@ -255,2 +271,9 @@ if hint.is_zero() {

fn try_writeable_borrow(&self) -> Option<Result<&str, (Self::Error, &str)>> {
match self {
Ok(t) => t.writeable_borrow().map(Ok),
Err(e) => e.writeable_borrow().map(|s| Err((e.clone(), s))),
}
}
#[inline]

@@ -301,2 +324,8 @@ #[cfg(feature = "alloc")]

#[inline]
fn writeable_borrow(&self) -> Option<&str> {
let Ok(s) = self.0.try_writeable_borrow()?;
Some(s)
}
#[inline]
#[cfg(feature = "alloc")]

@@ -356,2 +385,7 @@ fn write_to_string(&self) -> Cow<'_, str> {

#[inline]
fn try_writeable_borrow(&self) -> Option<Result<&str, (Self::Error, &str)>> {
self.0.writeable_borrow().map(Ok)
}
#[inline]
#[cfg(feature = "alloc")]

@@ -363,2 +397,160 @@ fn try_write_to_string(&self) -> Result<Cow<'_, str>, (Infallible, Cow<'_, str>)> {

/// Macro to implement [`TryWriteable`] by delegating to another `TryWriteable`.
///
/// Useful for wrapper types.
///
/// # Examples
///
/// ```
/// struct MyStruct(Result<String, String>);
/// writeable::impl_try_writeable_delegate!(
/// MyStruct,
/// |&self| &self.0,
/// Error = String
/// );
///
/// writeable::assert_try_writeable_eq!(
/// MyStruct(Ok("hello".to_string())),
/// "hello"
/// );
/// ```
///
/// With an error mapping fn:
///
/// ```
/// struct MyStruct(Result<String, String>);
/// #[derive(Debug, PartialEq)]
/// struct MyError;
/// writeable::impl_try_writeable_delegate!(
/// MyStruct,
/// |&self| &self.0,
/// Error = MyError,
/// |_error| MyError
/// );
///
/// writeable::assert_try_writeable_eq!(
/// MyStruct(Ok("hello".to_string())),
/// "hello"
/// );
/// writeable::assert_try_writeable_eq!(
/// MyStruct(Err("hello".to_string())),
/// "hello",
/// Err(MyError)
/// );
/// ```
///
/// With a cfg on fn `write_to_string`:
///
/// ```
/// struct MyStruct(Result<String, String>);
/// writeable::impl_try_writeable_delegate!(MyStruct, |&self| &self.0, Error = String, #[cfg(feature = "alloc")] fn try_write_to_string);
///
/// writeable::assert_try_writeable_eq!(
/// MyStruct(Ok("hello".to_string())),
/// "hello"
/// );
/// ```
///
/// With generics:
///
/// ```
/// use writeable::Writeable;
///
/// struct MyStruct<T>(Result<T, T>);
/// writeable::impl_try_writeable_delegate!(MyStruct<T>, |&self| &self.0, Error = T, where T: Writeable + Clone);
///
/// writeable::assert_try_writeable_eq!(
/// MyStruct(Ok("hello".to_string())),
/// "hello"
/// );
/// ```
///
/// Implement both `Writeable` and `TryWriteable`:
///
/// ```
/// use writeable::adapters::LossyWrap;
///
/// // The LossyWrap needs to be a field of MyStruct since it can be borrowed from.
/// struct MyStruct(LossyWrap<Result<String, String>>);
/// writeable::impl_try_writeable_delegate!(MyStruct, |&self| &self.0.0, Error = String);
/// writeable::impl_writeable_delegate!(MyStruct, |&self| &self.0);
/// writeable::impl_display_with_writeable!(MyStruct);
///
/// writeable::assert_try_writeable_eq!(
/// MyStruct(LossyWrap(Ok("hello".to_string()))),
/// "hello"
/// );
///
/// writeable::assert_writeable_eq!(
/// MyStruct(LossyWrap(Ok("hello".to_string()))),
/// "hello"
/// );
/// ```
#[macro_export]
macro_rules! impl_try_writeable_delegate {
($ty:ty, |&$self:ident| $delegate:expr, Error = $error:ty $(, |$error_arg:ident| $error_map:expr)? $(, #[$alloc_feature:meta] fn try_write_to_string)? $(, where $($generics:tt)*)?) => {
impl$(<$($generics)*>)? $crate::TryWriteable for $ty {
type Error = $error;
#[inline]
fn try_write_to<W: core::fmt::Write + ?Sized>(
&$self,
sink: &mut W,
) -> core::result::Result<core::result::Result<(), Self::Error>, core::fmt::Error> {
let result = ($delegate).try_write_to(sink)?;
$(
let result = result.map_err(|$error_arg| { $error_map });
)?
Ok(result)
}
#[inline]
fn try_write_to_parts<S: $crate::PartsWrite + ?Sized>(
&$self,
sink: &mut S,
) -> core::result::Result<core::result::Result<(), Self::Error>, core::fmt::Error> {
let result = ($delegate).try_write_to_parts(sink)?;
$(
let result = result.map_err(|$error_arg| { $error_map });
)?
Ok(result)
}
#[inline]
fn writeable_length_hint(&$self) -> $crate::LengthHint {
($delegate).writeable_length_hint()
}
#[inline]
fn try_writeable_borrow(&$self) -> Option<Result<&str, (Self::Error, &str)>> {
let result = ($delegate).try_writeable_borrow()?;
$(
let error_map = |$error_arg| { $error_map };
let result = result.map_err(|(err, cow)| (error_map(err), cow));
)?
Some(result)
}
#[inline]
$(#[$alloc_feature])?
fn try_write_to_string(
&$self,
) -> core::result::Result<
$crate::_internal::Cow<'_, str>,
(Self::Error, $crate::_internal::Cow<'_, str>),
> {
let result = ($delegate).try_write_to_string();
$(
let error_map = |$error_arg| { $error_map };
let result = result.map_err(|(err, cow)| (error_map(err), cow));
)?
result
}
}
};
}
impl_try_writeable_delegate!(
&T,
|&self| *self,
Error = T::Error,
#[cfg(feature = "alloc")] fn try_write_to_string,
where T: TryWriteable + ?Sized
);
/// Testing macros for types implementing [`TryWriteable`].

@@ -396,3 +588,2 @@ ///

(@internal, $actual_writeable:expr, $expected_str:expr, $expected_result:expr, $($arg:tt)+) => {{
use $crate::TryWriteable;
let actual_writeable = &$actual_writeable;

@@ -402,3 +593,3 @@ let (actual_str, actual_parts, actual_error) = $crate::_internal::try_writeable_to_parts_for_test(actual_writeable);

assert_eq!(actual_error, Result::<(), _>::from($expected_result).err(), $($arg)*);
let actual_result = match actual_writeable.try_write_to_string() {
let actual_result = match $crate::TryWriteable::try_write_to_string(&actual_writeable) {
Ok(actual_cow_str) => {

@@ -414,3 +605,3 @@ assert_eq!(actual_cow_str, $expected_str, $($arg)+);

assert_eq!(actual_result, Result::<(), _>::from($expected_result), $($arg)*);
let length_hint = actual_writeable.writeable_length_hint();
let length_hint = $crate::TryWriteable::writeable_length_hint(&actual_writeable);
assert!(

@@ -455,1 +646,15 @@ length_hint.0 <= actual_str.len(),

}
#[cfg(test)]
struct DelegatedTryMessage<'s>(Result<&'s str, usize>);
#[cfg(test)]
impl_try_writeable_delegate!(DelegatedTryMessage<'_>, |&self| &self.0, Error = usize);
#[test]
fn test_delegated_try_writeable() {
let mut message = DelegatedTryMessage(Ok("success"));
assert_try_writeable_eq!(message, "success");
message = DelegatedTryMessage(Err(44));
assert_try_writeable_eq!(message, "44", Err(44));
}

@@ -35,1 +35,15 @@ // This file is part of ICU4X. For terms of use, please see the file

}
struct DelegatedMessage<'s>(WriteableMessage<'s>);
writeable::impl_writeable_delegate!(DelegatedMessage<'_>, |&self| &self.0);
writeable::impl_display_with_writeable!(DelegatedMessage<'_>);
#[test]
fn test_delegated() {
let input_string = "hello world 2";
let message = DelegatedMessage(WriteableMessage {
message: input_string,
});
assert_writeable_eq!(&message, input_string);
}

Sorry, the diff of this file is not supported yet