mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-08-01 05:27:45 +00:00
Merge branch 'master' of github.com:uutils/coreutils into hbina-tr-reimplement-expansion
This commit is contained in:
commit
da46cc8015
23 changed files with 1769 additions and 1138 deletions
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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -76,6 +76,17 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
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]]
|
[[package]]
|
||||||
name = "binary-heap-plus"
|
name = "binary-heap-plus"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
@ -101,7 +112,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"peeking_take_while",
|
"peeking_take_while",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"regex",
|
"regex",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"shlex",
|
"shlex",
|
||||||
|
@ -149,12 +160,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "0.2.16"
|
version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
|
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -166,9 +177,9 @@ checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byte-unit"
|
name = "byte-unit"
|
||||||
version = "4.0.12"
|
version = "4.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8"
|
checksum = "956ffc5b0ec7d7a6949e3f21fd63ba5af4cffdc2ba1e0b7bf62b481458c4ae7f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"utf8-width",
|
"utf8-width",
|
||||||
]
|
]
|
||||||
|
@ -187,9 +198,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.69"
|
version = "1.0.71"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cexpr"
|
name = "cexpr"
|
||||||
|
@ -227,9 +238,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clang-sys"
|
name = "clang-sys"
|
||||||
version = "1.2.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c"
|
checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -479,7 +490,7 @@ dependencies = [
|
||||||
"if_rust_version",
|
"if_rust_version",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -560,9 +571,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.20.0"
|
version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d"
|
checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
|
@ -576,30 +587,30 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm_winapi"
|
name = "crossterm_winapi"
|
||||||
version = "0.8.0"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507"
|
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ctor"
|
name = "ctor"
|
||||||
version = "0.1.20"
|
version = "0.1.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
|
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ctrlc"
|
name = "ctrlc"
|
||||||
version = "3.1.9"
|
version = "3.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a"
|
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nix 0.20.0",
|
"nix 0.23.0",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -642,7 +653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -672,9 +683,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dns-lookup"
|
name = "dns-lookup"
|
||||||
version = "1.0.5"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "093d88961fd18c4ecacb8c80cd0b356463ba941ba11e0e01f9cf5271380b79dc"
|
checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -792,9 +803,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gcd"
|
name = "gcd"
|
||||||
version = "2.0.1"
|
version = "2.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c7cd301bf2ab11ae4e5bdfd79c221d97a25e46c089144a62ee9d09cb32d2b92"
|
checksum = "6c8763772808ee8fe3128f0fc424bed6d9942293fddbcfd595ecfa58a81fe00b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
|
@ -858,9 +869,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "1.7.1"
|
version = "1.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
|
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
|
@ -920,9 +931,9 @@ checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.10"
|
version = "0.1.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
|
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
@ -975,15 +986,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.101"
|
version = "0.2.107"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
|
checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
|
@ -991,9 +1002,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.4"
|
version = "0.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
|
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
@ -1022,12 +1033,6 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "maybe-uninit"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md5"
|
name = "md5"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
|
@ -1045,9 +1050,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memmap2"
|
name = "memmap2"
|
||||||
|
@ -1075,9 +1080,9 @@ checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.7.7"
|
version = "0.7.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
|
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1132,6 +1137,19 @@ dependencies = [
|
||||||
"memoffset",
|
"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]]
|
[[package]]
|
||||||
name = "nodrop"
|
name = "nodrop"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
|
@ -1146,7 +1164,7 @@ checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
"funty",
|
"funty",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1172,9 +1190,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535"
|
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
|
@ -1228,7 +1246,7 @@ checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1282,6 +1300,15 @@ dependencies = [
|
||||||
"hashbrown",
|
"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]]
|
[[package]]
|
||||||
name = "ouroboros"
|
name = "ouroboros"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
|
@ -1302,7 +1329,7 @@ dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"proc-macro-error",
|
"proc-macro-error",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1317,9 +1344,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.11.1"
|
version = "0.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"instant",
|
"instant",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
|
@ -1328,15 +1355,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.8.3"
|
version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"instant",
|
"instant",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec 1.6.1",
|
"smallvec",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1367,9 +1394,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.19"
|
version = "0.3.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platform-info"
|
name = "platform-info"
|
||||||
|
@ -1383,9 +1410,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.10"
|
version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "pretty_assertions"
|
||||||
|
@ -1401,9 +1428,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92"
|
checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"toml",
|
"toml",
|
||||||
|
@ -1417,7 +1444,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-error-attr",
|
"proc-macro-error-attr",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
@ -1429,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1441,9 +1468,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.28"
|
version = "1.0.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
|
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid 0.2.2",
|
"unicode-xid 0.2.2",
|
||||||
]
|
]
|
||||||
|
@ -1480,9 +1507,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.9"
|
version = "1.0.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -1668,7 +1695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1695,9 +1722,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "retain_mut"
|
name = "retain_mut"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b"
|
checksum = "448296241d034b96c11173591deaa1302f2c17b56092106c1f92c1bc0183a8c9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rlimit"
|
name = "rlimit"
|
||||||
|
@ -1742,9 +1769,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selinux"
|
name = "selinux"
|
||||||
version = "0.2.3"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608"
|
checksum = "09715d6b4356e916047e61e4dce40a67ac93036851957b91713d3d9c282d1548"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1782,7 +1809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1819,15 +1846,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
|
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.9"
|
version = "0.3.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39"
|
checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
|
@ -1855,18 +1882,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "0.6.14"
|
version = "1.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
|
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
|
||||||
dependencies = [
|
|
||||||
"maybe-uninit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "smallvec"
|
|
||||||
version = "1.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smawk"
|
name = "smawk"
|
||||||
|
@ -1876,11 +1894,10 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.3.19"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
|
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
|
||||||
"libc",
|
"libc",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
@ -1911,18 +1928,18 @@ checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.74"
|
version = "1.0.81"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
|
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"unicode-xid 0.2.2",
|
"unicode-xid 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2033,21 +2050,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.26"
|
version = "1.0.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
|
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.26"
|
version = "1.0.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
|
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2072,9 +2089,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.13.0"
|
version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-linebreak"
|
name = "unicode-linebreak"
|
||||||
|
@ -2093,9 +2110,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
|
@ -2316,7 +2333,7 @@ dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bstr",
|
"bstr",
|
||||||
"clap",
|
"clap",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
@ -2441,7 +2458,7 @@ dependencies = [
|
||||||
"paste",
|
"paste",
|
||||||
"quickcheck",
|
"quickcheck",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"smallvec 0.6.14",
|
"smallvec",
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
@ -2494,7 +2511,7 @@ dependencies = [
|
||||||
"hex",
|
"hex",
|
||||||
"libc",
|
"libc",
|
||||||
"md5",
|
"md5",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
"sha1",
|
"sha1",
|
||||||
|
@ -2509,7 +2526,7 @@ name = "uu_head"
|
||||||
version = "0.0.8"
|
version = "0.0.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
@ -2713,7 +2730,7 @@ dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
"uucore",
|
"uucore",
|
||||||
|
@ -2831,7 +2848,7 @@ dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
"uucore",
|
"uucore",
|
||||||
|
@ -2914,6 +2931,7 @@ dependencies = [
|
||||||
name = "uu_seq"
|
name = "uu_seq"
|
||||||
version = "0.0.8"
|
version = "0.0.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bigdecimal",
|
||||||
"clap",
|
"clap",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
@ -2961,7 +2979,7 @@ dependencies = [
|
||||||
"ctrlc",
|
"ctrlc",
|
||||||
"fnv",
|
"fnv",
|
||||||
"itertools 0.10.1",
|
"itertools 0.10.1",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"ouroboros",
|
"ouroboros",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
@ -3036,7 +3054,7 @@ name = "uu_tac"
|
||||||
version = "0.0.8"
|
version = "0.0.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"memchr 2.4.0",
|
"memchr 2.4.1",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"regex",
|
"regex",
|
||||||
"uucore",
|
"uucore",
|
||||||
|
@ -3265,6 +3283,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"nix 0.20.0",
|
"nix 0.20.0",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"os_display",
|
||||||
"termion",
|
"termion",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
|
@ -3279,7 +3298,7 @@ name = "uucore_procs"
|
||||||
version = "0.0.7"
|
version = "0.0.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote 1.0.9",
|
"quote 1.0.10",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3402,6 +3421,6 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "z85"
|
name = "z85"
|
||||||
version = "3.0.3"
|
version = "3.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ac8b56e4f9906a4ef5412875e9ce448364023335cec645fd457ecf51d4f2781"
|
checksum = "af896e93db81340b74b65f74276a99b210c086f3d34ed0abf433182a462af856"
|
||||||
|
|
|
@ -15,13 +15,13 @@ edition = "2018"
|
||||||
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
|
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "2.33", features = ["wrap_help"] }
|
||||||
coz = { version = "0.1.3", optional = true }
|
coz = { version = "0.1.3", optional = true }
|
||||||
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
|
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
|
||||||
rand = { version = "0.7", features = ["small_rng"] }
|
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 = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
|
||||||
uucore_procs = { version=">=0.0.7", package = "uucore_procs", path = "../../uucore_procs" }
|
uucore_procs = { version=">=0.0.7", package = "uucore_procs", path = "../../uucore_procs" }
|
||||||
clap = { version = "2.33", features = ["wrap_help"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
paste = "0.1.18"
|
paste = "0.1.18"
|
||||||
|
|
|
@ -1111,6 +1111,9 @@ only ignore '.' and '..'.",
|
||||||
.long(options::COLOR)
|
.long(options::COLOR)
|
||||||
.help("Color output based on file type.")
|
.help("Color output based on file type.")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.possible_values(&[
|
||||||
|
"always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none",
|
||||||
|
])
|
||||||
.require_equals(true)
|
.require_equals(true)
|
||||||
.min_values(0),
|
.min_values(0),
|
||||||
)
|
)
|
||||||
|
|
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]
|
[package]
|
||||||
name = "uu_seq"
|
name = "uu_seq"
|
||||||
version = "0.0.8"
|
version = "0.0.8"
|
||||||
|
@ -15,6 +16,7 @@ edition = "2018"
|
||||||
path = "src/seq.rs"
|
path = "src/seq.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bigdecimal = "0.3"
|
||||||
clap = { version = "2.33", features = ["wrap_help"] }
|
clap = { version = "2.33", features = ["wrap_help"] }
|
||||||
num-bigint = "0.4.0"
|
num-bigint = "0.4.0"
|
||||||
num-traits = "0.2.14"
|
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: Make -w flag work with decimals
|
||||||
// TODO: Support -f flag
|
// 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]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
|
||||||
use clap::{crate_version, App, AppSettings, Arg};
|
use clap::{crate_version, App, AppSettings, Arg};
|
||||||
use num_bigint::BigInt;
|
|
||||||
use num_traits::One;
|
|
||||||
use num_traits::Zero;
|
use num_traits::Zero;
|
||||||
use num_traits::{Num, ToPrimitive};
|
|
||||||
use std::cmp;
|
|
||||||
use std::io::{stdout, ErrorKind, Write};
|
use std::io::{stdout, ErrorKind, Write};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
mod digits;
|
mod extendedbigdecimal;
|
||||||
use crate::digits::num_fractional_digits;
|
mod extendedbigint;
|
||||||
use crate::digits::num_integral_digits;
|
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;
|
use uucore::display::Quotable;
|
||||||
|
|
||||||
|
@ -43,124 +44,55 @@ struct SeqOptions {
|
||||||
widths: bool,
|
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.
|
/// A range of integers.
|
||||||
///
|
///
|
||||||
/// The elements are (first, increment, last).
|
/// 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).
|
/// 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 {
|
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
let usage = usage();
|
let usage = usage();
|
||||||
|
@ -174,53 +106,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
widths: matches.is_present(OPT_WIDTHS),
|
widths: matches.is_present(OPT_WIDTHS),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut largest_dec = 0;
|
|
||||||
let mut padding = 0;
|
|
||||||
let first = if numbers.len() > 1 {
|
let first = if numbers.len() > 1 {
|
||||||
let slice = numbers[0];
|
let slice = numbers[0];
|
||||||
largest_dec = num_fractional_digits(slice).unwrap_or_else(|_| {
|
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
|
||||||
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())
|
|
||||||
} else {
|
} else {
|
||||||
Number::BigInt(BigInt::one())
|
PreciseNumber::one()
|
||||||
};
|
};
|
||||||
let increment = if numbers.len() > 2 {
|
let increment = if numbers.len() > 2 {
|
||||||
let slice = numbers[1];
|
let slice = numbers[1];
|
||||||
let dec = num_fractional_digits(slice).unwrap_or_else(|_| {
|
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
|
||||||
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())
|
|
||||||
} else {
|
} else {
|
||||||
Number::BigInt(BigInt::one())
|
PreciseNumber::one()
|
||||||
};
|
};
|
||||||
if increment.is_zero() {
|
if increment.is_zero() {
|
||||||
show_error!(
|
show_error!(
|
||||||
|
@ -230,54 +126,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
);
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
let last: Number = {
|
let last: PreciseNumber = {
|
||||||
let slice = numbers[numbers.len() - 1];
|
let slice = numbers[numbers.len() - 1];
|
||||||
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
|
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
|
||||||
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())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_negative_zero_f64 = |x: f64| x == -0.0 && x.is_sign_negative() && largest_dec == 0;
|
let padding = first
|
||||||
let result = match (first, last, increment) {
|
.num_integral_digits
|
||||||
// For example, `seq -0 1 2` or `seq -0 1 2.0`.
|
.max(increment.num_integral_digits)
|
||||||
(Number::MinusZero, last, Number::BigInt(increment)) => print_seq_integers(
|
.max(last.num_integral_digits);
|
||||||
(BigInt::zero(), increment, last.into_bigint()),
|
let largest_dec = first
|
||||||
options.separator,
|
.num_fractional_digits
|
||||||
options.terminator,
|
.max(increment.num_fractional_digits);
|
||||||
options.widths,
|
|
||||||
padding,
|
let result = match (first.number, increment.number, last.number) {
|
||||||
true,
|
(Number::Int(first), Number::Int(increment), last) => {
|
||||||
),
|
let last = last.round_towards(&first);
|
||||||
// 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) => {
|
|
||||||
print_seq_integers(
|
print_seq_integers(
|
||||||
(BigInt::zero(), increment, last.into_bigint()),
|
(first, increment, last),
|
||||||
options.separator,
|
options.separator,
|
||||||
options.terminator,
|
options.terminator,
|
||||||
options.widths,
|
options.widths,
|
||||||
padding,
|
padding,
|
||||||
true,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// For example, `seq 0 1 2` or `seq 0 1 2.0`.
|
(first, increment, last) => print_seq(
|
||||||
(Number::BigInt(first), last, Number::BigInt(increment)) => print_seq_integers(
|
(
|
||||||
(first, increment, last.into_bigint()),
|
first.into_extended_big_decimal(),
|
||||||
options.separator,
|
increment.into_extended_big_decimal(),
|
||||||
options.terminator,
|
last.into_extended_big_decimal(),
|
||||||
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()),
|
|
||||||
largest_dec,
|
largest_dec,
|
||||||
options.separator,
|
options.separator,
|
||||||
options.terminator,
|
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() {
|
if increment >= &T::zero() {
|
||||||
next > last
|
next > last
|
||||||
} else {
|
} 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
|
/// Floating point based code path
|
||||||
fn print_seq(
|
fn print_seq(
|
||||||
range: RangeF64,
|
range: RangeFloat,
|
||||||
largest_dec: usize,
|
largest_dec: usize,
|
||||||
separator: String,
|
separator: String,
|
||||||
terminator: String,
|
terminator: String,
|
||||||
|
@ -349,30 +291,23 @@ fn print_seq(
|
||||||
let stdout = stdout();
|
let stdout = stdout();
|
||||||
let mut stdout = stdout.lock();
|
let mut stdout = stdout.lock();
|
||||||
let (first, increment, last) = range;
|
let (first, increment, last) = range;
|
||||||
let mut i = 0isize;
|
let mut value = first;
|
||||||
let is_first_minus_zero = first == -0.0 && first.is_sign_negative();
|
|
||||||
let mut value = first + i as f64 * increment;
|
|
||||||
let padding = if pad { padding + 1 + largest_dec } else { 0 };
|
let padding = if pad { padding + 1 + largest_dec } else { 0 };
|
||||||
let mut is_first_iteration = true;
|
let mut is_first_iteration = true;
|
||||||
while !done_printing(&value, &increment, &last) {
|
while !done_printing(&value, &increment, &last) {
|
||||||
if !is_first_iteration {
|
if !is_first_iteration {
|
||||||
write!(stdout, "{}", separator)?;
|
write!(stdout, "{}", separator)?;
|
||||||
}
|
}
|
||||||
let mut width = padding;
|
write_value_float(
|
||||||
if is_first_iteration && is_first_minus_zero {
|
&mut stdout,
|
||||||
write!(stdout, "-")?;
|
&value,
|
||||||
width -= 1;
|
padding,
|
||||||
}
|
largest_dec,
|
||||||
is_first_iteration = false;
|
is_first_iteration,
|
||||||
write!(
|
|
||||||
stdout,
|
|
||||||
"{value:>0width$.precision$}",
|
|
||||||
value = value,
|
|
||||||
width = width,
|
|
||||||
precision = largest_dec,
|
|
||||||
)?;
|
)?;
|
||||||
i += 1;
|
// TODO Implement augmenting addition.
|
||||||
value = first + i as f64 * increment;
|
value = value + increment.clone();
|
||||||
|
is_first_iteration = false;
|
||||||
}
|
}
|
||||||
if !is_first_iteration {
|
if !is_first_iteration {
|
||||||
write!(stdout, "{}", terminator)?;
|
write!(stdout, "{}", terminator)?;
|
||||||
|
@ -401,7 +336,6 @@ fn print_seq_integers(
|
||||||
terminator: String,
|
terminator: String,
|
||||||
pad: bool,
|
pad: bool,
|
||||||
padding: usize,
|
padding: usize,
|
||||||
is_first_minus_zero: bool,
|
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
let stdout = stdout();
|
let stdout = stdout();
|
||||||
let mut stdout = stdout.lock();
|
let mut stdout = stdout.lock();
|
||||||
|
@ -412,18 +346,10 @@ fn print_seq_integers(
|
||||||
if !is_first_iteration {
|
if !is_first_iteration {
|
||||||
write!(stdout, "{}", separator)?;
|
write!(stdout, "{}", separator)?;
|
||||||
}
|
}
|
||||||
let mut width = padding;
|
write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?;
|
||||||
if is_first_iteration && is_first_minus_zero {
|
// TODO Implement augmenting addition.
|
||||||
write!(stdout, "-")?;
|
value = value + increment.clone();
|
||||||
width -= 1;
|
|
||||||
}
|
|
||||||
is_first_iteration = false;
|
is_first_iteration = false;
|
||||||
if pad {
|
|
||||||
write!(stdout, "{number:>0width$}", number = value, width = width)?;
|
|
||||||
} else {
|
|
||||||
write!(stdout, "{}", value)?;
|
|
||||||
}
|
|
||||||
value += &increment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_first_iteration {
|
if !is_first_iteration {
|
||||||
|
|
|
@ -30,6 +30,7 @@ data-encoding-macro = { version="0.1.12", optional=true }
|
||||||
z85 = { version="3.0.3", optional=true }
|
z85 = { version="3.0.3", optional=true }
|
||||||
libc = { version="0.2.15", optional=true }
|
libc = { version="0.2.15", optional=true }
|
||||||
once_cell = "1.8.0"
|
once_cell = "1.8.0"
|
||||||
|
os_display = "0.1.0"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
walkdir = { version="2.3.2", optional=true }
|
walkdir = { version="2.3.2", optional=true }
|
||||||
|
|
|
@ -19,378 +19,16 @@
|
||||||
/// println_verbatim(path)?; // Prints "foo/bar.baz"
|
/// println_verbatim(path)?; // Prints "foo/bar.baz"
|
||||||
/// # Ok::<(), std::io::Error>(())
|
/// # Ok::<(), std::io::Error>(())
|
||||||
/// ```
|
/// ```
|
||||||
// spell-checker:ignore Fbar
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::ffi::OsStr;
|
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};
|
use std::io::{self, Write as IoWrite};
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
#[cfg(target_os = "wasi")]
|
#[cfg(target_os = "wasi")]
|
||||||
use std::os::wasi::ffi::OsStrExt;
|
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.
|
// These used to be defined here, but they live in their own crate now.
|
||||||
pub trait Quotable {
|
pub use os_display::{Quotable, Quoted};
|
||||||
/// 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()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print a path (or `OsStr`-like object) directly to stdout, with a trailing newline,
|
/// Print a path (or `OsStr`-like object) directly to stdout, with a trailing newline,
|
||||||
/// without losing any information if its encoding is invalid.
|
/// 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())
|
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]
|
#[test]
|
||||||
fn test_base32_extra_operand() {
|
fn test_base32_extra_operand() {
|
||||||
let ts = TestScenario::new(util_name!());
|
|
||||||
|
|
||||||
// Expect a failure when multiple files are specified.
|
// Expect a failure when multiple files are specified.
|
||||||
ts.ucmd()
|
new_ucmd!()
|
||||||
.arg("a.txt")
|
.arg("a.txt")
|
||||||
.arg("b.txt")
|
.arg("b.txt")
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_only(format!(
|
.usage_error("extra operand 'b.txt'");
|
||||||
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
|
|
||||||
ts.util_name,
|
|
||||||
ts.bin_path.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -95,18 +95,12 @@ fn test_wrap_bad_arg() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_base64_extra_operand() {
|
fn test_base64_extra_operand() {
|
||||||
let ts = TestScenario::new(util_name!());
|
|
||||||
|
|
||||||
// Expect a failure when multiple files are specified.
|
// Expect a failure when multiple files are specified.
|
||||||
ts.ucmd()
|
new_ucmd!()
|
||||||
.arg("a.txt")
|
.arg("a.txt")
|
||||||
.arg("b.txt")
|
.arg("b.txt")
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_only(format!(
|
.usage_error("extra operand 'b.txt'");
|
||||||
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
|
|
||||||
ts.util_name,
|
|
||||||
ts.bin_path.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -114,12 +114,7 @@ fn test_no_args() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_args_output() {
|
fn test_no_args_output() {
|
||||||
let ts = TestScenario::new(util_name!());
|
new_ucmd!().fails().usage_error("missing operand");
|
||||||
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()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -129,12 +124,10 @@ fn test_too_many_args() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_too_many_args_output() {
|
fn test_too_many_args_output() {
|
||||||
let ts = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
ts.ucmd().args(&["a", "b", "c"]).fails().stderr_is(format!(
|
.args(&["a", "b", "c"])
|
||||||
"{0}: extra operand 'c'\nTry '{1} {0} --help' for more information.",
|
.fails()
|
||||||
ts.util_name,
|
.usage_error("extra operand 'c'");
|
||||||
ts.bin_path.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(unix, target_os = "redox"))]
|
#[cfg(any(unix, target_os = "redox"))]
|
||||||
|
|
|
@ -563,17 +563,13 @@ fn test_cp_backup_off() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cp_backup_no_clobber_conflicting_options() {
|
fn test_cp_backup_no_clobber_conflicting_options() {
|
||||||
let ts = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
ts.ucmd()
|
|
||||||
.arg("--backup")
|
.arg("--backup")
|
||||||
.arg("--no-clobber")
|
.arg("--no-clobber")
|
||||||
.arg(TEST_HELLO_WORLD_SOURCE)
|
.arg(TEST_HELLO_WORLD_SOURCE)
|
||||||
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
.arg(TEST_HOW_ARE_YOU_SOURCE)
|
||||||
.fails().stderr_is(&format!(
|
.fails()
|
||||||
"{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.",
|
.usage_error("options --backup and --no-clobber are mutually exclusive");
|
||||||
ts.util_name,
|
|
||||||
ts.bin_path.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -15,15 +15,10 @@ fn test_more_dir_arg() {
|
||||||
// Maybe we could capture the error, i.e. "Device not found" in that case
|
// Maybe we could capture the error, i.e. "Device not found" in that case
|
||||||
// but I am leaving this for later
|
// but I am leaving this for later
|
||||||
if atty::is(atty::Stream::Stdout) {
|
if atty::is(atty::Stream::Stdout) {
|
||||||
let ts = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
let result = ts.ucmd().arg(".").run();
|
.arg(".")
|
||||||
result.failure();
|
.fails()
|
||||||
let expected_error_message = &format!(
|
.usage_error("'.' is a directory.");
|
||||||
"{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);
|
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -522,17 +522,13 @@ fn test_mv_backup_off() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mv_backup_no_clobber_conflicting_options() {
|
fn test_mv_backup_no_clobber_conflicting_options() {
|
||||||
let ts = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
|
.arg("--backup")
|
||||||
ts.ucmd().arg("--backup")
|
|
||||||
.arg("--no-clobber")
|
.arg("--no-clobber")
|
||||||
.arg("file1")
|
.arg("file1")
|
||||||
.arg("file2")
|
.arg("file2")
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_is(&format!("{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.",
|
.usage_error("options --backup and --no-clobber are mutually exclusive");
|
||||||
ts.util_name,
|
|
||||||
ts.bin_path.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -22,15 +22,10 @@ fn test_negative_adjustment() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_adjustment_with_no_command_should_error() {
|
fn test_adjustment_with_no_command_should_error() {
|
||||||
let ts = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
|
.args(&["-n", "19"])
|
||||||
ts.ucmd()
|
.fails()
|
||||||
.args(&["-n", "19"])
|
.usage_error("A command must be given with an adjustment.");
|
||||||
.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()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -7,25 +7,25 @@ fn test_hex_rejects_sign_after_identifier() {
|
||||||
.args(&["0x-123ABC"])
|
.args(&["0x-123ABC"])
|
||||||
.fails()
|
.fails()
|
||||||
.no_stdout()
|
.no_stdout()
|
||||||
.stderr_contains("invalid hexadecimal argument: '0x-123ABC'")
|
.stderr_contains("invalid floating point argument: '0x-123ABC'")
|
||||||
.stderr_contains("for more information.");
|
.stderr_contains("for more information.");
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["0x+123ABC"])
|
.args(&["0x+123ABC"])
|
||||||
.fails()
|
.fails()
|
||||||
.no_stdout()
|
.no_stdout()
|
||||||
.stderr_contains("invalid hexadecimal argument: '0x+123ABC'")
|
.stderr_contains("invalid floating point argument: '0x+123ABC'")
|
||||||
.stderr_contains("for more information.");
|
.stderr_contains("for more information.");
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["-0x-123ABC"])
|
.args(&["-0x-123ABC"])
|
||||||
.fails()
|
.fails()
|
||||||
.no_stdout()
|
.no_stdout()
|
||||||
.stderr_contains("invalid hexadecimal argument: '-0x-123ABC'")
|
.stderr_contains("invalid floating point argument: '-0x-123ABC'")
|
||||||
.stderr_contains("for more information.");
|
.stderr_contains("for more information.");
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["-0x+123ABC"])
|
.args(&["-0x+123ABC"])
|
||||||
.fails()
|
.fails()
|
||||||
.no_stdout()
|
.no_stdout()
|
||||||
.stderr_contains("invalid hexadecimal argument: '-0x+123ABC'")
|
.stderr_contains("invalid floating point argument: '-0x+123ABC'")
|
||||||
.stderr_contains("for more information.");
|
.stderr_contains("for more information.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,30 +60,24 @@ fn test_hex_identifier_in_wrong_place() {
|
||||||
.args(&["1234ABCD0x"])
|
.args(&["1234ABCD0x"])
|
||||||
.fails()
|
.fails()
|
||||||
.no_stdout()
|
.no_stdout()
|
||||||
.stderr_contains("invalid hexadecimal argument: '1234ABCD0x'")
|
.stderr_contains("invalid floating point argument: '1234ABCD0x'")
|
||||||
.stderr_contains("for more information.");
|
.stderr_contains("for more information.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rejects_nan() {
|
fn test_rejects_nan() {
|
||||||
let ts = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
|
.arg("NaN")
|
||||||
ts.ucmd().args(&["NaN"]).fails().stderr_only(format!(
|
.fails()
|
||||||
"{0}: invalid 'not-a-number' argument: 'NaN'\nTry '{1} {0} --help' for more information.",
|
.usage_error("invalid 'not-a-number' argument: 'NaN'");
|
||||||
ts.util_name,
|
|
||||||
ts.bin_path.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rejects_non_floats() {
|
fn test_rejects_non_floats() {
|
||||||
let ts = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
|
.arg("foo")
|
||||||
ts.ucmd().args(&["foo"]).fails().stderr_only(&format!(
|
.fails()
|
||||||
"{0}: invalid floating point argument: 'foo'\nTry '{1} {0} --help' for more information.",
|
.usage_error("invalid floating point argument: 'foo'");
|
||||||
ts.util_name,
|
|
||||||
ts.bin_path.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -479,6 +473,15 @@ fn test_width_decimal_scientific_notation_trailing_zeros_increment() {
|
||||||
.no_stderr();
|
.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 that trailing zeros in the end argument do not contribute to width.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_width_decimal_scientific_notation_trailing_zeros_end() {
|
fn test_width_decimal_scientific_notation_trailing_zeros_end() {
|
||||||
|
@ -521,6 +524,22 @@ fn test_inf() {
|
||||||
run(&["inf"], b"1\n2\n3\n");
|
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]
|
#[test]
|
||||||
fn test_ignore_leading_whitespace() {
|
fn test_ignore_leading_whitespace() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
@ -538,9 +557,65 @@ fn test_trailing_whitespace_error() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg("1 ")
|
.arg("1 ")
|
||||||
.fails()
|
.fails()
|
||||||
.no_stdout()
|
.usage_error("invalid floating point argument: '1 '");
|
||||||
.stderr_contains("seq: invalid floating point argument: '1 '")
|
}
|
||||||
// FIXME The second line of the error message is "Try 'seq
|
|
||||||
// --help' for more information."
|
#[test]
|
||||||
.stderr_contains("for more information.");
|
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"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stdbuf_line_buffering_stdin_fails() {
|
fn test_stdbuf_line_buffering_stdin_fails() {
|
||||||
let ts = TestScenario::new(util_name!());
|
new_ucmd!()
|
||||||
|
|
||||||
ts.ucmd()
|
|
||||||
.args(&["-i", "L", "head"])
|
.args(&["-i", "L", "head"])
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_is(&format!(
|
.usage_error("line buffering stdin is meaningless");
|
||||||
"{0}: line buffering stdin is meaningless\nTry '{1} {0} --help' for more information.",
|
|
||||||
ts.util_name,
|
|
||||||
ts.bin_path.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[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
|
/// within a struct which has convenience assertion functions about those outputs
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CmdResult {
|
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 is used for convenience functions for asserts against fixtures
|
||||||
tmpd: Option<Rc<TempDir>>,
|
tmpd: Option<Rc<TempDir>>,
|
||||||
/// exit status for command (if there is one)
|
/// exit status for command (if there is one)
|
||||||
|
@ -77,6 +81,8 @@ pub struct CmdResult {
|
||||||
|
|
||||||
impl CmdResult {
|
impl CmdResult {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
bin_path: String,
|
||||||
|
util_name: Option<String>,
|
||||||
tmpd: Option<Rc<TempDir>>,
|
tmpd: Option<Rc<TempDir>>,
|
||||||
code: Option<i32>,
|
code: Option<i32>,
|
||||||
success: bool,
|
success: bool,
|
||||||
|
@ -84,6 +90,8 @@ impl CmdResult {
|
||||||
stderr: &[u8],
|
stderr: &[u8],
|
||||||
) -> CmdResult {
|
) -> CmdResult {
|
||||||
CmdResult {
|
CmdResult {
|
||||||
|
bin_path,
|
||||||
|
util_name,
|
||||||
tmpd,
|
tmpd,
|
||||||
code,
|
code,
|
||||||
success,
|
success,
|
||||||
|
@ -357,6 +365,23 @@ impl CmdResult {
|
||||||
self
|
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 {
|
pub fn stdout_contains<T: AsRef<str>>(&self, cmp: T) -> &CmdResult {
|
||||||
assert!(
|
assert!(
|
||||||
self.stdout_str().contains(cmp.as_ref()),
|
self.stdout_str().contains(cmp.as_ref()),
|
||||||
|
@ -780,31 +805,36 @@ impl TestScenario {
|
||||||
/// Returns builder for invoking the target uutils binary. Paths given are
|
/// Returns builder for invoking the target uutils binary. Paths given are
|
||||||
/// treated relative to the environment's unique temporary test directory.
|
/// treated relative to the environment's unique temporary test directory.
|
||||||
pub fn ucmd(&self) -> UCommand {
|
pub fn ucmd(&self) -> UCommand {
|
||||||
let mut cmd = self.cmd(&self.bin_path);
|
self.composite_cmd(&self.bin_path, &self.util_name, true)
|
||||||
cmd.arg(&self.util_name);
|
}
|
||||||
cmd
|
|
||||||
|
/// 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
|
/// Returns builder for invoking any system command. Paths given are treated
|
||||||
/// relative to the environment's unique temporary test directory.
|
/// relative to the environment's unique temporary test directory.
|
||||||
pub fn cmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
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
|
/// Returns builder for invoking any uutils command. Paths given are treated
|
||||||
/// relative to the environment's unique temporary test directory.
|
/// relative to the environment's unique temporary test directory.
|
||||||
pub fn ccmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
pub fn ccmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
||||||
let mut cmd = self.cmd(&self.bin_path);
|
self.composite_cmd(&self.bin_path, bin, true)
|
||||||
cmd.arg(bin);
|
|
||||||
cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// different names are used rather than an argument
|
// different names are used rather than an argument
|
||||||
// because the need to keep the environment is exceedingly rare.
|
// because the need to keep the environment is exceedingly rare.
|
||||||
pub fn ucmd_keepenv(&self) -> UCommand {
|
pub fn ucmd_keepenv(&self) -> UCommand {
|
||||||
let mut cmd = self.cmd_keepenv(&self.bin_path);
|
self.composite_cmd(&self.bin_path, &self.util_name, false)
|
||||||
cmd.arg(&self.util_name);
|
|
||||||
cmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns builder for invoking any system command. Paths given are treated
|
/// Returns builder for invoking any system command. Paths given are treated
|
||||||
|
@ -812,7 +842,7 @@ impl TestScenario {
|
||||||
/// Differs from the builder returned by `cmd` in that `cmd_keepenv` does not call
|
/// 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.)
|
/// `Command::env_clear` (Clears the entire environment map for the child process.)
|
||||||
pub fn cmd_keepenv<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -826,6 +856,8 @@ impl TestScenario {
|
||||||
pub struct UCommand {
|
pub struct UCommand {
|
||||||
pub raw: Command,
|
pub raw: Command,
|
||||||
comm_string: String,
|
comm_string: String,
|
||||||
|
bin_path: String,
|
||||||
|
util_name: Option<String>,
|
||||||
tmpd: Option<Rc<TempDir>>,
|
tmpd: Option<Rc<TempDir>>,
|
||||||
has_run: bool,
|
has_run: bool,
|
||||||
ignore_stdin_write_error: bool,
|
ignore_stdin_write_error: bool,
|
||||||
|
@ -838,12 +870,20 @@ pub struct UCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UCommand {
|
impl UCommand {
|
||||||
pub fn new<T: AsRef<OsStr>, U: AsRef<OsStr>>(arg: T, curdir: U, env_clear: bool) -> UCommand {
|
pub fn new<T: AsRef<OsStr>, S: AsRef<OsStr>, U: AsRef<OsStr>>(
|
||||||
UCommand {
|
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,
|
tmpd: None,
|
||||||
has_run: false,
|
has_run: false,
|
||||||
raw: {
|
raw: {
|
||||||
let mut cmd = Command::new(arg.as_ref());
|
let mut cmd = Command::new(bin_path);
|
||||||
cmd.current_dir(curdir.as_ref());
|
cmd.current_dir(curdir.as_ref());
|
||||||
if env_clear {
|
if env_clear {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
|
@ -863,7 +903,9 @@ impl UCommand {
|
||||||
}
|
}
|
||||||
cmd
|
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,
|
ignore_stdin_write_error: false,
|
||||||
bytes_into_stdin: None,
|
bytes_into_stdin: None,
|
||||||
stdin: None,
|
stdin: None,
|
||||||
|
@ -871,12 +913,23 @@ impl UCommand {
|
||||||
stderr: None,
|
stderr: None,
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
limits: vec![],
|
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 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.tmpd = Some(tmpd);
|
||||||
ucmd
|
ucmd
|
||||||
}
|
}
|
||||||
|
@ -1021,6 +1074,8 @@ impl UCommand {
|
||||||
let prog = self.run_no_wait().wait_with_output().unwrap();
|
let prog = self.run_no_wait().wait_with_output().unwrap();
|
||||||
|
|
||||||
CmdResult {
|
CmdResult {
|
||||||
|
bin_path: self.bin_path.clone(),
|
||||||
|
util_name: self.util_name.clone(),
|
||||||
tmpd: self.tmpd.clone(),
|
tmpd: self.tmpd.clone(),
|
||||||
code: prog.status.code(),
|
code: prog.status.code(),
|
||||||
success: prog.status.success(),
|
success: prog.status.success(),
|
||||||
|
@ -1268,6 +1323,8 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(CmdResult::new(
|
Ok(CmdResult::new(
|
||||||
|
ts.bin_path.as_os_str().to_str().unwrap().to_string(),
|
||||||
|
Some(ts.util_name.clone()),
|
||||||
Some(result.tmpd()),
|
Some(result.tmpd()),
|
||||||
Some(result.code()),
|
Some(result.code()),
|
||||||
result.succeeded(),
|
result.succeeded(),
|
||||||
|
@ -1285,6 +1342,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_code_is() {
|
fn test_code_is() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: Some(32),
|
code: Some(32),
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -1298,6 +1357,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_code_is_fail() {
|
fn test_code_is_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: Some(32),
|
code: Some(32),
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -1310,6 +1371,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_failure() {
|
fn test_failure() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -1323,6 +1386,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_failure_fail() {
|
fn test_failure_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1335,6 +1400,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_success() {
|
fn test_success() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1348,6 +1415,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_success_fail() {
|
fn test_success_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -1360,6 +1429,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_no_stderr_output() {
|
fn test_no_stderr_output() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1374,6 +1445,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_no_stderr_fail() {
|
fn test_no_stderr_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1388,6 +1461,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_no_stdout_fail() {
|
fn test_no_stdout_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1401,6 +1476,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_std_does_not_contain() {
|
fn test_std_does_not_contain() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1415,6 +1492,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_stdout_does_not_contain_fail() {
|
fn test_stdout_does_not_contain_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1429,6 +1508,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_stderr_does_not_contain_fail() {
|
fn test_stderr_does_not_contain_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1442,6 +1523,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_stdout_matches() {
|
fn test_stdout_matches() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1458,6 +1541,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_stdout_matches_fail() {
|
fn test_stdout_matches_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1473,6 +1558,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_stdout_not_matches_fail() {
|
fn test_stdout_not_matches_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1487,6 +1574,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_normalized_newlines_stdout_is() {
|
fn test_normalized_newlines_stdout_is() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
@ -1503,6 +1592,8 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_normalized_newlines_stdout_is_fail() {
|
fn test_normalized_newlines_stdout_is_fail() {
|
||||||
let res = CmdResult {
|
let res = CmdResult {
|
||||||
|
bin_path: "".into(),
|
||||||
|
util_name: None,
|
||||||
tmpd: None,
|
tmpd: None,
|
||||||
code: None,
|
code: None,
|
||||||
success: true,
|
success: true,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue