+321
| // 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, "🚀星"); | ||
| } |
| { | ||
| "git": { | ||
| "sha1": "e8d5b7561cf6cc4bee35286f5d5e569100dfa79d" | ||
| "sha1": "a14f2dad852be26bad277ae704abf27a15cbfed1" | ||
| }, | ||
| "path_in_vcs": "utils/writeable" | ||
| } |
+75
-64
@@ -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" |
+7
-5
@@ -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)", | ||
| ] |
+48
-0
@@ -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)), | ||
| } | ||
| } | ||
| } |
+2
-43
@@ -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); |
+209
-4
@@ -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)); | ||
| } |
+14
-0
@@ -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