refactored nvm_interpret_complex_semver to clearer structure; removed semver.md

This commit is contained in:
edwmurph 2018-05-28 21:46:18 -04:00
parent c996ac7c5c
commit 79319ea7a3
4 changed files with 71 additions and 118 deletions

127
nvm.sh
View File

@ -478,11 +478,7 @@ nvm_validate_semver() { (
nvm_interpret_complex_semver() { (
semver="${1-}"
version_list="${2-}" # expected to be sorted from oldest to newest
if [ -z "$semver" ] || [ -z "$version_list" ]; then
nvm_err "Error interpretting complex semver: Missing required parameter(s)"
return 1
elif ! nvm_is_valid_semver "$semver"; then
nvm_err "Error interpretting complex semver: Given an invalid semver"
if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_is_valid_semver "$semver"; then
return 1
fi
@ -499,18 +495,14 @@ nvm_interpret_complex_semver() { (
semver=$(command printf "%s" "$semver" | command tail -n +2)
[ -n "$current_comparator_set" ] || continue
is_current_version_compatible=1 # initialized to false
# For each version in the version_list_copy (from newest to oldest):
# - If current_version satisfies all comparators, we've found the newest version compatible with all comparators in current current_comparator_set.
# - Add discovered version to highest_compatible_versions and stop iterating through versions.
while [ -n "$version_list_copy" ] && [ $is_current_version_compatible -eq 1 ]; do
# For each version in the version_list_copy (iterating from newest to oldest):
# - If current_version satisfies all comparators in current_comparator_set, we've found the newest version compatible with all comparators in current current_comparator_set.
# - Add discovered version to highest_compatible_versions and stop iterating through versions for current_comparator_set.
while [ -n "$version_list_copy" ]; do
current_version=$(command printf "%s" "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')
version_list_copy=$(command printf "%s" "$version_list_copy" | command sed '$d')
[ -n "$current_version" ] || continue
is_current_version_compatible=1 # initialized to false
# For each comparator in the current_comparator_set_copy:
# - If current_version is compatible with all comparators, we know current_version is the newest compatible version
current_comparator_set_copy=$(command printf "%s" "$current_comparator_set" | command tr ' ' '\n')
@ -520,67 +512,86 @@ nvm_interpret_complex_semver() { (
[ -n "$current_comparator" ] || continue
stripped_version_from_comparator="$(command printf "%s" "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')"
if [ -z "$stripped_version_from_comparator" ]; then
return 1
fi
if nvm_string_contains_regexp "$current_comparator" '^[0-9]+\.[0-9]+\.[0-9]+$'; then
if [ "$current_comparator" = "$current_version" ]; then
# current_omparator is looking for an exact match, and the current_version is the exact match, so this current_comparator is satisfied.
is_current_version_compatible=0
elif nvm_version_greater "$current_comparator" "$current_version"; then
# Looking for a version that is equal to current_comparator but current_version is less than current_comparator so there is no point continuing.
is_current_version_compatible=1
no_remote_version_will_satisfy_all_comparators=0
if [ "$stripped_version_from_comparator" = "$current_version" ]; then
# current_comparator is looking for an exact match, and the current_version is the exact match, so this current_comparator is satisfied.
if [ -z "$current_comparator_set_copy" ]; then
# Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set.
highest_compatible_versions="$highest_compatible_versions $current_version"
version_list_copy=''
fi
elif nvm_version_greater "$stripped_version_from_comparator" "$current_version"; then
# current_version is less than the stripped_version_from_comparator, so continuing iterating through version_list_copy is pointless.
current_comparator_set_copy=''
version_list_copy=''
else
is_current_version_compatible=1
# current_version is greater than the stripped_version_from_comparator, so we should continuing iterating through version_list_copy.
current_comparator_set_copy=''
fi
elif nvm_string_contains_regexp "$current_comparator" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then
if nvm_version_greater_than_or_equal_to "$stripped_version_from_comparator" "$current_version"; then
# current_version is less and or equal to the current_comparator version number so this current_comparator is satisfied.
is_current_version_compatible=0
# current_version is less than or equal to the current_comparator version number so this current_comparator is satisfied.
if [ -z "$current_comparator_set_copy" ]; then
# Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set.
highest_compatible_versions="$highest_compatible_versions $current_version"
version_list_copy=''
fi
else
is_current_version_compatible=1
# current_version is greater than the current_comparator version number so we should continue iterating through version_list_copy.
current_comparator_set_copy=''
fi
elif nvm_string_contains_regexp "$current_comparator" '^>=[0-9]+\.[0-9]+\.[0-9]+$'; then
if nvm_version_greater_than_or_equal_to "$current_version" "$stripped_version_from_comparator"; then
is_current_version_compatible=0
# current_version is greater than or equal to the current_comparator version number so this current_comparator is satisfied.
if [ -z "$current_comparator_set_copy" ]; then
# Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set.
highest_compatible_versions="$highest_compatible_versions $current_version"
version_list_copy=''
fi
else
is_current_version_compatible=1
no_remote_version_will_satisfy_all_comparators=0
# current_version is less than the current_comparator version number so continuing iterating through version_list_copy is pointless.
current_comparator_set_copy=''
version_list_copy=''
fi
elif nvm_string_contains_regexp "$current_comparator" '^<[0-9]+\.[0-9]+\.[0-9]+$'; then
if nvm_version_greater "$stripped_version_from_comparator" "$current_version"; then
is_current_version_compatible=0
# current_version is less than the current_comparator version number so this current_comparator is satisfied.
if [ -z "$current_comparator_set_copy" ]; then
# Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set.
highest_compatible_versions="$highest_compatible_versions $current_version"
version_list_copy=''
fi
else
is_current_version_compatible=1
# current_version is greater than or equal to the current_comparator version number so we should continue iterating through version_list_copy.
current_comparator_set_copy=''
fi
elif nvm_string_contains_regexp "$current_comparator" '^>[0-9]+\.[0-9]+\.[0-9]+$'; then
if nvm_version_greater "$current_version" "$stripped_version_from_comparator"; then
is_current_version_compatible=0
# current_version is greater than the current_comparator version number so this current_comparator is satisfied.
if [ -z "$current_comparator_set_copy" ];then
# Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set.
highest_compatible_versions="$highest_compatible_versions $current_version"
version_list_copy=''
fi
else
is_current_version_compatible=1
no_remote_version_will_satisfy_all_comparators=0
# current_version is less than or equal to the current_comparator version number so continuing iterating through version_list_copy is pointless.
current_comparator_set_copy=''
version_list_copy=''
fi
else
is_current_version_compatible=1
fi
# If current_version is not compatible with current_comparator, stop iterating through current_comparator_set_copy.
if [ $is_current_version_compatible -eq 1 ]; then
current_comparator_set_copy=''
fi
# If determined that continuing iterating through version_list_copy will not find a version compatible with all comparators, stop iterating through version_list_copy.
if [ ${no_remote_version_will_satisfy_all_comparators-1} -eq 0 ]; then
version_list_copy=''
fi
done # while [ -n "$current_comparator_set_copy" ] && [ $no_remote_version_will_satisfy_all_comparators -eq 1 ]; do
# If the current_version is compatible with all comparators in current_comparator_set, add it to the list of highest_compatible_versions.
if [ $is_current_version_compatible -eq 0 ]; then
highest_compatible_versions="$highest_compatible_versions $current_version"
fi
done # while [ -n "$version_list_copy" ] && [ $is_current_version_compatible -eq 1 ]; do
done # while [ -n "$current_comparator_set_copy" ]; do
done # while [ -n "$version_list_copy" ]; do
done # while [ -n "$semver" ]; do
# Iterate through each of the versions in highest_compatible_versions, which are the highest versions that satisfy each of the comparator sets.
@ -607,18 +618,12 @@ nvm_interpret_complex_semver() { (
nvm_interpret_simple_semver() { (
semver="${1-}"
version_list="${2-}" # expected to be sorted from oldest to newest
if [ -z "$semver" ] || [ -z "$version_list" ]; then
nvm_err "Error interpretting simple semver: Missing required parameter(s)"
return 1
fi
if ! nvm_string_contains_regexp "$semver" '^[<>=]*[0-9]+\.[0-9]+\.[0-9]+$'; then
# semver is not a semver with only a single comparator
if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_string_contains_regexp "$semver" '^[<>=]*[0-9]+\.[0-9]+\.[0-9]+$'; then
return 1
fi
stripped_version_from_semver="$(command printf "%s" "$semver" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')"
newest_version_from_list=$(command printf "%s" "$version_list" | tail -n 1 | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')
if [ -z "$stripped_version_from_semver" ]; then
nvm_err "Error interpretting simple semver: error retrieving stripped version from semver"
if [ -z "$stripped_version_from_semver" ] || [ -z "$newest_version_from_list" ]; then
return 1
fi
# if the semver is looking for an exact match, and it exists in the provided list of versions, resolve to that version
@ -627,6 +632,7 @@ nvm_interpret_simple_semver() { (
command printf "%s" "$stripped_version_from_semver"
return 0
else
# TODO we know it's not worth doing the complex semver interpratation at this point
return 1
fi
@ -645,6 +651,7 @@ nvm_interpret_simple_semver() { (
command printf "%s" "$newest_version_from_list"
return 0
else
# TODO we know it's not worth doing the complex semver interpretation at this point
return 1
fi
@ -653,6 +660,7 @@ nvm_interpret_simple_semver() { (
command printf "%s" "$newest_version_from_list"
return 0
else
# TODO we know it's not worth doing the complex semver interpretation at this point
return 1
fi
@ -663,28 +671,25 @@ nvm_interpret_simple_semver() { (
# Given a semantic version, resolve it to the newest compatible remote node version.
# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function.
nvm_interpret_semver() { (
nvm_interpret_node_semver() { (
semver="${1-}"
if [ -z "$semver" ]; then
nvm_err "Error interpretting semver: Missing semver parameter"
return 1
fi
# Validate incoming semver and transform it into the grammar that is expected by the following logic
valid_transformed_semver=$(nvm_validate_semver "$semver")
if [ -z "$valid_transformed_semver" ]; then
nvm_err "Error interpretting semver: invalid semver: '$semver'"
return 1
fi
# TODO add tests verifying this logic correctly gets list of node versions and that the order is from oldest to newest
# list of node versions is sorted from oldest to newest
remote_node_versions=$(nvm_ls_remote | nvm_grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+')
if [ -z "$remote_node_versions" ]; then
nvm_err "Error interpretting semver: failure retrieving remote node versions"
return 1
fi
# TODO update nvm_interpret_simple_semver failure output to indicate if it is worth doing complex semver interpretation
# If semver is a single comparator, use quick algorithm to determine newest compatible version
resolved_version=$(nvm_interpret_simple_semver "$valid_transformed_semver" "$remote_node_versions")
if [ -n "$resolved_version" ]; then
@ -774,7 +779,7 @@ nvm_package_json_version() {
else
nvm_echo "Found '${pkg_json_path}' with semver expression <${pkg_json_semver}>"
# attempt complex semver range evaluation
RESOLVED_PKG_JSON_VERSION=$(nvm_interpret_semver "$pkg_json_semver")
RESOLVED_PKG_JSON_VERSION=$(nvm_interpret_node_semver "$pkg_json_semver")
if [ ! -n "${RESOLVED_PKG_JSON_VERSION}" ]; then
nvm_err "Warning: could not interpret engines.node semver expression obtained from package.json file."
return 2
@ -4040,7 +4045,7 @@ nvm() {
node_version_has_solaris_binary iojs_version_has_solaris_binary \
nvm_curl_libz_support nvm_command_info \
nvm_get_node_from_pkg_json nvm_find_package_json nvm_package_json_version \
nvm_interpret_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_validate_semver \
nvm_interpret_node_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_validate_semver \
nvm_is_valid_semver nvm_string_contains_regexp \
> /dev/null 2>&1
unset NVM_RC_VERSION NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \

View File

@ -1,52 +0,0 @@
# Semantic version interpretation documentation
Node versions are interpretted from the semver expression located in the engines.node value of the local package.json file. The algorithm for interpretting the semver is expressed at a high level below and is intended to work with the [semantic versioner for npm](https://docs.npmjs.com/misc/semver).
## 1. Convert the semver into the following grammar:
> ```
> semver ::= comparator_set ( ' || ' comparator_set )*
> comparator_set ::= comparator ( ' ' comparator )*
> comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+
> ```
## 2. Resolve each comparator set to its newest compatible node version
**First, if semver only contains a single comparator_set, we may be able to quickly find the newest compatible version.**
```pseudocode
if semver is looking for an exact match of some specified version and the specified version is a valid node version
resolve to the specified version
else if semver is looking for a version less than or equal to some specified version and the specified version is a valid node version
resolve to the specified version
else if semver is looking for a version greater than or equal to some specified version and the current newest node version is greater than or equal to the specified version
resolve to the current newest node version
else if semver is looking for a version strictly greater than to some specified version and the current newest node version is greater than the specified version
resolve to the current newest node version
else
quick resolution of semver interpretation not possible
```
**If quick resolution of semver interpretation does not work, try more complex semver interpretation algorithm.**
```pseudocode
initialize highest_compatible_versions to an empty list
initialize node_version_list to the list of current remote node versions
for each current_comparator_set in the semver {
for each current_node_version in node_version_list {
for each current_comparator in current_comparator_set {
if current_node_version is compatible with current_comparator
continue seeing if current_node_version might be compatible with all comparators in current_comparator_set
else if current_node_version is not compatible with current_comparator
if it can be determined that no older version will satisfy this comparator, we can move on to the next comparator_set in the semver
else
stop seeing if current_node_version is compatible with all comparators in current_comparator_set and move on to the next version
}
if current_node_version was found to be compatible with all comparators in current_comparator_set
add current_node_version to the highest_compatible_versions list
}
}
resolve to the highest version among all the versions collected in highest_compatible_versions
```

View File

@ -131,7 +131,7 @@ for TEMPLATE_NAME in package_json_templates/_valid_*; do
TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }')
EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }')
if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then
if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then
die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #1). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n"
fi
PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT")
@ -159,7 +159,7 @@ for TEMPLATE_NAME in package_json_templates/_invalid_*; do
[ -n "$LINE" ] || continue
TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }')
if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then
if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then
die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #2). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n"
fi
PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT")
@ -201,7 +201,7 @@ for TEMPLATE_NAME in package_json_templates/_valid_*; do
[ -n "$TEST_SEMVER_INPUT" ] || continue
TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2)
if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then
if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then
die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #3). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n"
fi
PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT")
@ -224,7 +224,7 @@ for TEMPLATE_NAME in package_json_templates/_invalid_*; do
[ -n "$TEST_SEMVER_INPUT" ] || continue
TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2)
if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then
if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then
die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #4). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n"
fi
PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT")

View File

@ -29,7 +29,7 @@ while [ -n "$test_cases" ]; do
LINE=$(echo "$test_cases" | head -n1)
INPUT=$(echo "$LINE" | awk -F: '{ print $1 }')
EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }')
ACTUAL_OUTPUT=$(nvm_interpret_semver "$INPUT")
ACTUAL_OUTPUT=$(nvm_interpret_node_semver "$INPUT")
if [ "$ACTUAL_OUTPUT" != "$EXPECTED_OUTPUT" ] || [ -z "$INPUT" ]; then
die "Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n"
fi