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