diff --git a/libmamba/include/mamba/specs/match_spec.hpp b/libmamba/include/mamba/specs/match_spec.hpp index c932efd36f..e3d6be77b6 100644 --- a/libmamba/include/mamba/specs/match_spec.hpp +++ b/libmamba/include/mamba/specs/match_spec.hpp @@ -12,6 +12,8 @@ #include #include +#include + #include "mamba/specs/build_number_spec.hpp" #include "mamba/specs/error.hpp" #include "mamba/specs/glob_spec.hpp" @@ -146,4 +148,12 @@ namespace mamba::specs auto operator""_ms(const char* str, std::size_t len) -> MatchSpec; } } + +template <> +struct fmt::formatter<::mamba::specs::MatchSpec> +{ + auto parse(format_parse_context& ctx) -> decltype(ctx.begin()); + + auto format(const ::mamba::specs::MatchSpec& spec, format_context& ctx) -> decltype(ctx.out()); +}; #endif diff --git a/libmamba/src/specs/match_spec.cpp b/libmamba/src/specs/match_spec.cpp index ba19ce016f..1fcf01803c 100644 --- a/libmamba/src/specs/match_spec.cpp +++ b/libmamba/src/specs/match_spec.cpp @@ -4,7 +4,6 @@ // // The full license is in the file LICENSE, distributed with this software. -#include #include #include #include @@ -891,137 +890,188 @@ namespace mamba::specs auto MatchSpec::str() const -> std::string { - std::stringstream res; - // builder = [] - // brackets = [] - - // channel_matcher = self._match_components.get('channel') - // if channel_matcher and channel_matcher.exact_value: - // builder.append(text_type(channel_matcher)) - // elif channel_matcher and not channel_matcher.matches_all: - // brackets.append("channel=%s" % text_type(channel_matcher)) + return fmt::format("{}", *this); + } - // subdir_matcher = self._match_components.get('subdir') - // if subdir_matcher: - // if channel_matcher and channel_matcher.exact_value: - // builder.append('/%s' % subdir_matcher) - // else: - // brackets.append("subdir=%s" % subdir_matcher) + auto MatchSpec::is_simple() const -> bool + { + return m_version.is_explicitly_free() && m_build_string.is_free() + && m_build_number.is_explicitly_free(); + } - // TODO change as attribute if complex URL, and has "url" if PackageUrl - if (m_channel.has_value()) + auto MatchSpec::extra() -> ExtraMembers& + { + if (!m_extra.has_value()) { - res << fmt::format("{}::", *m_channel); + m_extra.emplace(); } - // TODO when namespaces are implemented! - // if (!ns.empty()) - // { - // res << ns; - // res << ":"; - // } - res << m_name.str(); - std::vector formatted_brackets; - - auto is_complex_relation = [](const std::string& s) - { return s.find_first_of("><$^|,") != s.npos; }; + return *m_extra; + } - if (!m_version.is_explicitly_free()) + namespace match_spec_literals + { + auto operator""_ms(const char* str, std::size_t len) -> MatchSpec { - auto ver = m_version.str(); - if (is_complex_relation(ver)) // TODO do on VersionSpec - { - formatted_brackets.push_back(util::concat("version='", ver, "'")); - } - else - { - res << ver; - // version_exact = true; - } + return MatchSpec::parse({ str, len }) + .or_else([](specs::ParseError&& err) { throw std::move(err); }) + .value(); } + } +} - if (!m_build_string.is_free()) - { - if (m_build_string.is_exact()) - { - res << "=" << m_build_string.str(); - } - else - { - formatted_brackets.push_back(util::concat("build='", m_build_string.str(), '\'')); - } - } +auto +fmt::formatter<::mamba::specs::MatchSpec>::parse(format_parse_context& ctx) -> decltype(ctx.begin()) +{ + // make sure that range is empty + if (ctx.begin() != ctx.end() && *ctx.begin() != '}') + { + throw fmt::format_error("Invalid format"); + } + return ctx.begin(); +} - if (const auto& num = build_number(); !num.is_explicitly_free()) - { - formatted_brackets.push_back(util::concat("build_number=", num.str())); - } - if (const auto& tf = track_features(); tf.has_value() && !tf->get().empty()) - { - formatted_brackets.push_back( - fmt::format(R"(track_features="{}")", fmt::join(tf->get(), " ")) - ); - } - if (const auto& feats = features(); !feats.empty()) - { - const auto& q = find_needed_quote(feats); - formatted_brackets.push_back(util::concat("features=", q, feats, q)); - } - else if (const auto& fn = filename(); !fn.empty() && !channel_is_file()) - { - // No "fn" when we have a URL - const auto& q = find_needed_quote(fn); - formatted_brackets.push_back(util::concat("fn=", q, fn, q)); - } - if (const auto& hash = md5(); !hash.empty()) +auto +fmt::formatter<::mamba::specs::MatchSpec>::format( + const ::mamba::specs::MatchSpec& spec, + format_context& ctx +) -> decltype(ctx.out()) +{ + using MatchSpec = ::mamba::specs::MatchSpec; + + auto out = ctx.out(); + + bool channel_is_package = false; + if (const auto& chan = spec.channel(); chan.has_value() && chan->is_package()) + { + out = fmt::format_to(out, "{}", chan.value()); + if (const auto& md5 = spec.md5(); !md5.empty()) { - formatted_brackets.push_back(util::concat("md5=", hash)); + out = fmt::format_to(out, "{}{}", MatchSpec::url_md5_sep, md5); } - if (const auto& hash = sha256(); !hash.empty()) + return out; + } + + if (const auto& chan = spec.channel()) + { + out = fmt::format_to( + out, + "{}{}{}{}", + chan.value(), + MatchSpec::channel_namespace_spec_sep, + spec.name_space(), + MatchSpec::channel_namespace_spec_sep + ); + } + else if (auto ns = spec.name_space(); !ns.empty()) + { + out = fmt::format_to(out, "{}{}", ns, MatchSpec::channel_namespace_spec_sep); + } + out = fmt::format_to(out, "{}", spec.name()); + + const bool is_complex_version = spec.version().expression_size() > 1; + const bool is_complex_build_string = !( + spec.build_string().is_exact() || spec.build_string().is_free() + ); + + // Any relation is complex, we'll write them all inside the attribute section. + // For package filename, we avoid writing the version and build string again as they are part + // of the url. + if (!is_complex_version && !is_complex_build_string) + { + if (!spec.build_string().is_free()) { - formatted_brackets.push_back(util::concat("sha256=", hash)); + out = fmt::format_to(out, "{}={}", spec.version(), spec.build_string()); } - if (const auto& l = license(); !l.empty()) + else if (!spec.version().is_explicitly_free()) { - formatted_brackets.push_back(util::concat("license=", l)); + out = fmt::format_to(out, "{}", spec.version()); } - if (const auto& lf = license_family(); !lf.empty()) + } + + bool bracket_written = false; + auto ensure_bracket_open_or_comma = [&]() + { + out = fmt::format_to( + out, + "{}", + bracket_written ? MatchSpec::attribute_sep : MatchSpec::prefered_list_open + ); + bracket_written = true; + }; + auto ensure_bracket_close = [&]() + { + if (bracket_written) { - formatted_brackets.push_back(util::concat("license_family=", lf)); + out = fmt::format_to(out, "{}", MatchSpec::prefered_list_close); } - if (optional()) + }; + + if (is_complex_version || is_complex_build_string) + { + if (const auto& ver = spec.version(); !ver.is_explicitly_free()) { - formatted_brackets.emplace_back("optional"); + ensure_bracket_open_or_comma(); + out = fmt::format_to(out, "version={0}{1}{0}", MatchSpec::prefered_quote, ver); } - - if (!formatted_brackets.empty()) + if (const auto& bs = spec.build_string(); !bs.is_free()) { - res << "[" << util::join(",", formatted_brackets) << "]"; + ensure_bracket_open_or_comma(); + out = fmt::format_to(out, "build={0}{1}{0}", MatchSpec::prefered_quote, bs); } - return res.str(); } - - auto MatchSpec::is_simple() const -> bool + if (const auto& num = spec.build_number(); !num.is_explicitly_free()) { - return m_version.is_explicitly_free() && m_build_string.is_free() - && m_build_number.is_explicitly_free(); + ensure_bracket_open_or_comma(); + out = fmt::format_to(out, "build_number={0}{1}{0}", MatchSpec::prefered_quote, num); } - - auto MatchSpec::extra() -> ExtraMembers& + if (const auto& tf = spec.track_features(); tf.has_value() && !tf->get().empty()) { - if (!m_extra.has_value()) - { - m_extra.emplace(); - } - return *m_extra; + ensure_bracket_open_or_comma(); + out = fmt::format_to( + out, + "track_features={0}{1}{0}", + MatchSpec::prefered_quote, + fmt::join(tf->get(), std::string_view(&MatchSpec::feature_sep.front(), 1)) + ); } - - namespace match_spec_literals + if (const auto& feats = spec.features(); !feats.empty()) { - auto operator""_ms(const char* str, std::size_t len) -> MatchSpec - { - return MatchSpec::parse({ str, len }) - .or_else([](specs::ParseError&& err) { throw std::move(err); }) - .value(); - } + ensure_bracket_open_or_comma(); + const auto& q = mamba::specs::find_needed_quote(feats); + out = fmt::format_to(out, "features={0}{1}{0}", q, feats); } + if (const auto& fn = spec.filename(); !fn.empty()) + { + ensure_bracket_open_or_comma(); + const auto& q = mamba::specs::find_needed_quote(fn); + out = fmt::format_to(out, "fn={0}{1}{0}", q, fn); + } + if (const auto& hash = spec.md5(); !hash.empty()) + { + ensure_bracket_open_or_comma(); + out = fmt::format_to(out, "md5={}", hash); + } + if (const auto& hash = spec.sha256(); !hash.empty()) + { + ensure_bracket_open_or_comma(); + out = fmt::format_to(out, "sha256={}", hash); + } + if (const auto& license = spec.license(); !license.empty()) + { + ensure_bracket_open_or_comma(); + out = fmt::format_to(out, "license={}", license); + } + if (const auto& lf = spec.license_family(); !lf.empty()) + { + ensure_bracket_open_or_comma(); + out = fmt::format_to(out, "license_family={}", lf); + } + if (spec.optional()) + { + ensure_bracket_open_or_comma(); + out = fmt::format_to(out, "optional"); + } + ensure_bracket_close(); + + return out; } diff --git a/libmamba/tests/src/specs/test_match_spec.cpp b/libmamba/tests/src/specs/test_match_spec.cpp index 3180087fb1..c78f2104e7 100644 --- a/libmamba/tests/src/specs/test_match_spec.cpp +++ b/libmamba/tests/src/specs/test_match_spec.cpp @@ -17,39 +17,44 @@ TEST_SUITE("specs::match_spec") TEST_CASE("parse") { - SUBCASE("xtensor==0.12.3") + SUBCASE("") { - auto ms = MatchSpec::parse("xtensor==0.12.3").value(); - CHECK_EQ(ms.version().str(), "==0.12.3"); - CHECK_EQ(ms.name().str(), "xtensor"); + auto ms = MatchSpec::parse("").value(); + CHECK(ms.name().is_free()); + CHECK(ms.version().is_explicitly_free()); + CHECK(ms.build_string().is_free()); + CHECK(ms.build_number().is_explicitly_free()); + CHECK_EQ(ms.str(), "*"); } - SUBCASE("") + SUBCASE("xtensor==0.12.3") { - auto ms = MatchSpec::parse("").value(); - CHECK_EQ(ms.version().str(), "=*"); - CHECK_EQ(ms.name().str(), "*"); + auto ms = MatchSpec::parse("xtensor==0.12.3").value(); + CHECK_EQ(ms.name().str(), "xtensor"); + CHECK_EQ(ms.version().str(), "==0.12.3"); + CHECK_EQ(ms.str(), "xtensor==0.12.3"); } - SUBCASE("ipykernel ") + SUBCASE("ipykernel") { auto ms = MatchSpec::parse("ipykernel").value(); - CHECK_EQ(ms.version().str(), "=*"); CHECK_EQ(ms.name().str(), "ipykernel"); + CHECK(ms.version().is_explicitly_free()); + CHECK_EQ(ms.str(), "ipykernel"); } SUBCASE("ipykernel ") { auto ms = MatchSpec::parse("ipykernel ").value(); - CHECK_EQ(ms.version().str(), "=*"); CHECK_EQ(ms.name().str(), "ipykernel"); + CHECK(ms.version().is_explicitly_free()); } SUBCASE("numpy 1.7*") { auto ms = MatchSpec::parse("numpy 1.7*").value(); - CHECK_EQ(ms.version().str(), "=1.7"); CHECK_EQ(ms.name().str(), "numpy"); + CHECK_EQ(ms.version().str(), "=1.7"); CHECK_EQ(ms.conda_build_form(), "numpy 1.7.*"); CHECK_EQ(ms.str(), "numpy=1.7"); } @@ -58,8 +63,10 @@ TEST_SUITE("specs::match_spec") { auto ms = MatchSpec::parse("conda-forge:pypi:xtensor==0.12.3").value(); CHECK_EQ(ms.name().str(), "xtensor"); + CHECK_EQ(ms.version().str(), "==0.12.3"); CHECK_EQ(ms.channel().value().str(), "conda-forge"); CHECK_EQ(ms.name_space(), "pypi"); + CHECK_EQ(ms.str(), "conda-forge:pypi:xtensor==0.12.3"); } SUBCASE("conda-forge/linux-64::xtensor==0.12.3") @@ -67,36 +74,39 @@ TEST_SUITE("specs::match_spec") auto ms = MatchSpec::parse("numpy[version='1.7|1.8']").value(); CHECK_EQ(ms.name().str(), "numpy"); CHECK_EQ(ms.version().str(), "==1.7|==1.8"); - CHECK_EQ(ms.str(), "numpy[version='==1.7|==1.8']"); + CHECK_EQ(ms.str(), R"(numpy[version="==1.7|==1.8"])"); } SUBCASE("conda-forge/linux-64::xtensor==0.12.3") { auto ms = MatchSpec::parse("conda-forge/linux-64::xtensor==0.12.3").value(); - CHECK_EQ(ms.version().str(), "==0.12.3"); CHECK_EQ(ms.name().str(), "xtensor"); + CHECK_EQ(ms.version().str(), "==0.12.3"); REQUIRE(ms.channel().has_value()); CHECK_EQ(ms.channel()->location(), "conda-forge"); - CHECK_EQ(ms.channel()->platform_filters(), PlatformSet{ "linux-64" }); - CHECK_EQ(ms.optional(), false); + CHECK_EQ(ms.platforms().value().get(), PlatformSet{ "linux-64" }); + CHECK_EQ(ms.str(), "conda-forge[linux-64]::xtensor==0.12.3"); } - SUBCASE("conda-forge::foo[build=3](target=blarg,optional)") + SUBCASE("conda-forge::foo[build=bld](target=blarg,optional)") { - auto ms = MatchSpec::parse("conda-forge::foo[build=3](target=blarg,optional)").value(); - CHECK_EQ(ms.version().str(), "=*"); + auto ms = MatchSpec::parse("conda-forge::foo[build=bld](target=blarg,optional)").value(); CHECK_EQ(ms.name().str(), "foo"); + CHECK(ms.version().is_explicitly_free()); REQUIRE(ms.channel().has_value()); CHECK_EQ(ms.channel()->location(), "conda-forge"); - CHECK_EQ(ms.build_string().str(), "3"); + CHECK_EQ(ms.build_string().str(), "bld"); CHECK_EQ(ms.optional(), true); + CHECK_EQ(ms.str(), "conda-forge::foo=*=bld[optional]"); } SUBCASE("python[build_number=3]") { auto ms = MatchSpec::parse("python[build_number=3]").value(); CHECK_EQ(ms.name().str(), "python"); + CHECK_EQ(ms.version().str(), "=*"); CHECK_EQ(ms.build_number().str(), "=3"); + CHECK_EQ(ms.str(), R"(python[build_number="=3"])"); } SUBCASE(R"(blas[track_features="mkl avx"])") @@ -104,6 +114,7 @@ TEST_SUITE("specs::match_spec") auto ms = MatchSpec::parse(R"(blas[track_features="mkl avx"])").value(); CHECK_EQ(ms.name().str(), "blas"); CHECK_EQ(ms.track_features().value().get(), MatchSpec::string_set{ "avx", "mkl" }); + CHECK_EQ(ms.str(), R"(blas[track_features="avx mkl"])"); } SUBCASE("python[build_number='<=3']") @@ -111,16 +122,18 @@ TEST_SUITE("specs::match_spec") auto ms = MatchSpec::parse("python[build_number='<=3']").value(); CHECK_EQ(ms.name().str(), "python"); CHECK_EQ(ms.build_number().str(), "<=3"); + CHECK_EQ(ms.str(), R"(python[build_number="<=3"])"); } SUBCASE("https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4-h59595ed_2.conda#7dbaa197d7ba6032caf7ae7f32c1efa0" ) { - auto ms = MatchSpec::parse( - "https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4-h59595ed_2.conda" - "#7dbaa197d7ba6032caf7ae7f32c1efa0" - ) - .value(); + constexpr auto str = std::string_view{ + "https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.4-h59595ed_2.conda" + "#7dbaa197d7ba6032caf7ae7f32c1efa0" + }; + + auto ms = MatchSpec::parse(str).value(); CHECK_EQ(ms.name().str(), "ncurses"); CHECK_EQ(ms.version().str(), "==6.4"); CHECK_EQ(ms.build_string().str(), "h59595ed_2"); @@ -130,14 +143,15 @@ TEST_SUITE("specs::match_spec") ); CHECK_EQ(ms.filename(), "ncurses-6.4-h59595ed_2.conda"); CHECK_EQ(ms.md5(), "7dbaa197d7ba6032caf7ae7f32c1efa0"); + CHECK_EQ(ms.str(), str); } SUBCASE("https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2") { - auto ms = MatchSpec::parse( - "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2" - ) - .value(); + constexpr auto str = std::string_view{ + "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2" + }; + auto ms = MatchSpec::parse(str).value(); CHECK_EQ(ms.name().str(), "_libgcc_mutex"); CHECK_EQ(ms.version().str(), "==0.1"); CHECK_EQ(ms.build_string().str(), "conda_forge"); @@ -146,14 +160,15 @@ TEST_SUITE("specs::match_spec") "https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2" ); CHECK_EQ(ms.filename(), "_libgcc_mutex-0.1-conda_forge.tar.bz2"); + CHECK_EQ(ms.str(), str); } SUBCASE("https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2") { - auto ms = MatchSpec::parse( - "https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2" - ) - .value(); + constexpr auto str = std::string_view{ + "https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2" + }; + auto ms = MatchSpec::parse(str).value(); CHECK_EQ(ms.name().str(), "libgcc-ng"); CHECK_EQ(ms.version().str(), "==11.2.0"); CHECK_EQ(ms.build_string().str(), "h1d223b6_13"); @@ -162,26 +177,28 @@ TEST_SUITE("specs::match_spec") "https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2" ); CHECK_EQ(ms.filename(), "libgcc-ng-11.2.0-h1d223b6_13.tar.bz2"); + CHECK_EQ(ms.str(), str); } SUBCASE("https://conda.anaconda.org/conda-canary/linux-64/conda-4.3.21.post699+1dab973-py36h4a561cd_0.tar.bz2" ) { - auto ms = MatchSpec::parse( - "https://conda.anaconda.org/conda-canary/linux-64/conda-4.3.21.post699+1dab973-py36h4a561cd_0.tar.bz2" - ) - .value(); + constexpr auto str = std::string_view{ + "https://conda.anaconda.org/conda-canary/linux-64/conda-4.3.21.post699+1dab973-py36h4a561cd_0.tar.bz2" + }; + auto ms = MatchSpec::parse(str).value(); CHECK_EQ(ms.name().str(), "conda"); CHECK_EQ(ms.version().str(), "==4.3.21.0post699+1dab973"); // Note the ``.0post`` CHECK_EQ(ms.build_string().str(), "py36h4a561cd_0"); + CHECK_EQ(ms.str(), str); } SUBCASE("/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2") { - auto ms = MatchSpec::parse( - "/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2" - ) - .value(); + constexpr auto str = std::string_view{ + "/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2" + }; + auto ms = MatchSpec::parse(str).value(); CHECK_EQ(ms.name().str(), "_libgcc_mutex"); CHECK_EQ(ms.version().str(), "==0.1"); CHECK_EQ(ms.build_string().str(), "conda_forge"); @@ -190,6 +207,7 @@ TEST_SUITE("specs::match_spec") "/home/randomguy/Downloads/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2" ); CHECK_EQ(ms.filename(), "_libgcc_mutex-0.1-conda_forge.tar.bz2"); + CHECK_EQ(ms.str(), str); } SUBCASE("xtensor[url=file:///home/wolfv/Downloads/xtensor-0.21.4-hc9558a2_0.tar.bz2]") @@ -203,6 +221,7 @@ TEST_SUITE("specs::match_spec") ms.channel().value().str(), "file:///home/wolfv/Downloads/xtensor-0.21.4-hc9558a2_0.tar.bz2" ); + CHECK_EQ(ms.str(), "file:///home/wolfv/Downloads/xtensor-0.21.4-hc9558a2_0.tar.bz2"); } SUBCASE("foo=1.0=2") @@ -243,29 +262,32 @@ TEST_SUITE("specs::match_spec") ); } - SUBCASE(R"(defaults::numpy=1.8=py27_0 [name="pytorch" channel='anaconda',version=">=1.8,<2|1.9", build='3'])" + SUBCASE(R"(defaults::numpy=1.8=py27_0 [name="pytorch",channel='anaconda',version=">=1.8,<2|1.9", build='3'])" ) { auto ms = MatchSpec::parse( - R"(defaults::numpy=1.8=py27_0 [name="pytorch" channel='anaconda',version=">=1.8,<2|1.9", build='3'])" + R"(defaults::numpy=1.8=py27_0 [name="pytorch",channel='anaconda',version=">=1.8,<2|1.9", build='3'])" ) .value(); - CHECK_EQ(ms.channel().value().str(), "defaults"); + CHECK_EQ(ms.channel().value().str(), "anaconda"); CHECK_EQ(ms.name().str(), "numpy"); CHECK_EQ(ms.version().str(), "=1.8"); CHECK_EQ(ms.build_string().str(), "py27_0"); + CHECK_EQ(ms.str(), R"(anaconda::numpy=1.8=py27_0)"); } - SUBCASE(R"(defaults::numpy [ "pytorch" channel='anaconda',version=">=1.8,<2|1.9", build='3'])") + SUBCASE(R"(defaults::numpy [ name="pytorch",channel='anaconda',version=">=1.8,<2|1.9", build='3'])" + ) { auto ms = MatchSpec::parse( - R"(defaults::numpy [ "pytorch" channel='anaconda',version=">=1.8,<2|1.9", build='3'])" + R"(defaults::numpy [ name="pytorch",channel='anaconda',version=">=1.8,<2|1.9", build='3'])" ) .value(); - CHECK_EQ(ms.channel().value().str(), "defaults"); + CHECK_EQ(ms.channel().value().str(), "anaconda"); CHECK_EQ(ms.name().str(), "numpy"); CHECK_EQ(ms.version().str(), ">=1.8,(<2|==1.9)"); CHECK_EQ(ms.build_string().str(), "3"); + CHECK_EQ(ms.str(), R"ms(anaconda::numpy[version=">=1.8,(<2|==1.9)",build="3"])ms"); } SUBCASE("numpy >1.8,<2|==1.7,!=1.9,~=1.7.1 py34_0") @@ -274,6 +296,7 @@ TEST_SUITE("specs::match_spec") CHECK_EQ(ms.name().str(), "numpy"); CHECK_EQ(ms.version().str(), ">1.8,((<2|==1.7),(!=1.9,~=1.7))"); CHECK_EQ(ms.build_string().str(), "py34_0"); + CHECK_EQ(ms.str(), R"ms(numpy[version=">1.8,((<2|==1.7),(!=1.9,~=1.7))",build="py34_0"])ms"); } SUBCASE("*[md5=fewjaflknd]") @@ -281,118 +304,120 @@ TEST_SUITE("specs::match_spec") auto ms = MatchSpec::parse("*[md5=fewjaflknd]").value(); CHECK(ms.name().is_free()); CHECK_EQ(ms.md5(), "fewjaflknd"); + CHECK_EQ(ms.str(), "*[md5=fewjaflknd]"); } SUBCASE("libblas=*=*mkl") { auto ms = MatchSpec::parse("libblas=*=*mkl").value(); - CHECK_EQ(ms.conda_build_form(), "libblas * *mkl"); CHECK_EQ(ms.name().str(), "libblas"); - CHECK_EQ(ms.version().str(), "=*"); + CHECK(ms.version().is_explicitly_free()); CHECK_EQ(ms.build_string().str(), "*mkl"); - // CHECK_EQ(ms.str(), "foo==1.0=2"); + CHECK_EQ(ms.str(), R"(libblas[build="*mkl"])"); + CHECK_EQ(ms.conda_build_form(), "libblas * *mkl"); } SUBCASE("libblas=0.15*") { // '*' is part of the version, not the glob auto ms = MatchSpec::parse("libblas=0.15*").value(); - CHECK_EQ(ms.conda_build_form(), "libblas 0.15*.*"); CHECK_EQ(ms.name().str(), "libblas"); CHECK_EQ(ms.version().str(), "=0.15*"); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "libblas=0.15*"); + CHECK_EQ(ms.conda_build_form(), "libblas 0.15*.*"); } SUBCASE("xtensor =0.15*") { // '*' is part of the version, not the glob auto ms = MatchSpec::parse("xtensor =0.15*").value(); - CHECK_EQ(ms.conda_build_form(), "xtensor 0.15*.*"); - CHECK_EQ(ms.str(), "xtensor=0.15*"); CHECK_EQ(ms.name().str(), "xtensor"); CHECK_EQ(ms.version().str(), "=0.15*"); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "xtensor=0.15*"); + CHECK_EQ(ms.conda_build_form(), "xtensor 0.15*.*"); } SUBCASE("numpy=1.20") { auto ms = MatchSpec::parse("numpy=1.20").value(); - CHECK_EQ(ms.str(), "numpy=1.20"); CHECK_EQ(ms.name().str(), "numpy"); CHECK_EQ(ms.version().str(), "=1.20"); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "numpy=1.20"); } SUBCASE("conda-forge::tzdata") { auto ms = MatchSpec::parse("conda-forge::tzdata").value(); - CHECK_EQ(ms.str(), "conda-forge::tzdata"); CHECK_EQ(ms.channel().value().str(), "conda-forge"); CHECK_EQ(ms.name().str(), "tzdata"); CHECK(ms.version().is_explicitly_free()); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "conda-forge::tzdata"); } SUBCASE("conda-forge/noarch::tzdata") { auto ms = MatchSpec::parse("conda-forge/noarch::tzdata").value(); - CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata"); CHECK_EQ(ms.channel().value().str(), "conda-forge[noarch]"); CHECK_EQ(ms.name().str(), "tzdata"); CHECK(ms.version().is_explicitly_free()); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata"); } SUBCASE("conda-forge[noarch]::tzdata") { auto ms = MatchSpec::parse("conda-forge/noarch::tzdata").value(); - CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata"); CHECK_EQ(ms.channel().value().str(), "conda-forge[noarch]"); CHECK_EQ(ms.name().str(), "tzdata"); CHECK(ms.version().is_explicitly_free()); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata"); } SUBCASE("pkgs/main::tzdata") { auto ms = MatchSpec::parse("pkgs/main::tzdata").value(); - CHECK_EQ(ms.str(), "pkgs/main::tzdata"); CHECK_EQ(ms.channel().value().str(), "pkgs/main"); CHECK_EQ(ms.name().str(), "tzdata"); CHECK(ms.version().is_explicitly_free()); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "pkgs/main::tzdata"); } SUBCASE("pkgs/main/noarch::tzdata") { auto ms = MatchSpec::parse("pkgs/main/noarch::tzdata").value(); - CHECK_EQ(ms.str(), "pkgs/main[noarch]::tzdata"); CHECK_EQ(ms.channel().value().str(), "pkgs/main[noarch]"); CHECK_EQ(ms.name().str(), "tzdata"); CHECK(ms.version().is_explicitly_free()); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "pkgs/main[noarch]::tzdata"); } SUBCASE("conda-forge[noarch]::tzdata[subdir=linux64]") { auto ms = MatchSpec::parse("conda-forge[noarch]::tzdata[subdir=linux64]").value(); - CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata"); CHECK_EQ(ms.channel().value().str(), "conda-forge[noarch]"); CHECK_EQ(ms.platforms().value().get(), MatchSpec::platform_set{ "noarch" }); CHECK_EQ(ms.name().str(), "tzdata"); CHECK(ms.version().is_explicitly_free()); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "conda-forge[noarch]::tzdata"); } SUBCASE("conda-forge::tzdata[subdir=mamba-37]") { auto ms = MatchSpec::parse("conda-forge::tzdata[subdir=mamba-37]").value(); - CHECK_EQ(ms.str(), "conda-forge[mamba-37]::tzdata"); CHECK_EQ(ms.channel().value().str(), "conda-forge[mamba-37]"); CHECK_EQ(ms.platforms().value().get(), MatchSpec::platform_set{ "mamba-37" }); CHECK_EQ(ms.name().str(), "tzdata"); CHECK(ms.version().is_explicitly_free()); CHECK(ms.build_string().is_free()); + CHECK_EQ(ms.str(), "conda-forge[mamba-37]::tzdata"); } SUBCASE("conda-canary/linux-64::conda==4.3.21.post699+1dab973=py36h4a561cd_0") @@ -406,6 +431,7 @@ TEST_SUITE("specs::match_spec") CHECK_EQ(ms.name().str(), "conda"); CHECK_EQ(ms.version().str(), "==4.3.21.0post699+1dab973"); // Not ``.0post`` diff CHECK_EQ(ms.build_string().str(), "py36h4a561cd_0"); + CHECK_EQ(ms.str(), "conda-canary[linux-64]::conda==4.3.21.0post699+1dab973=py36h4a561cd_0"); } } diff --git a/libmambapy/tests/test_specs.py b/libmambapy/tests/test_specs.py index 9baeb838a7..4dbdd0ddec 100644 --- a/libmambapy/tests/test_specs.py +++ b/libmambapy/tests/test_specs.py @@ -860,8 +860,9 @@ def test_MatchSpec(): # str assert str(ms) == ( - "conda-forge[plat]::python=3.7" - """[build='*pypy',track_features="ft",md5=m,sha256=s,license=l,license_family=lf,optional]""" + "conda-forge[plat]:ns:python" + """[version="=3.7",build="*pypy",track_features="ft",md5=m,sha256=s,""" + """license=l,license_family=lf,optional]""" ) # Copy