# -*- mode: sh; sh-shell: bash -*-
# vim: set ft=bash:
# shellcheck shell=bash
# shellcheck disable=2016

### Parsing and manipulating (simplified) semantic versioning strings
###
### For the sake of sanity not all possible semver syntax variants are
### handled. Esp. manipulation is only implemented for the common cases. Patches welcome.

function semver_parse ## <semver> [major [minor [patch [prerelease [build]]]]] - parses semver into the provided variables
{
    if [[ "$1" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)((-.*)*)((\+.*)*)$ ]]; then
        iset "${2:-}" "${BASH_REMATCH[1]}"
        iset "${3:-}" "${BASH_REMATCH[2]}"
        iset "${4:-}" "${BASH_REMATCH[3]}"
        iset "${5:-}" "${BASH_REMATCH[4]}"
        iset "${6:-}" "${BASH_REMATCH[6]}"
        return 0
    else
        error "invalid semver: $1"
        return 1
    fi
}

function semver_validate ## <version> - Validate if string is a valid semver
{
    semver_parse "$1"
}

function semver_shortversion ## <version> - shortens a semver to the first non zero part 0.2.1-pre0+123 -> 0.2
{
    # shellcheck disable=2034
    local major minor patch
    semver_parse "$1" major minor patch || return 1

    case "$major.$minor" in
    "0.0")
        echo "$major.$minor.$patch"
        ;;
    "0"*)
        echo "$major.$minor"
        ;;
    *)
        echo "$major"
        ;;
    esac
}

function semver_major ## <version> - shortens a semver to major
{
    # shellcheck disable=2034
    local major
    semver_parse "$1" major || return 1

    echo "$major"
}

function semver_majorpre1x ## <version> - shortens a semver to major with 0.x major semantic
{
    # shellcheck disable=2034
    local major minor
    semver_parse "$1" major minor || return 1

    if [[ "$major" = 0 ]]; then
        echo "$major.$minor"
    else
        echo "$major"
    fi
}

function semver_majorminor ## <version> - shortens a semver to major.minor
{
    # shellcheck disable=2034
    local major minor
    semver_parse "$1" major minor || return 1

    echo "$major.$minor"
}

function semver_is_patch ## <version>
{
    # shellcheck disable=2034
    local major minor patch
    semver_parse "$1" major minor patch || return 1
    [[ "$patch" != 0 ]]
}

function semver_increment ## <version> <release|major|majorpre1x|minor|patch> - Increment selected part
{
    ## 'release' strips any 'prerelease' part
    ## 'majorpre1x' increments the minor part on a 0.x version
    local major minor patch prerelease build
    semver_parse "$1" major minor patch prerelease build || return 1

    local level="$2"
    if [[ "$level" = 'majorpre1x' ]]; then
        if [[ "$major" = 0 ]]; then
            level="minor"
        else
            level="major"
        fi
    fi

    case "$level" in
    release) # strips the prerelease
        [[ -n "$prerelease" ]] || {
            error "can't strip prerelease from $1"
            return 1
        }
        prerelease=""
        ;;
    major)
        [[ -z "$prerelease" ]] || {
            error "can't increment major on a prerelease"
            return 1
        }
        ((major++))
        minor=0
        patch=0
        ;;
    minor)
        [[ -z "$prerelease" ]] || {
            error "can't increment minor on a prerelease"
            return 1
        }
        ((minor++))
        patch=0
        ;;
    patch)
        [[ -z "$prerelease" ]] || {
            error "can't increment patch on a prerelease"
            return 1
        }
        ((patch++))
        ;;
    *)
        error "semver_increment: unknown part $level"
        return 1
        ;;
    esac

    echo "$major.$minor.$patch$prerelease$build"
}

function semver_cmp ## <a> <b> - comparing 'a' with 'b', prints 'lt', 'eq' or 'gt'
{
    local major_a minor_a patch_a prerelease_a
    semver_parse "$1" major_a minor_a patch_a prerelease_a || return 1

    local major_b minor_b patch_b prerelease_b
    semver_parse "$2" major_b minor_b patch_b prerelease_b || return 1

    if (( major_a < major_b )); then echo "lt"; return; fi
    if (( major_a > major_b )); then echo "gt"; return; fi
    if (( minor_a < minor_b )); then echo "lt"; return; fi
    if (( minor_a > minor_b )); then echo "gt"; return; fi
    if (( patch_a < patch_b )); then echo "lt"; return; fi
    if (( patch_a > patch_b )); then echo "gt"; return; fi

    if [[ -n "$prerelease_a" && -z "$prerelease_b" ]]; then echo "lt"; return; fi
    if [[ -z "$prerelease_a" && -n "$prerelease_b" ]]; then echo "gt"; return; fi
    if [[ "$prerelease_a" < "$prerelease_b" ]]; then echo "lt"; return; fi
    if [[ "$prerelease_a" > "$prerelease_b" ]]; then echo "gt"; return; fi

    echo "eq"
}

function semver_lt ## <a> <b> - succeeds if a < b
{
    case $(semver_cmp "$1" "$2") in
    lt)
        return 0
        ;;
    *)
        return 1
    esac
}

function semver_le ## <a> <b> - succeeds if a <= b
{
    case $(semver_cmp "$1" "$2") in
    lt|eq)
        return 0
        ;;
    *)
        return 1
    esac
}

function semver_gt ## <a> <b> - succeeds if a > b
{
    case $(semver_cmp "$1" "$2") in
    gt)
        return 0
        ;;
    *)
        return 1
    esac
}

function semver_ge ## <a> <b> - succeeds if a >= b
{
    case $(semver_cmp "$1" "$2") in
    gt|eq)
        return 0
        ;;
    *)
        return 1
    esac
}

function semver_eq ## <a> <b> - succeeds if a == b
{
    case $(semver_cmp "$1" "$2") in
    eq)
        return 0
        ;;
    *)
        return 1
    esac
}

# Define --bare rules for all semver functions
rule --bare semver_parse: -
rule --bare semver_validate: -
rule --bare semver_shortversion: -
rule --bare semver_major: -
rule --bare semver_majorpre1x: -
rule --bare semver_majorminor: -
rule --bare semver_is_patch: -
rule --bare semver_increment: -
rule --bare semver_cmp: -
rule --bare semver_lt: -
rule --bare semver_le: -
rule --bare semver_gt: -
rule --bare semver_ge: -
rule --bare semver_eq: -
