1#!/usr/bin/env bash 2 3set -o errexit -o nounset -o pipefail 4 5SEMVER_REGEX="^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$" 6 7PROG=semver 8PROG_VERSION=1.0.0 9 10VERSION_FILE=vulndb/version.txt 11DEFAULT_VERSION=0.1.0 12 13USAGE="\ 14Usage: 15 $PROG 16 $PROG init [<version>] 17 $PROG bump [(major|minor|patch|prerel <prerel>|build <build>) | --force <version>] [--pretend] 18 $PROG compare <version> [<oldversion>] 19 $PROG --help 20 $PROG --version 21 22Arguments: 23 <version> A version must match the following regex pattern: 24 \"${SEMVER_REGEX}\". 25 In english, the version must match X.Y.Z(-PRERELEASE)(+BUILD) 26 where X, Y and Z are positive integers, PRERELEASE is an optionnal 27 string composed of alphanumeric characters and hyphens and 28 BUILD is also an optional string composed of alphanumeric 29 characters and hyphens. 30 31 <oldversion> See <version> definition. 32 33 <prerel> String that must be composed of alphanumeric characters and hyphens. 34 35 <build> String that must be composed of alphanumeric characters and hyphens. 36 37Options: 38 -f, --force=<version> Forces a bump of any version without checking if it 39 respects semver bumping rules. 40 -p, --pretend Do not overwrite the project's version file, only 41 output what the new version string would be. 42 -v, --version Print the version of this tool. 43 -h, --help Print this help message. 44 45Commands: 46 init initialize this project's version. 47 bump this project's version by one of major, minor, patch, prerel, build 48 or a forced potentialy conflicting version. 49 compare <version> to this project's version or to provided <oldversion>." 50 51 52function warning { 53 echo -e "$1" >&2 54} 55 56function error { 57 echo -e "$1" >&2 58 exit 1 59} 60 61function usage-help { 62 error "$USAGE" 63} 64 65function usage-version { 66 echo -e "${PROG}: $PROG_VERSION" 67 exit 0 68} 69 70function validate-version { 71 local version=$1 72 if [[ "$version" =~ $SEMVER_REGEX ]]; then 73 # if a second argument is passed, store the result in var named by $2 74 if [ "$#" -eq "2" ]; then 75 local major=${BASH_REMATCH[1]} 76 local minor=${BASH_REMATCH[2]} 77 local patch=${BASH_REMATCH[3]} 78 local prere=${BASH_REMATCH[4]} 79 local build=${BASH_REMATCH[5]} 80 eval "$2=(\"$major\" \"$minor\" \"$patch\" \"$prere\" \"$build\")" 81 else 82 echo "$version" 83 fi 84 else 85 error "version $version does not match the semver scheme 'X.Y.Z(-PRERELEASE)(+BUILD)'. See help for more information." 86 fi 87} 88 89# this function will reverse-traverse folders until VERSION_FILE is found 90# or '/' is reached. 91function get-version { 92 while [ -w . ]; do 93 if [ -e $VERSION_FILE ]; then 94 validate-version "$(cat $VERSION_FILE)" 95 return 0 96 fi 97 98 pushd .. > /dev/null 99 done 100 101 error "Version file $VERSION_FILE not found, you may want to initialize this project with 'version init'" 102} 103 104function compare-version { 105 validate-version "$1" V 106 validate-version "$2" V_ 107 108 # MAJOR, MINOR and PATCH should compare numericaly 109 for i in 0 1 2; do 110 case $((${V[$i]} - ${V_[$i]})) in 111 0) ;; 112 -[0-9]*) echo -1; return 0;; 113 [0-9]*) echo 1; return 0 ;; 114 esac 115 done 116 117 # PREREL should compare with the ASCII order. 118 if [[ "${V[3]}" > "${V_[3]}" ]]; then 119 echo 1; return 0; 120 elif [[ "${V[3]}" < "${V_[3]}" ]]; then 121 echo -1; return 0; 122 fi 123 124 echo 0 125} 126 127function cli-print { 128 get-version 129 exit 0 130} 131 132function command-init { 133 local version="" 134 case $# in 135 0) version="$DEFAULT_VERSION" ;; 136 1) version=$(validate-version "$1") ;; 137 2) usage-help;; 138 esac 139 140 if [ -e "$VERSION_FILE" ]; then 141 error "version file $VERSION_FILE exists, cannot initialize project. Either remove the current file or use 'version bump --force <newversion>'." 142 fi 143 144 echo "$version" | tee "$VERSION_FILE" 145 exit 0 146} 147 148function command-bump { 149 local new; local pretend=0; local version=$(get-version) 150 validate-version $version split 151 local major=${split[0]} 152 local minor=${split[1]} 153 local patch=${split[2]} 154 local prere=${split[3]} 155 local build=${split[4]} 156 while [[ $# -gt 0 ]]; do 157 case "$1" in 158 major) new="$(($major + 1)).0.0"; shift ;; 159 minor) new="${major}.$(($minor + 1)).0"; shift ;; 160 patch) new="${major}.${minor}.$(($patch + 1))"; shift ;; 161 prerel) 162 if [[ $# -lt 2 ]]; then 163 usage-help 164 else 165 new=$(validate-version "${major}.${minor}.${patch}-${2}") 166 shift 2 167 fi ;; 168 build) 169 if [[ $# -lt 2 ]]; then 170 usage-help 171 else 172 new=$(validate-version "${major}.${minor}.${patch}${prere}+${2}") 173 shift 2 174 fi ;; 175 --force|-f) 176 if [[ $# -lt 2 ]]; then 177 usage-help 178 else 179 new=$(validate-version "$2") 180 shift 2 181 fi ;; 182 --pretend|-p) pretend=1; shift ;; 183 "") break;; 184 *) usage-help ;; 185 esac 186 done 187 188 if [[ "$pretend" -eq 1 ]]; then 189 echo $new 190 else 191 echo $new | tee $VERSION_FILE 192 fi 193 exit 0 194} 195 196function command-compare { 197 local v; local v_; local version=$(get-version) 198 199 case $# in 200 0) usage-help ;; 201 1) v=$(validate-version "$1"); v_=$version ;; 202 2) v=$(validate-version "$1"); v_=$(validate-version "$2") ;; 203 esac 204 205 echo $(compare-version "$v" "$v_") 206 exit 0 207} 208 209case $# in 210 0) cli-print ;; 211esac 212 213case $1 in 214 --help|-h) echo -e "$USAGE"; exit 0;; 215 --version|-v) usage-version ;; 216 init) shift; command-init $@;; 217 bump) shift; command-bump $@;; 218 compare) shift; command-compare $@;; 219 *) echo "Unknown arguments: $@"; usage-help;; 220esac 221