├── .bumpversion.toml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ └── feature_request.yaml ├── dependabot.yml ├── pull_request_template.md ├── pull_request_template.md.license └── workflows │ ├── CD.yaml │ ├── CI.yaml │ ├── REUSE.yaml │ ├── SemverChecks.yaml │ ├── actionlint.yaml │ ├── dependabot_auto_merge.yaml │ └── deploy.yaml ├── .gitignore ├── AUTHORS.adoc ├── BUILD.adoc ├── CHANGELOG.adoc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.adoc ├── Cargo.lock ├── Cargo.lock.license ├── Cargo.toml ├── Cross.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── LICENSES ├── Apache-2.0.txt ├── CC-BY-4.0.txt └── MIT.txt ├── README.md ├── antora-playbook.yml ├── assets ├── README.adoc ├── demo.gif ├── demo.gif.license ├── demo.yaml └── generate.bash ├── build.rs ├── clippy.toml ├── docs ├── book │ ├── antora.yml │ ├── modules │ │ └── ROOT │ │ │ ├── examples │ │ │ └── LICENSES │ │ │ ├── images │ │ │ └── demo.gif │ │ │ ├── nav.adoc │ │ │ ├── pages │ │ │ ├── authors.adoc │ │ │ ├── build.adoc │ │ │ ├── changelog.adoc │ │ │ ├── contributing.adoc │ │ │ ├── index.adoc │ │ │ ├── install.adoc │ │ │ ├── license.adoc │ │ │ ├── man │ │ │ │ └── man1 │ │ │ │ │ ├── hf-completion.1.adoc │ │ │ │ │ ├── hf-hide.1.adoc │ │ │ │ │ ├── hf-show.1.adoc │ │ │ │ │ └── hf.1.adoc │ │ │ ├── repository.adoc │ │ │ ├── usage.adoc │ │ │ └── use-as-a-library.adoc │ │ │ └── partials │ │ │ └── man │ │ │ └── man1 │ │ │ └── include │ └── supplemental-ui │ │ └── partials │ │ ├── footer-content.hbs │ │ └── header-content.hbs └── man │ └── man1 │ ├── hf-completion.1.adoc │ ├── hf-hide.1.adoc │ ├── hf-show.1.adoc │ ├── hf.1.adoc │ └── include │ ├── section-copyright.adoc │ ├── section-exit-status.adoc │ ├── section-notes.adoc │ └── section-reporting-bugs.adoc ├── justfile ├── package-lock.json ├── package-lock.json.license ├── package.json ├── package.json.license ├── rustfmt.toml ├── src ├── bin │ └── hf │ │ ├── app.rs │ │ ├── cli.rs │ │ └── main.rs ├── lib.rs ├── ops.rs ├── platform.rs └── platform │ ├── unix.rs │ └── windows.rs └── tests ├── completion.rs ├── hide.rs ├── integration.rs ├── show.rs └── utils ├── command.rs └── mod.rs /.bumpversion.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | [tool.bumpversion] 6 | current_version = "0.4.0" 7 | 8 | [[tool.bumpversion.files]] 9 | glob = "docs/man/man1/*.1.adoc" 10 | 11 | [[tool.bumpversion.files]] 12 | filename = "src/lib.rs" 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | name: 🐞 Bug report 6 | description: Create a report to help us improve 7 | labels: ["bug"] 8 | 9 | body: 10 | - type: checkboxes 11 | attributes: 12 | label: Checklist 13 | options: 14 | - label: I have searched the existing issues 15 | required: true 16 | 17 | - type: textarea 18 | attributes: 19 | label: Describe the bug 20 | description: A clear and concise description of what the bug is. 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | attributes: 26 | label: To Reproduce 27 | description: Steps to reproduce the behavior. 28 | validations: 29 | required: false 30 | 31 | - type: textarea 32 | attributes: 33 | label: Expected behavior 34 | description: A clear and concise description of what you expected to happen. 35 | validations: 36 | required: true 37 | 38 | - type: input 39 | attributes: 40 | label: hf version 41 | description: Output of `hf -V`, or can be found in `Cargo.toml` of your project. 42 | placeholder: 0.1.0 43 | validations: 44 | required: true 45 | 46 | - type: input 47 | attributes: 48 | label: Rust version 49 | description: Output of `rustc -V`. 50 | placeholder: 1.56.0 51 | validations: 52 | required: true 53 | 54 | - type: textarea 55 | attributes: 56 | label: Environment 57 | description: Tell us where on the platform it happens. 58 | placeholder: | 59 | - OS: Debian 12 60 | - arch: x86_64 61 | validations: 62 | required: false 63 | 64 | - type: textarea 65 | attributes: 66 | label: Additional context 67 | description: Add any other context about the problem here. 68 | validations: 69 | required: false 70 | 71 | - type: checkboxes 72 | attributes: 73 | label: Code of Conduct 74 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/sorairolake/hf/blob/develop/CODE_OF_CONDUCT.md) 75 | options: 76 | - label: I agree to follow this project's Code of Conduct 77 | required: true 78 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | name: 💡 Feature request 6 | description: Suggest an idea for this project 7 | labels: ["enhancement"] 8 | 9 | body: 10 | - type: checkboxes 11 | attributes: 12 | label: Checklist 13 | options: 14 | - label: I have searched the existing issues 15 | required: true 16 | 17 | - type: textarea 18 | attributes: 19 | label: Summary 20 | description: A clear and concise description of what the suggestion is. 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | attributes: 26 | label: Additional context 27 | description: Add any other context about the feature request here. 28 | validations: 29 | required: false 30 | 31 | - type: checkboxes 32 | attributes: 33 | label: Code of Conduct 34 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/sorairolake/hf/blob/develop/CODE_OF_CONDUCT.md) 35 | options: 36 | - label: I agree to follow this project's Code of Conduct 37 | required: true 38 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | version: 2 6 | updates: 7 | - package-ecosystem: "cargo" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | open-pull-requests-limit: 15 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | open-pull-requests-limit: 20 18 | 19 | - package-ecosystem: "npm" 20 | directory: "/" 21 | schedule: 22 | interval: "weekly" 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | 9 | 10 | Closes # 11 | 12 | ## Additional context 13 | 14 | 15 | 16 | ## Checklist 17 | 18 | - [ ] I have read the [Contribution Guide]. 19 | - [ ] I agree to follow the [Code of Conduct]. 20 | 21 | [Contribution Guide]: https://github.com/sorairolake/hf/blob/develop/CONTRIBUTING.adoc 22 | [Code of Conduct]: https://github.com/sorairolake/hf/blob/develop/CODE_OF_CONDUCT.md 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2024 Shun Sakai 2 | 3 | SPDX-License-Identifier: CC-BY-4.0 4 | -------------------------------------------------------------------------------- /.github/workflows/CD.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | name: CD 6 | 7 | on: 8 | push: 9 | tags: 10 | - "v[0-9]+.[0-9]+.[0-9]+" 11 | workflow_dispatch: 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | get-version: 18 | name: Get version 19 | runs-on: ubuntu-24.04 20 | outputs: 21 | version: ${{ steps.get_version.outputs.version }} 22 | version_without_v: ${{ steps.get_version_without_v.outputs.version-without-v }} 23 | steps: 24 | - name: Get version 25 | id: get_version 26 | run: echo "version=${GITHUB_REF_NAME##*/}" >> "$GITHUB_OUTPUT" 27 | - name: Get version without v 28 | id: get_version_without_v 29 | run: echo "version-without-v=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" 30 | 31 | build: 32 | name: Build 33 | needs: get-version 34 | runs-on: ${{ matrix.os }} 35 | strategy: 36 | matrix: 37 | target: 38 | - aarch64-unknown-linux-musl 39 | - x86_64-unknown-linux-musl 40 | - aarch64-apple-darwin 41 | - x86_64-apple-darwin 42 | - aarch64-pc-windows-msvc 43 | - x86_64-pc-windows-msvc 44 | include: 45 | - target: aarch64-unknown-linux-musl 46 | os: ubuntu-24.04 47 | use-cross: true 48 | - target: x86_64-unknown-linux-musl 49 | os: ubuntu-24.04 50 | use-cross: true 51 | - target: aarch64-apple-darwin 52 | os: macos-14 53 | - target: x86_64-apple-darwin 54 | os: macos-14 55 | - target: aarch64-pc-windows-msvc 56 | os: windows-2022 57 | - target: x86_64-pc-windows-msvc 58 | os: windows-2022 59 | steps: 60 | - name: Checkout code 61 | uses: actions/checkout@v4 62 | - name: Setup Rust toolchain 63 | uses: dtolnay/rust-toolchain@v1 64 | with: 65 | toolchain: stable 66 | targets: ${{ matrix.target }} 67 | - name: Install cross 68 | if: ${{ matrix.use-cross }} 69 | uses: taiki-e/install-action@v2.52.1 70 | with: 71 | tool: cross 72 | - name: Cache build artifacts 73 | uses: Swatinem/rust-cache@v2.7.8 74 | with: 75 | key: ${{ matrix.target }} 76 | - name: Setup Ruby 77 | if: matrix.os != 'windows-2022' && !matrix.use-cross 78 | uses: ruby/setup-ruby@v1 79 | with: 80 | ruby-version: 3.3 81 | - name: Install Asciidoctor 82 | if: matrix.os != 'windows-2022' && !matrix.use-cross 83 | run: | 84 | gem install asciidoctor 85 | asciidoctor -V 86 | - name: Build a package 87 | if: ${{ !matrix.use-cross }} 88 | run: cargo build --release --target ${{ matrix.target }} 89 | - name: Build a package with cross 90 | if: ${{ matrix.use-cross }} 91 | run: cross build --release --target ${{ matrix.target }} 92 | - name: Create a package 93 | shell: bash 94 | run: | 95 | if [ "${{ matrix.os }}" != "windows-2022" ] ; then 96 | bin="target/${{ matrix.target }}/release/hf" 97 | else 98 | bin="target/${{ matrix.target }}/release/hf.exe" 99 | fi 100 | package="hf-${{ needs.get-version.outputs.version }}-${{ matrix.target }}" 101 | 102 | mkdir -p "${package}"/docs 103 | cp README.md "${bin}" "${package}" 104 | cp -r LICENSES "${package}" 105 | cp {AUTHORS,BUILD,CHANGELOG,CONTRIBUTING}.adoc "${package}"/docs 106 | if [ "${{ matrix.os }}" != "windows-2022" ] ; then 107 | mkdir -p "${package}"/man 108 | cp "$(find ./target -path '*/hf-*/out' -type d)"/* "${package}"/man 109 | fi 110 | 111 | if [ "${{ matrix.os }}" != "windows-2022" ] ; then 112 | tar -cv --format=pax -f "${package}.tar" "${package}" 113 | zstd --rm -19 -v "${package}.tar" 114 | else 115 | 7z a -bb -mx=9 -m0=LZMA "${package}.7z" "${package}" 116 | fi 117 | rm -rv hf-*/ 118 | - name: Upload artifact 119 | uses: actions/upload-artifact@v4 120 | with: 121 | name: "hf-${{ needs.get-version.outputs.version }}-${{ matrix.target }}" 122 | path: "hf-*-*" 123 | 124 | release: 125 | name: Release 126 | needs: 127 | - get-version 128 | - build 129 | runs-on: ubuntu-24.04 130 | steps: 131 | - name: Download artifact 132 | uses: actions/download-artifact@v4 133 | - name: Calculate checksums 134 | run: | 135 | mv hf-*/* . 136 | rmdir -v hf-*/ 137 | sha256sum hf-* | tee sha256sums.txt 138 | b2sum hf-* | tee b2sums.txt 139 | - name: Release 140 | uses: softprops/action-gh-release@v2.2.2 141 | if: startsWith(github.ref, 'refs/tags/') 142 | with: 143 | draft: true 144 | files: | 145 | hf-* 146 | sha256sums.txt 147 | b2sums.txt 148 | name: "Release version ${{ needs.get-version.outputs.version_without_v }}" 149 | env: 150 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 151 | -------------------------------------------------------------------------------- /.github/workflows/CI.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | name: CI 6 | 7 | on: 8 | pull_request: 9 | push: 10 | branches: 11 | - "develop" 12 | - "master" 13 | schedule: 14 | - cron: "0 0 * * 0" 15 | 16 | jobs: 17 | check: 18 | name: Check 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | os-alias: 23 | - ubuntu 24 | - macos 25 | - windows 26 | toolchain-alias: 27 | - msrv 28 | - stable 29 | include: 30 | - os-alias: ubuntu 31 | os: ubuntu-24.04 32 | target: x86_64-unknown-linux-gnu 33 | - os-alias: macos 34 | os: macos-14 35 | target: aarch64-apple-darwin 36 | - os-alias: windows 37 | os: windows-2022 38 | target: x86_64-pc-windows-msvc 39 | - toolchain-alias: msrv 40 | toolchain: 1.85.0 41 | - toolchain-alias: stable 42 | toolchain: stable 43 | steps: 44 | - name: Checkout code 45 | uses: actions/checkout@v4 46 | - name: Setup Rust toolchain 47 | uses: dtolnay/rust-toolchain@v1 48 | with: 49 | toolchain: ${{ matrix.toolchain }} 50 | targets: ${{ matrix.target }} 51 | - name: Cache build artifacts 52 | uses: Swatinem/rust-cache@v2.7.8 53 | with: 54 | key: ${{ matrix.target }} 55 | - name: Check a package 56 | run: cargo check --target ${{ matrix.target }} 57 | - name: Check a package (no default features) 58 | run: cargo check --target ${{ matrix.target }} --no-default-features 59 | 60 | test: 61 | name: Test 62 | runs-on: ${{ matrix.os }} 63 | strategy: 64 | matrix: 65 | os-alias: 66 | - ubuntu 67 | - macos 68 | - windows 69 | toolchain-alias: 70 | - msrv 71 | - stable 72 | include: 73 | - os-alias: ubuntu 74 | os: ubuntu-24.04 75 | target: x86_64-unknown-linux-gnu 76 | - os-alias: macos 77 | os: macos-14 78 | target: aarch64-apple-darwin 79 | - os-alias: windows 80 | os: windows-2022 81 | target: x86_64-pc-windows-msvc 82 | - toolchain-alias: msrv 83 | toolchain: 1.85.0 84 | - toolchain-alias: stable 85 | toolchain: stable 86 | steps: 87 | - name: Set Git to use LF 88 | if: matrix.os == 'windows-2022' 89 | run: | 90 | git config --global core.autocrlf false 91 | git config --global core.eol lf 92 | - name: Checkout code 93 | uses: actions/checkout@v4 94 | - name: Setup Rust toolchain 95 | uses: dtolnay/rust-toolchain@v1 96 | with: 97 | toolchain: ${{ matrix.toolchain }} 98 | targets: ${{ matrix.target }} 99 | - name: Cache build artifacts 100 | uses: Swatinem/rust-cache@v2.7.8 101 | with: 102 | key: ${{ matrix.target }} 103 | - name: Run tests 104 | run: cargo test --target ${{ matrix.target }} 105 | - name: Run tests (no default features) 106 | run: cargo test --target ${{ matrix.target }} --no-default-features 107 | 108 | rustfmt: 109 | name: Rustfmt 110 | runs-on: ubuntu-24.04 111 | steps: 112 | - name: Checkout code 113 | uses: actions/checkout@v4 114 | - name: Setup Rust toolchain 115 | uses: dtolnay/rust-toolchain@v1 116 | with: 117 | toolchain: stable 118 | components: rustfmt 119 | - name: Cache build artifacts 120 | uses: Swatinem/rust-cache@v2.7.8 121 | - name: Check code formatted 122 | run: cargo fmt -- --check 123 | 124 | clippy: 125 | name: Clippy 126 | runs-on: ubuntu-24.04 127 | steps: 128 | - name: Checkout code 129 | uses: actions/checkout@v4 130 | - name: Setup Rust toolchain 131 | uses: dtolnay/rust-toolchain@v1 132 | with: 133 | toolchain: stable 134 | components: clippy 135 | - name: Cache build artifacts 136 | uses: Swatinem/rust-cache@v2.7.8 137 | - name: Check no lint warnings 138 | run: cargo clippy -- -D warnings 139 | - name: Check no lint warnings (no default features) 140 | run: cargo clippy --no-default-features -- -D warnings 141 | 142 | doc: 143 | name: Documentation 144 | runs-on: ubuntu-24.04 145 | steps: 146 | - name: Checkout code 147 | uses: actions/checkout@v4 148 | - name: Setup Rust toolchain 149 | uses: dtolnay/rust-toolchain@v1 150 | with: 151 | toolchain: stable 152 | - name: Cache build artifacts 153 | uses: Swatinem/rust-cache@v2.7.8 154 | - name: Check no `rustdoc` lint warnings 155 | run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items --no-default-features 156 | -------------------------------------------------------------------------------- /.github/workflows/REUSE.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | name: REUSE Compliance Check 6 | 7 | on: 8 | pull_request: 9 | push: 10 | branches: 11 | - "develop" 12 | - "master" 13 | 14 | jobs: 15 | reuse: 16 | name: REUSE 17 | runs-on: ubuntu-24.04 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | - name: REUSE Compliance Check 22 | uses: fsfe/reuse-action@v5.0.0 23 | -------------------------------------------------------------------------------- /.github/workflows/SemverChecks.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | name: Check Semantic Versioning 6 | 7 | on: 8 | push: 9 | branches: 10 | - "release/*" 11 | tags: 12 | - "v[0-9]+.[0-9]+.[0-9]+" 13 | workflow_dispatch: 14 | 15 | jobs: 16 | semver: 17 | name: Check Semantic Versioning 18 | runs-on: ubuntu-24.04 19 | steps: 20 | - name: Checkout code 21 | uses: actions/checkout@v4 22 | - name: Check Semantic Versioning 23 | uses: obi1kenobi/cargo-semver-checks-action@v2.7 24 | -------------------------------------------------------------------------------- /.github/workflows/actionlint.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Kevin Matthes 2 | # SPDX-FileCopyrightText: 2023 Shun Sakai 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 OR MIT 5 | 6 | name: actionlint 7 | 8 | on: 9 | pull_request: 10 | paths: 11 | - .github/workflows/*.yaml 12 | push: 13 | paths: 14 | - .github/workflows/*.yaml 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | validation: 21 | name: Validate 22 | runs-on: ubuntu-24.04 23 | steps: 24 | - name: Checkout code 25 | uses: actions/checkout@v4 26 | with: 27 | persist-credentials: false 28 | - name: Check no lint warnings 29 | uses: docker://rhysd/actionlint:latest 30 | with: 31 | args: -color -verbose 32 | -------------------------------------------------------------------------------- /.github/workflows/dependabot_auto_merge.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | name: Dependabot auto-merge 6 | 7 | on: pull_request_target 8 | 9 | permissions: 10 | contents: write 11 | pull-requests: write 12 | 13 | jobs: 14 | dependabot: 15 | name: Dependabot auto-merge 16 | if: github.actor == 'dependabot[bot]' && github.repository_owner == 'sorairolake' 17 | runs-on: ubuntu-24.04 18 | steps: 19 | - name: Dependabot metadata 20 | id: metadata 21 | uses: dependabot/fetch-metadata@v2.4.0 22 | with: 23 | github-token: ${{ secrets.GITHUB_TOKEN }} 24 | - name: Approve a PR 25 | run: gh pr review --approve "$PR_URL" 26 | env: 27 | PR_URL: ${{ github.event.pull_request.html_url }} 28 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | - name: Enable auto-merge for Dependabot PRs 30 | if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' }} 31 | run: gh pr merge --auto --squash "$PR_URL" 32 | env: 33 | PR_URL: ${{ github.event.pull_request.html_url }} 34 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | name: Deployment 6 | 7 | on: 8 | push: 9 | tags: 10 | - "v[0-9]+.[0-9]+.[0-9]+" 11 | workflow_dispatch: 12 | 13 | permissions: 14 | contents: read 15 | id-token: write 16 | pages: write 17 | 18 | concurrency: 19 | group: "pages" 20 | cancel-in-progress: false 21 | 22 | jobs: 23 | build: 24 | name: Build 25 | runs-on: ubuntu-24.04 26 | steps: 27 | - name: Checkout code 28 | uses: actions/checkout@v4 29 | - name: Setup Pages 30 | uses: actions/configure-pages@v5 31 | - name: Setup Node.js environment 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: 22 35 | cache: "npm" 36 | - name: Install dependencies 37 | run: | 38 | npm ci 39 | npx antora -v 40 | - name: Setup just 41 | uses: extractions/setup-just@v3 42 | - name: Build a book 43 | run: just build-book 44 | - name: Minify a book 45 | uses: docker://tdewolff/minify:latest 46 | with: 47 | args: --exclude "build/site/_/**" -o build/ -r build/ 48 | - name: Upload artifact 49 | uses: actions/upload-pages-artifact@v3 50 | with: 51 | path: build/site 52 | 53 | deploy: 54 | name: Deploy 55 | needs: build 56 | runs-on: ubuntu-24.04 57 | environment: 58 | name: github-pages 59 | url: ${{ steps.deployment.outputs.page_url }} 60 | steps: 61 | - name: Deploy to GitHub Pages 62 | id: deployment 63 | uses: actions/deploy-pages@v4 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | # Generated by Cargo 6 | # will have compiled files and executables 7 | debug/ 8 | target/ 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # Node.js dependency directory 14 | node_modules/ 15 | 16 | # Generated files by Antora 17 | build/ 18 | 19 | # Files generated when generating demo animation 20 | assets/*.cast 21 | -------------------------------------------------------------------------------- /AUTHORS.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = List of Authors 6 | 7 | == Original author 8 | 9 | * https://github.com/sorairolake[Shun Sakai] 10 | -------------------------------------------------------------------------------- /BUILD.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = How to Build 6 | 7 | == Prerequisites 8 | 9 | .To build *hf*, you will need the following dependencies 10 | * https://doc.rust-lang.org/stable/cargo/[Cargo] (v1.85.0 or later) 11 | 12 | .To build man pages, you will need the following additional dependencies 13 | * https://asciidoctor.org/[Asciidoctor] 14 | 15 | == Building from source 16 | 17 | .To clone the repository 18 | [source,sh] 19 | ---- 20 | git clone https://github.com/sorairolake/hf.git 21 | cd hf 22 | ---- 23 | 24 | .To build a package 25 | [source,sh] 26 | ---- 27 | just build 28 | ---- 29 | 30 | .To find the directory where man pages are generated 31 | [source,sh] 32 | ---- 33 | fd -t directory out ./target/*/build/hf-* 34 | ---- 35 | 36 | == Crate features 37 | 38 | `application`:: 39 | 40 | Enable building the application. This is enabled by default. 41 | -------------------------------------------------------------------------------- /CHANGELOG.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = Changelog 6 | :toc: preamble 7 | :project-url: https://github.com/sorairolake/hf 8 | :compare-url: {project-url}/compare 9 | :issue-url: {project-url}/issues 10 | :pull-request-url: {project-url}/pull 11 | 12 | All notable changes to this project will be documented in this file. 13 | 14 | The format is based on https://keepachangelog.com/[Keep a Changelog], and this 15 | project adheres to https://semver.org/[Semantic Versioning]. 16 | 17 | == {compare-url}/v0.4.0\...HEAD[Unreleased] 18 | 19 | === Added 20 | 21 | * Add demo animation ({pull-request-url}/368[#368]) 22 | 23 | === Changed 24 | 25 | * Change the license for documents and assets to CC BY 4.0 26 | ({pull-request-url}/380[#380]) 27 | * Replace `--generate-completion` with `completion` subcommand 28 | ({pull-request-url}/387[#387]) 29 | * Remove help text for after auto-generated `--help` 30 | ({pull-request-url}/393[#393]) 31 | * Make the message for `--version` the same as the message for `-V` 32 | ({pull-request-url}/393[#393]) 33 | 34 | === Removed 35 | 36 | * Remove `hf-help(1)` 37 | 38 | == {compare-url}/v0.3.10\...v0.4.0[0.4.0] - 2025-03-23 39 | 40 | === Changed 41 | 42 | * Bump MSRV to 1.85.0 ({pull-request-url}/347[#347]) 43 | 44 | == {compare-url}/v0.3.9\...v0.3.10[0.3.10] - 2025-01-15 45 | 46 | === Changed 47 | 48 | * Update tests ({pull-request-url}/329[#329]) 49 | 50 | == {compare-url}/v0.3.8\...v0.3.9[0.3.9] - 2025-01-14 51 | 52 | === Changed 53 | 54 | * Restore crate level doctests ({pull-request-url}/326[#326]) 55 | 56 | == {compare-url}/v0.3.7\...v0.3.8[0.3.8] - 2025-01-14 57 | 58 | === Changed 59 | 60 | * Update tests ({pull-request-url}/324[#324]) 61 | 62 | == {compare-url}/v0.3.6\...v0.3.7[0.3.7] - 2025-01-09 63 | 64 | === Changed 65 | 66 | * Update documentation ({pull-request-url}/318[#318]) 67 | 68 | == {compare-url}/v0.3.5\...v0.3.6[0.3.6] - 2024-08-04 69 | 70 | === Changed 71 | 72 | * Update man pages ({pull-request-url}/234[#234]) 73 | 74 | == {compare-url}/v0.3.4\...v0.3.5[0.3.5] - 2024-07-03 75 | 76 | === Changed 77 | 78 | * Show possible values for `--log-level` in the help message 79 | ({pull-request-url}/189[#189]) 80 | * Change to use `Display` to display path ({pull-request-url}/192[#192]) 81 | 82 | === Fixed 83 | 84 | * Fix panic when specifying `--log-level` without subcommand 85 | ({pull-request-url}/189[#189]) 86 | 87 | == {compare-url}/v0.3.3\...v0.3.4[0.3.4] - 2024-04-18 88 | 89 | === Changed 90 | 91 | * Change to remove unnecessary files from the book 92 | ({pull-request-url}/121[#121]) 93 | 94 | == {compare-url}/v0.3.2\...v0.3.3[0.3.3] - 2024-04-16 95 | 96 | === Added 97 | 98 | * Add link to `true` ({pull-request-url}/110[#110]) 99 | 100 | === Changed 101 | 102 | * Update examples in man pages 103 | 104 | == {compare-url}/v0.3.1\...v0.3.2[0.3.2] - 2024-04-01 105 | 106 | === Changed 107 | 108 | * Update documents ({pull-request-url}/99[#99]) 109 | 110 | == {compare-url}/v0.3.0\...v0.3.1[0.3.1] - 2024-03-31 111 | 112 | === Added 113 | 114 | * Add functions to get the path after making it visible or invisible for Unix 115 | platforms ({pull-request-url}/91[#91]) 116 | 117 | === Fixed 118 | 119 | * Fix not being able to take path of Unicode string in Windows environment 120 | ({pull-request-url}/90[#90]) 121 | 122 | == {compare-url}/v0.2.2\...v0.3.0[0.3.0] - 2024-03-28 123 | 124 | === Added 125 | 126 | * Add `hf` as a library ({pull-request-url}/86[#86]) 127 | 128 | === Changed 129 | 130 | * Change to use subcommands ({pull-request-url}/86[#86]) 131 | * Bump MSRV to 1.74.0 ({pull-request-url}/86[#86]) 132 | 133 | == {compare-url}/v0.2.1\...v0.2.2[0.2.2] - 2022-09-18 134 | 135 | === Changed 136 | 137 | * Allow non-UTF-8 paths as arguments 138 | * Update dependencies 139 | 140 | == {compare-url}/v0.2.0\...v0.2.1[0.2.1] - 2022-06-10 141 | 142 | === Changed 143 | 144 | * Enable the `lto` and `strip` settings in the release profile 145 | 146 | == {compare-url}/v0.1.0\...v0.2.0[0.2.0] - 2022-06-09 147 | 148 | === Added 149 | 150 | * Add exit codes as defined by `` 151 | * Add `--quiet` option 152 | * Add `--verbose` option 153 | 154 | === Changed 155 | 156 | * Use the Win32 API instead of `attrib` command 157 | 158 | == {project-url}/releases/tag/v0.1.0[0.1.0] - 2022-05-26 159 | 160 | === Added 161 | 162 | * Initial release 163 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | # Contributor Covenant Code of Conduct 10 | 11 | ## Our Pledge 12 | 13 | We as members, contributors, and leaders pledge to make participation in our 14 | community a harassment-free experience for everyone, regardless of age, body 15 | size, visible or invisible disability, ethnicity, sex characteristics, gender 16 | identity and expression, level of experience, education, socio-economic status, 17 | nationality, personal appearance, race, caste, color, religion, or sexual 18 | identity and orientation. 19 | 20 | We pledge to act and interact in ways that contribute to an open, welcoming, 21 | diverse, inclusive, and healthy community. 22 | 23 | ## Our Standards 24 | 25 | Examples of behavior that contributes to a positive environment for our 26 | community include: 27 | 28 | - Demonstrating empathy and kindness toward other people 29 | - Being respectful of differing opinions, viewpoints, and experiences 30 | - Giving and gracefully accepting constructive feedback 31 | - Accepting responsibility and apologizing to those affected by our mistakes, 32 | and learning from the experience 33 | - Focusing on what is best not just for us as individuals, but for the overall 34 | community 35 | 36 | Examples of unacceptable behavior include: 37 | 38 | - The use of sexualized language or imagery, and sexual attention or advances 39 | of any kind 40 | - Trolling, insulting or derogatory comments, and personal or political attacks 41 | - Public or private harassment 42 | - Publishing others' private information, such as a physical or email address, 43 | without their explicit permission 44 | - Other conduct which could reasonably be considered inappropriate in a 45 | professional setting 46 | 47 | ## Enforcement Responsibilities 48 | 49 | Community leaders are responsible for clarifying and enforcing our standards of 50 | acceptable behavior and will take appropriate and fair corrective action in 51 | response to any behavior that they deem inappropriate, threatening, offensive, 52 | or harmful. 53 | 54 | Community leaders have the right and responsibility to remove, edit, or reject 55 | comments, commits, code, wiki edits, issues, and other contributions that are 56 | not aligned to this Code of Conduct, and will communicate reasons for 57 | moderation decisions when appropriate. 58 | 59 | ## Scope 60 | 61 | This Code of Conduct applies within all community spaces, and also applies when 62 | an individual is officially representing the community in public spaces. 63 | Examples of representing our community include using an official e-mail 64 | address, posting via an official social media account, or acting as an 65 | appointed representative at an online or offline event. 66 | 67 | ## Enforcement 68 | 69 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 70 | reported to the community leaders responsible for enforcement at 71 | . All complaints will be reviewed and investigated 72 | promptly and fairly. 73 | 74 | All community leaders are obligated to respect the privacy and security of the 75 | reporter of any incident. 76 | 77 | ## Enforcement Guidelines 78 | 79 | Community leaders will follow these Community Impact Guidelines in determining 80 | the consequences for any action they deem in violation of this Code of Conduct: 81 | 82 | ### 1. Correction 83 | 84 | **Community Impact**: Use of inappropriate language or other behavior deemed 85 | unprofessional or unwelcome in the community. 86 | 87 | **Consequence**: A private, written warning from community leaders, providing 88 | clarity around the nature of the violation and an explanation of why the 89 | behavior was inappropriate. A public apology may be requested. 90 | 91 | ### 2. Warning 92 | 93 | **Community Impact**: A violation through a single incident or series of 94 | actions. 95 | 96 | **Consequence**: A warning with consequences for continued behavior. No 97 | interaction with the people involved, including unsolicited interaction with 98 | those enforcing the Code of Conduct, for a specified period of time. This 99 | includes avoiding interactions in community spaces as well as external channels 100 | like social media. Violating these terms may lead to a temporary or permanent 101 | ban. 102 | 103 | ### 3. Temporary Ban 104 | 105 | **Community Impact**: A serious violation of community standards, including 106 | sustained inappropriate behavior. 107 | 108 | **Consequence**: A temporary ban from any sort of interaction or public 109 | communication with the community for a specified period of time. No public or 110 | private interaction with the people involved, including unsolicited interaction 111 | with those enforcing the Code of Conduct, is allowed during this period. 112 | Violating these terms may lead to a permanent ban. 113 | 114 | ### 4. Permanent Ban 115 | 116 | **Community Impact**: Demonstrating a pattern of violation of community 117 | standards, including sustained inappropriate behavior, harassment of an 118 | individual, or aggression toward or disparagement of classes of individuals. 119 | 120 | **Consequence**: A permanent ban from any sort of public interaction within the 121 | community. 122 | 123 | ## Attribution 124 | 125 | This Code of Conduct is adapted from the [Contributor Covenant], version 2.1, 126 | available at 127 | . 128 | 129 | Community Impact Guidelines were inspired by 130 | [Mozilla's code of conduct enforcement ladder]. 131 | 132 | For answers to common questions about this code of conduct, see the FAQ at 133 | . Translations are available at 134 | . 135 | 136 | [Contributor Covenant]: https://www.contributor-covenant.org 137 | [Mozilla's code of conduct enforcement ladder]: https://github.com/mozilla/diversity 138 | -------------------------------------------------------------------------------- /CONTRIBUTING.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = Contribution Guide 6 | :git-flow-url: https://nvie.com/posts/a-successful-git-branching-model/ 7 | :commit-messages-guide-url: https://github.com/RomuloOliveira/commit-messages-guide 8 | :conventionalcommits-url: https://www.conventionalcommits.org/en/v1.0.0/ 9 | ifdef::site-gen-antora[] 10 | :coc-url: https://www.contributor-covenant.org/version/2/1/code_of_conduct/ 11 | endif::[] 12 | 13 | Thank you for your interest in contributing to this project! If you would like 14 | to contribute to this project, please follow the instructions below if possible. 15 | 16 | == Branching model 17 | 18 | The branching model of this project is based on the {git-flow-url}[git-flow]. 19 | 20 | == Style guides 21 | 22 | === Commit message 23 | 24 | Please see the {commit-messages-guide-url}[Commit messages guide] and the 25 | {conventionalcommits-url}[Conventional Commits]. 26 | 27 | == Submitting a pull request 28 | 29 | . Create a working branch from the `develop` branch. The branch name should be 30 | something other than `develop` or `master`. 31 | . Create your patch. If your change is a feature or a bugfix, please add a test 32 | case if possible. Note that the change must pass the CI. 33 | . Please update the copyright information if possible. This project is 34 | compliant with version 3.3 of the 35 | https://reuse.software/spec-3.3/[_REUSE Specification_]. 36 | https://github.com/fsfe/reuse-tool[`reuse`] is useful for updating the 37 | copyright information. 38 | ifdef::site-gen-antora[] 39 | . Please update the xref:changelog.adoc[] if possible. 40 | endif::[] 41 | ifndef::site-gen-antora[] 42 | . Please update the link:CHANGELOG.adoc[Changelog] if possible. 43 | endif::[] 44 | ifdef::site-gen-antora[] 45 | . Please read and agree to follow the {coc-url}[Code of Conduct]. 46 | endif::[] 47 | ifndef::site-gen-antora[] 48 | . Please read and agree to follow the link:CODE_OF_CONDUCT.md[Code of Conduct]. 49 | endif::[] 50 | 51 | == Development 52 | 53 | === Useful development tools 54 | 55 | The https://github.com/casey/just[just] command runner can be used. Run 56 | `just --list` for more details. 57 | 58 | .Run tests 59 | [source,sh] 60 | ---- 61 | just test 62 | ---- 63 | 64 | .Run the formatter 65 | [source,sh] 66 | ---- 67 | just fmt 68 | ---- 69 | 70 | .Run the linter 71 | [source,sh] 72 | ---- 73 | just lint 74 | ---- 75 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.18" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.10" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 49 | dependencies = [ 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.7" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 58 | dependencies = [ 59 | "anstyle", 60 | "once_cell", 61 | "windows-sys", 62 | ] 63 | 64 | [[package]] 65 | name = "anyhow" 66 | version = "1.0.98" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 69 | 70 | [[package]] 71 | name = "assert_cmd" 72 | version = "2.0.17" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" 75 | dependencies = [ 76 | "anstyle", 77 | "bstr", 78 | "doc-comment", 79 | "libc", 80 | "predicates", 81 | "predicates-core", 82 | "predicates-tree", 83 | "wait-timeout", 84 | ] 85 | 86 | [[package]] 87 | name = "autocfg" 88 | version = "1.4.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 91 | 92 | [[package]] 93 | name = "bitflags" 94 | version = "2.9.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 97 | 98 | [[package]] 99 | name = "bstr" 100 | version = "1.11.3" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" 103 | dependencies = [ 104 | "memchr", 105 | "regex-automata", 106 | "serde", 107 | ] 108 | 109 | [[package]] 110 | name = "cfg-if" 111 | version = "1.0.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 114 | 115 | [[package]] 116 | name = "clap" 117 | version = "4.5.38" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" 120 | dependencies = [ 121 | "clap_builder", 122 | "clap_derive", 123 | ] 124 | 125 | [[package]] 126 | name = "clap_builder" 127 | version = "4.5.38" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" 130 | dependencies = [ 131 | "anstream", 132 | "anstyle", 133 | "clap_lex", 134 | "strsim", 135 | "terminal_size", 136 | ] 137 | 138 | [[package]] 139 | name = "clap_complete" 140 | version = "4.5.50" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "c91d3baa3bcd889d60e6ef28874126a0b384fd225ab83aa6d8a801c519194ce1" 143 | dependencies = [ 144 | "clap", 145 | ] 146 | 147 | [[package]] 148 | name = "clap_complete_nushell" 149 | version = "4.5.5" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "c6a8b1593457dfc2fe539002b795710d022dc62a65bf15023f039f9760c7b18a" 152 | dependencies = [ 153 | "clap", 154 | "clap_complete", 155 | ] 156 | 157 | [[package]] 158 | name = "clap_derive" 159 | version = "4.5.32" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 162 | dependencies = [ 163 | "heck", 164 | "proc-macro2", 165 | "quote", 166 | "syn", 167 | ] 168 | 169 | [[package]] 170 | name = "clap_lex" 171 | version = "0.7.4" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 174 | 175 | [[package]] 176 | name = "colorchoice" 177 | version = "1.0.3" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 180 | 181 | [[package]] 182 | name = "deranged" 183 | version = "0.4.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 186 | dependencies = [ 187 | "powerfmt", 188 | ] 189 | 190 | [[package]] 191 | name = "difflib" 192 | version = "0.4.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 195 | 196 | [[package]] 197 | name = "doc-comment" 198 | version = "0.3.3" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 201 | 202 | [[package]] 203 | name = "errno" 204 | version = "0.3.10" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 207 | dependencies = [ 208 | "libc", 209 | "windows-sys", 210 | ] 211 | 212 | [[package]] 213 | name = "fastrand" 214 | version = "2.3.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 217 | 218 | [[package]] 219 | name = "float-cmp" 220 | version = "0.10.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" 223 | dependencies = [ 224 | "num-traits", 225 | ] 226 | 227 | [[package]] 228 | name = "getrandom" 229 | version = "0.3.2" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 232 | dependencies = [ 233 | "cfg-if", 234 | "libc", 235 | "r-efi", 236 | "wasi", 237 | ] 238 | 239 | [[package]] 240 | name = "heck" 241 | version = "0.5.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 244 | 245 | [[package]] 246 | name = "hf" 247 | version = "0.4.0" 248 | dependencies = [ 249 | "anyhow", 250 | "assert_cmd", 251 | "clap", 252 | "clap_complete", 253 | "clap_complete_nushell", 254 | "log", 255 | "predicates", 256 | "simplelog", 257 | "sysexits", 258 | "tempfile", 259 | "windows", 260 | ] 261 | 262 | [[package]] 263 | name = "is_terminal_polyfill" 264 | version = "1.70.1" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 267 | 268 | [[package]] 269 | name = "itoa" 270 | version = "1.0.15" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 273 | 274 | [[package]] 275 | name = "libc" 276 | version = "0.2.171" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 279 | 280 | [[package]] 281 | name = "linux-raw-sys" 282 | version = "0.9.3" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" 285 | 286 | [[package]] 287 | name = "log" 288 | version = "0.4.27" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 291 | 292 | [[package]] 293 | name = "memchr" 294 | version = "2.7.4" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 297 | 298 | [[package]] 299 | name = "normalize-line-endings" 300 | version = "0.3.0" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 303 | 304 | [[package]] 305 | name = "num-conv" 306 | version = "0.1.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 309 | 310 | [[package]] 311 | name = "num-traits" 312 | version = "0.2.19" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 315 | dependencies = [ 316 | "autocfg", 317 | ] 318 | 319 | [[package]] 320 | name = "num_threads" 321 | version = "0.1.7" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 324 | dependencies = [ 325 | "libc", 326 | ] 327 | 328 | [[package]] 329 | name = "once_cell" 330 | version = "1.21.1" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" 333 | 334 | [[package]] 335 | name = "powerfmt" 336 | version = "0.2.0" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 339 | 340 | [[package]] 341 | name = "predicates" 342 | version = "3.1.3" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" 345 | dependencies = [ 346 | "anstyle", 347 | "difflib", 348 | "float-cmp", 349 | "normalize-line-endings", 350 | "predicates-core", 351 | "regex", 352 | ] 353 | 354 | [[package]] 355 | name = "predicates-core" 356 | version = "1.0.9" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" 359 | 360 | [[package]] 361 | name = "predicates-tree" 362 | version = "1.0.12" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" 365 | dependencies = [ 366 | "predicates-core", 367 | "termtree", 368 | ] 369 | 370 | [[package]] 371 | name = "proc-macro2" 372 | version = "1.0.94" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 375 | dependencies = [ 376 | "unicode-ident", 377 | ] 378 | 379 | [[package]] 380 | name = "quote" 381 | version = "1.0.40" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 384 | dependencies = [ 385 | "proc-macro2", 386 | ] 387 | 388 | [[package]] 389 | name = "r-efi" 390 | version = "5.2.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 393 | 394 | [[package]] 395 | name = "regex" 396 | version = "1.11.1" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 399 | dependencies = [ 400 | "aho-corasick", 401 | "memchr", 402 | "regex-automata", 403 | "regex-syntax", 404 | ] 405 | 406 | [[package]] 407 | name = "regex-automata" 408 | version = "0.4.9" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 411 | dependencies = [ 412 | "aho-corasick", 413 | "memchr", 414 | "regex-syntax", 415 | ] 416 | 417 | [[package]] 418 | name = "regex-syntax" 419 | version = "0.8.5" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 422 | 423 | [[package]] 424 | name = "rustix" 425 | version = "1.0.3" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" 428 | dependencies = [ 429 | "bitflags", 430 | "errno", 431 | "libc", 432 | "linux-raw-sys", 433 | "windows-sys", 434 | ] 435 | 436 | [[package]] 437 | name = "serde" 438 | version = "1.0.219" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 441 | dependencies = [ 442 | "serde_derive", 443 | ] 444 | 445 | [[package]] 446 | name = "serde_derive" 447 | version = "1.0.219" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 450 | dependencies = [ 451 | "proc-macro2", 452 | "quote", 453 | "syn", 454 | ] 455 | 456 | [[package]] 457 | name = "simplelog" 458 | version = "0.12.2" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" 461 | dependencies = [ 462 | "log", 463 | "termcolor", 464 | "time", 465 | ] 466 | 467 | [[package]] 468 | name = "strsim" 469 | version = "0.11.1" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 472 | 473 | [[package]] 474 | name = "syn" 475 | version = "2.0.100" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 478 | dependencies = [ 479 | "proc-macro2", 480 | "quote", 481 | "unicode-ident", 482 | ] 483 | 484 | [[package]] 485 | name = "sysexits" 486 | version = "0.9.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "198f60d1f7f003f168507691e42d082df109ef0f05c6fd006e22528371a5f1b4" 489 | 490 | [[package]] 491 | name = "tempfile" 492 | version = "3.20.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" 495 | dependencies = [ 496 | "fastrand", 497 | "getrandom", 498 | "once_cell", 499 | "rustix", 500 | "windows-sys", 501 | ] 502 | 503 | [[package]] 504 | name = "termcolor" 505 | version = "1.4.1" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 508 | dependencies = [ 509 | "winapi-util", 510 | ] 511 | 512 | [[package]] 513 | name = "terminal_size" 514 | version = "0.4.2" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" 517 | dependencies = [ 518 | "rustix", 519 | "windows-sys", 520 | ] 521 | 522 | [[package]] 523 | name = "termtree" 524 | version = "0.5.1" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" 527 | 528 | [[package]] 529 | name = "time" 530 | version = "0.3.40" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618" 533 | dependencies = [ 534 | "deranged", 535 | "itoa", 536 | "libc", 537 | "num-conv", 538 | "num_threads", 539 | "powerfmt", 540 | "serde", 541 | "time-core", 542 | "time-macros", 543 | ] 544 | 545 | [[package]] 546 | name = "time-core" 547 | version = "0.1.4" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 550 | 551 | [[package]] 552 | name = "time-macros" 553 | version = "0.2.21" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04" 556 | dependencies = [ 557 | "num-conv", 558 | "time-core", 559 | ] 560 | 561 | [[package]] 562 | name = "unicode-ident" 563 | version = "1.0.18" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 566 | 567 | [[package]] 568 | name = "utf8parse" 569 | version = "0.2.2" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 572 | 573 | [[package]] 574 | name = "wait-timeout" 575 | version = "0.2.1" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" 578 | dependencies = [ 579 | "libc", 580 | ] 581 | 582 | [[package]] 583 | name = "wasi" 584 | version = "0.14.2+wasi-0.2.4" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 587 | dependencies = [ 588 | "wit-bindgen-rt", 589 | ] 590 | 591 | [[package]] 592 | name = "winapi-util" 593 | version = "0.1.9" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 596 | dependencies = [ 597 | "windows-sys", 598 | ] 599 | 600 | [[package]] 601 | name = "windows" 602 | version = "0.61.1" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" 605 | dependencies = [ 606 | "windows-collections", 607 | "windows-core", 608 | "windows-future", 609 | "windows-link", 610 | "windows-numerics", 611 | ] 612 | 613 | [[package]] 614 | name = "windows-collections" 615 | version = "0.2.0" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 618 | dependencies = [ 619 | "windows-core", 620 | ] 621 | 622 | [[package]] 623 | name = "windows-core" 624 | version = "0.61.0" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" 627 | dependencies = [ 628 | "windows-implement", 629 | "windows-interface", 630 | "windows-link", 631 | "windows-result", 632 | "windows-strings", 633 | ] 634 | 635 | [[package]] 636 | name = "windows-future" 637 | version = "0.2.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32" 640 | dependencies = [ 641 | "windows-core", 642 | "windows-link", 643 | ] 644 | 645 | [[package]] 646 | name = "windows-implement" 647 | version = "0.60.0" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 650 | dependencies = [ 651 | "proc-macro2", 652 | "quote", 653 | "syn", 654 | ] 655 | 656 | [[package]] 657 | name = "windows-interface" 658 | version = "0.59.1" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 661 | dependencies = [ 662 | "proc-macro2", 663 | "quote", 664 | "syn", 665 | ] 666 | 667 | [[package]] 668 | name = "windows-link" 669 | version = "0.1.1" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 672 | 673 | [[package]] 674 | name = "windows-numerics" 675 | version = "0.2.0" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 678 | dependencies = [ 679 | "windows-core", 680 | "windows-link", 681 | ] 682 | 683 | [[package]] 684 | name = "windows-result" 685 | version = "0.3.2" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" 688 | dependencies = [ 689 | "windows-link", 690 | ] 691 | 692 | [[package]] 693 | name = "windows-strings" 694 | version = "0.4.0" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" 697 | dependencies = [ 698 | "windows-link", 699 | ] 700 | 701 | [[package]] 702 | name = "windows-sys" 703 | version = "0.59.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 706 | dependencies = [ 707 | "windows-targets", 708 | ] 709 | 710 | [[package]] 711 | name = "windows-targets" 712 | version = "0.52.6" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 715 | dependencies = [ 716 | "windows_aarch64_gnullvm", 717 | "windows_aarch64_msvc", 718 | "windows_i686_gnu", 719 | "windows_i686_gnullvm", 720 | "windows_i686_msvc", 721 | "windows_x86_64_gnu", 722 | "windows_x86_64_gnullvm", 723 | "windows_x86_64_msvc", 724 | ] 725 | 726 | [[package]] 727 | name = "windows_aarch64_gnullvm" 728 | version = "0.52.6" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 731 | 732 | [[package]] 733 | name = "windows_aarch64_msvc" 734 | version = "0.52.6" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 737 | 738 | [[package]] 739 | name = "windows_i686_gnu" 740 | version = "0.52.6" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 743 | 744 | [[package]] 745 | name = "windows_i686_gnullvm" 746 | version = "0.52.6" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 749 | 750 | [[package]] 751 | name = "windows_i686_msvc" 752 | version = "0.52.6" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 755 | 756 | [[package]] 757 | name = "windows_x86_64_gnu" 758 | version = "0.52.6" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 761 | 762 | [[package]] 763 | name = "windows_x86_64_gnullvm" 764 | version = "0.52.6" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 767 | 768 | [[package]] 769 | name = "windows_x86_64_msvc" 770 | version = "0.52.6" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 773 | 774 | [[package]] 775 | name = "wit-bindgen-rt" 776 | version = "0.39.0" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 779 | dependencies = [ 780 | "bitflags", 781 | ] 782 | -------------------------------------------------------------------------------- /Cargo.lock.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2022 Shun Sakai 2 | 3 | SPDX-License-Identifier: Apache-2.0 OR MIT 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | [package] 6 | name = "hf" 7 | version = "0.4.0" 8 | authors = ["Shun Sakai "] 9 | edition = "2024" 10 | rust-version = "1.85.0" 11 | description = "Cross-platform hidden file library and utility" 12 | documentation = "https://docs.rs/hf" 13 | readme = "README.md" 14 | homepage = "https://sorairolake.github.io/hf/" 15 | repository = "https://github.com/sorairolake/hf" 16 | license = "Apache-2.0 OR MIT" 17 | keywords = ["hidden"] 18 | categories = ["command-line-utilities", "filesystem"] 19 | include = ["/LICENSES", "/README.md", "/src"] 20 | 21 | [package.metadata.docs.rs] 22 | all-features = true 23 | 24 | [[bin]] 25 | name = "hf" 26 | required-features = ["application"] 27 | 28 | [dependencies] 29 | anyhow = { version = "1.0.98", optional = true } 30 | clap = { version = "4.5.38", features = ["derive", "wrap_help"], optional = true } 31 | clap_complete = { version = "4.5.50", optional = true } 32 | clap_complete_nushell = { version = "4.5.5", optional = true } 33 | log = { version = "0.4.27", optional = true } 34 | simplelog = { version = "0.12.2", optional = true } 35 | sysexits = { version = "0.9.0", optional = true } 36 | 37 | [target.'cfg(windows)'.dependencies] 38 | windows = { version = "0.61.1", features = ["Win32_Foundation", "Win32_Storage_FileSystem"] } 39 | 40 | [dev-dependencies] 41 | assert_cmd = "2.0.17" 42 | predicates = "3.1.3" 43 | tempfile = "3.20.0" 44 | 45 | [features] 46 | default = ["application"] 47 | application = [ 48 | "dep:anyhow", 49 | "dep:clap", 50 | "dep:clap_complete", 51 | "dep:clap_complete_nushell", 52 | "dep:log", 53 | "dep:simplelog", 54 | "dep:sysexits", 55 | ] 56 | 57 | [lints.clippy] 58 | cargo = { level = "warn", priority = -1 } 59 | multiple_crate_versions = "allow" 60 | nursery = { level = "warn", priority = -1 } 61 | pedantic = { level = "warn", priority = -1 } 62 | 63 | [lints.rust] 64 | missing_debug_implementations = "deny" 65 | rust_2018_idioms = { level = "warn", priority = -1 } 66 | 67 | [profile.release] 68 | codegen-units = 1 69 | lto = true 70 | panic = "abort" 71 | strip = true 72 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | [target.aarch64-unknown-linux-musl] 6 | pre-build = ["apt-get update && apt-get install -y asciidoctor"] 7 | 8 | [target.arm-unknown-linux-musleabihf] 9 | pre-build = ["apt-get update && apt-get install -y asciidoctor"] 10 | 11 | [target.armv7-unknown-linux-musleabihf] 12 | pre-build = ["apt-get update && apt-get install -y asciidoctor"] 13 | 14 | [target.x86_64-unknown-linux-musl] 15 | pre-build = ["apt-get update && apt-get install -y asciidoctor"] 16 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shun Sakai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSES/CC-BY-4.0.txt: -------------------------------------------------------------------------------- 1 | Attribution 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution 4.0 International Public License 58 | 59 | By exercising the Licensed Rights (defined below), You accept and agree 60 | to be bound by the terms and conditions of this Creative Commons 61 | Attribution 4.0 International Public License ("Public License"). To the 62 | extent this Public License may be interpreted as a contract, You are 63 | granted the Licensed Rights in consideration of Your acceptance of 64 | these terms and conditions, and the Licensor grants You such rights in 65 | consideration of benefits the Licensor receives from making the 66 | Licensed Material available under these terms and conditions. 67 | 68 | 69 | Section 1 -- Definitions. 70 | 71 | a. Adapted Material means material subject to Copyright and Similar 72 | Rights that is derived from or based upon the Licensed Material 73 | and in which the Licensed Material is translated, altered, 74 | arranged, transformed, or otherwise modified in a manner requiring 75 | permission under the Copyright and Similar Rights held by the 76 | Licensor. For purposes of this Public License, where the Licensed 77 | Material is a musical work, performance, or sound recording, 78 | Adapted Material is always produced where the Licensed Material is 79 | synched in timed relation with a moving image. 80 | 81 | b. Adapter's License means the license You apply to Your Copyright 82 | and Similar Rights in Your contributions to Adapted Material in 83 | accordance with the terms and conditions of this Public License. 84 | 85 | c. Copyright and Similar Rights means copyright and/or similar rights 86 | closely related to copyright including, without limitation, 87 | performance, broadcast, sound recording, and Sui Generis Database 88 | Rights, without regard to how the rights are labeled or 89 | categorized. For purposes of this Public License, the rights 90 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 91 | Rights. 92 | 93 | d. Effective Technological Measures means those measures that, in the 94 | absence of proper authority, may not be circumvented under laws 95 | fulfilling obligations under Article 11 of the WIPO Copyright 96 | Treaty adopted on December 20, 1996, and/or similar international 97 | agreements. 98 | 99 | e. Exceptions and Limitations means fair use, fair dealing, and/or 100 | any other exception or limitation to Copyright and Similar Rights 101 | that applies to Your use of the Licensed Material. 102 | 103 | f. Licensed Material means the artistic or literary work, database, 104 | or other material to which the Licensor applied this Public 105 | License. 106 | 107 | g. Licensed Rights means the rights granted to You subject to the 108 | terms and conditions of this Public License, which are limited to 109 | all Copyright and Similar Rights that apply to Your use of the 110 | Licensed Material and that the Licensor has authority to license. 111 | 112 | h. Licensor means the individual(s) or entity(ies) granting rights 113 | under this Public License. 114 | 115 | i. Share means to provide material to the public by any means or 116 | process that requires permission under the Licensed Rights, such 117 | as reproduction, public display, public performance, distribution, 118 | dissemination, communication, or importation, and to make material 119 | available to the public including in ways that members of the 120 | public may access the material from a place and at a time 121 | individually chosen by them. 122 | 123 | j. Sui Generis Database Rights means rights other than copyright 124 | resulting from Directive 96/9/EC of the European Parliament and of 125 | the Council of 11 March 1996 on the legal protection of databases, 126 | as amended and/or succeeded, as well as other essentially 127 | equivalent rights anywhere in the world. 128 | 129 | k. You means the individual or entity exercising the Licensed Rights 130 | under this Public License. Your has a corresponding meaning. 131 | 132 | 133 | Section 2 -- Scope. 134 | 135 | a. License grant. 136 | 137 | 1. Subject to the terms and conditions of this Public License, 138 | the Licensor hereby grants You a worldwide, royalty-free, 139 | non-sublicensable, non-exclusive, irrevocable license to 140 | exercise the Licensed Rights in the Licensed Material to: 141 | 142 | a. reproduce and Share the Licensed Material, in whole or 143 | in part; and 144 | 145 | b. produce, reproduce, and Share Adapted Material. 146 | 147 | 2. Exceptions and Limitations. For the avoidance of doubt, where 148 | Exceptions and Limitations apply to Your use, this Public 149 | License does not apply, and You do not need to comply with 150 | its terms and conditions. 151 | 152 | 3. Term. The term of this Public License is specified in Section 153 | 6(a). 154 | 155 | 4. Media and formats; technical modifications allowed. The 156 | Licensor authorizes You to exercise the Licensed Rights in 157 | all media and formats whether now known or hereafter created, 158 | and to make technical modifications necessary to do so. The 159 | Licensor waives and/or agrees not to assert any right or 160 | authority to forbid You from making technical modifications 161 | necessary to exercise the Licensed Rights, including 162 | technical modifications necessary to circumvent Effective 163 | Technological Measures. For purposes of this Public License, 164 | simply making modifications authorized by this Section 2(a) 165 | (4) never produces Adapted Material. 166 | 167 | 5. Downstream recipients. 168 | 169 | a. Offer from the Licensor -- Licensed Material. Every 170 | recipient of the Licensed Material automatically 171 | receives an offer from the Licensor to exercise the 172 | Licensed Rights under the terms and conditions of this 173 | Public License. 174 | 175 | b. No downstream restrictions. You may not offer or impose 176 | any additional or different terms or conditions on, or 177 | apply any Effective Technological Measures to, the 178 | Licensed Material if doing so restricts exercise of the 179 | Licensed Rights by any recipient of the Licensed 180 | Material. 181 | 182 | 6. No endorsement. Nothing in this Public License constitutes or 183 | may be construed as permission to assert or imply that You 184 | are, or that Your use of the Licensed Material is, connected 185 | with, or sponsored, endorsed, or granted official status by, 186 | the Licensor or others designated to receive attribution as 187 | provided in Section 3(a)(1)(A)(i). 188 | 189 | b. Other rights. 190 | 191 | 1. Moral rights, such as the right of integrity, are not 192 | licensed under this Public License, nor are publicity, 193 | privacy, and/or other similar personality rights; however, to 194 | the extent possible, the Licensor waives and/or agrees not to 195 | assert any such rights held by the Licensor to the limited 196 | extent necessary to allow You to exercise the Licensed 197 | Rights, but not otherwise. 198 | 199 | 2. Patent and trademark rights are not licensed under this 200 | Public License. 201 | 202 | 3. To the extent possible, the Licensor waives any right to 203 | collect royalties from You for the exercise of the Licensed 204 | Rights, whether directly or through a collecting society 205 | under any voluntary or waivable statutory or compulsory 206 | licensing scheme. In all other cases the Licensor expressly 207 | reserves any right to collect such royalties. 208 | 209 | 210 | Section 3 -- License Conditions. 211 | 212 | Your exercise of the Licensed Rights is expressly made subject to the 213 | following conditions. 214 | 215 | a. Attribution. 216 | 217 | 1. If You Share the Licensed Material (including in modified 218 | form), You must: 219 | 220 | a. retain the following if it is supplied by the Licensor 221 | with the Licensed Material: 222 | 223 | i. identification of the creator(s) of the Licensed 224 | Material and any others designated to receive 225 | attribution, in any reasonable manner requested by 226 | the Licensor (including by pseudonym if 227 | designated); 228 | 229 | ii. a copyright notice; 230 | 231 | iii. a notice that refers to this Public License; 232 | 233 | iv. a notice that refers to the disclaimer of 234 | warranties; 235 | 236 | v. a URI or hyperlink to the Licensed Material to the 237 | extent reasonably practicable; 238 | 239 | b. indicate if You modified the Licensed Material and 240 | retain an indication of any previous modifications; and 241 | 242 | c. indicate the Licensed Material is licensed under this 243 | Public License, and include the text of, or the URI or 244 | hyperlink to, this Public License. 245 | 246 | 2. You may satisfy the conditions in Section 3(a)(1) in any 247 | reasonable manner based on the medium, means, and context in 248 | which You Share the Licensed Material. For example, it may be 249 | reasonable to satisfy the conditions by providing a URI or 250 | hyperlink to a resource that includes the required 251 | information. 252 | 253 | 3. If requested by the Licensor, You must remove any of the 254 | information required by Section 3(a)(1)(A) to the extent 255 | reasonably practicable. 256 | 257 | 4. If You Share Adapted Material You produce, the Adapter's 258 | License You apply must not prevent recipients of the Adapted 259 | Material from complying with this Public License. 260 | 261 | 262 | Section 4 -- Sui Generis Database Rights. 263 | 264 | Where the Licensed Rights include Sui Generis Database Rights that 265 | apply to Your use of the Licensed Material: 266 | 267 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 268 | to extract, reuse, reproduce, and Share all or a substantial 269 | portion of the contents of the database; 270 | 271 | b. if You include all or a substantial portion of the database 272 | contents in a database in which You have Sui Generis Database 273 | Rights, then the database in which You have Sui Generis Database 274 | Rights (but not its individual contents) is Adapted Material; and 275 | 276 | c. You must comply with the conditions in Section 3(a) if You Share 277 | all or a substantial portion of the contents of the database. 278 | 279 | For the avoidance of doubt, this Section 4 supplements and does not 280 | replace Your obligations under this Public License where the Licensed 281 | Rights include other Copyright and Similar Rights. 282 | 283 | 284 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 285 | 286 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 287 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 288 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 289 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 290 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 291 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 292 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 293 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 294 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 295 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 296 | 297 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 298 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 299 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 300 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 301 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 302 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 303 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 304 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 305 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 306 | 307 | c. The disclaimer of warranties and limitation of liability provided 308 | above shall be interpreted in a manner that, to the extent 309 | possible, most closely approximates an absolute disclaimer and 310 | waiver of all liability. 311 | 312 | 313 | Section 6 -- Term and Termination. 314 | 315 | a. This Public License applies for the term of the Copyright and 316 | Similar Rights licensed here. However, if You fail to comply with 317 | this Public License, then Your rights under this Public License 318 | terminate automatically. 319 | 320 | b. Where Your right to use the Licensed Material has terminated under 321 | Section 6(a), it reinstates: 322 | 323 | 1. automatically as of the date the violation is cured, provided 324 | it is cured within 30 days of Your discovery of the 325 | violation; or 326 | 327 | 2. upon express reinstatement by the Licensor. 328 | 329 | For the avoidance of doubt, this Section 6(b) does not affect any 330 | right the Licensor may have to seek remedies for Your violations 331 | of this Public License. 332 | 333 | c. For the avoidance of doubt, the Licensor may also offer the 334 | Licensed Material under separate terms or conditions or stop 335 | distributing the Licensed Material at any time; however, doing so 336 | will not terminate this Public License. 337 | 338 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 339 | License. 340 | 341 | 342 | Section 7 -- Other Terms and Conditions. 343 | 344 | a. The Licensor shall not be bound by any additional or different 345 | terms or conditions communicated by You unless expressly agreed. 346 | 347 | b. Any arrangements, understandings, or agreements regarding the 348 | Licensed Material not stated herein are separate from and 349 | independent of the terms and conditions of this Public License. 350 | 351 | 352 | Section 8 -- Interpretation. 353 | 354 | a. For the avoidance of doubt, this Public License does not, and 355 | shall not be interpreted to, reduce, limit, restrict, or impose 356 | conditions on any use of the Licensed Material that could lawfully 357 | be made without permission under this Public License. 358 | 359 | b. To the extent possible, if any provision of this Public License is 360 | deemed unenforceable, it shall be automatically reformed to the 361 | minimum extent necessary to make it enforceable. If the provision 362 | cannot be reformed, it shall be severed from this Public License 363 | without affecting the enforceability of the remaining terms and 364 | conditions. 365 | 366 | c. No term or condition of this Public License will be waived and no 367 | failure to comply consented to unless expressly agreed to by the 368 | Licensor. 369 | 370 | d. Nothing in this Public License constitutes or may be interpreted 371 | as a limitation upon, or waiver of, any privileges and immunities 372 | that apply to the Licensor or You, including from the legal 373 | processes of any jurisdiction or authority. 374 | 375 | 376 | ======================================================================= 377 | 378 | Creative Commons is not a party to its public 379 | licenses. Notwithstanding, Creative Commons may elect to apply one of 380 | its public licenses to material it publishes and in those instances 381 | will be considered the “Licensor.” The text of the Creative Commons 382 | public licenses is dedicated to the public domain under the CC0 Public 383 | Domain Dedication. Except for the limited purpose of indicating that 384 | material is shared under a Creative Commons public license or as 385 | otherwise permitted by the Creative Commons policies published at 386 | creativecommons.org/policies, Creative Commons does not authorize the 387 | use of the trademark "Creative Commons" or any other trademark or logo 388 | of Creative Commons without its prior written consent including, 389 | without limitation, in connection with any unauthorized modifications 390 | to any of its public licenses or any other arrangements, 391 | understandings, or agreements concerning use of licensed material. For 392 | the avoidance of doubt, this paragraph does not form part of the 393 | public licenses. 394 | 395 | Creative Commons may be contacted at creativecommons.org. 396 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shun Sakai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # hf 8 | 9 | [![CI][ci-badge]][ci-url] 10 | [![Version][version-badge]][version-url] 11 | ![MSRV][msrv-badge] 12 | [![Docs][docs-badge]][docs-url] 13 | ![License][license-badge] 14 | [![REUSE status][reuse-badge]][reuse-url] 15 | 16 | **hf** is a cross-platform hidden file library and utility. 17 | 18 | This crate supports both Unix and Windows. On Unix, hidden files and 19 | directories are files and directories that starts with a dot character (`.`). 20 | On Windows, hidden files and directories are files and directories with the 21 | hidden file attribute. This crate provides operations related to hidden files 22 | and directories, such as making files and directories invisible and visible. 23 | 24 | ![Demo animation](assets/demo.gif) 25 | 26 | ## Installation 27 | 28 | ### From source 29 | 30 | ```sh 31 | cargo install hf 32 | ``` 33 | 34 | ### Via a package manager 35 | 36 | [![Packaging status][repology-badge]][repology-versions] 37 | 38 | ### From binaries 39 | 40 | The [release page] contains pre-built binaries for Linux, macOS and Windows. 41 | 42 | ### How to build 43 | 44 | Please see [BUILD.adoc]. 45 | 46 | ## Usage 47 | 48 | ### Make files invisible 49 | 50 | Don't actually hide anything, just show what would be done: 51 | 52 | ```sh 53 | hf hide -n data.txt 54 | ``` 55 | 56 | Actually hide files: 57 | 58 | ```sh 59 | hf hide -f data.txt 60 | ``` 61 | 62 | ### Make hidden files visible 63 | 64 | Don't actually show anything, just show what would be done: 65 | 66 | ```sh 67 | hf show -n .data.txt 68 | ``` 69 | 70 | Actually show hidden files: 71 | 72 | ```sh 73 | hf show -f .data.txt 74 | ``` 75 | 76 | ### Generate shell completion 77 | 78 | `completion` subcommand generates shell completions to standard output. 79 | 80 | The following shells are supported: 81 | 82 | - `bash` 83 | - `elvish` 84 | - `fish` 85 | - `nushell` 86 | - `powershell` 87 | - `zsh` 88 | 89 | Example: 90 | 91 | ```sh 92 | hf completion bash > hf.bash 93 | ``` 94 | 95 | ## Use as a library 96 | 97 | This crate is also available as a library. 98 | 99 | Run the following command in your project directory: 100 | 101 | ```sh 102 | cargo add --no-default-features hf 103 | ``` 104 | 105 | By default, the dependencies required to build the application are also built. 106 | If you disable the `default` feature, only the dependencies required to build 107 | the library will be built. 108 | 109 | ### Documentation 110 | 111 | See the [documentation][docs-url] for more details. 112 | 113 | ## Minimum supported Rust version 114 | 115 | The minimum supported Rust version (MSRV) of this library is v1.85.0. 116 | 117 | ## Command-line options 118 | 119 | Please see the following: 120 | 121 | - [`hf(1)`] 122 | - [`hf-hide(1)`] 123 | - [`hf-show(1)`] 124 | - [`hf-completion(1)`] 125 | 126 | ## Source code 127 | 128 | The upstream repository is available at 129 | . 130 | 131 | ## Changelog 132 | 133 | Please see [CHANGELOG.adoc]. 134 | 135 | ## Contributing 136 | 137 | Please see [CONTRIBUTING.adoc]. 138 | 139 | ## Home page 140 | 141 | 142 | 143 | ## License 144 | 145 | Copyright (C) 2022 Shun Sakai (see [AUTHORS.adoc]) 146 | 147 | 1. This program is distributed under the terms of either the _Apache License 148 | 2.0_ or the _MIT License_. 149 | 2. Some files are distributed under the terms of the _Creative Commons 150 | Attribution 4.0 International Public License_. 151 | 152 | This project is compliant with version 3.3 of the [_REUSE Specification_]. See 153 | copyright notices of individual files for more details on copyright and 154 | licensing information. 155 | 156 | [ci-badge]: https://img.shields.io/github/actions/workflow/status/sorairolake/hf/CI.yaml?branch=develop&style=for-the-badge&logo=github&label=CI 157 | [ci-url]: https://github.com/sorairolake/hf/actions?query=branch%3Adevelop+workflow%3ACI++ 158 | [version-badge]: https://img.shields.io/crates/v/hf?style=for-the-badge&logo=rust 159 | [version-url]: https://crates.io/crates/hf 160 | [msrv-badge]: https://img.shields.io/crates/msrv/hf?style=for-the-badge&logo=rust 161 | [docs-badge]: https://img.shields.io/docsrs/hf?style=for-the-badge&logo=docsdotrs&label=Docs.rs 162 | [docs-url]: https://docs.rs/hf 163 | [license-badge]: https://img.shields.io/crates/l/hf?style=for-the-badge 164 | [reuse-badge]: https://img.shields.io/reuse/compliance/github.com%2Fsorairolake%2Fhf?style=for-the-badge 165 | [reuse-url]: https://api.reuse.software/info/github.com/sorairolake/hf 166 | [repology-badge]: https://repology.org/badge/vertical-allrepos/hf.svg?columns=3 167 | [repology-versions]: https://repology.org/project/hf/versions 168 | [release page]: https://github.com/sorairolake/hf/releases 169 | [BUILD.adoc]: BUILD.adoc 170 | [`hf(1)`]: docs/man/man1/hf.1.adoc 171 | [`hf-hide(1)`]: docs/man/man1/hf-hide.1.adoc 172 | [`hf-show(1)`]: docs/man/man1/hf-show.1.adoc 173 | [`hf-completion(1)`]: docs/man/man1/hf-completion.1.adoc 174 | [CHANGELOG.adoc]: CHANGELOG.adoc 175 | [CONTRIBUTING.adoc]: CONTRIBUTING.adoc 176 | [AUTHORS.adoc]: AUTHORS.adoc 177 | [_REUSE Specification_]: https://reuse.software/spec-3.3/ 178 | -------------------------------------------------------------------------------- /antora-playbook.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | site: 6 | title: hf Documentation 7 | url: https://sorairolake.github.io/hf/ 8 | start_page: book::index.adoc 9 | 10 | content: 11 | sources: 12 | - url: . 13 | start_path: docs/book 14 | 15 | ui: 16 | bundle: 17 | url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable 18 | snapshot: true 19 | supplemental_files: docs/book/supplemental-ui 20 | 21 | runtime: 22 | log: 23 | failure_level: warn 24 | 25 | antora: 26 | extensions: 27 | - "@antora/lunr-extension" 28 | -------------------------------------------------------------------------------- /assets/README.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = Prerequisites 6 | 7 | * https://crates.io/crates/autocast[autocast] 8 | * https://docs.asciinema.org/manual/agg/[agg] 9 | * https://www.lcdf.org/gifsicle/[Gifsicle] 10 | -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sorairolake/hf/58adf79f556a0e51998323d61392425fb78bfcd5/assets/demo.gif -------------------------------------------------------------------------------- /assets/demo.gif.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2025 Shun Sakai 2 | 3 | SPDX-License-Identifier: CC-BY-4.0 4 | -------------------------------------------------------------------------------- /assets/demo.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | settings: 6 | width: 120 7 | height: 30 8 | title: hf demo 9 | 10 | instructions: 11 | - !Command 12 | command: rsync -ac ../{.github,.gitignore,build.rs,Cargo.toml,README.md} . 13 | hidden: true 14 | 15 | - !Marker Make files invisible 16 | - !Command 17 | command: eza -a 18 | - !Wait 1s 19 | - !Command 20 | command: hf hide -n .github .gitignore build.rs Cargo.toml README.md 21 | - !Wait 1s 22 | - !Command 23 | command: hf hide -f .github .gitignore build.rs Cargo.toml README.md 24 | - !Wait 1s 25 | - !Command 26 | command: eza -a 27 | - !Wait 3s 28 | - !Clear 29 | 30 | - !Command 31 | command: git clean -df 32 | hidden: true 33 | - !Command 34 | command: rsync -ac ../{.github,.gitignore,build.rs,Cargo.toml,README.md} . 35 | hidden: true 36 | 37 | - !Marker Make hidden files visible 38 | - !Command 39 | command: eza -a 40 | - !Wait 1s 41 | - !Command 42 | command: hf show -n .github .gitignore build.rs Cargo.toml README.md 43 | - !Wait 1s 44 | - !Command 45 | command: hf show -f .github .gitignore build.rs Cargo.toml README.md 46 | - !Wait 1s 47 | - !Command 48 | command: eza -a 49 | - !Wait 3s 50 | 51 | - !Command 52 | command: git clean -df 53 | hidden: true 54 | -------------------------------------------------------------------------------- /assets/generate.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # SPDX-FileCopyrightText: 2025 Shun Sakai 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 OR MIT 6 | 7 | set -euxCo pipefail 8 | 9 | scriptDir=$(cd "$(dirname "$0")" && pwd) 10 | cd "$scriptDir" 11 | 12 | autocast --overwrite demo.yaml demo.cast 13 | agg --font-family "Cascadia Code,Hack,Source Code Pro" demo.cast demo.gif 14 | gifsicle -b -O3 demo.gif 15 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | use std::{ 6 | env, io, 7 | process::{Command, ExitStatus}, 8 | }; 9 | 10 | fn generate_man_page(out_dir: &str) -> io::Result { 11 | let man_dir = env::current_dir()?.join("docs/man/man1"); 12 | let mut command = Command::new("asciidoctor"); 13 | command 14 | .args(["-b", "manpage"]) 15 | .args(["-D", out_dir]) 16 | .arg(man_dir.join("*.1.adoc")) 17 | .status() 18 | } 19 | 20 | fn main() { 21 | if cfg!(feature = "application") { 22 | println!("cargo:rerun-if-changed=docs/man"); 23 | 24 | let out_dir = env::var("OUT_DIR").expect("environment variable `OUT_DIR` not defined"); 25 | match generate_man_page(&out_dir) { 26 | Ok(exit_status) => { 27 | if !exit_status.success() { 28 | println!("cargo:warning=Asciidoctor failed: {exit_status}"); 29 | } 30 | } 31 | Err(err) => { 32 | println!("cargo:warning=failed to execute Asciidoctor: {err}"); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | msrv = "1.85.0" 6 | -------------------------------------------------------------------------------- /docs/book/antora.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | name: book 6 | title: hf 7 | version: ~ 8 | nav: 9 | - modules/ROOT/nav.adoc 10 | -------------------------------------------------------------------------------- /docs/book/modules/ROOT/examples/LICENSES: -------------------------------------------------------------------------------- 1 | ../../../../../LICENSES -------------------------------------------------------------------------------- /docs/book/modules/ROOT/images/demo.gif: -------------------------------------------------------------------------------- 1 | ../../../../../assets/demo.gif -------------------------------------------------------------------------------- /docs/book/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | * xref:install.adoc[] 6 | * xref:build.adoc[] 7 | * xref:usage.adoc[] 8 | * xref:use-as-a-library.adoc[] 9 | * https://docs.rs/hf[API Reference] 10 | 11 | .Man pages 12 | * xref:man/man1/hf.1.adoc[`hf(1)`] 13 | * xref:man/man1/hf-hide.1.adoc[`hf-hide(1)`] 14 | * xref:man/man1/hf-show.1.adoc[`hf-show(1)`] 15 | * xref:man/man1/hf-completion.1.adoc[`hf-completion(1)`] 16 | 17 | .Resources 18 | * xref:repository.adoc[] 19 | * xref:changelog.adoc[] 20 | * xref:contributing.adoc[] 21 | * xref:authors.adoc[] 22 | * xref:license.adoc[] 23 | -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/authors.adoc: -------------------------------------------------------------------------------- 1 | ../../../../../AUTHORS.adoc -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/build.adoc: -------------------------------------------------------------------------------- 1 | ../../../../../BUILD.adoc -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/changelog.adoc: -------------------------------------------------------------------------------- 1 | ../../../../../CHANGELOG.adoc -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/contributing.adoc: -------------------------------------------------------------------------------- 1 | ../../../../../CONTRIBUTING.adoc -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = hf 6 | :project-url: https://github.com/sorairolake/hf 7 | :shields-url: https://img.shields.io 8 | :ci-badge: {shields-url}/github/actions/workflow/status/sorairolake/hf/CI.yaml?branch=develop&style=for-the-badge&logo=github&label=CI 9 | :ci-url: {project-url}/actions?query=branch%3Adevelop+workflow%3ACI++ 10 | :version-badge: {shields-url}/crates/v/hf?style=for-the-badge&logo=rust 11 | :version-url: https://crates.io/crates/hf 12 | :msrv-badge: {shields-url}/crates/msrv/hf?style=for-the-badge&logo=rust 13 | :docs-badge: {shields-url}/docsrs/hf?style=for-the-badge&logo=docsdotrs&label=Docs.rs 14 | :docs-url: https://docs.rs/hf 15 | :license-badge: {shields-url}/crates/l/hf?style=for-the-badge 16 | :reuse-badge: {shields-url}/reuse/compliance/github.com%2Fsorairolake%2Fhf?style=for-the-badge 17 | :reuse-url: https://api.reuse.software/info/github.com/sorairolake/hf 18 | 19 | image:{ci-badge}[CI,link={ci-url}] 20 | image:{version-badge}[Version,link={version-url}] 21 | image:{msrv-badge}[MSRV] 22 | image:{docs-badge}[Docs,link={docs-url}] 23 | image:{license-badge}[License] 24 | image:{reuse-badge}[REUSE status,link={reuse-url}] 25 | 26 | *hf* is a cross-platform hidden file library and utility. 27 | 28 | This crate supports both Unix and Windows. On Unix, hidden files and 29 | directories are files and directories that starts with a dot character (`.`). 30 | On Windows, hidden files and directories are files and directories with the 31 | hidden file attribute. This crate provides operations related to hidden files 32 | and directories, such as making files and directories invisible and visible. 33 | 34 | image::demo.gif[Demo animation] 35 | -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/install.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = Installation 6 | :repology-url: https://repology.org 7 | :github-url: https://github.com 8 | :repology-badge: {repology-url}/badge/vertical-allrepos/hf.svg?columns=3 9 | :repology-versions: {repology-url}/project/hf/versions 10 | :release-page-url: {github-url}/sorairolake/hf/releases 11 | 12 | == From source 13 | 14 | [source,sh] 15 | ---- 16 | cargo install hf 17 | ---- 18 | 19 | == Via a package manager 20 | 21 | image::{repology-badge}[Packaging status,link={repology-versions}] 22 | 23 | == From binaries 24 | 25 | The {release-page-url}[release page] contains pre-built binaries for Linux, 26 | macOS and Windows. 27 | -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/license.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = License 6 | :reuse-spec-url: https://reuse.software/spec-3.3/ 7 | 8 | Copyright (C) 2022 Shun Sakai 9 | 10 | . This program is distributed under the terms of either the _Apache License 11 | 2.0_ or the _MIT License_. 12 | . Some documentations are distributed under the terms of the _Creative Commons 13 | Attribution 4.0 International Public License_. 14 | 15 | This project is compliant with version 3.3 of the 16 | {reuse-spec-url}[_REUSE Specification_]. 17 | 18 | == Apache License 2.0 19 | 20 | .... 21 | include::example$LICENSES/Apache-2.0.txt[] 22 | .... 23 | 24 | == MIT License 25 | 26 | .... 27 | include::example$LICENSES/MIT.txt[] 28 | .... 29 | 30 | == Creative Commons Attribution 4.0 International Public License 31 | 32 | .... 33 | include::example$LICENSES/CC-BY-4.0.txt[] 34 | .... 35 | -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/man/man1/hf-completion.1.adoc: -------------------------------------------------------------------------------- 1 | ../../../../../../man/man1/hf-completion.1.adoc -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/man/man1/hf-hide.1.adoc: -------------------------------------------------------------------------------- 1 | ../../../../../../man/man1/hf-hide.1.adoc -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/man/man1/hf-show.1.adoc: -------------------------------------------------------------------------------- 1 | ../../../../../../man/man1/hf-show.1.adoc -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/man/man1/hf.1.adoc: -------------------------------------------------------------------------------- 1 | ../../../../../../man/man1/hf.1.adoc -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/repository.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = Source Code 6 | 7 | The upstream repository is available at 8 | https://github.com/sorairolake/hf.git. 9 | -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/usage.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = Usage 6 | 7 | == Make files invisible 8 | 9 | .Don't actually hide anything, just show what would be done 10 | [source,sh] 11 | ---- 12 | hf hide -n data.txt 13 | ---- 14 | 15 | .Actually hide files 16 | [source,sh] 17 | ---- 18 | hf hide -f data.txt 19 | ---- 20 | 21 | == Make hidden files visible 22 | 23 | .Don't actually show anything, just show what would be done 24 | [source,sh] 25 | ---- 26 | hf show -n .data.txt 27 | ---- 28 | 29 | .Actually show hidden files 30 | [source,sh] 31 | ---- 32 | hf show -f .data.txt 33 | ---- 34 | 35 | == Generate shell completion 36 | 37 | `completion` subcommand generates shell completions to standard output. 38 | 39 | .The following shells are supported 40 | * `bash` 41 | * `elvish` 42 | * `fish` 43 | * `nushell` 44 | * `powershell` 45 | * `zsh` 46 | 47 | .Example 48 | [source,sh] 49 | ---- 50 | hf completion bash > hf.bash 51 | ---- 52 | -------------------------------------------------------------------------------- /docs/book/modules/ROOT/pages/use-as-a-library.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = Use as a library 6 | 7 | This crate is also available as a library. 8 | 9 | .Run the following command in your project directory 10 | [source,sh] 11 | ---- 12 | cargo add --no-default-features hf 13 | ---- 14 | 15 | By default, the dependencies required to build the application are also built. 16 | If you disable the `default` feature, only the dependencies required to build 17 | the library will be built. 18 | -------------------------------------------------------------------------------- /docs/book/modules/ROOT/partials/man/man1/include: -------------------------------------------------------------------------------- 1 | ../../../../../../man/man1/include -------------------------------------------------------------------------------- /docs/book/supplemental-ui/partials/footer-content.hbs: -------------------------------------------------------------------------------- 1 | {{!-- 2 | SPDX-FileCopyrightText: 2024 Shun Sakai 3 | 4 | SPDX-License-Identifier: CC-BY-4.0 5 | --}} 6 | 7 | 11 | -------------------------------------------------------------------------------- /docs/book/supplemental-ui/partials/header-content.hbs: -------------------------------------------------------------------------------- 1 | {{!-- 2 | SPDX-FileCopyrightText: 2024 Shun Sakai 3 | 4 | SPDX-License-Identifier: CC-BY-4.0 5 | --}} 6 | 7 |
8 | 31 |
32 | -------------------------------------------------------------------------------- /docs/man/man1/hf-completion.1.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = hf-completion(1) 6 | // Specify in UTC. 7 | :docdate: 2025-05-26 8 | :revnumber: 0.4.0 9 | :doctype: manpage 10 | :mansource: hf {revnumber} 11 | :manmanual: General Commands Manual 12 | ifndef::site-gen-antora[:includedir: ./include] 13 | 14 | == NAME 15 | 16 | hf-completion - generate shell completion 17 | 18 | == SYNOPSIS 19 | 20 | *hf completion* _SHELL_ 21 | 22 | == DESCRIPTION 23 | 24 | This command generates shell completion. The completion is output to standard 25 | output. 26 | 27 | == POSITIONAL ARGUMENTS 28 | 29 | _SHELL_:: 30 | 31 | Shell to generate completion for. 32 | 33 | The possible values are:{blank}::: 34 | 35 | *bash*:::: 36 | 37 | Bash. 38 | 39 | *elvish*:::: 40 | 41 | Elvish. 42 | 43 | *fish*:::: 44 | 45 | fish. 46 | 47 | *nushell*:::: 48 | 49 | Nushell. 50 | 51 | *powershell*:::: 52 | 53 | PowerShell. 54 | 55 | *zsh*:::: 56 | 57 | Zsh. 58 | 59 | == OPTIONS 60 | 61 | *-h*, *--help*:: 62 | 63 | Print help message. The short flag (*-h*) will print a condensed help message 64 | while the long flag (*--help*) will print a detailed help message. 65 | 66 | *-V*, *--version*:: 67 | 68 | Print version number. 69 | 70 | ifndef::site-gen-antora[include::{includedir}/section-exit-status.adoc[]] 71 | ifdef::site-gen-antora[include::partial$man/man1/include/section-exit-status.adoc[]] 72 | 73 | ifndef::site-gen-antora[include::{includedir}/section-notes.adoc[]] 74 | ifdef::site-gen-antora[include::partial$man/man1/include/section-notes.adoc[]] 75 | 76 | ifndef::site-gen-antora[include::{includedir}/section-reporting-bugs.adoc[]] 77 | ifdef::site-gen-antora[include::partial$man/man1/include/section-reporting-bugs.adoc[]] 78 | 79 | ifndef::site-gen-antora[include::{includedir}/section-copyright.adoc[]] 80 | ifdef::site-gen-antora[include::partial$man/man1/include/section-copyright.adoc[]] 81 | 82 | == SEE ALSO 83 | 84 | *hf*(1), *hf-hide*(1), *hf-show*(1) 85 | -------------------------------------------------------------------------------- /docs/man/man1/hf-hide.1.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = hf-hide(1) 6 | // Specify in UTC. 7 | :docdate: 2025-05-26 8 | :revnumber: 0.4.0 9 | :doctype: manpage 10 | :mansource: hf {revnumber} 11 | :manmanual: General Commands Manual 12 | ifndef::site-gen-antora[:includedir: ./include] 13 | 14 | == NAME 15 | 16 | hf-hide - make files and directories invisible 17 | 18 | == SYNOPSIS 19 | 20 | *hf hide* [_OPTION_]... [_FILE_]... 21 | 22 | == DESCRIPTION 23 | 24 | This command makes files and directories invisible. 25 | 26 | On Unix, this command renames the file to start with `.`. On Windows, this 27 | command sets the hidden file attribute to the file. 28 | 29 | When you run this command, you must specify either *-f* or *-n*. When *-n* is 30 | specified, it just shows what would be done. When *-f* is specified, it 31 | actually hide files. 32 | 33 | == POSITIONAL ARGUMENTS 34 | 35 | _FILE_:: 36 | 37 | Files and directories to hide. 38 | 39 | == OPTIONS 40 | 41 | *--log-level* _LEVEL_:: 42 | 43 | The minimum log level to print. 44 | 45 | The possible values are:{blank}::: 46 | 47 | *OFF*:::: 48 | 49 | Lowest log level. 50 | 51 | *ERROR*:::: 52 | 53 | Error log level. 54 | 55 | *WARN*:::: 56 | 57 | Warn log level. 58 | 59 | *INFO*:::: 60 | 61 | Info log level. This is the default value. 62 | 63 | *DEBUG*:::: 64 | 65 | Debug log level. 66 | 67 | *TRACE*:::: 68 | 69 | Trace log level. 70 | 71 | *-f*, *--force*:: 72 | 73 | Actually hide files and directories. 74 | 75 | *-n*, *--dry-run*:: 76 | 77 | Don't actually hide anything, just show what would be done. 78 | 79 | *-h*, *--help*:: 80 | 81 | Print help message. The short flag (*-h*) will print a condensed help message 82 | while the long flag (*--help*) will print a detailed help message. 83 | 84 | *-V*, *--version*:: 85 | 86 | Print version number. 87 | 88 | ifndef::site-gen-antora[include::{includedir}/section-exit-status.adoc[]] 89 | ifdef::site-gen-antora[include::partial$man/man1/include/section-exit-status.adoc[]] 90 | 91 | ifndef::site-gen-antora[include::{includedir}/section-notes.adoc[]] 92 | ifdef::site-gen-antora[include::partial$man/man1/include/section-notes.adoc[]] 93 | 94 | == EXAMPLES 95 | 96 | Make the given file invisible:{blank}:: 97 | 98 | $ *hf hide -f data.txt* 99 | 100 | Show what would be done:{blank}:: 101 | 102 | $ *hf hide -n Cargo.toml src/* 103 | 104 | ifndef::site-gen-antora[include::{includedir}/section-reporting-bugs.adoc[]] 105 | ifdef::site-gen-antora[include::partial$man/man1/include/section-reporting-bugs.adoc[]] 106 | 107 | ifndef::site-gen-antora[include::{includedir}/section-copyright.adoc[]] 108 | ifdef::site-gen-antora[include::partial$man/man1/include/section-copyright.adoc[]] 109 | 110 | == SEE ALSO 111 | 112 | *hf*(1), *hf-completion*(1), *hf-show*(1) 113 | -------------------------------------------------------------------------------- /docs/man/man1/hf-show.1.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = hf-show(1) 6 | // Specify in UTC. 7 | :docdate: 2025-05-26 8 | :revnumber: 0.4.0 9 | :doctype: manpage 10 | :mansource: hf {revnumber} 11 | :manmanual: General Commands Manual 12 | ifndef::site-gen-antora[:includedir: ./include] 13 | 14 | == NAME 15 | 16 | hf-show - make hidden files and directories visible 17 | 18 | == SYNOPSIS 19 | 20 | *hf show* [_OPTION_]... [_FILE_]... 21 | 22 | == DESCRIPTION 23 | 24 | This command makes hidden files and directories visible. 25 | 26 | On Unix, this command renames the file to start with a character other than 27 | `.`. On Windows, this command clears the hidden file attribute to the file. 28 | 29 | When you run this command, you must specify either *-f* or *-n*. When *-n* is 30 | specified, it just shows what would be done. When *-f* is specified, it 31 | actually show hidden files. 32 | 33 | == POSITIONAL ARGUMENTS 34 | 35 | _FILE_:: 36 | 37 | Hidden files and directories to show. 38 | 39 | == OPTIONS 40 | 41 | *--log-level* _LEVEL_:: 42 | 43 | The minimum log level to print. 44 | 45 | The possible values are:{blank}::: 46 | 47 | *OFF*:::: 48 | 49 | Lowest log level. 50 | 51 | *ERROR*:::: 52 | 53 | Error log level. 54 | 55 | *WARN*:::: 56 | 57 | Warn log level. 58 | 59 | *INFO*:::: 60 | 61 | Info log level. This is the default value. 62 | 63 | *DEBUG*:::: 64 | 65 | Debug log level. 66 | 67 | *TRACE*:::: 68 | 69 | Trace log level. 70 | 71 | *-f*, *--force*:: 72 | 73 | Actually show hidden files and directories. 74 | 75 | *-n*, *--dry-run*:: 76 | 77 | Don't actually show anything, just show what would be done. 78 | 79 | *-h*, *--help*:: 80 | 81 | Print help message. The short flag (*-h*) will print a condensed help message 82 | while the long flag (*--help*) will print a detailed help message. 83 | 84 | *-V*, *--version*:: 85 | 86 | Print version number. 87 | 88 | ifndef::site-gen-antora[include::{includedir}/section-exit-status.adoc[]] 89 | ifdef::site-gen-antora[include::partial$man/man1/include/section-exit-status.adoc[]] 90 | 91 | ifndef::site-gen-antora[include::{includedir}/section-notes.adoc[]] 92 | ifdef::site-gen-antora[include::partial$man/man1/include/section-notes.adoc[]] 93 | 94 | == EXAMPLES 95 | 96 | Make the given hidden file visible:{blank}:: 97 | 98 | $ *hf show -f .data.txt* 99 | 100 | Show what would be done:{blank}:: 101 | 102 | $ *hf show -n .github/ .gitignore* 103 | 104 | ifndef::site-gen-antora[include::{includedir}/section-reporting-bugs.adoc[]] 105 | ifdef::site-gen-antora[include::partial$man/man1/include/section-reporting-bugs.adoc[]] 106 | 107 | ifndef::site-gen-antora[include::{includedir}/section-copyright.adoc[]] 108 | ifdef::site-gen-antora[include::partial$man/man1/include/section-copyright.adoc[]] 109 | 110 | == SEE ALSO 111 | 112 | *hf*(1), *hf-completion*(1), *hf-hide*(1) 113 | -------------------------------------------------------------------------------- /docs/man/man1/hf.1.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | = hf(1) 6 | // Specify in UTC. 7 | :docdate: 2025-05-26 8 | :revnumber: 0.4.0 9 | :doctype: manpage 10 | :mansource: hf {revnumber} 11 | :manmanual: General Commands Manual 12 | ifndef::site-gen-antora[:includedir: ./include] 13 | :enwp-url: https://en.wikipedia.org 14 | :enwp-article-url: {enwp-url}/wiki 15 | :hidden-file-url: {enwp-article-url}/Hidden_file_and_hidden_directory 16 | 17 | == NAME 18 | 19 | hf - an utility for hidden files and directories 20 | 21 | == SYNOPSIS 22 | 23 | *{manname}* [_OPTION_]... _COMMAND_ 24 | 25 | == DESCRIPTION 26 | 27 | *{manname}* is a command-line utility for 28 | {hidden-file-url}[hidden files and directories]. 29 | 30 | == COMMANDS 31 | 32 | *hf-hide*(1):: 33 | 34 | Make files and directories invisible. 35 | 36 | *hf-show*(1):: 37 | 38 | Make hidden files and directories visible. 39 | 40 | *hf-completion*(1):: 41 | 42 | Generate shell completion. 43 | 44 | == OPTIONS 45 | 46 | *--log-level* _LEVEL_:: 47 | 48 | The minimum log level to print. 49 | 50 | The possible values are:{blank}::: 51 | 52 | *OFF*:::: 53 | 54 | Lowest log level. 55 | 56 | *ERROR*:::: 57 | 58 | Error log level. 59 | 60 | *WARN*:::: 61 | 62 | Warn log level. 63 | 64 | *INFO*:::: 65 | 66 | Info log level. This is the default value. 67 | 68 | *DEBUG*:::: 69 | 70 | Debug log level. 71 | 72 | *TRACE*:::: 73 | 74 | Trace log level. 75 | 76 | *-h*, *--help*:: 77 | 78 | Print help message. The short flag (*-h*) will print a condensed help message 79 | while the long flag (*--help*) will print a detailed help message. 80 | 81 | *-V*, *--version*:: 82 | 83 | Print version number. 84 | 85 | ifndef::site-gen-antora[include::{includedir}/section-exit-status.adoc[]] 86 | ifdef::site-gen-antora[include::partial$man/man1/include/section-exit-status.adoc[]] 87 | 88 | ifndef::site-gen-antora[include::{includedir}/section-notes.adoc[]] 89 | ifdef::site-gen-antora[include::partial$man/man1/include/section-notes.adoc[]] 90 | 91 | ifndef::site-gen-antora[include::{includedir}/section-reporting-bugs.adoc[]] 92 | ifdef::site-gen-antora[include::partial$man/man1/include/section-reporting-bugs.adoc[]] 93 | 94 | ifndef::site-gen-antora[include::{includedir}/section-copyright.adoc[]] 95 | ifdef::site-gen-antora[include::partial$man/man1/include/section-copyright.adoc[]] 96 | -------------------------------------------------------------------------------- /docs/man/man1/include/section-copyright.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | == COPYRIGHT 6 | 7 | Copyright (C) 2022 Shun Sakai 8 | 9 | . This program is distributed under the terms of either the Apache License 2.0 10 | or the MIT License. 11 | . This manual page is distributed under the terms of the Creative Commons 12 | Attribution 4.0 International Public License. 13 | 14 | This is free software: you are free to change and redistribute it. There is NO 15 | WARRANTY, to the extent permitted by law. 16 | -------------------------------------------------------------------------------- /docs/man/man1/include/section-exit-status.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | == EXIT STATUS 6 | :sysexits-man-page-url: https://man.openbsd.org/sysexits 7 | 8 | *0*:: 9 | 10 | Successful program execution. 11 | 12 | *1*:: 13 | 14 | An error occurred. 15 | 16 | *2*:: 17 | 18 | An error occurred while parsing command-line arguments. 19 | 20 | Exit statuses other than these are defined by 21 | {sysexits-man-page-url}[``]. 22 | -------------------------------------------------------------------------------- /docs/man/man1/include/section-notes.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | == NOTES 6 | 7 | Source repository:{blank}:: 8 | 9 | https://github.com/sorairolake/hf 10 | -------------------------------------------------------------------------------- /docs/man/man1/include/section-reporting-bugs.adoc: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: CC-BY-4.0 4 | 5 | == REPORTING BUGS 6 | 7 | Report bugs to:{blank}:: 8 | 9 | https://github.com/sorairolake/hf/issues 10 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | alias lint := clippy 6 | 7 | # Run default recipe 8 | _default: 9 | just -l 10 | 11 | # Build a package 12 | build: 13 | cargo build 14 | 15 | # Remove generated artifacts 16 | clean: 17 | cargo clean 18 | 19 | # Check a package 20 | check: 21 | cargo check 22 | 23 | # Run tests 24 | test: 25 | cargo test 26 | 27 | # Run the formatter 28 | fmt: 29 | cargo fmt 30 | 31 | # Run the formatter with options 32 | fmt-with-options: 33 | cargo +nightly fmt 34 | 35 | # Run the linter 36 | clippy: 37 | cargo clippy -- -D warnings 38 | 39 | # Apply lint suggestions 40 | clippy-fix: 41 | cargo clippy --fix --allow-dirty --allow-staged -- -D warnings 42 | 43 | # Build the package documentation 44 | doc $RUSTDOCFLAGS="--cfg docsrs": 45 | cargo +nightly doc --all-features 46 | 47 | # Run the linter for GitHub Actions workflow files 48 | lint-github-actions: 49 | actionlint -verbose 50 | 51 | # Run the formatter for the README 52 | fmt-readme: 53 | npx prettier -w README.md 54 | 55 | # Build the book 56 | build-book: 57 | npx antora antora-playbook.yml 58 | 59 | # Increment the version 60 | bump part: 61 | bump-my-version bump {{ part }} 62 | cargo set-version --bump {{ part }} 63 | -------------------------------------------------------------------------------- /package-lock.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2024 Shun Sakai 2 | 3 | SPDX-License-Identifier: Apache-2.0 OR MIT 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "hf", 4 | "version": "1.0.0", 5 | "description": "Cross-platform hidden file library and utility", 6 | "license": "CC-BY-4.0", 7 | "author": "Shun Sakai ", 8 | "homepage": "https://github.com/sorairolake/hf#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/sorairolake/hf.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/sorairolake/hf/issues" 15 | }, 16 | "devDependencies": { 17 | "@antora/cli": "^3.1.10", 18 | "@antora/lunr-extension": "^1.0.0-alpha.10", 19 | "@antora/site-generator": "^3.1.10" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2024 Shun Sakai 2 | 3 | SPDX-License-Identifier: Apache-2.0 OR MIT 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Shun Sakai 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | edition = "2024" 6 | format_code_in_doc_comments = true 7 | format_macro_matchers = true 8 | group_imports = "StdExternalCrate" 9 | imports_granularity = "Crate" 10 | style_edition = "2024" 11 | wrap_comments = true 12 | -------------------------------------------------------------------------------- /src/bin/hf/app.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | use anyhow::Context; 6 | use clap::Parser; 7 | use log::{info, warn}; 8 | use simplelog::{ColorChoice, Config, SimpleLogger, TermLogger, TerminalMode}; 9 | 10 | use crate::cli::{Command, Opt}; 11 | 12 | /// Runs the program and returns the result. 13 | #[allow(clippy::too_many_lines)] 14 | pub fn run() -> anyhow::Result<()> { 15 | let opt = Opt::parse(); 16 | 17 | let log_level = opt.log_level.into(); 18 | TermLogger::init( 19 | log_level, 20 | Config::default(), 21 | TerminalMode::Mixed, 22 | ColorChoice::Auto, 23 | ) 24 | .or_else(|_| SimpleLogger::init(log_level, Config::default()))?; 25 | 26 | match opt.command { 27 | Command::Hide(arg) => { 28 | let files = arg 29 | .input 30 | .into_iter() 31 | .map(|f| { 32 | #[cfg(unix)] 33 | std::fs::metadata(&f) 34 | .with_context(|| format!("{} does not exist", f.display()))?; 35 | let is_hidden = hf::is_hidden(&f).with_context(|| { 36 | format!("could not read information from {}", f.display()) 37 | }); 38 | match is_hidden { 39 | Ok(false) => Ok((f, true)), 40 | Ok(true) => Ok((f, false)), 41 | Err(err) => Err(err), 42 | } 43 | }) 44 | .collect::>>()?; 45 | 46 | match (arg.dry_run, arg.force) { 47 | (true, _) => { 48 | for file in files { 49 | if file.1 { 50 | println!("{}", file.0.display()); 51 | } else { 52 | warn!("{} is ignored", file.0.display()); 53 | } 54 | } 55 | } 56 | (_, true) => { 57 | for file in files { 58 | if file.1 { 59 | hf::hide(&file.0) 60 | .with_context(|| format!("could not hide {}", file.0.display()))?; 61 | info!("{} has been hidden", file.0.display()); 62 | } else { 63 | warn!("{} is already hidden", file.0.display()); 64 | } 65 | } 66 | } 67 | _ => unreachable!(), 68 | } 69 | } 70 | Command::Show(arg) => { 71 | let files = arg 72 | .input 73 | .into_iter() 74 | .map(|f| { 75 | #[cfg(unix)] 76 | std::fs::metadata(&f) 77 | .with_context(|| format!("{} does not exist", f.display()))?; 78 | let is_hidden = hf::is_hidden(&f).with_context(|| { 79 | format!("could not read information from {}", f.display()) 80 | }); 81 | match is_hidden { 82 | Ok(true) => Ok((f, true)), 83 | Ok(false) => Ok((f, false)), 84 | Err(err) => Err(err), 85 | } 86 | }) 87 | .collect::>>()?; 88 | 89 | match (arg.dry_run, arg.force) { 90 | (true, _) => { 91 | for file in files { 92 | if file.1 { 93 | println!("{}", file.0.display()); 94 | } else { 95 | warn!("{} is ignored", file.0.display()); 96 | } 97 | } 98 | } 99 | (_, true) => { 100 | for file in files { 101 | if file.1 { 102 | hf::show(&file.0) 103 | .with_context(|| format!("could not show {}", file.0.display()))?; 104 | info!("{} has been shown", file.0.display()); 105 | } else { 106 | warn!("{} is already shown", file.0.display()); 107 | } 108 | } 109 | } 110 | _ => unreachable!(), 111 | } 112 | } 113 | Command::Completion(arg) => { 114 | Opt::print_completion(arg.shell); 115 | } 116 | } 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /src/bin/hf/cli.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | use std::{ 6 | io::{self, Write}, 7 | path::PathBuf, 8 | }; 9 | 10 | use clap::{ArgGroup, Args, CommandFactory, Parser, Subcommand, ValueEnum, ValueHint}; 11 | use clap_complete::Generator; 12 | use simplelog::LevelFilter; 13 | 14 | #[derive(Debug, Parser)] 15 | #[command( 16 | version, 17 | about, 18 | max_term_width(100), 19 | propagate_version(true), 20 | arg_required_else_help(false) 21 | )] 22 | pub struct Opt { 23 | /// The minimum log level to print. 24 | #[arg( 25 | long, 26 | value_enum, 27 | default_value_t, 28 | global(true), 29 | value_name("LEVEL"), 30 | ignore_case(true) 31 | )] 32 | pub log_level: LogLevel, 33 | 34 | #[command(subcommand)] 35 | pub command: Command, 36 | } 37 | 38 | #[derive(Debug, Subcommand)] 39 | pub enum Command { 40 | /// Make files and directories invisible. 41 | Hide(Hide), 42 | 43 | /// Make hidden files and directories visible. 44 | Show(Show), 45 | 46 | /// Generate shell completion. 47 | /// 48 | /// The completion is output to standard output. 49 | Completion(Completion), 50 | } 51 | 52 | #[derive(Args, Debug)] 53 | #[command(group(ArgGroup::new("mode").required(true)))] 54 | pub struct Hide { 55 | /// Actually hide files and directories. 56 | #[arg(short, long, group("mode"))] 57 | pub force: bool, 58 | 59 | /// Don't actually hide anything, just show what would be done. 60 | #[arg(short('n'), long, group("mode"))] 61 | pub dry_run: bool, 62 | 63 | /// Files and directories to hide. 64 | #[arg(value_name("FILE"), value_hint(ValueHint::FilePath))] 65 | pub input: Vec, 66 | } 67 | 68 | #[derive(Args, Debug)] 69 | #[command(group(ArgGroup::new("mode").required(true)))] 70 | pub struct Show { 71 | /// Actually show hidden files and directories. 72 | #[arg(short, long, group("mode"))] 73 | pub force: bool, 74 | 75 | /// Don't actually show anything, just show what would be done. 76 | #[arg(short('n'), long, group("mode"))] 77 | pub dry_run: bool, 78 | 79 | /// Hidden files and directories to show. 80 | #[arg(value_name("FILE"), value_hint(ValueHint::FilePath))] 81 | pub input: Vec, 82 | } 83 | 84 | #[derive(Args, Debug)] 85 | pub struct Completion { 86 | /// Shell to generate completion for. 87 | #[arg(value_enum, ignore_case(true))] 88 | pub shell: Shell, 89 | } 90 | 91 | impl Opt { 92 | /// Generates shell completion and print it. 93 | pub fn print_completion(generator: impl Generator) { 94 | clap_complete::generate( 95 | generator, 96 | &mut Self::command(), 97 | Self::command().get_name(), 98 | &mut io::stdout(), 99 | ); 100 | } 101 | } 102 | 103 | #[derive(Clone, Debug, ValueEnum)] 104 | #[allow(clippy::doc_markdown)] 105 | #[value(rename_all = "lower")] 106 | pub enum Shell { 107 | /// Bash. 108 | Bash, 109 | 110 | /// Elvish. 111 | Elvish, 112 | 113 | /// fish. 114 | Fish, 115 | 116 | /// Nushell. 117 | Nushell, 118 | 119 | #[allow(clippy::enum_variant_names)] 120 | /// PowerShell. 121 | PowerShell, 122 | 123 | /// Zsh. 124 | Zsh, 125 | } 126 | 127 | impl Generator for Shell { 128 | fn file_name(&self, name: &str) -> String { 129 | match self { 130 | Self::Bash => clap_complete::Shell::Bash.file_name(name), 131 | Self::Elvish => clap_complete::Shell::Elvish.file_name(name), 132 | Self::Fish => clap_complete::Shell::Fish.file_name(name), 133 | Self::Nushell => clap_complete_nushell::Nushell.file_name(name), 134 | Self::PowerShell => clap_complete::Shell::PowerShell.file_name(name), 135 | Self::Zsh => clap_complete::Shell::Zsh.file_name(name), 136 | } 137 | } 138 | 139 | fn generate(&self, cmd: &clap::Command, buf: &mut dyn Write) { 140 | match self { 141 | Self::Bash => clap_complete::Shell::Bash.generate(cmd, buf), 142 | Self::Elvish => clap_complete::Shell::Elvish.generate(cmd, buf), 143 | Self::Fish => clap_complete::Shell::Fish.generate(cmd, buf), 144 | Self::Nushell => clap_complete_nushell::Nushell.generate(cmd, buf), 145 | Self::PowerShell => clap_complete::Shell::PowerShell.generate(cmd, buf), 146 | Self::Zsh => clap_complete::Shell::Zsh.generate(cmd, buf), 147 | } 148 | } 149 | } 150 | 151 | #[derive(Clone, Debug, Default, Eq, PartialEq, ValueEnum)] 152 | #[value(rename_all = "UPPER")] 153 | pub enum LogLevel { 154 | /// Lowest log level. 155 | Off, 156 | 157 | /// Error log level. 158 | Error, 159 | 160 | /// Warn log level. 161 | Warn, 162 | 163 | /// Info log level. 164 | #[default] 165 | Info, 166 | 167 | /// Debug log level. 168 | Debug, 169 | 170 | /// Trace log level. 171 | Trace, 172 | } 173 | 174 | impl From for LevelFilter { 175 | fn from(level: LogLevel) -> Self { 176 | match level { 177 | LogLevel::Off => Self::Off, 178 | LogLevel::Error => Self::Error, 179 | LogLevel::Warn => Self::Warn, 180 | LogLevel::Info => Self::Info, 181 | LogLevel::Debug => Self::Debug, 182 | LogLevel::Trace => Self::Trace, 183 | } 184 | } 185 | } 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | use super::*; 190 | 191 | #[test] 192 | fn verify_app() { 193 | Opt::command().debug_assert(); 194 | } 195 | 196 | #[test] 197 | fn file_name_shell() { 198 | assert_eq!(Shell::Bash.file_name("hf"), "hf.bash"); 199 | assert_eq!(Shell::Elvish.file_name("hf"), "hf.elv"); 200 | assert_eq!(Shell::Fish.file_name("hf"), "hf.fish"); 201 | assert_eq!(Shell::Nushell.file_name("hf"), "hf.nu"); 202 | assert_eq!(Shell::PowerShell.file_name("hf"), "_hf.ps1"); 203 | assert_eq!(Shell::Zsh.file_name("hf"), "_hf"); 204 | } 205 | 206 | #[test] 207 | fn default_log_level() { 208 | assert_eq!(LogLevel::default(), LogLevel::Info); 209 | } 210 | 211 | #[test] 212 | fn from_log_level_to_level_filter() { 213 | assert_eq!(LevelFilter::from(LogLevel::Off), LevelFilter::Off); 214 | assert_eq!(LevelFilter::from(LogLevel::Error), LevelFilter::Error); 215 | assert_eq!(LevelFilter::from(LogLevel::Warn), LevelFilter::Warn); 216 | assert_eq!(LevelFilter::from(LogLevel::Info), LevelFilter::Info); 217 | assert_eq!(LevelFilter::from(LogLevel::Debug), LevelFilter::Debug); 218 | assert_eq!(LevelFilter::from(LogLevel::Trace), LevelFilter::Trace); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/bin/hf/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | // Lint levels of rustc. 6 | #![forbid(unsafe_code)] 7 | 8 | mod app; 9 | mod cli; 10 | 11 | use std::{io, process::ExitCode}; 12 | 13 | fn main() -> ExitCode { 14 | match app::run() { 15 | Ok(()) => ExitCode::SUCCESS, 16 | Err(err) => { 17 | eprintln!("Error: {err:?}"); 18 | if let Some(e) = err.downcast_ref::() { 19 | return sysexits::ExitCode::from(e.kind()).into(); 20 | } 21 | ExitCode::FAILURE 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | //! The `hf` crate is a cross-platform library for manipulating [hidden files 6 | //! and directories]. 7 | //! 8 | //! This crate supports both Unix and Windows. On Unix, hidden files and 9 | //! directories are files and directories that starts with a dot character 10 | //! (`.`). On Windows, hidden files and directories are files and directories 11 | //! with the hidden file attribute. 12 | //! 13 | //! # Examples 14 | //! 15 | //! ## On Unix 16 | //! 17 | //! ``` 18 | //! # #[cfg(unix)] 19 | //! # { 20 | //! use std::fs::File; 21 | //! 22 | //! let temp_dir = tempfile::tempdir().unwrap(); 23 | //! let temp_dir = temp_dir.path(); 24 | //! let file_path = temp_dir.join("foo.txt"); 25 | //! let hidden_file_path = hf::unix::hidden_file_name(&file_path).unwrap(); 26 | //! assert_eq!(hidden_file_path, temp_dir.join(".foo.txt")); 27 | //! assert!(!file_path.exists()); 28 | //! assert!(!hidden_file_path.exists()); 29 | //! 30 | //! File::create(&file_path).unwrap(); 31 | //! assert!(file_path.exists()); 32 | //! assert!(!hidden_file_path.exists()); 33 | //! 34 | //! hf::hide(&file_path).unwrap(); 35 | //! assert!(!file_path.exists()); 36 | //! assert!(hidden_file_path.exists()); 37 | //! 38 | //! hf::show(&hidden_file_path).unwrap(); 39 | //! assert!(file_path.exists()); 40 | //! assert!(!hidden_file_path.exists()); 41 | //! # } 42 | //! ``` 43 | //! 44 | //! ## On Windows 45 | //! 46 | //! ``` 47 | //! # #[cfg(windows)] 48 | //! # { 49 | //! use std::fs::File; 50 | //! 51 | //! let temp_dir = tempfile::tempdir().unwrap(); 52 | //! let file_path = temp_dir.path().join("foo.txt"); 53 | //! assert!(!file_path.exists()); 54 | //! 55 | //! File::create(&file_path).unwrap(); 56 | //! assert!(file_path.exists()); 57 | //! assert_eq!(hf::is_hidden(&file_path).unwrap(), false); 58 | //! 59 | //! hf::hide(&file_path).unwrap(); 60 | //! assert!(file_path.exists()); 61 | //! assert_eq!(hf::is_hidden(&file_path).unwrap(), true); 62 | //! 63 | //! hf::show(&file_path).unwrap(); 64 | //! assert!(file_path.exists()); 65 | //! assert_eq!(hf::is_hidden(file_path).unwrap(), false); 66 | //! # } 67 | //! ``` 68 | //! 69 | //! [hidden files and directories]: https://en.wikipedia.org/wiki/Hidden_file_and_hidden_directory 70 | 71 | #![doc(html_root_url = "https://docs.rs/hf/0.4.0/")] 72 | #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] 73 | // Lint levels of rustc. 74 | #![deny(missing_docs)] 75 | 76 | mod ops; 77 | mod platform; 78 | 79 | pub use crate::ops::{hide, is_hidden, show}; 80 | #[cfg(unix)] 81 | pub use crate::platform::unix; 82 | -------------------------------------------------------------------------------- /src/ops.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | use std::{io, path::Path}; 6 | 7 | use crate::platform::imp; 8 | 9 | /// Returns [`true`] if the path is a hidden file or directory. 10 | /// 11 | /// # Platform-specific behavior 12 | /// 13 | /// - On Unix, returns [`true`] if the file name starts with `.`. 14 | /// - On Windows, returns [`true`] if the file has the hidden file attribute. 15 | /// 16 | /// # Errors 17 | /// 18 | /// ## On Unix 19 | /// 20 | /// Returns [`Err`] if `path` terminates in `..`. 21 | /// 22 | /// ## On Windows 23 | /// 24 | /// Returns [`Err`] if metadata about a file could not be obtained. 25 | /// 26 | /// # Examples 27 | /// 28 | /// ## On Unix 29 | /// 30 | /// ``` 31 | /// # #[cfg(unix)] 32 | /// # { 33 | /// assert_eq!(hf::is_hidden(".foo.txt").unwrap(), true); 34 | /// assert_eq!(hf::is_hidden("foo.txt").unwrap(), false); 35 | /// 36 | /// assert!(hf::is_hidden(".foo.txt/..").is_err()); 37 | /// # } 38 | /// ``` 39 | /// 40 | /// ## On Windows 41 | /// 42 | /// ``` 43 | /// # #[cfg(windows)] 44 | /// # { 45 | /// # use std::{ 46 | /// # fs::{self, File}, 47 | /// # process::Command, 48 | /// # }; 49 | /// # 50 | /// let temp_dir = tempfile::tempdir().unwrap(); 51 | /// let file_path = temp_dir.path().join("foo.txt"); 52 | /// assert!(!file_path.exists()); 53 | /// 54 | /// File::create(&file_path).unwrap(); 55 | /// assert!(file_path.exists()); 56 | /// 57 | /// // Set the hidden file attribute. 58 | /// Command::new("attrib") 59 | /// .arg("+h") 60 | /// .arg(&file_path) 61 | /// .status() 62 | /// .unwrap(); 63 | /// assert_eq!(hf::is_hidden(&file_path).unwrap(), true); 64 | /// 65 | /// // Clear the hidden file attribute. 66 | /// Command::new("attrib") 67 | /// .arg("-h") 68 | /// .arg(&file_path) 69 | /// .status() 70 | /// .unwrap(); 71 | /// assert_eq!(hf::is_hidden(&file_path).unwrap(), false); 72 | /// 73 | /// fs::remove_file(&file_path).unwrap(); 74 | /// assert!(hf::is_hidden(file_path).is_err()); 75 | /// # } 76 | /// ``` 77 | #[inline] 78 | pub fn is_hidden(path: impl AsRef) -> io::Result { 79 | let inner = |path: &Path| -> io::Result { imp::is_hidden(path) }; 80 | inner(path.as_ref()) 81 | } 82 | 83 | /// Hides a file or directory. 84 | /// 85 | /// # Platform-specific behavior 86 | /// 87 | /// - On Unix, this function renames the file to start with `.`. 88 | /// - On Windows, this function sets the hidden file attribute to the file. 89 | /// 90 | /// # Errors 91 | /// 92 | /// ## On Unix 93 | /// 94 | /// Returns [`Err`] if any of the following are true: 95 | /// 96 | /// - The file name starts with `.`. 97 | /// - `path` terminates in `..`. 98 | /// - [`std::fs::rename`] returns an error. 99 | /// 100 | /// ## On Windows 101 | /// 102 | /// Returns [`Err`] if any of the following are true: 103 | /// 104 | /// - Metadata about a file could not be obtained. 105 | /// - The [`SetFileAttributesW`] function fails. 106 | /// 107 | /// # Examples 108 | /// 109 | /// ## On Unix 110 | /// 111 | /// ``` 112 | /// # #[cfg(unix)] 113 | /// # { 114 | /// # use std::fs::File; 115 | /// # 116 | /// let temp_dir = tempfile::tempdir().unwrap(); 117 | /// let temp_dir = temp_dir.path(); 118 | /// let file_path = temp_dir.join("foo.txt"); 119 | /// let hidden_file_path = hf::unix::hidden_file_name(&file_path).unwrap(); 120 | /// assert_eq!(hidden_file_path, temp_dir.join(".foo.txt")); 121 | /// assert!(!file_path.exists()); 122 | /// assert!(!hidden_file_path.exists()); 123 | /// 124 | /// File::create(&file_path).unwrap(); 125 | /// assert!(file_path.exists()); 126 | /// assert!(!hidden_file_path.exists()); 127 | /// 128 | /// hf::hide(&file_path).unwrap(); 129 | /// assert!(!file_path.exists()); 130 | /// assert!(hidden_file_path.exists()); 131 | /// 132 | /// assert!(hf::hide(".bar.txt").is_err()); 133 | /// assert!(hf::hide("bar.txt/..").is_err()); 134 | /// assert!(hf::hide("bar.txt").is_err()); 135 | /// # } 136 | /// ``` 137 | /// 138 | /// ## On Windows 139 | /// 140 | /// ``` 141 | /// # #[cfg(windows)] 142 | /// # { 143 | /// # use std::fs::File; 144 | /// # 145 | /// let temp_dir = tempfile::tempdir().unwrap(); 146 | /// let file_path = temp_dir.path().join("foo.txt"); 147 | /// assert!(!file_path.exists()); 148 | /// 149 | /// File::create(&file_path).unwrap(); 150 | /// assert!(file_path.exists()); 151 | /// assert!(!hf::is_hidden(&file_path).unwrap()); 152 | /// 153 | /// hf::hide(&file_path).unwrap(); 154 | /// assert!(file_path.exists()); 155 | /// assert!(hf::is_hidden(file_path).unwrap()); 156 | /// 157 | /// assert!(hf::hide("bar.txt").is_err()); 158 | /// # } 159 | /// ``` 160 | /// 161 | /// [`SetFileAttributesW`]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw 162 | #[inline] 163 | pub fn hide(path: impl AsRef) -> io::Result<()> { 164 | let inner = |path: &Path| -> io::Result<()> { imp::hide(path) }; 165 | inner(path.as_ref()) 166 | } 167 | 168 | /// Shows a hidden file or directory. 169 | /// 170 | /// # Platform-specific behavior 171 | /// 172 | /// - On Unix, this function renames the file to start with a character other 173 | /// than `.`. 174 | /// - On Windows, this function clears the hidden file attribute to the file. 175 | /// 176 | /// # Errors 177 | /// 178 | /// ## On Unix 179 | /// 180 | /// Returns [`Err`] if any of the following are true: 181 | /// 182 | /// - The file name does not start with `.`. 183 | /// - `path` terminates in `..`. 184 | /// - [`std::fs::rename`] returns an error. 185 | /// 186 | /// ## On Windows 187 | /// 188 | /// Returns [`Err`] if any of the following are true: 189 | /// 190 | /// - Metadata about a file could not be obtained. 191 | /// - The [`SetFileAttributesW`] function fails. 192 | /// 193 | /// # Examples 194 | /// 195 | /// ## On Unix 196 | /// 197 | /// ``` 198 | /// # #[cfg(unix)] 199 | /// # { 200 | /// # use std::fs::File; 201 | /// # 202 | /// let temp_dir = tempfile::tempdir().unwrap(); 203 | /// let temp_dir = temp_dir.path(); 204 | /// let hidden_file_path = temp_dir.join(".foo.txt"); 205 | /// let file_path = hf::unix::normal_file_name(&hidden_file_path).unwrap(); 206 | /// assert_eq!(file_path, temp_dir.join("foo.txt")); 207 | /// assert!(!hidden_file_path.exists()); 208 | /// assert!(!file_path.exists()); 209 | /// 210 | /// File::create(&hidden_file_path).unwrap(); 211 | /// assert!(hidden_file_path.exists()); 212 | /// assert!(!file_path.exists()); 213 | /// 214 | /// hf::show(&hidden_file_path).unwrap(); 215 | /// assert!(!hidden_file_path.exists()); 216 | /// assert!(file_path.exists()); 217 | /// 218 | /// assert!(hf::show("bar.txt").is_err()); 219 | /// assert!(hf::show(".bar.txt/..").is_err()); 220 | /// assert!(hf::show(".bar.txt").is_err()); 221 | /// # } 222 | /// ``` 223 | /// 224 | /// ## On Windows 225 | /// 226 | /// ``` 227 | /// # #[cfg(windows)] 228 | /// # { 229 | /// # use std::{fs::File, process::Command}; 230 | /// # 231 | /// let temp_dir = tempfile::tempdir().unwrap(); 232 | /// let file_path = temp_dir.path().join("foo.txt"); 233 | /// assert!(!file_path.exists()); 234 | /// 235 | /// File::create(&file_path).unwrap(); 236 | /// assert!(file_path.exists()); 237 | /// assert!(!hf::is_hidden(&file_path).unwrap()); 238 | /// 239 | /// // Set the hidden file attribute. 240 | /// Command::new("attrib") 241 | /// .arg("+h") 242 | /// .arg(&file_path) 243 | /// .status() 244 | /// .unwrap(); 245 | /// assert!(hf::is_hidden(&file_path).unwrap()); 246 | /// 247 | /// hf::show(&file_path).unwrap(); 248 | /// assert!(file_path.exists()); 249 | /// assert!(!hf::is_hidden(file_path).unwrap()); 250 | /// 251 | /// assert!(hf::show("bar.txt").is_err()); 252 | /// # } 253 | /// ``` 254 | /// 255 | /// [`SetFileAttributesW`]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw 256 | #[inline] 257 | pub fn show(path: impl AsRef) -> io::Result<()> { 258 | let inner = |path: &Path| -> io::Result<()> { imp::show(path) }; 259 | inner(path.as_ref()) 260 | } 261 | -------------------------------------------------------------------------------- /src/platform.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | #[cfg(unix)] 6 | pub mod unix; 7 | #[cfg(windows)] 8 | pub mod windows; 9 | 10 | #[cfg(unix)] 11 | pub use unix as imp; 12 | #[cfg(windows)] 13 | pub use windows as imp; 14 | -------------------------------------------------------------------------------- /src/platform/unix.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | //! Provides functionality for Unix platforms. 6 | 7 | use std::{ 8 | ffi::OsStr, 9 | fs, 10 | io::{self, Error, ErrorKind}, 11 | path::{Path, PathBuf}, 12 | }; 13 | 14 | #[inline] 15 | pub(crate) fn is_hidden(path: &Path) -> io::Result { 16 | let file_name = path 17 | .file_name() 18 | .ok_or_else(|| Error::from(ErrorKind::InvalidInput))?; 19 | let is_hidden = file_name.to_string_lossy().starts_with('.'); 20 | Ok(is_hidden) 21 | } 22 | 23 | #[inline] 24 | pub(crate) fn hide(path: &Path) -> io::Result<()> { 25 | let dest_path = hidden_file_name(path).ok_or_else(|| Error::from(ErrorKind::InvalidInput))?; 26 | fs::rename(path, dest_path) 27 | } 28 | 29 | #[inline] 30 | pub(crate) fn show(path: &Path) -> io::Result<()> { 31 | let dest_path = normal_file_name(path).ok_or_else(|| Error::from(ErrorKind::InvalidInput))?; 32 | fs::rename(path, dest_path) 33 | } 34 | 35 | /// Returns the path after making `path` invisible. 36 | /// 37 | /// Returns [`None`] if the file name starts with `.` or `path` terminates in 38 | /// `..`. 39 | /// 40 | /// # Examples 41 | /// 42 | /// ``` 43 | /// # use std::path::Path; 44 | /// # 45 | /// assert_eq!( 46 | /// hf::unix::hidden_file_name("foo.txt").as_deref(), 47 | /// Some(Path::new(".foo.txt")) 48 | /// ); 49 | /// assert_eq!( 50 | /// hf::unix::hidden_file_name("foo/bar.txt").as_deref(), 51 | /// Some(Path::new("foo/.bar.txt")) 52 | /// ); 53 | /// 54 | /// assert_eq!(hf::unix::hidden_file_name(".foo.txt"), None); 55 | /// assert_eq!(hf::unix::hidden_file_name("foo/.bar.txt"), None); 56 | /// assert_eq!(hf::unix::hidden_file_name("foo.txt/.."), None); 57 | /// ``` 58 | #[inline] 59 | pub fn hidden_file_name(path: impl AsRef) -> Option { 60 | let inner = |path: &Path| -> Option { 61 | let file_name = path 62 | .file_name() 63 | .map(OsStr::to_string_lossy) 64 | .filter(|n| !n.starts_with('.'))?; 65 | let dest_path = path.with_file_name(String::from('.') + &file_name); 66 | Some(dest_path) 67 | }; 68 | inner(path.as_ref()) 69 | } 70 | 71 | /// Returns the path after making `path` visible. 72 | /// 73 | /// Returns [`None`] if the file name does not start with `.` or `path` 74 | /// terminates in `..`. 75 | /// 76 | /// # Examples 77 | /// 78 | /// ``` 79 | /// # use std::path::Path; 80 | /// # 81 | /// assert_eq!( 82 | /// hf::unix::normal_file_name(".foo.txt").as_deref(), 83 | /// Some(Path::new("foo.txt")) 84 | /// ); 85 | /// assert_eq!( 86 | /// hf::unix::normal_file_name("foo/.bar.txt").as_deref(), 87 | /// Some(Path::new("foo/bar.txt")) 88 | /// ); 89 | /// 90 | /// assert_eq!(hf::unix::normal_file_name("foo.txt"), None); 91 | /// assert_eq!(hf::unix::normal_file_name("foo/bar.txt"), None); 92 | /// assert_eq!(hf::unix::normal_file_name(".foo.txt/.."), None); 93 | /// ``` 94 | #[inline] 95 | pub fn normal_file_name(path: impl AsRef) -> Option { 96 | let inner = |path: &Path| -> Option { 97 | let file_name = path 98 | .file_name() 99 | .map(OsStr::to_string_lossy) 100 | .filter(|n| n.starts_with('.'))?; 101 | let dest_path = path.with_file_name(file_name.trim_start_matches('.')); 102 | Some(dest_path) 103 | }; 104 | inner(path.as_ref()) 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use std::fs::File; 110 | 111 | use super::*; 112 | 113 | #[test] 114 | fn is_hidden() { 115 | assert!(super::is_hidden(Path::new(".foo.txt")).unwrap()); 116 | assert!(super::is_hidden(Path::new("..foo.txt")).unwrap()); 117 | assert!(super::is_hidden(Path::new(".ファイル.txt")).unwrap()); 118 | assert!(super::is_hidden(Path::new("foo/.bar.txt")).unwrap()); 119 | assert!(super::is_hidden(Path::new(".foo/.bar.txt")).unwrap()); 120 | } 121 | 122 | #[test] 123 | fn is_hidden_when_non_hidden_file() { 124 | assert!(!super::is_hidden(Path::new("foo.txt")).unwrap()); 125 | assert!(!super::is_hidden(Path::new("ファイル.txt")).unwrap()); 126 | assert!(!super::is_hidden(Path::new("foo/bar.txt")).unwrap()); 127 | assert!(!super::is_hidden(Path::new(".foo/bar.txt")).unwrap()); 128 | } 129 | 130 | #[test] 131 | fn is_hidden_with_invalid_path() { 132 | assert_eq!( 133 | super::is_hidden(Path::new(".foo.txt/..")) 134 | .unwrap_err() 135 | .kind(), 136 | ErrorKind::InvalidInput 137 | ); 138 | assert_eq!( 139 | super::is_hidden(Path::new("foo.txt/..")) 140 | .unwrap_err() 141 | .kind(), 142 | ErrorKind::InvalidInput 143 | ); 144 | assert_eq!( 145 | super::is_hidden(Path::new("/")).unwrap_err().kind(), 146 | ErrorKind::InvalidInput 147 | ); 148 | } 149 | 150 | #[test] 151 | fn hide() { 152 | { 153 | let temp_dir = tempfile::tempdir().unwrap(); 154 | let temp_dir = temp_dir.path(); 155 | let file_path = temp_dir.join("foo.txt"); 156 | let hidden_file_path = super::hidden_file_name(&file_path).unwrap(); 157 | assert_eq!(hidden_file_path, temp_dir.join(".foo.txt")); 158 | assert!(!file_path.exists()); 159 | assert!(!hidden_file_path.exists()); 160 | 161 | File::create(&file_path).unwrap(); 162 | assert!(file_path.exists()); 163 | assert!(!hidden_file_path.exists()); 164 | 165 | super::hide(&file_path).unwrap(); 166 | assert!(!file_path.exists()); 167 | assert!(hidden_file_path.exists()); 168 | } 169 | { 170 | let temp_dir = tempfile::tempdir().unwrap(); 171 | let temp_dir = temp_dir.path(); 172 | let file_path = temp_dir.join("ファイル.txt"); 173 | let hidden_file_path = super::hidden_file_name(&file_path).unwrap(); 174 | assert_eq!(hidden_file_path, temp_dir.join(".ファイル.txt")); 175 | assert!(!file_path.exists()); 176 | assert!(!hidden_file_path.exists()); 177 | 178 | File::create(&file_path).unwrap(); 179 | assert!(file_path.exists()); 180 | assert!(!hidden_file_path.exists()); 181 | 182 | super::hide(&file_path).unwrap(); 183 | assert!(!file_path.exists()); 184 | assert!(hidden_file_path.exists()); 185 | } 186 | { 187 | let temp_dir = tempfile::tempdir().unwrap(); 188 | let temp_dir = temp_dir.path(); 189 | let parent_dir = temp_dir.join("foo"); 190 | let file_path = parent_dir.join("bar.txt"); 191 | let hidden_file_path = super::hidden_file_name(&file_path).unwrap(); 192 | assert_eq!(hidden_file_path, temp_dir.join("foo/.bar.txt")); 193 | fs::create_dir(parent_dir).unwrap(); 194 | assert!(!file_path.exists()); 195 | assert!(!hidden_file_path.exists()); 196 | 197 | File::create(&file_path).unwrap(); 198 | assert!(file_path.exists()); 199 | assert!(!hidden_file_path.exists()); 200 | 201 | super::hide(&file_path).unwrap(); 202 | assert!(!file_path.exists()); 203 | assert!(hidden_file_path.exists()); 204 | } 205 | { 206 | let temp_dir = tempfile::tempdir().unwrap(); 207 | let temp_dir = temp_dir.path(); 208 | let parent_dir = temp_dir.join(".foo"); 209 | let file_path = parent_dir.join("bar.txt"); 210 | let hidden_file_path = super::hidden_file_name(&file_path).unwrap(); 211 | assert_eq!(hidden_file_path, temp_dir.join(".foo/.bar.txt")); 212 | fs::create_dir(parent_dir).unwrap(); 213 | assert!(!file_path.exists()); 214 | assert!(!hidden_file_path.exists()); 215 | 216 | File::create(&file_path).unwrap(); 217 | assert!(file_path.exists()); 218 | assert!(!hidden_file_path.exists()); 219 | 220 | super::hide(&file_path).unwrap(); 221 | assert!(!file_path.exists()); 222 | assert!(hidden_file_path.exists()); 223 | } 224 | } 225 | 226 | #[test] 227 | fn hide_when_hidden_file() { 228 | { 229 | let hidden_file_path = Path::new(".foo.txt"); 230 | assert_eq!( 231 | super::hide(hidden_file_path).unwrap_err().kind(), 232 | ErrorKind::InvalidInput 233 | ); 234 | } 235 | { 236 | let hidden_file_path = Path::new("..foo.txt"); 237 | assert_eq!( 238 | super::hide(hidden_file_path).unwrap_err().kind(), 239 | ErrorKind::InvalidInput 240 | ); 241 | } 242 | { 243 | let hidden_file_path = Path::new("foo/.bar.txt"); 244 | assert_eq!( 245 | super::hide(hidden_file_path).unwrap_err().kind(), 246 | ErrorKind::InvalidInput 247 | ); 248 | } 249 | { 250 | let hidden_file_path = Path::new(".foo/.bar.txt"); 251 | assert_eq!( 252 | super::hide(hidden_file_path).unwrap_err().kind(), 253 | ErrorKind::InvalidInput 254 | ); 255 | } 256 | } 257 | 258 | #[test] 259 | fn hide_with_invalid_path() { 260 | let file_path = Path::new("foo.txt/.."); 261 | assert_eq!( 262 | super::hide(file_path).unwrap_err().kind(), 263 | ErrorKind::InvalidInput 264 | ); 265 | let file_path = Path::new("/"); 266 | assert_eq!( 267 | super::hide(file_path).unwrap_err().kind(), 268 | ErrorKind::InvalidInput 269 | ); 270 | } 271 | 272 | #[test] 273 | fn hide_when_file_does_not_exist() { 274 | let file_path = Path::new("foo.txt"); 275 | assert_eq!( 276 | super::hide(file_path).unwrap_err().kind(), 277 | ErrorKind::NotFound 278 | ); 279 | } 280 | 281 | #[test] 282 | fn show() { 283 | { 284 | let temp_dir = tempfile::tempdir().unwrap(); 285 | let temp_dir = temp_dir.path(); 286 | let hidden_file_path = temp_dir.join(".foo.txt"); 287 | let file_path = super::normal_file_name(&hidden_file_path).unwrap(); 288 | assert_eq!(file_path, temp_dir.join("foo.txt")); 289 | assert!(!hidden_file_path.exists()); 290 | assert!(!file_path.exists()); 291 | 292 | File::create(&hidden_file_path).unwrap(); 293 | assert!(hidden_file_path.exists()); 294 | assert!(!file_path.exists()); 295 | 296 | super::show(&hidden_file_path).unwrap(); 297 | assert!(!hidden_file_path.exists()); 298 | assert!(file_path.exists()); 299 | } 300 | { 301 | let temp_dir = tempfile::tempdir().unwrap(); 302 | let temp_dir = temp_dir.path(); 303 | let hidden_file_path = temp_dir.join("..foo.txt"); 304 | let file_path = super::normal_file_name(&hidden_file_path).unwrap(); 305 | assert_eq!(file_path, temp_dir.join("foo.txt")); 306 | assert!(!hidden_file_path.exists()); 307 | assert!(!file_path.exists()); 308 | 309 | File::create(&hidden_file_path).unwrap(); 310 | assert!(hidden_file_path.exists()); 311 | assert!(!file_path.exists()); 312 | 313 | super::show(&hidden_file_path).unwrap(); 314 | assert!(!hidden_file_path.exists()); 315 | assert!(file_path.exists()); 316 | } 317 | { 318 | let temp_dir = tempfile::tempdir().unwrap(); 319 | let temp_dir = temp_dir.path(); 320 | let hidden_file_path = temp_dir.join(".ファイル.txt"); 321 | let file_path = super::normal_file_name(&hidden_file_path).unwrap(); 322 | assert_eq!(file_path, temp_dir.join("ファイル.txt")); 323 | assert!(!hidden_file_path.exists()); 324 | assert!(!file_path.exists()); 325 | 326 | File::create(&hidden_file_path).unwrap(); 327 | assert!(hidden_file_path.exists()); 328 | assert!(!file_path.exists()); 329 | 330 | super::show(&hidden_file_path).unwrap(); 331 | assert!(!hidden_file_path.exists()); 332 | assert!(file_path.exists()); 333 | } 334 | { 335 | let temp_dir = tempfile::tempdir().unwrap(); 336 | let temp_dir = temp_dir.path(); 337 | let parent_dir = temp_dir.join("foo"); 338 | let hidden_file_path = parent_dir.join(".bar.txt"); 339 | let file_path = super::normal_file_name(&hidden_file_path).unwrap(); 340 | assert_eq!(file_path, temp_dir.join("foo/bar.txt")); 341 | fs::create_dir(parent_dir).unwrap(); 342 | assert!(!hidden_file_path.exists()); 343 | assert!(!file_path.exists()); 344 | 345 | File::create(&hidden_file_path).unwrap(); 346 | assert!(hidden_file_path.exists()); 347 | assert!(!file_path.exists()); 348 | 349 | super::show(&hidden_file_path).unwrap(); 350 | assert!(!hidden_file_path.exists()); 351 | assert!(file_path.exists()); 352 | } 353 | { 354 | let temp_dir = tempfile::tempdir().unwrap(); 355 | let temp_dir = temp_dir.path(); 356 | let parent_dir = temp_dir.join(".foo"); 357 | let hidden_file_path = parent_dir.join(".bar.txt"); 358 | let file_path = super::normal_file_name(&hidden_file_path).unwrap(); 359 | assert_eq!(file_path, temp_dir.join(".foo/bar.txt")); 360 | fs::create_dir(parent_dir).unwrap(); 361 | assert!(!hidden_file_path.exists()); 362 | assert!(!file_path.exists()); 363 | 364 | File::create(&hidden_file_path).unwrap(); 365 | assert!(hidden_file_path.exists()); 366 | assert!(!file_path.exists()); 367 | 368 | super::show(&hidden_file_path).unwrap(); 369 | assert!(!hidden_file_path.exists()); 370 | assert!(file_path.exists()); 371 | } 372 | } 373 | 374 | #[test] 375 | fn show_when_non_hidden_file() { 376 | { 377 | let file_path = Path::new("foo.txt"); 378 | assert_eq!( 379 | super::show(file_path).unwrap_err().kind(), 380 | ErrorKind::InvalidInput 381 | ); 382 | } 383 | { 384 | let file_path = Path::new("foo/bar.txt"); 385 | assert_eq!( 386 | super::show(file_path).unwrap_err().kind(), 387 | ErrorKind::InvalidInput 388 | ); 389 | } 390 | { 391 | let file_path = Path::new(".foo/bar.txt"); 392 | assert_eq!( 393 | super::show(file_path).unwrap_err().kind(), 394 | ErrorKind::InvalidInput 395 | ); 396 | } 397 | } 398 | 399 | #[test] 400 | fn show_with_invalid_path() { 401 | let hidden_file_path = Path::new(".foo.txt/.."); 402 | assert_eq!( 403 | super::show(hidden_file_path).unwrap_err().kind(), 404 | ErrorKind::InvalidInput 405 | ); 406 | let hidden_file_path = Path::new("/"); 407 | assert_eq!( 408 | super::show(hidden_file_path).unwrap_err().kind(), 409 | ErrorKind::InvalidInput 410 | ); 411 | } 412 | 413 | #[test] 414 | fn show_when_file_does_not_exist() { 415 | let hidden_file_path = Path::new(".foo.txt"); 416 | assert_eq!( 417 | super::show(hidden_file_path).unwrap_err().kind(), 418 | ErrorKind::NotFound 419 | ); 420 | } 421 | 422 | #[test] 423 | fn hidden_file_name() { 424 | assert_eq!( 425 | super::hidden_file_name("foo.txt").unwrap(), 426 | Path::new(".foo.txt") 427 | ); 428 | assert_eq!( 429 | super::hidden_file_name("ファイル.txt").unwrap(), 430 | Path::new(".ファイル.txt") 431 | ); 432 | assert_eq!( 433 | super::hidden_file_name("foo/bar.txt").unwrap(), 434 | Path::new("foo/.bar.txt") 435 | ); 436 | assert_eq!( 437 | super::hidden_file_name(".foo/bar.txt").unwrap(), 438 | Path::new(".foo/.bar.txt") 439 | ); 440 | } 441 | 442 | #[test] 443 | fn hidden_file_name_when_hidden_file() { 444 | assert!(super::hidden_file_name(".foo.txt").is_none()); 445 | assert!(super::hidden_file_name("..foo.txt").is_none()); 446 | assert!(super::hidden_file_name("foo/.bar.txt").is_none()); 447 | assert!(super::hidden_file_name(".foo/.bar.txt").is_none()); 448 | } 449 | 450 | #[test] 451 | fn hidden_file_name_with_invalid_path() { 452 | assert!(super::hidden_file_name("foo.txt/..").is_none()); 453 | assert!(super::hidden_file_name("/").is_none()); 454 | } 455 | 456 | #[test] 457 | fn normal_file_name() { 458 | assert_eq!( 459 | super::normal_file_name(".foo.txt").unwrap(), 460 | Path::new("foo.txt") 461 | ); 462 | assert_eq!( 463 | super::normal_file_name("..foo.txt").unwrap(), 464 | Path::new("foo.txt") 465 | ); 466 | assert_eq!( 467 | super::normal_file_name(".ファイル.txt").unwrap(), 468 | Path::new("ファイル.txt") 469 | ); 470 | assert_eq!( 471 | super::normal_file_name("foo/.bar.txt").unwrap(), 472 | Path::new("foo/bar.txt") 473 | ); 474 | assert_eq!( 475 | super::normal_file_name(".foo/.bar.txt").unwrap(), 476 | Path::new(".foo/bar.txt") 477 | ); 478 | } 479 | 480 | #[test] 481 | fn normal_file_name_when_non_hidden_file() { 482 | assert!(super::normal_file_name("foo.txt").is_none()); 483 | assert!(super::normal_file_name("foo/bar.txt").is_none()); 484 | assert!(super::normal_file_name(".foo/bar.txt").is_none()); 485 | } 486 | 487 | #[test] 488 | fn normal_file_name_with_invalid_path() { 489 | assert!(super::normal_file_name(".foo.txt/..").is_none()); 490 | assert!(super::normal_file_name("/").is_none()); 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /src/platform/windows.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | //! Provides functionality for Windows. 6 | 7 | use std::{ 8 | fs, 9 | io::{self, Error}, 10 | os::windows::fs::MetadataExt, 11 | path::Path, 12 | }; 13 | 14 | use windows::{Win32::Storage::FileSystem, core::HSTRING}; 15 | 16 | fn get_file_attributes(path: &Path) -> io::Result { 17 | let attributes = fs::metadata(path)?.file_attributes(); 18 | let attributes = FileSystem::FILE_FLAGS_AND_ATTRIBUTES(attributes); 19 | Ok(attributes) 20 | } 21 | 22 | #[inline] 23 | pub fn is_hidden(path: &Path) -> io::Result { 24 | let attributes = get_file_attributes(path)?; 25 | let is_hidden = attributes.contains(FileSystem::FILE_ATTRIBUTE_HIDDEN); 26 | Ok(is_hidden) 27 | } 28 | 29 | #[inline] 30 | pub fn hide(path: &Path) -> io::Result<()> { 31 | let attributes = get_file_attributes(path)? | FileSystem::FILE_ATTRIBUTE_HIDDEN; 32 | let path = HSTRING::from(path); 33 | unsafe { FileSystem::SetFileAttributesW(&path, attributes) }.map_err(Error::from) 34 | } 35 | 36 | #[inline] 37 | pub fn show(path: &Path) -> io::Result<()> { 38 | let attributes = get_file_attributes(path)? & !FileSystem::FILE_ATTRIBUTE_HIDDEN; 39 | let path = HSTRING::from(path); 40 | unsafe { FileSystem::SetFileAttributesW(&path, attributes) }.map_err(Error::from) 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use std::{fs::File, io::ErrorKind, process::Command}; 46 | 47 | use super::*; 48 | 49 | #[test] 50 | fn is_hidden() { 51 | { 52 | let temp_dir = tempfile::tempdir().unwrap(); 53 | let file_path = temp_dir.path().join("foo.txt"); 54 | assert!(!file_path.exists()); 55 | 56 | File::create(&file_path).unwrap(); 57 | assert!(file_path.exists()); 58 | 59 | Command::new("attrib") 60 | .arg("+h") 61 | .arg(&file_path) 62 | .status() 63 | .unwrap(); 64 | assert!(super::is_hidden(&file_path).unwrap()); 65 | } 66 | { 67 | let temp_dir = tempfile::tempdir().unwrap(); 68 | let file_path = temp_dir.path().join("ファイル.txt"); 69 | assert!(!file_path.exists()); 70 | 71 | File::create(&file_path).unwrap(); 72 | assert!(file_path.exists()); 73 | 74 | Command::new("attrib") 75 | .arg("+h") 76 | .arg(&file_path) 77 | .status() 78 | .unwrap(); 79 | assert!(super::is_hidden(&file_path).unwrap()); 80 | } 81 | { 82 | let temp_dir = tempfile::tempdir().unwrap(); 83 | let file_path = temp_dir.path().join("foo/bar.txt"); 84 | fs::create_dir(file_path.parent().unwrap()).unwrap(); 85 | assert!(!file_path.exists()); 86 | 87 | File::create(&file_path).unwrap(); 88 | assert!(file_path.exists()); 89 | 90 | Command::new("attrib") 91 | .arg("+h") 92 | .arg(&file_path) 93 | .status() 94 | .unwrap(); 95 | assert!(super::is_hidden(&file_path).unwrap()); 96 | } 97 | } 98 | 99 | #[test] 100 | fn is_hidden_when_non_hidden_file() { 101 | { 102 | let temp_dir = tempfile::tempdir().unwrap(); 103 | let file_path = temp_dir.path().join("foo.txt"); 104 | assert!(!file_path.exists()); 105 | 106 | File::create(&file_path).unwrap(); 107 | assert!(file_path.exists()); 108 | 109 | assert!(!super::is_hidden(&file_path).unwrap()); 110 | } 111 | { 112 | let temp_dir = tempfile::tempdir().unwrap(); 113 | let file_path = temp_dir.path().join("ファイル.txt"); 114 | assert!(!file_path.exists()); 115 | 116 | File::create(&file_path).unwrap(); 117 | assert!(file_path.exists()); 118 | 119 | assert!(!super::is_hidden(&file_path).unwrap()); 120 | } 121 | { 122 | let temp_dir = tempfile::tempdir().unwrap(); 123 | let file_path = temp_dir.path().join("foo/bar.txt"); 124 | fs::create_dir(file_path.parent().unwrap()).unwrap(); 125 | assert!(!file_path.exists()); 126 | 127 | File::create(&file_path).unwrap(); 128 | assert!(file_path.exists()); 129 | 130 | assert!(!super::is_hidden(&file_path).unwrap()); 131 | } 132 | { 133 | let temp_dir = tempfile::tempdir().unwrap(); 134 | let file_path = temp_dir.path().join("foo/bar.txt"); 135 | fs::create_dir(file_path.parent().unwrap()).unwrap(); 136 | assert!(!file_path.exists()); 137 | 138 | File::create(&file_path).unwrap(); 139 | assert!(file_path.exists()); 140 | 141 | Command::new("attrib") 142 | .arg("+h") 143 | .arg(file_path.parent().unwrap()) 144 | .status() 145 | .unwrap(); 146 | assert!(!super::is_hidden(&file_path).unwrap()); 147 | } 148 | } 149 | 150 | #[test] 151 | fn is_hidden_when_file_does_not_exist() { 152 | { 153 | let file_path = Path::new("foo.txt"); 154 | assert_eq!( 155 | super::is_hidden(&file_path).unwrap_err().kind(), 156 | ErrorKind::NotFound 157 | ); 158 | } 159 | { 160 | let file_path = Path::new("foo/bar.txt"); 161 | assert_eq!( 162 | super::is_hidden(&file_path).unwrap_err().kind(), 163 | ErrorKind::NotFound 164 | ); 165 | } 166 | } 167 | 168 | #[test] 169 | fn hide() { 170 | { 171 | let temp_dir = tempfile::tempdir().unwrap(); 172 | let file_path = temp_dir.path().join("foo.txt"); 173 | assert!(!file_path.exists()); 174 | 175 | File::create(&file_path).unwrap(); 176 | assert!(file_path.exists()); 177 | assert!(!super::is_hidden(&file_path).unwrap()); 178 | 179 | super::hide(&file_path).unwrap(); 180 | assert!(file_path.exists()); 181 | assert!(super::is_hidden(&file_path).unwrap()); 182 | } 183 | { 184 | let temp_dir = tempfile::tempdir().unwrap(); 185 | let file_path = temp_dir.path().join("ファイル.txt"); 186 | assert!(!file_path.exists()); 187 | 188 | File::create(&file_path).unwrap(); 189 | assert!(file_path.exists()); 190 | assert!(!super::is_hidden(&file_path).unwrap()); 191 | 192 | super::hide(&file_path).unwrap(); 193 | assert!(file_path.exists()); 194 | assert!(super::is_hidden(&file_path).unwrap()); 195 | } 196 | { 197 | let temp_dir = tempfile::tempdir().unwrap(); 198 | let file_path = temp_dir.path().join("foo/bar.txt"); 199 | fs::create_dir(file_path.parent().unwrap()).unwrap(); 200 | assert!(!file_path.exists()); 201 | 202 | File::create(&file_path).unwrap(); 203 | assert!(file_path.exists()); 204 | assert!(!super::is_hidden(&file_path).unwrap()); 205 | 206 | super::hide(&file_path).unwrap(); 207 | assert!(file_path.exists()); 208 | assert!(super::is_hidden(&file_path).unwrap()); 209 | } 210 | } 211 | 212 | #[test] 213 | fn hide_when_file_does_not_exist() { 214 | let file_path = Path::new("foo.txt"); 215 | assert_eq!( 216 | super::hide(&file_path).unwrap_err().kind(), 217 | ErrorKind::NotFound 218 | ); 219 | } 220 | 221 | #[test] 222 | fn show() { 223 | { 224 | let temp_dir = tempfile::tempdir().unwrap(); 225 | let file_path = temp_dir.path().join("foo.txt"); 226 | assert!(!file_path.exists()); 227 | 228 | File::create(&file_path).unwrap(); 229 | assert!(file_path.exists()); 230 | assert!(!super::is_hidden(&file_path).unwrap()); 231 | 232 | Command::new("attrib") 233 | .arg("+h") 234 | .arg(&file_path) 235 | .status() 236 | .unwrap(); 237 | assert!(super::is_hidden(&file_path).unwrap()); 238 | 239 | super::show(&file_path).unwrap(); 240 | assert!(file_path.exists()); 241 | assert!(!super::is_hidden(&file_path).unwrap()); 242 | } 243 | { 244 | let temp_dir = tempfile::tempdir().unwrap(); 245 | let file_path = temp_dir.path().join("ファイル.txt"); 246 | assert!(!file_path.exists()); 247 | 248 | File::create(&file_path).unwrap(); 249 | assert!(file_path.exists()); 250 | assert!(!super::is_hidden(&file_path).unwrap()); 251 | 252 | Command::new("attrib") 253 | .arg("+h") 254 | .arg(&file_path) 255 | .status() 256 | .unwrap(); 257 | assert!(super::is_hidden(&file_path).unwrap()); 258 | 259 | super::show(&file_path).unwrap(); 260 | assert!(file_path.exists()); 261 | assert!(!super::is_hidden(&file_path).unwrap()); 262 | } 263 | { 264 | let temp_dir = tempfile::tempdir().unwrap(); 265 | let file_path = temp_dir.path().join("foo/bar.txt"); 266 | fs::create_dir(file_path.parent().unwrap()).unwrap(); 267 | assert!(!file_path.exists()); 268 | 269 | File::create(&file_path).unwrap(); 270 | assert!(file_path.exists()); 271 | assert!(!super::is_hidden(&file_path).unwrap()); 272 | 273 | Command::new("attrib") 274 | .arg("+h") 275 | .arg(&file_path) 276 | .status() 277 | .unwrap(); 278 | assert!(super::is_hidden(&file_path).unwrap()); 279 | 280 | super::show(&file_path).unwrap(); 281 | assert!(file_path.exists()); 282 | assert!(!super::is_hidden(&file_path).unwrap()); 283 | } 284 | } 285 | 286 | #[test] 287 | fn show_when_file_does_not_exist() { 288 | let file_path = Path::new("foo.txt"); 289 | assert_eq!( 290 | super::show(&file_path).unwrap_err().kind(), 291 | ErrorKind::NotFound 292 | ); 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /tests/completion.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | mod utils; 6 | 7 | use predicates::prelude::predicate; 8 | 9 | #[test] 10 | fn completion() { 11 | utils::command::command() 12 | .arg("completion") 13 | .arg("bash") 14 | .assert() 15 | .success() 16 | .stdout(predicate::ne("")); 17 | utils::command::command() 18 | .arg("completion") 19 | .arg("elvish") 20 | .assert() 21 | .success() 22 | .stdout(predicate::ne("")); 23 | utils::command::command() 24 | .arg("completion") 25 | .arg("fish") 26 | .assert() 27 | .success() 28 | .stdout(predicate::ne("")); 29 | utils::command::command() 30 | .arg("completion") 31 | .arg("nushell") 32 | .assert() 33 | .success() 34 | .stdout(predicate::ne("")); 35 | utils::command::command() 36 | .arg("completion") 37 | .arg("powershell") 38 | .assert() 39 | .success() 40 | .stdout(predicate::ne("")); 41 | utils::command::command() 42 | .arg("completion") 43 | .arg("zsh") 44 | .assert() 45 | .success() 46 | .stdout(predicate::ne("")); 47 | } 48 | 49 | #[test] 50 | fn completion_with_invalid_shell() { 51 | utils::command::command() 52 | .arg("completion") 53 | .arg("a") 54 | .assert() 55 | .failure() 56 | .code(2) 57 | .stderr(predicate::str::contains("invalid value 'a' for ''")); 58 | } 59 | -------------------------------------------------------------------------------- /tests/hide.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | mod utils; 6 | 7 | use std::fs::File; 8 | 9 | use predicates::prelude::predicate; 10 | 11 | #[test] 12 | fn basic_hide() { 13 | let temp_dir = tempfile::tempdir().unwrap(); 14 | let temp_dir = temp_dir.path(); 15 | let file_path = temp_dir.join("foo.txt"); 16 | 17 | File::create(&file_path).unwrap(); 18 | 19 | utils::command::command() 20 | .arg("hide") 21 | .arg("-n") 22 | .arg(&file_path) 23 | .assert() 24 | .success() 25 | .stdout(predicate::str::contains(format!("{}", file_path.display()))); 26 | 27 | utils::command::command() 28 | .arg("hide") 29 | .arg("-f") 30 | .arg(&file_path) 31 | .assert() 32 | .success() 33 | .stdout(predicate::str::contains(format!( 34 | "{} has been hidden", 35 | file_path.display() 36 | ))); 37 | 38 | #[cfg(unix)] 39 | assert!(temp_dir.join(".foo.txt").exists()); 40 | #[cfg(windows)] 41 | assert!(hf::is_hidden(file_path).unwrap()); 42 | } 43 | 44 | #[test] 45 | fn hide_with_multiple_files() { 46 | let temp_dir = tempfile::tempdir().unwrap(); 47 | let temp_dir = temp_dir.path(); 48 | let file_path = (temp_dir.join("foo.txt"), temp_dir.join("bar.txt")); 49 | 50 | File::create(&file_path.0).unwrap(); 51 | File::create(&file_path.1).unwrap(); 52 | 53 | utils::command::command() 54 | .arg("hide") 55 | .arg("-n") 56 | .arg(&file_path.0) 57 | .arg(&file_path.1) 58 | .assert() 59 | .success() 60 | .stdout(predicate::str::contains(format!( 61 | "{}", 62 | file_path.0.display() 63 | ))) 64 | .stdout(predicate::str::contains(format!( 65 | "{}", 66 | file_path.1.display() 67 | ))); 68 | 69 | utils::command::command() 70 | .arg("hide") 71 | .arg("-f") 72 | .arg(&file_path.0) 73 | .arg(&file_path.1) 74 | .assert() 75 | .success() 76 | .stdout(predicate::str::contains(format!( 77 | "{} has been hidden", 78 | file_path.0.display() 79 | ))) 80 | .stdout(predicate::str::contains(format!( 81 | "{} has been hidden", 82 | file_path.1.display() 83 | ))); 84 | } 85 | 86 | #[test] 87 | fn hide_when_hidden_file() { 88 | let temp_dir = tempfile::tempdir().unwrap(); 89 | let temp_dir = temp_dir.path(); 90 | let file_path = temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }); 91 | 92 | File::create(&file_path).unwrap(); 93 | #[cfg(windows)] 94 | std::process::Command::new("attrib") 95 | .arg("+h") 96 | .arg(&file_path) 97 | .status() 98 | .unwrap(); 99 | 100 | utils::command::command() 101 | .arg("hide") 102 | .arg("-n") 103 | .arg(&file_path) 104 | .assert() 105 | .success() 106 | .stdout(predicate::str::contains(format!( 107 | "{} is ignored", 108 | file_path.display() 109 | ))); 110 | 111 | utils::command::command() 112 | .arg("hide") 113 | .arg("-f") 114 | .arg(&file_path) 115 | .assert() 116 | .success() 117 | .stdout(predicate::str::contains(format!( 118 | "{} is already hidden", 119 | file_path.display() 120 | ))); 121 | 122 | #[cfg(unix)] 123 | assert!(temp_dir.join(".foo.txt").exists()); 124 | #[cfg(windows)] 125 | assert!(hf::is_hidden(file_path).unwrap()); 126 | } 127 | 128 | #[test] 129 | fn hide_when_file_does_not_exist() { 130 | { 131 | let command = utils::command::command() 132 | .arg("hide") 133 | .arg("-n") 134 | .arg("non_existent.txt") 135 | .assert() 136 | .failure() 137 | .code(66); 138 | if cfg!(windows) { 139 | command.stderr(predicate::str::contains( 140 | "could not read information from non_existent.txt", 141 | )); 142 | } else { 143 | command.stderr(predicate::str::contains("non_existent.txt does not exist")); 144 | } 145 | } 146 | 147 | { 148 | let command = utils::command::command() 149 | .arg("hide") 150 | .arg("-f") 151 | .arg("non_existent.txt") 152 | .assert() 153 | .failure() 154 | .code(66); 155 | if cfg!(windows) { 156 | command.stderr(predicate::str::contains( 157 | "could not read information from non_existent.txt", 158 | )); 159 | } else { 160 | command.stderr(predicate::str::contains("non_existent.txt does not exist")); 161 | } 162 | } 163 | } 164 | 165 | #[test] 166 | fn hide_with_force_and_dry_run() { 167 | let temp_dir = tempfile::tempdir().unwrap(); 168 | let temp_dir = temp_dir.path(); 169 | let file_path = temp_dir.join("foo.txt"); 170 | 171 | File::create(&file_path).unwrap(); 172 | 173 | utils::command::command() 174 | .arg("hide") 175 | .arg("-f") 176 | .arg("-n") 177 | .arg(&file_path) 178 | .assert() 179 | .failure() 180 | .code(2) 181 | .stderr(predicate::str::contains( 182 | "the argument '--force' cannot be used with '--dry-run'", 183 | )); 184 | } 185 | 186 | #[test] 187 | fn hide_with_off_log_level() { 188 | let temp_dir = tempfile::tempdir().unwrap(); 189 | let temp_dir = temp_dir.path(); 190 | let file_path = temp_dir.join("foo.txt"); 191 | 192 | File::create(&file_path).unwrap(); 193 | 194 | utils::command::command() 195 | .arg("hide") 196 | .arg("--log-level") 197 | .arg("OFF") 198 | .arg("-f") 199 | .arg(&file_path) 200 | .assert() 201 | .success() 202 | .stdout(predicate::str::is_empty()); 203 | } 204 | 205 | #[test] 206 | fn hide_with_error_log_level() { 207 | let temp_dir = tempfile::tempdir().unwrap(); 208 | let temp_dir = temp_dir.path(); 209 | let file_path = temp_dir.join("foo.txt"); 210 | 211 | File::create(&file_path).unwrap(); 212 | 213 | utils::command::command() 214 | .arg("hide") 215 | .arg("--log-level") 216 | .arg("ERROR") 217 | .arg("-f") 218 | .arg(&file_path) 219 | .assert() 220 | .success() 221 | .stdout(predicate::str::is_empty()); 222 | } 223 | 224 | #[test] 225 | fn hide_with_warn_log_level() { 226 | let temp_dir = tempfile::tempdir().unwrap(); 227 | let temp_dir = temp_dir.path(); 228 | let file_path = temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }); 229 | 230 | File::create(&file_path).unwrap(); 231 | #[cfg(windows)] 232 | std::process::Command::new("attrib") 233 | .arg("+h") 234 | .arg(&file_path) 235 | .status() 236 | .unwrap(); 237 | 238 | utils::command::command() 239 | .arg("hide") 240 | .arg("--log-level") 241 | .arg("WARN") 242 | .arg("-f") 243 | .arg(&file_path) 244 | .assert() 245 | .success() 246 | .stdout(predicate::str::contains(format!( 247 | "{} is already hidden", 248 | file_path.display() 249 | ))); 250 | } 251 | 252 | #[test] 253 | fn hide_with_info_log_level() { 254 | let temp_dir = tempfile::tempdir().unwrap(); 255 | let temp_dir = temp_dir.path(); 256 | let file_path = ( 257 | temp_dir.join("foo.txt"), 258 | temp_dir.join(if cfg!(unix) { ".bar.txt" } else { "bar.txt" }), 259 | ); 260 | 261 | File::create(&file_path.0).unwrap(); 262 | File::create(&file_path.1).unwrap(); 263 | #[cfg(windows)] 264 | std::process::Command::new("attrib") 265 | .arg("+h") 266 | .arg(&file_path.1) 267 | .status() 268 | .unwrap(); 269 | 270 | utils::command::command() 271 | .arg("hide") 272 | .arg("--log-level") 273 | .arg("INFO") 274 | .arg("-f") 275 | .arg(&file_path.0) 276 | .arg(&file_path.1) 277 | .assert() 278 | .success() 279 | .stdout(predicate::str::contains(format!( 280 | "{} has been hidden", 281 | file_path.0.display() 282 | ))) 283 | .stdout(predicate::str::contains(format!( 284 | "{} is already hidden", 285 | file_path.1.display() 286 | ))); 287 | } 288 | 289 | #[test] 290 | fn hide_with_debug_log_level() { 291 | let temp_dir = tempfile::tempdir().unwrap(); 292 | let temp_dir = temp_dir.path(); 293 | let file_path = ( 294 | temp_dir.join("foo.txt"), 295 | temp_dir.join(if cfg!(unix) { ".bar.txt" } else { "bar.txt" }), 296 | ); 297 | 298 | File::create(&file_path.0).unwrap(); 299 | File::create(&file_path.1).unwrap(); 300 | #[cfg(windows)] 301 | std::process::Command::new("attrib") 302 | .arg("+h") 303 | .arg(&file_path.1) 304 | .status() 305 | .unwrap(); 306 | 307 | utils::command::command() 308 | .arg("hide") 309 | .arg("--log-level") 310 | .arg("DEBUG") 311 | .arg("-f") 312 | .arg(&file_path.0) 313 | .arg(&file_path.1) 314 | .assert() 315 | .success() 316 | .stdout(predicate::str::contains(format!( 317 | "{} has been hidden", 318 | file_path.0.display() 319 | ))) 320 | .stdout(predicate::str::contains(format!( 321 | "{} is already hidden", 322 | file_path.1.display() 323 | ))); 324 | } 325 | 326 | #[test] 327 | fn hide_with_trace_log_level() { 328 | let temp_dir = tempfile::tempdir().unwrap(); 329 | let temp_dir = temp_dir.path(); 330 | let file_path = ( 331 | temp_dir.join("foo.txt"), 332 | temp_dir.join(if cfg!(unix) { ".bar.txt" } else { "bar.txt" }), 333 | ); 334 | 335 | File::create(&file_path.0).unwrap(); 336 | File::create(&file_path.1).unwrap(); 337 | #[cfg(windows)] 338 | std::process::Command::new("attrib") 339 | .arg("+h") 340 | .arg(&file_path.1) 341 | .status() 342 | .unwrap(); 343 | 344 | utils::command::command() 345 | .arg("hide") 346 | .arg("--log-level") 347 | .arg("TRACE") 348 | .arg("-f") 349 | .arg(&file_path.0) 350 | .arg(&file_path.1) 351 | .assert() 352 | .success() 353 | .stdout(predicate::str::contains(format!( 354 | "{} has been hidden", 355 | file_path.0.display() 356 | ))) 357 | .stdout(predicate::str::contains(format!( 358 | "{} is already hidden", 359 | file_path.1.display() 360 | ))); 361 | } 362 | 363 | #[test] 364 | fn hide_with_invalid_log_level() { 365 | let temp_dir = tempfile::tempdir().unwrap(); 366 | let temp_dir = temp_dir.path(); 367 | let file_path = temp_dir.join("foo.txt"); 368 | 369 | File::create(&file_path).unwrap(); 370 | 371 | utils::command::command() 372 | .arg("hide") 373 | .arg("--log-level") 374 | .arg("a") 375 | .arg("-f") 376 | .arg(&file_path) 377 | .assert() 378 | .failure() 379 | .code(2) 380 | .stderr(predicate::str::contains( 381 | "invalid value 'a' for '--log-level '", 382 | )); 383 | } 384 | -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | mod utils; 6 | 7 | use predicates::prelude::predicate; 8 | 9 | #[test] 10 | fn without_subcommand() { 11 | utils::command::command() 12 | .assert() 13 | .failure() 14 | .code(2) 15 | .stderr(predicate::str::contains( 16 | "requires a subcommand but one was not provided", 17 | )); 18 | } 19 | 20 | #[test] 21 | fn log_level_without_subcommand() { 22 | utils::command::command() 23 | .arg("--log-level") 24 | .arg("WARN") 25 | .assert() 26 | .failure() 27 | .code(2) 28 | .stderr(predicate::str::contains( 29 | "requires a subcommand but one was not provided", 30 | )); 31 | } 32 | -------------------------------------------------------------------------------- /tests/show.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | mod utils; 6 | 7 | use std::fs::File; 8 | 9 | use predicates::prelude::predicate; 10 | 11 | #[test] 12 | fn basic_show() { 13 | let temp_dir = tempfile::tempdir().unwrap(); 14 | let temp_dir = temp_dir.path(); 15 | let file_path = temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }); 16 | 17 | File::create(&file_path).unwrap(); 18 | #[cfg(windows)] 19 | std::process::Command::new("attrib") 20 | .arg("+h") 21 | .arg(&file_path) 22 | .status() 23 | .unwrap(); 24 | 25 | utils::command::command() 26 | .arg("show") 27 | .arg("-n") 28 | .arg(&file_path) 29 | .assert() 30 | .success() 31 | .stdout(predicate::str::contains(format!("{}", file_path.display()))); 32 | 33 | utils::command::command() 34 | .arg("show") 35 | .arg("-f") 36 | .arg(&file_path) 37 | .assert() 38 | .success() 39 | .stdout(predicate::str::contains(format!( 40 | "{} has been shown", 41 | file_path.display() 42 | ))); 43 | 44 | #[cfg(unix)] 45 | assert!(temp_dir.join("foo.txt").exists()); 46 | #[cfg(windows)] 47 | assert!(!hf::is_hidden(file_path).unwrap()); 48 | } 49 | 50 | #[test] 51 | fn show_with_multiple_files() { 52 | let temp_dir = tempfile::tempdir().unwrap(); 53 | let temp_dir = temp_dir.path(); 54 | let file_path = ( 55 | temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }), 56 | temp_dir.join(if cfg!(unix) { ".bar.txt" } else { "bar.txt" }), 57 | ); 58 | 59 | File::create(&file_path.0).unwrap(); 60 | File::create(&file_path.1).unwrap(); 61 | #[cfg(windows)] 62 | std::process::Command::new("attrib") 63 | .arg("+h") 64 | .arg(&file_path.0) 65 | .status() 66 | .unwrap(); 67 | #[cfg(windows)] 68 | std::process::Command::new("attrib") 69 | .arg("+h") 70 | .arg(&file_path.1) 71 | .status() 72 | .unwrap(); 73 | 74 | utils::command::command() 75 | .arg("show") 76 | .arg("-n") 77 | .arg(&file_path.0) 78 | .arg(&file_path.1) 79 | .assert() 80 | .success() 81 | .stdout(predicate::str::contains(format!( 82 | "{}", 83 | file_path.0.display() 84 | ))) 85 | .stdout(predicate::str::contains(format!( 86 | "{}", 87 | file_path.1.display() 88 | ))); 89 | 90 | utils::command::command() 91 | .arg("show") 92 | .arg("-f") 93 | .arg(&file_path.0) 94 | .arg(&file_path.1) 95 | .assert() 96 | .success() 97 | .stdout(predicate::str::contains(format!( 98 | "{} has been shown", 99 | file_path.0.display() 100 | ))) 101 | .stdout(predicate::str::contains(format!( 102 | "{} has been shown", 103 | file_path.1.display() 104 | ))); 105 | } 106 | 107 | #[test] 108 | fn show_when_non_hidden_file() { 109 | let temp_dir = tempfile::tempdir().unwrap(); 110 | let temp_dir = temp_dir.path(); 111 | let file_path = temp_dir.join("foo.txt"); 112 | 113 | File::create(&file_path).unwrap(); 114 | 115 | utils::command::command() 116 | .arg("show") 117 | .arg("-n") 118 | .arg(&file_path) 119 | .assert() 120 | .success() 121 | .stdout(predicate::str::contains(format!( 122 | "{} is ignored", 123 | file_path.display() 124 | ))); 125 | 126 | utils::command::command() 127 | .arg("show") 128 | .arg("-f") 129 | .arg(&file_path) 130 | .assert() 131 | .success() 132 | .stdout(predicate::str::contains(format!( 133 | "{} is already shown", 134 | file_path.display() 135 | ))); 136 | 137 | #[cfg(unix)] 138 | assert!(temp_dir.join("foo.txt").exists()); 139 | #[cfg(windows)] 140 | assert!(!hf::is_hidden(file_path).unwrap()); 141 | } 142 | 143 | #[test] 144 | fn show_when_file_does_not_exist() { 145 | { 146 | let command = utils::command::command() 147 | .arg("show") 148 | .arg("-n") 149 | .arg("non_existent.txt") 150 | .assert() 151 | .failure() 152 | .code(66); 153 | if cfg!(windows) { 154 | command.stderr(predicate::str::contains( 155 | "could not read information from non_existent.txt", 156 | )); 157 | } else { 158 | command.stderr(predicate::str::contains("non_existent.txt does not exist")); 159 | } 160 | } 161 | 162 | { 163 | let command = utils::command::command() 164 | .arg("show") 165 | .arg("-f") 166 | .arg("non_existent.txt") 167 | .assert() 168 | .failure() 169 | .code(66); 170 | if cfg!(windows) { 171 | command.stderr(predicate::str::contains( 172 | "could not read information from non_existent.txt", 173 | )); 174 | } else { 175 | command.stderr(predicate::str::contains("non_existent.txt does not exist")); 176 | } 177 | } 178 | } 179 | 180 | #[test] 181 | fn show_with_force_and_dry_run() { 182 | let temp_dir = tempfile::tempdir().unwrap(); 183 | let temp_dir = temp_dir.path(); 184 | let file_path = temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }); 185 | 186 | File::create(&file_path).unwrap(); 187 | #[cfg(windows)] 188 | std::process::Command::new("attrib") 189 | .arg("+h") 190 | .arg(&file_path) 191 | .status() 192 | .unwrap(); 193 | 194 | utils::command::command() 195 | .arg("show") 196 | .arg("-f") 197 | .arg("-n") 198 | .arg(&file_path) 199 | .assert() 200 | .failure() 201 | .code(2) 202 | .stderr(predicate::str::contains( 203 | "the argument '--force' cannot be used with '--dry-run'", 204 | )); 205 | } 206 | 207 | #[test] 208 | fn show_with_off_log_level() { 209 | let temp_dir = tempfile::tempdir().unwrap(); 210 | let temp_dir = temp_dir.path(); 211 | let file_path = temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }); 212 | 213 | File::create(&file_path).unwrap(); 214 | #[cfg(windows)] 215 | std::process::Command::new("attrib") 216 | .arg("+h") 217 | .arg(&file_path) 218 | .status() 219 | .unwrap(); 220 | 221 | utils::command::command() 222 | .arg("show") 223 | .arg("--log-level") 224 | .arg("OFF") 225 | .arg("-f") 226 | .arg(&file_path) 227 | .assert() 228 | .success() 229 | .stdout(predicate::str::is_empty()); 230 | } 231 | 232 | #[test] 233 | fn show_with_error_log_level() { 234 | let temp_dir = tempfile::tempdir().unwrap(); 235 | let temp_dir = temp_dir.path(); 236 | let file_path = temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }); 237 | 238 | File::create(&file_path).unwrap(); 239 | #[cfg(windows)] 240 | std::process::Command::new("attrib") 241 | .arg("+h") 242 | .arg(&file_path) 243 | .status() 244 | .unwrap(); 245 | 246 | utils::command::command() 247 | .arg("show") 248 | .arg("--log-level") 249 | .arg("ERROR") 250 | .arg("-f") 251 | .arg(&file_path) 252 | .assert() 253 | .success() 254 | .stdout(predicate::str::is_empty()); 255 | } 256 | 257 | #[test] 258 | fn show_with_warn_log_level() { 259 | let temp_dir = tempfile::tempdir().unwrap(); 260 | let temp_dir = temp_dir.path(); 261 | let file_path = temp_dir.join("foo.txt"); 262 | 263 | File::create(&file_path).unwrap(); 264 | 265 | utils::command::command() 266 | .arg("show") 267 | .arg("--log-level") 268 | .arg("WARN") 269 | .arg("-f") 270 | .arg(&file_path) 271 | .assert() 272 | .success() 273 | .stdout(predicate::str::contains(format!( 274 | "{} is already shown", 275 | file_path.display() 276 | ))); 277 | } 278 | 279 | #[test] 280 | fn show_with_info_log_level() { 281 | let temp_dir = tempfile::tempdir().unwrap(); 282 | let temp_dir = temp_dir.path(); 283 | let file_path = ( 284 | temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }), 285 | temp_dir.join("bar.txt"), 286 | ); 287 | 288 | File::create(&file_path.0).unwrap(); 289 | File::create(&file_path.1).unwrap(); 290 | #[cfg(windows)] 291 | std::process::Command::new("attrib") 292 | .arg("+h") 293 | .arg(&file_path.0) 294 | .status() 295 | .unwrap(); 296 | 297 | utils::command::command() 298 | .arg("show") 299 | .arg("--log-level") 300 | .arg("INFO") 301 | .arg("-f") 302 | .arg(&file_path.0) 303 | .arg(&file_path.1) 304 | .assert() 305 | .success() 306 | .stdout(predicate::str::contains(format!( 307 | "{} has been shown", 308 | file_path.0.display() 309 | ))) 310 | .stdout(predicate::str::contains(format!( 311 | "{} is already shown", 312 | file_path.1.display() 313 | ))); 314 | } 315 | 316 | #[test] 317 | fn show_with_debug_log_level() { 318 | let temp_dir = tempfile::tempdir().unwrap(); 319 | let temp_dir = temp_dir.path(); 320 | let file_path = ( 321 | temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }), 322 | temp_dir.join("bar.txt"), 323 | ); 324 | 325 | File::create(&file_path.0).unwrap(); 326 | File::create(&file_path.1).unwrap(); 327 | #[cfg(windows)] 328 | std::process::Command::new("attrib") 329 | .arg("+h") 330 | .arg(&file_path.0) 331 | .status() 332 | .unwrap(); 333 | 334 | utils::command::command() 335 | .arg("show") 336 | .arg("--log-level") 337 | .arg("DEBUG") 338 | .arg("-f") 339 | .arg(&file_path.0) 340 | .arg(&file_path.1) 341 | .assert() 342 | .success() 343 | .stdout(predicate::str::contains(format!( 344 | "{} has been shown", 345 | file_path.0.display() 346 | ))) 347 | .stdout(predicate::str::contains(format!( 348 | "{} is already shown", 349 | file_path.1.display() 350 | ))); 351 | } 352 | 353 | #[test] 354 | fn show_with_trace_log_level() { 355 | let temp_dir = tempfile::tempdir().unwrap(); 356 | let temp_dir = temp_dir.path(); 357 | let file_path = ( 358 | temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }), 359 | temp_dir.join("bar.txt"), 360 | ); 361 | 362 | File::create(&file_path.0).unwrap(); 363 | File::create(&file_path.1).unwrap(); 364 | #[cfg(windows)] 365 | std::process::Command::new("attrib") 366 | .arg("+h") 367 | .arg(&file_path.0) 368 | .status() 369 | .unwrap(); 370 | 371 | utils::command::command() 372 | .arg("show") 373 | .arg("--log-level") 374 | .arg("TRACE") 375 | .arg("-f") 376 | .arg(&file_path.0) 377 | .arg(&file_path.1) 378 | .assert() 379 | .success() 380 | .stdout(predicate::str::contains(format!( 381 | "{} has been shown", 382 | file_path.0.display() 383 | ))) 384 | .stdout(predicate::str::contains(format!( 385 | "{} is already shown", 386 | file_path.1.display() 387 | ))); 388 | } 389 | 390 | #[test] 391 | fn show_with_invalid_log_level() { 392 | let temp_dir = tempfile::tempdir().unwrap(); 393 | let temp_dir = temp_dir.path(); 394 | let file_path = temp_dir.join(if cfg!(unix) { ".foo.txt" } else { "foo.txt" }); 395 | 396 | File::create(&file_path).unwrap(); 397 | #[cfg(windows)] 398 | std::process::Command::new("attrib") 399 | .arg("+h") 400 | .arg(&file_path) 401 | .status() 402 | .unwrap(); 403 | 404 | utils::command::command() 405 | .arg("show") 406 | .arg("--log-level") 407 | .arg("a") 408 | .arg("-f") 409 | .arg(&file_path) 410 | .assert() 411 | .failure() 412 | .code(2) 413 | .stderr(predicate::str::contains( 414 | "invalid value 'a' for '--log-level '", 415 | )); 416 | } 417 | -------------------------------------------------------------------------------- /tests/utils/command.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | use assert_cmd::Command; 6 | 7 | pub fn command() -> Command { 8 | let mut command = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); 9 | command.current_dir("tests"); 10 | command 11 | } 12 | -------------------------------------------------------------------------------- /tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Shun Sakai 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 OR MIT 4 | 5 | pub mod command; 6 | --------------------------------------------------------------------------------