anstream-0.6.15/.cargo_vcs_info.json0000644000000001550000000000100127400ustar { "git": { "sha1": "6e96e966c1e75ed81194dea225570355161e300a" }, "path_in_vcs": "crates/anstream" }anstream-0.6.15/Cargo.lock0000644000000427470000000000100107300ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anstream" version = "0.6.15" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "divan", "is_terminal_polyfill", "lexopt", "owo-colors", "proptest", "strip-ansi-escapes", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" [[package]] name = "anstyle-parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys 0.48.0", ] [[package]] name = "anstyle-wincon" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "autocfg" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e" dependencies = [ "anstyle", "clap_lex", "terminal_size", ] [[package]] name = "clap_lex" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "condtype" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" [[package]] name = "divan" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d567df2c9c2870a43f3f2bd65aaeb18dbce1c18f217c3e564b4fbaeb3ee56c" dependencies = [ "cfg-if", "clap", "condtype", "divan-macros", "libc", "regex-lite", ] [[package]] name = "divan-macros" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi", "libc", "windows-sys 0.48.0", ] [[package]] name = "is-terminal" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] [[package]] name = "is_terminal_polyfill" version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b52b2de84ed0341893ce61ca1af04fa54eea0a764ecc38c6855cc5db84dc1927" dependencies = [ "is-terminal", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lexopt" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" [[package]] name = "libc" version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "num-traits" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", ] [[package]] name = "owo-colors" version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", "bitflags 2.5.0", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", "unarray", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 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 = "rand_xorshift" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ "rand_core", ] [[package]] name = "regex-lite" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" [[package]] name = "regex-syntax" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustix" version = "0.37.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" dependencies = [ "bitflags 1.3.2", "errno", "io-lifetimes", "libc", "linux-raw-sys 0.3.8", "windows-sys 0.48.0", ] [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", "libc", "linux-raw-sys 0.4.13", "windows-sys 0.52.0", ] [[package]] name = "rusty-fork" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", "quick-error", "tempfile", "wait-timeout", ] [[package]] name = "strip-ansi-escapes" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" dependencies = [ "vte", ] [[package]] name = "syn" version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix 0.38.34", "windows-sys 0.52.0", ] [[package]] name = "terminal_size" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" dependencies = [ "rustix 0.37.27", "windows-sys 0.48.0", ] [[package]] name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "vte" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" dependencies = [ "utf8parse", "vte_generate_state_changes", ] [[package]] name = "vte_generate_state_changes" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.5", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm 0.52.5", "windows_aarch64_msvc 0.52.5", "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", "windows_i686_msvc 0.52.5", "windows_x86_64_gnu 0.52.5", "windows_x86_64_gnullvm 0.52.5", "windows_x86_64_msvc 0.52.5", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] name = "windows_i686_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" anstream-0.6.15/Cargo.toml0000644000000121070000000000100107360ustar # 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.65.0" name = "anstream" version = "0.6.15" build = false include = [ "build.rs", "src/**/*", "Cargo.toml", "Cargo.lock", "LICENSE*", "README.md", "benches/**/*", "examples/**/*", ] autobins = false autoexamples = false autotests = false autobenches = false description = "A simple cross platform library for writing colored text to a terminal." homepage = "https://github.com/rust-cli/anstyle" readme = "README.md" keywords = [ "ansi", "terminal", "color", "strip", "wincon", ] categories = ["command-line-interface"] license = "MIT OR Apache-2.0" repository = "https://github.com/rust-cli/anstyle.git" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" min = 1 replace = "{{version}}" search = "Unreleased" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = "...{{tag_name}}" search = '\.\.\.HEAD' [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" min = 1 replace = "{{date}}" search = "ReleaseDate" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = """ ## [Unreleased] - ReleaseDate """ search = "" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = """ [Unreleased]: https://github.com/rust-cli/anstyle/compare/{{tag_name}}...HEAD""" search = "" [lib] name = "anstream" path = "src/lib.rs" [[example]] name = "dump-stream" path = "examples/dump-stream.rs" required-features = ["auto"] [[example]] name = "query-stream" path = "examples/query-stream.rs" required-features = ["auto"] [[bench]] name = "stream" path = "benches/stream.rs" harness = false [[bench]] name = "strip" path = "benches/strip.rs" harness = false [[bench]] name = "wincon" path = "benches/wincon.rs" harness = false [dependencies.anstyle] version = "1.0.0" [dependencies.anstyle-parse] version = "0.2.0" [dependencies.anstyle-query] version = "1.0.0" optional = true [dependencies.colorchoice] version = "1.0.0" [dependencies.is_terminal_polyfill] version = "1.48" [dependencies.utf8parse] version = "0.2.1" [dev-dependencies.divan] version = "0.1.11" [dev-dependencies.lexopt] version = "0.3.0" [dev-dependencies.owo-colors] version = "4.0.0" [dev-dependencies.proptest] version = "1.4.0" [dev-dependencies.strip-ansi-escapes] version = "0.2.0" [features] auto = ["dep:anstyle-query"] default = [ "auto", "wincon", ] test = [] wincon = ["dep:anstyle-wincon"] [target."cfg(windows)".dependencies.anstyle-wincon] version = "3.0.1" optional = true [lints.clippy] bool_assert_comparison = "allow" branches_sharing_code = "allow" checked_conversions = "warn" collapsible_else_if = "allow" create_dir = "warn" dbg_macro = "warn" debug_assert_with_mut_call = "warn" doc_markdown = "warn" empty_enum = "warn" enum_glob_use = "warn" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" explicit_into_iter_loop = "warn" fallible_impl_from = "warn" filter_map_next = "warn" flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" from_iter_instead_of_collect = "warn" if_same_then_else = "allow" implicit_clone = "warn" imprecise_flops = "warn" inconsistent_struct_constructor = "warn" inefficient_to_string = "warn" infinite_loop = "warn" invalid_upcast_comparisons = "warn" items_after_statements = "warn" large_digit_groups = "warn" large_stack_arrays = "warn" large_types_passed_by_value = "warn" let_and_return = "allow" linkedlist = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" match_wildcard_for_single_variants = "warn" mem_forget = "warn" mutex_integer = "warn" needless_continue = "warn" needless_for_each = "warn" negative_feature_names = "warn" path_buf_push_overwrite = "warn" ptr_as_ptr = "warn" rc_mutex = "warn" redundant_feature_names = "warn" ref_option_ref = "warn" rest_pat_in_fully_bound_structs = "warn" same_functions_in_if_condition = "warn" self_named_module_files = "warn" semicolon_if_nothing_returned = "warn" single_match_else = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" string_lit_as_bytes = "warn" string_to_string = "warn" todo = "warn" trait_duplication_in_bounds = "warn" verbose_file_reads = "warn" wildcard_imports = "warn" zero_sized_map_values = "warn" [lints.rust] rust_2018_idioms = "warn" unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" anstream-0.6.15/Cargo.toml.orig000064400000000000000000000041651046102023000144240ustar 00000000000000[package] name = "anstream" version = "0.6.15" description = "A simple cross platform library for writing colored text to a terminal." repository = "https://github.com/rust-cli/anstyle.git" homepage = "https://github.com/rust-cli/anstyle" categories = ["command-line-interface"] keywords = ["ansi", "terminal", "color", "strip", "wincon"] license.workspace = true edition.workspace = true rust-version.workspace = true include.workspace = true [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [package.metadata.release] pre-release-replacements = [ {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/rust-cli/anstyle/compare/{{tag_name}}...HEAD", exactly=1}, ] [features] default = ["auto", "wincon"] auto = ["dep:anstyle-query"] wincon = ["dep:anstyle-wincon"] # Enable in `dev-dependencies` to make sure output is captured for tests test = [] [dependencies] anstyle = { version = "1.0.0", path = "../anstyle" } anstyle-parse = { version = "0.2.0", path = "../anstyle-parse" } colorchoice = { version = "1.0.0", path = "../colorchoice" } anstyle-query = { version = "1.0.0", path = "../anstyle-query", optional = true } utf8parse = "0.2.1" is_terminal_polyfill = "1.48" [target.'cfg(windows)'.dependencies] anstyle-wincon = { version = "3.0.1", path = "../anstyle-wincon", optional = true } [dev-dependencies] divan = "0.1.11" lexopt = "0.3.0" owo-colors = "4.0.0" proptest = "1.4.0" strip-ansi-escapes = "0.2.0" [[example]] name = "dump-stream" required-features = ["auto"] [[example]] name = "query-stream" required-features = ["auto"] [[bench]] name = "strip" harness = false [[bench]] name = "wincon" harness = false [[bench]] name = "stream" harness = false [lints] workspace = true anstream-0.6.15/LICENSE-APACHE000064400000000000000000000261361046102023000134630ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. anstream-0.6.15/LICENSE-MIT000064400000000000000000000020461046102023000131650ustar 00000000000000Copyright (c) Individual contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. anstream-0.6.15/README.md000064400000000000000000000022421046102023000130060ustar 00000000000000# anstream > A simple cross platform library for writing colored text to a terminal. *A portmanteau of "ansi stream"* [![Documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation] ![License](https://img.shields.io/crates/l/anstream.svg) [![Crates Status](https://img.shields.io/crates/v/anstream.svg)](https://crates.io/crates/anstream) Specialized `stdout` and `stderr` that accept ANSI escape codes and adapt them based on the terminal's capabilities. `anstream::adapter::strip_str` may also be of interest on its own for low overhead stripping of ANSI escape codes. ## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [Crates.io]: https://crates.io/crates/anstream [Documentation]: https://docs.rs/anstream anstream-0.6.15/benches/stream.rs000064400000000000000000000046631046102023000150100ustar 00000000000000#![allow(clippy::unwrap_used)] use std::io::Write as _; #[divan::bench(args = DATA)] fn nop(data: &Data) -> Vec { let buffer = Vec::with_capacity(data.content().len()); let mut stream = buffer; stream.write_all(data.content()).unwrap(); stream } #[divan::bench(args = DATA)] fn strip_stream(data: &Data) -> Vec { let buffer = Vec::with_capacity(data.content().len()); let mut stream = anstream::StripStream::new(buffer); stream.write_all(data.content()).unwrap(); stream.into_inner() } #[divan::bench(args = DATA)] #[cfg(all(windows, feature = "wincon"))] fn wincon_stream(data: &Data) -> Vec { let buffer = Vec::with_capacity(data.content().len()); let mut stream = anstream::WinconStream::new(buffer); stream.write_all(data.content()).unwrap(); stream.into_inner() } #[divan::bench(args = DATA)] fn auto_stream_always_ansi(data: &Data) -> Vec { let buffer = Vec::with_capacity(data.content().len()); let mut stream = anstream::AutoStream::always_ansi(buffer); stream.write_all(data.content()).unwrap(); stream.into_inner() } #[divan::bench(args = DATA)] fn auto_stream_always(data: &Data) -> Vec { let buffer = Vec::with_capacity(data.content().len()); let mut stream = anstream::AutoStream::always(buffer); stream.write_all(data.content()).unwrap(); stream.into_inner() } #[divan::bench(args = DATA)] fn auto_stream_never(data: &Data) -> Vec { let buffer = Vec::with_capacity(data.content().len()); let mut stream = anstream::AutoStream::never(buffer); stream.write_all(data.content()).unwrap(); stream.into_inner() } const DATA: &[Data] = &[ Data( "0-state_changes", b"\x1b]2;X\x1b\\ \x1b[0m \x1bP0@\x1b\\".as_slice(), ), #[cfg(feature = "utf8")] Data("1-demo.vte", include_bytes!("../tests/demo.vte").as_slice()), Data( "2-rg_help.vte", include_bytes!("../tests/rg_help.vte").as_slice(), ), Data( "3-rg_linus.vte", include_bytes!("../tests/rg_linus.vte").as_slice(), ), ]; #[derive(Debug)] struct Data(&'static str, &'static [u8]); impl Data { const fn name(&self) -> &'static str { self.0 } const fn content(&self) -> &'static [u8] { self.1 } } impl std::fmt::Display for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.name().fmt(f) } } fn main() { divan::main(); } anstream-0.6.15/benches/strip.rs000064400000000000000000000065121046102023000146510ustar 00000000000000#![allow(clippy::unwrap_used)] #[derive(Default)] struct Strip(String); impl Strip { fn with_capacity(capacity: usize) -> Self { Self(String::with_capacity(capacity)) } } impl anstyle_parse::Perform for Strip { fn print(&mut self, c: char) { self.0.push(c); } fn execute(&mut self, byte: u8) { if byte.is_ascii_whitespace() { self.0.push(byte as char); } } } #[divan::bench(args = DATA)] fn advance_strip(data: &Data) -> String { let mut stripped = Strip::with_capacity(data.content().len()); let mut parser = anstyle_parse::Parser::::new(); for byte in data.content() { parser.advance(&mut stripped, *byte); } stripped.0 } #[divan::bench(args = DATA)] fn strip_ansi_escapes(data: &Data) -> Vec { let stripped = strip_ansi_escapes::strip(data.content()); stripped } #[divan::bench(args = DATA)] fn strip_str(data: &Data) -> String { if let Ok(content) = std::str::from_utf8(data.content()) { let stripped = anstream::adapter::strip_str(content).to_string(); stripped } else { "".to_owned() } } #[divan::bench(args = DATA)] fn strip_str_strip_next(data: &Data) -> String { if let Ok(content) = std::str::from_utf8(data.content()) { let mut stripped = String::with_capacity(data.content().len()); let mut state = anstream::adapter::StripStr::new(); for printable in state.strip_next(content) { stripped.push_str(printable); } stripped } else { "".to_owned() } } #[divan::bench(args = DATA)] fn strip_bytes(data: &Data) -> Vec { let stripped = anstream::adapter::strip_bytes(data.content()).into_vec(); stripped } const DATA: &[Data] = &[ Data( "0-state_changes", b"\x1b]2;X\x1b\\ \x1b[0m \x1bP0@\x1b\\".as_slice(), ), #[cfg(feature = "utf8")] Data("1-demo.vte", include_bytes!("../tests/demo.vte").as_slice()), Data( "2-rg_help.vte", include_bytes!("../tests/rg_help.vte").as_slice(), ), Data( "3-rg_linus.vte", include_bytes!("../tests/rg_linus.vte").as_slice(), ), ]; #[derive(Debug)] struct Data(&'static str, &'static [u8]); impl Data { const fn name(&self) -> &'static str { self.0 } const fn content(&self) -> &'static [u8] { self.1 } } impl std::fmt::Display for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.name().fmt(f) } } #[test] fn verify_data() { for data in DATA { // Make sure the comparison is fair if let Ok(content) = std::str::from_utf8(data.content()) { let mut stripped = Strip::with_capacity(content.len()); let mut parser = anstyle_parse::Parser::::new(); for byte in content.as_bytes() { parser.advance(&mut stripped, *byte); } assert_eq!( stripped.0, anstream::adapter::strip_str(content).to_string() ); assert_eq!( stripped.0, String::from_utf8(anstream::adapter::strip_bytes(content.as_bytes()).into_vec()) .unwrap() ); } } } fn main() { divan::main(); } anstream-0.6.15/benches/wincon.rs000064400000000000000000000020411046102023000147760ustar 00000000000000#[divan::bench(args = DATA)] fn nop(data: &Data) -> Vec<(anstyle::Style, String)> { let mut state = anstream::adapter::WinconBytes::new(); let stripped = state.extract_next(data.content()).collect::>(); stripped } const DATA: &[Data] = &[ Data( "0-state_changes", b"\x1b]2;X\x1b\\ \x1b[0m \x1bP0@\x1b\\".as_slice(), ), #[cfg(feature = "utf8")] Data("1-demo.vte", include_bytes!("../tests/demo.vte").as_slice()), Data( "2-rg_help.vte", include_bytes!("../tests/rg_help.vte").as_slice(), ), Data( "3-rg_linus.vte", include_bytes!("../tests/rg_linus.vte").as_slice(), ), ]; #[derive(Debug)] struct Data(&'static str, &'static [u8]); impl Data { const fn name(&self) -> &'static str { self.0 } const fn content(&self) -> &'static [u8] { self.1 } } impl std::fmt::Display for Data { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.name().fmt(f) } } fn main() { divan::main(); } anstream-0.6.15/examples/dump-stream.rs000064400000000000000000000105631046102023000161560ustar 00000000000000//! Write colored text, adapting to the terminals capabilities use std::io::Write; fn main() -> Result<(), lexopt::Error> { let args = Args::parse()?; let stdout = anstream::stdout(); let mut stdout = stdout.lock(); for fixed in 0..16 { let color = anstyle::Ansi256Color(fixed) .into_ansi() .expect("within 4-bit color range"); let style = style(color, args.layer, args.effects); let _ = print_number(&mut stdout, fixed, style); if fixed == 7 || fixed == 15 { let _ = writeln!(&mut stdout); } } for fixed in 16..232 { let col = (fixed - 16) % 36; if col == 0 { let _ = writeln!(stdout); } let color = anstyle::Ansi256Color(fixed); let style = style(color, args.layer, args.effects); let _ = print_number(&mut stdout, fixed, style); } let _ = writeln!(stdout); let _ = writeln!(stdout); for fixed in 232..=255 { let color = anstyle::Ansi256Color(fixed); let style = style(color, args.layer, args.effects); let _ = print_number(&mut stdout, fixed, style); } let _ = writeln!(stdout); Ok(()) } fn style( color: impl Into, layer: Layer, effects: anstyle::Effects, ) -> anstyle::Style { let color = color.into(); (match layer { Layer::Fg => anstyle::Style::new().fg_color(Some(color)), Layer::Bg => anstyle::Style::new().bg_color(Some(color)), Layer::Underline => anstyle::Style::new().underline_color(Some(color)), }) | effects } fn print_number(stdout: &mut impl Write, fixed: u8, style: anstyle::Style) -> std::io::Result<()> { write!(stdout, "{style}{fixed:>3X}{style:#}",) } #[derive(Default)] struct Args { effects: anstyle::Effects, layer: Layer, } #[derive(Copy, Clone, Default)] enum Layer { #[default] Fg, Bg, Underline, } impl Args { fn parse() -> Result { use lexopt::prelude::*; let mut res = Args::default(); let mut args = lexopt::Parser::from_env(); while let Some(arg) = args.next()? { match arg { Long("layer") => { res.layer = args.value()?.parse_with(|s| match s { "fg" => Ok(Layer::Fg), "bg" => Ok(Layer::Bg), "underline" => Ok(Layer::Underline), _ => Err("expected values fg, bg, underline"), })?; } Long("effect") => { const EFFECTS: [(&str, anstyle::Effects); 12] = [ ("bold", anstyle::Effects::BOLD), ("dimmed", anstyle::Effects::DIMMED), ("italic", anstyle::Effects::ITALIC), ("underline", anstyle::Effects::UNDERLINE), ("double_underline", anstyle::Effects::DOUBLE_UNDERLINE), ("curly_underline", anstyle::Effects::CURLY_UNDERLINE), ("dotted_underline", anstyle::Effects::DOTTED_UNDERLINE), ("dashed_underline", anstyle::Effects::DASHED_UNDERLINE), ("blink", anstyle::Effects::BLINK), ("invert", anstyle::Effects::INVERT), ("hidden", anstyle::Effects::HIDDEN), ("strikethrough", anstyle::Effects::STRIKETHROUGH), ]; let effect = args.value()?.parse_with(|s| { EFFECTS .into_iter() .find(|(name, _)| *name == s) .map(|(_, effect)| effect) .ok_or_else(|| { format!( "expected one of {}", EFFECTS .into_iter() .map(|(n, _)| n) .collect::>() .join(", ") ) }) })?; res.effects = res.effects.insert(effect); } _ => return Err(arg.unexpected()), } } Ok(res) } } anstream-0.6.15/examples/query-stream.rs000064400000000000000000000010421046102023000163460ustar 00000000000000//! Report a terminal's capabilities fn main() { println!("stdout:"); println!( " choice: {:?}", anstream::AutoStream::choice(&std::io::stdout()) ); println!( " choice: {:?}", anstream::AutoStream::auto(std::io::stdout()).current_choice() ); println!("stderr:"); println!( " choice: {:?}", anstream::AutoStream::choice(&std::io::stderr()) ); println!( " choice: {:?}", anstream::AutoStream::auto(std::io::stderr()).current_choice() ); } anstream-0.6.15/src/adapter/mod.rs000064400000000000000000000005341046102023000150650ustar 00000000000000//! Gracefully degrade styled output mod strip; mod wincon; pub use strip::strip_bytes; pub use strip::strip_str; pub use strip::StripBytes; pub use strip::StripBytesIter; pub use strip::StripStr; pub use strip::StripStrIter; pub use strip::StrippedBytes; pub use strip::StrippedStr; pub use wincon::WinconBytes; pub use wincon::WinconBytesIter; anstream-0.6.15/src/adapter/strip.rs000064400000000000000000000337751046102023000154640ustar 00000000000000use anstyle_parse::state::state_change; use anstyle_parse::state::Action; use anstyle_parse::state::State; /// Strip ANSI escapes from a `&str`, returning the printable content /// /// This can be used to take output from a program that includes escape sequences and write it /// somewhere that does not easily support them, such as a log file. /// /// For non-contiguous data, see [`StripStr`]. /// /// # Example /// /// ```rust /// use std::io::Write as _; /// /// let styled_text = "\x1b[32mfoo\x1b[m bar"; /// let plain_str = anstream::adapter::strip_str(&styled_text).to_string(); /// assert_eq!(plain_str, "foo bar"); /// ``` #[inline] pub fn strip_str(data: &str) -> StrippedStr<'_> { StrippedStr::new(data) } /// See [`strip_str`] #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct StrippedStr<'s> { bytes: &'s [u8], state: State, } impl<'s> StrippedStr<'s> { #[inline] fn new(data: &'s str) -> Self { Self { bytes: data.as_bytes(), state: State::Ground, } } /// Create a [`String`] of the printable content #[inline] #[allow(clippy::inherent_to_string_shadow_display)] // Single-allocation implementation pub fn to_string(&self) -> String { use std::fmt::Write as _; let mut stripped = String::with_capacity(self.bytes.len()); let _ = write!(&mut stripped, "{}", self); stripped } } impl<'s> std::fmt::Display for StrippedStr<'s> { /// **Note:** this does *not* exhaust the [`Iterator`] #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let iter = Self { bytes: self.bytes, state: self.state, }; for printable in iter { printable.fmt(f)?; } Ok(()) } } impl<'s> Iterator for StrippedStr<'s> { type Item = &'s str; #[inline] fn next(&mut self) -> Option { next_str(&mut self.bytes, &mut self.state) } } /// Incrementally strip non-contiguous data #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct StripStr { state: State, } impl StripStr { /// Initial state pub fn new() -> Self { Default::default() } /// Strip the next segment of data pub fn strip_next<'s>(&'s mut self, data: &'s str) -> StripStrIter<'s> { StripStrIter { bytes: data.as_bytes(), state: &mut self.state, } } } /// See [`StripStr`] #[derive(Debug, PartialEq, Eq)] pub struct StripStrIter<'s> { bytes: &'s [u8], state: &'s mut State, } impl<'s> Iterator for StripStrIter<'s> { type Item = &'s str; #[inline] fn next(&mut self) -> Option { next_str(&mut self.bytes, self.state) } } #[inline] fn next_str<'s>(bytes: &mut &'s [u8], state: &mut State) -> Option<&'s str> { let offset = bytes.iter().copied().position(|b| { let (next_state, action) = state_change(*state, b); if next_state != State::Anywhere { *state = next_state; } is_printable_bytes(action, b) }); let (_, next) = bytes.split_at(offset.unwrap_or(bytes.len())); *bytes = next; *state = State::Ground; let offset = bytes.iter().copied().position(|b| { let (_next_state, action) = state_change(State::Ground, b); !(is_printable_bytes(action, b) || is_utf8_continuation(b)) }); let (printable, next) = bytes.split_at(offset.unwrap_or(bytes.len())); *bytes = next; if printable.is_empty() { None } else { let printable = unsafe { from_utf8_unchecked( printable, "`bytes` was validated as UTF-8, the parser preserves UTF-8 continuations", ) }; Some(printable) } } #[inline] unsafe fn from_utf8_unchecked<'b>(bytes: &'b [u8], safety_justification: &'static str) -> &'b str { unsafe { if cfg!(debug_assertions) { // Catch problems more quickly when testing std::str::from_utf8(bytes).expect(safety_justification) } else { std::str::from_utf8_unchecked(bytes) } } } #[inline] fn is_utf8_continuation(b: u8) -> bool { matches!(b, 0x80..=0xbf) } /// Strip ANSI escapes from bytes, returning the printable content /// /// This can be used to take output from a program that includes escape sequences and write it /// somewhere that does not easily support them, such as a log file. /// /// # Example /// /// ```rust /// use std::io::Write as _; /// /// let styled_text = "\x1b[32mfoo\x1b[m bar"; /// let plain_str = anstream::adapter::strip_bytes(styled_text.as_bytes()).into_vec(); /// assert_eq!(plain_str.as_slice(), &b"foo bar"[..]); /// ``` #[inline] pub fn strip_bytes(data: &[u8]) -> StrippedBytes<'_> { StrippedBytes::new(data) } /// See [`strip_bytes`] #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct StrippedBytes<'s> { bytes: &'s [u8], state: State, utf8parser: Utf8Parser, } impl<'s> StrippedBytes<'s> { /// See [`strip_bytes`] #[inline] pub fn new(bytes: &'s [u8]) -> Self { Self { bytes, state: State::Ground, utf8parser: Default::default(), } } /// Strip the next slice of bytes /// /// Used when the content is in several non-contiguous slices /// /// # Panic /// /// May panic if it is not exhausted / empty #[inline] pub fn extend(&mut self, bytes: &'s [u8]) { debug_assert!( self.is_empty(), "current bytes must be processed to ensure we end at the right state" ); self.bytes = bytes; } /// Report the bytes has been exhausted #[inline] pub fn is_empty(&self) -> bool { self.bytes.is_empty() } /// Create a [`Vec`] of the printable content #[inline] pub fn into_vec(self) -> Vec { let mut stripped = Vec::with_capacity(self.bytes.len()); for printable in self { stripped.extend(printable); } stripped } } impl<'s> Iterator for StrippedBytes<'s> { type Item = &'s [u8]; #[inline] fn next(&mut self) -> Option { next_bytes(&mut self.bytes, &mut self.state, &mut self.utf8parser) } } /// Incrementally strip non-contiguous data #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct StripBytes { state: State, utf8parser: Utf8Parser, } impl StripBytes { /// Initial state pub fn new() -> Self { Default::default() } /// Strip the next segment of data pub fn strip_next<'s>(&'s mut self, bytes: &'s [u8]) -> StripBytesIter<'s> { StripBytesIter { bytes, state: &mut self.state, utf8parser: &mut self.utf8parser, } } } /// See [`StripBytes`] #[derive(Debug, PartialEq, Eq)] pub struct StripBytesIter<'s> { bytes: &'s [u8], state: &'s mut State, utf8parser: &'s mut Utf8Parser, } impl<'s> Iterator for StripBytesIter<'s> { type Item = &'s [u8]; #[inline] fn next(&mut self) -> Option { next_bytes(&mut self.bytes, self.state, self.utf8parser) } } #[inline] fn next_bytes<'s>( bytes: &mut &'s [u8], state: &mut State, utf8parser: &mut Utf8Parser, ) -> Option<&'s [u8]> { let offset = bytes.iter().copied().position(|b| { if *state == State::Utf8 { true } else { let (next_state, action) = state_change(*state, b); if next_state != State::Anywhere { *state = next_state; } is_printable_bytes(action, b) } }); let (_, next) = bytes.split_at(offset.unwrap_or(bytes.len())); *bytes = next; let offset = bytes.iter().copied().position(|b| { if *state == State::Utf8 { if utf8parser.add(b) { *state = State::Ground; } false } else { let (next_state, action) = state_change(State::Ground, b); if next_state != State::Anywhere { *state = next_state; } if *state == State::Utf8 { utf8parser.add(b); false } else { !is_printable_bytes(action, b) } } }); let (printable, next) = bytes.split_at(offset.unwrap_or(bytes.len())); *bytes = next; if printable.is_empty() { None } else { Some(printable) } } #[derive(Default, Clone, Debug, PartialEq, Eq)] pub(crate) struct Utf8Parser { utf8_parser: utf8parse::Parser, } impl Utf8Parser { fn add(&mut self, byte: u8) -> bool { let mut b = false; let mut receiver = VtUtf8Receiver(&mut b); self.utf8_parser.advance(&mut receiver, byte); b } } struct VtUtf8Receiver<'a>(&'a mut bool); impl<'a> utf8parse::Receiver for VtUtf8Receiver<'a> { fn codepoint(&mut self, _: char) { *self.0 = true; } fn invalid_sequence(&mut self) { *self.0 = true; } } #[inline] fn is_printable_bytes(action: Action, byte: u8) -> bool { // VT320 considered 0x7f to be `Print`able but we expect to be working in UTF-8 systems and not // ISO Latin-1, making it DEL and non-printable const DEL: u8 = 0x7f; // Continuations aren't included as they may also be control codes, requiring more context (action == Action::Print && byte != DEL) || action == Action::BeginUtf8 || (action == Action::Execute && byte.is_ascii_whitespace()) } #[cfg(test)] mod test { use super::*; use proptest::prelude::*; /// Model based off full parser fn parser_strip(bytes: &[u8]) -> String { #[derive(Default)] struct Strip(String); impl Strip { fn with_capacity(capacity: usize) -> Self { Self(String::with_capacity(capacity)) } } impl anstyle_parse::Perform for Strip { fn print(&mut self, c: char) { self.0.push(c); } fn execute(&mut self, byte: u8) { if byte.is_ascii_whitespace() { self.0.push(byte as char); } } } let mut stripped = Strip::with_capacity(bytes.len()); let mut parser = anstyle_parse::Parser::::new(); for byte in bytes { parser.advance(&mut stripped, *byte); } stripped.0 } /// Model verifying incremental parsing fn strip_char(mut s: &str) -> String { let mut result = String::new(); let mut state = StripStr::new(); while !s.is_empty() { let mut indices = s.char_indices(); indices.next(); // current let offset = indices.next().map(|(i, _)| i).unwrap_or_else(|| s.len()); let (current, remainder) = s.split_at(offset); for printable in state.strip_next(current) { result.push_str(printable); } s = remainder; } result } /// Model verifying incremental parsing fn strip_byte(s: &[u8]) -> Vec { let mut result = Vec::new(); let mut state = StripBytes::default(); for start in 0..s.len() { let current = &s[start..=start]; for printable in state.strip_next(current) { result.extend(printable); } } result } #[test] fn test_strip_bytes_multibyte() { let bytes = [240, 145, 141, 139]; let expected = parser_strip(&bytes); let actual = String::from_utf8(strip_bytes(&bytes).into_vec()).unwrap(); assert_eq!(expected, actual); } #[test] fn test_strip_byte_multibyte() { let bytes = [240, 145, 141, 139]; let expected = parser_strip(&bytes); let actual = String::from_utf8(strip_byte(&bytes).clone()).unwrap(); assert_eq!(expected, actual); } #[test] fn test_strip_str_del() { let input = std::str::from_utf8(&[0x7f]).unwrap(); let expected = ""; let actual = strip_str(input).to_string(); assert_eq!(expected, actual); } #[test] fn test_strip_byte_del() { let bytes = [0x7f]; let expected = ""; let actual = String::from_utf8(strip_byte(&bytes).clone()).unwrap(); assert_eq!(expected, actual); } #[test] fn test_strip_str_handles_broken_sequence() { // valid utf8: \xc3\xb6 then \x1b then \xf0\x9f\x98\x80 let s = "ö\x1b😀hello😀goodbye"; let mut it = strip_str(s); assert_eq!("ö", it.next().unwrap()); assert_eq!("ello😀goodbye", it.next().unwrap()); } proptest! { #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn strip_str_no_escapes(s in "\\PC*") { let expected = parser_strip(s.as_bytes()); let actual = strip_str(&s).to_string(); assert_eq!(expected, actual); } #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn strip_char_no_escapes(s in "\\PC*") { let expected = parser_strip(s.as_bytes()); let actual = strip_char(&s); assert_eq!(expected, actual); } #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn strip_bytes_no_escapes(s in "\\PC*") { dbg!(&s); dbg!(s.as_bytes()); let expected = parser_strip(s.as_bytes()); let actual = String::from_utf8(strip_bytes(s.as_bytes()).into_vec()).unwrap(); assert_eq!(expected, actual); } #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn strip_byte_no_escapes(s in "\\PC*") { dbg!(&s); dbg!(s.as_bytes()); let expected = parser_strip(s.as_bytes()); let actual = String::from_utf8(strip_byte(s.as_bytes()).clone()).unwrap(); assert_eq!(expected, actual); } } } anstream-0.6.15/src/adapter/wincon.rs000064400000000000000000000313151046102023000156040ustar 00000000000000/// Incrementally convert to wincon calls for non-contiguous data #[derive(Default, Clone, Debug, PartialEq, Eq)] pub struct WinconBytes { parser: anstyle_parse::Parser, capture: WinconCapture, } impl WinconBytes { /// Initial state pub fn new() -> Self { Default::default() } /// Strip the next segment of data pub fn extract_next<'s>(&'s mut self, bytes: &'s [u8]) -> WinconBytesIter<'s> { self.capture.reset(); self.capture.printable.reserve(bytes.len()); WinconBytesIter { bytes, parser: &mut self.parser, capture: &mut self.capture, } } } /// See [`WinconBytes`] #[derive(Debug, PartialEq, Eq)] pub struct WinconBytesIter<'s> { bytes: &'s [u8], parser: &'s mut anstyle_parse::Parser, capture: &'s mut WinconCapture, } impl<'s> Iterator for WinconBytesIter<'s> { type Item = (anstyle::Style, String); #[inline] fn next(&mut self) -> Option { next_bytes(&mut self.bytes, self.parser, self.capture) } } #[inline] fn next_bytes( bytes: &mut &[u8], parser: &mut anstyle_parse::Parser, capture: &mut WinconCapture, ) -> Option<(anstyle::Style, String)> { capture.reset(); while capture.ready.is_none() { let byte = if let Some((byte, remainder)) = (*bytes).split_first() { *bytes = remainder; *byte } else { break; }; parser.advance(capture, byte); } if capture.printable.is_empty() { return None; } let style = capture.ready.unwrap_or(capture.style); Some((style, std::mem::take(&mut capture.printable))) } #[derive(Default, Clone, Debug, PartialEq, Eq)] struct WinconCapture { style: anstyle::Style, printable: String, ready: Option, } impl WinconCapture { fn reset(&mut self) { self.ready = None; } } impl anstyle_parse::Perform for WinconCapture { /// Draw a character to the screen and update states. fn print(&mut self, c: char) { self.printable.push(c); } /// Execute a C0 or C1 control function. fn execute(&mut self, byte: u8) { if byte.is_ascii_whitespace() { self.printable.push(byte as char); } } fn csi_dispatch( &mut self, params: &anstyle_parse::Params, _intermediates: &[u8], ignore: bool, action: u8, ) { if ignore { return; } if action != b'm' { return; } let mut style = self.style; // param/value differences are dependent on the escape code let mut state = State::Normal; let mut r = None; let mut g = None; let mut color_target = ColorTarget::Fg; for param in params { for value in param { match (state, *value) { (State::Normal, 0) => { style = anstyle::Style::default(); break; } (State::Normal, 1) => { style = style.bold(); break; } (State::Normal, 2) => { style = style.dimmed(); break; } (State::Normal, 3) => { style = style.italic(); break; } (State::Normal, 4) => { style = style.underline(); state = State::Underline; } (State::Normal, 21) => { style |= anstyle::Effects::DOUBLE_UNDERLINE; break; } (State::Normal, 7) => { style = style.invert(); break; } (State::Normal, 8) => { style = style.hidden(); break; } (State::Normal, 9) => { style = style.strikethrough(); break; } (State::Normal, 30..=37) => { let color = to_ansi_color(value - 30).expect("within 4-bit range"); style = style.fg_color(Some(color.into())); break; } (State::Normal, 38) => { color_target = ColorTarget::Fg; state = State::PrepareCustomColor; } (State::Normal, 39) => { style = style.fg_color(None); break; } (State::Normal, 40..=47) => { let color = to_ansi_color(value - 40).expect("within 4-bit range"); style = style.bg_color(Some(color.into())); break; } (State::Normal, 48) => { color_target = ColorTarget::Bg; state = State::PrepareCustomColor; } (State::Normal, 49) => { style = style.bg_color(None); break; } (State::Normal, 58) => { color_target = ColorTarget::Underline; state = State::PrepareCustomColor; } (State::Normal, 90..=97) => { let color = to_ansi_color(value - 90) .expect("within 4-bit range") .bright(true); style = style.fg_color(Some(color.into())); break; } (State::Normal, 100..=107) => { let color = to_ansi_color(value - 100) .expect("within 4-bit range") .bright(true); style = style.bg_color(Some(color.into())); break; } (State::PrepareCustomColor, 5) => { state = State::Ansi256; } (State::PrepareCustomColor, 2) => { state = State::Rgb; r = None; g = None; } (State::Ansi256, n) => { let color = anstyle::Ansi256Color(n as u8); style = match color_target { ColorTarget::Fg => style.fg_color(Some(color.into())), ColorTarget::Bg => style.bg_color(Some(color.into())), ColorTarget::Underline => style.underline_color(Some(color.into())), }; break; } (State::Rgb, b) => match (r, g) { (None, _) => { r = Some(b); } (Some(_), None) => { g = Some(b); } (Some(r), Some(g)) => { let color = anstyle::RgbColor(r as u8, g as u8, b as u8); style = match color_target { ColorTarget::Fg => style.fg_color(Some(color.into())), ColorTarget::Bg => style.bg_color(Some(color.into())), ColorTarget::Underline => style.underline_color(Some(color.into())), }; break; } }, (State::Underline, 0) => { style = style.effects(style.get_effects().remove(anstyle::Effects::UNDERLINE)); } (State::Underline, 1) => { // underline already set } (State::Underline, 2) => { style = style .effects(style.get_effects().remove(anstyle::Effects::UNDERLINE)) | anstyle::Effects::DOUBLE_UNDERLINE; } (State::Underline, 3) => { style = style .effects(style.get_effects().remove(anstyle::Effects::UNDERLINE)) | anstyle::Effects::CURLY_UNDERLINE; } (State::Underline, 4) => { style = style .effects(style.get_effects().remove(anstyle::Effects::UNDERLINE)) | anstyle::Effects::DOTTED_UNDERLINE; } (State::Underline, 5) => { style = style .effects(style.get_effects().remove(anstyle::Effects::UNDERLINE)) | anstyle::Effects::DASHED_UNDERLINE; } _ => { break; } } } } if style != self.style && !self.printable.is_empty() { self.ready = Some(self.style); } self.style = style; } } #[derive(Copy, Clone, PartialEq, Eq, Debug)] enum State { Normal, PrepareCustomColor, Ansi256, Rgb, Underline, } #[derive(Copy, Clone, PartialEq, Eq, Debug)] enum ColorTarget { Fg, Bg, Underline, } fn to_ansi_color(digit: u16) -> Option { match digit { 0 => Some(anstyle::AnsiColor::Black), 1 => Some(anstyle::AnsiColor::Red), 2 => Some(anstyle::AnsiColor::Green), 3 => Some(anstyle::AnsiColor::Yellow), 4 => Some(anstyle::AnsiColor::Blue), 5 => Some(anstyle::AnsiColor::Magenta), 6 => Some(anstyle::AnsiColor::Cyan), 7 => Some(anstyle::AnsiColor::White), _ => None, } } #[cfg(test)] mod test { use super::*; use owo_colors::OwoColorize as _; use proptest::prelude::*; #[track_caller] fn verify(input: &str, expected: Vec<(anstyle::Style, &str)>) { let expected = expected .into_iter() .map(|(style, value)| (style, value.to_owned())) .collect::>(); let mut state = WinconBytes::new(); let actual = state.extract_next(input.as_bytes()).collect::>(); assert_eq!(expected, actual, "{input:?}"); } #[test] fn start() { let input = format!("{} world!", "Hello".green().on_red()); let expected = vec![ ( anstyle::AnsiColor::Green.on(anstyle::AnsiColor::Red), "Hello", ), (anstyle::Style::default(), " world!"), ]; verify(&input, expected); } #[test] fn middle() { let input = format!("Hello {}!", "world".green().on_red()); let expected = vec![ (anstyle::Style::default(), "Hello "), ( anstyle::AnsiColor::Green.on(anstyle::AnsiColor::Red), "world", ), (anstyle::Style::default(), "!"), ]; verify(&input, expected); } #[test] fn end() { let input = format!("Hello {}", "world!".green().on_red()); let expected = vec![ (anstyle::Style::default(), "Hello "), ( anstyle::AnsiColor::Green.on(anstyle::AnsiColor::Red), "world!", ), ]; verify(&input, expected); } #[test] fn ansi256_colors() { // termcolor only supports "brights" via these let input = format!( "Hello {}!", "world".color(owo_colors::XtermColors::UserBrightYellow) ); let expected = vec![ (anstyle::Style::default(), "Hello "), (anstyle::Ansi256Color(11).on_default(), "world"), (anstyle::Style::default(), "!"), ]; verify(&input, expected); } proptest! { #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn wincon_no_escapes(s in "\\PC*") { let expected = if s.is_empty() { vec![] } else { vec![(anstyle::Style::default(), s.clone())] }; let mut state = WinconBytes::new(); let actual = state.extract_next(s.as_bytes()).collect::>(); assert_eq!(expected, actual); } } } anstream-0.6.15/src/auto.rs000064400000000000000000000242411046102023000136370ustar 00000000000000use crate::stream::AsLockedWrite; use crate::stream::RawStream; use crate::ColorChoice; use crate::StripStream; #[cfg(all(windows, feature = "wincon"))] use crate::WinconStream; /// [`std::io::Write`] that adapts ANSI escape codes to the underlying `Write`s capabilities /// /// This includes /// - Stripping colors for non-terminals /// - Respecting env variables like [NO_COLOR](https://no-color.org/) or [CLICOLOR](https://bixense.com/clicolors/) /// - *(windows)* Falling back to the wincon API where [ENABLE_VIRTUAL_TERMINAL_PROCESSING](https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#output-sequences) is unsupported /// /// You can customize auto-detection by calling into /// [anstyle_query](https://docs.rs/anstyle-query/latest/anstyle_query/) /// to get a [`ColorChoice`] and then calling [`AutoStream::new(stream, choice)`]. #[derive(Debug)] pub struct AutoStream { inner: StreamInner, } #[derive(Debug)] enum StreamInner { PassThrough(S), Strip(StripStream), #[cfg(all(windows, feature = "wincon"))] Wincon(WinconStream), } impl AutoStream where S: RawStream, { /// Runtime control over styling behavior /// /// # Example /// /// ```rust /// # #[cfg(feature = "auto")] { /// # use std::io::IsTerminal as _; /// // Like `AutoStream::choice` but without `NO_COLOR`, `CLICOLOR_FORCE`, `CI` /// fn choice(raw: &dyn anstream::stream::RawStream) -> anstream::ColorChoice { /// let choice = anstream::ColorChoice::global(); /// if choice == anstream::ColorChoice::Auto { /// if raw.is_terminal() && anstyle_query::term_supports_color() { /// anstream::ColorChoice::Always /// } else { /// anstream::ColorChoice::Never /// } /// } else { /// choice /// } /// } /// /// let stream = std::io::stdout(); /// let choice = choice(&stream); /// let auto = anstream::AutoStream::new(stream, choice); /// # } /// ``` #[inline] pub fn new(raw: S, choice: ColorChoice) -> Self { match choice { #[cfg(feature = "auto")] ColorChoice::Auto => Self::auto(raw), #[cfg(not(feature = "auto"))] ColorChoice::Auto => Self::never(raw), ColorChoice::AlwaysAnsi => Self::always_ansi(raw), ColorChoice::Always => Self::always(raw), ColorChoice::Never => Self::never(raw), } } /// Auto-adapt for the stream's capabilities #[cfg(feature = "auto")] #[inline] pub fn auto(raw: S) -> Self { let choice = Self::choice(&raw); debug_assert_ne!(choice, ColorChoice::Auto); Self::new(raw, choice) } /// Report the desired choice for the given stream #[cfg(feature = "auto")] pub fn choice(raw: &S) -> ColorChoice { choice(raw) } /// Force ANSI escape codes to be passed through as-is, no matter what the inner `Write` /// supports. #[inline] pub fn always_ansi(raw: S) -> Self { #[cfg(feature = "auto")] { if raw.is_terminal() { let _ = anstyle_query::windows::enable_ansi_colors(); } } Self::always_ansi_(raw) } #[inline] fn always_ansi_(raw: S) -> Self { let inner = StreamInner::PassThrough(raw); AutoStream { inner } } /// Force color, no matter what the inner `Write` supports. #[inline] pub fn always(raw: S) -> Self { if cfg!(windows) { #[cfg(feature = "auto")] let use_wincon = raw.is_terminal() && !anstyle_query::windows::enable_ansi_colors().unwrap_or(true) && !anstyle_query::term_supports_ansi_color(); #[cfg(not(feature = "auto"))] let use_wincon = true; if use_wincon { Self::wincon(raw).unwrap_or_else(|raw| Self::always_ansi_(raw)) } else { Self::always_ansi_(raw) } } else { Self::always_ansi(raw) } } /// Only pass printable data to the inner `Write`. #[inline] pub fn never(raw: S) -> Self { let inner = StreamInner::Strip(StripStream::new(raw)); AutoStream { inner } } #[inline] fn wincon(raw: S) -> Result { #[cfg(all(windows, feature = "wincon"))] { Ok(Self { inner: StreamInner::Wincon(WinconStream::new(raw)), }) } #[cfg(not(all(windows, feature = "wincon")))] { Err(raw) } } /// Get the wrapped [`RawStream`] #[inline] pub fn into_inner(self) -> S { match self.inner { StreamInner::PassThrough(w) => w, StreamInner::Strip(w) => w.into_inner(), #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(w) => w.into_inner(), } } /// Returns `true` if the descriptor/handle refers to a terminal/tty. #[inline] pub fn is_terminal(&self) -> bool { match &self.inner { StreamInner::PassThrough(w) => w.is_terminal(), StreamInner::Strip(w) => w.is_terminal(), #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(_) => true, // its only ever a terminal } } /// Prefer [`AutoStream::choice`] /// /// This doesn't report what is requested but what is currently active. #[inline] #[cfg(feature = "auto")] pub fn current_choice(&self) -> ColorChoice { match &self.inner { StreamInner::PassThrough(_) => ColorChoice::AlwaysAnsi, StreamInner::Strip(_) => ColorChoice::Never, #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(_) => ColorChoice::Always, } } } #[cfg(feature = "auto")] fn choice(raw: &dyn RawStream) -> ColorChoice { let choice = ColorChoice::global(); match choice { ColorChoice::Auto => { let clicolor = anstyle_query::clicolor(); let clicolor_enabled = clicolor.unwrap_or(false); let clicolor_disabled = !clicolor.unwrap_or(true); if anstyle_query::no_color() { ColorChoice::Never } else if anstyle_query::clicolor_force() { ColorChoice::Always } else if clicolor_disabled { ColorChoice::Never } else if raw.is_terminal() && (anstyle_query::term_supports_color() || clicolor_enabled || anstyle_query::is_ci()) { ColorChoice::Always } else { ColorChoice::Never } } ColorChoice::AlwaysAnsi | ColorChoice::Always | ColorChoice::Never => choice, } } impl AutoStream { /// Get exclusive access to the `AutoStream` /// /// Why? /// - Faster performance when writing in a loop /// - Avoid other threads interleaving output with the current thread #[inline] pub fn lock(self) -> AutoStream> { let inner = match self.inner { StreamInner::PassThrough(w) => StreamInner::PassThrough(w.lock()), StreamInner::Strip(w) => StreamInner::Strip(w.lock()), #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(w) => StreamInner::Wincon(w.lock()), }; AutoStream { inner } } } impl AutoStream { /// Get exclusive access to the `AutoStream` /// /// Why? /// - Faster performance when writing in a loop /// - Avoid other threads interleaving output with the current thread #[inline] pub fn lock(self) -> AutoStream> { let inner = match self.inner { StreamInner::PassThrough(w) => StreamInner::PassThrough(w.lock()), StreamInner::Strip(w) => StreamInner::Strip(w.lock()), #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(w) => StreamInner::Wincon(w.lock()), }; AutoStream { inner } } } impl std::io::Write for AutoStream where S: RawStream + AsLockedWrite, { // Must forward all calls to ensure locking happens appropriately #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { match &mut self.inner { StreamInner::PassThrough(w) => w.as_locked_write().write(buf), StreamInner::Strip(w) => w.write(buf), #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(w) => w.write(buf), } } #[inline] fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { match &mut self.inner { StreamInner::PassThrough(w) => w.as_locked_write().write_vectored(bufs), StreamInner::Strip(w) => w.write_vectored(bufs), #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(w) => w.write_vectored(bufs), } } // is_write_vectored: nightly only #[inline] fn flush(&mut self) -> std::io::Result<()> { match &mut self.inner { StreamInner::PassThrough(w) => w.as_locked_write().flush(), StreamInner::Strip(w) => w.flush(), #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(w) => w.flush(), } } #[inline] fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { match &mut self.inner { StreamInner::PassThrough(w) => w.as_locked_write().write_all(buf), StreamInner::Strip(w) => w.write_all(buf), #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(w) => w.write_all(buf), } } // write_all_vectored: nightly only #[inline] fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::io::Result<()> { match &mut self.inner { StreamInner::PassThrough(w) => w.as_locked_write().write_fmt(args), StreamInner::Strip(w) => w.write_fmt(args), #[cfg(all(windows, feature = "wincon"))] StreamInner::Wincon(w) => w.write_fmt(args), } } } anstream-0.6.15/src/buffer.rs000064400000000000000000000027411046102023000141410ustar 00000000000000#![allow(deprecated)] /// In-memory [`RawStream`][crate::stream::RawStream] #[derive(Clone, Default, Debug, PartialEq, Eq)] #[deprecated(since = "0.6.2", note = "Use Vec")] #[doc(hidden)] pub struct Buffer(Vec); impl Buffer { #[inline] pub fn new() -> Self { Default::default() } #[inline] pub fn with_capacity(capacity: usize) -> Self { Self(Vec::with_capacity(capacity)) } #[inline] pub fn as_bytes(&self) -> &[u8] { &self.0 } } impl AsRef<[u8]> for Buffer { #[inline] fn as_ref(&self) -> &[u8] { self.as_bytes() } } impl std::io::Write for Buffer { #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { self.0.extend(buf); Ok(buf.len()) } #[inline] fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } #[cfg(all(windows, feature = "wincon"))] impl anstyle_wincon::WinconStream for Buffer { fn write_colored( &mut self, fg: Option, bg: Option, data: &[u8], ) -> std::io::Result { self.0.write_colored(fg, bg, data) } } #[cfg(all(windows, feature = "wincon"))] impl anstyle_wincon::WinconStream for &'_ mut Buffer { fn write_colored( &mut self, fg: Option, bg: Option, data: &[u8], ) -> std::io::Result { (**self).write_colored(fg, bg, data) } } anstream-0.6.15/src/fmt.rs000064400000000000000000000026201046102023000134520ustar 00000000000000/// A shim which allows a [`std::io::Write`] to be implemented in terms of a [`std::fmt::Write`] /// /// This saves off I/O errors. instead of discarding them pub(crate) struct Adapter where W: FnMut(&[u8]) -> std::io::Result<()>, { writer: W, error: std::io::Result<()>, } impl Adapter where W: FnMut(&[u8]) -> std::io::Result<()>, { pub(crate) fn new(writer: W) -> Self { Adapter { writer, error: Ok(()), } } pub(crate) fn write_fmt(mut self, fmt: std::fmt::Arguments<'_>) -> std::io::Result<()> { match std::fmt::write(&mut self, fmt) { Ok(()) => Ok(()), Err(..) => { // check if the error came from the underlying `Write` or not if self.error.is_err() { self.error } else { Err(std::io::Error::new( std::io::ErrorKind::Other, "formatter error", )) } } } } } impl std::fmt::Write for Adapter where W: FnMut(&[u8]) -> std::io::Result<()>, { fn write_str(&mut self, s: &str) -> std::fmt::Result { match (self.writer)(s.as_bytes()) { Ok(()) => Ok(()), Err(e) => { self.error = Err(e); Err(std::fmt::Error) } } } } anstream-0.6.15/src/lib.rs000064400000000000000000000051121046102023000134310ustar 00000000000000//! **Auto-adapting [`stdout`] / [`stderr`] streams** //! //! *A portmanteau of "ansi stream"* //! //! [`AutoStream`] always accepts [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code), //! [adapting to the user's terminal's capabilities][AutoStream]. //! //! Benefits //! - Allows the caller to not be concerned with the terminal's capabilities //! - Semver safe way of passing styled text between crates as ANSI escape codes offer more //! compatibility than most crate APIs. //! //! Available styling crates: //! - [anstyle](https://docs.rs/anstyle) for minimal runtime styling, designed to go in public APIs //! - [owo-colors](https://docs.rs/owo-colors) for feature-rich runtime styling //! - [color-print](https://docs.rs/color-print) for feature-rich compile-time styling //! //! # Example //! //! ``` //! # #[cfg(feature = "auto")] { //! use anstream::println; //! use owo_colors::OwoColorize as _; //! //! // Foreground colors //! println!("My number is {:#x}!", 10.green()); //! // Background colors //! println!("My number is not {}!", 4.on_red()); //! # } //! ``` //! //! And this will correctly handle piping to a file, etc #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(missing_docs)] #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] pub mod adapter; pub mod stream; mod buffer; #[macro_use] mod macros; mod auto; mod fmt; mod strip; #[cfg(all(windows, feature = "wincon"))] mod wincon; pub use auto::AutoStream; pub use strip::StripStream; #[cfg(all(windows, feature = "wincon"))] pub use wincon::WinconStream; #[allow(deprecated)] pub use buffer::Buffer; /// An adaptive wrapper around the global standard output stream of the current process pub type Stdout = AutoStream; /// An adaptive wrapper around the global standard error stream of the current process pub type Stderr = AutoStream; /// Create an ANSI escape code compatible stdout /// /// **Note:** Call [`AutoStream::lock`] in loops to avoid the performance hit of acquiring/releasing /// from the implicit locking in each [`std::io::Write`] call #[cfg(feature = "auto")] pub fn stdout() -> Stdout { let stdout = std::io::stdout(); AutoStream::auto(stdout) } /// Create an ANSI escape code compatible stderr /// /// **Note:** Call [`AutoStream::lock`] in loops to avoid the performance hit of acquiring/releasing /// from the implicit locking in each [`std::io::Write`] call #[cfg(feature = "auto")] pub fn stderr() -> Stderr { let stderr = std::io::stderr(); AutoStream::auto(stderr) } /// Selection for overriding color output pub use colorchoice::ColorChoice; anstream-0.6.15/src/macros.rs000064400000000000000000000331441046102023000141550ustar 00000000000000/// Prints to [`stdout`][crate::stdout]. /// /// Equivalent to the [`println!`] macro except that a newline is not printed at /// the end of the message. /// /// Note that stdout is frequently line-buffered by default so it may be /// necessary to use [`std::io::Write::flush()`] to ensure the output is emitted /// immediately. /// /// **NOTE:** The `print!` macro will lock the standard output on each call. If you call /// `print!` within a hot loop, this behavior may be the bottleneck of the loop. /// To avoid this, lock stdout with [`AutoStream::lock`][crate::AutoStream::lock]: /// ``` /// # #[cfg(feature = "auto")] { /// use std::io::Write as _; /// /// let mut lock = anstream::stdout().lock(); /// write!(lock, "hello world").unwrap(); /// # } /// ``` /// /// Use `print!` only for the primary output of your program. Use /// [`eprint!`] instead to print error and progress messages. /// /// **NOTE:** Not all `print!` calls will be captured in tests like [`std::print!`] /// - Capturing will automatically be activated in test binaries /// - Otherwise, only when the `test` feature is enabled /// /// # Panics /// /// Panics if writing to `stdout` fails for any reason **except** broken pipe. /// /// Writing to non-blocking stdout can cause an error, which will lead /// this macro to panic. /// /// # Examples /// /// ``` /// # #[cfg(feature = "auto")] { /// use std::io::Write as _; /// use anstream::print; /// use anstream::stdout; /// /// print!("this "); /// print!("will "); /// print!("be "); /// print!("on "); /// print!("the "); /// print!("same "); /// print!("line "); /// /// stdout().flush().unwrap(); /// /// print!("this string has a newline, why not choose println! instead?\n"); /// /// stdout().flush().unwrap(); /// # } /// ``` #[cfg(feature = "auto")] #[macro_export] macro_rules! print { ($($arg:tt)*) => {{ if cfg!(any(feature = "test", test)) { use std::io::Write as _; let stdio = std::io::stdout(); let choice = $crate::AutoStream::choice(&stdio); let buffer = Vec::new(); let mut stream = $crate::AutoStream::new(buffer, choice); // Ignore errors rather than panic let _ = ::std::write!(&mut stream, $($arg)*); let buffer = stream.into_inner(); // Should be UTF-8 but not wanting to panic let buffer = String::from_utf8_lossy(&buffer); ::std::print!("{}", buffer) } else { use std::io::Write as _; let mut stream = $crate::stdout(); match ::std::write!(&mut stream, $($arg)*) { Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { ::std::panic!("failed printing to stdout: {e}"); } Err(_) | Ok(_) => {} } } }}; } /// Prints to [`stdout`][crate::stdout], with a newline. /// /// On all platforms, the newline is the LINE FEED character (`\n`/`U+000A`) alone /// (no additional CARRIAGE RETURN (`\r`/`U+000D`)). /// /// This macro uses the same syntax as [`format!`], but writes to the standard output instead. /// See [`std::fmt`] for more information. /// /// **NOTE:** The `println!` macro will lock the standard output on each call. If you call /// `println!` within a hot loop, this behavior may be the bottleneck of the loop. /// To avoid this, lock stdout with [`AutoStream::lock`][crate::AutoStream::lock]: /// ``` /// # #[cfg(feature = "auto")] { /// use std::io::Write as _; /// /// let mut lock = anstream::stdout().lock(); /// writeln!(lock, "hello world").unwrap(); /// # } /// ``` /// /// Use `println!` only for the primary output of your program. Use /// [`eprintln!`] instead to print error and progress messages. /// /// **NOTE:** Not all `println!` calls will be captured in tests like [`std::println!`] /// - Capturing will automatically be activated in test binaries /// - Otherwise, only when the `test` feature is enabled /// /// # Panics /// /// Panics if writing to `stdout` fails for any reason **except** broken pipe. /// /// Writing to non-blocking stdout can cause an error, which will lead /// this macro to panic. /// /// # Examples /// /// ``` /// # #[cfg(feature = "auto")] { /// use anstream::println; /// /// println!(); // prints just a newline /// println!("hello there!"); /// println!("format {} arguments", "some"); /// let local_variable = "some"; /// println!("format {local_variable} arguments"); /// # } /// ``` #[cfg(feature = "auto")] #[macro_export] macro_rules! println { () => { $crate::print!("\n") }; ($($arg:tt)*) => {{ if cfg!(any(feature = "test", test)) { use std::io::Write as _; let stdio = std::io::stdout(); let choice = $crate::AutoStream::choice(&stdio); let buffer = Vec::new(); let mut stream = $crate::AutoStream::new(buffer, choice); // Ignore errors rather than panic let _ = ::std::write!(&mut stream, $($arg)*); let buffer = stream.into_inner(); // Should be UTF-8 but not wanting to panic let buffer = String::from_utf8_lossy(&buffer); ::std::println!("{}", buffer) } else { use std::io::Write as _; let mut stream = $crate::stdout(); match ::std::writeln!(&mut stream, $($arg)*) { Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { ::std::panic!("failed printing to stdout: {e}"); } Err(_) | Ok(_) => {} } } }}; } /// Prints to [`stderr`][crate::stderr]. /// /// Equivalent to the [`print!`] macro, except that output goes to /// `stderr` instead of `stdout`. See [`print!`] for /// example usage. /// /// Use `eprint!` only for error and progress messages. Use `print!` /// instead for the primary output of your program. /// /// **NOTE:** Not all `eprint!` calls will be captured in tests like [`std::eprint!`] /// - Capturing will automatically be activated in test binaries /// - Otherwise, only when the `test` feature is enabled /// /// # Panics /// /// Panics if writing to `stderr` fails for any reason **except** broken pipe. /// /// Writing to non-blocking stdout can cause an error, which will lead /// this macro to panic. /// /// # Examples /// /// ``` /// # #[cfg(feature = "auto")] { /// use anstream::eprint; /// /// eprint!("Error: Could not complete task"); /// # } /// ``` #[cfg(feature = "auto")] #[macro_export] macro_rules! eprint { ($($arg:tt)*) => {{ if cfg!(any(feature = "test", test)) { use std::io::Write as _; let stdio = std::io::stderr(); let choice = $crate::AutoStream::choice(&stdio); let buffer = Vec::new(); let mut stream = $crate::AutoStream::new(buffer, choice); // Ignore errors rather than panic let _ = ::std::write!(&mut stream, $($arg)*); let buffer = stream.into_inner(); // Should be UTF-8 but not wanting to panic let buffer = String::from_utf8_lossy(&buffer); ::std::eprint!("{}", buffer) } else { use std::io::Write as _; let mut stream = $crate::stderr(); match ::std::write!(&mut stream, $($arg)*) { Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { ::std::panic!("failed printing to stdout: {e}"); } Err(_) | Ok(_) => {} } } }}; } /// Prints to [`stderr`][crate::stderr], with a newline. /// /// Equivalent to the [`println!`] macro, except that output goes to /// `stderr` instead of `stdout`. See [`println!`] for /// example usage. /// /// Use `eprintln!` only for error and progress messages. Use `println!` /// instead for the primary output of your program. /// /// **NOTE:** Not all `eprintln!` calls will be captured in tests like [`std::eprintln!`] /// - Capturing will automatically be activated in test binaries /// - Otherwise, only when the `test` feature is enabled /// /// # Panics /// /// Panics if writing to `stderr` fails for any reason **except** broken pipe. /// /// Writing to non-blocking stdout can cause an error, which will lead /// this macro to panic. /// /// # Examples /// /// ``` /// # #[cfg(feature = "auto")] { /// use anstream::eprintln; /// /// eprintln!("Error: Could not complete task"); /// # } /// ``` #[cfg(feature = "auto")] #[macro_export] macro_rules! eprintln { () => { $crate::eprint!("\n") }; ($($arg:tt)*) => {{ if cfg!(any(feature = "test", test)) { use std::io::Write as _; let stdio = std::io::stderr(); let choice = $crate::AutoStream::choice(&stdio); let buffer = Vec::new(); let mut stream = $crate::AutoStream::new(buffer, choice); // Ignore errors rather than panic let _ = ::std::write!(&mut stream, $($arg)*); let buffer = stream.into_inner(); // Should be UTF-8 but not wanting to panic let buffer = String::from_utf8_lossy(&buffer); ::std::eprintln!("{}", buffer) } else { use std::io::Write as _; let mut stream = $crate::stderr(); match ::std::writeln!(&mut stream, $($arg)*) { Err(e) if e.kind() != ::std::io::ErrorKind::BrokenPipe => { ::std::panic!("failed printing to stdout: {e}"); } Err(_) | Ok(_) => {} } } }}; } /// Panics the current thread. /// /// This allows a program to terminate immediately and provide feedback /// to the caller of the program. /// /// This macro is the perfect way to assert conditions in example code and in /// tests. `panic!` is closely tied with the `unwrap` method of both /// [`Option`][ounwrap] and [`Result`][runwrap] enums. Both implementations call /// `panic!` when they are set to [`None`] or [`Err`] variants. /// /// When using `panic!()` you can specify a string payload, that is built using /// the [`format!`] syntax. That payload is used when injecting the panic into /// the calling Rust thread, causing the thread to panic entirely. /// /// The behavior of the default `std` hook, i.e. the code that runs directly /// after the panic is invoked, is to print the message payload to /// `stderr` along with the file/line/column information of the `panic!()` /// call. You can override the panic hook using [`std::panic::set_hook()`]. /// Inside the hook a panic can be accessed as a `&dyn Any + Send`, /// which contains either a `&str` or `String` for regular `panic!()` invocations. /// To panic with a value of another other type, [`panic_any`] can be used. /// /// See also the macro [`compile_error!`], for raising errors during compilation. /// /// # When to use `panic!` vs `Result` /// /// The Rust language provides two complementary systems for constructing / /// representing, reporting, propagating, reacting to, and discarding errors. These /// responsibilities are collectively known as "error handling." `panic!` and /// `Result` are similar in that they are each the primary interface of their /// respective error handling systems; however, the meaning these interfaces attach /// to their errors and the responsibilities they fulfill within their respective /// error handling systems differ. /// /// The `panic!` macro is used to construct errors that represent a bug that has /// been detected in your program. With `panic!` you provide a message that /// describes the bug and the language then constructs an error with that message, /// reports it, and propagates it for you. /// /// `Result` on the other hand is used to wrap other types that represent either /// the successful result of some computation, `Ok(T)`, or error types that /// represent an anticipated runtime failure mode of that computation, `Err(E)`. /// `Result` is used alongside user defined types which represent the various /// anticipated runtime failure modes that the associated computation could /// encounter. `Result` must be propagated manually, often with the the help of the /// `?` operator and `Try` trait, and they must be reported manually, often with /// the help of the `Error` trait. /// /// For more detailed information about error handling check out the [book] or the /// [`std::result`] module docs. /// /// [ounwrap]: Option::unwrap /// [runwrap]: Result::unwrap /// [`std::panic::set_hook()`]: ../std/panic/fn.set_hook.html /// [`panic_any`]: ../std/panic/fn.panic_any.html /// [`Box`]: ../std/boxed/struct.Box.html /// [`Any`]: crate::any::Any /// [`format!`]: ../std/macro.format.html /// [book]: ../book/ch09-00-error-handling.html /// [`std::result`]: ../std/result/index.html /// /// # Current implementation /// /// If the main thread panics it will terminate all your threads and end your /// program with code `101`. /// /// # Examples /// /// ```should_panic /// # #![allow(unreachable_code)] /// use anstream::panic; /// panic!(); /// panic!("this is a terrible mistake!"); /// panic!("this is a {} {message}", "fancy", message = "message"); /// ``` #[cfg(feature = "auto")] #[macro_export] macro_rules! panic { () => { ::std::panic!() }; ($($arg:tt)*) => {{ use std::io::Write as _; let panic_stream = std::io::stderr(); let choice = $crate::AutoStream::choice(&panic_stream); let buffer = Vec::new(); let mut stream = $crate::AutoStream::new(buffer, choice); // Ignore errors rather than panic let _ = ::std::write!(&mut stream, $($arg)*); let buffer = stream.into_inner(); // Should be UTF-8 but not wanting to panic let buffer = String::from_utf8_lossy(&buffer).into_owned(); ::std::panic!("{}", buffer) }}; } anstream-0.6.15/src/stream.rs000064400000000000000000000131021046102023000141540ustar 00000000000000//! Higher-level traits to describe writeable streams /// Required functionality for underlying [`std::io::Write`] for adaptation #[cfg(not(all(windows, feature = "wincon")))] pub trait RawStream: std::io::Write + IsTerminal + private::Sealed {} /// Required functionality for underlying [`std::io::Write`] for adaptation #[cfg(all(windows, feature = "wincon"))] pub trait RawStream: std::io::Write + IsTerminal + anstyle_wincon::WinconStream + private::Sealed { } impl RawStream for std::io::Stdout {} impl RawStream for std::io::StdoutLock<'_> {} impl RawStream for &'_ mut std::io::StdoutLock<'_> {} impl RawStream for std::io::Stderr {} impl RawStream for std::io::StderrLock<'_> {} impl RawStream for &'_ mut std::io::StderrLock<'_> {} impl RawStream for Box {} impl RawStream for &'_ mut Box {} impl RawStream for Vec {} impl RawStream for &'_ mut Vec {} impl RawStream for std::fs::File {} impl RawStream for &'_ mut std::fs::File {} #[allow(deprecated)] impl RawStream for crate::Buffer {} #[allow(deprecated)] impl RawStream for &'_ mut crate::Buffer {} /// Trait to determine if a descriptor/handle refers to a terminal/tty. pub trait IsTerminal: private::Sealed { /// Returns `true` if the descriptor/handle refers to a terminal/tty. fn is_terminal(&self) -> bool; } impl IsTerminal for std::io::Stdout { #[inline] fn is_terminal(&self) -> bool { is_terminal_polyfill::IsTerminal::is_terminal(self) } } impl IsTerminal for std::io::StdoutLock<'_> { #[inline] fn is_terminal(&self) -> bool { is_terminal_polyfill::IsTerminal::is_terminal(self) } } impl IsTerminal for &'_ mut std::io::StdoutLock<'_> { #[inline] fn is_terminal(&self) -> bool { (**self).is_terminal() } } impl IsTerminal for std::io::Stderr { #[inline] fn is_terminal(&self) -> bool { is_terminal_polyfill::IsTerminal::is_terminal(self) } } impl IsTerminal for std::io::StderrLock<'_> { #[inline] fn is_terminal(&self) -> bool { is_terminal_polyfill::IsTerminal::is_terminal(self) } } impl IsTerminal for &'_ mut std::io::StderrLock<'_> { #[inline] fn is_terminal(&self) -> bool { (**self).is_terminal() } } impl IsTerminal for Box { #[inline] fn is_terminal(&self) -> bool { false } } impl IsTerminal for &'_ mut Box { #[inline] fn is_terminal(&self) -> bool { false } } impl IsTerminal for Vec { #[inline] fn is_terminal(&self) -> bool { false } } impl IsTerminal for &'_ mut Vec { #[inline] fn is_terminal(&self) -> bool { false } } impl IsTerminal for std::fs::File { #[inline] fn is_terminal(&self) -> bool { is_terminal_polyfill::IsTerminal::is_terminal(self) } } impl IsTerminal for &'_ mut std::fs::File { #[inline] fn is_terminal(&self) -> bool { (**self).is_terminal() } } #[allow(deprecated)] impl IsTerminal for crate::Buffer { #[inline] fn is_terminal(&self) -> bool { false } } #[allow(deprecated)] impl IsTerminal for &'_ mut crate::Buffer { #[inline] fn is_terminal(&self) -> bool { (**self).is_terminal() } } /// Lock a stream pub trait AsLockedWrite: private::Sealed { /// Locked writer type type Write<'w>: RawStream + 'w where Self: 'w; /// Lock a stream fn as_locked_write(&mut self) -> Self::Write<'_>; } impl AsLockedWrite for std::io::Stdout { type Write<'w> = std::io::StdoutLock<'w>; #[inline] fn as_locked_write(&mut self) -> Self::Write<'_> { self.lock() } } impl AsLockedWrite for std::io::StdoutLock<'static> { type Write<'w> = &'w mut Self; #[inline] fn as_locked_write(&mut self) -> Self::Write<'_> { self } } impl AsLockedWrite for std::io::Stderr { type Write<'w> = std::io::StderrLock<'w>; #[inline] fn as_locked_write(&mut self) -> Self::Write<'_> { self.lock() } } impl AsLockedWrite for std::io::StderrLock<'static> { type Write<'w> = &'w mut Self; #[inline] fn as_locked_write(&mut self) -> Self::Write<'_> { self } } impl AsLockedWrite for Box { type Write<'w> = &'w mut Self; #[inline] fn as_locked_write(&mut self) -> Self::Write<'_> { self } } impl AsLockedWrite for Vec { type Write<'w> = &'w mut Self; #[inline] fn as_locked_write(&mut self) -> Self::Write<'_> { self } } impl AsLockedWrite for std::fs::File { type Write<'w> = &'w mut Self; #[inline] fn as_locked_write(&mut self) -> Self::Write<'_> { self } } #[allow(deprecated)] impl AsLockedWrite for crate::Buffer { type Write<'w> = &'w mut Self; #[inline] fn as_locked_write(&mut self) -> Self::Write<'_> { self } } mod private { pub trait Sealed {} impl Sealed for std::io::Stdout {} impl Sealed for std::io::StdoutLock<'_> {} impl Sealed for &'_ mut std::io::StdoutLock<'_> {} impl Sealed for std::io::Stderr {} impl Sealed for std::io::StderrLock<'_> {} impl Sealed for &'_ mut std::io::StderrLock<'_> {} impl Sealed for Box {} impl Sealed for &'_ mut Box {} impl Sealed for Vec {} impl Sealed for &'_ mut Vec {} impl Sealed for std::fs::File {} impl Sealed for &'_ mut std::fs::File {} #[allow(deprecated)] impl Sealed for crate::Buffer {} #[allow(deprecated)] impl Sealed for &'_ mut crate::Buffer {} } anstream-0.6.15/src/strip.rs000064400000000000000000000151031046102023000140250ustar 00000000000000use crate::adapter::StripBytes; use crate::stream::AsLockedWrite; use crate::stream::IsTerminal; /// Only pass printable data to the inner `Write` #[derive(Debug)] pub struct StripStream where S: std::io::Write, { raw: S, state: StripBytes, } impl StripStream where S: std::io::Write, { /// Only pass printable data to the inner `Write` #[inline] pub fn new(raw: S) -> Self { Self { raw, state: Default::default(), } } /// Get the wrapped [`std::io::Write`] #[inline] pub fn into_inner(self) -> S { self.raw } } impl StripStream where S: std::io::Write, S: IsTerminal, { /// Returns `true` if the descriptor/handle refers to a terminal/tty. #[inline] pub fn is_terminal(&self) -> bool { self.raw.is_terminal() } } impl StripStream { /// Get exclusive access to the `StripStream` /// /// Why? /// - Faster performance when writing in a loop /// - Avoid other threads interleaving output with the current thread #[inline] pub fn lock(self) -> StripStream> { StripStream { raw: self.raw.lock(), state: self.state, } } } impl StripStream { /// Get exclusive access to the `StripStream` /// /// Why? /// - Faster performance when writing in a loop /// - Avoid other threads interleaving output with the current thread #[inline] pub fn lock(self) -> StripStream> { StripStream { raw: self.raw.lock(), state: self.state, } } } impl std::io::Write for StripStream where S: std::io::Write, S: AsLockedWrite, { // Must forward all calls to ensure locking happens appropriately #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { write(&mut self.raw.as_locked_write(), &mut self.state, buf) } #[inline] fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { let buf = bufs .iter() .find(|b| !b.is_empty()) .map(|b| &**b) .unwrap_or(&[][..]); self.write(buf) } // is_write_vectored: nightly only #[inline] fn flush(&mut self) -> std::io::Result<()> { self.raw.as_locked_write().flush() } #[inline] fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { write_all(&mut self.raw.as_locked_write(), &mut self.state, buf) } // write_all_vectored: nightly only #[inline] fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::io::Result<()> { write_fmt(&mut self.raw.as_locked_write(), &mut self.state, args) } } fn write( raw: &mut dyn std::io::Write, state: &mut StripBytes, buf: &[u8], ) -> std::io::Result { let initial_state = state.clone(); for printable in state.strip_next(buf) { let possible = printable.len(); let written = raw.write(printable)?; if possible != written { let divergence = &printable[written..]; let offset = offset_to(buf, divergence); let consumed = &buf[offset..]; *state = initial_state; state.strip_next(consumed).last(); return Ok(offset); } } Ok(buf.len()) } fn write_all( raw: &mut dyn std::io::Write, state: &mut StripBytes, buf: &[u8], ) -> std::io::Result<()> { for printable in state.strip_next(buf) { raw.write_all(printable)?; } Ok(()) } fn write_fmt( raw: &mut dyn std::io::Write, state: &mut StripBytes, args: std::fmt::Arguments<'_>, ) -> std::io::Result<()> { let write_all = |buf: &[u8]| write_all(raw, state, buf); crate::fmt::Adapter::new(write_all).write_fmt(args) } #[inline] fn offset_to(total: &[u8], subslice: &[u8]) -> usize { let total = total.as_ptr(); let subslice = subslice.as_ptr(); debug_assert!( total <= subslice, "`Offset::offset_to` only accepts slices of `self`" ); subslice as usize - total as usize } #[cfg(test)] mod test { use super::*; use proptest::prelude::*; use std::io::Write as _; proptest! { #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn write_all_no_escapes(s in "\\PC*") { let buffer = Vec::new(); let mut stream = StripStream::new(buffer); stream.write_all(s.as_bytes()).unwrap(); let buffer = stream.into_inner(); let actual = std::str::from_utf8(buffer.as_ref()).unwrap(); assert_eq!(s, actual); } #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn write_byte_no_escapes(s in "\\PC*") { let buffer = Vec::new(); let mut stream = StripStream::new(buffer); for byte in s.as_bytes() { stream.write_all(&[*byte]).unwrap(); } let buffer = stream.into_inner(); let actual = std::str::from_utf8(buffer.as_ref()).unwrap(); assert_eq!(s, actual); } #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn write_all_random(s in any::>()) { let buffer = Vec::new(); let mut stream = StripStream::new(buffer); stream.write_all(s.as_slice()).unwrap(); let buffer = stream.into_inner(); if let Ok(actual) = std::str::from_utf8(buffer.as_ref()) { for char in actual.chars() { assert!(!char.is_ascii() || !char.is_control() || char.is_ascii_whitespace(), "{:?} -> {:?}: {:?}", String::from_utf8_lossy(&s), actual, char); } } } #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn write_byte_random(s in any::>()) { let buffer = Vec::new(); let mut stream = StripStream::new(buffer); for byte in s.as_slice() { stream.write_all(&[*byte]).unwrap(); } let buffer = stream.into_inner(); if let Ok(actual) = std::str::from_utf8(buffer.as_ref()) { for char in actual.chars() { assert!(!char.is_ascii() || !char.is_control() || char.is_ascii_whitespace(), "{:?} -> {:?}: {:?}", String::from_utf8_lossy(&s), actual, char); } } } } } anstream-0.6.15/src/wincon.rs000064400000000000000000000153551046102023000141720ustar 00000000000000use crate::adapter::WinconBytes; use crate::stream::AsLockedWrite; use crate::stream::IsTerminal; /// Only pass printable data to the inner `Write` #[cfg(feature = "wincon")] // here mostly for documentation purposes #[derive(Debug)] pub struct WinconStream where S: anstyle_wincon::WinconStream, { raw: S, // `WinconBytes` is especially large compared to other variants of `AutoStream`, so boxing it // here so `AutoStream` doesn't have to discard one allocation and create another one when // calling `AutoStream::lock` state: Box, } impl WinconStream where S: anstyle_wincon::WinconStream, { /// Only pass printable data to the inner `Write` #[inline] pub fn new(raw: S) -> Self { Self { raw, state: Default::default(), } } /// Get the wrapped [`anstyle_wincon::WinconStream`] #[inline] pub fn into_inner(self) -> S { self.raw } } impl WinconStream where S: anstyle_wincon::WinconStream, S: IsTerminal, { #[inline] pub fn is_terminal(&self) -> bool { self.raw.is_terminal() } } impl WinconStream { /// Get exclusive access to the `WinconStream` /// /// Why? /// - Faster performance when writing in a loop /// - Avoid other threads interleaving output with the current thread #[inline] pub fn lock(self) -> WinconStream> { WinconStream { raw: self.raw.lock(), state: self.state, } } } impl WinconStream { /// Get exclusive access to the `WinconStream` /// /// Why? /// - Faster performance when writing in a loop /// - Avoid other threads interleaving output with the current thread #[inline] pub fn lock(self) -> WinconStream> { WinconStream { raw: self.raw.lock(), state: self.state, } } } impl std::io::Write for WinconStream where S: anstyle_wincon::WinconStream, S: AsLockedWrite, { // Must forward all calls to ensure locking happens appropriately #[inline] fn write(&mut self, buf: &[u8]) -> std::io::Result { write(&mut self.raw.as_locked_write(), &mut self.state, buf) } #[inline] fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result { let buf = bufs .iter() .find(|b| !b.is_empty()) .map(|b| &**b) .unwrap_or(&[][..]); self.write(buf) } // is_write_vectored: nightly only #[inline] fn flush(&mut self) -> std::io::Result<()> { self.raw.as_locked_write().flush() } #[inline] fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { write_all(&mut self.raw.as_locked_write(), &mut self.state, buf) } // write_all_vectored: nightly only #[inline] fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) -> std::io::Result<()> { write_fmt(&mut self.raw.as_locked_write(), &mut self.state, args) } } fn write( raw: &mut dyn anstyle_wincon::WinconStream, state: &mut WinconBytes, buf: &[u8], ) -> std::io::Result { for (style, printable) in state.extract_next(buf) { let fg = style.get_fg_color().and_then(cap_wincon_color); let bg = style.get_bg_color().and_then(cap_wincon_color); let written = raw.write_colored(fg, bg, printable.as_bytes())?; let possible = printable.len(); if possible != written { // HACK: Unsupported atm break; } } Ok(buf.len()) } fn write_all( raw: &mut dyn anstyle_wincon::WinconStream, state: &mut WinconBytes, buf: &[u8], ) -> std::io::Result<()> { for (style, printable) in state.extract_next(buf) { let mut buf = printable.as_bytes(); let fg = style.get_fg_color().and_then(cap_wincon_color); let bg = style.get_bg_color().and_then(cap_wincon_color); while !buf.is_empty() { match raw.write_colored(fg, bg, buf) { Ok(0) => { return Err(std::io::Error::new( std::io::ErrorKind::WriteZero, "failed to write whole buffer", )); } Ok(n) => buf = &buf[n..], Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {} Err(e) => return Err(e), } } } Ok(()) } fn write_fmt( raw: &mut dyn anstyle_wincon::WinconStream, state: &mut WinconBytes, args: std::fmt::Arguments<'_>, ) -> std::io::Result<()> { let write_all = |buf: &[u8]| write_all(raw, state, buf); crate::fmt::Adapter::new(write_all).write_fmt(args) } fn cap_wincon_color(color: anstyle::Color) -> Option { match color { anstyle::Color::Ansi(c) => Some(c), anstyle::Color::Ansi256(c) => c.into_ansi(), anstyle::Color::Rgb(_) => None, } } #[cfg(test)] mod test { use super::*; use proptest::prelude::*; use std::io::Write as _; proptest! { #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn write_all_no_escapes(s in "\\PC*") { let buffer = Vec::new(); let mut stream = WinconStream::new(buffer); stream.write_all(s.as_bytes()).unwrap(); let buffer = stream.into_inner(); let actual = std::str::from_utf8(buffer.as_ref()).unwrap(); assert_eq!(s, actual); } #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn write_byte_no_escapes(s in "\\PC*") { let buffer = Vec::new(); let mut stream = WinconStream::new(buffer); for byte in s.as_bytes() { stream.write_all(&[*byte]).unwrap(); } let buffer = stream.into_inner(); let actual = std::str::from_utf8(buffer.as_ref()).unwrap(); assert_eq!(s, actual); } #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn write_all_random(s in any::>()) { let buffer = Vec::new(); let mut stream = WinconStream::new(buffer); stream.write_all(s.as_slice()).unwrap(); } #[test] #[cfg_attr(miri, ignore)] // See https://github.com/AltSysrq/proptest/issues/253 fn write_byte_random(s in any::>()) { let buffer = Vec::new(); let mut stream = WinconStream::new(buffer); for byte in s.as_slice() { stream.write_all(&[*byte]).unwrap(); } } } }