From dff126f6ccb249c6bcde753a41aea9ec356fdf0e Mon Sep 17 00:00:00 2001 From: David Parker Date: Wed, 16 Oct 2024 16:02:44 +0100 Subject: [PATCH] Updates --- .gitignore | 4 +- build/README.md | 21 +++ build/bin/.env.sh | 68 +++++++ build/bin/.functions.sh | 80 ++++++++ build/bin/initbuild.sh | 111 ++++++++++++ build/bin/semver | 391 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 673 insertions(+), 2 deletions(-) create mode 100644 build/README.md create mode 100644 build/bin/.env.sh create mode 100644 build/bin/.functions.sh create mode 100644 build/bin/initbuild.sh create mode 100644 build/bin/semver diff --git a/.gitignore b/.gitignore index adff9ae..16e515e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ _build/* src/wiotp_sdk.egg-info/* MANIFEST README.txt -build/* dist/* .coverage tox.out @@ -25,5 +24,6 @@ samples/*.exe samples/rbac-config.yaml test/.DS_Store /venv +/.venv pandoc-*-amd64.deb -README.rst +README.rst diff --git a/build/README.md b/build/README.md new file mode 100644 index 0000000..ba5a65c --- /dev/null +++ b/build/README.md @@ -0,0 +1,21 @@ +## Overview + +Very cut back version of continuous integration build system for IBM Maximo Application Suite, based on Travis CI, utilizing automated semantic versioning of releases. + +## Concepts + +**Managed Branches** are ones where any commit will result in a new version being released. These branches take one of two forms: +- `master` (current major version) +- `v1.x`, `v2.x`, `v3.x`, `v4.x` etc + +Versioning is controlled by the GitHub commit messages: +- A commit comment of `[patch] Fix the thing` will result in a version change from `1.0.0` to `1.0.1` +- A commit comment of `[minor] Add the new feature` will result in a version change from `1.0.0` to `1.1.0` +- A commit comment of `[major] Rewrite the thing` will result in a version change from `1.0.0` to `2.0.0` +- A commit comment without any of the above will result in build with no new release being published +- A commit comment of `[skip ci] Something not worth a new build` will result in Travis not even running + +When a build contains multiple commits the most significant commit "wins". + +Commits in **Unmanaged Branches** will still result in a new build, but any artifacts published will overwrite the previous release. Effectively there is only a latest version for these branches. + diff --git a/build/bin/.env.sh b/build/bin/.env.sh new file mode 100644 index 0000000..25d5747 --- /dev/null +++ b/build/bin/.env.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# GitHub Actions environment variables documentation: +# https://docs.github.com/en/actions/learn-github-actions/environment-variables + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export PATH=$PATH:$DIR:$DIR/ptc + +# Version file (semver) +export VERSION_FILE=${GITHUB_WORKSPACE}/.version +if [ -f "$VERSION_FILE" ]; then + export VERSION=$(cat ${VERSION_FILE}) +fi + +export VERSION_FILE_NOPREREL=${GITHUB_WORKSPACE}/.version-noprerel +if [ -f "$VERSION_FILE_NOPREREL" ]; then + export VERSION_NOPREREL=$(cat ${VERSION_FILE_NOPREREL}) +fi + +# During initbuild we record the release level (aka the version bump from the last release) +export SEMVER_RELEASE_LEVEL_FILE=${GITHUB_WORKSPACE}/.releaselevel +if [ -f "$SEMVER_RELEASE_LEVEL_FILE" ]; then + export SEMVER_RELEASE_LEVEL=$(cat ${SEMVER_RELEASE_LEVEL_FILE}) +fi + +# Docker does not support "+" characters from semvar syntax so we replace "+" with "_" +# We should not actually deploy any "+build" releases anyway +export DOCKER_TAG=$(echo "$VERSION" | sed -e's/\+/_/g') + +if [ -z $BUILD_SYSTEM_ENV_LOADED ]; then + echo "BUILD_SYSTEM_ENV_LOADED is not defined yet" + export BUILD_SYSTEM_ENV_LOADED=1 + + if [ ! -z $GITHUB_ENV ]; then + # https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#environment-files + echo "GITHUB_ENV is defined, exporting properties to $GITHUB_ENV" + + echo "VERSION_FILE=$VERSION_FILE" >> $GITHUB_ENV + echo "VERSION_FILE_NOPREREL=$VERSION_FILE_NOPREREL" >> $GITHUB_ENV + echo "VERSION=$VERSION" >> $GITHUB_ENV + echo "VERSION_NOPREREL=$VERSION_NOPREREL" >> $GITHUB_ENV + echo "DOCKER_TAG=$DOCKER_TAG" >> $GITHUB_ENV + + echo "SEMVER_RELEASE_LEVEL_FILE=$SEMVER_RELEASE_LEVEL_FILE" >> $GITHUB_ENV + echo "SEMVER_RELEASE_LEVEL=$SEMVER_RELEASE_LEVEL" >> $GITHUB_ENV + + echo "BUILD_SYSTEM_ENV_LOADED=1" >> $GITHUB_ENV + else + echo "GITHUB_ENV is not defined" + fi + + echo_h1 "Build Properties" + echo_highlight "DIR ........................ $DIR" + echo_highlight "PATH ....................... $PATH" + echo_highlight "" + echo_highlight "VERSION_FILE ............... $VERSION_FILE" + echo_highlight "VERSION_FILE_NOPREREL ...... $VERSION_FILE_NOPREREL" + echo_highlight "VERSION .................... $VERSION" + echo_highlight "VERSION_NOPREREL ........... $VERSION_NOPREREL" + echo_highlight "DOCKER_TAG ................. $DOCKER_TAG" + echo_highlight "" + echo_highlight "SEMVER_RELEASE_LEVEL_FILE .. $SEMVER_RELEASE_LEVEL_FILE" + echo_highlight "SEMVER_RELEASE_LEVEL ....... $SEMVER_RELEASE_LEVEL" +else + echo "BUILD_SYSTEM_ENV_LOADED is already defined, skipping debug and export to GitHub env file" +fi + + diff --git a/build/bin/.functions.sh b/build/bin/.functions.sh new file mode 100644 index 0000000..21929a3 --- /dev/null +++ b/build/bin/.functions.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# COLOR_RED=`tput setaf 1` +# COLOR_GREEN=`tput setaf 2` +# COLOR_YELLOW=`tput setaf 3` +# COLOR_BLUE=`tput setaf 4` +# COLOR_MAGENTA=`tput setaf 5` +# COLOR_CYAN=`tput setaf 6` +# TEXT_RESET=`tput sgr0` + +# tput doesn't work in GitHub actions +# TODO: Integrate properly with GitHub actions to annotate the output as errors etc +COLOR_RED="" +COLOR_GREEN="" +COLOR_YELLOW="" +COLOR_BLUE="" +COLOR_MAGENTA="" +COLOR_CYAN="" +TEXT_RESET="" + + +function echo_h1() { + echo "${COLOR_YELLOW}================================================================================" + echo "${COLOR_YELLOW}$1" + echo "${COLOR_YELLOW}================================================================================" +} + + +function echo_h2() { + echo "${COLOR_YELLOW}$1" + echo "${COLOR_YELLOW}--------------------------------------------------------------------------------" +} + + +function echo_warning() { + echo "${COLOR_RED}$1" +} + + +function echo_highlight() { + echo "${COLOR_MAGENTA}$1" +} + + +# Install yq +# ---------- +function install_yq() { + python -m pip install yq || exit 1 +} + + +# These should be loaded already, but just incase! +# ------------------------------------------------ +if [[ -z "$BUILD_SYSTEM_ENV_LOADED" ]]; then + source $DIR/.env.sh +fi + + +# Upload a file to Artifactory +# ----------------------------------------------------------------------------- +# Usage example: +# artifactory_upload $FILE_PATH $TARGET_URL +# +function artifactory_upload() { + if [ ! -e $1 ]; then + echo_warning "Artifactory upload failed - $1 does not exist" + exit 1 + fi + + md5Value="`md5sum "$1"`" + md5Value="${md5Value:0:32}" + + sha1Value="`sha1sum "$1"`" + sha1Value="${sha1Value:0:40}" + + echo "Uploading $1 to $2" + curl -H "Authorization:Bearer $ARTIFACTORY_TOKEN" -H "X-Checksum-Md5: $md5Value" -H "X-Checksum-Sha1: $sha1Value" -T $1 $2 || exit 1 +} diff --git a/build/bin/initbuild.sh b/build/bin/initbuild.sh new file mode 100644 index 0000000..60f4f05 --- /dev/null +++ b/build/bin/initbuild.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +# Simplified port of a portion of the MAS common build system for public GitHub +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PATH=$PATH:$DIR + +pip install --quiet pyyaml yamllint + +# 1. Set up semantic versioning +# ----------------------------------------------------------------------------- +VERSION_FILE=$GITHUB_WORKSPACE/.version +VERSION_FILE_NOPREREL=$GITHUB_WORKSPACE/.version-noprerel + +if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then + echo "Note: non-branch build for a tag named '${GITHUB_REF_NAME}'" + TAG_BASED_RELEASE=true + + SEMVER_XYZ="(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)" + SEMVER_PRE="(-(0|[1-9][0-9]*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*)?" + SEMVER_BUILD="(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?" + SEMVER_REGEXP="^${SEMVER_XYZ}${SEMVER_PRE}${SEMVER_BUILD}$" + + if [[ ! $GITHUB_REF_NAME =~ $SEMVER_REGEXP ]]; then + echo "Aborting release build. Tag '$GITHUB_REF_NAME' does not match a valid semantic version string" + exit 1 + fi + + echo "${GITHUB_REF_NAME}" > $VERSION_FILE +else + # Finds the most recent tag that is reachable from a commit. If the tag points + # to the commit, then only the tag is shown. Otherwise, it suffixes the tag name with the number + # of additional commits on top of the tagged object and the abbreviated object name of the most + # recent commit. + echo "npm install of git-latest-semver-tag starting" + npm install -g git-latest-semver-tag@1.0.2 + echo "- npm install complete" + SEMVER_LAST_TAG=$(git-latest-semver-tag 2>/dev/null) + + echo "LAST TAG = ${SEMVER_LAST_TAG}" + + if [ -z $SEMVER_LAST_TAG ]; then + SEMVER_LAST_TAG="1.0.0" + SEMVER_RELEASE_LEVEL="initial" + echo "Creating $GITHUB_WORKSPACE/.changelog" + # Obtain a list of commits since dawn of time + git log --oneline -1 --pretty=%B > $GITHUB_WORKSPACE/.changelog + else + echo "Creating $GITHUB_WORKSPACE/.changelog (${SEMVER_LAST_TAG}..HEAD)" + # Obtain a list of commits since ${SEMVER_LAST_TAG} + git log ${SEMVER_LAST_TAG}..HEAD --oneline --pretty=%B > $GITHUB_WORKSPACE/.changelog + + echo "Changelog START:##################################################################" + cat $GITHUB_WORKSPACE/.changelog + echo "Changelog DONE:###################################################################" + + # Work out what has changed + MAJOR_COUNT=`grep -ciF '[major]' $GITHUB_WORKSPACE/.changelog` + echo "Major commits : ${MAJOR_COUNT}" + + MINOR_COUNT=`grep -ciF '[minor]' $GITHUB_WORKSPACE/.changelog` + echo "Minor commits : ${MINOR_COUNT}" + + PATCH_COUNT=`grep -ciF '[patch]' $GITHUB_WORKSPACE/.changelog` + echo "Patch commits : ${PATCH_COUNT}" + + if [ "$MAJOR_COUNT" -gt "0" ]; then + SEMVER_RELEASE_LEVEL="major" + elif [ "$MINOR_COUNT" -gt "0" ]; then + SEMVER_RELEASE_LEVEL="minor" + elif [ "$PATCH_COUNT" -gt "0" ]; then + SEMVER_RELEASE_LEVEL="patch" + fi + fi + + echo "RELEASE LEVEL = ${SEMVER_RELEASE_LEVEL}" + echo "${SEMVER_RELEASE_LEVEL}" > $GITHUB_WORKSPACE/.releaselevel + + # See: https://github.com/fsaintjacques/semver-tool/blob/master/src/semver + if [ "${SEMVER_RELEASE_LEVEL}" == "initial" ]; then + echo "1.0.0" > $VERSION_FILE + echo "Configuring semver for initial release of $(cat $VERSION_FILE)" + elif [[ "${SEMVER_RELEASE_LEVEL}" =~ ^(major|minor|patch)$ ]]; then + semver bump ${SEMVER_RELEASE_LEVEL} ${SEMVER_LAST_TAG} > $VERSION_FILE + echo "Configuring semver for ${SEMVER_RELEASE_LEVEL} bump from ${SEMVER_LAST_TAG} to $(cat $VERSION_FILE)" + else + # Default to a patch revision + semver bump patch ${SEMVER_LAST_TAG} > $VERSION_FILE + echo "Configuring semver for rebuild of ${SEMVER_LAST_TAG}: $(cat $VERSION_FILE)" + fi +fi + + +# 2. Tweak version string for pre-release builds +# ----------------------------------------------------------------------------- +cp $VERSION_FILE $VERSION_FILE_NOPREREL +if [[ "${GITHUB_REF_TYPE}" == "branch" ]]; then + semver bump prerel pre.$GITHUB_REF_NAME $(cat $VERSION_FILE) > $VERSION_FILE + echo "Pre-release build: $(cat $VERSION_FILE)" +fi + +echo "Semantic versioning system initialized: $(cat $VERSION_FILE)" + + +# 3. Version python modules (if they exist) +# ----------------------------------------------------------------------------- +if [ -f ${GITHUB_WORKSPACE}/setup.py ]; then + sed -i "s/version='1.0.0'/version='${VERSION}'/" setup.py +fi + + +exit 0 diff --git a/build/bin/semver b/build/bin/semver new file mode 100644 index 0000000..86e6039 --- /dev/null +++ b/build/bin/semver @@ -0,0 +1,391 @@ +#!/usr/bin/env bash + +# https://github.com/fsaintjacques/semver-tool/blob/master/src/semver + +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.2.0" + +USAGE="\ +Usage: + $PROG bump (major|minor|patch|release|prerel []|build ) + $PROG compare + $PROG diff + $PROG get (major|minor|patch|release|prerel|build) + $PROG --help + $PROG --version +Arguments: + 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. + See definition. + A string as defined by PRERELEASE above. Or, it can be a PRERELEASE + prototype string (or empty) followed by a dot. + 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 argument + introduces an incrementing numeric field which is added or + bumped. If no 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 with , output to stdout the + following values: -1 if is newer, 0 if equal, 1 if + older. The BUILD part is not used in comparisons. + diff Compare with , output to stdout the + difference between two versions by the release type (MAJOR, MINOR, + PATCH, PRERELEASE, BUILD). + get Extract given part of , where part is one of major, minor, + patch, prerel, build, or release. +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 +} + +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 "$@";; + *) echo "Unknown arguments: $*"; usage_help;; +esac