[libcamera-devel,2/4] utils: semver: Add version helper
diff mbox series

Message ID 20220929143626.3100668-3-kieran.bingham@ideasonboard.com
State Superseded
Headers show
Series
  • Add release infrastructure
Related show

Commit Message

Kieran Bingham Sept. 29, 2022, 2:36 p.m. UTC
Provide the semver utility from [0] to make use of it with our
versioning and release scripts.

[0] https://github.com/fsaintjacques/semver-tool

Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
---
 utils/semver | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 419 insertions(+)
 create mode 100755 utils/semver

Comments

Jacopo Mondi Sept. 30, 2022, 3:43 p.m. UTC | #1
Hi Kieran

I wonder if importing from a raw file is the best approach here, we'll
have to track updates manually...

At the same time a meson subproject might be an overkill, so I'm kind
of debated...

On Thu, Sep 29, 2022 at 03:36:24PM +0100, Kieran Bingham via libcamera-devel wrote:
> Provide the semver utility from [0] to make use of it with our
> versioning and release scripts.
>
> [0] https://github.com/fsaintjacques/semver-tool
>
> Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> ---
>  utils/semver | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 419 insertions(+)
>  create mode 100755 utils/semver
>
> diff --git a/utils/semver b/utils/semver
> new file mode 100755
> index 000000000000..5b25f40ba48c
> --- /dev/null
> +++ b/utils/semver
> @@ -0,0 +1,419 @@
> +#!/usr/bin/env bash
> +
> +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.3.0"
> +
> +USAGE="\
> +Usage:
> +  $PROG bump (major|minor|patch|release|prerel [<prerel>]|build <build>) <version>
> +  $PROG compare <version> <other_version>
> +  $PROG diff <version> <other_version>
> +  $PROG get (major|minor|patch|release|prerel|build) <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 (or empty) 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\" 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.
> +
> +  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).
> +
> +  get       Extract given part of <version>, where part is one of major, minor,
> +            patch, prerel, build, or release.
> +
> +  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
> +}
> +
> +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;
> +
> +  case $# in
> +    2) case $1 in
> +        major|minor|patch|prerel|release) command=$1; sub_version="+."; version=$2;;
> +        *) usage_help;;
> +       esac ;;
> +    3) case $1 in
> +        prerel|build) command=$1; 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}"
> +
> +    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
> --
> 2.34.1
>
Laurent Pinchart Sept. 30, 2022, 8:42 p.m. UTC | #2
On Fri, Sep 30, 2022 at 05:43:22PM +0200, Jacopo Mondi via libcamera-devel wrote:
> Hi Kieran
> 
> I wonder if importing from a raw file is the best approach here, we'll
> have to track updates manually...
> 
> At the same time a meson subproject might be an overkill, so I'm kind
> of debated...

I think it would be overkill indeed. This is a small script, I don't
expect it to be updated often (if ever), so I think it's fine as-is.

> On Thu, Sep 29, 2022 at 03:36:24PM +0100, Kieran Bingham via libcamera-devel wrote:
> > Provide the semver utility from [0] to make use of it with our
> > versioning and release scripts.
> >
> > [0] https://github.com/fsaintjacques/semver-tool

Following the comments on patch 3/4, the commit ID (or tag) should be
specified here.

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> > Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> > ---
> >  utils/semver | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 419 insertions(+)
> >  create mode 100755 utils/semver
> >
> > diff --git a/utils/semver b/utils/semver
> > new file mode 100755
> > index 000000000000..5b25f40ba48c
> > --- /dev/null
> > +++ b/utils/semver
> > @@ -0,0 +1,419 @@
> > +#!/usr/bin/env bash
> > +
> > +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.3.0"
> > +
> > +USAGE="\
> > +Usage:
> > +  $PROG bump (major|minor|patch|release|prerel [<prerel>]|build <build>) <version>
> > +  $PROG compare <version> <other_version>
> > +  $PROG diff <version> <other_version>
> > +  $PROG get (major|minor|patch|release|prerel|build) <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 (or empty) 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\" 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.
> > +
> > +  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).
> > +
> > +  get       Extract given part of <version>, where part is one of major, minor,
> > +            patch, prerel, build, or release.
> > +
> > +  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
> > +}
> > +
> > +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;
> > +
> > +  case $# in
> > +    2) case $1 in
> > +        major|minor|patch|prerel|release) command=$1; sub_version="+."; version=$2;;
> > +        *) usage_help;;
> > +       esac ;;
> > +    3) case $1 in
> > +        prerel|build) command=$1; 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}"
> > +
> > +    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

Patch
diff mbox series

diff --git a/utils/semver b/utils/semver
new file mode 100755
index 000000000000..5b25f40ba48c
--- /dev/null
+++ b/utils/semver
@@ -0,0 +1,419 @@ 
+#!/usr/bin/env bash
+
+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.3.0"
+
+USAGE="\
+Usage:
+  $PROG bump (major|minor|patch|release|prerel [<prerel>]|build <build>) <version>
+  $PROG compare <version> <other_version>
+  $PROG diff <version> <other_version>
+  $PROG get (major|minor|patch|release|prerel|build) <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 (or empty) 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\" 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.
+
+  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).
+
+  get       Extract given part of <version>, where part is one of major, minor,
+            patch, prerel, build, or release.
+
+  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
+}
+
+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;
+
+  case $# in
+    2) case $1 in
+        major|minor|patch|prerel|release) command=$1; sub_version="+."; version=$2;;
+        *) usage_help;;
+       esac ;;
+    3) case $1 in
+        prerel|build) command=$1; 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}"
+
+    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