diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..ef7519b88 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 5 diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index dd83c66df..9ca47e613 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -5,7 +5,7 @@ name: CICD # spell-checker:ignore (jargon) SHAs deps dequote softprops subshell toolchain # spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy # spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rsync rustc rustfmt rustup shopt xargs -# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend pell runtest tempfile testsuite uutils DESTDIR sizemulti Swatinem +# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend pell runtest tempfile testsuite uutils DESTDIR multisize Swatinem # ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45 @@ -433,6 +433,58 @@ jobs: make test + build_rust_stable: + name: Build/stable + needs: [ min_version, deps ] + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + - { os: macos-latest , features: feat_os_macos } + - { os: windows-latest , features: feat_os_windows } + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + default: true + profile: minimal # minimal component installation (ie, no documentation) + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + + build_rust_nightly: + name: Build/nightly + needs: [ min_version, deps ] + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + - { os: macos-latest , features: feat_os_macos } + - { os: windows-latest , features: feat_os_windows } + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + default: true + profile: minimal # minimal component installation (ie, no documentation) + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + compute_size: name: Binary sizes needs: [ min_version, deps ] @@ -473,8 +525,8 @@ jobs: --arg date "$(date --rfc-email)" \ --arg sha "$GITHUB_SHA" \ --arg size "$SIZE" \ - --arg sizemulti "$SIZEMULTI" \ - '{($date): { sha: $sha, size: $size, sizemulti: $sizemulti, }}' > size-result.json + --arg multisize "$SIZEMULTI" \ + '{($date): { sha: $sha, size: $size, multisize: $multisize, }}' > size-result.json - uses: actions/upload-artifact@v2 with: name: size-result @@ -505,7 +557,6 @@ jobs: #- { os: ubuntu-18.04 , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } #- { os: ubuntu-18.04 , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_macos } - - { os: windows-latest , target: i686-pc-windows-gnu , features: feat_os_windows } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } @@ -631,12 +682,8 @@ jobs: esac - name: rust toolchain ~ install uses: actions-rs/toolchain@v1 - # env: - # # Override auto-detection of RAM for Rustc install. - # # https://github.com/rust-lang/rustup/issues/2229#issuecomment-585855925 - # RUSTUP_UNPACK_RAM: "21474836480" with: - toolchain: ${{ steps.vars.outputs.TOOLCHAIN }} + toolchain: ${{ env.RUST_MIN_SRV }} target: ${{ matrix.job.target }} default: true profile: minimal # minimal component installation (ie, no documentation) @@ -688,18 +735,21 @@ jobs: use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }} command: build args: --release --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + toolchain: ${{ env.RUST_MIN_SRV }} - name: Test uses: actions-rs/cargo@v1 with: use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }} command: test args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + toolchain: ${{ env.RUST_MIN_SRV }} - name: Test individual utilities uses: actions-rs/cargo@v1 with: use-cross: ${{ steps.vars.outputs.CARGO_USE_CROSS }} command: test args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} + toolchain: ${{ env.RUST_MIN_SRV }} - name: Archive executable artifacts uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 9582b2af7..3b332a51c 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -201,7 +201,6 @@ jobs: gnu_coverage: name: Run GNU tests with coverage runs-on: ubuntu-latest - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} steps: - name: Checkout code uutil uses: actions/checkout@v2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a8a80315..ac2e0811e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to coreutils -Contributions are very welcome, and should target Rust's master branch until the +Contributions are very welcome, and should target Rust's main branch until the standard libraries are stabilized. You may *claim* an item on the to-do list by following these steps: diff --git a/Cargo.lock b/Cargo.lock index fd064dc76..a4e46ba99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -890,15 +890,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "unicode-width", -] - [[package]] name = "getrandom" version = "0.2.4" @@ -1153,9 +1144,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memmap2" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe3179b85e1fd8b14447cbebadb75e45a1002f541b925f0bfec366d56a81c56d" +checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" dependencies = [ "libc", ] @@ -1328,9 +1319,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "onig" @@ -1384,9 +1375,9 @@ dependencies = [ [[package]] name = "ouroboros" -version = "0.10.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84236d64f1718c387232287cf036eb6632a5ecff226f4ff9dccb8c2b79ba0bde" +checksum = "71643f290d126e18ac2598876d01e1d57aed164afc78fdb6e2a0c6589a1f6662" dependencies = [ "aliasable", "ouroboros_macro", @@ -1395,9 +1386,9 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.10.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f463857a6eb96c0136b1d56e56c718350cef30412ec065b48294799a088bca68" +checksum = "ed9a247206016d424fe8497bc611e510887af5c261fbbf977877c4bb55ca4d82" dependencies = [ "Inflector", "proc-macro-error", @@ -2009,9 +2000,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" [[package]] name = "strum_macros" @@ -2217,9 +2208,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" @@ -2241,9 +2232,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "unindent" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" +checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8" [[package]] name = "unix_socket" @@ -2394,7 +2385,6 @@ dependencies = [ "clap 3.0.10", "libc", "uucore", - "walkdir", ] [[package]] @@ -2905,7 +2895,6 @@ version = "0.0.12" dependencies = [ "chrono", "clap 3.0.10", - "getopts", "itertools", "quick-error", "regex", @@ -3181,6 +3170,7 @@ dependencies = [ "filetime", "time", "uucore", + "winapi 0.3.9", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4222f1749..43797e71e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -375,7 +375,7 @@ pretty_assertions = "1" rand = "0.8" regex = "1.0" sha1 = { version="0.10", features=["std"] } -tempfile = "3.2.0" +tempfile = "3" time = "0.1" unindent = "0.1" uucore = { version=">=0.0.11", package="uucore", path="src/uucore", features=["entries", "process"] } diff --git a/LICENSE b/LICENSE index 0177c4ab7..49fdbd4cf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) Jordi Boggiano +Copyright (c) Jordi Boggiano and many others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index d30fd4c05..ae217dc49 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils) [![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ) -[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/coreutils/blob/master/LICENSE) +[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/coreutils/blob/main/LICENSE) [![LOC](https://tokei.rs/b1/github/uutils/coreutils?category=code)](https://github.com/Aaronepower/tokei) [![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) @@ -410,12 +410,12 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). | link | | | | ln | | | | logname | | | -| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | -| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/master/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | +| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | | | | mkdir | | | | mkfifo | | | | mknod | | | diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..9dc8c6919 --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "extends": [ + "config:base" + ] +} diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index 85888c498..207100f59 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "arch ~ (uutils) display machine architecture" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/arch" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/arch" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/arch/LICENSE b/src/uu/arch/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/arch/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index cb8ccc3aa..0b695f0aa 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/base32" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/base32" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/base32/LICENSE b/src/uu/base32/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/base32/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index d30df608b..2e46ca8d5 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/base64" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/base64" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/base64/LICENSE b/src/uu/base64/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/base64/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index da93877c0..6426e5046 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/basename" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/basename" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/basename/LICENSE b/src/uu/basename/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/basename/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index 07a7b7f67..a0f652cee 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "basenc ~ (uutils) decode/encode input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/basenc" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/basenc" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/basenc/LICENSE b/src/uu/basenc/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/basenc/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index e1e95bc76..3eef5598b 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "cat ~ (uutils) concatenate and display input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/cat" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/cat" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/cat/LICENSE b/src/uu/cat/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/cat/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index a0a526d3b..75ce3fbd9 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -5,7 +5,7 @@ authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chcon" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chcon" keywords = ["coreutils", "uutils", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/chcon/LICENSE b/src/uu/chcon/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chcon/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 3800daa6c..ff60de836 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chgrp" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chgrp" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/chgrp/LICENSE b/src/uu/chgrp/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chgrp/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index 237368cf4..b2eb42574 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "chmod ~ (uutils) change mode of FILE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chmod" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chmod" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -18,7 +18,6 @@ path = "src/chmod.rs" clap = { version = "3.0", features = ["wrap_help", "cargo"] } libc = "0.2.42" uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode"] } -walkdir = "2.2" [[bin]] name = "chmod" diff --git a/src/uu/chmod/LICENSE b/src/uu/chmod/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chmod/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 84c5850d6..c2b51ae5e 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -18,7 +18,6 @@ use uucore::libc::mode_t; #[cfg(not(windows))] use uucore::mode; use uucore::{format_usage, show_error, InvalidEncodingHandling}; -use walkdir::WalkDir; static ABOUT: &str = "Change the mode of each FILE to MODE. With --reference, change the mode of each FILE to that of RFILE."; @@ -227,9 +226,19 @@ impl Chmoder { if !self.recursive { r = self.chmod_file(file).and(r); } else { - for entry in WalkDir::new(&filename).into_iter().filter_map(|e| e.ok()) { - let file = entry.path(); - r = self.chmod_file(file).and(r); + r = self.walk_dir(file); + } + } + r + } + + fn walk_dir(&self, file_path: &Path) -> UResult<()> { + let mut r = self.chmod_file(file_path); + if !is_symlink(file_path) && file_path.is_dir() { + for dir_entry in file_path.read_dir()? { + let path = dir_entry?.path(); + if !is_symlink(&path) { + r = self.walk_dir(path.as_path()); } } } diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index ed84d3fbb..b18310646 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chown" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chown" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/chown/LICENSE b/src/uu/chown/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chown/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index ef745009c..d6a9dd080 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chroot" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/chroot" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/chroot/LICENSE b/src/uu/chroot/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/chroot/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 5a6076648..6643a2bab 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/cksum" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/cksum" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/cksum/LICENSE b/src/uu/cksum/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/cksum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index 8d9babdcb..58868dfc5 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "comm ~ (uutils) compare sorted inputs" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/comm" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/comm" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/comm/LICENSE b/src/uu/comm/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/comm/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 221c02465..ffc008503 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT" description = "cp ~ (uutils) copy SOURCE to DESTINATION" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/cp" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/cp" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/cp/LICENSE b/src/uu/cp/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/cp/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 3673be931..a4a512d6b 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1245,7 +1245,8 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe } /// Copy the a file from `source` to `dest`. `source` will be dereferenced if -/// `options.dereference` is set to true. `dest` will always be dereferenced. +/// `options.dereference` is set to true. `dest` will be dereferenced only if +/// the source was not a symlink. /// /// Behavior when copying to existing files is contingent on the /// `options.overwrite` mode. If a file is skipped, the return type @@ -1295,13 +1296,32 @@ fn copy_file( let context = context.as_str(); // canonicalize dest and source so that later steps can work with the paths directly - let dest = canonicalize(dest, MissingHandling::Missing, ResolveMode::Physical).unwrap(); let source = if options.dereference { canonicalize(source, MissingHandling::Missing, ResolveMode::Physical).unwrap() } else { source.to_owned() }; + let source_file_type = fs::symlink_metadata(&source).context(context)?.file_type(); + let source_is_symlink = source_file_type.is_symlink(); + + #[cfg(unix)] + let source_is_fifo = source_file_type.is_fifo(); + #[cfg(not(unix))] + let source_is_fifo = false; + + let dest_already_exists_as_symlink = fs::symlink_metadata(&dest) + .map(|meta| meta.file_type().is_symlink()) + .unwrap_or(false); + + let dest = if !(source_is_symlink && dest_already_exists_as_symlink) { + canonicalize(dest, MissingHandling::Missing, ResolveMode::Physical).unwrap() + } else { + // Don't canonicalize a symlink copied over another symlink, because + // then we'll end up overwriting the destination's target. + dest.to_path_buf() + }; + let dest_permissions = if dest.exists() { dest.symlink_metadata().context(context)?.permissions() } else { @@ -1327,10 +1347,27 @@ fn copy_file( match options.copy_mode { CopyMode::Link => { + if dest.exists() { + let backup_path = + backup_control::get_backup_path(options.backup, &dest, &options.backup_suffix); + if let Some(backup_path) = backup_path { + backup_dest(&dest, &backup_path)?; + fs::remove_file(&dest)?; + } + } + fs::hard_link(&source, &dest).context(context)?; } CopyMode::Copy => { - copy_helper(&source, &dest, options, context, symlinked_files)?; + copy_helper( + &source, + &dest, + options, + context, + source_is_symlink, + source_is_fifo, + symlinked_files, + )?; } CopyMode::SymLink => { symlink_file(&source, &dest, context, symlinked_files)?; @@ -1346,10 +1383,26 @@ fn copy_file( if src_time <= dest_time { return Ok(()); } else { - copy_helper(&source, &dest, options, context, symlinked_files)?; + copy_helper( + &source, + &dest, + options, + context, + source_is_symlink, + source_is_fifo, + symlinked_files, + )?; } } else { - copy_helper(&source, &dest, options, context, symlinked_files)?; + copy_helper( + &source, + &dest, + options, + context, + source_is_symlink, + source_is_fifo, + symlinked_files, + )?; } } CopyMode::AttrOnly => { @@ -1388,6 +1441,8 @@ fn copy_helper( dest: &Path, options: &Options, context: &str, + source_is_symlink: bool, + source_is_fifo: bool, symlinked_files: &mut HashSet, ) -> CopyResult<()> { if options.parents { @@ -1395,23 +1450,15 @@ fn copy_helper( fs::create_dir_all(parent)?; } - let file_type = fs::symlink_metadata(&source)?.file_type(); - let is_symlink = file_type.is_symlink(); - - #[cfg(unix)] - let is_fifo = file_type.is_fifo(); - #[cfg(not(unix))] - let is_fifo = false; - if source.as_os_str() == "/dev/null" { /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 */ File::create(dest).context(dest.display().to_string())?; - } else if is_fifo && options.recursive { + } else if source_is_fifo && options.recursive { #[cfg(unix)] copy_fifo(dest, options.overwrite)?; - } else if is_symlink { + } else if source_is_symlink { copy_link(source, dest, symlinked_files)?; } else if options.reflink_mode != ReflinkMode::Never { #[cfg(not(any(target_os = "linux", target_os = "macos")))] diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index 79927594b..89fc118dd 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/ls" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/ls" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/csplit/LICENSE b/src/uu/csplit/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/csplit/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index dd27fa435..302f8f0c1 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/cut" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/cut" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/cut/LICENSE b/src/uu/cut/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/cut/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index 4ca628998..793022992 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "date ~ (uutils) display or set the current time" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/date" +repository = "https://github.com/uutils/coreutils/tree/main/src/date" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/date/LICENSE b/src/uu/date/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/date/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index afb61aded..139a4845d 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "dd ~ (uutils) copy and convert files" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/dd" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/dd" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -22,7 +22,7 @@ libc = "0.2" uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } [dev-dependencies] -tempfile = "^3" +tempfile = "3" [target.'cfg(target_os = "linux")'.dependencies] signal-hook = "0.3.9" diff --git a/src/uu/dd/LICENSE b/src/uu/dd/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/dd/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 081055cf9..d8639fca9 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -807,15 +807,16 @@ mod tests { #[test] fn test_parse_bytes_with_opt_multiplier() { assert_eq!(parse_bytes_with_opt_multiplier("123").unwrap(), 123); - assert_eq!(parse_bytes_with_opt_multiplier("123c").unwrap(), 123 * 1); + assert_eq!(parse_bytes_with_opt_multiplier("123c").unwrap(), 123); // 123 * 1 assert_eq!(parse_bytes_with_opt_multiplier("123w").unwrap(), 123 * 2); assert_eq!(parse_bytes_with_opt_multiplier("123b").unwrap(), 123 * 512); assert_eq!(parse_bytes_with_opt_multiplier("123x3").unwrap(), 123 * 3); assert_eq!(parse_bytes_with_opt_multiplier("123k").unwrap(), 123 * 1024); - assert_eq!(parse_bytes_with_opt_multiplier("1x2x3").unwrap(), 1 * 2 * 3); + assert_eq!(parse_bytes_with_opt_multiplier("1x2x3").unwrap(), 6); // 1 * 2 * 3 + assert_eq!( parse_bytes_with_opt_multiplier("1wx2cx3w").unwrap(), - (1 * 2) * (2 * 1) * (3 * 2) + 2 * 2 * (3 * 2) // (1 * 2) * (2 * 1) * (3 * 2) ); assert!(parse_bytes_with_opt_multiplier("123asdf").is_err()); } diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index d3d2fd395..29f32c6cf 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "df ~ (uutils) display file system information" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/df" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/df" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/df/LICENSE b/src/uu/df/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/df/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/df/src/blocks.rs b/src/uu/df/src/blocks.rs new file mode 100644 index 000000000..10ec22012 --- /dev/null +++ b/src/uu/df/src/blocks.rs @@ -0,0 +1,197 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +//! Types for representing and displaying block sizes. +use crate::{OPT_BLOCKSIZE, OPT_HUMAN_READABLE, OPT_HUMAN_READABLE_2}; +use clap::ArgMatches; +use std::fmt; +use std::num::ParseIntError; + +/// The first ten powers of 1024. +const IEC_BASES: [u128; 10] = [ + 1, + 1_024, + 1_048_576, + 1_073_741_824, + 1_099_511_627_776, + 1_125_899_906_842_624, + 1_152_921_504_606_846_976, + 1_180_591_620_717_411_303_424, + 1_208_925_819_614_629_174_706_176, + 1_237_940_039_285_380_274_899_124_224, +]; + +/// Suffixes for the first nine multi-byte unit suffixes. +const SUFFIXES: [char; 9] = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; + +/// Convert a multiple of 1024 into a string like "12K" or "34M". +/// +/// # Examples +/// +/// Powers of 1024 become "1K", "1M", "1G", etc. +/// +/// ```rust,ignore +/// assert_eq!(to_magnitude_and_suffix_1024(1024).unwrap(), "1K"); +/// assert_eq!(to_magnitude_and_suffix_1024(1024 * 1024).unwrap(), "1M"); +/// assert_eq!(to_magnitude_and_suffix_1024(1024 * 1024 * 1024).unwrap(), "1G"); +/// ``` +/// +/// Multiples of those powers affect the magnitude part of the +/// returned string: +/// +/// ```rust,ignore +/// assert_eq!(to_magnitude_and_suffix_1024(123 * 1024).unwrap(), "123K"); +/// assert_eq!(to_magnitude_and_suffix_1024(456 * 1024 * 1024).unwrap(), "456M"); +/// assert_eq!(to_magnitude_and_suffix_1024(789 * 1024 * 1024 * 1024).unwrap(), "789G"); +/// ``` +fn to_magnitude_and_suffix_1024(n: u128) -> Result { + // Find the smallest power of 1024 that is larger than `n`. That + // number indicates which units and suffix to use. + for i in 0..IEC_BASES.len() - 1 { + if n < IEC_BASES[i + 1] { + return Ok(format!("{}{}", n / IEC_BASES[i], SUFFIXES[i])); + } + } + Err(()) +} + +/// Convert a number into a magnitude and a multi-byte unit suffix. +/// +/// # Errors +/// +/// If the number is too large to represent. +fn to_magnitude_and_suffix(n: u128) -> Result { + if n % 1024 == 0 { + to_magnitude_and_suffix_1024(n) + } else { + // TODO Implement this, probably using code from `numfmt`. + Ok("1kB".into()) + } +} + +/// A block size to use in condensing the display of a large number of bytes. +/// +/// The [`BlockSize::Bytes`] variant represents a static block +/// size. The [`BlockSize::HumanReadableDecimal`] and +/// [`BlockSize::HumanReadableBinary`] variants represent dynamic +/// block sizes: as the number of bytes increases, the divisor +/// increases as well (for example, from 1 to 1,000 to 1,000,000 and +/// so on in the case of [`BlockSize::HumanReadableDecimal`]). +/// +/// The default variant is `Bytes(1024)`. +pub(crate) enum BlockSize { + /// A fixed number of bytes. + /// + /// The number must be positive. + Bytes(u64), + + /// Use the largest divisor corresponding to a unit, like B, K, M, G, etc. + /// + /// This variant represents powers of 1,000. Contrast with + /// [`BlockSize::HumanReadableBinary`], which represents powers of + /// 1,024. + HumanReadableDecimal, + + /// Use the largest divisor corresponding to a unit, like B, K, M, G, etc. + /// + /// This variant represents powers of 1,024. Contrast with + /// [`BlockSize::HumanReadableDecimal`], which represents powers + /// of 1,000. + HumanReadableBinary, +} + +impl Default for BlockSize { + fn default() -> Self { + Self::Bytes(1024) + } +} + +pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result { + if matches.is_present(OPT_HUMAN_READABLE) { + Ok(BlockSize::HumanReadableBinary) + } else if matches.is_present(OPT_HUMAN_READABLE_2) { + Ok(BlockSize::HumanReadableDecimal) + } else if matches.is_present(OPT_BLOCKSIZE) { + let s = matches.value_of(OPT_BLOCKSIZE).unwrap(); + Ok(BlockSize::Bytes(s.parse()?)) + } else { + Ok(Default::default()) + } +} + +impl fmt::Display for BlockSize { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::HumanReadableBinary => write!(f, "Size"), + Self::HumanReadableDecimal => write!(f, "Size"), + Self::Bytes(n) => match to_magnitude_and_suffix(*n as u128) { + Ok(s) => write!(f, "{}-blocks", s), + Err(_) => Err(fmt::Error), + }, + } + } +} + +#[cfg(test)] +mod tests { + + use crate::blocks::{to_magnitude_and_suffix, BlockSize}; + + #[test] + fn test_to_magnitude_and_suffix_powers_of_1024() { + assert_eq!(to_magnitude_and_suffix(1024).unwrap(), "1K"); + assert_eq!(to_magnitude_and_suffix(2048).unwrap(), "2K"); + assert_eq!(to_magnitude_and_suffix(4096).unwrap(), "4K"); + assert_eq!(to_magnitude_and_suffix(1024 * 1024).unwrap(), "1M"); + assert_eq!(to_magnitude_and_suffix(2 * 1024 * 1024).unwrap(), "2M"); + assert_eq!(to_magnitude_and_suffix(1024 * 1024 * 1024).unwrap(), "1G"); + assert_eq!( + to_magnitude_and_suffix(34 * 1024 * 1024 * 1024).unwrap(), + "34G" + ); + } + + // TODO We have not yet implemented this behavior, but when we do, + // uncomment this test. + + // #[test] + // fn test_to_magnitude_and_suffix_not_powers_of_1024() { + // assert_eq!(to_magnitude_and_suffix(1).unwrap(), "1B"); + // assert_eq!(to_magnitude_and_suffix(999).unwrap(), "999B"); + + // assert_eq!(to_magnitude_and_suffix(1000).unwrap(), "1kB"); + // assert_eq!(to_magnitude_and_suffix(1001).unwrap(), "1.1kB"); + // assert_eq!(to_magnitude_and_suffix(1023).unwrap(), "1.1kB"); + // assert_eq!(to_magnitude_and_suffix(1025).unwrap(), "1.1kB"); + // assert_eq!(to_magnitude_and_suffix(999_000).unwrap(), "999kB"); + + // assert_eq!(to_magnitude_and_suffix(999_001).unwrap(), "1MB"); + // assert_eq!(to_magnitude_and_suffix(999_999).unwrap(), "1MB"); + // assert_eq!(to_magnitude_and_suffix(1_000_000).unwrap(), "1MB"); + // assert_eq!(to_magnitude_and_suffix(1_000_001).unwrap(), "1.1MB"); + // assert_eq!(to_magnitude_and_suffix(1_100_000).unwrap(), "1.1MB"); + // assert_eq!(to_magnitude_and_suffix(1_100_001).unwrap(), "1.2MB"); + // assert_eq!(to_magnitude_and_suffix(1_900_000).unwrap(), "1.9MB"); + // assert_eq!(to_magnitude_and_suffix(1_900_001).unwrap(), "2MB"); + // assert_eq!(to_magnitude_and_suffix(9_900_000).unwrap(), "9.9MB"); + // assert_eq!(to_magnitude_and_suffix(9_900_001).unwrap(), "10MB"); + // assert_eq!(to_magnitude_and_suffix(999_000_000).unwrap(), "999MB"); + + // assert_eq!(to_magnitude_and_suffix(999_000_001).unwrap(), "1GB"); + // assert_eq!(to_magnitude_and_suffix(1_000_000_000).unwrap(), "1GB"); + // // etc. + // } + + #[test] + fn test_block_size_display() { + assert_eq!(format!("{}", BlockSize::HumanReadableBinary), "Size"); + assert_eq!(format!("{}", BlockSize::HumanReadableDecimal), "Size"); + assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K-blocks"); + assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K-blocks"); + assert_eq!( + format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), + "3M-blocks" + ); + } +} diff --git a/src/uu/df/src/columns.rs b/src/uu/df/src/columns.rs new file mode 100644 index 000000000..89dd35220 --- /dev/null +++ b/src/uu/df/src/columns.rs @@ -0,0 +1,166 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +// spell-checker:ignore itotal iused iavail ipcent pcent squashfs +use crate::{OPT_INODES, OPT_OUTPUT, OPT_PRINT_TYPE}; +use clap::ArgMatches; + +/// The columns in the output table produced by `df`. +/// +/// The [`Row`] struct has a field corresponding to each of the +/// variants of this enumeration. +/// +/// [`Row`]: crate::table::Row +#[derive(PartialEq, Copy, Clone)] +pub(crate) enum Column { + /// The source of the mount point, usually a device. + Source, + + /// Total number of blocks. + Size, + + /// Number of used blocks. + Used, + + /// Number of available blocks. + Avail, + + /// Percentage of blocks used out of total number of blocks. + Pcent, + + /// The mount point. + Target, + + /// Total number of inodes. + Itotal, + + /// Number of used inodes. + Iused, + + /// Number of available inodes. + Iavail, + + /// Percentage of inodes used out of total number of inodes. + Ipcent, + + /// The filename given as a command-line argument. + File, + + /// The filesystem type, like "ext4" or "squashfs". + Fstype, + + /// Percentage of bytes available to non-privileged processes. + #[cfg(target_os = "macos")] + Capacity, +} + +impl Column { + /// Convert from command-line arguments to sequence of columns. + /// + /// The set of columns that will appear in the output table can be + /// specified by command-line arguments. This function converts + /// those arguments to a [`Vec`] of [`Column`] variants. + pub(crate) fn from_matches(matches: &ArgMatches) -> Vec { + match ( + matches.is_present(OPT_PRINT_TYPE), + matches.is_present(OPT_INODES), + matches.occurrences_of(OPT_OUTPUT) > 0, + ) { + (false, false, false) => vec![ + Self::Source, + Self::Size, + Self::Used, + Self::Avail, + #[cfg(target_os = "macos")] + Self::Capacity, + Self::Pcent, + Self::Target, + ], + (false, false, true) => { + matches + .values_of(OPT_OUTPUT) + .unwrap() + .map(|s| { + // Unwrapping here should not panic because the + // command-line argument parsing library should be + // responsible for ensuring each comma-separated + // string is a valid column label. + Self::parse(s).unwrap() + }) + .collect() + } + (false, true, false) => vec![ + Self::Source, + Self::Itotal, + Self::Iused, + Self::Iavail, + Self::Ipcent, + Self::Target, + ], + (true, false, false) => vec![ + Self::Source, + Self::Fstype, + Self::Size, + Self::Used, + Self::Avail, + #[cfg(target_os = "macos")] + Self::Capacity, + Self::Pcent, + Self::Target, + ], + (true, true, false) => vec![ + Self::Source, + Self::Fstype, + Self::Itotal, + Self::Iused, + Self::Iavail, + Self::Ipcent, + Self::Target, + ], + // The command-line arguments -T and -i are each mutually + // exclusive with --output, so the command-line argument + // parser should reject those combinations before we get + // to this point in the code. + _ => unreachable!(), + } + } + + /// Convert a column name to the corresponding enumeration variant. + /// + /// There are twelve valid column names, one for each variant: + /// + /// - "source" + /// - "fstype" + /// - "itotal" + /// - "iused" + /// - "iavail" + /// - "ipcent" + /// - "size" + /// - "used" + /// - "avail" + /// - "pcent" + /// - "file" + /// - "target" + /// + /// # Errors + /// + /// If the string `s` is not one of the valid column names. + fn parse(s: &str) -> Result { + match s { + "source" => Ok(Self::Source), + "fstype" => Ok(Self::Fstype), + "itotal" => Ok(Self::Itotal), + "iused" => Ok(Self::Iused), + "iavail" => Ok(Self::Iavail), + "ipcent" => Ok(Self::Ipcent), + "size" => Ok(Self::Size), + "used" => Ok(Self::Used), + "avail" => Ok(Self::Avail), + "pcent" => Ok(Self::Pcent), + "file" => Ok(Self::File), + "target" => Ok(Self::Target), + _ => Err(()), + } + } +} diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index b7d27e245..b877faa72 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -5,22 +5,24 @@ // // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -// spell-checker:ignore itotal iused iavail ipcent pcent +// spell-checker:ignore itotal iused iavail ipcent pcent tmpfs squashfs lofs +mod blocks; +mod columns; +mod filesystem; mod table; -#[cfg(unix)] -use uucore::fsext::statfs; -use uucore::fsext::{read_fs_list, FsUsage, MountInfo}; -use uucore::{error::UResult, format_usage}; +use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; +use uucore::fsext::{read_fs_list, MountInfo}; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; -use std::collections::HashSet; -use std::iter::FromIterator; - -#[cfg(windows)] +use std::fmt; use std::path::Path; +use crate::blocks::{block_size_from_matches, BlockSize}; +use crate::columns::Column; +use crate::filesystem::Filesystem; use crate::table::{DisplayRow, Header, Row}; static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\ @@ -49,145 +51,93 @@ static OUTPUT_FIELD_LIST: [&str; 12] = [ "file", "target", ]; -/// Store names of file systems as a selector. -/// Note: `exclude` takes priority over `include`. -#[derive(Default)] -struct FsSelector { - include: HashSet, - exclude: HashSet, -} - -/// A block size to use in condensing the display of a large number of bytes. +/// Parameters that control the behavior of `df`. /// -/// The [`BlockSize::Bytes`] variant represents a static block -/// size. The [`BlockSize::HumanReadableDecimal`] and -/// [`BlockSize::HumanReadableBinary`] variants represent dynamic -/// block sizes: as the number of bytes increases, the divisor -/// increases as well (for example, from 1 to 1,000 to 1,000,000 and -/// so on in the case of [`BlockSize::HumanReadableDecimal`]). -/// -/// The default variant is `Bytes(1024)`. -enum BlockSize { - /// A fixed number of bytes. - /// - /// The number must be positive. - Bytes(u64), - - /// Use the largest divisor corresponding to a unit, like B, K, M, G, etc. - /// - /// This variant represents powers of 1,000. Contrast with - /// [`BlockSize::HumanReadableBinary`], which represents powers of - /// 1,024. - HumanReadableDecimal, - - /// Use the largest divisor corresponding to a unit, like B, K, M, G, etc. - /// - /// This variant represents powers of 1,024. Contrast with - /// [`BlockSize::HumanReadableDecimal`], which represents powers - /// of 1,000. - HumanReadableBinary, -} - -impl Default for BlockSize { - fn default() -> Self { - Self::Bytes(1024) - } -} - -impl From<&ArgMatches> for BlockSize { - fn from(matches: &ArgMatches) -> Self { - if matches.is_present(OPT_HUMAN_READABLE) { - Self::HumanReadableBinary - } else if matches.is_present(OPT_HUMAN_READABLE_2) { - Self::HumanReadableDecimal - } else { - Self::default() - } - } -} - -#[derive(Default)] +/// Most of these parameters control which rows and which columns are +/// displayed. The `block_size` determines the units to use when +/// displaying numbers of bytes or inodes. struct Options { show_local_fs: bool, show_all_fs: bool, show_listed_fs: bool, - show_fs_type: bool, - show_inode_instead: bool, block_size: BlockSize, - fs_selector: FsSelector, + + /// Optional list of filesystem types to include in the output table. + /// + /// If this is not `None`, only filesystems that match one of + /// these types will be listed. + include: Option>, + + /// Optional list of filesystem types to exclude from the output table. + /// + /// If this is not `None`, filesystems that match one of these + /// types will *not* be listed. + exclude: Option>, + + /// Whether to show a final row comprising the totals for each column. + show_total: bool, + + /// Sequence of columns to display in the output table. + columns: Vec, +} + +impl Default for Options { + fn default() -> Self { + Self { + show_local_fs: Default::default(), + show_all_fs: Default::default(), + show_listed_fs: Default::default(), + block_size: Default::default(), + include: Default::default(), + exclude: Default::default(), + show_total: Default::default(), + columns: vec![ + Column::Source, + Column::Size, + Column::Used, + Column::Avail, + Column::Pcent, + Column::Target, + ], + } + } +} + +enum OptionsError { + InvalidBlockSize, +} + +impl fmt::Display for OptionsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + // TODO This should include the raw string provided as the argument. + // + // TODO This needs to vary based on whether `--block-size` + // or `-B` were provided. + Self::InvalidBlockSize => write!(f, "invalid --block-size argument"), + } + } } impl Options { /// Convert command-line arguments into [`Options`]. - fn from(matches: &ArgMatches) -> Self { - Self { + fn from(matches: &ArgMatches) -> Result { + Ok(Self { show_local_fs: matches.is_present(OPT_LOCAL), show_all_fs: matches.is_present(OPT_ALL), show_listed_fs: false, - show_fs_type: matches.is_present(OPT_PRINT_TYPE), - show_inode_instead: matches.is_present(OPT_INODES), - block_size: BlockSize::from(matches), - fs_selector: FsSelector::from(matches), - } - } -} - -#[derive(Debug, Clone)] -struct Filesystem { - mount_info: MountInfo, - usage: FsUsage, -} - -impl FsSelector { - /// Convert command-line arguments into a [`FsSelector`]. - /// - /// This function reads the include and exclude sets from - /// [`ArgMatches`] and returns the corresponding [`FsSelector`] - /// instance. - fn from(matches: &ArgMatches) -> Self { - let include = HashSet::from_iter(matches.values_of_lossy(OPT_TYPE).unwrap_or_default()); - let exclude = HashSet::from_iter( - matches - .values_of_lossy(OPT_EXCLUDE_TYPE) - .unwrap_or_default(), - ); - Self { include, exclude } - } - - fn should_select(&self, fs_type: &str) -> bool { - if self.exclude.contains(fs_type) { - return false; - } - self.include.is_empty() || self.include.contains(fs_type) - } -} - -impl Filesystem { - // TODO: resolve uuid in `mount_info.dev_name` if exists - fn new(mount_info: MountInfo) -> Option { - let _stat_path = if !mount_info.mount_dir.is_empty() { - mount_info.mount_dir.clone() - } else { - #[cfg(unix)] - { - mount_info.dev_name.clone() - } - #[cfg(windows)] - { - // On windows, we expect the volume id - mount_info.dev_id.clone() - } - }; - #[cfg(unix)] - let usage = FsUsage::new(statfs(_stat_path).ok()?); - #[cfg(windows)] - let usage = FsUsage::new(Path::new(&_stat_path)); - Some(Self { mount_info, usage }) + block_size: block_size_from_matches(matches) + .map_err(|_| OptionsError::InvalidBlockSize)?, + include: matches.values_of_lossy(OPT_TYPE), + exclude: matches.values_of_lossy(OPT_EXCLUDE_TYPE), + show_total: matches.is_present(OPT_TOTAL), + columns: Column::from_matches(matches), + }) } } /// Whether to display the mount info given the inclusion settings. -fn is_included(mi: &MountInfo, paths: &[String], opt: &Options) -> bool { +fn is_included(mi: &MountInfo, opt: &Options) -> bool { // Don't show remote filesystems if `--local` has been given. if mi.remote && opt.show_local_fs { return false; @@ -199,14 +149,15 @@ fn is_included(mi: &MountInfo, paths: &[String], opt: &Options) -> bool { } // Don't show filesystems if they have been explicitly excluded. - if !opt.fs_selector.should_select(&mi.fs_type) { - return false; + if let Some(ref excludes) = opt.exclude { + if excludes.contains(&mi.fs_type) { + return false; + } } - - // Don't show filesystems other than the ones specified on the - // command line, if any. - if !paths.is_empty() && !paths.contains(&mi.mount_dir) { - return false; + if let Some(ref includes) = opt.include { + if !includes.contains(&mi.fs_type) { + return false; + } } true @@ -258,15 +209,13 @@ fn is_best(previous: &[MountInfo], mi: &MountInfo) -> bool { /// Keep only the specified subset of [`MountInfo`] instances. /// -/// If `paths` is non-empty, this function excludes any [`MountInfo`] -/// that is not mounted at the specified path. -/// /// The `opt` argument specifies a variety of ways of excluding /// [`MountInfo`] instances; see [`Options`] for more information. /// /// Finally, if there are duplicate entries, the one with the shorter /// path is kept. -fn filter_mount_list(vmi: Vec, paths: &[String], opt: &Options) -> Vec { + +fn filter_mount_list(vmi: Vec, opt: &Options) -> Vec { let mut result = vec![]; for mi in vmi { // TODO The running time of the `is_best()` function is linear @@ -274,21 +223,45 @@ fn filter_mount_list(vmi: Vec, paths: &[String], opt: &Options) -> Ve // this loop quadratic in the length of `vmi`. This could be // improved by a more efficient implementation of `is_best()`, // but `vmi` is probably not very long in practice. - if is_included(&mi, paths, opt) && is_best(&result, &mi) { + if is_included(&mi, opt) && is_best(&result, &mi) { result.push(mi); } } result } +/// Assign 1 `MountInfo` entry to each path +/// `lofs` entries are skipped and dummy mount points are skipped +/// Only the longest matching prefix for that path is considered +/// `lofs` is for Solaris style loopback filesystem and is present in Solaris and FreeBSD. +/// It works similar to symlinks +fn get_point_list(vmi: &[MountInfo], paths: &[String]) -> Vec { + paths + .iter() + .map(|p| { + vmi.iter() + .filter(|mi| mi.fs_type.ne("lofs")) + .filter(|mi| !mi.dummy) + .filter(|mi| p.starts_with(&mi.mount_dir)) + .max_by_key(|mi| mi.mount_dir.len()) + .unwrap() + .clone() + }) + .collect::>() +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from(args); - let paths: Vec = matches + // Canonicalize the input_paths and then convert to string + let paths = matches .values_of(OPT_PATHS) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); + .unwrap_or_default() + .map(Path::new) + .filter_map(|v| v.canonicalize().ok()) + .filter_map(|v| v.into_os_string().into_string().ok()) + .collect::>(); #[cfg(windows)] { @@ -298,18 +271,32 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - let opt = Options::from(&matches); + let opt = Options::from(&matches).map_err(|e| USimpleError::new(1, format!("{}", e)))?; let mounts = read_fs_list(); - let data: Vec = filter_mount_list(mounts, &paths, &opt) + + let op_mount_points: Vec = if paths.is_empty() { + // Get all entries + filter_mount_list(mounts, &opt) + } else { + // Get Point for each input_path + get_point_list(&mounts, &paths) + }; + let data: Vec = op_mount_points .into_iter() .filter_map(Filesystem::new) .filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs) .map(Into::into) .collect(); + println!("{}", Header::new(&opt)); + let mut total = Row::new("total"); for row in data { - println!("{}", DisplayRow::new(row, &opt)); + println!("{}", DisplayRow::new(&row, &opt)); + total += row; + } + if opt.show_total { + println!("{}", DisplayRow::new(&total, &opt)); } Ok(()) @@ -431,3 +418,268 @@ pub fn uu_app<'a>() -> App<'a> { ) .arg(Arg::new(OPT_PATHS).multiple_occurrences(true)) } + +#[cfg(test)] +mod tests { + + mod mount_info_lt { + + use crate::mount_info_lt; + use uucore::fsext::MountInfo; + + /// Instantiate a [`MountInfo`] with the given fields. + fn mount_info(dev_name: &str, mount_root: &str, mount_dir: &str) -> MountInfo { + MountInfo { + dev_id: String::new(), + dev_name: String::from(dev_name), + fs_type: String::new(), + mount_dir: String::from(mount_dir), + mount_option: String::new(), + mount_root: String::from(mount_root), + remote: false, + dummy: false, + } + } + + #[test] + fn test_absolute() { + // Prefer device name "/dev/foo" over "dev_foo". + let m1 = mount_info("/dev/foo", "/", "/mnt/bar"); + let m2 = mount_info("dev_foo", "/", "/mnt/bar"); + assert!(!mount_info_lt(&m1, &m2)); + } + + #[test] + fn test_shorter() { + // Prefer mount directory "/mnt/bar" over "/mnt/bar/baz"... + let m1 = mount_info("/dev/foo", "/", "/mnt/bar"); + let m2 = mount_info("/dev/foo", "/", "/mnt/bar/baz"); + assert!(!mount_info_lt(&m1, &m2)); + + // ..but prefer mount root "/root" over "/". + let m1 = mount_info("/dev/foo", "/root", "/mnt/bar"); + let m2 = mount_info("/dev/foo", "/", "/mnt/bar/baz"); + assert!(mount_info_lt(&m1, &m2)); + } + + #[test] + fn test_over_mounted() { + // Prefer the earlier entry if the devices are different but + // the mount directory is the same. + let m1 = mount_info("/dev/foo", "/", "/mnt/baz"); + let m2 = mount_info("/dev/bar", "/", "/mnt/baz"); + assert!(!mount_info_lt(&m1, &m2)); + } + } + + mod is_best { + + use crate::is_best; + use uucore::fsext::MountInfo; + + /// Instantiate a [`MountInfo`] with the given fields. + fn mount_info(dev_id: &str, mount_dir: &str) -> MountInfo { + MountInfo { + dev_id: String::from(dev_id), + dev_name: String::new(), + fs_type: String::new(), + mount_dir: String::from(mount_dir), + mount_option: String::new(), + mount_root: String::new(), + remote: false, + dummy: false, + } + } + + #[test] + fn test_empty() { + let m = mount_info("0", "/mnt/bar"); + assert!(is_best(&[], &m)); + } + + #[test] + fn test_different_dev_id() { + let m1 = mount_info("0", "/mnt/bar"); + let m2 = mount_info("1", "/mnt/bar"); + assert!(is_best(&[m1.clone()], &m2)); + assert!(is_best(&[m2], &m1)); + } + + #[test] + fn test_same_dev_id() { + // There are several conditions under which a `MountInfo` is + // considered "better" than the others, we're just checking + // one condition in this test. + let m1 = mount_info("0", "/mnt/bar"); + let m2 = mount_info("0", "/mnt/bar/baz"); + assert!(!is_best(&[m1.clone()], &m2)); + assert!(is_best(&[m2], &m1)); + } + } + + mod is_included { + + use crate::{is_included, Options}; + use uucore::fsext::MountInfo; + + /// Instantiate a [`MountInfo`] with the given fields. + fn mount_info(fs_type: &str, mount_dir: &str, remote: bool, dummy: bool) -> MountInfo { + MountInfo { + dev_id: String::new(), + dev_name: String::new(), + fs_type: String::from(fs_type), + mount_dir: String::from(mount_dir), + mount_option: String::new(), + mount_root: String::new(), + remote, + dummy, + } + } + + #[test] + fn test_remote_included() { + let opt = Default::default(); + let m = mount_info("ext4", "/mnt/foo", true, false); + assert!(is_included(&m, &opt)); + } + + #[test] + fn test_remote_excluded() { + let opt = Options { + show_local_fs: true, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", true, false); + assert!(!is_included(&m, &opt)); + } + + #[test] + fn test_dummy_included() { + let opt = Options { + show_all_fs: true, + show_listed_fs: true, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", false, true); + assert!(is_included(&m, &opt)); + } + + #[test] + fn test_dummy_excluded() { + let opt = Default::default(); + let m = mount_info("ext4", "/mnt/foo", false, true); + assert!(!is_included(&m, &opt)); + } + + #[test] + fn test_exclude_match() { + let exclude = Some(vec![String::from("ext4")]); + let opt = Options { + exclude, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", false, false); + assert!(!is_included(&m, &opt)); + } + + #[test] + fn test_exclude_no_match() { + let exclude = Some(vec![String::from("tmpfs")]); + let opt = Options { + exclude, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", false, false); + assert!(is_included(&m, &opt)); + } + + #[test] + fn test_include_match() { + let include = Some(vec![String::from("ext4")]); + let opt = Options { + include, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", false, false); + assert!(is_included(&m, &opt)); + } + + #[test] + fn test_include_no_match() { + let include = Some(vec![String::from("tmpfs")]); + let opt = Options { + include, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", false, false); + assert!(!is_included(&m, &opt)); + } + + #[test] + fn test_include_and_exclude_match_neither() { + let include = Some(vec![String::from("tmpfs")]); + let exclude = Some(vec![String::from("squashfs")]); + let opt = Options { + include, + exclude, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", false, false); + assert!(!is_included(&m, &opt)); + } + + #[test] + fn test_include_and_exclude_match_exclude() { + let include = Some(vec![String::from("tmpfs")]); + let exclude = Some(vec![String::from("ext4")]); + let opt = Options { + include, + exclude, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", false, false); + assert!(!is_included(&m, &opt)); + } + + #[test] + fn test_include_and_exclude_match_include() { + let include = Some(vec![String::from("ext4")]); + let exclude = Some(vec![String::from("squashfs")]); + let opt = Options { + include, + exclude, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", false, false); + assert!(is_included(&m, &opt)); + } + + #[test] + fn test_include_and_exclude_match_both() { + // TODO The same filesystem type in both `include` and + // `exclude` should cause an error, but currently does + // not. + let include = Some(vec![String::from("ext4")]); + let exclude = Some(vec![String::from("ext4")]); + let opt = Options { + include, + exclude, + ..Default::default() + }; + let m = mount_info("ext4", "/mnt/foo", false, false); + assert!(!is_included(&m, &opt)); + } + } + + mod filter_mount_list { + + use crate::filter_mount_list; + + #[test] + fn test_empty() { + let opt = Default::default(); + let mount_infos = vec![]; + assert!(filter_mount_list(mount_infos, &opt).is_empty()); + } + } +} diff --git a/src/uu/df/src/filesystem.rs b/src/uu/df/src/filesystem.rs new file mode 100644 index 000000000..abea48fad --- /dev/null +++ b/src/uu/df/src/filesystem.rs @@ -0,0 +1,55 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +//! Provides a summary representation of a filesystem. +//! +//! A [`Filesystem`] struct represents a device containing a +//! filesystem mounted at a particular directory. It also includes +//! information on amount of space available and amount of space used. +#[cfg(windows)] +use std::path::Path; + +#[cfg(unix)] +use uucore::fsext::statfs; +use uucore::fsext::{FsUsage, MountInfo}; + +/// Summary representation of a filesystem. +/// +/// A [`Filesystem`] struct represents a device containing a +/// filesystem mounted at a particular directory. The +/// [`Filesystem::mount_info`] field exposes that information. The +/// [`Filesystem::usage`] field provides information on the amount of +/// space available on the filesystem and the amount of space used. +#[derive(Debug, Clone)] +pub(crate) struct Filesystem { + /// Information about the mounted device, mount directory, and related options. + pub mount_info: MountInfo, + + /// Information about the amount of space used on the filesystem. + pub usage: FsUsage, +} + +impl Filesystem { + // TODO: resolve uuid in `mount_info.dev_name` if exists + pub(crate) fn new(mount_info: MountInfo) -> Option { + let _stat_path = if !mount_info.mount_dir.is_empty() { + mount_info.mount_dir.clone() + } else { + #[cfg(unix)] + { + mount_info.dev_name.clone() + } + #[cfg(windows)] + { + // On windows, we expect the volume id + mount_info.dev_id.clone() + } + }; + #[cfg(unix)] + let usage = FsUsage::new(statfs(_stat_path).ok()?); + #[cfg(windows)] + let usage = FsUsage::new(Path::new(&_stat_path)); + Some(Self { mount_info, usage }) + } +} diff --git a/src/uu/df/src/table.rs b/src/uu/df/src/table.rs index 2f7fba456..a126876dd 100644 --- a/src/uu/df/src/table.rs +++ b/src/uu/df/src/table.rs @@ -2,7 +2,7 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore tmpfs +// spell-checker:ignore tmpfs Pcent Itotal Iused Iavail Ipcent //! The filesystem usage data table. //! //! A table comprises a header row ([`Header`]) and a collection of @@ -11,10 +11,13 @@ //! [`DisplayRow`] implements [`std::fmt::Display`]. use number_prefix::NumberPrefix; -use crate::{BlockSize, Filesystem, Options}; +use crate::columns::Column; +use crate::filesystem::Filesystem; +use crate::{BlockSize, Options}; use uucore::fsext::{FsUsage, MountInfo}; use std::fmt; +use std::ops::AddAssign; /// A row in the filesystem usage data table. /// @@ -67,6 +70,63 @@ pub(crate) struct Row { inodes_usage: Option, } +impl Row { + pub(crate) fn new(source: &str) -> Self { + Self { + fs_device: source.into(), + fs_type: "-".into(), + fs_mount: "-".into(), + bytes: 0, + bytes_used: 0, + bytes_free: 0, + bytes_usage: None, + #[cfg(target_os = "macos")] + bytes_capacity: None, + inodes: 0, + inodes_used: 0, + inodes_free: 0, + inodes_usage: None, + } + } +} + +impl AddAssign for Row { + /// Sum the numeric values of two rows. + /// + /// The `Row::fs_device` field is set to `"total"` and the + /// remaining `String` fields are set to `"-"`. + fn add_assign(&mut self, rhs: Self) { + let bytes = self.bytes + rhs.bytes; + let bytes_used = self.bytes_used + rhs.bytes_used; + let inodes = self.inodes + rhs.inodes; + let inodes_used = self.inodes_used + rhs.inodes_used; + *self = Self { + fs_device: "total".into(), + fs_type: "-".into(), + fs_mount: "-".into(), + bytes, + bytes_used, + bytes_free: self.bytes_free + rhs.bytes_free, + bytes_usage: if bytes == 0 { + None + } else { + Some(bytes_used as f64 / bytes as f64) + }, + // TODO Figure out how to compute this. + #[cfg(target_os = "macos")] + bytes_capacity: None, + inodes, + inodes_used, + inodes_free: self.inodes_free + rhs.inodes_free, + inodes_usage: if inodes == 0 { + None + } else { + Some(inodes_used as f64 / inodes as f64) + }, + } + } +} + impl From for Row { fn from(fs: Filesystem) -> Self { let MountInfo { @@ -120,7 +180,7 @@ impl From for Row { /// The `options` control how the information in the row gets displayed. pub(crate) struct DisplayRow<'a> { /// The data in this row. - row: Row, + row: &'a Row, /// Options that control how to display the data. options: &'a Options, @@ -135,7 +195,7 @@ pub(crate) struct DisplayRow<'a> { impl<'a> DisplayRow<'a> { /// Instantiate this struct. - pub(crate) fn new(row: Row, options: &'a Options) -> Self { + pub(crate) fn new(row: &'a Row, options: &'a Options) -> Self { Self { row, options } } @@ -164,59 +224,40 @@ impl<'a> DisplayRow<'a> { fn percentage(fraction: Option) -> String { match fraction { None => "-".to_string(), - Some(x) => format!("{:.0}%", 100.0 * x), + Some(x) => format!("{:.0}%", (100.0 * x).ceil()), } } - - /// Write the bytes data for this row. - /// - /// # Errors - /// - /// If there is a problem writing to `f`. - /// - /// If the scaling factor is not 1000, 1024, or a negative number. - fn fmt_bytes(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{0: >12} ", self.scaled(self.row.bytes)?)?; - write!(f, "{0: >12} ", self.scaled(self.row.bytes_used)?)?; - write!(f, "{0: >12} ", self.scaled(self.row.bytes_free)?)?; - #[cfg(target_os = "macos")] - write!( - f, - "{0: >12} ", - DisplayRow::percentage(self.row.bytes_capacity) - )?; - write!(f, "{0: >5} ", DisplayRow::percentage(self.row.bytes_usage))?; - Ok(()) - } - - /// Write the inodes data for this row. - /// - /// # Errors - /// - /// If there is a problem writing to `f`. - /// - /// If the scaling factor is not 1000, 1024, or a negative number. - fn fmt_inodes(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{0: >12} ", self.scaled(self.row.inodes)?)?; - write!(f, "{0: >12} ", self.scaled(self.row.inodes_used)?)?; - write!(f, "{0: >12} ", self.scaled(self.row.inodes_free)?)?; - write!(f, "{0: >5} ", DisplayRow::percentage(self.row.inodes_usage))?; - Ok(()) - } } impl fmt::Display for DisplayRow<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{0: <16} ", self.row.fs_device)?; - if self.options.show_fs_type { - write!(f, "{0: <5} ", self.row.fs_type)?; + for column in &self.options.columns { + match column { + Column::Source => write!(f, "{0: <16} ", self.row.fs_device)?, + Column::Size => write!(f, "{0: >12} ", self.scaled(self.row.bytes)?)?, + Column::Used => write!(f, "{0: >12} ", self.scaled(self.row.bytes_used)?)?, + Column::Avail => write!(f, "{0: >12} ", self.scaled(self.row.bytes_free)?)?, + Column::Pcent => { + write!(f, "{0: >5} ", DisplayRow::percentage(self.row.bytes_usage))?; + } + Column::Target => write!(f, "{0: <16}", self.row.fs_mount)?, + Column::Itotal => write!(f, "{0: >12} ", self.scaled(self.row.inodes)?)?, + Column::Iused => write!(f, "{0: >12} ", self.scaled(self.row.inodes_used)?)?, + Column::Iavail => write!(f, "{0: >12} ", self.scaled(self.row.inodes_free)?)?, + Column::Ipcent => { + write!(f, "{0: >5} ", DisplayRow::percentage(self.row.inodes_usage))?; + } + // TODO Implement this. + Column::File => {} + Column::Fstype => write!(f, "{0: <5} ", self.row.fs_type)?, + #[cfg(target_os = "macos")] + Column::Capacity => write!( + f, + "{0: >12} ", + DisplayRow::percentage(self.row.bytes_capacity) + )?, + } } - if self.options.show_inode_instead { - self.fmt_inodes(f)?; - } else { - self.fmt_bytes(f)?; - } - write!(f, "{0: <16}", self.row.fs_mount)?; Ok(()) } } @@ -238,30 +279,29 @@ impl<'a> Header<'a> { impl fmt::Display for Header<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{0: <16} ", "Filesystem")?; - if self.options.show_fs_type { - write!(f, "{0: <5} ", "Type")?; + for column in &self.options.columns { + match column { + Column::Source => write!(f, "{0: <16} ", "Filesystem")?, + // `Display` is implemented for `BlockSize`, but + // `Display` only works when formatting an object into + // an empty format, `{}`. So we use `format!()` first + // to create the string, then use `write!()` to align + // the string and pad with spaces. + Column::Size => write!(f, "{0: >12} ", format!("{}", self.options.block_size))?, + Column::Used => write!(f, "{0: >12} ", "Used")?, + Column::Avail => write!(f, "{0: >12} ", "Available")?, + Column::Pcent => write!(f, "{0: >5} ", "Use%")?, + Column::Target => write!(f, "{0: <16} ", "Mounted on")?, + Column::Itotal => write!(f, "{0: >12} ", "Inodes")?, + Column::Iused => write!(f, "{0: >12} ", "IUsed")?, + Column::Iavail => write!(f, "{0: >12} ", "IFree")?, + Column::Ipcent => write!(f, "{0: >5} ", "IUse%")?, + Column::File => write!(f, "{0: <16}", "File")?, + Column::Fstype => write!(f, "{0: <5} ", "Type")?, + #[cfg(target_os = "macos")] + Column::Capacity => write!(f, "{0: >12} ", "Capacity")?, + } } - if self.options.show_inode_instead { - write!(f, "{0: >12} ", "Inodes")?; - write!(f, "{0: >12} ", "IUsed")?; - write!(f, "{0: >12} ", "IFree")?; - write!(f, "{0: >5} ", "IUse%")?; - } else { - // TODO Support arbitrary positive scaling factors (from - // the `--block-size` command-line argument). - if let BlockSize::Bytes(_) = self.options.block_size { - write!(f, "{0: >12} ", "1k-blocks")?; - } else { - write!(f, "{0: >12} ", "Size")?; - }; - write!(f, "{0: >12} ", "Used")?; - write!(f, "{0: >12} ", "Available")?; - #[cfg(target_os = "macos")] - write!(f, "{0: >12} ", "Capacity")?; - write!(f, "{0: >5} ", "Use%")?; - } - write!(f, "{0: <16} ", "Mounted on")?; Ok(()) } } @@ -269,34 +309,53 @@ impl fmt::Display for Header<'_> { #[cfg(test)] mod tests { + use crate::columns::Column; use crate::table::{DisplayRow, Header, Row}; use crate::{BlockSize, Options}; + const COLUMNS_WITH_FS_TYPE: [Column; 7] = [ + Column::Source, + Column::Fstype, + Column::Size, + Column::Used, + Column::Avail, + Column::Pcent, + Column::Target, + ]; + const COLUMNS_WITH_INODES: [Column; 6] = [ + Column::Source, + Column::Itotal, + Column::Iused, + Column::Iavail, + Column::Ipcent, + Column::Target, + ]; + #[test] fn test_header_display() { let options = Default::default(); assert_eq!( Header::new(&options).to_string(), - "Filesystem 1k-blocks Used Available Use% Mounted on " + "Filesystem 1K-blocks Used Available Use% Mounted on " ); } #[test] fn test_header_display_fs_type() { let options = Options { - show_fs_type: true, + columns: COLUMNS_WITH_FS_TYPE.to_vec(), ..Default::default() }; assert_eq!( Header::new(&options).to_string(), - "Filesystem Type 1k-blocks Used Available Use% Mounted on " + "Filesystem Type 1K-blocks Used Available Use% Mounted on " ); } #[test] fn test_header_display_inode() { let options = Options { - show_inode_instead: true, + columns: COLUMNS_WITH_INODES.to_vec(), ..Default::default() }; assert_eq!( @@ -305,6 +364,18 @@ mod tests { ); } + #[test] + fn test_header_display_block_size_1024() { + let options = Options { + block_size: BlockSize::Bytes(3 * 1024), + ..Default::default() + }; + assert_eq!( + Header::new(&options).to_string(), + "Filesystem 3K-blocks Used Available Use% Mounted on " + ); + } + #[test] fn test_header_display_human_readable_binary() { let options = Options { @@ -354,7 +425,7 @@ mod tests { inodes_usage: Some(0.2), }; assert_eq!( - DisplayRow::new(row, &options).to_string(), + DisplayRow::new(&row, &options).to_string(), "my_device 100 25 75 25% my_mount " ); } @@ -362,8 +433,8 @@ mod tests { #[test] fn test_row_display_fs_type() { let options = Options { + columns: COLUMNS_WITH_FS_TYPE.to_vec(), block_size: BlockSize::Bytes(1), - show_fs_type: true, ..Default::default() }; let row = Row { @@ -385,7 +456,7 @@ mod tests { inodes_usage: Some(0.2), }; assert_eq!( - DisplayRow::new(row, &options).to_string(), + DisplayRow::new(&row, &options).to_string(), "my_device my_type 100 25 75 25% my_mount " ); } @@ -393,8 +464,8 @@ mod tests { #[test] fn test_row_display_inodes() { let options = Options { + columns: COLUMNS_WITH_INODES.to_vec(), block_size: BlockSize::Bytes(1), - show_inode_instead: true, ..Default::default() }; let row = Row { @@ -416,7 +487,7 @@ mod tests { inodes_usage: Some(0.2), }; assert_eq!( - DisplayRow::new(row, &options).to_string(), + DisplayRow::new(&row, &options).to_string(), "my_device 10 2 8 20% my_mount " ); } @@ -425,7 +496,7 @@ mod tests { fn test_row_display_human_readable_si() { let options = Options { block_size: BlockSize::HumanReadableDecimal, - show_fs_type: true, + columns: COLUMNS_WITH_FS_TYPE.to_vec(), ..Default::default() }; let row = Row { @@ -447,7 +518,7 @@ mod tests { inodes_usage: Some(0.2), }; assert_eq!( - DisplayRow::new(row, &options).to_string(), + DisplayRow::new(&row, &options).to_string(), "my_device my_type 4.0k 1.0k 3.0k 25% my_mount " ); } @@ -456,7 +527,7 @@ mod tests { fn test_row_display_human_readable_binary() { let options = Options { block_size: BlockSize::HumanReadableBinary, - show_fs_type: true, + columns: COLUMNS_WITH_FS_TYPE.to_vec(), ..Default::default() }; let row = Row { @@ -478,8 +549,38 @@ mod tests { inodes_usage: Some(0.2), }; assert_eq!( - DisplayRow::new(row, &options).to_string(), + DisplayRow::new(&row, &options).to_string(), "my_device my_type 4.0Ki 1.0Ki 3.0Ki 25% my_mount " ); } + + #[test] + fn test_row_display_round_up_usage() { + let options = Options { + block_size: BlockSize::Bytes(1), + ..Default::default() + }; + let row = Row { + fs_device: "my_device".to_string(), + fs_type: "my_type".to_string(), + fs_mount: "my_mount".to_string(), + + bytes: 100, + bytes_used: 25, + bytes_free: 75, + bytes_usage: Some(0.251), + + #[cfg(target_os = "macos")] + bytes_capacity: Some(0.5), + + inodes: 10, + inodes_used: 2, + inodes_free: 8, + inodes_usage: Some(0.2), + }; + assert_eq!( + DisplayRow::new(&row, &options).to_string(), + "my_device 100 25 75 26% my_mount " + ); + } } diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 403da5171..28a520eef 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/dircolors" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/dircolors" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/dircolors/LICENSE b/src/uu/dircolors/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/dircolors/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index 9980d31a6..9b85edac0 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/dirname" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/dirname" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/dirname/LICENSE b/src/uu/dirname/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/dirname/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 2fb7a0625..a6af1a3ae 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "du ~ (uutils) display disk usage" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/du" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/du" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/du/LICENSE b/src/uu/du/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/du/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/echo/Cargo.toml b/src/uu/echo/Cargo.toml index 042a1225a..8b8ba0819 100644 --- a/src/uu/echo/Cargo.toml +++ b/src/uu/echo/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "echo ~ (uutils) display TEXT" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/echo" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/echo" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/echo/LICENSE b/src/uu/echo/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/echo/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index bb43a75bd..d5537fb54 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "env ~ (uutils) set each NAME to VALUE in the environment and run COMMAND" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/env" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/env" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/env/LICENSE b/src/uu/env/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/env/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index 42a10dabf..ba2cdd317 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "expand ~ (uutils) convert input tabs to spaces" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/expand" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/expand" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/expand/LICENSE b/src/uu/expand/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/expand/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 5d163a55f..278ebe46e 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "expr ~ (uutils) display the value of EXPRESSION" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/expr" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/expr" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/expr/LICENSE b/src/uu/expr/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/expr/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index ae9a0969b..aec43af76 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "factor ~ (uutils) display the prime factors of each NUMBER" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/factor/LICENSE b/src/uu/factor/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/factor/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/false/Cargo.toml b/src/uu/false/Cargo.toml index 7ac5602b7..7cbdcd41c 100644 --- a/src/uu/false/Cargo.toml +++ b/src/uu/false/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "false ~ (uutils) do nothing and fail" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/false" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/false" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/false/LICENSE b/src/uu/false/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/false/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index b86c94d86..47574b384 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "fmt ~ (uutils) reformat each paragraph of input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/fmt" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/fmt" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/fmt/LICENSE b/src/uu/fmt/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/fmt/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index b177eb4f7..40e3fceb0 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "fold ~ (uutils) wrap each line of input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/fold" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/fold" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/fold/LICENSE b/src/uu/fold/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/fold/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/groups/Cargo.toml b/src/uu/groups/Cargo.toml index 93b67ba43..d7b3bf01f 100644 --- a/src/uu/groups/Cargo.toml +++ b/src/uu/groups/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "groups ~ (uutils) display group memberships for USERNAME" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/groups" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/groups" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/groups/LICENSE b/src/uu/groups/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/groups/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 1f8f1b923..d0362e3a3 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "hashsum ~ (uutils) display or check input digests" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/hashsum" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/hashsum" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/hashsum/LICENSE b/src/uu/hashsum/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/hashsum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 04a512492..65ac7f7cb 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "head ~ (uutils) display the first lines of input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/head" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/head" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/head/LICENSE b/src/uu/head/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/head/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/hostid/Cargo.toml b/src/uu/hostid/Cargo.toml index 309671a29..f0013fb05 100644 --- a/src/uu/hostid/Cargo.toml +++ b/src/uu/hostid/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "hostid ~ (uutils) display the numeric identifier of the current host" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/hostid" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/hostid" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/hostid/LICENSE b/src/uu/hostid/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/hostid/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/hostname/Cargo.toml b/src/uu/hostname/Cargo.toml index 55fc064f3..8f72dc402 100644 --- a/src/uu/hostname/Cargo.toml +++ b/src/uu/hostname/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "hostname ~ (uutils) display or set the host name of the current host" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/hostname" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/hostname" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/hostname/LICENSE b/src/uu/hostname/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/hostname/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index b611e1e91..a0ccefbd0 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "id ~ (uutils) display user and group information for USER" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/id" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/id" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/id/LICENSE b/src/uu/id/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/id/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/install/Cargo.toml b/src/uu/install/Cargo.toml index d00655e8b..c5ca889ec 100644 --- a/src/uu/install/Cargo.toml +++ b/src/uu/install/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" description = "install ~ (uutils) copy files from SOURCE to DESTINATION (with specified attributes)" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/install" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/install" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/install/LICENSE b/src/uu/install/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/install/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index 2a03fe002..706b246c2 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "join ~ (uutils) merge lines from inputs with matching join fields" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/join" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/join" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/join/LICENSE b/src/uu/join/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/join/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml index 42c90b89f..46853a85d 100644 --- a/src/uu/kill/Cargo.toml +++ b/src/uu/kill/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "kill ~ (uutils) send a signal to a process" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/kill" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/kill" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/kill/LICENSE b/src/uu/kill/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/kill/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index aef64847e..46d7d26f0 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "link ~ (uutils) create a hard (file system) link to FILE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/link" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/link" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/link/LICENSE b/src/uu/link/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/link/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/ln/Cargo.toml b/src/uu/ln/Cargo.toml index 6d268408a..b1f42cad0 100644 --- a/src/uu/ln/Cargo.toml +++ b/src/uu/ln/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "ln ~ (uutils) create a (file system) link to TARGET" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/ln" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/ln" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/ln/LICENSE b/src/uu/ln/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/ln/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/logname/Cargo.toml b/src/uu/logname/Cargo.toml index 5a109491e..8e1241b3b 100644 --- a/src/uu/logname/Cargo.toml +++ b/src/uu/logname/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "logname ~ (uutils) display the login name of the current user" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/logname" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/logname" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/logname/LICENSE b/src/uu/logname/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/logname/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index ed38712be..6ad91e8e7 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "ls ~ (uutils) display directory contents" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/ls" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/ls" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -24,7 +24,7 @@ termsize = "0.1.6" glob = "0.3.0" lscolors = { version = "0.7.1", features = ["ansi_term"] } uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore", features = ["entries", "fs"] } -once_cell = "1.7.2" +once_cell = "1.10.0" atty = "0.2" selinux = { version="0.2", optional = true } diff --git a/src/uu/ls/LICENSE b/src/uu/ls/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/ls/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/mkdir/Cargo.toml b/src/uu/mkdir/Cargo.toml index a984367be..4bb12e43d 100644 --- a/src/uu/mkdir/Cargo.toml +++ b/src/uu/mkdir/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "mkdir ~ (uutils) create DIRECTORY" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/mkdir" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/mkdir" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/mkdir/LICENSE b/src/uu/mkdir/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/mkdir/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index f472465ec..cf1dc3d1b 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -11,10 +11,11 @@ extern crate uucore; use clap::{crate_version, App, AppSettings, Arg, ArgMatches, OsValues}; -use std::fs; use std::path::Path; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError}; +#[cfg(not(windows))] +use uucore::error::FromIo; +use uucore::error::{UResult, USimpleError}; #[cfg(not(windows))] use uucore::mode; use uucore::{format_usage, InvalidEncodingHandling}; @@ -152,22 +153,7 @@ fn exec(dirs: OsValues, recursive: bool, mode: u32, verbose: bool) -> UResult<() } fn mkdir(path: &Path, recursive: bool, mode: u32, verbose: bool) -> UResult<()> { - let create_dir = if recursive { - fs::create_dir_all - } else { - fs::create_dir - }; - - create_dir(path).map_err_context(|| format!("cannot create directory {}", path.quote()))?; - - if verbose { - println!( - "{}: created directory {}", - uucore::util_name(), - path.quote() - ); - } - + create_dir(path, recursive, verbose)?; chmod(path, mode) } @@ -187,3 +173,39 @@ fn chmod(_path: &Path, _mode: u32) -> UResult<()> { // chmod on Windows only sets the readonly flag, which isn't even honored on directories Ok(()) } + +fn create_dir(path: &Path, recursive: bool, verbose: bool) -> UResult<()> { + if path.exists() && !recursive { + return Err(USimpleError::new( + 1, + format!("{}: File exists", path.display()), + )); + } + if path == Path::new("") { + return Ok(()); + } + + if recursive { + match path.parent() { + Some(p) => create_dir(p, recursive, verbose)?, + None => { + USimpleError::new(1, "failed to create whole tree"); + } + } + } + + match std::fs::create_dir(path) { + Ok(()) => { + if verbose { + println!( + "{}: created directory {}", + uucore::util_name(), + path.quote() + ); + } + Ok(()) + } + Err(_) if path.is_dir() => Ok(()), + Err(e) => Err(e.into()), + } +} diff --git a/src/uu/mkfifo/Cargo.toml b/src/uu/mkfifo/Cargo.toml index 3a61c55a5..473cc0143 100644 --- a/src/uu/mkfifo/Cargo.toml +++ b/src/uu/mkfifo/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "mkfifo ~ (uutils) create FIFOs (named pipes)" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/mkfifo" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/mkfifo" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/mkfifo/LICENSE b/src/uu/mkfifo/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/mkfifo/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/mknod/Cargo.toml b/src/uu/mknod/Cargo.toml index 7af88e7b5..9bed05fd4 100644 --- a/src/uu/mknod/Cargo.toml +++ b/src/uu/mknod/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "mknod ~ (uutils) create special file NAME of TYPE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/mknod" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/mknod" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/mknod/LICENSE b/src/uu/mknod/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/mknod/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/mktemp/Cargo.toml b/src/uu/mktemp/Cargo.toml index 95ab09aa6..1ae00fa6f 100644 --- a/src/uu/mktemp/Cargo.toml +++ b/src/uu/mktemp/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "mktemp ~ (uutils) create and display a temporary file or directory from TEMPLATE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/mktemp" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/mktemp" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -17,7 +17,7 @@ path = "src/mktemp.rs" [dependencies] clap = { version = "3.0", features = ["wrap_help", "cargo"] } rand = "0.8" -tempfile = "3.1" +tempfile = "3" uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } [[bin]] diff --git a/src/uu/mktemp/LICENSE b/src/uu/mktemp/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/mktemp/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 067dfe8c2..6975a3ecc 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "more ~ (uutils) input perusal filter" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/more" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/more" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -20,7 +20,7 @@ uucore = { version = ">=0.0.7", package = "uucore", path = "../../uucore" } crossterm = ">=0.19" atty = "0.2" unicode-width = "0.1.7" -unicode-segmentation = "1.7.1" +unicode-segmentation = "1.9.0" [target.'cfg(target_os = "redox")'.dependencies] redox_termios = "0.1" diff --git a/src/uu/more/LICENSE b/src/uu/more/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/more/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index fdce84366..79012b5e0 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "mv ~ (uutils) move (rename) SOURCE to DESTINATION" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/mv" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/mv" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/mv/LICENSE b/src/uu/mv/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/mv/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index db95dc99a..fc42cb801 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "nice ~ (uutils) run PROGRAM with modified scheduling priority" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/nice" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/nice" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/nice/LICENSE b/src/uu/nice/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/nice/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index 9817cf6f8..9ce451f27 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "nl ~ (uutils) display input with added line numbers" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/nl" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/nl" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/nl/LICENSE b/src/uu/nl/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/nl/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/nohup/Cargo.toml b/src/uu/nohup/Cargo.toml index 13551a361..a19de6eb5 100644 --- a/src/uu/nohup/Cargo.toml +++ b/src/uu/nohup/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "nohup ~ (uutils) run COMMAND, ignoring hangup signals" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/nohup" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/nohup" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/nohup/LICENSE b/src/uu/nohup/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/nohup/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/nproc/Cargo.toml b/src/uu/nproc/Cargo.toml index cded0c381..ef9ee5b2a 100644 --- a/src/uu/nproc/Cargo.toml +++ b/src/uu/nproc/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "nproc ~ (uutils) display the number of processing units available" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/nproc" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/nproc" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/nproc/LICENSE b/src/uu/nproc/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/nproc/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index de8f0c3ed..f10b41e40 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -32,8 +32,8 @@ const USAGE: &str = "{} [OPTIONS]..."; pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().get_matches_from(args); - let mut ignore = match matches.value_of(OPT_IGNORE) { - Some(numstr) => match numstr.parse() { + let ignore = match matches.value_of(OPT_IGNORE) { + Some(numstr) => match numstr.trim().parse() { Ok(num) => num, Err(e) => { return Err(USimpleError::new( @@ -45,18 +45,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { None => 0, }; - if !matches.is_present(OPT_ALL) { - // OMP_NUM_THREADS doesn't have an impact on --all - ignore += match env::var("OMP_NUM_THREADS") { - Ok(threadstr) => threadstr.parse().unwrap_or(0), - Err(_) => 0, - }; - } - let mut cores = if matches.is_present(OPT_ALL) { num_cpus_all() } else { - num_cpus::get() + // OMP_NUM_THREADS doesn't have an impact on --all + match env::var("OMP_NUM_THREADS") { + // Uses the OpenMP variable to force the number of threads + // If the parsing fails, returns the number of CPU + Ok(threadstr) => threadstr.parse().unwrap_or_else(|_| num_cpus::get()), + // the variable 'OMP_NUM_THREADS' doesn't exit + // fallback to the regular CPU detection + Err(_) => num_cpus::get(), + } }; if cores <= ignore { diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index 336513b55..8afc04f11 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "numfmt ~ (uutils) reformat NUMBER" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/numfmt" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/numfmt" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/numfmt/LICENSE b/src/uu/numfmt/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/numfmt/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index 3da001265..10609ae47 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "od ~ (uutils) display formatted representation of input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/od" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/od" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/od/LICENSE b/src/uu/od/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/od/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index 0a6728ca7..c37c7a939 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "paste ~ (uutils) merge lines from inputs" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/paste" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/paste" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/paste/LICENSE b/src/uu/paste/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/paste/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/pathchk/Cargo.toml b/src/uu/pathchk/Cargo.toml index d9469d49e..982a4ea89 100644 --- a/src/uu/pathchk/Cargo.toml +++ b/src/uu/pathchk/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "pathchk ~ (uutils) diagnose invalid or non-portable PATHNAME" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/pathchk" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/pathchk" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/pathchk/LICENSE b/src/uu/pathchk/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/pathchk/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/pinky/Cargo.toml b/src/uu/pinky/Cargo.toml index 05fa71eba..3bbffe981 100644 --- a/src/uu/pinky/Cargo.toml +++ b/src/uu/pinky/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "pinky ~ (uutils) display user information" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/pinky" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/pinky" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/pinky/LICENSE b/src/uu/pinky/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/pinky/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index a1fdae4a2..100f37998 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "pr ~ (uutils) convert text files for printing" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/pr" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/pr" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -17,7 +17,6 @@ path = "src/pr.rs" [dependencies] clap = { version = "3.0", features = ["wrap_help", "cargo"] } uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] } -getopts = "0.2.21" chrono = "0.4.19" quick-error = "2.0.1" itertools = "0.10.0" diff --git a/src/uu/pr/LICENSE b/src/uu/pr/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/pr/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index c167770c0..79baf72c9 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -1,5 +1,3 @@ -#![crate_name = "uu_pr"] - // This file is part of the uutils coreutils package. // // For the full copyright and license information, please view the LICENSE file @@ -13,9 +11,7 @@ extern crate quick_error; use chrono::offset::Local; use chrono::DateTime; -use clap::{App, AppSettings}; -use getopts::Matches; -use getopts::{HasArg, Occur}; +use clap::{App, AppSettings, Arg, ArgMatches}; use itertools::Itertools; use quick_error::ResultExt; use regex::Regex; @@ -26,14 +22,35 @@ use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Write}; use std::os::unix::fs::FileTypeExt; use uucore::display::Quotable; -use uucore::error::UResult; +use uucore::error::{set_exit_code, UResult}; -type IOError = std::io::Error; - -const NAME: &str = "pr"; const VERSION: &str = env!("CARGO_PKG_VERSION"); const ABOUT: &str = "Write content of given file or standard input to standard output with pagination filter"; +const AFTER_HELP: &str = + " +PAGE\n Begin output at page number page of the formatted input. + -COLUMN\n Produce multi-column output. See --column + +The pr utility is a printing and pagination filter +for text files. When multiple input files are specified, +each is read, formatted, and written to standard +output. By default, the input is separated +into 66-line pages, each with + +o A 5-line header with the page number, date, + time, and the pathname of the file. + +o A 5-line trailer consisting of blank lines. + +If standard output is associated with a terminal, +diagnostic messages are suppressed until the pr +utility has completed processing. + +When multiple column output is specified, text columns +are of equal width. By default text columns +are separated by at least one . Input lines +that do not fit into a text column are truncated. +Lines are not truncated under single column output."; const TAB: char = '\t'; const LINES_PER_PAGE: usize = 66; const LINES_PER_PAGE_FOR_FORM_FEED: usize = 63; @@ -47,25 +64,27 @@ const DEFAULT_COLUMN_SEPARATOR: &char = &TAB; const FF: u8 = 0x0C_u8; mod options { - pub const STRING_HEADER_OPTION: &str = "h"; - pub const DOUBLE_SPACE_OPTION: &str = "d"; - pub const NUMBERING_MODE_OPTION: &str = "n"; - pub const FIRST_LINE_NUMBER_OPTION: &str = "N"; - pub const PAGE_RANGE_OPTION: &str = "pages"; - pub const NO_HEADER_TRAILER_OPTION: &str = "t"; - pub const PAGE_LENGTH_OPTION: &str = "l"; - pub const SUPPRESS_PRINTING_ERROR: &str = "r"; - pub const FORM_FEED_OPTION: &str = "F"; - pub const FORM_FEED_OPTION_SMALL: &str = "f"; - pub const COLUMN_WIDTH_OPTION: &str = "w"; - pub const PAGE_WIDTH_OPTION: &str = "W"; - pub const ACROSS_OPTION: &str = "a"; - pub const COLUMN_OPTION: &str = "column"; - pub const COLUMN_CHAR_SEPARATOR_OPTION: &str = "s"; - pub const COLUMN_STRING_SEPARATOR_OPTION: &str = "S"; - pub const MERGE_FILES_PRINT: &str = "m"; - pub const OFFSET_SPACES_OPTION: &str = "o"; - pub const JOIN_LINES_OPTION: &str = "J"; + pub const HEADER: &str = "header"; + pub const DOUBLE_SPACE: &str = "double-space"; + pub const NUMBER_LINES: &str = "number-lines"; + pub const FIRST_LINE_NUMBER: &str = "first-line-number"; + pub const PAGES: &str = "pages"; + pub const OMIT_HEADER: &str = "omit-header"; + pub const PAGE_LENGTH: &str = "length"; + pub const NO_FILE_WARNINGS: &str = "no-file-warnings"; + pub const FORM_FEED: &str = "form-feed"; + pub const COLUMN_WIDTH: &str = "width"; + pub const PAGE_WIDTH: &str = "page-width"; + pub const ACROSS: &str = "across"; + pub const COLUMN: &str = "column"; + pub const COLUMN_CHAR_SEPARATOR: &str = "separator"; + pub const COLUMN_STRING_SEPARATOR: &str = "sep-string"; + pub const MERGE: &str = "merge"; + pub const INDENT: &str = "indent"; + pub const JOIN_LINES: &str = "join-lines"; + pub const HELP: &str = "help"; + pub const VERSION: &str = "version"; + pub const FILES: &str = "files"; } struct OutputOptions { @@ -95,7 +114,7 @@ struct FileLine { line_number: usize, page_number: usize, group_key: usize, - line_content: Result, + line_content: Result, form_feeds_after: usize, } @@ -136,8 +155,8 @@ impl Default for FileLine { } } -impl From for PrError { - fn from(err: IOError) -> Self { +impl From for PrError { + fn from(err: std::io::Error) -> Self { Self::EncounteredErrors(err.to_string()) } } @@ -145,8 +164,8 @@ impl From for PrError { quick_error! { #[derive(Debug)] enum PrError { - Input(err: IOError, path: String) { - context(path: &'a str, err: IOError) -> (err, path.to_owned()) + Input(err: std::io::Error, path: String) { + context(path: &'a str, err: std::io::Error) -> (err, path.to_owned()) display("pr: Reading from input {0} gave error", path) source(err) } @@ -177,7 +196,183 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(VERSION) .about(ABOUT) + .after_help(AFTER_HELP) .setting(AppSettings::InferLongArgs) + .setting(AppSettings::AllArgsOverrideSelf) + .setting(AppSettings::NoAutoHelp) + .setting(AppSettings::NoAutoVersion) + .arg( + Arg::new(options::PAGES) + .long(options::PAGES) + .help("Begin and stop printing with page FIRST_PAGE[:LAST_PAGE]") + .takes_value(true) + .value_name("FIRST_PAGE[:LAST_PAGE]"), + ) + .arg( + Arg::new(options::HEADER) + .short('h') + .long(options::HEADER) + .help( + "Use the string header to replace the file name \ + in the header line.", + ) + .takes_value(true) + .value_name("STRING"), + ) + .arg( + Arg::new(options::DOUBLE_SPACE) + .short('d') + .long(options::DOUBLE_SPACE) + .help("Produce output that is double spaced. An extra character is output following every + found in the input.") + ) + .arg( + Arg::new(options::NUMBER_LINES) + .short('n') + .long(options::NUMBER_LINES) + .help("Provide width digit line numbering. The default for width, if not specified, is 5. The number occupies + the first width column positions of each text column or each line of -m output. If char (any non-digit + character) is given, it is appended to the line number to separate it from whatever follows. The default + for char is a . Line numbers longer than width columns are truncated.") + .takes_value(true) + .allow_hyphen_values(true) + .value_name("[char][width]") + ) + .arg( + Arg::new(options::FIRST_LINE_NUMBER) + .short('N') + .long(options::FIRST_LINE_NUMBER) + .help("start counting with NUMBER at 1st line of first page printed") + .takes_value(true) + .value_name("NUMBER") + ) + .arg( + Arg::new(options::OMIT_HEADER) + .short('t') + .long(options::OMIT_HEADER) + .help("Write neither the five-line identifying header nor the five-line trailer usually supplied for each page. Quit + writing after the last line of each file without spacing to the end of the page.") + ) + .arg( + Arg::new(options::PAGE_LENGTH) + .short('l') + .long(options::PAGE_LENGTH) + .help("Override the 66-line default (default number of lines of text 56, and with -F 63) and reset the page length to lines. If lines is not greater than the sum of both + the header and trailer depths (in lines), the pr utility shall suppress both the header and trailer, as if the + -t option were in effect. ") + .takes_value(true) + .value_name("PAGE_LENGTH") + ) + .arg( + Arg::new(options::NO_FILE_WARNINGS) + .short('r') + .long(options::NO_FILE_WARNINGS) + .help("omit warning when a file cannot be opened") + ) + .arg( + Arg::new(options::FORM_FEED) + .short('F') + .short_alias('f') + .long(options::FORM_FEED) + .help("Use a for new pages, instead of the default behavior that uses a sequence of s.") + ) + .arg( + Arg::new(options::COLUMN_WIDTH) + .short('w') + .long(options::COLUMN_WIDTH) + .help("Set the width of the line to width column positions for multiple text-column output only. If the -w option is + not specified and the -s option is not specified, the default width shall be 72. If the -w option is not specified + and the -s option is specified, the default width shall be 512.") + .takes_value(true) + .value_name("width") + ) + .arg( + Arg::new(options::PAGE_WIDTH) + .short('W') + .long(options::PAGE_WIDTH) + .help("set page width to PAGE_WIDTH (72) characters always, + truncate lines, except -J option is set, no interference + with -S or -s") + .takes_value(true) + .value_name("width") + ) + .arg( + Arg::new(options::ACROSS) + .short('a') + .long(options::ACROSS) + .help("Modify the effect of the - column option so that the columns are filled across the page in a round-robin order + (for example, when column is 2, the first input line heads column 1, the second heads column 2, the third is the + second line in column 1, and so on).") + ) + .arg( + Arg::new(options::COLUMN) + .long(options::COLUMN) + .help("Produce multi-column output that is arranged in column columns (the default shall be 1) and is written down each + column in the order in which the text is received from the input file. This option should not be used with -m. + The options -e and -i shall be assumed for multiple text-column output. Whether or not text columns are produced + with identical vertical lengths is unspecified, but a text column shall never exceed the length of the + page (see the -l option). When used with -t, use the minimum number of lines to write the output.") + .takes_value(true) + .value_name("column") + ) + .arg( + Arg::new(options::COLUMN_CHAR_SEPARATOR) + .short('s') + .long(options::COLUMN_CHAR_SEPARATOR) + .help("Separate text columns by the single character char instead of by the appropriate number of s + (default for char is the character).") + .takes_value(true) + .value_name("char") + ) + .arg( + Arg::new(options::COLUMN_STRING_SEPARATOR) + .short('S') + .long(options::COLUMN_STRING_SEPARATOR) + .help("separate columns by STRING, + without -S: Default separator with -J and + otherwise (same as -S\" \"), no effect on column options") + .takes_value(true) + .value_name("string") + ) + .arg( + Arg::new(options::MERGE) + .short('m') + .long(options::MERGE) + .help("Merge files. Standard output shall be formatted so the pr utility writes one line from each file specified by a + file operand, side by side into text columns of equal fixed widths, in terms of the number of column positions. + Implementations shall support merging of at least nine file operands.") + ) + .arg( + Arg::new(options::INDENT) + .short('o') + .long(options::INDENT) + .help("Each line of output shall be preceded by offset s. If the -o option is not specified, the default offset + shall be zero. The space taken is in addition to the output line width (see the -w option below).") + .takes_value(true) + .value_name("margin") + ) + .arg( + Arg::new(options::JOIN_LINES) + .short('J') + .help("merge full lines, turns off -W line truncation, no column + alignment, --sep-string[=STRING] sets separators") + ) + .arg( + Arg::new(options::HELP) + .long(options::HELP) + .help("Show this help message") + ) + .arg( + Arg::new(options::VERSION) + .short('V') + .long(options::VERSION) + .help("Show version information") + ) + .arg( + Arg::new(options::FILES) + .multiple_occurrences(true) + .multiple_values(true) + ) } #[uucore::main] @@ -185,229 +380,39 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(uucore::InvalidEncodingHandling::Ignore) .accept_any(); - let mut opts = getopts::Options::new(); - - opts.opt( - "", - options::PAGE_RANGE_OPTION, - "Begin and stop printing with page FIRST_PAGE[:LAST_PAGE]", - "FIRST_PAGE[:LAST_PAGE]", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::STRING_HEADER_OPTION, - "header", - "Use the string header to replace the file name \ - in the header line.", - "STRING", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::DOUBLE_SPACE_OPTION, - "double-space", - "Produce output that is double spaced. An extra character is output following every - found in the input.", - "", - HasArg::No, - Occur::Optional, - ); - - opts.opt( - options::NUMBERING_MODE_OPTION, - "number-lines", - "Provide width digit line numbering. The default for width, if not specified, is 5. The number occupies - the first width column positions of each text column or each line of -m output. If char (any non-digit - character) is given, it is appended to the line number to separate it from whatever follows. The default - for char is a . Line numbers longer than width columns are truncated.", - "[char][width]", - HasArg::Maybe, - Occur::Optional, - ); - - opts.opt( - options::FIRST_LINE_NUMBER_OPTION, - "first-line-number", - "start counting with NUMBER at 1st line of first page printed", - "NUMBER", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::NO_HEADER_TRAILER_OPTION, - "omit-header", - "Write neither the five-line identifying header nor the five-line trailer usually supplied for each page. Quit - writing after the last line of each file without spacing to the end of the page.", - "", - HasArg::No, - Occur::Optional, - ); - - opts.opt( - options::PAGE_LENGTH_OPTION, - "length", - "Override the 66-line default (default number of lines of text 56, and with -F 63) and reset the page length to lines. If lines is not greater than the sum of both - the header and trailer depths (in lines), the pr utility shall suppress both the header and trailer, as if the - -t option were in effect. ", - "lines", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::SUPPRESS_PRINTING_ERROR, - "no-file-warnings", - "omit warning when a file cannot be opened", - "", - HasArg::No, - Occur::Optional, - ); - - opts.opt( - options::FORM_FEED_OPTION, - "form-feed", - "Use a for new pages, instead of the default behavior that uses a sequence of s.", - "", - HasArg::No, - Occur::Optional, - ); - opts.opt( - options::FORM_FEED_OPTION_SMALL, - "form-feed", - "Same as -F but pause before beginning the first page if standard output is a - terminal.", - "", - HasArg::No, - Occur::Optional, - ); - - opts.opt( - "", - options::COLUMN_OPTION, - "Produce multi-column output that is arranged in column columns (the default shall be 1) and is written down each - column in the order in which the text is received from the input file. This option should not be used with -m. - The options -e and -i shall be assumed for multiple text-column output. Whether or not text columns are produced - with identical vertical lengths is unspecified, but a text column shall never exceed the length of the - page (see the -l option). When used with -t, use the minimum number of lines to write the output.", - "[column]", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::COLUMN_WIDTH_OPTION, - "width", - "Set the width of the line to width column positions for multiple text-column output only. If the -w option is - not specified and the -s option is not specified, the default width shall be 72. If the -w option is not specified - and the -s option is specified, the default width shall be 512.", - "[width]", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::PAGE_WIDTH_OPTION, - "page-width", - "set page width to PAGE_WIDTH (72) characters always, - truncate lines, except -J option is set, no interference - with -S or -s", - "[width]", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::ACROSS_OPTION, - "across", - "Modify the effect of the - column option so that the columns are filled across the page in a round-robin order - (for example, when column is 2, the first input line heads column 1, the second heads column 2, the third is the - second line in column 1, and so on).", - "", - HasArg::No, - Occur::Optional, - ); - - opts.opt( - options::COLUMN_CHAR_SEPARATOR_OPTION, - "separator", - "Separate text columns by the single character char instead of by the appropriate number of s - (default for char is the character).", - "char", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::COLUMN_STRING_SEPARATOR_OPTION, - "sep-string", - "separate columns by STRING, - without -S: Default separator with -J and - otherwise (same as -S\" \"), no effect on column options", - "string", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::MERGE_FILES_PRINT, - "merge", - "Merge files. Standard output shall be formatted so the pr utility writes one line from each file specified by a - file operand, side by side into text columns of equal fixed widths, in terms of the number of column positions. - Implementations shall support merging of at least nine file operands.", - "", - HasArg::No, - Occur::Optional, - ); - - opts.opt( - options::OFFSET_SPACES_OPTION, - "indent", - "Each line of output shall be preceded by offset s. If the -o option is not specified, the default offset - shall be zero. The space taken is in addition to the output line width (see the -w option below).", - "offset", - HasArg::Yes, - Occur::Optional, - ); - - opts.opt( - options::JOIN_LINES_OPTION, - "join-lines", - "merge full lines, turns off -W line truncation, no column - alignment, --sep-string[=STRING] sets separators", - "offset", - HasArg::No, - Occur::Optional, - ); - - opts.optflag("", "help", "display this help and exit"); - opts.optflag("V", "version", "output version information and exit"); let opt_args = recreate_arguments(&args); - let matches = match opts.parse(&opt_args[1..]) { + let mut app = uu_app(); + let matches = match app.try_get_matches_from_mut(opt_args) { Ok(m) => m, - Err(e) => panic!("Invalid options\n{}", e), + Err(e) => { + e.print()?; + set_exit_code(1); + return Ok(()); + } }; - if matches.opt_present("version") { - println!("{} {}", NAME, VERSION); + if matches.is_present(options::VERSION) { + println!("{}", app.render_long_version()); return Ok(()); } - let mut files = matches.free.clone(); + if matches.is_present(options::HELP) { + app.print_long_help()?; + return Ok(()); + } + + let mut files = matches + .values_of(options::FILES) + .map(|v| v.collect::>()) + .unwrap_or_default() + .clone(); if files.is_empty() { - files.insert(0, FILE_STDIN.to_owned()); + files.insert(0, FILE_STDIN); } - if matches.opt_present("help") { - return print_usage(&mut opts, &matches); - } - - let file_groups: Vec<_> = if matches.opt_present(options::MERGE_FILES_PRINT) { + let file_groups: Vec<_> = if matches.is_present(options::MERGE) { vec![files] } else { files.into_iter().map(|i| vec![i]).collect() @@ -449,7 +454,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { /// * `args` - Command line arguments fn recreate_arguments(args: &[String]) -> Vec { let column_page_option = Regex::new(r"^[-+]\d+.*").unwrap(); - let num_regex = Regex::new(r"(.\d+)|(\d+)|^[^-]$").unwrap(); + let num_regex = Regex::new(r"^[^-]\d*$").unwrap(); //let a_file: Regex = Regex::new(r"^[^-+].*").unwrap(); let n_regex = Regex::new(r"^-n\s*$").unwrap(); let mut arguments = args.to_owned(); @@ -470,56 +475,13 @@ fn recreate_arguments(args: &[String]) -> Vec { .collect() } -fn print_error(matches: &Matches, err: &PrError) { - if !matches.opt_present(options::SUPPRESS_PRINTING_ERROR) { +fn print_error(matches: &ArgMatches, err: &PrError) { + if !matches.is_present(options::NO_FILE_WARNINGS) { eprintln!("{}", err); } } -fn print_usage(opts: &mut getopts::Options, matches: &Matches) -> UResult<()> { - println!("{} {} -- print files", NAME, VERSION); - println!(); - println!( - "Usage: {} [+page] [-column] [-adFfmprt] [[-e] [char] [gap]] - [-L locale] [-h header] [[-i] [char] [gap]] - [-l lines] [-o offset] [[-s] [char]] [[-n] [char] - [width]] [-w width] [-] [file ...].", - NAME - ); - println!(); - let usage: &str = "The pr utility is a printing and pagination filter - for text files. When multiple input files are specified, - each is read, formatted, and written to standard - output. By default, the input is separated - into 66-line pages, each with - - o A 5-line header with the page number, date, - time, and the pathname of the file. - - o A 5-line trailer consisting of blank lines. - - If standard output is associated with a terminal, - diagnostic messages are suppressed until the pr - utility has completed processing. - - When multiple column output is specified, text columns - are of equal width. By default text columns - are separated by at least one . Input lines - that do not fit into a text column are truncated. - Lines are not truncated under single column output."; - println!("{}", opts.usage(usage)); - println!(" +page \t\tBegin output at page number page of the formatted input."); - println!( - " -column \t\tProduce multi-column output. Refer --{}", - options::COLUMN_OPTION - ); - if matches.free.is_empty() { - return Err(1.into()); - } - Ok(()) -} - -fn parse_usize(matches: &Matches, opt: &str) -> Option> { +fn parse_usize(matches: &ArgMatches, opt: &str) -> Option> { let from_parse_error_to_pr_error = |value_to_parse: (String, String)| { let i = value_to_parse.0; let option = value_to_parse.1; @@ -528,51 +490,51 @@ fn parse_usize(matches: &Matches, opt: &str) -> Option> { }) }; matches - .opt_str(opt) - .map(|i| (i, format!("-{}", opt))) + .value_of(opt) + .map(|i| (i.to_string(), format!("-{}", opt))) .map(from_parse_error_to_pr_error) } fn build_options( - matches: &Matches, - paths: &[String], + matches: &ArgMatches, + paths: &[&str], free_args: &str, ) -> Result { - let form_feed_used = matches.opt_present(options::FORM_FEED_OPTION) - || matches.opt_present(options::FORM_FEED_OPTION_SMALL); + let form_feed_used = matches.is_present(options::FORM_FEED); - let is_merge_mode = matches.opt_present(options::MERGE_FILES_PRINT); + let is_merge_mode = matches.is_present(options::MERGE); - if is_merge_mode && matches.opt_present(options::COLUMN_OPTION) { + if is_merge_mode && matches.is_present(options::COLUMN) { let err_msg = String::from("cannot specify number of columns when printing in parallel"); return Err(PrError::EncounteredErrors(err_msg)); } - if is_merge_mode && matches.opt_present(options::ACROSS_OPTION) { + if is_merge_mode && matches.is_present(options::ACROSS) { let err_msg = String::from("cannot specify both printing across and printing in parallel"); return Err(PrError::EncounteredErrors(err_msg)); } - let merge_files_print = if matches.opt_present(options::MERGE_FILES_PRINT) { + let merge_files_print = if matches.is_present(options::MERGE) { Some(paths.len()) } else { None }; - let header = matches.opt_str(options::STRING_HEADER_OPTION).unwrap_or( - if is_merge_mode || paths[0] == FILE_STDIN { - String::new() + let header = matches + .value_of(options::HEADER) + .unwrap_or(if is_merge_mode || paths[0] == FILE_STDIN { + "" } else { - paths[0].to_string() - }, - ); + paths[0] + }) + .to_string(); let default_first_number = NumberingMode::default().first_number; - let first_number = parse_usize(matches, options::FIRST_LINE_NUMBER_OPTION) - .unwrap_or(Ok(default_first_number))?; + let first_number = + parse_usize(matches, options::FIRST_LINE_NUMBER).unwrap_or(Ok(default_first_number))?; let number = matches - .opt_str(options::NUMBERING_MODE_OPTION) + .value_of(options::NUMBER_LINES) .map(|i| { let parse_result = i.parse::(); @@ -596,14 +558,14 @@ fn build_options( } }) .or_else(|| { - if matches.opt_present(options::NUMBERING_MODE_OPTION) { + if matches.is_present(options::NUMBER_LINES) { Some(NumberingMode::default()) } else { None } }); - let double_space = matches.opt_present(options::DOUBLE_SPACE_OPTION); + let double_space = matches.is_present(options::DOUBLE_SPACE); let content_line_separator = if double_space { "\n".repeat(2) @@ -652,7 +614,7 @@ fn build_options( }; let invalid_pages_map = |i: String| { - let unparsed_value = matches.opt_str(options::PAGE_RANGE_OPTION).unwrap(); + let unparsed_value = matches.value_of(options::PAGES).unwrap(); i.parse::().map_err(|_e| { PrError::EncounteredErrors(format!( "invalid --pages argument {}", @@ -662,7 +624,7 @@ fn build_options( }; let start_page = match matches - .opt_str(options::PAGE_RANGE_OPTION) + .value_of(options::PAGES) .map(|i| { let x: Vec<_> = i.split(':').collect(); x[0].to_string() @@ -674,7 +636,7 @@ fn build_options( }; let end_page = match matches - .opt_str(options::PAGE_RANGE_OPTION) + .value_of(options::PAGES) .filter(|i| i.contains(':')) .map(|i| { let x: Vec<_> = i.split(':').collect(); @@ -702,12 +664,12 @@ fn build_options( }; let page_length = - parse_usize(matches, options::PAGE_LENGTH_OPTION).unwrap_or(Ok(default_lines_per_page))?; + parse_usize(matches, options::PAGE_LENGTH).unwrap_or(Ok(default_lines_per_page))?; let page_length_le_ht = page_length < (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE); let display_header_and_trailer = - !(page_length_le_ht) && !matches.opt_present(options::NO_HEADER_TRAILER_OPTION); + !(page_length_le_ht) && !matches.is_present(options::OMIT_HEADER); let content_lines_per_page = if page_length_le_ht { page_length @@ -715,23 +677,24 @@ fn build_options( page_length - (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE) }; - let page_separator_char = if matches.opt_present(options::FORM_FEED_OPTION) { + let page_separator_char = if matches.is_present(options::FORM_FEED) { let bytes = vec![FF]; String::from_utf8(bytes).unwrap() } else { "\n".to_string() }; - let across_mode = matches.opt_present(options::ACROSS_OPTION); + let across_mode = matches.is_present(options::ACROSS); - let column_separator = match matches.opt_str(options::COLUMN_STRING_SEPARATOR_OPTION) { + let column_separator = match matches.value_of(options::COLUMN_STRING_SEPARATOR) { Some(x) => Some(x), - None => matches.opt_str(options::COLUMN_CHAR_SEPARATOR_OPTION), + None => matches.value_of(options::COLUMN_CHAR_SEPARATOR), } + .map(ToString::to_string) .unwrap_or_else(|| DEFAULT_COLUMN_SEPARATOR.to_string()); - let default_column_width = if matches.opt_present(options::COLUMN_WIDTH_OPTION) - && matches.opt_present(options::COLUMN_CHAR_SEPARATOR_OPTION) + let default_column_width = if matches.is_present(options::COLUMN_WIDTH) + && matches.is_present(options::COLUMN_CHAR_SEPARATOR) { DEFAULT_COLUMN_WIDTH_WITH_S_OPTION } else { @@ -739,12 +702,12 @@ fn build_options( }; let column_width = - parse_usize(matches, options::COLUMN_WIDTH_OPTION).unwrap_or(Ok(default_column_width))?; + parse_usize(matches, options::COLUMN_WIDTH).unwrap_or(Ok(default_column_width))?; - let page_width = if matches.opt_present(options::JOIN_LINES_OPTION) { + let page_width = if matches.is_present(options::JOIN_LINES) { None } else { - match parse_usize(matches, options::PAGE_WIDTH_OPTION) { + match parse_usize(matches, options::PAGE_WIDTH) { Some(res) => Some(res?), None => None, } @@ -764,7 +727,7 @@ fn build_options( // --column has more priority than -column - let column_option_value = match parse_usize(matches, options::COLUMN_OPTION) { + let column_option_value = match parse_usize(matches, options::COLUMN) { Some(res) => Some(res?), None => start_column_option, }; @@ -776,9 +739,8 @@ fn build_options( across_mode, }); - let offset_spaces = - " ".repeat(parse_usize(matches, options::OFFSET_SPACES_OPTION).unwrap_or(Ok(0))?); - let join_lines = matches.opt_present(options::JOIN_LINES_OPTION); + let offset_spaces = " ".repeat(parse_usize(matches, options::INDENT).unwrap_or(Ok(0))?); + let join_lines = matches.is_present(options::JOIN_LINES); let col_sep_for_printing = column_mode_options .as_ref() @@ -855,7 +817,7 @@ fn open(path: &str) -> Result, PrError> { .unwrap_or_else(|_| Err(PrError::NotExists(path.to_string()))) } -fn split_lines_if_form_feed(file_content: Result) -> Vec { +fn split_lines_if_form_feed(file_content: Result) -> Vec { file_content .map(|content| { let mut lines = Vec::new(); @@ -972,7 +934,7 @@ fn read_stream_and_create_pages( ) } -fn mpr(paths: &[String], options: &OutputOptions) -> Result { +fn mpr(paths: &[&str], options: &OutputOptions) -> Result { let n_files = paths.len(); // Check if files exists @@ -1032,7 +994,11 @@ fn mpr(paths: &[String], options: &OutputOptions) -> Result { Ok(0) } -fn print_page(lines: &[FileLine], options: &OutputOptions, page: usize) -> Result { +fn print_page( + lines: &[FileLine], + options: &OutputOptions, + page: usize, +) -> Result { let line_separator = options.line_separator.as_bytes(); let page_separator = options.page_separator_char.as_bytes(); @@ -1064,7 +1030,7 @@ fn write_columns( lines: &[FileLine], options: &OutputOptions, out: &mut impl Write, -) -> Result { +) -> Result { let line_separator = options.content_line_separator.as_bytes(); let content_lines_per_page = if options.double_space { diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index fbc5094dc..ff8e6ffe1 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "printenv ~ (uutils) display value of environment VAR" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/printenv" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/printenv" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/printenv/LICENSE b/src/uu/printenv/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/printenv/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index da49875fb..98257f9aa 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT" description = "printf ~ (uutils) FORMAT and display ARGUMENTS" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/printf" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/printf" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/printf/LICENSE b/src/uu/printf/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/printf/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index 6493cafb0..90285ebf3 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "ptx ~ (uutils) display a permuted index of input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/ptx" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/ptx" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/ptx/LICENSE b/src/uu/ptx/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/ptx/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/pwd/Cargo.toml b/src/uu/pwd/Cargo.toml index bb47b9f2d..faf718ebd 100644 --- a/src/uu/pwd/Cargo.toml +++ b/src/uu/pwd/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "pwd ~ (uutils) display current working directory" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/pwd" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/pwd" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/pwd/LICENSE b/src/uu/pwd/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/pwd/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index 638d123b5..30bb7e925 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "readlink ~ (uutils) display resolved path of PATHNAME" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/readlink" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/readlink" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/readlink/LICENSE b/src/uu/readlink/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/readlink/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index 38996de69..cabd33256 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "realpath ~ (uutils) display resolved absolute path of PATHNAME" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/realpath" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/realpath" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/realpath/LICENSE b/src/uu/realpath/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/realpath/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/relpath/Cargo.toml b/src/uu/relpath/Cargo.toml index d631fa66c..29eab9ac2 100644 --- a/src/uu/relpath/Cargo.toml +++ b/src/uu/relpath/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "relpath ~ (uutils) display relative path of PATHNAME_TO from PATHNAME_FROM" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/relpath" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/relpath" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/relpath/LICENSE b/src/uu/relpath/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/relpath/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 190294090..2ef29d435 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "rm ~ (uutils) remove PATHNAME" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/rm" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/rm" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/rm/LICENSE b/src/uu/rm/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/rm/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index 8dcb0cf97..86d5031c8 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "rmdir ~ (uutils) remove empty DIRECTORY" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/rmdir" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/rmdir" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/rmdir/LICENSE b/src/uu/rmdir/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/rmdir/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/runcon/Cargo.toml b/src/uu/runcon/Cargo.toml index 7cdb4d9a3..5c68a8d68 100644 --- a/src/uu/runcon/Cargo.toml +++ b/src/uu/runcon/Cargo.toml @@ -5,7 +5,7 @@ authors = ["uutils developers"] license = "MIT" description = "runcon ~ (uutils) run command with specified security context" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/runcon" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/runcon" keywords = ["coreutils", "uutils", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/runcon/LICENSE b/src/uu/runcon/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/runcon/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index e6b5a2863..fe986529d 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT" description = "seq ~ (uutils) display a sequence of numbers" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/seq" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/seq" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/seq/LICENSE b/src/uu/seq/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/seq/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 857e371bb..c73e0741b 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "shred ~ (uutils) hide former FILE contents with repeated overwrites" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/shred" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/shred" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/shred/LICENSE b/src/uu/shred/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/shred/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index 03dd11abc..ab74e08b8 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "shuf ~ (uutils) display random permutations of input lines" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/shuf" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/shuf" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/shuf/LICENSE b/src/uu/shuf/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/shuf/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/sleep/Cargo.toml b/src/uu/sleep/Cargo.toml index 37066f3cf..7f5413a3f 100644 --- a/src/uu/sleep/Cargo.toml +++ b/src/uu/sleep/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "sleep ~ (uutils) pause for DURATION" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/sleep" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/sleep" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/sleep/LICENSE b/src/uu/sleep/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/sleep/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/sort/Cargo.toml b/src/uu/sort/Cargo.toml index 1a99e0445..12b171b74 100644 --- a/src/uu/sort/Cargo.toml +++ b/src/uu/sort/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "sort ~ (uutils) sort input lines" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/sort" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/sort" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -22,7 +22,7 @@ ctrlc = { version = "3.0", features = ["termination"] } fnv = "1.0.7" itertools = "0.10.0" memchr = "2.4.0" -ouroboros = "0.10.1" +ouroboros = "0.14.2" rand = "0.8" rayon = "1.5" tempfile = "3" diff --git a/src/uu/sort/LICENSE b/src/uu/sort/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/sort/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index f6920e797..233563d1c 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "split ~ (uutils) split input into output files" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/split" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/split" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/split/LICENSE b/src/uu/split/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/split/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/split/src/number.rs b/src/uu/split/src/number.rs index d5427e2ca..c7557271d 100644 --- a/src/uu/split/src/number.rs +++ b/src/uu/split/src/number.rs @@ -479,7 +479,7 @@ mod tests { fn num(n: usize) -> Number { let mut number = Number::DynamicWidth(DynamicWidthNumber::new(16)); for _ in 0..n { - number.increment().unwrap() + number.increment().unwrap(); } number } diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index e2504f305..73abc966b 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -5,7 +5,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore (ToDO) PREFIXaa +// spell-checker:ignore (ToDO) PREFIXaa nbbbb ncccc mod filenames; mod number; @@ -760,6 +760,187 @@ impl<'a> Write for LineChunkWriter<'a> { } } +/// Write lines to each sequential output files, limited by bytes. +/// +/// This struct maintains an underlying writer representing the +/// current chunk of the output. On each call to [`write`], it writes +/// as many lines as possible to the current chunk without exceeding +/// the specified byte limit. If a single line has more bytes than the +/// limit, then fill an entire single chunk with those bytes and +/// handle the remainder of the line as if it were its own distinct +/// line. As many new underlying writers are created as needed to +/// write all the data in the input buffer. +struct LineBytesChunkWriter<'a> { + /// Parameters for creating the underlying writer for each new chunk. + settings: &'a Settings, + + /// The maximum number of bytes allowed for a single chunk of output. + chunk_size: u64, + + /// Running total of number of chunks that have been completed. + num_chunks_written: usize, + + /// Remaining capacity in number of bytes in the current chunk. + /// + /// This number starts at `chunk_size` and decreases as lines are + /// written. Once it reaches zero, a writer for a new chunk is + /// initialized and this number gets reset to `chunk_size`. + num_bytes_remaining_in_current_chunk: usize, + + /// The underlying writer for the current chunk. + /// + /// Once the number of bytes written to this writer exceeds + /// `chunk_size`, a new writer is initialized and assigned to this + /// field. + inner: BufWriter>, + + /// Iterator that yields filenames for each chunk. + filename_iterator: FilenameIterator<'a>, +} + +impl<'a> LineBytesChunkWriter<'a> { + fn new(chunk_size: u64, settings: &'a Settings) -> Option> { + let mut filename_iterator = FilenameIterator::new( + &settings.prefix, + &settings.additional_suffix, + settings.suffix_length, + settings.suffix_type, + ); + let filename = filename_iterator.next()?; + if settings.verbose { + println!("creating file {}", filename.quote()); + } + let inner = platform::instantiate_current_writer(&settings.filter, &filename); + Some(LineBytesChunkWriter { + settings, + chunk_size, + num_bytes_remaining_in_current_chunk: chunk_size.try_into().unwrap(), + num_chunks_written: 0, + inner, + filename_iterator, + }) + } +} + +impl<'a> Write for LineBytesChunkWriter<'a> { + /// Write as many lines to a chunk as possible without + /// exceeding the byte limit. If a single line has more bytes + /// than the limit, then fill an entire single chunk with those + /// bytes and handle the remainder of the line as if it were + /// its own distinct line. + /// + /// For example: if the `chunk_size` is 8 and the input is: + /// + /// ```text + /// aaaaaaaaa\nbbbb\ncccc\ndd\nee\n + /// ``` + /// + /// then the output gets broken into chunks like this: + /// + /// ```text + /// chunk 0 chunk 1 chunk 2 chunk 3 + /// + /// 0 1 2 + /// 01234567 89 01234 56789 012 345 6 + /// |------| |-------| |--------| |---| + /// aaaaaaaa a\nbbbb\n cccc\ndd\n ee\n + /// ``` + fn write(&mut self, mut buf: &[u8]) -> std::io::Result { + // The total number of bytes written during the loop below. + // + // It is necessary to keep this running total because we may + // be making multiple calls to `write()` on multiple different + // underlying writers and we want the final reported number of + // bytes written to reflect the total number of bytes written + // to all of the underlying writers. + let mut total_bytes_written = 0; + + // Loop until we have written all bytes in the input buffer + // (or an IO error occurs). + loop { + // If we have filled the current chunk with bytes, then + // start a new chunk and initialize its corresponding + // writer. + if self.num_bytes_remaining_in_current_chunk == 0 { + self.num_chunks_written += 1; + let filename = self.filename_iterator.next().ok_or_else(|| { + std::io::Error::new(ErrorKind::Other, "output file suffixes exhausted") + })?; + if self.settings.verbose { + println!("creating file {}", filename.quote()); + } + self.inner = platform::instantiate_current_writer(&self.settings.filter, &filename); + self.num_bytes_remaining_in_current_chunk = self.chunk_size.try_into().unwrap(); + } + + // Find the first newline character in the buffer. + match memchr::memchr(b'\n', buf) { + // If there is no newline character and the buffer is + // empty, then we are done writing. + None if buf.is_empty() => { + return Ok(total_bytes_written); + } + + // If there is no newline character and the buffer is + // not empty, then write as many bytes as we can and + // then move on to the next chunk if necessary. + None => { + let end = self.num_bytes_remaining_in_current_chunk; + let num_bytes_written = self.inner.write(&buf[..end])?; + self.num_bytes_remaining_in_current_chunk -= num_bytes_written; + total_bytes_written += num_bytes_written; + buf = &buf[num_bytes_written..]; + } + + // If there is a newline character and the line + // (including the newline character) will fit in the + // current chunk, then write the entire line and + // continue to the next iteration. (See chunk 1 in the + // example comment above.) + Some(i) if i < self.num_bytes_remaining_in_current_chunk => { + let num_bytes_written = self.inner.write(&buf[..i + 1])?; + self.num_bytes_remaining_in_current_chunk -= num_bytes_written; + total_bytes_written += num_bytes_written; + buf = &buf[num_bytes_written..]; + } + + // If there is a newline character, the line + // (including the newline character) will not fit in + // the current chunk, *and* no other lines have been + // written to the current chunk, then write as many + // bytes as we can and continue to the next + // iteration. (See chunk 0 in the example comment + // above.) + Some(_) + if self.num_bytes_remaining_in_current_chunk + == self.chunk_size.try_into().unwrap() => + { + let end = self.num_bytes_remaining_in_current_chunk; + let num_bytes_written = self.inner.write(&buf[..end])?; + self.num_bytes_remaining_in_current_chunk -= num_bytes_written; + total_bytes_written += num_bytes_written; + buf = &buf[num_bytes_written..]; + } + + // If there is a newline character, the line + // (including the newline character) will not fit in + // the current chunk, and at least one other line has + // been written to the current chunk, then signal to + // the next iteration that a new chunk needs to be + // created and continue to the next iteration of the + // loop to try writing the line there. + Some(_) => { + self.num_bytes_remaining_in_current_chunk = 0; + } + } + } + } + + fn flush(&mut self) -> std::io::Result<()> { + self.inner.flush() + } +} + /// Split a file into a specific number of chunks by byte. /// /// This function always creates one output file for each chunk, even @@ -859,6 +1040,11 @@ where /// /// This function returns an error if there is a problem reading from /// `reader` or writing to one of the output files. +/// +/// # See also +/// +/// * [`kth_chunk_by_line`], which splits its input in the same way, +/// but writes only one specified chunk to stdout. fn split_into_n_chunks_by_line( settings: &Settings, reader: &mut R, @@ -915,6 +1101,67 @@ where Ok(()) } +/// Print the k-th chunk of a file, splitting by line. +/// +/// This function is like [`split_into_n_chunks_by_line`], but instead +/// of writing each chunk to its own file, it only writes to stdout +/// the contents of the chunk identified by `chunk_number`. +/// +/// # Errors +/// +/// This function returns an error if there is a problem reading from +/// `reader` or writing to one of the output files. +/// +/// # See also +/// +/// * [`split_into_n_chunks_by_line`], which splits its input in the +/// same way, but writes each chunk to its own file. +fn kth_chunk_by_line( + settings: &Settings, + reader: &mut R, + chunk_number: u64, + num_chunks: u64, +) -> UResult<()> +where + R: BufRead, +{ + // Get the size of the input file in bytes and compute the number + // of bytes per chunk. + let metadata = metadata(&settings.input).unwrap(); + let num_bytes = metadata.len(); + let chunk_size = (num_bytes / (num_chunks as u64)) as usize; + + // Write to stdout instead of to a file. + let stdout = std::io::stdout(); + let mut writer = stdout.lock(); + + let mut num_bytes_remaining_in_current_chunk = chunk_size; + let mut i = 0; + for line_result in reader.lines() { + let line = line_result?; + let bytes = line.as_bytes(); + if i == chunk_number { + writer.write_all(bytes)?; + writer.write_all(b"\n")?; + } + + // Add one byte for the newline character. + let num_bytes = bytes.len() + 1; + if num_bytes >= num_bytes_remaining_in_current_chunk { + num_bytes_remaining_in_current_chunk = chunk_size; + i += 1; + } else { + num_bytes_remaining_in_current_chunk -= num_bytes; + } + + if i > chunk_number { + break; + } + } + + Ok(()) +} + fn split(settings: &Settings) -> UResult<()> { let mut reader = BufReader::new(if settings.input == "-" { Box::new(stdin()) as Box @@ -935,6 +1182,12 @@ fn split(settings: &Settings) -> UResult<()> { Strategy::Number(NumberType::Lines(num_chunks)) => { split_into_n_chunks_by_line(settings, &mut reader, num_chunks) } + Strategy::Number(NumberType::KthLines(chunk_number, num_chunks)) => { + // The chunk number is given as a 1-indexed number, but it + // is a little easier to deal with a 0-indexed number. + let chunk_number = chunk_number - 1; + kth_chunk_by_line(settings, &mut reader, chunk_number, num_chunks) + } Strategy::Number(_) => Err(USimpleError::new(1, "-n mode not yet fully implemented")), Strategy::Lines(chunk_size) => { let mut writer = LineChunkWriter::new(chunk_size, settings) @@ -955,7 +1208,7 @@ fn split(settings: &Settings) -> UResult<()> { }, } } - Strategy::Bytes(chunk_size) | Strategy::LineBytes(chunk_size) => { + Strategy::Bytes(chunk_size) => { let mut writer = ByteChunkWriter::new(chunk_size, settings) .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; match std::io::copy(&mut reader, &mut writer) { @@ -974,6 +1227,25 @@ fn split(settings: &Settings) -> UResult<()> { }, } } + Strategy::LineBytes(chunk_size) => { + let mut writer = LineBytesChunkWriter::new(chunk_size, settings) + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + match std::io::copy(&mut reader, &mut writer) { + Ok(_) => Ok(()), + Err(e) => match e.kind() { + // TODO Since the writer object controls the creation of + // new files, we need to rely on the `std::io::Result` + // returned by its `write()` method to communicate any + // errors to this calling scope. If a new file cannot be + // created because we have exceeded the number of + // allowable filenames, we use `ErrorKind::Other` to + // indicate that. A special error message needs to be + // printed in that case. + ErrorKind::Other => Err(USimpleError::new(1, "output file suffixes exhausted")), + _ => Err(uio_error!(e, "input/output error")), + }, + } + } } } diff --git a/src/uu/stat/Cargo.toml b/src/uu/stat/Cargo.toml index e0ed8aa73..ecf5aa5bd 100644 --- a/src/uu/stat/Cargo.toml +++ b/src/uu/stat/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "stat ~ (uutils) display FILE status" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/stat" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/stat" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/stat/LICENSE b/src/uu/stat/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/stat/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/stdbuf/Cargo.toml b/src/uu/stdbuf/Cargo.toml index a65d77c76..a977adfe6 100644 --- a/src/uu/stdbuf/Cargo.toml +++ b/src/uu/stdbuf/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "stdbuf ~ (uutils) run COMMAND with modified standard stream buffering" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/stdbuf" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/stdbuf" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -16,7 +16,7 @@ path = "src/stdbuf.rs" [dependencies] clap = { version = "3.0", features = ["wrap_help", "cargo"] } -tempfile = "3.1" +tempfile = "3" uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } [build-dependencies] diff --git a/src/uu/stdbuf/LICENSE b/src/uu/stdbuf/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/stdbuf/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/stdbuf/src/libstdbuf/Cargo.toml b/src/uu/stdbuf/src/libstdbuf/Cargo.toml index 5c8a80f0e..fc623b666 100644 --- a/src/uu/stdbuf/src/libstdbuf/Cargo.toml +++ b/src/uu/stdbuf/src/libstdbuf/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "stdbuf/libstdbuf ~ (uutils); dynamic library required for stdbuf" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/stdbuf" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/stdbuf" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index 7bc4468d0..56a4edf9a 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "sum ~ (uutils) display checksum and block counts for input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/sum" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/sum" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/sum/LICENSE b/src/uu/sum/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/sum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/sync/Cargo.toml b/src/uu/sync/Cargo.toml index 364ab181b..a84d8c192 100644 --- a/src/uu/sync/Cargo.toml +++ b/src/uu/sync/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "sync ~ (uutils) synchronize cache writes to storage" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/sync" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/sync" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/sync/LICENSE b/src/uu/sync/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/sync/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 1c5242ba7..a430c1e53 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT" description = "tac ~ (uutils) concatenate and display input lines in reverse order" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/tac" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/tac" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/tac/LICENSE b/src/uu/tac/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/tac/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 4f40431b1..7abc10a56 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "tail ~ (uutils) display the last lines of input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/tail" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/tail" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/tail/LICENSE b/src/uu/tail/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/tail/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index 054e3592e..b71e9b0ef 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "tee ~ (uutils) display input and copy to FILE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/tee" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/tee" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/tee/LICENSE b/src/uu/tee/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/tee/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index 0acad31ab..bf0558a7a 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "test ~ (uutils) evaluate comparison and file type expressions" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/test" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/test" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/test/LICENSE b/src/uu/test/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/test/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/timeout/Cargo.toml b/src/uu/timeout/Cargo.toml index ae0cb1a0b..c250381e8 100644 --- a/src/uu/timeout/Cargo.toml +++ b/src/uu/timeout/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "timeout ~ (uutils) run COMMAND with a DURATION time limit" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/timeout" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/timeout" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/timeout/LICENSE b/src/uu/timeout/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/timeout/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/timeout/src/status.rs b/src/uu/timeout/src/status.rs new file mode 100644 index 000000000..9a8558954 --- /dev/null +++ b/src/uu/timeout/src/status.rs @@ -0,0 +1,51 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. +//! Exit status codes produced by `timeout`. +use std::convert::From; +use uucore::error::UError; + +/// Enumerates the exit statuses produced by `timeout`. +/// +/// Use [`Into::into`] (or [`From::from`]) to convert an enumeration +/// member into a numeric status code. You can also convert into a +/// [`UError`]. +/// +/// # Examples +/// +/// Convert into an [`i32`]: +/// +/// ```rust,ignore +/// assert_eq!(i32::from(ExitStatus::CommandTimedOut), 124); +/// ``` +pub(crate) enum ExitStatus { + /// When the child process times out and `--preserve-status` is not specified. + CommandTimedOut, + + /// When `timeout` itself fails. + TimeoutFailed, + + /// When a signal is sent to the child process or `timeout` itself. + SignalSent(usize), + + /// When there is a failure while waiting for the child process to terminate. + WaitingFailed, +} + +impl From for i32 { + fn from(exit_status: ExitStatus) -> Self { + match exit_status { + ExitStatus::CommandTimedOut => 124, + ExitStatus::TimeoutFailed => 125, + ExitStatus::SignalSent(s) => 128 + s as Self, + ExitStatus::WaitingFailed => 124, + } + } +} + +impl From for Box { + fn from(exit_status: ExitStatus) -> Self { + Box::from(i32::from(exit_status)) + } +} diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 2caed8cab..3da0bcd2a 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -6,15 +6,17 @@ // * file that was distributed with this source code. // spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld +mod status; #[macro_use] extern crate uucore; extern crate clap; +use crate::status::ExitStatus; use clap::{crate_version, App, AppSettings, Arg}; use std::io::ErrorKind; -use std::process::{Command, Stdio}; +use std::process::{Child, Command, Stdio}; use std::time::Duration; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; @@ -25,8 +27,6 @@ use uucore::{format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; const USAGE: &str = "{} [OPTION] DURATION COMMAND..."; -const ERR_EXIT_STATUS: i32 = 125; - pub mod options { pub static FOREGROUND: &str = "foreground"; pub static KILL_AFTER: &str = "kill-after"; @@ -51,7 +51,7 @@ struct Config { } impl Config { - fn from(options: &clap::ArgMatches) -> Self { + fn from(options: &clap::ArgMatches) -> UResult { let signal = match options.value_of(options::SIGNAL) { Some(signal_) => { let signal_result = signal_by_name_or_value(signal_); @@ -69,8 +69,11 @@ impl Config { .value_of(options::KILL_AFTER) .map(|time| uucore::parse_time::from_str(time).unwrap()); - let duration: Duration = - uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()).unwrap(); + let duration = + match uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()) { + Ok(duration) => duration, + Err(err) => return Err(USimpleError::new(1, err)), + }; let preserve_status: bool = options.is_present(options::PRESERVE_STATUS); let foreground = options.is_present(options::FOREGROUND); @@ -82,7 +85,7 @@ impl Config { .map(String::from) .collect::>(); - Self { + Ok(Self { foreground, kill_after, signal, @@ -90,7 +93,7 @@ impl Config { preserve_status, verbose, command, - } + }) } } @@ -104,7 +107,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = app.get_matches_from(args); - let config = Config::from(&matches); + let config = Config::from(&matches)?; timeout( &config.command, config.duration, @@ -174,6 +177,60 @@ fn unblock_sigchld() { } } +/// Report that a signal is being sent if the verbose flag is set. +fn report_if_verbose(signal: usize, cmd: &str, verbose: bool) { + if verbose { + let s = signal_name_by_value(signal).unwrap(); + show_error!("sending signal {} to command {}", s, cmd.quote()); + } +} + +/// Wait for a child process and send a kill signal if it does not terminate. +/// +/// This function waits for the child `process` for the time period +/// given by `duration`. If the child process does not terminate +/// within that time, we send the `SIGKILL` signal to it. If `verbose` +/// is `true`, then a message is printed to `stderr` when that +/// happens. +/// +/// If the child process terminates within the given time period and +/// `preserve_status` is `true`, then the status code of the child +/// process is returned. If the child process terminates within the +/// given time period and `preserve_status` is `false`, then 124 is +/// returned. If the child does not terminate within the time period, +/// then 137 is returned. Finally, if there is an error while waiting +/// for the child process to terminate, then 124 is returned. +/// +/// # Errors +/// +/// If there is a problem sending the `SIGKILL` signal or waiting for +/// the process after that signal is sent. +fn wait_or_kill_process( + mut process: Child, + cmd: &str, + duration: Duration, + preserve_status: bool, + verbose: bool, +) -> std::io::Result { + match process.wait_or_timeout(duration) { + Ok(Some(status)) => { + if preserve_status { + Ok(status.code().unwrap_or_else(|| status.signal().unwrap())) + } else { + Ok(ExitStatus::TimeoutFailed.into()) + } + } + Ok(None) => { + let signal = signal_by_name_or_value("KILL").unwrap(); + report_if_verbose(signal, cmd, verbose); + process.send_signal(signal)?; + process.wait()?; + Ok(ExitStatus::SignalSent(signal).into()) + } + Err(_) => Ok(ExitStatus::WaitingFailed.into()), + } +} + /// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes. fn timeout( @@ -205,67 +262,58 @@ fn timeout( USimpleError::new(status_code, format!("failed to execute process: {}", err)) })?; unblock_sigchld(); + // Wait for the child process for the specified time period. + // + // If the process exits within the specified time period (the + // `Ok(Some(_))` arm), then return the appropriate status code. + // + // If the process does not exit within that time (the `Ok(None)` + // arm) and `kill_after` is specified, then try sending `SIGKILL`. + // + // TODO The structure of this block is extremely similar to the + // structure of `wait_or_kill_process()`. They can probably be + // refactored into some common function. match process.wait_or_timeout(duration) { - Ok(Some(status)) => { - let status_code = status.code().unwrap_or_else(|| status.signal().unwrap()); - if status_code == 0 { - Ok(()) - } else { - Err(status_code.into()) - } - } + Ok(Some(status)) => Err(status + .code() + .unwrap_or_else(|| status.signal().unwrap()) + .into()), Ok(None) => { - if verbose { - show_error!( - "sending signal {} to command {}", - signal_name_by_value(signal).unwrap(), - cmd[0].quote() - ); - } - process - .send_signal(signal) - .map_err(|e| USimpleError::new(ERR_EXIT_STATUS, format!("{}", e)))?; - if let Some(kill_after) = kill_after { - match process.wait_or_timeout(kill_after) { - Ok(Some(status)) => { - if preserve_status { - let status_code = - status.code().unwrap_or_else(|| status.signal().unwrap()); - if status_code == 0 { - Ok(()) - } else { - Err(status_code.into()) - } - } else { - Err(124.into()) - } + report_if_verbose(signal, &cmd[0], verbose); + process.send_signal(signal)?; + match kill_after { + None => { + if preserve_status { + Err(ExitStatus::SignalSent(signal).into()) + } else { + Err(ExitStatus::CommandTimedOut.into()) + } + } + Some(kill_after) => { + match wait_or_kill_process( + process, + &cmd[0], + kill_after, + preserve_status, + verbose, + ) { + Ok(status) => Err(status.into()), + Err(e) => Err(USimpleError::new( + ExitStatus::TimeoutFailed.into(), + format!("{}", e), + )), } - Ok(None) => { - if verbose { - show_error!("sending signal KILL to command {}", cmd[0].quote()); - } - process - .send_signal(uucore::signals::signal_by_name_or_value("KILL").unwrap()) - .map_err(|e| USimpleError::new(ERR_EXIT_STATUS, format!("{}", e)))?; - process - .wait() - .map_err(|e| USimpleError::new(ERR_EXIT_STATUS, format!("{}", e)))?; - Err(137.into()) - } - Err(_) => Err(124.into()), } - } else { - Err(124.into()) } } Err(_) => { // We're going to return ERR_EXIT_STATUS regardless of // whether `send_signal()` succeeds or fails, so just // ignore the return value. - process - .send_signal(signal) - .map_err(|e| USimpleError::new(ERR_EXIT_STATUS, format!("{}", e)))?; - Err(ERR_EXIT_STATUS.into()) + process.send_signal(signal).map_err(|e| { + USimpleError::new(ExitStatus::TimeoutFailed.into(), format!("{}", e)) + })?; + Err(ExitStatus::TimeoutFailed.into()) } } } diff --git a/src/uu/touch/Cargo.toml b/src/uu/touch/Cargo.toml index dd024eacd..bb6e0dc1e 100644 --- a/src/uu/touch/Cargo.toml +++ b/src/uu/touch/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "touch ~ (uutils) change FILE timestamps" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/touch" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/touch" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -20,6 +20,9 @@ clap = { version = "3.0", features = ["wrap_help", "cargo"] } time = "0.1.40" uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] } +[target.'cfg(target_os = "windows")'.dependencies] +winapi = { version = "0.3" } + [[bin]] name = "touch" path = "src/main.rs" diff --git a/src/uu/touch/LICENSE b/src/uu/touch/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/touch/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 620538bbf..2b56cdb95 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -6,7 +6,7 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv +// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult pub extern crate filetime; @@ -16,7 +16,7 @@ extern crate uucore; use clap::{crate_version, App, AppSettings, Arg, ArgGroup}; use filetime::*; use std::fs::{self, File}; -use std::path::Path; +use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; use uucore::format_usage; @@ -77,7 +77,15 @@ Try 'touch --help' for more information."##, }; for filename in files { - let path = Path::new(filename); + // FIXME: find a way to avoid having to clone the path + let pathbuf = if filename == "-" { + pathbuf_from_stdout()? + } else { + PathBuf::from(filename) + }; + + let path = pathbuf.as_path(); + if !path.exists() { if matches.is_present(options::NO_CREATE) { continue; @@ -299,3 +307,89 @@ fn parse_timestamp(s: &str) -> UResult { Ok(ft) } + +// TODO: this may be a good candidate to put in fsext.rs +/// Returns a PathBuf to stdout. +/// +/// On Windows, uses GetFinalPathNameByHandleW to attempt to get the path +/// from the stdout handle. +fn pathbuf_from_stdout() -> UResult { + #[cfg(unix)] + { + Ok(PathBuf::from("/dev/stdout")) + } + #[cfg(windows)] + { + use std::os::windows::prelude::AsRawHandle; + use winapi::shared::minwindef::{DWORD, MAX_PATH}; + use winapi::shared::winerror::{ + ERROR_INVALID_PARAMETER, ERROR_NOT_ENOUGH_MEMORY, ERROR_PATH_NOT_FOUND, + }; + use winapi::um::errhandlingapi::GetLastError; + use winapi::um::fileapi::GetFinalPathNameByHandleW; + use winapi::um::winnt::WCHAR; + + let handle = std::io::stdout().lock().as_raw_handle(); + let mut file_path_buffer: [WCHAR; MAX_PATH as usize] = [0; MAX_PATH as usize]; + + // Couldn't find this in winapi + const FILE_NAME_OPENED: DWORD = 0x8; + + // https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea#examples + // SAFETY: We transmute the handle to be able to cast *mut c_void into a + // HANDLE (i32) so rustc will let us call GetFinalPathNameByHandleW. The + // reference example code for GetFinalPathNameByHandleW implies that + // it is safe for us to leave lpszfilepath uninitialized, so long as + // the buffer size is correct. We know the buffer size (MAX_PATH) at + // compile time. MAX_PATH is a small number (260) so we can cast it + // to a u32. + let ret = unsafe { + GetFinalPathNameByHandleW( + std::mem::transmute(handle), + file_path_buffer.as_mut_ptr(), + file_path_buffer.len() as u32, + FILE_NAME_OPENED, + ) + }; + + let buffer_size = match ret { + ERROR_PATH_NOT_FOUND | ERROR_NOT_ENOUGH_MEMORY | ERROR_INVALID_PARAMETER => { + return Err(USimpleError::new( + 1, + format!("GetFinalPathNameByHandleW failed with code {}", ret), + )) + } + e if e == 0 => { + return Err(USimpleError::new( + 1, + format!( + "GetFinalPathNameByHandleW failed with code {}", + // SAFETY: GetLastError is thread-safe and has no documented memory unsafety. + unsafe { GetLastError() } + ), + )); + } + e => e as usize, + }; + + // Don't include the null terminator + Ok(String::from_utf16(&file_path_buffer[0..buffer_size]) + .map_err(|e| USimpleError::new(1, e.to_string()))? + .into()) + } +} + +#[cfg(test)] +mod tests { + #[cfg(windows)] + #[test] + fn test_get_pathbuf_from_stdout_fails_if_stdout_is_not_a_file() { + // We can trigger an error by not setting stdout to anything (will + // fail with code 1) + assert!(super::pathbuf_from_stdout() + .err() + .expect("pathbuf_from_stdout should have failed") + .to_string() + .contains("GetFinalPathNameByHandleW failed with code 1")); + } +} diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index bfccfb4e7..4ecfe2045 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "tr ~ (uutils) translate characters within input and display" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/tr" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/tr" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/tr/LICENSE b/src/uu/tr/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/tr/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/true/Cargo.toml b/src/uu/true/Cargo.toml index 82023585f..c737d2234 100644 --- a/src/uu/true/Cargo.toml +++ b/src/uu/true/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "true ~ (uutils) do nothing and succeed" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/true" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/true" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/true/LICENSE b/src/uu/true/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/true/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index 402c201f6..3048df521 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "truncate ~ (uutils) truncate (or extend) FILE to SIZE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/truncate" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/truncate" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/truncate/LICENSE b/src/uu/truncate/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/truncate/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index 8964486bf..39e73f0a1 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "tsort ~ (uutils) topologically sort input (partially ordered) pairs" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/tsort" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/tsort" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/tsort/LICENSE b/src/uu/tsort/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/tsort/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/tty/Cargo.toml b/src/uu/tty/Cargo.toml index bdb7ebe9c..43a0d2edc 100644 --- a/src/uu/tty/Cargo.toml +++ b/src/uu/tty/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "tty ~ (uutils) display the name of the terminal connected to standard input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/tty" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/tty" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/tty/LICENSE b/src/uu/tty/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/tty/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/uname/Cargo.toml b/src/uu/uname/Cargo.toml index b840b7584..4a68e1b64 100644 --- a/src/uu/uname/Cargo.toml +++ b/src/uu/uname/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "uname ~ (uutils) display system information" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/uname" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/uname" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/uname/LICENSE b/src/uu/uname/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/uname/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index b679d786e..86de08b9f 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "unexpand ~ (uutils) convert input spaces to tabs" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/unexpand" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/unexpand" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/unexpand/LICENSE b/src/uu/unexpand/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/unexpand/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index 5d4a085c7..1fe571b0e 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "uniq ~ (uutils) filter identical adjacent lines from input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/uniq" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/uniq" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" @@ -16,7 +16,7 @@ path = "src/uniq.rs" [dependencies] clap = { version = "3.0", features = ["wrap_help", "cargo"] } -strum = "0.23.0" +strum = "0.24.0" strum_macros = "0.23.1" uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } diff --git a/src/uu/uniq/LICENSE b/src/uu/uniq/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/uniq/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index 0869427e9..8d3ac6b26 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "unlink ~ (uutils) remove a (file system) link to FILE" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/unlink" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/unlink" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/unlink/LICENSE b/src/uu/unlink/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/unlink/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/uptime/Cargo.toml b/src/uu/uptime/Cargo.toml index fcce5bb47..8edb568d3 100644 --- a/src/uu/uptime/Cargo.toml +++ b/src/uu/uptime/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "uptime ~ (uutils) display dynamic system information" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/uptime" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/uptime" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/uptime/LICENSE b/src/uu/uptime/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/uptime/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/users/Cargo.toml b/src/uu/users/Cargo.toml index 5125bcc33..dfd6d0309 100644 --- a/src/uu/users/Cargo.toml +++ b/src/uu/users/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "users ~ (uutils) display names of currently logged-in users" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/users" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/users" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/users/LICENSE b/src/uu/users/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/users/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 28520f1d7..0e998b74e 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "wc ~ (uutils) display newline, word, and byte counts for input" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/wc" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/wc" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/wc/LICENSE b/src/uu/wc/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/wc/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/who/Cargo.toml b/src/uu/who/Cargo.toml index 9559bdc3c..c0fd70ae8 100644 --- a/src/uu/who/Cargo.toml +++ b/src/uu/who/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "who ~ (uutils) display information about currently logged-in users" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/who" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/who" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/who/LICENSE b/src/uu/who/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/who/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 8098e8065..151ef1141 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "whoami ~ (uutils) display user name of current effective user ID" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/whoami" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/whoami" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/whoami/LICENSE b/src/uu/whoami/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/whoami/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index 131b999db..2054274f8 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "yes ~ (uutils) repeatedly display a line with STRING (or 'y')" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uu/yes" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/yes" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] edition = "2018" diff --git a/src/uu/yes/LICENSE b/src/uu/yes/LICENSE new file mode 120000 index 000000000..5853aaea5 --- /dev/null +++ b/src/uu/yes/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 949ec25f6..c62e25d0b 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "uutils ~ 'core' uutils code library (cross-platform)" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uucore" +repository = "https://github.com/uutils/coreutils/tree/main/src/uucore" # readme = "README.md" keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] @@ -30,7 +30,7 @@ data-encoding = { version="2.1", optional=true } data-encoding-macro = { version="0.1.12", optional=true } z85 = { version="3.0.3", optional=true } libc = { version="0.2.15", optional=true } -once_cell = "1.8.0" +once_cell = "1.10.0" os_display = "0.1.0" [target.'cfg(unix)'.dependencies] diff --git a/src/uucore/LICENSE b/src/uucore/LICENSE new file mode 120000 index 000000000..30cff7403 --- /dev/null +++ b/src/uucore/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/src/uucore/src/lib/features/tokenize/num_format/formatters/float_common.rs b/src/uucore/src/lib/features/tokenize/num_format/formatters/float_common.rs index e62245534..eed543807 100644 --- a/src/uucore/src/lib/features/tokenize/num_format/formatters/float_common.rs +++ b/src/uucore/src/lib/features/tokenize/num_format/formatters/float_common.rs @@ -153,7 +153,9 @@ fn de_hex(src: &str, before_decimal: bool) -> String { // bumps the last digit up one, // and if the digit was nine // propagate to the next, etc. -fn _round_str_from(in_str: &str, position: usize) -> (String, bool) { +// If before the decimal and the most +// significant digit is a 9, it becomes a 1 +fn _round_str_from(in_str: &str, position: usize, before_dec: bool) -> (String, bool) { let mut it = in_str[0..position].chars(); let mut rev = String::new(); let mut i = position; @@ -162,7 +164,14 @@ fn _round_str_from(in_str: &str, position: usize) -> (String, bool) { i -= 1; match c { '9' => { - rev.push('0'); + // If we're before the decimal + // and on the most significant digit, + // round 9 to 1, else to 0. + if before_dec && i == 0 { + rev.push('1'); + } else { + rev.push('0'); + } } e => { rev.push(((e as u8) + 1) as char); @@ -182,24 +191,32 @@ fn round_terminal_digit( before_dec: String, after_dec: String, position: usize, -) -> (String, String) { +) -> (String, String, bool) { if position < after_dec.len() { let digit_at_pos: char; { digit_at_pos = after_dec[position..=position].chars().next().expect(""); } if let '5'..='9' = digit_at_pos { - let (new_after_dec, finished_in_dec) = _round_str_from(&after_dec, position); + let (new_after_dec, finished_in_dec) = _round_str_from(&after_dec, position, false); if finished_in_dec { - return (before_dec, new_after_dec); + return (before_dec, new_after_dec, false); } else { - let (new_before_dec, _) = _round_str_from(&before_dec, before_dec.len()); - return (new_before_dec, new_after_dec); + let (new_before_dec, _) = _round_str_from(&before_dec, before_dec.len(), true); + let mut dec_place_chg = false; + let mut before_dec_chars = new_before_dec.chars(); + if before_dec_chars.next() == Some('1') && before_dec_chars.all(|c| c == '0') { + // If the first digit is a one and remaining are zeros, we have + // rounded to a new decimal place, so the decimal place must be updated. + // Only update decimal place if the before decimal != 0 + dec_place_chg = before_dec != "0"; + } + return (new_before_dec, new_after_dec, dec_place_chg); } // TODO } } - (before_dec, after_dec) + (before_dec, after_dec, false) } pub fn get_primitive_dec( @@ -237,7 +254,7 @@ pub fn get_primitive_dec( String::from(second_segment_raw), ), }; - let (pre_dec_unrounded, post_dec_unrounded, mantissa) = if sci_mode.is_some() { + let (pre_dec_unrounded, post_dec_unrounded, mut mantissa) = if sci_mode.is_some() { if first_segment.len() > 1 { let mut post_dec = String::from(&first_segment[1..]); post_dec.push_str(&second_segment); @@ -277,13 +294,15 @@ pub fn get_primitive_dec( (first_segment, second_segment, 0) }; - let (pre_dec_draft, post_dec_draft) = + let (pre_dec_draft, post_dec_draft, dec_place_chg) = round_terminal_digit(pre_dec_unrounded, post_dec_unrounded, last_dec_place - 1); - - f.pre_decimal = Some(pre_dec_draft); f.post_decimal = Some(post_dec_draft); if let Some(capitalized) = sci_mode { let si_ind = if capitalized { 'E' } else { 'e' }; + // Increase the mantissa if we're adding a decimal place + if dec_place_chg { + mantissa += 1; + } f.suffix = Some(if mantissa >= 0 { format!("{}+{:02}", si_ind, mantissa) } else { @@ -291,6 +310,12 @@ pub fn get_primitive_dec( // leading zeroes format!("{}{:03}", si_ind, mantissa) }); + f.pre_decimal = Some(pre_dec_draft); + } else if dec_place_chg { + // We've rounded up to a new decimal place so append 0 + f.pre_decimal = Some(pre_dec_draft + "0"); + } else { + f.pre_decimal = Some(pre_dec_draft); } f diff --git a/src/uucore_procs/Cargo.toml b/src/uucore_procs/Cargo.toml index 2da22dbac..3760fe7f7 100644 --- a/src/uucore_procs/Cargo.toml +++ b/src/uucore_procs/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" description = "uutils ~ 'uucore' proc-macros" homepage = "https://github.com/uutils/coreutils" -repository = "https://github.com/uutils/coreutils/tree/master/src/uucore_procs" +repository = "https://github.com/uutils/coreutils/tree/main/src/uucore_procs" # readme = "README.md" keywords = ["cross-platform", "proc-macros", "uucore", "uutils"] # categories = ["os"] diff --git a/src/uucore_procs/LICENSE b/src/uucore_procs/LICENSE new file mode 120000 index 000000000..30cff7403 --- /dev/null +++ b/src/uucore_procs/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index f3c01722e..373ad97ce 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -334,19 +334,20 @@ fn test_chmod_recursive() { make_file(&at.plus_as_string("a/b/c/c"), 0o100444); make_file(&at.plus_as_string("z/y"), 0o100444); + // only the permissions of folder `a` and `z` are changed + // folder can't be read after read permission is removed ucmd.arg("-R") .arg("--verbose") .arg("-r,a+w") .arg("a") .arg("z") - .succeeds() - .stdout_contains(&"to 0333 (-wx-wx-wx)") - .stdout_contains(&"to 0222 (-w--w--w-)"); + .fails() + .stderr_is("chmod: Permission denied"); - assert_eq!(at.metadata("z/y").permissions().mode(), 0o100222); - assert_eq!(at.metadata("a/a").permissions().mode(), 0o100222); - assert_eq!(at.metadata("a/b/b").permissions().mode(), 0o100222); - assert_eq!(at.metadata("a/b/c/c").permissions().mode(), 0o100222); + assert_eq!(at.metadata("z/y").permissions().mode(), 0o100444); + assert_eq!(at.metadata("a/a").permissions().mode(), 0o100444); + assert_eq!(at.metadata("a/b/b").permissions().mode(), 0o100444); + assert_eq!(at.metadata("a/b/c/c").permissions().mode(), 0o100444); println!("mode {:o}", at.metadata("a").permissions().mode()); assert_eq!(at.metadata("a").permissions().mode(), 0o40333); assert_eq!(at.metadata("z").permissions().mode(), 0o40333); @@ -356,6 +357,23 @@ fn test_chmod_recursive() { } } +#[test] +#[allow(clippy::unreadable_literal)] +fn test_chmod_recursive_read_permission() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("a"); + at.mkdir("a/b"); + let mut perms = at.metadata("a/b").permissions(); + perms.set_mode(0o311); + set_permissions(at.plus_as_string("a/b"), perms.clone()).unwrap(); + set_permissions(at.plus_as_string("a"), perms).unwrap(); + + ucmd.arg("-R").arg("u+r").arg("a").succeeds(); + + assert_eq!(at.metadata("a").permissions().mode(), 0o40711); + assert_eq!(at.metadata("a/b").permissions().mode(), 0o40711); +} + #[test] fn test_chmod_non_existing_file() { new_ucmd!() @@ -455,8 +473,8 @@ fn test_chmod_symlink_non_existing_file_recursive() { let expected_stdout = &format!( // spell-checker:disable-next-line - "mode of '{}' retained as 0755 (rwxr-xr-x)\nneither symbolic link '{}/{}' nor referent has been changed", - test_directory, test_directory, test_symlink + "mode of '{}' retained as 0755 (rwxr-xr-x)", + test_directory ); // '-v': this should succeed without stderr diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index c9199151a..9ee76bb5e 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -29,6 +29,7 @@ static TEST_EXISTING_FILE: &str = "existing_file.txt"; static TEST_HELLO_WORLD_SOURCE: &str = "hello_world.txt"; static TEST_HELLO_WORLD_SOURCE_SYMLINK: &str = "hello_world.txt.link"; static TEST_HELLO_WORLD_DEST: &str = "copy_of_hello_world.txt"; +static TEST_HELLO_WORLD_DEST_SYMLINK: &str = "copy_of_hello_world.txt.link"; static TEST_HOW_ARE_YOU_SOURCE: &str = "how_are_you.txt"; static TEST_HOW_ARE_YOU_DEST: &str = "hello_dir/how_are_you.txt"; static TEST_COPY_TO_FOLDER: &str = "hello_dir/"; @@ -688,6 +689,51 @@ fn test_cp_no_deref() { assert_eq!(at.read(path_to_check), "Hello, World!\n"); } +#[test] +fn test_cp_no_deref_link_onto_link() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.copy(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_DEST); + + #[cfg(not(windows))] + let _r = fs::symlink( + TEST_HELLO_WORLD_SOURCE, + at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK), + ); + #[cfg(windows)] + let _r = symlink_file( + TEST_HELLO_WORLD_SOURCE, + at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK), + ); + + #[cfg(not(windows))] + let _r = fs::symlink( + TEST_HELLO_WORLD_DEST, + at.subdir.join(TEST_HELLO_WORLD_DEST_SYMLINK), + ); + #[cfg(windows)] + let _r = symlink_file( + TEST_HELLO_WORLD_DEST, + at.subdir.join(TEST_HELLO_WORLD_DEST_SYMLINK), + ); + + ucmd.arg("-P") + .arg(TEST_HELLO_WORLD_SOURCE_SYMLINK) + .arg(TEST_HELLO_WORLD_DEST_SYMLINK) + .succeeds(); + + // Ensure that the target of the destination was not modified. + assert!(!at + .symlink_metadata(TEST_HELLO_WORLD_DEST) + .file_type() + .is_symlink()); + assert!(at + .symlink_metadata(TEST_HELLO_WORLD_DEST_SYMLINK) + .file_type() + .is_symlink()); + assert_eq!(at.read(TEST_HELLO_WORLD_DEST_SYMLINK), "Hello, World!\n"); +} + #[test] fn test_cp_strip_trailing_slashes() { let (at, mut ucmd) = at_and_ucmd!(); @@ -1463,6 +1509,20 @@ fn test_cp_archive_on_nonexistent_file() { ); } +#[test] +fn test_cp_link_backup() { + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file2"); + ucmd.arg("-l") + .arg("-b") + .arg(TEST_HELLO_WORLD_SOURCE) + .arg("file2") + .succeeds(); + + assert!(at.file_exists("file2~")); + assert_eq!(at.read("file2"), "Hello, World!\n"); +} + #[test] #[cfg(unix)] fn test_cp_fifo() { diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index 3af02428e..a096b4b4c 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -1,3 +1,4 @@ +// spell-checker:ignore udev use crate::common::util::*; #[test] @@ -27,6 +28,8 @@ fn test_df_compatible_si() { #[test] fn test_df_output() { + // TODO These should fail because `-total` should have two dashes, + // not just one. But they don't fail. if cfg!(target_os = "macos") { new_ucmd!().arg("-H").arg("-total").succeeds(). stdout_only("Filesystem Size Used Available Capacity Use% Mounted on \n"); @@ -40,24 +43,29 @@ fn test_df_output() { /// Test that the order of rows in the table does not change across executions. #[test] fn test_order_same() { - // TODO When #3057 is resolved, we should just use - // - // new_ucmd!().arg("--output=source").succeeds().stdout_move_str(); - // - // instead of parsing the entire `df` table as a string. - let output1 = new_ucmd!().succeeds().stdout_move_str(); - let output2 = new_ucmd!().succeeds().stdout_move_str(); + let output1 = new_ucmd!() + .arg("--output=source") + .succeeds() + .stdout_move_str(); + let output2 = new_ucmd!() + .arg("--output=source") + .succeeds() + .stdout_move_str(); + assert_eq!(output1, output2); +} + +/// Test of mount point begin repeated +#[cfg(unix)] +#[test] +fn test_output_mp_repeat() { + let output1 = new_ucmd!().arg("/").arg("/").succeeds().stdout_move_str(); let output1: Vec = output1 .lines() .map(|l| String::from(l.split_once(' ').unwrap().0)) .collect(); - let output2: Vec = output2 - .lines() - .map(|l| String::from(l.split_once(' ').unwrap().0)) - .collect(); - assert_eq!(output1, output2); + assert_eq!(3, output1.len()); + assert_eq!(output1[1], output1[2]); } - #[test] fn test_output_conflict_options() { for option in ["-i", "-T", "-P"] { @@ -77,4 +85,136 @@ fn test_type_option() { new_ucmd!().args(&["-t", "ext4", "-t", "ext3"]).succeeds(); } +#[test] +fn test_total() { + // Example output: + // + // Filesystem 1K-blocks Used Available Use% Mounted on + // udev 3858016 0 3858016 0% /dev + // ... + // /dev/loop14 63488 63488 0 100% /snap/core20/1361 + // total 258775268 98099712 148220200 40% - + let output = new_ucmd!().arg("--total").succeeds().stdout_move_str(); + + // Skip the header line. + let lines: Vec<&str> = output.lines().skip(1).collect(); + + // Parse the values from the last row. + let last_line = lines.last().unwrap(); + let mut iter = last_line.split_whitespace(); + assert_eq!(iter.next().unwrap(), "total"); + let reported_total_size = iter.next().unwrap().parse().unwrap(); + let reported_total_used = iter.next().unwrap().parse().unwrap(); + let reported_total_avail = iter.next().unwrap().parse().unwrap(); + + // Loop over each row except the last, computing the sum of each column. + let mut computed_total_size = 0; + let mut computed_total_used = 0; + let mut computed_total_avail = 0; + let n = lines.len(); + for line in &lines[..n - 1] { + let mut iter = line.split_whitespace(); + iter.next().unwrap(); + computed_total_size += iter.next().unwrap().parse::().unwrap(); + computed_total_used += iter.next().unwrap().parse::().unwrap(); + computed_total_avail += iter.next().unwrap().parse::().unwrap(); + } + + // Check that the sum of each column matches the reported value in + // the last row. + assert_eq!(computed_total_size, reported_total_size); + assert_eq!(computed_total_used, reported_total_used); + assert_eq!(computed_total_avail, reported_total_avail); +} + +#[test] +fn test_use_percentage() { + // Example output: + // + // Filesystem 1K-blocks Used Available Use% Mounted on + // udev 3858016 0 3858016 0% /dev + // ... + // /dev/loop14 63488 63488 0 100% /snap/core20/1361 + let output = new_ucmd!().succeeds().stdout_move_str(); + + // Skip the header line. + let lines: Vec<&str> = output.lines().skip(1).collect(); + + for line in lines { + let mut iter = line.split_whitespace(); + iter.next(); + let reported_size = iter.next().unwrap().parse::().unwrap(); + let reported_used = iter.next().unwrap().parse::().unwrap(); + // Skip "Available" column + iter.next(); + if cfg!(target_os = "macos") { + // Skip "Capacity" column + iter.next(); + } + let reported_percentage = iter.next().unwrap(); + let reported_percentage = reported_percentage[..reported_percentage.len() - 1] + .parse::() + .unwrap(); + let computed_percentage = (100.0 * (reported_used / reported_size)).ceil() as u8; + + assert_eq!(computed_percentage, reported_percentage); + } +} + +#[test] +fn test_block_size_1024() { + fn get_header(block_size: u64) -> String { + // TODO When #3057 is resolved, we should just use + // + // new_ucmd!().arg("--output=size").succeeds().stdout_move_str(); + // + // instead of parsing the entire `df` table as a string. + let output = new_ucmd!() + .args(&["-B", &format!("{}", block_size)]) + .succeeds() + .stdout_move_str(); + let first_line = output.lines().next().unwrap(); + let mut column_labels = first_line.split_whitespace(); + let size_column_label = column_labels.nth(1).unwrap(); + size_column_label.into() + } + + assert_eq!(get_header(1024), "1K-blocks"); + assert_eq!(get_header(2048), "2K-blocks"); + assert_eq!(get_header(4096), "4K-blocks"); + assert_eq!(get_header(1024 * 1024), "1M-blocks"); + assert_eq!(get_header(2 * 1024 * 1024), "2M-blocks"); + assert_eq!(get_header(1024 * 1024 * 1024), "1G-blocks"); + assert_eq!(get_header(34 * 1024 * 1024 * 1024), "34G-blocks"); +} + +// TODO The spacing does not match GNU df. Also we need to remove +// trailing spaces from the heading row. +#[test] +fn test_output_selects_columns() { + let output = new_ucmd!() + .args(&["--output=source"]) + .succeeds() + .stdout_move_str(); + assert_eq!(output.lines().next().unwrap(), "Filesystem "); + + let output = new_ucmd!() + .args(&["--output=source,target"]) + .succeeds() + .stdout_move_str(); + assert_eq!( + output.lines().next().unwrap(), + "Filesystem Mounted on " + ); + + let output = new_ucmd!() + .args(&["--output=source,target,used"]) + .succeeds() + .stdout_move_str(); + assert_eq!( + output.lines().next().unwrap(), + "Filesystem Mounted on Used " + ); +} + // ToDO: more tests... diff --git a/tests/by-util/test_mkdir.rs b/tests/by-util/test_mkdir.rs index 730beaef1..6d617f4e1 100644 --- a/tests/by-util/test_mkdir.rs +++ b/tests/by-util/test_mkdir.rs @@ -11,7 +11,9 @@ static TEST_DIR4: &str = "mkdir_test4/mkdir_test4_1"; static TEST_DIR5: &str = "mkdir_test5/mkdir_test5_1"; static TEST_DIR6: &str = "mkdir_test6"; static TEST_FILE7: &str = "mkdir_test7"; -static TEST_DIR8: &str = "mkdir_test8"; +static TEST_DIR8: &str = "mkdir_test8/mkdir_test8_1/mkdir_test8_2"; +static TEST_DIR9: &str = "mkdir_test9/../mkdir_test9_1/../mkdir_test9_2"; +static TEST_DIR10: &str = "mkdir_test10"; #[test] fn test_mkdir_mkdir() { @@ -104,6 +106,27 @@ fn test_multi_symbolic() { assert_eq!(perms, 0o40750); } +#[test] +fn test_recursive_reporting() { + new_ucmd!() + .arg("-p") + .arg("-v") + .arg(TEST_DIR8) + .succeeds() + .stdout_contains("created directory 'mkdir_test8'") + .stdout_contains("created directory 'mkdir_test8/mkdir_test8_1'") + .stdout_contains("created directory 'mkdir_test8/mkdir_test8_1/mkdir_test8_2'"); + new_ucmd!().arg("-v").arg(TEST_DIR8).fails().no_stdout(); + new_ucmd!() + .arg("-p") + .arg("-v") + .arg(TEST_DIR9) + .succeeds() + .stdout_contains("created directory 'mkdir_test9'") + .stdout_contains("created directory 'mkdir_test9/../mkdir_test9_1'") + .stdout_contains("created directory 'mkdir_test9/../mkdir_test9_1/../mkdir_test9_2'"); +} + #[test] #[cfg(not(windows))] fn test_umask_compliance() { @@ -112,8 +135,8 @@ fn test_umask_compliance() { let original_umask = unsafe { umask(umask_set) }; - ucmd.arg(TEST_DIR8).succeeds(); - let perms = at.metadata(TEST_DIR8).permissions().mode() as mode_t; + ucmd.arg(TEST_DIR10).succeeds(); + let perms = at.metadata(TEST_DIR10).permissions().mode() as mode_t; assert_eq!(perms, (!umask_set & 0o0777) + 0o40000); // before compare, add the setguid, uid bits unsafe { umask(original_umask); } // set umask back to original @@ -122,5 +145,4 @@ fn test_umask_compliance() { for i in 0o0..0o777 { // tests all permission combinations test_single_case(i); } - } diff --git a/tests/by-util/test_nproc.rs b/tests/by-util/test_nproc.rs index abf758829..5657e6b7e 100644 --- a/tests/by-util/test_nproc.rs +++ b/tests/by-util/test_nproc.rs @@ -1,3 +1,4 @@ +// spell-checker:ignore incorrectnumber use crate::common::util::*; #[test] @@ -15,11 +16,11 @@ fn test_nproc_all_omp() { let result = TestScenario::new(util_name!()) .ucmd_keepenv() - .env("OMP_NUM_THREADS", "1") + .env("OMP_NUM_THREADS", "60") .succeeds(); let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap(); - assert!(nproc - 1 == nproc_omp); + assert!(nproc_omp == 60); let result = TestScenario::new(util_name!()) .ucmd_keepenv() @@ -28,20 +29,46 @@ fn test_nproc_all_omp() { .succeeds(); let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap(); assert!(nproc == nproc_omp); + + // If the parsing fails, returns the number of CPU + let result = TestScenario::new(util_name!()) + .ucmd_keepenv() + .env("OMP_NUM_THREADS", "incorrectnumber") // returns the number CPU + .succeeds(); + let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap(); + assert!(nproc == nproc_omp); } #[test] fn test_nproc_ignore() { let result = new_ucmd!().succeeds(); - let nproc: u8 = result.stdout_str().trim().parse().unwrap(); - if nproc > 1 { + let nproc_total: u8 = result.stdout_str().trim().parse().unwrap(); + if nproc_total > 1 { // Ignore all CPU but one let result = TestScenario::new(util_name!()) .ucmd_keepenv() .arg("--ignore") - .arg((nproc - 1).to_string()) + .arg((nproc_total - 1).to_string()) .succeeds(); let nproc: u8 = result.stdout_str().trim().parse().unwrap(); assert!(nproc == 1); + // Ignore all CPU but one with a string + let result = TestScenario::new(util_name!()) + .ucmd_keepenv() + .arg("--ignore= 1") + .succeeds(); + let nproc: u8 = result.stdout_str().trim().parse().unwrap(); + assert!(nproc_total - 1 == nproc); } } + +#[test] +fn test_nproc_ignore_all_omp() { + let result = TestScenario::new(util_name!()) + .ucmd_keepenv() + .env("OMP_NUM_THREADS", "42") + .arg("--ignore=40") + .succeeds(); + let nproc: u8 = result.stdout_str().trim().parse().unwrap(); + assert!(nproc == 2); +} diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index 5c16e7acc..2e72aaed7 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -448,3 +448,16 @@ fn test_with_join_lines_option() { &valid_last_modified_template_vars(start), ); } + +#[test] +fn test_value_for_number_lines() { + // *5 is of the form [SEP[NUMBER]] so is accepted and succeeds + new_ucmd!().args(&["-n", "*5", "test.log"]).succeeds(); + + // a is of the form [SEP[NUMBER]] so is accepted and succeeds + new_ucmd!().args(&["-n", "a", "test.log"]).succeeds(); + + // foo5.txt is of not the form [SEP[NUMBER]] so is not used as value. + // Therefore, pr tries to access the file, which does not exist. + new_ucmd!().args(&["-n", "foo5.txt", "test.log"]).fails(); +} diff --git a/tests/by-util/test_printf.rs b/tests/by-util/test_printf.rs index b3e608dc9..72ca4535a 100644 --- a/tests/by-util/test_printf.rs +++ b/tests/by-util/test_printf.rs @@ -228,6 +228,22 @@ fn sub_num_float() { .stdout_only("twenty is 20.000000"); } +#[test] +fn sub_num_float_e_round() { + new_ucmd!() + .args(&["%e", "99999999"]) + .succeeds() + .stdout_only("1.000000e+08"); +} + +#[test] +fn sub_num_float_e_no_round() { + new_ucmd!() + .args(&["%e", "99999994"]) + .succeeds() + .stdout_only("9.999999e+07"); +} + #[test] fn sub_num_float_round() { new_ucmd!() @@ -236,6 +252,14 @@ fn sub_num_float_round() { .stdout_only("two is 2.000000"); } +#[test] +fn sub_num_float_round_nines_dec() { + new_ucmd!() + .args(&["%f", "0.99999999"]) + .succeeds() + .stdout_only("1.000000"); +} + #[test] fn sub_num_sci_lower() { new_ucmd!() diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index 5ae56b29e..c33d1a986 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -338,24 +338,20 @@ fn test_dictionary_order() { #[test] fn test_dictionary_order2() { - for non_dictionary_order2_param in &["-d"] { - new_ucmd!() - .pipe_in("a👦🏻aa\tb\naaaa\tb") // spell-checker:disable-line - .arg(non_dictionary_order2_param) // spell-checker:disable-line - .succeeds() - .stdout_only("a👦🏻aa\tb\naaaa\tb\n"); // spell-checker:disable-line - } + new_ucmd!() + .pipe_in("a👦🏻aa\tb\naaaa\tb") // spell-checker:disable-line + .arg("-d") + .succeeds() + .stdout_only("a👦🏻aa\tb\naaaa\tb\n"); // spell-checker:disable-line } #[test] fn test_non_printing_chars() { - for non_printing_chars_param in &["-i"] { - new_ucmd!() - .pipe_in("a👦🏻aa\naaaa") // spell-checker:disable-line - .arg(non_printing_chars_param) // spell-checker:disable-line - .succeeds() - .stdout_only("a👦🏻aa\naaaa\n"); // spell-checker:disable-line - } + new_ucmd!() + .pipe_in("a👦🏻aa\naaaa") // spell-checker:disable-line + .arg("-i") + .succeeds() + .stdout_only("a👦🏻aa\naaaa\n"); // spell-checker:disable-line } #[test] @@ -486,14 +482,12 @@ fn test_default_unsorted_ints2() { #[test] fn test_numeric_unique_ints2() { - for numeric_unique_sort_param in &["-nu"] { - let input = "9\n9\n8\n1\n"; - new_ucmd!() - .arg(numeric_unique_sort_param) - .pipe_in(input) - .succeeds() - .stdout_only("1\n8\n9\n"); - } + let input = "9\n9\n8\n1\n"; + new_ucmd!() + .arg("-nu") + .pipe_in(input) + .succeeds() + .stdout_only("1\n8\n9\n"); } #[test] diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index ab59a573a..642cb7c68 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -2,7 +2,7 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes +// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes onehundredlines nbbbb extern crate rand; extern crate regex; @@ -587,3 +587,21 @@ fn test_lines() { assert_eq!(file_read("xaa"), "1\n2\n3\n"); assert_eq!(file_read("xab"), "4\n5\n"); } + +#[test] +fn test_lines_kth() { + new_ucmd!() + .args(&["-n", "l/3/10", "onehundredlines.txt"]) + .succeeds() + .stdout_only("20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n"); +} + +#[test] +fn test_line_bytes() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-C", "8", "letters.txt"]).succeeds(); + assert_eq!(at.read("xaa"), "aaaaaaaa"); + assert_eq!(at.read("xab"), "a\nbbbb\n"); + assert_eq!(at.read("xac"), "cccc\ndd\n"); + assert_eq!(at.read("xad"), "ee\n"); +} diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 9be29065a..b9c8a59ed 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -45,3 +45,22 @@ fn test_zero_timeout() { .no_stderr() .no_stdout(); } + +#[test] +fn test_command_empty_args() { + new_ucmd!() + .args(&["", ""]) + .fails() + .stderr_contains("timeout: empty string"); +} + +#[test] +fn test_preserve_status() { + new_ucmd!() + .args(&["--preserve-status", ".1", "sleep", "10"]) + .fails() + // 128 + SIGTERM = 128 + 15 + .code_is(128 + 15) + .no_stderr() + .no_stdout(); +} diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index ac82a81ea..f869d1560 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -510,6 +510,27 @@ fn test_touch_no_such_file_error_msg() { )); } +#[test] +fn test_touch_changes_time_of_file_in_stdout() { + // command like: `touch - 1< ./c` + // should change the timestamp of c + + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_touch_changes_time_of_file_in_stdout"; + + at.touch(file); + assert!(at.file_exists(file)); + let (_, mtime) = get_file_times(&at, file); + + ucmd.args(&["-"]) + .set_stdout(at.make_file(file)) + .succeeds() + .no_stderr(); + + let (_, mtime_after) = get_file_times(&at, file); + assert!(mtime_after != mtime); +} + #[test] #[cfg(unix)] fn test_touch_permission_denied_error_msg() { diff --git a/tests/by-util/test_who.rs b/tests/by-util/test_who.rs index 8c3bba25c..2687539f1 100644 --- a/tests/by-util/test_who.rs +++ b/tests/by-util/test_who.rs @@ -69,10 +69,8 @@ fn test_login() { #[test] fn test_m() { let ts = TestScenario::new(util_name!()); - for opt in &["-m"] { - let expected_stdout = unwrap_or_return!(expected_result(&ts, &[opt])).stdout_move_str(); - ts.ucmd().arg(opt).succeeds().stdout_is(expected_stdout); - } + let expected_stdout = unwrap_or_return!(expected_result(&ts, &["-m"])).stdout_move_str(); + ts.ucmd().arg("-m").succeeds().stdout_is(expected_stdout); } #[cfg(unix)] diff --git a/tests/fixtures/split/letters.txt b/tests/fixtures/split/letters.txt new file mode 100644 index 000000000..03e1003e3 --- /dev/null +++ b/tests/fixtures/split/letters.txt @@ -0,0 +1,5 @@ +aaaaaaaaa +bbbb +cccc +dd +ee diff --git a/tests/fixtures/split/onehundredlines.txt b/tests/fixtures/split/onehundredlines.txt new file mode 100644 index 000000000..f2abdb403 --- /dev/null +++ b/tests/fixtures/split/onehundredlines.txt @@ -0,0 +1,100 @@ +00 +01 +02 +03 +04 +05 +06 +07 +08 +09 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 diff --git a/util/build-gnu.sh b/util/build-gnu.sh index bb8d6e522..adf69e3be 100755 --- a/util/build-gnu.sh +++ b/util/build-gnu.sh @@ -70,33 +70,17 @@ sed -i 's|^"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${UU_BUILD_DIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile sed -i 's| tr | /usr/bin/tr |' tests/init.sh make -j "$(nproc)" -first=00 if test ${UU_MAKE_PROFILE} != "debug"; then # Generate the factor tests, so they can be fixed # * reduced to 20 to decrease log size (down from 36 expected by GNU) # * only for 'release', skipped for 'debug' as redundant and too time consuming (causing timeout errors) - seq=$( - i=${first} - while test "$i" -le 20; do - printf '%02d ' $i - i=$(($i + 1)) - done - ) - for i in ${seq}; do + for i in $(seq -w 0 20); do make "tests/factor/t${i}.sh" done - sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh - first=21 + sed -i -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh fi # strip all (debug) or just the longer (release) factor tests from Makefile -seq=$( - i=${first} - while test "$i" -le 36; do - printf '%02d ' $i - i=$(($i + 1)) - done -) -for i in ${seq}; do +for i in $(seq 20 36); do echo "strip t${i}.sh from Makefile" sed -i -e "s/\$(tf)\/t${i}.sh//g" Makefile done @@ -118,22 +102,17 @@ sed -i -e '/tests\/misc\/seq-precision.sh/ D' \ sed -i '/INT_OFLOW/ D' tests/misc/printf.sh # Use the system coreutils where the test fails due to error in a util that is not the one being tested -sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh -sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh -sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh +sed -i 's|stat|/usr/bin/stat|' tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh +sed -i 's|ls -|/usr/bin/ls -|' tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh sed -i 's|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh tests/misc/shuf.sh # Don't break the function called 'grep_timeout' -sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.sh tests/misc/shuf.sh +sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/mv/i-2.sh tests/misc/shuf.sh sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh -sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh -sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh init.cfg sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh -sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/ls/abmon-align.sh tests/ls/rt-1.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh +sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh -sed -i 's|printf |/usr/bin/printf |g' tests/dd/ascii.sh sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh -sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh # Add specific timeout to tests that currently hang to limit time spent waiting sed -i 's|\(^\s*\)seq \$|\1/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh