cockup 0.2.3

Yet another backup tool for various configurations
default:
    @just --list

run *ARGS:
    cargo run --locked -- {{ ARGS }}

# Download the locked dependencies
sync:
    cargo fetch --locked

# Update workspace dependencies in Cargo.lock
update:
    cargo update --workspace

# Set the package version (X.Y.Z) and sync Cargo.lock without network access
version VERSION:
    #!/usr/bin/env bash
    set -euo pipefail
    version={{ quote(VERSION) }}
    if [[ ! "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
        echo "Version must use X.Y.Z format, without a v prefix" >&2
        exit 1
    fi
    COCKUP_VERSION="$version" perl -0pi -e 's/^(version\s*=\s*)"[^"]+"/$1"$ENV{COCKUP_VERSION}"/m' Cargo.toml
    cargo update --workspace --offline

test *ARGS:
    cargo test --locked {{ ARGS }}

build *ARGS:
    cargo build --release --locked {{ ARGS }}

# Format all Rust source files in the workspace
format:
    cargo fmt --all

check:
    cargo fmt --check
    cargo clippy --locked --all-targets -- -D warnings

clean:
    cargo clean

# Create and push a specific tag
tag VERSION:
    #!/usr/bin/env bash
    set -euo pipefail
    tag={{ quote(VERSION) }}
    if [[ ! "$tag" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
        echo "Version must use vX.Y.Z format" >&2
        exit 1
    fi

    package_version=$(awk '
        /^\[/ { in_package = ($0 == "[package]") }
        in_package && /^version[[:space:]]*=/ {
            split($0, parts, "\"")
            print parts[2]
            exit
        }
    ' Cargo.toml)
    if [[ -z "$package_version" ]]; then
        echo "Cannot read package version from Cargo.toml" >&2
        exit 1
    fi
    if [[ "${tag#v}" != "$package_version" ]]; then
        echo "Warning: requested tag $tag differs from Cargo.toml version $package_version." >&2
        echo "The released binary and crate will still use version $package_version." >&2
    fi

    while true; do
        if ! read -r -p "Create and push tag $tag? [y/N]: " REPLY; then
            echo "Operation cancelled"
            exit 0
        fi
        case "$REPLY" in
            y|Y|yes|YES) break ;;
            ""|n|N|no|NO) echo "Operation cancelled"; exit 0 ;;
            *) echo "Please enter y or n." >&2 ;;
        esac
    done

    git tag "$tag"
    git push origin "refs/tags/$tag"

# Delete a specific tag
dtag VERSION:
    #!/usr/bin/env bash
    if [[ ! "{{ VERSION }}" =~ ^v.+ ]]; then
        echo "Version must start with v"
        exit 1
    fi

    read -p "Are you sure to delete and push tag {{ VERSION }}? [y/N] " REPLY
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
        echo "Operation cancelled"
        exit 0
    fi

    git tag -d {{ VERSION }}
    git push origin --delete {{ VERSION }}

# Back up with sample/CASE.yaml; special cases use fixture homes only for Cockup.
sample-backup CASE="basic" *ARGS:
    #!/bin/sh
    set -eu
    cargo build --locked
    case {{ quote(CASE) }} in
        user-remap) env HOME="$(pwd -P)/sample/src/homes/alice" USER=alice ./target/debug/cockup backup sample/user-remap.yaml {{ ARGS }} ;;
        ingredient) env HOME="$(pwd -P)/sample/src/homes/ghostty" USER=sample ./target/debug/cockup backup sample/ingredient.yaml {{ ARGS }} ;;
        *) ./target/debug/cockup backup "sample/"{{ quote(CASE) }}".yaml" {{ ARGS }} ;;
    esac

# Restore with sample/CASE.yaml; user-remap simulates Bob only for Cockup.
sample-restore CASE="basic" *ARGS:
    #!/bin/sh
    set -eu
    cargo build --locked
    case {{ quote(CASE) }} in
        user-remap) env HOME="$(pwd -P)/sample/src/homes/bob" USER=bob ./target/debug/cockup restore sample/user-remap.yaml {{ ARGS }} ;;
        ingredient) env HOME="$(pwd -P)/sample/src/homes/ghostty" USER=sample ./target/debug/cockup restore sample/ingredient.yaml {{ ARGS }} ;;
        *) ./target/debug/cockup restore "sample/"{{ quote(CASE) }}".yaml" {{ ARGS }} ;;
    esac

# Verify the backup for sample/CASE.yaml without running hooks or changing files.
sample-verify CASE="basic":
    #!/bin/sh
    set -eu
    cargo build --locked
    case {{ quote(CASE) }} in
        ingredient) env HOME="$(pwd -P)/sample/src/homes/ghostty" USER=sample ./target/debug/cockup verify sample/ingredient.yaml ;;
        *) ./target/debug/cockup verify "sample/"{{ quote(CASE) }}".yaml" ;;
    esac

# Compare the backup for sample/CASE.yaml with its fixture files.
sample-diff CASE="basic":
    #!/bin/sh
    set -eu
    cargo build --locked
    case {{ quote(CASE) }} in
        user-remap) env HOME="$(pwd -P)/sample/src/homes/bob" USER=bob ./target/debug/cockup diff sample/user-remap.yaml ;;
        ingredient) env HOME="$(pwd -P)/sample/src/homes/ghostty" USER=sample ./target/debug/cockup diff sample/ingredient.yaml ;;
        *) ./target/debug/cockup diff "sample/"{{ quote(CASE) }}".yaml" ;;
    esac

# Run a named sample hook, or select interactively when NAME is omitted.
sample-hook NAME="":
    cargo run --locked -- hook sample/hooks.yaml --approve-hooks {{ if NAME == "" { "" } else { "--name " + quote(NAME) } }}