From 79319ea7a30d7933a45d00cfaccffa7be47bbc1f Mon Sep 17 00:00:00 2001 From: edwmurph Date: Mon, 28 May 2018 21:46:18 -0400 Subject: [PATCH] refactored nvm_interpret_complex_semver to clearer structure; removed semver.md --- nvm.sh | 127 +++++++++--------- semver.md | 52 ------- .../Unit tests/nvm_get_node_from_pkg_json | 8 +- test/fast/Unit tests/nvm_interpret_semver | 2 +- 4 files changed, 71 insertions(+), 118 deletions(-) delete mode 100644 semver.md diff --git a/nvm.sh b/nvm.sh index 31faaee..14f2f93 100644 --- a/nvm.sh +++ b/nvm.sh @@ -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 \ diff --git a/semver.md b/semver.md deleted file mode 100644 index bb4d68c..0000000 --- a/semver.md +++ /dev/null @@ -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 -``` - diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json index c9e2f01..fee0f9f 100755 --- a/test/fast/Unit tests/nvm_get_node_from_pkg_json +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -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") diff --git a/test/fast/Unit tests/nvm_interpret_semver b/test/fast/Unit tests/nvm_interpret_semver index e7d5a64..b853b39 100755 --- a/test/fast/Unit tests/nvm_interpret_semver +++ b/test/fast/Unit tests/nvm_interpret_semver @@ -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