#!/bin/bash # set -x # Stupid Simple Semver range validator # (no build metadata and no prereleases, and no multiple ranges (||)) # Prints a list of versions that match a semver range # ./semver.sh "range" version version version... # ./semver.sh ">=0.10.32 <7" 0.10.32 6.7.0 6.9.2 6.9.4 6.10.0 7.3.0 7.4.0 | xargs # 0.10.32 6.7.0 6.9.2 6.9.4 6.10.0 # ./semver.sh "^6.9.4" 0.10.32 6.7.0 6.9.2 6.9.4 6.10.0 7.3.0 7.4.0 | xargs # 6.9.4 6.10.0 # ./semver.sh "~6.9.4" 0.10.32 6.7.0 6.9.2 6.9.4 6.10.0 7.3.0 7.4.0 | xargs # 6.9.4 # TODO: error handling, validation # Basic concept: # 1.x.x = ^1.0.0 = (>=1.0.0 <2.0.0) # 1.1.x = ~1.1.0 = (>=1.1.0 <1.2.0) # ^1.x.x = ^1.0.0 # ~1.x.x = ~1.0.0 SEMVER_RANGE="$1"; shift SEMVER_AVAILABLE_VERSIONS="$@" SEMVER_RE_COMPARATOR='[<>=\^~]*' SEMVER_RE_VERSION='[0-9x\.]+' # Takes a range with multiple comparisons, and splits it into lines # >=1.2.3 < 3 # >=1.2.3 # < 3 semver_split_range () { egrep -o "($SEMVER_RE_COMPARATOR\s*$SEMVER_RE_VERSION)" } # Hacky way to make versions comparable # Won't work if version numbers are over 100k' # 1.2.3 => 000001000002000003 semver_to_number () { tr "." " " | xargs printf "%06g" } # Convert a range part into a set of comparison lines processable by bc # ^1.2.3 => >=1.2.3 <2.0.0 # >= 000001000002000003 # < 000002000000000000 semver_create_comparator () { local COMPARATOR="$(egrep -o "^$SEMVER_RE_COMPARATOR" <<< "$1")" local VERSION="$(egrep -o "$SEMVER_RE_VERSION$" <<< "$1")" local VP local ZERO_OR_X if [[ -n "$COMPARATOR" ]]; then ZERO_OR_X="0" else ZERO_OR_X="x" fi # Append either 0's or x's to the version, depending on comparator presence # 4 => 4.x.x # ^4 => 4.0.0 while [[ "$(egrep -o "\." <<< "$VERSION" | wc -l)" -lt "2" ]]; do VERSION="$VERSION.$ZERO_OR_X" done # If comparator is not defined (1.x.x, 1.x, 1, 1.0.x, 1.0.0 etc), # assign one based on the number of x's # 1.x.x => ^ 1.x.x # 1.0.x => ~ 1.0.x # 1.0.0 => == 1.0.0 if [[ -z "$COMPARATOR" ]]; then if [[ "$VERSION" =~ \.x\.x ]]; then COMPARATOR="^" elif [[ "$VERSION" =~ \.x ]]; then COMPARATOR="~" else COMPARATOR="==" fi fi # Finally, replace all the x's with 0s' # ^ 1.x.x => ^ 1.0.0 VERSION=$(sed -E 's|\.x|\.0|g' <<< "$VERSION") # Convert the ~,^ into proper >=, < comparator ranges # ^1.0.0 = (>=1.0.0 <2.0.0) # ~1.0.0 = (>=1.0.0 <1.1.0) if [[ "$COMPARATOR" =~ \^|~ ]]; then IFS="." read -ra VP <<< "$VERSION" if [[ "$COMPARATOR" == "~" ]]; then # Up the minor; 0 the patch VP[1]=$(( ${VP[1]} + 1 )) VP[2]=0 else # Up the major; 0 the minor and patch VP[0]=$(( ${VP[0]} + 1 )) VP[1]=0 VP[2]=0 fi COMPARATOR=">=" echo "< $(semver_to_number <<< "${VP[0]}.${VP[1]}.${VP[2]}")" fi echo "$COMPARATOR $(semver_to_number <<< "$VERSION")" } # Create all of the comparator lines processable by bc for the entire semver range # TODO: make it break for invalid ranges semver_create_comparators () { local R while read R; do semver_create_comparator "$R" done < <(semver_split_range <<< "$SEMVER_RANGE") } # Run a semver against all comparisons # If all conditions pass, return success semver_compare () { local C while read C; do [[ "$MATCH" == "false" ]] && break if [[ "$(bc <<< "$1 $C")" == "0" ]]; then return 1 fi done < <(echo -e "$V_COMPARATORS") } # Precompute the range into bc-processable comparator strings V_COMPARATORS="$(semver_create_comparators)" # Compare each provided version against the provided range, and print # the ones that succeed for A in $SEMVER_AVAILABLE_VERSIONS; do semver_compare "$(semver_to_number <<< "$A")" && echo $A done