diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml new file mode 100644 index 0000000..cde5cf2 --- /dev/null +++ b/.github/workflows/pull_request.yaml @@ -0,0 +1,67 @@ +name: Pull Request + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +on: + pull_request: + branches: + - "main" + +jobs: + test: + name: ๐Ÿงช Test + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + - run: cargo test + + platform: + name: ๐Ÿงช Test (${{ matrix.runner }}) + strategy: + fail-fast: false + matrix: + runner: + - macos-15 + - windows-latest + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + - run: cargo test + + format: + name: ๐Ÿ“ Format + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + - run: cargo install cargo-xfmt + - run: cargo xfmt --check + + lint: + name: ๐Ÿค“ Lint + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + - run: cargo clippy --all-targets --workspace -- -D warnings + + build: + name: ๐Ÿ”จ Build + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + - run: cargo build diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fa95131..20213ae 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,144 +8,299 @@ defaults: run: shell: bash -on: +on: push: branches: - "main" jobs: - build: - strategy: - matrix: - config: - - name: ๐Ÿฅ Linux AMD64 - os: linux - arch: amd64 - runner: ubuntu-24.04 - run: rustup target add x86_64-unknown-linux-musl - - - name: ๐Ÿฅ Linux ARM64 - os: linux - arch: arm64 - runner: ubuntu-24.04-arm - run: rustup target add aarch64-unknown-linux-musl - - - name: ๐ŸŽ MacOS AMD64 - os: macos - arch: amd64 - runner: macos-latest - run: rustup target add x86_64-apple-darwin - - - name: ๐ŸŽ MacOS ARM64 - os: macos - arch: arm64 - runner: macos-latest - - - name: ๐ŸŸฆ Windows AMD64 - os: windows - arch: amd64 - runner: windows-latest - - - name: ๐ŸŸฆ Windows ARM64 - os: windows - arch: arm64 - runner: windows-latest - run: rustup target add aarch64-pc-windows-msvc - - name: ${{ matrix.config.name }} - runs-on: ${{ matrix.config.runner }} + version: + name: ๐ŸŒ Auto Version Increment + runs-on: ubuntu-24.04 + outputs: + VERSION: ${{ steps.generate.outputs.VERSION }} + GITHUB_REPOSITORY_NAME: ${{ steps.generate.outputs.GITHUB_REPOSITORY_NAME }} + GITHUB_REPOSITORY: ${{ steps.generate.outputs.GITHUB_REPOSITORY }} steps: - - uses: actions/checkout@v4 - - run: eval $(curl -sSf sh.davidalsh.com/rust.sh | sh) - - run: eval $(curl -sSf sh.davidalsh.com/just.sh | sh) - - if: matrix.config.run - run: ${{ matrix.config.run }} - - run: just build - env: - os: "${{ matrix.config.os }}" - arch: "${{ matrix.config.arch }}" - profile: release - - uses: actions/upload-artifact@v4 + - id: generate + run: | + set -ex + + GITHUB_URL="https://api.github.com/repos/${{ github.repository }}" + LAST_VERSION=$(curl -sSL "$GITHUB_URL/releases/latest" | jq -r '.tag_name // empty') + + if [ -z "$LAST_VERSION" ]; then + # No releases yet + NEW_VERSION="0.0.1" + else + IFS='.' read -r MAJOR MINOR PATCH <<< "$LAST_VERSION" + NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))" + fi + + echo "Bumping ${LAST_VERSION:-} -> $NEW_VERSION" + + GITHUB_REPOSITORY_NAME="${GITHUB_REPOSITORY#*/}" + + echo $NEW_VERSION + echo $GITHUB_REPOSITORY + echo $GITHUB_REPOSITORY_NAME + + echo "VERSION=$NEW_VERSION" >> "$GITHUB_OUTPUT" + echo "GITHUB_REPOSITORY_NAME=$GITHUB_REPOSITORY_NAME" >> "$GITHUB_OUTPUT" + echo "GITHUB_REPOSITORY=$GITHUB_REPOSITORY" >> "$GITHUB_OUTPUT" + + test: + name: ๐Ÿงช Test + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + - run: cargo test + + format: + name: ๐Ÿ“ Format + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + - run: cargo install cargo-xfmt + - run: cargo xfmt --check + + lint: + name: ๐Ÿค“ Lint + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + - run: cargo clippy --all-targets --workspace -- -D warnings + + # Due to platform differences I have opted out of using a matrix. + # Examples; + # - Windows executables have different names + # - Linux glibc builds require containers (not used here but I use this as a template) + build_linux_amd64: + name: ๐Ÿฅ Linux AMD64 + runs-on: ubuntu-latest + needs: + - version + - format + - lint + - test + env: + VERSION: ${{needs.version.outputs.VERSION}} + GITHUB_REPOSITORY_NAME: ${{needs.version.outputs.GITHUB_REPOSITORY_NAME}} + CARGO_TARGET_DIR: target + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + with: { key: "x86_64-unknown-linux-musl" } + - run: sudo apt-get update + - run: sudo apt-get install -y musl-tools + - run: rustup target add x86_64-unknown-linux-musl + - run: sed -E "s/^version = \"[^\"]*\"/version = \"${VERSION}\"/" "Cargo.toml" > "Cargo.toml.new" && mv "Cargo.toml.new" "Cargo.toml" + - run: cargo build --target x86_64-unknown-linux-musl --release + - run: mkdir -p artifacts/${GITHUB_REPOSITORY_NAME}-linux-amd64 + - run: cp target/x86_64-unknown-linux-musl/release/${GITHUB_REPOSITORY_NAME} ./artifacts/${GITHUB_REPOSITORY_NAME}-linux-amd64 + - uses: actions/upload-artifact@v7 with: - name: flatdir-${{ matrix.config.os }}-${{ matrix.config.arch }} - path: ${{ github.workspace }}/target/${{ matrix.config.os }}-${{ matrix.config.arch }}/**/* + name: ${{ env.GITHUB_REPOSITORY_NAME }}-linux-amd64 + path: ${{ github.workspace }}/artifacts/**/* if-no-files-found: error retention-days: 1 - publish-github-release: - name: "๐Ÿ”„ Publish Github Release" - runs-on: ubuntu-latest - needs: [build] + build_linux_arm64: + name: ๐Ÿฅ Linux ARM64 + runs-on: ubuntu-26.04-arm + needs: + - version + - format + - lint + - test + env: + VERSION: ${{needs.version.outputs.VERSION}} + GITHUB_REPOSITORY_NAME: ${{needs.version.outputs.GITHUB_REPOSITORY_NAME}} + CARGO_TARGET_DIR: target + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + with: { key: "aarch64-unknown-linux-musl" } + - run: sudo apt-get update + - run: sudo apt-get install -y musl-tools + - run: rustup target add aarch64-unknown-linux-musl + - run: sed -E "s/^version = \"[^\"]*\"/version = \"${VERSION}\"/" "Cargo.toml" > "Cargo.toml.new" && mv "Cargo.toml.new" "Cargo.toml" + - run: cargo build --target aarch64-unknown-linux-musl --release + - run: mkdir -p artifacts/${GITHUB_REPOSITORY_NAME}-linux-arm64 + - run: cp target/aarch64-unknown-linux-musl/release/${GITHUB_REPOSITORY_NAME} ./artifacts/${GITHUB_REPOSITORY_NAME}-linux-arm64 + - uses: actions/upload-artifact@v7 + with: + name: ${{ env.GITHUB_REPOSITORY_NAME }}-linux-arm64 + path: ${{ github.workspace }}/artifacts/**/* + if-no-files-found: error + retention-days: 1 + + build_macos_arm64: + name: ๐ŸŽ MacOS ARM64 + runs-on: macos-latest + needs: + - version + - format + - lint + - test + env: + VERSION: ${{needs.version.outputs.VERSION}} + GITHUB_REPOSITORY_NAME: ${{needs.version.outputs.GITHUB_REPOSITORY_NAME}} + CARGO_TARGET_DIR: target + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + with: { key: "aarch64-apple-darwin" } + - run: rustup target add aarch64-apple-darwin + - run: sed -E "s/^version = \"[^\"]*\"/version = \"${VERSION}\"/" "Cargo.toml" > "Cargo.toml.new" && mv "Cargo.toml.new" "Cargo.toml" + - run: cargo build --target aarch64-apple-darwin --release + - run: mkdir -p artifacts/${GITHUB_REPOSITORY_NAME}-macos-arm64 + - run: cp target/aarch64-apple-darwin/release/${GITHUB_REPOSITORY_NAME} ./artifacts/${GITHUB_REPOSITORY_NAME}-macos-arm64 + - uses: actions/upload-artifact@v7 + with: + name: ${{ env.GITHUB_REPOSITORY_NAME }}-macos-arm64 + path: ${{ github.workspace }}/artifacts/**/* + if-no-files-found: error + retention-days: 1 + + build_windows_amd64: + name: ๐ŸŸฆ Windows AMD64 + runs-on: windows-latest + needs: + - version + - format + - lint + - test + env: + VERSION: ${{needs.version.outputs.VERSION}} + GITHUB_REPOSITORY_NAME: ${{needs.version.outputs.GITHUB_REPOSITORY_NAME}} + CARGO_TARGET_DIR: target + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + with: { key: "x86_64-pc-windows-msvc" } + - run: rustup target add x86_64-pc-windows-msvc + - run: sed -E "s/^version = \"[^\"]*\"/version = \"${VERSION}\"/" "Cargo.toml" > "Cargo.toml.new" && mv "Cargo.toml.new" "Cargo.toml" + - run: cargo build --target x86_64-pc-windows-msvc --release + - run: mkdir -p artifacts/${GITHUB_REPOSITORY_NAME}-windows-amd64 + - run: cp target/x86_64-pc-windows-msvc/release/${GITHUB_REPOSITORY_NAME}.exe ./artifacts/${GITHUB_REPOSITORY_NAME}-windows-amd64 + - uses: actions/upload-artifact@v7 + with: + name: ${{ env.GITHUB_REPOSITORY_NAME }}-windows-amd64 + path: ${{ github.workspace }}/artifacts/**/* + if-no-files-found: error + retention-days: 1 + + build_windows_arm64: + name: ๐ŸŸฆ Windows ARM64 + runs-on: windows-latest + needs: + - version + - format + - lint + - test + env: + VERSION: ${{needs.version.outputs.VERSION}} + GITHUB_REPOSITORY_NAME: ${{needs.version.outputs.GITHUB_REPOSITORY_NAME}} + CARGO_TARGET_DIR: target + steps: + - uses: actions/checkout@v7 + - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - uses: Swatinem/rust-cache@v2 + with: { key: "aarch64-pc-windows-msvc" } + - run: rustup target add aarch64-pc-windows-msvc + - run: sed -E "s/^version = \"[^\"]*\"/version = \"${VERSION}\"/" "Cargo.toml" > "Cargo.toml.new" && mv "Cargo.toml.new" "Cargo.toml" + - run: cargo build --target aarch64-pc-windows-msvc --release + - run: mkdir -p artifacts/${GITHUB_REPOSITORY_NAME}-windows-arm64 + - run: cp target/aarch64-pc-windows-msvc/release/${GITHUB_REPOSITORY_NAME}.exe ./artifacts/${GITHUB_REPOSITORY_NAME}-windows-arm64 + - uses: actions/upload-artifact@v7 + with: + name: ${{ env.GITHUB_REPOSITORY_NAME }}-windows-arm64 + path: ${{ github.workspace }}/artifacts/**/* + if-no-files-found: error + retention-days: 1 + + publish_github_release: + name: "๐Ÿฑโ€๐Ÿ‘ค Publish Github Release" + runs-on: ubuntu-24.04 + needs: + - build_linux_amd64 + - build_linux_arm64 + - build_macos_arm64 + - build_windows_amd64 + - build_windows_arm64 + - version steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@v7 + - uses: actions/download-artifact@v8 + with: + path: artifacts + merge-multiple: true - name: Publish` Github Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{needs.version.outputs.VERSION}} run: | - set -e + set -ex - LAST_TAG=$(curl -sSL https://api.github.com/repos/alshdavid/flatdir/releases/latest | jq -r ".tag_name" | cut -d "." -f 3) - if [ "$LAST_TAG" = "" ]; then - LAST_TAG="0" - fi - declare -i var="$LAST_TAG" - var=$var+1 - TAG="0.0.$var" - - echo "Tag: ${TAG}" - - cd ${{ github.workspace }}/flatdir-linux-amd64/release - chmod +x ./* - tar -cJvf ../flatdir-linux-amd64.tar.xz ./* - tar -czvf ../flatdir-linux-amd64.tar.gz ./* - - cd ${{ github.workspace }}/flatdir-linux-arm64/release - chmod +x ./* - tar -cJvf ../flatdir-linux-arm64.tar.xz ./* - tar -czvf ../flatdir-linux-arm64.tar.gz ./* - - cd ${{ github.workspace }}/flatdir-macos-amd64/release - chmod +x ./* - tar -cJvf ../flatdir-macos-amd64.tar.xz ./* - tar -czvf ../flatdir-macos-amd64.tar.gz ./* - - cd ${{ github.workspace }}/flatdir-macos-arm64/release - chmod +x ./* - tar -cJvf ../flatdir-macos-arm64.tar.xz ./* - tar -czvf ../flatdir-macos-arm64.tar.gz ./* - - cd ${{ github.workspace }}/flatdir-windows-amd64/release - chmod +x ./* - tar -cJvf ../flatdir-windows-amd64.tar.xz ./* - tar -czvf ../flatdir-windows-amd64.tar.gz ./* - - cd ${{ github.workspace }}/flatdir-windows-arm64/release - chmod +x ./* - tar -cJvf ../flatdir-windows-arm64.tar.xz ./* - tar -czvf ../flatdir-windows-arm64.tar.gz ./* - - cd ${{ github.workspace }} - - gh release create $TAG --draft --notes "Automatically built binaries" - gh release edit $TAG --title "๐Ÿš€ Latest" - - gh release upload $TAG flatdir-linux-amd64/flatdir-linux-amd64.tar.gz - gh release upload $TAG flatdir-linux-amd64/flatdir-linux-amd64.tar.xz - - gh release upload $TAG flatdir-linux-arm64/flatdir-linux-arm64.tar.gz - gh release upload $TAG flatdir-linux-arm64/flatdir-linux-arm64.tar.xz - - gh release upload $TAG flatdir-macos-amd64/flatdir-macos-amd64.tar.gz - gh release upload $TAG flatdir-macos-amd64/flatdir-macos-amd64.tar.xz - - gh release upload $TAG flatdir-macos-arm64/flatdir-macos-arm64.tar.gz - gh release upload $TAG flatdir-macos-arm64/flatdir-macos-arm64.tar.xz - - gh release upload $TAG flatdir-windows-amd64/flatdir-windows-amd64.tar.gz - gh release upload $TAG flatdir-windows-amd64/flatdir-windows-amd64.tar.xz - - gh release upload $TAG flatdir-windows-arm64/flatdir-windows-arm64.tar.gz - gh release upload $TAG flatdir-windows-arm64/flatdir-windows-arm64.tar.xz - - gh release edit $TAG --draft=false + echo "Tag: ${VERSION}" + + gh release delete "$VERSION" --yes --cleanup-tag 2>/dev/null || true + gh release create $VERSION --draft --notes "Automatically built binaries" + gh release edit "$VERSION" --title "v${VERSION}" + + cd artifacts + ls -la + + for name in *; do + echo $name + + cd $name + chmod +x ./* + + tar -czf ./$name.tar.gz ./* + gh release upload $VERSION ${name}.tar.gz + rm -rf ./*.tar.gz + + tar -cJf ./$name.tar.xz ./* + gh release upload $VERSION ${name}.tar.xz + rm -rf ./*.tar.xz + + zip ./$name.zip ./* + gh release upload $VERSION ${name}.zip + rm -rf ./*.zip + + cd .. + done + + gh release edit $VERSION --draft=false + + changelog: + name: "๐Ÿ“ Generate Changelog" + runs-on: ubuntu-latest + needs: + - version + - publish_github_release + steps: + - uses: actions/checkout@v7 + - env: + VERSION: ${{needs.version.outputs.VERSION}} + run: | + payload="$(jq -n \ + --arg repository "$GITHUB_REPOSITORY" \ + --arg version "$VERSION" \ + '{repository: $repository, version: $version}')" + + curl -fsS -X POST "${{ secrets.SENTINEL_CHANGELOG_WEBHOOK }}" \ + -H "Content-Type: application/json" \ + -d "$payload" || true diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2ce1b42 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,74 @@ +# flatdir + +You are a lazy senior developer. The best code is the code never written. You solve the whole problem with the least new code. + +## Response style + +Lead with the outcome. No preamble, no restating the task, no filler. Bullets only when they aid parsing. End with one or two lines: what you skipped or did not check, and any risk the user must know. + +## Architecture + +Modules are lightweight packages. Each owns one concern and exposes a minimal public API; everything else stays private. + +- Code for an existing concern goes in its package. A new concern gets a new module, not a corner of an existing one. +- Cross-package calls go through the public API only. Never reach into another package's submodules, and never widen visibility (`pub`, `pub(crate)`) just to get at internals. +- Packages don't orchestrate each other. The entrypoint wires packages together through their public APIs. +- A package's API is plain `pub` functions and types. No trait or facade unless the task needs one. +- Keep public APIs stable. If the task requires a change, update every caller in the same change. + +## Before you write + +Read the task and the code it touches. List every place your change must reach: callers, tests, fixtures, config, public APIs. Check what your change could break: data it would destroy or expose, callers that stop working. That is scope. Extra features are not. + +## The smallest complete change + +Take the first option that fully works: + +1. Does it need to exist? Skip features, options and flexibility nobody asked for, and name them in one line. A vague request gets the smallest version that does the core job. +2. Already in this codebase (a package, helper, pattern)? Use it through its public API, the way the surrounding code does. +3. In std? Use it, unless the project has its own. +4. A crate already in `Cargo.toml`? Use it. Never add a crate for a few lines. +5. Can it be one line a reader gets at a glance? One line. +6. Otherwise: the minimum code that works. + +- Be lazy about the solution, never about the change: finish every caller, test and fixture your change breaks. +- No abstraction, wrapper, generic, type conversion, option, config or "for later" code nobody asked for. Keep values in the form you already have them; no needless `clone`, `to_string` or `into`. Deletion beats addition. +- The shortest working diff wins, once you know everything it must touch. A one-liner that needs decoding is not short. +- Bug fix: grep every caller of the function you touch, then fix the root cause once, in the package that owns the concern. +- Code you move or merge keeps its error handling and validation. +- Between options of equal size, take the one that is correct on edge cases. + +Never cut: validation at trust boundaries (no `unwrap`/`expect` on external input), error handling that prevents data loss, security, anything the user asked for. + +## Panics and unsafe + +Non-test code contains no `unsafe` and nothing that can panic. These rules win over the shortest diff. + +- No `unsafe` blocks, functions, traits or impls. +- No `unwrap`, `expect`, `panic!`, `unreachable!`, `todo!` or `unimplemented!`. +- No indexing or slicing (`v[i]`, `&v[a..b]`, `&s[a..b]`). Use `get`, `get_mut`, `first`, `split_first`, `chunks` or iterators, and propagate failure with `?`. +- Integer arithmetic that can overflow, underflow or divide by zero uses `checked_*` and handles `None`. Use `saturating_*` or `wrapping_*` only when that behavior is intended. +- Convert between integer types with `try_from`, not `as`. + +## Tests + +Write tests only when asked. Tests exercise a package's public API only: `tests/` for a crate, and for a module a `#[cfg(test)]` block that imports only its `pub` items. + +## Comments + +Default is none. Add one only when it states a reason, constraint or quirk the code cannot show, and omitting it would plausibly cause a wrong "fix" later. One line. + +- No narration, no restating names, types or signatures, no banners, no doc comments on code that reads clearly from its name and type. +- A shortcut with a known limit gets `// shortcut: , `. +- Leave existing comments alone. Changing them is a separate, explicit request. + +## Validation + +Run in order after every change and fix failures before continuing: + +1. `cargo check --quiet --workspace` +2. `cargo clippy --quiet --all-targets --workspace -- -D warnings`. On failure, run `cargo clippy --quiet --fix --all-targets --workspace --allow-dirty` first, then fix the rest by hand. +3. `cargo test --quiet --workspace` (existing tests must stay green) +4. `cargo xfmt` + +`cargo run` launches the app. \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index c605cfc..c527c88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,15 @@ [package] name = "flatdir" version = "0.1.0" -edition = "2021" +edition = "2024" + +[lints.rust] +elided_lifetimes_in_paths = "allow" +rust_2018_idioms = { priority = -1, level = "deny" } + +[lints.clippy] +module_inception = "allow" +uninlined_format_args = "allow" [dependencies] clap = { version = "4.5.48", features = ["derive"] } diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..3bd6c56 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +1.99.1 diff --git a/rustfmt.toml b/rustfmt.toml index aa38e73..5482a82 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -3,4 +3,5 @@ indent_style = "Block" hard_tabs = false unstable_features = false tab_spaces = 2 -fn_params_layout = "Vertical" \ No newline at end of file +fn_params_layout = "Vertical" +group_imports = "StdExternalCrate" diff --git a/src/main.rs b/src/main.rs index 036bfa6..53e74c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,13 @@ -use clap::Parser; -use normalize_path::NormalizePath; use std::collections::HashMap; use std::collections::HashSet; use std::fs; use std::io::Write; use std::path::PathBuf; -use walkdir::WalkDir; + +use clap::Parser; +use normalize_path::NormalizePath; use slugify::slugify; +use walkdir::WalkDir; #[derive(Parser, Debug)] struct Commands { @@ -25,15 +26,12 @@ fn main() -> anyhow::Result<()> { let scan_dir: PathBuf; if let Some(target) = cmd.scan_dir { if target.is_relative() { - scan_dir = std::env::current_dir()? - .join(target) - .normalize(); + scan_dir = std::env::current_dir()?.join(target).normalize(); } else { scan_dir = target; } } else { - scan_dir = std::env::current_dir()? - .normalize(); + scan_dir = std::env::current_dir()?.normalize(); } println!("Config:"); @@ -44,13 +42,13 @@ fn main() -> anyhow::Result<()> { let mut matches = HashMap::::new(); let mut delete = HashSet::::new(); - println!(""); + println!(); for entry in WalkDir::new(&scan_dir) { let entry = entry?; let entry_path = entry.path(); - if entry_path.is_dir() && entry_path != &scan_dir { + if entry_path.is_dir() && entry_path != scan_dir { delete.insert(entry_path.to_path_buf()); continue; } @@ -59,30 +57,52 @@ fn main() -> anyhow::Result<()> { continue; } - let file_ext = entry_path.extension().unwrap().to_str().unwrap().to_string(); - let file_name = entry_path.file_name().unwrap().to_str().unwrap().to_string(); - let file_stem = entry_path.file_stem().unwrap().to_str().unwrap().to_string(); - let file_name_slug: String; - - if cmd.no_slugify { - file_name_slug = format!("{}.{}", &file_stem, &file_ext); + let file_ext = entry_path + .extension() + .unwrap() + .to_str() + .unwrap() + .to_string(); + let file_name = entry_path + .file_name() + .unwrap() + .to_str() + .unwrap() + .to_string(); + let file_stem = entry_path + .file_stem() + .unwrap() + .to_str() + .unwrap() + .to_string(); + + let file_name_slug: String = if cmd.no_slugify { + format!("{}.{}", file_stem, file_ext) } else { - file_name_slug = format!("{}.{}", slugify!(entry_path.file_stem().unwrap().to_str().unwrap()), file_ext); - } + format!( + "{}.{}", + slugify!(entry_path.file_stem().unwrap().to_str().unwrap()), + file_ext + ) + }; - if entry_path.parent().unwrap() == &scan_dir && file_name == file_name_slug { + if entry_path.parent().unwrap() == scan_dir && file_name == file_name_slug { continue; }; let target = scan_dir.join(&file_name_slug); matches.insert(entry_path.to_path_buf(), target); - let src = pathdiff::diff_paths(&entry_path, &scan_dir).unwrap(); - println!(" From: {}\n To: {}\n", src.to_str().unwrap(), file_name_slug); + let src = pathdiff::diff_paths(entry_path, &scan_dir).unwrap(); + println!( + " From: {}\n To: {}\n", + src.to_str().unwrap(), + file_name_slug + ); } if delete.is_empty() && matches.is_empty() { - println!("Nothing to do"); + println!("Nothing to do"); return Ok(()); } @@ -90,9 +110,9 @@ fn main() -> anyhow::Result<()> { for entry in &delete { println!(" {}", entry.to_str().unwrap()); } - + if !cmd.force { - println!(""); + println!(); print!("Continue? ({} found) [y/N] ", matches.len()); let mut line = String::new(); let _ = std::io::stdout().flush(); @@ -104,7 +124,7 @@ fn main() -> anyhow::Result<()> { return Ok(()); } } else { - println!(""); + println!(); } for (from, to) in matches.iter() { @@ -114,8 +134,8 @@ fn main() -> anyhow::Result<()> { for entry in &delete { println!(" Del: {}", entry.to_str().unwrap()); - fs::remove_dir_all(&entry).ok(); + fs::remove_dir_all(entry).ok(); } - return Ok(()) + Ok(()) }