1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-30 12:37:49 +00:00

Merge branch 'master' of github.com:uutils/coreutils into hbina-tr-reimplement-expansion

This commit is contained in:
Hanif Bin Ariffin 2021-11-13 18:33:25 +08:00
commit da46cc8015
23 changed files with 1769 additions and 1138 deletions

253
Cargo.lock generated
View file

@ -20,7 +20,7 @@ version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr 2.4.0",
"memchr 2.4.1",
]
[[package]]
@ -76,6 +76,17 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bigdecimal"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "binary-heap-plus"
version = "0.4.1"
@ -101,7 +112,7 @@ dependencies = [
"log",
"peeking_take_while",
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"regex",
"rustc-hash",
"shlex",
@ -149,12 +160,12 @@ dependencies = [
[[package]]
name = "bstr"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90682c8d613ad3373e66de8c6411e0ae2ab2571e879d2efbf73558cc66f21279"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr 2.4.0",
"memchr 2.4.1",
"regex-automata",
]
@ -166,9 +177,9 @@ checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
[[package]]
name = "byte-unit"
version = "4.0.12"
version = "4.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063197e6eb4b775b64160dedde7a0986bb2836cce140e9492e9e96f28e18bcd8"
checksum = "956ffc5b0ec7d7a6949e3f21fd63ba5af4cffdc2ba1e0b7bf62b481458c4ae7f"
dependencies = [
"utf8-width",
]
@ -187,9 +198,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cc"
version = "1.0.69"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
[[package]]
name = "cexpr"
@ -227,9 +238,9 @@ dependencies = [
[[package]]
name = "clang-sys"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c"
checksum = "fa66045b9cb23c2e9c1520732030608b02ee07e5cfaa5a521ec15ded7fa24c90"
dependencies = [
"glob",
"libc",
@ -479,7 +490,7 @@ dependencies = [
"if_rust_version",
"lazy_static",
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"syn",
]
@ -560,9 +571,9 @@ dependencies = [
[[package]]
name = "crossterm"
version = "0.20.0"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d"
checksum = "c85525306c4291d1b73ce93c8acf9c339f9b213aef6c1d85c3830cbf1c16325c"
dependencies = [
"bitflags",
"crossterm_winapi",
@ -576,30 +587,30 @@ dependencies = [
[[package]]
name = "crossterm_winapi"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507"
checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "ctor"
version = "0.1.20"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
dependencies = [
"quote 1.0.9",
"quote 1.0.10",
"syn",
]
[[package]]
name = "ctrlc"
version = "3.1.9"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a"
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
dependencies = [
"nix 0.20.0",
"nix 0.23.0",
"winapi 0.3.9",
]
@ -642,7 +653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"syn",
]
@ -672,9 +683,9 @@ dependencies = [
[[package]]
name = "dns-lookup"
version = "1.0.5"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093d88961fd18c4ecacb8c80cd0b356463ba941ba11e0e01f9cf5271380b79dc"
checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872"
dependencies = [
"cfg-if 1.0.0",
"libc",
@ -792,9 +803,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "gcd"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c7cd301bf2ab11ae4e5bdfd79c221d97a25e46c089144a62ee9d09cb32d2b92"
checksum = "6c8763772808ee8fe3128f0fc424bed6d9942293fddbcfd595ecfa58a81fe00b"
[[package]]
name = "generic-array"
@ -858,9 +869,9 @@ dependencies = [
[[package]]
name = "half"
version = "1.7.1"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hashbrown"
@ -920,9 +931,9 @@ checksum = "46dbcb333e86939721589d25a3557e180b52778cb33c7fdfe9e0158ff790d5ec"
[[package]]
name = "instant"
version = "0.1.10"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if 1.0.0",
]
@ -975,15 +986,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.101"
version = "0.2.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
[[package]]
name = "libloading"
version = "0.7.0"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
checksum = "c0cf036d15402bea3c5d4de17b3fce76b3e4a56ebc1f577be0e7a72f7c607cf0"
dependencies = [
"cfg-if 1.0.0",
"winapi 0.3.9",
@ -991,9 +1002,9 @@ dependencies = [
[[package]]
name = "lock_api"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
dependencies = [
"scopeguard",
]
@ -1022,12 +1033,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "md5"
version = "0.3.8"
@ -1045,9 +1050,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memmap2"
@ -1075,9 +1080,9 @@ checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677"
[[package]]
name = "mio"
version = "0.7.7"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
"log",
@ -1132,6 +1137,19 @@ dependencies = [
"memoffset",
]
[[package]]
name = "nix"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]]
name = "nodrop"
version = "0.1.14"
@ -1146,7 +1164,7 @@ checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
dependencies = [
"bitvec",
"funty",
"memchr 2.4.0",
"memchr 2.4.1",
"version_check",
]
@ -1172,9 +1190,9 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535"
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
dependencies = [
"autocfg",
"num-integer",
@ -1228,7 +1246,7 @@ checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"syn",
]
@ -1282,6 +1300,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "os_display"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "748cc1d0dc55247316a5bedd8dc8c5478c8a0c2e2001176b38ce7c0ed732c7a5"
dependencies = [
"unicode-width",
]
[[package]]
name = "ouroboros"
version = "0.10.1"
@ -1302,7 +1329,7 @@ dependencies = [
"Inflector",
"proc-macro-error",
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"syn",
]
@ -1317,9 +1344,9 @@ dependencies = [
[[package]]
name = "parking_lot"
version = "0.11.1"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
@ -1328,15 +1355,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.8.3"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
"smallvec 1.6.1",
"smallvec",
"winapi 0.3.9",
]
@ -1367,9 +1394,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "pkg-config"
version = "0.3.19"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
[[package]]
name = "platform-info"
@ -1383,9 +1410,9 @@ dependencies = [
[[package]]
name = "ppv-lite86"
version = "0.2.10"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "pretty_assertions"
@ -1401,9 +1428,9 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92"
checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83"
dependencies = [
"thiserror",
"toml",
@ -1417,7 +1444,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"syn",
"version_check",
]
@ -1429,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"version_check",
]
@ -1441,9 +1468,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.28"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
dependencies = [
"unicode-xid 0.2.2",
]
@ -1480,9 +1507,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
[[package]]
name = "quote"
version = "1.0.9"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
@ -1668,7 +1695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr 2.4.0",
"memchr 2.4.1",
"regex-syntax",
]
@ -1695,9 +1722,9 @@ dependencies = [
[[package]]
name = "retain_mut"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9c17925a9027d298a4603d286befe3f9dc0e8ed02523141914eb628798d6e5b"
checksum = "448296241d034b96c11173591deaa1302f2c17b56092106c1f92c1bc0183a8c9"
[[package]]
name = "rlimit"
@ -1742,9 +1769,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "selinux"
version = "0.2.3"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608"
checksum = "09715d6b4356e916047e61e4dce40a67ac93036851957b91713d3d9c282d1548"
dependencies = [
"bitflags",
"libc",
@ -1782,7 +1809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"syn",
]
@ -1819,15 +1846,15 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "signal-hook"
version = "0.3.9"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39"
checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1"
dependencies = [
"libc",
"signal-hook-registry",
@ -1855,18 +1882,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "0.6.14"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
]
[[package]]
name = "smallvec"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "smawk"
@ -1876,11 +1894,10 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "socket2"
version = "0.3.19"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
dependencies = [
"cfg-if 1.0.0",
"libc",
"winapi 0.3.9",
]
@ -1911,18 +1928,18 @@ checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [
"heck",
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"syn",
]
[[package]]
name = "syn"
version = "1.0.74"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"unicode-xid 0.2.2",
]
@ -2033,21 +2050,21 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.26"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.26"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"syn",
]
@ -2072,9 +2089,9 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.13.0"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
[[package]]
name = "unicode-linebreak"
@ -2093,9 +2110,9 @@ checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
@ -2316,7 +2333,7 @@ dependencies = [
"atty",
"bstr",
"clap",
"memchr 2.4.0",
"memchr 2.4.1",
"uucore",
"uucore_procs",
]
@ -2441,7 +2458,7 @@ dependencies = [
"paste",
"quickcheck",
"rand 0.7.3",
"smallvec 0.6.14",
"smallvec",
"uucore",
"uucore_procs",
]
@ -2494,7 +2511,7 @@ dependencies = [
"hex",
"libc",
"md5",
"memchr 2.4.0",
"memchr 2.4.1",
"regex",
"regex-syntax",
"sha1",
@ -2509,7 +2526,7 @@ name = "uu_head"
version = "0.0.8"
dependencies = [
"clap",
"memchr 2.4.0",
"memchr 2.4.1",
"uucore",
"uucore_procs",
]
@ -2713,7 +2730,7 @@ dependencies = [
"aho-corasick",
"clap",
"libc",
"memchr 2.4.0",
"memchr 2.4.1",
"regex",
"regex-syntax",
"uucore",
@ -2831,7 +2848,7 @@ dependencies = [
"aho-corasick",
"clap",
"libc",
"memchr 2.4.0",
"memchr 2.4.1",
"regex",
"regex-syntax",
"uucore",
@ -2914,6 +2931,7 @@ dependencies = [
name = "uu_seq"
version = "0.0.8"
dependencies = [
"bigdecimal",
"clap",
"num-bigint",
"num-traits",
@ -2961,7 +2979,7 @@ dependencies = [
"ctrlc",
"fnv",
"itertools 0.10.1",
"memchr 2.4.0",
"memchr 2.4.1",
"ouroboros",
"rand 0.7.3",
"rayon",
@ -3036,7 +3054,7 @@ name = "uu_tac"
version = "0.0.8"
dependencies = [
"clap",
"memchr 2.4.0",
"memchr 2.4.1",
"memmap2",
"regex",
"uucore",
@ -3265,6 +3283,7 @@ dependencies = [
"libc",
"nix 0.20.0",
"once_cell",
"os_display",
"termion",
"thiserror",
"time",
@ -3279,7 +3298,7 @@ name = "uucore_procs"
version = "0.0.7"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"quote 1.0.10",
"syn",
]
@ -3402,6 +3421,6 @@ dependencies = [
[[package]]
name = "z85"
version = "3.0.3"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ac8b56e4f9906a4ef5412875e9ce448364023335cec645fd457ecf51d4f2781"
checksum = "af896e93db81340b74b65f74276a99b210c086f3d34ed0abf433182a462af856"

View file

@ -15,13 +15,13 @@ edition = "2018"
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
[dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
coz = { version = "0.1.3", optional = true }
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
rand = { version = "0.7", features = ["small_rng"] }
smallvec = { version = "0.6.14, < 1.0" }
smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later.
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
uucore_procs = { version=">=0.0.7", package = "uucore_procs", path = "../../uucore_procs" }
clap = { version = "2.33", features = ["wrap_help"] }
[dev-dependencies]
paste = "0.1.18"

View file

@ -1111,6 +1111,9 @@ only ignore '.' and '..'.",
.long(options::COLOR)
.help("Color output based on file type.")
.takes_value(true)
.possible_values(&[
"always", "yes", "force", "auto", "tty", "if-tty", "never", "no", "none",
])
.require_equals(true)
.min_values(0),
)

View 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

View file

@ -1,3 +1,4 @@
# spell-checker:ignore bigdecimal
[package]
name = "uu_seq"
version = "0.0.8"
@ -15,6 +16,7 @@ edition = "2018"
path = "src/seq.rs"
[dependencies]
bigdecimal = "0.3"
clap = { version = "2.33", features = ["wrap_help"] }
num-bigint = "0.4.0"
num-traits = "0.2.14"

View file

@ -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);
}
}
}

View 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");
//
}
}

View 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
View 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()
}
}

View 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);
}
}

View file

@ -1,23 +1,24 @@
// TODO: Make -w flag work with decimals
// TODO: Support -f flag
// spell-checker:ignore (ToDO) istr chiter argptr ilen
// spell-checker:ignore (ToDO) istr chiter argptr ilen extendedbigdecimal extendedbigint numberparse
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, AppSettings, Arg};
use num_bigint::BigInt;
use num_traits::One;
use num_traits::Zero;
use num_traits::{Num, ToPrimitive};
use std::cmp;
use std::io::{stdout, ErrorKind, Write};
use std::str::FromStr;
mod digits;
use crate::digits::num_fractional_digits;
use crate::digits::num_integral_digits;
mod extendedbigdecimal;
mod extendedbigint;
mod number;
mod numberparse;
use crate::extendedbigdecimal::ExtendedBigDecimal;
use crate::extendedbigint::ExtendedBigInt;
use crate::number::Number;
use crate::number::PreciseNumber;
use crate::numberparse::ParseNumberError;
use uucore::display::Quotable;
@ -43,124 +44,55 @@ struct SeqOptions {
widths: bool,
}
enum Number {
/// Negative zero, as if it were an integer.
MinusZero,
BigInt(BigInt),
F64(f64),
}
impl Number {
fn is_zero(&self) -> bool {
match self {
Number::MinusZero => true,
Number::BigInt(n) => n.is_zero(),
Number::F64(n) => n.is_zero(),
}
}
fn into_f64(self) -> f64 {
match self {
Number::MinusZero => -0.,
// BigInt::to_f64() can not return None.
Number::BigInt(n) => n.to_f64().unwrap(),
Number::F64(n) => n,
}
}
/// Convert this number into a bigint, consuming it.
///
/// For floats, this returns the [`BigInt`] corresponding to the
/// floor of the number.
fn into_bigint(self) -> BigInt {
match self {
Number::MinusZero => BigInt::zero(),
Number::F64(x) => BigInt::from(x.floor() as i64),
Number::BigInt(n) => n,
}
}
}
impl FromStr for Number {
type Err = String;
fn from_str(mut s: &str) -> Result<Self, Self::Err> {
s = s.trim_start();
if s.starts_with('+') {
s = &s[1..];
}
let is_neg = s.starts_with('-');
match s.to_lowercase().find("0x") {
Some(i) if i <= 1 => match &s.as_bytes()[i + 2] {
b'-' | b'+' => Err(format!(
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
// TODO: hexadecimal floating point parsing (see #2660)
b'.' => Err(format!(
"NotImplemented: hexadecimal floating point numbers: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
_ => {
let num = BigInt::from_str_radix(&s[i + 2..], 16)
.map_err(|_| format!(
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
))?;
match (is_neg, num == BigInt::zero()) {
(true, true) => Ok(Number::MinusZero),
(true, false) => Ok(Number::BigInt(-num)),
(false, _) => Ok(Number::BigInt(num)),
}
}
},
Some(_) => Err(format!(
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
None => match s.parse::<BigInt>() {
Ok(n) => {
// If `s` is '-0', then `parse()` returns
// `BigInt::zero()`, but we need to return
// `Number::MinusZero` instead.
if n == BigInt::zero() && is_neg {
Ok(Number::MinusZero)
} else {
Ok(Number::BigInt(n))
}
}
Err(_) => match s.parse::<f64>() {
Ok(value) if value.is_nan() => Err(format!(
"invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
Ok(value) => Ok(Number::F64(value)),
Err(_) => Err(format!(
"invalid floating point argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
},
},
}
}
}
/// A range of integers.
///
/// The elements are (first, increment, last).
type RangeInt = (BigInt, BigInt, BigInt);
type RangeInt = (ExtendedBigInt, ExtendedBigInt, ExtendedBigInt);
/// A range of f64.
/// A range of floats.
///
/// The elements are (first, increment, last).
type RangeF64 = (f64, f64, f64);
type RangeFloat = (ExtendedBigDecimal, ExtendedBigDecimal, ExtendedBigDecimal);
/// Terminate the process with error code 1.
///
/// Before terminating the process, this function prints an error
/// message that depends on `arg` and `e`.
///
/// Although the signature of this function states that it returns a
/// [`PreciseNumber`], it never reaches the return statement. It is just
/// there to make it easier to use this function when unwrapping the
/// result of calling [`str::parse`] when attempting to parse a
/// [`PreciseNumber`].
///
/// # Examples
///
/// ```rust,ignore
/// let s = "1.2e-3";
/// s.parse::<PreciseNumber>.unwrap_or_else(|e| exit_with_error(s, e))
/// ```
fn exit_with_error(arg: &str, e: ParseNumberError) -> ! {
match e {
ParseNumberError::Float => crash!(
1,
"invalid floating point argument: {}\nTry '{} --help' for more information.",
arg.quote(),
uucore::execution_phrase()
),
ParseNumberError::Nan => crash!(
1,
"invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.",
arg.quote(),
uucore::execution_phrase()
),
ParseNumberError::Hex => crash!(
1,
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
arg.quote(),
uucore::execution_phrase()
),
}
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = usage();
@ -174,53 +106,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
widths: matches.is_present(OPT_WIDTHS),
};
let mut largest_dec = 0;
let mut padding = 0;
let first = if numbers.len() > 1 {
let slice = numbers[0];
largest_dec = num_fractional_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
padding = num_integral_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
crash_if_err!(1, slice.parse())
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
} else {
Number::BigInt(BigInt::one())
PreciseNumber::one()
};
let increment = if numbers.len() > 2 {
let slice = numbers[1];
let dec = num_fractional_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
largest_dec = cmp::max(largest_dec, dec);
padding = cmp::max(padding, int_digits);
crash_if_err!(1, slice.parse())
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
} else {
Number::BigInt(BigInt::one())
PreciseNumber::one()
};
if increment.is_zero() {
show_error!(
@ -230,54 +126,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
);
return 1;
}
let last: Number = {
let last: PreciseNumber = {
let slice = numbers[numbers.len() - 1];
let int_digits = num_integral_digits(slice).unwrap_or_else(|_| {
crash!(
1,
"invalid floating point argument: {}\n Try '{} --help' for more information.",
slice.quote(),
uucore::execution_phrase()
)
});
padding = cmp::max(padding, int_digits);
crash_if_err!(1, slice.parse())
slice.parse().unwrap_or_else(|e| exit_with_error(slice, e))
};
let is_negative_zero_f64 = |x: f64| x == -0.0 && x.is_sign_negative() && largest_dec == 0;
let result = match (first, last, increment) {
// For example, `seq -0 1 2` or `seq -0 1 2.0`.
(Number::MinusZero, last, Number::BigInt(increment)) => print_seq_integers(
(BigInt::zero(), increment, last.into_bigint()),
options.separator,
options.terminator,
options.widths,
padding,
true,
),
// For example, `seq -0e0 1 2` or `seq -0e0 1 2.0`.
(Number::F64(x), last, Number::BigInt(increment)) if is_negative_zero_f64(x) => {
let padding = first
.num_integral_digits
.max(increment.num_integral_digits)
.max(last.num_integral_digits);
let largest_dec = first
.num_fractional_digits
.max(increment.num_fractional_digits);
let result = match (first.number, increment.number, last.number) {
(Number::Int(first), Number::Int(increment), last) => {
let last = last.round_towards(&first);
print_seq_integers(
(BigInt::zero(), increment, last.into_bigint()),
(first, increment, last),
options.separator,
options.terminator,
options.widths,
padding,
true,
)
}
// For example, `seq 0 1 2` or `seq 0 1 2.0`.
(Number::BigInt(first), last, Number::BigInt(increment)) => print_seq_integers(
(first, increment, last.into_bigint()),
options.separator,
options.terminator,
options.widths,
padding,
false,
),
// For example, `seq 0 0.5 1` or `seq 0.0 0.5 1` or `seq 0.0 0.5 1.0`.
(first, last, increment) => print_seq(
(first.into_f64(), increment.into_f64(), last.into_f64()),
(first, increment, last) => print_seq(
(
first.into_extended_big_decimal(),
increment.into_extended_big_decimal(),
last.into_extended_big_decimal(),
),
largest_dec,
options.separator,
options.terminator,
@ -329,7 +207,7 @@ pub fn uu_app() -> App<'static, 'static> {
)
}
fn done_printing<T: Num + PartialOrd>(next: &T, increment: &T, last: &T) -> bool {
fn done_printing<T: Zero + PartialOrd>(next: &T, increment: &T, last: &T) -> bool {
if increment >= &T::zero() {
next > last
} else {
@ -337,9 +215,73 @@ fn done_printing<T: Num + PartialOrd>(next: &T, increment: &T, last: &T) -> bool
}
}
/// Write a big decimal formatted according to the given parameters.
///
/// This method is an adapter to support displaying negative zero on
/// Rust versions earlier than 1.53.0. After that version, we should be
/// able to display negative zero using the default formatting provided
/// by `-0.0f32`, for example.
fn write_value_float(
writer: &mut impl Write,
value: &ExtendedBigDecimal,
width: usize,
precision: usize,
is_first_iteration: bool,
) -> std::io::Result<()> {
let value_as_str = if *value == ExtendedBigDecimal::MinusZero && is_first_iteration {
format!(
"-{value:>0width$.precision$}",
value = value,
width = if width > 0 { width - 1 } else { width },
precision = precision,
)
} else if *value == ExtendedBigDecimal::Infinity || *value == ExtendedBigDecimal::MinusInfinity
{
format!(
"{value:>width$.precision$}",
value = value,
width = width,
precision = precision,
)
} else {
format!(
"{value:>0width$.precision$}",
value = value,
width = width,
precision = precision,
)
};
write!(writer, "{}", value_as_str)
}
/// Write a big int formatted according to the given parameters.
fn write_value_int(
writer: &mut impl Write,
value: &ExtendedBigInt,
width: usize,
pad: bool,
is_first_iteration: bool,
) -> std::io::Result<()> {
let value_as_str = if pad {
if *value == ExtendedBigInt::MinusZero && is_first_iteration {
format!("-{value:>0width$}", value = value, width = width - 1,)
} else {
format!("{value:>0width$}", value = value, width = width,)
}
} else if *value == ExtendedBigInt::MinusZero && is_first_iteration {
format!("-{}", value)
} else {
format!("{}", value)
};
write!(writer, "{}", value_as_str)
}
// TODO `print_seq()` and `print_seq_integers()` are nearly identical,
// they could be refactored into a single more general function.
/// Floating point based code path
fn print_seq(
range: RangeF64,
range: RangeFloat,
largest_dec: usize,
separator: String,
terminator: String,
@ -349,30 +291,23 @@ fn print_seq(
let stdout = stdout();
let mut stdout = stdout.lock();
let (first, increment, last) = range;
let mut i = 0isize;
let is_first_minus_zero = first == -0.0 && first.is_sign_negative();
let mut value = first + i as f64 * increment;
let mut value = first;
let padding = if pad { padding + 1 + largest_dec } else { 0 };
let mut is_first_iteration = true;
while !done_printing(&value, &increment, &last) {
if !is_first_iteration {
write!(stdout, "{}", separator)?;
}
let mut width = padding;
if is_first_iteration && is_first_minus_zero {
write!(stdout, "-")?;
width -= 1;
}
is_first_iteration = false;
write!(
stdout,
"{value:>0width$.precision$}",
value = value,
width = width,
precision = largest_dec,
write_value_float(
&mut stdout,
&value,
padding,
largest_dec,
is_first_iteration,
)?;
i += 1;
value = first + i as f64 * increment;
// TODO Implement augmenting addition.
value = value + increment.clone();
is_first_iteration = false;
}
if !is_first_iteration {
write!(stdout, "{}", terminator)?;
@ -401,7 +336,6 @@ fn print_seq_integers(
terminator: String,
pad: bool,
padding: usize,
is_first_minus_zero: bool,
) -> std::io::Result<()> {
let stdout = stdout();
let mut stdout = stdout.lock();
@ -412,18 +346,10 @@ fn print_seq_integers(
if !is_first_iteration {
write!(stdout, "{}", separator)?;
}
let mut width = padding;
if is_first_iteration && is_first_minus_zero {
write!(stdout, "-")?;
width -= 1;
}
write_value_int(&mut stdout, &value, padding, pad, is_first_iteration)?;
// TODO Implement augmenting addition.
value = value + increment.clone();
is_first_iteration = false;
if pad {
write!(stdout, "{number:>0width$}", number = value, width = width)?;
} else {
write!(stdout, "{}", value)?;
}
value += &increment;
}
if !is_first_iteration {

View file

@ -30,6 +30,7 @@ data-encoding-macro = { version="0.1.12", optional=true }
z85 = { version="3.0.3", optional=true }
libc = { version="0.2.15", optional=true }
once_cell = "1.8.0"
os_display = "0.1.0"
[target.'cfg(unix)'.dependencies]
walkdir = { version="2.3.2", optional=true }

View file

@ -19,378 +19,16 @@
/// println_verbatim(path)?; // Prints "foo/bar.baz"
/// # Ok::<(), std::io::Error>(())
/// ```
// spell-checker:ignore Fbar
use std::borrow::Cow;
use std::ffi::OsStr;
#[cfg(any(unix, target_os = "wasi", windows))]
use std::fmt::Write as FmtWrite;
use std::fmt::{self, Display, Formatter};
use std::io::{self, Write as IoWrite};
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(target_os = "wasi")]
use std::os::wasi::ffi::OsStrExt;
#[cfg(any(unix, target_os = "wasi"))]
use std::str::from_utf8;
/// An extension trait for displaying filenames to users.
pub trait Quotable {
/// Returns an object that implements [`Display`] for printing filenames with
/// proper quoting and escaping for the platform.
///
/// On Unix this corresponds to sh/bash syntax, on Windows Powershell syntax
/// is used.
///
/// # Examples
///
/// ```
/// use std::path::Path;
/// use uucore::display::Quotable;
///
/// let path = Path::new("foo/bar.baz");
///
/// println!("Found file {}", path.quote()); // Prints "Found file 'foo/bar.baz'"
/// ```
fn quote(&self) -> Quoted<'_>;
/// Like `quote()`, but don't actually add quotes unless necessary because of
/// whitespace or special characters.
///
/// # Examples
///
/// ```
/// use std::path::Path;
/// use uucore::display::Quotable;
/// use uucore::show_error;
///
/// let foo = Path::new("foo/bar.baz");
/// let bar = Path::new("foo bar");
///
/// show_error!("{}: Not found", foo.maybe_quote()); // Prints "util: foo/bar.baz: Not found"
/// show_error!("{}: Not found", bar.maybe_quote()); // Prints "util: 'foo bar': Not found"
/// ```
fn maybe_quote(&self) -> Quoted<'_> {
let mut quoted = self.quote();
quoted.force_quote = false;
quoted
}
}
macro_rules! impl_as_ref {
($type: ty) => {
impl Quotable for $type {
fn quote(&self) -> Quoted<'_> {
Quoted::new(self.as_ref())
}
}
};
}
impl_as_ref!(str);
impl_as_ref!(&'_ str);
impl_as_ref!(String);
impl_as_ref!(std::path::Path);
impl_as_ref!(&'_ std::path::Path);
impl_as_ref!(std::path::PathBuf);
impl_as_ref!(std::path::Component<'_>);
impl_as_ref!(std::path::Components<'_>);
impl_as_ref!(std::path::Iter<'_>);
impl_as_ref!(std::ffi::OsStr);
impl_as_ref!(&'_ std::ffi::OsStr);
impl_as_ref!(std::ffi::OsString);
// Cow<'_, str> does not implement AsRef<OsStr> and this is unlikely to be fixed
// for backward compatibility reasons. Otherwise we'd use a blanket impl.
impl Quotable for Cow<'_, str> {
fn quote(&self) -> Quoted<'_> {
let text: &str = self.as_ref();
Quoted::new(text.as_ref())
}
}
impl Quotable for Cow<'_, std::path::Path> {
fn quote(&self) -> Quoted<'_> {
let text: &std::path::Path = self.as_ref();
Quoted::new(text.as_ref())
}
}
/// A wrapper around [`OsStr`] for printing paths with quoting and escaping applied.
#[derive(Debug, Copy, Clone)]
pub struct Quoted<'a> {
text: &'a OsStr,
force_quote: bool,
}
impl<'a> Quoted<'a> {
fn new(text: &'a OsStr) -> Self {
Quoted {
text,
force_quote: true,
}
}
}
impl Display for Quoted<'_> {
#[cfg(any(windows, unix, target_os = "wasi"))]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// On Unix we emulate sh syntax. On Windows Powershell.
// They're just similar enough to share some code.
/// Characters with special meaning outside quotes.
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02
// I don't know why % is in there, and GNU doesn't quote it either.
// {} were used in a version elsewhere but seem unnecessary, GNU doesn't
// quote them. They're used in function definitions but not in a way we
// have to worry about.
#[cfg(any(unix, target_os = "wasi"))]
const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\\\"'*?[]=";
// FIXME: I'm not a PowerShell wizard and don't know if this is correct.
// I just copied the Unix version, removed \, and added ,{} based on
// experimentation.
// I have noticed that ~?*[] only get expanded in some contexts, so watch
// out for that if doing your own tests.
// Get-ChildItem seems unwilling to quote anything so it doesn't help.
// There's the additional wrinkle that Windows has stricter requirements
// for filenames: I've been testing using a Linux build of PowerShell, but
// this code doesn't even compile on Linux.
#[cfg(windows)]
const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\"'*?[]=,{}";
/// Characters with a special meaning at the beginning of a name.
// ~ expands a home directory.
// # starts a comment.
// ! is a common extension for expanding the shell history.
#[cfg(any(unix, target_os = "wasi"))]
const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '!'];
// Same deal as before, this is possibly incomplete.
// A single stand-alone exclamation mark seems to have some special meaning.
#[cfg(windows)]
const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '@', '!'];
/// Characters that are interpreted specially in a double-quoted string.
#[cfg(any(unix, target_os = "wasi"))]
const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$', b'\\'];
#[cfg(windows)]
const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$'];
let text = match self.text.to_str() {
None => return write_escaped(f, self.text),
Some(text) => text,
};
let mut is_single_safe = true;
let mut is_double_safe = true;
let mut requires_quote = self.force_quote;
if let Some(first) = text.chars().next() {
if SPECIAL_SHELL_CHARS_START.contains(&first) {
requires_quote = true;
}
// Unlike in Unix, quoting an argument may stop it
// from being recognized as an option. I like that very much.
// But we don't want to quote "-" because that's a common
// special argument and PowerShell doesn't mind it.
#[cfg(windows)]
if first == '-' && text.len() > 1 {
requires_quote = true;
}
} else {
// Empty string
requires_quote = true;
}
for ch in text.chars() {
if ch.is_ascii() {
let ch = ch as u8;
if ch == b'\'' {
is_single_safe = false;
}
if DOUBLE_UNSAFE.contains(&ch) {
is_double_safe = false;
}
if !requires_quote && SPECIAL_SHELL_CHARS.contains(&ch) {
requires_quote = true;
}
if ch.is_ascii_control() {
return write_escaped(f, self.text);
}
}
if !requires_quote && ch.is_whitespace() {
// This includes unicode whitespace.
// We maybe don't have to escape it, we don't escape other lookalike
// characters either, but it's confusing if it goes unquoted.
requires_quote = true;
}
}
if !requires_quote {
return f.write_str(text);
} else if is_single_safe {
return write_simple(f, text, '\'');
} else if is_double_safe {
return write_simple(f, text, '\"');
} else {
return write_single_escaped(f, text);
}
fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result {
f.write_char(quote)?;
f.write_str(text)?;
f.write_char(quote)?;
Ok(())
}
#[cfg(any(unix, target_os = "wasi"))]
fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result {
let mut iter = text.split('\'');
if let Some(chunk) = iter.next() {
if !chunk.is_empty() {
write_simple(f, chunk, '\'')?;
}
}
for chunk in iter {
f.write_str("\\'")?;
if !chunk.is_empty() {
write_simple(f, chunk, '\'')?;
}
}
Ok(())
}
/// Write using the syntax described here:
/// https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html
///
/// Supported by these shells:
/// - bash
/// - zsh
/// - busybox sh
/// - mksh
///
/// Not supported by these:
/// - fish
/// - dash
/// - tcsh
#[cfg(any(unix, target_os = "wasi"))]
fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result {
f.write_str("$'")?;
for chunk in from_utf8_iter(text.as_bytes()) {
match chunk {
Ok(chunk) => {
for ch in chunk.chars() {
match ch {
'\n' => f.write_str("\\n")?,
'\t' => f.write_str("\\t")?,
'\r' => f.write_str("\\r")?,
// We could do \b, \f, \v, etc., but those are
// rare enough to be confusing.
// \0 doesn't work consistently because of the
// octal \nnn syntax, and null bytes can't appear
// in filenames anyway.
ch if ch.is_ascii_control() => write!(f, "\\x{:02X}", ch as u8)?,
'\\' | '\'' => {
// '?' and '"' can also be escaped this way
// but AFAICT there's no reason to do so
f.write_char('\\')?;
f.write_char(ch)?;
}
ch => {
f.write_char(ch)?;
}
}
}
}
Err(unit) => write!(f, "\\x{:02X}", unit)?,
}
}
f.write_char('\'')?;
Ok(())
}
#[cfg(windows)]
fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result {
// Quotes in Powershell can be escaped by doubling them
f.write_char('\'')?;
let mut iter = text.split('\'');
if let Some(chunk) = iter.next() {
f.write_str(chunk)?;
}
for chunk in iter {
f.write_str("''")?;
f.write_str(chunk)?;
}
f.write_char('\'')?;
Ok(())
}
#[cfg(windows)]
fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result {
// ` takes the role of \ since \ is already used as the path separator.
// Things are UTF-16-oriented, so we escape code units as "`u{1234}".
use std::char::decode_utf16;
use std::os::windows::ffi::OsStrExt;
f.write_char('"')?;
for ch in decode_utf16(text.encode_wide()) {
match ch {
Ok(ch) => match ch {
'\0' => f.write_str("`0")?,
'\r' => f.write_str("`r")?,
'\n' => f.write_str("`n")?,
'\t' => f.write_str("`t")?,
ch if ch.is_ascii_control() => write!(f, "`u{{{:04X}}}", ch as u8)?,
'`' => f.write_str("``")?,
'$' => f.write_str("`$")?,
'"' => f.write_str("\"\"")?,
ch => f.write_char(ch)?,
},
Err(err) => write!(f, "`u{{{:04X}}}", err.unpaired_surrogate())?,
}
}
f.write_char('"')?;
Ok(())
}
}
#[cfg(not(any(unix, target_os = "wasi", windows)))]
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
// As a fallback, we use Rust's own escaping rules.
// This is reasonably sane and very easy to implement.
// We use single quotes because that's hardcoded in a lot of tests.
let text = self.text.to_string_lossy();
if self.force_quote || !text.chars().all(|ch| ch.is_alphanumeric() || ch == '.') {
write!(f, "'{}'", text.escape_debug())
} else {
f.write_str(&text)
}
}
}
#[cfg(any(unix, target_os = "wasi"))]
fn from_utf8_iter(mut bytes: &[u8]) -> impl Iterator<Item = Result<&str, u8>> {
std::iter::from_fn(move || {
if bytes.is_empty() {
return None;
}
match from_utf8(bytes) {
Ok(text) => {
bytes = &[];
Some(Ok(text))
}
Err(err) if err.valid_up_to() == 0 => {
let res = bytes[0];
bytes = &bytes[1..];
Some(Err(res))
}
Err(err) => {
let (valid, rest) = bytes.split_at(err.valid_up_to());
bytes = rest;
Some(Ok(from_utf8(valid).unwrap()))
}
}
})
}
// These used to be defined here, but they live in their own crate now.
pub use os_display::{Quotable, Quoted};
/// Print a path (or `OsStr`-like object) directly to stdout, with a trailing newline,
/// without losing any information if its encoding is invalid.
@ -429,129 +67,3 @@ pub fn print_verbatim<S: AsRef<OsStr>>(text: S) -> io::Result<()> {
write!(stdout, "{}", std::path::Path::new(text.as_ref()).display())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn verify_quote(cases: &[(impl Quotable, &str)]) {
for (case, expected) in cases {
assert_eq!(case.quote().to_string(), *expected);
}
}
fn verify_maybe(cases: &[(impl Quotable, &str)]) {
for (case, expected) in cases {
assert_eq!(case.maybe_quote().to_string(), *expected);
}
}
/// This should hold on any platform, or else other tests fail.
#[test]
fn test_basic() {
verify_quote(&[
("foo", "'foo'"),
("", "''"),
("foo/bar.baz", "'foo/bar.baz'"),
]);
verify_maybe(&[
("foo", "foo"),
("", "''"),
("foo bar", "'foo bar'"),
("$foo", "'$foo'"),
("-", "-"),
]);
}
#[cfg(any(unix, target_os = "wasi", windows))]
#[test]
fn test_common() {
verify_maybe(&[
("a#b", "a#b"),
("#ab", "'#ab'"),
("a~b", "a~b"),
("!", "'!'"),
]);
}
#[cfg(any(unix, target_os = "wasi"))]
#[test]
fn test_unix() {
verify_quote(&[
("can't", r#""can't""#),
(r#"can'"t"#, r#"'can'\''"t'"#),
(r#"can'$t"#, r#"'can'\''$t'"#),
("foo\nb\ta\r\\\0`r", r#"$'foo\nb\ta\r\\\x00`r'"#),
("foo\x02", r#"$'foo\x02'"#),
(r#"'$''"#, r#"\''$'\'\'"#),
]);
verify_quote(&[(OsStr::from_bytes(b"foo\xFF"), r#"$'foo\xFF'"#)]);
verify_maybe(&[
("-x", "-x"),
("a,b", "a,b"),
("a\\b", "'a\\b'"),
("}", ("}")),
]);
}
#[cfg(windows)]
#[test]
fn test_windows() {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
verify_quote(&[
(r#"foo\bar"#, r#"'foo\bar'"#),
("can't", r#""can't""#),
(r#"can'"t"#, r#"'can''"t'"#),
(r#"can'$t"#, r#"'can''$t'"#),
("foo\nb\ta\r\\\0`r", r#""foo`nb`ta`r\`0``r""#),
("foo\x02", r#""foo`u{0002}""#),
(r#"'$''"#, r#"'''$'''''"#),
]);
verify_quote(&[(
OsString::from_wide(&[b'x' as u16, 0xD800]),
r#""x`u{D800}""#,
)]);
verify_maybe(&[
("-x", "'-x'"),
("a,b", "'a,b'"),
("a\\b", "a\\b"),
("}", "'}'"),
]);
}
#[cfg(any(unix, target_os = "wasi"))]
#[test]
fn test_utf8_iter() {
type ByteStr = &'static [u8];
type Chunk = Result<&'static str, u8>;
const CASES: &[(ByteStr, &[Chunk])] = &[
(b"", &[]),
(b"hello", &[Ok("hello")]),
// Immediately invalid
(b"\xFF", &[Err(b'\xFF')]),
// Incomplete UTF-8
(b"\xC2", &[Err(b'\xC2')]),
(b"\xF4\x8F", &[Err(b'\xF4'), Err(b'\x8F')]),
(b"\xFF\xFF", &[Err(b'\xFF'), Err(b'\xFF')]),
(b"hello\xC2", &[Ok("hello"), Err(b'\xC2')]),
(b"\xFFhello", &[Err(b'\xFF'), Ok("hello")]),
(b"\xFF\xC2hello", &[Err(b'\xFF'), Err(b'\xC2'), Ok("hello")]),
(b"foo\xFFbar", &[Ok("foo"), Err(b'\xFF'), Ok("bar")]),
(
b"foo\xF4\x8Fbar",
&[Ok("foo"), Err(b'\xF4'), Err(b'\x8F'), Ok("bar")],
),
(
b"foo\xFF\xC2bar",
&[Ok("foo"), Err(b'\xFF'), Err(b'\xC2'), Ok("bar")],
),
];
for &(case, expected) in CASES {
assert_eq!(
from_utf8_iter(case).collect::<Vec<_>>().as_slice(),
expected
);
}
}
}

View file

@ -113,18 +113,12 @@ fn test_wrap_bad_arg() {
#[test]
fn test_base32_extra_operand() {
let ts = TestScenario::new(util_name!());
// Expect a failure when multiple files are specified.
ts.ucmd()
new_ucmd!()
.arg("a.txt")
.arg("b.txt")
.fails()
.stderr_only(format!(
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
.usage_error("extra operand 'b.txt'");
}
#[test]

View file

@ -95,18 +95,12 @@ fn test_wrap_bad_arg() {
#[test]
fn test_base64_extra_operand() {
let ts = TestScenario::new(util_name!());
// Expect a failure when multiple files are specified.
ts.ucmd()
new_ucmd!()
.arg("a.txt")
.arg("b.txt")
.fails()
.stderr_only(format!(
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
.usage_error("extra operand 'b.txt'");
}
#[test]

View file

@ -114,12 +114,7 @@ fn test_no_args() {
#[test]
fn test_no_args_output() {
let ts = TestScenario::new(util_name!());
ts.ucmd().fails().stderr_is(&format!(
"{0}: missing operand\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
new_ucmd!().fails().usage_error("missing operand");
}
#[test]
@ -129,12 +124,10 @@ fn test_too_many_args() {
#[test]
fn test_too_many_args_output() {
let ts = TestScenario::new(util_name!());
ts.ucmd().args(&["a", "b", "c"]).fails().stderr_is(format!(
"{0}: extra operand 'c'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
new_ucmd!()
.args(&["a", "b", "c"])
.fails()
.usage_error("extra operand 'c'");
}
#[cfg(any(unix, target_os = "redox"))]

View file

@ -563,17 +563,13 @@ fn test_cp_backup_off() {
#[test]
fn test_cp_backup_no_clobber_conflicting_options() {
let ts = TestScenario::new(util_name!());
ts.ucmd()
new_ucmd!()
.arg("--backup")
.arg("--no-clobber")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.fails().stderr_is(&format!(
"{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
.fails()
.usage_error("options --backup and --no-clobber are mutually exclusive");
}
#[test]

View file

@ -15,15 +15,10 @@ fn test_more_dir_arg() {
// Maybe we could capture the error, i.e. "Device not found" in that case
// but I am leaving this for later
if atty::is(atty::Stream::Stdout) {
let ts = TestScenario::new(util_name!());
let result = ts.ucmd().arg(".").run();
result.failure();
let expected_error_message = &format!(
"{0}: '.' is a directory.\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
);
assert_eq!(result.stderr_str().trim(), expected_error_message);
new_ucmd!()
.arg(".")
.fails()
.usage_error("'.' is a directory.");
} else {
}
}

View file

@ -522,17 +522,13 @@ fn test_mv_backup_off() {
#[test]
fn test_mv_backup_no_clobber_conflicting_options() {
let ts = TestScenario::new(util_name!());
ts.ucmd().arg("--backup")
new_ucmd!()
.arg("--backup")
.arg("--no-clobber")
.arg("file1")
.arg("file2")
.fails()
.stderr_is(&format!("{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
.usage_error("options --backup and --no-clobber are mutually exclusive");
}
#[test]

View file

@ -22,15 +22,10 @@ fn test_negative_adjustment() {
#[test]
fn test_adjustment_with_no_command_should_error() {
let ts = TestScenario::new(util_name!());
ts.ucmd()
.args(&["-n", "19"])
.run()
.stderr_is(&format!("{0}: A command must be given with an adjustment.\nTry '{1} {0} --help' for more information.\n",
ts.util_name,
ts.bin_path.to_string_lossy()
));
new_ucmd!()
.args(&["-n", "19"])
.fails()
.usage_error("A command must be given with an adjustment.");
}
#[test]

View file

@ -7,25 +7,25 @@ fn test_hex_rejects_sign_after_identifier() {
.args(&["0x-123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '0x-123ABC'")
.stderr_contains("invalid floating point argument: '0x-123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["0x+123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '0x+123ABC'")
.stderr_contains("invalid floating point argument: '0x+123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["-0x-123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '-0x-123ABC'")
.stderr_contains("invalid floating point argument: '-0x-123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["-0x+123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '-0x+123ABC'")
.stderr_contains("invalid floating point argument: '-0x+123ABC'")
.stderr_contains("for more information.");
}
@ -60,30 +60,24 @@ fn test_hex_identifier_in_wrong_place() {
.args(&["1234ABCD0x"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '1234ABCD0x'")
.stderr_contains("invalid floating point argument: '1234ABCD0x'")
.stderr_contains("for more information.");
}
#[test]
fn test_rejects_nan() {
let ts = TestScenario::new(util_name!());
ts.ucmd().args(&["NaN"]).fails().stderr_only(format!(
"{0}: invalid 'not-a-number' argument: 'NaN'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
new_ucmd!()
.arg("NaN")
.fails()
.usage_error("invalid 'not-a-number' argument: 'NaN'");
}
#[test]
fn test_rejects_non_floats() {
let ts = TestScenario::new(util_name!());
ts.ucmd().args(&["foo"]).fails().stderr_only(&format!(
"{0}: invalid floating point argument: 'foo'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
new_ucmd!()
.arg("foo")
.fails()
.usage_error("invalid floating point argument: 'foo'");
}
#[test]
@ -479,6 +473,15 @@ fn test_width_decimal_scientific_notation_trailing_zeros_increment() {
.no_stderr();
}
#[test]
fn test_width_negative_scientific_notation() {
new_ucmd!()
.args(&["-w", "-1e-3", "1"])
.succeeds()
.stdout_is("-0.001\n00.999\n")
.no_stderr();
}
/// Test that trailing zeros in the end argument do not contribute to width.
#[test]
fn test_width_decimal_scientific_notation_trailing_zeros_end() {
@ -521,6 +524,22 @@ fn test_inf() {
run(&["inf"], b"1\n2\n3\n");
}
#[test]
fn test_inf_width() {
run(
&["-w", "1.000", "inf", "inf"],
b"1.000\n inf\n inf\n inf\n",
);
}
#[test]
fn test_neg_inf_width() {
run(
&["-w", "1.000", "-inf", "-inf"],
b"1.000\n -inf\n -inf\n -inf\n",
);
}
#[test]
fn test_ignore_leading_whitespace() {
new_ucmd!()
@ -538,9 +557,65 @@ fn test_trailing_whitespace_error() {
new_ucmd!()
.arg("1 ")
.fails()
.no_stdout()
.stderr_contains("seq: invalid floating point argument: '1 '")
// FIXME The second line of the error message is "Try 'seq
// --help' for more information."
.stderr_contains("for more information.");
.usage_error("invalid floating point argument: '1 '");
}
#[test]
fn test_negative_zero_int_start_float_increment() {
new_ucmd!()
.args(&["-0", "0.1", "0.1"])
.succeeds()
.stdout_is("-0.0\n0.1\n")
.no_stderr();
}
#[test]
fn test_float_precision_increment() {
new_ucmd!()
.args(&["999", "0.1", "1000.1"])
.succeeds()
.stdout_is(
"999.0
999.1
999.2
999.3
999.4
999.5
999.6
999.7
999.8
999.9
1000.0
1000.1
",
)
.no_stderr();
}
/// Test for floating point precision issues.
#[test]
fn test_negative_increment_decimal() {
new_ucmd!()
.args(&["0.1", "-0.1", "-0.2"])
.succeeds()
.stdout_is("0.1\n0.0\n-0.1\n-0.2\n")
.no_stderr();
}
#[test]
fn test_zero_not_first() {
new_ucmd!()
.args(&["-w", "-0.1", "0.1", "0.1"])
.succeeds()
.stdout_is("-0.1\n00.0\n00.1\n")
.no_stderr();
}
#[test]
fn test_rounding_end() {
new_ucmd!()
.args(&["1", "-1", "0.1"])
.succeeds()
.stdout_is("1\n")
.no_stderr();
}

View file

@ -53,16 +53,10 @@ fn test_stdbuf_trailing_var_arg() {
#[cfg(not(target_os = "windows"))]
#[test]
fn test_stdbuf_line_buffering_stdin_fails() {
let ts = TestScenario::new(util_name!());
ts.ucmd()
new_ucmd!()
.args(&["-i", "L", "head"])
.fails()
.stderr_is(&format!(
"{0}: line buffering stdin is meaningless\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
.usage_error("line buffering stdin is meaningless");
}
#[cfg(not(target_os = "windows"))]

View file

@ -62,6 +62,10 @@ fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_p
/// within a struct which has convenience assertion functions about those outputs
#[derive(Debug, Clone)]
pub struct CmdResult {
/// bin_path provided by `TestScenario` or `UCommand`
bin_path: String,
/// util_name provided by `TestScenario` or `UCommand`
util_name: Option<String>,
//tmpd is used for convenience functions for asserts against fixtures
tmpd: Option<Rc<TempDir>>,
/// exit status for command (if there is one)
@ -77,6 +81,8 @@ pub struct CmdResult {
impl CmdResult {
pub fn new(
bin_path: String,
util_name: Option<String>,
tmpd: Option<Rc<TempDir>>,
code: Option<i32>,
success: bool,
@ -84,6 +90,8 @@ impl CmdResult {
stderr: &[u8],
) -> CmdResult {
CmdResult {
bin_path,
util_name,
tmpd,
code,
success,
@ -357,6 +365,23 @@ impl CmdResult {
self
}
/// asserts that
/// 1. the command resulted in stderr stream output that equals the
/// the following format when both are trimmed of trailing whitespace
/// `"{util_name}: {msg}\nTry '{bin_path} {util_name} --help' for more information."`
/// This the expected format when a UUsageError is returned or when show_error! is called
/// `msg` should be the same as the one provided to UUsageError::new or show_error!
///
/// 2. the command resulted in empty (zero-length) stdout stream output
pub fn usage_error<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
self.stderr_only(format!(
"{0}: {2}\nTry '{1} {0} --help' for more information.",
self.util_name.as_ref().unwrap(), // This shouldn't be called using a normal command
self.bin_path,
msg.as_ref()
))
}
pub fn stdout_contains<T: AsRef<str>>(&self, cmp: T) -> &CmdResult {
assert!(
self.stdout_str().contains(cmp.as_ref()),
@ -780,31 +805,36 @@ impl TestScenario {
/// Returns builder for invoking the target uutils binary. Paths given are
/// treated relative to the environment's unique temporary test directory.
pub fn ucmd(&self) -> UCommand {
let mut cmd = self.cmd(&self.bin_path);
cmd.arg(&self.util_name);
cmd
self.composite_cmd(&self.bin_path, &self.util_name, true)
}
/// Returns builder for invoking the target uutils binary. Paths given are
/// treated relative to the environment's unique temporary test directory.
pub fn composite_cmd<S: AsRef<OsStr>, T: AsRef<OsStr>>(
&self,
bin: S,
util_name: T,
env_clear: bool,
) -> UCommand {
UCommand::new_from_tmp(bin, Some(util_name), self.tmpd.clone(), env_clear)
}
/// Returns builder for invoking any system command. Paths given are treated
/// relative to the environment's unique temporary test directory.
pub fn cmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
UCommand::new_from_tmp(bin, self.tmpd.clone(), true)
UCommand::new_from_tmp::<S, S>(bin, None, self.tmpd.clone(), true)
}
/// Returns builder for invoking any uutils command. Paths given are treated
/// relative to the environment's unique temporary test directory.
pub fn ccmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
let mut cmd = self.cmd(&self.bin_path);
cmd.arg(bin);
cmd
self.composite_cmd(&self.bin_path, bin, true)
}
// different names are used rather than an argument
// because the need to keep the environment is exceedingly rare.
pub fn ucmd_keepenv(&self) -> UCommand {
let mut cmd = self.cmd_keepenv(&self.bin_path);
cmd.arg(&self.util_name);
cmd
self.composite_cmd(&self.bin_path, &self.util_name, false)
}
/// Returns builder for invoking any system command. Paths given are treated
@ -812,7 +842,7 @@ impl TestScenario {
/// Differs from the builder returned by `cmd` in that `cmd_keepenv` does not call
/// `Command::env_clear` (Clears the entire environment map for the child process.)
pub fn cmd_keepenv<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
UCommand::new_from_tmp(bin, self.tmpd.clone(), false)
UCommand::new_from_tmp::<S, S>(bin, None, self.tmpd.clone(), false)
}
}
@ -826,6 +856,8 @@ impl TestScenario {
pub struct UCommand {
pub raw: Command,
comm_string: String,
bin_path: String,
util_name: Option<String>,
tmpd: Option<Rc<TempDir>>,
has_run: bool,
ignore_stdin_write_error: bool,
@ -838,12 +870,20 @@ pub struct UCommand {
}
impl UCommand {
pub fn new<T: AsRef<OsStr>, U: AsRef<OsStr>>(arg: T, curdir: U, env_clear: bool) -> UCommand {
UCommand {
pub fn new<T: AsRef<OsStr>, S: AsRef<OsStr>, U: AsRef<OsStr>>(
bin_path: T,
util_name: Option<S>,
curdir: U,
env_clear: bool,
) -> UCommand {
let bin_path = bin_path.as_ref();
let util_name = util_name.as_ref().map(|un| un.as_ref());
let mut ucmd = UCommand {
tmpd: None,
has_run: false,
raw: {
let mut cmd = Command::new(arg.as_ref());
let mut cmd = Command::new(bin_path);
cmd.current_dir(curdir.as_ref());
if env_clear {
if cfg!(windows) {
@ -863,7 +903,9 @@ impl UCommand {
}
cmd
},
comm_string: String::from(arg.as_ref().to_str().unwrap()),
comm_string: String::from(bin_path.to_str().unwrap()),
bin_path: bin_path.to_str().unwrap().to_string(),
util_name: util_name.map(|un| un.to_str().unwrap().to_string()),
ignore_stdin_write_error: false,
bytes_into_stdin: None,
stdin: None,
@ -871,12 +913,23 @@ impl UCommand {
stderr: None,
#[cfg(target_os = "linux")]
limits: vec![],
};
if let Some(un) = util_name {
ucmd.arg(un);
}
ucmd
}
pub fn new_from_tmp<T: AsRef<OsStr>>(arg: T, tmpd: Rc<TempDir>, env_clear: bool) -> UCommand {
pub fn new_from_tmp<T: AsRef<OsStr>, S: AsRef<OsStr>>(
bin_path: T,
util_name: Option<S>,
tmpd: Rc<TempDir>,
env_clear: bool,
) -> UCommand {
let tmpd_path_buf = String::from(&(*tmpd.as_ref().path().to_str().unwrap()));
let mut ucmd: UCommand = UCommand::new(arg.as_ref(), tmpd_path_buf, env_clear);
let mut ucmd: UCommand = UCommand::new(bin_path, util_name, tmpd_path_buf, env_clear);
ucmd.tmpd = Some(tmpd);
ucmd
}
@ -1021,6 +1074,8 @@ impl UCommand {
let prog = self.run_no_wait().wait_with_output().unwrap();
CmdResult {
bin_path: self.bin_path.clone(),
util_name: self.util_name.clone(),
tmpd: self.tmpd.clone(),
code: prog.status.code(),
success: prog.status.success(),
@ -1268,6 +1323,8 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
};
Ok(CmdResult::new(
ts.bin_path.as_os_str().to_str().unwrap().to_string(),
Some(ts.util_name.clone()),
Some(result.tmpd()),
Some(result.code()),
result.succeeded(),
@ -1285,6 +1342,8 @@ mod tests {
#[test]
fn test_code_is() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: Some(32),
success: false,
@ -1298,6 +1357,8 @@ mod tests {
#[should_panic]
fn test_code_is_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: Some(32),
success: false,
@ -1310,6 +1371,8 @@ mod tests {
#[test]
fn test_failure() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: false,
@ -1323,6 +1386,8 @@ mod tests {
#[should_panic]
fn test_failure_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1335,6 +1400,8 @@ mod tests {
#[test]
fn test_success() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1348,6 +1415,8 @@ mod tests {
#[should_panic]
fn test_success_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: false,
@ -1360,6 +1429,8 @@ mod tests {
#[test]
fn test_no_stderr_output() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1374,6 +1445,8 @@ mod tests {
#[should_panic]
fn test_no_stderr_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1388,6 +1461,8 @@ mod tests {
#[should_panic]
fn test_no_stdout_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1401,6 +1476,8 @@ mod tests {
#[test]
fn test_std_does_not_contain() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1415,6 +1492,8 @@ mod tests {
#[should_panic]
fn test_stdout_does_not_contain_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1429,6 +1508,8 @@ mod tests {
#[should_panic]
fn test_stderr_does_not_contain_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1442,6 +1523,8 @@ mod tests {
#[test]
fn test_stdout_matches() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1458,6 +1541,8 @@ mod tests {
#[should_panic]
fn test_stdout_matches_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1473,6 +1558,8 @@ mod tests {
#[should_panic]
fn test_stdout_not_matches_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1487,6 +1574,8 @@ mod tests {
#[test]
fn test_normalized_newlines_stdout_is() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,
@ -1503,6 +1592,8 @@ mod tests {
#[should_panic]
fn test_normalized_newlines_stdout_is_fail() {
let res = CmdResult {
bin_path: "".into(),
util_name: None,
tmpd: None,
code: None,
success: true,