Merge branch 'uutils:main' into cp_permissions

This commit is contained in:
Juul-Mc-Goa 2025-07-02 16:46:17 +02:00 committed by GitHub
commit aab575327a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
259 changed files with 9533 additions and 3172 deletions

View file

@ -4,9 +4,9 @@ linker = "x86_64-unknown-redox-gcc"
[env]
PROJECT_NAME_FOR_VERSION_STRING = "uutils coreutils"
# See feat_external_libstdbuf in src/uu/stdbuf/Cargo.toml
LIBSTDBUF_DIR = "/usr/lib"
LIBSTDBUF_DIR = "/usr/local/libexec/coreutils"
# libstdbuf must be a shared library, so musl libc can't be linked statically
# https://github.com/rust-lang/rust/issues/82193
[build]
rustflags = [ "-C", "target-feature=-crt-static" ]
rustflags = ["-C", "target-feature=-crt-static"]

28
.github/fluent_linter_config.yml vendored Normal file
View file

@ -0,0 +1,28 @@
---
ID01:
enabled: true
exclusions:
messages: []
files: []
ID02:
enabled: true
min_length: 7
VC:
disabled: true
# Disable: # TE01: single quote instead of apostrophe for genitive (foo's)
TE01:
enabled: false
# TE03: single quotes ('foo')
TE03:
enabled: false
# TE04: Double-quoted strings should use Unicode " instead of "foo".
TE04:
enabled: false
# Disable: TE05: 3 dots for ellipsis ("...")
TE05:
enabled: false
# Should be fixed
VC01:
disabled: true
ID03:
enabled: true

View file

@ -166,6 +166,7 @@ jobs:
sudo locale-gen --keep-existing sv_SE
sudo locale-gen --keep-existing sv_SE.UTF-8
sudo locale-gen --keep-existing en_US
sudo locale-gen --keep-existing en_US.UTF-8
sudo locale-gen --keep-existing ru_RU.KOI8-R
sudo update-locale

View file

@ -19,10 +19,12 @@ repos:
args: [ --fix=lf ]
- id: trailing-whitespace
- repo: https://github.com/uutils/pre-commit-fluent-hook
rev: v0.0.1
- repo: https://github.com/mozilla-l10n/moz-fluent-linter
rev: v0.4.8
hooks:
- id: check-fluent
- id: fluent_linter
files: \.ftl$
args: [--config, .github/fluent_linter_config.yml, src/uu/]
- repo: local
hooks:

View file

@ -25,6 +25,7 @@ getrandom
globset
indicatif
itertools
iuse
langid
lscolors
mdbook

399
Cargo.lock generated
View file

@ -4,9 +4,9 @@ version = 4
[[package]]
name = "adler2"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
@ -55,9 +55,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.18"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
@ -70,33 +70,33 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.10"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.8"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
@ -268,9 +268,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.17.0"
version = "3.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
[[package]]
name = "bytecount"
@ -292,9 +292,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.25"
version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [
"shlex",
]
@ -310,9 +310,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "cfg_aliases"
@ -403,9 +403,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "clap_mangen"
@ -419,9 +419,9 @@ dependencies = [
[[package]]
name = "colorchoice"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "compare"
@ -431,15 +431,15 @@ checksum = "120133d4db2ec47efe2e26502ee984747630c67f51974fca0b6c1340cf2368d3"
[[package]]
name = "console"
version = "0.15.11"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"unicode-width 0.2.1",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@ -992,9 +992,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.1.1"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
dependencies = [
"crc32fast",
"libz-rs-sys",
@ -1156,7 +1156,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi 0.11.1+wasi-snapshot-preview1",
]
[[package]]
@ -1195,9 +1195,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.3"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
dependencies = [
"allocator-api2",
"equivalent",
@ -1251,6 +1251,140 @@ dependencies = [
"cc",
]
[[package]]
name = "icu_collator"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ad4c6a556938dfd31f75a8c54141079e8821dc697ffb799cfe0f0fa11f2edc"
dependencies = [
"displaydoc",
"icu_collator_data",
"icu_collections",
"icu_locale",
"icu_locale_core",
"icu_normalizer",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_collator_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d880b8e680799eabd90c054e1b95526cd48db16c95269f3c89fb3117e1ac92c5"
[[package]]
name = "icu_collections"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ae5921528335e91da1b6c695dbf1ec37df5ac13faa3f91e5640be93aa2fbefd"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_locale_data",
"icu_provider",
"potential_utf",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locale_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fdef0c124749d06a743c69e938350816554eb63ac979166590e2b4ee4252765"
[[package]]
name = "icu_normalizer"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "indexmap"
version = "2.9.0"
@ -1258,19 +1392,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown 0.15.3",
"hashbrown 0.15.4",
]
[[package]]
name = "indicatif"
version = "0.17.11"
version = "0.17.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
checksum = "4adb2ee6ad319a912210a36e56e3623555817bcc877a7e6e8802d1d69c4d8056"
dependencies = [
"console",
"number_prefix",
"portable-atomic",
"unicode-width 0.2.1",
"unit-prefix",
"web-time",
]
@ -1467,9 +1601,9 @@ dependencies = [
[[package]]
name = "libz-rs-sys"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a"
checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221"
dependencies = [
"zlib-rs",
]
@ -1480,6 +1614,18 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "linux-raw-sys"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13d6a630ed4f43c11056af8768c4773df2c43bc780b6d8a46de345c17236c562"
[[package]]
name = "litemap"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "litrs"
version = "0.4.1"
@ -1508,7 +1654,7 @@ version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
dependencies = [
"hashbrown 0.15.3",
"hashbrown 0.15.4",
]
[[package]]
@ -1554,9 +1700,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.8"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
]
@ -1569,7 +1715,7 @@ checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.59.0",
]
@ -1905,9 +2051,9 @@ dependencies = [
[[package]]
name = "portable-atomic"
version = "1.11.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
@ -1918,6 +2064,16 @@ dependencies = [
"portable-atomic",
]
[[package]]
name = "potential_utf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
dependencies = [
"serde",
"zerovec",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -1945,9 +2101,9 @@ dependencies = [
[[package]]
name = "prettyplease"
version = "0.2.33"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d"
checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
dependencies = [
"proc-macro2",
"syn",
@ -2073,9 +2229,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.12"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
dependencies = [
"bitflags 2.9.1",
]
@ -2195,7 +2351,7 @@ dependencies = [
"bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys",
"linux-raw-sys 0.9.4",
"windows-sys 0.59.0",
]
@ -2426,6 +2582,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strsim"
version = "0.11.1"
@ -2434,15 +2596,26 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.101"
version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tap"
version = "1.0.1"
@ -2588,15 +2761,15 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.9"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
[[package]]
name = "toml_edit"
version = "0.22.26"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"toml_datetime",
@ -2678,12 +2851,30 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
[[package]]
name = "unit-prefix"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
@ -2797,6 +2988,7 @@ version = "0.1.0"
dependencies = [
"clap",
"libc",
"thiserror 2.0.12",
"uucore",
]
@ -2844,7 +3036,7 @@ dependencies = [
"filetime",
"indicatif",
"libc",
"linux-raw-sys",
"linux-raw-sys 0.10.0",
"selinux",
"thiserror 2.0.12",
"uucore",
@ -3027,6 +3219,7 @@ name = "uu_fmt"
version = "0.1.0"
dependencies = [
"clap",
"thiserror 2.0.12",
"unicode-width 0.2.1",
"uucore",
]
@ -3387,6 +3580,7 @@ version = "0.1.0"
dependencies = [
"clap",
"libc",
"thiserror 2.0.12",
"uucore",
"windows-sys 0.60.2",
]
@ -3489,6 +3683,7 @@ version = "0.1.0"
dependencies = [
"chrono",
"clap",
"thiserror 2.0.12",
"uucore",
]
@ -3498,6 +3693,7 @@ version = "0.1.0"
dependencies = [
"clap",
"tempfile",
"thiserror 2.0.12",
"uu_stdbuf_libstdbuf",
"uucore",
]
@ -3774,6 +3970,8 @@ dependencies = [
"fluent-syntax",
"glob",
"hex",
"icu_collator",
"icu_locale",
"itertools 0.14.0",
"libc",
"md-5",
@ -3876,9 +4074,9 @@ dependencies = [
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
@ -4044,9 +4242,9 @@ dependencies = [
[[package]]
name = "windows-link"
version = "0.1.1"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-result"
@ -4289,9 +4487,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.10"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd"
dependencies = [
"memchr",
]
@ -4305,6 +4503,12 @@ dependencies = [
"bitflags 2.9.1",
]
[[package]]
name = "writeable"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "wyz"
version = "0.5.1"
@ -4316,9 +4520,9 @@ dependencies = [
[[package]]
name = "xattr"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909"
dependencies = [
"libc",
"rustix",
@ -4330,6 +4534,30 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yoke"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "z85"
version = "3.0.6"
@ -4382,6 +4610,32 @@ name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
@ -4389,14 +4643,27 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zip"
version = "4.1.0"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af7dcdb4229c0e79c2531a24de7726a0e980417a74fb4d030a35f535665439a0"
checksum = "95ab361742de920c5535880f89bbd611ee62002bf11341d16a5f057bb8ba6899"
dependencies = [
"arbitrary",
"crc32fast",
@ -4408,9 +4675,9 @@ dependencies = [
[[package]]
name = "zlib-rs"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8"
checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
[[package]]
name = "zopfli"

View file

@ -1,7 +1,7 @@
# coreutils (uutils)
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
# spell-checker:ignore (libs) bigdecimal datetime serde bincode gethostid kqueue libselinux mangen memmap uuhelp startswith constness expl
# spell-checker:ignore (libs) bigdecimal datetime serde bincode gethostid kqueue libselinux mangen memmap uuhelp startswith constness expl unnested
[package]
name = "coreutils"
@ -313,6 +313,8 @@ gcd = "2.3"
glob = "0.3.1"
half = "2.4.1"
hostname = "0.4"
icu_collator = "2.0.0"
icu_locale = "2.0.0"
indicatif = "0.17.8"
itertools = "0.14.0"
jiff = { version = "0.2.10", default-features = false, features = [
@ -321,7 +323,7 @@ jiff = { version = "0.2.10", default-features = false, features = [
"tz-system",
] }
libc = "0.2.172"
linux-raw-sys = "0.9"
linux-raw-sys = "0.10"
lscolors = { version = "0.20.0", default-features = false, features = [
"gnu_legacy",
] }
@ -665,8 +667,7 @@ return_self_not_must_use = "allow" # 8
needless_pass_by_value = "allow" # 8
# manual_let_else = "allow" # 8
# needless_raw_string_hashes = "allow" # 7
match_on_vec_items = "allow" # 6
inline_always = "allow" # 6
inline_always = "allow" # 6
# format_push_string = "allow" # 6
fn_params_excessive_bools = "allow" # 6
# single_char_pattern = "allow" # 4

View file

@ -33,6 +33,9 @@ PREFIX ?= /usr/local
DESTDIR ?=
BINDIR ?= $(PREFIX)/bin
DATAROOTDIR ?= $(PREFIX)/share
LIBSTDBUF_DIR ?= $(PREFIX)/libexec/coreutils
# Export variable so that it is used during the build
export LIBSTDBUF_DIR
INSTALLDIR_BIN=$(DESTDIR)$(BINDIR)
@ -199,6 +202,8 @@ ifneq ($(OS),Windows_NT)
PROGS := $(PROGS) $(UNIX_PROGS)
# Build the selinux command even if not on the system
PROGS := $(PROGS) $(SELINUX_PROGS)
# Always use external libstdbuf when building with make (Unix only)
CARGOFLAGS += --features feat_external_libstdbuf
endif
UTILS ?= $(PROGS)
@ -438,6 +443,10 @@ endif
install: build install-manpages install-completions install-locales
mkdir -p $(INSTALLDIR_BIN)
ifneq ($(OS),Windows_NT)
mkdir -p $(DESTDIR)$(LIBSTDBUF_DIR)
$(INSTALL) -m 755 $(BUILDDIR)/deps/libstdbuf* $(DESTDIR)$(LIBSTDBUF_DIR)/
endif
ifeq (${MULTICALL}, y)
$(INSTALL) $(BUILDDIR)/coreutils $(INSTALLDIR_BIN)/$(PROG_PREFIX)coreutils
$(foreach prog, $(filter-out coreutils, $(INSTALLEES)), \
@ -452,6 +461,10 @@ else
endif
uninstall:
ifneq ($(OS),Windows_NT)
rm -f $(DESTDIR)$(LIBSTDBUF_DIR)/libstdbuf*
-rmdir $(DESTDIR)$(LIBSTDBUF_DIR) 2>/dev/null || true
endif
ifeq (${MULTICALL}, y)
rm -f $(addprefix $(INSTALLDIR_BIN)/,$(PROG_PREFIX)coreutils)
endif

View file

@ -120,6 +120,8 @@ skip = [
{ name = "rand_core", version = "0.6.4" },
# utmp-classic
{ name = "zerocopy", version = "0.7.35" },
# rustix
{ name = "linux-raw-sys", version = "0.9.4" },
]
# spell-checker: enable

378
fuzz/Cargo.lock generated
View file

@ -28,9 +28,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.18"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
@ -43,33 +43,33 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.10"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.8"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
@ -174,21 +174,21 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.17.0"
version = "3.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
[[package]]
name = "bytecount"
version = "0.6.8"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
name = "cc"
version = "1.2.23"
version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [
"jobserver",
"libc",
@ -197,9 +197,9 @@ dependencies = [
[[package]]
name = "cfg-if"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "cfg_aliases"
@ -221,18 +221,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.38"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.38"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
"anstream",
"anstyle",
@ -243,15 +243,15 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.4"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "compare"
@ -261,15 +261,15 @@ checksum = "120133d4db2ec47efe2e26502ee984747630c67f51974fca0b6c1340cf2368d3"
[[package]]
name = "console"
version = "0.15.11"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"unicode-width",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@ -483,7 +483,7 @@ dependencies = [
"fluent-syntax",
"intl-memoizer",
"intl_pluralrules",
"rustc-hash 2.1.1",
"rustc-hash",
"self_cell",
"smallvec",
"unic-langid",
@ -532,7 +532,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi 0.11.1+wasi-snapshot-preview1",
]
[[package]]
@ -589,6 +589,140 @@ dependencies = [
"cc",
]
[[package]]
name = "icu_collator"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ad4c6a556938dfd31f75a8c54141079e8821dc697ffb799cfe0f0fa11f2edc"
dependencies = [
"displaydoc",
"icu_collator_data",
"icu_collections",
"icu_locale",
"icu_locale_core",
"icu_normalizer",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_collator_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d880b8e680799eabd90c054e1b95526cd48db16c95269f3c89fb3117e1ac92c5"
[[package]]
name = "icu_collections"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ae5921528335e91da1b6c695dbf1ec37df5ac13faa3f91e5640be93aa2fbefd"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_locale_data",
"icu_provider",
"potential_utf",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locale_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fdef0c124749d06a743c69e938350816554eb63ac979166590e2b4ee4252765"
[[package]]
name = "icu_normalizer"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "intl-memoizer"
version = "0.5.3"
@ -625,9 +759,9 @@ dependencies = [
[[package]]
name = "jiff"
version = "0.2.11"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
@ -640,9 +774,9 @@ dependencies = [
[[package]]
name = "jiff-static"
version = "0.2.11"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a"
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
@ -721,6 +855,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litemap"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "log"
version = "0.4.27"
@ -739,9 +879,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "nix"
@ -870,9 +1010,9 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.0"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
@ -883,6 +1023,16 @@ dependencies = [
"portable-atomic",
]
[[package]]
name = "potential_utf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
dependencies = [
"serde",
"zerovec",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@ -1005,12 +1155,6 @@ dependencies = [
"trim-in-place",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.1.1"
@ -1032,9 +1176,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.20"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "self_cell"
@ -1117,9 +1261,15 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.15.0"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strsim"
@ -1129,15 +1279,26 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.101"
version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tempfile"
version = "3.20.0"
@ -1208,11 +1369,11 @@ checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
[[package]]
name = "type-map"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deb68604048ff8fa93347f02441e4487594adc20bb8a084f9e564d2b827a0a9f"
checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
dependencies = [
"rustc-hash 1.1.0",
"rustc-hash",
]
[[package]]
@ -1247,9 +1408,21 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-width"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
@ -1287,7 +1460,7 @@ dependencies = [
"libc",
"parse_datetime",
"uucore",
"windows-sys 0.60.1",
"windows-sys 0.60.2",
]
[[package]]
@ -1423,6 +1596,8 @@ dependencies = [
"fluent-syntax",
"glob",
"hex",
"icu_collator",
"icu_locale",
"itertools",
"libc",
"md-5",
@ -1440,7 +1615,7 @@ dependencies = [
"uucore_procs",
"wild",
"winapi-util",
"windows-sys 0.60.1",
"windows-sys 0.60.2",
"z85",
]
@ -1500,9 +1675,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
@ -1626,9 +1801,9 @@ dependencies = [
[[package]]
name = "windows-link"
version = "0.1.1"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-result"
@ -1659,11 +1834,11 @@ dependencies = [
[[package]]
name = "windows-sys"
version = "0.60.1"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b36e9ed89376c545e20cbf5a13c306b49106b21b9d1d4f9cb9a1cb6b1e9ee06a"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.1",
"windows-targets 0.53.2",
]
[[package]]
@ -1684,9 +1859,9 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.53.1"
version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30357ec391cde730f8fbfcdc29adc47518b06504528df977ab5af02ef23fdee9"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
@ -1803,6 +1978,36 @@ dependencies = [
"bitflags",
]
[[package]]
name = "writeable"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "yoke"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "z85"
version = "3.0.6"
@ -1834,6 +2039,32 @@ name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
@ -1841,5 +2072,18 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -8,7 +8,7 @@ edition.workspace = true
license.workspace = true
[dependencies]
console = "0.15.0"
console = "0.16.0"
libc = "0.2.153"
rand = { version = "0.9.0", features = ["small_rng"] }
similar = "2.5.0"

View file

@ -1,3 +1,8 @@
# This file contains base32, base64 and basenc strings
# This is because we have some common strings for all these tools
# and it is easier to have a single file than one file for program
# and loading several bundles at the same time.
base32-about = encode/decode data and print to standard output
With no FILE, or when FILE is -, read standard input.
@ -7,3 +12,47 @@ base32-about = encode/decode data and print to standard output
to attempt to recover from any other non-alphabet bytes in the
encoded stream.
base32-usage = base32 [OPTION]... [FILE]
base64-about = encode/decode data and print to standard output
With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base64 alphabet in RFC 3548.
When decoding, the input may contain newlines in addition
to the bytes of the formal base64 alphabet. Use --ignore-garbage
to attempt to recover from any other non-alphabet bytes in the
encoded stream.
base64-usage = base64 [OPTION]... [FILE]
basenc-about = Encode/decode data and print to standard output
With no FILE, or when FILE is -, read standard input.
When decoding, the input may contain newlines in addition to the bytes of
the formal alphabet. Use --ignore-garbage to attempt to recover
from any other non-alphabet bytes in the encoded stream.
basenc-usage = basenc [OPTION]... [FILE]
# Help messages for encoding formats
basenc-help-base64 = same as 'base64' program
basenc-help-base64url = file- and url-safe base64
basenc-help-base32 = same as 'base32' program
basenc-help-base32hex = extended hex alphabet base32
basenc-help-base16 = hex encoding
basenc-help-base2lsbf = bit string with least significant bit (lsb) first
basenc-help-base2msbf = bit string with most significant bit (msb) first
basenc-help-z85 = ascii85-like encoding;
when encoding, input length must be a multiple of 4;
when decoding, input length must be a multiple of 5
# Error messages
basenc-error-missing-encoding-type = missing encoding type
# Shared base_common error messages (used by base32, base64, basenc)
base-common-extra-operand = extra operand {$operand}
base-common-no-such-file = {$file}: No such file or directory
base-common-invalid-wrap-size = invalid wrap size: {$size}
base-common-read-error = read error: {$error}
# Shared base_common help messages
base-common-help-decode = decode data
base-common-help-ignore-garbage = when decoding, ignore non-alphabetic characters
base-common-help-wrap = wrap encoded lines after COLS character (default {$default}, 0 to disable wrapping)

View file

@ -0,0 +1,53 @@
base32-about = encoder/décoder les données et les imprimer sur la sortie standard
Sans FICHIER, ou quand FICHIER est -, lire l'entrée standard.
Les données sont encodées comme décrit pour l'alphabet base32 dans RFC 4648.
Lors du décodage, l'entrée peut contenir des retours à la ligne en plus
des octets de l'alphabet base32 formel. Utilisez --ignore-garbage
pour tenter de récupérer des autres octets non-alphabétiques dans
le flux encodé.
base32-usage = base32 [OPTION]... [FICHIER]
base64-about = encoder/décoder les données et les imprimer sur la sortie standard
Sans FICHIER, ou quand FICHIER est -, lire l'entrée standard.
Les données sont encodées comme décrit pour l'alphabet base64 dans RFC 3548.
Lors du décodage, l'entrée peut contenir des retours à la ligne en plus
des octets de l'alphabet base64 formel. Utilisez --ignore-garbage
pour tenter de récupérer des autres octets non-alphabétiques dans
le flux encodé.
base64-usage = base64 [OPTION]... [FICHIER]
basenc-about = Encoder/décoder des données et afficher vers la sortie standard
Sans FICHIER, ou lorsque FICHIER est -, lire l'entrée standard.
Lors du décodage, l'entrée peut contenir des nouvelles lignes en plus des octets de
l'alphabet formel. Utilisez --ignore-garbage pour tenter de récupérer
depuis tout autre octet non-alphabétique dans le flux encodé.
basenc-usage = basenc [OPTION]... [FICHIER]
# Messages d'aide pour les formats d'encodage
basenc-help-base64 = identique au programme 'base64'
basenc-help-base64url = base64 sécurisé pour fichiers et URLs
basenc-help-base32 = identique au programme 'base32'
basenc-help-base32hex = base32 avec alphabet hexadécimal étendu
basenc-help-base16 = encodage hexadécimal
basenc-help-base2lsbf = chaîne de bits avec le bit de poids faible (lsb) en premier
basenc-help-base2msbf = chaîne de bits avec le bit de poids fort (msb) en premier
basenc-help-z85 = encodage de type ascii85 ;
lors de l'encodage, la longueur d'entrée doit être un multiple de 4 ;
lors du décodage, la longueur d'entrée doit être un multiple de 5
# Messages d'erreur
basenc-error-missing-encoding-type = type d'encodage manquant
# Messages d'erreur partagés de base_common (utilisés par base32, base64, basenc)
base-common-extra-operand = opérande supplémentaire {$operand}
base-common-no-such-file = {$file} : Aucun fichier ou répertoire de ce type
base-common-invalid-wrap-size = taille de retour à la ligne invalide : {$size}
base-common-read-error = erreur de lecture : {$error}
# Messages d'aide partagés de base_common
base-common-help-decode = décoder les données
base-common-help-ignore-garbage = lors du décodage, ignorer les caractères non-alphabétiques
base-common-help-wrap = retour à la ligne des lignes encodées après COLS caractères (par défaut {$default}, 0 pour désactiver le retour à la ligne)

View file

@ -6,6 +6,7 @@
// spell-checker:ignore hexupper lsbf msbf unpadded nopad aGVsbG8sIHdvcmxkIQ
use clap::{Arg, ArgAction, Command};
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, ErrorKind, Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
@ -17,6 +18,7 @@ use uucore::encoding::{
use uucore::encoding::{EncodingWrapper, SupportsFastDecodeAndEncode};
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::format_usage;
use uucore::locale::{get_message, get_message_with_args};
pub const BASE_CMD_PARSE_ERROR: i32 = 1;
@ -50,7 +52,10 @@ impl Config {
if let Some(extra_op) = values.next() {
return Err(UUsageError::new(
BASE_CMD_PARSE_ERROR,
format!("extra operand {}", extra_op.quote()),
get_message_with_args(
"base-common-extra-operand",
HashMap::from([("operand".to_string(), extra_op.quote().to_string())]),
),
));
}
@ -62,7 +67,13 @@ impl Config {
if !path.exists() {
return Err(USimpleError::new(
BASE_CMD_PARSE_ERROR,
format!("{}: No such file or directory", path.maybe_quote()),
get_message_with_args(
"base-common-no-such-file",
HashMap::from([(
"file".to_string(),
path.maybe_quote().to_string(),
)]),
),
));
}
@ -78,7 +89,10 @@ impl Config {
num.parse::<usize>().map_err(|_| {
USimpleError::new(
BASE_CMD_PARSE_ERROR,
format!("invalid wrap size: {}", num.quote()),
get_message_with_args(
"base-common-invalid-wrap-size",
HashMap::from([("size".to_string(), num.quote().to_string())]),
),
)
})
})
@ -114,7 +128,7 @@ pub fn base_app(about: &'static str, usage: &str) -> Command {
.short('d')
.visible_short_alias('D')
.long(options::DECODE)
.help("decode data")
.help(get_message("base-common-help-decode"))
.action(ArgAction::SetTrue)
.overrides_with(options::DECODE),
)
@ -122,7 +136,7 @@ pub fn base_app(about: &'static str, usage: &str) -> Command {
Arg::new(options::IGNORE_GARBAGE)
.short('i')
.long(options::IGNORE_GARBAGE)
.help("when decoding, ignore non-alphabetic characters")
.help(get_message("base-common-help-ignore-garbage"))
.action(ArgAction::SetTrue)
.overrides_with(options::IGNORE_GARBAGE),
)
@ -131,7 +145,10 @@ pub fn base_app(about: &'static str, usage: &str) -> Command {
.short('w')
.long(options::WRAP)
.value_name("COLS")
.help(format!("wrap encoded lines after COLS character (default {WRAP_DEFAULT}, 0 to disable wrapping)"))
.help(get_message_with_args(
"base-common-help-wrap",
HashMap::from([("default".to_string(), WRAP_DEFAULT.to_string())]),
))
.overrides_with(options::WRAP),
)
// "multiple" arguments are used to check whether there is more than one
@ -813,7 +830,10 @@ fn format_read_error(kind: ErrorKind) -> String {
}
}
format!("read error: {kind_string_capitalized}")
get_message_with_args(
"base-common-read-error",
HashMap::from([("error".to_string(), kind_string_capitalized)]),
)
}
#[cfg(test)]

1
src/uu/base64/locales Symbolic link
View file

@ -0,0 +1 @@
../base32/locales

View file

@ -1,9 +0,0 @@
base64-about = encode/decode data and print to standard output
With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base64 alphabet in RFC 3548.
When decoding, the input may contain newlines in addition
to the bytes of the formal base64 alphabet. Use --ignore-garbage
to attempt to recover from any other non-alphabet bytes in the
encoded stream.
base64-usage = base64 [OPTION]... [FILE]

1
src/uu/basenc/locales Symbolic link
View file

@ -0,0 +1 @@
../base32/locales

View file

@ -1,22 +0,0 @@
basenc-about = Encode/decode data and print to standard output
With no FILE, or when FILE is -, read standard input.
When decoding, the input may contain newlines in addition to the bytes of
the formal alphabet. Use --ignore-garbage to attempt to recover
from any other non-alphabet bytes in the encoded stream.
basenc-usage = basenc [OPTION]... [FILE]
# Help messages for encoding formats
basenc-help-base64 = same as 'base64' program
basenc-help-base64url = file- and url-safe base64
basenc-help-base32 = same as 'base32' program
basenc-help-base32hex = extended hex alphabet base32
basenc-help-base16 = hex encoding
basenc-help-base2lsbf = bit string with least significant bit (lsb) first
basenc-help-base2msbf = bit string with most significant bit (msb) first
basenc-help-z85 = ascii85-like encoding;
when encoding, input length must be a multiple of 4;
when decoding, input length must be a multiple of 5
# Error messages
basenc-error-missing-encoding-type = missing encoding type

View file

@ -1,22 +0,0 @@
basenc-about = Encoder/décoder des données et afficher vers la sortie standard
Sans FICHIER, ou lorsque FICHIER est -, lire l'entrée standard.
Lors du décodage, l'entrée peut contenir des nouvelles lignes en plus des octets de
l'alphabet formel. Utilisez --ignore-garbage pour tenter de récupérer
depuis tout autre octet non-alphabétique dans le flux encodé.
basenc-usage = basenc [OPTION]... [FICHIER]
# Messages d'aide pour les formats d'encodage
basenc-help-base64 = identique au programme 'base64'
basenc-help-base64url = base64 sécurisé pour fichiers et URLs
basenc-help-base32 = identique au programme 'base32'
basenc-help-base32hex = base32 avec alphabet hexadécimal étendu
basenc-help-base16 = encodage hexadécimal
basenc-help-base2lsbf = chaîne de bits avec le bit de poids faible (lsb) en premier
basenc-help-base2msbf = chaîne de bits avec le bit de poids fort (msb) en premier
basenc-help-z85 = encodage de type ascii85 ;
lors de l'encodage, la longueur d'entrée doit être un multiple de 4 ;
lors du décodage, la longueur d'entrée doit être un multiple de 5
# Messages d'erreur
basenc-error-missing-encoding-type = type d'encodage manquant

View file

@ -1,3 +1,20 @@
chgrp-about = Change the group of each FILE to GROUP.
chgrp-usage = chgrp [OPTION]... GROUP FILE...
chgrp [OPTION]... --reference=RFILE FILE...
# Help messages
chgrp-help-print-help = Print help information.
chgrp-help-changes = like verbose but report only when a change is made
chgrp-help-quiet = suppress most error messages
chgrp-help-verbose = output a diagnostic for every file processed
chgrp-help-preserve-root = fail to operate recursively on '/'
chgrp-help-no-preserve-root = do not treat '/' specially (the default)
chgrp-help-reference = use RFILE's group rather than specifying GROUP values
chgrp-help-from = change the group only if its current group matches GROUP
chgrp-help-recursive = operate on files and directories recursively
# Error messages
chgrp-error-invalid-group-id = invalid group id: '{ $gid_str }'
chgrp-error-invalid-group = invalid group: '{ $group }'
chgrp-error-failed-to-get-attributes = failed to get attributes of { $file }
chgrp-error-invalid-user = invalid user: '{ $from_group }'

View file

@ -0,0 +1,20 @@
chgrp-about = Changer le groupe de chaque FICHIER vers GROUPE.
chgrp-usage = chgrp [OPTION]... GROUPE FICHIER...
chgrp [OPTION]... --reference=RFICHIER FICHIER...
# Messages d'aide
chgrp-help-print-help = Afficher les informations d'aide.
chgrp-help-changes = comme verbeux mais rapporter seulement lors d'un changement
chgrp-help-quiet = supprimer la plupart des messages d'erreur
chgrp-help-verbose = afficher un diagnostic pour chaque fichier traité
chgrp-help-preserve-root = échouer à opérer récursivement sur '/'
chgrp-help-no-preserve-root = ne pas traiter '/' spécialement (par défaut)
chgrp-help-reference = utiliser le groupe de RFICHIER plutôt que spécifier les valeurs de GROUPE
chgrp-help-from = changer le groupe seulement si son groupe actuel correspond à GROUPE
chgrp-help-recursive = opérer sur les fichiers et répertoires récursivement
# Messages d'erreur
chgrp-error-invalid-group-id = identifiant de groupe invalide : '{ $gid_str }'
chgrp-error-invalid-group = groupe invalide : '{ $group }'
chgrp-error-failed-to-get-attributes = échec de l'obtention des attributs de { $file }
chgrp-error-invalid-user = utilisateur invalide : '{ $from_group }'

View file

@ -12,26 +12,33 @@ use uucore::format_usage;
use uucore::perms::{GidUidOwnerFilter, IfFrom, chown_base, options};
use clap::{Arg, ArgAction, ArgMatches, Command};
use std::collections::HashMap;
use std::fs;
use std::os::unix::fs::MetadataExt;
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
fn parse_gid_from_str(group: &str) -> Result<u32, String> {
if let Some(gid_str) = group.strip_prefix(':') {
// Handle :gid format
gid_str
.parse::<u32>()
.map_err(|_| format!("invalid group id: '{gid_str}'"))
gid_str.parse::<u32>().map_err(|_| {
get_message_with_args(
"chgrp-error-invalid-group-id",
HashMap::from([("gid_str".to_string(), gid_str.to_string())]),
)
})
} else {
// Try as group name first
match entries::grp2gid(group) {
Ok(g) => Ok(g),
// If group name lookup fails, try parsing as raw number
Err(_) => group
.parse::<u32>()
.map_err(|_| format!("invalid group: '{group}'")),
Err(_) => group.parse::<u32>().map_err(|_| {
get_message_with_args(
"chgrp-error-invalid-group",
HashMap::from([("group".to_string(), group.to_string())]),
)
}),
}
}
}
@ -45,7 +52,12 @@ fn get_dest_gid(matches: &ArgMatches) -> UResult<(Option<u32>, String)> {
raw_group = entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string());
Some(gid)
})
.map_err_context(|| format!("failed to get attributes of {}", file.quote()))?
.map_err_context(|| {
get_message_with_args(
"chgrp-error-failed-to-get-attributes",
HashMap::from([("file".to_string(), file.quote().to_string())]),
)
})?
} else {
let group = matches
.get_one::<String>(options::ARG_GROUP)
@ -74,7 +86,10 @@ fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<GidUidOwnerFilter> {
Err(_) => {
return Err(USimpleError::new(
1,
format!("invalid user: '{from_group}'"),
get_message_with_args(
"chgrp-error-invalid-user",
HashMap::from([("from_group".to_string(), from_group.to_string())]),
),
));
}
}
@ -105,14 +120,14 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::HELP)
.long(options::HELP)
.help("Print help information.")
.help(get_message("chgrp-help-print-help"))
.action(ArgAction::Help),
)
.arg(
Arg::new(options::verbosity::CHANGES)
.short('c')
.long(options::verbosity::CHANGES)
.help("like verbose but report only when a change is made")
.help(get_message("chgrp-help-changes"))
.action(ArgAction::SetTrue),
)
.arg(
@ -124,26 +139,26 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::verbosity::QUIET)
.long(options::verbosity::QUIET)
.help("suppress most error messages")
.help(get_message("chgrp-help-quiet"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::verbosity::VERBOSE)
.short('v')
.long(options::verbosity::VERBOSE)
.help("output a diagnostic for every file processed")
.help(get_message("chgrp-help-verbose"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::preserve_root::PRESERVE)
.long(options::preserve_root::PRESERVE)
.help("fail to operate recursively on '/'")
.help(get_message("chgrp-help-preserve-root"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::preserve_root::NO_PRESERVE)
.long(options::preserve_root::NO_PRESERVE)
.help("do not treat '/' specially (the default)")
.help(get_message("chgrp-help-no-preserve-root"))
.action(ArgAction::SetTrue),
)
.arg(
@ -151,19 +166,19 @@ pub fn uu_app() -> Command {
.long(options::REFERENCE)
.value_name("RFILE")
.value_hint(clap::ValueHint::FilePath)
.help("use RFILE's group rather than specifying GROUP values"),
.help(get_message("chgrp-help-reference")),
)
.arg(
Arg::new(options::FROM)
.long(options::FROM)
.value_name("GROUP")
.help("change the group only if its current group matches GROUP"),
.help(get_message("chgrp-help-from")),
)
.arg(
Arg::new(options::RECURSIVE)
.short('R')
.long(options::RECURSIVE)
.help("operate on files and directories recursively")
.help(get_message("chgrp-help-recursive"))
.action(ArgAction::SetTrue),
)
// Add common arguments with chgrp, chown & chmod

View file

@ -20,6 +20,7 @@ path = "src/chmod.rs"
[dependencies]
clap = { workspace = true }
libc = { workspace = true }
thiserror = { workspace = true }
uucore = { workspace = true, features = ["entries", "fs", "mode", "perms"] }
[[bin]]

View file

@ -4,3 +4,28 @@ chmod-usage = chmod [OPTION]... MODE[,MODE]... FILE...
chmod [OPTION]... OCTAL-MODE FILE...
chmod [OPTION]... --reference=RFILE FILE...
chmod-after-help = Each MODE is of the form [ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+.
chmod-error-cannot-stat = cannot stat attributes of {$file}
chmod-error-dangling-symlink = cannot operate on dangling symlink {$file}
chmod-error-no-such-file = cannot access {$file}: No such file or directory
chmod-error-preserve-root = it is dangerous to operate recursively on {$file}
chmod: use --no-preserve-root to override this failsafe
chmod-error-permission-denied = {$file}: Permission denied
chmod-error-new-permissions = {$file}: new permissions are {$actual}, not {$expected}
chmod-error-missing-operand = missing operand
# Help messages
chmod-help-print-help = Print help information.
chmod-help-changes = like verbose but report only when a change is made
chmod-help-quiet = suppress most error messages
chmod-help-verbose = output a diagnostic for every file processed
chmod-help-no-preserve-root = do not treat '/' specially (the default)
chmod-help-preserve-root = fail to operate recursively on '/'
chmod-help-recursive = change files and directories recursively
chmod-help-reference = use RFILE's mode instead of MODE values
# Verbose messages
chmod-verbose-failed-dangling = failed to change mode of {$file} from 0000 (---------) to 1500 (r-x-----T)
chmod-verbose-neither-changed = neither symbolic link {$file} nor referent has been changed
chmod-verbose-mode-retained = mode of {$file} retained as {$mode_octal} ({$mode_display})
chmod-verbose-failed-change = failed to change mode of file {$file} from {$old_mode} ({$old_mode_display}) to {$new_mode} ({$new_mode_display})
chmod-verbose-mode-changed = mode of {$file} changed from {$old_mode} ({$old_mode_display}) to {$new_mode} ({$new_mode_display})

View file

@ -0,0 +1,33 @@
chmod-about = Changer le mode de chaque FICHIER vers MODE.
Avec --reference, changer le mode de chaque FICHIER vers celui de RFICHIER.
chmod-usage = chmod [OPTION]... MODE[,MODE]... FICHIER...
chmod [OPTION]... MODE-OCTAL FICHIER...
chmod [OPTION]... --reference=RFICHIER FICHIER...
chmod-after-help = Chaque MODE est de la forme [ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+.
# Messages d'aide
chmod-help-print-help = Afficher les informations d'aide.
chmod-help-changes = comme verbeux mais rapporter seulement lors d'un changement
chmod-help-quiet = supprimer la plupart des messages d'erreur
chmod-help-verbose = afficher un diagnostic pour chaque fichier traité
chmod-help-no-preserve-root = ne pas traiter '/' spécialement (par défaut)
chmod-help-preserve-root = échouer à opérer récursivement sur '/'
chmod-help-recursive = changer les fichiers et répertoires récursivement
chmod-help-reference = utiliser le mode de RFICHIER au lieu des valeurs de MODE
# Messages d'erreur
chmod-error-cannot-stat = impossible d'obtenir les attributs de {$file}
chmod-error-dangling-symlink = impossible d'opérer sur le lien symbolique pendouillant {$file}
chmod-error-no-such-file = impossible d'accéder à {$file} : Aucun fichier ou dossier de ce type
chmod-error-preserve-root = il est dangereux d'opérer récursivement sur {$file}
chmod: utiliser --no-preserve-root pour outrepasser cette protection
chmod-error-permission-denied = {$file} : Permission refusée
chmod-error-new-permissions = {$file} : les nouvelles permissions sont {$actual}, pas {$expected}
chmod-error-missing-operand = opérande manquant
# Messages verbeux/de statut
chmod-verbose-failed-dangling = échec du changement de mode de {$file} de 0000 (---------) vers 1500 (r-x-----T)
chmod-verbose-neither-changed = ni le lien symbolique {$file} ni la référence n'ont été changés
chmod-verbose-mode-retained = mode de {$file} conservé comme {$mode_octal} ({$mode_display})
chmod-verbose-failed-change = échec du changement de mode du fichier {$file} de {$old_mode} ({$old_mode_display}) vers {$new_mode} ({$new_mode_display})
chmod-verbose-mode-changed = mode de {$file} changé de {$old_mode} ({$old_mode_display}) vers {$new_mode} ({$new_mode_display})

View file

@ -6,12 +6,14 @@
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
use clap::{Arg, ArgAction, Command};
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::{ExitCode, UResult, USimpleError, UUsageError, set_exit_code};
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError, set_exit_code};
use uucore::fs::display_permissions_unix;
use uucore::libc::mode_t;
#[cfg(not(windows))]
@ -19,7 +21,25 @@ use uucore::mode;
use uucore::perms::{TraverseSymlinks, configure_symlink_and_recursion};
use uucore::{format_usage, show, show_error};
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
#[derive(Debug, Error)]
enum ChmodError {
#[error("{}", get_message_with_args("chmod-error-cannot-stat", HashMap::from([("file".to_string(), _0.quote().to_string())])))]
CannotStat(String),
#[error("{}", get_message_with_args("chmod-error-dangling-symlink", HashMap::from([("file".to_string(), _0.quote().to_string())])))]
DanglingSymlink(String),
#[error("{}", get_message_with_args("chmod-error-no-such-file", HashMap::from([("file".to_string(), _0.quote().to_string())])))]
NoSuchFile(String),
#[error("{}", get_message_with_args("chmod-error-preserve-root", HashMap::from([("file".to_string(), _0.quote().to_string())])))]
PreserveRoot(String),
#[error("{}", get_message_with_args("chmod-error-permission-denied", HashMap::from([("file".to_string(), _0.quote().to_string())])))]
PermissionDenied(String),
#[error("{}", get_message_with_args("chmod-error-new-permissions", HashMap::from([("file".to_string(), _0.clone()), ("actual".to_string(), _1.clone()), ("expected".to_string(), _2.clone())])))]
NewPermissions(String, String, String),
}
impl UError for ChmodError {}
mod options {
pub const HELP: &str = "help";
@ -103,11 +123,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let fmode = match matches.get_one::<String>(options::REFERENCE) {
Some(fref) => match fs::metadata(fref) {
Ok(meta) => Some(meta.mode() & 0o7777),
Err(err) => {
return Err(USimpleError::new(
1,
format!("cannot stat attributes of {}: {err}", fref.quote()),
));
Err(_) => {
return Err(ChmodError::CannotStat(fref.to_string()).into());
}
},
None => None,
@ -135,7 +152,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
};
if files.is_empty() {
return Err(UUsageError::new(1, "missing operand".to_string()));
return Err(UUsageError::new(
1,
get_message("chmod-error-missing-operand"),
));
}
let (recursive, dereference, traverse_symlinks) =
@ -168,14 +188,14 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::HELP)
.long(options::HELP)
.help("Print help information.")
.help(get_message("chmod-help-print-help"))
.action(ArgAction::Help),
)
.arg(
Arg::new(options::CHANGES)
.long(options::CHANGES)
.short('c')
.help("like verbose but report only when a change is made")
.help(get_message("chmod-help-changes"))
.action(ArgAction::SetTrue),
)
.arg(
@ -183,40 +203,40 @@ pub fn uu_app() -> Command {
.long(options::QUIET)
.visible_alias("silent")
.short('f')
.help("suppress most error messages")
.help(get_message("chmod-help-quiet"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::VERBOSE)
.long(options::VERBOSE)
.short('v')
.help("output a diagnostic for every file processed")
.help(get_message("chmod-help-verbose"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::NO_PRESERVE_ROOT)
.long(options::NO_PRESERVE_ROOT)
.help("do not treat '/' specially (the default)")
.help(get_message("chmod-help-no-preserve-root"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::PRESERVE_ROOT)
.long(options::PRESERVE_ROOT)
.help("fail to operate recursively on '/'")
.help(get_message("chmod-help-preserve-root"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::RECURSIVE)
.long(options::RECURSIVE)
.short('R')
.help("change files and directories recursively")
.help(get_message("chmod-help-recursive"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::REFERENCE)
.long("reference")
.value_hint(clap::ValueHint::FilePath)
.help("use RFILE's mode instead of MODE values"),
.help(get_message("chmod-help-reference")),
)
.arg(
Arg::new(options::MODE).required_unless_present(options::REFERENCE),
@ -265,27 +285,21 @@ impl Chmoder {
}
if !self.quiet {
show!(USimpleError::new(
1,
format!("cannot operate on dangling symlink {}", filename.quote()),
));
show!(ChmodError::DanglingSymlink(filename.to_string()));
set_exit_code(1);
}
if self.verbose {
println!(
"failed to change mode of {} from 0000 (---------) to 1500 (r-x-----T)",
filename.quote()
"{}",
get_message_with_args(
"chmod-verbose-failed-dangling",
HashMap::from([("file".to_string(), filename.quote().to_string())])
)
);
}
} else if !self.quiet {
show!(USimpleError::new(
1,
format!(
"cannot access {}: No such file or directory",
filename.quote()
)
));
show!(ChmodError::NoSuchFile(filename.to_string()));
}
// GNU exits with exit code 1 even if -q or --quiet are passed
// So we set the exit code, because it hasn't been set yet if `self.quiet` is true.
@ -298,13 +312,7 @@ impl Chmoder {
continue;
}
if self.recursive && self.preserve_root && filename == "/" {
return Err(USimpleError::new(
1,
format!(
"it is dangerous to operate recursively on {}\nchmod: use --no-preserve-root to override this failsafe",
filename.quote()
),
));
return Err(ChmodError::PreserveRoot(filename.to_string()).into());
}
if self.recursive {
r = self.walk_dir(file);
@ -368,12 +376,9 @@ impl Chmoder {
} else if err.kind() == std::io::ErrorKind::PermissionDenied {
// These two filenames would normally be conditionally
// quoted, but GNU's tests expect them to always be quoted
Err(USimpleError::new(
1,
format!("{}: Permission denied", file.quote()),
))
Err(ChmodError::PermissionDenied(file.to_string_lossy().to_string()).into())
} else {
Err(USimpleError::new(1, format!("{}: {err}", file.quote())))
Err(ChmodError::CannotStat(file.to_string_lossy().to_string()).into())
};
}
};
@ -420,15 +425,12 @@ impl Chmoder {
self.change_file(fperm, new_mode, file)?;
// if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail
if (new_mode & !naively_expected_new_mode) != 0 {
return Err(USimpleError::new(
1,
format!(
"{}: new permissions are {}, not {}",
file.maybe_quote(),
display_permissions_unix(new_mode as mode_t, false),
display_permissions_unix(naively_expected_new_mode as mode_t, false)
),
));
return Err(ChmodError::NewPermissions(
file.to_string_lossy().to_string(),
display_permissions_unix(new_mode as mode_t, false),
display_permissions_unix(naively_expected_new_mode as mode_t, false),
)
.into());
}
}
}

View file

@ -1,3 +1,23 @@
chown-about = Change file owner and group
chown-usage = chown [OPTION]... [OWNER][:[GROUP]] FILE...
chown [OPTION]... --reference=RFILE FILE...
# Help messages
chown-help-print-help = Print help information.
chown-help-changes = like verbose but report only when a change is made
chown-help-from = change the owner and/or group of each file only if its
current owner and/or group match those specified here.
Either may be omitted, in which case a match is not required
for the omitted attribute
chown-help-preserve-root = fail to operate recursively on '/'
chown-help-no-preserve-root = do not treat '/' specially (the default)
chown-help-quiet = suppress most error messages
chown-help-recursive = operate on files and directories recursively
chown-help-reference = use RFILE's owner and group rather than specifying OWNER:GROUP values
chown-help-verbose = output a diagnostic for every file processed
# Error messages
chown-error-failed-to-get-attributes = failed to get attributes of { $file }
chown-error-invalid-user = invalid user: { $user }
chown-error-invalid-group = invalid group: { $group }
chown-error-invalid-spec = invalid spec: { $spec }

View file

@ -0,0 +1,23 @@
chown-about = Changer le propriétaire et le groupe des fichiers
chown-usage = chown [OPTION]... [PROPRIÉTAIRE][:[GROUPE]] FICHIER...
chown [OPTION]... --reference=RFICHIER FICHIER...
# Messages d'aide
chown-help-print-help = Afficher les informations d'aide.
chown-help-changes = comme verbeux mais rapporter seulement lors d'un changement
chown-help-from = changer le propriétaire et/ou le groupe de chaque fichier seulement si son
propriétaire et/ou groupe actuel correspondent à ceux spécifiés ici.
L'un ou l'autre peut être omis, auquel cas une correspondance n'est pas requise
pour l'attribut omis
chown-help-preserve-root = échouer à opérer récursivement sur '/'
chown-help-no-preserve-root = ne pas traiter '/' spécialement (par défaut)
chown-help-quiet = supprimer la plupart des messages d'erreur
chown-help-recursive = opérer sur les fichiers et répertoires récursivement
chown-help-reference = utiliser le propriétaire et groupe de RFICHIER plutôt que spécifier les valeurs PROPRIÉTAIRE:GROUPE
chown-help-verbose = afficher un diagnostic pour chaque fichier traité
# Messages d'erreur
chown-error-failed-to-get-attributes = échec de l'obtention des attributs de { $file }
chown-error-invalid-user = utilisateur invalide : { $user }
chown-error-invalid-group = groupe invalide : { $group }
chown-error-invalid-spec = spécification invalide : { $spec }

View file

@ -17,7 +17,8 @@ use clap::{Arg, ArgAction, ArgMatches, Command};
use std::fs;
use std::os::unix::fs::MetadataExt;
use uucore::locale::get_message;
use std::collections::HashMap;
use uucore::locale::{get_message, get_message_with_args};
fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<GidUidOwnerFilter> {
let filter = if let Some(spec) = matches.get_one::<String>(options::FROM) {
@ -35,8 +36,12 @@ fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<GidUidOwnerFilter>
let dest_gid: Option<u32>;
let raw_owner: String;
if let Some(file) = matches.get_one::<String>(options::REFERENCE) {
let meta = fs::metadata(file)
.map_err_context(|| format!("failed to get attributes of {}", file.quote()))?;
let meta = fs::metadata(file).map_err_context(|| {
get_message_with_args(
"chown-error-failed-to-get-attributes",
HashMap::from([("file".to_string(), file.quote().to_string())]),
)
})?;
let gid = meta.gid();
let uid = meta.uid();
dest_gid = Some(gid);
@ -84,56 +89,51 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::HELP)
.long(options::HELP)
.help("Print help information.")
.help(get_message("chown-help-print-help"))
.action(ArgAction::Help),
)
.arg(
Arg::new(options::verbosity::CHANGES)
.short('c')
.long(options::verbosity::CHANGES)
.help("like verbose but report only when a change is made")
.help(get_message("chown-help-changes"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::FROM)
.long(options::FROM)
.help(
"change the owner and/or group of each file only if its \
current owner and/or group match those specified here. \
Either may be omitted, in which case a match is not required \
for the omitted attribute",
)
.help(get_message("chown-help-from"))
.value_name("CURRENT_OWNER:CURRENT_GROUP"),
)
.arg(
Arg::new(options::preserve_root::PRESERVE)
.long(options::preserve_root::PRESERVE)
.help("fail to operate recursively on '/'")
.help(get_message("chown-help-preserve-root"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::preserve_root::NO_PRESERVE)
.long(options::preserve_root::NO_PRESERVE)
.help("do not treat '/' specially (the default)")
.help(get_message("chown-help-no-preserve-root"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::verbosity::QUIET)
.long(options::verbosity::QUIET)
.help("suppress most error messages")
.help(get_message("chown-help-quiet"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::RECURSIVE)
.short('R')
.long(options::RECURSIVE)
.help("operate on files and directories recursively")
.help(get_message("chown-help-recursive"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::REFERENCE)
.long(options::REFERENCE)
.help("use RFILE's owner and group rather than specifying OWNER:GROUP values")
.help(get_message("chown-help-reference"))
.value_name("RFILE")
.value_hint(clap::ValueHint::FilePath)
.num_args(1..),
@ -148,7 +148,7 @@ pub fn uu_app() -> Command {
Arg::new(options::verbosity::VERBOSE)
.long(options::verbosity::VERBOSE)
.short('v')
.help("output a diagnostic for every file processed")
.help(get_message("chown-help-verbose"))
.action(ArgAction::SetTrue),
)
// Add common arguments with chgrp, chown & chmod
@ -177,7 +177,10 @@ fn parse_uid(user: &str, spec: &str, sep: char) -> UResult<Option<u32>> {
Ok(uid) => Ok(Some(uid)),
Err(_) => Err(USimpleError::new(
1,
format!("invalid user: {}", spec.quote()),
get_message_with_args(
"chown-error-invalid-user",
HashMap::from([("user".to_string(), spec.quote().to_string())]),
),
)),
}
}
@ -196,7 +199,10 @@ fn parse_gid(group: &str, spec: &str) -> UResult<Option<u32>> {
Ok(gid) => Ok(Some(gid)),
Err(_) => Err(USimpleError::new(
1,
format!("invalid group: {}", spec.quote()),
get_message_with_args(
"chown-error-invalid-group",
HashMap::from([("group".to_string(), spec.quote().to_string())]),
),
)),
},
}
@ -231,7 +237,10 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
// we should fail with an error
return Err(USimpleError::new(
1,
format!("invalid spec: {}", spec.quote()),
get_message_with_args(
"chown-error-invalid-spec",
HashMap::from([("spec".to_string(), spec.quote().to_string())]),
),
));
}
@ -241,9 +250,15 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
#[cfg(test)]
mod test {
use super::*;
use std::env;
use uucore::locale;
#[test]
fn test_parse_spec() {
unsafe {
env::set_var("LANG", "C");
}
let _ = locale::setup_localization("chown");
assert!(matches!(parse_spec(":", ':'), Ok((None, None))));
assert!(matches!(parse_spec(".", ':'), Ok((None, None))));
assert!(matches!(parse_spec(".", '.'), Ok((None, None))));

View file

@ -21,7 +21,7 @@ path = "src/cp.rs"
clap = { workspace = true }
filetime = { workspace = true }
libc = { workspace = true }
linux-raw-sys = { workspace = true }
linux-raw-sys = { workspace = true, features = ["ioctl"] }
selinux = { workspace = true, optional = true }
uucore = { workspace = true, features = [
"backup-control",

View file

@ -14,4 +14,103 @@ cp-after-help = Do not copy a non-directory that has an existing destination wit
- all This is the default operation when an --update option is not specified, and results in all existing files in the destination being replaced.
- none This is similar to the --no-clobber option, in that no files in the destination are replaced, but also skipping a file does not induce a failure.
- older This is the default operation when --update is specified, and results in files being replaced if theyre older than the corresponding source file.
- older This is the default operation when --update is specified, and results in files being replaced if they're older than the corresponding source file.
# Help messages
cp-help-target-directory = copy all SOURCE arguments into target-directory
cp-help-no-target-directory = Treat DEST as a regular file and not a directory
cp-help-interactive = ask before overwriting files
cp-help-link = hard-link files instead of copying
cp-help-no-clobber = don't overwrite a file that already exists
cp-help-recursive = copy directories recursively
cp-help-strip-trailing-slashes = remove any trailing slashes from each SOURCE argument
cp-help-debug = explain how a file is copied. Implies -v
cp-help-verbose = explicitly state what is being done
cp-help-symbolic-link = make symbolic links instead of copying
cp-help-force = if an existing destination file cannot be opened, remove it and try again (this option is ignored when the -n option is also used). Currently not implemented for Windows.
cp-help-remove-destination = remove each existing destination file before attempting to open it (contrast with --force). On Windows, currently only works for writeable files.
cp-help-reflink = control clone/CoW copies. See below
cp-help-attributes-only = Don't copy the file data, just the attributes
cp-help-preserve = Preserve the specified attributes (default: mode, ownership (unix only), timestamps), if possible additional attributes: context, links, xattr, all
cp-help-preserve-default = same as --preserve=mode,ownership(unix only),timestamps
cp-help-no-preserve = don't preserve the specified attributes
cp-help-parents = use full source file name under DIRECTORY
cp-help-no-dereference = never follow symbolic links in SOURCE
cp-help-dereference = always follow symbolic links in SOURCE
cp-help-cli-symbolic-links = follow command-line symbolic links in SOURCE
cp-help-archive = Same as -dR --preserve=all
cp-help-no-dereference-preserve-links = same as --no-dereference --preserve=links
cp-help-one-file-system = stay on this file system
cp-help-sparse = control creation of sparse files. See below
cp-help-selinux = set SELinux security context of destination file to default type
cp-help-context = like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX
cp-help-progress = Display a progress bar. Note: this feature is not supported by GNU coreutils.
cp-help-copy-contents = NotImplemented: copy contents of special files when recursive
# Error messages
cp-error-missing-file-operand = missing file operand
cp-error-missing-destination-operand = missing destination file operand after { $source }
cp-error-extra-operand = extra operand { $operand }
cp-error-same-file = { $source } and { $dest } are the same file
cp-error-backing-up-destroy-source = backing up { $dest } might destroy source; { $source } not copied
cp-error-cannot-open-for-reading = cannot open { $source } for reading
cp-error-not-writing-dangling-symlink = not writing through dangling symlink { $dest }
cp-error-failed-to-clone = failed to clone { $source } from { $dest }: { $error }
cp-error-cannot-change-attribute = cannot change attribute { $dest }: Source file is a non regular file
cp-error-cannot-stat = cannot stat { $source }: No such file or directory
cp-error-cannot-create-symlink = cannot create symlink { $dest } to { $source }
cp-error-cannot-create-hard-link = cannot create hard link { $dest } to { $source }
cp-error-omitting-directory = -r not specified; omitting directory { $dir }
cp-error-cannot-copy-directory-into-itself = cannot copy a directory, { $source }, into itself, { $dest }
cp-error-will-not-copy-through-symlink = will not copy { $source } through just-created symlink { $dest }
cp-error-will-not-overwrite-just-created = will not overwrite just-created { $dest } with { $source }
cp-error-target-not-directory = target: { $target } is not a directory
cp-error-cannot-overwrite-directory-with-non-directory = cannot overwrite directory { $dir } with non-directory
cp-error-cannot-overwrite-non-directory-with-directory = cannot overwrite non-directory with directory
cp-error-with-parents-dest-must-be-dir = with --parents, the destination must be a directory
cp-error-not-replacing = not replacing { $file }
cp-error-failed-get-current-dir = failed to get current directory { $error }
cp-error-failed-set-permissions = cannot set permissions { $path }
cp-error-backup-mutually-exclusive = options --backup and --no-clobber are mutually exclusive
cp-error-invalid-argument = invalid argument { $arg } for '{ $option }'
cp-error-option-not-implemented = Option '{ $option }' not yet implemented.
cp-error-not-all-files-copied = Not all files were copied
cp-error-reflink-always-sparse-auto = `--reflink=always` can be used only with --sparse=auto
cp-error-file-exists = { $path }: File exists
cp-error-invalid-backup-argument = --backup is mutually exclusive with -n or --update=none-fail
cp-error-reflink-not-supported = --reflink is only supported on linux and macOS
cp-error-sparse-not-supported = --sparse is only supported on linux
cp-error-not-a-directory = { $path } is not a directory
cp-error-selinux-not-enabled = SELinux was not enabled during the compile time!
cp-error-selinux-set-context = failed to set the security context of { $path }: { $error }
cp-error-selinux-get-context = failed to get security context of { $path }
cp-error-selinux-error = SELinux error: { $error }
cp-error-cannot-create-fifo = cannot create fifo { $path }: File exists
cp-error-invalid-attribute = invalid attribute { $value }
cp-error-failed-to-create-whole-tree = failed to create whole tree
cp-error-failed-to-create-directory = Failed to create directory: { $error }
cp-error-backup-format = cp: { $error }
Try '{ $exec } --help' for more information.
# Debug enum strings
cp-debug-enum-no = no
cp-debug-enum-yes = yes
cp-debug-enum-avoided = avoided
cp-debug-enum-unsupported = unsupported
cp-debug-enum-unknown = unknown
cp-debug-enum-zeros = zeros
cp-debug-enum-seek-hole = SEEK_HOLE
cp-debug-enum-seek-hole-zeros = SEEK_HOLE + zeros
# Warning messages
cp-warning-source-specified-more-than-once = source { $file_type } { $source } specified more than once
# Verbose and debug messages
cp-verbose-copied = { $source } -> { $dest }
cp-debug-skipped = skipped { $path }
cp-verbose-created-directory = { $source } -> { $dest }
cp-debug-copy-offload = copy offload: { $offload }, reflink: { $reflink }, sparse detection: { $sparse }
# Prompts
cp-prompt-overwrite = overwrite { $path }?
cp-prompt-overwrite-with-mode = replace { $path }, overriding mode

116
src/uu/cp/locales/fr-FR.ftl Normal file
View file

@ -0,0 +1,116 @@
cp-about = Copier SOURCE vers DEST, ou plusieurs SOURCE(s) vers RÉPERTOIRE.
cp-usage = cp [OPTION]... [-T] SOURCE DEST
cp [OPTION]... SOURCE... RÉPERTOIRE
cp [OPTION]... -t RÉPERTOIRE SOURCE...
cp-after-help = Ne pas copier un non-répertoire qui a une destination existante avec le même horodatage de modification ou plus récent ;
à la place, ignorer silencieusement le fichier sans échec. Si les horodatages sont préservés, la comparaison est faite avec
l'horodatage source tronqué aux résolutions du système de fichiers de destination et des appels système utilisés pour
mettre à jour les horodatages ; cela évite le travail en double si plusieurs commandes cp -pu sont exécutées avec la même source
et destination. Cette option est ignorée si l'option -n ou --no-clobber est également spécifiée. De plus, si
--preserve=links est également spécifié (comme avec cp -au par exemple), cela aura la priorité ; par conséquent,
selon l'ordre dans lequel les fichiers sont traités depuis la source, les fichiers plus récents dans la destination peuvent être remplacés,
pour refléter les liens durs dans la source. ce qui donne plus de contrôle sur les fichiers existants dans la destination qui sont
remplacés, et sa valeur peut être l'une des suivantes :
- all C'est l'opération par défaut lorsqu'une option --update n'est pas spécifiée, et entraîne le remplacement de tous les fichiers existants dans la destination.
- none Cela est similaire à l'option --no-clobber, en ce sens qu'aucun fichier dans la destination n'est remplacé, mais ignorer un fichier n'induit pas d'échec.
- older C'est l'opération par défaut lorsque --update est spécifié, et entraîne le remplacement des fichiers s'ils sont plus anciens que le fichier source correspondant.
# Messages d'aide
cp-help-target-directory = copier tous les arguments SOURCE dans le répertoire cible
cp-help-no-target-directory = Traiter DEST comme un fichier régulier et non comme un répertoire
cp-help-interactive = demander avant d'écraser les fichiers
cp-help-link = créer des liens durs au lieu de copier
cp-help-no-clobber = ne pas écraser un fichier qui existe déjà
cp-help-recursive = copier les répertoires récursivement
cp-help-strip-trailing-slashes = supprimer les barres obliques finales de chaque argument SOURCE
cp-help-debug = expliquer comment un fichier est copié. Implique -v
cp-help-verbose = indiquer explicitement ce qui est fait
cp-help-symbolic-link = créer des liens symboliques au lieu de copier
cp-help-force = si un fichier de destination existant ne peut pas être ouvert, le supprimer et réessayer (cette option est ignorée lorsque l'option -n est également utilisée). Actuellement non implémenté pour Windows.
cp-help-remove-destination = supprimer chaque fichier de destination existant avant de tenter de l'ouvrir (contraste avec --force). Sur Windows, ne fonctionne actuellement que pour les fichiers inscriptibles.
cp-help-reflink = contrôler les copies clone/CoW. Voir ci-dessous
cp-help-attributes-only = Ne pas copier les données du fichier, juste les attributs
cp-help-preserve = Préserver les attributs spécifiés (par défaut : mode, propriété (unix uniquement), horodatages), si possible attributs supplémentaires : contexte, liens, xattr, all
cp-help-preserve-default = identique à --preserve=mode,ownership(unix uniquement),timestamps
cp-help-no-preserve = ne pas préserver les attributs spécifiés
cp-help-parents = utiliser le nom complet du fichier source sous RÉPERTOIRE
cp-help-no-dereference = ne jamais suivre les liens symboliques dans SOURCE
cp-help-dereference = toujours suivre les liens symboliques dans SOURCE
cp-help-cli-symbolic-links = suivre les liens symboliques de la ligne de commande dans SOURCE
cp-help-archive = Identique à -dR --preserve=all
cp-help-no-dereference-preserve-links = identique à --no-dereference --preserve=links
cp-help-one-file-system = rester sur ce système de fichiers
cp-help-sparse = contrôler la création de fichiers épars. Voir ci-dessous
cp-help-selinux = définir le contexte de sécurité SELinux du fichier de destination au type par défaut
cp-help-context = comme -Z, ou si CTX est spécifié, définir le contexte de sécurité SELinux ou SMACK à CTX
cp-help-progress = Afficher une barre de progression. Note : cette fonctionnalité n'est pas supportée par GNU coreutils.
cp-help-copy-contents = Non implémenté : copier le contenu des fichiers spéciaux lors de la récursion
# Messages d'erreur
cp-error-missing-file-operand = opérande fichier manquant
cp-error-missing-destination-operand = opérande fichier de destination manquant après { $source }
cp-error-extra-operand = opérande supplémentaire { $operand }
cp-error-same-file = { $source } et { $dest } sont le même fichier
cp-error-backing-up-destroy-source = sauvegarder { $dest } pourrait détruire la source ; { $source } non copié
cp-error-cannot-open-for-reading = impossible d'ouvrir { $source } en lecture
cp-error-not-writing-dangling-symlink = ne pas écrire à travers le lien symbolique pendant { $dest }
cp-error-failed-to-clone = échec du clonage de { $source } depuis { $dest } : { $error }
cp-error-cannot-change-attribute = impossible de changer l'attribut { $dest } : Le fichier source n'est pas un fichier régulier
cp-error-cannot-stat = impossible de faire stat sur { $source } : Aucun fichier ou répertoire de ce type
cp-error-cannot-create-symlink = impossible de créer le lien symbolique { $dest } vers { $source }
cp-error-cannot-create-hard-link = impossible de créer le lien dur { $dest } vers { $source }
cp-error-omitting-directory = -r non spécifié ; répertoire { $dir } omis
cp-error-cannot-copy-directory-into-itself = impossible de copier un répertoire, { $source }, dans lui-même, { $dest }
cp-error-will-not-copy-through-symlink = ne copiera pas { $source } à travers le lien symbolique tout juste créé { $dest }
cp-error-will-not-overwrite-just-created = n'écrasera pas le fichier tout juste créé { $dest } avec { $source }
cp-error-target-not-directory = cible : { $target } n'est pas un répertoire
cp-error-cannot-overwrite-directory-with-non-directory = impossible d'écraser le répertoire { $dir } avec un non-répertoire
cp-error-cannot-overwrite-non-directory-with-directory = impossible d'écraser un non-répertoire avec un répertoire
cp-error-with-parents-dest-must-be-dir = avec --parents, la destination doit être un répertoire
cp-error-not-replacing = ne remplace pas { $file }
cp-error-failed-get-current-dir = échec de l'obtention du répertoire actuel { $error }
cp-error-failed-set-permissions = impossible de définir les permissions { $path }
cp-error-backup-mutually-exclusive = les options --backup et --no-clobber sont mutuellement exclusives
cp-error-invalid-argument = argument invalide { $arg } pour '{ $option }'
cp-error-option-not-implemented = Option '{ $option }' pas encore implémentée.
cp-error-not-all-files-copied = Tous les fichiers n'ont pas été copiés
cp-error-reflink-always-sparse-auto = `--reflink=always` ne peut être utilisé qu'avec --sparse=auto
cp-error-file-exists = { $path } : Le fichier existe
cp-error-invalid-backup-argument = --backup est mutuellement exclusif avec -n ou --update=none-fail
cp-error-reflink-not-supported = --reflink n'est supporté que sur linux et macOS
cp-error-sparse-not-supported = --sparse n'est supporté que sur linux
cp-error-not-a-directory = { $path } n'est pas un répertoire
cp-error-selinux-not-enabled = SELinux n'était pas activé lors de la compilation !
cp-error-selinux-set-context = échec de la définition du contexte de sécurité de { $path } : { $error }
cp-error-selinux-get-context = échec de l'obtention du contexte de sécurité de { $path }
cp-error-selinux-error = Erreur SELinux : { $error }
cp-error-cannot-create-fifo = impossible de créer le fifo { $path } : Le fichier existe
cp-error-invalid-attribute = attribut invalide { $value }
cp-error-failed-to-create-whole-tree = échec de la création de l'arborescence complète
cp-error-failed-to-create-directory = Échec de la création du répertoire : { $error }
cp-error-backup-format = cp : { $error }
Tentez '{ $exec } --help' pour plus d'informations.
# Debug enum strings
cp-debug-enum-no = non
cp-debug-enum-yes = oui
cp-debug-enum-avoided = évité
cp-debug-enum-unsupported = non supporté
cp-debug-enum-unknown = inconnu
cp-debug-enum-zeros = zéros
cp-debug-enum-seek-hole = SEEK_HOLE
cp-debug-enum-seek-hole-zeros = SEEK_HOLE + zéros
# Messages d'avertissement
cp-warning-source-specified-more-than-once = { $file_type } source { $source } spécifié plus d'une fois
# Messages verbeux et de débogage
cp-verbose-copied = { $source } -> { $dest }
cp-debug-skipped = { $path } ignoré
cp-verbose-created-directory = { $source } -> { $dest }
cp-debug-copy-offload = copy offload : { $offload }, reflink : { $reflink }, sparse detection : { $sparse }
# Invites
cp-prompt-overwrite = écraser { $path } ?
cp-prompt-overwrite-with-mode = remplacer { $path }, en écrasant le mode

View file

@ -20,6 +20,7 @@ use uucore::error::UIoError;
use uucore::fs::{
FileInformation, MissingHandling, ResolveMode, canonicalize, path_ends_with_terminator,
};
use uucore::locale::{get_message, get_message_with_args};
use uucore::show;
use uucore::show_error;
use uucore::uio_error;
@ -177,7 +178,13 @@ impl Entry {
let source_is_dir = source.is_dir();
if path_ends_with_terminator(context.target) && source_is_dir {
if let Err(e) = fs::create_dir_all(context.target) {
eprintln!("Failed to create directory: {e}");
eprintln!(
"{}",
get_message_with_args(
"cp-error-failed-to-create-directory",
HashMap::from([("error".to_string(), e.to_string())])
)
);
}
} else {
descendant = descendant.strip_prefix(context.root)?.to_path_buf();
@ -223,7 +230,7 @@ fn copy_direntry(
// exist, ...
if source_absolute.is_dir() && !local_to_target.exists() {
return if target_is_file {
Err("cannot overwrite non-directory with directory".into())
Err(get_message("cp-error-cannot-overwrite-non-directory-with-directory").into())
} else {
build_dir(&local_to_target, false, options, Some(&source_absolute))?;
if options.verbose {
@ -263,8 +270,14 @@ fn copy_direntry(
CpError::IoErrContext(e, _) if e.kind() == io::ErrorKind::PermissionDenied => {
show!(uio_error!(
e,
"cannot open {} for reading",
source_relative.quote(),
"{}",
get_message_with_args(
"cp-error-cannot-open-for-reading",
HashMap::from([(
"source".to_string(),
source_relative.quote().to_string()
)])
),
));
}
e => return Err(e),
@ -309,15 +322,24 @@ pub(crate) fn copy_directory(
}
if !options.recursive {
return Err(format!("-r not specified; omitting directory {}", root.quote()).into());
return Err(get_message_with_args(
"cp-error-omitting-directory",
HashMap::from([("dir".to_string(), root.quote().to_string())]),
)
.into());
}
// check if root is a prefix of target
if path_has_prefix(target, root)? {
return Err(format!(
"cannot copy a directory, {}, into itself, {}",
root.quote(),
target.join(root.file_name().unwrap()).quote()
return Err(get_message_with_args(
"cp-error-cannot-copy-directory-into-itself",
HashMap::from([
("source".to_string(), root.quote().to_string()),
(
"dest".to_string(),
target.join(root.file_name().unwrap()).quote().to_string(),
),
]),
)
.into());
}
@ -362,7 +384,13 @@ pub(crate) fn copy_directory(
// the target directory.
let context = match Context::new(root, target) {
Ok(c) => c,
Err(e) => return Err(format!("failed to get current directory {e}").into()),
Err(e) => {
return Err(get_message_with_args(
"cp-error-failed-get-current-dir",
HashMap::from([("error".to_string(), e.to_string())]),
)
.into());
}
};
// The directory we were in during the previous iteration

View file

@ -41,7 +41,7 @@ use uucore::{
};
use crate::copydir::copy_directory;
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
mod copydir;
mod platform;
@ -62,7 +62,7 @@ pub enum CpError {
/// Represents the state when a non-fatal error has occurred
/// and not all files were copied.
#[error("Not all files were copied")]
#[error("{}", get_message("cp-error-not-all-files-copied"))]
NotAllFilesCopied,
/// Simple walkdir::Error wrapper
@ -80,21 +80,21 @@ pub enum CpError {
#[error("Skipped copying file (exit with error = {0})")]
Skipped(bool),
/// Result of a skipped file
/// Invalid argument error
#[error("{0}")]
InvalidArgument(String),
/// All standard options are included as an an implementation
/// path, but those that are not implemented yet should return
/// a NotImplemented error.
#[error("Option '{0}' not yet implemented.")]
#[error("{}", get_message_with_args("cp-error-option-not-implemented", HashMap::from([("option".to_string(), 0.to_string())])))]
NotImplemented(String),
/// Invalid arguments to backup
#[error(transparent)]
Backup(#[from] BackupError),
#[error("'{}' is not a directory", .0.display())]
#[error("{}", get_message_with_args("cp-error-not-a-directory", HashMap::from([("path".to_string(), .0.quote().to_string())])))]
NotADirectory(PathBuf),
}
@ -118,9 +118,14 @@ impl Display for BackupError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"cp: {}\nTry '{} --help' for more information.",
self.0,
uucore::execution_phrase()
"{}",
get_message_with_args(
"cp-error-backup-format",
HashMap::from([
("error".to_string(), self.0.clone()),
("exec".to_string(), uucore::execution_phrase().to_string())
])
)
)
}
}
@ -415,28 +420,30 @@ struct CopyDebug {
sparse_detection: SparseDebug,
}
impl OffloadReflinkDebug {
fn to_string(&self) -> &'static str {
match self {
Self::No => "no",
Self::Yes => "yes",
Self::Avoided => "avoided",
Self::Unsupported => "unsupported",
Self::Unknown => "unknown",
}
impl Display for OffloadReflinkDebug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
Self::No => get_message("cp-debug-enum-no"),
Self::Yes => get_message("cp-debug-enum-yes"),
Self::Avoided => get_message("cp-debug-enum-avoided"),
Self::Unsupported => get_message("cp-debug-enum-unsupported"),
Self::Unknown => get_message("cp-debug-enum-unknown"),
};
write!(f, "{}", msg)
}
}
impl SparseDebug {
fn to_string(&self) -> &'static str {
match self {
Self::No => "no",
Self::Zeros => "zeros",
Self::SeekHole => "SEEK_HOLE",
Self::SeekHoleZeros => "SEEK_HOLE + zeros",
Self::Unsupported => "unsupported",
Self::Unknown => "unknown",
}
impl Display for SparseDebug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
Self::No => get_message("cp-debug-enum-no"),
Self::Zeros => get_message("cp-debug-enum-zeros"),
Self::SeekHole => get_message("cp-debug-enum-seek-hole"),
Self::SeekHoleZeros => get_message("cp-debug-enum-seek-hole-zeros"),
Self::Unsupported => get_message("cp-debug-enum-unsupported"),
Self::Unknown => get_message("cp-debug-enum-unknown"),
};
write!(f, "{}", msg)
}
}
@ -445,10 +452,18 @@ impl SparseDebug {
/// It prints the debug information of the offload, reflink, and sparse detection actions.
fn show_debug(copy_debug: &CopyDebug) {
println!(
"copy offload: {}, reflink: {}, sparse detection: {}",
copy_debug.offload.to_string(),
copy_debug.reflink.to_string(),
copy_debug.sparse_detection.to_string(),
"{}",
get_message_with_args(
"cp-debug-copy-offload",
HashMap::from([
("offload".to_string(), copy_debug.offload.to_string()),
("reflink".to_string(), copy_debug.reflink.to_string()),
(
"sparse".to_string(),
copy_debug.sparse_detection.to_string()
),
])
)
);
}
@ -537,14 +552,14 @@ pub fn uu_app() -> Command {
.value_name(options::TARGET_DIRECTORY)
.value_hint(clap::ValueHint::DirPath)
.value_parser(ValueParser::path_buf())
.help("copy all SOURCE arguments into target-directory"),
.help(get_message("cp-help-target-directory")),
)
.arg(
Arg::new(options::NO_TARGET_DIRECTORY)
.short('T')
.long(options::NO_TARGET_DIRECTORY)
.conflicts_with(options::TARGET_DIRECTORY)
.help("Treat DEST as a regular file and not a directory")
.help(get_message("cp-help-no-target-directory"))
.action(ArgAction::SetTrue),
)
.arg(
@ -552,7 +567,7 @@ pub fn uu_app() -> Command {
.short('i')
.long(options::INTERACTIVE)
.overrides_with(options::NO_CLOBBER)
.help("ask before overwriting files")
.help(get_message("cp-help-interactive"))
.action(ArgAction::SetTrue),
)
.arg(
@ -560,7 +575,7 @@ pub fn uu_app() -> Command {
.short('l')
.long(options::LINK)
.overrides_with_all(MODE_ARGS)
.help("hard-link files instead of copying")
.help(get_message("cp-help-link"))
.action(ArgAction::SetTrue),
)
.arg(
@ -568,7 +583,7 @@ pub fn uu_app() -> Command {
.short('n')
.long(options::NO_CLOBBER)
.overrides_with(options::INTERACTIVE)
.help("don't overwrite a file that already exists")
.help(get_message("cp-help-no-clobber"))
.action(ArgAction::SetTrue),
)
.arg(
@ -577,26 +592,26 @@ pub fn uu_app() -> Command {
.visible_short_alias('r')
.long(options::RECURSIVE)
// --archive sets this option
.help("copy directories recursively")
.help(get_message("cp-help-recursive"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::STRIP_TRAILING_SLASHES)
.long(options::STRIP_TRAILING_SLASHES)
.help("remove any trailing slashes from each SOURCE argument")
.help(get_message("cp-help-strip-trailing-slashes"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::DEBUG)
.long(options::DEBUG)
.help("explain how a file is copied. Implies -v")
.help(get_message("cp-help-debug"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::VERBOSE)
.short('v')
.long(options::VERBOSE)
.help("explicitly state what is being done")
.help(get_message("cp-help-verbose"))
.action(ArgAction::SetTrue),
)
.arg(
@ -604,29 +619,21 @@ pub fn uu_app() -> Command {
.short('s')
.long(options::SYMBOLIC_LINK)
.overrides_with_all(MODE_ARGS)
.help("make symbolic links instead of copying")
.help(get_message("cp-help-symbolic-link"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::FORCE)
.short('f')
.long(options::FORCE)
.help(
"if an existing destination file cannot be opened, remove it and \
try again (this option is ignored when the -n option is also used). \
Currently not implemented for Windows.",
)
.help(get_message("cp-help-force"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::REMOVE_DESTINATION)
.long(options::REMOVE_DESTINATION)
.overrides_with(options::FORCE)
.help(
"remove each existing destination file before attempting to open it \
(contrast with --force). On Windows, currently only works for \
writeable files.",
)
.help(get_message("cp-help-remove-destination"))
.action(ArgAction::SetTrue),
)
.arg(backup_control::arguments::backup())
@ -643,13 +650,13 @@ pub fn uu_app() -> Command {
.default_missing_value("always")
.value_parser(ShortcutValueParser::new(["auto", "always", "never"]))
.num_args(0..=1)
.help("control clone/CoW copies. See below"),
.help(get_message("cp-help-reflink")),
)
.arg(
Arg::new(options::ATTRIBUTES_ONLY)
.long(options::ATTRIBUTES_ONLY)
.overrides_with_all(MODE_ARGS)
.help("Don't copy the file data, just the attributes")
.help(get_message("cp-help-attributes-only"))
.action(ArgAction::SetTrue),
)
.arg(
@ -664,16 +671,13 @@ pub fn uu_app() -> Command {
.default_missing_value(PRESERVE_DEFAULT_VALUES)
// -d sets this option
// --archive sets this option
.help(
"Preserve the specified attributes (default: mode, ownership (unix only), \
timestamps), if possible additional attributes: context, links, xattr, all",
),
.help(get_message("cp-help-preserve")),
)
.arg(
Arg::new(options::PRESERVE_DEFAULT_ATTRIBUTES)
.short('p')
.long(options::PRESERVE_DEFAULT_ATTRIBUTES)
.help("same as --preserve=mode,ownership(unix only),timestamps")
.help(get_message("cp-help-preserve-default"))
.action(ArgAction::SetTrue),
)
.arg(
@ -685,13 +689,13 @@ pub fn uu_app() -> Command {
.num_args(0..)
.require_equals(true)
.value_name("ATTR_LIST")
.help("don't preserve the specified attributes"),
.help(get_message("cp-help-no-preserve")),
)
.arg(
Arg::new(options::PARENTS)
.long(options::PARENTS)
.alias(options::PARENT)
.help("use full source file name under DIRECTORY")
.help(get_message("cp-help-parents"))
.action(ArgAction::SetTrue),
)
.arg(
@ -700,7 +704,7 @@ pub fn uu_app() -> Command {
.long(options::NO_DEREFERENCE)
.overrides_with(options::DEREFERENCE)
// -d sets this option
.help("never follow symbolic links in SOURCE")
.help(get_message("cp-help-no-dereference"))
.action(ArgAction::SetTrue),
)
.arg(
@ -708,33 +712,33 @@ pub fn uu_app() -> Command {
.short('L')
.long(options::DEREFERENCE)
.overrides_with(options::NO_DEREFERENCE)
.help("always follow symbolic links in SOURCE")
.help(get_message("cp-help-dereference"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::CLI_SYMBOLIC_LINKS)
.short('H')
.help("follow command-line symbolic links in SOURCE")
.help(get_message("cp-help-cli-symbolic-links"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::ARCHIVE)
.short('a')
.long(options::ARCHIVE)
.help("Same as -dR --preserve=all")
.help(get_message("cp-help-archive"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::NO_DEREFERENCE_PRESERVE_LINKS)
.short('d')
.help("same as --no-dereference --preserve=links")
.help(get_message("cp-help-no-dereference-preserve-links"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::ONE_FILE_SYSTEM)
.short('x')
.long(options::ONE_FILE_SYSTEM)
.help("stay on this file system")
.help(get_message("cp-help-one-file-system"))
.action(ArgAction::SetTrue),
)
.arg(
@ -742,12 +746,12 @@ pub fn uu_app() -> Command {
.long(options::SPARSE)
.value_name("WHEN")
.value_parser(ShortcutValueParser::new(["never", "auto", "always"]))
.help("control creation of sparse files. See below"),
.help(get_message("cp-help-sparse")),
)
.arg(
Arg::new(options::SELINUX)
.short('Z')
.help("set SELinux security context of destination file to default type")
.help(get_message("cp-help-selinux"))
.action(ArgAction::SetTrue),
)
.arg(
@ -755,10 +759,7 @@ pub fn uu_app() -> Command {
.long(options::CONTEXT)
.value_name("CTX")
.value_parser(value_parser!(String))
.help(
"like -Z, or if CTX is specified then set the SELinux or SMACK security \
context to CTX",
)
.help(get_message("cp-help-context"))
.num_args(0..=1)
.require_equals(true)
.default_missing_value(""),
@ -770,17 +771,14 @@ pub fn uu_app() -> Command {
.long(options::PROGRESS_BAR)
.short('g')
.action(ArgAction::SetTrue)
.help(
"Display a progress bar. \n\
Note: this feature is not supported by GNU coreutils.",
),
.help(get_message("cp-help-progress")),
)
// TODO: implement the following args
.arg(
Arg::new(options::COPY_CONTENTS)
.long(options::COPY_CONTENTS)
.overrides_with(options::ATTRIBUTES_ONLY)
.help("NotImplemented: copy contents of special files when recursive")
.help(get_message("cp-help-copy-contents"))
.action(ArgAction::SetTrue),
)
// END TODO
@ -803,7 +801,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::None {
return Err(UUsageError::new(
EXIT_ERR,
"options --backup and --no-clobber are mutually exclusive",
get_message("cp-error-backup-mutually-exclusive"),
));
}
@ -984,9 +982,9 @@ impl Attributes {
"link" | "links" => &mut new.links,
"xattr" => &mut new.xattr,
_ => {
return Err(CpError::InvalidArgument(format!(
"invalid attribute {}",
value.quote()
return Err(CpError::InvalidArgument(get_message_with_args(
"cp-error-invalid-attribute",
HashMap::from([("value".to_string(), value.quote().to_string())]),
)));
}
};
@ -1030,7 +1028,7 @@ impl Options {
.is_some_and(|v| v == "none" || v == "none-fail")
{
return Err(CpError::InvalidArgument(
"--backup is mutually exclusive with -n or --update=none-fail".to_string(),
get_message("cp-error-invalid-backup-argument").to_string(),
));
}
@ -1134,7 +1132,7 @@ impl Options {
#[cfg(not(feature = "selinux"))]
if let Preserve::Yes { required } = attributes.context {
let selinux_disabled_error =
CpError::Error("SELinux was not enabled during the compile time!".to_owned());
CpError::Error(get_message("cp-error-selinux-not-enabled"));
if required {
return Err(selinux_disabled_error);
}
@ -1176,9 +1174,12 @@ impl Options {
"auto" => ReflinkMode::Auto,
"never" => ReflinkMode::Never,
value => {
return Err(CpError::InvalidArgument(format!(
"invalid argument {} for \'reflink\'",
value.quote()
return Err(CpError::InvalidArgument(get_message_with_args(
"cp-error-invalid-argument",
HashMap::from([
("arg".to_string(), value.quote().to_string()),
("option".to_string(), "reflink".to_string()),
]),
)));
}
}
@ -1193,8 +1194,12 @@ impl Options {
"auto" => SparseMode::Auto,
"never" => SparseMode::Never,
_ => {
return Err(CpError::InvalidArgument(format!(
"invalid argument {val} for \'sparse\'"
return Err(CpError::InvalidArgument(get_message_with_args(
"cp-error-invalid-argument",
HashMap::from([
("arg".to_string(), val.to_string()),
("option".to_string(), "sparse".to_string()),
]),
)));
}
}
@ -1269,12 +1274,15 @@ fn parse_path_args(
) -> CopyResult<(Vec<PathBuf>, PathBuf)> {
if paths.is_empty() {
// No files specified
return Err("missing file operand".into());
return Err(get_message("cp-error-missing-file-operand").into());
} else if paths.len() == 1 && options.target_dir.is_none() {
// Only one file specified
return Err(format!(
"missing destination file operand after {}",
paths[0].display().to_string().quote()
return Err(get_message_with_args(
"cp-error-missing-destination-operand",
HashMap::from([(
"source".to_string(),
paths[0].display().to_string().quote().to_string(),
)]),
)
.into());
}
@ -1282,7 +1290,14 @@ fn parse_path_args(
// Return an error if the user requested to copy more than one
// file source to a file target
if options.no_target_dir && options.target_dir.is_none() && paths.len() > 2 {
return Err(format!("extra operand {:}", paths[2].display().to_string().quote()).into());
return Err(get_message_with_args(
"cp-error-extra-operand",
HashMap::from([(
"operand".to_string(),
paths[2].display().to_string().quote().to_string(),
)]),
)
.into());
}
let target = match options.target_dir {
@ -1377,10 +1392,14 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult
} else {
"file"
};
show_warning!(
"source {file_type} {} specified more than once",
source.quote()
let msg = get_message_with_args(
"cp-warning-source-specified-more-than-once",
HashMap::from([
("file_type".to_string(), file_type.to_string()),
("source".to_string(), source.quote().to_string()),
]),
);
show_warning!("{}", msg);
} else {
let dest = construct_dest_path(source, target, target_type, options)
.unwrap_or_else(|_| target.to_path_buf());
@ -1395,10 +1414,12 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult
// There is already a file and it isn't a symlink (managed in a different place)
if copied_destinations.contains(&dest) && options.backup != BackupMode::Numbered {
// If the target file was already created in this cp call, do not overwrite
return Err(CpError::Error(format!(
"will not overwrite just-created '{}' with '{}'",
dest.display(),
source.display()
return Err(CpError::Error(get_message_with_args(
"cp-error-will-not-overwrite-just-created",
HashMap::from([
("dest".to_string(), dest.quote().to_string()),
("source".to_string(), source.quote().to_string()),
]),
)));
}
}
@ -1442,15 +1463,15 @@ fn construct_dest_path(
options: &Options,
) -> CopyResult<PathBuf> {
if options.no_target_dir && target.is_dir() {
return Err(format!(
"cannot overwrite directory {} with non-directory",
target.quote()
return Err(get_message_with_args(
"cp-error-cannot-overwrite-directory-with-non-directory",
HashMap::from([("dir".to_string(), target.quote().to_string())]),
)
.into());
}
if options.parents && !target.is_dir() {
return Err("with --parents, the destination must be a directory".into());
return Err(get_message("cp-error-with-parents-dest-must-be-dir").into());
}
Ok(match target_type {
@ -1575,7 +1596,13 @@ impl OverwriteMode {
match *self {
Self::NoClobber => {
if debug {
println!("skipped {}", path.quote());
println!(
"{}",
get_message_with_args(
"cp-debug-skipped",
HashMap::from([("path".to_string(), path.quote().to_string())])
)
);
}
Err(CpError::Skipped(false))
}
@ -1583,12 +1610,17 @@ impl OverwriteMode {
let prompt_yes_result = if let Some((octal, human_readable)) =
file_mode_for_interactive_overwrite(path)
{
prompt_yes!(
"replace {}, overriding mode {octal} ({human_readable})?",
path.quote()
)
let prompt_msg = get_message_with_args(
"cp-prompt-overwrite-with-mode",
HashMap::from([("path".to_string(), path.quote().to_string())]),
);
prompt_yes!("{} {octal} ({human_readable})?", prompt_msg)
} else {
prompt_yes!("overwrite {}?", path.quote())
let prompt_msg = get_message_with_args(
"cp-prompt-overwrite",
HashMap::from([("path".to_string(), path.quote().to_string())]),
);
prompt_yes!("{}", prompt_msg)
};
if prompt_yes_result {
@ -1621,8 +1653,8 @@ fn handle_preserve<F: Fn() -> CopyResult<()>>(p: &Preserve, f: F) -> CopyResult<
}
/// Copies extended attributes (xattrs) from `source` to `dest`, ensuring that `dest` is temporarily
/// user-writable if needed and restoring its original permissions afterward. This avoids Operation
/// not permitted errors on read-only files. Returns an error if permission or metadata operations fail,
/// user-writable if needed and restoring its original permissions afterward. This avoids "Operation
/// not permitted" errors on read-only files. Returns an error if permission or metadata operations fail,
/// or if xattr copying fails.
#[cfg(all(unix, not(target_os = "android")))]
fn copy_extended_attrs(source: &Path, dest: &Path) -> CopyResult<()> {
@ -1730,16 +1762,19 @@ pub(crate) fn copy_attributes(
if let Ok(context) = selinux::SecurityContext::of_path(source, false, false) {
if let Some(context) = context {
if let Err(e) = context.set_for_path(dest, false, false) {
return Err(CpError::Error(format!(
"failed to set the security context of {}: {e}",
dest.display()
return Err(CpError::Error(get_message_with_args(
"cp-error-selinux-set-context",
HashMap::from([
("path".to_string(), dest.display().to_string()),
("error".to_string(), e.to_string()),
]),
)));
}
}
} else {
return Err(CpError::Error(format!(
"failed to get security context of {}",
source.display()
return Err(CpError::Error(get_message_with_args(
"cp-error-selinux-get-context",
HashMap::from([("path".to_string(), source.display().to_string())]),
)));
}
Ok(())
@ -1780,10 +1815,24 @@ fn symlink_file(
std::os::unix::fs::symlink(source, dest).map_err(|e| {
CpError::IoErrContext(
e,
format!(
"cannot create symlink {} to {}",
get_filename(dest).unwrap_or("invalid file name").quote(),
get_filename(source).unwrap_or("invalid file name").quote()
get_message_with_args(
"cp-error-cannot-create-symlink",
HashMap::from([
(
"dest".to_string(),
get_filename(dest)
.unwrap_or("invalid file name")
.quote()
.to_string(),
),
(
"source".to_string(),
get_filename(source)
.unwrap_or("invalid file name")
.quote()
.to_string(),
),
]),
),
)
})?;
@ -1793,10 +1842,24 @@ fn symlink_file(
std::os::windows::fs::symlink_file(source, dest).map_err(|e| {
CpError::IoErrContext(
e,
format!(
"cannot create symlink {} to {}",
get_filename(dest).unwrap_or("invalid file name").quote(),
get_filename(source).unwrap_or("invalid file name").quote()
get_message_with_args(
"cp-error-cannot-create-symlink",
HashMap::from([
(
"dest".to_string(),
get_filename(dest)
.unwrap_or("invalid file name")
.quote()
.to_string(),
),
(
"source".to_string(),
get_filename(source)
.unwrap_or("invalid file name")
.quote()
.to_string(),
),
]),
),
)
})?;
@ -1887,7 +1950,14 @@ fn handle_existing_dest(
// Disallow copying a file to itself, unless `--force` and
// `--backup` are both specified.
if is_forbidden_to_copy_to_same_file(source, dest, options, source_in_command_line) {
return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into());
return Err(get_message_with_args(
"cp-error-same-file",
HashMap::from([
("source".to_string(), source.quote().to_string()),
("dest".to_string(), dest.quote().to_string()),
]),
)
.into());
}
if options.update == UpdateMode::None {
@ -1905,10 +1975,12 @@ fn handle_existing_dest(
let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
if let Some(backup_path) = backup_path {
if paths_refer_to_same_file(source, &backup_path, true) {
return Err(format!(
"backing up {} might destroy source; {} not copied",
dest.quote(),
source.quote()
return Err(get_message_with_args(
"cp-error-backing-up-destroy-source",
HashMap::from([
("dest".to_string(), dest.quote().to_string()),
("source".to_string(), source.quote().to_string()),
]),
)
.into());
}
@ -2063,7 +2135,16 @@ fn print_paths(parents: bool, source: &Path, dest: &Path) {
// a/b -> d/a/b
//
for (x, y) in aligned_ancestors(source, dest) {
println!("{} -> {}", x.display(), y.display());
println!(
"{}",
get_message_with_args(
"cp-verbose-created-directory",
HashMap::from([
("source".to_string(), x.display().to_string()),
("dest".to_string(), y.display().to_string())
])
)
);
}
}
@ -2117,10 +2198,24 @@ fn handle_copy_mode(
.map_err(|e| {
CpError::IoErrContext(
e,
format!(
"cannot create hard link {} to {}",
get_filename(dest).unwrap_or("invalid file name").quote(),
get_filename(source).unwrap_or("invalid file name").quote()
get_message_with_args(
"cp-error-cannot-create-hard-link",
HashMap::from([
(
"dest".to_string(),
get_filename(dest)
.unwrap_or("invalid file name")
.quote()
.to_string(),
),
(
"source".to_string(),
get_filename(source)
.unwrap_or("invalid file name")
.quote()
.to_string(),
),
]),
),
)
})?;
@ -2168,9 +2263,9 @@ fn handle_copy_mode(
return Ok(PerformedAction::Skipped);
}
UpdateMode::NoneFail => {
return Err(CpError::Error(format!(
"not replacing '{}'",
dest.display()
return Err(CpError::Error(get_message_with_args(
"cp-error-not-replacing",
HashMap::from([("file".to_string(), dest.quote().to_string())]),
)));
}
UpdateMode::IfOlder => {
@ -2296,20 +2391,24 @@ fn copy_file(
.map(|info| symlinked_files.contains(&info))
.unwrap_or(false)
{
return Err(CpError::Error(format!(
"will not copy '{}' through just-created symlink '{}'",
source.display(),
dest.display()
return Err(CpError::Error(get_message_with_args(
"cp-error-will-not-copy-through-symlink",
HashMap::from([
("source".to_string(), source.quote().to_string()),
("dest".to_string(), dest.quote().to_string()),
]),
)));
}
// Fail if cp tries to copy two sources of the same name into a single symlink
// Example: "cp file1 dir1/file1 tmp" where "tmp" is a directory containing a symlink "file1" pointing to a file named "foo".
// foo will contain the contents of "file1" and "dir1/file1" will not be copied over to "tmp/file1"
if copied_destinations.contains(dest) {
return Err(CpError::Error(format!(
"will not copy '{}' through just-created symlink '{}'",
source.display(),
dest.display()
return Err(CpError::Error(get_message_with_args(
"cp-error-will-not-copy-through-symlink",
HashMap::from([
("source".to_string(), source.quote().to_string()),
("dest".to_string(), dest.quote().to_string()),
]),
)));
}
@ -2323,9 +2422,9 @@ fn copy_file(
&& !is_symlink_loop(dest)
&& std::env::var_os("POSIXLY_CORRECT").is_none()
{
return Err(CpError::Error(format!(
"not writing through dangling symlink '{}'",
dest.display()
return Err(CpError::Error(get_message_with_args(
"cp-error-not-writing-dangling-symlink",
HashMap::from([("dest".to_string(), dest.quote().to_string())]),
)));
}
if paths_refer_to_same_file(source, dest, true)
@ -2392,9 +2491,9 @@ fn copy_file(
OverwriteMode::Clobber(ClobberMode::RemoveDestination)
)
{
return Err(format!(
"cannot change attribute {}: Source file is a non regular file",
dest.quote()
return Err(get_message_with_args(
"cp-error-cannot-change-attribute",
HashMap::from([("dest".to_string(), dest.quote().to_string())]),
)
.into());
}
@ -2430,7 +2529,10 @@ fn copy_file(
// this is just for gnu tests compatibility
result.map_err(|err| {
if err.to_string().contains("No such file or directory") {
return format!("cannot stat {}: No such file or directory", source.quote());
return get_message_with_args(
"cp-error-cannot-stat",
HashMap::from([("source".to_string(), source.quote().to_string())]),
);
}
err.to_string()
})?
@ -2499,7 +2601,10 @@ fn copy_file(
if let Err(e) =
uucore::selinux::set_selinux_security_context(dest, options.context.as_ref())
{
return Err(CpError::Error(format!("SELinux error: {}", e)));
return Err(CpError::Error(get_message_with_args(
"cp-error-selinux-error",
HashMap::from([("error".to_string(), e.to_string())]),
)));
}
}
@ -2617,7 +2722,13 @@ fn copy_fifo(dest: &Path, overwrite: OverwriteMode, debug: bool) -> CopyResult<(
fs::remove_file(dest)?;
}
make_fifo(dest).map_err(|_| format!("cannot create fifo {}: File exists", dest.quote()).into())
make_fifo(dest).map_err(|_| {
get_message_with_args(
"cp-error-cannot-create-fifo",
HashMap::from([("path".to_string(), dest.quote().to_string())]),
)
.into()
})
}
fn copy_link(
@ -2638,12 +2749,14 @@ fn copy_link(
/// Generate an error message if `target` is not the correct `target_type`
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
match (target_type, target.is_dir()) {
(&TargetType::Directory, false) => {
Err(format!("target: {} is not a directory", target.quote()).into())
}
(&TargetType::File, true) => Err(format!(
"cannot overwrite directory {} with non-directory",
target.quote()
(&TargetType::Directory, false) => Err(get_message_with_args(
"cp-error-target-not-directory",
HashMap::from([("target".to_string(), target.quote().to_string())]),
)
.into()),
(&TargetType::File, true) => Err(get_message_with_args(
"cp-error-cannot-overwrite-directory-with-non-directory",
HashMap::from([("dir".to_string(), target.quote().to_string())]),
)
.into()),
_ => Ok(()),

View file

@ -13,7 +13,7 @@ use std::os::unix::fs::{FileTypeExt, OpenOptionsExt};
use std::os::unix::io::AsRawFd;
use std::path::Path;
use uucore::buf_copy;
use uucore::locale::get_message;
use uucore::mode::get_umask;
use crate::{
@ -401,7 +401,7 @@ pub(crate) fn copy_on_write(
clone(source, dest, CloneFallback::Error)
}
(ReflinkMode::Always, _) => {
return Err("`--reflink=always` can be used only with --sparse=auto".into());
return Err(get_message("cp-error-reflink-always-sparse-auto").into());
}
};
result.map_err(|e| CpError::IoErrContext(e, context.to_owned()))?;

View file

@ -3,6 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore reflink
use std::collections::HashMap;
use std::ffi::CString;
use std::fs::{self, File, OpenOptions};
use std::os::unix::ffi::OsStrExt;
@ -10,6 +11,7 @@ use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use uucore::buf_copy;
use uucore::locale::{get_message, get_message_with_args};
use uucore::mode::get_umask;
use crate::{
@ -30,7 +32,9 @@ pub(crate) fn copy_on_write(
source_is_stream: bool,
) -> CopyResult<CopyDebug> {
if sparse_mode != SparseMode::Auto {
return Err("--sparse is only supported on linux".to_string().into());
return Err(get_message("cp-error-sparse-not-supported")
.to_string()
.into());
}
let mut copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unknown,
@ -85,10 +89,13 @@ pub(crate) fn copy_on_write(
// support COW).
match reflink_mode {
ReflinkMode::Always => {
return Err(format!(
"failed to clone {} from {}: {error}",
source.display(),
dest.display()
return Err(get_message_with_args(
"cp-error-failed-to-clone",
HashMap::from([
("source".to_string(), source.display().to_string()),
("dest".to_string(), dest.display().to_string()),
("error".to_string(), error.to_string()),
]),
)
.into());
}

View file

@ -5,6 +5,7 @@
// spell-checker:ignore reflink
use std::fs;
use std::path::Path;
use uucore::locale::get_message;
use crate::{
CopyDebug, CopyResult, CpError, OffloadReflinkDebug, ReflinkMode, SparseDebug, SparseMode,
@ -19,12 +20,14 @@ pub(crate) fn copy_on_write(
context: &str,
) -> CopyResult<CopyDebug> {
if reflink_mode != ReflinkMode::Never {
return Err("--reflink is only supported on linux and macOS"
return Err(get_message("cp-error-reflink-not-supported")
.to_string()
.into());
}
if sparse_mode != SparseMode::Auto {
return Err("--sparse is only supported on linux".to_string().into());
return Err(get_message("cp-error-sparse-not-supported")
.to_string()
.into());
}
let copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unsupported,

View file

@ -8,6 +8,7 @@ use std::os::unix::fs::OpenOptionsExt;
use std::path::Path;
use uucore::buf_copy;
use uucore::locale::get_message;
use uucore::mode::get_umask;
use crate::{
@ -25,12 +26,14 @@ pub(crate) fn copy_on_write(
source_is_stream: bool,
) -> CopyResult<CopyDebug> {
if reflink_mode != ReflinkMode::Never {
return Err("--reflink is only supported on linux and macOS"
return Err(get_message("cp-error-reflink-not-supported")
.to_string()
.into());
}
if sparse_mode != SparseMode::Auto {
return Err("--sparse is only supported on linux".to_string().into());
return Err(get_message("cp-error-sparse-not-supported")
.to_string()
.into());
}
let copy_debug = CopyDebug {
offload: OffloadReflinkDebug::Unsupported,

View file

@ -1,3 +1,29 @@
csplit-about = Split a file into sections determined by context lines
csplit-usage = csplit [OPTION]... FILE PATTERN...
csplit-after-help = Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output.
# Help messages
csplit-help-suffix-format = use sprintf FORMAT instead of %02d
csplit-help-prefix = use PREFIX instead of 'xx'
csplit-help-keep-files = do not remove output files on errors
csplit-help-suppress-matched = suppress the lines matching PATTERN
csplit-help-digits = use specified number of digits instead of 2
csplit-help-quiet = do not print counts of output file sizes
csplit-help-elide-empty-files = remove empty output files
# Error messages
csplit-error-line-out-of-range = { $pattern }: line number out of range
csplit-error-line-out-of-range-on-repetition = { $pattern }: line number out of range on repetition { $repetition }
csplit-error-match-not-found = { $pattern }: match not found
csplit-error-match-not-found-on-repetition = { $pattern }: match not found on repetition { $repetition }
csplit-error-line-number-is-zero = 0: line number must be greater than zero
csplit-error-line-number-smaller-than-previous = line number '{ $current }' is smaller than preceding line number, { $previous }
csplit-error-invalid-pattern = { $pattern }: invalid pattern
csplit-error-invalid-number = invalid number: { $number }
csplit-error-suffix-format-incorrect = incorrect conversion specification in suffix
csplit-error-suffix-format-too-many-percents = too many % conversion specifications in suffix
csplit-error-not-regular-file = { $file } is not a regular file
csplit-warning-line-number-same-as-previous = line number '{ $line_number }' is the same as preceding line number
csplit-stream-not-utf8 = stream did not contain valid UTF-8
csplit-read-error = read error
csplit-write-split-not-created = trying to write to a split that was not created

View file

@ -0,0 +1,29 @@
csplit-about = Diviser un fichier en sections déterminées par des lignes de contexte
csplit-usage = csplit [OPTION]... FICHIER MOTIF...
csplit-after-help = Sortir les morceaux de FICHIER séparés par MOTIF(S) dans les fichiers 'xx00', 'xx01', ..., et sortir le nombre d'octets de chaque morceau sur la sortie standard.
# Messages d'aide
csplit-help-suffix-format = utiliser le FORMAT sprintf au lieu de %02d
csplit-help-prefix = utiliser PRÉFIXE au lieu de 'xx'
csplit-help-keep-files = ne pas supprimer les fichiers de sortie en cas d'erreurs
csplit-help-suppress-matched = supprimer les lignes correspondant au MOTIF
csplit-help-digits = utiliser le nombre spécifié de chiffres au lieu de 2
csplit-help-quiet = ne pas afficher le nombre d'octets des fichiers de sortie
csplit-help-elide-empty-files = supprimer les fichiers de sortie vides
# Messages d'erreur
csplit-error-line-out-of-range = { $pattern } : numéro de ligne hors limites
csplit-error-line-out-of-range-on-repetition = { $pattern } : numéro de ligne hors limites à la répétition { $repetition }
csplit-error-match-not-found = { $pattern } : correspondance non trouvée
csplit-error-match-not-found-on-repetition = { $pattern } : correspondance non trouvée à la répétition { $repetition }
csplit-error-line-number-is-zero = 0 : le numéro de ligne doit être supérieur à zéro
csplit-error-line-number-smaller-than-previous = le numéro de ligne '{ $current }' est plus petit que le numéro de ligne précédent, { $previous }
csplit-error-invalid-pattern = { $pattern } : motif invalide
csplit-error-invalid-number = nombre invalide : { $number }
csplit-error-suffix-format-incorrect = spécification de conversion incorrecte dans le suffixe
csplit-error-suffix-format-too-many-percents = trop de spécifications de conversion % dans le suffixe
csplit-error-not-regular-file = { $file } n'est pas un fichier régulier
csplit-warning-line-number-same-as-previous = le numéro de ligne '{ $line_number }' est identique au numéro de ligne précédent
csplit-stream-not-utf8 = le flux ne contenait pas d'UTF-8 valide
csplit-read-error = erreur de lecture
csplit-write-split-not-created = tentative d'écriture dans une division qui n'a pas été créée

View file

@ -85,7 +85,10 @@ impl<T: BufRead> Iterator for LinesWithNewlines<T> {
fn next(&mut self) -> Option<Self::Item> {
fn ret(v: Vec<u8>) -> io::Result<String> {
String::from_utf8(v).map_err(|_| {
io::Error::new(ErrorKind::InvalidData, "stream did not contain valid UTF-8")
io::Error::new(
ErrorKind::InvalidData,
get_message("csplit-stream-not-utf8"),
)
})
}
@ -115,7 +118,7 @@ where
T: BufRead,
{
let enumerated_input_lines = LinesWithNewlines::new(input)
.map(|line| line.map_err_context(|| "read error".to_string()))
.map(|line| line.map_err_context(|| get_message("csplit-read-error")))
.enumerate();
let mut input_iter = InputSplitter::new(enumerated_input_lines);
let mut split_writer = SplitWriter::new(options);
@ -283,7 +286,7 @@ impl SplitWriter<'_> {
current_writer.write_all(bytes)?;
self.size += bytes.len();
}
None => panic!("trying to write to a split that was not created"),
None => panic!("{}", get_message("csplit-write-split-not-created")),
}
}
Ok(())
@ -638,26 +641,26 @@ pub fn uu_app() -> Command {
.short('b')
.long(options::SUFFIX_FORMAT)
.value_name("FORMAT")
.help("use sprintf FORMAT instead of %02d"),
.help(get_message("csplit-help-suffix-format")),
)
.arg(
Arg::new(options::PREFIX)
.short('f')
.long(options::PREFIX)
.value_name("PREFIX")
.help("use PREFIX instead of 'xx'"),
.help(get_message("csplit-help-prefix")),
)
.arg(
Arg::new(options::KEEP_FILES)
.short('k')
.long(options::KEEP_FILES)
.help("do not remove output files on errors")
.help(get_message("csplit-help-keep-files"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::SUPPRESS_MATCHED)
.long(options::SUPPRESS_MATCHED)
.help("suppress the lines matching PATTERN")
.help(get_message("csplit-help-suppress-matched"))
.action(ArgAction::SetTrue),
)
.arg(
@ -665,7 +668,7 @@ pub fn uu_app() -> Command {
.short('n')
.long(options::DIGITS)
.value_name("DIGITS")
.help("use specified number of digits instead of 2"),
.help(get_message("csplit-help-digits")),
)
.arg(
Arg::new(options::QUIET)
@ -673,14 +676,14 @@ pub fn uu_app() -> Command {
.long(options::QUIET)
.visible_short_alias('s')
.visible_alias("silent")
.help("do not print counts of output file sizes")
.help(get_message("csplit-help-quiet"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::ELIDE_EMPTY_FILES)
.short('z')
.long(options::ELIDE_EMPTY_FILES)
.help("remove empty output files")
.help(get_message("csplit-help-elide-empty-files"))
.action(ArgAction::SetTrue),
)
.arg(

View file

@ -2,38 +2,40 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::collections::HashMap;
use std::io;
use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::UError;
use uucore::locale::{get_message, get_message_with_args};
/// Errors thrown by the csplit command
#[derive(Debug, Error)]
pub enum CsplitError {
#[error("IO error: {}", _0)]
IoError(#[from] io::Error),
#[error("{}: line number out of range", ._0.quote())]
#[error("{}", get_message_with_args("csplit-error-line-out-of-range", HashMap::from([("pattern".to_string(), _0.quote().to_string())])))]
LineOutOfRange(String),
#[error("{}: line number out of range on repetition {}", ._0.quote(), _1)]
#[error("{}", get_message_with_args("csplit-error-line-out-of-range-on-repetition", HashMap::from([("pattern".to_string(), _0.quote().to_string()), ("repetition".to_string(), _1.to_string())])))]
LineOutOfRangeOnRepetition(String, usize),
#[error("{}: match not found", ._0.quote())]
#[error("{}", get_message_with_args("csplit-error-match-not-found", HashMap::from([("pattern".to_string(), _0.quote().to_string())])))]
MatchNotFound(String),
#[error("{}: match not found on repetition {}", ._0.quote(), _1)]
#[error("{}", get_message_with_args("csplit-error-match-not-found-on-repetition", HashMap::from([("pattern".to_string(), _0.quote().to_string()), ("repetition".to_string(), _1.to_string())])))]
MatchNotFoundOnRepetition(String, usize),
#[error("0: line number must be greater than zero")]
#[error("{}", get_message("csplit-error-line-number-is-zero"))]
LineNumberIsZero,
#[error("line number '{}' is smaller than preceding line number, {}", _0, _1)]
#[error("{}", get_message_with_args("csplit-error-line-number-smaller-than-previous", HashMap::from([("current".to_string(), _0.to_string()), ("previous".to_string(), _1.to_string())])))]
LineNumberSmallerThanPrevious(usize, usize),
#[error("{}: invalid pattern", ._0.quote())]
#[error("{}", get_message_with_args("csplit-error-invalid-pattern", HashMap::from([("pattern".to_string(), _0.quote().to_string())])))]
InvalidPattern(String),
#[error("invalid number: {}", ._0.quote())]
#[error("{}", get_message_with_args("csplit-error-invalid-number", HashMap::from([("number".to_string(), _0.quote().to_string())])))]
InvalidNumber(String),
#[error("incorrect conversion specification in suffix")]
#[error("{}", get_message("csplit-error-suffix-format-incorrect"))]
SuffixFormatIncorrect,
#[error("too many % conversion specifications in suffix")]
#[error("{}", get_message("csplit-error-suffix-format-too-many-percents"))]
SuffixFormatTooManyPercents,
#[error("{} is not a regular file", ._0.quote())]
#[error("{}", get_message_with_args("csplit-error-not-regular-file", HashMap::from([("file".to_string(), _0.quote().to_string())])))]
NotRegularFile(String),
#[error("{}", _0)]
UError(Box<dyn UError>),

View file

@ -6,6 +6,8 @@
use crate::csplit_error::CsplitError;
use regex::Regex;
use std::collections::HashMap;
use uucore::locale::get_message_with_args;
use uucore::show_warning;
/// The definition of a pattern to match on a line.
@ -168,7 +170,13 @@ fn validate_line_numbers(patterns: &[Pattern]) -> Result<(), CsplitError> {
(_, 0) => Err(CsplitError::LineNumberIsZero),
// two consecutive numbers should not be equal
(n, m) if n == m => {
show_warning!("line number '{n}' is the same as preceding line number");
show_warning!(
"{}",
get_message_with_args(
"csplit-warning-line-number-same-as-previous",
HashMap::from([("line_number".to_string(), n.to_string())])
)
);
Ok(n)
}
// a number cannot be greater than the one that follows

View file

@ -74,3 +74,31 @@ date-usage =
Show the time on the west coast of the US (use tzselect(1) to find TZ)
TZ='America/Los_Angeles' date
date-help-date = display time described by STRING, not 'now'
date-help-file = like --date; once for each line of DATEFILE
date-help-iso-8601 = output date/time in ISO 8601 format.
FMT='date' for date only (the default),
'hours', 'minutes', 'seconds', or 'ns'
for date and time to the indicated precision.
Example: 2006-08-14T02:34:56-06:00
date-help-rfc-email = output date and time in RFC 5322 format.
Example: Mon, 14 Aug 2006 02:34:56 -0600
date-help-rfc-3339 = output date/time in RFC 3339 format.
FMT='date', 'seconds', or 'ns'
for date and time to the indicated precision.
Example: 2006-08-14 02:34:56-06:00
date-help-debug = annotate the parsed date, and warn about questionable usage to stderr
date-help-reference = display the last modification time of FILE
date-help-set = set time described by STRING
date-help-set-macos = set time described by STRING (not available on mac yet)
date-help-set-redox = set time described by STRING (not available on redox yet)
date-help-universal = print or set Coordinated Universal Time (UTC)
date-error-invalid-date = invalid date '{$date}'
date-error-invalid-format = invalid format '{$format}' ({$error})
date-error-expected-file-got-directory = expected file, got directory '{$path}'
date-error-date-overflow = date overflow '{$date}'
date-error-setting-date-not-supported-macos = setting the date is not supported by macOS
date-error-setting-date-not-supported-redox = setting the date is not supported by Redox
date-error-cannot-set-date = cannot set date

View file

@ -0,0 +1,101 @@
date-about = afficher ou définir la date système
date-usage = [OPTION]... [+FORMAT]
date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
FORMAT contrôle la sortie. Les séquences interprétées sont :
{ "| Séquence | Description | Exemple |" }
{ "| -------- | -------------------------------------------------------------- | ---------------------- |" }
{ "| %% | un % littéral | % |" }
{ "| %a | nom abrégé du jour de la semaine selon la locale | dim |" }
{ "| %A | nom complet du jour de la semaine selon la locale | dimanche |" }
{ "| %b | nom abrégé du mois selon la locale | jan |" }
{ "| %B | nom complet du mois selon la locale | janvier |" }
{ "| %c | date et heure selon la locale | jeu 3 mar 23:05:25 2005|" }
{ "| %C | siècle ; comme %Y, sauf qu'on omet les deux derniers chiffres | 20 |" }
{ "| %d | jour du mois | 01 |" }
{ "| %D | date ; identique à %m/%d/%y | 12/31/99 |" }
{ "| %e | jour du mois, rempli avec des espaces ; identique à %_d | 3 |" }
{ "| %F | date complète ; identique à %Y-%m-%d | 2005-03-03 |" }
{ "| %g | deux derniers chiffres de l'année du numéro de semaine ISO (voir %G) | 05 |" }
{ "| %G | année du numéro de semaine ISO (voir %V) ; normalement utile seulement avec %V | 2005 |" }
{ "| %h | identique à %b | jan |" }
{ "| %H | heure (00..23) | 23 |" }
{ "| %I | heure (01..12) | 11 |" }
{ "| %j | jour de l'année (001..366) | 062 |" }
{ "| %k | heure, remplie avec des espaces ( 0..23) ; identique à %_H | 3 |" }
{ "| %l | heure, remplie avec des espaces ( 1..12) ; identique à %_I | 9 |" }
{ "| %m | mois (01..12) | 03 |" }
{ "| %M | minute (00..59) | 30 |" }
{ "| %n | une nouvelle ligne | \\n |" }
{ "| %N | nanosecondes (000000000..999999999) | 123456789 |" }
{ "| %p | équivalent locale de AM ou PM ; vide si inconnu | PM |" }
{ "| %P | comme %p, mais en minuscules | pm |" }
{ "| %q | trimestre de l'année (1..4) | 1 |" }
{ "| %r | heure sur 12 heures selon la locale | 11:11:04 PM |" }
{ "| %R | heure sur 24 heures et minute ; identique à %H:%M | 23:30 |" }
{ "| %s | secondes depuis 1970-01-01 00:00:00 UTC | 1615432800 |" }
{ "| %S | seconde (00..60) | 30 |" }
{ "| %t | une tabulation | \\t |" }
{ "| %T | heure ; identique à %H:%M:%S | 23:30:30 |" }
{ "| %u | jour de la semaine (1..7) ; 1 est lundi | 4 |" }
{ "| %U | numéro de semaine de l'année, avec dimanche comme premier jour de la semaine (00..53) | 10 |" }
{ "| %V | numéro de semaine ISO, avec lundi comme premier jour de la semaine (01..53) | 12 |" }
{ "| %w | jour de la semaine (0..6) ; 0 est dimanche | 4 |" }
{ "| %W | numéro de semaine de l'année, avec lundi comme premier jour de la semaine (00..53) | 11 |" }
{ "| %x | représentation de la date selon la locale | 03/03/2005 |" }
{ "| %X | représentation de l'heure selon la locale | 23:30:30 |" }
{ "| %y | deux derniers chiffres de l'année (00..99) | 05 |" }
{ "| %Y | année | 2005 |" }
{ "| %z | fuseau horaire numérique +hhmm | -0400 |" }
{ "| %:z | fuseau horaire numérique +hh:mm | -04:00 |" }
{ "| %::z | fuseau horaire numérique +hh:mm:ss | -04:00:00 |" }
{ "| %:::z | fuseau horaire numérique avec : à la précision nécessaire | -04, +05:30 |" }
{ "| %Z | abréviation alphabétique du fuseau horaire | EDT |" }
Par défaut, date remplit les champs numériques avec des zéros.
Les indicateurs optionnels suivants peuvent suivre '%' :
{ "* `-` (tiret) ne pas remplir le champ" }
{ "* `_` (soulignement) remplir avec des espaces" }
{ "* `0` (zéro) remplir avec des zéros" }
{ "* `^` utiliser des majuscules si possible" }
{ "* `#` utiliser l'inverse si possible" }
Après tout indicateur vient une largeur de champ optionnelle, comme nombre décimal ;
puis un modificateur optionnel, qui est soit
{ "* `E` pour utiliser les représentations alternatives de la locale si disponibles, ou" }
{ "* `O` pour utiliser les symboles numériques alternatifs de la locale si disponibles." }
Exemples :
Convertir les secondes depuis l'époque (1970-01-01 UTC) en date
date --date='@2147483647'
Montrer l'heure sur la côte ouest des États-Unis (utiliser tzselect(1) pour trouver TZ)
TZ='America/Los_Angeles' date
date-help-date = afficher l'heure décrite par CHAÎNE, pas 'maintenant'
date-help-file = comme --date ; une fois pour chaque ligne de FICHIER_DATE
date-help-iso-8601 = afficher la date/heure au format ISO 8601.
FMT='date' pour la date seulement (par défaut),
'hours', 'minutes', 'seconds', ou 'ns'
pour la date et l'heure à la précision indiquée.
Exemple : 2006-08-14T02:34:56-06:00
date-help-rfc-email = afficher la date et l'heure au format RFC 5322.
Exemple : Mon, 14 Aug 2006 02:34:56 -0600
date-help-rfc-3339 = afficher la date/heure au format RFC 3339.
FMT='date', 'seconds', ou 'ns'
pour la date et l'heure à la précision indiquée.
Exemple : 2006-08-14 02:34:56-06:00
date-help-debug = annoter la date analysée et avertir des usages douteux sur stderr
date-help-reference = afficher l'heure de dernière modification du FICHIER
date-help-set = définir l'heure décrite par CHAÎNE
date-help-set-macos = définir l'heure décrite par CHAÎNE (pas encore disponible sur mac)
date-help-set-redox = définir l'heure décrite par CHAÎNE (pas encore disponible sur redox)
date-help-universal = afficher ou définir le Temps Universel Coordonné (UTC)
date-error-invalid-date = date invalide '{$date}'
date-error-invalid-format = format invalide '{$format}' ({$error})
date-error-expected-file-got-directory = fichier attendu, répertoire obtenu '{$path}'
date-error-date-overflow = débordement de date '{$date}'
date-error-setting-date-not-supported-macos = la définition de la date n'est pas prise en charge par macOS
date-error-setting-date-not-supported-redox = la définition de la date n'est pas prise en charge par Redox
date-error-cannot-set-date = impossible de définir la date

View file

@ -14,14 +14,14 @@ use libc::{CLOCK_REALTIME, clock_settime, timespec};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use uucore::display::Quotable;
use uucore::error::FromIo;
use uucore::error::{UResult, USimpleError};
use uucore::{format_usage, show};
#[cfg(windows)]
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
use uucore::locale::get_message;
use std::collections::HashMap;
use uucore::locale::{get_message, get_message_with_args};
use uucore::parser::shortcut_value_parser::ShortcutValueParser;
// Options
@ -43,29 +43,6 @@ const OPT_REFERENCE: &str = "reference";
const OPT_UNIVERSAL: &str = "universal";
const OPT_UNIVERSAL_2: &str = "utc";
// Help strings
static ISO_8601_HELP_STRING: &str = "output date/time in ISO 8601 format.
FMT='date' for date only (the default),
'hours', 'minutes', 'seconds', or 'ns'
for date and time to the indicated precision.
Example: 2006-08-14T02:34:56-06:00";
static RFC_5322_HELP_STRING: &str = "output date and time in RFC 5322 format.
Example: Mon, 14 Aug 2006 02:34:56 -0600";
static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format.
FMT='date', 'seconds', or 'ns'
for date and time to the indicated precision.
Example: 2006-08-14 02:34:56-06:00";
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
static OPT_SET_HELP_STRING: &str = "set time described by STRING";
#[cfg(target_os = "macos")]
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)";
#[cfg(target_os = "redox")]
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on redox yet)";
/// Settings for this program, parsed from the command line
struct Settings {
utc: bool,
@ -141,7 +118,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if !form.starts_with('+') {
return Err(USimpleError::new(
1,
format!("invalid date {}", form.quote()),
get_message_with_args(
"date-error-invalid-date",
HashMap::from([("date".to_string(), form.to_string())]),
),
));
}
let form = form[1..].to_string();
@ -182,7 +162,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Some(Err((input, _err))) => {
return Err(USimpleError::new(
1,
format!("invalid date {}", input.quote()),
get_message_with_args(
"date-error-invalid-date",
HashMap::from([("date".to_string(), input.to_string())]),
),
));
}
Some(Ok(date)) => Some(date),
@ -231,7 +214,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Err(_) => {
return Err(USimpleError::new(
1,
format!("invalid date {relative_time}"),
get_message_with_args(
"date-error-date-overflow",
HashMap::from([("date".to_string(), relative_time.to_string())]),
),
));
}
}
@ -245,7 +231,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if path.is_dir() {
return Err(USimpleError::new(
2,
format!("expected file, got directory {}", path.quote()),
get_message_with_args(
"date-error-expected-file-got-directory",
HashMap::from([("path".to_string(), path.to_string_lossy().to_string())]),
),
));
}
let file = File::open(path)
@ -271,13 +260,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Err(e) => {
return Err(USimpleError::new(
1,
format!("invalid format {format_string} ({e})"),
get_message_with_args(
"date-error-invalid-format",
HashMap::from([
("format".to_string(), format_string.to_string()),
("error".to_string(), e.to_string()),
]),
),
));
}
},
Err((input, _err)) => show!(USimpleError::new(
1,
format!("invalid date {}", input.quote())
get_message_with_args(
"date-error-invalid-date",
HashMap::from([("date".to_string(), input.to_string())])
)
)),
}
}
@ -297,7 +295,7 @@ pub fn uu_app() -> Command {
.long(OPT_DATE)
.value_name("STRING")
.allow_hyphen_values(true)
.help("display time described by STRING, not 'now'"),
.help(get_message("date-help-date")),
)
.arg(
Arg::new(OPT_FILE)
@ -305,7 +303,7 @@ pub fn uu_app() -> Command {
.long(OPT_FILE)
.value_name("DATEFILE")
.value_hint(clap::ValueHint::FilePath)
.help("like --date; once for each line of DATEFILE"),
.help(get_message("date-help-file")),
)
.arg(
Arg::new(OPT_ISO_8601)
@ -317,13 +315,13 @@ pub fn uu_app() -> Command {
]))
.num_args(0..=1)
.default_missing_value(OPT_DATE)
.help(ISO_8601_HELP_STRING),
.help(get_message("date-help-iso-8601")),
)
.arg(
Arg::new(OPT_RFC_EMAIL)
.short('R')
.long(OPT_RFC_EMAIL)
.help(RFC_5322_HELP_STRING)
.help(get_message("date-help-rfc-email"))
.action(ArgAction::SetTrue),
)
.arg(
@ -331,12 +329,12 @@ pub fn uu_app() -> Command {
.long(OPT_RFC_3339)
.value_name("FMT")
.value_parser(ShortcutValueParser::new([DATE, SECONDS, NS]))
.help(RFC_3339_HELP_STRING),
.help(get_message("date-help-rfc-3339")),
)
.arg(
Arg::new(OPT_DEBUG)
.long(OPT_DEBUG)
.help("annotate the parsed date, and warn about questionable usage to stderr")
.help(get_message("date-help-debug"))
.action(ArgAction::SetTrue),
)
.arg(
@ -345,21 +343,34 @@ pub fn uu_app() -> Command {
.long(OPT_REFERENCE)
.value_name("FILE")
.value_hint(clap::ValueHint::AnyPath)
.help("display the last modification time of FILE"),
.help(get_message("date-help-reference")),
)
.arg(
Arg::new(OPT_SET)
.short('s')
.long(OPT_SET)
.value_name("STRING")
.help(OPT_SET_HELP_STRING),
.help({
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
{
get_message("date-help-set")
}
#[cfg(target_os = "macos")]
{
get_message("date-help-set-macos")
}
#[cfg(target_os = "redox")]
{
get_message("date-help-set-redox")
}
}),
)
.arg(
Arg::new(OPT_UNIVERSAL)
.short('u')
.long(OPT_UNIVERSAL)
.alias(OPT_UNIVERSAL_2)
.help("print or set Coordinated Universal Time (UTC)")
.help(get_message("date-help-universal"))
.action(ArgAction::SetTrue),
)
.arg(Arg::new(OPT_FORMAT))
@ -427,7 +438,7 @@ fn set_system_datetime(_date: Zoned) -> UResult<()> {
fn set_system_datetime(_date: Zoned) -> UResult<()> {
Err(USimpleError::new(
1,
"setting the date is not supported by macOS".to_string(),
get_message("date-error-setting-date-not-supported-macos"),
))
}
@ -435,7 +446,7 @@ fn set_system_datetime(_date: Zoned) -> UResult<()> {
fn set_system_datetime(_date: Zoned) -> UResult<()> {
Err(USimpleError::new(
1,
"setting the date is not supported by Redox".to_string(),
get_message("date-error-setting-date-not-supported-redox"),
))
}
@ -452,12 +463,13 @@ fn set_system_datetime(date: Zoned) -> UResult<()> {
tv_nsec: ts.subsec_nanosecond() as _,
};
let result = unsafe { clock_settime(CLOCK_REALTIME, &timespec) };
let result = unsafe { clock_settime(CLOCK_REALTIME, &raw const timespec) };
if result == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error().map_err_context(|| "cannot set date".to_string()))
Err(std::io::Error::last_os_error()
.map_err_context(|| get_message("date-error-cannot-set-date")))
}
}
@ -480,10 +492,11 @@ fn set_system_datetime(date: Zoned) -> UResult<()> {
wMilliseconds: ((date.subsec_nanosecond() / 1_000_000) % 1000) as u16,
};
let result = unsafe { SetSystemTime(&system_time) };
let result = unsafe { SetSystemTime(&raw const system_time) };
if result == 0 {
Err(std::io::Error::last_os_error().map_err_context(|| "cannot set date".to_string()))
Err(std::io::Error::last_os_error()
.map_err_context(|| get_message("date-error-cannot-set-date")))
} else {
Ok(())
}

View file

@ -113,3 +113,48 @@ dd-after-help = ### Operands
- nocache : request that OS drop cache.
- noctty : do not assign a controlling tty.
- nofollow : do not follow system links.
# Error messages
dd-error-failed-to-open = failed to open { $path }
dd-error-write-error = write error
dd-error-failed-to-seek = failed to seek in output file
dd-error-io-error = IO error
dd-error-cannot-skip-offset = '{ $file }': cannot skip to specified offset
dd-error-cannot-skip-invalid = '{ $file }': cannot skip: Invalid argument
dd-error-cannot-seek-invalid = '{ $output }': cannot seek: Invalid argument
dd-error-not-directory = setting flags for '{ $file }': Not a directory
dd-error-failed-discard-cache-input = failed to discard cache for: 'standard input'
dd-error-failed-discard-cache-output = failed to discard cache for: 'standard output'
# Parse errors
dd-error-unrecognized-operand = Unrecognized operand '{ $operand }'
dd-error-multiple-format-table = Only one of conv=ascii conv=ebcdic or conv=ibm may be specified
dd-error-multiple-case = Only one of conv=lcase or conv=ucase may be specified
dd-error-multiple-block = Only one of conv=block or conv=unblock may be specified
dd-error-multiple-excl = Only one ov conv=excl or conv=nocreat may be specified
dd-error-invalid-flag = invalid input flag: { $flag }
Try '{ $cmd } --help' for more information.
dd-error-conv-flag-no-match = Unrecognized conv=CONV -> { $flag }
dd-error-multiplier-parse-failure = invalid number: '{ $input }'
dd-error-multiplier-overflow = Multiplier string would overflow on current system -> { $input }
dd-error-block-without-cbs = conv=block or conv=unblock specified without cbs=N
dd-error-status-not-recognized = status=LEVEL not recognized -> { $level }
dd-error-unimplemented = feature not implemented on this system -> { $feature }
dd-error-bs-out-of-range = { $param }=N cannot fit into memory
dd-error-invalid-number = invalid number: { $input }
# Progress messages
dd-progress-records-in = { $complete }+{ $partial } records in
dd-progress-records-out = { $complete }+{ $partial } records out
dd-progress-truncated-record = { $count ->
[one] { $count } truncated record
*[other] { $count } truncated records
}
dd-progress-byte-copied = { $bytes } byte copied, { $duration } s, { $rate }/s
dd-progress-bytes-copied = { $bytes } bytes copied, { $duration } s, { $rate }/s
dd-progress-bytes-copied-si = { $bytes } bytes ({ $si }) copied, { $duration } s, { $rate }/s
dd-progress-bytes-copied-si-iec = { $bytes } bytes ({ $si }, { $iec }) copied, { $duration } s, { $rate }/s
# Warnings
dd-warning-zero-multiplier = { $zero } is a zero multiplier; use { $alternative } if that is intended
dd-warning-signal-handler = Internal dd Warning: Unable to register signal handler

160
src/uu/dd/locales/fr-FR.ftl Normal file
View file

@ -0,0 +1,160 @@
dd-about = Copier, et optionnellement convertir, une ressource du système de fichiers
dd-usage = dd [OPÉRANDE]...
dd OPTION
dd-after-help = ### Opérandes
- bs=OCTETS : lire et écrire jusqu'à OCTETS octets à la fois (par défaut : 512) ;
remplace ibs et obs.
- cbs=OCTETS : la 'taille de bloc de conversion' en octets. S'applique aux
opérations conv=block et conv=unblock.
- conv=CONVS : une liste séparée par des virgules d'options de conversion ou (pour des
raisons historiques) d'indicateurs de fichier.
- count=N : arrêter la lecture de l'entrée après N opérations de lecture de taille ibs
plutôt que de continuer jusqu'à EOF. Voir iflag=count_bytes si l'arrêt après N octets
est préféré
- ibs=N : la taille du tampon utilisé pour les lectures (par défaut : 512)
- if=FICHIER : le fichier utilisé pour l'entrée. Quand non spécifié, stdin est utilisé à la place
- iflag=INDICATEURS : une liste séparée par des virgules d'indicateurs d'entrée qui spécifient comment
la source d'entrée est traitée. INDICATEURS peut être n'importe lequel des indicateurs d'entrée ou
indicateurs généraux spécifiés ci-dessous.
- skip=N (ou iseek=N) : ignorer N enregistrements de taille ibs dans l'entrée avant de commencer
les opérations de copie/conversion. Voir iflag=seek_bytes si la recherche de N octets est préférée.
- obs=N : la taille du tampon utilisé pour les écritures (par défaut : 512)
- of=FICHIER : le fichier utilisé pour la sortie. Quand non spécifié, stdout est utilisé
à la place
- oflag=INDICATEURS : liste séparée par des virgules d'indicateurs de sortie qui spécifient comment la
source de sortie est traitée. INDICATEURS peut être n'importe lequel des indicateurs de sortie ou
indicateurs généraux spécifiés ci-dessous
- seek=N (ou oseek=N) : recherche N enregistrements de taille obs dans la sortie avant de
commencer les opérations de copie/conversion. Voir oflag=seek_bytes si la recherche de N octets est
préférée
- status=NIVEAU : contrôle si les statistiques de volume et de performance sont écrites sur
stderr.
Quand non spécifié, dd affichera les statistiques à la fin. Un exemple est ci-dessous.
```plain
6+0 enregistrements en entrée
16+0 enregistrements en sortie
8192 octets (8.2 kB, 8.0 KiB) copiés, 0.00057009 s,
14.4 MB/s
Les deux premières lignes sont les statistiques de 'volume' et la dernière ligne est les
statistiques de 'performance'.
Les statistiques de volume indiquent le nombre de lectures complètes et partielles de taille ibs,
ou d'écritures de taille obs qui ont eu lieu pendant la copie. Le format des statistiques de
volume est <complètes>+<partielles>. Si des enregistrements ont été tronqués (voir
conv=block), les statistiques de volume contiendront le nombre d'enregistrements tronqués.
Les valeurs possibles de NIVEAU sont :
- progress : Afficher les statistiques de performance périodiques pendant la copie.
- noxfer : Afficher les statistiques de volume finales, mais pas les statistiques de performance.
- none : N'afficher aucune statistique.
L'affichage des statistiques de performance est aussi déclenché par le signal INFO (quand supporté),
ou le signal USR1. Définir la variable d'environnement POSIXLY_CORRECT à n'importe quelle valeur
(y compris une valeur vide) fera ignorer le signal USR1.
### Options de conversion
- ascii : convertir d'EBCDIC vers ASCII. C'est l'inverse de l'option ebcdic.
Implique conv=unblock.
- ebcdic : convertir d'ASCII vers EBCDIC. C'est l'inverse de l'option ascii.
Implique conv=block.
- ibm : convertir d'ASCII vers EBCDIC, en appliquant les conventions pour [, ]
et ~ spécifiées dans POSIX. Implique conv=block.
- ucase : convertir de minuscules vers majuscules.
- lcase : convertir de majuscules vers minuscules.
- block : pour chaque nouvelle ligne inférieure à la taille indiquée par cbs=OCTETS, supprimer
la nouvelle ligne et remplir avec des espaces jusqu'à cbs. Les lignes plus longues que cbs sont tronquées.
- unblock : pour chaque bloc d'entrée de la taille indiquée par cbs=OCTETS, supprimer
les espaces de fin à droite et remplacer par un caractère de nouvelle ligne.
- sparse : tente de rechercher la sortie quand un bloc de taille obs ne contient que
des zéros.
- swab : échange chaque paire d'octets adjacents. Si un nombre impair d'octets est
présent, l'octet final est omis.
- sync : remplit chaque bloc de taille ibs avec des zéros. Si block ou unblock est
spécifié, remplit avec des espaces à la place.
- excl : le fichier de sortie doit être créé. Échoue si le fichier de sortie est déjà
présent.
- nocreat : le fichier de sortie ne sera pas créé. Échoue si le fichier de sortie n'est
pas déjà présent.
- notrunc : le fichier de sortie ne sera pas tronqué. Si cette option n'est pas
présente, la sortie sera tronquée à l'ouverture.
- noerror : toutes les erreurs de lecture seront ignorées. Si cette option n'est pas présente,
dd n'ignorera que Error::Interrupted.
- fdatasync : les données seront écrites avant la fin.
- fsync : les données et les métadonnées seront écrites avant la fin.
### Indicateurs d'entrée
- count_bytes : une valeur pour count=N sera interprétée comme des octets.
- skip_bytes : une valeur pour skip=N sera interprétée comme des octets.
- fullblock : attendre ibs octets de chaque lecture. les lectures de longueur zéro sont toujours
considérées comme EOF.
### Indicateurs de sortie
- append : ouvrir le fichier en mode ajout. Considérez définir conv=notrunc aussi.
- seek_bytes : une valeur pour seek=N sera interprétée comme des octets.
### Indicateurs généraux
- direct : utiliser les E/S directes pour les données.
- directory : échouer sauf si l'entrée donnée (si utilisée comme iflag) ou
la sortie (si utilisée comme oflag) est un répertoire.
- dsync : utiliser les E/S synchronisées pour les données.
- sync : utiliser les E/S synchronisées pour les données et les métadonnées.
- nonblock : utiliser les E/S non-bloquantes.
- noatime : ne pas mettre à jour l'heure d'accès.
- nocache : demander au système d'exploitation de supprimer le cache.
- noctty : ne pas assigner un tty de contrôle.
- nofollow : ne pas suivre les liens système.
# Error messages
dd-error-failed-to-open = échec de l'ouverture de { $path }
dd-error-write-error = erreur d'écriture
dd-error-failed-to-seek = échec de la recherche dans le fichier de sortie
dd-error-io-error = erreur E/S
dd-error-cannot-skip-offset = '{ $file }' : impossible d'ignorer jusqu'au décalage spécifié
dd-error-cannot-skip-invalid = '{ $file }' : impossible d'ignorer : Argument invalide
dd-error-cannot-seek-invalid = '{ $output }' : impossible de rechercher : Argument invalide
dd-error-not-directory = définir les indicateurs pour '{ $file }' : N'est pas un répertoire
dd-error-failed-discard-cache-input = échec de la suppression du cache pour : 'entrée standard'
dd-error-failed-discard-cache-output = échec de la suppression du cache pour : 'sortie standard'
# Parse errors
dd-error-unrecognized-operand = Opérande non reconnue '{ $operand }'
dd-error-multiple-format-table = Seul un seul de conv=ascii conv=ebcdic ou conv=ibm peut être spécifié
dd-error-multiple-case = Seul un seul de conv=lcase ou conv=ucase peut être spécifié
dd-error-multiple-block = Seul un seul de conv=block ou conv=unblock peut être spécifié
dd-error-multiple-excl = Seul un seul de conv=excl ou conv=nocreat peut être spécifié
dd-error-invalid-flag = indicateur d'entrée invalide : '{ $flag }'
Essayez '{ $cmd } --help' pour plus d'informations.
dd-error-conv-flag-no-match = conv=CONV non reconnu -> { $flag }
dd-error-multiplier-parse-failure = nombre invalide : { $input }
dd-error-multiplier-overflow = La chaîne de multiplicateur déborderait sur le système actuel -> { $input }
dd-error-block-without-cbs = conv=block ou conv=unblock spécifié sans cbs=N
dd-error-status-not-recognized = status=NIVEAU non reconnu -> { $level }
dd-error-unimplemented = fonctionnalité non implémentée sur ce système -> { $feature }
dd-error-bs-out-of-range = { $param }=N ne peut pas tenir en mémoire
dd-error-invalid-number = nombre invalide : { $input }
# Progress messages
dd-progress-records-in = { $complete }+{ $partial } enregistrements en entrée
dd-progress-records-out = { $complete }+{ $partial } enregistrements en sortie
dd-progress-truncated-record = { $count ->
[one] { $count } enregistrement tronqué
*[other] { $count } enregistrements tronqués
}
dd-progress-byte-copied = { $bytes } octet copié, { $duration } s, { $rate }/s
dd-progress-bytes-copied = { $bytes } octets copiés, { $duration } s, { $rate }/s
dd-progress-bytes-copied-si = { $bytes } octets ({ $si }) copiés, { $duration } s, { $rate }/s
dd-progress-bytes-copied-si-iec = { $bytes } octets ({ $si }, { $iec }) copiés, { $duration } s, { $rate }/s
# Warnings
dd-warning-zero-multiplier = { $zero } est un multiplicateur zéro ; utilisez { $alternative } si c'est voulu
dd-warning-signal-handler = Avertissement dd interne : Impossible d'enregistrer le gestionnaire de signal

View file

@ -26,6 +26,7 @@ use progress::{ProgUpdate, ReadStat, StatusLevel, WriteStat, gen_prog_updater};
use uucore::io::OwnedFileDescriptorOrHandle;
use std::cmp;
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::fs::{File, OpenOptions};
@ -62,7 +63,7 @@ use uucore::error::{USimpleError, set_exit_code};
use uucore::show_if_err;
use uucore::{format_usage, show_error};
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
const BUF_INIT_BYTE: u8 = 0xDD;
/// Final settings after parsing
@ -235,7 +236,13 @@ impl Source {
#[cfg(not(unix))]
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
Ok(m) if m < n => {
show_error!("'standard input': cannot skip to specified offset");
show_error!(
"{}",
get_message_with_args(
"dd-error-cannot-skip-offset",
HashMap::from([("file".to_string(), "standard input".to_string())])
)
);
Ok(m)
}
Ok(m) => Ok(m),
@ -247,14 +254,26 @@ impl Source {
if len < n {
// GNU compatibility:
// this case prints the stats but sets the exit code to 1
show_error!("'standard input': cannot skip: Invalid argument");
show_error!(
"{}",
get_message_with_args(
"dd-error-cannot-skip-invalid",
HashMap::from([("file".to_string(), "standard input".to_string())])
)
);
set_exit_code(1);
return Ok(len);
}
}
match io::copy(&mut f.take(n), &mut io::sink()) {
Ok(m) if m < n => {
show_error!("'standard input': cannot skip to specified offset");
show_error!(
"{}",
get_message_with_args(
"dd-error-cannot-skip-offset",
HashMap::from([("file".to_string(), "standard input".to_string())])
)
);
Ok(m)
}
Ok(m) => Ok(m),
@ -343,7 +362,10 @@ impl<'a> Input<'a> {
if settings.iflags.directory && !f.metadata()?.is_dir() {
return Err(USimpleError::new(
1,
"setting flags for 'standard input': Not a directory",
get_message_with_args(
"dd-error-not-directory",
HashMap::from([("file".to_string(), "standard input".to_string())]),
),
));
}
};
@ -364,8 +386,12 @@ impl<'a> Input<'a> {
opts.custom_flags(libc_flags);
}
opts.open(filename)
.map_err_context(|| format!("failed to open {}", filename.quote()))?
opts.open(filename).map_err_context(|| {
get_message_with_args(
"dd-error-failed-to-open",
HashMap::from([("path".to_string(), filename.quote().to_string())]),
)
})?
};
let mut src = Source::File(src);
@ -457,10 +483,11 @@ impl Input<'_> {
fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) {
#[cfg(target_os = "linux")]
{
show_if_err!(self
.src
.discard_cache(offset, len)
.map_err_context(|| "failed to discard cache for: 'standard input'".to_string()));
show_if_err!(
self.src
.discard_cache(offset, len)
.map_err_context(|| get_message("dd-error-failed-discard-cache-input"))
);
}
#[cfg(not(target_os = "linux"))]
{
@ -609,7 +636,16 @@ impl Dest {
if len < n {
// GNU compatibility:
// this case prints the stats but sets the exit code to 1
show_error!("'standard output': cannot seek: Invalid argument");
show_error!(
"{}",
get_message_with_args(
"dd-error-cannot-seek-invalid",
HashMap::from([(
"output".to_string(),
"standard output".to_string()
)])
)
);
set_exit_code(1);
return Ok(len);
}
@ -723,7 +759,7 @@ impl<'a> Output<'a> {
fn new_stdout(settings: &'a Settings) -> UResult<Self> {
let mut dst = Dest::Stdout(io::stdout());
dst.seek(settings.seek)
.map_err_context(|| "write error".to_string())?;
.map_err_context(|| get_message("dd-error-write-error"))?;
Ok(Self { dst, settings })
}
@ -744,8 +780,12 @@ impl<'a> Output<'a> {
opts.open(path)
}
let dst = open_dst(filename, &settings.oconv, &settings.oflags)
.map_err_context(|| format!("failed to open {}", filename.quote()))?;
let dst = open_dst(filename, &settings.oconv, &settings.oflags).map_err_context(|| {
get_message_with_args(
"dd-error-failed-to-open",
HashMap::from([("path".to_string(), filename.quote().to_string())]),
)
})?;
// Seek to the index in the output file, truncating if requested.
//
@ -770,7 +810,7 @@ impl<'a> Output<'a> {
};
let mut dst = Dest::File(dst, density);
dst.seek(settings.seek)
.map_err_context(|| "failed to seek in output file".to_string())?;
.map_err_context(|| get_message("dd-error-failed-to-seek"))?;
Ok(Self { dst, settings })
}
@ -832,9 +872,11 @@ impl<'a> Output<'a> {
fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) {
#[cfg(target_os = "linux")]
{
show_if_err!(self.dst.discard_cache(offset, len).map_err_context(|| {
"failed to discard cache for: 'standard output'".to_string()
}));
show_if_err!(
self.dst
.discard_cache(offset, len)
.map_err_context(|| { get_message("dd-error-failed-discard-cache-output") })
);
}
#[cfg(not(target_os = "linux"))]
{
@ -1083,7 +1125,7 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> {
#[cfg(target_os = "linux")]
if let Err(e) = &signal_handler {
if Some(StatusLevel::None) != i.settings.status {
eprintln!("Internal dd Warning: Unable to register signal handler \n\t{e}");
eprintln!("{}\n\t{e}", get_message("dd-warning-signal-handler"));
}
}
@ -1419,7 +1461,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
None if is_stdout_redirected_to_seekable_file() => Output::new_file_from_stdout(&settings)?,
None => Output::new_stdout(&settings)?,
};
dd_copy(i, o).map_err_context(|| "IO error".to_string())
dd_copy(i, o).map_err_context(|| get_message("dd-error-io-error"))
}
pub fn uu_app() -> Command {

View file

@ -9,42 +9,53 @@ mod unit_tests;
use super::{ConversionMode, IConvFlags, IFlags, Num, OConvFlags, OFlags, Settings, StatusLevel};
use crate::conversion_tables::ConversionTable;
use std::collections::HashMap;
use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::UError;
use uucore::locale::{get_message, get_message_with_args};
use uucore::parser::parse_size::{ParseSizeError, Parser as SizeParser};
use uucore::show_warning;
/// Parser Errors describe errors with parser input
#[derive(Debug, PartialEq, Eq, Error)]
pub enum ParseError {
#[error("Unrecognized operand '{0}'")]
#[error("{}", get_message_with_args("dd-error-unrecognized-operand",
HashMap::from([("operand".to_string(), .0.clone())])))]
UnrecognizedOperand(String),
#[error("Only one of conv=ascii conv=ebcdic or conv=ibm may be specified")]
#[error("{}", get_message("dd-error-multiple-format-table"))]
MultipleFmtTable,
#[error("Only one of conv=lcase or conv=ucase may be specified")]
#[error("{}", get_message("dd-error-multiple-case"))]
MultipleUCaseLCase,
#[error("Only one of conv=block or conv=unblock may be specified")]
#[error("{}", get_message("dd-error-multiple-block"))]
MultipleBlockUnblock,
#[error("Only one ov conv=excl or conv=nocreat may be specified")]
#[error("{}", get_message("dd-error-multiple-excl"))]
MultipleExclNoCreate,
#[error("invalid input flag: {}\nTry '{} --help' for more information.", .0, uucore::execution_phrase())]
#[error("{}", get_message_with_args("dd-error-invalid-flag",
HashMap::from([("flag".to_string(), .0.clone()),("cmd".to_string(), uucore::execution_phrase().to_string())])))]
FlagNoMatch(String),
#[error("Unrecognized conv=CONV -> {0}")]
#[error("{}", get_message_with_args("dd-error-conv-flag-no-match",
HashMap::from([("flag".to_string(), .0.clone())])))]
ConvFlagNoMatch(String),
#[error("invalid number: {0}")]
#[error("{}", get_message_with_args("dd-error-multiplier-parse-failure",
HashMap::from([("input".to_string(), .0.clone())])))]
MultiplierStringParseFailure(String),
#[error("Multiplier string would overflow on current system -> {0}")]
#[error("{}", get_message_with_args("dd-error-multiplier-overflow",
HashMap::from([("input".to_string(), .0.clone())])))]
MultiplierStringOverflow(String),
#[error("conv=block or conv=unblock specified without cbs=N")]
#[error("{}", get_message("dd-error-block-without-cbs"))]
BlockUnblockWithoutCBS,
#[error("status=LEVEL not recognized -> {0}")]
#[error("{}", get_message_with_args("dd-error-status-not-recognized",
HashMap::from([("level".to_string(), .0.clone())])))]
StatusLevelNotRecognized(String),
#[error("feature not implemented on this system -> {0}")]
#[error("{}", get_message_with_args("dd-error-unimplemented",
HashMap::from([("feature".to_string(), .0.clone())])))]
Unimplemented(String),
#[error("{0}=N cannot fit into memory")]
#[error("{}", get_message_with_args("dd-error-bs-out-of-range",
HashMap::from([("param".to_string(), .0.clone())])))]
BsOutOfRange(String),
#[error("invalid number: {0}")]
#[error("{}", get_message_with_args("dd-error-invalid-number",
HashMap::from([("input".to_string(), .0.clone())])))]
InvalidNumber(String),
}
@ -424,9 +435,14 @@ impl UError for ParseError {
fn show_zero_multiplier_warning() {
show_warning!(
"{} is a zero multiplier; use {} if that is intended",
"0x".quote(),
"00x".quote()
"{}",
get_message_with_args(
"dd-warning-zero-multiplier",
HashMap::from([
("zero".to_string(), "0x".quote().to_string()),
("alternative".to_string(), "00x".quote().to_string())
])
)
);
}

View file

@ -9,6 +9,7 @@
//! read and write progress of a running `dd` process. The
//! [`gen_prog_updater`] function can be used to implement a progress
//! updater that runs in its own thread.
use std::collections::HashMap;
use std::io::Write;
use std::sync::mpsc;
#[cfg(target_os = "linux")]
@ -20,6 +21,8 @@ use signal_hook::iterator::Handle;
use uucore::{
error::UResult,
format::num_format::{FloatVariant, Formatter},
locale::get_message_with_args,
locale::setup_localization,
};
use crate::numbers::{SuffixType, to_magnitude_and_suffix};
@ -102,8 +105,13 @@ impl ProgUpdate {
self.write_stat.report(w)?;
match self.read_stat.records_truncated {
0 => {}
1 => writeln!(w, "1 truncated record")?,
n => writeln!(w, "{n} truncated records")?,
count => {
let message = get_message_with_args(
"dd-progress-truncated-record",
HashMap::from([("count".to_string(), count.to_string())]),
);
writeln!(w, "{}", message)?;
}
}
Ok(())
}
@ -164,24 +172,45 @@ impl ProgUpdate {
// If the number of bytes written is sufficiently large, then
// print a more concise representation of the number, like
// "1.2 kB" and "1.0 KiB".
match btotal {
1 => write!(
w,
"{carriage_return}{btotal} byte copied, {duration_str} s, {transfer_rate}/s{newline}",
)?,
0..=999 => write!(
w,
"{carriage_return}{btotal} bytes copied, {duration_str} s, {transfer_rate}/s{newline}",
)?,
1000..=1023 => write!(
w,
"{carriage_return}{btotal} bytes ({btotal_metric}) copied, {duration_str} s, {transfer_rate}/s{newline}",
)?,
_ => write!(
w,
"{carriage_return}{btotal} bytes ({btotal_metric}, {btotal_bin}) copied, {duration_str} s, {transfer_rate}/s{newline}",
)?,
let message = match btotal {
1 => get_message_with_args(
"dd-progress-byte-copied",
HashMap::from([
("bytes".to_string(), btotal.to_string()),
("duration".to_string(), duration_str.to_string()),
("rate".to_string(), transfer_rate.to_string()),
]),
),
0..=999 => get_message_with_args(
"dd-progress-bytes-copied",
HashMap::from([
("bytes".to_string(), btotal.to_string()),
("duration".to_string(), duration_str.to_string()),
("rate".to_string(), transfer_rate.to_string()),
]),
),
1000..=1023 => get_message_with_args(
"dd-progress-bytes-copied-si",
HashMap::from([
("bytes".to_string(), btotal.to_string()),
("si".to_string(), btotal_metric.to_string()),
("duration".to_string(), duration_str.to_string()),
("rate".to_string(), transfer_rate.to_string()),
]),
),
_ => get_message_with_args(
"dd-progress-bytes-copied-si-iec",
HashMap::from([
("bytes".to_string(), btotal.to_string()),
("si".to_string(), btotal_metric.to_string()),
("iec".to_string(), btotal_bin.to_string()),
("duration".to_string(), duration_str.to_string()),
("rate".to_string(), transfer_rate.to_string()),
]),
),
};
write!(w, "{carriage_return}{message}{newline}")?;
Ok(())
}
@ -311,11 +340,14 @@ impl ReadStat {
///
/// If there is a problem writing to `w`.
fn report(&self, w: &mut impl Write) -> std::io::Result<()> {
writeln!(
w,
"{}+{} records in",
self.reads_complete, self.reads_partial
)?;
let message = get_message_with_args(
"dd-progress-records-in",
HashMap::from([
("complete".to_string(), self.reads_complete.to_string()),
("partial".to_string(), self.reads_partial.to_string()),
]),
);
writeln!(w, "{}", message)?;
Ok(())
}
}
@ -368,11 +400,14 @@ impl WriteStat {
///
/// If there is a problem writing to `w`.
fn report(&self, w: &mut impl Write) -> std::io::Result<()> {
writeln!(
w,
"{}+{} records out",
self.writes_complete, self.writes_partial
)
let message = get_message_with_args(
"dd-progress-records-out",
HashMap::from([
("complete".to_string(), self.writes_complete.to_string()),
("partial".to_string(), self.writes_partial.to_string()),
]),
);
writeln!(w, "{}", message)
}
}
@ -428,6 +463,9 @@ pub(crate) fn gen_prog_updater(
print_level: Option<StatusLevel>,
) -> impl Fn() {
move || {
// As we are in a thread, we need to set up localization independently.
let _ = setup_localization("dd");
let mut progress_printed = false;
while let Ok(update) = rx.recv() {
// Print the final read/write statistics.
@ -502,6 +540,9 @@ pub(crate) fn gen_prog_updater(
) -> impl Fn() {
// --------------------------------------------------------------
move || {
// As we are in a thread, we need to set up localization independently.
let _ = setup_localization("dd");
// Holds the state of whether we have printed the current progress.
// This is needed so that we know whether or not to print a newline
// character before outputting non-progress data.
@ -532,11 +573,18 @@ pub(crate) fn gen_prog_updater(
#[cfg(test)]
mod tests {
use std::env;
use std::io::Cursor;
use std::time::Duration;
use uucore::locale::setup_localization;
use super::{ProgUpdate, ReadStat, WriteStat};
fn init() {
unsafe {
env::set_var("LANG", "C");
}
let _ = setup_localization("dd");
}
fn prog_update_write(n: u128) -> ProgUpdate {
ProgUpdate {
@ -561,22 +609,31 @@ mod tests {
#[test]
fn test_read_stat_report() {
init();
let read_stat = ReadStat::new(1, 2, 3, 4);
let mut cursor = Cursor::new(vec![]);
read_stat.report(&mut cursor).unwrap();
assert_eq!(cursor.get_ref(), b"1+2 records in\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1+2 records in\n"
);
}
#[test]
fn test_write_stat_report() {
init();
let write_stat = WriteStat::new(1, 2, 3);
let mut cursor = Cursor::new(vec![]);
write_stat.report(&mut cursor).unwrap();
assert_eq!(cursor.get_ref(), b"1+2 records out\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1+2 records out\n"
);
}
#[test]
fn test_prog_update_write_io_lines() {
init();
let read_stat = ReadStat::new(1, 2, 3, 4);
let write_stat = WriteStat::new(4, 5, 6);
let duration = Duration::new(789, 0);
@ -591,13 +648,14 @@ mod tests {
let mut cursor = Cursor::new(vec![]);
prog_update.write_io_lines(&mut cursor).unwrap();
assert_eq!(
cursor.get_ref(),
b"1+2 records in\n4+5 records out\n3 truncated records\n"
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1+2 records in\n4+5 records out\n3 truncated records\n"
);
}
#[test]
fn test_prog_update_write_prog_line() {
init();
let prog_update = ProgUpdate {
read_stat: ReadStat::default(),
write_stat: WriteStat::default(),
@ -615,45 +673,55 @@ mod tests {
// 0 bytes copied, 7.9151e-05 s, 0.0 kB/s
//
// The throughput still does not match GNU dd.
assert_eq!(cursor.get_ref(), b"0 bytes copied, 1 s, 0.0 B/s\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"0 bytes copied, 1 s, 0.0 B/s\n"
);
let prog_update = prog_update_write(1);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(cursor.get_ref(), b"1 byte copied, 1 s, 0.0 B/s\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1 byte copied, 1 s, 0.0 B/s\n"
);
let prog_update = prog_update_write(999);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(cursor.get_ref(), b"999 bytes copied, 1 s, 0.0 B/s\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"999 bytes copied, 1 s, 0.0 B/s\n"
);
let prog_update = prog_update_write(1000);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(
cursor.get_ref(),
b"1000 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1000 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
);
let prog_update = prog_update_write(1023);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(
cursor.get_ref(),
b"1023 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1023 bytes (1.0 kB) copied, 1 s, 1.0 kB/s\n"
);
let prog_update = prog_update_write(1024);
let mut cursor = Cursor::new(vec![]);
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(
cursor.get_ref(),
b"1024 bytes (1.0 kB, 1.0 KiB) copied, 1 s, 1.0 kB/s\n"
std::str::from_utf8(cursor.get_ref()).unwrap(),
"1024 bytes (1.0 kB, 1.0 KiB) copied, 1 s, 1.0 kB/s\n"
);
}
#[test]
fn write_transfer_stats() {
init();
let prog_update = ProgUpdate {
read_stat: ReadStat::default(),
write_stat: WriteStat::default(),
@ -664,16 +732,18 @@ mod tests {
prog_update
.write_transfer_stats(&mut cursor, false)
.unwrap();
let mut iter = cursor.get_ref().split(|v| *v == b'\n');
assert_eq!(iter.next().unwrap(), b"0+0 records in");
assert_eq!(iter.next().unwrap(), b"0+0 records out");
assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), b"");
let output_str = std::str::from_utf8(cursor.get_ref()).unwrap();
let mut iter = output_str.split('\n');
assert_eq!(iter.next().unwrap(), "0+0 records in");
assert_eq!(iter.next().unwrap(), "0+0 records out");
assert_eq!(iter.next().unwrap(), "0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), "");
assert!(iter.next().is_none());
}
#[test]
fn write_final_transfer_stats() {
init();
// Tests the formatting of the final statistics written after a progress line.
let prog_update = ProgUpdate {
read_stat: ReadStat::default(),
@ -685,21 +755,26 @@ mod tests {
let rewrite = true;
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
prog_update.write_transfer_stats(&mut cursor, true).unwrap();
let mut iter = cursor.get_ref().split(|v| *v == b'\n');
assert_eq!(iter.next().unwrap(), b"\r0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), b"0+0 records in");
assert_eq!(iter.next().unwrap(), b"0+0 records out");
assert_eq!(iter.next().unwrap(), b"0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), b"");
let output_str = std::str::from_utf8(cursor.get_ref()).unwrap();
let mut iter = output_str.split('\n');
assert_eq!(iter.next().unwrap(), "\r0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), "0+0 records in");
assert_eq!(iter.next().unwrap(), "0+0 records out");
assert_eq!(iter.next().unwrap(), "0 bytes copied, 1 s, 0.0 B/s");
assert_eq!(iter.next().unwrap(), "");
assert!(iter.next().is_none());
}
#[test]
fn test_duration_precision() {
init();
let prog_update = prog_update_duration(Duration::from_nanos(123));
let mut cursor = Cursor::new(vec![]);
let rewrite = false;
prog_update.write_prog_line(&mut cursor, rewrite).unwrap();
assert_eq!(cursor.get_ref(), b"0 bytes copied, 1.23e-07 s, 0.0 B/s\n");
assert_eq!(
std::str::from_utf8(cursor.get_ref()).unwrap(),
"0 bytes copied, 0.000000123 s, 0.0 B/s\n"
);
}
}

View file

@ -8,3 +8,53 @@ df-after-help = Display values are in units of the first available SIZE from --b
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
of 1000).
# Help messages
df-help-print-help = Print help information.
df-help-all = include dummy file systems
df-help-block-size = scale sizes by SIZE before printing them; e.g. '-BM' prints sizes in units of 1,048,576 bytes
df-help-total = produce a grand total
df-help-human-readable = print sizes in human readable format (e.g., 1K 234M 2G)
df-help-si = likewise, but use powers of 1000 not 1024
df-help-inodes = list inode information instead of block usage
df-help-kilo = like --block-size=1K
df-help-local = limit listing to local file systems
df-help-no-sync = do not invoke sync before getting usage info (default)
df-help-output = use output format defined by FIELD_LIST, or print all fields if FIELD_LIST is omitted.
df-help-portability = use the POSIX output format
df-help-sync = invoke sync before getting usage info (non-windows only)
df-help-type = limit listing to file systems of type TYPE
df-help-print-type = print file system type
df-help-exclude-type = limit listing to file systems not of type TYPE
# Error messages
df-error-block-size-too-large = --block-size argument '{ $size }' too large
df-error-invalid-block-size = invalid --block-size argument { $size }
df-error-invalid-suffix = invalid suffix in --block-size argument { $size }
df-error-field-used-more-than-once = option --output: field { $field } used more than once
df-error-filesystem-type-both-selected-and-excluded = file system type { $type } both selected and excluded
df-error-no-such-file-or-directory = { $path }: No such file or directory
df-error-no-file-systems-processed = no file systems processed
df-error-cannot-access-over-mounted = cannot access { $path }: over-mounted by another device
df-error-cannot-read-table-of-mounted-filesystems = cannot read table of mounted file systems
df-error-inodes-not-supported-windows = { $program }: doesn't support -i option
# Headers
df-header-filesystem = Filesystem
df-header-size = Size
df-header-used = Used
df-header-avail = Avail
df-header-available = Available
df-header-use-percent = Use%
df-header-capacity = Capacity
df-header-mounted-on = Mounted on
df-header-inodes = Inodes
df-header-iused = IUsed
df-header-iavail = IFree
df-header-iuse-percent = IUse%
df-header-file = File
df-header-type = Type
# Other
df-total = total
df-blocks-suffix = -blocks

View file

@ -0,0 +1,60 @@
df-about = afficher des informations sur le système de fichiers sur lequel chaque FICHIER réside,
ou tous les systèmes de fichiers par défaut.
df-usage = df [OPTION]... [FICHIER]...
df-after-help = Les valeurs affichées sont en unités de la première TAILLE disponible de --block-size,
et des variables d'environnement DF_BLOCK_SIZE, BLOCK_SIZE et BLOCKSIZE.
Sinon, les unités par défaut sont 1024 octets (ou 512 si POSIXLY_CORRECT est défini).
TAILLE est un entier et une unité optionnelle (exemple : 10M est 10*1024*1024).
Les unités sont K, M, G, T, P, E, Z, Y (puissances de 1024) ou KB, MB,... (puissances
de 1000).
# Messages d'aide
df-help-print-help = afficher les informations d'aide.
df-help-all = inclure les systèmes de fichiers factices
df-help-block-size = mettre les tailles à l'échelle par TAILLE avant de les afficher ; par ex. '-BM' affiche les tailles en unités de 1 048 576 octets
df-help-total = produire un total général
df-help-human-readable = afficher les tailles dans un format lisible par l'homme (par ex., 1K 234M 2G)
df-help-si = pareillement, mais utiliser les puissances de 1000 pas 1024
df-help-inodes = lister les informations d'inode au lieu de l'utilisation des blocs
df-help-kilo = comme --block-size=1K
df-help-local = limiter l'affichage aux systèmes de fichiers locaux
df-help-no-sync = ne pas invoquer sync avant d'obtenir les informations d'utilisation (par défaut)
df-help-output = utiliser le format de sortie défini par LISTE_CHAMPS, ou afficher tous les champs si LISTE_CHAMPS est omise.
df-help-portability = utiliser le format de sortie POSIX
df-help-sync = invoquer sync avant d'obtenir les informations d'utilisation (non-windows seulement)
df-help-type = limiter l'affichage aux systèmes de fichiers de type TYPE
df-help-print-type = afficher le type de système de fichiers
df-help-exclude-type = limiter l'affichage aux systèmes de fichiers pas de type TYPE
# Messages d'erreur
df-error-block-size-too-large = argument --block-size '{ $size }' trop grand
df-error-invalid-block-size = argument --block-size invalide { $size }
df-error-invalid-suffix = suffixe invalide dans l'argument --block-size { $size }
df-error-field-used-more-than-once = option --output : champ { $field } utilisé plus d'une fois
df-error-filesystem-type-both-selected-and-excluded = type de système de fichiers { $type } à la fois sélectionné et exclu
df-error-no-such-file-or-directory = { $path } : aucun fichier ou répertoire de ce type
df-error-no-file-systems-processed = aucun système de fichiers traité
df-error-cannot-access-over-mounted = impossible d'accéder à { $path } : sur-monté par un autre périphérique
df-error-cannot-read-table-of-mounted-filesystems = impossible de lire la table des systèmes de fichiers montés
df-error-inodes-not-supported-windows = { $program } : ne supporte pas l'option -i
# En-têtes du tableau
df-header-filesystem = Sys. de fichiers
df-header-size = Taille
df-header-used = Utilisé
df-header-avail = Disp.
df-header-available = Disponible
df-header-use-percent = Util%
df-header-capacity = Capacité
df-header-mounted-on = Monté sur
df-header-inodes = Inodes
df-header-iused = IUtil
df-header-iavail = ILibre
df-header-iuse-percent = IUtil%
df-header-file = Fichier
df-header-type = Type
# Autres messages
df-total = total
df-blocks-suffix = -blocs

View file

@ -29,7 +29,8 @@ use crate::filesystem::Filesystem;
use crate::filesystem::FsError;
use crate::table::Table;
use uucore::locale::get_message;
use std::collections::HashMap;
use uucore::locale::{get_message, get_message_with_args};
static OPT_HELP: &str = "help";
static OPT_ALL: &str = "all";
@ -115,25 +116,28 @@ impl Default for Options {
enum OptionsError {
// TODO This needs to vary based on whether `--block-size`
// or `-B` were provided.
#[error("--block-size argument '{0}' too large")]
#[error("{}", get_message_with_args("df-error-block-size-too-large", HashMap::from([("size".to_string(), .0.clone())])))]
BlockSizeTooLarge(String),
// TODO This needs to vary based on whether `--block-size`
// or `-B` were provided.,
#[error("invalid --block-size argument {0}")]
#[error("{}", get_message_with_args("df-error-invalid-block-size", HashMap::from([("size".to_string(), .0.clone())])))]
InvalidBlockSize(String),
// TODO This needs to vary based on whether `--block-size`
// or `-B` were provided.
#[error("invalid suffix in --block-size argument {0}")]
#[error("{}", get_message_with_args("df-error-invalid-suffix", HashMap::from([("size".to_string(), .0.clone())])))]
InvalidSuffix(String),
/// An error getting the columns to display in the output table.
#[error("option --output: field {0} used more than once")]
#[error("{}", get_message_with_args("df-error-field-used-more-than-once", HashMap::from([("field".to_string(), format!("{}", .0))])))]
ColumnError(ColumnError),
#[error("{}", .0.iter()
.map(|t| format!("file system type {} both selected and excluded", t.quote()))
#[error(
"{}",
.0.iter()
.map(|t| get_message_with_args("df-error-filesystem-type-both-selected-and-excluded", HashMap::from([("type".to_string(), t.quote().to_string())])))
.collect::<Vec<_>>()
.join(format!("\n{}: ", uucore::util_name()).as_str()))]
.join(format!("\n{}: ", uucore::util_name()).as_str())
)]
FilesystemTypeBothSelectedAndExcluded(Vec<String>),
}
@ -359,26 +363,35 @@ where
Err(FsError::InvalidPath) => {
show!(USimpleError::new(
1,
format!("{}: No such file or directory", path.as_ref().display())
get_message_with_args(
"df-error-no-such-file-or-directory",
HashMap::from([("path".to_string(), path.as_ref().display().to_string())])
)
));
}
Err(FsError::MountMissing) => {
show!(USimpleError::new(1, "no file systems processed"));
show!(USimpleError::new(
1,
get_message("df-error-no-file-systems-processed")
));
}
#[cfg(not(windows))]
Err(FsError::OverMounted) => {
show!(USimpleError::new(
1,
format!(
"cannot access {}: over-mounted by another device",
path.as_ref().quote()
get_message_with_args(
"df-error-cannot-access-over-mounted",
HashMap::from([("path".to_string(), path.as_ref().quote().to_string())])
)
));
}
}
}
if get_exit_code() == 0 && result.is_empty() {
show!(USimpleError::new(1, "no file systems processed"));
show!(USimpleError::new(
1,
get_message("df-error-no-file-systems-processed")
));
return Ok(result);
}
@ -405,7 +418,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
#[cfg(windows)]
{
if matches.get_flag(OPT_INODES) {
println!("{}: doesn't support -i option", uucore::util_name());
println!(
"{}",
get_message_with_args(
"df-error-inodes-not-supported-windows",
HashMap::from([("program".to_string(), uucore::util_name().to_string())])
)
);
return Ok(());
}
}
@ -415,12 +434,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let filesystems: Vec<Filesystem> = match matches.get_many::<String>(OPT_PATHS) {
None => {
let filesystems = get_all_filesystems(&opt).map_err(|e| {
let context = "cannot read table of mounted file systems";
let context = get_message("df-error-cannot-read-table-of-mounted-filesystems");
USimpleError::new(e.code(), format!("{context}: {e}"))
})?;
if filesystems.is_empty() {
return Err(USimpleError::new(1, "no file systems processed"));
return Err(USimpleError::new(
1,
get_message("df-error-no-file-systems-processed"),
));
}
filesystems
@ -428,7 +450,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Some(paths) => {
let paths: Vec<_> = paths.collect();
let filesystems = get_named_filesystems(&paths, &opt).map_err(|e| {
let context = "cannot read table of mounted file systems";
let context = get_message("df-error-cannot-read-table-of-mounted-filesystems");
USimpleError::new(e.code(), format!("{context}: {e}"))
})?;
@ -458,7 +480,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(OPT_HELP)
.long(OPT_HELP)
.help("Print help information.")
.help(get_message("df-help-print-help"))
.action(ArgAction::Help),
)
.arg(
@ -466,7 +488,7 @@ pub fn uu_app() -> Command {
.short('a')
.long("all")
.overrides_with(OPT_ALL)
.help("include dummy file systems")
.help(get_message("df-help-all"))
.action(ArgAction::SetTrue),
)
.arg(
@ -475,16 +497,13 @@ pub fn uu_app() -> Command {
.long("block-size")
.value_name("SIZE")
.overrides_with_all([OPT_KILO, OPT_BLOCKSIZE])
.help(
"scale sizes by SIZE before printing them; e.g.\
'-BM' prints sizes in units of 1,048,576 bytes",
),
.help(get_message("df-help-block-size")),
)
.arg(
Arg::new(OPT_TOTAL)
.long("total")
.overrides_with(OPT_TOTAL)
.help("produce a grand total")
.help(get_message("df-help-total"))
.action(ArgAction::SetTrue),
)
.arg(
@ -492,7 +511,7 @@ pub fn uu_app() -> Command {
.short('h')
.long("human-readable")
.overrides_with_all([OPT_HUMAN_READABLE_DECIMAL, OPT_HUMAN_READABLE_BINARY])
.help("print sizes in human readable format (e.g., 1K 234M 2G)")
.help(get_message("df-help-human-readable"))
.action(ArgAction::SetTrue),
)
.arg(
@ -500,7 +519,7 @@ pub fn uu_app() -> Command {
.short('H')
.long("si")
.overrides_with_all([OPT_HUMAN_READABLE_BINARY, OPT_HUMAN_READABLE_DECIMAL])
.help("likewise, but use powers of 1000 not 1024")
.help(get_message("df-help-si"))
.action(ArgAction::SetTrue),
)
.arg(
@ -508,13 +527,13 @@ pub fn uu_app() -> Command {
.short('i')
.long("inodes")
.overrides_with(OPT_INODES)
.help("list inode information instead of block usage")
.help(get_message("df-help-inodes"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_KILO)
.short('k')
.help("like --block-size=1K")
.help(get_message("df-help-kilo"))
.overrides_with_all([OPT_BLOCKSIZE, OPT_KILO])
.action(ArgAction::SetTrue),
)
@ -523,14 +542,14 @@ pub fn uu_app() -> Command {
.short('l')
.long("local")
.overrides_with(OPT_LOCAL)
.help("limit listing to local file systems")
.help(get_message("df-help-local"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_NO_SYNC)
.long("no-sync")
.overrides_with_all([OPT_SYNC, OPT_NO_SYNC])
.help("do not invoke sync before getting usage info (default)")
.help(get_message("df-help-no-sync"))
.action(ArgAction::SetTrue),
)
.arg(
@ -545,24 +564,21 @@ pub fn uu_app() -> Command {
.default_missing_values(OUTPUT_FIELD_LIST)
.default_values(["source", "size", "used", "avail", "pcent", "target"])
.conflicts_with_all([OPT_INODES, OPT_PORTABILITY, OPT_PRINT_TYPE])
.help(
"use the output format defined by FIELD_LIST, \
or print all fields if FIELD_LIST is omitted.",
),
.help(get_message("df-help-output")),
)
.arg(
Arg::new(OPT_PORTABILITY)
.short('P')
.long("portability")
.overrides_with(OPT_PORTABILITY)
.help("use the POSIX output format")
.help(get_message("df-help-portability"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_SYNC)
.long("sync")
.overrides_with_all([OPT_NO_SYNC, OPT_SYNC])
.help("invoke sync before getting usage info (non-windows only)")
.help(get_message("df-help-sync"))
.action(ArgAction::SetTrue),
)
.arg(
@ -572,14 +588,14 @@ pub fn uu_app() -> Command {
.value_parser(ValueParser::os_string())
.value_name("TYPE")
.action(ArgAction::Append)
.help("limit listing to file systems of type TYPE"),
.help(get_message("df-help-type")),
)
.arg(
Arg::new(OPT_PRINT_TYPE)
.short('T')
.long("print-type")
.overrides_with(OPT_PRINT_TYPE)
.help("print file system type")
.help(get_message("df-help-print-type"))
.action(ArgAction::SetTrue),
)
.arg(
@ -590,7 +606,7 @@ pub fn uu_app() -> Command {
.value_parser(ValueParser::os_string())
.value_name("TYPE")
.use_value_delimiter(true)
.help("limit listing to file systems not of type TYPE"),
.help(get_message("df-help-exclude-type")),
)
.arg(
Arg::new(OPT_PATHS)

View file

@ -14,6 +14,7 @@ use crate::columns::{Alignment, Column};
use crate::filesystem::Filesystem;
use crate::{BlockSize, Options};
use uucore::fsext::{FsUsage, MountInfo};
use uucore::locale::get_message;
use std::fmt;
use std::ops::AddAssign;
@ -106,7 +107,7 @@ impl AddAssign for Row {
let inodes_used = self.inodes_used + rhs.inodes_used;
*self = Self {
file: None,
fs_device: "total".into(),
fs_device: get_message("df-total"),
fs_type: "-".into(),
fs_mount: "-".into(),
bytes,
@ -261,7 +262,7 @@ impl<'a> RowFormatter<'a> {
let string = match column {
Column::Source => {
if self.is_total_row {
"total".to_string()
get_message("df-total")
} else {
self.row.fs_device.to_string()
}
@ -273,7 +274,7 @@ impl<'a> RowFormatter<'a> {
Column::Target => {
if self.is_total_row && !self.options.columns.contains(&Column::Source) {
"total".to_string()
get_message("df-total")
} else {
self.row.fs_mount.to_string()
}
@ -325,32 +326,38 @@ impl Header {
for column in &options.columns {
let header = match column {
Column::Source => String::from("Filesystem"),
Column::Source => get_message("df-header-filesystem"),
Column::Size => match options.header_mode {
HeaderMode::HumanReadable => String::from("Size"),
HeaderMode::HumanReadable => get_message("df-header-size"),
HeaderMode::PosixPortability => {
format!("{}-blocks", options.block_size.as_u64())
format!(
"{}{}",
options.block_size.as_u64(),
get_message("df-blocks-suffix")
)
}
_ => format!("{}-blocks", options.block_size),
_ => format!("{}{}", options.block_size, get_message("df-blocks-suffix")),
},
Column::Used => String::from("Used"),
Column::Used => get_message("df-header-used"),
Column::Avail => match options.header_mode {
HeaderMode::HumanReadable | HeaderMode::Output => String::from("Avail"),
_ => String::from("Available"),
HeaderMode::HumanReadable | HeaderMode::Output => {
get_message("df-header-avail")
}
_ => get_message("df-header-available"),
},
Column::Pcent => match options.header_mode {
HeaderMode::PosixPortability => String::from("Capacity"),
_ => String::from("Use%"),
HeaderMode::PosixPortability => get_message("df-header-capacity"),
_ => get_message("df-header-use-percent"),
},
Column::Target => String::from("Mounted on"),
Column::Itotal => String::from("Inodes"),
Column::Iused => String::from("IUsed"),
Column::Iavail => String::from("IFree"),
Column::Ipcent => String::from("IUse%"),
Column::File => String::from("File"),
Column::Fstype => String::from("Type"),
Column::Target => get_message("df-header-mounted-on"),
Column::Itotal => get_message("df-header-inodes"),
Column::Iused => get_message("df-header-iused"),
Column::Iavail => get_message("df-header-iavail"),
Column::Ipcent => get_message("df-header-iuse-percent"),
Column::File => get_message("df-header-file"),
Column::Fstype => get_message("df-header-type"),
#[cfg(target_os = "macos")]
Column::Capacity => String::from("Capacity"),
Column::Capacity => get_message("df-header-capacity"),
};
headers.push(header);
@ -383,7 +390,7 @@ impl Table {
//
// This accumulator is computed in case we need to display the
// total counts in the last row of the table.
let mut total = Row::new("total");
let mut total = Row::new(&get_message("df-total"));
for filesystem in filesystems {
// If the filesystem is not empty, or if the options require
@ -440,8 +447,8 @@ impl fmt::Display for Table {
while let Some((i, elem)) = col_iter.next() {
let is_last_col = col_iter.peek().is_none();
match self.alignments[i] {
Alignment::Left => {
match self.alignments.get(i) {
Some(Alignment::Left) => {
if is_last_col {
// no trailing spaces in last column
write!(f, "{elem}")?;
@ -449,7 +456,10 @@ impl fmt::Display for Table {
write!(f, "{:<width$}", elem, width = self.widths[i])?;
}
}
Alignment::Right => write!(f, "{:>width$}", elem, width = self.widths[i])?,
Some(Alignment::Right) => {
write!(f, "{:>width$}", elem, width = self.widths[i])?;
}
None => break,
}
if !is_last_col {
@ -471,12 +481,20 @@ impl fmt::Display for Table {
mod tests {
use std::vec;
use uucore::locale::setup_localization;
use crate::blocks::HumanReadable;
use crate::columns::Column;
use crate::table::{Header, HeaderMode, Row, RowFormatter, Table};
use crate::{BlockSize, Options};
fn init() {
unsafe {
std::env::set_var("LANG", "C");
}
let _ = setup_localization("df");
}
const COLUMNS_WITH_FS_TYPE: [Column; 7] = [
Column::Source,
Column::Fstype,
@ -521,7 +539,9 @@ mod tests {
#[test]
fn test_default_header() {
init();
let options = Options::default();
assert_eq!(
Header::get_headers(&options),
vec!(
@ -537,6 +557,7 @@ mod tests {
#[test]
fn test_header_with_fs_type() {
init();
let options = Options {
columns: COLUMNS_WITH_FS_TYPE.to_vec(),
..Default::default()
@ -557,6 +578,7 @@ mod tests {
#[test]
fn test_header_with_inodes() {
init();
let options = Options {
columns: COLUMNS_WITH_INODES.to_vec(),
..Default::default()
@ -576,6 +598,7 @@ mod tests {
#[test]
fn test_header_with_block_size_1024() {
init();
let options = Options {
block_size: BlockSize::Bytes(3 * 1024),
..Default::default()
@ -595,6 +618,7 @@ mod tests {
#[test]
fn test_human_readable_header() {
init();
let options = Options {
header_mode: HeaderMode::HumanReadable,
..Default::default()
@ -607,6 +631,7 @@ mod tests {
#[test]
fn test_posix_portability_header() {
init();
let options = Options {
header_mode: HeaderMode::PosixPortability,
..Default::default()
@ -626,6 +651,7 @@ mod tests {
#[test]
fn test_output_header() {
init();
let options = Options {
header_mode: HeaderMode::Output,
..Default::default()
@ -645,6 +671,7 @@ mod tests {
#[test]
fn test_row_formatter() {
init();
let options = Options {
block_size: BlockSize::Bytes(1),
..Default::default()
@ -669,6 +696,7 @@ mod tests {
#[test]
fn test_row_formatter_with_fs_type() {
init();
let options = Options {
columns: COLUMNS_WITH_FS_TYPE.to_vec(),
block_size: BlockSize::Bytes(1),
@ -695,6 +723,7 @@ mod tests {
#[test]
fn test_row_formatter_with_inodes() {
init();
let options = Options {
columns: COLUMNS_WITH_INODES.to_vec(),
block_size: BlockSize::Bytes(1),
@ -720,6 +749,7 @@ mod tests {
#[test]
fn test_row_formatter_with_bytes_and_inodes() {
init();
let options = Options {
columns: vec![Column::Size, Column::Itotal],
block_size: BlockSize::Bytes(100),
@ -736,6 +766,7 @@ mod tests {
#[test]
fn test_row_formatter_with_human_readable_si() {
init();
let options = Options {
human_readable: Some(HumanReadable::Decimal),
columns: COLUMNS_WITH_FS_TYPE.to_vec(),
@ -762,6 +793,7 @@ mod tests {
#[test]
fn test_row_formatter_with_human_readable_binary() {
init();
let options = Options {
human_readable: Some(HumanReadable::Binary),
columns: COLUMNS_WITH_FS_TYPE.to_vec(),
@ -788,6 +820,7 @@ mod tests {
#[test]
fn test_row_formatter_with_round_up_usage() {
init();
let options = Options {
columns: vec![Column::Pcent],
..Default::default()
@ -802,6 +835,7 @@ mod tests {
#[test]
fn test_row_formatter_with_round_up_byte_values() {
init();
fn get_formatted_values(bytes: u64, bytes_used: u64, bytes_avail: u64) -> Vec<String> {
let options = Options {
block_size: BlockSize::Bytes(1000),
@ -826,6 +860,7 @@ mod tests {
#[test]
fn test_row_converter_with_invalid_numbers() {
init();
// copy from wsl linux
let d = crate::Filesystem {
file: None,
@ -857,6 +892,7 @@ mod tests {
#[test]
fn test_table_column_width_computation_include_total_row() {
init();
let d1 = crate::Filesystem {
file: None,
mount_info: crate::MountInfo {
@ -915,6 +951,7 @@ mod tests {
#[test]
fn test_row_accumulation_u64_overflow() {
init();
let total = u64::MAX as u128;
let used1 = 3000u128;
let used2 = 50000u128;

View file

@ -8,7 +8,7 @@ use std::ffi::OsString;
use std::path::Path;
use uu_ls::{Config, Format, options};
use uucore::error::UResult;
use uucore::quoting_style::{Quotes, QuotingStyle};
use uucore::quoting_style::QuotingStyle;
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
@ -45,9 +45,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut config = Config::from(&matches)?;
if default_quoting_style {
config.quoting_style = QuotingStyle::C {
quotes: Quotes::None,
};
config.quoting_style = QuotingStyle::C_NO_QUOTES;
}
if default_format_style {
config.format = Format::Columns;

View file

@ -230,7 +230,7 @@ fn get_size_on_disk(path: &Path) -> u64 {
unsafe {
let mut file_info: FILE_STANDARD_INFO = core::mem::zeroed();
let file_info_ptr: *mut FILE_STANDARD_INFO = &mut file_info;
let file_info_ptr: *mut FILE_STANDARD_INFO = &raw mut file_info;
let success = GetFileInformationByHandleEx(
file.as_raw_handle() as HANDLE,
@ -257,7 +257,7 @@ fn get_file_info(path: &Path) -> Option<FileInfo> {
unsafe {
let mut file_info: FILE_ID_INFO = core::mem::zeroed();
let file_info_ptr: *mut FILE_ID_INFO = &mut file_info;
let file_info_ptr: *mut FILE_ID_INFO = &raw mut file_info;
let success = GetFileInformationByHandleEx(
file.as_raw_handle() as HANDLE,

View file

@ -16,3 +16,9 @@ echo-after-help = Echo the STRING(s) to standard output.
- \v vertical tab
- \0NNN byte with octal value NNN (1 to 3 digits)
- \xHH byte with hexadecimal value HH (1 to 2 digits)
echo-help-no-newline = do not output the trailing newline
echo-help-enable-escapes = enable interpretation of backslash escapes
echo-help-disable-escapes = disable interpretation of backslash escapes (default)
echo-error-non-utf8 = Non-UTF-8 arguments provided, but this platform does not support them

View file

@ -0,0 +1,24 @@
echo-about = Affiche une ligne de texte
echo-usage = echo [OPTIONS]... [CHAÎNE]...
echo-after-help = Affiche la ou les CHAÎNE(s) sur la sortie standard.
Si -e est activé, les séquences suivantes sont reconnues :
- \ barre oblique inverse
- \a alerte (BEL)
- \b retour arrière
- \c ne produit aucune sortie supplémentaire
- \e échappement
- \f saut de page
- \n nouvelle ligne
- \r retour chariot
- \t tabulation horizontale
- \v tabulation verticale
- \0NNN octet avec valeur octale NNN (1 à 3 chiffres)
- \xHH octet avec valeur hexadécimale HH (1 à 2 chiffres)
echo-help-no-newline = ne pas afficher la nouvelle ligne finale
echo-help-enable-escapes = activer l'interprétation des séquences d'échappement
echo-help-disable-escapes = désactiver l'interprétation des séquences d'échappement (par défaut)
echo-error-non-utf8 = Arguments non-UTF-8 fournis, mais cette plateforme ne les prend pas en charge

View file

@ -145,20 +145,20 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::NO_NEWLINE)
.short('n')
.help("do not output the trailing newline")
.help(get_message("echo-help-no-newline"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::ENABLE_BACKSLASH_ESCAPE)
.short('e')
.help("enable interpretation of backslash escapes")
.help(get_message("echo-help-enable-escapes"))
.action(ArgAction::SetTrue)
.overrides_with(options::DISABLE_BACKSLASH_ESCAPE),
)
.arg(
Arg::new(options::DISABLE_BACKSLASH_ESCAPE)
.short('E')
.help("disable interpretation of backslash escapes (default)")
.help(get_message("echo-help-disable-escapes"))
.action(ArgAction::SetTrue)
.overrides_with(options::ENABLE_BACKSLASH_ESCAPE),
)
@ -177,10 +177,7 @@ fn execute(
) -> UResult<()> {
for (i, input) in arguments_after_options.into_iter().enumerate() {
let Some(bytes) = bytes_from_os_string(input.as_os_str()) else {
return Err(USimpleError::new(
1,
"Non-UTF-8 arguments provided, but this platform does not support them",
));
return Err(USimpleError::new(1, get_message("echo-error-non-utf8")));
};
if i > 0 {

View file

@ -1,3 +1,43 @@
env-about = Set each NAME to VALUE in the environment and run COMMAND
env-usage = env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]
env-after-help = A mere - implies -i. If no COMMAND, print the resulting environment.
# Help messages
env-help-ignore-environment = start with an empty environment
env-help-chdir = change working directory to DIR
env-help-null = end each output line with a 0 byte rather than a newline (only valid when printing the environment)
env-help-file = read and set variables from a ".env"-style configuration file (prior to any unset and/or set)
env-help-unset = remove variable from the environment
env-help-debug = print verbose information for each processing step
env-help-split-string = process and split S into separate arguments; used to pass multiple arguments on shebang lines
env-help-argv0 = Override the zeroth argument passed to the command being executed. Without this option a default value of `command` is used.
env-help-ignore-signal = set handling of SIG signal(s) to do nothing
# Error messages
env-error-missing-closing-quote = no terminating quote in -S string at position { $position } for quote '{ $quote }'
env-error-invalid-backslash-at-end = invalid backslash at end of string in -S at position { $position } in context { $context }
env-error-backslash-c-not-allowed = '\c' must not appear in double-quoted -S string at position { $position }
env-error-invalid-sequence = invalid sequence '\{ $char }' in -S at position { $position }
env-error-missing-closing-brace = Missing closing brace at position { $position }
env-error-missing-variable = Missing variable name at position { $position }
env-error-missing-closing-brace-after-value = Missing closing brace after default value at position { $position }
env-error-unexpected-number = Unexpected character: '{ $char }', expected variable name must not start with 0..9 at position { $position }
env-error-expected-brace-or-colon = Unexpected character: '{ $char }', expected a closing brace ('{"}"}') or colon (':') at position { $position }
env-error-cannot-specify-null-with-command = cannot specify --null (-0) with command
env-error-invalid-signal = { $signal }: invalid signal
env-error-config-file = { $file }: { $error }
env-error-variable-name-issue = variable name issue (at { $position }): { $error }
env-error-generic = Error: { $error }
env-error-no-such-file = { $program }: No such file or directory
env-error-use-s-shebang = use -[v]S to pass options in shebang lines
env-error-cannot-unset = cannot unset '{ $name }': Invalid argument
env-error-cannot-unset-invalid = cannot unset { $name }: Invalid argument
env-error-must-specify-command-with-chdir = must specify command with --chdir (-C)
env-error-cannot-change-directory = cannot change directory to { $directory }: { $error }
env-error-argv0-not-supported = --argv0 is currently not supported on this platform
env-error-permission-denied = { $program }: Permission denied
env-error-unknown = unknown error: { $error }
env-error-failed-set-signal-action = failed to set signal action for signal { $signal }: { $error }
# Warning messages
env-warning-no-name-specified = no name specified for value { $value }

43
src/uu/env/locales/fr-FR.ftl vendored Normal file
View file

@ -0,0 +1,43 @@
env-about = Définir chaque NOM à VALEUR dans l'environnement et exécuter COMMANDE
env-usage = env [OPTION]... [-] [NOM=VALEUR]... [COMMANDE [ARG]...]
env-after-help = Un simple - implique -i. Si aucune COMMANDE, afficher l'environnement résultant.
# Messages d'aide
env-help-ignore-environment = commencer avec un environnement vide
env-help-chdir = changer le répertoire de travail vers RÉP
env-help-null = terminer chaque ligne de sortie avec un octet 0 plutôt qu'un retour à la ligne (valide uniquement lors de l'affichage de l'environnement)
env-help-file = lire et définir les variables à partir d'un fichier de configuration de style ".env" (avant toute suppression et/ou définition)
env-help-unset = supprimer la variable de l'environnement
env-help-debug = afficher des informations détaillées pour chaque étape de traitement
env-help-split-string = traiter et diviser S en arguments séparés ; utilisé pour passer plusieurs arguments sur les lignes shebang
env-help-argv0 = Remplacer le zéroième argument passé à la commande en cours d'exécution. Sans cette option, une valeur par défaut de `command` est utilisée.
env-help-ignore-signal = définir la gestion du/des signal/signaux SIG pour ne rien faire
# Messages d'erreur
env-error-missing-closing-quote = aucune guillemet de fermeture dans la chaîne -S à la position { $position } pour la guillemet '{ $quote }'
env-error-invalid-backslash-at-end = barre oblique inverse invalide à la fin de la chaîne dans -S à la position { $position } dans le contexte { $context }
env-error-backslash-c-not-allowed = '\\c' ne doit pas apparaître dans une chaîne -S entre guillemets doubles à la position { $position }
env-error-invalid-sequence = séquence invalide '\\{ $char }' dans -S à la position { $position }
env-error-missing-closing-brace = Accolade fermante manquante à la position { $position }
env-error-missing-variable = Nom de variable manquant à la position { $position }
env-error-missing-closing-brace-after-value = Accolade fermante manquante après la valeur par défaut à la position { $position }
env-error-unexpected-number = Caractère inattendu : '{ $char }', le nom de variable attendu ne doit pas commencer par 0..9 à la position { $position }
env-error-expected-brace-or-colon = Caractère inattendu : '{ $char }', accolade fermante ('}') ou deux-points (':') attendu à la position { $position }
env-error-cannot-specify-null-with-command = impossible de spécifier --null (-0) avec une commande
env-error-invalid-signal = { $signal } : signal invalide
env-error-config-file = { $file } : { $error }
env-error-variable-name-issue = problème de nom de variable (à { $position }) : { $error }
env-error-generic = Erreur : { $error }
env-error-no-such-file = { $program } : Aucun fichier ou répertoire de ce type
env-error-use-s-shebang = utilisez -[v]S pour passer des options dans les lignes shebang
env-error-cannot-unset = impossible de supprimer '{ $name }' : Argument invalide
env-error-cannot-unset-invalid = impossible de supprimer { $name } : Argument invalide
env-error-must-specify-command-with-chdir = doit spécifier une commande avec --chdir (-C)
env-error-cannot-change-directory = impossible de changer de répertoire vers { $directory } : { $error }
env-error-argv0-not-supported = --argv0 n'est actuellement pas supporté sur cette plateforme
env-error-permission-denied = { $program } : Permission refusée
env-error-unknown = erreur inconnue : { $error }
env-error-failed-set-signal-action = échec de la définition de l'action du signal pour le signal { $signal } : { $error }
# Messages d'avertissement
env-warning-no-name-specified = aucun nom spécifié pour la valeur { $value }

233
src/uu/env/src/env.rs vendored
View file

@ -22,6 +22,7 @@ use nix::sys::signal::{
SaFlags, SigAction, SigHandler, SigHandler::SigIgn, SigSet, Signal, raise, sigaction, signal,
};
use std::borrow::Cow;
use std::collections::HashMap;
use std::env;
use std::ffi::{OsStr, OsString};
use std::io::{self, Write};
@ -34,6 +35,7 @@ use std::process::{self};
use uucore::display::Quotable;
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError};
use uucore::line_ending::LineEnding;
use uucore::locale::{get_message, get_message_with_args};
#[cfg(unix)]
use uucore::signals::signal_by_name_or_value;
use uucore::{format_usage, show_warning};
@ -42,23 +44,23 @@ use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum EnvError {
#[error("no terminating quote in -S string")]
#[error("{}", get_message_with_args("env-error-missing-closing-quote", HashMap::from([("position".to_string(), .0.to_string()), ("quote".to_string(), .1.to_string())])))]
EnvMissingClosingQuote(usize, char),
#[error("invalid backslash at end of string in -S")]
#[error("{}", get_message_with_args("env-error-invalid-backslash-at-end", HashMap::from([("position".to_string(), .0.to_string()), ("context".to_string(), .1.clone())])))]
EnvInvalidBackslashAtEndOfStringInMinusS(usize, String),
#[error("'\\c' must not appear in double-quoted -S string")]
#[error("{}", get_message_with_args("env-error-backslash-c-not-allowed", HashMap::from([("position".to_string(), .0.to_string())])))]
EnvBackslashCNotAllowedInDoubleQuotes(usize),
#[error("invalid sequence '\\{}' in -S",.1)]
#[error("{}", get_message_with_args("env-error-invalid-sequence", HashMap::from([("position".to_string(), .0.to_string()), ("char".to_string(), .1.to_string())])))]
EnvInvalidSequenceBackslashXInMinusS(usize, char),
#[error("Missing closing brace")]
#[error("{}", get_message_with_args("env-error-missing-closing-brace", HashMap::from([("position".to_string(), .0.to_string())])))]
EnvParsingOfVariableMissingClosingBrace(usize),
#[error("Missing variable name")]
#[error("{}", get_message_with_args("env-error-missing-variable", HashMap::from([("position".to_string(), .0.to_string())])))]
EnvParsingOfMissingVariable(usize),
#[error("Missing closing brace after default value at {}",.0)]
#[error("{}", get_message_with_args("env-error-missing-closing-brace-after-value", HashMap::from([("position".to_string(), .0.to_string())])))]
EnvParsingOfVariableMissingClosingBraceAfterValue(usize),
#[error("Unexpected character: '{}', expected variable name must not start with 0..9",.1)]
#[error("{}", get_message_with_args("env-error-unexpected-number", HashMap::from([("position".to_string(), .0.to_string()), ("char".to_string(), .1.clone())])))]
EnvParsingOfVariableUnexpectedNumber(usize, String),
#[error("Unexpected character: '{}', expected a closing brace ('}}') or colon (':')",.1)]
#[error("{}", get_message_with_args("env-error-expected-brace-or-colon", HashMap::from([("position".to_string(), .0.to_string()), ("char".to_string(), .1.clone())])))]
EnvParsingOfVariableExceptedBraceOrColon(usize, String),
#[error("")]
EnvReachedEnd,
@ -74,8 +76,6 @@ impl From<string_parser::Error> for EnvError {
}
}
use uucore::locale::get_message;
mod options {
pub const IGNORE_ENVIRONMENT: &str = "ignore-environment";
pub const CHDIR: &str = "chdir";
@ -88,8 +88,6 @@ mod options {
pub const IGNORE_SIGNAL: &str = "ignore-signal";
}
const ERROR_MSG_S_SHEBANG: &str = "use -[v]S to pass options in shebang lines";
struct Options<'a> {
ignore_env: bool,
line_ending: LineEnding,
@ -131,7 +129,7 @@ fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()>
if opts.line_ending == LineEnding::Nul {
Err(UUsageError::new(
125,
"cannot specify --null (-0) with command".to_string(),
get_message("env-error-cannot-specify-null-with-command"),
))
} else {
opts.program.push(opt);
@ -143,7 +141,13 @@ fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()>
fn parse_signal_value(signal_name: &str) -> UResult<usize> {
let signal_name_upcase = signal_name.to_uppercase();
let optional_signal_value = signal_by_name_or_value(&signal_name_upcase);
let error = USimpleError::new(125, format!("{}: invalid signal", signal_name.quote()));
let error = USimpleError::new(
125,
get_message_with_args(
"env-error-invalid-signal",
HashMap::from([("signal".to_string(), signal_name.quote().to_string())]),
),
);
match optional_signal_value {
Some(sig_val) => {
if sig_val == 0 {
@ -177,7 +181,10 @@ fn parse_signal_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()> {
let Some(sig_str) = sig.to_str() else {
return Err(USimpleError::new(
1,
format!("{}: invalid signal", sig.quote()),
get_message_with_args(
"env-error-invalid-signal",
HashMap::from([("signal".to_string(), sig.quote().to_string())]),
),
));
};
let sig_val = parse_signal_value(sig_str)?;
@ -201,8 +208,18 @@ fn load_config_file(opts: &mut Options) -> UResult<()> {
Ini::load_from_file(file)
};
let conf =
conf.map_err(|e| USimpleError::new(1, format!("{}: {e}", file.maybe_quote())))?;
let conf = conf.map_err(|e| {
USimpleError::new(
1,
get_message_with_args(
"env-error-config-file",
HashMap::from([
("file".to_string(), file.maybe_quote().to_string()),
("error".to_string(), e.to_string()),
]),
),
)
})?;
for (_, prop) in &conf {
// ignore all INI section lines (treat them as comments)
@ -229,7 +246,7 @@ pub fn uu_app() -> Command {
Arg::new(options::IGNORE_ENVIRONMENT)
.short('i')
.long(options::IGNORE_ENVIRONMENT)
.help("start with an empty environment")
.help(get_message("env-help-ignore-environment"))
.action(ArgAction::SetTrue),
)
.arg(
@ -240,16 +257,13 @@ pub fn uu_app() -> Command {
.value_name("DIR")
.value_parser(ValueParser::os_string())
.value_hint(clap::ValueHint::DirPath)
.help("change working directory to DIR"),
.help(get_message("env-help-chdir")),
)
.arg(
Arg::new(options::NULL)
.short('0')
.long(options::NULL)
.help(
"end each output line with a 0 byte rather than a newline (only \
valid when printing the environment)",
)
.help(get_message("env-help-null"))
.action(ArgAction::SetTrue),
)
.arg(
@ -260,10 +274,7 @@ pub fn uu_app() -> Command {
.value_hint(clap::ValueHint::FilePath)
.value_parser(ValueParser::os_string())
.action(ArgAction::Append)
.help(
"read and set variables from a \".env\"-style configuration file \
(prior to any unset and/or set)",
),
.help(get_message("env-help-file")),
)
.arg(
Arg::new(options::UNSET)
@ -272,14 +283,14 @@ pub fn uu_app() -> Command {
.value_name("NAME")
.action(ArgAction::Append)
.value_parser(ValueParser::os_string())
.help("remove variable from the environment"),
.help(get_message("env-help-unset")),
)
.arg(
Arg::new(options::DEBUG)
.short('v')
.long(options::DEBUG)
.action(ArgAction::Count)
.help("print verbose information for each processing step"),
.help(get_message("env-help-debug")),
)
.arg(
Arg::new(options::SPLIT_STRING) // split string handling is implemented directly, not using CLAP. But this entry here is needed for the help information output.
@ -288,8 +299,9 @@ pub fn uu_app() -> Command {
.value_name("S")
.action(ArgAction::Set)
.value_parser(ValueParser::os_string())
.help("process and split S into separate arguments; used to pass multiple arguments on shebang lines")
).arg(
.help(get_message("env-help-split-string")),
)
.arg(
Arg::new(options::ARGV0)
.overrides_with(options::ARGV0)
.short('a')
@ -297,13 +309,12 @@ pub fn uu_app() -> Command {
.value_name("a")
.action(ArgAction::Set)
.value_parser(ValueParser::os_string())
.help("Override the zeroth argument passed to the command being executed. \
Without this option a default value of `command` is used.")
.help(get_message("env-help-argv0")),
)
.arg(
Arg::new("vars")
.action(ArgAction::Append)
.value_parser(ValueParser::os_string())
.value_parser(ValueParser::os_string()),
)
.arg(
Arg::new(options::IGNORE_SIGNAL)
@ -311,7 +322,7 @@ pub fn uu_app() -> Command {
.value_name("SIG")
.action(ArgAction::Append)
.value_parser(ValueParser::os_string())
.help("set handling of SIG signal(s) to do nothing")
.help(get_message("env-help-ignore-signal")),
)
}
@ -325,22 +336,63 @@ pub fn parse_args_from_str(text: &NativeIntStr) -> UResult<Vec<NativeIntString>>
USimpleError::new(125, e.to_string())
}
EnvError::EnvMissingClosingQuote(_, _) => USimpleError::new(125, e.to_string()),
EnvError::EnvParsingOfVariableMissingClosingBrace(pos) => {
USimpleError::new(125, format!("variable name issue (at {pos}): {e}"))
}
EnvError::EnvParsingOfMissingVariable(pos) => {
USimpleError::new(125, format!("variable name issue (at {pos}): {e}"))
}
EnvError::EnvParsingOfVariableMissingClosingBraceAfterValue(pos) => {
USimpleError::new(125, format!("variable name issue (at {pos}): {e}"))
}
EnvError::EnvParsingOfVariableUnexpectedNumber(pos, _) => {
USimpleError::new(125, format!("variable name issue (at {pos}): {e}"))
}
EnvError::EnvParsingOfVariableExceptedBraceOrColon(pos, _) => {
USimpleError::new(125, format!("variable name issue (at {pos}): {e}"))
}
_ => USimpleError::new(125, format!("Error: {e:?}")),
EnvError::EnvParsingOfVariableMissingClosingBrace(pos) => USimpleError::new(
125,
get_message_with_args(
"env-error-variable-name-issue",
HashMap::from([
("position".to_string(), pos.to_string()),
("error".to_string(), e.to_string()),
]),
),
),
EnvError::EnvParsingOfMissingVariable(pos) => USimpleError::new(
125,
get_message_with_args(
"env-error-variable-name-issue",
HashMap::from([
("position".to_string(), pos.to_string()),
("error".to_string(), e.to_string()),
]),
),
),
EnvError::EnvParsingOfVariableMissingClosingBraceAfterValue(pos) => USimpleError::new(
125,
get_message_with_args(
"env-error-variable-name-issue",
HashMap::from([
("position".to_string(), pos.to_string()),
("error".to_string(), e.to_string()),
]),
),
),
EnvError::EnvParsingOfVariableUnexpectedNumber(pos, _) => USimpleError::new(
125,
get_message_with_args(
"env-error-variable-name-issue",
HashMap::from([
("position".to_string(), pos.to_string()),
("error".to_string(), e.to_string()),
]),
),
),
EnvError::EnvParsingOfVariableExceptedBraceOrColon(pos, _) => USimpleError::new(
125,
get_message_with_args(
"env-error-variable-name-issue",
HashMap::from([
("position".to_string(), pos.to_string()),
("error".to_string(), e.to_string()),
]),
),
),
_ => USimpleError::new(
125,
get_message_with_args(
"env-error-generic",
HashMap::from([("error".to_string(), format!("{e:?}"))]),
),
),
})
}
@ -385,9 +437,15 @@ struct EnvAppData {
impl EnvAppData {
fn make_error_no_such_file_or_dir(&self, prog: &OsStr) -> Box<dyn UError> {
uucore::show_error!("{}: No such file or directory", prog.quote());
uucore::show_error!(
"{}",
get_message_with_args(
"env-error-no-such-file",
HashMap::from([("program".to_string(), prog.quote().to_string())])
)
);
if !self.had_string_argument {
uucore::show_error!("{ERROR_MSG_S_SHEBANG}");
uucore::show_error!("{}", get_message("env-error-use-s-shebang"));
}
ExitCode::new(127)
}
@ -461,7 +519,10 @@ impl EnvAppData {
{
return Err(USimpleError::new(
125,
format!("cannot unset '{}': Invalid argument", &arg_str[2..]),
get_message_with_args(
"env-error-cannot-unset",
HashMap::from([("name".to_string(), arg_str[2..].to_string())]),
),
));
}
@ -493,7 +554,7 @@ impl EnvAppData {
let s = s.trim_end();
uucore::show_error!("{s}");
}
uucore::show_error!("{ERROR_MSG_S_SHEBANG}");
uucore::show_error!("{}", get_message("env-error-use-s-shebang"));
ExitCode::new(125)
}
}
@ -578,7 +639,7 @@ impl EnvAppData {
#[cfg(not(unix))]
return Err(USimpleError::new(
2,
"--argv0 is currently not supported on this platform",
get_message("env-error-argv0-not-supported"),
));
}
@ -629,11 +690,23 @@ impl EnvAppData {
Err(self.make_error_no_such_file_or_dir(&prog))
}
io::ErrorKind::PermissionDenied => {
uucore::show_error!("{}: Permission denied", prog.quote());
uucore::show_error!(
"{}",
get_message_with_args(
"env-error-permission-denied",
HashMap::from([("program".to_string(), prog.quote().to_string())])
)
);
Err(126.into())
}
_ => {
uucore::show_error!("unknown error: {err:?}");
uucore::show_error!(
"{}",
get_message_with_args(
"env-error-unknown",
HashMap::from([("error".to_string(), format!("{err:?}"))])
)
);
Err(126.into())
}
};
@ -722,7 +795,10 @@ fn apply_unset_env_vars(opts: &Options<'_>) -> Result<(), Box<dyn UError>> {
{
return Err(USimpleError::new(
125,
format!("cannot unset {}: Invalid argument", name.quote()),
get_message_with_args(
"env-error-cannot-unset-invalid",
HashMap::from([("name".to_string(), name.quote().to_string())]),
),
));
}
unsafe {
@ -737,7 +813,7 @@ fn apply_change_directory(opts: &Options<'_>) -> Result<(), Box<dyn UError>> {
if opts.program.is_empty() && opts.running_directory.is_some() {
return Err(UUsageError::new(
125,
"must specify command with --chdir (-C)".to_string(),
get_message("env-error-must-specify-command-with-chdir"),
));
}
@ -747,7 +823,13 @@ fn apply_change_directory(opts: &Options<'_>) -> Result<(), Box<dyn UError>> {
Err(error) => {
return Err(USimpleError::new(
125,
format!("cannot change directory to {}: {error}", d.quote()),
get_message_with_args(
"env-error-cannot-change-directory",
HashMap::from([
("directory".to_string(), d.quote().to_string()),
("error".to_string(), error.to_string()),
]),
),
));
}
};
@ -781,7 +863,13 @@ fn apply_specified_env_vars(opts: &Options<'_>) {
*/
if name.is_empty() {
show_warning!("no name specified for value {}", val.quote());
show_warning!(
"{}",
get_message_with_args(
"env-warning-no-name-specified",
HashMap::from([("value".to_string(), val.quote().to_string())])
)
);
continue;
}
unsafe {
@ -809,10 +897,12 @@ fn ignore_signal(sig: Signal) -> UResult<()> {
if let Err(err) = result {
return Err(USimpleError::new(
125,
format!(
"failed to set signal action for signal {}: {}",
sig as i32,
err.desc()
get_message_with_args(
"env-error-failed-set-signal-action",
HashMap::from([
("signal".to_string(), (sig as i32).to_string()),
("error".to_string(), err.desc().to_string()),
]),
),
));
}
@ -827,6 +917,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
#[cfg(test)]
mod tests {
use super::*;
use uucore::locale;
#[test]
fn test_split_string_environment_vars_test() {
@ -861,12 +952,14 @@ mod tests {
#[test]
fn test_error_cases() {
let _ = locale::setup_localization("env");
// Test EnvBackslashCNotAllowedInDoubleQuotes
let result = parse_args_from_str(&NCvt::convert(r#"sh -c "echo \c""#));
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"'\\c' must not appear in double-quoted -S string"
"'\\c' must not appear in double-quoted -S string at position 13"
);
// Test EnvInvalidBackslashAtEndOfStringInMinusS
@ -874,7 +967,7 @@ mod tests {
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"no terminating quote in -S string"
"no terminating quote in -S string at position 13 for quote '\"'"
);
// Test EnvInvalidSequenceBackslashXInMinusS
@ -892,7 +985,7 @@ mod tests {
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"no terminating quote in -S string"
"no terminating quote in -S string at position 12 for quote '\"'"
);
// Test variable-related errors

View file

@ -381,10 +381,10 @@ fn expand_line(
}
} else {
(
match buf[byte] {
match buf.get(byte) {
// always take exactly 1 byte in strict ASCII mode
0x09 => Tab,
0x08 => Backspace,
Some(0x09) => Tab,
Some(0x08) => Backspace,
_ => Other,
},
1,

View file

@ -21,6 +21,7 @@ path = "src/fmt.rs"
clap = { workspace = true }
unicode-width = { workspace = true }
uucore = { workspace = true }
thiserror = { workspace = true }
[[bin]]
name = "fmt"

View file

@ -1,2 +1,32 @@
fmt-about = Reformat paragraphs from input files (or stdin) to stdout.
fmt-usage = fmt [-WIDTH] [OPTION]... [FILE]...
fmt-about = Reformat paragraphs from input (or standard input) to stdout.
fmt-usage = [OPTION]... [FILE]...
# Help messages
fmt-crown-margin-help = First and second line of paragraph may have different indentations, in which case the first line's indentation is preserved, and each subsequent line's indentation matches the second line.
fmt-tagged-paragraph-help = Like -c, except that the first and second line of a paragraph *must* have different indentation or they are treated as separate paragraphs.
fmt-preserve-headers-help = Attempt to detect and preserve mail headers in the input. Be careful when combining this flag with -p.
fmt-split-only-help = Split lines only, do not reflow.
fmt-uniform-spacing-help = Insert exactly one space between words, and two between sentences. Sentence breaks in the input are detected as [?!.] followed by two spaces or a newline; other punctuation is not interpreted as a sentence break.
fmt-prefix-help = Reformat only lines beginning with PREFIX, reattaching PREFIX to reformatted lines. Unless -x is specified, leading whitespace will be ignored when matching PREFIX.
fmt-skip-prefix-help = Do not reformat lines beginning with PSKIP. Unless -X is specified, leading whitespace will be ignored when matching PSKIP
fmt-exact-prefix-help = PREFIX must match at the beginning of the line with no preceding whitespace.
fmt-exact-skip-prefix-help = PSKIP must match at the beginning of the line with no preceding whitespace.
fmt-width-help = Fill output lines up to a maximum of WIDTH columns, default 75. This can be specified as a negative number in the first argument.
fmt-goal-help = Goal width, default of 93% of WIDTH. Must be less than or equal to WIDTH.
fmt-quick-help = Break lines more quickly at the expense of a potentially more ragged appearance.
fmt-tab-width-help = Treat tabs as TABWIDTH spaces for determining line length, default 8. Note that this is used only for calculating line lengths; tabs are preserved in the output.
# Error messages
fmt-error-invalid-goal = invalid goal: {$goal}
fmt-error-goal-greater-than-width = GOAL cannot be greater than WIDTH.
fmt-error-invalid-width = invalid width: {$width}
fmt-error-width-out-of-range = invalid width: '{$width}': Numerical result out of range
fmt-error-invalid-tabwidth = Invalid TABWIDTH specification: {$tabwidth}
fmt-error-first-option-width = invalid option -- {$option}; -WIDTH is recognized only when it is the first
option; use -w N instead
Try 'fmt --help' for more information.
fmt-error-read = read error
fmt-error-invalid-width-malformed = invalid width: {$width}
fmt-error-cannot-open-for-reading = cannot open {$file} for reading
fmt-error-cannot-get-metadata = cannot get metadata for {$file}
fmt-error-failed-to-write-output = failed to write output

View file

@ -0,0 +1,32 @@
fmt-about = Reformate les paragraphes depuis l'entrée (ou l'entrée standard) vers la sortie standard.
fmt-usage = [OPTION]... [FICHIER]...
# Messages d'aide
fmt-crown-margin-help = La première et la deuxième ligne d'un paragraphe peuvent avoir des indentations différentes, auquel cas l'indentation de la première ligne est préservée, et chaque ligne suivante correspond à l'indentation de la deuxième ligne.
fmt-tagged-paragraph-help = Comme -c, sauf que la première et la deuxième ligne d'un paragraphe *doivent* avoir des indentations différentes ou elles sont traitées comme des paragraphes séparés.
fmt-preserve-headers-help = Tente de détecter et préserver les en-têtes de courrier dans l'entrée. Attention en combinant ce drapeau avec -p.
fmt-split-only-help = Divise les lignes seulement, ne les reformate pas.
fmt-uniform-spacing-help = Insère exactement un espace entre les mots, et deux entre les phrases. Les fins de phrase dans l'entrée sont détectées comme [?!.] suivies de deux espaces ou d'une nouvelle ligne ; les autres ponctuations ne sont pas interprétées comme des fins de phrase.
fmt-prefix-help = Reformate seulement les lignes commençant par PRÉFIXE, en rattachant PRÉFIXE aux lignes reformatées. À moins que -x soit spécifié, les espaces de début seront ignorés lors de la correspondance avec PRÉFIXE.
fmt-skip-prefix-help = Ne reformate pas les lignes commençant par PSKIP. À moins que -X soit spécifié, les espaces de début seront ignorés lors de la correspondance avec PSKIP
fmt-exact-prefix-help = PRÉFIXE doit correspondre au début de la ligne sans espace précédent.
fmt-exact-skip-prefix-help = PSKIP doit correspondre au début de la ligne sans espace précédent.
fmt-width-help = Remplit les lignes de sortie jusqu'à un maximum de WIDTH colonnes, par défaut 75. Cela peut être spécifié comme un nombre négatif dans le premier argument.
fmt-goal-help = Largeur objectif, par défaut 93% de WIDTH. Doit être inférieur ou égal à WIDTH.
fmt-quick-help = Divise les lignes plus rapidement au détriment d'un aspect potentiellement plus irrégulier.
fmt-tab-width-help = Traite les tabulations comme TABWIDTH espaces pour déterminer la longueur de ligne, par défaut 8. Notez que ceci n'est utilisé que pour calculer les longueurs de ligne ; les tabulations sont préservées dans la sortie.
# Messages d'erreur
fmt-error-invalid-goal = objectif invalide : {$goal}
fmt-error-goal-greater-than-width = GOAL ne peut pas être supérieur à WIDTH.
fmt-error-invalid-width = largeur invalide : {$width}
fmt-error-width-out-of-range = largeur invalide : '{$width}' : Résultat numérique hors limites
fmt-error-invalid-tabwidth = Spécification TABWIDTH invalide : {$tabwidth}
fmt-error-first-option-width = option invalide -- {$option} ; -WIDTH n'est reconnu que lorsqu'il est la première
option ; utilisez -w N à la place
Essayez 'fmt --help' pour plus d'informations.
fmt-error-read = erreur de lecture
fmt-error-invalid-width-malformed = largeur invalide : {$width}
fmt-error-cannot-open-for-reading = impossible d'ouvrir {$file} en lecture
fmt-error-cannot-get-metadata = impossible d'obtenir les métadonnées pour {$file}
fmt-error-failed-to-write-output = échec de l'écriture de sortie

View file

@ -9,16 +9,45 @@ use clap::{Arg, ArgAction, ArgMatches, Command};
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Stdout, Write, stdin, stdout};
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::error::{FromIo, UResult, USimpleError};
use uucore::format_usage;
use linebreak::break_lines;
use parasplit::ParagraphStream;
use uucore::locale::get_message;
use std::collections::HashMap;
use thiserror::Error;
use uucore::locale::{get_message, get_message_with_args};
mod linebreak;
mod parasplit;
#[derive(Debug, Error)]
enum FmtError {
#[error("{}", get_message_with_args("fmt-error-invalid-goal", HashMap::from([("goal".to_string(), .0.quote().to_string())])))]
InvalidGoal(String),
#[error("{}", get_message("fmt-error-goal-greater-than-width"))]
GoalGreaterThanWidth,
#[error("{}", get_message_with_args("fmt-error-invalid-width", HashMap::from([("width".to_string(), .0.quote().to_string())])))]
InvalidWidth(String),
#[error("{}", get_message_with_args("fmt-error-width-out-of-range", HashMap::from([("width".to_string(), .0.to_string())])))]
WidthOutOfRange(usize),
#[error("{}", get_message_with_args("fmt-error-invalid-tabwidth", HashMap::from([("tabwidth".to_string(), .0.quote().to_string())])))]
InvalidTabWidth(String),
#[error("{}", get_message_with_args("fmt-error-first-option-width", HashMap::from([("option".to_string(), .0.to_string())])))]
FirstOptionWidth(char),
#[error("{}", get_message("fmt-error-read"))]
ReadError,
#[error("{}", get_message_with_args("fmt-error-invalid-width-malformed", HashMap::from([("width".to_string(), .0.quote().to_string())])))]
InvalidWidthMalformed(String),
}
impl From<FmtError> for Box<dyn uucore::error::UError> {
fn from(err: FmtError) -> Self {
USimpleError::new(1, err.to_string())
}
}
const MAX_WIDTH: usize = 2500;
const DEFAULT_GOAL: usize = 70;
const DEFAULT_WIDTH: usize = 75;
@ -92,10 +121,7 @@ impl FmtOptions {
match goal_str.parse::<usize>() {
Ok(goal) => Some(goal),
Err(_) => {
return Err(USimpleError::new(
1,
format!("invalid goal: {}", goal_str.quote()),
));
return Err(FmtError::InvalidGoal(goal_str.clone()).into());
}
}
} else {
@ -105,7 +131,7 @@ impl FmtOptions {
let (width, goal) = match (width_opt, goal_opt) {
(Some(w), Some(g)) => {
if g > w {
return Err(USimpleError::new(1, "GOAL cannot be greater than WIDTH."));
return Err(FmtError::GoalGreaterThanWidth.into());
}
(w, g)
}
@ -119,7 +145,7 @@ impl FmtOptions {
}
(None, Some(g)) => {
if g > DEFAULT_WIDTH {
return Err(USimpleError::new(1, "GOAL cannot be greater than WIDTH."));
return Err(FmtError::GoalGreaterThanWidth.into());
}
let w = (g * 100 / DEFAULT_GOAL_TO_WIDTH_RATIO).max(g + 3);
(w, g)
@ -132,21 +158,15 @@ impl FmtOptions {
);
if width > MAX_WIDTH {
return Err(USimpleError::new(
1,
format!("invalid width: '{width}': Numerical result out of range"),
));
return Err(FmtError::WidthOutOfRange(width).into());
}
let mut tabwidth = 8;
if let Some(s) = matches.get_one::<String>(options::TAB_WIDTH) {
tabwidth = match s.parse::<usize>() {
Ok(t) => t,
Err(e) => {
return Err(USimpleError::new(
1,
format!("Invalid TABWIDTH specification: {}: {e}", s.quote()),
));
Err(_) => {
return Err(FmtError::InvalidTabWidth(s.clone()).into());
}
};
};
@ -192,13 +212,22 @@ fn process_file(
let mut fp = BufReader::new(match file_name {
"-" => Box::new(stdin()) as Box<dyn Read + 'static>,
_ => {
let f = File::open(file_name)
.map_err_context(|| format!("cannot open {} for reading", file_name.quote()))?;
let f = File::open(file_name).map_err_context(|| {
get_message_with_args(
"fmt-error-cannot-open-for-reading",
HashMap::from([("file".to_string(), file_name.quote().to_string())]),
)
})?;
if f.metadata()
.map_err_context(|| format!("cannot get metadata for {}", file_name.quote()))?
.map_err_context(|| {
get_message_with_args(
"fmt-error-cannot-get-metadata",
HashMap::from([("file".to_string(), file_name.quote().to_string())]),
)
})?
.is_dir()
{
return Err(USimpleError::new(1, "read error".to_string()));
return Err(FmtError::ReadError.into());
}
Box::new(f) as Box<dyn Read + 'static>
@ -211,20 +240,20 @@ fn process_file(
Err(s) => {
ostream
.write_all(s.as_bytes())
.map_err_context(|| "failed to write output".to_string())?;
.map_err_context(|| get_message("fmt-error-failed-to-write-output"))?;
ostream
.write_all(b"\n")
.map_err_context(|| "failed to write output".to_string())?;
.map_err_context(|| get_message("fmt-error-failed-to-write-output"))?;
}
Ok(para) => break_lines(&para, fmt_opts, ostream)
.map_err_context(|| "failed to write output".to_string())?,
.map_err_context(|| get_message("fmt-error-failed-to-write-output"))?,
}
}
// flush the output after each file
ostream
.flush()
.map_err_context(|| "failed to write output".to_string())?;
.map_err_context(|| get_message("fmt-error-failed-to-write-output"))?;
Ok(())
}
@ -251,10 +280,11 @@ fn extract_files(matches: &ArgMatches) -> UResult<Vec<String>> {
if in_first_pos && i == 0 {
None
} else {
let first_num = x.chars().nth(1).expect("a negative number should be at least two characters long");
Some(Err(
UUsageError::new(1, format!("invalid option -- {first_num}; -WIDTH is recognized only when it is the first\noption; use -w N instead"))
))
let first_num = x
.chars()
.nth(1)
.expect("a negative number should be at least two characters long");
Some(Err(FmtError::FirstOptionWidth(first_num).into()))
}
} else {
Some(Ok(x.clone()))
@ -275,10 +305,7 @@ fn extract_width(matches: &ArgMatches) -> UResult<Option<usize>> {
return if let Ok(width) = width_str.parse::<usize>() {
Ok(Some(width))
} else {
Err(USimpleError::new(
1,
format!("invalid width: {}", width_str.quote()),
))
Err(FmtError::InvalidWidth(width_str.clone()).into())
};
}
@ -307,13 +334,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
&& first_arg.chars().nth(1).is_some_and(|c| c.is_ascii_digit())
&& first_arg.chars().skip(2).any(|c| !c.is_ascii_digit());
if malformed_number {
return Err(USimpleError::new(
1,
format!(
"invalid width: {}",
first_arg.strip_prefix('-').unwrap().quote()
),
));
return Err(FmtError::InvalidWidthMalformed(
first_arg.strip_prefix('-').unwrap().to_string(),
)
.into());
}
}
@ -343,102 +367,70 @@ pub fn uu_app() -> Command {
Arg::new(options::CROWN_MARGIN)
.short('c')
.long(options::CROWN_MARGIN)
.help(
"First and second line of paragraph \
may have different indentations, in which \
case the first line's indentation is preserved, \
and each subsequent line's indentation matches the second line.",
)
.help(get_message("fmt-crown-margin-help"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::TAGGED_PARAGRAPH)
.short('t')
.long("tagged-paragraph")
.help(
"Like -c, except that the first and second line of a paragraph *must* \
have different indentation or they are treated as separate paragraphs.",
)
.help(get_message("fmt-tagged-paragraph-help"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::PRESERVE_HEADERS)
.short('m')
.long("preserve-headers")
.help(
"Attempt to detect and preserve mail headers in the input. \
Be careful when combining this flag with -p.",
)
.help(get_message("fmt-preserve-headers-help"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::SPLIT_ONLY)
.short('s')
.long("split-only")
.help("Split lines only, do not reflow.")
.help(get_message("fmt-split-only-help"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::UNIFORM_SPACING)
.short('u')
.long("uniform-spacing")
.help(
"Insert exactly one \
space between words, and two between sentences. \
Sentence breaks in the input are detected as [?!.] \
followed by two spaces or a newline; other punctuation \
is not interpreted as a sentence break.",
)
.help(get_message("fmt-uniform-spacing-help"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::PREFIX)
.short('p')
.long("prefix")
.help(
"Reformat only lines \
beginning with PREFIX, reattaching PREFIX to reformatted lines. \
Unless -x is specified, leading whitespace will be ignored \
when matching PREFIX.",
)
.help(get_message("fmt-prefix-help"))
.value_name("PREFIX"),
)
.arg(
Arg::new(options::SKIP_PREFIX)
.short('P')
.long("skip-prefix")
.help(
"Do not reformat lines \
beginning with PSKIP. Unless -X is specified, leading whitespace \
will be ignored when matching PSKIP",
)
.help(get_message("fmt-skip-prefix-help"))
.value_name("PSKIP"),
)
.arg(
Arg::new(options::EXACT_PREFIX)
.short('x')
.long("exact-prefix")
.help(
"PREFIX must match at the \
beginning of the line with no preceding whitespace.",
)
.help(get_message("fmt-exact-prefix-help"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::EXACT_SKIP_PREFIX)
.short('X')
.long("exact-skip-prefix")
.help(
"PSKIP must match at the \
beginning of the line with no preceding whitespace.",
)
.help(get_message("fmt-exact-skip-prefix-help"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::WIDTH)
.short('w')
.long("width")
.help("Fill output lines up to a maximum of WIDTH columns, default 75. This can be specified as a negative number in the first argument.")
.help(get_message("fmt-width-help"))
// We must accept invalid values if they are overridden later. This is not supported by clap, so accept all strings instead.
.value_name("WIDTH"),
)
@ -446,7 +438,7 @@ pub fn uu_app() -> Command {
Arg::new(options::GOAL)
.short('g')
.long("goal")
.help("Goal width, default of 93% of WIDTH. Must be less than or equal to WIDTH.")
.help(get_message("fmt-goal-help"))
// We must accept invalid values if they are overridden later. This is not supported by clap, so accept all strings instead.
.value_name("GOAL"),
)
@ -454,21 +446,14 @@ pub fn uu_app() -> Command {
Arg::new(options::QUICK)
.short('q')
.long("quick")
.help(
"Break lines more quickly at the \
expense of a potentially more ragged appearance.",
)
.help(get_message("fmt-quick-help"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::TAB_WIDTH)
.short('T')
.long("tab-width")
.help(
"Treat tabs as TABWIDTH spaces for \
determining line length, default 8. Note that this is used only for \
calculating line lengths; tabs are preserved in the output.",
)
.help(get_message("fmt-tab-width-help"))
.value_name("TABWIDTH"),
)
.arg(

View file

@ -236,7 +236,11 @@ fn find_kp_breakpoints<'a, T: Iterator<Item = &'a WordInfo<'a>>>(
let mut next_active_breaks = vec![];
let stretch = args.opts.width - args.opts.goal;
let minlength = args.opts.goal.max(stretch + 1) - stretch;
let minlength = if args.opts.goal <= 10 {
1
} else {
args.opts.goal.max(stretch + 1) - stretch
};
let mut new_linebreaks = vec![];
let mut is_sentence_start = false;
let mut least_demerits = 0;
@ -384,11 +388,11 @@ fn build_best_path<'a>(paths: &[LineBreak<'a>], active: &[usize]) -> Vec<(&'a Wo
const BAD_INFTY: i64 = 10_000_000;
const BAD_INFTY_SQ: i64 = BAD_INFTY * BAD_INFTY;
// badness = BAD_MULT * abs(r) ^ 3
const BAD_MULT: f32 = 100.0;
const BAD_MULT: f32 = 200.0;
// DR_MULT is multiplier for delta-R between lines
const DR_MULT: f32 = 600.0;
// DL_MULT is penalty multiplier for short words at end of line
const DL_MULT: f32 = 300.0;
const DL_MULT: f32 = 10.0;
fn compute_demerits(delta_len: isize, stretch: usize, wlen: usize, prev_rat: f32) -> (i64, f32) {
// how much stretch are we using?

View file

@ -26,6 +26,14 @@ fn char_width(c: char) -> usize {
}
}
// GNU fmt has a more restrictive definition of whitespace than Unicode.
// It only considers ASCII whitespace characters (space, tab, newline, etc.)
// and excludes many Unicode whitespace characters like non-breaking spaces.
fn is_fmt_whitespace(c: char) -> bool {
// Only ASCII whitespace characters are considered whitespace in GNU fmt
matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0B' | '\x0C')
}
// lines with PSKIP, lacking PREFIX, or which are entirely blank are
// NoFormatLines; otherwise, they are FormatLines
#[derive(Debug)]
@ -109,7 +117,7 @@ impl FileLines<'_> {
for (i, char) in line.char_indices() {
if line[i..].starts_with(pfx) {
return (true, i);
} else if !char.is_whitespace() {
} else if !is_fmt_whitespace(char) {
break;
}
}
@ -128,7 +136,7 @@ impl FileLines<'_> {
prefix_len = indent_len;
}
if (os >= prefix_end) && !c.is_whitespace() {
if (os >= prefix_end) && !is_fmt_whitespace(c) {
// found first non-whitespace after prefix, this is indent_end
indent_end = os;
break;
@ -154,7 +162,7 @@ impl Iterator for FileLines<'_> {
// emit a blank line
// Err(true) indicates that this was a linebreak,
// which is important to know when detecting mail headers
if n.chars().all(char::is_whitespace) {
if n.chars().all(is_fmt_whitespace) {
return Some(Line::NoFormatLine(String::new(), true));
}
@ -174,7 +182,7 @@ impl Iterator for FileLines<'_> {
if pmatch
&& n[poffset + self.opts.prefix.as_ref().map_or(0, |s| s.len())..]
.chars()
.all(char::is_whitespace)
.all(is_fmt_whitespace)
{
return Some(Line::NoFormatLine(n, false));
}
@ -498,7 +506,7 @@ impl WordSplit<'_> {
let mut aftertab = 0;
let mut word_start = None;
for (os, c) in string.char_indices() {
if !c.is_whitespace() {
if !is_fmt_whitespace(c) {
word_start = Some(os);
break;
} else if c == '\t' {
@ -519,7 +527,7 @@ impl WordSplit<'_> {
impl WordSplit<'_> {
fn new<'b>(opts: &'b FmtOptions, string: &'b str) -> WordSplit<'b> {
// wordsplits *must* start at a non-whitespace character
let trim_string = string.trim_start();
let trim_string = string.trim_start_matches(is_fmt_whitespace);
WordSplit {
opts,
string: trim_string,
@ -571,7 +579,7 @@ impl<'a> Iterator for WordSplit<'a> {
// points to whitespace character OR end of string
let mut word_nchars = 0;
self.position = match self.string[word_start..].find(|x: char| {
if x.is_whitespace() {
if is_fmt_whitespace(x) {
true
} else {
word_nchars += char_width(x);

View file

@ -8,7 +8,7 @@
use clap::{Arg, ArgAction, Command};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, stdin};
use std::io::{BufRead, BufReader, Read, Write, stdin, stdout};
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError};
@ -16,6 +16,9 @@ use uucore::format_usage;
use uucore::locale::{get_message, get_message_with_args};
const TAB_WIDTH: usize = 8;
const NL: u8 = b'\n';
const CR: u8 = b'\r';
const TAB: u8 = b'\t';
mod options {
pub const BYTES: &str = "bytes";
@ -141,18 +144,18 @@ fn fold(filenames: &[String], bytes: bool, spaces: bool, width: usize) -> UResul
///
/// If `spaces` is `true`, attempt to break lines at whitespace boundaries.
fn fold_file_bytewise<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) -> UResult<()> {
let mut line = String::new();
let mut line = Vec::new();
loop {
if file
.read_line(&mut line)
.read_until(NL, &mut line)
.map_err_context(|| get_message("fold-error-readline"))?
== 0
{
break;
}
if line == "\n" {
if line == [NL] {
println!();
line.truncate(0);
continue;
@ -166,8 +169,13 @@ fn fold_file_bytewise<T: Read>(mut file: BufReader<T>, spaces: bool, width: usiz
let slice = {
let slice = &line[i..i + width];
if spaces && i + width < len {
match slice.rfind(|c: char| c.is_whitespace() && c != '\r') {
Some(m) => &slice[..=m],
match slice
.iter()
.enumerate()
.rev()
.find(|(_, c)| c.is_ascii_whitespace() && **c != CR)
{
Some((m, _)) => &slice[..=m],
None => slice,
}
} else {
@ -178,7 +186,7 @@ fn fold_file_bytewise<T: Read>(mut file: BufReader<T>, spaces: bool, width: usiz
// Don't duplicate trailing newlines: if the slice is "\n", the
// previous iteration folded just before the end of the line and
// has already printed this newline.
if slice == "\n" {
if slice == [NL] {
break;
}
@ -187,9 +195,10 @@ fn fold_file_bytewise<T: Read>(mut file: BufReader<T>, spaces: bool, width: usiz
let at_eol = i >= len;
if at_eol {
print!("{slice}");
stdout().write_all(slice)?;
} else {
println!("{slice}");
stdout().write_all(slice)?;
stdout().write_all(&[NL])?;
}
}
@ -209,8 +218,8 @@ fn fold_file_bytewise<T: Read>(mut file: BufReader<T>, spaces: bool, width: usiz
#[allow(unused_assignments)]
#[allow(clippy::cognitive_complexity)]
fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) -> UResult<()> {
let mut line = String::new();
let mut output = String::new();
let mut line = Vec::new();
let mut output = Vec::new();
let mut col_count = 0;
let mut last_space = None;
@ -226,8 +235,9 @@ fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) -> URe
None => output.len(),
};
println!("{}", &output[..consume]);
output.replace_range(..consume, "");
stdout().write_all(&output[..consume])?;
stdout().write_all(&[NL])?;
output.drain(..consume);
// we know there are no tabs left in output, so each char counts
// as 1 column
@ -239,15 +249,15 @@ fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) -> URe
loop {
if file
.read_line(&mut line)
.read_until(NL, &mut line)
.map_err_context(|| get_message("fold-error-readline"))?
== 0
{
break;
}
for ch in line.chars() {
if ch == '\n' {
for ch in &line {
if *ch == NL {
// make sure to _not_ split output at whitespace, since we
// know the entire output will fit
last_space = None;
@ -259,9 +269,9 @@ fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) -> URe
emit_output!();
}
match ch {
'\r' => col_count = 0,
'\t' => {
match *ch {
CR => col_count = 0,
TAB => {
let next_tab_stop = col_count + TAB_WIDTH - col_count % TAB_WIDTH;
if next_tab_stop > width && !output.is_empty() {
@ -271,21 +281,21 @@ fn fold_file<T: Read>(mut file: BufReader<T>, spaces: bool, width: usize) -> URe
col_count = next_tab_stop;
last_space = if spaces { Some(output.len()) } else { None };
}
'\x08' => {
0x08 => {
col_count = col_count.saturating_sub(1);
}
_ if spaces && ch.is_whitespace() => {
_ if spaces && ch.is_ascii_whitespace() => {
last_space = Some(output.len());
col_count += 1;
}
_ => col_count += 1,
}
output.push(ch);
output.push(*ch);
}
if !output.is_empty() {
print!("{output}");
stdout().write_all(&output)?;
output.truncate(0);
}

View file

@ -1,3 +1,59 @@
install-about = Copy SOURCE to DEST or multiple SOURCE(s) to the existing
DIRECTORY, while setting permission modes and owner/group
install-usage = install [OPTION]... [FILE]...
# Help messages
install-help-ignored = ignored
install-help-compare = compare each pair of source and destination files, and in some cases, do not modify the destination at all
install-help-directory = treat all arguments as directory names. create all components of the specified directories
install-help-create-leading = create all leading components of DEST except the last, then copy SOURCE to DEST
install-help-group = set group ownership, instead of process's current group
install-help-mode = set permission mode (as in chmod), instead of rwxr-xr-x
install-help-owner = set ownership (super-user only)
install-help-preserve-timestamps = apply access/modification times of SOURCE files to corresponding destination files
install-help-strip = strip symbol tables (no action Windows)
install-help-strip-program = program used to strip binaries (no action Windows)
install-help-target-directory = move all SOURCE arguments into DIRECTORY
install-help-no-target-directory = treat DEST as a normal file
install-help-verbose = explain what is being done
install-help-preserve-context = preserve security context
install-help-context = set security context of files and directories
# Error messages
install-error-dir-needs-arg = { $util_name } with -d requires at least one argument.
install-error-create-dir-failed = failed to create { $path }
install-error-chmod-failed = failed to chmod { $path }
install-error-chmod-failed-detailed = { $path }: chmod failed with error { $error }
install-error-chown-failed = failed to chown { $path }: { $error }
install-error-invalid-target = invalid target { $path }: No such file or directory
install-error-target-not-dir = target { $path } is not a directory
install-error-backup-failed = cannot backup { $from } to { $to }
install-error-install-failed = cannot install { $from } to { $to }
install-error-strip-failed = strip program failed: { $error }
install-error-strip-abnormal = strip process terminated abnormally - exit code: { $code }
install-error-metadata-failed = metadata error
install-error-invalid-user = invalid user: { $user }
install-error-invalid-group = invalid group: { $group }
install-error-omitting-directory = omitting directory { $path }
install-error-not-a-directory = failed to access { $path }: Not a directory
install-error-override-directory-failed = cannot overwrite directory { $dir } with non-directory { $file }
install-error-same-file = '{ $file1 }' and '{ $file2 }' are the same file
install-error-extra-operand = extra operand { $operand }
{ $usage }
install-error-invalid-mode = Invalid mode string: { $error }
install-error-mutually-exclusive-target = Options --target-directory and --no-target-directory are mutually exclusive
install-error-mutually-exclusive-compare-preserve = Options --compare and --preserve-timestamps are mutually exclusive
install-error-mutually-exclusive-compare-strip = Options --compare and --strip are mutually exclusive
install-error-missing-file-operand = missing file operand
install-error-missing-destination-operand = missing destination file operand after '{ $path }'
install-error-failed-to-remove = Failed to remove existing file { $path }. Error: { $error }
# Warning messages
install-warning-compare-ignored = the --compare (-C) option is ignored when you specify a mode with non-permission bits
# Verbose output
install-verbose-creating-directory = creating directory { $path }
install-verbose-creating-directory-step = install: creating directory { $path }
install-verbose-removed = removed { $path }
install-verbose-copy = { $from } -> { $to }
install-verbose-backup = (backup: { $backup })

View file

@ -0,0 +1,59 @@
install-about = Copier SOURCE vers DEST ou plusieurs SOURCE(s) vers le
RÉPERTOIRE existant, tout en définissant les modes de permission et propriétaire/groupe
install-usage = install [OPTION]... [FICHIER]...
# Messages d'aide
install-help-ignored = ignoré
install-help-compare = comparer chaque paire de fichiers source et destination, et dans certains cas, ne pas modifier la destination du tout
install-help-directory = traiter tous les arguments comme des noms de répertoires. créer tous les composants des répertoires spécifiés
install-help-create-leading = créer tous les composants principaux de DEST sauf le dernier, puis copier SOURCE vers DEST
install-help-group = définir la propriété du groupe, au lieu du groupe actuel du processus
install-help-mode = définir le mode de permission (comme dans chmod), au lieu de rwxr-xr-x
install-help-owner = définir la propriété (super-utilisateur uniquement)
install-help-preserve-timestamps = appliquer les temps d'accès/modification des fichiers SOURCE aux fichiers de destination correspondants
install-help-strip = supprimer les tables de symboles (aucune action Windows)
install-help-strip-program = programme utilisé pour supprimer les binaires (aucune action Windows)
install-help-target-directory = déplacer tous les arguments SOURCE dans RÉPERTOIRE
install-help-no-target-directory = traiter DEST comme un fichier normal
install-help-verbose = expliquer ce qui est fait
install-help-preserve-context = préserver le contexte de sécurité
install-help-context = définir le contexte de sécurité des fichiers et répertoires
# Messages d'erreur
install-error-dir-needs-arg = { $util_name } avec -d nécessite au moins un argument.
install-error-create-dir-failed = échec de la création de { $path }
install-error-chmod-failed = échec du chmod { $path }
install-error-chmod-failed-detailed = { $path } : échec du chmod avec l'erreur { $error }
install-error-chown-failed = échec du chown { $path } : { $error }
install-error-invalid-target = cible invalide { $path } : Aucun fichier ou répertoire de ce type
install-error-target-not-dir = la cible { $path } n'est pas un répertoire
install-error-backup-failed = impossible de sauvegarder { $from } vers { $to }
install-error-install-failed = impossible d'installer { $from } vers { $to }
install-error-strip-failed = échec du programme strip : { $error }
install-error-strip-abnormal = le processus strip s'est terminé anormalement - code de sortie : { $code }
install-error-metadata-failed = erreur de métadonnées
install-error-invalid-user = utilisateur invalide : { $user }
install-error-invalid-group = groupe invalide : { $group }
install-error-omitting-directory = omission du répertoire { $path }
install-error-not-a-directory = échec de l'accès à { $path } : N'est pas un répertoire
install-error-override-directory-failed = impossible d'écraser le répertoire { $dir } avec un non-répertoire { $file }
install-error-same-file = '{ $file1 }' et '{ $file2 }' sont le même fichier
install-error-extra-operand = opérande supplémentaire { $operand }
{ $usage }
install-error-invalid-mode = Chaîne de mode invalide : { $error }
install-error-mutually-exclusive-target = Les options --target-directory et --no-target-directory sont mutuellement exclusives
install-error-mutually-exclusive-compare-preserve = Les options --compare et --preserve-timestamps sont mutuellement exclusives
install-error-mutually-exclusive-compare-strip = Les options --compare et --strip sont mutuellement exclusives
install-error-missing-file-operand = opérande de fichier manquant
install-error-missing-destination-operand = opérande de fichier de destination manquant après '{ $path }'
install-error-failed-to-remove = Échec de la suppression du fichier existant { $path }. Erreur : { $error }
# Messages d'avertissement
install-warning-compare-ignored = l'option --compare (-C) est ignorée quand un mode est indiqué avec des bits non liés à des droits
# Sortie détaillée
install-verbose-creating-directory = création du répertoire { $path }
install-verbose-creating-directory-step = install : création du répertoire { $path }
install-verbose-removed = supprimé { $path }
install-verbose-copy = { $from } -> { $to }
install-verbose-backup = (sauvegarde : { $backup })

View file

@ -10,6 +10,7 @@ mod mode;
use clap::{Arg, ArgAction, ArgMatches, Command};
use file_diff::diff;
use filetime::{FileTime, set_file_times};
use std::collections::HashMap;
use std::fmt::Debug;
use std::fs::File;
use std::fs::{self, metadata};
@ -33,7 +34,7 @@ use uucore::{format_usage, show, show_error, show_if_err};
use std::os::unix::fs::{FileTypeExt, MetadataExt};
#[cfg(unix)]
use std::os::unix::prelude::OsStrExt;
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
const DEFAULT_MODE: u32 = 0o755;
const DEFAULT_STRIP_PROGRAM: &str = "strip";
@ -60,55 +61,55 @@ pub struct Behavior {
#[derive(Error, Debug)]
enum InstallError {
#[error("{} with -d requires at least one argument.", uucore::util_name())]
#[error("{}", get_message_with_args("install-error-dir-needs-arg", HashMap::from([("util_name".to_string(), uucore::util_name().to_string())])))]
DirNeedsArg,
#[error("failed to create {0}")]
#[error("{}", get_message_with_args("install-error-create-dir-failed", HashMap::from([("path".to_string(), .0.quote().to_string())])))]
CreateDirFailed(PathBuf, #[source] std::io::Error),
#[error("failed to chmod {}", .0.quote())]
#[error("{}", get_message_with_args("install-error-chmod-failed", HashMap::from([("path".to_string(), .0.quote().to_string())])))]
ChmodFailed(PathBuf),
#[error("failed to chown {}: {}", .0.quote(), .1)]
#[error("{}", get_message_with_args("install-error-chown-failed", HashMap::from([("path".to_string(), .0.quote().to_string()), ("error".to_string(), .1.clone())])))]
ChownFailed(PathBuf, String),
#[error("invalid target {}: No such file or directory", .0.quote())]
#[error("{}", get_message_with_args("install-error-invalid-target", HashMap::from([("path".to_string(), .0.quote().to_string())])))]
InvalidTarget(PathBuf),
#[error("target {} is not a directory", .0.quote())]
#[error("{}", get_message_with_args("install-error-target-not-dir", HashMap::from([("path".to_string(), .0.quote().to_string())])))]
TargetDirIsntDir(PathBuf),
#[error("cannot backup {0} to {1}")]
#[error("{}", get_message_with_args("install-error-backup-failed", HashMap::from([("from".to_string(), .0.to_string_lossy().to_string()), ("to".to_string(), .1.to_string_lossy().to_string())])))]
BackupFailed(PathBuf, PathBuf, #[source] std::io::Error),
#[error("cannot install {0} to {1}")]
#[error("{}", get_message_with_args("install-error-install-failed", HashMap::from([("from".to_string(), .0.to_string_lossy().to_string()), ("to".to_string(), .1.to_string_lossy().to_string())])))]
InstallFailed(PathBuf, PathBuf, #[source] std::io::Error),
#[error("strip program failed: {0}")]
#[error("{}", get_message_with_args("install-error-strip-failed", HashMap::from([("error".to_string(), .0.clone())])))]
StripProgramFailed(String),
#[error("metadata error")]
#[error("{}", get_message("install-error-metadata-failed"))]
MetadataFailed(#[source] std::io::Error),
#[error("invalid user: {}", .0.quote())]
#[error("{}", get_message_with_args("install-error-invalid-user", HashMap::from([("user".to_string(), .0.quote().to_string())])))]
InvalidUser(String),
#[error("invalid group: {}", .0.quote())]
#[error("{}", get_message_with_args("install-error-invalid-group", HashMap::from([("group".to_string(), .0.quote().to_string())])))]
InvalidGroup(String),
#[error("omitting directory {}", .0.quote())]
#[error("{}", get_message_with_args("install-error-omitting-directory", HashMap::from([("path".to_string(), .0.quote().to_string())])))]
OmittingDirectory(PathBuf),
#[error("failed to access {}: Not a directory", .0.quote())]
#[error("{}", get_message_with_args("install-error-not-a-directory", HashMap::from([("path".to_string(), .0.quote().to_string())])))]
NotADirectory(PathBuf),
#[error("cannot overwrite directory {} with non-directory {}", .0.quote(), .1.quote())]
#[error("{}", get_message_with_args("install-error-override-directory-failed", HashMap::from([("dir".to_string(), .0.quote().to_string()), ("file".to_string(), .1.quote().to_string())])))]
OverrideDirectoryFailed(PathBuf, PathBuf),
#[error("'{0}' and '{1}' are the same file")]
#[error("{}", get_message_with_args("install-error-same-file", HashMap::from([("file1".to_string(), .0.to_string_lossy().to_string()), ("file2".to_string(), .1.to_string_lossy().to_string())])))]
SameFile(PathBuf, PathBuf),
#[error("extra operand {}\n{}", .0.quote(), .1.quote())]
#[error("{}", get_message_with_args("install-error-extra-operand", HashMap::from([("operand".to_string(), .0.quote().to_string()), ("usage".to_string(), .1.clone())])))]
ExtraOperand(String, String),
#[cfg(feature = "selinux")]
@ -186,62 +187,54 @@ pub fn uu_app() -> Command {
.about(get_message("install-about"))
.override_usage(format_usage(&get_message("install-usage")))
.infer_long_args(true)
.args_override_self(true)
.arg(backup_control::arguments::backup())
.arg(backup_control::arguments::backup_no_args())
.arg(
Arg::new(OPT_IGNORED)
.short('c')
.help("ignored")
.help(get_message("install-help-ignored"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_COMPARE)
.short('C')
.long(OPT_COMPARE)
.help(
"compare each pair of source and destination files, and in some cases, \
do not modify the destination at all",
)
.help(get_message("install-help-compare"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_DIRECTORY)
.short('d')
.long(OPT_DIRECTORY)
.help(
"treat all arguments as directory names. create all components of \
the specified directories",
)
.help(get_message("install-help-directory"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_CREATE_LEADING)
.short('D')
.help(
"create all leading components of DEST except the last, then copy \
SOURCE to DEST",
)
.help(get_message("install-help-create-leading"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_GROUP)
.short('g')
.long(OPT_GROUP)
.help("set group ownership, instead of process's current group")
.help(get_message("install-help-group"))
.value_name("GROUP"),
)
.arg(
Arg::new(OPT_MODE)
.short('m')
.long(OPT_MODE)
.help("set permission mode (as in chmod), instead of rwxr-xr-x")
.help(get_message("install-help-mode"))
.value_name("MODE"),
)
.arg(
Arg::new(OPT_OWNER)
.short('o')
.long(OPT_OWNER)
.help("set ownership (super-user only)")
.help(get_message("install-help-owner"))
.value_name("OWNER")
.value_hint(clap::ValueHint::Username),
)
@ -249,23 +242,20 @@ pub fn uu_app() -> Command {
Arg::new(OPT_PRESERVE_TIMESTAMPS)
.short('p')
.long(OPT_PRESERVE_TIMESTAMPS)
.help(
"apply access/modification times of SOURCE files to \
corresponding destination files",
)
.help(get_message("install-help-preserve-timestamps"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_STRIP)
.short('s')
.long(OPT_STRIP)
.help("strip symbol tables (no action Windows)")
.help(get_message("install-help-strip"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_STRIP_PROGRAM)
.long(OPT_STRIP_PROGRAM)
.help("program used to strip binaries (no action Windows)")
.help(get_message("install-help-strip-program"))
.value_name("PROGRAM")
.value_hint(clap::ValueHint::CommandName),
)
@ -274,7 +264,7 @@ pub fn uu_app() -> Command {
Arg::new(OPT_TARGET_DIRECTORY)
.short('t')
.long(OPT_TARGET_DIRECTORY)
.help("move all SOURCE arguments into DIRECTORY")
.help(get_message("install-help-target-directory"))
.value_name("DIRECTORY")
.value_hint(clap::ValueHint::DirPath),
)
@ -282,28 +272,28 @@ pub fn uu_app() -> Command {
Arg::new(OPT_NO_TARGET_DIRECTORY)
.short('T')
.long(OPT_NO_TARGET_DIRECTORY)
.help("treat DEST as a normal file")
.help(get_message("install-help-no-target-directory"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_VERBOSE)
.short('v')
.long(OPT_VERBOSE)
.help("explain what is being done")
.help(get_message("install-help-verbose"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_PRESERVE_CONTEXT)
.short('P')
.long(OPT_PRESERVE_CONTEXT)
.help("preserve security context")
.help(get_message("install-help-preserve-context"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_CONTEXT)
.short('Z')
.long(OPT_CONTEXT)
.help("set security context of files and directories")
.help(get_message("install-help-context"))
.value_name("CONTEXT")
.value_parser(clap::value_parser!(String))
.num_args(0..=1),
@ -336,7 +326,13 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
let specified_mode: Option<u32> = if matches.contains_id(OPT_MODE) {
let x = matches.get_one::<String>(OPT_MODE).ok_or(1)?;
Some(mode::parse(x, considering_dir, get_umask()).map_err(|err| {
show_error!("Invalid mode string: {err}");
show_error!(
"{}",
get_message_with_args(
"install-error-invalid-mode",
HashMap::from([("error".to_string(), err)])
)
);
1
})?)
} else {
@ -347,7 +343,7 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
let target_dir = matches.get_one::<String>(OPT_TARGET_DIRECTORY).cloned();
let no_target_dir = matches.get_flag(OPT_NO_TARGET_DIRECTORY);
if target_dir.is_some() && no_target_dir {
show_error!("Options --target-directory and --no-target-directory are mutually exclusive");
show_error!("{}", get_message("install-error-mutually-exclusive-target"));
return Err(1.into());
}
@ -355,14 +351,29 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
let compare = matches.get_flag(OPT_COMPARE);
let strip = matches.get_flag(OPT_STRIP);
if preserve_timestamps && compare {
show_error!("Options --compare and --preserve-timestamps are mutually exclusive");
show_error!(
"{}",
get_message("install-error-mutually-exclusive-compare-preserve")
);
return Err(1.into());
}
if compare && strip {
show_error!("Options --compare and --strip are mutually exclusive");
show_error!(
"{}",
get_message("install-error-mutually-exclusive-compare-strip")
);
return Err(1.into());
}
// Check if compare is used with non-permission mode bits
if compare && specified_mode.is_some() {
let mode = specified_mode.unwrap();
let non_permission_bits = 0o7000; // setuid, setgid, sticky bits
if mode & non_permission_bits != 0 {
show_error!("{}", get_message("install-warning-compare-ignored"));
}
}
let owner = matches
.get_one::<String>(OPT_OWNER)
.map(|s| s.as_str())
@ -456,7 +467,16 @@ fn directory(paths: &[String], b: &Behavior) -> UResult<()> {
}
if b.verbose {
println!("creating directory {}", path_to_create.quote());
println!(
"{}",
get_message_with_args(
"install-verbose-creating-directory",
HashMap::from([(
"path".to_string(),
path_to_create.quote().to_string()
)])
)
);
}
}
@ -511,7 +531,10 @@ fn is_potential_directory_path(path: &Path) -> bool {
fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
// first check that paths contains at least one element
if paths.is_empty() {
return Err(UUsageError::new(1, "missing file operand"));
return Err(UUsageError::new(
1,
get_message("install-error-missing-file-operand"),
));
}
if b.no_target_dir && paths.len() > 2 {
return Err(InstallError::ExtraOperand(
@ -531,9 +554,9 @@ fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
if paths.is_empty() {
return Err(UUsageError::new(
1,
format!(
"missing destination file operand after '{}'",
last_path.to_str().unwrap()
get_message_with_args(
"install-error-missing-destination-operand",
HashMap::from([("path".to_string(), last_path.to_str().unwrap().to_string())]),
),
));
}
@ -570,7 +593,16 @@ fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
result.push(part.as_os_str());
if !result.is_dir() {
// Don't display when the directory already exists
println!("install: creating directory {}", result.quote());
println!(
"{}",
get_message_with_args(
"install-verbose-creating-directory-step",
HashMap::from([(
"path".to_string(),
result.quote().to_string()
)])
)
);
}
}
}
@ -716,7 +748,13 @@ fn chown_optional_user_group(path: &Path, b: &Behavior) -> UResult<()> {
fn perform_backup(to: &Path, b: &Behavior) -> UResult<Option<PathBuf>> {
if to.exists() {
if b.verbose {
println!("removed {}", to.quote());
println!(
"{}",
get_message_with_args(
"install-verbose-removed",
HashMap::from([("path".to_string(), to.quote().to_string())])
)
);
}
let backup_path = backup_control::get_backup_path(b.backup_mode, to, &b.suffix);
if let Some(ref backup_path) = backup_path {
@ -777,8 +815,14 @@ fn copy_file(from: &Path, to: &Path) -> UResult<()> {
if let Err(e) = fs::remove_file(to) {
if e.kind() != std::io::ErrorKind::NotFound {
show_error!(
"Failed to remove existing file {}. Error: {e:?}",
to.display(),
"{}",
get_message_with_args(
"install-error-failed-to-remove",
HashMap::from([
("path".to_string(), to.display().to_string()),
("error".to_string(), format!("{e:?}"))
])
)
);
}
}
@ -832,9 +876,9 @@ fn strip_file(to: &Path, b: &Behavior) -> UResult<()> {
if !status.success() {
// Follow GNU's behavior: if strip fails, removes the target
let _ = fs::remove_file(to);
return Err(InstallError::StripProgramFailed(format!(
"strip process terminated abnormally - exit code: {}",
status.code().unwrap()
return Err(InstallError::StripProgramFailed(get_message_with_args(
"install-error-strip-abnormal",
HashMap::from([("code".to_string(), status.code().unwrap().to_string())]),
))
.into());
}
@ -912,7 +956,7 @@ fn preserve_timestamps(from: &Path, to: &Path) -> UResult<()> {
/// If the copy system call fails, we print a verbose error and return an empty error value.
///
fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
if b.compare && !need_copy(from, to, b)? {
if b.compare && !need_copy(from, to, b) {
return Ok(());
}
// Declare the path here as we may need it for the verbose output below.
@ -940,9 +984,24 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
}
if b.verbose {
print!("{} -> {}", from.quote(), to.quote());
print!(
"{}",
get_message_with_args(
"install-verbose-copy",
HashMap::from([
("from".to_string(), from.quote().to_string()),
("to".to_string(), to.quote().to_string())
])
)
);
match backup_path {
Some(path) => println!(" (backup: {})", path.quote()),
Some(path) => println!(
" {}",
get_message_with_args(
"install-verbose-backup",
HashMap::from([("backup".to_string(), path.quote().to_string())])
)
),
None => println!(),
}
}
@ -950,6 +1009,30 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
Ok(())
}
/// Check if a file needs to be copied due to ownership differences when no explicit group is specified.
/// Returns true if the destination file's ownership would differ from what it should be after installation.
fn needs_copy_for_ownership(to: &Path, to_meta: &fs::Metadata) -> bool {
use std::os::unix::fs::MetadataExt;
// Check if the destination file's owner differs from the effective user ID
if to_meta.uid() != geteuid() {
return true;
}
// For group, we need to determine what the group would be after installation
// If no group is specified, the behavior depends on the directory:
// - If the directory has setgid bit, the file inherits the directory's group
// - Otherwise, the file gets the user's effective group
let expected_gid = to
.parent()
.and_then(|parent| metadata(parent).ok())
.filter(|parent_meta| parent_meta.mode() & 0o2000 != 0)
.map(|parent_meta| parent_meta.gid())
.unwrap_or(getegid());
to_meta.gid() != expected_gid
}
/// Return true if a file is necessary to copy. This is the case when:
///
/// - _from_ or _to_ is nonexistent;
@ -967,19 +1050,26 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
///
/// Crashes the program if a nonexistent owner or group is specified in _b_.
///
fn need_copy(from: &Path, to: &Path, b: &Behavior) -> UResult<bool> {
fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
// Attempt to retrieve metadata for the source file.
// If this fails, assume the file needs to be copied.
let Ok(from_meta) = metadata(from) else {
return Ok(true);
return true;
};
// Attempt to retrieve metadata for the destination file.
// If this fails, assume the file needs to be copied.
let Ok(to_meta) = metadata(to) else {
return Ok(true);
return true;
};
// Check if the destination is a symlink (should always be replaced)
if let Ok(to_symlink_meta) = fs::symlink_metadata(to) {
if to_symlink_meta.file_type().is_symlink() {
return true;
}
}
// Define special file mode bits (setuid, setgid, sticky).
let extra_mode: u32 = 0o7000;
// Define all file mode bits (including permissions).
@ -988,31 +1078,31 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> UResult<bool> {
// Check if any special mode bits are set in the specified mode,
// source file mode, or destination file mode.
if b.specified_mode.unwrap_or(0) & extra_mode != 0
if b.mode() & extra_mode != 0
|| from_meta.mode() & extra_mode != 0
|| to_meta.mode() & extra_mode != 0
{
return Ok(true);
return true;
}
// Check if the mode of the destination file differs from the specified mode.
if b.mode() != to_meta.mode() & all_modes {
return Ok(true);
return true;
}
// Check if either the source or destination is not a file.
if !from_meta.is_file() || !to_meta.is_file() {
return Ok(true);
return true;
}
// Check if the file sizes differ.
if from_meta.len() != to_meta.len() {
return Ok(true);
return true;
}
#[cfg(feature = "selinux")]
if b.preserve_context && contexts_differ(from, to) {
return Ok(true);
return true;
}
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
@ -1020,30 +1110,25 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> UResult<bool> {
// Check if the owner ID is specified and differs from the destination file's owner.
if let Some(owner_id) = b.owner_id {
if owner_id != to_meta.uid() {
return Ok(true);
return true;
}
}
// Check if the group ID is specified and differs from the destination file's group.
if let Some(group_id) = b.group_id {
if group_id != to_meta.gid() {
return Ok(true);
}
} else {
#[cfg(not(target_os = "windows"))]
// Check if the destination file's owner or group
// differs from the effective user/group ID of the process.
if to_meta.uid() != geteuid() || to_meta.gid() != getegid() {
return Ok(true);
return true;
}
} else if needs_copy_for_ownership(to, &to_meta) {
return true;
}
// Check if the contents of the source and destination files differ.
if !diff(from.to_str().unwrap(), to.to_str().unwrap()) {
return Ok(true);
return true;
}
Ok(false)
false
}
#[cfg(feature = "selinux")]

View file

@ -2,8 +2,10 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use uucore::locale::get_message_with_args;
#[cfg(not(windows))]
use uucore::mode;
@ -25,7 +27,16 @@ pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> {
use std::os::unix::fs::PermissionsExt;
use uucore::{display::Quotable, show_error};
fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|err| {
show_error!("{}: chmod failed with error {err}", path.maybe_quote());
show_error!(
"{}",
get_message_with_args(
"install-error-chmod-failed-detailed",
HashMap::from([
("path".to_string(), path.maybe_quote().to_string()),
("error".to_string(), err.to_string())
])
)
);
})
}

View file

@ -11,3 +11,28 @@ ln-after-help = In the 1st form, create a link to TARGET with the name LINK_NAME
When creating hard links, each TARGET must exist. Symbolic links
can hold arbitrary text; if later resolved, a relative link is
interpreted in relation to its parent directory.
ln-help-force = remove existing destination files
ln-help-interactive = prompt whether to remove existing destination files
ln-help-no-dereference = treat LINK_NAME as a normal file if it is a
symbolic link to a directory
ln-help-logical = follow TARGETs that are symbolic links
ln-help-physical = make hard links directly to symbolic links
ln-help-symbolic = make symbolic links instead of hard links
ln-help-target-directory = specify the DIRECTORY in which to create the links
ln-help-no-target-directory = treat LINK_NAME as a normal file always
ln-help-relative = create symbolic links relative to link location
ln-help-verbose = print name of each linked file
ln-error-target-is-not-directory = target {$target} is not a directory
ln-error-same-file = {$file1} and {$file2} are the same file
ln-error-missing-destination = missing destination file operand after {$operand}
ln-error-extra-operand = extra operand {$operand}
Try '{$program} --help' for more information.
ln-error-could-not-update = Could not update {$target}: {$error}
ln-error-cannot-stat = cannot stat {$path}: No such file or directory
ln-error-will-not-overwrite = will not overwrite just-created '{$target}' with '{$source}'
ln-prompt-replace = replace {$file}?
ln-cannot-backup = cannot backup {$file}
ln-failed-to-access = failed to access {$file}
ln-failed-to-create-hard-link = failed to create hard link {$source} => {$dest}
ln-backup = backup: {$backup}

View file

@ -0,0 +1,39 @@
ln-about = Créer des liens entre fichiers
ln-usage = ln [OPTION]... [-T] CIBLE NOM_LIEN
ln [OPTION]... CIBLE
ln [OPTION]... CIBLE... RÉPERTOIRE
ln [OPTION]... -t RÉPERTOIRE CIBLE...
ln-after-help = Dans la 1ère forme, créer un lien vers CIBLE avec le nom NOM_LIEN.
Dans la 2ème forme, créer un lien vers CIBLE dans le répertoire courant.
Dans les 3ème et 4ème formes, créer des liens vers chaque CIBLE dans RÉPERTOIRE.
Créer des liens physiques par défaut, des liens symboliques avec --symbolic.
Par défaut, chaque destination (nom du nouveau lien) ne doit pas déjà exister.
Lors de la création de liens physiques, chaque CIBLE doit exister. Les liens symboliques
peuvent contenir du texte arbitraire ; s'ils sont résolus plus tard, un lien relatif est
interprété en relation avec son répertoire parent.
ln-help-force = supprimer les fichiers de destination existants
ln-help-interactive = demander avant de supprimer les fichiers de destination existants
ln-help-no-dereference = traiter NOM_LIEN comme un fichier normal s'il s'agit d'un
lien symbolique vers un répertoire
ln-help-logical = suivre les CIBLEs qui sont des liens symboliques
ln-help-physical = créer des liens physiques directement vers les liens symboliques
ln-help-symbolic = créer des liens symboliques au lieu de liens physiques
ln-help-target-directory = spécifier le RÉPERTOIRE dans lequel créer les liens
ln-help-no-target-directory = toujours traiter NOM_LIEN comme un fichier normal
ln-help-relative = créer des liens symboliques relatifs à l'emplacement du lien
ln-help-verbose = afficher le nom de chaque fichier lié
ln-error-target-is-not-directory = la cible {$target} n'est pas un répertoire
ln-error-same-file = {$file1} et {$file2} sont le même fichier
ln-error-missing-destination = opérande de fichier de destination manquant après {$operand}
ln-error-extra-operand = opérande supplémentaire {$operand}
Essayez « {$program} --help » pour plus d'informations.
ln-error-could-not-update = Impossible de mettre à jour {$target} : {$error}
ln-error-cannot-stat = impossible d'analyser {$path} : Aucun fichier ou répertoire de ce nom
ln-error-will-not-overwrite = ne remplacera pas le fichier « {$target} » qui vient d'être créé par « {$source} »
ln-prompt-replace = remplacer {$file} ?
ln-cannot-backup = impossible de sauvegarder {$file}
ln-failed-to-access = échec d'accès à {$file}
ln-failed-to-create-hard-link = échec de création du lien physique {$source} => {$dest}
ln-backup = sauvegarde : {$backup}

View file

@ -17,6 +17,7 @@ use std::ffi::OsString;
use std::fs;
use thiserror::Error;
use std::collections::HashMap;
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::symlink;
#[cfg(windows)]
@ -24,7 +25,7 @@ use std::os::windows::fs::{symlink_dir, symlink_file};
use std::path::{Path, PathBuf};
use uucore::backup_control::{self, BackupMode};
use uucore::fs::{MissingHandling, ResolveMode, canonicalize};
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
pub struct Settings {
overwrite: OverwriteMode,
@ -48,20 +49,19 @@ pub enum OverwriteMode {
#[derive(Error, Debug)]
enum LnError {
#[error("target {} is not a directory", _0.quote())]
#[error("{}", get_message_with_args("ln-error-target-is-not-directory", HashMap::from([("target".to_string(), _0.quote().to_string())])))]
TargetIsNotADirectory(PathBuf),
#[error("")]
SomeLinksFailed,
#[error("{} and {} are the same file", _0.quote(), _1.quote())]
#[error("{}", get_message_with_args("ln-error-same-file", HashMap::from([("file1".to_string(), _0.quote().to_string()), ("file2".to_string(), _1.quote().to_string())])))]
SameFile(PathBuf, PathBuf),
#[error("missing destination file operand after {}", _0.quote())]
#[error("{}", get_message_with_args("ln-error-missing-destination", HashMap::from([("operand".to_string(), _0.quote().to_string())])))]
MissingDestination(PathBuf),
#[error("extra operand {}\nTry '{} --help' for more information.",
format!("{_0:?}").trim_matches('"'), _1)]
#[error("{}", get_message_with_args("ln-error-extra-operand", HashMap::from([("operand".to_string(), format!("{_0:?}").trim_matches('"').to_string()), ("program".to_string(), _1.clone())])))]
ExtraOperand(OsString, String),
}
@ -157,31 +157,28 @@ pub fn uu_app() -> Command {
Arg::new(options::FORCE)
.short('f')
.long(options::FORCE)
.help("remove existing destination files")
.help(get_message("ln-help-force"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::INTERACTIVE)
.short('i')
.long(options::INTERACTIVE)
.help("prompt whether to remove existing destination files")
.help(get_message("ln-help-interactive"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::NO_DEREFERENCE)
.short('n')
.long(options::NO_DEREFERENCE)
.help(
"treat LINK_NAME as a normal file if it is a \
symbolic link to a directory",
)
.help(get_message("ln-help-no-dereference"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::LOGICAL)
.short('L')
.long(options::LOGICAL)
.help("follow TARGETs that are symbolic links")
.help(get_message("ln-help-logical"))
.overrides_with(options::PHYSICAL)
.action(ArgAction::SetTrue),
)
@ -190,14 +187,14 @@ pub fn uu_app() -> Command {
Arg::new(options::PHYSICAL)
.short('P')
.long(options::PHYSICAL)
.help("make hard links directly to symbolic links")
.help(get_message("ln-help-physical"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::SYMBOLIC)
.short('s')
.long(options::SYMBOLIC)
.help("make symbolic links instead of hard links")
.help(get_message("ln-help-symbolic"))
// override added for https://github.com/uutils/coreutils/issues/2359
.overrides_with(options::SYMBOLIC)
.action(ArgAction::SetTrue),
@ -207,7 +204,7 @@ pub fn uu_app() -> Command {
Arg::new(options::TARGET_DIRECTORY)
.short('t')
.long(options::TARGET_DIRECTORY)
.help("specify the DIRECTORY in which to create the links")
.help(get_message("ln-help-target-directory"))
.value_name("DIRECTORY")
.value_hint(clap::ValueHint::DirPath)
.conflicts_with(options::NO_TARGET_DIRECTORY),
@ -216,14 +213,14 @@ pub fn uu_app() -> Command {
Arg::new(options::NO_TARGET_DIRECTORY)
.short('T')
.long(options::NO_TARGET_DIRECTORY)
.help("treat LINK_NAME as a normal file always")
.help(get_message("ln-help-no-target-directory"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::RELATIVE)
.short('r')
.long(options::RELATIVE)
.help("create symbolic links relative to link location")
.help(get_message("ln-help-relative"))
.requires(options::SYMBOLIC)
.action(ArgAction::SetTrue),
)
@ -231,7 +228,7 @@ pub fn uu_app() -> Command {
Arg::new(options::VERBOSE)
.short('v')
.long(options::VERBOSE)
.help("print name of each linked file")
.help(get_message("ln-help-verbose"))
.action(ArgAction::SetTrue),
)
.arg(
@ -296,7 +293,16 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
// We need to clean the target
if target_dir.is_file() {
if let Err(e) = fs::remove_file(target_dir) {
show_error!("Could not update {}: {e}", target_dir.quote());
show_error!(
"{}",
get_message_with_args(
"ln-error-could-not-update",
HashMap::from([
("target".to_string(), target_dir.quote().to_string()),
("error".to_string(), e.to_string())
])
)
);
};
}
#[cfg(windows)]
@ -305,7 +311,16 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
// considered as a dir
// See test_ln::test_symlink_no_deref_dir
if let Err(e) = fs::remove_dir(target_dir) {
show_error!("Could not update {}: {e}", target_dir.quote());
show_error!(
"{}",
get_message_with_args(
"ln-error-could-not-update",
HashMap::from([
("target".to_string(), target_dir.quote().to_string()),
("error".to_string(), e.to_string())
])
)
);
};
}
target_dir.to_path_buf()
@ -322,7 +337,13 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
}
}
None => {
show_error!("cannot stat {}: No such file or directory", srcpath.quote());
show_error!(
"{}",
get_message_with_args(
"ln-error-cannot-stat",
HashMap::from([("path".to_string(), srcpath.quote().to_string())])
)
);
all_successful = false;
continue;
}
@ -332,9 +353,14 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
if linked_destinations.contains(&targetpath) {
// If the target file was already created in this ln call, do not overwrite
show_error!(
"will not overwrite just-created '{}' with '{}'",
targetpath.display(),
srcpath.display()
"{}",
get_message_with_args(
"ln-error-will-not-overwrite",
HashMap::from([
("target".to_string(), targetpath.display().to_string()),
("source".to_string(), srcpath.display().to_string())
])
)
);
all_successful = false;
} else if let Err(e) = link(srcpath, &targetpath, settings) {
@ -387,12 +413,23 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
}
}
if let Some(ref p) = backup_path {
fs::rename(dst, p).map_err_context(|| format!("cannot backup {}", dst.quote()))?;
fs::rename(dst, p).map_err_context(|| {
get_message_with_args(
"ln-cannot-backup",
HashMap::from([("file".to_string(), dst.quote().to_string())]),
)
})?;
}
match settings.overwrite {
OverwriteMode::NoClobber => {}
OverwriteMode::Interactive => {
if !prompt_yes!("replace {}?", dst.quote()) {
if !prompt_yes!(
"{}",
get_message_with_args(
"ln-prompt-replace",
HashMap::from([("file".to_string(), dst.quote().to_string())])
)
) {
return Err(LnError::SomeLinksFailed.into());
}
@ -416,16 +453,22 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
// if we want to have an hard link,
// source is a symlink and -L is passed
// we want to resolve the symlink to create the hardlink
fs::canonicalize(&source)
.map_err_context(|| format!("failed to access {}", source.quote()))?
fs::canonicalize(&source).map_err_context(|| {
get_message_with_args(
"ln-failed-to-access",
HashMap::from([("file".to_string(), source.quote().to_string())]),
)
})?
} else {
source.to_path_buf()
};
fs::hard_link(p, dst).map_err_context(|| {
format!(
"failed to create hard link {} => {}",
source.quote(),
dst.quote()
get_message_with_args(
"ln-failed-to-create-hard-link",
HashMap::from([
("source".to_string(), source.quote().to_string()),
("dest".to_string(), dst.quote().to_string()),
]),
)
})?;
}
@ -433,7 +476,13 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> UResult<()> {
if settings.verbose {
print!("{} -> {}", dst.quote(), source.quote());
match backup_path {
Some(path) => println!(" (backup: {})", path.quote()),
Some(path) => println!(
" ({})",
get_message_with_args(
"ln-backup",
HashMap::from([("backup".to_string(), path.quote().to_string())])
)
),
None => println!(),
}
}

View file

@ -52,4 +52,4 @@ name = "ls"
path = "src/main.rs"
[features]
feat_selinux = ["selinux"]
feat_selinux = ["selinux", "uucore/selinux"]

View file

@ -2,3 +2,119 @@ ls-about = List directory contents.
Ignore files and directories starting with a '.' by default
ls-usage = ls [OPTION]... [FILE]...
ls-after-help = The TIME_STYLE argument can be full-iso, long-iso, iso, locale or +FORMAT. FORMAT is interpreted like in date. Also the TIME_STYLE environment variable sets the default style to use.
# Error messages
ls-error-invalid-line-width = invalid line width: {$width}
ls-error-general-io = general io error: {$error}
ls-error-cannot-access-no-such-file = cannot access '{$path}': No such file or directory
ls-error-cannot-access-operation-not-permitted = cannot access '{$path}': Operation not permitted
ls-error-cannot-open-directory-permission-denied = cannot open directory '{$path}': Permission denied
ls-error-cannot-open-file-permission-denied = cannot open file '{$path}': Permission denied
ls-error-cannot-open-directory-bad-descriptor = cannot open directory '{$path}': Bad file descriptor
ls-error-unknown-io-error = unknown io error: '{$path}', '{$error}'
ls-error-invalid-block-size = invalid --block-size argument {$size}
ls-error-dired-and-zero-incompatible = --dired and --zero are incompatible
ls-error-not-listing-already-listed = {$path}: not listing already-listed directory
ls-error-invalid-time-style = invalid --time-style argument {$style}
Possible values are: {$values}
For more information try --help
# Help messages
ls-help-print-help = Print help information.
ls-help-set-display-format = Set the display format.
ls-help-display-files-columns = Display the files in columns.
ls-help-display-detailed-info = Display detailed information.
ls-help-list-entries-rows = List entries in rows instead of in columns.
ls-help-assume-tab-stops = Assume tab stops at each COLS instead of 8
ls-help-list-entries-commas = List entries separated by commas.
ls-help-list-entries-nul = List entries separated by ASCII NUL characters.
ls-help-generate-dired-output = generate output designed for Emacs' dired (Directory Editor) mode
ls-help-hyperlink-filenames = hyperlink file names WHEN
ls-help-list-one-file-per-line = List one file per line.
ls-help-long-format-no-group = Long format without group information.
Identical to --format=long with --no-group.
ls-help-long-no-owner = Long format without owner information.
ls-help-long-numeric-uid-gid = -l with numeric UIDs and GIDs.
ls-help-set-quoting-style = Set quoting style.
ls-help-literal-quoting-style = Use literal quoting style. Equivalent to `--quoting-style=literal`
ls-help-escape-quoting-style = Use escape quoting style. Equivalent to `--quoting-style=escape`
ls-help-c-quoting-style = Use C quoting style. Equivalent to `--quoting-style=c`
ls-help-replace-control-chars = Replace control characters with '?' if they are not escaped.
ls-help-show-control-chars = Show control characters 'as is' if they are not escaped.
ls-help-show-time-field = Show time in <field>:
access time (-u): atime, access, use;
change time (-t): ctime, status.
modification time: mtime, modification.
birth time: birth, creation;
ls-help-time-change = If the long listing format (e.g., -l, -o) is being used, print the
status change time (the 'ctime' in the inode) instead of the modification
time. When explicitly sorting by time (--sort=time or -t) or when not
using a long listing format, sort according to the status change time.
ls-help-time-access = If the long listing format (e.g., -l, -o) is being used, print the
status access time instead of the modification time. When explicitly
sorting by time (--sort=time or -t) or when not using a long listing
format, sort according to the access time.
ls-help-hide-pattern = do not list implied entries matching shell PATTERN (overridden by -a or -A)
ls-help-ignore-pattern = do not list implied entries matching shell PATTERN
ls-help-ignore-backups = Ignore entries which end with ~.
ls-help-sort-by-field = Sort by <field>: name, none (-U), time (-t), size (-S), extension (-X) or width
ls-help-sort-by-size = Sort by file size, largest first.
ls-help-sort-by-time = Sort by modification time (the 'mtime' in the inode), newest first.
ls-help-sort-by-version = Natural sort of (version) numbers in the filenames.
ls-help-sort-by-extension = Sort alphabetically by entry extension.
ls-help-sort-none = Do not sort; list the files in whatever order they are stored in the
directory. This is especially useful when listing very large directories,
since not doing any sorting can be noticeably faster.
ls-help-dereference-all = When showing file information for a symbolic link, show information for the
file the link references rather than the link itself.
ls-help-dereference-dir-args = Do not follow symlinks except when they link to directories and are
given as command line arguments.
ls-help-dereference-args = Do not follow symlinks except when given as command line arguments.
ls-help-no-group = Do not show group in long format.
ls-help-author = Show author in long format. On the supported platforms,
the author always matches the file owner.
ls-help-all-files = Do not ignore hidden files (files with names that start with '.').
ls-help-almost-all = In a directory, do not ignore all file names that start with '.',
only ignore '.' and '..'.
ls-help-directory = Only list the names of directories, rather than listing directory contents.
This will not follow symbolic links unless one of `--dereference-command-line
(-H)`, `--dereference (-L)`, or `--dereference-command-line-symlink-to-dir` is
specified.
ls-help-human-readable = Print human readable file sizes (e.g. 1K 234M 56G).
ls-help-kibibytes = default to 1024-byte blocks for file system usage; used only with -s and per
directory totals
ls-help-si = Print human readable file sizes using powers of 1000 instead of 1024.
ls-help-block-size = scale sizes by BLOCK_SIZE when printing them
ls-help-print-inode = print the index number of each file
ls-help-reverse-sort = Reverse whatever the sorting method is e.g., list files in reverse
alphabetical order, youngest first, smallest first, or whatever.
ls-help-recursive = List the contents of all directories recursively.
ls-help-terminal-width = Assume that the terminal is COLS columns wide.
ls-help-allocation-size = print the allocated size of each file, in blocks
ls-help-color-output = Color output based on file type.
ls-help-indicator-style = Append indicator with style WORD to entry names:
none (default), slash (-p), file-type (--file-type), classify (-F)
ls-help-classify = Append a character to each file name indicating the file type. Also, for
regular files that are executable, append '*'. The file type indicators are
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets,
'>' for doors, and nothing for regular files. when may be omitted, or one of:
none - Do not classify. This is the default.
auto - Only classify if standard output is a terminal.
always - Always classify.
Specifying --classify and no when is equivalent to --classify=always. This will
not follow symbolic links listed on the command line unless the
--dereference-command-line (-H), --dereference (-L), or
--dereference-command-line-symlink-to-dir options are specified.
ls-help-file-type = Same as --classify, but do not append '*'
ls-help-slash-directories = Append / indicator to directories.
ls-help-time-style = time/date format with -l; see TIME_STYLE below
ls-help-full-time = like -l --time-style=full-iso
ls-help-context = print any security context of each file
ls-help-group-directories-first = group directories before files; can be augmented with
a --sort option, but any use of --sort=none (-U) disables grouping
ls-invalid-quoting-style = {$program}: Ignoring invalid value of environment variable QUOTING_STYLE: '{$style}'
ls-invalid-columns-width = ignoring invalid width in environment variable COLUMNS: {$width}
ls-invalid-ignore-pattern = Invalid pattern for ignore: {$pattern}
ls-invalid-hide-pattern = Invalid pattern for hide: {$pattern}
ls-total = total {$size}

120
src/uu/ls/locales/fr-FR.ftl Normal file
View file

@ -0,0 +1,120 @@
ls-about = Lister le contenu des répertoires.
Ignorer les fichiers et répertoires commençant par un '.' par défaut
ls-usage = ls [OPTION]... [FICHIER]...
ls-after-help = L'argument TIME_STYLE peut être full-iso, long-iso, iso, locale ou +FORMAT. FORMAT est interprété comme dans date. De plus, la variable d'environnement TIME_STYLE définit le style par défaut à utiliser.
# Messages d'erreur
ls-error-invalid-line-width = largeur de ligne invalide : {$width}
ls-error-general-io = erreur d'E/S générale : {$error}
ls-error-cannot-access-no-such-file = impossible d'accéder à '{$path}' : Aucun fichier ou répertoire de ce type
ls-error-cannot-access-operation-not-permitted = impossible d'accéder à '{$path}' : Opération non autorisée
ls-error-cannot-open-directory-permission-denied = impossible d'ouvrir le répertoire '{$path}' : Permission refusée
ls-error-cannot-open-file-permission-denied = impossible d'ouvrir le fichier '{$path}' : Permission refusée
ls-error-cannot-open-directory-bad-descriptor = impossible d'ouvrir le répertoire '{$path}' : Mauvais descripteur de fichier
ls-error-unknown-io-error = erreur d'E/S inconnue : '{$path}', '{$error}'
ls-error-invalid-block-size = argument --block-size invalide {$size}
ls-error-dired-and-zero-incompatible = --dired et --zero sont incompatibles
ls-error-not-listing-already-listed = {$path} : ne liste pas un répertoire déjà listé
ls-error-invalid-time-style = argument --time-style invalide {$style}
Les valeurs possibles sont : {$values}
Pour plus d'informations, essayez --help
# Messages d'aide
ls-help-print-help = Afficher les informations d'aide.
ls-help-set-display-format = Définir le format d'affichage.
ls-help-display-files-columns = Afficher les fichiers en colonnes.
ls-help-display-detailed-info = Afficher des informations détaillées.
ls-help-list-entries-rows = Lister les entrées en lignes au lieu de colonnes.
ls-help-assume-tab-stops = Supposer des arrêts de tabulation à chaque COLS au lieu de 8
ls-help-list-entries-commas = Lister les entrées séparées par des virgules.
ls-help-list-entries-nul = Lister les entrées séparées par des caractères NUL ASCII.
ls-help-generate-dired-output = générer une sortie conçue pour le mode dired (Directory Editor) d'Emacs
ls-help-hyperlink-filenames = créer des hyperliens pour les noms de fichiers QUAND
ls-help-list-one-file-per-line = Lister un fichier par ligne.
ls-help-long-format-no-group = Format long sans informations de groupe.
Identique à --format=long avec --no-group.
ls-help-long-no-owner = Format long sans informations de propriétaire.
ls-help-long-numeric-uid-gid = -l avec des UID et GID numériques.
ls-help-set-quoting-style = Définir le style de citation.
ls-help-literal-quoting-style = Utiliser le style de citation littéral. Équivalent à `--quoting-style=literal`
ls-help-escape-quoting-style = Utiliser le style de citation d'échappement. Équivalent à `--quoting-style=escape`
ls-help-c-quoting-style = Utiliser le style de citation C. Équivalent à `--quoting-style=c`
ls-help-replace-control-chars = Remplacer les caractères de contrôle par '?' s'ils ne sont pas échappés.
ls-help-show-control-chars = Afficher les caractères de contrôle 'tels quels' s'ils ne sont pas échappés.
ls-help-show-time-field = Afficher l'heure dans <champ> :
heure d'accès (-u) : atime, access, use ;
heure de changement (-t) : ctime, status.
heure de modification : mtime, modification.
heure de création : birth, creation ;
ls-help-time-change = Si le format de liste long (par ex., -l, -o) est utilisé, afficher
l'heure de changement de statut (le 'ctime' dans l'inode) au lieu de l'heure
de modification. Lors du tri explicite par heure (--sort=time ou -t) ou lors
de l'absence de format de liste long, trier selon l'heure de changement de statut.
ls-help-time-access = Si le format de liste long (par ex., -l, -o) est utilisé, afficher
l'heure d'accès au statut au lieu de l'heure de modification. Lors du tri
explicite par heure (--sort=time ou -t) ou lors de l'absence de format de
liste long, trier selon l'heure d'accès.
ls-help-hide-pattern = ne pas lister les entrées implicites correspondant au MOTIF shell (surchargé par -a ou -A)
ls-help-ignore-pattern = ne pas lister les entrées implicites correspondant au MOTIF shell
ls-help-ignore-backups = Ignorer les entrées qui se terminent par ~.
ls-help-sort-by-field = Trier par <champ> : name, none (-U), time (-t), size (-S), extension (-X) ou width
ls-help-sort-by-size = Trier par taille de fichier, le plus grand en premier.
ls-help-sort-by-time = Trier par heure de modification (le 'mtime' dans l'inode), le plus récent en premier.
ls-help-sort-by-version = Tri naturel des numéros (de version) dans les noms de fichiers.
ls-help-sort-by-extension = Trier alphabétiquement par extension d'entrée.
ls-help-sort-none = Ne pas trier ; lister les fichiers dans l'ordre où ils sont stockés dans le
répertoire. Ceci est particulièrement utile lors de l'affichage de très grands répertoires,
car ne pas trier peut être sensiblement plus rapide.
ls-help-dereference-all = Lors de l'affichage d'informations de fichier pour un lien symbolique, afficher les informations pour le
fichier référencé par le lien plutôt que le lien lui-même.
ls-help-dereference-dir-args = Ne pas suivre les liens symboliques sauf quand ils pointent vers des répertoires et sont
donnés comme arguments de ligne de commande.
ls-help-dereference-args = Ne pas suivre les liens symboliques sauf quand ils sont donnés comme arguments de ligne de commande.
ls-help-no-group = Ne pas afficher le groupe en format long.
ls-help-author = Afficher l'auteur en format long. Sur les plateformes supportées,
l'auteur correspond toujours au propriétaire du fichier.
ls-help-all-files = Ne pas ignorer les fichiers cachés (fichiers dont les noms commencent par '.').
ls-help-almost-all = Dans un répertoire, ne pas ignorer tous les noms de fichiers qui commencent par '.',
ignorer seulement '.' et '..'.
ls-help-directory = Lister seulement les noms des répertoires, plutôt que le contenu des répertoires.
Ceci ne suivra pas les liens symboliques à moins qu'une des options
`--dereference-command-line (-H)`, `--dereference (-L)`, ou
`--dereference-command-line-symlink-to-dir` soit spécifiée.
ls-help-human-readable = Afficher les tailles de fichiers lisibles par l'homme (par ex. 1K 234M 56G).
ls-help-kibibytes = par défaut aux blocs de 1024 octets pour l'utilisation du système de fichiers ; utilisé seulement avec -s et par
totaux de répertoire
ls-help-si = Afficher les tailles de fichiers lisibles par l'homme utilisant des puissances de 1000 au lieu de 1024.
ls-help-block-size = dimensionner les tailles par BLOCK_SIZE lors de l'affichage
ls-help-print-inode = afficher le numéro d'index de chaque fichier
ls-help-reverse-sort = Inverser quelle que soit la méthode de tri, par ex., lister les fichiers en ordre
alphabétique inverse, le plus jeune en premier, le plus petit en premier, ou autre.
ls-help-recursive = Lister le contenu de tous les répertoires récursivement.
ls-help-terminal-width = Supposer que le terminal a COLS colonnes de largeur.
ls-help-allocation-size = afficher la taille allouée de chaque fichier, en blocs
ls-help-color-output = Colorier la sortie basée sur le type de fichier.
ls-help-indicator-style = Ajouter un indicateur avec le style WORD aux noms d'entrée :
none (par défaut), slash (-p), file-type (--file-type), classify (-F)
ls-help-classify = Ajouter un caractère à chaque nom de fichier indiquant le type de fichier. Aussi, pour
les fichiers réguliers qui sont exécutables, ajouter '*'. Les indicateurs de type de fichier sont
'/' pour les répertoires, '@' pour les liens symboliques, '|' pour les FIFOs, '=' pour les sockets,
'>' pour les portes, et rien pour les fichiers réguliers. when peut être omis, ou un de :
none - Ne pas classifier. C'est la valeur par défaut.
auto - Classifier seulement si la sortie standard est un terminal.
always - Toujours classifier.
Spécifier --classify et aucun when est équivalent à --classify=always. Ceci ne
suivra pas les liens symboliques listés sur la ligne de commande à moins que les
options --dereference-command-line (-H), --dereference (-L), ou
--dereference-command-line-symlink-to-dir soient spécifiées.
ls-help-file-type = Identique à --classify, mais ne pas ajouter '*'
ls-help-slash-directories = Ajouter l'indicateur / aux répertoires.
ls-help-time-style = format de date/heure avec -l ; voir TIME_STYLE ci-dessous
ls-help-full-time = comme -l --time-style=full-iso
ls-help-context = afficher tout contexte de sécurité de chaque fichier
ls-help-group-directories-first = grouper les répertoires avant les fichiers ; peut être augmenté avec
une option --sort, mais toute utilisation de --sort=none (-U) désactive le groupement
ls-invalid-quoting-style = {$program} : Ignorer la valeur invalide de la variable d'environnement QUOTING_STYLE : '{$style}'
ls-invalid-columns-width = ignorer la largeur invalide dans la variable d'environnement COLUMNS : {$width}
ls-invalid-ignore-pattern = Motif invalide pour ignore : {$pattern}
ls-invalid-hide-pattern = Motif invalide pour hide : {$pattern}
ls-total = total {$size}

View file

@ -6,6 +6,8 @@
// spell-checker:ignore (ToDO) somegroup nlink tabsize dired subdired dtype colorterm stringly nohash strtime
use std::iter;
#[cfg(unix)]
use std::os::unix::fs::{FileTypeExt, MetadataExt};
#[cfg(windows)]
use std::os::windows::fs::MetadataExt;
use std::{cell::LazyCell, cell::OnceCell, num::IntErrorKind};
@ -18,12 +20,10 @@ use std::{
path::{Path, PathBuf},
time::{Duration, SystemTime, UNIX_EPOCH},
};
#[cfg(unix)]
use std::{
collections::HashMap,
os::unix::fs::{FileTypeExt, MetadataExt},
collections::{HashMap, HashSet},
io::IsTerminal,
};
use std::{collections::HashSet, io::IsTerminal};
use ansi_width::ansi_width;
use clap::{
@ -60,8 +60,8 @@ use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
))]
use uucore::libc::{dev_t, major, minor};
use uucore::line_ending::LineEnding;
use uucore::locale::get_message;
use uucore::quoting_style::{self, QuotingStyle, escape_name};
use uucore::locale::{get_message, get_message_with_args};
use uucore::quoting_style::{QuotingStyle, locale_aware_escape_dir_name, locale_aware_escape_name};
use uucore::{
display::Quotable,
error::{UError, UResult, set_exit_code},
@ -79,11 +79,6 @@ use dired::{DiredOutput, is_dired_arg_present};
mod colors;
use colors::{StyleManager, color_name};
#[cfg(not(feature = "selinux"))]
static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)";
#[cfg(feature = "selinux")]
static CONTEXT_HELP_TEXT: &str = "print any security context of each file";
pub mod options {
pub mod format {
pub static ONE_LINE: &str = "1";
@ -177,39 +172,39 @@ const DEFAULT_FILE_SIZE_BLOCK_SIZE: u64 = 1;
#[derive(Error, Debug)]
enum LsError {
#[error("invalid line width: '{0}'")]
#[error("{}", get_message_with_args("ls-error-invalid-line-width", HashMap::from([("width".to_string(), format!("'{}'", _0))])))]
InvalidLineWidth(String),
#[error("general io error: {0}")]
#[error("{}", get_message_with_args("ls-error-general-io", HashMap::from([("error".to_string(), _0.to_string())])))]
IOError(#[from] std::io::Error),
#[error("{}", match .1.kind() {
ErrorKind::NotFound => format!("cannot access '{}': No such file or directory", .0.to_string_lossy()),
ErrorKind::NotFound => get_message_with_args("ls-error-cannot-access-no-such-file", HashMap::from([("path".to_string(), .0.to_string_lossy().to_string())])),
ErrorKind::PermissionDenied => match .1.raw_os_error().unwrap_or(1) {
1 => format!("cannot access '{}': Operation not permitted", .0.to_string_lossy()),
1 => get_message_with_args("ls-error-cannot-access-operation-not-permitted", HashMap::from([("path".to_string(), .0.to_string_lossy().to_string())])),
_ => if .0.is_dir() {
format!("cannot open directory '{}': Permission denied", .0.to_string_lossy())
get_message_with_args("ls-error-cannot-open-directory-permission-denied", HashMap::from([("path".to_string(), .0.to_string_lossy().to_string())]))
} else {
format!("cannot open file '{}': Permission denied", .0.to_string_lossy())
get_message_with_args("ls-error-cannot-open-file-permission-denied", HashMap::from([("path".to_string(), .0.to_string_lossy().to_string())]))
},
},
_ => match .1.raw_os_error().unwrap_or(1) {
9 => format!("cannot open directory '{}': Bad file descriptor", .0.to_string_lossy()),
_ => format!("unknown io error: '{:?}', '{:?}'", .0.to_string_lossy(), .1),
9 => get_message_with_args("ls-error-cannot-open-directory-bad-descriptor", HashMap::from([("path".to_string(), .0.to_string_lossy().to_string())])),
_ => get_message_with_args("ls-error-unknown-io-error", HashMap::from([("path".to_string(), .0.to_string_lossy().to_string()), ("error".to_string(), format!("{:?}", .1))])),
},
})]
IOErrorContext(PathBuf, std::io::Error, bool),
#[error("invalid --block-size argument '{0}'")]
#[error("{}", get_message_with_args("ls-error-invalid-block-size", HashMap::from([("size".to_string(), format!("'{}'", _0))])))]
BlockSizeParseError(String),
#[error("--dired and --zero are incompatible")]
#[error("{}", get_message("ls-error-dired-and-zero-incompatible"))]
DiredAndZeroAreIncompatible,
#[error("{}: not listing already-listed directory", .0.to_string_lossy())]
#[error("{}", get_message_with_args("ls-error-not-listing-already-listed", HashMap::from([("path".to_string(), .0.to_string_lossy().to_string())])))]
AlreadyListedError(PathBuf),
#[error("invalid --time-style argument {}\nPossible values are: {:?}\n\nFor more information try --help", .0.quote(), .1)]
#[error("{}", get_message_with_args("ls-error-invalid-time-style", HashMap::from([("style".to_string(), .0.quote().to_string()), ("values".to_string(), format!("{:?}", .1))])))]
TimeStyleParseError(String, Vec<String>),
}
@ -601,34 +596,15 @@ fn extract_hyperlink(options: &clap::ArgMatches) -> bool {
fn match_quoting_style_name(style: &str, show_control: bool) -> Option<QuotingStyle> {
match style {
"literal" => Some(QuotingStyle::Literal { show_control }),
"shell" => Some(QuotingStyle::Shell {
escape: false,
always_quote: false,
show_control,
}),
"shell-always" => Some(QuotingStyle::Shell {
escape: false,
always_quote: true,
show_control,
}),
"shell-escape" => Some(QuotingStyle::Shell {
escape: true,
always_quote: false,
show_control,
}),
"shell-escape-always" => Some(QuotingStyle::Shell {
escape: true,
always_quote: true,
show_control,
}),
"c" => Some(QuotingStyle::C {
quotes: quoting_style::Quotes::Double,
}),
"escape" => Some(QuotingStyle::C {
quotes: quoting_style::Quotes::None,
}),
"shell" => Some(QuotingStyle::SHELL),
"shell-always" => Some(QuotingStyle::SHELL_QUOTE),
"shell-escape" => Some(QuotingStyle::SHELL_ESCAPE),
"shell-escape-always" => Some(QuotingStyle::SHELL_ESCAPE_QUOTE),
"c" => Some(QuotingStyle::C_DOUBLE),
"escape" => Some(QuotingStyle::C_NO_QUOTES),
_ => None,
}
.map(|qs| qs.show_control(show_control))
}
/// Extracts the quoting style to use based on the options provided.
@ -654,13 +630,9 @@ fn extract_quoting_style(options: &clap::ArgMatches, show_control: bool) -> Quot
} else if options.get_flag(options::quoting::LITERAL) {
QuotingStyle::Literal { show_control }
} else if options.get_flag(options::quoting::ESCAPE) {
QuotingStyle::C {
quotes: quoting_style::Quotes::None,
}
QuotingStyle::C_NO_QUOTES
} else if options.get_flag(options::quoting::C) {
QuotingStyle::C {
quotes: quoting_style::Quotes::Double,
}
QuotingStyle::C_DOUBLE
} else if options.get_flag(options::DIRED) {
QuotingStyle::Literal { show_control }
} else {
@ -669,8 +641,17 @@ fn extract_quoting_style(options: &clap::ArgMatches, show_control: bool) -> Quot
match match_quoting_style_name(style.as_str(), show_control) {
Some(qs) => return qs,
None => eprintln!(
"{}: Ignoring invalid value of environment variable QUOTING_STYLE: '{style}'",
std::env::args().next().unwrap_or_else(|| "ls".to_string()),
"{}",
get_message_with_args(
"ls-invalid-quoting-style",
HashMap::from([
(
"program".to_string(),
std::env::args().next().unwrap_or_else(|| "ls".to_string())
),
("style".to_string(), style.clone())
])
)
),
}
}
@ -678,11 +659,7 @@ fn extract_quoting_style(options: &clap::ArgMatches, show_control: bool) -> Quot
// By default, `ls` uses Shell escape quoting style when writing to a terminal file
// descriptor and Literal otherwise.
if stdout().is_terminal() {
QuotingStyle::Shell {
escape: true,
always_quote: false,
show_control,
}
QuotingStyle::SHELL_ESCAPE.show_control(show_control)
} else {
QuotingStyle::Literal { show_control }
}
@ -747,8 +724,11 @@ fn parse_width(width_match: Option<&String>) -> Result<u16, LsError> {
Some(columns) => columns,
None => {
show_error!(
"ignoring invalid width in environment variable COLUMNS: {}",
columns.quote()
"{}",
get_message_with_args(
"ls-invalid-columns-width",
HashMap::from([("width".to_string(), columns.quote().to_string())])
)
);
DEFAULT_TERM_WIDTH
}
@ -966,7 +946,13 @@ impl Config {
Ok(p) => {
ignore_patterns.push(p);
}
Err(_) => show_warning!("Invalid pattern for ignore: {}", pattern.quote()),
Err(_) => show_warning!(
"{}",
get_message_with_args(
"ls-invalid-ignore-pattern",
HashMap::from([("pattern".to_string(), pattern.quote().to_string())])
)
),
}
}
@ -980,7 +966,13 @@ impl Config {
Ok(p) => {
ignore_patterns.push(p);
}
Err(_) => show_warning!("Invalid pattern for hide: {}", pattern.quote()),
Err(_) => show_warning!(
"{}",
get_message_with_args(
"ls-invalid-hide-pattern",
HashMap::from([("pattern".to_string(), pattern.quote().to_string())])
)
),
}
}
}
@ -1184,14 +1176,14 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::HELP)
.long(options::HELP)
.help("Print help information.")
.help(get_message("ls-help-print-help"))
.action(ArgAction::Help),
)
// Format arguments
.arg(
Arg::new(options::FORMAT)
.long(options::FORMAT)
.help("Set the display format.")
.help(get_message("ls-help-set-display-format"))
.value_parser(ShortcutValueParser::new([
"long",
"verbose",
@ -1216,7 +1208,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::format::COLUMNS)
.short('C')
.help("Display the files in columns.")
.help(get_message("ls-help-display-files-columns"))
.overrides_with_all([
options::FORMAT,
options::format::COLUMNS,
@ -1230,7 +1222,7 @@ pub fn uu_app() -> Command {
Arg::new(options::format::LONG)
.short('l')
.long(options::format::LONG)
.help("Display detailed information.")
.help(get_message("ls-help-display-detailed-info"))
.overrides_with_all([
options::FORMAT,
options::format::COLUMNS,
@ -1243,7 +1235,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::format::ACROSS)
.short('x')
.help("List entries in rows instead of in columns.")
.help(get_message("ls-help-list-entries-rows"))
.overrides_with_all([
options::FORMAT,
options::format::COLUMNS,
@ -1259,12 +1251,12 @@ pub fn uu_app() -> Command {
.long(options::format::TAB_SIZE)
.env("TABSIZE")
.value_name("COLS")
.help("Assume tab stops at each COLS instead of 8"),
.help(get_message("ls-help-assume-tab-stops")),
)
.arg(
Arg::new(options::format::COMMAS)
.short('m')
.help("List entries separated by commas.")
.help(get_message("ls-help-list-entries-commas"))
.overrides_with_all([
options::FORMAT,
options::format::COLUMNS,
@ -1278,21 +1270,21 @@ pub fn uu_app() -> Command {
Arg::new(options::ZERO)
.long(options::ZERO)
.overrides_with(options::ZERO)
.help("List entries separated by ASCII NUL characters.")
.help(get_message("ls-help-list-entries-nul"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::DIRED)
.long(options::DIRED)
.short('D')
.help("generate output designed for Emacs' dired (Directory Editor) mode")
.help(get_message("ls-help-generate-dired-output"))
.action(ArgAction::SetTrue)
.overrides_with(options::HYPERLINK),
)
.arg(
Arg::new(options::HYPERLINK)
.long(options::HYPERLINK)
.help("hyperlink file names WHEN")
.help(get_message("ls-help-hyperlink-filenames"))
.value_parser(ShortcutValueParser::new([
PossibleValue::new("always").alias("yes").alias("force"),
PossibleValue::new("auto").alias("tty").alias("if-tty"),
@ -1314,36 +1306,33 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::format::ONE_LINE)
.short('1')
.help("List one file per line.")
.help(get_message("ls-help-list-one-file-per-line"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::format::LONG_NO_GROUP)
.short('o')
.help(
"Long format without group information. \
Identical to --format=long with --no-group.",
)
.help(get_message("ls-help-long-format-no-group"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::format::LONG_NO_OWNER)
.short('g')
.help("Long format without owner information.")
.help(get_message("ls-help-long-no-owner"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::format::LONG_NUMERIC_UID_GID)
.short('n')
.long(options::format::LONG_NUMERIC_UID_GID)
.help("-l with numeric UIDs and GIDs.")
.help(get_message("ls-help-long-numeric-uid-gid"))
.action(ArgAction::SetTrue),
)
// Quoting style
.arg(
Arg::new(options::QUOTING_STYLE)
.long(options::QUOTING_STYLE)
.help("Set quoting style.")
.help(get_message("ls-help-set-quoting-style"))
.value_parser(ShortcutValueParser::new([
PossibleValue::new("literal"),
PossibleValue::new("shell"),
@ -1365,7 +1354,7 @@ pub fn uu_app() -> Command {
.short('N')
.long(options::quoting::LITERAL)
.alias("l")
.help("Use literal quoting style. Equivalent to `--quoting-style=literal`")
.help(get_message("ls-help-literal-quoting-style"))
.overrides_with_all([
options::QUOTING_STYLE,
options::quoting::LITERAL,
@ -1378,7 +1367,7 @@ pub fn uu_app() -> Command {
Arg::new(options::quoting::ESCAPE)
.short('b')
.long(options::quoting::ESCAPE)
.help("Use escape quoting style. Equivalent to `--quoting-style=escape`")
.help(get_message("ls-help-escape-quoting-style"))
.overrides_with_all([
options::QUOTING_STYLE,
options::quoting::LITERAL,
@ -1391,7 +1380,7 @@ pub fn uu_app() -> Command {
Arg::new(options::quoting::C)
.short('Q')
.long(options::quoting::C)
.help("Use C quoting style. Equivalent to `--quoting-style=c`")
.help(get_message("ls-help-c-quoting-style"))
.overrides_with_all([
options::QUOTING_STYLE,
options::quoting::LITERAL,
@ -1405,14 +1394,14 @@ pub fn uu_app() -> Command {
Arg::new(options::HIDE_CONTROL_CHARS)
.short('q')
.long(options::HIDE_CONTROL_CHARS)
.help("Replace control characters with '?' if they are not escaped.")
.help(get_message("ls-help-replace-control-chars"))
.overrides_with_all([options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS])
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::SHOW_CONTROL_CHARS)
.long(options::SHOW_CONTROL_CHARS)
.help("Show control characters 'as is' if they are not escaped.")
.help(get_message("ls-help-show-control-chars"))
.overrides_with_all([options::HIDE_CONTROL_CHARS, options::SHOW_CONTROL_CHARS])
.action(ArgAction::SetTrue),
)
@ -1420,13 +1409,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::TIME)
.long(options::TIME)
.help(
"Show time in <field>:\n\
\taccess time (-u): atime, access, use;\n\
\tchange time (-t): ctime, status.\n\
\tmodification time: mtime, modification.\n\
\tbirth time: birth, creation;",
)
.help(get_message("ls-help-show-time-field"))
.value_name("field")
.value_parser(ShortcutValueParser::new([
PossibleValue::new("atime").alias("access").alias("use"),
@ -1441,24 +1424,14 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::time::CHANGE)
.short('c')
.help(
"If the long listing format (e.g., -l, -o) is being used, print the \
status change time (the 'ctime' in the inode) instead of the modification \
time. When explicitly sorting by time (--sort=time or -t) or when not \
using a long listing format, sort according to the status change time.",
)
.help(get_message("ls-help-time-change"))
.overrides_with_all([options::TIME, options::time::ACCESS, options::time::CHANGE])
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::time::ACCESS)
.short('u')
.help(
"If the long listing format (e.g., -l, -o) is being used, print the \
status access time instead of the modification time. When explicitly \
sorting by time (--sort=time or -t) or when not using a long listing \
format, sort according to the access time.",
)
.help(get_message("ls-help-time-access"))
.overrides_with_all([options::TIME, options::time::ACCESS, options::time::CHANGE])
.action(ArgAction::SetTrue),
)
@ -1468,9 +1441,7 @@ pub fn uu_app() -> Command {
.long(options::HIDE)
.action(ArgAction::Append)
.value_name("PATTERN")
.help(
"do not list implied entries matching shell PATTERN (overridden by -a or -A)",
),
.help(get_message("ls-help-hide-pattern")),
)
.arg(
Arg::new(options::IGNORE)
@ -1478,22 +1449,30 @@ pub fn uu_app() -> Command {
.long(options::IGNORE)
.action(ArgAction::Append)
.value_name("PATTERN")
.help("do not list implied entries matching shell PATTERN"),
.help(get_message("ls-help-ignore-pattern")),
)
.arg(
Arg::new(options::IGNORE_BACKUPS)
.short('B')
.long(options::IGNORE_BACKUPS)
.help("Ignore entries which end with ~.")
.help(get_message("ls-help-ignore-backups"))
.action(ArgAction::SetTrue),
)
// Sort arguments
.arg(
Arg::new(options::SORT)
.long(options::SORT)
.help("Sort by <field>: name, none (-U), time (-t), size (-S), extension (-X) or width")
.help(get_message("ls-help-sort-by-field"))
.value_name("field")
.value_parser(ShortcutValueParser::new(["name", "none", "time", "size", "version", "extension", "width"]))
.value_parser(ShortcutValueParser::new([
"name",
"none",
"time",
"size",
"version",
"extension",
"width",
]))
.require_equals(true)
.overrides_with_all([
options::SORT,
@ -1507,7 +1486,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::sort::SIZE)
.short('S')
.help("Sort by file size, largest first.")
.help(get_message("ls-help-sort-by-size"))
.overrides_with_all([
options::SORT,
options::sort::SIZE,
@ -1521,7 +1500,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::sort::TIME)
.short('t')
.help("Sort by modification time (the 'mtime' in the inode), newest first.")
.help(get_message("ls-help-sort-by-time"))
.overrides_with_all([
options::SORT,
options::sort::SIZE,
@ -1535,7 +1514,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::sort::VERSION)
.short('v')
.help("Natural sort of (version) numbers in the filenames.")
.help(get_message("ls-help-sort-by-version"))
.overrides_with_all([
options::SORT,
options::sort::SIZE,
@ -1549,7 +1528,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::sort::EXTENSION)
.short('X')
.help("Sort alphabetically by entry extension.")
.help(get_message("ls-help-sort-by-extension"))
.overrides_with_all([
options::SORT,
options::sort::SIZE,
@ -1563,11 +1542,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::sort::NONE)
.short('U')
.help(
"Do not sort; list the files in whatever order they are stored in the \
directory. This is especially useful when listing very large directories, \
since not doing any sorting can be noticeably faster.",
)
.help(get_message("ls-help-sort-none"))
.overrides_with_all([
options::SORT,
options::sort::SIZE,
@ -1583,10 +1558,7 @@ pub fn uu_app() -> Command {
Arg::new(options::dereference::ALL)
.short('L')
.long(options::dereference::ALL)
.help(
"When showing file information for a symbolic link, show information for the \
file the link references rather than the link itself.",
)
.help(get_message("ls-help-dereference-all"))
.overrides_with_all([
options::dereference::ALL,
options::dereference::DIR_ARGS,
@ -1597,10 +1569,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::dereference::DIR_ARGS)
.long(options::dereference::DIR_ARGS)
.help(
"Do not follow symlinks except when they link to directories and are \
given as command line arguments.",
)
.help(get_message("ls-help-dereference-dir-args"))
.overrides_with_all([
options::dereference::ALL,
options::dereference::DIR_ARGS,
@ -1612,7 +1581,7 @@ pub fn uu_app() -> Command {
Arg::new(options::dereference::ARGS)
.short('H')
.long(options::dereference::ARGS)
.help("Do not follow symlinks except when given as command line arguments.")
.help(get_message("ls-help-dereference-args"))
.overrides_with_all([
options::dereference::ALL,
options::dereference::DIR_ARGS,
@ -1625,13 +1594,15 @@ pub fn uu_app() -> Command {
Arg::new(options::NO_GROUP)
.long(options::NO_GROUP)
.short('G')
.help("Do not show group in long format.")
.help(get_message("ls-help-no-group"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::AUTHOR)
.long(options::AUTHOR)
.help(get_message("ls-help-author"))
.action(ArgAction::SetTrue),
)
.arg(Arg::new(options::AUTHOR).long(options::AUTHOR).help(
"Show author in long format. On the supported platforms, \
the author always matches the file owner.",
).action(ArgAction::SetTrue))
// Other Flags
.arg(
Arg::new(options::files::ALL)
@ -1639,7 +1610,7 @@ pub fn uu_app() -> Command {
.long(options::files::ALL)
// Overrides -A (as the order matters)
.overrides_with_all([options::files::ALL, options::files::ALMOST_ALL])
.help("Do not ignore hidden files (files with names that start with '.').")
.help(get_message("ls-help-all-files"))
.action(ArgAction::SetTrue),
)
.arg(
@ -1648,29 +1619,21 @@ pub fn uu_app() -> Command {
.long(options::files::ALMOST_ALL)
// Overrides -a (as the order matters)
.overrides_with_all([options::files::ALL, options::files::ALMOST_ALL])
.help(
"In a directory, do not ignore all file names that start with '.', \
only ignore '.' and '..'.",
)
.help(get_message("ls-help-almost-all"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::DIRECTORY)
.short('d')
.long(options::DIRECTORY)
.help(
"Only list the names of directories, rather than listing directory contents. \
This will not follow symbolic links unless one of `--dereference-command-line \
(-H)`, `--dereference (-L)`, or `--dereference-command-line-symlink-to-dir` is \
specified.",
)
.help(get_message("ls-help-directory"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::size::HUMAN_READABLE)
.short('h')
.long(options::size::HUMAN_READABLE)
.help("Print human readable file sizes (e.g. 1K 234M 56G).")
.help(get_message("ls-help-human-readable"))
.overrides_with_all([options::size::BLOCK_SIZE, options::size::SI])
.action(ArgAction::SetTrue),
)
@ -1678,16 +1641,13 @@ pub fn uu_app() -> Command {
Arg::new(options::size::KIBIBYTES)
.short('k')
.long(options::size::KIBIBYTES)
.help(
"default to 1024-byte blocks for file system usage; used only with -s and per \
directory totals",
)
.help(get_message("ls-help-kibibytes"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::size::SI)
.long(options::size::SI)
.help("Print human readable file sizes using powers of 1000 instead of 1024.")
.help(get_message("ls-help-si"))
.overrides_with_all([options::size::BLOCK_SIZE, options::size::HUMAN_READABLE])
.action(ArgAction::SetTrue),
)
@ -1696,51 +1656,48 @@ pub fn uu_app() -> Command {
.long(options::size::BLOCK_SIZE)
.require_equals(true)
.value_name("BLOCK_SIZE")
.help("scale sizes by BLOCK_SIZE when printing them")
.help(get_message("ls-help-block-size"))
.overrides_with_all([options::size::SI, options::size::HUMAN_READABLE]),
)
.arg(
Arg::new(options::INODE)
.short('i')
.long(options::INODE)
.help("print the index number of each file")
.help(get_message("ls-help-print-inode"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::REVERSE)
.short('r')
.long(options::REVERSE)
.help(
"Reverse whatever the sorting method is e.g., list files in reverse \
alphabetical order, youngest first, smallest first, or whatever.",
)
.help(get_message("ls-help-reverse-sort"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::RECURSIVE)
.short('R')
.long(options::RECURSIVE)
.help("List the contents of all directories recursively.")
.help(get_message("ls-help-recursive"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::WIDTH)
.long(options::WIDTH)
.short('w')
.help("Assume that the terminal is COLS columns wide.")
.help(get_message("ls-help-terminal-width"))
.value_name("COLS"),
)
.arg(
Arg::new(options::size::ALLOCATION_SIZE)
.short('s')
.long(options::size::ALLOCATION_SIZE)
.help("print the allocated size of each file, in blocks")
.help(get_message("ls-help-allocation-size"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::COLOR)
.long(options::COLOR)
.help("Color output based on file type.")
.help(get_message("ls-help-color-output"))
.value_parser(ShortcutValueParser::new([
PossibleValue::new("always").alias("yes").alias("force"),
PossibleValue::new("auto").alias("tty").alias("if-tty"),
@ -1752,11 +1709,13 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::INDICATOR_STYLE)
.long(options::INDICATOR_STYLE)
.help(
"Append indicator with style WORD to entry names: \
none (default), slash (-p), file-type (--file-type), classify (-F)",
)
.value_parser(ShortcutValueParser::new(["none", "slash", "file-type", "classify"]))
.help(get_message("ls-help-indicator-style"))
.value_parser(ShortcutValueParser::new([
"none",
"slash",
"file-type",
"classify",
]))
.overrides_with_all([
options::indicator_style::FILE_TYPE,
options::indicator_style::SLASH,
@ -1773,19 +1732,7 @@ pub fn uu_app() -> Command {
Arg::new(options::indicator_style::CLASSIFY)
.short('F')
.long(options::indicator_style::CLASSIFY)
.help(
"Append a character to each file name indicating the file type. Also, for \
regular files that are executable, append '*'. The file type indicators are \
'/' for directories, '@' for symbolic links, '|' for FIFOs, '=' for sockets, \
'>' for doors, and nothing for regular files. when may be omitted, or one of:\n\
\tnone - Do not classify. This is the default.\n\
\tauto - Only classify if standard output is a terminal.\n\
\talways - Always classify.\n\
Specifying --classify and no when is equivalent to --classify=always. This will \
not follow symbolic links listed on the command line unless the \
--dereference-command-line (-H), --dereference (-L), or \
--dereference-command-line-symlink-to-dir options are specified.",
)
.help(get_message("ls-help-classify"))
.value_name("when")
.value_parser(ShortcutValueParser::new([
PossibleValue::new("always").alias("yes").alias("force"),
@ -1805,7 +1752,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::indicator_style::FILE_TYPE)
.long(options::indicator_style::FILE_TYPE)
.help("Same as --classify, but do not append '*'")
.help(get_message("ls-help-file-type"))
.overrides_with_all([
options::indicator_style::FILE_TYPE,
options::indicator_style::SLASH,
@ -1817,7 +1764,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::indicator_style::SLASH)
.short('p')
.help("Append / indicator to directories.")
.help(get_message("ls-help-slash-directories"))
.overrides_with_all([
options::indicator_style::FILE_TYPE,
options::indicator_style::SLASH,
@ -1830,7 +1777,7 @@ pub fn uu_app() -> Command {
//This still needs support for posix-*
Arg::new(options::TIME_STYLE)
.long(options::TIME_STYLE)
.help("time/date format with -l; see TIME_STYLE below")
.help(get_message("ls-help-time-style"))
.value_name("TIME_STYLE")
.env("TIME_STYLE")
.value_parser(NonEmptyStringValueParser::new())
@ -1840,23 +1787,20 @@ pub fn uu_app() -> Command {
Arg::new(options::FULL_TIME)
.long(options::FULL_TIME)
.overrides_with(options::FULL_TIME)
.help("like -l --time-style=full-iso")
.help(get_message("ls-help-full-time"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::CONTEXT)
.short('Z')
.long(options::CONTEXT)
.help(CONTEXT_HELP_TEXT)
.help(get_message("ls-help-context"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::GROUP_DIRECTORIES_FIRST)
.long(options::GROUP_DIRECTORIES_FIRST)
.help(
"group directories before files; can be augmented with \
a --sort option, but any use of --sort=none (-U) disables grouping",
)
.help(get_message("ls-help-group-directories-first"))
.action(ArgAction::SetTrue),
)
// Positional arguments
@ -1956,11 +1900,7 @@ impl PathData {
None => OnceCell::new(),
};
let security_context = if config.context {
get_security_context(config, &p_buf, must_dereference)
} else {
String::new()
};
let security_context = get_security_context(config, &p_buf, must_dereference);
Self {
md: OnceCell::new(),
@ -2037,7 +1977,7 @@ fn show_dir_name(
config: &Config,
) -> std::io::Result<()> {
let escaped_name =
quoting_style::escape_dir_name(path_data.p_buf.as_os_str(), &config.quoting_style);
locale_aware_escape_dir_name(path_data.p_buf.as_os_str(), config.quoting_style);
let name = if config.hyperlink && !config.dired {
create_hyperlink(&escaped_name, path_data)
@ -2482,8 +2422,11 @@ fn return_total(
dired::indent(out)?;
}
Ok(format!(
"total {}{}",
display_size(total_size, config),
"{}{}",
get_message_with_args(
"ls-total",
HashMap::from([("size".to_string(), display_size(total_size, config))])
),
config.line_ending
))
}
@ -2535,7 +2478,7 @@ fn display_items(
// option, print the security context to the left of the size column.
let quoted = items.iter().any(|item| {
let name = escape_name(&item.display_name, &config.quoting_style);
let name = locale_aware_escape_name(&item.display_name, config.quoting_style);
os_str_starts_with(&name, b"'")
});
@ -3178,7 +3121,7 @@ fn classify_file(path: &PathData, out: &mut BufWriter<Stdout>) -> Option<char> {
/// Takes a [`PathData`] struct and returns a cell with a name ready for displaying.
///
/// This function relies on the following parameters in the provided `&Config`:
/// * `config.quoting_style` to decide how we will escape `name` using [`escape_name`].
/// * `config.quoting_style` to decide how we will escape `name` using [`locale_aware_escape_name`].
/// * `config.inode` decides whether to display inode numbers beside names using [`get_inode`].
/// * `config.color` decides whether it's going to color `name` using [`color_name`].
/// * `config.indicator_style` to append specific characters to `name` using [`classify_file`].
@ -3199,7 +3142,7 @@ fn display_item_name(
current_column: LazyCell<usize, Box<dyn FnOnce() -> usize + '_>>,
) -> OsString {
// This is our return value. We start by `&path.display_name` and modify it along the way.
let mut name = escape_name(&path.display_name, &config.quoting_style);
let mut name = locale_aware_escape_name(&path.display_name, config.quoting_style);
let is_wrap =
|namelen: usize| config.width != 0 && *current_column + namelen > config.width.into();
@ -3291,7 +3234,7 @@ fn display_item_name(
name.push(path.p_buf.read_link().unwrap());
} else {
name.push(color_name(
escape_name(target.as_os_str(), &config.quoting_style),
locale_aware_escape_name(target.as_os_str(), config.quoting_style),
path,
style_manager,
&mut state.out,
@ -3302,7 +3245,10 @@ fn display_item_name(
} else {
// If no coloring is required, we just use target as is.
// Apply the right quoting
name.push(escape_name(target.as_os_str(), &config.quoting_style));
name.push(locale_aware_escape_name(
target.as_os_str(),
config.quoting_style,
));
}
}
Err(err) => {
@ -3389,7 +3335,10 @@ fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -
Err(err) => {
// The Path couldn't be dereferenced, so return early and set exit code 1
// to indicate a minor error
show!(LsError::IOErrorContext(p_buf.to_path_buf(), err, false));
// Only show error when context display is requested to avoid duplicate messages
if config.context {
show!(LsError::IOErrorContext(p_buf.to_path_buf(), err, false));
}
return substitute_string;
}
Ok(_md) => (),

View file

@ -1,2 +1,30 @@
more-about = Display the contents of a text file
more-usage = more [OPTIONS] FILE...
# Error messages
more-error-is-directory = {$path} is a directory.
more-error-cannot-open-no-such-file = cannot open {$path}: No such file or directory
more-error-cannot-open-io-error = cannot open {$path}: {$error}
more-error-bad-usage = bad usage
more-error-cannot-seek-to-line = Cannot seek to line number {$line}
more-error-pattern-not-found = Pattern not found
more-error-unknown-key = Unknown key: '{$key}'. Press 'h' for instructions. (unimplemented)
# Help messages
more-help-silent = Display help instead of ringing bell when an illegal key is pressed
more-help-logical = Do not pause after any line containing a ^L (form feed)
more-help-exit-on-eof = Exit on End-Of-File
more-help-no-pause = Count logical lines, rather than screen lines
more-help-print-over = Do not scroll, clear screen and display text
more-help-clean-print = Do not scroll, display text and clean line ends
more-help-squeeze = Squeeze multiple blank lines into one
more-help-plain = Suppress underlining
more-help-lines = The number of lines per screen full
more-help-number = Same as --lines option argument
more-help-from-line = Start displaying each file at line number
more-help-pattern = The string to be searched in each file before starting to display it
more-help-files = Path to the files to be read
# Other messages
more-help-message = [Press space to continue, 'q' to quit.]
more-press-return = press RETURN

View file

@ -0,0 +1,30 @@
more-about = Afficher le contenu d'un fichier texte
more-usage = more [OPTIONS] FICHIER...
# Messages d'erreur
more-error-is-directory = {$path} est un répertoire.
more-error-cannot-open-no-such-file = impossible d'ouvrir {$path} : Aucun fichier ou répertoire de ce nom
more-error-cannot-open-io-error = impossible d'ouvrir {$path} : {$error}
more-error-bad-usage = mauvaise utilisation
more-error-cannot-seek-to-line = Impossible d'atteindre la ligne numéro {$line}
more-error-pattern-not-found = Motif non trouvé
more-error-unknown-key = Touche inconnue : '{$key}'. Appuyez sur 'h' pour les instructions. (non implémenté)
# Messages d'aide
more-help-silent = Afficher l'aide au lieu de sonner la cloche lorsqu'une touche illégale est pressée
more-help-logical = Ne pas faire de pause après une ligne contenant un ^L (saut de page)
more-help-exit-on-eof = Quitter à la fin du fichier
more-help-no-pause = Compter les lignes logiques plutôt que les lignes d'écran
more-help-print-over = Ne pas défiler, effacer l'écran et afficher le texte
more-help-clean-print = Ne pas défiler, afficher le texte et nettoyer les fins de ligne
more-help-squeeze = Compresser plusieurs lignes vides en une seule
more-help-plain = Supprimer le soulignement
more-help-lines = Le nombre de lignes par écran complet
more-help-number = Identique à l'argument de l'option --lines
more-help-from-line = Commencer l'affichage de chaque fichier au numéro de ligne
more-help-pattern = La chaîne à rechercher dans chaque fichier avant de commencer à l'afficher
more-help-files = Chemin vers les fichiers à lire
# Autres messages
more-help-message = [Appuyez sur espace pour continuer, 'q' pour quitter.]
more-press-return = appuyez sur ENTRÉE

View file

@ -4,6 +4,7 @@
// file that was distributed with this source code.
use std::{
collections::HashMap,
fs::File,
io::{BufRead, BufReader, Stdin, Stdout, Write, stdin, stdout},
panic::set_hook,
@ -26,13 +27,66 @@ use uucore::error::{UResult, USimpleError, UUsageError};
use uucore::format_usage;
use uucore::{display::Quotable, show};
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
#[derive(Debug)]
enum MoreError {
IsDirectory(String),
CannotOpenNoSuchFile(String),
CannotOpenIOError(String, std::io::ErrorKind),
BadUsage,
}
impl std::fmt::Display for MoreError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MoreError::IsDirectory(path) => {
write!(
f,
"{}",
get_message_with_args(
"more-error-is-directory",
HashMap::from([("path".to_string(), path.quote().to_string())])
)
)
}
MoreError::CannotOpenNoSuchFile(path) => {
write!(
f,
"{}",
get_message_with_args(
"more-error-cannot-open-no-such-file",
HashMap::from([("path".to_string(), path.quote().to_string())])
)
)
}
MoreError::CannotOpenIOError(path, error) => {
write!(
f,
"{}",
get_message_with_args(
"more-error-cannot-open-io-error",
HashMap::from([
("path".to_string(), path.quote().to_string()),
("error".to_string(), error.to_string())
])
)
)
}
MoreError::BadUsage => {
write!(f, "{}", get_message("more-error-bad-usage"))
}
}
}
}
impl std::error::Error for MoreError {}
const BELL: char = '\x07'; // Printing this character will ring the bell
// The prompt to be displayed at the top of the screen when viewing multiple files,
// with the file name in the middle
const MULTI_FILE_TOP_PROMPT: &str = "\r::::::::::::::\n\r{}\n\r::::::::::::::\n";
const HELP_MESSAGE: &str = "[Press space to continue, 'q' to quit.]";
pub mod options {
pub const SILENT: &str = "silent";
@ -111,14 +165,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if file.is_dir() {
show!(UUsageError::new(
0,
format!("{} is a directory.", file.quote()),
MoreError::IsDirectory(file.to_string_lossy().to_string()).to_string(),
));
continue;
}
if !file.exists() {
show!(USimpleError::new(
0,
format!("cannot open {}: No such file or directory", file.quote()),
MoreError::CannotOpenNoSuchFile(file.to_string_lossy().to_string()).to_string(),
));
continue;
}
@ -126,7 +180,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Err(why) => {
show!(USimpleError::new(
0,
format!("cannot open {}: {}", file.quote(), why.kind()),
MoreError::CannotOpenIOError(
file.to_string_lossy().to_string(),
why.kind()
)
.to_string(),
));
continue;
}
@ -144,7 +202,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let stdin = stdin();
if stdin.is_tty() {
// stdin is not a pipe
return Err(UUsageError::new(1, "bad usage"));
return Err(UUsageError::new(1, MoreError::BadUsage.to_string()));
}
more(InputType::Stdin(stdin), false, None, None, &mut options)?;
}
@ -163,49 +221,49 @@ pub fn uu_app() -> Command {
.short('d')
.long(options::SILENT)
.action(ArgAction::SetTrue)
.help("Display help instead of ringing bell when an illegal key is pressed"),
.help(get_message("more-help-silent")),
)
.arg(
Arg::new(options::LOGICAL)
.short('l')
.long(options::LOGICAL)
.action(ArgAction::SetTrue)
.help("Do not pause after any line containing a ^L (form feed)"),
.help(get_message("more-help-logical")),
)
.arg(
Arg::new(options::EXIT_ON_EOF)
.short('e')
.long(options::EXIT_ON_EOF)
.action(ArgAction::SetTrue)
.help("Exit on End-Of-File"),
.help(get_message("more-help-exit-on-eof")),
)
.arg(
Arg::new(options::NO_PAUSE)
.short('f')
.long(options::NO_PAUSE)
.action(ArgAction::SetTrue)
.help("Count logical lines, rather than screen lines"),
.help(get_message("more-help-no-pause")),
)
.arg(
Arg::new(options::PRINT_OVER)
.short('p')
.long(options::PRINT_OVER)
.action(ArgAction::SetTrue)
.help("Do not scroll, clear screen and display text"),
.help(get_message("more-help-print-over")),
)
.arg(
Arg::new(options::CLEAN_PRINT)
.short('c')
.long(options::CLEAN_PRINT)
.action(ArgAction::SetTrue)
.help("Do not scroll, display text and clean line ends"),
.help(get_message("more-help-clean-print")),
)
.arg(
Arg::new(options::SQUEEZE)
.short('s')
.long(options::SQUEEZE)
.action(ArgAction::SetTrue)
.help("Squeeze multiple blank lines into one"),
.help(get_message("more-help-squeeze")),
)
.arg(
Arg::new(options::PLAIN)
@ -213,7 +271,7 @@ pub fn uu_app() -> Command {
.long(options::PLAIN)
.action(ArgAction::SetTrue)
.hide(true)
.help("Suppress underlining"),
.help(get_message("more-help-plain")),
)
.arg(
Arg::new(options::LINES)
@ -222,14 +280,14 @@ pub fn uu_app() -> Command {
.value_name("number")
.num_args(1)
.value_parser(value_parser!(u16).range(0..))
.help("The number of lines per screen full"),
.help(get_message("more-help-lines")),
)
.arg(
Arg::new(options::NUMBER)
.long(options::NUMBER)
.num_args(1)
.value_parser(value_parser!(u16).range(0..))
.help("Same as --lines option argument"),
.help(get_message("more-help-number")),
)
.arg(
Arg::new(options::FROM_LINE)
@ -238,7 +296,7 @@ pub fn uu_app() -> Command {
.num_args(1)
.value_name("number")
.value_parser(value_parser!(usize))
.help("Start displaying each file at line number"),
.help(get_message("more-help-from-line")),
)
.arg(
Arg::new(options::PATTERN)
@ -247,13 +305,13 @@ pub fn uu_app() -> Command {
.allow_hyphen_values(true)
.required(false)
.value_name("pattern")
.help("The string to be searched in each file before starting to display it"),
.help(get_message("more-help-pattern")),
)
.arg(
Arg::new(options::FILES)
.required(false)
.action(ArgAction::Append)
.help("Path to the files to be read")
.help(get_message("more-help-files"))
.value_hint(clap::ValueHint::FilePath),
)
}
@ -454,9 +512,13 @@ impl<'a> Pager<'a> {
if !self.read_until_line(self.upper_mark)? {
write!(
self.stdout,
"\r{}Cannot seek to line number {} (press RETURN){}",
"\r{}{} ({}){}",
Attribute::Reverse,
self.upper_mark + 1,
get_message_with_args(
"more-error-cannot-seek-to-line",
HashMap::from([("line".to_string(), (self.upper_mark + 1).to_string())])
),
get_message("more-press-return"),
Attribute::Reset,
)?;
self.stdout.flush()?;
@ -515,8 +577,10 @@ impl<'a> Pager<'a> {
self.pattern = None;
write!(
self.stdout,
"\r{}Pattern not found (press RETURN){}",
"\r{}{} ({}){}",
Attribute::Reverse,
get_message("more-error-pattern-not-found"),
get_message("more-press-return"),
Attribute::Reset,
)?;
self.stdout.flush()?;
@ -807,9 +871,13 @@ impl<'a> Pager<'a> {
// - In normal mode: ring bell (BELL char) on wrong key or show basic prompt
let banner = match (self.silent, wrong_key) {
(true, Some(key)) => format!(
"{status}[Unknown key: '{key}'. Press 'h' for instructions. (unimplemented)]"
"{status}[{}]",
get_message_with_args(
"more-error-unknown-key",
HashMap::from([("key".to_string(), key.to_string())])
)
),
(true, None) => format!("{status}{HELP_MESSAGE}"),
(true, None) => format!("{status}{}", get_message("more-help-message")),
(false, Some(_)) => format!("{status}{BELL}"),
(false, None) => status,
};
@ -1007,7 +1075,7 @@ mod tests {
.build();
pager.draw_status_bar(None);
let stdout = String::from_utf8_lossy(&pager.stdout);
assert!(stdout.contains(HELP_MESSAGE));
assert!(stdout.contains(&get_message("more-help-message")));
}
#[test]
@ -1080,7 +1148,10 @@ mod tests {
assert!(pager.handle_from_line().is_ok());
assert_eq!(pager.upper_mark, 0);
let stdout = String::from_utf8_lossy(&pager.stdout);
assert!(stdout.contains("Cannot seek to line number 100"));
assert!(stdout.contains(&get_message_with_args(
"more-error-cannot-seek-to-line",
HashMap::from([("line".to_string(), "100".to_string())])
)));
}
#[test]
@ -1101,7 +1172,7 @@ mod tests {
let mut pager = TestPagerBuilder::new(content).pattern("qux").build();
assert!(pager.handle_pattern_search().is_ok());
let stdout = String::from_utf8_lossy(&pager.stdout);
assert!(stdout.contains("Pattern not found"));
assert!(stdout.contains(&get_message("more-error-pattern-not-found")));
assert_eq!(pager.pattern, None);
assert_eq!(pager.upper_mark, 0);
}
@ -1111,11 +1182,14 @@ mod tests {
let mut pager = TestPagerBuilder::default().silent().build();
pager.draw_status_bar(Some('x'));
let stdout = String::from_utf8_lossy(&pager.stdout);
assert!(stdout.contains("Unknown key: 'x'"));
assert!(stdout.contains(&get_message_with_args(
"more-error-unknown-key",
HashMap::from([("key".to_string(), "x".to_string())])
)));
pager = TestPagerBuilder::default().build();
pager.draw_status_bar(Some('x'));
let stdout = String::from_utf8_lossy(&pager.stdout);
assert!(stdout.contains(BELL));
assert!(stdout.contains(&BELL.to_string()));
}
}

View file

@ -14,3 +14,50 @@ mv-after-help = When specifying more than one of -i, -f, -n, only the final one
- all This is the default operation when an --update option is not specified, and results in all existing files in the destination being replaced.
- none This is similar to the --no-clobber option, in that no files in the destination are replaced, but also skipping a file does not induce a failure.
- older This is the default operation when --update is specified, and results in files being replaced if theyre older than the corresponding source file.
# Error messages
mv-error-no-such-file = cannot stat {$path}: No such file or directory
mv-error-cannot-stat-not-directory = cannot stat {$path}: Not a directory
mv-error-same-file = {$source} and {$target} are the same file
mv-error-self-target-subdirectory = cannot move {$source} to a subdirectory of itself, {$target}
mv-error-directory-to-non-directory = cannot overwrite directory {$path} with non-directory
mv-error-non-directory-to-directory = cannot overwrite non-directory {$target} with directory {$source}
mv-error-not-directory = target {$path}: Not a directory
mv-error-target-not-directory = target directory {$path}: Not a directory
mv-error-failed-access-not-directory = failed to access {$path}: Not a directory
mv-error-backup-with-no-clobber = cannot combine --backup with -n/--no-clobber or --update=none-fail
mv-error-extra-operand = mv: extra operand {$operand}
mv-error-backup-might-destroy-source = backing up {$target} might destroy source; {$source} not moved
mv-error-will-not-overwrite-just-created = will not overwrite just-created '{$target}' with '{$source}'
mv-error-not-replacing = not replacing {$target}
mv-error-cannot-move = cannot move {$source} to {$target}
mv-error-directory-not-empty = Directory not empty
mv-error-dangling-symlink = can't determine symlink type, since it is dangling
mv-error-no-symlink-support = your operating system does not support symlinks
mv-error-permission-denied = Permission denied
mv-error-inter-device-move-failed = inter-device move failed: '{$from}' to '{$to}'; unable to remove target: {$err}
# Help messages
mv-help-force = do not prompt before overwriting
mv-help-interactive = prompt before override
mv-help-no-clobber = do not overwrite an existing file
mv-help-strip-trailing-slashes = remove any trailing slashes from each SOURCE argument
mv-help-target-directory = move all SOURCE arguments into DIRECTORY
mv-help-no-target-directory = treat DEST as a normal file
mv-help-verbose = explain what is being done
mv-help-progress = Display a progress bar.
Note: this feature is not supported by GNU coreutils.
mv-help-debug = explain how a file is copied. Implies -v
# Verbose messages
mv-verbose-renamed = renamed {$from} -> {$to}
mv-verbose-renamed-with-backup = renamed {$from} -> {$to} (backup: {$backup})
# Debug messages
mv-debug-skipped = skipped {$target}
# Prompt messages
mv-prompt-overwrite = overwrite {$target}?
# Progress messages
mv-progress-moving = moving

View file

@ -0,0 +1,63 @@
mv-about = Déplacer SOURCE vers DEST, ou plusieurs SOURCE(s) vers RÉPERTOIRE.
mv-usage = mv [OPTION]... [-T] SOURCE DEST
mv [OPTION]... SOURCE... RÉPERTOIRE
mv [OPTION]... -t RÉPERTOIRE SOURCE...
mv-after-help = Lors de la spécification de plus d'une option parmi -i, -f, -n, seule la dernière prend effet.
Ne pas déplacer un non-répertoire qui a une destination existante avec un horodatage de modification identique ou plus récent ;
au lieu de cela, ignorer silencieusement le fichier sans échouer. Si le déplacement traverse les limites du système de fichiers, la comparaison est
avec l'horodatage source tronqué aux résolutions du système de fichiers de destination et des appels système utilisés
pour mettre à jour les horodatages ; cela évite le travail en double si plusieurs commandes mv -u sont exécutées avec la même source
et destination. Cette option est ignorée si l'option -n ou --no-clobber est également spécifiée, qui donne plus de contrôle
sur quels fichiers existants dans la destination sont remplacés, et sa valeur peut être une des suivantes :
- all C'est l'opération par défaut quand une option --update n'est pas spécifiée, et résulte en tous les fichiers existants dans la destination étant remplacés.
- none C'est similaire à l'option --no-clobber, en ce que aucun fichier dans la destination n'est remplacé, mais aussi ignorer un fichier n'induit pas un échec.
- older C'est l'opération par défaut quand --update est spécifié, et résulte en des fichiers étant remplacés s'ils sont plus anciens que le fichier source correspondant.
# Messages d'erreur
mv-error-no-such-file = impossible de lire {$path} : Aucun fichier ou répertoire de ce nom
mv-error-cannot-stat-not-directory = impossible de lire {$path} : N'est pas un répertoire
mv-error-same-file = {$source} et {$target} sont le même fichier
mv-error-self-target-subdirectory = impossible de déplacer {$source} vers un sous-répertoire de lui-même, {$target}
mv-error-directory-to-non-directory = impossible d'écraser le répertoire {$path} avec un non-répertoire
mv-error-non-directory-to-directory = impossible d'écraser le non-répertoire {$target} avec le répertoire {$source}
mv-error-not-directory = cible {$path} : N'est pas un répertoire
mv-error-target-not-directory = répertoire cible {$path} : N'est pas un répertoire
mv-error-failed-access-not-directory = impossible d'accéder à {$path} : N'est pas un répertoire
mv-error-backup-with-no-clobber = impossible de combiner --backup avec -n/--no-clobber ou --update=none-fail
mv-error-extra-operand = mv : opérande supplémentaire {$operand}
mv-error-backup-might-destroy-source = sauvegarder {$target} pourrait détruire la source ; {$source} non déplacé
mv-error-will-not-overwrite-just-created = ne va pas écraser le fichier qui vient d'être créé '{$target}' avec '{$source}'
mv-error-not-replacing = ne remplace pas {$target}
mv-error-cannot-move = impossible de déplacer {$source} vers {$target}
mv-error-directory-not-empty = Répertoire non vide
mv-error-dangling-symlink = impossible de déterminer le type de lien symbolique, car il est suspendu
mv-error-no-symlink-support = votre système d'exploitation ne prend pas en charge les liens symboliques
mv-error-permission-denied = Permission refusée
mv-error-inter-device-move-failed = échec du déplacement inter-périphérique : '{$from}' vers '{$to}' ; impossible de supprimer la cible : {$err}
# Messages d'aide
mv-help-force = ne pas demander avant d'écraser
mv-help-interactive = demander avant d'écraser
mv-help-no-clobber = ne pas écraser un fichier existant
mv-help-strip-trailing-slashes = supprimer toutes les barres obliques de fin de chaque argument SOURCE
mv-help-target-directory = déplacer tous les arguments SOURCE dans RÉPERTOIRE
mv-help-no-target-directory = traiter DEST comme un fichier normal
mv-help-verbose = expliquer ce qui est fait
mv-help-progress = Afficher une barre de progression.
Note : cette fonctionnalité n'est pas supportée par GNU coreutils.
mv-help-debug = expliquer comment un fichier est copié. Implique -v
# Messages verbeux
mv-verbose-renamed = renommé {$from} -> {$to}
mv-verbose-renamed-with-backup = renommé {$from} -> {$to} (sauvegarde : {$backup})
# Messages de débogage
mv-debug-skipped = ignoré {$target}
# Messages de confirmation
mv-prompt-overwrite = écraser {$target} ?
# Messages de progression
mv-progress-moving = déplacement

View file

@ -2,36 +2,30 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::collections::HashMap;
use thiserror::Error;
use uucore::error::UError;
use uucore::locale::get_message_with_args;
#[derive(Debug, Error)]
pub enum MvError {
#[error("cannot stat {0}: No such file or directory")]
#[error("{}", get_message_with_args("mv-error-no-such-file", HashMap::from([("path".to_string(), .0.clone())])))]
NoSuchFile(String),
#[error("cannot stat {0}: Not a directory")]
#[error("{}", get_message_with_args("mv-error-cannot-stat-not-directory", HashMap::from([("path".to_string(), .0.clone())])))]
CannotStatNotADirectory(String),
#[error("{0} and {1} are the same file")]
#[error("{}", get_message_with_args("mv-error-same-file", HashMap::from([("source".to_string(), .0.clone()), ("target".to_string(), .1.clone())])))]
SameFile(String, String),
#[error("cannot move {0} to a subdirectory of itself, {1}")]
#[error("{}", get_message_with_args("mv-error-self-target-subdirectory", HashMap::from([("source".to_string(), .0.clone()), ("target".to_string(), .1.clone())])))]
SelfTargetSubdirectory(String, String),
#[error("cannot overwrite directory {0} with non-directory")]
#[error("{}", get_message_with_args("mv-error-directory-to-non-directory", HashMap::from([("path".to_string(), .0.clone())])))]
DirectoryToNonDirectory(String),
#[error("cannot overwrite non-directory {1} with directory {0}")]
#[error("{}", get_message_with_args("mv-error-non-directory-to-directory", HashMap::from([("source".to_string(), .0.clone()), ("target".to_string(), .1.clone())])))]
NonDirectoryToDirectory(String, String),
#[error("target {0}: Not a directory")]
#[error("{}", get_message_with_args("mv-error-not-directory", HashMap::from([("path".to_string(), .0.clone())])))]
NotADirectory(String),
#[error("target directory {0}: Not a directory")]
#[error("{}", get_message_with_args("mv-error-target-not-directory", HashMap::from([("path".to_string(), .0.clone())])))]
TargetNotADirectory(String),
#[error("failed to access {0}: Not a directory")]
#[error("{}", get_message_with_args("mv-error-failed-access-not-directory", HashMap::from([("path".to_string(), .0.clone())])))]
FailedToAccessNotADirectory(String),
}

View file

@ -11,7 +11,7 @@ use clap::builder::ValueParser;
use clap::{Arg, ArgAction, ArgMatches, Command, error::ErrorKind};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::env;
use std::ffi::OsString;
use std::fs;
@ -48,7 +48,7 @@ use fs_extra::dir::{
};
use crate::error::MvError;
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
/// Options contains all the possible behaviors and flags for mv.
///
@ -167,7 +167,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
{
return Err(UUsageError::new(
1,
"cannot combine --backup with -n/--no-clobber or --update=none-fail",
get_message("mv-error-backup-with-no-clobber"),
));
}
@ -214,7 +214,7 @@ pub fn uu_app() -> Command {
Arg::new(OPT_FORCE)
.short('f')
.long(OPT_FORCE)
.help("do not prompt before overwriting")
.help(get_message("mv-help-force"))
.overrides_with_all([OPT_INTERACTIVE, OPT_NO_CLOBBER])
.action(ArgAction::SetTrue),
)
@ -222,7 +222,7 @@ pub fn uu_app() -> Command {
Arg::new(OPT_INTERACTIVE)
.short('i')
.long(OPT_INTERACTIVE)
.help("prompt before override")
.help(get_message("mv-help-interactive"))
.overrides_with_all([OPT_FORCE, OPT_NO_CLOBBER])
.action(ArgAction::SetTrue),
)
@ -230,14 +230,14 @@ pub fn uu_app() -> Command {
Arg::new(OPT_NO_CLOBBER)
.short('n')
.long(OPT_NO_CLOBBER)
.help("do not overwrite an existing file")
.help(get_message("mv-help-no-clobber"))
.overrides_with_all([OPT_FORCE, OPT_INTERACTIVE])
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_STRIP_TRAILING_SLASHES)
.long(OPT_STRIP_TRAILING_SLASHES)
.help("remove any trailing slashes from each SOURCE argument")
.help(get_message("mv-help-strip-trailing-slashes"))
.action(ArgAction::SetTrue),
)
.arg(backup_control::arguments::backup())
@ -249,7 +249,7 @@ pub fn uu_app() -> Command {
Arg::new(OPT_TARGET_DIRECTORY)
.short('t')
.long(OPT_TARGET_DIRECTORY)
.help("move all SOURCE arguments into DIRECTORY")
.help(get_message("mv-help-target-directory"))
.value_name("DIRECTORY")
.value_hint(clap::ValueHint::DirPath)
.conflicts_with(OPT_NO_TARGET_DIRECTORY)
@ -259,24 +259,21 @@ pub fn uu_app() -> Command {
Arg::new(OPT_NO_TARGET_DIRECTORY)
.short('T')
.long(OPT_NO_TARGET_DIRECTORY)
.help("treat DEST as a normal file")
.help(get_message("mv-help-no-target-directory"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_VERBOSE)
.short('v')
.long(OPT_VERBOSE)
.help("explain what is being done")
.help(get_message("mv-help-verbose"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_PROGRESS)
.short('g')
.long(OPT_PROGRESS)
.help(
"Display a progress bar. \n\
Note: this feature is not supported by GNU coreutils.",
)
.help(get_message("mv-help-progress"))
.action(ArgAction::SetTrue),
)
.arg(
@ -290,7 +287,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(OPT_DEBUG)
.long(OPT_DEBUG)
.help("explain how a file is copied. Implies -v")
.help(get_message("mv-help-debug"))
.action(ArgAction::SetTrue),
)
}
@ -326,10 +323,12 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()>
if opts.backup == BackupMode::Simple && source_is_target_backup(source, target, &opts.suffix) {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!(
"backing up {} might destroy source; {} not moved",
target.quote(),
source.quote()
get_message_with_args(
"mv-error-backup-might-destroy-source",
HashMap::from([
("target".to_string(), target.quote().to_string()),
("source".to_string(), source.quote().to_string()),
]),
),
)
.into());
@ -359,7 +358,13 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()>
if opts.no_target_dir {
if source.is_dir() {
rename(source, target, opts, None).map_err_context(|| {
format!("cannot move {} to {}", source.quote(), target.quote())
get_message_with_args(
"mv-error-cannot-move",
HashMap::from([
("source".to_string(), source.quote().to_string()),
("target".to_string(), target.quote().to_string()),
]),
)
})
} else {
Err(MvError::DirectoryToNonDirectory(target.quote().to_string()).into())
@ -371,7 +376,13 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()>
match opts.overwrite {
OverwriteMode::NoClobber => return Ok(()),
OverwriteMode::Interactive => {
if !prompt_yes!("overwrite {}? ", target.quote()) {
if !prompt_yes!(
"{}",
get_message_with_args(
"mv-prompt-overwrite",
HashMap::from([("target".to_string(), target.quote().to_string())]),
)
) {
return Err(io::Error::other("").into());
}
}
@ -473,7 +484,10 @@ fn handle_multiple_paths(paths: &[PathBuf], opts: &Options) -> UResult<()> {
if opts.no_target_dir {
return Err(UUsageError::new(
1,
format!("mv: extra operand {}", paths[2].quote()),
get_message_with_args(
"mv-error-extra-operand",
HashMap::from([("operand".to_string(), paths[2].quote().to_string())]),
),
));
}
let target_dir = paths.last().unwrap();
@ -511,11 +525,17 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options)
let count_progress = if let Some(ref multi_progress) = multi_progress {
if files.len() > 1 {
Some(multi_progress.add(
ProgressBar::new(files.len().try_into().unwrap()).with_style(
ProgressStyle::with_template("moving {msg} {wide_bar} {pos}/{len}").unwrap(),
Some(
multi_progress.add(
ProgressBar::new(files.len().try_into().unwrap()).with_style(
ProgressStyle::with_template(&format!(
"{} {{msg}} {{wide_bar}} {{pos}}/{{len}}",
get_message("mv-progress-moving")
))
.unwrap(),
),
),
))
)
} else {
None
}
@ -545,10 +565,12 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options)
// If the target file was already created in this mv call, do not overwrite
show!(USimpleError::new(
1,
format!(
"will not overwrite just-created '{}' with '{}'",
targetpath.display(),
sourcepath.display()
get_message_with_args(
"mv-error-will-not-overwrite-just-created",
HashMap::from([
("target".to_string(), targetpath.display().to_string()),
("source".to_string(), sourcepath.display().to_string())
])
),
));
continue;
@ -565,10 +587,12 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options)
Err(e) if e.to_string().is_empty() => set_exit_code(1),
Err(e) => {
let e = e.map_err_context(|| {
format!(
"cannot move {} to {}",
sourcepath.quote(),
targetpath.quote()
get_message_with_args(
"mv-error-cannot-move",
HashMap::from([
("source".to_string(), sourcepath.quote().to_string()),
("target".to_string(), targetpath.quote().to_string()),
]),
)
});
match multi_progress {
@ -597,7 +621,13 @@ fn rename(
if to.exists() {
if opts.update == UpdateMode::None {
if opts.debug {
println!("skipped {}", to.quote());
println!(
"{}",
get_message_with_args(
"mv-debug-skipped",
HashMap::from([("target".to_string(), to.quote().to_string())])
)
);
}
return Ok(());
}
@ -609,19 +639,34 @@ fn rename(
}
if opts.update == UpdateMode::NoneFail {
let err_msg = format!("not replacing {}", to.quote());
let err_msg = get_message_with_args(
"mv-error-not-replacing",
HashMap::from([("target".to_string(), to.quote().to_string())]),
);
return Err(io::Error::other(err_msg));
}
match opts.overwrite {
OverwriteMode::NoClobber => {
if opts.debug {
println!("skipped {}", to.quote());
println!(
"{}",
get_message_with_args(
"mv-debug-skipped",
HashMap::from([("target".to_string(), to.quote().to_string())])
)
);
}
return Ok(());
}
OverwriteMode::Interactive => {
if !prompt_yes!("overwrite {}?", to.quote()) {
if !prompt_yes!(
"{}",
get_message_with_args(
"mv-prompt-overwrite",
HashMap::from([("target".to_string(), to.quote().to_string())]),
)
) {
return Err(io::Error::other(""));
}
}
@ -641,7 +686,9 @@ fn rename(
if is_empty_dir(to) {
fs::remove_dir(to)?;
} else {
return Err(io::Error::other("Directory not empty"));
return Err(io::Error::other(get_message(
"mv-error-directory-not-empty",
)));
}
}
}
@ -650,13 +697,21 @@ fn rename(
if opts.verbose {
let message = match backup_path {
Some(path) => format!(
"renamed {} -> {} (backup: {})",
from.quote(),
to.quote(),
path.quote()
Some(path) => get_message_with_args(
"mv-verbose-renamed-with-backup",
HashMap::from([
("from".to_string(), from.quote().to_string()),
("to".to_string(), to.quote().to_string()),
("backup".to_string(), path.quote().to_string()),
]),
),
None => get_message_with_args(
"mv-verbose-renamed",
HashMap::from([
("from".to_string(), from.quote().to_string()),
("to".to_string(), to.quote().to_string()),
]),
),
None => format!("renamed {} -> {}", from.quote(), to.quote()),
};
match multi_progress {
@ -751,7 +806,7 @@ fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
} else {
Err(io::Error::new(
io::ErrorKind::NotFound,
"can't determine symlink type, since it is dangling",
get_message("mv-error-dangling-symlink"),
))
}
}
@ -761,7 +816,7 @@ fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
let path_symlink_points_to = fs::read_link(from)?;
Err(io::Error::new(
io::ErrorKind::Other,
"your operating system does not support symlinks",
get_message("mv-error-no-symlink-support"),
))
}
@ -802,8 +857,7 @@ fn rename_dir_fallback(
};
#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
let xattrs =
fsxattr::retrieve_xattrs(from).unwrap_or_else(|_| std::collections::HashMap::new());
let xattrs = fsxattr::retrieve_xattrs(from).unwrap_or_else(|_| HashMap::new());
let result = if let Some(ref pb) = progress_bar {
move_dir_with_progress(from, to, &options, |process_info: TransitProcess| {
@ -822,7 +876,7 @@ fn rename_dir_fallback(
Err(err) => match err.kind {
fs_extra::error::ErrorKind::PermissionDenied => Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"Permission denied",
get_message("mv-error-permission-denied"),
)),
_ => Err(io::Error::other(format!("{err:?}"))),
},
@ -837,9 +891,13 @@ fn rename_file_fallback(from: &Path, to: &Path) -> io::Result<()> {
let from = from.to_string_lossy();
io::Error::new(
err.kind(),
format!(
"inter-device move failed: '{from}' to '{to}'\
; unable to remove target: {err}"
get_message_with_args(
"mv-error-inter-device-move-failed",
HashMap::from([
("from".to_string(), from.to_string()),
("to".to_string(), to.to_string()),
("err".to_string(), err.to_string()),
]),
),
)
})?;

View file

@ -4,11 +4,9 @@ nice-about = Run COMMAND with an adjusted niceness, which affects process schedu
nice-usage = nice [OPTION] [COMMAND [ARG]...]
# Error messages
nice-error-getpriority = getpriority: { $error }
nice-error-command-required-with-adjustment = A command must be given with an adjustment.
nice-error-invalid-number = "{ $value }" is not a valid number: { $error }
nice-warning-setpriority = { $util_name }: warning: setpriority: { $error }
nice-error-execvp = execvp: { $error }
# Help text for command-line arguments
nice-help-adjustment = add N to the niceness (default is 10)

View file

@ -111,10 +111,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if Error::last_os_error().raw_os_error().unwrap() != 0 {
return Err(USimpleError::new(
125,
get_message_with_args(
"nice-error-getpriority",
HashMap::from([("error".to_string(), Error::last_os_error().to_string())]),
),
format!("getpriority: {}", Error::last_os_error()),
));
}
@ -183,13 +180,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
execvp(args[0], args.as_mut_ptr());
}
show_error!(
"{}",
get_message_with_args(
"nice-error-execvp",
HashMap::from([("error".to_string(), Error::last_os_error().to_string())])
)
);
show_error!("execvp: {}", Error::last_os_error());
let exit_code = if Error::last_os_error().raw_os_error().unwrap() as c_int == libc::ENOENT {
127

View file

@ -13,3 +13,27 @@ nl-after-help = STYLE is one of:
- ln left justified, no leading zeros
- rn right justified, no leading zeros
- rz right justified, leading zeros
# Help messages
nl-help-help = Print help information.
nl-help-body-numbering = use STYLE for numbering body lines
nl-help-section-delimiter = use CC for separating logical pages
nl-help-footer-numbering = use STYLE for numbering footer lines
nl-help-header-numbering = use STYLE for numbering header lines
nl-help-line-increment = line number increment at each line
nl-help-join-blank-lines = group of NUMBER empty lines counted as one
nl-help-number-format = insert line numbers according to FORMAT
nl-help-no-renumber = do not reset line numbers at logical pages
nl-help-number-separator = add STRING after (possible) line number
nl-help-starting-line-number = first line number on each logical page
nl-help-number-width = use NUMBER columns for line numbers
# Error messages
nl-error-invalid-arguments = Invalid arguments supplied.
nl-error-could-not-read-line = could not read line
nl-error-line-number-overflow = line number overflow
nl-error-invalid-line-width = Invalid line number field width: { $value }: Numerical result out of range
nl-error-invalid-blank-lines = Invalid line number of blank lines: { $value }: Numerical result out of range
nl-error-invalid-regex = invalid regular expression
nl-error-invalid-numbering-style = invalid numbering style: '{ $style }'
nl-error-is-directory = { $path }: Is a directory

View file

@ -0,0 +1,39 @@
nl-about = Numéroter les lignes des fichiers
nl-usage = nl [OPTION]... [FICHIER]...
nl-after-help = STYLE est l'un des suivants :
- a numéroter toutes les lignes
- t numéroter seulement les lignes non vides
- n ne numéroter aucune ligne
- pBRE numéroter seulement les lignes qui contiennent une correspondance pour
l'expression régulière de base, BRE
FORMAT est l'un des suivants :
- ln justifié à gauche, sans zéros en tête
- rn justifié à droite, sans zéros en tête
- rz justifié à droite, avec zéros en tête
# Messages d'aide
nl-help-help = Afficher les informations d'aide.
nl-help-body-numbering = utiliser STYLE pour numéroter les lignes du corps
nl-help-section-delimiter = utiliser CC pour séparer les pages logiques
nl-help-footer-numbering = utiliser STYLE pour numéroter les lignes de pied de page
nl-help-header-numbering = utiliser STYLE pour numéroter les lignes d'en-tête
nl-help-line-increment = incrément du numéro de ligne à chaque ligne
nl-help-join-blank-lines = groupe de NUMBER lignes vides comptées comme une seule
nl-help-number-format = insérer les numéros de ligne selon FORMAT
nl-help-no-renumber = ne pas remettre à zéro les numéros de ligne aux pages logiques
nl-help-number-separator = ajouter STRING après le numéro de ligne (éventuel)
nl-help-starting-line-number = premier numéro de ligne sur chaque page logique
nl-help-number-width = utiliser NUMBER colonnes pour les numéros de ligne
# Messages d'erreur
nl-error-invalid-arguments = Arguments fournis invalides.
nl-error-could-not-read-line = impossible de lire la ligne
nl-error-line-number-overflow = débordement du numéro de ligne
nl-error-invalid-line-width = Largeur de champ de numéro de ligne invalide : { $value } : Résultat numérique hors limites
nl-error-invalid-blank-lines = Nombre de lignes vides invalide : { $value } : Résultat numérique hors limites
nl-error-invalid-regex = expression régulière invalide
nl-error-invalid-numbering-style = style de numérotation invalide : '{ $style }'
nl-error-is-directory = { $path } : Est un répertoire

View file

@ -5,6 +5,8 @@
// spell-checker:ignore (ToDO) conv
use crate::options;
use std::collections::HashMap;
use uucore::locale::get_message_with_args;
// parse_options loads the options into the settings, returning an array of
// error messages.
@ -59,15 +61,17 @@ pub fn parse_options(settings: &mut crate::Settings, opts: &clap::ArgMatches) ->
match opts.get_one::<usize>(options::NUMBER_WIDTH) {
None => {}
Some(num) if *num > 0 => settings.number_width = *num,
Some(_) => errs.push(String::from(
"Invalid line number field width: 0: Numerical result out of range",
Some(_) => errs.push(get_message_with_args(
"nl-error-invalid-line-width",
HashMap::from([("value".to_string(), "0".to_string())]),
)),
}
match opts.get_one::<u64>(options::JOIN_BLANK_LINES) {
None => {}
Some(num) if *num > 0 => settings.join_blank_lines = *num,
Some(_) => errs.push(String::from(
"Invalid line number of blank lines: 0: Numerical result out of range",
Some(_) => errs.push(get_message_with_args(
"nl-error-invalid-blank-lines",
HashMap::from([("value".to_string(), "0".to_string())]),
)),
}
if let Some(num) = opts.get_one::<i64>(options::LINE_INCREMENT) {

View file

@ -4,11 +4,12 @@
// file that was distributed with this source code.
use clap::{Arg, ArgAction, Command};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, stdin};
use std::path::Path;
use uucore::error::{FromIo, UResult, USimpleError, set_exit_code};
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
use uucore::{format_usage, show_error};
mod helper;
@ -89,9 +90,12 @@ impl TryFrom<&str> for NumberingStyle {
"n" => Ok(Self::None),
_ if s.starts_with('p') => match regex::Regex::new(&s[1..]) {
Ok(re) => Ok(Self::Regex(Box::new(re))),
Err(_) => Err(String::from("invalid regular expression")),
Err(_) => Err(get_message("nl-error-invalid-regex")),
},
_ => Err(format!("invalid numbering style: '{s}'")),
_ => Err(get_message_with_args(
"nl-error-invalid-numbering-style",
HashMap::from([("style".to_string(), s.to_string())]),
)),
}
}
}
@ -185,7 +189,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if !parse_errors.is_empty() {
return Err(USimpleError::new(
1,
format!("Invalid arguments supplied.\n{}", parse_errors.join("\n")),
format!(
"{}\n{}",
get_message("nl-error-invalid-arguments"),
parse_errors.join("\n")
),
));
}
@ -204,7 +212,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let path = Path::new(file);
if path.is_dir() {
show_error!("{}: Is a directory", path.display());
show_error!(
"{}",
get_message_with_args(
"nl-error-is-directory",
HashMap::from([("path".to_string(), path.display().to_string())])
)
);
set_exit_code(1);
} else {
let reader = File::open(path).map_err_context(|| file.to_string())?;
@ -228,7 +242,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(options::HELP)
.long(options::HELP)
.help("Print help information.")
.help(get_message("nl-help-help"))
.action(ArgAction::Help),
)
.arg(
@ -241,35 +255,35 @@ pub fn uu_app() -> Command {
Arg::new(options::BODY_NUMBERING)
.short('b')
.long(options::BODY_NUMBERING)
.help("use STYLE for numbering body lines")
.help(get_message("nl-help-body-numbering"))
.value_name("STYLE"),
)
.arg(
Arg::new(options::SECTION_DELIMITER)
.short('d')
.long(options::SECTION_DELIMITER)
.help("use CC for separating logical pages")
.help(get_message("nl-help-section-delimiter"))
.value_name("CC"),
)
.arg(
Arg::new(options::FOOTER_NUMBERING)
.short('f')
.long(options::FOOTER_NUMBERING)
.help("use STYLE for numbering footer lines")
.help(get_message("nl-help-footer-numbering"))
.value_name("STYLE"),
)
.arg(
Arg::new(options::HEADER_NUMBERING)
.short('h')
.long(options::HEADER_NUMBERING)
.help("use STYLE for numbering header lines")
.help(get_message("nl-help-header-numbering"))
.value_name("STYLE"),
)
.arg(
Arg::new(options::LINE_INCREMENT)
.short('i')
.long(options::LINE_INCREMENT)
.help("line number increment at each line")
.help(get_message("nl-help-line-increment"))
.value_name("NUMBER")
.value_parser(clap::value_parser!(i64)),
)
@ -277,7 +291,7 @@ pub fn uu_app() -> Command {
Arg::new(options::JOIN_BLANK_LINES)
.short('l')
.long(options::JOIN_BLANK_LINES)
.help("group of NUMBER empty lines counted as one")
.help(get_message("nl-help-join-blank-lines"))
.value_name("NUMBER")
.value_parser(clap::value_parser!(u64)),
)
@ -285,7 +299,7 @@ pub fn uu_app() -> Command {
Arg::new(options::NUMBER_FORMAT)
.short('n')
.long(options::NUMBER_FORMAT)
.help("insert line numbers according to FORMAT")
.help(get_message("nl-help-number-format"))
.value_name("FORMAT")
.value_parser(["ln", "rn", "rz"]),
)
@ -293,21 +307,21 @@ pub fn uu_app() -> Command {
Arg::new(options::NO_RENUMBER)
.short('p')
.long(options::NO_RENUMBER)
.help("do not reset line numbers at logical pages")
.help(get_message("nl-help-no-renumber"))
.action(ArgAction::SetFalse),
)
.arg(
Arg::new(options::NUMBER_SEPARATOR)
.short('s')
.long(options::NUMBER_SEPARATOR)
.help("add STRING after (possible) line number")
.help(get_message("nl-help-number-separator"))
.value_name("STRING"),
)
.arg(
Arg::new(options::STARTING_LINE_NUMBER)
.short('v')
.long(options::STARTING_LINE_NUMBER)
.help("first line number on each logical page")
.help(get_message("nl-help-starting-line-number"))
.value_name("NUMBER")
.value_parser(clap::value_parser!(i64)),
)
@ -315,7 +329,7 @@ pub fn uu_app() -> Command {
Arg::new(options::NUMBER_WIDTH)
.short('w')
.long(options::NUMBER_WIDTH)
.help("use NUMBER columns for line numbers")
.help(get_message("nl-help-number-width"))
.value_name("NUMBER")
.value_parser(clap::value_parser!(usize)),
)
@ -326,7 +340,7 @@ fn nl<T: Read>(reader: &mut BufReader<T>, stats: &mut Stats, settings: &Settings
let mut current_numbering_style = &settings.body_numbering;
for line in reader.lines() {
let line = line.map_err_context(|| "could not read line".to_string())?;
let line = line.map_err_context(|| get_message("nl-error-could-not-read-line"))?;
if line.is_empty() {
stats.consecutive_empty_lines += 1;
@ -366,7 +380,10 @@ fn nl<T: Read>(reader: &mut BufReader<T>, stats: &mut Stats, settings: &Settings
if is_line_numbered {
let Some(line_number) = stats.line_number else {
return Err(USimpleError::new(1, "line number overflow"));
return Err(USimpleError::new(
1,
get_message("nl-error-line-number-overflow"),
));
};
println!(
"{}{}{line}",

View file

@ -34,3 +34,41 @@ numfmt-after-help = UNIT options:
Optional width value (%10f) will pad output. Optional zero (%010f) width
will zero pad the number. Optional negative values (%-10f) will left align.
Optional precision (%.1f) will override the input determined precision.
# Help messages
numfmt-help-delimiter = use X instead of whitespace for field delimiter
numfmt-help-field = replace the numbers in these input fields; see FIELDS below
numfmt-help-format = use printf style floating-point FORMAT; see FORMAT below for details
numfmt-help-from = auto-scale input numbers to UNITs; see UNIT below
numfmt-help-from-unit = specify the input unit size
numfmt-help-to = auto-scale output numbers to UNITs; see UNIT below
numfmt-help-to-unit = the output unit size
numfmt-help-padding = pad the output to N characters; positive N will right-align; negative N will left-align; padding is ignored if the output is wider than N; the default is to automatically pad if a whitespace is found
numfmt-help-header = print (without converting) the first N header lines; N defaults to 1 if not specified
numfmt-help-round = use METHOD for rounding when scaling
numfmt-help-suffix = print SUFFIX after each formatted number, and accept inputs optionally ending with SUFFIX
numfmt-help-invalid = set the failure mode for invalid input
numfmt-help-zero-terminated = line delimiter is NUL, not newline
# Error messages
numfmt-error-unsupported-unit = Unsupported unit is specified
numfmt-error-invalid-unit-size = invalid unit size: { $size }
numfmt-error-invalid-padding = invalid padding value { $value }
numfmt-error-invalid-header = invalid header value { $value }
numfmt-error-grouping-cannot-be-combined-with-to = grouping cannot be combined with --to
numfmt-error-delimiter-must-be-single-character = the delimiter must be a single character
numfmt-error-invalid-number-empty = invalid number: ''
numfmt-error-invalid-suffix = invalid suffix in input: { $input }
numfmt-error-invalid-number = invalid number: { $input }
numfmt-error-missing-i-suffix = missing 'i' suffix in input: '{ $number }{ $suffix }' (e.g Ki/Mi/Gi)
numfmt-error-rejecting-suffix = rejecting suffix in input: '{ $number }{ $suffix }' (consider using --from)
numfmt-error-suffix-unsupported-for-unit = This suffix is unsupported for specified unit
numfmt-error-unit-auto-not-supported-with-to = Unit 'auto' isn't supported with --to options
numfmt-error-number-too-big = Number is too big and unsupported
numfmt-error-format-no-percent = format '{ $format }' has no % directive
numfmt-error-format-ends-in-percent = format '{ $format }' ends in %
numfmt-error-invalid-format-directive = invalid format '{ $format }', directive must be %[0]['][-][N][.][N]f
numfmt-error-invalid-format-width-overflow = invalid format '{ $format }' (width overflow)
numfmt-error-invalid-precision = invalid precision in format '{ $format }'
numfmt-error-format-too-many-percent = format '{ $format }' has too many % directives
numfmt-error-unknown-invalid-mode = Unknown invalid mode: { $mode }

View file

@ -0,0 +1,74 @@
numfmt-about = Convertir les nombres vers/depuis des chaînes lisibles par l'homme
numfmt-usage = numfmt [OPTION]... [NOMBRE]...
numfmt-after-help = Options d'UNITÉ :
- none : aucune mise à l'échelle automatique n'est effectuée ; les suffixes déclencheront une erreur
- auto : accepter un suffixe optionnel d'une/deux lettres :
1K = 1000, 1Ki = 1024, 1M = 1000000, 1Mi = 1048576,
- si : accepter un suffixe optionnel d'une lettre :
1K = 1000, 1M = 1000000, ...
- iec : accepter un suffixe optionnel d'une lettre :
1K = 1024, 1M = 1048576, ...
- iec-i : accepter un suffixe optionnel de deux lettres :
1Ki = 1024, 1Mi = 1048576, ...
- FIELDS supporte les plages de champs de style cut(1) :
N N-ième champ, compté à partir de 1
N- du N-ième champ jusqu'à la fin de la ligne
N-M du N-ième au M-ième champ (inclus)
-M du premier au M-ième champ (inclus)
- tous les champs
Plusieurs champs/plages peuvent être séparés par des virgules
FORMAT doit être adapté pour imprimer un argument à virgule flottante %f.
Une guillemet optionnelle (%'f) activera --grouping (si supporté par la locale actuelle).
Une valeur de largeur optionnelle (%10f) remplira la sortie. Un zéro optionnel (%010f)
remplira le nombre de zéros. Des valeurs négatives optionnelles (%-10f) aligneront à gauche.
Une précision optionnelle (%.1f) remplacera la précision déterminée par l'entrée.
# Messages d'aide
numfmt-help-delimiter = utiliser X au lieu d'espaces pour le délimiteur de champ
numfmt-help-field = remplacer les nombres dans ces champs d'entrée ; voir FIELDS ci-dessous
numfmt-help-format = utiliser le FORMAT à virgule flottante de style printf ; voir FORMAT ci-dessous pour les détails
numfmt-help-from = mettre automatiquement à l'échelle les nombres d'entrée vers les UNITÉs ; voir UNIT ci-dessous
numfmt-help-from-unit = spécifier la taille de l'unité d'entrée
numfmt-help-to = mettre automatiquement à l'échelle les nombres de sortie vers les UNITÉs ; voir UNIT ci-dessous
numfmt-help-to-unit = la taille de l'unité de sortie
numfmt-help-padding = remplir la sortie à N caractères ; N positif alignera à droite ; N négatif alignera à gauche ; le remplissage est ignoré si la sortie est plus large que N ; la valeur par défaut est de remplir automatiquement si un espace est trouvé
numfmt-help-header = imprimer (sans convertir) les N premières lignes d'en-tête ; N vaut 1 par défaut si non spécifié
numfmt-help-round = utiliser METHOD pour l'arrondi lors de la mise à l'échelle
numfmt-help-suffix = imprimer SUFFIX après chaque nombre formaté, et accepter les entrées se terminant optionnellement par SUFFIX
numfmt-help-invalid = définir le mode d'échec pour les entrées invalides
numfmt-help-zero-terminated = le délimiteur de ligne est NUL, pas retour à la ligne
# Messages d'erreur
numfmt-error-unsupported-unit = Une unité non supportée est spécifiée
numfmt-error-invalid-unit-size = taille d'unité invalide : { $size }
numfmt-error-invalid-padding = valeur de remplissage invalide { $value }
numfmt-error-invalid-header = valeur d'en-tête invalide { $value }
numfmt-error-grouping-cannot-be-combined-with-to = le groupement ne peut pas être combiné avec --to
numfmt-error-delimiter-must-be-single-character = le délimiteur doit être un seul caractère
numfmt-error-invalid-number-empty = nombre invalide : ''
numfmt-error-invalid-suffix = suffixe invalide dans l'entrée : { $input }
numfmt-error-invalid-number = nombre invalide : { $input }
numfmt-error-missing-i-suffix = suffixe 'i' manquant dans l'entrée : '{ $number }{ $suffix }' (par ex. Ki/Mi/Gi)
numfmt-error-rejecting-suffix = rejet du suffixe dans l'entrée : '{ $number }{ $suffix }' (considérez utiliser --from)
numfmt-error-suffix-unsupported-for-unit = Ce suffixe n'est pas supporté pour l'unité spécifiée
numfmt-error-unit-auto-not-supported-with-to = L'unité 'auto' n'est pas supportée avec les options --to
numfmt-error-number-too-big = Le nombre est trop grand et non supporté
numfmt-error-format-no-percent = le format '{ $format }' n'a pas de directive %
numfmt-error-format-ends-in-percent = le format '{ $format }' se termine par %
numfmt-error-invalid-format-directive = format invalide '{ $format }', la directive doit être %[0]['][-][N][.][N]f
numfmt-error-invalid-format-width-overflow = format invalide '{ $format }' (débordement de largeur)
numfmt-error-invalid-precision = précision invalide dans le format '{ $format }'
numfmt-error-format-too-many-percent = le format '{ $format }' a trop de directives %
numfmt-error-unknown-invalid-mode = Mode invalide inconnu : { $mode }

View file

@ -3,7 +3,9 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore powf
use std::collections::HashMap;
use uucore::display::Quotable;
use uucore::locale::{get_message, get_message_with_args};
use crate::options::{NumfmtOptions, RoundMethod, TransformOptions};
use crate::units::{DisplayableSuffix, IEC_BASES, RawSuffix, Result, SI_BASES, Suffix, Unit};
@ -63,7 +65,7 @@ impl<'a> Iterator for WhitespaceSplitter<'a> {
fn parse_suffix(s: &str) -> Result<(f64, Option<Suffix>)> {
if s.is_empty() {
return Err("invalid number: ''".to_string());
return Err(get_message("numfmt-error-invalid-number-empty"));
}
let with_i = s.ends_with('i');
@ -81,7 +83,12 @@ fn parse_suffix(s: &str) -> Result<(f64, Option<Suffix>)> {
Some('Z') => Some((RawSuffix::Z, with_i)),
Some('Y') => Some((RawSuffix::Y, with_i)),
Some('0'..='9') if !with_i => None,
_ => return Err(format!("invalid suffix in input: {}", s.quote())),
_ => {
return Err(get_message_with_args(
"numfmt-error-invalid-suffix",
HashMap::from([("input".to_string(), s.quote().to_string())]),
));
}
};
let suffix_len = match suffix {
@ -90,9 +97,12 @@ fn parse_suffix(s: &str) -> Result<(f64, Option<Suffix>)> {
Some((_, true)) => 2,
};
let number = s[..s.len() - suffix_len]
.parse::<f64>()
.map_err(|_| format!("invalid number: {}", s.quote()))?;
let number = s[..s.len() - suffix_len].parse::<f64>().map_err(|_| {
get_message_with_args(
"numfmt-error-invalid-number",
HashMap::from([("input".to_string(), s.quote().to_string())]),
)
})?;
Ok((number, suffix))
}
@ -132,15 +142,25 @@ fn remove_suffix(i: f64, s: Option<Suffix>, u: &Unit) -> Result<f64> {
RawSuffix::Z => Ok(i * IEC_BASES[7]),
RawSuffix::Y => Ok(i * IEC_BASES[8]),
},
(Some((raw_suffix, false)), &Unit::Iec(true)) => Err(format!(
"missing 'i' suffix in input: '{i}{raw_suffix:?}' (e.g Ki/Mi/Gi)"
(Some((raw_suffix, false)), &Unit::Iec(true)) => Err(get_message_with_args(
"numfmt-error-missing-i-suffix",
HashMap::from([
("number".to_string(), i.to_string()),
("suffix".to_string(), format!("{raw_suffix:?}")),
]),
)),
(Some((raw_suffix, with_i)), &Unit::None) => Err(format!(
"rejecting suffix in input: '{i}{raw_suffix:?}{}' (consider using --from)",
if with_i { "i" } else { "" }
(Some((raw_suffix, with_i)), &Unit::None) => Err(get_message_with_args(
"numfmt-error-rejecting-suffix",
HashMap::from([
("number".to_string(), i.to_string()),
(
"suffix".to_string(),
format!("{raw_suffix:?}{}", if with_i { "i" } else { "" }),
),
]),
)),
(None, _) => Ok(i),
(_, _) => Err("This suffix is unsupported for specified unit".to_owned()),
(_, _) => Err(get_message("numfmt-error-suffix-unsupported-for-unit")),
}
}
@ -218,7 +238,7 @@ fn consider_suffix(
let (bases, with_i) = match *u {
Unit::Si => (&SI_BASES, false),
Unit::Iec(with_i) => (&IEC_BASES, with_i),
Unit::Auto => return Err("Unit 'auto' isn't supported with --to options".to_owned()),
Unit::Auto => return Err(get_message("numfmt-error-unit-auto-not-supported-with-to")),
Unit::None => return Ok((n, None)),
};
@ -232,7 +252,7 @@ fn consider_suffix(
_ if abs_n < bases[7] => 6,
_ if abs_n < bases[8] => 7,
_ if abs_n < bases[9] => 8,
_ => return Err("Number is too big and unsupported".to_string()),
_ => return Err(get_message("numfmt-error-number-too-big")),
};
let v = if precision > 0 {

View file

@ -8,6 +8,7 @@ use crate::format::format_and_print;
use crate::options::*;
use crate::units::{Result, Unit};
use clap::{Arg, ArgAction, ArgMatches, Command, parser::ValueSource};
use std::collections::HashMap;
use std::io::{BufRead, Error, Write};
use std::result::Result as StdResult;
use std::str::FromStr;
@ -15,7 +16,7 @@ use std::str::FromStr;
use units::{IEC_BASES, SI_BASES};
use uucore::display::Quotable;
use uucore::error::UResult;
use uucore::locale::get_message;
use uucore::locale::{get_message, get_message_with_args};
use uucore::parser::shortcut_value_parser::ShortcutValueParser;
use uucore::ranges::Range;
use uucore::{format_usage, show, show_error};
@ -97,7 +98,7 @@ fn parse_unit(s: &str) -> Result<Unit> {
"iec" => Ok(Unit::Iec(false)),
"iec-i" => Ok(Unit::Iec(true)),
"none" => Ok(Unit::None),
_ => Err("Unsupported unit is specified".to_owned()),
_ => Err(get_message("numfmt-error-unsupported-unit")),
}
}
@ -119,7 +120,10 @@ fn parse_unit_size(s: &str) -> Result<usize> {
}
}
Err(format!("invalid unit size: {}", s.quote()))
Err(get_message_with_args(
"numfmt-error-invalid-unit-size",
HashMap::from([("size".to_string(), s.quote().to_string())]),
))
}
// Parses a suffix of a unit size and returns the corresponding multiplier. For example,
@ -170,7 +174,12 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
0 => Err(s),
_ => Ok(n),
})
.map_err(|s| format!("invalid padding value {}", s.quote())),
.map_err(|s| {
get_message_with_args(
"numfmt-error-invalid-padding",
HashMap::from([("value".to_string(), s.quote().to_string())]),
)
}),
None => Ok(0),
}?;
@ -184,7 +193,12 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
0 => Err(value),
_ => Ok(n),
})
.map_err(|value| format!("invalid header value {}", value.quote()))
.map_err(|value| {
get_message_with_args(
"numfmt-error-invalid-header",
HashMap::from([("value".to_string(), value.quote().to_string())]),
)
})
} else {
Ok(0)
}?;
@ -206,14 +220,18 @@ fn parse_options(args: &ArgMatches) -> Result<NumfmtOptions> {
};
if format.grouping && to != Unit::None {
return Err("grouping cannot be combined with --to".to_string());
return Err(get_message(
"numfmt-error-grouping-cannot-be-combined-with-to",
));
}
let delimiter = args.get_one::<String>(DELIMITER).map_or(Ok(None), |arg| {
if arg.len() == 1 {
Ok(Some(arg.to_string()))
} else {
Err("the delimiter must be a single character".to_string())
Err(get_message(
"numfmt-error-delimiter-must-be-single-character",
))
}
})?;
@ -284,12 +302,12 @@ pub fn uu_app() -> Command {
.short('d')
.long(DELIMITER)
.value_name("X")
.help("use X instead of whitespace for field delimiter"),
.help(get_message("numfmt-help-delimiter")),
)
.arg(
Arg::new(FIELD)
.long(FIELD)
.help("replace the numbers in these input fields; see FIELDS below")
.help(get_message("numfmt-help-field"))
.value_name("FIELDS")
.allow_hyphen_values(true)
.default_value(FIELD_DEFAULT),
@ -297,56 +315,48 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(FORMAT)
.long(FORMAT)
.help("use printf style floating-point FORMAT; see FORMAT below for details")
.help(get_message("numfmt-help-format"))
.value_name("FORMAT")
.allow_hyphen_values(true),
)
.arg(
Arg::new(FROM)
.long(FROM)
.help("auto-scale input numbers to UNITs; see UNIT below")
.help(get_message("numfmt-help-from"))
.value_name("UNIT")
.default_value(FROM_DEFAULT),
)
.arg(
Arg::new(FROM_UNIT)
.long(FROM_UNIT)
.help("specify the input unit size")
.help(get_message("numfmt-help-from-unit"))
.value_name("N")
.default_value(FROM_UNIT_DEFAULT),
)
.arg(
Arg::new(TO)
.long(TO)
.help("auto-scale output numbers to UNITs; see UNIT below")
.help(get_message("numfmt-help-to"))
.value_name("UNIT")
.default_value(TO_DEFAULT),
)
.arg(
Arg::new(TO_UNIT)
.long(TO_UNIT)
.help("the output unit size")
.help(get_message("numfmt-help-to-unit"))
.value_name("N")
.default_value(TO_UNIT_DEFAULT),
)
.arg(
Arg::new(PADDING)
.long(PADDING)
.help(
"pad the output to N characters; positive N will \
right-align; negative N will left-align; padding is \
ignored if the output is wider than N; the default is \
to automatically pad if a whitespace is found",
)
.help(get_message("numfmt-help-padding"))
.value_name("N"),
)
.arg(
Arg::new(HEADER)
.long(HEADER)
.help(
"print (without converting) the first N header lines; \
N defaults to 1 if not specified",
)
.help(get_message("numfmt-help-header"))
.num_args(..=1)
.value_name("N")
.default_missing_value(HEADER_DEFAULT)
@ -355,7 +365,7 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(ROUND)
.long(ROUND)
.help("use METHOD for rounding when scaling")
.help(get_message("numfmt-help-round"))
.value_name("METHOD")
.default_value("from-zero")
.value_parser(ShortcutValueParser::new([
@ -369,16 +379,13 @@ pub fn uu_app() -> Command {
.arg(
Arg::new(SUFFIX)
.long(SUFFIX)
.help(
"print SUFFIX after each formatted number, and accept \
inputs optionally ending with SUFFIX",
)
.help(get_message("numfmt-help-suffix"))
.value_name("SUFFIX"),
)
.arg(
Arg::new(INVALID)
.long(INVALID)
.help("set the failure mode for invalid input")
.help(get_message("numfmt-help-invalid"))
.default_value("abort")
.value_parser(["abort", "fail", "warn", "ignore"])
.value_name("INVALID"),
@ -387,7 +394,7 @@ pub fn uu_app() -> Command {
Arg::new(ZERO_TERMINATED)
.long(ZERO_TERMINATED)
.short('z')
.help("line delimiter is NUL, not newline")
.help(get_message("numfmt-help-zero-terminated"))
.action(ArgAction::SetTrue),
)
.arg(Arg::new(NUMBER).hide(true).action(ArgAction::Append))
@ -465,9 +472,9 @@ mod tests {
let result_display = format!("{result}");
assert_eq!(
result_debug,
"FormattingError(\"invalid suffix in input: 'hello'\")"
"FormattingError(\"numfmt-error-invalid-suffix\")"
);
assert_eq!(result_display, "invalid suffix in input: 'hello'");
assert_eq!(result_display, "numfmt-error-invalid-suffix");
assert_eq!(result.code(), 2);
}

View file

@ -2,9 +2,11 @@
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::collections::HashMap;
use std::str::FromStr;
use crate::units::Unit;
use uucore::locale::get_message_with_args;
use uucore::ranges::Range;
pub const DELIMITER: &str = "delimiter";
@ -145,9 +147,15 @@ impl FromStr for FormatOptions {
if iter.peek().is_none() {
return if options.prefix == s {
Err(format!("format '{s}' has no % directive"))
Err(get_message_with_args(
"numfmt-error-format-no-percent",
HashMap::from([("format".to_string(), s.to_string())]),
))
} else {
Err(format!("format '{s}' ends in %"))
Err(get_message_with_args(
"numfmt-error-format-ends-in-percent",
HashMap::from([("format".to_string(), s.to_string())]),
))
};
}
@ -167,8 +175,9 @@ impl FromStr for FormatOptions {
match iter.peek() {
Some(c) if c.is_ascii_digit() => padding.push('-'),
_ => {
return Err(format!(
"invalid format '{s}', directive must be %[0]['][-][N][.][N]f"
return Err(get_message_with_args(
"numfmt-error-invalid-format-directive",
HashMap::from([("format".to_string(), s.to_string())]),
));
}
}
@ -187,7 +196,10 @@ impl FromStr for FormatOptions {
if let Ok(p) = padding.parse() {
options.padding = Some(p);
} else {
return Err(format!("invalid format '{s}' (width overflow)"));
return Err(get_message_with_args(
"numfmt-error-invalid-format-width-overflow",
HashMap::from([("format".to_string(), s.to_string())]),
));
}
}
@ -195,7 +207,10 @@ impl FromStr for FormatOptions {
iter.next();
if matches!(iter.peek(), Some(' ' | '+' | '-')) {
return Err(format!("invalid precision in format '{s}'"));
return Err(get_message_with_args(
"numfmt-error-invalid-precision",
HashMap::from([("format".to_string(), s.to_string())]),
));
}
while let Some(c) = iter.peek() {
@ -212,15 +227,19 @@ impl FromStr for FormatOptions {
} else if let Ok(p) = precision.parse() {
options.precision = Some(p);
} else {
return Err(format!("invalid precision in format '{s}'"));
return Err(get_message_with_args(
"numfmt-error-invalid-precision",
HashMap::from([("format".to_string(), s.to_string())]),
));
}
}
if let Some('f') = iter.peek() {
iter.next();
} else {
return Err(format!(
"invalid format '{s}', directive must be %[0]['][-][N][.][N]f"
return Err(get_message_with_args(
"numfmt-error-invalid-format-directive",
HashMap::from([("format".to_string(), s.to_string())]),
));
}
@ -235,7 +254,10 @@ impl FromStr for FormatOptions {
}
iter.next();
} else {
return Err(format!("format '{s}' has too many % directives"));
return Err(get_message_with_args(
"numfmt-error-format-too-many-percent",
HashMap::from([("format".to_string(), s.to_string())]),
));
}
}
@ -252,7 +274,10 @@ impl FromStr for InvalidModes {
"fail" => Ok(Self::Fail),
"warn" => Ok(Self::Warn),
"ignore" => Ok(Self::Ignore),
unknown => Err(format!("Unknown invalid mode: {unknown}")),
unknown => Err(get_message_with_args(
"numfmt-error-unknown-invalid-mode",
HashMap::from([("mode".to_string(), unknown.to_string())]),
)),
}
}
}

Some files were not shown because too many files have changed in this diff Show more