forked from michael.heier/citadel-core
526 lines
12 KiB
Plaintext
526 lines
12 KiB
Plaintext
|
#!/usr/bin/env bash
|
||
|
|
||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||
|
#
|
||
|
# SPDX-FileCopyrightText: Copyright (c) 2014 Józef Sokołowski
|
||
|
|
||
|
# https://github.com/qzb/sh-semver
|
||
|
# Commit: 2ac2437
|
||
|
|
||
|
_num_part='([0-9]|[1-9][0-9]*)'
|
||
|
_lab_part='([0-9]|[1-9][0-9]*|[0-9]*[a-zA-Z-][a-zA-Z0-9-]*)'
|
||
|
_met_part='([0-9A-Za-z-]+)'
|
||
|
|
||
|
RE_NUM="$_num_part(\.$_num_part)*"
|
||
|
RE_LAB="$_lab_part(\.$_lab_part)*"
|
||
|
RE_MET="$_met_part(\.$_met_part)*"
|
||
|
RE_VER="[ \t]*$RE_NUM(-$RE_LAB)?(\+$RE_MET)?"
|
||
|
|
||
|
BRE_DIGIT='[0-9]\{1,\}'
|
||
|
BRE_ALNUM='[0-9a-zA-Z-]\{1,\}'
|
||
|
BRE_IDENT="$BRE_ALNUM\(\.$BRE_ALNUM\)*"
|
||
|
|
||
|
BRE_MAJOR="$BRE_DIGIT"
|
||
|
BRE_MINOR="\(\.$BRE_DIGIT\)\{0,1\}"
|
||
|
BRE_PATCH="\(\.$BRE_DIGIT\)\{0,1\}"
|
||
|
BRE_PRERE="\(-$BRE_IDENT\)\{0,1\}"
|
||
|
BRE_BUILD="\(+$BRE_IDENT\)\{0,1\}"
|
||
|
BRE_VERSION="${BRE_MAJOR}${BRE_MINOR}${BRE_PATCH}${BRE_PRERE}${BRE_BUILD}"
|
||
|
|
||
|
filter()
|
||
|
{
|
||
|
local text="$1"
|
||
|
local regex="$2"
|
||
|
shift 2
|
||
|
echo "$text" | grep -E "$@" "$regex"
|
||
|
}
|
||
|
|
||
|
# Gets number part from normalized version
|
||
|
get_number()
|
||
|
{
|
||
|
echo "${1%%-*}"
|
||
|
}
|
||
|
|
||
|
# Gets prerelase part from normalized version
|
||
|
get_prerelease()
|
||
|
{
|
||
|
local pre_and_meta=${1%+*}
|
||
|
local pre=${pre_and_meta#*-}
|
||
|
if [ "$pre" = "$1" ]; then
|
||
|
echo
|
||
|
else
|
||
|
echo "$pre"
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
# Gets major number from normalized version
|
||
|
get_major()
|
||
|
{
|
||
|
echo "${1%%.*}"
|
||
|
}
|
||
|
|
||
|
# Gets minor number from normalized version
|
||
|
get_minor()
|
||
|
{
|
||
|
local minor_major_bug=${1%%-*}
|
||
|
local minor_major=${minor_major_bug%.*}
|
||
|
local minor=${minor_major#*.}
|
||
|
|
||
|
if [ "$minor" = "$minor_major" ]; then
|
||
|
echo
|
||
|
else
|
||
|
echo "$minor"
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
get_bugfix()
|
||
|
{
|
||
|
local minor_major_bug=${1%%-*}
|
||
|
local bugfix=${minor_major_bug##*.*.}
|
||
|
|
||
|
if [ "$bugfix" = "$minor_major_bug" ]; then
|
||
|
echo
|
||
|
else
|
||
|
echo "$bugfix"
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
strip_metadata()
|
||
|
{
|
||
|
echo "${1%+*}"
|
||
|
}
|
||
|
|
||
|
semver_eq()
|
||
|
{
|
||
|
local ver1 ver2 part1 part2
|
||
|
ver1=$(get_number "$1")
|
||
|
ver2=$(get_number "$2")
|
||
|
|
||
|
local count=1
|
||
|
while true; do
|
||
|
part1=$(echo "$ver1"'.' | cut -d '.' -f $count)
|
||
|
part2=$(echo "$ver2"'.' | cut -d '.' -f $count)
|
||
|
|
||
|
if [ -z "$part1" ] || [ -z "$part2" ]; then
|
||
|
break
|
||
|
fi
|
||
|
|
||
|
if [ "$part1" != "$part2" ]; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
local count=$(( count + 1 ))
|
||
|
done
|
||
|
|
||
|
if [ "$(get_prerelease "$1")" = "$(get_prerelease "$2")" ]; then
|
||
|
return 0
|
||
|
else
|
||
|
return 1
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
semver_lt()
|
||
|
{
|
||
|
local number_a number_b prerelease_a prerelease_b
|
||
|
number_a=$(get_number "$1")
|
||
|
number_b=$(get_number "$2")
|
||
|
prerelease_a=$(get_prerelease "$1")
|
||
|
prerelease_b=$(get_prerelease "$2")
|
||
|
|
||
|
|
||
|
local head_a=''
|
||
|
local head_b=''
|
||
|
local rest_a=$number_a.
|
||
|
local rest_b=$number_b.
|
||
|
while [ -n "$rest_a" ] || [ -n "$rest_b" ]; do
|
||
|
head_a=${rest_a%%.*}
|
||
|
head_b=${rest_b%%.*}
|
||
|
rest_a=${rest_a#*.}
|
||
|
rest_b=${rest_b#*.}
|
||
|
|
||
|
if [ -z "$head_a" ] || [ -z "$head_b" ]; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
if [ "$head_a" -eq "$head_b" ]; then
|
||
|
continue
|
||
|
fi
|
||
|
|
||
|
if [ "$head_a" -lt "$head_b" ]; then
|
||
|
return 0
|
||
|
else
|
||
|
return 1
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
if [ -n "$prerelease_a" ] && [ -z "$prerelease_b" ]; then
|
||
|
return 0
|
||
|
elif [ -z "$prerelease_a" ] && [ -n "$prerelease_b" ]; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
local head_a=''
|
||
|
local head_b=''
|
||
|
local rest_a=$prerelease_a.
|
||
|
local rest_b=$prerelease_b.
|
||
|
while [ -n "$rest_a" ] || [ -n "$rest_b" ]; do
|
||
|
head_a=${rest_a%%.*}
|
||
|
head_b=${rest_b%%.*}
|
||
|
rest_a=${rest_a#*.}
|
||
|
rest_b=${rest_b#*.}
|
||
|
|
||
|
if [ -z "$head_a" ] && [ -n "$head_b" ]; then
|
||
|
return 0
|
||
|
elif [ -n "$head_a" ] && [ -z "$head_b" ]; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
if [ "$head_a" = "$head_b" ]; then
|
||
|
continue
|
||
|
fi
|
||
|
|
||
|
# If both are numbers then compare numerically
|
||
|
if [ "$head_a" = "${head_a%[!0-9]*}" ] && [ "$head_b" = "${head_b%[!0-9]*}" ]; then
|
||
|
[ "$head_a" -lt "$head_b" ] && return 0 || return 1
|
||
|
# If only a is a number then return true (number has lower precedence than strings)
|
||
|
elif [ "$head_a" = "${head_a%[!0-9]*}" ]; then
|
||
|
return 0
|
||
|
# If only b is a number then return false
|
||
|
elif [ "$head_b" = "${head_b%[!0-9]*}" ]; then
|
||
|
return 1
|
||
|
# Finally if of identifiers is a number compare them lexically
|
||
|
else
|
||
|
test "$head_a" \< "$head_b" && return 0 || return 1
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
semver_gt()
|
||
|
{
|
||
|
if semver_lt "$1" "$2" || semver_eq "$1" "$2"; then
|
||
|
return 1
|
||
|
else
|
||
|
return 0
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
semver_le()
|
||
|
{
|
||
|
semver_gt "$1" "$2" && return 1 || return 0
|
||
|
}
|
||
|
|
||
|
semver_ge()
|
||
|
{
|
||
|
semver_lt "$1" "$2" && return 1 || return 0
|
||
|
}
|
||
|
|
||
|
semver_sort()
|
||
|
{
|
||
|
if [ $# -le 1 ]; then
|
||
|
echo "$1"
|
||
|
return
|
||
|
fi
|
||
|
|
||
|
local pivot=$1
|
||
|
local args_a=()
|
||
|
local args_b=()
|
||
|
|
||
|
shift 1
|
||
|
|
||
|
for ver in "$@"; do
|
||
|
if semver_le "$ver" "$pivot"; then
|
||
|
args_a=( "${args_a[@]}" "$ver" )
|
||
|
else
|
||
|
args_b=( "$ver" "${args_b[@]}" )
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
args_a=( $(semver_sort "${args_a[@]}") )
|
||
|
args_b=( $(semver_sort "${args_b[@]}") )
|
||
|
echo "${args_a[@]}" "$pivot" "${args_b[@]}"
|
||
|
}
|
||
|
|
||
|
regex_match()
|
||
|
{
|
||
|
local string="$1 "
|
||
|
local regexp="$2"
|
||
|
local match
|
||
|
match="$(eval "echo '$string' | grep -E -o '^[ \t]*($regexp)[ \t]+'")";
|
||
|
|
||
|
for i in $(seq 0 9); do
|
||
|
unset "MATCHED_VER_$i"
|
||
|
unset "MATCHED_NUM_$i"
|
||
|
done
|
||
|
unset REST
|
||
|
|
||
|
if [ -z "$match" ]; then
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
local match_len=${#match}
|
||
|
REST="${string:$match_len}"
|
||
|
|
||
|
local part
|
||
|
local i=1
|
||
|
for part in $string; do
|
||
|
local ver num
|
||
|
ver="$(eval "echo '$part' | grep -E -o '$RE_VER' | head -n 1 | sed 's/ \t//g'")";
|
||
|
num=$(get_number "$ver")
|
||
|
|
||
|
if [ -n "$ver" ]; then
|
||
|
eval "MATCHED_VER_$i='$ver'"
|
||
|
eval "MATCHED_NUM_$i='$num'"
|
||
|
i=$(( i + 1 ))
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
# Normalizes rules string
|
||
|
#
|
||
|
# * replaces chains of whitespaces with single spaces
|
||
|
# * replaces whitespaces around hyphen operator with "_"
|
||
|
# * removes wildcards from version numbers (1.2.* -> 1.2)
|
||
|
# * replaces "x" with "*"
|
||
|
# * removes whitespace between operators and version numbers
|
||
|
# * removes leading "v" from version numbers
|
||
|
# * removes leading and trailing spaces
|
||
|
normalize_rules()
|
||
|
{
|
||
|
echo " $1" \
|
||
|
| sed 's/\\t/ /g' \
|
||
|
| sed 's/ / /g' \
|
||
|
| sed 's/ \{2,\}/ /g' \
|
||
|
| sed 's/ - /_-_/g' \
|
||
|
| sed 's/\([~^<>=]\) /\1/g' \
|
||
|
| sed 's/\([ _~^<>=]\)v/\1/g' \
|
||
|
| sed 's/\.[xX*]//g' \
|
||
|
| sed 's/[xX]/*/g' \
|
||
|
| sed 's/^ //g' \
|
||
|
| sed 's/ $//g'
|
||
|
}
|
||
|
|
||
|
# Reads rule from provided string
|
||
|
resolve_rule()
|
||
|
{
|
||
|
local rule operator operands
|
||
|
rule="$1"
|
||
|
operator="$( echo "$rule" | sed "s/$BRE_VERSION/#/g" )"
|
||
|
operands=( $( echo "$rule" | grep -o "$BRE_VERSION") )
|
||
|
|
||
|
case "$operator" in
|
||
|
'*') echo "all" ;;
|
||
|
'#') echo "eq ${operands[0]}" ;;
|
||
|
'=#') echo "eq ${operands[0]}" ;;
|
||
|
'<#') echo "lt ${operands[0]}" ;;
|
||
|
'>#') echo "gt ${operands[0]}" ;;
|
||
|
'<=#') echo "le ${operands[0]}" ;;
|
||
|
'>=#') echo "ge ${operands[0]}" ;;
|
||
|
'#_-_#') echo "ge ${operands[0]}"
|
||
|
echo "le ${operands[1]}" ;;
|
||
|
'~#') echo "tilde ${operands[0]}" ;;
|
||
|
'^#') echo "caret ${operands[0]}" ;;
|
||
|
*) return 1
|
||
|
esac
|
||
|
}
|
||
|
|
||
|
resolve_rules()
|
||
|
{
|
||
|
local rules
|
||
|
rules="$(normalize_rules "$1")"
|
||
|
IFS=' ' read -ra rules <<< "${rules:-all}"
|
||
|
|
||
|
for rule in "${rules[@]}"; do
|
||
|
resolve_rule "$rule"
|
||
|
done
|
||
|
}
|
||
|
|
||
|
rule_eq()
|
||
|
{
|
||
|
local rule_ver="$1"
|
||
|
local tested_ver="$2"
|
||
|
|
||
|
semver_eq "$tested_ver" "$rule_ver" && return 0 || return 1;
|
||
|
}
|
||
|
|
||
|
rule_le()
|
||
|
{
|
||
|
local rule_ver="$1"
|
||
|
local tested_ver="$2"
|
||
|
|
||
|
semver_le "$tested_ver" "$rule_ver" && return 0 || return 1;
|
||
|
}
|
||
|
|
||
|
rule_lt()
|
||
|
{
|
||
|
local rule_ver="$1"
|
||
|
local tested_ver="$2"
|
||
|
|
||
|
semver_lt "$tested_ver" "$rule_ver" && return 0 || return 1;
|
||
|
}
|
||
|
|
||
|
rule_ge()
|
||
|
{
|
||
|
local rule_ver="$1"
|
||
|
local tested_ver="$2"
|
||
|
|
||
|
semver_ge "$tested_ver" "$rule_ver" && return 0 || return 1;
|
||
|
}
|
||
|
|
||
|
rule_gt()
|
||
|
{
|
||
|
local rule_ver="$1"
|
||
|
local tested_ver="$2"
|
||
|
|
||
|
semver_gt "$tested_ver" "$rule_ver" && return 0 || return 1;
|
||
|
}
|
||
|
|
||
|
rule_tilde()
|
||
|
{
|
||
|
local rule_ver="$1"
|
||
|
local tested_ver="$2"
|
||
|
|
||
|
if rule_ge "$rule_ver" "$tested_ver"; then
|
||
|
local rule_major rule_minor
|
||
|
rule_major=$(get_major "$rule_ver")
|
||
|
rule_minor=$(get_minor "$rule_ver")
|
||
|
|
||
|
if [ -n "$rule_minor" ] && rule_eq "$rule_major.$rule_minor" "$(get_number "$tested_ver")"; then
|
||
|
return 0
|
||
|
fi
|
||
|
if [ -z "$rule_minor" ] && rule_eq "$rule_major" "$(get_number "$tested_ver")"; then
|
||
|
return 0
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
rule_caret()
|
||
|
{
|
||
|
local rule_ver="$1"
|
||
|
local tested_ver="$2"
|
||
|
|
||
|
if rule_ge "$rule_ver" "$tested_ver"; then
|
||
|
local rule_major
|
||
|
rule_major="$(get_major "$rule_ver")"
|
||
|
|
||
|
if [ "$rule_major" != "0" ] && rule_eq "$rule_major" "$(get_number "$tested_ver")"; then
|
||
|
return 0
|
||
|
fi
|
||
|
if [ "$rule_major" = "0" ] && rule_eq "$rule_ver" "$(get_number "$tested_ver")"; then
|
||
|
return 0
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
rule_all()
|
||
|
{
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
apply_rules()
|
||
|
{
|
||
|
local rules_string="$1"
|
||
|
shift
|
||
|
local versions=( "$@" )
|
||
|
|
||
|
# Loop over sets of rules (sets of rules are separated with ||)
|
||
|
for ver in "${versions[@]}"; do
|
||
|
rules_tail="$rules_string";
|
||
|
|
||
|
while [ -n "$rules_tail" ]; do
|
||
|
head="${rules_tail%%||*}"
|
||
|
|
||
|
if [ "$head" = "$rules_tail" ]; then
|
||
|
rules_string=""
|
||
|
else
|
||
|
rules_tail="${rules_tail#*||}"
|
||
|
fi
|
||
|
|
||
|
#if [ -z "$head" ] || [ -n "$(echo "$head" | grep -E -x '[ \t]*')" ]; then
|
||
|
#group=$(( $group + 1 ))
|
||
|
#continue
|
||
|
#fi
|
||
|
|
||
|
rules="$(resolve_rules "$head")"
|
||
|
|
||
|
# If specified rule cannot be recognised - end with error
|
||
|
if [ $? -eq 1 ]; then
|
||
|
exit 1
|
||
|
fi
|
||
|
|
||
|
if ! echo "$ver" | grep -q -E -x "[v=]?[ \t]*$RE_VER"; then
|
||
|
continue
|
||
|
fi
|
||
|
|
||
|
ver=$(echo "$ver" | grep -E -x "$RE_VER")
|
||
|
|
||
|
success=true
|
||
|
allow_prerel=false
|
||
|
if $FORCE_ALLOW_PREREL; then
|
||
|
allow_prerel=true
|
||
|
fi
|
||
|
|
||
|
while read -r rule; do
|
||
|
comparator="${rule%% *}"
|
||
|
operand="${rule#* }"
|
||
|
|
||
|
if [ -n "$(get_prerelease "$operand")" ] && semver_eq "$(get_number "$operand")" "$(get_number "$ver")" || [ "$rule" = "all" ]; then
|
||
|
allow_prerel=true
|
||
|
fi
|
||
|
|
||
|
"rule_$comparator" "$operand" "$ver"
|
||
|
if [ $? -eq 1 ]; then
|
||
|
success=false
|
||
|
break
|
||
|
fi
|
||
|
done <<< "$rules"
|
||
|
|
||
|
if $success; then
|
||
|
if [ -z "$(get_prerelease "$ver")" ] || $allow_prerel; then
|
||
|
echo "$ver"
|
||
|
break;
|
||
|
fi
|
||
|
fi
|
||
|
done
|
||
|
|
||
|
group=$(( group + 1 ))
|
||
|
done
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
FORCE_ALLOW_PREREL=false
|
||
|
USAGE="Usage: $0 [-r <rule>] [<version>... ]
|
||
|
|
||
|
Omitting <version>s reads them from STDIN.
|
||
|
Omitting -r <rule> simply sorts the versions according to semver ordering."
|
||
|
|
||
|
while getopts ar:h o; do
|
||
|
case "$o" in
|
||
|
a) FORCE_ALLOW_PREREL=true ;;
|
||
|
r) RULES_STRING="$OPTARG||";;
|
||
|
h) echo "$USAGE" && exit ;;
|
||
|
?) echo "$USAGE" && exit 1;;
|
||
|
esac
|
||
|
done
|
||
|
|
||
|
shift $(( OPTIND-1 ))
|
||
|
|
||
|
VERSIONS=( ${@:-$(cat -)} )
|
||
|
|
||
|
# Sort versions
|
||
|
VERSIONS=( $(semver_sort "${VERSIONS[@]}") )
|
||
|
|
||
|
if [ -z "$RULES_STRING" ]; then
|
||
|
printf '%s\n' "${VERSIONS[@]}"
|
||
|
else
|
||
|
apply_rules "$RULES_STRING" "${VERSIONS[@]}"
|
||
|
fi
|