diff --git a/azure-pipelines/end-to-end-tests-dir/autocomplete.ps1 b/azure-pipelines/end-to-end-tests-dir/autocomplete.ps1 new file mode 100644 index 0000000000..f1a9275847 --- /dev/null +++ b/azure-pipelines/end-to-end-tests-dir/autocomplete.ps1 @@ -0,0 +1,83 @@ +. $PSScriptRoot/../end-to-end-tests-prelude.ps1 + +$publicCommands = @( + 'install' + 'search' + 'remove' + 'list' + 'update' + 'hash' + 'help' + 'integrate' + 'export' + 'edit' + 'create' + 'owns' + 'cache' + 'version' + 'contact' + 'upgrade' +) + +$privateCommands = @( + 'build' + 'buildexternal' + 'ci' + 'depend-info' + 'env' + 'portsdiff' +) + +function getOptionsForPrefix($prefix, $commands) { + $commands | Sort-Object | ? { $_.StartsWith($prefix) } +} +function arraysEqual($arr1, $arr2) { + if ($arr1.Length -ne $arr2.Length) { + $False + } else { + for ($i = 0; $i -lt $arr1.Length; ++$i) { + if ($arr1[$i] -ne $arr2[$i]) { + return $False + } + } + } + $True +} + +$publicPrefixesToTest = @( + 'in' + 's' + 'rem' + 'h' + 'upgra' + 'e' +) +$privatePrefixesToTest = @( + 'b' + 'build' + 'buildext' + 'ci' + 'dep' + 'en' + 'port' + 'notprefix' +) + +function testPrefixes($toTest, $commands) { + $toTest | % { + $expected = getOptionsForPrefix $_ $commands + $found = Run-Vcpkg autocomplete $_ + Throw-IfFailed + if (-not (arraysEqual @($expected) @($found))) { + Write-Host "unexpected result from vcpkg autocomplete: + expected: `"$($expected -join '" "')`" + found : `"$($found -join '" "')`"" + throw + } + } +} + +testPrefixes $publicPrefixesToTest $publicCommands +testPrefixes $privatePrefixesToTest $privateCommands + +# autocomplete is currently broken, so we only test the parts that are correct diff --git a/include/vcpkg/base/parse.h b/include/vcpkg/base/parse.h index f2ff4aac1c..dcadad2af9 100644 --- a/include/vcpkg/base/parse.h +++ b/include/vcpkg/base/parse.h @@ -85,6 +85,12 @@ namespace vcpkg::Parse static constexpr bool is_alphadash(char32_t ch) { return is_icase_alpha(ch) || ch == '-'; } static constexpr bool is_alphanumdash(char32_t ch) { return is_alphanum(ch) || ch == '-'; } + static constexpr bool is_hex_digit(char32_t ch) + { + return is_ascii_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + } + static constexpr bool is_word_char(char32_t ch) { return is_alphanum(ch) || ch == '_'; } + StringView skip_whitespace() { return match_zero_or_more(is_whitespace); } StringView skip_tabs_spaces() { diff --git a/include/vcpkg/base/strings.h b/include/vcpkg/base/strings.h index 8aac6eb373..254d64c9d2 100644 --- a/include/vcpkg/base/strings.h +++ b/include/vcpkg/base/strings.h @@ -236,74 +236,16 @@ namespace vcpkg::Strings // Equivalent to one of the `::strto[T]` functions. Returns `nullopt` if there is an error. template - Optional strto(ZStringView sv); + Optional strto(StringView sv); template<> - inline Optional strto(ZStringView sv) - { - char* endptr = nullptr; - double res = strtod(sv.c_str(), &endptr); - if (endptr == sv.c_str()) - { - // no digits - return nullopt; - } - // else, we may have HUGE_VAL but we expect the caller to deal with that - return res; - } - + Optional strto(StringView); template<> - inline Optional strto(ZStringView sv) - { - char* endptr = nullptr; - long res = strtol(sv.c_str(), &endptr, 10); - if (endptr == sv.c_str()) - { - // no digits - return nullopt; - } - if (errno == ERANGE) - { - // out of bounds - return nullopt; - } - - return res; - } - + Optional strto(StringView); template<> - inline Optional strto(ZStringView sv) - { - char* endptr = nullptr; - long long res = strtoll(sv.c_str(), &endptr, 10); - if (endptr == sv.c_str()) - { - // no digits - return nullopt; - } - if (errno == ERANGE) - { - // out of bounds - return nullopt; - } - - return res; - } - + Optional strto(StringView); template<> - inline Optional strto(ZStringView sv) - { - auto res = strto(sv); - if (auto r = res.get()) - { - if (*r < INT_MIN || *r > INT_MAX) - { - return nullopt; - } - return static_cast(*r); - } - return nullopt; - } + Optional strto(StringView); const char* search(StringView haystack, StringView needle); diff --git a/include/vcpkg/binarycaching.private.h b/include/vcpkg/binarycaching.private.h index 7ad85832a5..a785d857ca 100644 --- a/include/vcpkg/binarycaching.private.h +++ b/include/vcpkg/binarycaching.private.h @@ -16,7 +16,7 @@ namespace vcpkg // - v?. -> ..0-vcpkg // - v?.. -> ..-vcpkg // - anything else -> 0.0.0-vcpkg - std::string reformat_version(StringView version, StringView abi_tag); + std::string format_version_for_nugetref(StringView version, StringView abi_tag); struct NugetReference { @@ -33,7 +33,7 @@ namespace vcpkg const std::string& abi_tag, const std::string& prefix) { - return {Strings::concat(prefix, spec.dir()), reformat_version(raw_version, abi_tag)}; + return {Strings::concat(prefix, spec.dir()), format_version_for_nugetref(raw_version, abi_tag)}; } inline NugetReference make_nugetref(const Dependencies::InstallPlanAction& action, const std::string& prefix) { diff --git a/include/vcpkg/export.ifw.h b/include/vcpkg/export.ifw.h index 0fe07227c2..88183b7a28 100644 --- a/include/vcpkg/export.ifw.h +++ b/include/vcpkg/export.ifw.h @@ -18,6 +18,8 @@ namespace vcpkg::Export::IFW Optional maybe_installer_file_path; }; + std::string safe_rich_from_plain_text(StringView text); + void do_export(const std::vector& export_plan, const std::string& export_id, const Options& ifw_options, diff --git a/include/vcpkg/export.prefab.h b/include/vcpkg/export.prefab.h index 2038949bee..fa94157617 100644 --- a/include/vcpkg/export.prefab.h +++ b/include/vcpkg/export.prefab.h @@ -31,6 +31,12 @@ namespace vcpkg::Export::Prefab std::string to_string(); void to_string(std::string& out); + friend bool operator==(const NdkVersion& lhs, const NdkVersion& rhs) + { + return lhs.m_major == rhs.m_major && lhs.m_minor == rhs.m_minor && lhs.m_patch == rhs.m_patch; + } + friend bool operator!=(const NdkVersion& lhs, const NdkVersion& rhs) { return !(lhs == rhs); } + private: int m_major; int m_minor; @@ -74,6 +80,6 @@ namespace vcpkg::Export::Prefab const VcpkgPaths& paths, const Options& prefab_options, const Triplet& triplet); - Optional find_ndk_version(const std::string& content); - Optional to_version(const std::string& version); + Optional find_ndk_version(StringView content); + Optional to_version(StringView version); } diff --git a/include/vcpkg/metrics.h b/include/vcpkg/metrics.h index db9c0ed5da..2e9aff7a52 100644 --- a/include/vcpkg/metrics.h +++ b/include/vcpkg/metrics.h @@ -31,5 +31,7 @@ namespace vcpkg void flush(Filesystem& fs); }; + Optional find_first_nonzero_mac(StringView sv); + extern LockGuarded g_metrics; } diff --git a/include/vcpkg/tools.h b/include/vcpkg/tools.h index c8a4e7a490..6ca1fda680 100644 --- a/include/vcpkg/tools.h +++ b/include/vcpkg/tools.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -44,5 +45,7 @@ namespace vcpkg virtual const std::string& get_tool_version(const VcpkgPaths& paths, StringView tool) const = 0; }; + Optional> parse_tool_version_string(StringView string_version); + std::unique_ptr get_tool_cache(RequireExactVersions abiToolVersionHandling); } diff --git a/include/vcpkg/versions.h b/include/vcpkg/versions.h index 893144b357..f635277aee 100644 --- a/include/vcpkg/versions.h +++ b/include/vcpkg/versions.h @@ -138,6 +138,23 @@ namespace vcpkg None, Minimum }; + + // this is for version parsing that isn't in vcpkg ports + // stuff like tools, nuget, etc. + struct ParsedExternalVersion + { + StringView major; + StringView minor; + StringView patch; + + void normalize(); + }; + + StringView normalize_external_version_zeros(StringView sv); + // /(\d\d\d\d)-(\d\d)-(\d\d).*/ + bool try_extract_external_date_version(ParsedExternalVersion& out, StringView version); + // /(\d+)(\.\d+|$)(\.\d+)?.*/ + bool try_extract_external_dot_version(ParsedExternalVersion& out, StringView version); } VCPKG_FORMAT_WITH_TO_STRING(vcpkg::VersionSpec); diff --git a/src/vcpkg-test/binarycaching.cpp b/src/vcpkg-test/binarycaching.cpp index 6da6ba9915..9bf828d6f4 100644 --- a/src/vcpkg-test/binarycaching.cpp +++ b/src/vcpkg-test/binarycaching.cpp @@ -206,30 +206,30 @@ TEST_CASE ("CacheStatus operations", "[BinaryCache]") REQUIRE(lhs_lines.size() == rhs_lines.size()); \ } -TEST_CASE ("reformat_version semver-ish", "[reformat_version]") +TEST_CASE ("format_version_for_nugetref semver-ish", "[format_version_for_nugetref]") { - REQUIRE(reformat_version("0.0.0", "abitag") == "0.0.0-vcpkgabitag"); - REQUIRE(reformat_version("1.0.1", "abitag") == "1.0.1-vcpkgabitag"); - REQUIRE(reformat_version("1.01.000", "abitag") == "1.1.0-vcpkgabitag"); - REQUIRE(reformat_version("1.2", "abitag") == "1.2.0-vcpkgabitag"); - REQUIRE(reformat_version("v52", "abitag") == "52.0.0-vcpkgabitag"); - REQUIRE(reformat_version("v09.01.02", "abitag") == "9.1.2-vcpkgabitag"); - REQUIRE(reformat_version("1.1.1q", "abitag") == "1.1.1-vcpkgabitag"); - REQUIRE(reformat_version("1", "abitag") == "1.0.0-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("0.0.0", "abitag") == "0.0.0-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("1.0.1", "abitag") == "1.0.1-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("1.01.000", "abitag") == "1.1.0-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("1.2", "abitag") == "1.2.0-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("v52", "abitag") == "52.0.0-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("v09.01.02", "abitag") == "9.1.2-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("1.1.1q", "abitag") == "1.1.1-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("1", "abitag") == "1.0.0-vcpkgabitag"); } -TEST_CASE ("reformat_version date", "[reformat_version]") +TEST_CASE ("format_version_for_nugetref date", "[format_version_for_nugetref]") { - REQUIRE(reformat_version("2020-06-26", "abitag") == "2020.6.26-vcpkgabitag"); - REQUIRE(reformat_version("20-06-26", "abitag") == "0.0.0-vcpkgabitag"); - REQUIRE(reformat_version("2020-06-26-release", "abitag") == "2020.6.26-vcpkgabitag"); - REQUIRE(reformat_version("2020-06-26000", "abitag") == "2020.6.26-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("2020-06-26", "abitag") == "2020.6.26-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("20-06-26", "abitag") == "0.0.0-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("2020-06-26-release", "abitag") == "2020.6.26-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("2020-06-26000", "abitag") == "2020.6.26-vcpkgabitag"); } -TEST_CASE ("reformat_version generic", "[reformat_version]") +TEST_CASE ("format_version_for_nugetref generic", "[format_version_for_nugetref]") { - REQUIRE(reformat_version("apr", "abitag") == "0.0.0-vcpkgabitag"); - REQUIRE(reformat_version("", "abitag") == "0.0.0-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("apr", "abitag") == "0.0.0-vcpkgabitag"); + REQUIRE(format_version_for_nugetref("", "abitag") == "0.0.0-vcpkgabitag"); } TEST_CASE ("generate_nuspec", "[generate_nuspec]") diff --git a/src/vcpkg-test/export.cpp b/src/vcpkg-test/export.cpp new file mode 100644 index 0000000000..7c171a434c --- /dev/null +++ b/src/vcpkg-test/export.cpp @@ -0,0 +1,120 @@ +#include + +#include +#include + +using namespace vcpkg; + +namespace IFW = Export::IFW; +namespace Prefab = Export::Prefab; + +TEST_CASE ("safe_rich_from_plain_text", "[export]") +{ + CHECK(IFW::safe_rich_from_plain_text("&") == "&"); + CHECK(IFW::safe_rich_from_plain_text("&asdf") == "&asdf"); + CHECK(IFW::safe_rich_from_plain_text("{") == "&#123"); + CHECK(IFW::safe_rich_from_plain_text("᫼") == "&#x1AfC"); + + CHECK(IFW::safe_rich_from_plain_text("&;") == "&;"); + CHECK(IFW::safe_rich_from_plain_text("&#;") == "&#;"); + CHECK(IFW::safe_rich_from_plain_text("&#x;") == "&#x;"); + + CHECK(IFW::safe_rich_from_plain_text("&asdf ;") == "&asdf ;"); + CHECK(IFW::safe_rich_from_plain_text("{a;") == "&#123a;"); + CHECK(IFW::safe_rich_from_plain_text("᫼x;") == "&#x1AfCx;"); + CHECK(IFW::safe_rich_from_plain_text("ģ") == "&#X123;"); + + CHECK(IFW::safe_rich_from_plain_text("&asdf;") == "&asdf;"); + CHECK(IFW::safe_rich_from_plain_text("&asdf_asdf123;") == "&asdf_asdf123;"); + CHECK(IFW::safe_rich_from_plain_text("{") == "{"); + CHECK(IFW::safe_rich_from_plain_text("᫼") == "᫼"); +} + +TEST_CASE ("find_ndk_version", "[export]") +{ + auto result = Prefab::find_ndk_version(R"( +Pkg.Desc = Android NDK +Pkg.Revision = 23.1.7779620 +)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "23.1.7779620"); + + result = Prefab::find_ndk_version(R"( +Pkg.Desc = Android NDK +Pkg.Revision = 23.1.7779620 +Pkg.Blah = doopadoopa +Pkg.Revision = foobar +)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "23.1.7779620"); + + result = Prefab::find_ndk_version(R"( +Pkg.Desc = Android NDK +Pkg.Revision = 1.2.3.4.5 +)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "1.2.3.4.5"); + + result = Prefab::find_ndk_version(R"( +Pkg.Revision = 1.2 +)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "1.2"); + + result = Prefab::find_ndk_version(R"( +Pkg.Revision `= +Pkg.Revision = 1.2.3 +)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "1.2.3"); + + result = Prefab::find_ndk_version(R"( +Pkg.Revision = foobar +Pkg.Revision = 1.2.3 +)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "1.2.3"); + + result = Prefab::find_ndk_version(R"( +Pkg.Desc = Android NDK +)"); + CHECK_FALSE(result.has_value()); + + result = Prefab::find_ndk_version(R"( +Pkg.Desc = Android NDK +Pkg.Revision `= +)"); + CHECK_FALSE(result.has_value()); + + result = Prefab::find_ndk_version(R"( +Pkg.Desc = Android NDK +Pkg.Revision = foobar +)"); + CHECK_FALSE(result.has_value()); +} + +TEST_CASE ("Prefab::to_version", "[export]") +{ + auto result = Prefab::to_version("1.2.3"); + REQUIRE(result.has_value()); + CHECK(*result.get() == Prefab::NdkVersion{1, 2, 3}); + + result = Prefab::to_version("20.180.2134324"); + REQUIRE(result.has_value()); + CHECK(*result.get() == Prefab::NdkVersion{20, 180, 2134324}); + + result = Prefab::to_version("1.2.3 "); + CHECK_FALSE(result.has_value()); + + result = Prefab::to_version(" 1.2.3"); + CHECK_FALSE(result.has_value()); + + result = Prefab::to_version("1.2.3.4"); + CHECK_FALSE(result.has_value()); + + result = Prefab::to_version("1.2"); + CHECK_FALSE(result.has_value()); + + result = Prefab::to_version("100000000000.2.3"); + CHECK_FALSE(result.has_value()); +} diff --git a/src/vcpkg-test/metrics.cpp b/src/vcpkg-test/metrics.cpp new file mode 100644 index 0000000000..a59f5671ab --- /dev/null +++ b/src/vcpkg-test/metrics.cpp @@ -0,0 +1,35 @@ +#include + +#include + +using namespace vcpkg; + +TEST_CASE ("find_first_nonzero_mac", "[metrics]") +{ + auto result = find_first_nonzero_mac(R"()"); + CHECK_FALSE(result.has_value()); + + result = find_first_nonzero_mac(R"(12-34-56-78-90-ab)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "12-34-56-78-90-ab"); + + result = find_first_nonzero_mac(R"(12-34-56-78-90-AB)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "12-34-56-78-90-AB"); + + result = find_first_nonzero_mac(R"(12-34-56-78-90-AB CD-EF-01-23-45-67)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "12-34-56-78-90-AB"); + + result = find_first_nonzero_mac(R"(00-00-00-00-00-00 CD-EF-01-23-45-67)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "CD-EF-01-23-45-67"); + + result = find_first_nonzero_mac(R"(asdfa00-00-00-00-00-00 jiojCD-EF-01-23-45-67-89)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "CD-EF-01-23-45-67"); + + result = find_first_nonzero_mac(R"(afCD-EF-01-23-45-67)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == "CD-EF-01-23-45-67"); +} diff --git a/src/vcpkg-test/tools.cpp b/src/vcpkg-test/tools.cpp new file mode 100644 index 0000000000..61bb380623 --- /dev/null +++ b/src/vcpkg-test/tools.cpp @@ -0,0 +1,43 @@ +#include + +#include + +#include + +using namespace vcpkg; + +TEST_CASE ("parse_tool_version_string", "[tools]") +{ + auto result = parse_tool_version_string("1.2.3"); + REQUIRE(result.has_value()); + CHECK(*result.get() == std::array{1, 2, 3}); + + result = parse_tool_version_string("3.22.3"); + REQUIRE(result.has_value()); + CHECK(*result.get() == std::array{3, 22, 3}); + + result = parse_tool_version_string("4.65"); + REQUIRE(result.has_value()); + CHECK(*result.get() == std::array{4, 65, 0}); + + result = parse_tool_version_string(R"(cmake version 3.22.2 +CMake suite maintained and supported by Kitware (kitware.com/cmake).)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == std::array{3, 22, 2}); + + result = parse_tool_version_string(R"(aria2 version 1.35.0 +Copyright (C) 2006, 2019 Tatsuhiro Tsujikawa)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == std::array{1, 35, 0}); + + result = parse_tool_version_string(R"(git version 2.17.1.windows.2)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == std::array{2, 17, 1}); + + result = parse_tool_version_string(R"(git version 2.17.windows.2)"); + REQUIRE(result.has_value()); + CHECK(*result.get() == std::array{2, 17, 0}); + + result = parse_tool_version_string("4"); + CHECK_FALSE(result.has_value()); +} diff --git a/src/vcpkg/archives.cpp b/src/vcpkg/archives.cpp index deaa466e52..4119b77946 100644 --- a/src/vcpkg/archives.cpp +++ b/src/vcpkg/archives.cpp @@ -188,7 +188,6 @@ namespace namespace vcpkg { - #ifdef _WIN32 void win32_extract_bootstrap_zip(const VcpkgPaths& paths, const Path& archive, const Path& to_path) { diff --git a/src/vcpkg/base/json.cpp b/src/vcpkg/base/json.cpp index d234acfef9..a8134953a0 100644 --- a/src/vcpkg/base/json.cpp +++ b/src/vcpkg/base/json.cpp @@ -482,23 +482,14 @@ namespace vcpkg::Json return Parse::ParserBase::next(); } - static constexpr bool is_digit(char32_t code_point) noexcept - { - return code_point >= '0' && code_point <= '9'; - } - static constexpr bool is_hex_digit(char32_t code_point) noexcept - { - return is_digit(code_point) || (code_point >= 'a' && code_point <= 'f') || - (code_point >= 'A' && code_point <= 'F'); - } static bool is_number_start(char32_t code_point) noexcept { - return code_point == '-' || is_digit(code_point); + return code_point == '-' || is_ascii_digit(code_point); } static unsigned char from_hex_digit(char32_t code_point) noexcept { - if (is_digit(code_point)) + if (is_ascii_digit(code_point)) { return static_cast(code_point) - '0'; } @@ -664,7 +655,7 @@ namespace vcpkg::Json floating = true; current = next(); } - else if (is_digit(current)) + else if (is_ascii_digit(current)) { add_error("Unexpected digits after a leading zero"); return Value(); @@ -682,7 +673,7 @@ namespace vcpkg::Json } } - while (is_digit(current)) + while (is_ascii_digit(current)) { number_to_parse.push_back(static_cast(current)); current = next(); @@ -692,12 +683,12 @@ namespace vcpkg::Json floating = true; number_to_parse.push_back('.'); current = next(); - if (!is_digit(current)) + if (!is_ascii_digit(current)) { add_error("Expected digits after the decimal point"); return Value(); } - while (is_digit(current)) + while (is_ascii_digit(current)) { number_to_parse.push_back(static_cast(current)); current = next(); diff --git a/src/vcpkg/base/strings.cpp b/src/vcpkg/base/strings.cpp index 237be59bb7..19ec7bd0ee 100644 --- a/src/vcpkg/base/strings.cpp +++ b/src/vcpkg/base/strings.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -446,6 +447,96 @@ size_t Strings::byte_edit_distance(StringView a, StringView b) return d[sa - 1]; } +template<> +Optional Strings::strto(StringView sv) +{ + auto opt = strto(sv); + if (auto p = opt.get()) + { + if (INT_MIN <= *p && *p <= INT_MAX) + { + return static_cast(*p); + } + } + return nullopt; +} + +template<> +Optional Strings::strto(StringView sv) +{ + // disallow initial whitespace + if (sv.empty() || Parse::ParserBase::is_whitespace(sv[0])) + { + return nullopt; + } + + auto with_nul_terminator = sv.to_string(); + + errno = 0; + char* endptr = nullptr; + long res = strtol(with_nul_terminator.c_str(), &endptr, 10); + if (endptr != with_nul_terminator.data() + with_nul_terminator.size()) + { + // contains invalid characters + return nullopt; + } + else if (errno == ERANGE) + { + return nullopt; + } + + return res; +} + +template<> +Optional Strings::strto(StringView sv) +{ + // disallow initial whitespace + if (sv.empty() || Parse::ParserBase::is_whitespace(sv[0])) + { + return nullopt; + } + + auto with_nul_terminator = sv.to_string(); + + errno = 0; + char* endptr = nullptr; + long long res = strtoll(with_nul_terminator.c_str(), &endptr, 10); + if (endptr != with_nul_terminator.data() + with_nul_terminator.size()) + { + // contains invalid characters + return nullopt; + } + else if (errno == ERANGE) + { + return nullopt; + } + + return res; +} + +template<> +Optional Strings::strto(StringView sv) +{ + // disallow initial whitespace + if (sv.empty() || Parse::ParserBase::is_whitespace(sv[0])) + { + return nullopt; + } + + auto with_nul_terminator = sv.to_string(); + + char* endptr = nullptr; + double res = strtod(with_nul_terminator.c_str(), &endptr); + if (endptr != with_nul_terminator.data() + with_nul_terminator.size()) + { + // contains invalid characters + return nullopt; + } + // else, we may have HUGE_VAL but we expect the caller to deal with that + return res; +} + namespace vcpkg::Strings { std::string b32_encode(std::uint64_t value) noexcept diff --git a/src/vcpkg/binarycaching.cpp b/src/vcpkg/binarycaching.cpp index 509e8aea6a..b5dfc9bd62 100644 --- a/src/vcpkg/binarycaching.cpp +++ b/src/vcpkg/binarycaching.cpp @@ -2156,117 +2156,27 @@ ExpectedS>> vcpkg::create_binary_pr return providers; } -namespace -{ - struct ReformatParsedVersion - { - StringView major; - StringView minor; - StringView patch; - }; - - StringView remove_leading_zeroes(StringView sv) - { - if (sv.empty()) return "0"; - - auto it = std::find_if_not(sv.begin(), sv.end(), [](char ch) { return ch == '0'; }); - if (it == sv.end()) - { - // all zeroes - just return "0" - return StringView{sv.begin(), sv.begin() + 1}; - } - else - { - return StringView{it, sv.end()}; - } - } - - // /(\d\d\d\d)-(\d\d)-(\d\d).*/ - bool try_parse_reformat_date_version(ReformatParsedVersion& out, StringView version) - { - using P = vcpkg::Parse::ParserBase; - // a b c d - e f - g h - // 0 1 2 3 4 5 6 7 8 9 10 - if (version.size() < 10) return false; - auto first = version.begin(); - if (!P::is_ascii_digit(*first++)) return false; - if (!P::is_ascii_digit(*first++)) return false; - if (!P::is_ascii_digit(*first++)) return false; - if (!P::is_ascii_digit(*first++)) return false; - if (*first++ != '-') return false; - if (!P::is_ascii_digit(*first++)) return false; - if (!P::is_ascii_digit(*first++)) return false; - if (*first++ != '-') return false; - if (!P::is_ascii_digit(*first++)) return false; - if (!P::is_ascii_digit(*first++)) return false; - - first = version.begin(); - out.major = remove_leading_zeroes(StringView{first, first + 4}); - out.minor = remove_leading_zeroes(StringView{first + 5, first + 7}); - out.patch = remove_leading_zeroes(StringView{first + 8, first + 10}); - - return true; - } - - // /v?(\d+)(\.\d+|$)(\.\d+)?.*/ - bool try_parse_reformat_dot_version(ReformatParsedVersion& out, StringView version) - { - using P = vcpkg::Parse::ParserBase; - auto first = version.begin(); - auto last = version.end(); - - out.major = out.minor = out.patch = "0"; - - if (first == last) return false; - if (*first == 'v') ++first; - if (first == last) return false; - - auto major_last = std::find_if_not(first, last, P::is_ascii_digit); - out.major = remove_leading_zeroes(StringView{first, major_last}); - if (major_last == last) - { - return true; - } - else if (*major_last != '.') - { - return false; - } - - auto minor_last = std::find_if_not(major_last + 1, last, P::is_ascii_digit); - if (minor_last == major_last + 1) - { - return false; - } - out.minor = remove_leading_zeroes(StringView{major_last + 1, minor_last}); - if (minor_last == last || *minor_last != '.') - { - return true; - } - - auto patch_last = std::find_if_not(minor_last + 1, last, P::is_ascii_digit); - if (minor_last == major_last + 1) - { - return false; - } - out.patch = remove_leading_zeroes(StringView{minor_last + 1, patch_last}); - return true; - } -} - -std::string vcpkg::reformat_version(StringView version, StringView abi_tag) +std::string vcpkg::format_version_for_nugetref(StringView version, StringView abi_tag) { // this cannot use DotVersion::try_parse or DateVersion::try_parse, // since this is a subtly different algorithm // and ignores random extra stuff from the end - ReformatParsedVersion parsed_version; - if (try_parse_reformat_date_version(parsed_version, version)) + ParsedExternalVersion parsed_version; + if (try_extract_external_date_version(parsed_version, version)) { + parsed_version.normalize(); return fmt::format( "{}.{}.{}-vcpkg{}", parsed_version.major, parsed_version.minor, parsed_version.patch, abi_tag); } - if (try_parse_reformat_dot_version(parsed_version, version)) + + if (!version.empty() && version[0] == 'v') + { + version = version.substr(1); + } + if (try_extract_external_dot_version(parsed_version, version)) { + parsed_version.normalize(); return fmt::format( "{}.{}.{}-vcpkg{}", parsed_version.major, parsed_version.minor, parsed_version.patch, abi_tag); } diff --git a/src/vcpkg/commands.autocomplete.cpp b/src/vcpkg/commands.autocomplete.cpp index 24d413d1c7..14350e70cf 100644 --- a/src/vcpkg/commands.autocomplete.cpp +++ b/src/vcpkg/commands.autocomplete.cpp @@ -23,7 +23,7 @@ namespace vcpkg::Commands::Autocomplete Checks::exit_success(line_info); } - static std::vector combine_port_with_triplets(const std::string& port, + static std::vector combine_port_with_triplets(StringView port, const std::vector& triplets) { return Util::fmap(triplets, @@ -33,15 +33,15 @@ namespace vcpkg::Commands::Autocomplete void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { LockGuardPtr(g_metrics)->set_send_metrics(false); - const std::string to_autocomplete = Strings::join(" ", args.command_arguments); - const std::vector tokens = Strings::split(to_autocomplete, ' '); - - std::smatch match; // Handles vcpkg - if (std::regex_match(to_autocomplete, match, std::regex{R"###(^(\S*)$)###"})) + if (args.command_arguments.size() <= 1) { - const std::string requested_command = match[1].str(); + StringView requested_command = ""; + if (args.command_arguments.size() == 1) + { + requested_command = args.command_arguments[0]; + } // First try public commands std::vector public_commands = {"install", @@ -87,71 +87,73 @@ namespace vcpkg::Commands::Autocomplete output_sorted_results_and_exit(VCPKG_LINE_INFO, std::move(private_commands)); } + // args.command_arguments.size() >= 2 + const auto& command_name = args.command_arguments[0]; + // Handles vcpkg install package: - if (std::regex_match(to_autocomplete, match, std::regex{R"###(^install(.*|)\s([^:]+):(\S*)$)###"})) + if (command_name == "install") { - const auto port_name = match[2].str(); - const auto triplet_prefix = match[3].str(); - - // TODO: Support autocomplete for ports in --overlay-ports - auto maybe_port = - Paragraphs::try_load_port(paths.get_filesystem(), paths.builtin_ports_directory() / port_name); - if (maybe_port.error()) + StringView last_arg = args.command_arguments.back(); + auto colon = Util::find(last_arg, ':'); + if (colon != last_arg.end()) { - Checks::exit_success(VCPKG_LINE_INFO); - } + auto port_name = StringView{last_arg.begin(), colon}; + auto triplet_prefix = StringView{colon + 1, last_arg.end()}; + // TODO: Support autocomplete for ports in --overlay-ports + auto maybe_port = + Paragraphs::try_load_port(paths.get_filesystem(), paths.builtin_ports_directory() / port_name); + if (maybe_port.error()) + { + Checks::exit_success(VCPKG_LINE_INFO); + } - std::vector triplets = paths.get_available_triplets_names(); - Util::erase_remove_if(triplets, [&](const std::string& s) { - return !Strings::case_insensitive_ascii_starts_with(s, triplet_prefix); - }); + std::vector triplets = paths.get_available_triplets_names(); + Util::erase_remove_if(triplets, [&](const std::string& s) { + return !Strings::case_insensitive_ascii_starts_with(s, triplet_prefix); + }); - auto result = combine_port_with_triplets(port_name, triplets); + auto result = combine_port_with_triplets(port_name, triplets); - output_sorted_results_and_exit(VCPKG_LINE_INFO, std::move(result)); + output_sorted_results_and_exit(VCPKG_LINE_INFO, std::move(result)); + } } struct CommandEntry { - constexpr CommandEntry(const ZStringView& name, const ZStringView& regex, const CommandStructure& structure) - : name(name), regex(regex), structure(structure) - { - } - ZStringView name; - ZStringView regex; const CommandStructure& structure; }; static constexpr CommandEntry COMMANDS[] = { - CommandEntry{"install", R"###(^install\s(.*\s|)(\S*)$)###", Install::COMMAND_STRUCTURE}, - CommandEntry{"edit", R"###(^edit\s(.*\s|)(\S*)$)###", Edit::COMMAND_STRUCTURE}, - CommandEntry{"remove", R"###(^remove\s(.*\s|)(\S*)$)###", Remove::COMMAND_STRUCTURE}, - CommandEntry{"integrate", R"###(^integrate(\s+)(\S*)$)###", Integrate::COMMAND_STRUCTURE}, - CommandEntry{"upgrade", R"###(^upgrade(\s+)(\S*)$)###", Upgrade::COMMAND_STRUCTURE}, + CommandEntry{"install", Install::COMMAND_STRUCTURE}, + CommandEntry{"edit", Edit::COMMAND_STRUCTURE}, + CommandEntry{"remove", Remove::COMMAND_STRUCTURE}, + CommandEntry{"integrate", Integrate::COMMAND_STRUCTURE}, + CommandEntry{"upgrade", Upgrade::COMMAND_STRUCTURE}, }; for (auto&& command : COMMANDS) { - if (std::regex_match(to_autocomplete, match, std::regex{command.regex.c_str()})) + if (command_name == command.name) { - const auto prefix = match[2].str(); + StringView prefix = args.command_arguments.back(); std::vector results; const bool is_option = Strings::starts_with(prefix, "-"); if (is_option) { - results = Util::fmap(command.structure.options.switches, [](const CommandSwitch& s) -> std::string { - return Strings::format("--%s", s.name.to_string()); - }); - - auto settings = Util::fmap(command.structure.options.settings, - [](auto&& s) { return Strings::format("--%s", s.name); }); - results.insert(results.end(), settings.begin(), settings.end()); - - auto multisettings = Util::fmap(command.structure.options.multisettings, - [](auto&& s) { return Strings::format("--%s", s.name); }); - results.insert(results.end(), multisettings.begin(), multisettings.end()); + for (const auto& s : command.structure.options.switches) + { + results.push_back(Strings::concat("--", s.name)); + } + for (const auto& s : command.structure.options.settings) + { + results.push_back(Strings::concat("--", s.name)); + } + for (const auto& s : command.structure.options.multisettings) + { + results.push_back(Strings::concat("--", s.name)); + } } else { diff --git a/src/vcpkg/commands.xdownload.cpp b/src/vcpkg/commands.xdownload.cpp index 6ef692a8cc..9bb17841ad 100644 --- a/src/vcpkg/commands.xdownload.cpp +++ b/src/vcpkg/commands.xdownload.cpp @@ -39,12 +39,7 @@ namespace vcpkg::Commands::X_Download nullptr, }; - static bool is_hex(StringView sha) - { - return std::all_of(sha.begin(), sha.end(), [](char ch) { - return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); - }); - } + static bool is_hex(StringView sha) { return std::all_of(sha.begin(), sha.end(), Parse::ParserBase::is_hex_digit); } static bool is_sha512(StringView sha) { return sha.size() == 128 && is_hex(sha); } static Optional get_sha512_check(const VcpkgCmdArguments& args, const ParsedArguments& parsed) diff --git a/src/vcpkg/export.ifw.cpp b/src/vcpkg/export.ifw.cpp index ea89068999..5d3b6b7a01 100644 --- a/src/vcpkg/export.ifw.cpp +++ b/src/vcpkg/export.ifw.cpp @@ -15,6 +15,81 @@ namespace vcpkg::Export::IFW using Dependencies::ExportPlanType; using Install::InstallDir; + // requires: after_prefix <= semi + // requires: *semi == ';' + static bool is_character_ref(const char* after_prefix, const char* semi) + { + if (after_prefix == semi) + { + return false; + } + + if (*after_prefix == '#') + { + ++after_prefix; + if (*after_prefix == 'x') + { + ++after_prefix; + // hex character escape: ઼ + return after_prefix != semi && std::all_of(after_prefix, semi, Parse::ParserBase::is_hex_digit); + } + + // decimal character escape: { + return after_prefix != semi && std::all_of(after_prefix, semi, Parse::ParserBase::is_ascii_digit); + } + + // word character escape: & + return std::all_of(after_prefix, semi, Parse::ParserBase::is_word_char); + } + + std::string safe_rich_from_plain_text(StringView text) + { + // looking for `&`, not followed by: + // - '#;` + // - '#x;` + // - `;` + // (basically, an HTML character entity reference) + constexpr static StringLiteral escaped_amp = "&"; + + auto first = text.begin(); + const auto last = text.end(); + + std::string result; + for (;;) + { + auto amp = std::find(first, last, '&'); + result.append(first, amp); + first = amp; + if (first == last) + { + break; + } + + ++first; // skip amp + if (first == last) + { + result.append(escaped_amp); + break; + } + else + { + auto semi = std::find(first, last, ';'); + + if (semi != last && is_character_ref(first, semi)) + { + first = amp; + } + else + { + result.append(escaped_amp.begin(), escaped_amp.end()); + } + result.append(first, semi); + first = semi; + } + } + return result; + } + namespace { std::string create_release_date() @@ -33,14 +108,6 @@ namespace vcpkg::Export::IFW return date_time_as_string; } - std::string safe_rich_from_plain_text(const std::string& text) - { - // match standalone ampersand, no HTML number or name - std::regex standalone_ampersand(R"###(&(?!(#[0-9]+|\w+);))###"); - - return std::regex_replace(text, standalone_ampersand, "&"); - } - Path get_packages_dir_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) { return ifw_options.maybe_packages_dir_path.has_value() diff --git a/src/vcpkg/export.prefab.cpp b/src/vcpkg/export.prefab.cpp index 635bf69368..624dd877e3 100644 --- a/src/vcpkg/export.prefab.cpp +++ b/src/vcpkg/export.prefab.cpp @@ -153,44 +153,63 @@ namespace vcpkg::Export::Prefab return json; } - Optional find_ndk_version(const std::string& content) + Optional find_ndk_version(StringView content) { - std::smatch pkg_match; - std::regex pkg_regex(R"(Pkg\.Revision\s*=\s*(\d+)(\.\d+)(\.\d+)\s*)"); + constexpr static StringLiteral pkg_revision = "Pkg.Revision"; - if (std::regex_search(content, pkg_match, pkg_regex)) + constexpr static auto is_version_character = [](char ch) { + return ch == '.' || Parse::ParserBase::is_ascii_digit(ch); + }; + + auto first = content.begin(); + auto last = content.end(); + + for (;;) { - for (const auto& p : pkg_match) - { - std::string delimiter = "="; - std::string s = p.str(); - auto it = s.find(delimiter); - if (it != std::string::npos) - { - std::string token = (s.substr(s.find(delimiter) + 1, s.size())); - return Strings::trim(std::move(token)); - } - } + first = std::search(first, last, pkg_revision.begin(), pkg_revision.end()); + if (first == last) break; + + first += pkg_revision.size(); + first = std::find_if_not(first, last, Parse::ParserBase::is_whitespace); + if (first == last) break; + if (*first != '=') continue; + + // Pkg.Revision = x.y.z + ++first; // skip = + first = std::find_if_not(first, last, Parse::ParserBase::is_whitespace); + auto end_of_version = std::find_if_not(first, last, is_version_character); + if (first == end_of_version) continue; + return StringView{first, end_of_version}; } + return {}; } - Optional to_version(const std::string& version) + Optional to_version(StringView version) { if (version.size() > 100) return {}; - size_t last = 0; - size_t next = 0; - std::vector fragments(0); + std::vector fragments; - while ((next = version.find(".", last)) != std::string::npos) + for (auto first = version.begin(), last = version.end(); first != last;) { - fragments.push_back(std::stoi(version.substr(last, next - last))); - last = next + 1; + auto next = std::find(first, last, '.'); + auto parsed = Strings::strto(StringView{first, next}); + if (auto p = parsed.get()) + { + fragments.push_back(*p); + } + else + { + return {}; + } + if (next == last) break; + ++next; + first = next; } - fragments.push_back(std::stoi(version.substr(last))); - if (fragments.size() == kFragmentSize) + + if (fragments.size() == 3) { - return NdkVersion(fragments[0], fragments[1], fragments[2]); + return NdkVersion{fragments[0], fragments[1], fragments[2]}; } return {}; } diff --git a/src/vcpkg/metrics.cpp b/src/vcpkg/metrics.cpp index e7ce8dbb7b..137ebfc023 100644 --- a/src/vcpkg/metrics.cpp +++ b/src/vcpkg/metrics.cpp @@ -19,6 +19,67 @@ namespace vcpkg { LockGuarded g_metrics; + Optional find_first_nonzero_mac(StringView sv) + { + static constexpr StringLiteral ZERO_MAC = "00-00-00-00-00-00"; + + auto first = sv.begin(); + const auto last = sv.end(); + + while (first != last) + { + // XX-XX-XX-XX-XX-XX + // 1 2 3 4 5 6 + // size = 6 * 2 + 5 = 17 + first = std::find_if(first, last, Parse::ParserBase::is_hex_digit); + if (last - first < 17) + { + break; + } + + bool is_first = true; + bool is_valid = true; + auto end_of_mac = first; + for (int i = 0; is_valid && i < 6; ++i) + { + if (!is_first) + { + if (*end_of_mac != '-') + { + is_valid = false; + break; + } + ++end_of_mac; + } + is_first = false; + + if (!Parse::ParserBase::is_hex_digit(*end_of_mac)) + { + is_valid = false; + break; + } + ++end_of_mac; + + if (!Parse::ParserBase::is_hex_digit(*end_of_mac)) + { + is_valid = false; + break; + } + ++end_of_mac; + } + if (is_valid && StringView{first, end_of_mac} != ZERO_MAC) + { + return StringView{first, end_of_mac}; + } + else + { + first = end_of_mac; + } + } + + return nullopt; + } + static std::string get_current_date_time_string() { auto maybe_time = CTime::get_current_date_time(); @@ -262,21 +323,15 @@ namespace vcpkg if (getmac.exit_code != 0) return "0"; - std::regex mac_regex("([a-fA-F0-9]{2}(-[a-fA-F0-9]{2}){5})"); - std::sregex_iterator next(getmac.output.begin(), getmac.output.end(), mac_regex); - std::sregex_iterator last; - - while (next != last) + auto found_mac = find_first_nonzero_mac(getmac.output); + if (auto p = found_mac.get()) { - const auto match = *next; - if (match[0] != "00-00-00-00-00-00") - { - return vcpkg::Hash::get_string_hash(match[0].str(), Hash::Algorithm::Sha256); - } - ++next; + return Hash::get_string_hash(*p, Hash::Algorithm::Sha256); + } + else + { + return "0"; } - - return "0"; } #endif diff --git a/src/vcpkg/tools.cpp b/src/vcpkg/tools.cpp index 5bdb93c9fb..fe43274e3a 100644 --- a/src/vcpkg/tools.cpp +++ b/src/vcpkg/tools.cpp @@ -25,25 +25,44 @@ namespace vcpkg std::string sha512; }; - static Optional> parse_version_string(const std::string& version_as_string) + // /\d+\.\d+(\.\d+)?/ + Optional> parse_tool_version_string(StringView string_version) { - static const std::regex RE(R"###((\d+)\.(\d+)(\.(\d+))?)###"); + // first, find the beginning of the version + auto first = string_version.begin(); + const auto last = string_version.end(); - std::match_results match; - const auto found = std::regex_search(version_as_string, match, RE); - if (!found) + // we're looking for the first instance of `.` + ParsedExternalVersion parsed_version{}; + for (;;) { - return {}; + first = std::find_if(first, last, Parse::ParserBase::is_ascii_digit); + if (first == last) + { + return nullopt; + } + + if (try_extract_external_dot_version(parsed_version, StringView{first, last}) && + !parsed_version.minor.empty()) + { + break; + } + + first = std::find_if_not(first, last, Parse::ParserBase::is_ascii_digit); } - const int d1 = atoi(match[1].str().c_str()); - const int d2 = atoi(match[2].str().c_str()); - const int d3 = [&] { - if (match[4].str().empty()) return 0; - return atoi(match[4].str().c_str()); - }(); - const std::array result = {d1, d2, d3}; - return result; + parsed_version.normalize(); + + auto d1 = Strings::strto(parsed_version.major); + if (!d1.has_value()) return {}; + + auto d2 = Strings::strto(parsed_version.minor); + if (!d2.has_value()) return {}; + + auto d3 = Strings::strto(parsed_version.patch); + if (!d3.has_value()) return {}; + + return std::array{*d1.get(), *d2.get(), *d3.get()}; } static ExpectedT parse_tool_data_from_xml(const VcpkgPaths& paths, StringView tool) @@ -112,7 +131,7 @@ namespace vcpkg const std::string sha512 = Strings::find_exactly_one_enclosed(tool_data, "", "").to_string(); auto archive_name = Strings::find_at_most_one_enclosed(tool_data, "", ""); - const Optional> version = parse_version_string(version_as_string); + const Optional> version = parse_tool_version_string(version_as_string); Checks::check_exit(VCPKG_LINE_INFO, version.has_value(), "Could not parse version for tool %s. Version string was: %s", @@ -165,7 +184,7 @@ namespace vcpkg auto maybe_version = tool_provider.get_version(paths, candidate); const auto version = maybe_version.get(); if (!version) continue; - const auto parsed_version = parse_version_string(*version); + const auto parsed_version = parse_tool_version_string(*version); if (!parsed_version) continue; auto& actual_version = *parsed_version.get(); if (!accept_version(actual_version)) continue; diff --git a/src/vcpkg/versiondeserializers.cpp b/src/vcpkg/versiondeserializers.cpp index ce1a97394b..7eb7ca4fbb 100644 --- a/src/vcpkg/versiondeserializers.cpp +++ b/src/vcpkg/versiondeserializers.cpp @@ -34,7 +34,7 @@ namespace } else if (pv.size() > 1) { - ret.second = Strings::strto(pv.substr(1).to_string()); + ret.second = Strings::strto(pv.substr(1)); if (ret.second.value_or(-1) < 0) { r.add_generic_error(type_name(), diff --git a/src/vcpkg/versions.cpp b/src/vcpkg/versions.cpp index 8edcc7943c..da3b57463d 100644 --- a/src/vcpkg/versions.cpp +++ b/src/vcpkg/versions.cpp @@ -326,19 +326,11 @@ namespace vcpkg ExpectedL DateVersion::try_parse(StringView version) { - if (version.size() < 10) return format_invalid_date_version(version); - - bool valid = Parse::ParserBase::is_ascii_digit(version[0]); - valid |= Parse::ParserBase::is_ascii_digit(version[1]); - valid |= Parse::ParserBase::is_ascii_digit(version[2]); - valid |= Parse::ParserBase::is_ascii_digit(version[3]); - valid |= version[4] != '-'; - valid |= Parse::ParserBase::is_ascii_digit(version[5]); - valid |= Parse::ParserBase::is_ascii_digit(version[6]); - valid |= version[7] != '-'; - valid |= Parse::ParserBase::is_ascii_digit(version[8]); - valid |= Parse::ParserBase::is_ascii_digit(version[9]); - if (!valid) return format_invalid_date_version(version); + ParsedExternalVersion parsed; + if (!try_extract_external_date_version(parsed, version)) + { + return format_invalid_date_version(version); + } DateVersion ret; ret.original_string.assign(version.data(), version.size()); @@ -409,4 +401,89 @@ namespace vcpkg return static_cast(Util::range_lexcomp(a.identifiers, b.identifiers, uint64_comp)); } + + StringView normalize_external_version_zeros(StringView sv) + { + if (sv.empty()) return "0"; + + auto it = std::find_if_not(sv.begin(), sv.end(), [](char ch) { return ch == '0'; }); + if (it == sv.end()) + { + // all zeroes - just return "0" + return StringView{sv.end() - 1, sv.end()}; + } + else + { + return StringView{it, sv.end()}; + } + } + + void ParsedExternalVersion::normalize() + { + major = normalize_external_version_zeros(major); + minor = normalize_external_version_zeros(minor); + patch = normalize_external_version_zeros(patch); + } + + // /(\d\d\d\d)-(\d\d)-(\d\d).*/ + bool try_extract_external_date_version(ParsedExternalVersion& out, StringView version) + { + using P = vcpkg::Parse::ParserBase; + // a b c d - e f - g h + // 0 1 2 3 4 5 6 7 8 9 10 + if (version.size() < 10) return false; + auto first = version.begin(); + if (!P::is_ascii_digit(*first++)) return false; + if (!P::is_ascii_digit(*first++)) return false; + if (!P::is_ascii_digit(*first++)) return false; + if (!P::is_ascii_digit(*first++)) return false; + if (*first++ != '-') return false; + if (!P::is_ascii_digit(*first++)) return false; + if (!P::is_ascii_digit(*first++)) return false; + if (*first++ != '-') return false; + if (!P::is_ascii_digit(*first++)) return false; + if (!P::is_ascii_digit(*first++)) return false; + + first = version.begin(); + out.major = StringView{first, first + 4}; + out.minor = StringView{first + 5, first + 7}; + out.patch = StringView{first + 8, first + 10}; + + return true; + } + + // /(\d+)(\.\d+|$)(\.\d+)?.*/ + bool try_extract_external_dot_version(ParsedExternalVersion& out, StringView version) + { + using P = vcpkg::Parse::ParserBase; + auto first = version.begin(); + auto last = version.end(); + + out.major = out.minor = out.patch = StringView{}; + + if (first == last) return false; + + auto major_last = std::find_if_not(first, last, P::is_ascii_digit); + out.major = StringView{first, major_last}; + if (major_last == last) + { + return true; + } + else if (*major_last != '.') + { + return false; + } + + auto minor_last = std::find_if_not(major_last + 1, last, P::is_ascii_digit); + out.minor = StringView{major_last + 1, minor_last}; + if (minor_last == last || minor_last == major_last + 1 || *minor_last != '.') + { + return true; + } + + auto patch_last = std::find_if_not(minor_last + 1, last, P::is_ascii_digit); + out.patch = StringView{minor_last + 1, patch_last}; + + return true; + } }