diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..3ac6bd6 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# +# termscp pre-commit hook. +# +# Runs three gates before a commit is recorded: +# 1. trufflehog -- scan the staged tree for verified/unknown secrets +# 2. dprint -- check formatting (Markdown, TOML, YAML, Rust) +# 3. cargo-deny -- advisories, licenses, bans, and sources +# +# Install with `just setup_githooks` (sets core.hooksPath to .githooks). +# Bypass in an emergency with `git commit --no-verify`. + +set -euo pipefail + +fail() { + echo "pre-commit: $1" >&2 + exit 1 +} + +command -v just >/dev/null 2>&1 || fail "just not found; install it to run the pre-commit checks" + +if ! git diff --cached --quiet --diff-filter=ACMR --; then + command -v trufflehog >/dev/null 2>&1 || fail "trufflehog not found; install it or commit with --no-verify" + + # Check the exact tree that will be committed, not possibly different + # working-tree contents. The trailing slash is required by checkout-index. + index_tree="$(mktemp -d)" + cleanup() { + rm -rf -- "$index_tree" + } + trap cleanup EXIT + git checkout-index --all --prefix="$index_tree/" + + echo "pre-commit: scanning staged tree for secrets" + ( + cd "$index_tree" + just scan_secrets . --fail-on-scan-errors + ) + + echo "pre-commit: checking staged-tree formatting" + ( + cd "$index_tree" + just fmt_check + ) + + echo "pre-commit: checking staged-tree dependencies" + ( + cd "$index_tree" + just deny + ) +fi + +echo "pre-commit: all checks passed" diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 578c27f..3839acd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report of the bug you've encountered title: "[BUG] - ISSUE_TITLE" labels: bug assignees: veeso - --- ## Description diff --git a/.github/ISSUE_TEMPLATE/copy.md b/.github/ISSUE_TEMPLATE/copy.md index b26f9de..4944292 100644 --- a/.github/ISSUE_TEMPLATE/copy.md +++ b/.github/ISSUE_TEMPLATE/copy.md @@ -4,7 +4,6 @@ about: Report a typo/error in a repository document title: "[COPY] - ISSUE_TITLE" labels: documentation assignees: veeso - --- ## Report diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index b2790ed..684626d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea to improve termscp title: "[Feature Request] - FEATURE_TITLE" labels: "new feature" assignees: veeso - --- ## Description diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index d827421..2d8f664 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -4,5 +4,4 @@ about: Ask what you want about the project title: "[QUESTION] - TITLE" labels: question assignees: veeso - --- diff --git a/.github/ISSUE_TEMPLATE/security.md b/.github/ISSUE_TEMPLATE/security.md index 6c00db8..897c354 100644 --- a/.github/ISSUE_TEMPLATE/security.md +++ b/.github/ISSUE_TEMPLATE/security.md @@ -4,7 +4,6 @@ about: Create a report of a security vulnerability title: "[SECURITY] - ISSUE_TITLE" labels: security assignees: veeso - --- ## Description diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9163c5d..e5fc723 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -29,12 +29,12 @@ Please select relevant options. - [ ] I formatted the code with `cargo fmt` - [ ] I checked my code using `cargo clippy` and reports no warnings - [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] I have introduced no new *C-bindings* +- [ ] I have introduced no new _C-bindings_ - [ ] The changes I've made are Windows, MacOS, UNIX, Linux compatible (or I've handled them using `cfg target_os`) - [ ] I increased or maintained the code coverage for the project, compared to the previous commit ## Acceptance tests -wait for a *project maintainer* to fulfill this section... +wait for a _project maintainer_ to fulfill this section... - [ ] regression test: ... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7eb0ac..20a137d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,12 +5,12 @@ on: branches: [main] paths-ignore: - "*.md" - - "./site/**/*" + - "site/**" push: branches: [main] paths-ignore: - "*.md" - - "./site/**/*" + - "site/**" env: CARGO_TERM_COLOR: always @@ -19,50 +19,120 @@ permissions: contents: read jobs: - build: - name: build-(${{ matrix.os }}) + toolchain: + runs-on: ubuntu-latest + outputs: + channel: ${{ steps.extract.outputs.result }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Extract toolchain channel from rust-toolchain.toml + id: extract + uses: mikefarah/yq@c14f446382944492701b16c1ddb48bb9dbe683e3 # v4.53.6 + with: + cmd: yq '.toolchain.channel' rust-toolchain.toml + + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust (nightly) + uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable + with: + toolchain: nightly + components: rustfmt + - name: Check formatting + uses: dprint/check@9cb3a2b17a8e606d37aae341e49df3654933fc23 # v2.3 + + install-scripts: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install just + uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 + - name: Check install scripts + run: just check_install_scripts + + crates: + needs: toolchain + name: crates-${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Install Linux dependencies if: runner.os == 'Linux' - run: sudo apt update && sudo apt install -y libdbus-1-dev libsmbclient-dev + run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev libsmbclient-dev - name: Install macOS dependencies if: runner.os == 'macOS' run: | brew update brew install \ - pkg-config \ - samba + pkg-config \ + samba brew link --force samba - - name: Install nightly toolchain - if: runner.os == 'Linux' - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + - name: Install Rust + uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: - toolchain: nightly - components: rustfmt - - name: Format - if: runner.os == 'Linux' - run: cargo +nightly fmt --all -- --check - - name: Install stable toolchain - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - with: - toolchain: stable + toolchain: ${{ needs.toolchain.outputs.channel }} components: clippy + - name: Install just + uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 - name: Build - if: runner.os != 'Linux' - run: cargo build - - name: Run tests (Linux) + run: just build_crates + - name: Test (Linux) if: runner.os == 'Linux' - run: cargo test --no-default-features --features github-actions --no-fail-fast - - name: Run tests + run: just test "--no-default-features --features github-actions --no-fail-fast" + - name: Test if: runner.os != 'Linux' - run: cargo test --verbose --features github-actions + run: just test "--verbose --features github-actions" - name: Clippy - run: cargo clippy -- -Dwarnings + run: just clippy "-- -D warnings" + + doc: + needs: toolchain + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Linux dependencies + run: sudo apt-get update && sudo apt-get install -y libdbus-1-dev libsmbclient-dev + - name: Install Rust + uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable + with: + toolchain: ${{ needs.toolchain.outputs.channel }} + - name: Install just + uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 + - name: Build documentation + run: just doc + + deny: + needs: toolchain + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install Rust + uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable + with: + toolchain: ${{ needs.toolchain.outputs.channel }} + - name: Install cargo-deny + uses: taiki-e/install-action@37f7c5781271959fb65b6b35224e28652ff2b63d # v2.87.0 + with: + tool: cargo-deny + - name: Install just + uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 + - name: Check dependencies + run: just deny diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index 39b1d1e..dc9b2df 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -22,7 +22,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Install termscp from script diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index d99b466..c749b69 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -26,7 +26,7 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Install mdBook @@ -58,9 +58,9 @@ jobs: termscp documentation HTML - name: Upload artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: path: site_out - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84f78f5..c37dfb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,23 +22,35 @@ jobs: outputs: version: ${{ inputs.version }} steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: token: ${{ secrets.RELEASE_PAT }} persist-credentials: true fetch-depth: 0 fetch-tags: true + - name: Validate version + env: + VERSION: ${{ inputs.version }} + run: | + if [[ ! "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "invalid release version: $VERSION (expected MAJOR.MINOR.PATCH)" >&2 + exit 2 + fi + - name: Configure git identity run: | git config user.name "veeso" git config user.email "christian.visintin@veeso.dev" - name: Install git-cliff - uses: taiki-e/install-action@56545b37b57562edd73171cb6c62cc509db4c34e # v2 + uses: taiki-e/install-action@37f7c5781271959fb65b6b35224e28652ff2b63d # v2.87.0 with: tool: git-cliff + - name: Install just + uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 + - name: Bump version env: VERSION: ${{ inputs.version }} @@ -47,7 +59,7 @@ jobs: - name: Generate CHANGELOG env: VERSION: ${{ inputs.version }} - run: git-cliff --tag "v$VERSION" -o CHANGELOG.md + run: just changelog "$VERSION" - name: Generate release notes env: @@ -55,7 +67,7 @@ jobs: run: git-cliff --unreleased --tag "v$VERSION" --strip header -o RELEASE_NOTES.md - name: Upload release notes - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: release-notes path: RELEASE_NOTES.md @@ -111,15 +123,20 @@ jobs: TARGET: ${{ matrix.target }} FEATURES: ${{ matrix.features }} steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ inputs.dry_run && github.sha || 'main' }} persist-credentials: false fetch-depth: 0 fetch-tags: true - - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - with: - targets: ${{ matrix.target }} + - name: Install just + uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 + - name: Install Rust target + if: matrix.kind != 'windows' + run: rustup target add "$TARGET" + - name: Install Rust target + if: matrix.kind == 'windows' + run: rustup target add "$env:TARGET" # ---- Linux: native per-arch build (x86_64 on ubuntu-latest, aarch64 on ubuntu-24.04-arm) ---- - name: Install dependencies (Linux) @@ -156,10 +173,10 @@ jobs: cargo install cargo-deb - name: Build (Linux) if: matrix.kind == 'linux' - run: cargo build --release --features smb-vendored --target "$TARGET" + run: just build_release "$TARGET" "--features smb-vendored" - name: Build deb (Linux) if: matrix.kind == 'linux' - run: cargo deb --no-build --target "$TARGET" --features smb-vendored + run: just package_deb "$TARGET" # ---- macOS ---- - name: Install deps (macOS) @@ -173,12 +190,12 @@ jobs: cpanm Parse::Yapp::Driver - name: Build (macOS) if: matrix.kind == 'macos' - run: cargo build --release $FEATURES --target "$TARGET" + run: just build_release "$TARGET" "$FEATURES" # ---- Windows ---- - name: Build (Windows) if: matrix.kind == 'windows' - run: cargo build --release --features smb-vendored --target "$env:TARGET" + run: just build_release "$env:TARGET" "--features smb-vendored" # ---- Package posix (tar.gz) ---- - name: Package (posix) @@ -204,7 +221,7 @@ jobs: run: cp target/"$TARGET"/debian/*.deb artifact/ - name: Upload build artifacts - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: build-${{ matrix.target }} path: artifact/* @@ -218,14 +235,14 @@ jobs: VERSION: ${{ needs.prepare.outputs.version }} steps: - name: Download build artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: build-* path: dl merge-multiple: true - name: Checkout homebrew tap - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: veeso/homebrew-termscp token: ${{ secrets.RELEASE_PAT }} @@ -316,21 +333,21 @@ jobs: env: VERSION: ${{ needs.prepare.outputs.version }} steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: token: ${{ secrets.RELEASE_PAT }} ref: ${{ inputs.dry_run && github.sha || 'main' }} persist-credentials: true - name: Download build artifacts - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: build-* path: dl merge-multiple: true - name: Download release notes - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: release-notes path: notes @@ -355,7 +372,7 @@ jobs: - name: Upload assets artifact (dry run) if: ${{ inputs.dry_run }} - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: release-assets-dryrun path: out/* @@ -380,13 +397,14 @@ jobs: env: VERSION: ${{ needs.prepare.outputs.version }} steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: main persist-credentials: false fetch-depth: 0 fetch-tags: true - - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + - name: Install just + uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 - name: Install dependencies (Linux) run: | @@ -421,12 +439,12 @@ jobs: - name: Authenticate to crates.io id: auth - uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4 + uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 - name: Publish to crates.io env: CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} - run: cargo publish --features smb-vendored + run: just publish_crate publish-choco: needs: [prepare, release] diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index c5c9062..3c38437 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -9,30 +9,28 @@ on: permissions: contents: read -defaults: - run: - working-directory: site - jobs: build-site: runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: 24 cache: npm cache-dependency-path: site/package-lock.json + - name: Install just + uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4 - name: Install dependencies - run: npm ci + run: just site_install - name: Format - run: npm run format:check + run: just site_fmt_check - name: Lint - run: npm run check + run: just site_check - name: Test - run: npm test --if-present + run: just site_test - name: Build - run: npm run build + run: just site_build diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 7bdf878..7e93dbb 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@a20b814fb01b71def3bd6f56e7494d667ddf28da # v4.1.1 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 with: days-before-issue-stale: 30 days-before-issue-close: 7 diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..681311e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e095ce2..1022ace 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +# Changelog + ## 1.1.1 Released on 2026-06-08 @@ -10,6 +12,7 @@ Released on 2026-06-08 > crates.io versions are immutable (no overwrite) and Chocolatey's > moderation queue blocks a fast re-push, so a clean 1.1.1 without vergen > is the only fix. + ## 1.1.0 Released on 2026-06-08 @@ -29,7 +32,7 @@ Released on 2026-06-08 - **install:** add Windows PowerShell installer and copy buttons on site > Add install.ps1 mirroring install.sh for Windows: arch detection, > release zip download, binary extraction, user PATH update. - > + > > - copy install.ps1 to site public/ at build time (copy-install.mjs) > - serve /install.ps1 with text/plain Content-Type (vercel.json) > - add PowerShell one-liner to install page and README @@ -38,11 +41,11 @@ Released on 2026-06-08 - **config:** move config dir to ~/.config/termscp on macOS and %USERPROFILE%\.termscp on Windows > Resolve the config directory through a single per-platform config_dir() > function instead of relying on dirs::config_dir everywhere: - > + > > - macOS: ~/.config/termscp (was ~/Library/Application Support/termscp) > - Windows: %USERPROFILE%\.termscp (was roaming %APPDATA%\termscp) > - Linux/other: /termscp (unchanged) - > + > > Existing users are migrated automatically on first run: when the new > directory is absent and the legacy location exists, the whole config > directory is moved to the new path. The cache directory stays at the @@ -54,7 +57,7 @@ Released on 2026-06-08 > Single workflow_dispatch (version, dry_run) that bumps versions, regenerates > CHANGELOG via git-cliff, rebuilds site CSS, builds all targets, creates the > GitHub release, updates the Homebrew tap and publishes Chocolatey. - > + > > - dist/release/bump_version.sh: version replacer across all tracked locations (+tests) > - .github/workflows/release.yml: prepare -> build matrix -> homebrew/release -> choco > - retire build-artifacts.yml (merged into release.yml) @@ -66,7 +69,7 @@ Released on 2026-06-08 - fix release notes generation in release workflow > prepare job failed: git-cliff --latest crashed with 'trim_start_matches on > null' because the checkout was shallow (no tags/history) so no release existed. - > + > > - checkout prepare with fetch-depth: 0 + fetch-tags so git-cliff sees full > history and tags (also fixes an otherwise-truncated CHANGELOG) > - generate release notes with --unreleased --tag v$VERSION instead of --latest: @@ -143,7 +146,7 @@ Released on 2026-06-08 > Migrate the transfer progress UI to tuirealm 4, where the stdlib > `ProgressBar` widget was dropped, by rebuilding the dual-bar panel on > top of `Gauge`. - > + > > - Restore the unified two-bar look: the full bar (top) and partial bar > (bottom) draw joined borders so they read as a single panel; a single > file shows one fully-bordered bar. @@ -159,19 +162,19 @@ Released on 2026-06-08 > its top border is dropped to join the seam with the full bar. That left > the partial bar with one inner row while the full bar kept two, so the > two gauges rendered at unequal heights. - > + > > - Move the filename from the partial bar's title into its gauge label. > - Skip setting an empty title so no phantom top-positioned title triggers > the inset. > - Put the panel title on the top (full) bar for multi-file transfers. > - Bump the two-bar popup height to fit the joined panel. - > + > > Also bump Cargo.lock and adapt the embedded terminal to the new vt100 > `screen_mut()` API. - **copy:** prevent emptying file when copy destination is empty (#421) > An empty copy destination resolved to the source file's own path, so > std::fs::copy truncated the original file to 0 bytes. - > + > > - localhost::copy now refuses to copy a file onto itself, returning an > error instead of truncating it (root cause). > - action_copy treats an empty/whitespace destination as a cancel. @@ -180,7 +183,7 @@ Released on 2026-06-08 > path. Downstream upload logic treats the queued destination as the full > file path and passes it straight to create_file, so transfers failed > with a Failure error when the remote target resolved to a directory. - > + > > Append each entry's file name to the destination directory at enqueue > time in both enqueue_file and enqueue_all, matching the single-file > transfer path which already builds the full target path. @@ -191,6 +194,7 @@ Released on 2026-06-08 > The old manual dist/build/* scripts and dist/{deb,rpm}.sh are superseded by the > automated release workflow. Add the chocolatey package consumed by release.yml. - **site:** copy install.sh from repo root at build time (single source) + ## 1.0.0 Released on 2026-04-18 @@ -243,6 +247,7 @@ Released on 2026-04-18 > `fs_pane_mut()`. This eliminates most `is_local_tab()` branching across > 15+ action files. > Key changes: + > > - Add `fs: Box` to Pane, remove from FileTransferActivity > - Replace per-side method pairs with unified pane-dispatched methods > - Unify navigation (changedir, reload, scan, file_exists, has_file_changed) @@ -250,7 +255,7 @@ Released on 2026-04-18 > - Replace assert!/panic!/unreachable! with proper error handling > - Fix typo "filetransfer_activiy" across ~29 files > - Add unit tests for Pane - > + > > Net result: -473 lines, single code path for most file operations. - replace lazy_static with std::sync::LazyLock - migrate from mod.rs to named module files @@ -278,14 +283,14 @@ Released on 2026-04-18 > encryption (authenticated, with random nonces) while keeping a legacy > AES-128-CBC decryption path to transparently handle existing bookmarks. - replace recursive byte-counting with entry-based transfer progress (#395) - > * fix: replace recursive byte-counting with entry-based transfer progress - > + > - fix: replace recursive byte-counting with entry-based transfer progress + > > Replace the expensive recursive `get_total_transfer_size` pre-calculation > with a lightweight entry-based counter (`TransferProgress`) for the > overall progress bar. This avoids deep `list_dir` traversals before > transfers begin, which could cause FTP idle-timeout disconnections on > large directory trees. - > + > > The per-file byte-level progress bar (`ProgressStates`) remains > unchanged. Bytes are still tracked via `TransferStates::add_bytes` for > notification threshold logic. @@ -302,7 +307,7 @@ Released on 2026-04-18 - sync browsing when entering a directory from filtered/fuzzy view - stabilize core error handling > Remove production panic and unwrap paths from core modules. - > + > > Propagate bookmark encryption failures, harden file watcher and temp mapped file handling, and clean up dead code in shared utilities. - normalize localhost relative path checks - use time-based redraw interval instead of progress-delta threshold @@ -341,9 +346,9 @@ Released on 2026-04-18 > Upgrade tuirealm (3.x -> 4.0.0), tui-realm-stdlib (3 -> 4), tui-term > (0.2 -> 0.3). Apply all breaking changes from the 4.0 migration guide > across the termscp UI. - > + > > Key changes: - > + > > - Root-level re-exports removed; imports moved to module-qualified > paths (`tuirealm::application`, `::component`, `::event`, `::props`, > `::state`, `::subscription`, `::listener`, `::ratatui`). Same for @@ -394,6 +399,7 @@ Released on 2026-04-18 ### Style - linter + ## 0.19.1 Released on 2025-12-20 @@ -407,6 +413,7 @@ Released on 2025-12-20 - install.sh deb name - install.sh deb name - Updated dependencies to allow build on NetBSD + ## 0.19.0 Released on 2025-11-11 @@ -414,13 +421,13 @@ Released on 2025-11-11 ### Added - Import bookmarks from ssh config with a CLI command (#364) - > * feat: Import bookmarks from ssh config with a CLI command - > + > - feat: Import bookmarks from ssh config with a CLI command + > > Use import-ssh-hosts to import all the possible hosts by the configured ssh config or the default one on your machine - Changed file overwrite behaviour (#366) > Now the user can choose for each file whether to overwrite, skip or overwrite all/skip all. - Added `` keybinding to get the total size of selected paths. (#367) - > * feat: Added `` keybinding to get the total size of selected paths. + > - feat: Added `` keybinding to get the total size of selected paths. - Merge branch '0.19.0' ### CI @@ -440,8 +447,8 @@ Released on 2025-11-11 - typo in file open error message (#349) - SMB support for MacOS with vendored build of libsmbclient. - Report a message while calculating total size of files to transfer. (#362) - > * fix: Report a message while calculating total size of files to transfer. - > + > - fix: Report a message while calculating total size of files to transfer. + > > Currently, in case of huge transfers the app may look frozen while calculating the transfer size. We should at least report to the user we are actually doing something. - Issues with update checks (#363) > Removed error popup message if failed to check for updates. @@ -456,6 +463,7 @@ Released on 2025-11-11 - 0.19 deps - remotefs-ssh 0.7.1 > This version fixes compatibility with hosts which don't use bash/sh as the default shell. + ## 0.18.0 Released on 2025-06-10 @@ -464,7 +472,7 @@ Released on 2025-06-10 - **Updated dependencies** and updated the Rust edition to `2024` - Replaced the `Exec` popup with a fully functional terminal emulator (#348) - > * feat: Replaced the `Exec` popup with a fully functional terminal emulator + > - feat: Replaced the `Exec` popup with a fully functional terminal emulator - 0.18 ### Fixed @@ -475,6 +483,7 @@ Released on 2025-06-10 ### Style - catppuccin themes + ## 0.17.0 Released on 2025-03-23 @@ -523,6 +532,7 @@ Released on 2025-03-23 - aws-s3 0.4.2 - build docker for x86 - so apparently native-tls vendored tries to build openssl on windows, wtf guys? + ## 0.16.1 Released on 2024-11-12 @@ -532,6 +542,7 @@ Released on 2024-11-12 - cfg unix forbidden in rust .82 - gg rust 1.82 for introducing a nice breaking change in config which was not mentioned in changelog - 0.16.1 + ## 0.16.0 Released on 2024-10-14 @@ -550,6 +561,7 @@ Released on 2024-10-14 - issue 292 New version alert was not displayed due to a semver regex issue. (#300) - 0.16 - tiny ui issue + ## 0.15.0 Released on 2024-10-03 @@ -572,14 +584,14 @@ Released on 2024-10-03 - issue 277 Fix a bug in the configuration page, which caused being stuck if the added SSH key was empty - popup texts - `isolated-tests` feature to run tests for releasing on distributions which run in isolated environments (#286) - > * fix: `isolated-tests` feature to run tests for releasing on distributions which run in isolated environments - > - > * fix: cond + > - fix: `isolated-tests` feature to run tests for releasing on distributions which run in isolated environments + > - fix: cond - set date - github ci is stable and reliable (one worker broken each 2 weeks) - ci - readme - include build.rs + ## 0.14.0 Released on 2024-07-17 @@ -608,6 +620,7 @@ Released on 2024-07-17 - german manual - removed support for RPM - changelog + ## 0.13.0 Released on 2024-03-02 @@ -624,6 +637,7 @@ Released on 2024-03-02 - debian script - debian script - lint??? + ## 0.12.2 Released on 2023-10-01 @@ -636,6 +650,7 @@ Released on 2023-10-01 - fmt - panic if the terminal screen is too small + ## 0.12.1 Released on 2023-07-06 @@ -659,6 +674,7 @@ Released on 2023-07-06 - don't run CI on site/.md change - rustup target - don't update path breadcrumb if enter/scan dir failed (#203) + ## 0.12.0 Released on 2023-05-16 @@ -677,6 +693,7 @@ Released on 2023-05-16 - pavao 0.2.3 - macos script - release date + ## 0.11.3 Released on 2023-04-19 @@ -688,6 +705,7 @@ Released on 2023-04-19 ### Fixed - relative paths windows (#167) + ## 0.11.2 Released on 2023-04-18 @@ -696,6 +714,7 @@ Released on 2023-04-18 - dependencies up-to-date - site 0.11.2 + ## 0.8.1 Released on 2022-03-22 @@ -703,6 +722,7 @@ Released on 2022-03-22 ### Fixed - footer listed "Delete" shortcut as "Make Dir" + ## 0.8.0 Released on 2022-01-06 @@ -710,6 +730,7 @@ Released on 2022-01-06 ### Arch - install rust only if not found on local system + ## 0.7.0 Released on 2021-10-12 @@ -717,6 +738,7 @@ Released on 2021-10-12 ### Option - prompt user when about to replace an existing file caused by a file transfer + ## 0.6.1 Released on 2021-08-30 @@ -724,6 +746,7 @@ Released on 2021-08-30 ### Fixed - When copying files with tricky copy, the upper progress bar shows no text + ## 0.5.1 Released on 2021-06-21 @@ -731,6 +754,7 @@ Released on 2021-06-21 ### Fix - target_family unix means also macos and linux; use BSD target_os + ## 0.5.0 Released on 2021-05-23 @@ -743,6 +767,7 @@ Released on 2021-05-23 ### Grcov - exclude activities + ## 0.4.1 Released on 2021-04-06 @@ -755,18 +780,19 @@ Released on 2021-04-06 ### Readme - one-liner for Homebrew - > The one-liner command - > - > brew install veeso/termscp/termscp - > - > is equivalent to the two commands - > - > brew tap veeso/termscp - > brew install termscp + > The one-liner command + > + > brew install veeso/termscp/termscp + > + > is equivalent to the two commands + > + > brew tap veeso/termscp + > brew install termscp ### SCP - fixed symlink not properly detected + ## 0.4.0 Released on 2021-03-27 @@ -786,6 +812,7 @@ Released on 2021-03-27 ### View - return String instead of id + ## 0.3.3 Released on 2021-02-28 @@ -793,6 +820,7 @@ Released on 2021-02-28 ### Git - check for new updates (utils) + ## 0.3.2 Released on 2021-01-24 @@ -800,6 +828,7 @@ Released on 2021-01-24 ### Testing - don't run on windows + ## 0.3.0 Released on 2021-01-10 @@ -834,6 +863,7 @@ Released on 2021-01-10 ### SetupActivity - as + ## 0.2.0 Released on 2020-12-21 @@ -845,6 +875,7 @@ Released on 2020-12-21 ### Scp - when username was not provided, it didn't fallback to current username + ## 0.1.2 Released on 2020-12-13 @@ -852,6 +883,7 @@ Released on 2020-12-13 ### FsEntry - :*::symlink is now a Option>; this improved symlinks, which gave errors some times + ## 0.1.0 Released on 2020-12-06 diff --git a/CLAUDE.md b/CLAUDE.md index 893a0ff..0b38679 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,28 +12,31 @@ termscp is a terminal file transfer client with a TUI (Terminal User Interface), ## Build & Development Commands +Task runner is `just` (modular recipes under `just/*.just`, imported by root `justfile`). Run `just --list` for the full set. + ```bash # Build -cargo build -cargo build --release -cargo build --no-default-features # minimal build without SMB/keyring +just build_crates # cargo build --workspace +just build_crates "--release" +cargo build --no-default-features # minimal build without SMB/keyring (no just recipe) # Test (CI-equivalent) -cargo test --no-default-features --features github-actions --no-fail-fast +just test "--no-default-features --features github-actions --no-fail-fast" -# Run a single test +# Run a single test / a module (use cargo directly, just recipes don't take test names) cargo test -- --nocapture - -# Run tests for a module cargo test --lib filetransfer:: cargo test --lib config::params::tests # Lint -cargo clippy -- -Dwarnings +just clippy "-- -D warnings" -# Format -cargo fmt --all -- --check # check only -cargo fmt --all # fix +# Format (dprint: Markdown, TOML, YAML, and Rust via nightly rustfmt) +just fmt_check # check only +just fmt # fix + +# All code checks at once (fmt_check, clippy -D warnings, doc, deny, install script lint) +just check_code ``` ### System Dependencies (for building) @@ -66,16 +69,16 @@ main.rs → parse CLI args → ActivityManager::new() → ActivityManager::run() ### Key Modules -| Module | Path | Purpose | -|--------|------|---------| -| **activity_manager** | `src/activity_manager.rs` | Orchestrates activity lifecycle and transitions | -| **ui/activities** | `src/ui/activities/{auth,filetransfer,setup}/` | Three main screens, each implementing the `Activity` trait | -| **ui/context** | `src/ui/context.rs` | Shared `Context` struct (terminal, config, bookmarks, theme) | -| **filetransfer** | `src/filetransfer/` | Protocol enum, `RemoteFsBuilder`, connection parameters | -| **host** | `src/host/` | `HostBridge` trait — abstracts local (`Localhost`) and remote (`RemoteBridged`) file operations | -| **explorer** | `src/explorer/` | `FileExplorer` — directory navigation, sorting, filtering, transfer queue | -| **system** | `src/system/` | `BookmarksClient`, `ConfigClient`, `ThemeProvider`, `SshKeyStorage`, `KeyStorage` trait | -| **config** | `src/config/` | TOML-based serialization for themes, bookmarks, user params | +| Module | Path | Purpose | +| -------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------- | +| **activity_manager** | `src/activity_manager.rs` | Orchestrates activity lifecycle and transitions | +| **ui/activities** | `src/ui/activities/{auth,filetransfer,setup}/` | Three main screens, each implementing the `Activity` trait | +| **ui/context** | `src/ui/context.rs` | Shared `Context` struct (terminal, config, bookmarks, theme) | +| **filetransfer** | `src/filetransfer/` | Protocol enum, `RemoteFsBuilder`, connection parameters | +| **host** | `src/host/` | `HostBridge` trait — abstracts local (`Localhost`) and remote (`RemoteBridged`) file operations | +| **explorer** | `src/explorer/` | `FileExplorer` — directory navigation, sorting, filtering, transfer queue | +| **system** | `src/system/` | `BookmarksClient`, `ConfigClient`, `ThemeProvider`, `SshKeyStorage`, `KeyStorage` trait | +| **config** | `src/config/` | TOML-based serialization for themes, bookmarks, user params | ### Core Traits @@ -86,6 +89,7 @@ main.rs → parse CLI args → ActivityManager::new() → ActivityManager::run() ### Conditional Compilation The `build.rs` defines cfg aliases via `cfg_aliases`: + - `posix`, `macos`, `linux`, `win` — platform shortcuts - `smb`, `smb_unix`, `smb_windows` — feature + platform combinations @@ -106,6 +110,7 @@ Platform-specific dependencies: SSH and FTP crates use different TLS backends on ## Other conventions -- Always run `cargo +nightly fmt --all` and `cargo clippy --no-default-features -- -Dwarnings` after modifying Rust code +- Always run `just fmt` and `just clippy "-- -D warnings"` after modifying Rust code - Always put plans to `./.claude/plans/` - When changing behavior that is documented under `docs/` (paths, config keys, commands, flags, etc.), update BOTH `docs/en-US/` and `docs/zh-CN/` to keep the translations in sync +- All code must be cross-platform compatible (Windows, macOS, Linux) — avoid POSIX-only APIs, hardcoded path separators, or shell-specific behavior unless gated behind the `posix`/`win` cfg aliases diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 67fe8ce..a545f08 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -17,23 +17,23 @@ diverse, inclusive, and healthy community. Examples of behavior that contributes to a positive environment for our community include: -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall +- Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: -* The use of sexualized language or imagery, and sexual attention or advances of +- The use of sexualized language or imagery, and sexual attention or advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e33b5d9..25a5f1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -150,5 +150,5 @@ You can view the developer guide [here](https://docs.termscp.rs/en-US/developer/ --- -Thank you for any contribution! +Thank you for any contribution!\ Christian Visintin diff --git a/Cargo.lock b/Cargo.lock index 89e0bf1..c317e0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,7 +514,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "h2 0.3.27", - "h2 0.4.14", + "h2 0.4.19", "http 0.2.12", "http 1.4.2", "http-body 0.4.6", @@ -924,9 +924,9 @@ dependencies = [ [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", "cipher 0.5.2", @@ -1117,7 +1117,7 @@ dependencies = [ "crc", "digest 0.10.7", "rustversion", - "spin 0.10.0", + "spin 0.10.1", ] [[package]] @@ -1147,9 +1147,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] @@ -1213,9 +1213,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3" +checksum = "1a52aa3fcda4e6302a9f48734f234d35d4721b96f8fe07d073f07ce9df4f0271" dependencies = [ "cpubits", "ctutils", @@ -1256,7 +1256,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a" dependencies = [ - "crypto-bigint 0.7.3", + "crypto-bigint 0.7.5", "libm", "rand_core 0.10.1", ] @@ -1568,7 +1568,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1770,7 +1770,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935" dependencies = [ "base16ct 1.0.0", - "crypto-bigint 0.7.3", + "crypto-bigint 0.7.5", "crypto-common 0.2.2", "digest 0.11.3", "ff 0.14.0", @@ -1826,7 +1826,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -2238,9 +2238,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.14" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", @@ -2492,7 +2492,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.14", + "h2 0.4.19", "http 1.4.2", "http-body 1.0.1", "httparse", @@ -2599,7 +2599,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.4", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -3088,7 +3088,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin 0.9.9", ] [[package]] @@ -4269,7 +4269,7 @@ version = "0.14.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f845ec3240cd5ed5e1e31cf3ff633a5bf47c698dc4092ba9e767415b3d393406" dependencies = [ - "crypto-bigint 0.7.3", + "crypto-bigint 0.7.5", "crypto-common 0.2.2", "ff 0.14.0", "rand_core 0.10.1", @@ -4335,7 +4335,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.6.4", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -4372,7 +4372,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.4", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] @@ -4425,7 +4425,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20 0.10.0", + "chacha20 0.10.2", "getrandom 0.4.2", "rand_core 0.10.1", ] @@ -4781,7 +4781,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.14", + "h2 0.4.19", "http 1.4.2", "http-body 1.0.1", "http-body-util", @@ -4895,7 +4895,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" dependencies = [ "const-oid 0.10.2", - "crypto-bigint 0.7.3", + "crypto-bigint 0.7.5", "crypto-primes", "digest 0.11.3", "pkcs1 0.8.0-rc.4", @@ -4931,7 +4931,7 @@ dependencies = [ "bytes", "cbc 0.2.1", "cipher 0.5.2", - "crypto-bigint 0.7.3", + "crypto-bigint 0.7.5", "ctr 0.10.1", "curve25519-dalek 5.0.0-rc.0", "data-encoding", @@ -5150,7 +5150,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5796,20 +5796,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" [[package]] name = "spin" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +checksum = "023a211cb3138dbc438680b32560ad89f699977624c9f8dbb95a47d5b4c07dd3" [[package]] name = "spki" @@ -5868,7 +5868,7 @@ dependencies = [ "aes 0.9.1", "aes-gcm 0.11.0-rc.4", "cbc 0.2.1", - "chacha20 0.10.0", + "chacha20 0.10.2", "cipher 0.5.2", "ctr 0.10.1", "ctutils", @@ -5898,7 +5898,7 @@ checksum = "7abf34aa716da5d5b4c496936d042ea282ab392092cd68a72ef6a8863ff8c96a" dependencies = [ "base64ct", "bytes", - "crypto-bigint 0.7.3", + "crypto-bigint 0.7.5", "ctutils", "digest 0.11.3", "pem-rfc7468 1.0.0", @@ -6117,10 +6117,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.3.4", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7031,7 +7031,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3bcc1f8..5fd1680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,17 @@ [package] name = "termscp" version = "1.1.1" -edition = "2024" authors = ["Christian Visintin "] -description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV" -license = "MIT" -repository = "https://github.com/veeso/termscp" categories = ["command-line-utilities"] +edition = "2024" homepage = "https://termscp.rs" include = ["/src/**/*", "/build.rs", "/LICENSE", "/README.md", "/CHANGELOG.md"] keywords = ["terminal", "ftp", "scp", "sftp", "tui"] +license = "MIT" readme = "README.md" -rust-version = "1.89.0" +repository = "https://github.com/veeso/termscp" +rust-version = "1.98.0" +description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/Kube/S3/WebDAV" [package.metadata.rpm] package = "termscp" @@ -62,18 +62,10 @@ remotefs = "0.3" remotefs-aws-s3 = "0.4" remotefs-kube = "0.4" remotefs-smb = { version = "0.3", optional = true } -remotefs-ssh = { version = "0.8", default-features = false, features = [ - "russh", -] } +remotefs-ssh = { version = "0.8", default-features = false, features = ["russh"] } remotefs-webdav = "0.2" rpassword = "7" -self_update = { version = "0.42", default-features = false, features = [ - "archive-tar", - "archive-zip", - "compression-flate2", - "compression-zip-deflate", - "rustls", -] } +self_update = { version = "0.42", default-features = false, features = ["archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate", "rustls"] } semver = "1" serde = { version = "1", features = ["derive"] } shellexpand = "3" @@ -91,16 +83,10 @@ whoami = "2" wildmatch = "2" [target."cfg(any(target_os = \"linux\", target_os = \"freebsd\"))".dependencies] -dbus-secret-service-keyring-store = { version = "1", features = [ - "crypto-rust", - "vendored", -] } +dbus-secret-service-keyring-store = { version = "1", features = ["crypto-rust", "vendored"] } [target."cfg(target_family = \"unix\")".dependencies] -remotefs-ftp = { version = "0.4", features = [ - "native-tls", - "native-tls-vendored", -] } +remotefs-ftp = { version = "0.4", features = ["native-tls", "native-tls-vendored"] } uzers = "0.12" [target."cfg(target_family = \"windows\")".dependencies] diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..833bb8f --- /dev/null +++ b/Justfile @@ -0,0 +1,10 @@ +import "./just/build.just" +import "./just/changelog.just" +import "./just/code_check.just" +import "./just/publish.just" +import "./just/site.just" +import "./just/test.just" + +# Lists all the available commands +default: + @just --list diff --git a/README.md b/README.md index cc80fd9..1bae905 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Termscp is a feature rich terminal file transfer and explorer, with support for ## Features 🎁 -- 📁 Different communication protocols +- 📁 Different communication protocols - **SFTP** - **SCP** - **FTP** and **FTPS** @@ -51,31 +51,31 @@ Termscp is a feature rich terminal file transfer and explorer, with support for - **S3** - **SMB** - **WebDAV** -- 🖥 Explore and operate on the remote and on the local machine file system with a handy UI +- 🖥 Explore and operate on the remote and on the local machine file system with a handy UI - Create, remove, rename, search, view and edit files -- ⭐ Connect to your favourite hosts through built-in bookmarks and recent connections -- 📝 View and edit files with your favourite applications -- 💁 SFTP/SCP authentication with SSH keys and username/password -- 🐧 Compatible with Windows, Linux, FreeBSD, NetBSD and MacOS -- 🐚 Embedded terminal for executing commands on the system. -- 🎨 Make it yours! +- ⭐ Connect to your favourite hosts through built-in bookmarks and recent connections +- 📝 View and edit files with your favourite applications +- 💁 SFTP/SCP authentication with SSH keys and username/password +- 🐧 Compatible with Windows, Linux, FreeBSD, NetBSD and MacOS +- 🐚 Embedded terminal for executing commands on the system. +- 🎨 Make it yours! - Themes - Custom file explorer format - Customizable text editor - Customizable file sorting - and many other parameters... -- 📫 Get notified via Desktop Notifications when a large file has been transferred -- 🔭 Keep file changes synchronized with the remote host -- 🔐 Save your password in your operating system key vault -- 🦀 Rust-powered -- 👀 Developed keeping an eye on performance -- 🦄 Frequent awesome updates +- 📫 Get notified via Desktop Notifications when a large file has been transferred +- 🔭 Keep file changes synchronized with the remote host +- 🔐 Save your password in your operating system key vault +- 🦀 Rust-powered +- 👀 Developed keeping an eye on performance +- 🦄 Frequent awesome updates --- ## Get started 🚀 -If you're considering to install termscp I want to thank you 💜 ! I hope you will enjoy termscp! +If you're considering to install termscp I want to thank you 💜 ! I hope you will enjoy termscp!\ If you want to contribute to this project, don't forget to check out our [contribute guide](CONTRIBUTING.md). If you are a Linux, a FreeBSD or a MacOS user this simple shell script will install termscp on your system with a single command: @@ -131,10 +131,10 @@ These requirements are not forced required to run termscp, but to enjoy all of i - **Linux/FreeBSD** users: - To **open** files via `V` (at least one of these) - - *xdg-open* - - *gio* - - *gnome-open* - - *kde-open* + - _xdg-open_ + - _gio_ + - _gnome-open_ + - _kde-open_ - **Linux** users: - A keyring manager: read more in the [User manual](https://docs.termscp.rs/en-US/configuration/password-security.html#linux-keyring) - **WSL** users diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..54f86f4 --- /dev/null +++ b/deny.toml @@ -0,0 +1,92 @@ +# cargo-deny configuration for termscp. +# +# Run locally with `just deny`. The same command runs in CI and enforces +# advisories, licenses, bans, and dependency sources. +# +# Docs: https://embarkstudios.github.io/cargo-deny/ + +[graph] +all-features = true + +[output] +feature-depth = 1 + +[advisories] +db-urls = ["https://github.com/rustsec/advisory-db"] +yanked = "deny" +unmaintained = "all" +# These are temporary, reachable transitive-risk acceptances owned by the +# termscp maintainers. Review or remove every exception by 2026-12-31. The +# upgrade target is a remotefs/self_update release line that accepts each fixed +# dependency version; until then, connections to untrusted endpoints remain an +# acknowledged denial-of-service and certificate-validation risk. +ignore = [ + # h2 0.3 is retained transitively by the reqwest 0.11/AWS HTTP stack; its + # dependency constraints cannot select the fixed h2 0.4 release. + { id = "RUSTSEC-2026-0258", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade reqwest/AWS stack to fixed h2" }, + # number_prefix is retained by self_update through indicatif. It is + # unmaintained, but has no reported vulnerability or compatible replacement. + { id = "RUSTSEC-2025-0119", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade self_update/indicatif" }, + # Both affected quick-xml lines are constrained by remotefs-webdav and + # self_update to versions older than the fixed 0.41 release. + { id = "RUSTSEC-2026-0194", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade remotefs-webdav/self_update to quick-xml >=0.41" }, + { id = "RUSTSEC-2026-0195", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade remotefs-webdav/self_update to quick-xml >=0.41" }, + # The SSH stack requires RSA, for which RustSec reports no safe upgrade. + { id = "RUSTSEC-2023-0071", reason = "owner: termscp maintainers; review by 2026-12-31; replace RSA dependency when upstream fix exists" }, + # russh-keys retains an older russh-cryptovec line and cannot select the + # fixed 0.60.3 release without an upstream dependency update. + { id = "RUSTSEC-2026-0153", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade remotefs-ssh/russh-keys to russh-cryptovec >=0.60.3" }, + # rustls-pemfile is retained by the WebDAV and Kubernetes clients. Both + # versions are unmaintained but have no reported vulnerability. + { id = "RUSTSEC-2025-0134", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade WebDAV/Kubernetes clients away from rustls-pemfile" }, + # The AWS HTTP stack retains rustls 0.21 and rustls-webpki 0.101, which cannot + # select the fixed rustls-webpki release. + { id = "RUSTSEC-2026-0098", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade AWS HTTP stack to fixed rustls-webpki" }, + { id = "RUSTSEC-2026-0099", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade AWS HTTP stack to fixed rustls-webpki" }, + { id = "RUSTSEC-2026-0104", reason = "owner: termscp maintainers; review by 2026-12-31; upgrade AWS HTTP stack to fixed rustls-webpki" }, +] + +[licenses] +allow = [ + "Apache-2.0", + "BSD-1-Clause", + "BSD-2-Clause", + "BSD-3-Clause", + "BSL-1.0", + "CC0-1.0", + "CDLA-Permissive-2.0", + "ISC", + "MIT", + "MIT-0", + "MPL-2.0", + "Unicode-3.0", + "Unlicense", + "Zlib", +] +confidence-threshold = 0.8 +exceptions = [ + # The WebDAV backend currently depends on rustydav, which declares GPL-3.0. + # Keep this exception crate-scoped so no other GPL dependency is admitted. + { allow = ["GPL-3.0"], crate = "rustydav" }, +] + +[licenses.private] +ignore = false + +[bans] +multiple-versions = "warn" +wildcards = "deny" +allow-wildcard-paths = true +highlight = "all" +workspace-default-features = "allow" +external-default-features = "allow" +allow = [] +deny = [] +skip = [] +skip-tree = [] + +[sources] +unknown-registry = "deny" +unknown-git = "deny" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] diff --git a/dist/release/bump_version.sh b/dist/release/bump_version.sh index f30ff8c..5317b52 100755 --- a/dist/release/bump_version.sh +++ b/dist/release/bump_version.sh @@ -4,6 +4,10 @@ set -euo pipefail VERSION="${1:?usage: bump_version.sh [date] [root]}" +if [[ ! "$VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "invalid release version: $VERSION (expected MAJOR.MINOR.PATCH)" >&2 + exit 2 +fi DATE="${2:-$(date +%F)}" ROOT="${3:-$(git rev-parse --show-toplevel)}" diff --git a/docs/en-US/configuration/themes.md b/docs/en-US/configuration/themes.md index 6e0a6ab..fe53368 100644 --- a/docs/en-US/configuration/themes.md +++ b/docs/en-US/configuration/themes.md @@ -47,20 +47,20 @@ are two quick fixes: 1. Re-import the official theme. After each release the official themes are patched, so download the updated theme from the repository and re-import it: - ```sh - termscp theme - ``` + ```sh + termscp theme + ``` 2. Edit your theme by hand. If you use a custom theme, edit the file and add the missing key. The theme is located at `$CONFIG_DIR/theme.toml`, where `$CONFIG_DIR` is: - - FreeBSD/Linux: `$HOME/.config/termscp` - - macOS: `$HOME/.config/termscp` - - Windows: `%USERPROFILE%\.termscp` + - FreeBSD/Linux: `$HOME/.config/termscp` + - macOS: `$HOME/.config/termscp` + - Windows: `%USERPROFILE%\.termscp` - Missing keys are reported in the CHANGELOG under `BREAKING CHANGES` for the - version you have just installed. + Missing keys are reported in the CHANGELOG under `BREAKING CHANGES` for the + version you have just installed. ## Styles diff --git a/docs/en-US/developer/developer.md b/docs/en-US/developer/developer.md index d41b0a0..6d5f5af 100644 --- a/docs/en-US/developer/developer.md +++ b/docs/en-US/developer/developer.md @@ -63,8 +63,8 @@ works best from different frameworks: more, read . - **Components**: components are built around tui in order to reuse widgets. This is achieved through the `Component` trait, inspired by - [React](https://reactjs.org/). Each component has its *Properties* and can have - its *States*. Each component must handle input events, accept new properties, + [React](https://reactjs.org/). Each component has its _Properties_ and can have + its _States_. Each component must handle input events, accept new properties, and provide a method to **render** itself. This logic now lives in [tui-realm](https://github.com/veeso/tui-realm). - **Messages: an Elm-based approach**: input events are handled with an approach diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index 2174d09..29ef5b8 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -43,7 +43,7 @@ termscp 是一个功能丰富的终端文件浏览和传输工具,支持 SCP/S ## 特性 🎁 -- 📁 支持多种通信协议 +- 📁 支持多种通信协议 - **SFTP** - **SCP** - **FTP** 和 **FTPS** @@ -51,31 +51,31 @@ termscp 是一个功能丰富的终端文件浏览和传输工具,支持 SCP/S - **S3** - **SMB** - **WebDAV** -- 🖥 使用便捷的 UI 在远程和本地文件系统上浏览和操作 +- 🖥 使用便捷的 UI 在远程和本地文件系统上浏览和操作 - 创建、删除、重命名、搜索、查看和编辑文件 -- ⭐ 通过“内置书签”和“最近连接”快速连接到您喜爱的主机 -- 📝 使用您喜欢的应用程序查看和编辑文件 -- 💁 使用 SSH 密钥和用户名/密码进行 SFTP/SCP 身份验证 -- 🐧 兼容 Windows、Linux、FreeBSD、NetBSD 和 MacOS 操作系统 -- 🐚 内置终端,可在系统上执行命令。 -- 🎨 丰富的个性化设置! +- ⭐ 通过“内置书签”和“最近连接”快速连接到您喜爱的主机 +- 📝 使用您喜欢的应用程序查看和编辑文件 +- 💁 使用 SSH 密钥和用户名/密码进行 SFTP/SCP 身份验证 +- 🐧 兼容 Windows、Linux、FreeBSD、NetBSD 和 MacOS 操作系统 +- 🐚 内置终端,可在系统上执行命令。 +- 🎨 丰富的个性化设置! - 主题 - 自定义文件浏览器格式 - 可自定义的文本编辑器 - 可自定义的文件排序 - 以及许多其他参数... -- 📫 传输大文件时通过桌面通知获得提醒 -- 🔭 与远程主机文件更改保持同步 -- 🔐 将密码保存在操作系统密钥保管库中 -- 🦀 由 Rust 提供强力支持 -- 👀 开发时更注重性能 -- 🦄 频繁的精彩更新 +- 📫 传输大文件时通过桌面通知获得提醒 +- 🔭 与远程主机文件更改保持同步 +- 🔐 将密码保存在操作系统密钥保管库中 +- 🦀 由 Rust 提供强力支持 +- 👀 开发时更注重性能 +- 🦄 频繁的精彩更新 --- ## 开始 🚀 -如果您正在考虑安装 termscp,我想对您表示感谢 💜 ! 希望您会喜欢 termscp! +如果您正在考虑安装 termscp,我想对您表示感谢 💜 ! 希望您会喜欢 termscp!\ 如果您想为此项目做出贡献,请不要忘记查看我们的[贡献指南](CONTRIBUTING.md)。 如果您是 Linux、FreeBSD 或 MacOS 用户,使用以下简单的 shell 脚本即可通过单行指令在您的系统上安装 termscp: @@ -131,10 +131,10 @@ pacman -S termscp - **Linux/FreeBSD** 用户: - 用 `V` **打开**文件(至少其中之一) - - *xdg-open* - - *gio* - - *gnome-open* - - *kde-open* + - _xdg-open_ + - _gio_ + - _gnome-open_ + - _kde-open_ - **Linux** 用户: - 密钥环管理器:在[用户手册](https://docs.termscp.rs/zh-CN/configuration/password-security.html#linux-密钥环)中阅读更多内容 - **WSL** 用户 diff --git a/docs/zh-CN/cli/cli.md b/docs/zh-CN/cli/cli.md index 826aa8c..b032e27 100644 --- a/docs/zh-CN/cli/cli.md +++ b/docs/zh-CN/cli/cli.md @@ -18,16 +18,16 @@ termscp [options]... -b [bookmark-name] -b [bookmark-name] [local-wrkdir] ## 选项 -| Key | 说明 | -| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| `-b ` | 将位置地址参数解析为书签名称。重复使用该标志可以打开多个书签。 | -| `-D` | 启用 `TRACE` 日志级别(调试/详细日志)。 | -| `-P ` | 从命令行提供密码。重复使用该标志可为多个远程主机提供密码;其顺序必须与地址参数一致。不推荐使用。 | -| `-q` | 禁用日志记录。 | -| `-T ` | 设置 UI 的 tick 间隔(以毫秒为单位)。默认值为 `10`。 | -| `--wno-keyring` | 禁用系统 keyring 支持。 | -| `-v` | 打印版本信息。 | -| `--help` | 打印帮助页面。 | +| Key | 说明 | +| -------------------- | ------------------------------------------------ | +| `-b ` | 将位置地址参数解析为书签名称。重复使用该标志可以打开多个书签。 | +| `-D` | 启用 `TRACE` 日志级别(调试/详细日志)。 | +| `-P ` | 从命令行提供密码。重复使用该标志可为多个远程主机提供密码;其顺序必须与地址参数一致。不推荐使用。 | +| `-q` | 禁用日志记录。 | +| `-T ` | 设置 UI 的 tick 间隔(以毫秒为单位)。默认值为 `10`。 | +| `--wno-keyring` | 禁用系统 keyring 支持。 | +| `-v` | 打印版本信息。 | +| `--help` | 打印帮助页面。 | 不推荐使用 `-P` 选项,因为密码可能会保留在 shell 历史记录中。请参阅书签和密码安全章节,了解更安全的凭据提供方式。 diff --git a/docs/zh-CN/configuration/explorer-format.md b/docs/zh-CN/configuration/explorer-format.md index a3ccc23..3660ad4 100644 --- a/docs/zh-CN/configuration/explorer-format.md +++ b/docs/zh-CN/configuration/explorer-format.md @@ -20,18 +20,18 @@ 以下是格式化器支持的键: -| 键 | 说明 | -| --------- | ------------------------------------------------------------------------------------------------ | -| `ATIME` | 最后访问时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{ATIME:8:%H:%M}`) | -| `CTIME` | 创建时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{CTIME:8:%H:%M}`) | -| `GROUP` | 所属组 | -| `MTIME` | 最后修改时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{MTIME:8:%H:%M}`) | -| `NAME` | 文件名(如果根目录与首个祖先目录之间的文件夹长度超过 `LENGTH`,则会被省略) | -| `PATH` | 文件绝对路径(如果根目录与首个祖先目录之间的文件夹长度超过 `LENGTH`,则会被省略) | -| `PEX` | 文件权限(UNIX 格式) | -| `SIZE` | 文件大小(目录省略) | -| `SYMLINK` | 符号链接目标(如果有,`-> {FILE_PATH}`) | -| `USER` | 所属用户 | +| 键 | 说明 | +| --------- | --------------------------------------------------------------- | +| `ATIME` | 最后访问时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{ATIME:8:%H:%M}`) | +| `CTIME` | 创建时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{CTIME:8:%H:%M}`) | +| `GROUP` | 所属组 | +| `MTIME` | 最后修改时间(默认 `%b %d %Y %H:%M`);`EXTRA` 为时间格式(例如 `{MTIME:8:%H:%M}`) | +| `NAME` | 文件名(如果根目录与首个祖先目录之间的文件夹长度超过 `LENGTH`,则会被省略) | +| `PATH` | 文件绝对路径(如果根目录与首个祖先目录之间的文件夹长度超过 `LENGTH`,则会被省略) | +| `PEX` | 文件权限(UNIX 格式) | +| `SIZE` | 文件大小(目录省略) | +| `SYMLINK` | 符号链接目标(如果有,`-> {FILE_PATH}`) | +| `USER` | 所属用户 | ## 默认格式 diff --git a/docs/zh-CN/configuration/themes.md b/docs/zh-CN/configuration/themes.md index 78b4ada..cec6dfb 100644 --- a/docs/zh-CN/configuration/themes.md +++ b/docs/zh-CN/configuration/themes.md @@ -36,17 +36,17 @@ termscp 接受以下颜色格式: 1. 重新导入官方主题。每次发布后,官方主题都会被修补,因此从仓库下载更新后的主题并重新导入: - ```sh - termscp theme - ``` + ```sh + termscp theme + ``` 2. 手动编辑你的主题。如果你使用自定义主题,请编辑该文件并添加缺失的键。主题位于 `$CONFIG_DIR/theme.toml`,其中 `$CONFIG_DIR` 为: - - FreeBSD/Linux:`$HOME/.config/termscp` - - macOS:`$HOME/.config/termscp` - - Windows:`%USERPROFILE%\.termscp` + - FreeBSD/Linux:`$HOME/.config/termscp` + - macOS:`$HOME/.config/termscp` + - Windows:`%USERPROFILE%\.termscp` - 缺失的键会在你刚安装的版本的 CHANGELOG 中 `BREAKING CHANGES` 部分列出。 + 缺失的键会在你刚安装的版本的 CHANGELOG 中 `BREAKING CHANGES` 部分列出。 ## 样式 @@ -54,44 +54,44 @@ termscp 接受以下颜色格式: ### 认证页面 -| 键 | 说明 | -| ---------------- | -------------------------- | -| `auth_address` | IP 地址输入框的颜色 | -| `auth_bookmarks` | 书签面板的颜色 | -| `auth_password` | 密码输入框的颜色 | -| `auth_port` | 端口号输入框的颜色 | -| `auth_protocol` | 协议单选框组的颜色 | -| `auth_recents` | 最近记录面板的颜色 | -| `auth_username` | 用户名输入框的颜色 | +| 键 | 说明 | +| ---------------- | ----------- | +| `auth_address` | IP 地址输入框的颜色 | +| `auth_bookmarks` | 书签面板的颜色 | +| `auth_password` | 密码输入框的颜色 | +| `auth_port` | 端口号输入框的颜色 | +| `auth_protocol` | 协议单选框组的颜色 | +| `auth_recents` | 最近记录面板的颜色 | +| `auth_username` | 用户名输入框的颜色 | ### 传输页面 -| 键 | 说明 | -| -------------------------------------- | -------------------------------------------------- | -| `transfer_local_explorer_background` | 本地主机浏览器的背景色 | -| `transfer_local_explorer_foreground` | 本地主机浏览器的前景色 | -| `transfer_local_explorer_highlighted` | 本地主机浏览器的边框及高亮颜色 | -| `transfer_remote_explorer_background` | 远程浏览器的背景色 | -| `transfer_remote_explorer_foreground` | 远程浏览器的前景色 | -| `transfer_remote_explorer_highlighted` | 远程浏览器的边框及高亮颜色 | -| `transfer_log_background` | 日志面板的背景色 | -| `transfer_log_window` | 日志面板的窗口颜色 | -| `transfer_progress_bar_partial` | 部分进度条的颜色 | -| `transfer_progress_bar_total` | 总进度条的颜色 | -| `transfer_status_hidden` | 状态栏 "hidden" 标签的颜色 | +| 键 | 说明 | +| -------------------------------------- | ------------------------------- | +| `transfer_local_explorer_background` | 本地主机浏览器的背景色 | +| `transfer_local_explorer_foreground` | 本地主机浏览器的前景色 | +| `transfer_local_explorer_highlighted` | 本地主机浏览器的边框及高亮颜色 | +| `transfer_remote_explorer_background` | 远程浏览器的背景色 | +| `transfer_remote_explorer_foreground` | 远程浏览器的前景色 | +| `transfer_remote_explorer_highlighted` | 远程浏览器的边框及高亮颜色 | +| `transfer_log_background` | 日志面板的背景色 | +| `transfer_log_window` | 日志面板的窗口颜色 | +| `transfer_progress_bar_partial` | 部分进度条的颜色 | +| `transfer_progress_bar_total` | 总进度条的颜色 | +| `transfer_status_hidden` | 状态栏 "hidden" 标签的颜色 | | `transfer_status_sorting` | 状态栏 "sorting" 标签的颜色;也适用于文件排序对话框 | -| `transfer_status_sync_browsing` | 状态栏 "sync browsing" 标签的颜色 | +| `transfer_status_sync_browsing` | 状态栏 "sync browsing" 标签的颜色 | ### 杂项 这些样式适用于应用程序的不同部分。 -| 键 | 说明 | -| ------------------- | -------------------------------- | -| `misc_error_dialog` | 错误消息的颜色 | -| `misc_info_dialog` | 信息对话框的颜色 | +| 键 | 说明 | +| ------------------- | ---------------- | +| `misc_error_dialog` | 错误消息的颜色 | +| `misc_info_dialog` | 信息对话框的颜色 | | `misc_input_dialog` | 输入对话框的颜色(例如复制文件) | -| `misc_keys` | 按键文本的颜色 | -| `misc_quit_dialog` | 退出对话框的颜色 | -| `misc_save_dialog` | 保存对话框的颜色 | -| `misc_warn_dialog` | 警告对话框的颜色 | +| `misc_keys` | 按键文本的颜色 | +| `misc_quit_dialog` | 退出对话框的颜色 | +| `misc_save_dialog` | 保存对话框的颜色 | +| `misc_warn_dialog` | 警告对话框的颜色 | diff --git a/docs/zh-CN/developer/developer.md b/docs/zh-CN/developer/developer.md index 14e7a9b..0f29ccd 100644 --- a/docs/zh-CN/developer/developer.md +++ b/docs/zh-CN/developer/developer.md @@ -30,7 +30,7 @@ termscp 支持以下协议:SFTP、SCP、FTP/FTPS、Kube、S3、SMB 和 WebDAV - **顶层的 Activities**:每个“视图”都是一个 Activity,并由 `Activity Manager` 来处理它们。这种方法受 Android 启发。它适用于具有不同视图的 ui,每个视图都有自己的组件和逻辑。Activities 与 `Context` 协作,`Context` 是用于在 activities 之间共享数据的数据持有者。 - **Activities 显示 Applications**:每个 activity 可以显示不同的 **Applications**。一个 application 包含一个 **View**,它基本上是一个 **components** 列表,每个组件都有其属性。view 是组件的门面,同时也处理焦点,即当前处于活动状态的组件。你不能拥有多个活动组件,因此必须对此进行处理;与此同时,如果当前组件被销毁,焦点必须交还给之前处于活动状态的组件。**Application** 负责处理所有这些工作。要了解更多信息,请阅读 。 -- **Components**:components 是围绕 tui 构建的,以便复用控件。这是通过 `Component` trait 实现的,该 trait 受 [React](https://reactjs.org/) 启发。每个组件都有其 *Properties*,并且可以拥有其 *States*。每个组件必须处理输入事件、接受新的属性,并提供一个用于**渲染**自身的方法。这一逻辑现在位于 [tui-realm](https://github.com/veeso/tui-realm) 中。 +- **Components**:components 是围绕 tui 构建的,以便复用控件。这是通过 `Component` trait 实现的,该 trait 受 [React](https://reactjs.org/) 启发。每个组件都有其 _Properties_,并且可以拥有其 _States_。每个组件必须处理输入事件、接受新的属性,并提供一个用于**渲染**自身的方法。这一逻辑现在位于 [tui-realm](https://github.com/veeso/tui-realm) 中。 - **Messages:基于 Elm 的方法**:输入事件采用受 [Elm](https://elm-lang.org/) 启发的方法来处理。在 Elm 中,你使用三个基本函数来实现 ui:**update**、**view** 和 **init**。termscp 将 Elm update 函数的等价实现编写为一个递归函数内部的大型 match 分支,你可以在每个 activity 的 `update.rs` 文件中找到它。这个 match 分支处理组件为响应传入的输入事件而产生的消息,并促使 activity 改变其状态。 termscp 实现了一个名为 `Activity` 的 trait,它是 Android activity 的一个大幅精简版本。该 trait 提供以下方法: diff --git a/docs/zh-CN/usage/keyboard-shortcuts.md b/docs/zh-CN/usage/keyboard-shortcuts.md index 46e8ef1..de1bc9c 100644 --- a/docs/zh-CN/usage/keyboard-shortcuts.md +++ b/docs/zh-CN/usage/keyboard-shortcuts.md @@ -2,48 +2,48 @@ 以下按键可在文件浏览器中使用。随时按 `` 可打开应用内帮助。 -| 按键 | 操作 | -| -------------- | ---------------------------------------------------------------------- | -| `` | 断开与远程的连接并返回认证页面 | -| `` | 返回导航栈中的上一个目录 | -| `` | 切换当前活动的浏览器选项卡 | -| `` | 移动到远程浏览器选项卡 | -| `` | 移动到本地浏览器选项卡 | -| `` | 在所选列表中向上移动 | -| `` | 在所选列表中向下移动 | -| `` | 在所选列表中向上移动 8 行 | -| `` | 在所选列表中向下移动 8 行 | -| `` | 进入所选目录 | -| `` | 上传或下载所选文件 | -| `` | 在日志选项卡与浏览器之间切换 | -| `` | 切换是否显示隐藏文件 | -| `` | 选择文件的排序方式 | -| `` | 复制所选文件或目录 | -| `` | 新建目录 | -| `` | 删除所选文件 | -| `` | 搜索文件(支持通配符匹配) | -| `` | 跳转到指定路径 | -| `` | 显示帮助 | -| `` | 显示所选文件或目录的信息 | -| `` | 创建指向当前所选条目的符号链接 | -| `` | 重新加载当前目录的内容,或清除当前选择 | -| `` | 选择一个文件 | -| `` | 使用提供的名称创建新文件 | -| `` | 在文本编辑器中编辑所选文件 | -| `

` | 打开日志面板 | +| `` | 退出 termscp | +| `` | 重命名所选文件 | +| `` | 将所选文件另存为新名称 | +| `` | 将所选路径上的更改同步到远程 | +| `` | 进入上级目录 | +| `` | 使用该文件类型的默认程序打开所选文件 | +| `` | 使用你指定的程序打开所选文件 | +| `` | 执行命令 | +| `` | 切换同步浏览 | +| `` | 更改文件模式 | +| `` | 过滤文件(同时支持正则表达式和通配符匹配) | +| `` | 选择所有文件 | +| `` | 取消选择所有文件 | +| `` | 中止文件传输过程 | +| `` | 获取所选路径的总大小 | +| `` | 显示所有已同步的路径 | diff --git a/dprint.json b/dprint.json new file mode 100644 index 0000000..892c8d2 --- /dev/null +++ b/dprint.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://dprint.dev/schemas/v0.json", + "indentWidth": 2, + "lineWidth": 80, + "newLineKind": "lf", + "markdown": { + "textWrap": "maintain" + }, + "toml": {}, + "yaml": {}, + "exec": { + "cwd": "${configDir}", + "commands": [ + { + "command": "rustup run nightly rustfmt --edition 2024", + "exts": ["rs"], + "cacheKeyFiles": ["rustfmt.toml", "rust-toolchain.toml"] + } + ] + }, + "excludes": [ + "**/target", + "**/node_modules", + "**/*-lock.json", + "Cargo.lock", + "docs/book", + "docs/zh-CN/cli/cli.md", + "docs/zh-CN/configuration/explorer-format.md", + "docs/zh-CN/configuration/themes.md", + "docs/zh-CN/usage/keyboard-shortcuts.md", + "**/tests/fixtures" + ], + "plugins": [ + "https://plugins.dprint.dev/markdown-0.22.1.wasm@4906fbb038977732aae0e216e4b0b957e2722f8d79282660ccb36d27dd051f17", + "https://plugins.dprint.dev/toml-0.7.0.wasm@0126c8112691542d30b52a639076ecc83e07bace877638cee7c6915fd36b8629", + "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.6.0.wasm@40a2fdda7040317eb1b23520f3a00769a5571eedb049c4ca9175c1b9eeba01ae", + "https://plugins.dprint.dev/dprint/exec-0.6.2.json@df98f54ffd3092b8a841aedd6d098a2651f16d0a796a40535774f1a8b4b9d463" + ] +} diff --git a/just/build.just b/just/build.just new file mode 100644 index 0000000..4983419 --- /dev/null +++ b/just/build.just @@ -0,0 +1,34 @@ +# Build everything +[group('build')] +build_all: build_crates + +# Build the Rust crate +[group('build')] +build_crates args="": + cargo build --workspace {{ args }} + +# Build all Rust crates in release mode +[group('build')] +build_crates_release: + just build_crates "--release" + +# Build a release binary for a target triple +[group('build')] +build_release target features="": + cargo build --locked --release --target {{ target }} {{ features }} + +# Package an already-built Linux release as a Debian package +[group('build')] +package_deb target: + cargo deb --no-build --target {{ target }} --features smb-vendored + +# Update Cargo.lock; pass cargo update arguments to scope the update. +[group('build')] +update_lock args="": + cargo update {{ args }} + +# Clean build artifacts +[group('build')] +[confirm("Are you sure you want to clean the build artifacts?")] +clean: + cargo clean diff --git a/just/changelog.just b/just/changelog.just new file mode 100644 index 0000000..1159e6f --- /dev/null +++ b/just/changelog.just @@ -0,0 +1,44 @@ +set positional-arguments + +# Print the unreleased changelog section to stdout (preview only), e.g. `just changelog_preview 8.1.0` +[group('changelog')] +changelog_preview version: + git-cliff --config cliff.toml --unreleased --tag "v$1" --strip all + +# Add a new entry to CHANGELOG.md from the unreleased conventional commits, e.g. `just changelog 8.1.0` +[group('changelog')] +changelog version: + #!/usr/bin/env bash + set -euo pipefail + version="$1" + if [[ ! "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + echo "invalid release version: $version (expected MAJOR.MINOR.PATCH)" >&2 + exit 2 + fi + tag="v$version" + section="$(git-cliff --config cliff.toml --unreleased --tag "$tag" --strip all)" + if [ -z "$section" ]; then + echo "No unreleased conventional commits found; nothing to add." >&2 + exit 0 + fi + anchor="$(printf '%s' "$version" | tr -d '.')" + secfile="$(mktemp)" + tmp="$(mktemp)" + printf '%s\n' "$section" > "$secfile" + # Insert a TOC entry before the first existing version entry and the rendered + # section before the first existing version heading, keeping the title + TOC. + awk -v secfile="$secfile" -v ver="$version" -v anc="$anchor" ' + !toc_done && /^[[:space:]]*- \[[0-9]/ { + print " - [" ver "](#" anc ")" + toc_done = 1 + } + !body_done && /^## [0-9]/ { + while ((getline line < secfile) > 0) print line + print "" + body_done = 1 + } + { print } + ' CHANGELOG.md > "$tmp" + rm -f "$secfile" + mv "$tmp" CHANGELOG.md + echo "CHANGELOG.md updated for $tag" diff --git a/just/code_check.just b/just/code_check.just new file mode 100644 index 0000000..5dd4cdb --- /dev/null +++ b/just/code_check.just @@ -0,0 +1,57 @@ +alias lint := clippy + +# Format all sources (Markdown, TOML, YAML and Rust via nightly rustfmt) with dprint +[group('code_check')] +fmt args="": + dprint fmt {{ args }} + +# Check formatting of all sources with dprint (no writes) +[group('code_check')] +fmt_check args="": + dprint check {{ args }} + +# Run clippy on all targets +[group('code_check')] +clippy args="": + cargo clippy --workspace --all-targets {{ args }} + +# Build the crate documentation, denying warnings +[group('code_check')] +doc args="": + RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps {{ args }} + +# Check dependencies for advisories, licenses, bans and sources (cargo-deny) +[group('code_check')] +deny args="": + cargo deny check {{ args }} + +# Scan for secrets with trufflehog (defaults to the whole working tree) +[group('code_check')] +scan_secrets *args=".": + trufflehog filesystem {{ args }} --results=verified,unknown --fail --no-update + +# Point git at the tracked .githooks directory (installs the pre-commit hook) +[group('code_check')] +setup_githooks: + git config core.hooksPath .githooks + @echo "git hooks installed: core.hooksPath = .githooks" + +# Lint the install scripts (shellcheck; PowerShell parse when pwsh is available) +[group('code_check')] +check_install_scripts: + sh -n install.sh + shellcheck install.sh + @if command -v pwsh >/dev/null 2>&1; then \ + pwsh -NoProfile -Command '$t = $null; $e = $null; $null = [System.Management.Automation.Language.Parser]::ParseFile("install.ps1", [ref]$t, [ref]$e); if ($e) { $e; exit 1 }'; \ + else \ + echo "pwsh not found: skipping install.ps1 parse check"; \ + fi + +# Run all code checks. Fails if any check fails +[group('code_check')] +check_code: + just fmt_check + just clippy "-- -D warnings" + just doc + just deny + just check_install_scripts diff --git a/just/publish.just b/just/publish.just new file mode 100644 index 0000000..8558634 --- /dev/null +++ b/just/publish.just @@ -0,0 +1,4 @@ +# Publish the termscp crate +[group('publish')] +publish_crate args="": + cargo publish --locked --features smb-vendored {{ args }} diff --git a/just/site.just b/just/site.just new file mode 100644 index 0000000..36c3e75 --- /dev/null +++ b/just/site.just @@ -0,0 +1,28 @@ +# Install website dependencies from the lockfile +[group('site')] +site_install: + cd site && npm ci + +# Check website formatting +[group('site')] +site_fmt_check: + cd site && npm run format:check + +# Run Astro and TypeScript checks +[group('site')] +site_check: + cd site && npm run check + +# Run website tests when the package defines them +[group('site')] +site_test: + cd site && npm test --if-present + +# Build the website +[group('site')] +site_build: + cd site && npm run build + +# Run every website validation step +[group('site')] +site_ci: site_fmt_check site_check site_test site_build diff --git a/just/test.just b/just/test.just new file mode 100644 index 0000000..b62145d --- /dev/null +++ b/just/test.just @@ -0,0 +1,13 @@ +# Run all tests +[group('test')] +test_all: test + +# Run the Rust test suite +[group('test')] +test args="": + cargo test --workspace {{ args }} + +# Generate an LCOV code coverage report for the Rust workspace (requires cargo-llvm-cov) +[group('test')] +coverage output="lcov.info": + cargo llvm-cov --workspace --lcov --output-path {{ output }} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..a866dcb --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.98.0" +components = ["clippy", "rustfmt"] diff --git a/src/config/serialization.rs b/src/config/serialization.rs index d945e8c..7bab606 100644 --- a/src/config/serialization.rs +++ b/src/config/serialization.rs @@ -200,7 +200,7 @@ mod tests { .unwrap(), PathBuf::from("/home/omar/.ssh/beaglebone.key") ); - assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none()); + assert!(!cfg.remote.ssh_keys.contains_key("1.1.1.1")); } #[test] @@ -240,7 +240,7 @@ mod tests { .unwrap(), PathBuf::from("/home/omar/.ssh/beaglebone.key") ); - assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none()); + assert!(!cfg.remote.ssh_keys.contains_key("1.1.1.1")); } #[test] diff --git a/src/explorer.rs b/src/explorer.rs index b4285b5..862f817 100644 --- a/src/explorer.rs +++ b/src/explorer.rs @@ -401,7 +401,7 @@ mod tests { assert_eq!(explorer.dirstack.len(), 2); assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev")); assert_eq!( - *explorer.dirstack.get(0).unwrap(), + *explorer.dirstack.front().unwrap(), PathBuf::from("/home/omar") ); } @@ -425,7 +425,7 @@ mod tests { assert!(explorer.get(100).is_none()); //assert_eq!(explorer.count(), 6); // Verify (files are sorted by name) - assert_eq!(explorer.files.get(0).unwrap().name(), ".git"); + assert_eq!(explorer.files.first().unwrap().name(), ".git"); // Iter files (all) assert_eq!(explorer.iter_files_all().count(), 6); // Iter files (hidden excluded) (.git, .gitignore are hidden) @@ -453,7 +453,7 @@ mod tests { ]); explorer.sort_by(FileSorting::Name); // First entry should be "Cargo.lock" - assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock"); + assert_eq!(explorer.files.first().unwrap().name(), "Cargo.lock"); // Last should be "src" assert_eq!(explorer.files.get(8).unwrap().name(), "src"); } @@ -469,7 +469,7 @@ mod tests { explorer.set_files(vec![entry1, entry2]); explorer.sort_by(FileSorting::ModifyTime); // First entry should be "CODE_OF_CONDUCT.md" - assert_eq!(explorer.files.get(0).unwrap().name(), "CODE_OF_CONDUCT.md"); + assert_eq!(explorer.files.first().unwrap().name(), "CODE_OF_CONDUCT.md"); // Last should be "src" assert_eq!(explorer.files.get(1).unwrap().name(), "README.md"); } @@ -485,7 +485,7 @@ mod tests { explorer.set_files(vec![entry1, entry2]); explorer.sort_by(FileSorting::CreationTime); // First entry should be "CODE_OF_CONDUCT.md" - assert_eq!(explorer.files.get(0).unwrap().name(), "CODE_OF_CONDUCT.md"); + assert_eq!(explorer.files.first().unwrap().name(), "CODE_OF_CONDUCT.md"); // Last should be "src" assert_eq!(explorer.files.get(1).unwrap().name(), "README.md"); } @@ -501,7 +501,7 @@ mod tests { ]); explorer.sort_by(FileSorting::Size); // Directory has size 4096 - assert_eq!(explorer.files.get(0).unwrap().name(), "src"); + assert_eq!(explorer.files.first().unwrap().name(), "src"); assert_eq!(explorer.files.get(1).unwrap().name(), "README.md"); assert_eq!(explorer.files.get(2).unwrap().name(), "CONTRIBUTING.md"); } @@ -525,7 +525,7 @@ mod tests { explorer.sort_by(FileSorting::Name); explorer.group_dirs_by(Some(GroupDirs::First)); // First entry should be "docs" - assert_eq!(explorer.files.get(0).unwrap().name(), "docs"); + assert_eq!(explorer.files.first().unwrap().name(), "docs"); assert_eq!(explorer.files.get(1).unwrap().name(), "src"); // 3rd is file first for alphabetical order assert_eq!(explorer.files.get(2).unwrap().name(), "Cargo.lock"); @@ -555,7 +555,7 @@ mod tests { assert_eq!(explorer.files.get(8).unwrap().name(), "docs"); assert_eq!(explorer.files.get(9).unwrap().name(), "src"); // first is file for alphabetical order - assert_eq!(explorer.files.get(0).unwrap().name(), "Cargo.lock"); + assert_eq!(explorer.files.first().unwrap().name(), "Cargo.lock"); // Last in files should be "README.md" (last file for alphabetical ordening) assert_eq!(explorer.files.get(7).unwrap().name(), "README.md"); } diff --git a/src/host/localhost.rs b/src/host/localhost.rs index ae06fae..63e0ef3 100644 --- a/src/host/localhost.rs +++ b/src/host/localhost.rs @@ -602,9 +602,11 @@ mod tests { use pretty_assertions::assert_eq; use super::*; + use crate::utils::test_helpers::create_sample_file; + #[cfg(posix)] + use crate::utils::test_helpers::make_file_at; #[cfg(posix)] use crate::utils::test_helpers::make_fsentry; - use crate::utils::test_helpers::{create_sample_file, make_file_at}; #[test] fn test_host_error_new() { @@ -632,13 +634,13 @@ mod tests { #[test] #[cfg(win)] fn test_host_localhost_new() { - let mut host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap(); + let host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap(); assert_eq!(host.wrkdir, PathBuf::from("C:\\users")); // Scan dir let entries = std::fs::read_dir(PathBuf::from("C:\\users").as_path()).unwrap(); let mut counter: usize = 0; for _ in entries { - counter = counter + 1; + counter += 1; } assert_eq!(host.files.len(), counter); } @@ -769,7 +771,7 @@ mod tests { let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let files: Vec = host.files.clone(); // Verify files - let file_0: &File = files.get(0).unwrap(); + let file_0: &File = files.first().unwrap(); if file_0.name() == *"foo.txt" { assert!(file_0.metadata.symlink.is_none()); } else { @@ -827,7 +829,7 @@ mod tests { let files: Vec = host.files.clone(); assert_eq!(files.len(), 1); // There should be 1 file now // Remove file - assert!(host.remove(files.get(0).unwrap()).is_ok()); + assert!(host.remove(files.first().unwrap()).is_ok()); // There should be 0 files now let files: Vec = host.files.clone(); assert_eq!(files.len(), 0); // There should be 0 files now @@ -836,7 +838,7 @@ mod tests { // Delete directory let files: Vec = host.files.clone(); assert_eq!(files.len(), 1); // There should be 1 file now - assert!(host.remove(files.get(0).unwrap()).is_ok()); + assert!(host.remove(files.first().unwrap()).is_ok()); // Remove unexisting directory assert!( host.remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true)) @@ -859,22 +861,22 @@ mod tests { let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); let files: Vec = host.files.clone(); assert_eq!(files.len(), 1); // There should be 1 file now - assert_eq!(files.get(0).unwrap().name(), "foo.txt"); + assert_eq!(files.first().unwrap().name(), "foo.txt"); // Rename file let dst_path: PathBuf = PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str()); assert!( - host.rename(files.get(0).unwrap(), dst_path.as_path()) + host.rename(files.first().unwrap(), dst_path.as_path()) .is_ok() ); // There should be still 1 file now, but named bar.txt let files: Vec = host.files.clone(); assert_eq!(files.len(), 1); // There should be 0 files now - assert_eq!(files.get(0).unwrap().name(), "bar.txt"); + assert_eq!(files.first().unwrap().name(), "bar.txt"); // Fail let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu"); assert!( - host.rename(files.get(0).unwrap(), bad_path.as_path()) + host.rename(files.first().unwrap(), bad_path.as_path()) .is_err() ); } @@ -939,7 +941,7 @@ mod tests { file2_path.push("bar.txt"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let file1_entry: File = host.files.get(0).unwrap().clone(); + let file1_entry: File = host.files.first().unwrap().clone(); assert_eq!(file1_entry.name(), String::from("foo.txt")); // Copy assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); @@ -969,7 +971,7 @@ mod tests { let file2_path: PathBuf = PathBuf::from("bar.txt"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let file1_entry: File = host.files.get(0).unwrap().clone(); + let file1_entry: File = host.files.first().unwrap().clone(); assert_eq!(file1_entry.name(), String::from("foo.txt")); // Copy assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok()); @@ -989,7 +991,7 @@ mod tests { assert!(file1.write_all(b"Hello world!\n").is_ok()); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let file1_entry: File = host.files.get(0).unwrap().clone(); + let file1_entry: File = host.files.first().unwrap().clone(); assert_eq!(file1_entry.name(), String::from("foo.txt")); // Copy with empty destination -> must fail and leave file untouched assert!( @@ -1022,7 +1024,7 @@ mod tests { dir_dest.push("test_dest_dir/"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let dir_src_entry: File = host.files.get(0).unwrap().clone(); + let dir_src_entry: File = host.files.first().unwrap().clone(); assert_eq!(dir_src_entry.name(), String::from("test_dir")); // Copy assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); @@ -1052,7 +1054,7 @@ mod tests { let dir_dest: PathBuf = PathBuf::from("test_dest_dir/"); // Create host let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap(); - let dir_src_entry: File = host.files.get(0).unwrap().clone(); + let dir_src_entry: File = host.files.first().unwrap().clone(); assert_eq!(dir_src_entry.name(), String::from("test_dir")); // Copy assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok()); diff --git a/src/main.rs b/src/main.rs index 2d0e5ea..af93a51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -88,13 +88,7 @@ fn parse_args(args: Args) -> Result { // Match ticks run_opts.ticks = Duration::from_millis(args.ticks); // Remote argument - match RemoteArgs::try_from(&args) { - Err(err) => return Err(err), - Ok(remote) => { - // Set params - run_opts.remote = remote; - } - } + run_opts.remote = RemoteArgs::try_from(&args)?; // set activity based on remote state run_opts.task = if run_opts.remote.remote.is_none() { diff --git a/src/system/auto_update.rs b/src/system/auto_update.rs index 150218d..5b567b6 100644 --- a/src/system/auto_update.rs +++ b/src/system/auto_update.rs @@ -68,7 +68,8 @@ impl Update { } /// Returns whether a new version of termscp is available - /// In case of success returns Ok(Option), where the Option is Some(new_version); + /// In case of success returns `Ok(Option)`, where the option is + /// `Some(new_version)`; /// otherwise if no version is available, return None /// In case of error returns Error with the error description pub fn is_new_version_available() -> Result, UpdateError> { diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index e21cfcf..ae1d10f 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -767,7 +767,7 @@ mod tests { // Limit is 2 assert_eq!(client.iter_recents().count(), 2); // Check that 192.168.1.1 has been removed - let key: String = client.iter_recents().next().unwrap().to_string(); + let key: String = client.iter_recents().next().unwrap().clone(); assert!(matches!( client .hosts @@ -781,7 +781,7 @@ mod tests { .as_str(), "192.168.1.2" | "192.168.1.3" )); - let key: String = client.iter_recents().nth(1).unwrap().to_string(); + let key: String = client.iter_recents().nth(1).unwrap().clone(); assert!(matches!( client .hosts @@ -938,7 +938,7 @@ mod tests { let protocol = params.protocol; let p = params.params.generic_params().unwrap(); ( - p.address.to_string(), + p.address.clone(), p.port, protocol, p.username.as_ref().cloned().unwrap_or_default(), diff --git a/src/system/config_client.rs b/src/system/config_client.rs index 3b88ce4..dab6ff9 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -595,7 +595,7 @@ mod tests { client.set_local_file_fmt(String::from("{NAME}")); assert_eq!(client.get_local_file_fmt().unwrap(), String::from("{NAME}")); // Delete - client.set_local_file_fmt(String::from("")); + client.set_local_file_fmt(String::new()); assert_eq!(client.get_local_file_fmt(), None); } @@ -613,7 +613,7 @@ mod tests { String::from("{NAME}") ); // Delete - client.set_remote_file_fmt(String::from("")); + client.set_remote_file_fmt(String::new()); assert_eq!(client.get_remote_file_fmt(), None); } diff --git a/src/system/environment.rs b/src/system/environment.rs index d991a46..d42ec82 100644 --- a/src/system/environment.rs +++ b/src/system/environment.rs @@ -207,6 +207,7 @@ mod tests { let mut f: File = OpenOptions::new() .create(true) .write(true) + .truncate(true) .open(conf_dir.as_path()) .ok() .unwrap(); diff --git a/src/system/watcher.rs b/src/system/watcher.rs index b927f3b..bc74001 100644 --- a/src/system/watcher.rs +++ b/src/system/watcher.rs @@ -294,7 +294,7 @@ mod test { ); // unwatch assert!(watcher.unwatch(tempdir.path()).is_ok()); - assert!(watcher.paths.get(tempdir.path()).is_none()); + assert!(!watcher.paths.contains_key(tempdir.path())); // close tempdir assert!(tempdir.close().is_ok()); } @@ -315,7 +315,7 @@ mod test { watcher.unwatch(subdir.as_path()).unwrap().as_path(), Path::new(tempdir.path()) ); - assert!(watcher.paths.get(tempdir.path()).is_none()); + assert!(!watcher.paths.contains_key(tempdir.path())); // close tempdir assert!(tempdir.close().is_ok()); } diff --git a/src/ui/activities/filetransfer/lib/pane.rs b/src/ui/activities/filetransfer/lib/pane.rs index 1a65551..04df007 100644 --- a/src/ui/activities/filetransfer/lib/pane.rs +++ b/src/ui/activities/filetransfer/lib/pane.rs @@ -28,8 +28,6 @@ impl Pane { #[cfg(test)] mod tests { - use std::path::PathBuf; - use super::*; use crate::explorer::builder::FileExplorerBuilder; use crate::host::Localhost; @@ -52,6 +50,6 @@ mod tests { fn test_pane_pwd() { let mut pane = make_pane(); let pwd = pane.fs.pwd().unwrap(); - assert_eq!(pwd, PathBuf::from(std::env::temp_dir())); + assert_eq!(pwd, std::env::temp_dir()); } } diff --git a/src/ui/activities/filetransfer/session/transfer.rs b/src/ui/activities/filetransfer/session/transfer.rs index e60075e..2898683 100644 --- a/src/ui/activities/filetransfer/session/transfer.rs +++ b/src/ui/activities/filetransfer/session/transfer.rs @@ -211,7 +211,8 @@ impl FileTransferActivity { /// Shared scan walk. `remote_side` selects which pane lists directories /// (remote for downloads, local for uploads). /// - /// The walk is abortable via [`crate::ui::activities::filetransfer::lib::TransferStates::aborted`] + /// The walk is abortable via + /// [`TransferStates::aborted`](crate::ui::activities::filetransfer::lib::transfer::TransferStates::aborted) /// and periodically redraws a "Scanning…" popup to keep the UI responsive. fn scan_worklist( &mut self, diff --git a/src/ui/activities/setup/actions.rs b/src/ui/activities/setup/actions.rs index 1d6dd0c..baf6497 100644 --- a/src/ui/activities/setup/actions.rs +++ b/src/ui/activities/setup/actions.rs @@ -15,8 +15,8 @@ use super::{Id, IdSsh, IdTheme, SetupActivity, ViewLayout}; use crate::config::themes::Theme; impl SetupActivity { - /// On , if there are changes in the configuration, the quit dialog must be shown, otherwise - /// we can exit without any problem + /// On `ESC`, if there are changes in the configuration, the quit dialog + /// must be shown; otherwise, we can exit without any problem. pub(super) fn action_on_esc(&mut self) { if self.config_changed() { self.mount_quit(); diff --git a/src/utils/fmt.rs b/src/utils/fmt.rs index 3e7f77a..8e31e38 100644 --- a/src/utils/fmt.rs +++ b/src/utils/fmt.rs @@ -43,13 +43,15 @@ pub fn fmt_millis(duration: Duration) -> String { } /// Elide a path if longer than width -/// In this case, the path is formatted to {ANCESTOR[0]}/…/{PARENT[0]}/{BASENAME} +/// In this case, the path is formatted to +/// `{ANCESTOR[0]}/…/{PARENT[0]}/{BASENAME}`. pub fn fmt_path_elide(p: &Path, width: usize) -> String { fmt_path_elide_ex(p, width, 0) } /// Elide a path if longer than width -/// In this case, the path is formatted to {ANCESTOR[0]}/…/{PARENT[0]}/{BASENAME} +/// In this case, the path is formatted to +/// `{ANCESTOR[0]}/…/{PARENT[0]}/{BASENAME}`. /// This function allows to specify an extra length to consider to elide path pub fn fmt_path_elide_ex(p: &Path, width: usize, extra_len: usize) -> String { let fmt_path: String = format!("{}", p.display()); diff --git a/src/utils/parser.rs b/src/utils/parser.rs index a03bc9b..2860eeb 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -68,7 +68,7 @@ static BYTESIZE_REGEX: Lazy = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B /// SFTP => 22 /// FTP => 21 /// The option string has the following syntax -/// [protocol://][username@]{address}[:port][:path] +/// `[protocol://][username@]{address}[:port][:path]` /// The only argument which is mandatory is address /// NOTE: possible strings /// - 172.26.104.1 @@ -80,17 +80,17 @@ static BYTESIZE_REGEX: Lazy = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B /// /// For s3: /// -/// s3://@[:profile][:/wrkdir] +/// `s3://@[:profile][:/wrkdir]` /// /// For SMB: /// /// on UNIX derived (macos, linux, ...) /// -/// smb://[username@]

[:port]/[/path] +/// `smb://[username@]
[:port]/[/path]` /// /// on Windows /// -/// \\
\[\path] +/// `\\
\[\path]` /// pub fn parse_remote_opt(s: &str) -> Result { remote::parse_remote_opt(s)

` | 打开日志面板 | -| `` | 退出 termscp | -| `` | 重命名所选文件 | -| `` | 将所选文件另存为新名称 | -| `` | 将所选路径上的更改同步到远程 | -| `` | 进入上级目录 | -| `` | 使用该文件类型的默认程序打开所选文件 | -| `` | 使用你指定的程序打开所选文件 | -| `` | 执行命令 | -| `` | 切换同步浏览 | -| `` | 更改文件模式 | -| `` | 过滤文件(同时支持正则表达式和通配符匹配) | -| `` | 选择所有文件 | -| `` | 取消选择所有文件 | -| `` | 中止文件传输过程 | -| `` | 获取所选路径的总大小 | -| `` | 显示所有已同步的路径 | +| 按键 | 操作 | +| -------------- | --------------------- | +| `` | 断开与远程的连接并返回认证页面 | +| `` | 返回导航栈中的上一个目录 | +| `` | 切换当前活动的浏览器选项卡 | +| `` | 移动到远程浏览器选项卡 | +| `` | 移动到本地浏览器选项卡 | +| `` | 在所选列表中向上移动 | +| `` | 在所选列表中向下移动 | +| `` | 在所选列表中向上移动 8 行 | +| `` | 在所选列表中向下移动 8 行 | +| `` | 进入所选目录 | +| `` | 上传或下载所选文件 | +| `` | 在日志选项卡与浏览器之间切换 | +| `` | 切换是否显示隐藏文件 | +| `` | 选择文件的排序方式 | +| `` | 复制所选文件或目录 | +| `` | 新建目录 | +| `` | 删除所选文件 | +| `` | 搜索文件(支持通配符匹配) | +| `` | 跳转到指定路径 | +| `` | 显示帮助 | +| `` | 显示所选文件或目录的信息 | +| `` | 创建指向当前所选条目的符号链接 | +| `` | 重新加载当前目录的内容,或清除当前选择 | +| `` | 选择一个文件 | +| `` | 使用提供的名称创建新文件 | +| `` | 在文本编辑器中编辑所选文件 | +| `