writeable-0.6.0/.cargo_vcs_info.json0000644000000001550000000000100130160ustar { "git": { "sha1": "6bd4893cc44c2ca2718de47a119a31cc40045fe5" }, "path_in_vcs": "utils/writeable" }writeable-0.6.0/Cargo.lock0000644000000433030000000000100107730ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bumpalo" version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d5f1946157a96594eb2d2c10eb7ad9a2b27518cb3000209dec700c35df9197d" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78116e32a042dd73c2901f0dc30790d20ff3447f3e3472fad359e8c3d282bcd6" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" [[package]] name = "criterion" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "is-terminal", "itertools", "num-traits", "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", "serde_derive", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", ] [[package]] name = "crossbeam-deque" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "half" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "is-terminal" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "js-sys" version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "serde" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "syn" version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "writeable" version = "0.6.0" dependencies = [ "criterion", "either", "rand", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] writeable-0.6.0/Cargo.toml0000644000000032650000000000100110210ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.71.1" name = "writeable" version = "0.6.0" authors = ["The ICU4X Project Developers"] build = false include = [ "data/**/*", "src/**/*", "examples/**/*", "benches/**/*", "tests/**/*", "Cargo.toml", "LICENSE", "README.md", ] autobins = false autoexamples = false autotests = false autobenches = false description = "A more efficient alternative to fmt::Display" readme = "README.md" license = "Unicode-3.0" repository = "https://github.com/unicode-org/icu4x" [package.metadata.cargo-all-features] denylist = ["bench"] [package.metadata.docs.rs] all-features = true [package.metadata.workspaces] independent = true [lib] name = "writeable" path = "src/lib.rs" bench = false [[example]] name = "writeable_message" path = "examples/writeable_message.rs" [[test]] name = "writeable" path = "tests/writeable.rs" [[bench]] name = "writeable" path = "benches/writeable.rs" harness = false [dependencies.either] version = "1.9.0" optional = true default-features = false [dev-dependencies.rand] version = "0.8" features = ["small_rng"] [features] bench = [] either = ["dep:either"] [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.criterion] version = "0.5.0" writeable-0.6.0/Cargo.toml.orig000064400000000000000000000021731046102023000144770ustar 00000000000000# 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 ). [package] name = "writeable" description = "A more efficient alternative to fmt::Display" version = "0.6.0" authors.workspace = true edition.workspace = true include.workspace = true license.workspace = true repository.workspace = true rust-version.workspace = true [package.metadata.workspaces] independent = true [package.metadata.docs.rs] all-features = true [dependencies] either = { workspace = true, optional = true } [dev-dependencies] icu_benchmark_macros = { path = "../../tools/benchmark/macros" } rand = { workspace = true, features = ["small_rng"] } [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = { workspace = true } [features] bench = [] either = ["dep:either"] [package.metadata.cargo-all-features] # Bench feature gets tested separately and is only relevant for CI denylist = ["bench"] [lib] bench = false # This option is required for Benchmark CI [[bench]] name = "writeable" harness = false writeable-0.6.0/LICENSE000064400000000000000000000042231046102023000126130ustar 00000000000000UNICODE LICENSE V3 COPYRIGHT AND PERMISSION NOTICE Copyright © 2020-2024 Unicode, Inc. NOTICE TO USER: Carefully read the following legal agreement. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. Permission is hereby granted, free of charge, to any person obtaining a copy of data files and any associated documentation (the "Data Files") or software and any associated documentation (the "Software") to deal in the Data Files or Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Data Files or Software, and to permit persons to whom the Data Files or Software are furnished to do so, provided that either (a) this copyright and permission notice appear with all copies of the Data Files or Software, or (b) this copyright and permission notice appear in associated Documentation. THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. SPDX-License-Identifier: Unicode-3.0 — Portions of ICU4X may have been adapted from ICU4C and/or ICU4J. ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others. writeable-0.6.0/README.md000064400000000000000000000034031046102023000130640ustar 00000000000000# writeable [![crates.io](https://img.shields.io/crates/v/writeable)](https://crates.io/crates/writeable) `writeable` is a utility crate of the [`ICU4X`] project. It includes [`Writeable`], a core trait representing an object that can be written to a sink implementing `std::fmt::Write`. It is an alternative to `std::fmt::Display` with the addition of a function indicating the number of bytes to be written. `Writeable` improves upon `std::fmt::Display` in two ways: 1. More efficient, since the sink can pre-allocate bytes. 2. Smaller code, since the format machinery can be short-circuited. ## Examples ```rust use std::fmt; use writeable::assert_writeable_eq; use writeable::LengthHint; use writeable::Writeable; struct WelcomeMessage<'s> { pub name: &'s str, } impl<'s> Writeable for WelcomeMessage<'s> { fn write_to(&self, sink: &mut W) -> fmt::Result { sink.write_str("Hello, ")?; sink.write_str(self.name)?; sink.write_char('!')?; Ok(()) } fn writeable_length_hint(&self) -> LengthHint { // "Hello, " + '!' + length of name LengthHint::exact(8 + self.name.len()) } } let message = WelcomeMessage { name: "Alice" }; assert_writeable_eq!(&message, "Hello, Alice!"); // Types implementing `Writeable` are recommended to also implement `fmt::Display`. // This can be simply done by redirecting to the `Writeable` implementation: writeable::impl_display_with_writeable!(WelcomeMessage<'_>); assert_eq!(message.to_string(), "Hello, Alice!"); ``` [`ICU4X`]: ../icu/index.html ## More Information For more information on development, authorship, contributing etc. please visit [`ICU4X home page`](https://github.com/unicode-org/icu4x). writeable-0.6.0/benches/writeable.rs000064400000000000000000000164001046102023000155410ustar 00000000000000// 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 criterion::{black_box, criterion_group, criterion_main, Criterion}; use std::fmt; use writeable::LengthHint; use writeable::Writeable; /// A sample type implementing Writeable struct WriteableMessage<'s> { message: &'s str, } impl Writeable for WriteableMessage<'_> { fn write_to(&self, sink: &mut W) -> fmt::Result { sink.write_str(self.message) } fn writeable_length_hint(&self) -> LengthHint { LengthHint::exact(self.message.len()) } } writeable::impl_display_with_writeable!(WriteableMessage<'_>); /// A sample type implementing Display #[cfg(feature = "bench")] struct DisplayMessage<'s> { message: &'s str, } #[cfg(feature = "bench")] impl fmt::Display for DisplayMessage<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.message) } } /// A sample type that contains multiple fields #[cfg(feature = "bench")] struct ComplexWriteable<'a> { prefix: &'a str, n0: usize, infix: &'a str, n1: usize, suffix: &'a str, } #[cfg(feature = "bench")] impl Writeable for ComplexWriteable<'_> { fn write_to(&self, sink: &mut W) -> fmt::Result { self.prefix.write_to(sink)?; self.n0.write_to(sink)?; self.infix.write_to(sink)?; self.n1.write_to(sink)?; self.suffix.write_to(sink)?; Ok(()) } fn writeable_length_hint(&self) -> LengthHint { self.prefix.writeable_length_hint() + self.n0.writeable_length_hint() + self.infix.writeable_length_hint() + self.n1.writeable_length_hint() + self.suffix.writeable_length_hint() } } #[cfg(feature = "bench")] writeable::impl_display_with_writeable!(ComplexWriteable<'_>); const SHORT_STR: &str = "short"; const MEDIUM_STR: &str = "this is a medium-length string"; const LONG_STR: &str = "this string is very very very very very very very very very very very very very very very very very very very very very very very very long"; #[cfg(feature = "bench")] const LONG_OVERLAP_STR: &str = "this string is very very very very very very very long but different"; fn overview_bench(c: &mut Criterion) { c.bench_function("writeable/overview", |b| { b.iter(|| { // This benchmark runs to_string on short, medium, and long strings in one batch. WriteableMessage { message: black_box(SHORT_STR), } .write_to_string(); WriteableMessage { message: black_box(MEDIUM_STR), } .write_to_string(); WriteableMessage { message: black_box(LONG_STR), } .write_to_string(); }); }); #[cfg(feature = "bench")] { writeable_benches(c); writeable_dyn_benches(c); display_benches(c); complex_benches(c); } } #[cfg(feature = "bench")] fn writeable_benches(c: &mut Criterion) { c.bench_function("writeable/to_string/short", |b| { b.iter(|| { WriteableMessage { message: black_box(SHORT_STR), } .write_to_string() .into_owned() }); }); c.bench_function("writeable/to_string/medium", |b| { b.iter(|| { WriteableMessage { message: black_box(MEDIUM_STR), } .write_to_string() .into_owned() }); }); c.bench_function("writeable/to_string/long", |b| { b.iter(|| { WriteableMessage { message: black_box(LONG_STR), } .write_to_string() .into_owned() }); }); c.bench_function("writeable/cmp_str", |b| { b.iter(|| { let short = black_box(SHORT_STR); let medium = black_box(MEDIUM_STR); let long = black_box(LONG_STR); let long_overlap = black_box(LONG_OVERLAP_STR); [short, medium, long, long_overlap].map(|s1| { [short, medium, long, long_overlap].map(|s2| { let message = WriteableMessage { message: s1 }; writeable::cmp_str(&message, s2) }) }) }); }); } #[cfg(feature = "bench")] fn writeable_dyn_benches(c: &mut Criterion) { // Same as `write_to_string`, but casts to a `dyn fmt::Write` fn writeable_dyn_to_string(w: &impl Writeable) -> String { let mut output = String::with_capacity(w.writeable_length_hint().capacity()); w.write_to(&mut output as &mut dyn fmt::Write) .expect("impl Write for String is infallible"); output } c.bench_function("writeable_dyn/to_string/short", |b| { b.iter(|| { writeable_dyn_to_string(&WriteableMessage { message: black_box(SHORT_STR), }) }); }); c.bench_function("writeable_dyn/to_string/medium", |b| { b.iter(|| { writeable_dyn_to_string(&WriteableMessage { message: black_box(MEDIUM_STR), }) }); }); c.bench_function("writeable_dyn/to_string/long", |b| { b.iter(|| { writeable_dyn_to_string(&WriteableMessage { message: black_box(LONG_STR), }) }); }); } #[cfg(feature = "bench")] fn display_benches(c: &mut Criterion) { c.bench_function("display/to_string/short", |b| { b.iter(|| { DisplayMessage { message: black_box(SHORT_STR), } .to_string() }); }); c.bench_function("display/to_string/medium", |b| { b.iter(|| { DisplayMessage { message: black_box(MEDIUM_STR), } .to_string() }); }); c.bench_function("display/to_string/long", |b| { b.iter(|| { DisplayMessage { message: black_box(LONG_STR), } .to_string() }); }); } #[cfg(feature = "bench")] fn complex_benches(c: &mut Criterion) { const COMPLEX_WRITEABLE_MEDIUM: ComplexWriteable = ComplexWriteable { prefix: "There are ", n0: 55, infix: " apples and ", n1: 8124, suffix: " oranges", }; c.bench_function("complex/write_to_string/medium", |b| { b.iter(|| { black_box(COMPLEX_WRITEABLE_MEDIUM) .write_to_string() .into_owned() }); }); c.bench_function("complex/display_to_string/medium", |b| { b.iter(|| black_box(COMPLEX_WRITEABLE_MEDIUM).to_string()); }); const REFERENCE_STRS: [&str; 6] = [ "There are 55 apples and 8124 oranges", "There are 55 apples and 0 oranges", "There are no apples", SHORT_STR, MEDIUM_STR, LONG_STR, ]; c.bench_function("complex/cmp_str", |b| { b.iter(|| { black_box(REFERENCE_STRS) .map(|s| writeable::cmp_str(black_box(&COMPLEX_WRITEABLE_MEDIUM), s)) }); }); } criterion_group!(benches, overview_bench,); criterion_main!(benches); writeable-0.6.0/examples/writeable_message.rs000064400000000000000000000032061046102023000174540ustar 00000000000000// 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 ). // This example illustrates a very simple type implementing Writeable. #![no_main] // https://github.com/unicode-org/icu4x/issues/395 icu_benchmark_macros::instrument!(); use std::fmt; use writeable::*; struct WriteableMessage(W); const GREETING: Part = Part { category: "meaning", value: "greeting", }; const EMOJI: Part = Part { category: "meaning", value: "emoji", }; impl Writeable for WriteableMessage { fn write_to_parts(&self, sink: &mut W) -> fmt::Result { use fmt::Write; sink.with_part(GREETING, |g| { g.write_str("Hello")?; g.write_str(" ")?; self.0.write_to(g) })?; sink.write_char(' ')?; sink.with_part(EMOJI, |e| e.write_char('😅')) } fn writeable_length_hint(&self) -> LengthHint { LengthHint::exact(11) + self.0.writeable_length_hint() } } impl fmt::Display for WriteableMessage { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.write_to(f) } } fn main() { let (string, parts) = writeable::_internal::writeable_to_parts_for_test(&WriteableMessage("world")); assert_eq!(string, "Hello world 😅"); // Print the greeting only let (start, end, _) = parts .into_iter() .find(|(_, _, part)| part == &GREETING) .unwrap(); println!("{}", &string[start..end]); } writeable-0.6.0/src/cmp.rs000064400000000000000000000110271046102023000135220ustar 00000000000000// 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::Writeable; use core::cmp::Ordering; use core::fmt; struct WriteComparator<'a> { code_units: &'a [u8], result: Ordering, } /// This is an infallible impl. Functions always return Ok, not Err. impl fmt::Write for WriteComparator<'_> { #[inline] fn write_str(&mut self, other: &str) -> fmt::Result { if self.result != Ordering::Equal { return Ok(()); } let cmp_len = core::cmp::min(other.len(), self.code_units.len()); let (this, remainder) = self.code_units.split_at(cmp_len); self.code_units = remainder; self.result = this.cmp(other.as_bytes()); Ok(()) } } impl<'a> WriteComparator<'a> { #[inline] fn new(code_units: &'a [u8]) -> Self { Self { code_units, result: Ordering::Equal, } } #[inline] fn finish(self) -> Ordering { if matches!(self.result, Ordering::Equal) && !self.code_units.is_empty() { // Self is longer than Other Ordering::Greater } else { self.result } } } /// Compares the contents of a [`Writeable`] to the given UTF-8 bytes without allocating memory. /// /// For more details, see: [`cmp_str`] pub fn cmp_utf8(writeable: &impl Writeable, other: &[u8]) -> Ordering { let mut wc = WriteComparator::new(other); let _ = writeable.write_to(&mut wc); wc.finish().reverse() } /// Compares the contents of a `Writeable` to the given bytes /// without allocating a String to hold the `Writeable` contents. /// /// This returns a lexicographical comparison, the same as if the Writeable /// were first converted to a String and then compared with `Ord`. For a /// string ordering suitable for display to end users, use a localized /// collation crate, such as `icu_collator`. /// /// # Examples /// /// ``` /// use core::cmp::Ordering; /// use core::fmt; /// use writeable::Writeable; /// /// struct WelcomeMessage<'s> { /// pub name: &'s str, /// } /// /// impl<'s> Writeable for WelcomeMessage<'s> { /// // see impl in Writeable docs /// # fn write_to(&self, sink: &mut W) -> fmt::Result { /// # sink.write_str("Hello, ")?; /// # sink.write_str(self.name)?; /// # sink.write_char('!')?; /// # Ok(()) /// # } /// } /// /// let message = WelcomeMessage { name: "Alice" }; /// let message_str = message.write_to_string(); /// /// assert_eq!(Ordering::Equal, writeable::cmp_str(&message, "Hello, Alice!")); /// /// assert_eq!(Ordering::Greater, writeable::cmp_str(&message, "Alice!")); /// assert_eq!(Ordering::Greater, (*message_str).cmp("Alice!")); /// /// assert_eq!(Ordering::Less, writeable::cmp_str(&message, "Hello, Bob!")); /// assert_eq!(Ordering::Less, (*message_str).cmp("Hello, Bob!")); /// ``` #[inline] pub fn cmp_str(writeable: &impl Writeable, other: &str) -> Ordering { cmp_utf8(writeable, other.as_bytes()) } #[cfg(test)] mod tests { use super::*; use core::fmt::Write; mod data { include!("../tests/data/data.rs"); } #[test] fn test_write_char() { for a in data::KEBAB_CASE_STRINGS { for b in data::KEBAB_CASE_STRINGS { let mut wc = WriteComparator::new(a.as_bytes()); for ch in b.chars() { wc.write_char(ch).unwrap(); } assert_eq!(a.cmp(b), wc.finish(), "{a} <=> {b}"); } } } #[test] fn test_write_str() { for a in data::KEBAB_CASE_STRINGS { for b in data::KEBAB_CASE_STRINGS { let mut wc = WriteComparator::new(a.as_bytes()); wc.write_str(b).unwrap(); assert_eq!(a.cmp(b), wc.finish(), "{a} <=> {b}"); } } } #[test] fn test_mixed() { for a in data::KEBAB_CASE_STRINGS { for b in data::KEBAB_CASE_STRINGS { let mut wc = WriteComparator::new(a.as_bytes()); let mut first = true; for substr in b.split('-') { if first { first = false; } else { wc.write_char('-').unwrap(); } wc.write_str(substr).unwrap(); } assert_eq!(a.cmp(b), wc.finish(), "{a} <=> {b}"); } } } } writeable-0.6.0/src/either.rs000064400000000000000000000023301046102023000142200ustar 00000000000000// 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::*; use ::either::Either; /// A [`Writeable`] impl that delegates to one type or another type. impl Writeable for Either where W0: Writeable, W1: Writeable, { fn write_to(&self, sink: &mut W) -> fmt::Result { match self { Either::Left(w) => w.write_to(sink), Either::Right(w) => w.write_to(sink), } } fn write_to_parts(&self, sink: &mut S) -> fmt::Result { match self { Either::Left(w) => w.write_to_parts(sink), Either::Right(w) => w.write_to_parts(sink), } } fn writeable_length_hint(&self) -> LengthHint { match self { Either::Left(w) => w.writeable_length_hint(), Either::Right(w) => w.writeable_length_hint(), } } fn write_to_string(&self) -> Cow { match self { Either::Left(w) => w.write_to_string(), Either::Right(w) => w.write_to_string(), } } } writeable-0.6.0/src/impls.rs000064400000000000000000000205451046102023000140740ustar 00000000000000// 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::*; use core::fmt; macro_rules! impl_write_num { ($u:ty, $i:ty, $test:ident) => { impl $crate::Writeable for $u { fn write_to(&self, sink: &mut W) -> core::fmt::Result { const MAX_LEN: usize = <$u>::MAX.ilog10() as usize + 1; let mut buf = [b'0'; MAX_LEN]; let mut n = *self; let mut i = MAX_LEN; #[allow(clippy::indexing_slicing)] // n < 10^i while n != 0 { i -= 1; buf[i] = b'0' + (n % 10) as u8; n /= 10; } if i == MAX_LEN { debug_assert_eq!(*self, 0); i -= 1; } #[allow(clippy::indexing_slicing)] // buf is ASCII let s = unsafe { core::str::from_utf8_unchecked(&buf[i..]) }; sink.write_str(s) } fn writeable_length_hint(&self) -> $crate::LengthHint { LengthHint::exact(self.checked_ilog10().unwrap_or(0) as usize + 1) } } impl $crate::Writeable for $i { fn write_to(&self, sink: &mut W) -> core::fmt::Result { if self.is_negative() { sink.write_str("-")?; } self.unsigned_abs().write_to(sink) } fn writeable_length_hint(&self) -> $crate::LengthHint { $crate::LengthHint::exact(if self.is_negative() { 1 } else { 0 }) + self.unsigned_abs().writeable_length_hint() } } #[test] fn $test() { use $crate::assert_writeable_eq; assert_writeable_eq!(&(0 as $u), "0"); assert_writeable_eq!(&(0 as $i), "0"); assert_writeable_eq!(&(-0 as $i), "0"); assert_writeable_eq!(&(1 as $u), "1"); assert_writeable_eq!(&(1 as $i), "1"); assert_writeable_eq!(&(-1 as $i), "-1"); assert_writeable_eq!(&(9 as $u), "9"); assert_writeable_eq!(&(9 as $i), "9"); assert_writeable_eq!(&(-9 as $i), "-9"); assert_writeable_eq!(&(10 as $u), "10"); assert_writeable_eq!(&(10 as $i), "10"); assert_writeable_eq!(&(-10 as $i), "-10"); assert_writeable_eq!(&(99 as $u), "99"); assert_writeable_eq!(&(99 as $i), "99"); assert_writeable_eq!(&(-99 as $i), "-99"); assert_writeable_eq!(&(100 as $u), "100"); assert_writeable_eq!(&(-100 as $i), "-100"); assert_writeable_eq!(&<$u>::MAX, <$u>::MAX.to_string()); assert_writeable_eq!(&<$i>::MAX, <$i>::MAX.to_string()); assert_writeable_eq!(&<$i>::MIN, <$i>::MIN.to_string()); use rand::{rngs::SmallRng, Rng, SeedableRng}; let mut rng = SmallRng::seed_from_u64(4); // chosen by fair dice roll. // guaranteed to be random. for _ in 0..1000 { let rand = rng.gen::<$u>(); assert_writeable_eq!(rand, rand.to_string()); } } }; } impl_write_num!(u8, i8, test_u8); impl_write_num!(u16, i16, test_u16); impl_write_num!(u32, i32, test_u32); impl_write_num!(u64, i64, test_u64); impl_write_num!(u128, i128, test_u128); impl_write_num!(usize, isize, test_usize); impl Writeable for str { #[inline] fn write_to(&self, sink: &mut W) -> fmt::Result { sink.write_str(self) } #[inline] fn writeable_length_hint(&self) -> LengthHint { LengthHint::exact(self.len()) } /// Returns a borrowed `str`. /// /// # Examples /// /// ``` /// use std::borrow::Cow; /// use writeable::Writeable; /// /// let cow = "foo".write_to_string(); /// assert!(matches!(cow, Cow::Borrowed(_))); /// ``` #[inline] fn write_to_string(&self) -> Cow { Cow::Borrowed(self) } } impl Writeable for String { #[inline] fn write_to(&self, sink: &mut W) -> fmt::Result { sink.write_str(self) } #[inline] fn writeable_length_hint(&self) -> LengthHint { LengthHint::exact(self.len()) } #[inline] fn write_to_string(&self) -> Cow { Cow::Borrowed(self) } } impl Writeable for char { #[inline] fn write_to(&self, sink: &mut W) -> fmt::Result { sink.write_char(*self) } #[inline] fn writeable_length_hint(&self) -> LengthHint { LengthHint::exact(self.len_utf8()) } #[inline] fn write_to_string(&self) -> Cow { let mut s = String::with_capacity(self.len_utf8()); s.push(*self); Cow::Owned(s) } } impl Writeable for &T { #[inline] fn write_to(&self, sink: &mut W) -> fmt::Result { (*self).write_to(sink) } #[inline] fn write_to_parts(&self, sink: &mut W) -> fmt::Result { (*self).write_to_parts(sink) } #[inline] fn writeable_length_hint(&self) -> LengthHint { (*self).writeable_length_hint() } #[inline] fn write_to_string(&self) -> Cow { (*self).write_to_string() } } macro_rules! impl_write_smart_pointer { ($ty:path, T: $extra_bound:path) => { impl<'a, T: ?Sized + Writeable + $extra_bound> Writeable for $ty { #[inline] fn write_to(&self, sink: &mut W) -> fmt::Result { core::borrow::Borrow::::borrow(self).write_to(sink) } #[inline] fn write_to_parts(&self, sink: &mut W) -> fmt::Result { core::borrow::Borrow::::borrow(self).write_to_parts(sink) } #[inline] fn writeable_length_hint(&self) -> LengthHint { core::borrow::Borrow::::borrow(self).writeable_length_hint() } #[inline] fn write_to_string(&self) -> Cow { core::borrow::Borrow::::borrow(self).write_to_string() } } }; ($ty:path) => { // Add a harmless duplicate Writeable bound impl_write_smart_pointer!($ty, T: Writeable); }; } impl_write_smart_pointer!(Cow<'a, T>, T: alloc::borrow::ToOwned); impl_write_smart_pointer!(alloc::boxed::Box); impl_write_smart_pointer!(alloc::rc::Rc); impl_write_smart_pointer!(alloc::sync::Arc); #[test] fn test_string_impls() { fn check_writeable_slice(writeables: &[W]) { assert_writeable_eq!(&writeables[0], ""); assert_writeable_eq!(&writeables[1], "abc"); assert!(matches!(writeables[0].write_to_string(), Cow::Borrowed(_))); assert!(matches!(writeables[1].write_to_string(), Cow::Borrowed(_))); } // test str impl let arr: &[&str] = &["", "abc"]; check_writeable_slice(arr); // test String impl let arr: &[String] = &[String::new(), "abc".to_owned()]; check_writeable_slice(arr); // test char impl let chars = ['a', 'β', '你', '😀']; for i in 0..chars.len() { let s = String::from(chars[i]); assert_writeable_eq!(&chars[i], s); for j in 0..chars.len() { assert_eq!( crate::cmp_str(&chars[j], &s), chars[j].cmp(&chars[i]), "{:?} vs {:?}", chars[j], chars[i] ); } } // test Cow impl let arr: &[Cow] = &[Cow::Borrowed(""), Cow::Owned("abc".to_string())]; check_writeable_slice(arr); // test Box impl let arr: &[Box] = &["".into(), "abc".into()]; check_writeable_slice(arr); // test Rc impl let arr: &[alloc::rc::Rc] = &["".into(), "abc".into()]; check_writeable_slice(arr); // test Arc impl let arr: &[alloc::sync::Arc] = &["".into(), "abc".into()]; check_writeable_slice(arr); // test &T impl let arr: &[&String] = &[&String::new(), &"abc".to_owned()]; check_writeable_slice(arr); } writeable-0.6.0/src/lib.rs000064400000000000000000000355471046102023000135260ustar 00000000000000// 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 ). // https://github.com/unicode-org/icu4x/blob/main/documents/process/boilerplate.md#library-annotations #![cfg_attr(all(not(test), not(doc)), no_std)] #![cfg_attr( not(test), deny( clippy::indexing_slicing, clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::exhaustive_structs, clippy::exhaustive_enums, missing_debug_implementations, ) )] //! `writeable` is a utility crate of the [`ICU4X`] project. //! //! It includes [`Writeable`], a core trait representing an object that can be written to a //! sink implementing `std::fmt::Write`. It is an alternative to `std::fmt::Display` with the //! addition of a function indicating the number of bytes to be written. //! //! `Writeable` improves upon `std::fmt::Display` in two ways: //! //! 1. More efficient, since the sink can pre-allocate bytes. //! 2. Smaller code, since the format machinery can be short-circuited. //! //! # Examples //! //! ``` //! use std::fmt; //! use writeable::assert_writeable_eq; //! use writeable::LengthHint; //! use writeable::Writeable; //! //! struct WelcomeMessage<'s> { //! pub name: &'s str, //! } //! //! impl<'s> Writeable for WelcomeMessage<'s> { //! fn write_to(&self, sink: &mut W) -> fmt::Result { //! sink.write_str("Hello, ")?; //! sink.write_str(self.name)?; //! sink.write_char('!')?; //! Ok(()) //! } //! //! fn writeable_length_hint(&self) -> LengthHint { //! // "Hello, " + '!' + length of name //! LengthHint::exact(8 + self.name.len()) //! } //! } //! //! let message = WelcomeMessage { name: "Alice" }; //! assert_writeable_eq!(&message, "Hello, Alice!"); //! //! // Types implementing `Writeable` are recommended to also implement `fmt::Display`. //! // This can be simply done by redirecting to the `Writeable` implementation: //! writeable::impl_display_with_writeable!(WelcomeMessage<'_>); //! assert_eq!(message.to_string(), "Hello, Alice!"); //! ``` //! //! [`ICU4X`]: ../icu/index.html extern crate alloc; mod cmp; #[cfg(feature = "either")] mod either; mod impls; mod ops; mod parts_write_adapter; mod testing; mod to_string_or_borrow; mod try_writeable; use alloc::borrow::Cow; use alloc::string::String; use core::fmt; pub use cmp::{cmp_str, cmp_utf8}; pub use to_string_or_borrow::to_string_or_borrow; pub use try_writeable::TryWriteable; /// Helper types for trait impls. pub mod adapters { use super::*; pub use parts_write_adapter::CoreWriteAsPartsWrite; pub use parts_write_adapter::WithPart; pub use try_writeable::TryWriteableInfallibleAsWriteable; pub use try_writeable::WriteableAsTryWriteableInfallible; #[derive(Debug)] #[allow(clippy::exhaustive_structs)] // newtype pub struct LossyWrap(pub T); impl Writeable for LossyWrap { fn write_to(&self, sink: &mut W) -> fmt::Result { let _ = self.0.try_write_to(sink)?; Ok(()) } fn writeable_length_hint(&self) -> LengthHint { self.0.writeable_length_hint() } } impl fmt::Display for LossyWrap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let _ = self.0.try_write_to(f)?; Ok(()) } } } #[doc(hidden)] // for testing and macros pub mod _internal { pub use super::testing::try_writeable_to_parts_for_test; pub use super::testing::writeable_to_parts_for_test; pub use alloc::string::String; } /// A hint to help consumers of `Writeable` pre-allocate bytes before they call /// [`write_to`](Writeable::write_to). /// /// This behaves like `Iterator::size_hint`: it is a tuple where the first element is the /// lower bound, and the second element is the upper bound. If the upper bound is `None` /// either there is no known upper bound, or the upper bound is larger than `usize`. /// /// `LengthHint` implements std`::ops::{Add, Mul}` and similar traits for easy composition. /// During computation, the lower bound will saturate at `usize::MAX`, while the upper /// bound will become `None` if `usize::MAX` is exceeded. #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[non_exhaustive] pub struct LengthHint(pub usize, pub Option); impl LengthHint { pub fn undefined() -> Self { Self(0, None) } /// `write_to` will use exactly n bytes. pub fn exact(n: usize) -> Self { Self(n, Some(n)) } /// `write_to` will use at least n bytes. pub fn at_least(n: usize) -> Self { Self(n, None) } /// `write_to` will use at most n bytes. pub fn at_most(n: usize) -> Self { Self(0, Some(n)) } /// `write_to` will use between `n` and `m` bytes. pub fn between(n: usize, m: usize) -> Self { Self(Ord::min(n, m), Some(Ord::max(n, m))) } /// Returns a recommendation for the number of bytes to pre-allocate. /// If an upper bound exists, this is used, otherwise the lower bound /// (which might be 0). /// /// # Examples /// /// ``` /// use writeable::Writeable; /// /// fn pre_allocate_string(w: &impl Writeable) -> String { /// String::with_capacity(w.writeable_length_hint().capacity()) /// } /// ``` pub fn capacity(&self) -> usize { self.1.unwrap_or(self.0) } /// Returns whether the `LengthHint` indicates that the string is exactly 0 bytes long. pub fn is_zero(&self) -> bool { self.1 == Some(0) } } /// [`Part`]s are used as annotations for formatted strings. /// /// For example, a string like `Alice, Bob` could assign a `NAME` part to the /// substrings `Alice` and `Bob`, and a `PUNCTUATION` part to `, `. This allows /// for example to apply styling only to names. /// /// `Part` contains two fields, whose usage is left up to the producer of the [`Writeable`]. /// Conventionally, the `category` field will identify the formatting logic that produces /// the string/parts, whereas the `value` field will have semantic meaning. `NAME` and /// `PUNCTUATION` could thus be defined as /// ``` /// # use writeable::Part; /// const NAME: Part = Part { /// category: "userlist", /// value: "name", /// }; /// const PUNCTUATION: Part = Part { /// category: "userlist", /// value: "punctuation", /// }; /// ``` /// /// That said, consumers should not usually have to inspect `Part` internals. Instead, /// formatters should expose the `Part`s they produces as constants. #[derive(Clone, Copy, Debug, PartialEq)] #[allow(clippy::exhaustive_structs)] // stable pub struct Part { pub category: &'static str, pub value: &'static str, } impl Part { /// A part that should annotate error segments in [`TryWriteable`] output. /// /// For an example, see [`TryWriteable`]. pub const ERROR: Part = Part { category: "writeable", value: "error", }; } /// A sink that supports annotating parts of the string with `Part`s. pub trait PartsWrite: fmt::Write { type SubPartsWrite: PartsWrite + ?Sized; fn with_part( &mut self, part: Part, f: impl FnMut(&mut Self::SubPartsWrite) -> fmt::Result, ) -> fmt::Result; } /// `Writeable` is an alternative to `std::fmt::Display` with the addition of a length function. pub trait Writeable { /// Writes a string to the given sink. Errors from the sink are bubbled up. /// The default implementation delegates to `write_to_parts`, and discards any /// `Part` annotations. fn write_to(&self, sink: &mut W) -> fmt::Result { self.write_to_parts(&mut parts_write_adapter::CoreWriteAsPartsWrite(sink)) } /// Write bytes and `Part` annotations to the given sink. Errors from the /// sink are bubbled up. The default implementation delegates to `write_to`, /// and doesn't produce any `Part` annotations. fn write_to_parts(&self, sink: &mut S) -> fmt::Result { self.write_to(sink) } /// Returns a hint for the number of UTF-8 bytes that will be written to the sink. /// /// Override this method if it can be computed quickly. fn writeable_length_hint(&self) -> LengthHint { LengthHint::undefined() } /// Creates a new `String` with the data from this `Writeable`. Like `ToString`, /// but smaller and faster. /// /// The default impl allocates an owned `String`. However, if it is possible to return a /// borrowed string, overwrite this method to return a `Cow::Borrowed`. /// /// To remove the `Cow` wrapper, call `.into_owned()` or `.as_str()` as appropriate. /// /// # Examples /// /// Inspect a `Writeable` before writing it to the sink: /// /// ``` /// use core::fmt::{Result, Write}; /// use writeable::Writeable; /// /// fn write_if_ascii(w: &W, sink: &mut S) -> Result /// where /// W: Writeable + ?Sized, /// S: Write + ?Sized, /// { /// let s = w.write_to_string(); /// if s.is_ascii() { /// sink.write_str(&s) /// } else { /// Ok(()) /// } /// } /// ``` /// /// Convert the `Writeable` into a fully owned `String`: /// /// ``` /// use writeable::Writeable; /// /// fn make_string(w: &impl Writeable) -> String { /// w.write_to_string().into_owned() /// } /// ``` fn write_to_string(&self) -> Cow { let hint = self.writeable_length_hint(); if hint.is_zero() { return Cow::Borrowed(""); } let mut output = String::with_capacity(hint.capacity()); let _ = self.write_to(&mut output); Cow::Owned(output) } } /// Implements [`Display`](core::fmt::Display) for types that implement [`Writeable`]. /// /// It's recommended to do this for every [`Writeable`] type, as it will add /// support for `core::fmt` features like [`fmt!`](std::fmt), /// [`print!`](std::print), [`write!`](std::write), etc. /// /// This macro also adds a concrete `to_string` function. This function will shadow the /// standard library `ToString`, using the more efficient writeable-based code path. /// To add only `Display`, use the `@display` macro variant. #[macro_export] macro_rules! impl_display_with_writeable { (@display, $type:ty) => { /// This trait is implemented for compatibility with [`fmt!`](alloc::fmt). /// To create a string, [`Writeable::write_to_string`] is usually more efficient. impl core::fmt::Display for $type { #[inline] fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { $crate::Writeable::write_to(&self, f) } } }; ($type:ty) => { $crate::impl_display_with_writeable!(@display, $type); impl $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. pub fn to_string(&self) -> $crate::_internal::String { $crate::Writeable::write_to_string(self).into_owned() } } }; } /// Testing macros for types implementing [`Writeable`]. /// /// Arguments, in order: /// /// 1. The [`Writeable`] under test /// 2. The expected string value /// 3. [`*_parts_eq`] only: a list of parts (`[(start, end, Part)]`) /// /// Any remaining arguments get passed to `format!` /// /// The macros tests the following: /// /// - Equality of string content /// - Equality of parts ([`*_parts_eq`] only) /// - Validity of size hint /// /// # Examples /// /// ``` /// # use writeable::Writeable; /// # use writeable::LengthHint; /// # use writeable::Part; /// # use writeable::assert_writeable_eq; /// # use writeable::assert_writeable_parts_eq; /// # use std::fmt::{self, Write}; /// /// const WORD: Part = Part { /// category: "foo", /// value: "word", /// }; /// /// struct Demo; /// impl Writeable for Demo { /// fn write_to_parts( /// &self, /// sink: &mut S, /// ) -> fmt::Result { /// sink.with_part(WORD, |w| w.write_str("foo")) /// } /// fn writeable_length_hint(&self) -> LengthHint { /// LengthHint::exact(3) /// } /// } /// /// writeable::impl_display_with_writeable!(Demo); /// /// assert_writeable_eq!(&Demo, "foo"); /// assert_writeable_eq!(&Demo, "foo", "Message: {}", "Hello World"); /// /// assert_writeable_parts_eq!(&Demo, "foo", [(0, 3, WORD)]); /// assert_writeable_parts_eq!( /// &Demo, /// "foo", /// [(0, 3, WORD)], /// "Message: {}", /// "Hello World" /// ); /// ``` /// /// [`*_parts_eq`]: assert_writeable_parts_eq #[macro_export] macro_rules! assert_writeable_eq { ($actual_writeable:expr, $expected_str:expr $(,)?) => { $crate::assert_writeable_eq!($actual_writeable, $expected_str, "") }; ($actual_writeable:expr, $expected_str:expr, $($arg:tt)+) => {{ $crate::assert_writeable_eq!(@internal, $actual_writeable, $expected_str, $($arg)*); }}; (@internal, $actual_writeable:expr, $expected_str:expr, $($arg:tt)+) => {{ let actual_writeable = &$actual_writeable; let (actual_str, actual_parts) = $crate::_internal::writeable_to_parts_for_test(actual_writeable); let actual_len = actual_str.len(); assert_eq!(actual_str, $expected_str, $($arg)*); assert_eq!(actual_str, $crate::Writeable::write_to_string(actual_writeable), $($arg)+); let length_hint = $crate::Writeable::writeable_length_hint(actual_writeable); let lower = length_hint.0; assert!( lower <= actual_len, "hint lower bound {lower} larger than actual length {actual_len}: {}", format!($($arg)*), ); if let Some(upper) = length_hint.1 { assert!( actual_len <= upper, "hint upper bound {upper} smaller than actual length {actual_len}: {}", format!($($arg)*), ); } assert_eq!(actual_writeable.to_string(), $expected_str); actual_parts // return for assert_writeable_parts_eq }}; } /// See [`assert_writeable_eq`]. #[macro_export] macro_rules! assert_writeable_parts_eq { ($actual_writeable:expr, $expected_str:expr, $expected_parts:expr $(,)?) => { $crate::assert_writeable_parts_eq!($actual_writeable, $expected_str, $expected_parts, "") }; ($actual_writeable:expr, $expected_str:expr, $expected_parts:expr, $($arg:tt)+) => {{ let actual_parts = $crate::assert_writeable_eq!(@internal, $actual_writeable, $expected_str, $($arg)*); assert_eq!(actual_parts, $expected_parts, $($arg)+); }}; } writeable-0.6.0/src/ops.rs000064400000000000000000000173051046102023000135510ustar 00000000000000// 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::LengthHint; impl core::ops::Add for LengthHint { type Output = Self; fn add(self, other: LengthHint) -> Self { LengthHint( self.0.saturating_add(other.0), match (self.1, other.1) { (Some(c), Some(d)) => c.checked_add(d), _ => None, }, ) } } impl core::ops::AddAssign for LengthHint { fn add_assign(&mut self, other: Self) { *self = *self + other; } } impl core::iter::Sum for LengthHint { fn sum(iter: I) -> Self where I: Iterator, { iter.fold(LengthHint::exact(0), core::ops::Add::add) } } impl core::ops::Add for LengthHint { type Output = Self; fn add(self, other: usize) -> Self { Self( self.0.saturating_add(other), self.1.and_then(|upper| upper.checked_add(other)), ) } } impl core::ops::AddAssign for LengthHint { fn add_assign(&mut self, other: usize) { *self = *self + other; } } impl core::ops::Mul for LengthHint { type Output = Self; fn mul(self, other: usize) -> Self { Self( self.0.saturating_mul(other), self.1.and_then(|upper| upper.checked_mul(other)), ) } } impl core::ops::MulAssign for LengthHint { fn mul_assign(&mut self, other: usize) { *self = *self * other; } } impl core::ops::BitOr for LengthHint { type Output = Self; /// Returns a new hint that is correct wherever `self` is correct, and wherever /// `other` is correct. /// /// Example: /// ``` /// # use writeable::{LengthHint, Writeable}; /// # use core::fmt; /// # fn coin_flip() -> bool { true } /// /// struct NonDeterministicWriteable(String, String); /// /// impl Writeable for NonDeterministicWriteable { /// fn write_to( /// &self, /// sink: &mut W, /// ) -> fmt::Result { /// sink.write_str(if coin_flip() { &self.0 } else { &self.1 }) /// } /// /// fn writeable_length_hint(&self) -> LengthHint { /// LengthHint::exact(self.0.len()) | LengthHint::exact(self.1.len()) /// } /// } /// /// writeable::impl_display_with_writeable!(NonDeterministicWriteable); /// ``` fn bitor(self, other: LengthHint) -> Self { LengthHint( Ord::min(self.0, other.0), match (self.1, other.1) { (Some(c), Some(d)) => Some(Ord::max(c, d)), _ => None, }, ) } } impl core::ops::BitOrAssign for LengthHint { fn bitor_assign(&mut self, other: Self) { *self = *self | other; } } impl core::iter::Sum for LengthHint { fn sum(iter: I) -> Self where I: Iterator, { LengthHint::exact(iter.sum::()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_add() { assert_eq!(LengthHint::exact(3) + 2, LengthHint::exact(5)); assert_eq!( LengthHint::exact(3) + LengthHint::exact(2), LengthHint::exact(5) ); assert_eq!( LengthHint::exact(3) + LengthHint::undefined(), LengthHint::at_least(3) ); assert_eq!(LengthHint::undefined() + 2, LengthHint::at_least(2)); assert_eq!( LengthHint::undefined() + LengthHint::exact(2), LengthHint::at_least(2) ); assert_eq!( LengthHint::undefined() + LengthHint::undefined(), LengthHint::undefined() ); assert_eq!( LengthHint::at_least(15) + LengthHint::exact(3), LengthHint::at_least(18) ); assert_eq!( LengthHint::at_least(15) + LengthHint::at_most(3), LengthHint::at_least(15) ); assert_eq!(LengthHint::between(48, 92) + 5, LengthHint::between(53, 97)); let mut len = LengthHint::exact(5); len += LengthHint::exact(3); assert_eq!(len, LengthHint::exact(8)); len += 2; assert_eq!(len, LengthHint::exact(10)); len += LengthHint::undefined(); assert_eq!(len, LengthHint::at_least(10)); len += LengthHint::exact(3); assert_eq!(len, LengthHint::at_least(13)); len += 2; assert_eq!(len, LengthHint::at_least(15)); len += LengthHint::undefined(); assert_eq!(len, LengthHint::at_least(15)); assert_eq!( LengthHint::between(usize::MAX - 10, usize::MAX - 5) + LengthHint::exact(20), LengthHint::at_least(usize::MAX) ); } #[test] fn test_sum() { let lens = [ LengthHint::exact(4), LengthHint::exact(1), LengthHint::exact(1), ]; assert_eq!( lens.iter().copied().sum::(), LengthHint::exact(6) ); let lens = [ LengthHint::exact(4), LengthHint::undefined(), LengthHint::at_least(1), ]; assert_eq!( lens.iter().copied().sum::(), LengthHint::at_least(5) ); let lens = [ LengthHint::exact(4), LengthHint::undefined(), LengthHint::at_most(1), ]; assert_eq!( lens.iter().copied().sum::(), LengthHint::at_least(4) ); let lens = [4, 1, 1]; assert_eq!( lens.iter().copied().sum::(), LengthHint::exact(6) ); } #[test] fn test_mul() { assert_eq!(LengthHint::exact(3) * 2, LengthHint::exact(6)); assert_eq!(LengthHint::undefined() * 2, LengthHint::undefined()); assert_eq!( LengthHint::between(48, 92) * 2, LengthHint::between(96, 184) ); let mut len = LengthHint::exact(5); len *= 2; assert_eq!(len, LengthHint::exact(10)); assert_eq!( LengthHint::between(usize::MAX - 10, usize::MAX - 5) * 2, LengthHint::at_least(usize::MAX) ); } #[test] fn test_bitor() { assert_eq!( LengthHint::exact(3) | LengthHint::exact(2), LengthHint::between(2, 3) ); assert_eq!( LengthHint::exact(3) | LengthHint::undefined(), LengthHint::undefined() ); assert_eq!( LengthHint::undefined() | LengthHint::undefined(), LengthHint::undefined() ); assert_eq!( LengthHint::exact(10) | LengthHint::exact(10), LengthHint::exact(10) ); assert_eq!( LengthHint::at_least(15) | LengthHint::exact(3), LengthHint::at_least(3) ); assert_eq!( LengthHint::at_least(15) | LengthHint::at_most(18), LengthHint::undefined() ); assert_eq!( LengthHint::at_least(15) | LengthHint::at_least(18), LengthHint::at_least(15) ); assert_eq!( LengthHint::at_most(15) | LengthHint::at_most(18), LengthHint::at_most(18) ); assert_eq!( LengthHint::between(5, 10) | LengthHint::at_most(3), LengthHint::at_most(10) ); let mut len = LengthHint::exact(5); len |= LengthHint::exact(3); assert_eq!(len, LengthHint::between(5, 3)); } } writeable-0.6.0/src/parts_write_adapter.rs000064400000000000000000000054371046102023000170160ustar 00000000000000// 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::*; /// A wrapper around a type implementing [`fmt::Write`] that implements [`PartsWrite`]. #[derive(Debug)] #[allow(clippy::exhaustive_structs)] // newtype pub struct CoreWriteAsPartsWrite(pub W); impl fmt::Write for CoreWriteAsPartsWrite { #[inline] fn write_str(&mut self, s: &str) -> fmt::Result { self.0.write_str(s) } #[inline] fn write_char(&mut self, c: char) -> fmt::Result { self.0.write_char(c) } } impl PartsWrite for CoreWriteAsPartsWrite { type SubPartsWrite = CoreWriteAsPartsWrite; #[inline] fn with_part( &mut self, _part: Part, mut f: impl FnMut(&mut Self::SubPartsWrite) -> fmt::Result, ) -> fmt::Result { f(self) } } /// A [`Writeable`] that writes out the given part. /// /// # Examples /// /// ``` /// use writeable::adapters::WithPart; /// use writeable::assert_writeable_parts_eq; /// use writeable::Part; /// /// // Simple usage: /// /// const PART: Part = Part { /// category: "foo", /// value: "bar", /// }; /// /// assert_writeable_parts_eq!( /// WithPart { /// writeable: "Hello World", /// part: PART /// }, /// "Hello World", /// [(0, 11, PART)], /// ); /// /// // Can be nested: /// /// const PART2: Part = Part { /// category: "foo2", /// value: "bar2", /// }; /// /// assert_writeable_parts_eq!( /// WithPart { /// writeable: WithPart { /// writeable: "Hello World", /// part: PART /// }, /// part: PART2 /// }, /// "Hello World", /// [(0, 11, PART), (0, 11, PART2)], /// ); /// ``` #[derive(Debug)] #[allow(clippy::exhaustive_structs)] // public adapter pub struct WithPart { pub part: Part, pub writeable: T, } impl Writeable for WithPart { #[inline] fn write_to(&self, sink: &mut W) -> fmt::Result { self.writeable.write_to(sink) } #[inline] fn write_to_parts(&self, sink: &mut W) -> fmt::Result { sink.with_part(self.part, |w| self.writeable.write_to_parts(w)) } #[inline] fn writeable_length_hint(&self) -> LengthHint { self.writeable.writeable_length_hint() } #[inline] fn write_to_string(&self) -> Cow { self.writeable.write_to_string() } } impl fmt::Display for WithPart { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Writeable::write_to(&self, f) } } writeable-0.6.0/src/testing.rs000064400000000000000000000043531046102023000144240ustar 00000000000000// 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::*; use alloc::string::String; use alloc::vec::Vec; pub(crate) struct TestWriter { pub(crate) string: String, pub(crate) parts: Vec<(usize, usize, Part)>, } impl TestWriter { pub(crate) fn finish(mut self) -> (String, Vec<(usize, usize, Part)>) { // Sort by first open and last closed self.parts .sort_unstable_by_key(|(begin, end, _)| (*begin, end.wrapping_neg())); (self.string, self.parts) } } impl fmt::Write for TestWriter { fn write_str(&mut self, s: &str) -> fmt::Result { self.string.write_str(s) } fn write_char(&mut self, c: char) -> fmt::Result { self.string.write_char(c) } } impl PartsWrite for TestWriter { type SubPartsWrite = Self; fn with_part( &mut self, part: Part, mut f: impl FnMut(&mut Self::SubPartsWrite) -> fmt::Result, ) -> fmt::Result { let start = self.string.len(); f(self)?; let end = self.string.len(); if start < end { self.parts.push((start, end, part)); } Ok(()) } } #[allow(clippy::type_complexity)] pub fn writeable_to_parts_for_test( writeable: &W, ) -> (String, Vec<(usize, usize, Part)>) { let mut writer = TestWriter { string: alloc::string::String::new(), parts: Vec::new(), }; #[allow(clippy::expect_used)] // for test code writeable .write_to_parts(&mut writer) .expect("String writer infallible"); writer.finish() } #[allow(clippy::type_complexity)] pub fn try_writeable_to_parts_for_test( writeable: &W, ) -> (String, Vec<(usize, usize, Part)>, Option) { let mut writer = TestWriter { string: alloc::string::String::new(), parts: Vec::new(), }; #[allow(clippy::expect_used)] // for test code let result = writeable .try_write_to_parts(&mut writer) .expect("String writer infallible"); let (actual_str, actual_parts) = writer.finish(); (actual_str, actual_parts, result.err()) } writeable-0.6.0/src/to_string_or_borrow.rs000064400000000000000000000157711046102023000170570ustar 00000000000000// 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::Writeable; use alloc::borrow::Cow; use alloc::string::String; use core::fmt; /// Bytes that have been partially validated as UTF-8 up to an offset. struct PartiallyValidatedUtf8<'a> { // Safety Invariants: // 1. The offset is less than or equal to the length of the slice. // 2. The slice is valid UTF-8 up to the offset. slice: &'a [u8], offset: usize, } impl<'a> PartiallyValidatedUtf8<'a> { fn new(slice: &'a [u8]) -> Self { // Safety: Field invariants maintained here trivially: // 1. The offset 0 is ≤ all possible lengths of slice // 2. The slice contains nothing up to the offset zero Self { slice, offset: 0 } } /// Check whether the given string is the next chunk of unvalidated bytes. /// If so, increment offset and return true. Otherwise, return false. fn try_push(&mut self, valid_str: &str) -> bool { let new_offset = self.offset + valid_str.len(); if self.slice.get(self.offset..new_offset) == Some(valid_str.as_bytes()) { // Safety: Field invariants maintained here: // 1. In the line above, `self.slice.get()` returned `Some()` for `new_offset` at // the end of a `Range`, so `new_offset` is ≤ the length of `self.slice`. // 2. By invariant, we have already validated the string up to `self.offset`, and // the portion of the slice between `self.offset` and `new_offset` is equal to // `valid_str`, which is a `&str`, so the string is valid up to `new_offset`. self.offset = new_offset; true } else { false } } /// Return the validated portion as `&str`. fn validated_as_str(&self) -> &'a str { debug_assert!(self.offset <= self.slice.len()); // Safety: self.offset is a valid end index in a range (from field invariant) let valid_slice = unsafe { self.slice.get_unchecked(..self.offset) }; debug_assert!(core::str::from_utf8(valid_slice).is_ok()); // Safety: the UTF-8 of slice has been validated up to offset (from field invariant) unsafe { core::str::from_utf8_unchecked(valid_slice) } } } enum SliceOrString<'a> { Slice(PartiallyValidatedUtf8<'a>), String(String), } /// This is an infallible impl. Functions always return Ok, not Err. impl fmt::Write for SliceOrString<'_> { #[inline] fn write_str(&mut self, other: &str) -> fmt::Result { match self { SliceOrString::Slice(slice) => { if !slice.try_push(other) { // We failed to match. Convert to owned. let valid_str = slice.validated_as_str(); let mut owned = String::with_capacity(valid_str.len() + other.len()); owned.push_str(valid_str); owned.push_str(other); *self = SliceOrString::String(owned); } Ok(()) } SliceOrString::String(owned) => owned.write_str(other), } } } impl<'a> SliceOrString<'a> { #[inline] fn new(slice: &'a [u8]) -> Self { Self::Slice(PartiallyValidatedUtf8::new(slice)) } #[inline] fn finish(self) -> Cow<'a, str> { match self { SliceOrString::Slice(slice) => Cow::Borrowed(slice.validated_as_str()), SliceOrString::String(owned) => Cow::Owned(owned), } } } /// Writes the contents of a `Writeable` to a string, returning a reference /// to a slice if it matches the provided reference bytes, and allocating a /// String otherwise. /// /// This function is useful if you have borrowed bytes which you expect /// to be equal to a writeable a high percentage of the time. /// /// You can also use this function to make a more efficient implementation of /// [`Writeable::write_to_string`]. /// /// # Examples /// /// Basic usage and behavior: /// /// ``` /// use std::fmt; /// use std::borrow::Cow; /// use writeable::Writeable; /// /// struct WelcomeMessage<'s> { /// pub name: &'s str, /// } /// /// impl<'s> Writeable for WelcomeMessage<'s> { /// // see impl in Writeable docs /// # fn write_to(&self, sink: &mut W) -> fmt::Result { /// # sink.write_str("Hello, ")?; /// # sink.write_str(self.name)?; /// # sink.write_char('!')?; /// # Ok(()) /// # } /// } /// /// let message = WelcomeMessage { name: "Alice" }; /// /// assert!(matches!( /// writeable::to_string_or_borrow(&message, b""), /// Cow::Owned(s) if s == "Hello, Alice!" /// )); /// assert!(matches!( /// writeable::to_string_or_borrow(&message, b"Hello"), /// Cow::Owned(s) if s == "Hello, Alice!" /// )); /// assert!(matches!( /// writeable::to_string_or_borrow(&message, b"Hello, Bob!"), /// Cow::Owned(s) if s == "Hello, Alice!" /// )); /// assert!(matches!( /// writeable::to_string_or_borrow(&message, b"Hello, Alice!"), /// Cow::Borrowed("Hello, Alice!") /// )); /// /// // Borrowing can use a prefix: /// assert!(matches!( /// writeable::to_string_or_borrow(&message, b"Hello, Alice!..\xFF\x00\xFF"), /// Cow::Borrowed("Hello, Alice!") /// )); /// ``` /// /// Example use case: a function that transforms a string to lowercase. /// We are also able to write a more efficient implementation of /// [`Writeable::write_to_string`] in this situation. /// /// ``` /// use std::fmt; /// use std::borrow::Cow; /// use writeable::Writeable; /// /// struct MakeAsciiLower<'a>(&'a str); /// /// impl<'a> Writeable for MakeAsciiLower<'a> { /// fn write_to(&self, sink: &mut W) -> fmt::Result { /// for c in self.0.chars() { /// sink.write_char(c.to_ascii_lowercase())?; /// } /// Ok(()) /// } /// #[inline] /// fn write_to_string(&self) -> Cow { /// writeable::to_string_or_borrow(self, self.0.as_bytes()) /// } /// } /// /// fn make_lowercase(input: &str) -> Cow { /// let writeable = MakeAsciiLower(input); /// writeable::to_string_or_borrow(&writeable, input.as_bytes()) /// } /// /// assert!(matches!( /// make_lowercase("this is lowercase"), /// Cow::Borrowed("this is lowercase") /// )); /// assert!(matches!( /// make_lowercase("this is UPPERCASE"), /// Cow::Owned(s) if s == "this is uppercase" /// )); /// /// assert!(matches!( /// MakeAsciiLower("this is lowercase").write_to_string(), /// Cow::Borrowed("this is lowercase") /// )); /// assert!(matches!( /// MakeAsciiLower("this is UPPERCASE").write_to_string(), /// Cow::Owned(s) if s == "this is uppercase" /// )); /// ``` pub fn to_string_or_borrow<'a>( writeable: &impl Writeable, reference_bytes: &'a [u8], ) -> Cow<'a, str> { let mut sink = SliceOrString::new(reference_bytes); let _ = writeable.write_to(&mut sink); sink.finish() } writeable-0.6.0/src/try_writeable.rs000064400000000000000000000353261046102023000156270ustar 00000000000000// 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 super::*; use crate::parts_write_adapter::CoreWriteAsPartsWrite; use core::convert::Infallible; /// A writeable object that can fail while writing. /// /// The default [`Writeable`] trait returns a [`fmt::Error`], which originates from the sink. /// In contrast, this trait allows the _writeable itself_ to trigger an error as well. /// /// Implementations are expected to always make a _best attempt_ at writing to the sink /// and should write replacement values in the error state. Therefore, the returned `Result` /// can be safely ignored to emulate a "lossy" mode. /// /// Any error substrings should be annotated with [`Part::ERROR`]. /// /// # Implementer Notes /// /// This trait requires that implementers make a _best attempt_ at writing to the sink, /// _even in the error state_, such as with a placeholder or fallback string. /// /// In [`TryWriteable::try_write_to_parts()`], error substrings should be annotated with /// [`Part::ERROR`]. Because of this, writing to parts is not default-implemented like /// it is on [`Writeable`]. /// /// The trait is implemented on [`Result`] where `T` and `E` both implement [`Writeable`]; /// In the `Ok` case, `T` is written, and in the `Err` case, `E` is written as a fallback value. /// This impl, which writes [`Part::ERROR`], can be used as a basis for more advanced impls. /// /// # Examples /// /// Implementing on a custom type: /// /// ``` /// use core::fmt; /// use writeable::LengthHint; /// use writeable::PartsWrite; /// use writeable::TryWriteable; /// /// #[derive(Debug, PartialEq, Eq)] /// enum HelloWorldWriteableError { /// MissingName, /// } /// /// #[derive(Debug, PartialEq, Eq)] /// struct HelloWorldWriteable { /// pub name: Option<&'static str>, /// } /// /// impl TryWriteable for HelloWorldWriteable { /// type Error = HelloWorldWriteableError; /// /// fn try_write_to_parts( /// &self, /// sink: &mut S, /// ) -> Result, fmt::Error> { /// sink.write_str("Hello, ")?; /// // Use `impl TryWriteable for Result` to generate the error part: /// let err = self.name.ok_or("nobody").try_write_to_parts(sink)?.err(); /// sink.write_char('!')?; /// // Return a doubly-wrapped Result. /// // The outer Result is for fmt::Error, handled by the `?`s above. /// // The inner Result is for our own Self::Error. /// if err.is_none() { /// Ok(Ok(())) /// } else { /// Ok(Err(HelloWorldWriteableError::MissingName)) /// } /// } /// /// fn writeable_length_hint(&self) -> LengthHint { /// self.name.ok_or("nobody").writeable_length_hint() + 8 /// } /// } /// /// // Success case: /// writeable::assert_try_writeable_eq!( /// HelloWorldWriteable { /// name: Some("Alice") /// }, /// "Hello, Alice!" /// ); /// /// // Failure case, including the ERROR part: /// writeable::assert_try_writeable_parts_eq!( /// HelloWorldWriteable { name: None }, /// "Hello, nobody!", /// Err(HelloWorldWriteableError::MissingName), /// [(7, 13, writeable::Part::ERROR)] /// ); /// ``` pub trait TryWriteable { type Error; /// Writes the content of this writeable to a sink. /// /// If the sink hits an error, writing immediately ends, /// `Err(`[`fmt::Error`]`)` is returned, and the sink does not contain valid output. /// /// If the writeable hits an error, writing is continued with a replacement value, /// `Ok(Err(`[`TryWriteable::Error`]`))` is returned, and the caller may continue using the sink. /// /// # Lossy Mode /// /// The [`fmt::Error`] should always be handled, but the [`TryWriteable::Error`] can be /// ignored if a fallback string is desired instead of an error. /// /// To handle the sink error, but not the writeable error, write: /// /// ``` /// # use writeable::TryWriteable; /// # let my_writeable: Result<&str, &str> = Ok(""); /// # let mut sink = String::new(); /// let _ = my_writeable.try_write_to(&mut sink)?; /// # Ok::<(), core::fmt::Error>(()) /// ``` /// /// # Examples /// /// The following examples use `Result<&str, usize>`, which implements [`TryWriteable`] because both `&str` and `usize` do. /// /// Success case: /// /// ``` /// use writeable::TryWriteable; /// /// let w: Result<&str, usize> = Ok("success"); /// let mut sink = String::new(); /// let result = w.try_write_to(&mut sink); /// /// assert_eq!(result, Ok(Ok(()))); /// assert_eq!(sink, "success"); /// ``` /// /// Failure case: /// /// ``` /// use writeable::TryWriteable; /// /// let w: Result<&str, usize> = Err(44); /// let mut sink = String::new(); /// let result = w.try_write_to(&mut sink); /// /// assert_eq!(result, Ok(Err(44))); /// assert_eq!(sink, "44"); /// ``` fn try_write_to( &self, sink: &mut W, ) -> Result, fmt::Error> { self.try_write_to_parts(&mut CoreWriteAsPartsWrite(sink)) } /// Writes the content of this writeable to a sink with parts (annotations). /// /// For more information, see: /// /// - [`TryWriteable::try_write_to()`] for the general behavior. /// - [`TryWriteable`] for an example with parts. /// - [`Part`] for more about parts. fn try_write_to_parts( &self, sink: &mut S, ) -> Result, fmt::Error>; /// Returns a hint for the number of UTF-8 bytes that will be written to the sink. /// /// This function returns the length of the "lossy mode" string; for more information, /// see [`TryWriteable::try_write_to()`]. fn writeable_length_hint(&self) -> LengthHint { LengthHint::undefined() } /// Writes the content of this writeable to a string. /// /// In the failure case, this function returns the error and the best-effort string ("lossy mode"). /// /// Examples /// /// ``` /// # use std::borrow::Cow; /// # use writeable::TryWriteable; /// // use the best-effort string /// let r1: Cow = Ok::<&str, u8>("ok") /// .try_write_to_string() /// .unwrap_or_else(|(_, s)| s); /// // propagate the error /// let r2: Result, u8> = Ok::<&str, u8>("ok") /// .try_write_to_string() /// .map_err(|(e, _)| e); /// ``` fn try_write_to_string(&self) -> Result, (Self::Error, Cow)> { let hint = self.writeable_length_hint(); if hint.is_zero() { return Ok(Cow::Borrowed("")); } let mut output = String::with_capacity(hint.capacity()); match self .try_write_to(&mut output) .unwrap_or_else(|fmt::Error| Ok(())) { Ok(()) => Ok(Cow::Owned(output)), Err(e) => Err((e, Cow::Owned(output))), } } } impl TryWriteable for Result where T: Writeable, E: Writeable + Clone, { type Error = E; #[inline] fn try_write_to( &self, sink: &mut W, ) -> Result, fmt::Error> { match self { Ok(t) => t.write_to(sink).map(Ok), Err(e) => e.write_to(sink).map(|()| Err(e.clone())), } } #[inline] fn try_write_to_parts( &self, sink: &mut S, ) -> Result, fmt::Error> { match self { Ok(t) => t.write_to_parts(sink).map(Ok), Err(e) => sink .with_part(Part::ERROR, |sink| e.write_to_parts(sink)) .map(|()| Err(e.clone())), } } #[inline] fn writeable_length_hint(&self) -> LengthHint { match self { Ok(t) => t.writeable_length_hint(), Err(e) => e.writeable_length_hint(), } } #[inline] fn try_write_to_string(&self) -> Result, (Self::Error, Cow)> { match self { Ok(t) => Ok(t.write_to_string()), Err(e) => Err((e.clone(), e.write_to_string())), } } } /// A wrapper around [`TryWriteable`] that implements [`Writeable`] /// if [`TryWriteable::Error`] is [`Infallible`]. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] #[allow(clippy::exhaustive_structs)] // transparent newtype pub struct TryWriteableInfallibleAsWriteable(pub T); impl Writeable for TryWriteableInfallibleAsWriteable where T: TryWriteable, { #[inline] fn write_to(&self, sink: &mut W) -> fmt::Result { match self.0.try_write_to(sink) { Ok(Ok(())) => Ok(()), Ok(Err(infallible)) => match infallible {}, Err(e) => Err(e), } } #[inline] fn write_to_parts(&self, sink: &mut S) -> fmt::Result { match self.0.try_write_to_parts(sink) { Ok(Ok(())) => Ok(()), Ok(Err(infallible)) => match infallible {}, Err(e) => Err(e), } } #[inline] fn writeable_length_hint(&self) -> LengthHint { self.0.writeable_length_hint() } #[inline] fn write_to_string(&self) -> Cow { match self.0.try_write_to_string() { Ok(s) => s, Err((infallible, _)) => match infallible {}, } } } impl fmt::Display for TryWriteableInfallibleAsWriteable where T: TryWriteable, { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.write_to(f) } } /// A wrapper around [`Writeable`] that implements [`TryWriteable`] /// with [`TryWriteable::Error`] set to [`Infallible`]. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] #[allow(clippy::exhaustive_structs)] // transparent newtype pub struct WriteableAsTryWriteableInfallible(pub T); impl TryWriteable for WriteableAsTryWriteableInfallible where T: Writeable, { type Error = Infallible; #[inline] fn try_write_to( &self, sink: &mut W, ) -> Result, fmt::Error> { self.0.write_to(sink).map(Ok) } #[inline] fn try_write_to_parts( &self, sink: &mut S, ) -> Result, fmt::Error> { self.0.write_to_parts(sink).map(Ok) } #[inline] fn writeable_length_hint(&self) -> LengthHint { self.0.writeable_length_hint() } #[inline] fn try_write_to_string(&self) -> Result, (Infallible, Cow)> { Ok(self.0.write_to_string()) } } /// Testing macros for types implementing [`TryWriteable`]. /// /// Arguments, in order: /// /// 1. The [`TryWriteable`] under test /// 2. The expected string value /// 3. The expected result value, or `Ok(())` if omitted /// 3. [`*_parts_eq`] only: a list of parts (`[(start, end, Part)]`) /// /// Any remaining arguments get passed to `format!` /// /// The macros tests the following: /// /// - Equality of string content /// - Equality of parts ([`*_parts_eq`] only) /// - Validity of size hint /// /// For a usage example, see [`TryWriteable`]. /// /// [`*_parts_eq`]: assert_try_writeable_parts_eq #[macro_export] macro_rules! assert_try_writeable_eq { ($actual_writeable:expr, $expected_str:expr $(,)?) => { $crate::assert_try_writeable_eq!($actual_writeable, $expected_str, Ok(())) }; ($actual_writeable:expr, $expected_str:expr, $expected_result:expr $(,)?) => { $crate::assert_try_writeable_eq!($actual_writeable, $expected_str, $expected_result, "") }; ($actual_writeable:expr, $expected_str:expr, $expected_result:expr, $($arg:tt)+) => {{ $crate::assert_try_writeable_eq!(@internal, $actual_writeable, $expected_str, $expected_result, $($arg)*); }}; (@internal, $actual_writeable:expr, $expected_str:expr, $expected_result:expr, $($arg:tt)+) => {{ use $crate::TryWriteable; let actual_writeable = &$actual_writeable; let (actual_str, actual_parts, actual_error) = $crate::_internal::try_writeable_to_parts_for_test(actual_writeable); assert_eq!(actual_str, $expected_str, $($arg)*); assert_eq!(actual_error, Result::<(), _>::from($expected_result).err(), $($arg)*); let actual_result = match actual_writeable.try_write_to_string() { Ok(actual_cow_str) => { assert_eq!(actual_cow_str, $expected_str, $($arg)+); Ok(()) } Err((e, actual_cow_str)) => { assert_eq!(actual_cow_str, $expected_str, $($arg)+); Err(e) } }; assert_eq!(actual_result, Result::<(), _>::from($expected_result), $($arg)*); let length_hint = actual_writeable.writeable_length_hint(); assert!( length_hint.0 <= actual_str.len(), "hint lower bound {} larger than actual length {}: {}", length_hint.0, actual_str.len(), format!($($arg)*), ); if let Some(upper) = length_hint.1 { assert!( actual_str.len() <= upper, "hint upper bound {} smaller than actual length {}: {}", length_hint.0, actual_str.len(), format!($($arg)*), ); } actual_parts // return for assert_try_writeable_parts_eq }}; } /// See [`assert_try_writeable_eq`]. #[macro_export] macro_rules! assert_try_writeable_parts_eq { ($actual_writeable:expr, $expected_str:expr, $expected_parts:expr $(,)?) => { $crate::assert_try_writeable_parts_eq!($actual_writeable, $expected_str, Ok(()), $expected_parts) }; ($actual_writeable:expr, $expected_str:expr, $expected_result:expr, $expected_parts:expr $(,)?) => { $crate::assert_try_writeable_parts_eq!($actual_writeable, $expected_str, $expected_result, $expected_parts, "") }; ($actual_writeable:expr, $expected_str:expr, $expected_result:expr, $expected_parts:expr, $($arg:tt)+) => {{ let actual_parts = $crate::assert_try_writeable_eq!(@internal, $actual_writeable, $expected_str, $expected_result, $($arg)*); assert_eq!(actual_parts, $expected_parts, $($arg)+); }}; } #[test] fn test_result_try_writeable() { let mut result: Result<&str, usize> = Ok("success"); assert_try_writeable_eq!(result, "success"); result = Err(44); assert_try_writeable_eq!(result, "44", Err(44)); assert_try_writeable_parts_eq!(result, "44", Err(44), [(0, 2, Part::ERROR)]) } writeable-0.6.0/tests/data/data.rs000064400000000000000000000011431046102023000151360ustar 00000000000000// 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 ). #[allow(dead_code)] pub const KEBAB_CASE_STRINGS: &[&str] = &[ "ca", "ca-ÉS", "ca-ÉS-u-ca-buddhist", "ca-ÉS-valencia", "ca-ÉS-x-gbp", "ca-ÉS-x-gbp-short", "ca-ÉS-x-usd", "ca-ÉS-xyzabc", "ca-x-eur", "cat", "cat-bus", "cat-🚐", "pl-Latn-PL", "und", "und-fonipa", "und-u-ca-hebrew", "und-u-ca-japanese", "und-x-mxn", "zh", ]; writeable-0.6.0/tests/writeable.rs000064400000000000000000000016411046102023000152750ustar 00000000000000// 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 std::fmt; use writeable::assert_writeable_eq; use writeable::LengthHint; use writeable::Writeable; /// A sample type implementing Writeable struct WriteableMessage<'s> { message: &'s str, } impl Writeable for WriteableMessage<'_> { fn write_to(&self, sink: &mut W) -> fmt::Result { sink.write_str(self.message) } fn writeable_length_hint(&self) -> LengthHint { LengthHint::exact(self.message.len()) } } writeable::impl_display_with_writeable!(WriteableMessage<'_>); #[test] fn test_basic() { let input_string = "hello world"; let message = WriteableMessage { message: input_string, }; assert_writeable_eq!(&message, input_string); }