semver.sh
· 3.7 KiB · Bash
Sin formato
#!/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
1 | #!/bin/bash |
2 | |
3 | # set -x |
4 | |
5 | # Stupid Simple Semver range validator |
6 | # (no build metadata and no prereleases, and no multiple ranges (||)) |
7 | |
8 | # Prints a list of versions that match a semver range |
9 | # ./semver.sh "range" version version version... |
10 | |
11 | # ./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 |
12 | # 0.10.32 6.7.0 6.9.2 6.9.4 6.10.0 |
13 | |
14 | # ./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 |
15 | # 6.9.4 6.10.0 |
16 | |
17 | # ./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 |
18 | # 6.9.4 |
19 | |
20 | # TODO: error handling, validation |
21 | |
22 | # Basic concept: |
23 | # 1.x.x = ^1.0.0 = (>=1.0.0 <2.0.0) |
24 | # 1.1.x = ~1.1.0 = (>=1.1.0 <1.2.0) |
25 | # ^1.x.x = ^1.0.0 |
26 | # ~1.x.x = ~1.0.0 |
27 | |
28 | SEMVER_RANGE="$1"; shift |
29 | SEMVER_AVAILABLE_VERSIONS="$@" |
30 | |
31 | SEMVER_RE_COMPARATOR='[<>=\^~]*' |
32 | SEMVER_RE_VERSION='[0-9x\.]+' |
33 | |
34 | # Takes a range with multiple comparisons, and splits it into lines |
35 | # >=1.2.3 < 3 |
36 | # >=1.2.3 |
37 | # < 3 |
38 | semver_split_range () { |
39 | egrep -o "($SEMVER_RE_COMPARATOR\s*$SEMVER_RE_VERSION)" |
40 | } |
41 | |
42 | # Hacky way to make versions comparable |
43 | # Won't work if version numbers are over 100k' |
44 | # 1.2.3 => 000001000002000003 |
45 | semver_to_number () { |
46 | tr "." " " | xargs printf "%06g" |
47 | } |
48 | |
49 | # Convert a range part into a set of comparison lines processable by bc |
50 | # ^1.2.3 => >=1.2.3 <2.0.0 |
51 | # >= 000001000002000003 |
52 | # < 000002000000000000 |
53 | semver_create_comparator () { |
54 | local COMPARATOR="$(egrep -o "^$SEMVER_RE_COMPARATOR" <<< "$1")" |
55 | local VERSION="$(egrep -o "$SEMVER_RE_VERSION$" <<< "$1")" |
56 | local VP |
57 | |
58 | local ZERO_OR_X |
59 | if [[ -n "$COMPARATOR" ]]; then |
60 | ZERO_OR_X="0" |
61 | else |
62 | ZERO_OR_X="x" |
63 | fi |
64 | |
65 | # Append either 0's or x's to the version, depending on comparator presence |
66 | # 4 => 4.x.x |
67 | # ^4 => 4.0.0 |
68 | while [[ "$(egrep -o "\." <<< "$VERSION" | wc -l)" -lt "2" ]]; do |
69 | VERSION="$VERSION.$ZERO_OR_X" |
70 | done |
71 | |
72 | # If comparator is not defined (1.x.x, 1.x, 1, 1.0.x, 1.0.0 etc), |
73 | # assign one based on the number of x's |
74 | # 1.x.x => ^ 1.x.x |
75 | # 1.0.x => ~ 1.0.x |
76 | # 1.0.0 => == 1.0.0 |
77 | if [[ -z "$COMPARATOR" ]]; then |
78 | if [[ "$VERSION" =~ \.x\.x ]]; then |
79 | COMPARATOR="^" |
80 | elif [[ "$VERSION" =~ \.x ]]; then |
81 | COMPARATOR="~" |
82 | else |
83 | COMPARATOR="==" |
84 | fi |
85 | fi |
86 | |
87 | # Finally, replace all the x's with 0s' |
88 | # ^ 1.x.x => ^ 1.0.0 |
89 | VERSION=$(sed -E 's|\.x|\.0|g' <<< "$VERSION") |
90 | |
91 | # Convert the ~,^ into proper >=, < comparator ranges |
92 | # ^1.0.0 = (>=1.0.0 <2.0.0) |
93 | # ~1.0.0 = (>=1.0.0 <1.1.0) |
94 | if [[ "$COMPARATOR" =~ \^|~ ]]; then |
95 | IFS="." read -ra VP <<< "$VERSION" |
96 | if [[ "$COMPARATOR" == "~" ]]; then |
97 | # Up the minor; 0 the patch |
98 | VP[1]=$(( ${VP[1]} + 1 )) |
99 | VP[2]=0 |
100 | else |
101 | # Up the major; 0 the minor and patch |
102 | VP[0]=$(( ${VP[0]} + 1 )) |
103 | VP[1]=0 |
104 | VP[2]=0 |
105 | fi |
106 | COMPARATOR=">=" |
107 | echo "< $(semver_to_number <<< "${VP[0]}.${VP[1]}.${VP[2]}")" |
108 | fi |
109 | |
110 | echo "$COMPARATOR $(semver_to_number <<< "$VERSION")" |
111 | } |
112 | |
113 | # Create all of the comparator lines processable by bc for the entire semver range |
114 | # TODO: make it break for invalid ranges |
115 | semver_create_comparators () { |
116 | local R |
117 | |
118 | while read R; do |
119 | semver_create_comparator "$R" |
120 | done < <(semver_split_range <<< "$SEMVER_RANGE") |
121 | } |
122 | |
123 | # Run a semver against all comparisons |
124 | # If all conditions pass, return success |
125 | semver_compare () { |
126 | local C |
127 | |
128 | while read C; do |
129 | [[ "$MATCH" == "false" ]] && break |
130 | if [[ "$(bc <<< "$1 $C")" == "0" ]]; then |
131 | return 1 |
132 | fi |
133 | done < <(echo -e "$V_COMPARATORS") |
134 | } |
135 | |
136 | # Precompute the range into bc-processable comparator strings |
137 | V_COMPARATORS="$(semver_create_comparators)" |
138 | |
139 | # Compare each provided version against the provided range, and print |
140 | # the ones that succeed |
141 | for A in $SEMVER_AVAILABLE_VERSIONS; do |
142 | semver_compare "$(semver_to_number <<< "$A")" && echo $A |
143 | done |