diff options
| author | Kieran Bingham <kieran.bingham@ideasonboard.com> | 2022-11-25 13:52:26 +0000 | 
|---|---|---|
| committer | Kieran Bingham <kieran.bingham@ideasonboard.com> | 2022-11-25 13:52:26 +0000 | 
| commit | 7f07136ed46bd36a22d6fb3bd4b6d3b844815379 (patch) | |
| tree | 5310568903d1031ee4e49461589b1a575a764c8d | |
| parent | 0df9d3ad860ecd78f1bf8ad6bd5ceccaf40a9e07 (diff) | |
scripts: semver: Add version helper
Provide the semver utility (version 3.4.0) from [0] to make use of it
with our versioning and release scripts.
[0] https://raw.githubusercontent.com/fsaintjacques/semver-tool/3c76a6f9d113f4045f693845131185611a62162e/src/semver
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
| -rwxr-xr-x | scripts/semver | 446 | 
1 files changed, 446 insertions, 0 deletions
| diff --git a/scripts/semver b/scripts/semver new file mode 100755 index 0000000..a160425 --- /dev/null +++ b/scripts/semver @@ -0,0 +1,446 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 + +set -o errexit -o nounset -o pipefail + +NAT='0|[1-9][0-9]*' +ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*' +IDENT="$NAT|$ALPHANUM" +FIELD='[0-9A-Za-z-]+' + +SEMVER_REGEX="\ +^[vV]?\ +($NAT)\\.($NAT)\\.($NAT)\ +(\\-(${IDENT})(\\.(${IDENT}))*)?\ +(\\+${FIELD}(\\.${FIELD})*)?$" + +PROG=semver +PROG_VERSION="3.4.0" + +USAGE="\ +Usage: +  $PROG bump major <version> +  $PROG bump minor <version> +  $PROG bump patch <version> +  $PROG bump prerel|prerelease [<prerel>] <version> +  $PROG bump build <build> <version> +  $PROG bump release <version> +  $PROG get major <version> +  $PROG get minor <version> +  $PROG get patch <version> +  $PROG get prerel|prerelease <version> +  $PROG get build <version> +  $PROG get release <version> +  $PROG compare <version> <other_version> +  $PROG diff <version> <other_version> +  $PROG validate <version> +  $PROG --help +  $PROG --version + +Arguments: +  <version>  A version must match the following regular expression: +             \"${SEMVER_REGEX}\" +             In English: +             -- The version must match X.Y.Z[-PRERELEASE][+BUILD] +                where X, Y and Z are non-negative integers. +             -- PRERELEASE is a dot separated sequence of non-negative integers and/or +                identifiers composed of alphanumeric characters and hyphens (with +                at least one non-digit). Numeric identifiers must not have leading +                zeros. A hyphen (\"-\") introduces this optional part. +             -- BUILD is a dot separated sequence of identifiers composed of alphanumeric +                characters and hyphens. A plus (\"+\") introduces this optional part. + +  <other_version>  See <version> definition. + +  <prerel>  A string as defined by PRERELEASE above. Or, it can be a PRERELEASE +            prototype string followed by a dot. + +  <build>   A string as defined by BUILD above. + +Options: +  -v, --version          Print the version of this tool. +  -h, --help             Print this help message. + +Commands: +  bump      Bump by one of major, minor, patch; zeroing or removing +            subsequent parts. \"bump prerel\" (or its synonym \"bump prerelease\") +            sets the PRERELEASE part and removes any BUILD part. A trailing dot +            in the <prerel> argument introduces an incrementing numeric field +            which is added or bumped. If no <prerel> argument is provided, an +            incrementing numeric field is introduced/bumped. \"bump build\" sets +            the BUILD part.  \"bump release\" removes any PRERELEASE or BUILD parts. +            The bumped version is written to stdout. + +  get       Extract given part of <version>, where part is one of major, minor, +            patch, prerel (alternatively: prerelease), build, or release. + +  compare   Compare <version> with <other_version>, output to stdout the +            following values: -1 if <other_version> is newer, 0 if equal, 1 if +            older. The BUILD part is not used in comparisons. + +  diff      Compare <version> with <other_version>, output to stdout the +            difference between two versions by the release type (MAJOR, MINOR, +            PATCH, PRERELEASE, BUILD). + +  validate  Validate if <version> follows the SEMVER pattern (see <version> +            definition). Print 'valid' to stdout if the version is valid, otherwise +            print 'invalid'. + +See also: +  https://semver.org -- Semantic Versioning 2.0.0" + +function error { +  echo -e "$1" >&2 +  exit 1 +} + +function usage_help { +  error "$USAGE" +} + +function usage_version { +  echo -e "${PROG}: $PROG_VERSION" +  exit 0 +} + +# normalize the "part" keywords to a canonical string.  At present, +# only "prerelease" is normalized to "prerel". + +function normalize_part { +    if [ "$1" == "prerelease" ] +    then +	echo "prerel" +    else +	echo "$1" +    fi +} + +function validate_version { +  local version=$1 +  if [[ "$version" =~ $SEMVER_REGEX ]]; then +    # if a second argument is passed, store the result in var named by $2 +    if [ "$#" -eq "2" ]; then +      local major=${BASH_REMATCH[1]} +      local minor=${BASH_REMATCH[2]} +      local patch=${BASH_REMATCH[3]} +      local prere=${BASH_REMATCH[4]} +      local build=${BASH_REMATCH[8]} +      eval "$2=(\"$major\" \"$minor\" \"$patch\" \"$prere\" \"$build\")" +    else +      echo "$version" +    fi +  else +    error "version $version does not match the semver scheme 'X.Y.Z(-PRERELEASE)(+BUILD)'. See help for more information." +  fi +} + +function is_nat { +    [[ "$1" =~ ^($NAT)$ ]] +} + +function is_null { +    [ -z "$1" ] +} + +function order_nat { +    [ "$1" -lt "$2" ] && { echo -1 ; return ; } +    [ "$1" -gt "$2" ] && { echo 1 ; return ; } +    echo 0 +} + +function order_string { +    [[ $1 < $2 ]] && { echo -1 ; return ; } +    [[ $1 > $2 ]] && { echo 1 ; return ; } +    echo 0 +} + +# given two (named) arrays containing NAT and/or ALPHANUM fields, compare them +# one by one according to semver 2.0.0 spec. Return -1, 0, 1 if left array ($1) +# is less-than, equal, or greater-than the right array ($2).  The longer array +# is considered greater-than the shorter if the shorter is a prefix of the longer. +# +function compare_fields { +    local l="$1[@]" +    local r="$2[@]" +    local leftfield=( "${!l}" ) +    local rightfield=( "${!r}" ) +    local left +    local right + +    local i=$(( -1 )) +    local order=$(( 0 )) + +    while true +    do +        [ $order -ne 0 ] && { echo $order ; return ; } + +        : $(( i++ )) +        left="${leftfield[$i]}" +        right="${rightfield[$i]}" + +        is_null "$left" && is_null "$right" && { echo 0  ; return ; } +        is_null "$left"                     && { echo -1 ; return ; } +                           is_null "$right" && { echo 1  ; return ; } + +        is_nat "$left" &&  is_nat "$right" && { order=$(order_nat "$left" "$right") ; continue ; } +        is_nat "$left"                     && { echo -1 ; return ; } +                           is_nat "$right" && { echo 1  ; return ; } +                                              { order=$(order_string "$left" "$right") ; continue ; } +    done +} + +# shellcheck disable=SC2206     # checked by "validate"; ok to expand prerel id's into array +function compare_version { +  local order +  validate_version "$1" V +  validate_version "$2" V_ + +  # compare major, minor, patch + +  local left=( "${V[0]}" "${V[1]}" "${V[2]}" ) +  local right=( "${V_[0]}" "${V_[1]}" "${V_[2]}" ) + +  order=$(compare_fields left right) +  [ "$order" -ne 0 ] && { echo "$order" ; return ; } + +  # compare pre-release ids when M.m.p are equal + +  local prerel="${V[3]:1}" +  local prerel_="${V_[3]:1}" +  local left=( ${prerel//./ } ) +  local right=( ${prerel_//./ } ) + +  # if left and right have no pre-release part, then left equals right +  # if only one of left/right has pre-release part, that one is less than simple M.m.p + +  [ -z "$prerel" ] && [ -z "$prerel_" ] && { echo 0  ; return ; } +  [ -z "$prerel" ]                      && { echo 1  ; return ; } +                      [ -z "$prerel_" ] && { echo -1 ; return ; } + +  # otherwise, compare the pre-release id's + +  compare_fields left right +} + +# render_prerel -- return a prerel field with a trailing numeric string +#                  usage: render_prerel numeric [prefix-string] +# +function render_prerel { +    if [ -z "$2" ] +    then +        echo "${1}" +    else +        echo "${2}${1}" +    fi +} + +# extract_prerel -- extract prefix and trailing numeric portions of a pre-release part +#                   usage: extract_prerel prerel prerel_parts +#                   The prefix and trailing numeric parts are returned in "prerel_parts". +# +PREFIX_ALPHANUM='[.0-9A-Za-z-]*[.A-Za-z-]' +DIGITS='[0-9][0-9]*' +EXTRACT_REGEX="^(${PREFIX_ALPHANUM})*(${DIGITS})$" + +function extract_prerel { +    local prefix; local numeric; + +    if [[ "$1" =~ $EXTRACT_REGEX ]] +    then                                        # found prefix and trailing numeric parts +        prefix="${BASH_REMATCH[1]}" +        numeric="${BASH_REMATCH[2]}" +    else                                        # no numeric part +        prefix="${1}" +        numeric= +    fi + +    eval "$2=(\"$prefix\" \"$numeric\")" +} + +# bump_prerel -- return the new pre-release part based on previous pre-release part +#                and prototype for bump +#                usage: bump_prerel proto previous +# +function bump_prerel { +    local proto; local prev_prefix; local prev_numeric; + +    # case one: no trailing dot in prototype => simply replace previous with proto +    if [[ ! ( "$1" =~ \.$ ) ]] +    then +        echo "$1" +        return +    fi + +    proto="${1%.}"                              # discard trailing dot marker from prototype + +    extract_prerel "${2#-}" prerel_parts        # extract parts of previous pre-release +#   shellcheck disable=SC2154 +    prev_prefix="${prerel_parts[0]}" +    prev_numeric="${prerel_parts[1]}" + +    # case two: bump or append numeric to previous pre-release part +    if [ "$proto" == "+" ]                      # dummy "+" indicates no prototype argument provided +    then +        if [ -n "$prev_numeric" ] +        then +            : $(( ++prev_numeric ))             # previous pre-release is already numbered, bump it +            render_prerel "$prev_numeric" "$prev_prefix" +        else +            render_prerel 1 "$prev_prefix"      # append starting number +        fi +        return +    fi + +    # case three: set, bump, or append using prototype prefix +    if [  "$prev_prefix" != "$proto" ] +    then +        render_prerel 1 "$proto"                # proto not same pre-release; set and start at '1' +    elif [ -n "$prev_numeric" ] +    then +        : $(( ++prev_numeric ))                 # pre-release is numbered; bump it +        render_prerel "$prev_numeric" "$prev_prefix" +    else +        render_prerel 1 "$prev_prefix"          # start pre-release at number '1' +    fi +} + +function command_bump { +  local new; local version; local sub_version; local command; + +  command="$(normalize_part "$1")" + +  case $# in +    2) case "$command" in +        major|minor|patch|prerel|release) sub_version="+."; version=$2;; +        *) usage_help;; +       esac ;; +    3) case "$command" in +        prerel|build) sub_version=$2 version=$3 ;; +        *) usage_help;; +       esac ;; +    *) usage_help;; +  esac + +  validate_version "$version" parts +  # shellcheck disable=SC2154 +  local major="${parts[0]}" +  local minor="${parts[1]}" +  local patch="${parts[2]}" +  local prere="${parts[3]}" +  local build="${parts[4]}" + +  case "$command" in +    major) new="$((major + 1)).0.0";; +    minor) new="${major}.$((minor + 1)).0";; +    patch) new="${major}.${minor}.$((patch + 1))";; +    release) new="${major}.${minor}.${patch}";; +    prerel) new=$(validate_version "${major}.${minor}.${patch}-$(bump_prerel "$sub_version" "$prere")");; +    build) new=$(validate_version "${major}.${minor}.${patch}${prere}+${sub_version}");; +    *) usage_help ;; +  esac + +  echo "$new" +  exit 0 +} + +function command_compare { +  local v; local v_; + +  case $# in +    2) v=$(validate_version "$1"); v_=$(validate_version "$2") ;; +    *) usage_help ;; +  esac + +  set +u                        # need unset array element to evaluate to null +  compare_version "$v" "$v_" +  exit 0 +} + +function command_diff { +  validate_version "$1" v1_parts +  # shellcheck disable=SC2154 +  local v1_major="${v1_parts[0]}" +  local v1_minor="${v1_parts[1]}" +  local v1_patch="${v1_parts[2]}" +  local v1_prere="${v1_parts[3]}" +  local v1_build="${v1_parts[4]}" + +  validate_version "$2" v2_parts +  # shellcheck disable=SC2154 +  local v2_major="${v2_parts[0]}" +  local v2_minor="${v2_parts[1]}" +  local v2_patch="${v2_parts[2]}" +  local v2_prere="${v2_parts[3]}" +  local v2_build="${v2_parts[4]}" + +  if [ "${v1_major}" != "${v2_major}" ]; then +    echo "major" +  elif [ "${v1_minor}" != "${v2_minor}" ]; then +    echo "minor" +  elif [ "${v1_patch}" != "${v2_patch}" ]; then +    echo "patch" +  elif [ "${v1_prere}" != "${v2_prere}" ]; then +    echo "prerelease" +  elif [ "${v1_build}" != "${v2_build}" ]; then +    echo "build" +  fi +} + +# shellcheck disable=SC2034 +function command_get { +    local part version + +    if [[ "$#" -ne "2" ]] || [[ -z "$1" ]] || [[ -z "$2" ]]; then +        usage_help +        exit 0 +    fi + +    part="$1" +    version="$2" + +    validate_version "$version" parts +    local major="${parts[0]}" +    local minor="${parts[1]}" +    local patch="${parts[2]}" +    local prerel="${parts[3]:1}" +    local build="${parts[4]:1}" +    local release="${major}.${minor}.${patch}" + +    part="$(normalize_part "$part")" + +    case "$part" in +        major|minor|patch|release|prerel|build) echo "${!part}" ;; +        *) usage_help ;; +    esac + +    exit 0 +} + +function command_validate { +  if [[ "$#" -ne "1" ]]; then +        usage_help +  fi   +   +  if [[ "$1" =~ $SEMVER_REGEX ]]; then +    echo "valid" +  else +    echo "invalid" +  fi + +  exit 0 +} + +case $# in +  0) echo "Unknown command: $*"; usage_help;; +esac + +case $1 in +  --help|-h) echo -e "$USAGE"; exit 0;; +  --version|-v) usage_version ;; +  bump) shift; command_bump "$@";; +  get) shift; command_get "$@";; +  compare) shift; command_compare "$@";; +  diff) shift; command_diff "$@";; +  validate) shift; command_validate "$@";; +  *) echo "Unknown arguments: $*"; usage_help;; +esac | 
