Ctrl K
ComunidadGuías…

server/src/version_compare.h

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#pragma once

#include <algorithm>
#include <charconv>
#include <cctype>
#include <sstream>
#include <string>
#include <string_view>
#include <system_error>
#include <variant>
#include <vector>

namespace version_compare {
  using prerelease_identifier_t = std::variant<int, std::string>;

  struct semver_t {
    int major {0};
    int minor {0};
    int patch {0};
    std::vector<prerelease_identifier_t> prerelease;
  };

  inline std::string ascii_lower(std::string value) {
    std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
      return static_cast<char>(std::tolower(ch));
    });
    return value;
  }

  inline bool is_numeric_identifier(const std::string &identifier) {
    return !identifier.empty() &&
           std::all_of(identifier.begin(), identifier.end(), [](unsigned char ch) {
             return std::isdigit(ch) != 0;
           });
  }

  inline bool parse_nonnegative_integer(std::string_view value, int &result) {
    if (value.empty()) {
      return false;
    }
    const auto [end, error] = std::from_chars(value.data(), value.data() + value.size(), result);
    return error == std::errc {} && end == value.data() + value.size() && result >= 0;
  }

  inline semver_t parse_semver(std::string_view version) {
    semver_t out;
    if (version.empty()) {
      return out;
    }

    std::string value(version);
    if (!value.empty() && (value[0] == 'v' || value[0] == 'V')) {
      value.erase(0, 1);
    }
    if (auto plus = value.find('+'); plus != std::string::npos) {
      value.resize(plus);
    }

    std::string core = value;
    if (auto dash = value.find('-'); dash != std::string::npos) {
      core = value.substr(0, dash);
      std::stringstream prerelease_stream(value.substr(dash + 1));
      std::string identifier;
      while (std::getline(prerelease_stream, identifier, '.')) {
        if (identifier.empty()) {
          continue;
        }
        if (is_numeric_identifier(identifier)) {
          int numeric_identifier = 0;
          if (parse_nonnegative_integer(identifier, numeric_identifier)) {
            out.prerelease.emplace_back(numeric_identifier);
            continue;
          }
        }
        out.prerelease.emplace_back(identifier);
      }
    }

    std::stringstream core_stream(core);
    std::string part;
    if (std::getline(core_stream, part, '.') && !parse_nonnegative_integer(part, out.major)) {
      return semver_t {};
    }
    if (std::getline(core_stream, part, '.') && !parse_nonnegative_integer(part, out.minor)) {
      return semver_t {};
    }
    if (std::getline(core_stream, part, '.') && !parse_nonnegative_integer(part, out.patch)) {
      return semver_t {};
    }

    return out;
  }

  inline bool is_stable_channel(const semver_t &version) {
    if (version.prerelease.empty() || !std::holds_alternative<std::string>(version.prerelease.front())) {
      return false;
    }
    return ascii_lower(std::get<std::string>(version.prerelease.front())) == "stable";
  }

  inline bool is_prerelease_channel(std::string_view version) {
    const auto parsed = parse_semver(version);
    return !parsed.prerelease.empty() && !is_stable_channel(parsed);
  }

  inline int compare_identifier_lists(
    const std::vector<prerelease_identifier_t> &lhs,
    const std::vector<prerelease_identifier_t> &rhs,
    size_t start_index = 0
  ) {
    const size_t len = std::max(lhs.size(), rhs.size());
    for (size_t i = start_index; i < len; ++i) {
      if (i >= lhs.size()) {
        return -1;
      }
      if (i >= rhs.size()) {
        return 1;
      }

      const auto &left = lhs[i];
      const auto &right = rhs[i];
      const bool left_is_num = std::holds_alternative<int>(left);
      const bool right_is_num = std::holds_alternative<int>(right);

      if (left_is_num && right_is_num) {
        const int left_value = std::get<int>(left);
        const int right_value = std::get<int>(right);
        if (left_value != right_value) {
          return left_value < right_value ? -1 : 1;
        }
        continue;
      }

      if (left_is_num != right_is_num) {
        return left_is_num ? -1 : 1;
      }

      const auto &left_value = std::get<std::string>(left);
      const auto &right_value = std::get<std::string>(right);
      if (left_value != right_value) {
        return left_value < right_value ? -1 : 1;
      }
    }

    return 0;
  }

  inline int compare_semver(std::string_view lhs, std::string_view rhs) {
    const auto left = parse_semver(lhs);
    const auto right = parse_semver(rhs);

    if (left.major != right.major) {
      return left.major < right.major ? -1 : 1;
    }
    if (left.minor != right.minor) {
      return left.minor < right.minor ? -1 : 1;
    }
    if (left.patch != right.patch) {
      return left.patch < right.patch ? -1 : 1;
    }

    const bool left_stable = is_stable_channel(left);
    const bool right_stable = is_stable_channel(right);

    // ArtLight Server uses `-stable.N` for post-release respins, so those need to sort
    // above the naked release while keeping normal prerelease semantics elsewhere.
    if (left_stable && right_stable) {
      return compare_identifier_lists(left.prerelease, right.prerelease, 1);
    }
    if (left_stable != right_stable) {
      return left_stable ? 1 : -1;
    }

    if (left.prerelease.empty() && right.prerelease.empty()) {
      return 0;
    }
    if (left.prerelease.empty()) {
      return 1;
    }
    if (right.prerelease.empty()) {
      return -1;
    }

    return compare_identifier_lists(left.prerelease, right.prerelease);
  }
}  // namespace version_compare