[private]
default:
    @just --list

# ----------------Settings----------------

set fallback := true

# ----------------Variables----------------

SQLITE_DB_FILE_PATH := 'state/keys-internal/keys.sqlite'

# Regular expresion for sed cmd to match variants of Rust versions
# Matched Rust versions: 1, 1.90, 1.90.0

SED_RUST_VERSION_REGEX := '[0-9](\.[0-9]+){1,2}'
RUST_MANIFEST_STABLE_TOML_URL := 'https://static.rust-lang.org/dist/channel-rust-stable.toml'

# Hierarchycal path in the file to current stable version

RUST_MANIFEST_STABLE_VERSION_QUERY := 'pkg.rust.version'

# Regex in sed(-r) format
# Parsed string example: "1.90.0 (1159e78c4 2025-09-14)"

RUST_MANIFEST_STABLE_VERSION_PARSE_REGEX := '^"([0-9]+).([0-9]+).([0-9]+) \(([0-9A-Fa-f]+) (([0-9]+)-([0-9]+)-([0-9]+))\)"$'

# GOTCH: See documentation to `rust-stable-version` recipe for explanation on what \1, \2, etc. means.

DEFAULT_RUST_STABLE_VERSION_FORMAT := '\1.\2'
GITLAB_CI_FILE_NAME := '.gitlab-ci.yml'
GITLAB_CI_FILE_PATH := absolute_path(GITLAB_CI_FILE_NAME)
CARGO_FILE_NAME := 'Cargo.toml'
CARGO_FILE_PATH := absolute_path(CARGO_FILE_NAME)
CARGO_RUST_VERSION_QUERY := 'package.rust-version'
CLIPPY_FILE_NAME := 'clippy.toml'
CLIPPY_FILE_PATH := absolute_path(CLIPPY_FILE_NAME)
CLIPPY_RUST_VERSION_QUERY := 'msrv'
TOOLCHAIN_FILE_NAME := 'rust-toolchain.toml'
TOOLCHAIN_FILE_PATH := absolute_path(TOOLCHAIN_FILE_NAME)
TOOLCHAIN_RUST_VERSION_QUERY := 'toolchain.channel'
GIT_BRANCH_NAME_PREFIX := 'upgrade_rust_to_'

# ----------------Recipes----------------

# Perform initial setup of developer's system.
[group('setup')]
init: init-rocket-config

# Copy Rocket's template configuration from Rocket.toml.dist to Rocket.toml. Rocket is Rust web framework. See https://rocket.rs/guide/v0.5/configuration/#configuration
[group('setup')]
init-rocket-config:
    #!/usr/bin/env -S bash -euo pipefail
    [ ! -f Rocket.toml ] \
        && cp Rocket.toml.dist Rocket.toml \
        && echo "Rocket.toml.dist copied to Rocket.toml" \
        || echo "Rocket.toml exists already!"

# Format justfile
[group('fmt')]
[group('format')]
[group('just')]
just-fmt:
    just --unstable --fmt

# Format Rust code in all packages (aka path based dependencies)
[group('fmt')]
[group('format')]
cargo-fmt:
    cargo fmt --all

# Format all code
[group('fmt')]
[group('format')]
fmt: just-fmt cargo-fmt

alias f := fmt

# Check justfile formatting
[group('fmt')]
[group('just')]
[group('lint')]
just-lint-fmt:
    just --unstable --fmt --check

# Check Rust code formatting in all packages (aka path based dependencies)
[group('fmt')]
[group('lint')]
cargo-lint-fmt:
    cargo fmt --all -- --check

# Check formatting of all code
[group('fmt')]
[group('lint')]
lint-fmt: just-lint-fmt cargo-lint-fmt

alias lf := lint-fmt

# Lint Rust code with Clippy
[group('clippy')]
[group('lint')]
clippy-lint:
    cargo clippy --tests --no-deps --workspace

alias cl := clippy-lint

# Lint all code
[group('lint')]
lint: lint-fmt clippy-lint

alias l := lint

# Fix compilation warnings by applying compiler suggestions
[group('fix')]
cargo-fix *args:
    cargo fix --workspace {{ args }}

# Apply Clippy's lint suggestions, i.e. fix Clippy linting warnings or errors
[group('clippy')]
[group('fix')]
clippy-fix *args:
    cargo clippy --fix --tests --no-deps --workspace {{ args }}

# Fix lint and compilation warnings and errors. Pass given arguments to all sub-recipes, i.e. `just fix --allow-dirty` calls `just cargo-fix --allow-dirty` and `just clippy-fix --allow-dirty`.
[group('fix')]
fix *args: (cargo-fix args) (clippy-fix args)

# Check Rust code errors
[group('compile')]
check:
    cargo check

alias c := check

# Compile all Rust code
[group('compile')]
build *args='--workspace':
    cargo build {{ args }}

alias b := build

# Run all tests (i.e. --workspace), but when args given pass them to `cargo test`, e.g. `just test fs::tests::init`
[group('test')]
test args='--workspace':
    cargo test {{ args }}

alias t := test

# Run continuous check of Rust code errors. Detect file changes and repeat check automatically. Ctrl+c to exit. You can pass additional arguments, e.g. --notify (-N).
[group('compile')]
[group('watch')]
watch-check *args:
    cargo watch --ignore *.pot {{ args }}

alias wc := watch-check

# Run web server and automatically restart on changes. Ctrl+c to exit. You can pass additional arguments, e.g. --notify (-N).
[group('compile')]
[group('run')]
[group('watch')]
watch-run *args:
    cargo watch --exec 'run --bin hagrid' --ignore *.pot {{ args }}

alias wr := watch-run

# Run tests every time files changed. Ctrl+c to exit. You can pass additional arguments, e.g. --notify (-N).
[group('test')]
[group('watch')]
watch-test *args:
    cargo watch --exec 'test --workspace' --ignore *.pot {{ args }}

alias wt := watch-test

# Run web server
[group('run')]
run:
    cargo run

alias r := run
alias run-hagrid := run
alias hagrid := run

# Run "hagridctl" which automate some operations working with database externally, e.g. import keys
[group('run')]
run-hagridctl *args:
    cargo run --package hagridctl -- {{ args }}

alias hagridctl := run-hagridctl

# Run "tester" which allows to seed database with sample data, e.g. for testing
[group('run')]
run-tester *args:
    cargo run --package tester -- {{ args }}

alias tester := run-tester

# Clean compilation artifacts (i.e. "target" directory)
[group('clean')]
clean:
    cargo clean

# Clean changes to translation files
[group('clean')]
[group('translation')]
clean-translations:
    git restore po/

# Open database prompt
[group('database')]
@db:
    command -v sqlite3 \
        && echo "See sqlite3 CLI Documentation: https://sqlite.org/cli.html\n" \
        && sqlite3 {{ SQLITE_DB_FILE_PATH }} \
        || echo "sqlite3 command has not been found. Please, install it using system's package manager or refer to documentation https://sqlite.org/cli.html for installation." >&2


# Upgrade Rust to a given version, by default current stable Rust version is used
[group('housekeeping')]
upgrade-rust version=`just _rust-stable-version`: _ensure-no-vcs-changes && _upgrade-rust-fixes-reminder
    #!/usr/bin/env -S bash -euo pipefail
    readonly OLD_VERSIONS=$( \
        for recipe in _current-ci-rust-version _current-cargo-rust-version _current-clippy-rust-version _current-toolchain-rust-version; do \
            just $recipe; \
        done \
            | sort -u \
    ); \

    just _upgrade-rust-git-create-branch "{{ replace(version, '.', '_') }}" "{{ GIT_BRANCH_NAME_PREFIX + replace(version, '.', '_') }}"

    for recipe in _upgrade-rust-ci _upgrade-rust-cargo _upgrade-rust-clippy _upgrade-rust-toolchain; do \
        just $recipe '{{ version }}'; \
    done \

    just _upgrade-rust-git-commit \
        "${OLD_VERSIONS//$'\n'/, }" \
        "{{ version }}" \
        "{{ GITLAB_CI_FILE_PATH }} {{ CARGO_FILE_PATH }} {{ CLIPPY_FILE_PATH }} {{ TOOLCHAIN_FILE_PATH }}" \
        "{{ GITLAB_CI_FILE_NAME }} {{ CARGO_FILE_NAME }} {{ CLIPPY_FILE_NAME }} {{ TOOLCHAIN_FILE_NAME }}"

_ensure-no-vcs-changes:
    #!/usr/bin/env -S bash -euo pipefail
    readonly CHANGED_FILES=$(git ls-files --deleted --modified --others --exclude-standard -- :/); \
    git diff-index --quiet HEAD -- >&2 && true
    readonly HAS_STAGED_FILES=$?
    if [ -n "$CHANGED_FILES" ] || [ $HAS_STAGED_FILES != 0 ]; \
    then \
        echo -e "{{ RED }}You have working directory changes! \nTo avoid loosing or corrupting your changes commit or stash (git stash -u) them before running commands which change code automatically!{{ NORMAL }}"; \
        exit 1; \
    fi

_upgrade-rust-git-create-branch branched_version branch_name:
    #!/usr/bin/env -S bash -euo pipefail
    readonly CURRENT_BRANCH=$(git branch --show-current)
    if [[ "$CURRENT_BRANCH" == *{{ branched_version }}* ]]; then
        echo "{{ GREEN }}It looks like you switched to new branch manually. Continue...{{ NORMAL }}"
        exit 0
    fi

    while true; do
        read -p "Would you like to create new branch ({{ branch_name }})? [y/n]: " input
        case "$input" in
            y)
                break
                ;;
            n)
                exit 0
                ;;
            *)
                echo "{{ RED }}Incorrect input. Please use only y or n.{{ NORMAL }}"
                ;;
        esac
    done

    git switch --create "{{ branch_name }}"

_upgrade-rust-fixes-reminder:
    #!/usr/bin/env -S bash -euo pipefail
    echo -e "\n    {{ YELLOW }}Don't forget to fix linting (just lint) and compilation (just check) warnings and errors!{{ NORMAL }}\n"

[confirm('Would you like to commit changes? [y/n]')]
_upgrade-rust-git-commit old_versions new_version file_paths file_names:
    #!/usr/bin/env -S bash -euo pipefail
    echo "Commiting changes ..."; \
    git add {{ file_paths }}; \
    sed -r 's/^ {4}//' <<'MSG' | git commit --file -
        Upgrade Rust toolchain: {{ old_versions }} -> {{ new_version }}

        If you don't have toolchain installed and you use rustup run:

            $ rustup toolchain install --profile default --component rustfmt,clippy {{ new_version }}

        NOTE: It might be that you have {{ new_version }}.0 installed as stable toolchain, in
        that case you still have to run the above command to install exactly {{ new_version }}.

        Command: `just upgrade-rust`

        Changes:
        - Upgrade version of used toolchain in the following places:
        {{ '  - ' + replace_regex(file_names, '\s+', "\n  - ") }}
    MSG

_upgrade-rust version file_path file_name current_version_recipe version_error_msg_part success_msg_part substitude_cmd:
    #!/usr/bin/env -S bash -euo pipefail
    readonly OLD=$(just {{ current_version_recipe }} {{ file_path }}); \
    [ -z "$OLD" ] \
        && ( \
            echo "{{ RED }}{{ file_name }}{{ NORMAL }}: Can't determine {{ version_error_msg_part }} before upgrade" >&2; \
            exit 1; \
        ); \
    sed -r -i "{{ substitude_cmd }}" {{ file_path }} \
        && ( \
            readonly NEW=$(just {{ current_version_recipe }} {{ file_path }}); \
            [ -z "NEW" ] \
                && ( \
                    echo "{{ RED }}{{ file_name }}{{ NORMAL }}: Can't determine {{ version_error_msg_part }} after upgrade" >&2; \
                    exit 1; \
                ); \

            echo "{{ GREEN }}{{ file_name }}{{ NORMAL }}: {{ success_msg_part }}: {{ BOLD }}$OLD{{ NORMAL }} -> {{ BOLD }}$NEW{{ NORMAL }}"; \
        ) \
        || echo "{{ RED }}{{ file_name }}{{ NORMAL }}: Upgrade failed" >&2

# Upgrade GitLab CI's Rust to a given version
_upgrade-rust-ci version: (_upgrade-rust version GITLAB_CI_FILE_PATH GITLAB_CI_FILE_NAME '_current-ci-image' 'CI image version' 'image upgraded' 's|image:\s+\"rust:(' + SED_RUST_VERSION_REGEX + ')-([a-z]+)\"\s*$|image: \"rust:' + version + '-\3\"|')

# Upgrade current Rust version in Cargo.toml
_upgrade-rust-cargo version: (_upgrade-rust version CARGO_FILE_PATH CARGO_FILE_NAME '_current-cargo-rust-version' 'Rust version in ' + CARGO_FILE_NAME 'version upgraded' 's|rust-version\s*=\s*\"(' + SED_RUST_VERSION_REGEX + ')\"|rust-version = \"' + version + '\"|')

# Upgrade current Rust version in clippy.toml
_upgrade-rust-clippy version: (_upgrade-rust version CLIPPY_FILE_PATH CLIPPY_FILE_NAME '_current-clippy-rust-version' 'Rust version in ' + CLIPPY_FILE_NAME 'version upgraded' 's|msrv\s*=\s*\"(' + SED_RUST_VERSION_REGEX + ')\"|msrv = \"' + version + '\"|')

# Upgrade current Rust version in rust-toolchain.toml
_upgrade-rust-toolchain version: (_upgrade-rust version TOOLCHAIN_FILE_PATH TOOLCHAIN_FILE_NAME '_current-toolchain-rust-version' 'Rust version in ' + CLIPPY_FILE_NAME 'version upgraded' 's|channel\s*=\s*\"(' + SED_RUST_VERSION_REGEX + ')\"|channel = \"' + version + '\"|')

# Get version of current stable Rust
#
# Parsed string example: "1.90.0 (1159e78c4 2025-09-14)"
# Parsed components:
# \1 - MAJOR
# \2 - MINOR
# \3 - PATCH
# \4 - HASH
# \5 - RELEASE DATE
# \6 - RELEASE YEAR
# \7 - RELEASE MONTH
# \8 - RELEASE DAY
#
# Example of custom format: just rust-stable-version '\5 \4 \1.\2'

# Ouputs: 2025-09-14 1159e78c4 1.90
_rust-stable-version format=DEFAULT_RUST_STABLE_VERSION_FORMAT:
    #!/usr/bin/env -S bash -euo pipefail
    curl -s {{ RUST_MANIFEST_STABLE_TOML_URL }} \
        | tq {{ RUST_MANIFEST_STABLE_VERSION_QUERY }} \
        | sed -rn 's|{{ RUST_MANIFEST_STABLE_VERSION_PARSE_REGEX }}|{{ format }}|p'

# Extract current CI image in use
_current-ci-image file_path=GITLAB_CI_FILE_PATH:
    #!/usr/bin/env -S bash -euo pipefail
    sed -rn "s|\s*image:\s+\"(rust:({{ SED_RUST_VERSION_REGEX }})-([a-z]+))\"\s*$|\1|p" "{{ file_path }}"

_current-ci-rust-version file_path=GITLAB_CI_FILE_PATH:
    #!/usr/bin/env -S bash -euo pipefail
    sed -rn "s|\s*image:\s+\"rust:({{ SED_RUST_VERSION_REGEX }})-([a-z]+)\"\s*$|\1|p" "{{ file_path }}"

# Extract current Rust version from Cargo.toml
_current-cargo-rust-version file_path=CARGO_FILE_PATH:
    #!/usr/bin/env -S bash -euo pipefail
    tq --file "{{ file_path }}" --raw {{ CARGO_RUST_VERSION_QUERY }}

# Extract current Rust version from clippy.toml
_current-clippy-rust-version file_path=CLIPPY_FILE_PATH:
    #!/usr/bin/env -S bash -euo pipefail
    tq --file "{{ file_path }}" --raw {{ CLIPPY_RUST_VERSION_QUERY }}

# Extract current Rust version from rust-toolchain.toml
_current-toolchain-rust-version file_path=TOOLCHAIN_FILE_PATH:
    #!/usr/bin/env -S bash -euo pipefail
    tq --file "{{ file_path }}" --raw {{ TOOLCHAIN_RUST_VERSION_QUERY }}
