mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 12:37:49 +00:00
Merge branch 'master' of https://github.com/uutils/coreutils into hbina-tr-reimplement-expansion
This commit is contained in:
commit
9a09b8a06c
31 changed files with 1840 additions and 1187 deletions
2
.github/workflows/CICD.yml
vendored
2
.github/workflows/CICD.yml
vendored
|
@ -532,7 +532,7 @@ jobs:
|
|||
if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi
|
||||
|
||||
test_freebsd:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-10.15
|
||||
name: Tests/FreeBSD test suite
|
||||
env:
|
||||
mem: 2048
|
||||
|
|
253
Cargo.lock
generated
253
Cargo.lock
generated
|
@ -20,7 +20,7 @@ version = "0.7.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -76,6 +76,17 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "bigdecimal"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binary-heap-plus"
|
||||
version = "0.4.1"
|
||||
|
@ -101,7 +112,7 @@ dependencies = [
|
|||
"log",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
|
@ -149,12 +160,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.16"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
|
@ -166,9 +177,9 @@ checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
|
|||
|
||||
[[package]]
|
||||
name = "byte-unit"
|
||||
version = "4.0.12"
|
||||
version = "4.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8"
|
||||
checksum = "956ffc5b0ec7d7a6949e3f21fd63ba5af4cffdc2ba1e0b7bf62b481458c4ae7f"
|
||||
dependencies = [
|
||||
"utf8-width",
|
||||
]
|
||||
|
@ -187,9 +198,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.69"
|
||||
version = "1.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
||||
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
|
@ -227,9 +238,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.2.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c"
|
||||
checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
|
@ -479,7 +490,7 @@ dependencies = [
|
|||
"if_rust_version",
|
||||
"lazy_static",
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
@ -560,9 +571,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.20.0"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d"
|
||||
checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
|
@ -576,30 +587,30 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507"
|
||||
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.20"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
|
||||
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
|
||||
dependencies = [
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.1.9"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a"
|
||||
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
|
||||
dependencies = [
|
||||
"nix 0.20.0",
|
||||
"nix 0.23.0",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
|
@ -642,7 +653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
@ -672,9 +683,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "dns-lookup"
|
||||
version = "1.0.5"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "093d88961fd18c4ecacb8c80cd0b356463ba941ba11e0e01f9cf5271380b79dc"
|
||||
checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
|
@ -792,9 +803,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
|||
|
||||
[[package]]
|
||||
name = "gcd"
|
||||
version = "2.0.1"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c7cd301bf2ab11ae4e5bdfd79c221d97a25e46c089144a62ee9d09cb32d2b92"
|
||||
checksum = "6c8763772808ee8fe3128f0fc424bed6d9942293fddbcfd595ecfa58a81fe00b"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
|
@ -858,9 +869,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.7.1"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
|
@ -920,9 +931,9 @@ checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec"
|
|||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.10"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
@ -975,15 +986,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.101"
|
||||
version = "0.2.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
|
||||
checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"winapi 0.3.9",
|
||||
|
@ -991,9 +1002,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
|
||||
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
@ -1022,12 +1033,6 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
|
||||
[[package]]
|
||||
name = "maybe-uninit"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.3.8"
|
||||
|
@ -1045,9 +1050,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
|
@ -1075,9 +1080,9 @@ checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677"
|
|||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.7"
|
||||
version = "0.7.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
|
||||
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
|
@ -1132,6 +1137,19 @@ dependencies = [
|
|||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodrop"
|
||||
version = "0.1.14"
|
||||
|
@ -1146,7 +1164,7 @@ checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
|
|||
dependencies = [
|
||||
"bitvec",
|
||||
"funty",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -1172,9 +1190,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535"
|
||||
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
|
@ -1228,7 +1246,7 @@ checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9"
|
|||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
@ -1282,6 +1300,15 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_display"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "748cc1d0dc55247316a5bedd8dc8c5478c8a0c2e2001176b38ce7c0ed732c7a5"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros"
|
||||
version = "0.10.1"
|
||||
|
@ -1302,7 +1329,7 @@ dependencies = [
|
|||
"Inflector",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
@ -1317,9 +1344,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
|
@ -1328,15 +1355,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.3"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec 1.6.1",
|
||||
"smallvec",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
|
@ -1367,9 +1394,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
|||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.19"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
|
||||
|
||||
[[package]]
|
||||
name = "platform-info"
|
||||
|
@ -1383,9 +1410,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.10"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
|
@ -1401,9 +1428,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92"
|
||||
checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"toml",
|
||||
|
@ -1417,7 +1444,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
|||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -1429,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -1441,9 +1468,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.28"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
|
||||
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.2",
|
||||
]
|
||||
|
@ -1480,9 +1507,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -1668,7 +1695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
|
@ -1695,9 +1722,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "retain_mut"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b"
|
||||
checksum = "448296241d034b96c11173591deaa1302f2c17b56092106c1f92c1bc0183a8c9"
|
||||
|
||||
[[package]]
|
||||
name = "rlimit"
|
||||
|
@ -1742,9 +1769,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||
|
||||
[[package]]
|
||||
name = "selinux"
|
||||
version = "0.2.3"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608"
|
||||
checksum = "09715d6b4356e916047e61e4dce40a67ac93036851957b91713d3d9c282d1548"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
|
@ -1782,7 +1809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
@ -1819,15 +1846,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.9"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39"
|
||||
checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
|
@ -1855,18 +1882,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "0.6.14"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
|
||||
dependencies = [
|
||||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
|
@ -1876,11 +1894,10 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
|||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.19"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
|
||||
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
@ -1911,18 +1928,18 @@ checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
|
|||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.74"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
|
||||
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"unicode-xid 0.2.2",
|
||||
]
|
||||
|
||||
|
@ -2033,21 +2050,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.26"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.26"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
@ -2072,9 +2089,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.13.0"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
||||
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
|
@ -2093,9 +2110,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
|
@ -2316,7 +2333,7 @@ dependencies = [
|
|||
"atty",
|
||||
"bstr",
|
||||
"clap",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
@ -2441,7 +2458,7 @@ dependencies = [
|
|||
"paste",
|
||||
"quickcheck",
|
||||
"rand 0.7.3",
|
||||
"smallvec 0.6.14",
|
||||
"smallvec",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
@ -2494,7 +2511,7 @@ dependencies = [
|
|||
"hex",
|
||||
"libc",
|
||||
"md5",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"sha1",
|
||||
|
@ -2509,7 +2526,7 @@ name = "uu_head"
|
|||
version = "0.0.8"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
@ -2713,7 +2730,7 @@ dependencies = [
|
|||
"aho-corasick",
|
||||
"clap",
|
||||
"libc",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"uucore",
|
||||
|
@ -2831,7 +2848,7 @@ dependencies = [
|
|||
"aho-corasick",
|
||||
"clap",
|
||||
"libc",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"uucore",
|
||||
|
@ -2914,6 +2931,7 @@ dependencies = [
|
|||
name = "uu_seq"
|
||||
version = "0.0.8"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"clap",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
|
@ -2961,7 +2979,7 @@ dependencies = [
|
|||
"ctrlc",
|
||||
"fnv",
|
||||
"itertools 0.10.1",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"ouroboros",
|
||||
"rand 0.7.3",
|
||||
"rayon",
|
||||
|
@ -3036,7 +3054,7 @@ name = "uu_tac"
|
|||
version = "0.0.8"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"memchr 2.4.0",
|
||||
"memchr 2.4.1",
|
||||
"memmap2",
|
||||
"regex",
|
||||
"uucore",
|
||||
|
@ -3265,6 +3283,7 @@ dependencies = [
|
|||
"libc",
|
||||
"nix 0.20.0",
|
||||
"once_cell",
|
||||
"os_display",
|
||||
"termion",
|
||||
"thiserror",
|
||||
"time",
|
||||
|
@ -3279,7 +3298,7 @@ name = "uucore_procs"
|
|||
version = "0.0.7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"quote 1.0.10",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
@ -3402,6 +3421,6 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "z85"
|
||||
version = "3.0.3"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ac8b56e4f9906a4ef5412875e9ce448364023335cec645fd457ecf51d4f2781"
|
||||
checksum = "af896e93db81340b74b65f74276a99b210c086f3d34ed0abf433182a462af856"
|
||||
|
|
|
@ -131,21 +131,15 @@ fn basename(fullname: &str, suffix: &str) -> String {
|
|||
// Convert to path buffer and get last path component
|
||||
let pb = PathBuf::from(path);
|
||||
match pb.components().last() {
|
||||
Some(c) => strip_suffix(c.as_os_str().to_str().unwrap(), suffix),
|
||||
Some(c) => {
|
||||
let name = c.as_os_str().to_str().unwrap();
|
||||
if name == suffix {
|
||||
name.to_string()
|
||||
} else {
|
||||
name.strip_suffix(suffix).unwrap_or(name).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
None => "".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
// can be replaced with strip_suffix once MSRV is 1.45
|
||||
#[allow(clippy::manual_strip)]
|
||||
fn strip_suffix(name: &str, suffix: &str) -> String {
|
||||
if name == suffix {
|
||||
return name.to_owned();
|
||||
}
|
||||
|
||||
if name.ends_with(suffix) {
|
||||
return name[..name.len() - suffix.len()].to_owned();
|
||||
}
|
||||
|
||||
name.to_owned()
|
||||
}
|
||||
|
|
36
src/uu/env/src/env.rs
vendored
36
src/uu/env/src/env.rs
vendored
|
@ -7,7 +7,7 @@
|
|||
|
||||
/* last synced with: env (GNU coreutils) 8.13 */
|
||||
|
||||
// spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets
|
||||
// spell-checker:ignore (ToDO) chdir execvp progname subcommand subcommands unsets setenv putenv posix_spawnp
|
||||
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
@ -256,7 +256,32 @@ fn run_env(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
// set specified env vars
|
||||
for &(name, val) in &opts.sets {
|
||||
// FIXME: set_var() panics if name is an empty string
|
||||
/*
|
||||
* set_var panics if name is an empty string
|
||||
* set_var internally calls setenv (on unix at least), while GNU env calls putenv instead.
|
||||
*
|
||||
* putenv returns successfully if provided with something like "=a" and modifies the environ
|
||||
* variable to contain "=a" inside it, effectively modifying the process' current environment
|
||||
* to contain a malformed string in it. Using GNU's implementation, the command `env =a`
|
||||
* prints out the malformed string and even invokes the child process with that environment.
|
||||
* This can be seen by using `env -i =a env` or `env -i =a cat /proc/self/environ`
|
||||
*
|
||||
* POSIX.1-2017 doesn't seem to mention what to do if the string is malformed (at least
|
||||
* not in "Chapter 8, Environment Variables" or in the definition for environ and various
|
||||
* exec*'s or in the description of env in the "Shell & Utilities" volume).
|
||||
*
|
||||
* It also doesn't specify any checks for putenv before modifying the environ variable, which
|
||||
* is likely why glibc doesn't do so. However, setenv's first argument cannot point to
|
||||
* an empty string or a string containing '='.
|
||||
*
|
||||
* There is no benefit in replicating GNU's env behavior, since it will only modify the
|
||||
* environment in weird ways
|
||||
*/
|
||||
|
||||
if name.is_empty() {
|
||||
show_warning!("no name specified for value {}", val.quote());
|
||||
continue;
|
||||
}
|
||||
env::set_var(name, val);
|
||||
}
|
||||
|
||||
|
@ -264,7 +289,12 @@ fn run_env(args: impl uucore::Args) -> UResult<()> {
|
|||
// we need to execute a command
|
||||
let (prog, args) = build_command(&mut opts.program);
|
||||
|
||||
// FIXME: this should just use execvp() (no fork()) on Unix-like systems
|
||||
/*
|
||||
* On Unix-like systems Command::status either ends up calling either fork or posix_spawnp
|
||||
* (which ends up calling clone). Keep using the current process would be ideal, but the
|
||||
* standard library contains many checks and fail-safes to ensure the process ends up being
|
||||
* created. This is much simpler than dealing with the hassles of calling execvp directly.
|
||||
*/
|
||||
match Command::new(&*prog).args(args).status() {
|
||||
Ok(exit) if !exit.success() => return Err(exit.code().unwrap().into()),
|
||||
Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127.into()),
|
||||
|
|
|
@ -44,7 +44,8 @@ on Daniel Lemire's [*Microbenchmarking calls for idealized conditions*][lemire],
|
|||
which I recommend reading if you want to add benchmarks to `factor`.
|
||||
|
||||
1. Select a small, self-contained, deterministic component
|
||||
`gcd` and `table::factor` are good example of such:
|
||||
(`gcd` and `table::factor` are good examples):
|
||||
|
||||
- no I/O or access to external data structures ;
|
||||
- no call into other components ;
|
||||
- behavior is deterministic: no RNG, no concurrency, ... ;
|
||||
|
@ -53,16 +54,19 @@ which I recommend reading if you want to add benchmarks to `factor`.
|
|||
maximizing the numbers of samples we can take in a given time.
|
||||
|
||||
2. Benchmarks are immutable (once merged in `uutils`)
|
||||
|
||||
Modifying a benchmark means previously-collected values cannot meaningfully
|
||||
be compared, silently giving nonsensical results. If you must modify an
|
||||
existing benchmark, rename it.
|
||||
|
||||
3. Test common cases
|
||||
|
||||
We are interested in overall performance, rather than specific edge-cases;
|
||||
use **reproducibly-randomized inputs**, sampling from either all possible
|
||||
input values or some subset of interest.
|
||||
|
||||
4. Use [`criterion`], `criterion::black_box`, ...
|
||||
|
||||
`criterion` isn't perfect, but it is also much better than ad-hoc
|
||||
solutions in each benchmark.
|
||||
|
||||
|
|
|
@ -15,13 +15,13 @@ edition = "2018"
|
|||
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
coz = { version = "0.1.3", optional = true }
|
||||
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
|
||||
rand = { version = "0.7", features = ["small_rng"] }
|
||||
smallvec = { version = "0.6.14, < 1.0" }
|
||||
smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later.
|
||||
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
|
||||
uucore_procs = { version=">=0.0.7", package = "uucore_procs", path = "../../uucore_procs" }
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
|
||||
[dev-dependencies]
|
||||
paste = "0.1.18"
|
||||
|
|
|
@ -52,7 +52,7 @@ pub fn gcd(mut u: u64, mut v: u64) -> u64 {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use quickcheck::quickcheck;
|
||||
use quickcheck::{quickcheck, TestResult};
|
||||
|
||||
quickcheck! {
|
||||
fn euclidean(a: u64, b: u64) -> bool {
|
||||
|
@ -76,13 +76,12 @@ mod tests {
|
|||
gcd(0, a) == a
|
||||
}
|
||||
|
||||
fn divisor(a: u64, b: u64) -> () {
|
||||
fn divisor(a: u64, b: u64) -> TestResult {
|
||||
// Test that gcd(a, b) divides a and b, unless a == b == 0
|
||||
if a == 0 && b == 0 { return; }
|
||||
if a == 0 && b == 0 { return TestResult::discard(); } // restrict test domain to !(a == b == 0)
|
||||
|
||||
let g = gcd(a, b);
|
||||
assert_eq!(a % g, 0);
|
||||
assert_eq!(b % g, 0);
|
||||
TestResult::from_bool( g != 0 && a % g == 0 && b % g == 0 )
|
||||
}
|
||||
|
||||
fn commutative(a: u64, b: u64) -> bool {
|
||||
|
|
|
@ -1111,6 +1111,9 @@ only ignore '.' and '..'.",
|
|||
.long(options::COLOR)
|
||||
.help("Color output based on file type.")
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none",
|
||||
])
|
||||
.require_equals(true)
|
||||
.min_values(0),
|
||||
)
|
||||
|
@ -2103,11 +2106,9 @@ fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -
|
|||
}
|
||||
Ok(None) => substitute_string,
|
||||
Ok(Some(context)) => {
|
||||
let mut context = context.as_bytes();
|
||||
if context.ends_with(&[0]) {
|
||||
// TODO: replace with `strip_prefix()` when MSRV >= 1.51
|
||||
context = &context[..context.len() - 1]
|
||||
};
|
||||
let context = context.as_bytes();
|
||||
|
||||
let context = context.strip_suffix(&[0]).unwrap_or(context);
|
||||
String::from_utf8(context.to_vec()).unwrap_or_else(|e| {
|
||||
show_warning!(
|
||||
"getting security context of: {}: {}",
|
||||
|
|
19
src/uu/seq/BENCHMARKING.md
Normal file
19
src/uu/seq/BENCHMARKING.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Benchmarking to measure performance
|
||||
|
||||
To compare the performance of the `uutils` version of `seq` with the
|
||||
GNU version of `seq`, you can use a benchmarking tool like
|
||||
[hyperfine][0]. On Ubuntu 18.04 or later, you can install `hyperfine` by
|
||||
running
|
||||
|
||||
sudo apt-get install hyperfine
|
||||
|
||||
Next, build the `seq` binary under the release profile:
|
||||
|
||||
cargo build --release -p uu_seq
|
||||
|
||||
Finally, you can compare the performance of the two versions of `head`
|
||||
by running, for example,
|
||||
|
||||
hyperfine "seq 1000000" "target/release/seq 1000000"
|
||||
|
||||
[0]: https://github.com/sharkdp/hyperfine
|
|
@ -1,3 +1,4 @@
|
|||
# spell-checker:ignore bigdecimal
|
||||
[package]
|
||||
name = "uu_seq"
|
||||
version = "0.0.8"
|
||||
|
@ -15,6 +16,7 @@ edition = "2018"
|
|||
path = "src/seq.rs"
|
||||
|
||||
[dependencies]
|
||||
bigdecimal = "0.3"
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
num-bigint = "0.4.0"
|
||||
num-traits = "0.2.14"
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
//! Counting number of digits needed to represent a number.
|
||||
//!
|
||||
//! The [`num_integral_digits`] and [`num_fractional_digits`] functions
|
||||
//! count the number of digits needed to represent a number in decimal
|
||||
//! notation (like "123.456").
|
||||
use std::convert::TryInto;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
|
||||
/// The number of digits after the decimal point in a given number.
|
||||
///
|
||||
/// The input `s` is a string representing a number, either an integer
|
||||
/// or a floating point number in either decimal notation or scientific
|
||||
/// notation. This function returns the number of digits after the
|
||||
/// decimal point needed to print the number in decimal notation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3);
|
||||
/// ```
|
||||
pub fn num_fractional_digits(s: &str) -> Result<usize, ParseIntError> {
|
||||
match (s.find('.'), s.find('e')) {
|
||||
// For example, "123456".
|
||||
(None, None) => Ok(0),
|
||||
|
||||
// For example, "123e456".
|
||||
(None, Some(j)) => {
|
||||
let exponent: i64 = s[j + 1..].parse()?;
|
||||
if exponent < 0 {
|
||||
Ok(-exponent as usize)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
// For example, "123.456".
|
||||
(Some(i), None) => Ok(s.len() - (i + 1)),
|
||||
|
||||
// For example, "123.456e789".
|
||||
(Some(i), Some(j)) if i < j => {
|
||||
// Because of the match guard, this subtraction will not underflow.
|
||||
let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64;
|
||||
let exponent: i64 = s[j + 1..].parse()?;
|
||||
if num_digits_between_decimal_point_and_e < exponent {
|
||||
Ok(0)
|
||||
} else {
|
||||
Ok((num_digits_between_decimal_point_and_e - exponent)
|
||||
.try_into()
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
_ => crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
s.quote(),
|
||||
uucore::execution_phrase()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of digits before the decimal point in a given number.
|
||||
///
|
||||
/// The input `s` is a string representing a number, either an integer
|
||||
/// or a floating point number in either decimal notation or scientific
|
||||
/// notation. This function returns the number of digits before the
|
||||
/// decimal point needed to print the number in decimal notation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 2);
|
||||
/// ```
|
||||
pub fn num_integral_digits(s: &str) -> Result<usize, ParseIntError> {
|
||||
match (s.find('.'), s.find('e')) {
|
||||
// For example, "123456".
|
||||
(None, None) => Ok(s.len()),
|
||||
|
||||
// For example, "123e456".
|
||||
(None, Some(j)) => {
|
||||
let exponent: i64 = s[j + 1..].parse()?;
|
||||
let total = j as i64 + exponent;
|
||||
if total < 1 {
|
||||
Ok(1)
|
||||
} else {
|
||||
Ok(total.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
// For example, "123.456".
|
||||
(Some(i), None) => Ok(i),
|
||||
|
||||
// For example, "123.456e789".
|
||||
(Some(i), Some(j)) => {
|
||||
let exponent: i64 = s[j + 1..].parse()?;
|
||||
let minimum: usize = {
|
||||
let integral_part: f64 = crash_if_err!(1, s[..j].parse());
|
||||
if integral_part == -0.0 && integral_part.is_sign_negative() {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
let total = i as i64 + exponent;
|
||||
if total < minimum as i64 {
|
||||
Ok(minimum)
|
||||
} else {
|
||||
Ok(total.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
mod test_num_integral_digits {
|
||||
use crate::num_integral_digits;
|
||||
|
||||
#[test]
|
||||
fn test_integer() {
|
||||
assert_eq!(num_integral_digits("123").unwrap(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal() {
|
||||
assert_eq!(num_integral_digits("123.45").unwrap(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_no_decimal_positive_exponent() {
|
||||
assert_eq!(num_integral_digits("123e4").unwrap(), 3 + 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_with_decimal_positive_exponent() {
|
||||
assert_eq!(num_integral_digits("123.45e6").unwrap(), 3 + 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_no_decimal_negative_exponent() {
|
||||
assert_eq!(num_integral_digits("123e-4").unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_with_decimal_negative_exponent() {
|
||||
assert_eq!(num_integral_digits("123.45e-6").unwrap(), 1);
|
||||
assert_eq!(num_integral_digits("123.45e-1").unwrap(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
mod test_num_fractional_digits {
|
||||
use crate::num_fractional_digits;
|
||||
|
||||
#[test]
|
||||
fn test_integer() {
|
||||
assert_eq!(num_fractional_digits("123").unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal() {
|
||||
assert_eq!(num_fractional_digits("123.45").unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_no_decimal_positive_exponent() {
|
||||
assert_eq!(num_fractional_digits("123e4").unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_with_decimal_positive_exponent() {
|
||||
assert_eq!(num_fractional_digits("123.45e6").unwrap(), 0);
|
||||
assert_eq!(num_fractional_digits("123.45e1").unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_no_decimal_negative_exponent() {
|
||||
assert_eq!(num_fractional_digits("123e-4").unwrap(), 4);
|
||||
assert_eq!(num_fractional_digits("123e-1").unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scientific_with_decimal_negative_exponent() {
|
||||
assert_eq!(num_fractional_digits("123.45e-6").unwrap(), 8);
|
||||
assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3);
|
||||
}
|
||||
}
|
||||
}
|
290
src/uu/seq/src/extendedbigdecimal.rs
Normal file
290
src/uu/seq/src/extendedbigdecimal.rs
Normal file
|
@ -0,0 +1,290 @@
|
|||
// spell-checker:ignore bigdecimal extendedbigdecimal extendedbigint
|
||||
//! An arbitrary precision float that can also represent infinity, NaN, etc.
|
||||
//!
|
||||
//! The finite values are stored as [`BigDecimal`] instances. Because
|
||||
//! the `bigdecimal` library does not represent infinity, NaN, etc., we
|
||||
//! need to represent them explicitly ourselves. The
|
||||
//! [`ExtendedBigDecimal`] enumeration does that.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Addition works for [`ExtendedBigDecimal`] as it does for floats. For
|
||||
//! example, adding infinity to any finite value results in infinity:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero());
|
||||
//! let summand2 = ExtendedBigDecimal::Infinity;
|
||||
//! assert_eq!(summand1 + summand2, ExtendedBigDecimal::Infinity);
|
||||
//! ```
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use std::ops::Add;
|
||||
|
||||
use bigdecimal::BigDecimal;
|
||||
use num_bigint::BigInt;
|
||||
use num_bigint::ToBigInt;
|
||||
use num_traits::One;
|
||||
use num_traits::Zero;
|
||||
|
||||
use crate::extendedbigint::ExtendedBigInt;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ExtendedBigDecimal {
|
||||
/// Arbitrary precision floating point number.
|
||||
BigDecimal(BigDecimal),
|
||||
|
||||
/// Floating point positive infinity.
|
||||
///
|
||||
/// This is represented as its own enumeration member instead of as
|
||||
/// a [`BigDecimal`] because the `bigdecimal` library does not
|
||||
/// support infinity, see [here][0].
|
||||
///
|
||||
/// [0]: https://github.com/akubera/bigdecimal-rs/issues/67
|
||||
Infinity,
|
||||
|
||||
/// Floating point negative infinity.
|
||||
///
|
||||
/// This is represented as its own enumeration member instead of as
|
||||
/// a [`BigDecimal`] because the `bigdecimal` library does not
|
||||
/// support infinity, see [here][0].
|
||||
///
|
||||
/// [0]: https://github.com/akubera/bigdecimal-rs/issues/67
|
||||
MinusInfinity,
|
||||
|
||||
/// Floating point negative zero.
|
||||
///
|
||||
/// This is represented as its own enumeration member instead of as
|
||||
/// a [`BigDecimal`] because the `bigdecimal` library does not
|
||||
/// support negative zero.
|
||||
MinusZero,
|
||||
|
||||
/// Floating point NaN.
|
||||
///
|
||||
/// This is represented as its own enumeration member instead of as
|
||||
/// a [`BigDecimal`] because the `bigdecimal` library does not
|
||||
/// support NaN, see [here][0].
|
||||
///
|
||||
/// [0]: https://github.com/akubera/bigdecimal-rs/issues/67
|
||||
Nan,
|
||||
}
|
||||
|
||||
/// The smallest integer greater than or equal to this number.
|
||||
fn ceil(x: BigDecimal) -> BigInt {
|
||||
if x.is_integer() {
|
||||
// Unwrapping the Option because it always returns Some
|
||||
x.to_bigint().unwrap()
|
||||
} else {
|
||||
(x + BigDecimal::one().half()).round(0).to_bigint().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// The largest integer less than or equal to this number.
|
||||
fn floor(x: BigDecimal) -> BigInt {
|
||||
if x.is_integer() {
|
||||
// Unwrapping the Option because it always returns Some
|
||||
x.to_bigint().unwrap()
|
||||
} else {
|
||||
(x - BigDecimal::one().half()).round(0).to_bigint().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendedBigDecimal {
|
||||
/// The smallest integer greater than or equal to this number.
|
||||
pub fn ceil(self) -> ExtendedBigInt {
|
||||
match self {
|
||||
ExtendedBigDecimal::BigDecimal(x) => ExtendedBigInt::BigInt(ceil(x)),
|
||||
other => From::from(other),
|
||||
}
|
||||
}
|
||||
|
||||
/// The largest integer less than or equal to this number.
|
||||
pub fn floor(self) -> ExtendedBigInt {
|
||||
match self {
|
||||
ExtendedBigDecimal::BigDecimal(x) => ExtendedBigInt::BigInt(floor(x)),
|
||||
other => From::from(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtendedBigInt> for ExtendedBigDecimal {
|
||||
fn from(big_int: ExtendedBigInt) -> Self {
|
||||
match big_int {
|
||||
ExtendedBigInt::BigInt(n) => Self::BigDecimal(BigDecimal::from(n)),
|
||||
ExtendedBigInt::Infinity => ExtendedBigDecimal::Infinity,
|
||||
ExtendedBigInt::MinusInfinity => ExtendedBigDecimal::MinusInfinity,
|
||||
ExtendedBigInt::MinusZero => ExtendedBigDecimal::MinusZero,
|
||||
ExtendedBigInt::Nan => ExtendedBigDecimal::Nan,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ExtendedBigDecimal {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExtendedBigDecimal::BigDecimal(x) => {
|
||||
let (n, p) = x.as_bigint_and_exponent();
|
||||
match p {
|
||||
0 => ExtendedBigDecimal::BigDecimal(BigDecimal::new(n * 10, 1)).fmt(f),
|
||||
_ => x.fmt(f),
|
||||
}
|
||||
}
|
||||
ExtendedBigDecimal::Infinity => f32::INFINITY.fmt(f),
|
||||
ExtendedBigDecimal::MinusInfinity => f32::NEG_INFINITY.fmt(f),
|
||||
ExtendedBigDecimal::MinusZero => {
|
||||
// FIXME In Rust version 1.53.0 and later, the display
|
||||
// of floats was updated to allow displaying negative
|
||||
// zero. See
|
||||
// https://github.com/rust-lang/rust/pull/78618. Currently,
|
||||
// this just formats "0.0".
|
||||
(0.0f32).fmt(f)
|
||||
}
|
||||
ExtendedBigDecimal::Nan => "nan".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Zero for ExtendedBigDecimal {
|
||||
fn zero() -> Self {
|
||||
ExtendedBigDecimal::BigDecimal(BigDecimal::zero())
|
||||
}
|
||||
fn is_zero(&self) -> bool {
|
||||
match self {
|
||||
Self::BigDecimal(n) => n.is_zero(),
|
||||
Self::MinusZero => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for ExtendedBigDecimal {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(Self::BigDecimal(m), Self::BigDecimal(n)) => Self::BigDecimal(m.add(n)),
|
||||
(Self::BigDecimal(_), Self::MinusInfinity) => Self::MinusInfinity,
|
||||
(Self::BigDecimal(_), Self::Infinity) => Self::Infinity,
|
||||
(Self::BigDecimal(_), Self::Nan) => Self::Nan,
|
||||
(Self::BigDecimal(m), Self::MinusZero) => Self::BigDecimal(m),
|
||||
(Self::Infinity, Self::BigDecimal(_)) => Self::Infinity,
|
||||
(Self::Infinity, Self::Infinity) => Self::Infinity,
|
||||
(Self::Infinity, Self::MinusZero) => Self::Infinity,
|
||||
(Self::Infinity, Self::MinusInfinity) => Self::Nan,
|
||||
(Self::Infinity, Self::Nan) => Self::Nan,
|
||||
(Self::MinusInfinity, Self::BigDecimal(_)) => Self::MinusInfinity,
|
||||
(Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity,
|
||||
(Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity,
|
||||
(Self::MinusInfinity, Self::Infinity) => Self::Nan,
|
||||
(Self::MinusInfinity, Self::Nan) => Self::Nan,
|
||||
(Self::Nan, _) => Self::Nan,
|
||||
(Self::MinusZero, other) => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ExtendedBigDecimal {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::BigDecimal(m), Self::BigDecimal(n)) => m.eq(n),
|
||||
(Self::BigDecimal(_), Self::MinusInfinity) => false,
|
||||
(Self::BigDecimal(_), Self::Infinity) => false,
|
||||
(Self::BigDecimal(_), Self::Nan) => false,
|
||||
(Self::BigDecimal(_), Self::MinusZero) => false,
|
||||
(Self::Infinity, Self::BigDecimal(_)) => false,
|
||||
(Self::Infinity, Self::Infinity) => true,
|
||||
(Self::Infinity, Self::MinusZero) => false,
|
||||
(Self::Infinity, Self::MinusInfinity) => false,
|
||||
(Self::Infinity, Self::Nan) => false,
|
||||
(Self::MinusInfinity, Self::BigDecimal(_)) => false,
|
||||
(Self::MinusInfinity, Self::Infinity) => false,
|
||||
(Self::MinusInfinity, Self::MinusZero) => false,
|
||||
(Self::MinusInfinity, Self::MinusInfinity) => true,
|
||||
(Self::MinusInfinity, Self::Nan) => false,
|
||||
(Self::Nan, _) => false,
|
||||
(Self::MinusZero, Self::BigDecimal(_)) => false,
|
||||
(Self::MinusZero, Self::Infinity) => false,
|
||||
(Self::MinusZero, Self::MinusZero) => true,
|
||||
(Self::MinusZero, Self::MinusInfinity) => false,
|
||||
(Self::MinusZero, Self::Nan) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ExtendedBigDecimal {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Self::BigDecimal(m), Self::BigDecimal(n)) => m.partial_cmp(n),
|
||||
(Self::BigDecimal(_), Self::MinusInfinity) => Some(Ordering::Greater),
|
||||
(Self::BigDecimal(_), Self::Infinity) => Some(Ordering::Less),
|
||||
(Self::BigDecimal(_), Self::Nan) => None,
|
||||
(Self::BigDecimal(m), Self::MinusZero) => m.partial_cmp(&BigDecimal::zero()),
|
||||
(Self::Infinity, Self::BigDecimal(_)) => Some(Ordering::Greater),
|
||||
(Self::Infinity, Self::Infinity) => Some(Ordering::Equal),
|
||||
(Self::Infinity, Self::MinusZero) => Some(Ordering::Greater),
|
||||
(Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater),
|
||||
(Self::Infinity, Self::Nan) => None,
|
||||
(Self::MinusInfinity, Self::BigDecimal(_)) => Some(Ordering::Less),
|
||||
(Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less),
|
||||
(Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less),
|
||||
(Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal),
|
||||
(Self::MinusInfinity, Self::Nan) => None,
|
||||
(Self::Nan, _) => None,
|
||||
(Self::MinusZero, Self::BigDecimal(n)) => BigDecimal::zero().partial_cmp(n),
|
||||
(Self::MinusZero, Self::Infinity) => Some(Ordering::Less),
|
||||
(Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal),
|
||||
(Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater),
|
||||
(Self::MinusZero, Self::Nan) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use bigdecimal::BigDecimal;
|
||||
use num_traits::Zero;
|
||||
|
||||
use crate::extendedbigdecimal::ExtendedBigDecimal;
|
||||
|
||||
#[test]
|
||||
fn test_addition_infinity() {
|
||||
let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero());
|
||||
let summand2 = ExtendedBigDecimal::Infinity;
|
||||
assert_eq!(summand1 + summand2, ExtendedBigDecimal::Infinity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addition_minus_infinity() {
|
||||
let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero());
|
||||
let summand2 = ExtendedBigDecimal::MinusInfinity;
|
||||
assert_eq!(summand1 + summand2, ExtendedBigDecimal::MinusInfinity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addition_nan() {
|
||||
let summand1 = ExtendedBigDecimal::BigDecimal(BigDecimal::zero());
|
||||
let summand2 = ExtendedBigDecimal::Nan;
|
||||
let sum = summand1 + summand2;
|
||||
match sum {
|
||||
ExtendedBigDecimal::Nan => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(
|
||||
format!("{}", ExtendedBigDecimal::BigDecimal(BigDecimal::zero())),
|
||||
"0.0"
|
||||
);
|
||||
assert_eq!(format!("{}", ExtendedBigDecimal::Infinity), "inf");
|
||||
assert_eq!(format!("{}", ExtendedBigDecimal::MinusInfinity), "-inf");
|
||||
assert_eq!(format!("{}", ExtendedBigDecimal::Nan), "nan");
|
||||
// FIXME In Rust version 1.53.0 and later, the display of floats
|
||||
// was updated to allow displaying negative zero. Until then, we
|
||||
// just display `MinusZero` as "0.0".
|
||||
//
|
||||
// assert_eq!(format!("{}", ExtendedBigDecimal::MinusZero), "-0.0");
|
||||
//
|
||||
}
|
||||
}
|
218
src/uu/seq/src/extendedbigint.rs
Normal file
218
src/uu/seq/src/extendedbigint.rs
Normal file
|
@ -0,0 +1,218 @@
|
|||
// spell-checker:ignore bigint extendedbigint extendedbigdecimal
|
||||
//! An arbitrary precision integer that can also represent infinity, NaN, etc.
|
||||
//!
|
||||
//! Usually infinity, NaN, and negative zero are only represented for
|
||||
//! floating point numbers. The [`ExtendedBigInt`] enumeration provides
|
||||
//! a representation of those things with the set of integers. The
|
||||
//! finite values are stored as [`BigInt`] instances.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Addition works for [`ExtendedBigInt`] as it does for floats. For
|
||||
//! example, adding infinity to any finite value results in infinity:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
|
||||
//! let summand2 = ExtendedBigInt::Infinity;
|
||||
//! assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity);
|
||||
//! ```
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use std::ops::Add;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use num_bigint::ToBigInt;
|
||||
use num_traits::One;
|
||||
use num_traits::Zero;
|
||||
|
||||
use crate::extendedbigdecimal::ExtendedBigDecimal;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ExtendedBigInt {
|
||||
BigInt(BigInt),
|
||||
Infinity,
|
||||
MinusInfinity,
|
||||
MinusZero,
|
||||
Nan,
|
||||
}
|
||||
|
||||
impl ExtendedBigInt {
|
||||
/// The integer number one.
|
||||
pub fn one() -> Self {
|
||||
// We would like to implement `num_traits::One`, but it requires
|
||||
// a multiplication implementation, and we don't want to
|
||||
// implement that here.
|
||||
ExtendedBigInt::BigInt(BigInt::one())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtendedBigDecimal> for ExtendedBigInt {
|
||||
fn from(big_decimal: ExtendedBigDecimal) -> Self {
|
||||
match big_decimal {
|
||||
// TODO When can this fail?
|
||||
ExtendedBigDecimal::BigDecimal(x) => Self::BigInt(x.to_bigint().unwrap()),
|
||||
ExtendedBigDecimal::Infinity => ExtendedBigInt::Infinity,
|
||||
ExtendedBigDecimal::MinusInfinity => ExtendedBigInt::MinusInfinity,
|
||||
ExtendedBigDecimal::MinusZero => ExtendedBigInt::MinusZero,
|
||||
ExtendedBigDecimal::Nan => ExtendedBigInt::Nan,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ExtendedBigInt {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ExtendedBigInt::BigInt(n) => n.fmt(f),
|
||||
ExtendedBigInt::Infinity => f32::INFINITY.fmt(f),
|
||||
ExtendedBigInt::MinusInfinity => f32::NEG_INFINITY.fmt(f),
|
||||
ExtendedBigInt::MinusZero => {
|
||||
// FIXME Come up with a way of formatting this with a
|
||||
// "-" prefix.
|
||||
0.fmt(f)
|
||||
}
|
||||
ExtendedBigInt::Nan => "nan".fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Zero for ExtendedBigInt {
|
||||
fn zero() -> Self {
|
||||
ExtendedBigInt::BigInt(BigInt::zero())
|
||||
}
|
||||
fn is_zero(&self) -> bool {
|
||||
match self {
|
||||
Self::BigInt(n) => n.is_zero(),
|
||||
Self::MinusZero => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for ExtendedBigInt {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
(Self::BigInt(m), Self::BigInt(n)) => Self::BigInt(m.add(n)),
|
||||
(Self::BigInt(_), Self::MinusInfinity) => Self::MinusInfinity,
|
||||
(Self::BigInt(_), Self::Infinity) => Self::Infinity,
|
||||
(Self::BigInt(_), Self::Nan) => Self::Nan,
|
||||
(Self::BigInt(m), Self::MinusZero) => Self::BigInt(m),
|
||||
(Self::Infinity, Self::BigInt(_)) => Self::Infinity,
|
||||
(Self::Infinity, Self::Infinity) => Self::Infinity,
|
||||
(Self::Infinity, Self::MinusZero) => Self::Infinity,
|
||||
(Self::Infinity, Self::MinusInfinity) => Self::Nan,
|
||||
(Self::Infinity, Self::Nan) => Self::Nan,
|
||||
(Self::MinusInfinity, Self::BigInt(_)) => Self::MinusInfinity,
|
||||
(Self::MinusInfinity, Self::MinusInfinity) => Self::MinusInfinity,
|
||||
(Self::MinusInfinity, Self::MinusZero) => Self::MinusInfinity,
|
||||
(Self::MinusInfinity, Self::Infinity) => Self::Nan,
|
||||
(Self::MinusInfinity, Self::Nan) => Self::Nan,
|
||||
(Self::Nan, _) => Self::Nan,
|
||||
(Self::MinusZero, other) => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ExtendedBigInt {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::BigInt(m), Self::BigInt(n)) => m.eq(n),
|
||||
(Self::BigInt(_), Self::MinusInfinity) => false,
|
||||
(Self::BigInt(_), Self::Infinity) => false,
|
||||
(Self::BigInt(_), Self::Nan) => false,
|
||||
(Self::BigInt(_), Self::MinusZero) => false,
|
||||
(Self::Infinity, Self::BigInt(_)) => false,
|
||||
(Self::Infinity, Self::Infinity) => true,
|
||||
(Self::Infinity, Self::MinusZero) => false,
|
||||
(Self::Infinity, Self::MinusInfinity) => false,
|
||||
(Self::Infinity, Self::Nan) => false,
|
||||
(Self::MinusInfinity, Self::BigInt(_)) => false,
|
||||
(Self::MinusInfinity, Self::Infinity) => false,
|
||||
(Self::MinusInfinity, Self::MinusZero) => false,
|
||||
(Self::MinusInfinity, Self::MinusInfinity) => true,
|
||||
(Self::MinusInfinity, Self::Nan) => false,
|
||||
(Self::Nan, _) => false,
|
||||
(Self::MinusZero, Self::BigInt(_)) => false,
|
||||
(Self::MinusZero, Self::Infinity) => false,
|
||||
(Self::MinusZero, Self::MinusZero) => true,
|
||||
(Self::MinusZero, Self::MinusInfinity) => false,
|
||||
(Self::MinusZero, Self::Nan) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ExtendedBigInt {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Self::BigInt(m), Self::BigInt(n)) => m.partial_cmp(n),
|
||||
(Self::BigInt(_), Self::MinusInfinity) => Some(Ordering::Greater),
|
||||
(Self::BigInt(_), Self::Infinity) => Some(Ordering::Less),
|
||||
(Self::BigInt(_), Self::Nan) => None,
|
||||
(Self::BigInt(m), Self::MinusZero) => m.partial_cmp(&BigInt::zero()),
|
||||
(Self::Infinity, Self::BigInt(_)) => Some(Ordering::Greater),
|
||||
(Self::Infinity, Self::Infinity) => Some(Ordering::Equal),
|
||||
(Self::Infinity, Self::MinusZero) => Some(Ordering::Greater),
|
||||
(Self::Infinity, Self::MinusInfinity) => Some(Ordering::Greater),
|
||||
(Self::Infinity, Self::Nan) => None,
|
||||
(Self::MinusInfinity, Self::BigInt(_)) => Some(Ordering::Less),
|
||||
(Self::MinusInfinity, Self::Infinity) => Some(Ordering::Less),
|
||||
(Self::MinusInfinity, Self::MinusZero) => Some(Ordering::Less),
|
||||
(Self::MinusInfinity, Self::MinusInfinity) => Some(Ordering::Equal),
|
||||
(Self::MinusInfinity, Self::Nan) => None,
|
||||
(Self::Nan, _) => None,
|
||||
(Self::MinusZero, Self::BigInt(n)) => BigInt::zero().partial_cmp(n),
|
||||
(Self::MinusZero, Self::Infinity) => Some(Ordering::Less),
|
||||
(Self::MinusZero, Self::MinusZero) => Some(Ordering::Equal),
|
||||
(Self::MinusZero, Self::MinusInfinity) => Some(Ordering::Greater),
|
||||
(Self::MinusZero, Self::Nan) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::Zero;
|
||||
|
||||
use crate::extendedbigint::ExtendedBigInt;
|
||||
|
||||
#[test]
|
||||
fn test_addition_infinity() {
|
||||
let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
|
||||
let summand2 = ExtendedBigInt::Infinity;
|
||||
assert_eq!(summand1 + summand2, ExtendedBigInt::Infinity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addition_minus_infinity() {
|
||||
let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
|
||||
let summand2 = ExtendedBigInt::MinusInfinity;
|
||||
assert_eq!(summand1 + summand2, ExtendedBigInt::MinusInfinity);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addition_nan() {
|
||||
let summand1 = ExtendedBigInt::BigInt(BigInt::zero());
|
||||
let summand2 = ExtendedBigInt::Nan;
|
||||
let sum = summand1 + summand2;
|
||||
match sum {
|
||||
ExtendedBigInt::Nan => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
assert_eq!(format!("{}", ExtendedBigInt::BigInt(BigInt::zero())), "0");
|
||||
assert_eq!(format!("{}", ExtendedBigInt::Infinity), "inf");
|
||||
assert_eq!(format!("{}", ExtendedBigInt::MinusInfinity), "-inf");
|
||||
assert_eq!(format!("{}", ExtendedBigInt::Nan), "nan");
|
||||
// FIXME Come up with a way of displaying negative zero as
|
||||
// "-0". Currently it displays as just "0".
|
||||
//
|
||||
// assert_eq!(format!("{}", ExtendedBigInt::MinusZero), "-0");
|
||||
//
|
||||
}
|
||||
}
|
119
src/uu/seq/src/number.rs
Normal file
119
src/uu/seq/src/number.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
// spell-checker:ignore extendedbigdecimal extendedbigint
|
||||
//! A type to represent the possible start, increment, and end values for seq.
|
||||
//!
|
||||
//! The [`Number`] enumeration represents the possible values for the
|
||||
//! start, increment, and end values for `seq`. These may be integers,
|
||||
//! floating point numbers, negative zero, etc. A [`Number`] can be
|
||||
//! parsed from a string by calling [`parse`].
|
||||
use num_traits::Zero;
|
||||
|
||||
use crate::extendedbigdecimal::ExtendedBigDecimal;
|
||||
use crate::extendedbigint::ExtendedBigInt;
|
||||
|
||||
/// An integral or floating point number.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Number {
|
||||
Int(ExtendedBigInt),
|
||||
Float(ExtendedBigDecimal),
|
||||
}
|
||||
|
||||
impl Number {
|
||||
/// Decide whether this number is zero (either positive or negative).
|
||||
pub fn is_zero(&self) -> bool {
|
||||
// We would like to implement `num_traits::Zero`, but it
|
||||
// requires an addition implementation, and we don't want to
|
||||
// implement that here.
|
||||
match self {
|
||||
Number::Int(n) => n.is_zero(),
|
||||
Number::Float(x) => x.is_zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this number into an `ExtendedBigDecimal`.
|
||||
pub fn into_extended_big_decimal(self) -> ExtendedBigDecimal {
|
||||
match self {
|
||||
Number::Int(n) => ExtendedBigDecimal::from(n),
|
||||
Number::Float(x) => x,
|
||||
}
|
||||
}
|
||||
|
||||
/// The integer number one.
|
||||
pub fn one() -> Self {
|
||||
// We would like to implement `num_traits::One`, but it requires
|
||||
// a multiplication implementation, and we don't want to
|
||||
// implement that here.
|
||||
Number::Int(ExtendedBigInt::one())
|
||||
}
|
||||
|
||||
/// Round this number towards the given other number.
|
||||
///
|
||||
/// If `other` is greater, then round up. If `other` is smaller,
|
||||
/// then round down.
|
||||
pub fn round_towards(self, other: &ExtendedBigInt) -> ExtendedBigInt {
|
||||
match self {
|
||||
// If this number is already an integer, it is already
|
||||
// rounded to the nearest integer in the direction of
|
||||
// `other`.
|
||||
Number::Int(num) => num,
|
||||
// Otherwise, if this number is a float, we need to decide
|
||||
// whether `other` is larger or smaller than it, and thus
|
||||
// whether to round up or round down, respectively.
|
||||
Number::Float(num) => {
|
||||
let other: ExtendedBigDecimal = From::from(other.clone());
|
||||
if other > num {
|
||||
num.ceil()
|
||||
} else {
|
||||
// If they are equal, then `self` is already an
|
||||
// integer, so calling `floor()` does no harm and
|
||||
// will just return that integer anyway.
|
||||
num.floor()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A number with a specified number of integer and fractional digits.
|
||||
///
|
||||
/// This struct can be used to represent a number along with information
|
||||
/// on how many significant digits to use when displaying the number.
|
||||
/// The [`num_integral_digits`] field also includes the width needed to
|
||||
/// display the "-" character for a negative number.
|
||||
///
|
||||
/// You can get an instance of this struct by calling [`str::parse`].
|
||||
#[derive(Debug)]
|
||||
pub struct PreciseNumber {
|
||||
pub number: Number,
|
||||
pub num_integral_digits: usize,
|
||||
pub num_fractional_digits: usize,
|
||||
}
|
||||
|
||||
impl PreciseNumber {
|
||||
pub fn new(
|
||||
number: Number,
|
||||
num_integral_digits: usize,
|
||||
num_fractional_digits: usize,
|
||||
) -> PreciseNumber {
|
||||
PreciseNumber {
|
||||
number,
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
}
|
||||
}
|
||||
|
||||
/// The integer number one.
|
||||
pub fn one() -> Self {
|
||||
// We would like to implement `num_traits::One`, but it requires
|
||||
// a multiplication implementation, and we don't want to
|
||||
// implement that here.
|
||||
PreciseNumber::new(Number::one(), 1, 0)
|
||||
}
|
||||
|
||||
/// Decide whether this number is zero (either positive or negative).
|
||||
pub fn is_zero(&self) -> bool {
|
||||
// We would like to implement `num_traits::Zero`, but it
|
||||
// requires an addition implementation, and we don't want to
|
||||
// implement that here.
|
||||
self.number.is_zero()
|
||||
}
|
||||
}
|
589
src/uu/seq/src/numberparse.rs
Normal file
589
src/uu/seq/src/numberparse.rs
Normal file
|
@ -0,0 +1,589 @@
|
|||
// spell-checker:ignore extendedbigdecimal extendedbigint bigdecimal numberparse
|
||||
//! Parsing numbers for use in `seq`.
|
||||
//!
|
||||
//! This module provides an implementation of [`FromStr`] for the
|
||||
//! [`PreciseNumber`] struct.
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bigdecimal::BigDecimal;
|
||||
use num_bigint::BigInt;
|
||||
use num_bigint::Sign;
|
||||
use num_traits::Num;
|
||||
use num_traits::Zero;
|
||||
|
||||
use crate::extendedbigdecimal::ExtendedBigDecimal;
|
||||
use crate::extendedbigint::ExtendedBigInt;
|
||||
use crate::number::Number;
|
||||
use crate::number::PreciseNumber;
|
||||
|
||||
/// An error returned when parsing a number fails.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ParseNumberError {
|
||||
Float,
|
||||
Nan,
|
||||
Hex,
|
||||
}
|
||||
|
||||
/// Decide whether a given string and its parsed `BigInt` is negative zero.
|
||||
fn is_minus_zero_int(s: &str, n: &BigInt) -> bool {
|
||||
s.starts_with('-') && n == &BigInt::zero()
|
||||
}
|
||||
|
||||
/// Decide whether a given string and its parsed `BigDecimal` is negative zero.
|
||||
fn is_minus_zero_float(s: &str, x: &BigDecimal) -> bool {
|
||||
s.starts_with('-') && x == &BigDecimal::zero()
|
||||
}
|
||||
|
||||
/// Parse a number with neither a decimal point nor an exponent.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function returns an error if the input string is a variant of
|
||||
/// "NaN" or if no [`BigInt`] could be parsed from the string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let actual = "0".parse::<Number>().unwrap().number;
|
||||
/// let expected = Number::BigInt(BigInt::zero());
|
||||
/// assert_eq!(actual, expected);
|
||||
/// ```
|
||||
fn parse_no_decimal_no_exponent(s: &str) -> Result<PreciseNumber, ParseNumberError> {
|
||||
match s.parse::<BigInt>() {
|
||||
Ok(n) => {
|
||||
// If `s` is '-0', then `parse()` returns `BigInt::zero()`,
|
||||
// but we need to return `Number::MinusZeroInt` instead.
|
||||
if is_minus_zero_int(s, &n) {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::MinusZero),
|
||||
s.len(),
|
||||
0,
|
||||
))
|
||||
} else {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::BigInt(n)),
|
||||
s.len(),
|
||||
0,
|
||||
))
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Possibly "NaN" or "inf".
|
||||
//
|
||||
// TODO In Rust v1.53.0, this change
|
||||
// https://github.com/rust-lang/rust/pull/78618 improves the
|
||||
// parsing of floats to include being able to parse "NaN"
|
||||
// and "inf". So when the minimum version of this crate is
|
||||
// increased to 1.53.0, we should just use the built-in
|
||||
// `f32` parsing instead.
|
||||
if s.eq_ignore_ascii_case("inf") {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::Infinity),
|
||||
0,
|
||||
0,
|
||||
))
|
||||
} else if s.eq_ignore_ascii_case("-inf") {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::MinusInfinity),
|
||||
0,
|
||||
0,
|
||||
))
|
||||
} else if s.eq_ignore_ascii_case("nan") || s.eq_ignore_ascii_case("-nan") {
|
||||
Err(ParseNumberError::Nan)
|
||||
} else {
|
||||
Err(ParseNumberError::Float)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a number with an exponent but no decimal point.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function returns an error if `s` is not a valid number.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let actual = "1e2".parse::<Number>().unwrap().number;
|
||||
/// let expected = "100".parse::<BigInt>().unwrap();
|
||||
/// assert_eq!(actual, expected);
|
||||
/// ```
|
||||
fn parse_exponent_no_decimal(s: &str, j: usize) -> Result<PreciseNumber, ParseNumberError> {
|
||||
let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?;
|
||||
// If the exponent is strictly less than zero, then the number
|
||||
// should be treated as a floating point number that will be
|
||||
// displayed in decimal notation. For example, "1e-2" will be
|
||||
// displayed as "0.01", but "1e2" will be displayed as "100",
|
||||
// without a decimal point.
|
||||
let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?;
|
||||
let num_integral_digits = if is_minus_zero_float(s, &x) {
|
||||
2
|
||||
} else {
|
||||
let total = j as i64 + exponent;
|
||||
let result = if total < 1 {
|
||||
1
|
||||
} else {
|
||||
total.try_into().unwrap()
|
||||
};
|
||||
if x.sign() == Sign::Minus {
|
||||
result + 1
|
||||
} else {
|
||||
result
|
||||
}
|
||||
};
|
||||
let num_fractional_digits = if exponent < 0 { -exponent as usize } else { 0 };
|
||||
|
||||
if exponent < 0 {
|
||||
if is_minus_zero_float(s, &x) {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::MinusZero),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
} else {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
let zeros = "0".repeat(exponent.try_into().unwrap());
|
||||
let expanded = [&s[0..j], &zeros].concat();
|
||||
parse_no_decimal_no_exponent(&expanded)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a number with a decimal point but no exponent.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function returns an error if `s` is not a valid number.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let actual = "1.2".parse::<Number>().unwrap().number;
|
||||
/// let expected = "1.2".parse::<BigDecimal>().unwrap();
|
||||
/// assert_eq!(actual, expected);
|
||||
/// ```
|
||||
fn parse_decimal_no_exponent(s: &str, i: usize) -> Result<PreciseNumber, ParseNumberError> {
|
||||
let x: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?;
|
||||
let num_integral_digits = i;
|
||||
let num_fractional_digits = s.len() - (i + 1);
|
||||
if is_minus_zero_float(s, &x) {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::MinusZero),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
} else {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(x)),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a number with both a decimal point and an exponent.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function returns an error if `s` is not a valid number.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let actual = "1.2e3".parse::<Number>().unwrap().number;
|
||||
/// let expected = "1200".parse::<BigInt>().unwrap();
|
||||
/// assert_eq!(actual, expected);
|
||||
/// ```
|
||||
fn parse_decimal_and_exponent(
|
||||
s: &str,
|
||||
i: usize,
|
||||
j: usize,
|
||||
) -> Result<PreciseNumber, ParseNumberError> {
|
||||
// Because of the match guard, this subtraction will not underflow.
|
||||
let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64;
|
||||
let exponent: i64 = s[j + 1..].parse().map_err(|_| ParseNumberError::Float)?;
|
||||
let val: BigDecimal = s.parse().map_err(|_| ParseNumberError::Float)?;
|
||||
|
||||
let num_integral_digits = {
|
||||
let minimum: usize = {
|
||||
let integral_part: f64 = s[..j].parse().map_err(|_| ParseNumberError::Float)?;
|
||||
if integral_part == -0.0 && integral_part.is_sign_negative() {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
};
|
||||
|
||||
let total = i as i64 + exponent;
|
||||
if total < minimum as i64 {
|
||||
minimum
|
||||
} else {
|
||||
total.try_into().unwrap()
|
||||
}
|
||||
};
|
||||
let num_fractional_digits = if num_digits_between_decimal_point_and_e < exponent {
|
||||
0
|
||||
} else {
|
||||
(num_digits_between_decimal_point_and_e - exponent)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
if num_digits_between_decimal_point_and_e <= exponent {
|
||||
if is_minus_zero_float(s, &val) {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::MinusZero),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
} else {
|
||||
let zeros: String = "0".repeat(
|
||||
(exponent - num_digits_between_decimal_point_and_e)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let expanded = [&s[0..i], &s[i + 1..j], &zeros].concat();
|
||||
let n = expanded
|
||||
.parse::<BigInt>()
|
||||
.map_err(|_| ParseNumberError::Float)?;
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::BigInt(n)),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
}
|
||||
} else if is_minus_zero_float(s, &val) {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::MinusZero),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
} else {
|
||||
Ok(PreciseNumber::new(
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(val)),
|
||||
num_integral_digits,
|
||||
num_fractional_digits,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a hexadecimal integer from a string.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function returns an error if no [`BigInt`] could be parsed from
|
||||
/// the string.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let actual = "0x0".parse::<Number>().unwrap().number;
|
||||
/// let expected = Number::BigInt(BigInt::zero());
|
||||
/// assert_eq!(actual, expected);
|
||||
/// ```
|
||||
fn parse_hexadecimal(s: &str) -> Result<PreciseNumber, ParseNumberError> {
|
||||
let (is_neg, s) = if s.starts_with('-') {
|
||||
(true, &s[3..])
|
||||
} else {
|
||||
(false, &s[2..])
|
||||
};
|
||||
|
||||
if s.starts_with('-') || s.starts_with('+') {
|
||||
// Even though this is more like an invalid hexadecimal number,
|
||||
// GNU reports this as an invalid floating point number, so we
|
||||
// use `ParseNumberError::Float` to match that behavior.
|
||||
return Err(ParseNumberError::Float);
|
||||
}
|
||||
|
||||
let num = BigInt::from_str_radix(s, 16).map_err(|_| ParseNumberError::Hex)?;
|
||||
|
||||
match (is_neg, num == BigInt::zero()) {
|
||||
(true, true) => Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::MinusZero),
|
||||
2,
|
||||
0,
|
||||
)),
|
||||
(true, false) => Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::BigInt(-num)),
|
||||
0,
|
||||
0,
|
||||
)),
|
||||
(false, _) => Ok(PreciseNumber::new(
|
||||
Number::Int(ExtendedBigInt::BigInt(num)),
|
||||
0,
|
||||
0,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PreciseNumber {
|
||||
type Err = ParseNumberError;
|
||||
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
|
||||
// Trim leading whitespace.
|
||||
s = s.trim_start();
|
||||
|
||||
// Trim a single leading "+" character.
|
||||
if s.starts_with('+') {
|
||||
s = &s[1..];
|
||||
}
|
||||
|
||||
// Check if the string seems to be in hexadecimal format.
|
||||
//
|
||||
// May be 0x123 or -0x123, so the index `i` may be either 0 or 1.
|
||||
if let Some(i) = s.to_lowercase().find("0x") {
|
||||
if i <= 1 {
|
||||
return parse_hexadecimal(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the decimal point and the exponent symbol. Parse the
|
||||
// number differently depending on its form. This is important
|
||||
// because the form of the input dictates how the output will be
|
||||
// presented.
|
||||
match (s.find('.'), s.find('e')) {
|
||||
// For example, "123456" or "inf".
|
||||
(None, None) => parse_no_decimal_no_exponent(s),
|
||||
// For example, "123e456" or "1e-2".
|
||||
(None, Some(j)) => parse_exponent_no_decimal(s, j),
|
||||
// For example, "123.456".
|
||||
(Some(i), None) => parse_decimal_no_exponent(s, i),
|
||||
// For example, "123.456e789".
|
||||
(Some(i), Some(j)) if i < j => parse_decimal_and_exponent(s, i, j),
|
||||
// For example, "1e2.3" or "1.2.3".
|
||||
_ => Err(ParseNumberError::Float),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use bigdecimal::BigDecimal;
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::Zero;
|
||||
|
||||
use crate::extendedbigdecimal::ExtendedBigDecimal;
|
||||
use crate::extendedbigint::ExtendedBigInt;
|
||||
use crate::number::Number;
|
||||
use crate::number::PreciseNumber;
|
||||
use crate::numberparse::ParseNumberError;
|
||||
|
||||
/// Convenience function for parsing a [`Number`] and unwrapping.
|
||||
fn parse(s: &str) -> Number {
|
||||
s.parse::<PreciseNumber>().unwrap().number
|
||||
}
|
||||
|
||||
/// Convenience function for getting the number of integral digits.
|
||||
fn num_integral_digits(s: &str) -> usize {
|
||||
s.parse::<PreciseNumber>().unwrap().num_integral_digits
|
||||
}
|
||||
|
||||
/// Convenience function for getting the number of fractional digits.
|
||||
fn num_fractional_digits(s: &str) -> usize {
|
||||
s.parse::<PreciseNumber>().unwrap().num_fractional_digits
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_minus_zero_int() {
|
||||
assert_eq!(parse("-0e0"), Number::Int(ExtendedBigInt::MinusZero));
|
||||
assert_eq!(parse("-0e-0"), Number::Int(ExtendedBigInt::MinusZero));
|
||||
assert_eq!(parse("-0e1"), Number::Int(ExtendedBigInt::MinusZero));
|
||||
assert_eq!(parse("-0e+1"), Number::Int(ExtendedBigInt::MinusZero));
|
||||
assert_eq!(parse("-0.0e1"), Number::Int(ExtendedBigInt::MinusZero));
|
||||
assert_eq!(parse("-0x0"), Number::Int(ExtendedBigInt::MinusZero));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_minus_zero_float() {
|
||||
assert_eq!(parse("-0.0"), Number::Float(ExtendedBigDecimal::MinusZero));
|
||||
assert_eq!(parse("-0e-1"), Number::Float(ExtendedBigDecimal::MinusZero));
|
||||
assert_eq!(
|
||||
parse("-0.0e-1"),
|
||||
Number::Float(ExtendedBigDecimal::MinusZero)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_big_int() {
|
||||
assert_eq!(parse("0"), Number::Int(ExtendedBigInt::zero()));
|
||||
assert_eq!(parse("0.1e1"), Number::Int(ExtendedBigInt::one()));
|
||||
assert_eq!(
|
||||
parse("1.0e1"),
|
||||
Number::Int(ExtendedBigInt::BigInt("10".parse::<BigInt>().unwrap()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_hexadecimal_big_int() {
|
||||
assert_eq!(parse("0x0"), Number::Int(ExtendedBigInt::zero()));
|
||||
assert_eq!(
|
||||
parse("0x10"),
|
||||
Number::Int(ExtendedBigInt::BigInt("16".parse::<BigInt>().unwrap()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_big_decimal() {
|
||||
assert_eq!(
|
||||
parse("0.0"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"0.0".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse(".0"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"0.0".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("1.0"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"1.0".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("10e-1"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"1.0".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("-1e-3"),
|
||||
Number::Float(ExtendedBigDecimal::BigDecimal(
|
||||
"-0.001".parse::<BigDecimal>().unwrap()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_inf() {
|
||||
assert_eq!(parse("inf"), Number::Float(ExtendedBigDecimal::Infinity));
|
||||
assert_eq!(parse("+inf"), Number::Float(ExtendedBigDecimal::Infinity));
|
||||
assert_eq!(
|
||||
parse("-inf"),
|
||||
Number::Float(ExtendedBigDecimal::MinusInfinity)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_float() {
|
||||
assert_eq!(
|
||||
"1.2.3".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Float
|
||||
);
|
||||
assert_eq!(
|
||||
"1e2e3".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Float
|
||||
);
|
||||
assert_eq!(
|
||||
"1e2.3".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Float
|
||||
);
|
||||
assert_eq!(
|
||||
"-+-1".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Float
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_hex() {
|
||||
assert_eq!(
|
||||
"0xg".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Hex
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_invalid_nan() {
|
||||
assert_eq!(
|
||||
"nan".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Nan
|
||||
);
|
||||
assert_eq!(
|
||||
"NAN".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Nan
|
||||
);
|
||||
assert_eq!(
|
||||
"NaN".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Nan
|
||||
);
|
||||
assert_eq!(
|
||||
"nAn".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Nan
|
||||
);
|
||||
assert_eq!(
|
||||
"-nan".parse::<PreciseNumber>().unwrap_err(),
|
||||
ParseNumberError::Nan
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_integral_digits() {
|
||||
// no decimal, no exponent
|
||||
assert_eq!(num_integral_digits("123"), 3);
|
||||
// decimal, no exponent
|
||||
assert_eq!(num_integral_digits("123.45"), 3);
|
||||
// exponent, no decimal
|
||||
assert_eq!(num_integral_digits("123e4"), 3 + 4);
|
||||
assert_eq!(num_integral_digits("123e-4"), 1);
|
||||
assert_eq!(num_integral_digits("-1e-3"), 2);
|
||||
// decimal and exponent
|
||||
assert_eq!(num_integral_digits("123.45e6"), 3 + 6);
|
||||
assert_eq!(num_integral_digits("123.45e-6"), 1);
|
||||
assert_eq!(num_integral_digits("123.45e-1"), 2);
|
||||
// minus zero int
|
||||
assert_eq!(num_integral_digits("-0e0"), 2);
|
||||
assert_eq!(num_integral_digits("-0e-0"), 2);
|
||||
assert_eq!(num_integral_digits("-0e1"), 3);
|
||||
assert_eq!(num_integral_digits("-0e+1"), 3);
|
||||
assert_eq!(num_integral_digits("-0.0e1"), 3);
|
||||
// minus zero float
|
||||
assert_eq!(num_integral_digits("-0.0"), 2);
|
||||
assert_eq!(num_integral_digits("-0e-1"), 2);
|
||||
assert_eq!(num_integral_digits("-0.0e-1"), 2);
|
||||
|
||||
// TODO In GNU `seq`, the `-w` option does not seem to work with
|
||||
// hexadecimal arguments. In order to match that behavior, we
|
||||
// report the number of integral digits as zero for hexadecimal
|
||||
// inputs.
|
||||
assert_eq!(num_integral_digits("0xff"), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_fractional_digits() {
|
||||
// no decimal, no exponent
|
||||
assert_eq!(num_fractional_digits("123"), 0);
|
||||
assert_eq!(num_fractional_digits("0xff"), 0);
|
||||
// decimal, no exponent
|
||||
assert_eq!(num_fractional_digits("123.45"), 2);
|
||||
// exponent, no decimal
|
||||
assert_eq!(num_fractional_digits("123e4"), 0);
|
||||
assert_eq!(num_fractional_digits("123e-4"), 4);
|
||||
assert_eq!(num_fractional_digits("123e-1"), 1);
|
||||
assert_eq!(num_fractional_digits("-1e-3"), 3);
|
||||
// decimal and exponent
|
||||
assert_eq!(num_fractional_digits("123.45e6"), 0);
|
||||
assert_eq!(num_fractional_digits("123.45e1"), 1);
|
||||
assert_eq!(num_fractional_digits("123.45e-6"), 8);
|
||||
assert_eq!(num_fractional_digits("123.45e-1"), 3);
|
||||
// minus zero int
|
||||
assert_eq!(num_fractional_digits("-0e0"), 0);
|
||||
assert_eq!(num_fractional_digits("-0e-0"), 0);
|
||||
assert_eq!(num_fractional_digits("-0e1"), 0);
|
||||
assert_eq!(num_fractional_digits("-0e+1"), 0);
|
||||
assert_eq!(num_fractional_digits("-0.0e1"), 0);
|
||||
// minus zero float
|
||||
assert_eq!(num_fractional_digits("-0.0"), 1);
|
||||
assert_eq!(num_fractional_digits("-0e-1"), 1);
|
||||
assert_eq!(num_fractional_digits("-0.0e-1"), 2);
|
||||
}
|
||||
}
|
|
@ -1,23 +1,24 @@
|
|||
// TODO: Make -w flag work with decimals
|
||||
// TODO: Support -f flag
|
||||
|
||||
// spell-checker:ignore (ToDO) istr chiter argptr ilen
|
||||
// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, AppSettings, Arg};
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::One;
|
||||
use num_traits::Zero;
|
||||
use num_traits::{Num, ToPrimitive};
|
||||
use std::cmp;
|
||||
use std::io::{stdout, ErrorKind, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
mod digits;
|
||||
use crate::digits::num_fractional_digits;
|
||||
use crate::digits::num_integral_digits;
|
||||
mod extendedbigdecimal;
|
||||
mod extendedbigint;
|
||||
mod number;
|
||||
mod numberparse;
|
||||
use crate::extendedbigdecimal::ExtendedBigDecimal;
|
||||
use crate::extendedbigint::ExtendedBigInt;
|
||||
use crate::number::Number;
|
||||
use crate::number::PreciseNumber;
|
||||
use crate::numberparse::ParseNumberError;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
|
||||
|
@ -43,124 +44,55 @@ struct SeqOptions {
|
|||
widths: bool,
|
||||
}
|
||||
|
||||
enum Number {
|
||||
/// Negative zero, as if it were an integer.
|
||||
MinusZero,
|
||||
BigInt(BigInt),
|
||||
F64(f64),
|
||||
}
|
||||
|
||||
impl Number {
|
||||
fn is_zero(&self) -> bool {
|
||||
match self {
|
||||
Number::MinusZero => true,
|
||||
Number::BigInt(n) => n.is_zero(),
|
||||
Number::F64(n) => n.is_zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_f64(self) -> f64 {
|
||||
match self {
|
||||
Number::MinusZero => -0.,
|
||||
// BigInt::to_f64() can not return None.
|
||||
Number::BigInt(n) => n.to_f64().unwrap(),
|
||||
Number::F64(n) => n,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this number into a bigint, consuming it.
|
||||
///
|
||||
/// For floats, this returns the [`BigInt`] corresponding to the
|
||||
/// floor of the number.
|
||||
fn into_bigint(self) -> BigInt {
|
||||
match self {
|
||||
Number::MinusZero => BigInt::zero(),
|
||||
Number::F64(x) => BigInt::from(x.floor() as i64),
|
||||
Number::BigInt(n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Number {
|
||||
type Err = String;
|
||||
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
|
||||
s = s.trim_start();
|
||||
if s.starts_with('+') {
|
||||
s = &s[1..];
|
||||
}
|
||||
let is_neg = s.starts_with('-');
|
||||
|
||||
match s.to_lowercase().find("0x") {
|
||||
Some(i) if i <= 1 => match &s.as_bytes()[i + 2] {
|
||||
b'-' | b'+' => Err(format!(
|
||||
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
|
||||
s.quote(),
|
||||
uucore::execution_phrase(),
|
||||
)),
|
||||
// TODO: hexadecimal floating point parsing (see #2660)
|
||||
b'.' => Err(format!(
|
||||
"NotImplemented: hexadecimal floating point numbers: {}\nTry '{} --help' for more information.",
|
||||
s.quote(),
|
||||
uucore::execution_phrase(),
|
||||
)),
|
||||
_ => {
|
||||
let num = BigInt::from_str_radix(&s[i + 2..], 16)
|
||||
.map_err(|_| format!(
|
||||
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
|
||||
s.quote(),
|
||||
uucore::execution_phrase(),
|
||||
))?;
|
||||
match (is_neg, num == BigInt::zero()) {
|
||||
(true, true) => Ok(Number::MinusZero),
|
||||
(true, false) => Ok(Number::BigInt(-num)),
|
||||
(false, _) => Ok(Number::BigInt(num)),
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(_) => Err(format!(
|
||||
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
|
||||
s.quote(),
|
||||
uucore::execution_phrase(),
|
||||
)),
|
||||
|
||||
None => match s.parse::<BigInt>() {
|
||||
Ok(n) => {
|
||||
// If `s` is '-0', then `parse()` returns
|
||||
// `BigInt::zero()`, but we need to return
|
||||
// `Number::MinusZero` instead.
|
||||
if n == BigInt::zero() && is_neg {
|
||||
Ok(Number::MinusZero)
|
||||
} else {
|
||||
Ok(Number::BigInt(n))
|
||||
}
|
||||
}
|
||||
Err(_) => match s.parse::<f64>() {
|
||||
Ok(value) if value.is_nan() => Err(format!(
|
||||
"invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.",
|
||||
s.quote(),
|
||||
uucore::execution_phrase(),
|
||||
)),
|
||||
Ok(value) => Ok(Number::F64(value)),
|
||||
Err(_) => Err(format!(
|
||||
"invalid floating point argument: {}\nTry '{} --help' for more information.",
|
||||
s.quote(),
|
||||
uucore::execution_phrase(),
|
||||
)),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A range of integers.
|
||||
///
|
||||
/// The elements are (first, increment, last).
|
||||
type RangeInt = (BigInt, BigInt, BigInt);
|
||||
type RangeInt = (ExtendedBigInt, ExtendedBigInt, ExtendedBigInt);
|
||||
|
||||
/// A range of f64.
|
||||
/// A range of floats.
|
||||
///
|
||||
/// The elements are (first, increment, last).
|
||||
type RangeF64 = (f64, f64, f64);
|
||||
type RangeFloat = (ExtendedBigDecimal, ExtendedBigDecimal, ExtendedBigDecimal);
|
||||
|
||||
/// Terminate the process with error code 1.
|
||||
///
|
||||
/// Before terminating the process, this function prints an error
|
||||
/// message that depends on `arg` and `e`.
|
||||
///
|
||||
/// Although the signature of this function states that it returns a
|
||||
/// [`PreciseNumber`], it never reaches the return statement. It is just
|
||||
/// there to make it easier to use this function when unwrapping the
|
||||
/// result of calling [`str::parse`] when attempting to parse a
|
||||
/// [`PreciseNumber`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let s = "1.2e-3";
|
||||
/// s.parse::<PreciseNumber>.unwrap_or_else(|e| exit_with_error(s, e))
|
||||
/// ```
|
||||
fn exit_with_error(arg: &str, e: ParseNumberError) -> ! {
|
||||
match e {
|
||||
ParseNumberError::Float => crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\nTry '{} --help' for more information.",
|
||||
arg.quote(),
|
||||
uucore::execution_phrase()
|
||||
),
|
||||
ParseNumberError::Nan => crash!(
|
||||
1,
|
||||
"invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.",
|
||||
arg.quote(),
|
||||
uucore::execution_phrase()
|
||||
),
|
||||
ParseNumberError::Hex => crash!(
|
||||
1,
|
||||
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
|
||||
arg.quote(),
|
||||
uucore::execution_phrase()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let usage = usage();
|
||||
|
@ -174,53 +106,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
widths: matches.is_present(OPT_WIDTHS),
|
||||
};
|
||||
|
||||
let mut largest_dec = 0;
|
||||
let mut padding = 0;
|
||||
let first = if numbers.len() > 1 {
|
||||
let slice = numbers[0];
|
||||
largest_dec = num_fractional_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
padding = num_integral_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
crash_if_err!(1, slice.parse())
|
||||
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
|
||||
} else {
|
||||
Number::BigInt(BigInt::one())
|
||||
PreciseNumber::one()
|
||||
};
|
||||
let increment = if numbers.len() > 2 {
|
||||
let slice = numbers[1];
|
||||
let dec = num_fractional_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
largest_dec = cmp::max(largest_dec, dec);
|
||||
padding = cmp::max(padding, int_digits);
|
||||
crash_if_err!(1, slice.parse())
|
||||
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
|
||||
} else {
|
||||
Number::BigInt(BigInt::one())
|
||||
PreciseNumber::one()
|
||||
};
|
||||
if increment.is_zero() {
|
||||
show_error!(
|
||||
|
@ -230,54 +126,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
);
|
||||
return 1;
|
||||
}
|
||||
let last: Number = {
|
||||
let last: PreciseNumber = {
|
||||
let slice = numbers[numbers.len() - 1];
|
||||
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
|
||||
crash!(
|
||||
1,
|
||||
"invalid floating point argument: {}\n Try '{} --help' for more information.",
|
||||
slice.quote(),
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
});
|
||||
padding = cmp::max(padding, int_digits);
|
||||
crash_if_err!(1, slice.parse())
|
||||
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
|
||||
};
|
||||
|
||||
let is_negative_zero_f64 = |x: f64| x == -0.0 && x.is_sign_negative() && largest_dec == 0;
|
||||
let result = match (first, last, increment) {
|
||||
// For example, `seq -0 1 2` or `seq -0 1 2.0`.
|
||||
(Number::MinusZero, last, Number::BigInt(increment)) => print_seq_integers(
|
||||
(BigInt::zero(), increment, last.into_bigint()),
|
||||
options.separator,
|
||||
options.terminator,
|
||||
options.widths,
|
||||
padding,
|
||||
true,
|
||||
),
|
||||
// For example, `seq -0e0 1 2` or `seq -0e0 1 2.0`.
|
||||
(Number::F64(x), last, Number::BigInt(increment)) if is_negative_zero_f64(x) => {
|
||||
let padding = first
|
||||
.num_integral_digits
|
||||
.max(increment.num_integral_digits)
|
||||
.max(last.num_integral_digits);
|
||||
let largest_dec = first
|
||||
.num_fractional_digits
|
||||
.max(increment.num_fractional_digits);
|
||||
|
||||
let result = match (first.number, increment.number, last.number) {
|
||||
(Number::Int(first), Number::Int(increment), last) => {
|
||||
let last = last.round_towards(&first);
|
||||
print_seq_integers(
|
||||
(BigInt::zero(), increment, last.into_bigint()),
|
||||
(first, increment, last),
|
||||
options.separator,
|
||||
options.terminator,
|
||||
options.widths,
|
||||
padding,
|
||||
true,
|
||||
)
|
||||
}
|
||||
// For example, `seq 0 1 2` or `seq 0 1 2.0`.
|
||||
(Number::BigInt(first), last, Number::BigInt(increment)) => print_seq_integers(
|
||||
(first, increment, last.into_bigint()),
|
||||
options.separator,
|
||||
options.terminator,
|
||||
options.widths,
|
||||
padding,
|
||||
false,
|
||||
),
|
||||
// For example, `seq 0 0.5 1` or `seq 0.0 0.5 1` or `seq 0.0 0.5 1.0`.
|
||||
(first, last, increment) => print_seq(
|
||||
(first.into_f64(), increment.into_f64(), last.into_f64()),
|
||||
(first, increment, last) => print_seq(
|
||||
(
|
||||
first.into_extended_big_decimal(),
|
||||
increment.into_extended_big_decimal(),
|
||||
last.into_extended_big_decimal(),
|
||||
),
|
||||
largest_dec,
|
||||
options.separator,
|
||||
options.terminator,
|
||||
|
@ -329,7 +207,7 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
)
|
||||
}
|
||||
|
||||
fn done_printing<T: Num + PartialOrd>(next: &T, increment: &T, last: &T) -> bool {
|
||||
fn done_printing<T: Zero + PartialOrd>(next: &T, increment: &T, last: &T) -> bool {
|
||||
if increment >= &T::zero() {
|
||||
next > last
|
||||
} else {
|
||||
|
@ -337,9 +215,73 @@ fn done_printing<T: Num + PartialOrd>(next: &T, increment: &T, last: &T) -> bool
|
|||
}
|
||||
}
|
||||
|
||||
/// Write a big decimal formatted according to the given parameters.
|
||||
///
|
||||
/// This method is an adapter to support displaying negative zero on
|
||||
/// Rust versions earlier than 1.53.0. After that version, we should be
|
||||
/// able to display negative zero using the default formatting provided
|
||||
/// by `-0.0f32`, for example.
|
||||
fn write_value_float(
|
||||
writer: &mut impl Write,
|
||||
value: &ExtendedBigDecimal,
|
||||
width: usize,
|
||||
precision: usize,
|
||||
is_first_iteration: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let value_as_str = if *value == ExtendedBigDecimal::MinusZero && is_first_iteration {
|
||||
format!(
|
||||
"-{value:>0width$.precision$}",
|
||||
value = value,
|
||||
width = if width > 0 { width - 1 } else { width },
|
||||
precision = precision,
|
||||
)
|
||||
} else if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity
|
||||
{
|
||||
format!(
|
||||
"{value:>width$.precision$}",
|
||||
value = value,
|
||||
width = width,
|
||||
precision = precision,
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"{value:>0width$.precision$}",
|
||||
value = value,
|
||||
width = width,
|
||||
precision = precision,
|
||||
)
|
||||
};
|
||||
write!(writer, "{}", value_as_str)
|
||||
}
|
||||
|
||||
/// Write a big int formatted according to the given parameters.
|
||||
fn write_value_int(
|
||||
writer: &mut impl Write,
|
||||
value: &ExtendedBigInt,
|
||||
width: usize,
|
||||
pad: bool,
|
||||
is_first_iteration: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let value_as_str = if pad {
|
||||
if *value == ExtendedBigInt::MinusZero && is_first_iteration {
|
||||
format!("-{value:>0width$}", value = value, width = width - 1,)
|
||||
} else {
|
||||
format!("{value:>0width$}", value = value, width = width,)
|
||||
}
|
||||
} else if *value == ExtendedBigInt::MinusZero && is_first_iteration {
|
||||
format!("-{}", value)
|
||||
} else {
|
||||
format!("{}", value)
|
||||
};
|
||||
write!(writer, "{}", value_as_str)
|
||||
}
|
||||
|
||||
// TODO `print_seq()` and `print_seq_integers()` are nearly identical,
|
||||
// they could be refactored into a single more general function.
|
||||
|
||||
/// Floating point based code path
|
||||
fn print_seq(
|
||||
range: RangeF64,
|
||||
range: RangeFloat,
|
||||
largest_dec: usize,
|
||||
separator: String,
|
||||
terminator: String,
|
||||
|
@ -349,30 +291,23 @@ fn print_seq(
|
|||
let stdout = stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
let (first, increment, last) = range;
|
||||
let mut i = 0isize;
|
||||
let is_first_minus_zero = first == -0.0 && first.is_sign_negative();
|
||||
let mut value = first + i as f64 * increment;
|
||||
let mut value = first;
|
||||
let padding = if pad { padding + 1 + largest_dec } else { 0 };
|
||||
let mut is_first_iteration = true;
|
||||
while !done_printing(&value, &increment, &last) {
|
||||
if !is_first_iteration {
|
||||
write!(stdout, "{}", separator)?;
|
||||
}
|
||||
let mut width = padding;
|
||||
if is_first_iteration && is_first_minus_zero {
|
||||
write!(stdout, "-")?;
|
||||
width -= 1;
|
||||
}
|
||||
is_first_iteration = false;
|
||||
write!(
|
||||
stdout,
|
||||
"{value:>0width$.precision$}",
|
||||
value = value,
|
||||
width = width,
|
||||
precision = largest_dec,
|
||||
write_value_float(
|
||||
&mut stdout,
|
||||
&value,
|
||||
padding,
|
||||
largest_dec,
|
||||
is_first_iteration,
|
||||
)?;
|
||||
i += 1;
|
||||
value = first + i as f64 * increment;
|
||||
// TODO Implement augmenting addition.
|
||||
value = value + increment.clone();
|
||||
is_first_iteration = false;
|
||||
}
|
||||
if !is_first_iteration {
|
||||
write!(stdout, "{}", terminator)?;
|
||||
|
@ -401,7 +336,6 @@ fn print_seq_integers(
|
|||
terminator: String,
|
||||
pad: bool,
|
||||
padding: usize,
|
||||
is_first_minus_zero: bool,
|
||||
) -> std::io::Result<()> {
|
||||
let stdout = stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
@ -412,18 +346,10 @@ fn print_seq_integers(
|
|||
if !is_first_iteration {
|
||||
write!(stdout, "{}", separator)?;
|
||||
}
|
||||
let mut width = padding;
|
||||
if is_first_iteration && is_first_minus_zero {
|
||||
write!(stdout, "-")?;
|
||||
width -= 1;
|
||||
}
|
||||
write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?;
|
||||
// TODO Implement augmenting addition.
|
||||
value = value + increment.clone();
|
||||
is_first_iteration = false;
|
||||
if pad {
|
||||
write!(stdout, "{number:>0width$}", number = value, width = width)?;
|
||||
} else {
|
||||
write!(stdout, "{}", value)?;
|
||||
}
|
||||
value += &increment;
|
||||
}
|
||||
|
||||
if !is_first_iteration {
|
||||
|
|
|
@ -193,16 +193,13 @@ pub fn read<T: Read>(
|
|||
|
||||
/// Split `read` into `Line`s, and add them to `lines`.
|
||||
fn parse_lines<'a>(
|
||||
mut read: &'a str,
|
||||
read: &'a str,
|
||||
lines: &mut Vec<Line<'a>>,
|
||||
line_data: &mut LineData<'a>,
|
||||
separator: u8,
|
||||
settings: &GlobalSettings,
|
||||
) {
|
||||
// Strip a trailing separator. TODO: Once our MinRustV is 1.45 or above, use strip_suffix() instead.
|
||||
if read.ends_with(separator as char) {
|
||||
read = &read[..read.len() - 1];
|
||||
}
|
||||
let read = read.strip_suffix(separator as char).unwrap_or(read);
|
||||
|
||||
assert!(lines.is_empty());
|
||||
assert!(line_data.selections.is_empty());
|
||||
|
|
|
@ -30,6 +30,7 @@ data-encoding-macro = { version="0.1.12", optional=true }
|
|||
z85 = { version="3.0.3", optional=true }
|
||||
libc = { version="0.2.15", optional=true }
|
||||
once_cell = "1.8.0"
|
||||
os_display = "0.1.0"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
walkdir = { version="2.3.2", optional=true }
|
||||
|
|
|
@ -163,12 +163,11 @@ pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
|
|||
if arg == "--" {
|
||||
break;
|
||||
}
|
||||
if arg.starts_with('-') {
|
||||
if let Some(arg_stripped) = arg.strip_prefix('-') {
|
||||
if let Some(second) = arg.chars().nth(1) {
|
||||
match second {
|
||||
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
|
||||
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0
|
||||
*arg = arg[1..arg.len()].to_string();
|
||||
*arg = arg_stripped.to_string();
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
@ -19,378 +19,16 @@
|
|||
/// println_verbatim(path)?; // Prints "foo/bar.baz"
|
||||
/// # Ok::<(), std::io::Error>(())
|
||||
/// ```
|
||||
// spell-checker:ignore Fbar
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
#[cfg(any(unix, target_os = "wasi", windows))]
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io::{self, Write as IoWrite};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
#[cfg(target_os = "wasi")]
|
||||
use std::os::wasi::ffi::OsStrExt;
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
use std::str::from_utf8;
|
||||
|
||||
/// An extension trait for displaying filenames to users.
|
||||
pub trait Quotable {
|
||||
/// Returns an object that implements [`Display`] for printing filenames with
|
||||
/// proper quoting and escaping for the platform.
|
||||
///
|
||||
/// On Unix this corresponds to sh/bash syntax, on Windows Powershell syntax
|
||||
/// is used.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::path::Path;
|
||||
/// use uucore::display::Quotable;
|
||||
///
|
||||
/// let path = Path::new("foo/bar.baz");
|
||||
///
|
||||
/// println!("Found file {}", path.quote()); // Prints "Found file 'foo/bar.baz'"
|
||||
/// ```
|
||||
fn quote(&self) -> Quoted<'_>;
|
||||
|
||||
/// Like `quote()`, but don't actually add quotes unless necessary because of
|
||||
/// whitespace or special characters.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::path::Path;
|
||||
/// use uucore::display::Quotable;
|
||||
/// use uucore::show_error;
|
||||
///
|
||||
/// let foo = Path::new("foo/bar.baz");
|
||||
/// let bar = Path::new("foo bar");
|
||||
///
|
||||
/// show_error!("{}: Not found", foo.maybe_quote()); // Prints "util: foo/bar.baz: Not found"
|
||||
/// show_error!("{}: Not found", bar.maybe_quote()); // Prints "util: 'foo bar': Not found"
|
||||
/// ```
|
||||
fn maybe_quote(&self) -> Quoted<'_> {
|
||||
let mut quoted = self.quote();
|
||||
quoted.force_quote = false;
|
||||
quoted
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_as_ref {
|
||||
($type: ty) => {
|
||||
impl Quotable for $type {
|
||||
fn quote(&self) -> Quoted<'_> {
|
||||
Quoted::new(self.as_ref())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_as_ref!(str);
|
||||
impl_as_ref!(&'_ str);
|
||||
impl_as_ref!(String);
|
||||
impl_as_ref!(std::path::Path);
|
||||
impl_as_ref!(&'_ std::path::Path);
|
||||
impl_as_ref!(std::path::PathBuf);
|
||||
impl_as_ref!(std::path::Component<'_>);
|
||||
impl_as_ref!(std::path::Components<'_>);
|
||||
impl_as_ref!(std::path::Iter<'_>);
|
||||
impl_as_ref!(std::ffi::OsStr);
|
||||
impl_as_ref!(&'_ std::ffi::OsStr);
|
||||
impl_as_ref!(std::ffi::OsString);
|
||||
|
||||
// Cow<'_, str> does not implement AsRef<OsStr> and this is unlikely to be fixed
|
||||
// for backward compatibility reasons. Otherwise we'd use a blanket impl.
|
||||
impl Quotable for Cow<'_, str> {
|
||||
fn quote(&self) -> Quoted<'_> {
|
||||
let text: &str = self.as_ref();
|
||||
Quoted::new(text.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Quotable for Cow<'_, std::path::Path> {
|
||||
fn quote(&self) -> Quoted<'_> {
|
||||
let text: &std::path::Path = self.as_ref();
|
||||
Quoted::new(text.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around [`OsStr`] for printing paths with quoting and escaping applied.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Quoted<'a> {
|
||||
text: &'a OsStr,
|
||||
force_quote: bool,
|
||||
}
|
||||
|
||||
impl<'a> Quoted<'a> {
|
||||
fn new(text: &'a OsStr) -> Self {
|
||||
Quoted {
|
||||
text,
|
||||
force_quote: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Quoted<'_> {
|
||||
#[cfg(any(windows, unix, target_os = "wasi"))]
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
// On Unix we emulate sh syntax. On Windows Powershell.
|
||||
// They're just similar enough to share some code.
|
||||
|
||||
/// Characters with special meaning outside quotes.
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02
|
||||
// I don't know why % is in there, and GNU doesn't quote it either.
|
||||
// {} were used in a version elsewhere but seem unnecessary, GNU doesn't
|
||||
// quote them. They're used in function definitions but not in a way we
|
||||
// have to worry about.
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\\\"'*?[]=";
|
||||
// FIXME: I'm not a PowerShell wizard and don't know if this is correct.
|
||||
// I just copied the Unix version, removed \, and added ,{} based on
|
||||
// experimentation.
|
||||
// I have noticed that ~?*[] only get expanded in some contexts, so watch
|
||||
// out for that if doing your own tests.
|
||||
// Get-ChildItem seems unwilling to quote anything so it doesn't help.
|
||||
// There's the additional wrinkle that Windows has stricter requirements
|
||||
// for filenames: I've been testing using a Linux build of PowerShell, but
|
||||
// this code doesn't even compile on Linux.
|
||||
#[cfg(windows)]
|
||||
const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\"'*?[]=,{}";
|
||||
|
||||
/// Characters with a special meaning at the beginning of a name.
|
||||
// ~ expands a home directory.
|
||||
// # starts a comment.
|
||||
// ! is a common extension for expanding the shell history.
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '!'];
|
||||
// Same deal as before, this is possibly incomplete.
|
||||
// A single stand-alone exclamation mark seems to have some special meaning.
|
||||
#[cfg(windows)]
|
||||
const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '@', '!'];
|
||||
|
||||
/// Characters that are interpreted specially in a double-quoted string.
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$', b'\\'];
|
||||
#[cfg(windows)]
|
||||
const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$'];
|
||||
|
||||
let text = match self.text.to_str() {
|
||||
None => return write_escaped(f, self.text),
|
||||
Some(text) => text,
|
||||
};
|
||||
|
||||
let mut is_single_safe = true;
|
||||
let mut is_double_safe = true;
|
||||
let mut requires_quote = self.force_quote;
|
||||
|
||||
if let Some(first) = text.chars().next() {
|
||||
if SPECIAL_SHELL_CHARS_START.contains(&first) {
|
||||
requires_quote = true;
|
||||
}
|
||||
// Unlike in Unix, quoting an argument may stop it
|
||||
// from being recognized as an option. I like that very much.
|
||||
// But we don't want to quote "-" because that's a common
|
||||
// special argument and PowerShell doesn't mind it.
|
||||
#[cfg(windows)]
|
||||
if first == '-' && text.len() > 1 {
|
||||
requires_quote = true;
|
||||
}
|
||||
} else {
|
||||
// Empty string
|
||||
requires_quote = true;
|
||||
}
|
||||
|
||||
for ch in text.chars() {
|
||||
if ch.is_ascii() {
|
||||
let ch = ch as u8;
|
||||
if ch == b'\'' {
|
||||
is_single_safe = false;
|
||||
}
|
||||
if DOUBLE_UNSAFE.contains(&ch) {
|
||||
is_double_safe = false;
|
||||
}
|
||||
if !requires_quote && SPECIAL_SHELL_CHARS.contains(&ch) {
|
||||
requires_quote = true;
|
||||
}
|
||||
if ch.is_ascii_control() {
|
||||
return write_escaped(f, self.text);
|
||||
}
|
||||
}
|
||||
if !requires_quote && ch.is_whitespace() {
|
||||
// This includes unicode whitespace.
|
||||
// We maybe don't have to escape it, we don't escape other lookalike
|
||||
// characters either, but it's confusing if it goes unquoted.
|
||||
requires_quote = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !requires_quote {
|
||||
return f.write_str(text);
|
||||
} else if is_single_safe {
|
||||
return write_simple(f, text, '\'');
|
||||
} else if is_double_safe {
|
||||
return write_simple(f, text, '\"');
|
||||
} else {
|
||||
return write_single_escaped(f, text);
|
||||
}
|
||||
|
||||
fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result {
|
||||
f.write_char(quote)?;
|
||||
f.write_str(text)?;
|
||||
f.write_char(quote)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result {
|
||||
let mut iter = text.split('\'');
|
||||
if let Some(chunk) = iter.next() {
|
||||
if !chunk.is_empty() {
|
||||
write_simple(f, chunk, '\'')?;
|
||||
}
|
||||
}
|
||||
for chunk in iter {
|
||||
f.write_str("\\'")?;
|
||||
if !chunk.is_empty() {
|
||||
write_simple(f, chunk, '\'')?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write using the syntax described here:
|
||||
/// https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html
|
||||
///
|
||||
/// Supported by these shells:
|
||||
/// - bash
|
||||
/// - zsh
|
||||
/// - busybox sh
|
||||
/// - mksh
|
||||
///
|
||||
/// Not supported by these:
|
||||
/// - fish
|
||||
/// - dash
|
||||
/// - tcsh
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result {
|
||||
f.write_str("$'")?;
|
||||
for chunk in from_utf8_iter(text.as_bytes()) {
|
||||
match chunk {
|
||||
Ok(chunk) => {
|
||||
for ch in chunk.chars() {
|
||||
match ch {
|
||||
'\n' => f.write_str("\\n")?,
|
||||
'\t' => f.write_str("\\t")?,
|
||||
'\r' => f.write_str("\\r")?,
|
||||
// We could do \b, \f, \v, etc., but those are
|
||||
// rare enough to be confusing.
|
||||
// \0 doesn't work consistently because of the
|
||||
// octal \nnn syntax, and null bytes can't appear
|
||||
// in filenames anyway.
|
||||
ch if ch.is_ascii_control() => write!(f, "\\x{:02X}", ch as u8)?,
|
||||
'\\' | '\'' => {
|
||||
// '?' and '"' can also be escaped this way
|
||||
// but AFAICT there's no reason to do so
|
||||
f.write_char('\\')?;
|
||||
f.write_char(ch)?;
|
||||
}
|
||||
ch => {
|
||||
f.write_char(ch)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(unit) => write!(f, "\\x{:02X}", unit)?,
|
||||
}
|
||||
}
|
||||
f.write_char('\'')?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result {
|
||||
// Quotes in Powershell can be escaped by doubling them
|
||||
f.write_char('\'')?;
|
||||
let mut iter = text.split('\'');
|
||||
if let Some(chunk) = iter.next() {
|
||||
f.write_str(chunk)?;
|
||||
}
|
||||
for chunk in iter {
|
||||
f.write_str("''")?;
|
||||
f.write_str(chunk)?;
|
||||
}
|
||||
f.write_char('\'')?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result {
|
||||
// ` takes the role of \ since \ is already used as the path separator.
|
||||
// Things are UTF-16-oriented, so we escape code units as "`u{1234}".
|
||||
use std::char::decode_utf16;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
|
||||
f.write_char('"')?;
|
||||
for ch in decode_utf16(text.encode_wide()) {
|
||||
match ch {
|
||||
Ok(ch) => match ch {
|
||||
'\0' => f.write_str("`0")?,
|
||||
'\r' => f.write_str("`r")?,
|
||||
'\n' => f.write_str("`n")?,
|
||||
'\t' => f.write_str("`t")?,
|
||||
ch if ch.is_ascii_control() => write!(f, "`u{{{:04X}}}", ch as u8)?,
|
||||
'`' => f.write_str("``")?,
|
||||
'$' => f.write_str("`$")?,
|
||||
'"' => f.write_str("\"\"")?,
|
||||
ch => f.write_char(ch)?,
|
||||
},
|
||||
Err(err) => write!(f, "`u{{{:04X}}}", err.unpaired_surrogate())?,
|
||||
}
|
||||
}
|
||||
f.write_char('"')?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(unix, target_os = "wasi", windows)))]
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
// As a fallback, we use Rust's own escaping rules.
|
||||
// This is reasonably sane and very easy to implement.
|
||||
// We use single quotes because that's hardcoded in a lot of tests.
|
||||
let text = self.text.to_string_lossy();
|
||||
if self.force_quote || !text.chars().all(|ch| ch.is_alphanumeric() || ch == '.') {
|
||||
write!(f, "'{}'", text.escape_debug())
|
||||
} else {
|
||||
f.write_str(&text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
fn from_utf8_iter(mut bytes: &[u8]) -> impl Iterator<Item = Result<&str, u8>> {
|
||||
std::iter::from_fn(move || {
|
||||
if bytes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
match from_utf8(bytes) {
|
||||
Ok(text) => {
|
||||
bytes = &[];
|
||||
Some(Ok(text))
|
||||
}
|
||||
Err(err) if err.valid_up_to() == 0 => {
|
||||
let res = bytes[0];
|
||||
bytes = &bytes[1..];
|
||||
Some(Err(res))
|
||||
}
|
||||
Err(err) => {
|
||||
let (valid, rest) = bytes.split_at(err.valid_up_to());
|
||||
bytes = rest;
|
||||
Some(Ok(from_utf8(valid).unwrap()))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// These used to be defined here, but they live in their own crate now.
|
||||
pub use os_display::{Quotable, Quoted};
|
||||
|
||||
/// Print a path (or `OsStr`-like object) directly to stdout, with a trailing newline,
|
||||
/// without losing any information if its encoding is invalid.
|
||||
|
@ -429,129 +67,3 @@ pub fn print_verbatim<S: AsRef<OsStr>>(text: S) -> io::Result<()> {
|
|||
write!(stdout, "{}", std::path::Path::new(text.as_ref()).display())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn verify_quote(cases: &[(impl Quotable, &str)]) {
|
||||
for (case, expected) in cases {
|
||||
assert_eq!(case.quote().to_string(), *expected);
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_maybe(cases: &[(impl Quotable, &str)]) {
|
||||
for (case, expected) in cases {
|
||||
assert_eq!(case.maybe_quote().to_string(), *expected);
|
||||
}
|
||||
}
|
||||
|
||||
/// This should hold on any platform, or else other tests fail.
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
verify_quote(&[
|
||||
("foo", "'foo'"),
|
||||
("", "''"),
|
||||
("foo/bar.baz", "'foo/bar.baz'"),
|
||||
]);
|
||||
verify_maybe(&[
|
||||
("foo", "foo"),
|
||||
("", "''"),
|
||||
("foo bar", "'foo bar'"),
|
||||
("$foo", "'$foo'"),
|
||||
("-", "-"),
|
||||
]);
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "wasi", windows))]
|
||||
#[test]
|
||||
fn test_common() {
|
||||
verify_maybe(&[
|
||||
("a#b", "a#b"),
|
||||
("#ab", "'#ab'"),
|
||||
("a~b", "a~b"),
|
||||
("!", "'!'"),
|
||||
]);
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
#[test]
|
||||
fn test_unix() {
|
||||
verify_quote(&[
|
||||
("can't", r#""can't""#),
|
||||
(r#"can'"t"#, r#"'can'\''"t'"#),
|
||||
(r#"can'$t"#, r#"'can'\''$t'"#),
|
||||
("foo\nb\ta\r\\\0`r", r#"$'foo\nb\ta\r\\\x00`r'"#),
|
||||
("foo\x02", r#"$'foo\x02'"#),
|
||||
(r#"'$''"#, r#"\''$'\'\'"#),
|
||||
]);
|
||||
verify_quote(&[(OsStr::from_bytes(b"foo\xFF"), r#"$'foo\xFF'"#)]);
|
||||
verify_maybe(&[
|
||||
("-x", "-x"),
|
||||
("a,b", "a,b"),
|
||||
("a\\b", "'a\\b'"),
|
||||
("}", ("}")),
|
||||
]);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_windows() {
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
verify_quote(&[
|
||||
(r#"foo\bar"#, r#"'foo\bar'"#),
|
||||
("can't", r#""can't""#),
|
||||
(r#"can'"t"#, r#"'can''"t'"#),
|
||||
(r#"can'$t"#, r#"'can''$t'"#),
|
||||
("foo\nb\ta\r\\\0`r", r#""foo`nb`ta`r\`0``r""#),
|
||||
("foo\x02", r#""foo`u{0002}""#),
|
||||
(r#"'$''"#, r#"'''$'''''"#),
|
||||
]);
|
||||
verify_quote(&[(
|
||||
OsString::from_wide(&[b'x' as u16, 0xD800]),
|
||||
r#""x`u{D800}""#,
|
||||
)]);
|
||||
verify_maybe(&[
|
||||
("-x", "'-x'"),
|
||||
("a,b", "'a,b'"),
|
||||
("a\\b", "a\\b"),
|
||||
("}", "'}'"),
|
||||
]);
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
#[test]
|
||||
fn test_utf8_iter() {
|
||||
type ByteStr = &'static [u8];
|
||||
type Chunk = Result<&'static str, u8>;
|
||||
const CASES: &[(ByteStr, &[Chunk])] = &[
|
||||
(b"", &[]),
|
||||
(b"hello", &[Ok("hello")]),
|
||||
// Immediately invalid
|
||||
(b"\xFF", &[Err(b'\xFF')]),
|
||||
// Incomplete UTF-8
|
||||
(b"\xC2", &[Err(b'\xC2')]),
|
||||
(b"\xF4\x8F", &[Err(b'\xF4'), Err(b'\x8F')]),
|
||||
(b"\xFF\xFF", &[Err(b'\xFF'), Err(b'\xFF')]),
|
||||
(b"hello\xC2", &[Ok("hello"), Err(b'\xC2')]),
|
||||
(b"\xFFhello", &[Err(b'\xFF'), Ok("hello")]),
|
||||
(b"\xFF\xC2hello", &[Err(b'\xFF'), Err(b'\xC2'), Ok("hello")]),
|
||||
(b"foo\xFFbar", &[Ok("foo"), Err(b'\xFF'), Ok("bar")]),
|
||||
(
|
||||
b"foo\xF4\x8Fbar",
|
||||
&[Ok("foo"), Err(b'\xF4'), Err(b'\x8F'), Ok("bar")],
|
||||
),
|
||||
(
|
||||
b"foo\xFF\xC2bar",
|
||||
&[Ok("foo"), Err(b'\xFF'), Err(b'\xC2'), Ok("bar")],
|
||||
),
|
||||
];
|
||||
for &(case, expected) in CASES {
|
||||
assert_eq!(
|
||||
from_utf8_iter(case).collect::<Vec<_>>().as_slice(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,18 +113,12 @@ fn test_wrap_bad_arg() {
|
|||
|
||||
#[test]
|
||||
fn test_base32_extra_operand() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
// Expect a failure when multiple files are specified.
|
||||
ts.ucmd()
|
||||
new_ucmd!()
|
||||
.arg("a.txt")
|
||||
.arg("b.txt")
|
||||
.fails()
|
||||
.stderr_only(format!(
|
||||
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
.usage_error("extra operand 'b.txt'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -95,18 +95,12 @@ fn test_wrap_bad_arg() {
|
|||
|
||||
#[test]
|
||||
fn test_base64_extra_operand() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
// Expect a failure when multiple files are specified.
|
||||
ts.ucmd()
|
||||
new_ucmd!()
|
||||
.arg("a.txt")
|
||||
.arg("b.txt")
|
||||
.fails()
|
||||
.stderr_only(format!(
|
||||
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
.usage_error("extra operand 'b.txt'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -114,12 +114,7 @@ fn test_no_args() {
|
|||
|
||||
#[test]
|
||||
fn test_no_args_output() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
ts.ucmd().fails().stderr_is(&format!(
|
||||
"{0}: missing operand\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
new_ucmd!().fails().usage_error("missing operand");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -129,12 +124,10 @@ fn test_too_many_args() {
|
|||
|
||||
#[test]
|
||||
fn test_too_many_args_output() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
ts.ucmd().args(&["a", "b", "c"]).fails().stderr_is(format!(
|
||||
"{0}: extra operand 'c'\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
new_ucmd!()
|
||||
.args(&["a", "b", "c"])
|
||||
.fails()
|
||||
.usage_error("extra operand 'c'");
|
||||
}
|
||||
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
|
|
|
@ -563,17 +563,13 @@ fn test_cp_backup_off() {
|
|||
|
||||
#[test]
|
||||
fn test_cp_backup_no_clobber_conflicting_options() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
ts.ucmd()
|
||||
new_ucmd!()
|
||||
.arg("--backup")
|
||||
.arg("--no-clobber")
|
||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||
.fails().stderr_is(&format!(
|
||||
"{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
.fails()
|
||||
.usage_error("options --backup and --no-clobber are mutually exclusive");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -108,6 +108,15 @@ fn test_ignore_environment() {
|
|||
scene.ucmd().arg("-").run().no_stdout();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_name() {
|
||||
new_ucmd!()
|
||||
.arg("-i")
|
||||
.arg("=xyz")
|
||||
.run()
|
||||
.stderr_only("env: warning: no name specified for value 'xyz'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_delimiter() {
|
||||
let out = new_ucmd!()
|
||||
|
|
|
@ -15,15 +15,10 @@ fn test_more_dir_arg() {
|
|||
// Maybe we could capture the error, i.e. "Device not found" in that case
|
||||
// but I am leaving this for later
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let result = ts.ucmd().arg(".").run();
|
||||
result.failure();
|
||||
let expected_error_message = &format!(
|
||||
"{0}: '.' is a directory.\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
);
|
||||
assert_eq!(result.stderr_str().trim(), expected_error_message);
|
||||
new_ucmd!()
|
||||
.arg(".")
|
||||
.fails()
|
||||
.usage_error("'.' is a directory.");
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -522,17 +522,13 @@ fn test_mv_backup_off() {
|
|||
|
||||
#[test]
|
||||
fn test_mv_backup_no_clobber_conflicting_options() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
ts.ucmd().arg("--backup")
|
||||
new_ucmd!()
|
||||
.arg("--backup")
|
||||
.arg("--no-clobber")
|
||||
.arg("file1")
|
||||
.arg("file2")
|
||||
.fails()
|
||||
.stderr_is(&format!("{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
.usage_error("options --backup and --no-clobber are mutually exclusive");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -22,15 +22,10 @@ fn test_negative_adjustment() {
|
|||
|
||||
#[test]
|
||||
fn test_adjustment_with_no_command_should_error() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
ts.ucmd()
|
||||
.args(&["-n", "19"])
|
||||
.run()
|
||||
.stderr_is(&format!("{0}: A command must be given with an adjustment.\nTry '{1} {0} --help' for more information.\n",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
new_ucmd!()
|
||||
.args(&["-n", "19"])
|
||||
.fails()
|
||||
.usage_error("A command must be given with an adjustment.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -7,25 +7,25 @@ fn test_hex_rejects_sign_after_identifier() {
|
|||
.args(&["0x-123ABC"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid hexadecimal argument: '0x-123ABC'")
|
||||
.stderr_contains("invalid floating point argument: '0x-123ABC'")
|
||||
.stderr_contains("for more information.");
|
||||
new_ucmd!()
|
||||
.args(&["0x+123ABC"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid hexadecimal argument: '0x+123ABC'")
|
||||
.stderr_contains("invalid floating point argument: '0x+123ABC'")
|
||||
.stderr_contains("for more information.");
|
||||
new_ucmd!()
|
||||
.args(&["-0x-123ABC"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid hexadecimal argument: '-0x-123ABC'")
|
||||
.stderr_contains("invalid floating point argument: '-0x-123ABC'")
|
||||
.stderr_contains("for more information.");
|
||||
new_ucmd!()
|
||||
.args(&["-0x+123ABC"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid hexadecimal argument: '-0x+123ABC'")
|
||||
.stderr_contains("invalid floating point argument: '-0x+123ABC'")
|
||||
.stderr_contains("for more information.");
|
||||
}
|
||||
|
||||
|
@ -60,30 +60,24 @@ fn test_hex_identifier_in_wrong_place() {
|
|||
.args(&["1234ABCD0x"])
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid hexadecimal argument: '1234ABCD0x'")
|
||||
.stderr_contains("invalid floating point argument: '1234ABCD0x'")
|
||||
.stderr_contains("for more information.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rejects_nan() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
ts.ucmd().args(&["NaN"]).fails().stderr_only(format!(
|
||||
"{0}: invalid 'not-a-number' argument: 'NaN'\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
new_ucmd!()
|
||||
.arg("NaN")
|
||||
.fails()
|
||||
.usage_error("invalid 'not-a-number' argument: 'NaN'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rejects_non_floats() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
ts.ucmd().args(&["foo"]).fails().stderr_only(&format!(
|
||||
"{0}: invalid floating point argument: 'foo'\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
new_ucmd!()
|
||||
.arg("foo")
|
||||
.fails()
|
||||
.usage_error("invalid floating point argument: 'foo'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -479,6 +473,15 @@ fn test_width_decimal_scientific_notation_trailing_zeros_increment() {
|
|||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_width_negative_scientific_notation() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-1e-3", "1"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.001\n00.999\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
/// Test that trailing zeros in the end argument do not contribute to width.
|
||||
#[test]
|
||||
fn test_width_decimal_scientific_notation_trailing_zeros_end() {
|
||||
|
@ -521,6 +524,22 @@ fn test_inf() {
|
|||
run(&["inf"], b"1\n2\n3\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inf_width() {
|
||||
run(
|
||||
&["-w", "1.000", "inf", "inf"],
|
||||
b"1.000\n inf\n inf\n inf\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neg_inf_width() {
|
||||
run(
|
||||
&["-w", "1.000", "-inf", "-inf"],
|
||||
b"1.000\n -inf\n -inf\n -inf\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_leading_whitespace() {
|
||||
new_ucmd!()
|
||||
|
@ -538,9 +557,65 @@ fn test_trailing_whitespace_error() {
|
|||
new_ucmd!()
|
||||
.arg("1 ")
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("seq: invalid floating point argument: '1 '")
|
||||
// FIXME The second line of the error message is "Try 'seq
|
||||
// --help' for more information."
|
||||
.stderr_contains("for more information.");
|
||||
.usage_error("invalid floating point argument: '1 '");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_zero_int_start_float_increment() {
|
||||
new_ucmd!()
|
||||
.args(&["-0", "0.1", "0.1"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.0\n0.1\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_float_precision_increment() {
|
||||
new_ucmd!()
|
||||
.args(&["999", "0.1", "1000.1"])
|
||||
.succeeds()
|
||||
.stdout_is(
|
||||
"999.0
|
||||
999.1
|
||||
999.2
|
||||
999.3
|
||||
999.4
|
||||
999.5
|
||||
999.6
|
||||
999.7
|
||||
999.8
|
||||
999.9
|
||||
1000.0
|
||||
1000.1
|
||||
",
|
||||
)
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
/// Test for floating point precision issues.
|
||||
#[test]
|
||||
fn test_negative_increment_decimal() {
|
||||
new_ucmd!()
|
||||
.args(&["0.1", "-0.1", "-0.2"])
|
||||
.succeeds()
|
||||
.stdout_is("0.1\n0.0\n-0.1\n-0.2\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_not_first() {
|
||||
new_ucmd!()
|
||||
.args(&["-w", "-0.1", "0.1", "0.1"])
|
||||
.succeeds()
|
||||
.stdout_is("-0.1\n00.0\n00.1\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rounding_end() {
|
||||
new_ucmd!()
|
||||
.args(&["1", "-1", "0.1"])
|
||||
.succeeds()
|
||||
.stdout_is("1\n")
|
||||
.no_stderr();
|
||||
}
|
||||
|
|
|
@ -53,16 +53,10 @@ fn test_stdbuf_trailing_var_arg() {
|
|||
#[cfg(not(target_os = "windows"))]
|
||||
#[test]
|
||||
fn test_stdbuf_line_buffering_stdin_fails() {
|
||||
let ts = TestScenario::new(util_name!());
|
||||
|
||||
ts.ucmd()
|
||||
new_ucmd!()
|
||||
.args(&["-i", "L", "head"])
|
||||
.fails()
|
||||
.stderr_is(&format!(
|
||||
"{0}: line buffering stdin is meaningless\nTry '{1} {0} --help' for more information.",
|
||||
ts.util_name,
|
||||
ts.bin_path.to_string_lossy()
|
||||
));
|
||||
.usage_error("line buffering stdin is meaningless");
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
|
|
|
@ -62,6 +62,10 @@ fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_p
|
|||
/// within a struct which has convenience assertion functions about those outputs
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CmdResult {
|
||||
/// bin_path provided by `TestScenario` or `UCommand`
|
||||
bin_path: String,
|
||||
/// util_name provided by `TestScenario` or `UCommand`
|
||||
util_name: Option<String>,
|
||||
//tmpd is used for convenience functions for asserts against fixtures
|
||||
tmpd: Option<Rc<TempDir>>,
|
||||
/// exit status for command (if there is one)
|
||||
|
@ -77,6 +81,8 @@ pub struct CmdResult {
|
|||
|
||||
impl CmdResult {
|
||||
pub fn new(
|
||||
bin_path: String,
|
||||
util_name: Option<String>,
|
||||
tmpd: Option<Rc<TempDir>>,
|
||||
code: Option<i32>,
|
||||
success: bool,
|
||||
|
@ -84,6 +90,8 @@ impl CmdResult {
|
|||
stderr: &[u8],
|
||||
) -> CmdResult {
|
||||
CmdResult {
|
||||
bin_path,
|
||||
util_name,
|
||||
tmpd,
|
||||
code,
|
||||
success,
|
||||
|
@ -357,6 +365,23 @@ impl CmdResult {
|
|||
self
|
||||
}
|
||||
|
||||
/// asserts that
|
||||
/// 1. the command resulted in stderr stream output that equals the
|
||||
/// the following format when both are trimmed of trailing whitespace
|
||||
/// `"{util_name}: {msg}\nTry '{bin_path} {util_name} --help' for more information."`
|
||||
/// This the expected format when a UUsageError is returned or when show_error! is called
|
||||
/// `msg` should be the same as the one provided to UUsageError::new or show_error!
|
||||
///
|
||||
/// 2. the command resulted in empty (zero-length) stdout stream output
|
||||
pub fn usage_error<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
|
||||
self.stderr_only(format!(
|
||||
"{0}: {2}\nTry '{1} {0} --help' for more information.",
|
||||
self.util_name.as_ref().unwrap(), // This shouldn't be called using a normal command
|
||||
self.bin_path,
|
||||
msg.as_ref()
|
||||
))
|
||||
}
|
||||
|
||||
pub fn stdout_contains<T: AsRef<str>>(&self, cmp: T) -> &CmdResult {
|
||||
assert!(
|
||||
self.stdout_str().contains(cmp.as_ref()),
|
||||
|
@ -728,20 +753,12 @@ impl AtPath {
|
|||
// Source:
|
||||
// http://stackoverflow.com/questions/31439011/getfinalpathnamebyhandle-without-prepended
|
||||
let prefix = "\\\\?\\";
|
||||
// FixME: replace ...
|
||||
#[allow(clippy::manual_strip)]
|
||||
if s.starts_with(prefix) {
|
||||
String::from(&s[prefix.len()..])
|
||||
|
||||
if let Some(stripped) = s.strip_prefix(prefix) {
|
||||
String::from(stripped)
|
||||
} else {
|
||||
s
|
||||
}
|
||||
// ... with ...
|
||||
// if let Some(stripped) = s.strip_prefix(prefix) {
|
||||
// String::from(stripped)
|
||||
// } else {
|
||||
// s
|
||||
// }
|
||||
// ... when using MSRV with stabilized `strip_prefix()`
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -788,31 +805,36 @@ impl TestScenario {
|
|||
/// Returns builder for invoking the target uutils binary. Paths given are
|
||||
/// treated relative to the environment's unique temporary test directory.
|
||||
pub fn ucmd(&self) -> UCommand {
|
||||
let mut cmd = self.cmd(&self.bin_path);
|
||||
cmd.arg(&self.util_name);
|
||||
cmd
|
||||
self.composite_cmd(&self.bin_path, &self.util_name, true)
|
||||
}
|
||||
|
||||
/// Returns builder for invoking the target uutils binary. Paths given are
|
||||
/// treated relative to the environment's unique temporary test directory.
|
||||
pub fn composite_cmd<S: AsRef<OsStr>, T: AsRef<OsStr>>(
|
||||
&self,
|
||||
bin: S,
|
||||
util_name: T,
|
||||
env_clear: bool,
|
||||
) -> UCommand {
|
||||
UCommand::new_from_tmp(bin, Some(util_name), self.tmpd.clone(), env_clear)
|
||||
}
|
||||
|
||||
/// Returns builder for invoking any system command. Paths given are treated
|
||||
/// relative to the environment's unique temporary test directory.
|
||||
pub fn cmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
||||
UCommand::new_from_tmp(bin, self.tmpd.clone(), true)
|
||||
UCommand::new_from_tmp::<S, S>(bin, None, self.tmpd.clone(), true)
|
||||
}
|
||||
|
||||
/// Returns builder for invoking any uutils command. Paths given are treated
|
||||
/// relative to the environment's unique temporary test directory.
|
||||
pub fn ccmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
||||
let mut cmd = self.cmd(&self.bin_path);
|
||||
cmd.arg(bin);
|
||||
cmd
|
||||
self.composite_cmd(&self.bin_path, bin, true)
|
||||
}
|
||||
|
||||
// different names are used rather than an argument
|
||||
// because the need to keep the environment is exceedingly rare.
|
||||
pub fn ucmd_keepenv(&self) -> UCommand {
|
||||
let mut cmd = self.cmd_keepenv(&self.bin_path);
|
||||
cmd.arg(&self.util_name);
|
||||
cmd
|
||||
self.composite_cmd(&self.bin_path, &self.util_name, false)
|
||||
}
|
||||
|
||||
/// Returns builder for invoking any system command. Paths given are treated
|
||||
|
@ -820,7 +842,7 @@ impl TestScenario {
|
|||
/// Differs from the builder returned by `cmd` in that `cmd_keepenv` does not call
|
||||
/// `Command::env_clear` (Clears the entire environment map for the child process.)
|
||||
pub fn cmd_keepenv<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
||||
UCommand::new_from_tmp(bin, self.tmpd.clone(), false)
|
||||
UCommand::new_from_tmp::<S, S>(bin, None, self.tmpd.clone(), false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -834,6 +856,8 @@ impl TestScenario {
|
|||
pub struct UCommand {
|
||||
pub raw: Command,
|
||||
comm_string: String,
|
||||
bin_path: String,
|
||||
util_name: Option<String>,
|
||||
tmpd: Option<Rc<TempDir>>,
|
||||
has_run: bool,
|
||||
ignore_stdin_write_error: bool,
|
||||
|
@ -846,12 +870,20 @@ pub struct UCommand {
|
|||
}
|
||||
|
||||
impl UCommand {
|
||||
pub fn new<T: AsRef<OsStr>, U: AsRef<OsStr>>(arg: T, curdir: U, env_clear: bool) -> UCommand {
|
||||
UCommand {
|
||||
pub fn new<T: AsRef<OsStr>, S: AsRef<OsStr>, U: AsRef<OsStr>>(
|
||||
bin_path: T,
|
||||
util_name: Option<S>,
|
||||
curdir: U,
|
||||
env_clear: bool,
|
||||
) -> UCommand {
|
||||
let bin_path = bin_path.as_ref();
|
||||
let util_name = util_name.as_ref().map(|un| un.as_ref());
|
||||
|
||||
let mut ucmd = UCommand {
|
||||
tmpd: None,
|
||||
has_run: false,
|
||||
raw: {
|
||||
let mut cmd = Command::new(arg.as_ref());
|
||||
let mut cmd = Command::new(bin_path);
|
||||
cmd.current_dir(curdir.as_ref());
|
||||
if env_clear {
|
||||
if cfg!(windows) {
|
||||
|
@ -871,7 +903,9 @@ impl UCommand {
|
|||
}
|
||||
cmd
|
||||
},
|
||||
comm_string: String::from(arg.as_ref().to_str().unwrap()),
|
||||
comm_string: String::from(bin_path.to_str().unwrap()),
|
||||
bin_path: bin_path.to_str().unwrap().to_string(),
|
||||
util_name: util_name.map(|un| un.to_str().unwrap().to_string()),
|
||||
ignore_stdin_write_error: false,
|
||||
bytes_into_stdin: None,
|
||||
stdin: None,
|
||||
|
@ -879,12 +913,23 @@ impl UCommand {
|
|||
stderr: None,
|
||||
#[cfg(target_os = "linux")]
|
||||
limits: vec![],
|
||||
};
|
||||
|
||||
if let Some(un) = util_name {
|
||||
ucmd.arg(un);
|
||||
}
|
||||
|
||||
ucmd
|
||||
}
|
||||
|
||||
pub fn new_from_tmp<T: AsRef<OsStr>>(arg: T, tmpd: Rc<TempDir>, env_clear: bool) -> UCommand {
|
||||
pub fn new_from_tmp<T: AsRef<OsStr>, S: AsRef<OsStr>>(
|
||||
bin_path: T,
|
||||
util_name: Option<S>,
|
||||
tmpd: Rc<TempDir>,
|
||||
env_clear: bool,
|
||||
) -> UCommand {
|
||||
let tmpd_path_buf = String::from(&(*tmpd.as_ref().path().to_str().unwrap()));
|
||||
let mut ucmd: UCommand = UCommand::new(arg.as_ref(), tmpd_path_buf, env_clear);
|
||||
let mut ucmd: UCommand = UCommand::new(bin_path, util_name, tmpd_path_buf, env_clear);
|
||||
ucmd.tmpd = Some(tmpd);
|
||||
ucmd
|
||||
}
|
||||
|
@ -1029,6 +1074,8 @@ impl UCommand {
|
|||
let prog = self.run_no_wait().wait_with_output().unwrap();
|
||||
|
||||
CmdResult {
|
||||
bin_path: self.bin_path.clone(),
|
||||
util_name: self.util_name.clone(),
|
||||
tmpd: self.tmpd.clone(),
|
||||
code: prog.status.code(),
|
||||
success: prog.status.success(),
|
||||
|
@ -1276,6 +1323,8 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
|
|||
};
|
||||
|
||||
Ok(CmdResult::new(
|
||||
ts.bin_path.as_os_str().to_str().unwrap().to_string(),
|
||||
Some(ts.util_name.clone()),
|
||||
Some(result.tmpd()),
|
||||
Some(result.code()),
|
||||
result.succeeded(),
|
||||
|
@ -1293,6 +1342,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_code_is() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: Some(32),
|
||||
success: false,
|
||||
|
@ -1306,6 +1357,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_code_is_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: Some(32),
|
||||
success: false,
|
||||
|
@ -1318,6 +1371,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_failure() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: false,
|
||||
|
@ -1331,6 +1386,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_failure_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1343,6 +1400,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_success() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1356,6 +1415,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_success_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: false,
|
||||
|
@ -1368,6 +1429,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_no_stderr_output() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1382,6 +1445,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_no_stderr_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1396,6 +1461,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_no_stdout_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1409,6 +1476,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_std_does_not_contain() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1423,6 +1492,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_stdout_does_not_contain_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1437,6 +1508,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_stderr_does_not_contain_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1450,6 +1523,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_stdout_matches() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1466,6 +1541,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_stdout_matches_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1481,6 +1558,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_stdout_not_matches_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1495,6 +1574,8 @@ mod tests {
|
|||
#[test]
|
||||
fn test_normalized_newlines_stdout_is() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
@ -1511,6 +1592,8 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_normalized_newlines_stdout_is_fail() {
|
||||
let res = CmdResult {
|
||||
bin_path: "".into(),
|
||||
util_name: None,
|
||||
tmpd: None,
|
||||
code: None,
|
||||
success: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue