Use expected for specs parsing (#3201)

* Expected in Version::parse

* Use boolean check in expr trees

* Expected in VersionSpec::parse

* Add assert caster safeguard

* Expected in BuildNumberSpec::parse

* Minor improvement in GlobSpec

* Expected in UnresolvedChannel::parse

* Expected in URL::parse
This commit is contained in:
Antoine Prouvost 2024-02-27 09:35:27 +01:00 committed by GitHub
parent 86b2b8c121
commit 88748be956
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 856 additions and 686 deletions

View File

@ -299,6 +299,7 @@ set(
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/build_number_spec.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/channel.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/conda_url.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/error.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/glob_spec.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/match_spec.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/package_info.hpp

View File

@ -14,6 +14,8 @@
#include <fmt/format.h>
#include "mamba/specs/error.hpp"
namespace mamba::specs
{
/**
@ -97,7 +99,7 @@ namespace mamba::specs
static constexpr std::string_view less_str = "<";
static constexpr std::string_view less_equal_str = "<=";
[[nodiscard]] static auto parse(std::string_view str) -> BuildNumberSpec;
[[nodiscard]] static auto parse(std::string_view str) -> expected_parse_t<BuildNumberSpec>;
/** Construct BuildNumberSpec that match all versions. */
BuildNumberSpec();

View File

@ -0,0 +1,39 @@
// Copyright (c) 2024, QuantStack and Mamba Contributors
//
// Distributed under the terms of the BSD 3-Clause License.
//
// The full license is in the file LICENSE, distributed with this software.
#ifndef MAMBA_SPECS_ERROR_HPP
#define MAMBA_SPECS_ERROR_HPP
#include <stdexcept>
#include <utility>
#include <tl/expected.hpp>
namespace mamba::specs
{
struct ParseError final : std::invalid_argument
{
using std::invalid_argument::invalid_argument;
};
template <typename T>
using expected_parse_t = tl::expected<T, ParseError>;
template <typename T>
[[nodiscard]] auto make_unexpected_parse(T&& err) -> tl::unexpected<ParseError>;
/********************
* Implementation *
********************/
template <typename T>
auto make_unexpected_parse(T&& err) -> tl::unexpected<ParseError>
{
return tl::make_unexpected(ParseError(std::forward<T>(err)));
}
}
#endif

View File

@ -24,6 +24,7 @@ namespace mamba::specs
public:
inline static constexpr std::string_view free_pattern = "*";
inline static constexpr char glob_pattern = '*';
GlobSpec() = default;
explicit GlobSpec(std::string pattern);

View File

@ -14,6 +14,7 @@
#include <fmt/core.h>
#include <fmt/format.h>
#include "mamba/specs/error.hpp"
#include "mamba/util/flat_set.hpp"
namespace mamba::specs
@ -88,7 +89,7 @@ namespace mamba::specs
using dynamic_platform_set = util::flat_set<std::string>;
[[nodiscard]] static auto parse(std::string_view str) -> UnresolvedChannel;
[[nodiscard]] static auto parse(std::string_view str) -> expected_parse_t<UnresolvedChannel>;
UnresolvedChannel() = default;
UnresolvedChannel(std::string location, dynamic_platform_set filters, Type type);

View File

@ -14,6 +14,8 @@
#include <fmt/format.h>
#include "mamba/specs/error.hpp"
namespace mamba::specs
{
@ -101,7 +103,7 @@ namespace mamba::specs
static constexpr char part_delim_alt = '-';
static constexpr char part_delim_special = '_';
static auto parse(std::string_view str) -> Version;
static auto parse(std::string_view str) -> expected_parse_t<Version>;
/** Construct version ``0.0``. */
Version() noexcept = default;

View File

@ -14,6 +14,7 @@
#include <fmt/format.h>
#include "mamba/specs/error.hpp"
#include "mamba/specs/version.hpp"
#include "mamba/util/flat_bool_expr_tree.hpp"
@ -149,7 +150,7 @@ namespace mamba::specs
static constexpr std::string_view glob_suffix_str = ".*";
static constexpr char glob_suffix_token = '*';
[[nodiscard]] static auto parse(std::string_view str) -> VersionSpec;
[[nodiscard]] static auto parse(std::string_view str) -> expected_parse_t<VersionSpec>;
/** Construct VersionSpec that match all versions. */
VersionSpec() = default;

View File

@ -9,17 +9,12 @@
#include <cassert>
#include <functional>
#include <stdexcept>
#include <utility>
#include <variant>
#include <vector>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include "mamba/util/flat_binary_tree.hpp"
#include "mamba/util/functional.hpp"
#include "mamba/util/type_traits.hpp"
namespace mamba::util
{
@ -40,11 +35,11 @@ namespace mamba::util
using variable_type = Variable;
using tree_type = flat_binary_tree<operator_type, variable_type>;
void push_variable(const variable_type& var);
void push_variable(variable_type&& var);
void push_operator(const operator_type& op);
void push_operator(operator_type&& op);
void finalize();
[[nodiscard]] auto push_variable(const variable_type& var) -> bool;
[[nodiscard]] auto push_variable(variable_type&& var) -> bool;
[[nodiscard]] auto push_operator(const operator_type& op) -> bool;
[[nodiscard]] auto push_operator(operator_type&& op) -> bool;
[[nodiscard]] auto finalize() -> bool;
[[nodiscard]] auto tree() const& -> const tree_type&;
[[nodiscard]] auto tree() && -> tree_type&&;
@ -63,9 +58,9 @@ namespace mamba::util
auto orphans_pop() -> idx_type;
template <typename V>
void push_variable_impl(V&& var);
[[nodiscard]] auto push_variable_impl(V&& var) -> bool;
template <typename O>
void push_operator_impl(O&& op);
[[nodiscard]] auto push_operator_impl(O&& op) -> bool;
};
/**
@ -89,13 +84,13 @@ namespace mamba::util
InfixParser(const operator_precedence_type& cmp);
InfixParser(operator_precedence_type&& cmp = {});
void push_variable(const variable_type& var);
void push_variable(variable_type&& var);
void push_operator(const operator_type& op);
void push_operator(operator_type&& op);
void push_left_parenthesis();
void push_right_parenthesis();
void finalize();
[[nodiscard]] auto push_variable(const variable_type& var) -> bool;
[[nodiscard]] auto push_variable(variable_type&& var) -> bool;
[[nodiscard]] auto push_operator(const operator_type& op) -> bool;
[[nodiscard]] auto push_operator(operator_type&& op) -> bool;
[[nodiscard]] auto push_left_parenthesis() -> bool;
[[nodiscard]] auto push_right_parenthesis() -> bool;
[[nodiscard]] auto finalize() -> bool;
[[nodiscard]] auto tree() const& -> const tree_type&;
[[nodiscard]] auto tree() && -> tree_type&&;
@ -120,15 +115,15 @@ namespace mamba::util
template <typename T>
void stack_push(T&& elem);
auto stack_pop() -> operator_or_parenthesis_type;
auto stack_empty() const -> bool;
[[nodiscard]] auto stack_empty() const -> bool;
auto stack_top() const -> const operator_or_parenthesis_type&;
auto stack_top_is_parenthesis() const -> bool;
[[nodiscard]] auto stack_top_is_parenthesis() const -> bool;
auto stack_top_is_op_with_greater_precedence_than(const operator_type&) const -> bool;
template <typename V>
void push_variable_impl(V&& var);
[[nodiscard]] auto push_variable_impl(V&& var) -> bool;
template <typename O>
void push_operator_impl(O&& op);
[[nodiscard]] auto push_operator_impl(O&& op) -> bool;
};
enum struct BoolOperator
@ -245,56 +240,58 @@ namespace mamba::util
template <typename V, typename O>
template <typename Var>
void PostfixParser<V, O>::push_variable_impl(Var&& var)
auto PostfixParser<V, O>::push_variable_impl(Var&& var) -> bool
{
orphans_push(m_tree.add_leaf(std::forward<Var>(var)));
return true; // Always valid
}
template <typename V, typename O>
void PostfixParser<V, O>::push_variable(const variable_type& var)
auto PostfixParser<V, O>::push_variable(const variable_type& var) -> bool
{
return push_variable_impl(var);
}
template <typename V, typename O>
void PostfixParser<V, O>::push_variable(variable_type&& var)
auto PostfixParser<V, O>::push_variable(variable_type&& var) -> bool
{
return push_variable_impl(std::move(var));
}
template <typename V, typename O>
template <typename Op>
void PostfixParser<V, O>::push_operator_impl(Op&& op)
auto PostfixParser<V, O>::push_operator_impl(Op&& op) -> bool
{
if (m_orphans.size() < 2)
{
throw std::invalid_argument("Invalid expression");
return false;
}
const auto right = orphans_pop();
const auto left = orphans_pop();
orphans_push(m_tree.add_branch(std::forward<Op>(op), left, right));
return true;
}
template <typename V, typename O>
void PostfixParser<V, O>::push_operator(const operator_type& op)
auto PostfixParser<V, O>::push_operator(const operator_type& op) -> bool
{
return push_operator_impl(op);
}
template <typename V, typename O>
void PostfixParser<V, O>::push_operator(operator_type&& op)
auto PostfixParser<V, O>::push_operator(operator_type&& op) -> bool
{
return push_operator_impl(std::move(op));
}
template <typename V, typename O>
void PostfixParser<V, O>::finalize()
auto PostfixParser<V, O>::finalize() -> bool
{
if (((m_orphans.size() == 1) && !m_tree.empty()) || (m_orphans.empty() && m_tree.empty()))
{
return;
return true;
}
throw std::invalid_argument("Incomplete expression");
return false; // Incomplete expression
}
template <typename V, typename O>
@ -377,138 +374,127 @@ namespace mamba::util
template <typename V, typename O, typename C>
template <typename Var>
void InfixParser<V, O, C>::push_variable_impl(Var&& var)
auto InfixParser<V, O, C>::push_variable_impl(Var&& var) -> bool
{
// Input check
if (m_expects_op)
{
std::string msg = {};
if constexpr (fmt::is_formattable<Var>::value)
{
msg = fmt::format("Unexpected variable: {}", var);
}
else if constexpr (is_ostreamable_v<Var>)
{
msg = fmt::format("Unexpected variable: {}", fmt::streamed(var));
}
else
{
msg = "Unexpected variable";
}
throw std::invalid_argument(std::move(msg));
return false; // Unexpected variable
}
m_expects_op = true;
// Parsing
m_postfix_parser.push_variable(std::forward<Var>(var));
return m_postfix_parser.push_variable(std::forward<Var>(var));
}
template <typename V, typename O, typename C>
void InfixParser<V, O, C>::push_variable(const variable_type& var)
auto InfixParser<V, O, C>::push_variable(const variable_type& var) -> bool
{
return push_variable_impl(var);
}
template <typename V, typename O, typename C>
void InfixParser<V, O, C>::push_variable(variable_type&& var)
auto InfixParser<V, O, C>::push_variable(variable_type&& var) -> bool
{
return push_variable_impl(std::move(var));
}
template <typename V, typename O, typename C>
template <typename Op>
void InfixParser<V, O, C>::push_operator_impl(Op&& op)
auto InfixParser<V, O, C>::push_operator_impl(Op&& op) -> bool
{
// Input check
if (!m_expects_op)
{
std::string msg = {};
if constexpr (fmt::is_formattable<Op>::value)
{
msg = fmt::format("Unexpected operator: {}", op);
}
else if constexpr (is_ostreamable_v<Op>)
{
msg = fmt::format("Unexpected operator: {}", fmt::streamed(op));
}
else
{
msg = "Unexpected operator";
}
throw std::invalid_argument(std::move(msg));
return false;
}
m_expects_op = false;
// Parsing
while (stack_top_is_op_with_greater_precedence_than(op))
{
m_postfix_parser.push_operator(std::get<operator_type>(stack_pop()));
bool pushed = m_postfix_parser.push_operator(std::get<operator_type>(stack_pop()));
if (!pushed)
{
return false;
}
}
stack_push(std::forward<Op>(op));
return true;
}
template <typename V, typename O, typename C>
void InfixParser<V, O, C>::push_operator(const operator_type& op)
auto InfixParser<V, O, C>::push_operator(const operator_type& op) -> bool
{
return push_operator_impl(op);
}
template <typename V, typename O, typename C>
void InfixParser<V, O, C>::push_operator(operator_type&& op)
auto InfixParser<V, O, C>::push_operator(operator_type&& op) -> bool
{
return push_operator_impl(std::move(op));
}
template <typename V, typename O, typename C>
void InfixParser<V, O, C>::push_left_parenthesis()
auto InfixParser<V, O, C>::push_left_parenthesis() -> bool
{
// Input check
if (m_expects_op)
{
throw std::invalid_argument("Unexpected left parenthesis");
return false; // Unexpected left parenthesis
}
++m_parenthesis_level;
// Parsing
stack_push(LeftParenthesis{});
return true;
}
template <typename V, typename O, typename C>
void InfixParser<V, O, C>::push_right_parenthesis()
auto InfixParser<V, O, C>::push_right_parenthesis() -> bool
{
// Input check
if (!m_expects_op || (m_parenthesis_level == 0))
{
throw std::invalid_argument("Unexpected right parenthesis");
return false; // Unexpected right parenthesis
}
--m_parenthesis_level;
// Parsing
while (!stack_top_is_parenthesis())
{
assert(!stack_empty());
m_postfix_parser.push_operator(std::get<operator_type>(stack_pop()));
bool pushed = m_postfix_parser.push_operator(std::get<operator_type>(stack_pop()));
if (!pushed)
{
return false;
}
}
assert(stack_top_is_parenthesis());
stack_pop();
return true;
}
template <typename V, typename O, typename C>
void InfixParser<V, O, C>::finalize()
auto InfixParser<V, O, C>::finalize() -> bool
{
// Empty expression case
if (m_postfix_parser.tree().empty() && stack_empty())
{
return;
return true;
}
// Input check
if (!m_expects_op || (m_parenthesis_level != 0))
{
throw std::invalid_argument("Invalid expression");
return false; // Invalid expression
}
// Parsing
while (!stack_empty())
{
assert(!stack_top_is_parenthesis());
m_postfix_parser.push_operator(std::get<operator_type>(stack_pop()));
bool pushed = m_postfix_parser.push_operator(std::get<operator_type>(stack_pop()));
if (!pushed)
{
return false;
}
}
m_postfix_parser.finalize();
return m_postfix_parser.finalize();
}
template <typename V, typename O, typename C>

View File

@ -12,6 +12,8 @@
#include <string>
#include <string_view>
#include <tl/expected.hpp>
namespace mamba::util
{
namespace detail
@ -68,6 +70,11 @@ namespace mamba::util
using Encode = detail::Encode;
using Decode = detail::Decode;
struct ParseError
{
std::string what;
};
inline static constexpr std::string_view https = "https";
inline static constexpr std::string_view localhost = "localhost";
@ -84,7 +91,7 @@ namespace mamba::util
* @see Encode
* @see mamba::util::url_encode
*/
[[nodiscard]] static auto parse(std::string_view url) -> URL;
[[nodiscard]] static auto parse(std::string_view url) -> tl::expected<URL, ParseError>;
/** Create a local URL. */
URL() = default;

View File

@ -36,7 +36,9 @@ namespace mamba
template <typename Param>
auto make_unique_chan(std::string_view loc, const Param& params) -> specs::Channel
{
auto uc = specs::UnresolvedChannel::parse(loc);
auto uc = specs::UnresolvedChannel::parse(loc)
.or_else([](specs::ParseError&& error) { throw std::move(error); })
.value();
auto channels = specs::Channel::resolve(std::move(uc), params);
assert(channels.size() == 1);
return std::move(channels.front());
@ -178,7 +180,9 @@ namespace mamba
out.reserve(ctx.repodata_has_zst.size());
for (const auto& loc : ctx.repodata_has_zst)
{
auto uc = specs::UnresolvedChannel::parse(loc);
auto uc = specs::UnresolvedChannel::parse(loc)
.or_else([](specs::ParseError&& error) { throw std::move(error); })
.value();
auto channels = specs::Channel::resolve(std::move(uc), params);
for (auto& chan : channels)
{
@ -239,7 +243,12 @@ namespace mamba
auto [it, inserted] = m_channel_cache.emplace(
name,
Channel::resolve(specs::UnresolvedChannel::parse(name), params())
Channel::resolve(
specs::UnresolvedChannel::parse(name)
.or_else([](specs::ParseError&& error) { throw std::move(error); })
.value(),
params()
)
);
assert(inserted);
return it->second;

View File

@ -66,7 +66,8 @@ namespace mamba
{
return std::tuple<decltype(pkg.name) const&, specs::Version>(
pkg.name,
specs::Version::parse(pkg.version)
// Failed parsing last
specs::Version::parse(pkg.version).value_or(specs::Version())
);
};
return attrs(*lhs) < attrs(*rhs);

View File

@ -67,7 +67,10 @@ namespace mamba
out.set_name(specs::MatchSpec::NameSpec(pkg.name));
if (!pkg.version.empty())
{
out.set_version(specs::VersionSpec::parse(fmt::format("=={}", pkg.version)));
out.set_version(specs::VersionSpec::parse(fmt::format("=={}", pkg.version))
.or_else([](specs::ParseError&& error)
{ throw std::move(error); })
.value());
}
if (!pkg.build_string.empty())
{

View File

@ -1568,7 +1568,7 @@ namespace mamba
return std::nullopt;
}
const auto url_parsed = util::URL::parse(url);
const auto url_parsed = util::URL::parse(url).value();
auto scheme = url_parsed.scheme();
auto host = url_parsed.host();
std::vector<std::string> options;

View File

@ -323,7 +323,7 @@ namespace mamba::download
p_handle->add_header(user_agent);
// get url host
const auto url_handler = util::URL::parse(p_request->url);
const auto url_handler = util::URL::parse(p_request->url).value();
auto host = url_handler.host();
const auto port = url_handler.port();
if (port.size())

View File

@ -1059,10 +1059,10 @@ namespace mamba::solver::libsolv
{
if (auto newer = find_new_python_in_solution(solution))
{
return !python_binary_compatible(
specs::Version::parse(installed->version()),
specs::Version::parse(newer->get().version)
);
auto installed_ver = specs::Version::parse(installed->version());
auto newer_ver = specs::Version::parse(newer->get().version);
return !installed_ver.has_value() || !newer_ver.has_value()
|| !python_binary_compatible(installed_ver.value(), newer_ver.value());
}
}
return false;
@ -1182,8 +1182,31 @@ namespace mamba::solver::libsolv
}
auto ms_modified = ms;
ms_modified.set_channel(specs::UnresolvedChannel::parse(solvable->channel()));
ms_modified.set_version(specs::VersionSpec::parse(solvable->version()));
auto unresolved_chan = specs::UnresolvedChannel::parse(solvable->channel());
if (unresolved_chan.has_value())
{
ms_modified.set_channel(std::move(unresolved_chan).value());
}
else
{
return make_unexpected(
std::move(unresolved_chan).error().what(),
mamba_error_code::invalid_spec
);
}
auto version_spec = specs::VersionSpec::parse(solvable->version());
if (version_spec.has_value())
{
ms_modified.set_version(std::move(version_spec).value());
}
else
{
return make_unexpected(
std::move(version_spec).error().what(),
mamba_error_code::invalid_spec
);
}
ms_modified.set_build_string(specs::GlobSpec(std::string(solvable->build_string())));
LOG_INFO << "Reinstall " << ms_modified.conda_build_form() << " from channel "

View File

@ -160,7 +160,7 @@ fmt::formatter<mamba::specs::BuildNumberPredicate>::format(
namespace mamba::specs
{
auto BuildNumberSpec::parse(std::string_view str) -> BuildNumberSpec
auto BuildNumberSpec::parse(std::string_view str) -> expected_parse_t<BuildNumberSpec>
{
str = util::strip(str);
if (std::find(all_free_strs.cbegin(), all_free_strs.cend(), str) != all_free_strs.cend())
@ -179,7 +179,7 @@ namespace mamba::specs
);
if ((ec != std::errc()) || (ptr != (num_str.data() + num_str.size())))
{
throw std::invalid_argument(
return make_unexpected_parse(
fmt::format(R"(Expected number in build number spec "{}")", str)
);
}
@ -213,7 +213,7 @@ namespace mamba::specs
return BuildNumberSpec(BuildNumberPredicate::make_less_equal(build_number));
}
throw std::invalid_argument(fmt::format(R"(Invalid build number operator in"{}")", str));
return make_unexpected_parse(fmt::format(R"(Invalid build number operator in"{}")", str));
}
BuildNumberSpec::BuildNumberSpec()
@ -245,7 +245,9 @@ namespace mamba::specs
{
auto operator""_bs(const char* str, std::size_t len) -> BuildNumberSpec
{
return BuildNumberSpec::parse({ str, len });
return BuildNumberSpec::parse({ str, len })
.or_else([](ParseError&& err) { throw std::move(err); })
.value();
}
}
}

View File

@ -13,6 +13,7 @@
#include "mamba/specs/archive.hpp"
#include "mamba/specs/conda_url.hpp"
#include "mamba/specs/error.hpp"
#include "mamba/util/encoding.hpp"
#include "mamba/util/string.hpp"
@ -120,7 +121,14 @@ namespace mamba::specs
auto CondaURL::parse(std::string_view url) -> CondaURL
{
return CondaURL(URL::parse(url));
return CondaURL(
URL::parse(url)
.or_else(
[&](const auto&)
{ throw specs::ParseError(fmt::format(R"(Failed to parse URL "{}")", url)); }
)
.value()
);
}
void CondaURL::set_path(std::string_view path, Encode::yes_type)

View File

@ -25,7 +25,7 @@ namespace mamba::specs
auto GlobSpec::contains(std::string_view str) const -> bool
{
// is_free is not required but returns faster in the default case.
return is_free() || util::glob_match(m_pattern, str);
return is_free() || util::glob_match(m_pattern, str, glob_pattern);
}
auto GlobSpec::is_free() const -> bool
@ -35,7 +35,7 @@ namespace mamba::specs
auto GlobSpec::is_exact() const -> bool
{
return !util::contains(m_pattern, '*');
return !util::contains(m_pattern, glob_pattern);
}
auto GlobSpec::str() const -> const std::string&

View File

@ -29,7 +29,9 @@ namespace mamba::specs
};
auto out = MatchSpec();
out.m_channel = UnresolvedChannel::parse(spec);
out.m_channel = UnresolvedChannel::parse(spec)
.or_else([](specs::ParseError&& error) { throw std::move(error); })
.value();
auto [_, pkg] = util::rsplit_once(out.m_channel->location(), '/');
out.m_filename = std::string(pkg);
out.m_url = util::path_or_url_to_url(spec);
@ -44,7 +46,9 @@ namespace mamba::specs
// Version
std::tie(head, tail) = util::rsplit_once(head.value(), '-');
out.m_version = VersionSpec::parse(tail);
out.m_version = VersionSpec::parse(tail)
.or_else([](ParseError&& error) { throw std::move(error); })
.value();
if (!head.has_value())
{
fail_parse();
@ -65,7 +69,9 @@ namespace mamba::specs
if (pos == s.npos || pos == 0)
{
return {
VersionSpec::parse(s),
VersionSpec::parse(s)
.or_else([](ParseError&& error) { throw std::move(error); })
.value(),
MatchSpec::BuildStringSpec(),
};
}
@ -77,7 +83,9 @@ namespace mamba::specs
if (d == '=' || d == '!' || d == '|' || d == ',' || d == '<' || d == '>' || d == '~')
{
return {
VersionSpec::parse(s),
VersionSpec::parse(s)
.or_else([](ParseError&& error) { throw std::move(error); })
.value(),
MatchSpec::BuildStringSpec(),
};
}
@ -85,7 +93,9 @@ namespace mamba::specs
// c is either ' ' or pm1 is none of the forbidden chars
return {
VersionSpec::parse(s.substr(0, pos)),
VersionSpec::parse(s.substr(0, pos))
.or_else([](ParseError&& error) { throw std::move(error); })
.value(),
MatchSpec::BuildStringSpec(std::string(s.substr(pos + 1))),
};
}
@ -170,7 +180,9 @@ namespace mamba::specs
std::string channel_str;
if (m5_len == 3)
{
out.m_channel = UnresolvedChannel::parse(m5[0]);
out.m_channel = UnresolvedChannel::parse(m5[0])
.or_else([](specs::ParseError&& error) { throw std::move(error); })
.value();
out.m_name_space = m5[1];
spec_str = m5[2];
}
@ -245,7 +257,11 @@ namespace mamba::specs
if (const auto& val = at_or(extra, "build_number", ""); !val.empty())
{
out.set_build_number(BuildNumberSpec::parse(val));
out.set_build_number(BuildNumberSpec::parse(val)
.or_else([](ParseError&& error) { throw std::move(error); })
.value()
);
}
if (const auto& val = at_or(extra, "build", ""); !val.empty())
{
@ -253,11 +269,16 @@ namespace mamba::specs
}
if (const auto& val = at_or(extra, "version", ""); !val.empty())
{
out.set_version(VersionSpec::parse(val));
out.set_version(
VersionSpec::parse(val).or_else([](ParseError&& error) { throw std::move(error); }
).value()
);
}
if (const auto& val = at_or(extra, "channel", ""); !val.empty())
{
out.set_channel(UnresolvedChannel::parse(val));
out.set_channel(UnresolvedChannel::parse(val)
.or_else([](ParseError&& error) { throw std::move(error); })
.value());
}
if (const auto& val = at_or(extra, "subdir", ""); !val.empty())
{

View File

@ -42,7 +42,9 @@ namespace mamba::specs
using mamba::util::deserialize_maybe_missing;
p.name = j.at("name");
p.version = Version::parse(j.at("version").template get<std::string_view>());
p.version = Version::parse(j.at("version").template get<std::string_view>())
.or_else([](ParseError&& error) { throw std::move(error); })
.value();
p.build_string = j.at("build");
p.build_number = j.at("build_number");
deserialize_maybe_missing(j, "subdir", p.subdir);

View File

@ -68,7 +68,7 @@ namespace mamba::specs
}
auto split_location_platform(std::string_view str)
-> std::pair<std::string, dynamic_platform_set>
-> expected_parse_t<std::pair<std::string, dynamic_platform_set>>
{
if (util::ends_with(str, ']'))
{
@ -76,10 +76,14 @@ namespace mamba::specs
const auto start_pos = str.find_last_of('[');
if ((start_pos != std::string_view::npos) && (start_pos != 0))
{
return {
return { {
std::string(util::rstrip(str.substr(0, start_pos))),
parse_platform_list(str.substr(start_pos + 1, str.size() - start_pos - 2)),
};
} };
}
else
{
return make_unexpected_parse(R"(Unexpected closing backet "]")");
}
}
@ -92,16 +96,16 @@ namespace mamba::specs
if (!plat.empty())
{
rest = util::rstrip(rest, '/');
return {
return { {
std::move(rest),
{ std::move(plat) },
};
} };
}
}
// For single archive channel specs, we don't need to compute platform filters
// since they are not needed to compute URLs.
return { std::string(util::rstrip(str)), {} };
return { { std::string(util::rstrip(str)), {} } };
}
auto parse_path(std::string_view str) -> std::string
@ -122,15 +126,27 @@ namespace mamba::specs
}
}
auto UnresolvedChannel::parse(std::string_view str) -> UnresolvedChannel
auto UnresolvedChannel::parse(std::string_view str) -> expected_parse_t<UnresolvedChannel>
{
str = util::strip(str);
if (is_unknown_channel(str))
{
return { std::string(unknown_channel), {}, Type::Unknown };
return { { std::string(unknown_channel), {}, Type::Unknown } };
}
auto [location, filters] = split_location_platform(str);
auto location = std::string();
auto filters = dynamic_platform_set();
auto split_outcome = split_location_platform(str);
if (split_outcome)
{
std::tie(location, filters) = std::move(split_outcome).value();
}
else
{
return make_unexpected_parse(
fmt::format(R"(Error parsing channel "{}": {})", str, split_outcome.error().what())
);
}
const std::string_view scheme = util::url_get_scheme(location);
Type type = {};
@ -152,7 +168,7 @@ namespace mamba::specs
type = Type::Name;
}
return { std::move(location), std::move(filters), type };
return { { std::move(location), std::move(filters), type } };
}
UnresolvedChannel::UnresolvedChannel(std::string location, dynamic_platform_set filters, Type type)

View File

@ -534,17 +534,18 @@ namespace mamba::specs
}
template <typename Int>
auto parse_leading_epoch(std::string_view str) -> std::pair<Int, std::string_view>
auto parse_leading_epoch(std::string_view str)
-> expected_parse_t<std::pair<Int, std::string_view>>
{
const auto delim_pos = str.find(Version::epoch_delim);
// No epoch is specified
if (delim_pos == std::string_view::npos) // TODO(C++20) [[likely]]
{
return { Int(0), str };
return { { Int(0), str } };
}
if (delim_pos == 0)
{
throw std::invalid_argument(
return make_unexpected_parse(
fmt::format("Empty epoch delimited by '{}'.", Version::epoch_delim)
);
}
@ -554,12 +555,12 @@ namespace mamba::specs
// Epoch is not a number (or empty)
if (!maybe_int.has_value())
{
throw std::invalid_argument(
return make_unexpected_parse(
fmt::format("Epoch should be a number, got '{}'.", epoch_str)
);
}
// Found an epoch
return { maybe_int.value(), str.substr(delim_pos + 1) };
return { { maybe_int.value(), str.substr(delim_pos + 1) } };
}
template <typename Int>
@ -617,7 +618,7 @@ namespace mamba::specs
return atoms;
}
void check_common_version(std::string_view str)
auto check_common_version(std::string_view str) -> expected_parse_t<void>
{
// `_` and `-` delimiter cannot be used together.
// Special meaning for `_` at the end of the string.
@ -625,7 +626,7 @@ namespace mamba::specs
&& (str.find(Version::part_delim_special) < str.size() - 1)) // TODO(C++20)
// [[unlikely]]
{
throw std::invalid_argument(fmt::format(
return make_unexpected_parse(fmt::format(
"Cannot use both '{}' and '{}' delimiters in {}'.",
Version::part_delim_alt,
Version::part_delim_special,
@ -643,16 +644,20 @@ namespace mamba::specs
};
if (std::find_if_not(str.cbegin(), str.cend(), allowed_char) != str.cend())
{
throw std::invalid_argument(
return make_unexpected_parse(
fmt::format("Version contains invalid characters in {}.", str)
);
}
return {};
}
auto parse_common_version(std::string_view str) -> CommonVersion
auto parse_common_version(std::string_view str) -> expected_parse_t<CommonVersion>
{
assert(!str.empty());
check_common_version(str);
if (auto outcome = check_common_version(str); !outcome)
{
return make_unexpected_parse(outcome.error());
}
static constexpr auto delims_buf = std::array{
Version::part_delim,
@ -678,7 +683,7 @@ namespace mamba::specs
if ((tail_delim_pos == 0) || (tail_delim_pos == tail.size() - 1)) // TODO(C++20)
// [[unlikely]]
{
throw std::invalid_argument(fmt::format("Empty part in '{}'.", str));
return make_unexpected_parse(fmt::format("Empty part in '{}'.", str));
}
parts.push_back(parse_part(tail.substr(0, tail_delim_pos)));
if (tail_delim_pos == std::string_view::npos)
@ -687,63 +692,86 @@ namespace mamba::specs
}
tail = tail.substr(tail_delim_pos + 1);
}
return parts;
return { std::move(parts) };
}
auto parse_trailing_local_version(std::string_view str)
-> std::pair<std::string_view, CommonVersion>
-> expected_parse_t<std::pair<std::string_view, CommonVersion>>
{
const auto delim_pos = str.rfind(Version::local_delim);
// No local is specified
if (delim_pos == std::string_view::npos) // TODO(C++20) [[likely]]
{
return { str, {} };
return { { str, {} } };
}
// local specified but empty
if (delim_pos + 1 == str.size())
{
throw std::invalid_argument(
return make_unexpected_parse(
fmt::format("Empty local version delimited by '{}'.", Version::local_delim)
);
}
return { str.substr(0, delim_pos), parse_common_version(str.substr(delim_pos + 1)) };
return parse_common_version(str.substr(delim_pos + 1))
.transform(
[&](CommonVersion&& version) {
return std::pair{ str.substr(0, delim_pos), std::move(version) };
}
);
}
auto parse_version(std::string_view str) -> CommonVersion
auto parse_version(std::string_view str) -> expected_parse_t<CommonVersion>
{
if (str.empty())
{
throw std::invalid_argument("Empty version.");
return make_unexpected_parse("Empty version.");
}
return parse_common_version(str);
}
}
auto Version::parse(std::string_view str) -> Version
auto Version::parse(std::string_view str) -> expected_parse_t<Version>
{
str = util::strip(str);
try
auto make_unexpected = [&str](const ParseError& error)
{
auto [epoch, version_and_local_str] = parse_leading_epoch<std::size_t>(str);
auto [version_str, local] = parse_trailing_local_version(version_and_local_str);
auto version = parse_version(version_str);
return {
/* .epoch= */ epoch,
/* .version= */ std::move(version),
/* .local= */ std::move(local),
};
}
catch (const std::invalid_argument& ia)
return make_unexpected_parse(
fmt::format(R"(Error parsing version "{}". {})", str, error.what())
);
};
auto epoch_rest = parse_leading_epoch<std::size_t>(str);
if (!epoch_rest)
{
throw std::invalid_argument(fmt::format("Error parsing version '{}'. {}", str, ia.what()));
return make_unexpected(epoch_rest.error());
}
auto version_and_local = parse_trailing_local_version(epoch_rest->second);
if (!version_and_local)
{
return make_unexpected(version_and_local.error());
}
auto version = parse_version(version_and_local->first);
if (!version)
{
return make_unexpected(version.error());
}
return { {
/* .epoch= */ epoch_rest->first,
/* .version= */ std::move(version).value(),
/* .local= */ std::move(version_and_local)->second,
} };
}
namespace version_literals
{
auto operator""_v(const char* str, std::size_t len) -> Version
{
return Version::parse(std::literals::string_view_literals::operator""sv(str, len));
return Version::parse(std::literals::string_view_literals::operator""sv(str, len))
.or_else([](ParseError&& error) { throw std::move(error); })
.value();
}
}
}

View File

@ -6,7 +6,6 @@
#include <algorithm>
#include <array>
#include <stdexcept>
#include <type_traits>
#include <fmt/format.h>
@ -320,7 +319,7 @@ namespace mamba::specs
return std::find(range.cbegin(), range.cend(), val) != range.cend();
}
auto parse_op_and_version(std::string_view str) -> VersionPredicate
auto parse_op_and_version(std::string_view str) -> expected_parse_t<VersionPredicate>
{
str = util::strip(str);
// WARNING order is important since some operator are prefix of others.
@ -330,35 +329,40 @@ namespace mamba::specs
}
if (util::starts_with(str, VersionSpec::greater_equal_str))
{
return VersionPredicate::make_greater_equal(
Version::parse(str.substr(VersionSpec::greater_equal_str.size()))
);
return Version::parse(str.substr(VersionSpec::greater_equal_str.size()))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_greater_equal(std::move(ver)); });
}
if (util::starts_with(str, VersionSpec::greater_str))
{
return VersionPredicate::make_greater(
Version::parse(str.substr(VersionSpec::greater_str.size()))
);
return Version::parse(str.substr(VersionSpec::greater_str.size()))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_greater(std::move(ver)); });
}
if (util::starts_with(str, VersionSpec::less_equal_str))
{
return VersionPredicate::make_less_equal(
Version::parse(str.substr(VersionSpec::less_equal_str.size()))
);
return Version::parse(str.substr(VersionSpec::less_equal_str.size()))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_less_equal(std::move(ver)); });
}
if (util::starts_with(str, VersionSpec::less_str))
{
return VersionPredicate::make_less(
Version::parse(str.substr(VersionSpec::less_str.size()))
);
return Version::parse(str.substr(VersionSpec::less_str.size()))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_less(std::move(ver)); });
}
if (util::starts_with(str, VersionSpec::compatible_str))
{
auto ver = Version::parse(str.substr(VersionSpec::compatible_str.size()));
// in ``~=1.1`` level is assumed to be 1, in ``~=1.1.1`` level 2, etc.
static constexpr auto one = std::size_t(1); // MSVC
const std::size_t level = std::max(ver.version().size(), one) - one;
return VersionPredicate::make_compatible_with(std::move(ver), level);
return Version::parse(str.substr(VersionSpec::compatible_str.size()))
.transform(
[](specs::Version&& ver)
{
// in ``~=1.1`` level is assumed to be 1, in ``~=1.1.1`` level 2, etc.
static constexpr auto one = std::size_t(1); // MSVC
const std::size_t level = std::max(ver.version().size(), one) - one;
return VersionPredicate::make_compatible_with(std::move(ver), level);
}
);
}
const bool has_glob_suffix = util::ends_with(str, VersionSpec::glob_suffix_str);
const std::size_t glob_len = has_glob_suffix * VersionSpec::glob_suffix_str.size();
@ -368,13 +372,15 @@ namespace mamba::specs
// Glob suffix changes meaning for ==1.3.*
if (has_glob_suffix)
{
return VersionPredicate::make_starts_with(
Version::parse(str.substr(start, str.size() - glob_len - start))
);
return Version::parse(str.substr(VersionSpec::less_equal_str.size()))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_starts_with(std::move(ver)); });
}
else
{
return VersionPredicate::make_equal_to(Version::parse(str.substr(start)));
return Version::parse(str.substr(start))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_equal_to(std::move(ver)); });
}
}
if (util::starts_with(str, VersionSpec::not_equal_str))
@ -383,22 +389,25 @@ namespace mamba::specs
// Glob suffix changes meaning for !=1.3.*
if (has_glob_suffix)
{
return VersionPredicate::make_not_starts_with(
Version::parse(str.substr(start, str.size() - glob_len - start))
);
return Version::parse(str.substr(start, str.size() - glob_len - start))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_not_starts_with(std::move(ver)); }
);
}
else
{
return VersionPredicate::make_not_equal_to(Version::parse(str.substr(start)));
return Version::parse(str.substr(start))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_not_equal_to(std::move(ver)); });
}
}
if (util::starts_with(str, VersionSpec::starts_with_str))
{
const std::size_t start = VersionSpec::starts_with_str.size();
// Glob suffix does not change meaning for =1.3.*
return VersionPredicate::make_starts_with(
Version::parse(str.substr(start, str.size() - glob_len - start))
);
return Version::parse(str.substr(start, str.size() - glob_len - start))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_starts_with(std::move(ver)); });
}
if (util::is_digit(str.front())) // All versions must start with a digit
{
@ -408,19 +417,25 @@ namespace mamba::specs
// either ".*" or "*"
static constexpr auto one = std::size_t(1); // MSVC
const std::size_t len = str.size() - std::max(glob_len, one);
return VersionPredicate::make_starts_with(Version::parse(str.substr(0, len)));
return Version::parse(str.substr(0, len))
.transform([](specs::Version&& ver)
{ return VersionPredicate::make_starts_with(std::move(ver)); });
}
else
{
return VersionPredicate::make_equal_to(Version::parse(str));
return Version::parse(str).transform(
[](specs::Version&& ver)
{ return VersionPredicate::make_equal_to(std::move(ver)); }
);
}
}
throw std::invalid_argument(fmt::format(R"(Found invalid version predicate in "{}")", str)
return tl::make_unexpected(
ParseError(fmt::format(R"(Found invalid version predicate in "{}")", str))
);
}
}
auto VersionSpec::parse(std::string_view str) -> VersionSpec
auto VersionSpec::parse(std::string_view str) -> expected_parse_t<VersionSpec>
{
static constexpr auto all_tokens = std::array{
VersionSpec::and_token,
@ -447,40 +462,95 @@ namespace mamba::specs
{
if (str.front() == VersionSpec::and_token)
{
parser.push_operator(util::BoolOperator::logical_and);
const bool pushed = parser.push_operator(util::BoolOperator::logical_and);
if (!pushed)
{
return tl::make_unexpected(ParseError(fmt::format(
R"(Found unexpected token "{}" in version spec "{}")",
VersionSpec::and_token,
str
)));
}
str = str.substr(1);
}
else if (str.front() == VersionSpec::or_token)
{
parser.push_operator(util::BoolOperator::logical_or);
const bool pushed = parser.push_operator(util::BoolOperator::logical_or);
if (!pushed)
{
return tl::make_unexpected(ParseError(fmt::format(
R"(Found unexpected token "{}" in version spec "{}")",
VersionSpec::or_token,
str
)));
}
str = str.substr(1);
}
else if (str.front() == VersionSpec::left_parenthesis_token)
{
parser.push_left_parenthesis();
const bool pushed = parser.push_left_parenthesis();
if (!pushed)
{
return tl::make_unexpected(ParseError(fmt::format(
R"(Found unexpected token "{}" in version spec "{}")",
VersionSpec::left_parenthesis_token,
str
)));
}
str = util::lstrip(str.substr(1));
}
else if (str.front() == VersionSpec::right_parenthesis_token)
{
parser.push_right_parenthesis();
const bool pushed = parser.push_right_parenthesis();
if (!pushed)
{
return tl::make_unexpected(ParseError(fmt::format(
R"(Found unexpected token "{}" in version spec "{}")",
VersionSpec::right_parenthesis_token,
str
)));
}
str = util::lstrip(str.substr(1));
}
else
{
auto [op_ver, rest] = util::lstrip_if_parts(str, [&](auto c) { return !is_token(c); });
parser.push_variable(parse_op_and_version(op_ver));
auto pred = parse_op_and_version(op_ver);
if (!pred.has_value())
{
return tl::make_unexpected(pred.error());
}
const bool pushed = parser.push_variable(std::move(pred).value());
if (!pushed)
{
return tl::make_unexpected(ParseError(fmt::format(
R"(Found unexpected version predicate "{}" in version spec "{}")",
pred.value(),
str
)));
}
str = rest;
}
}
parser.finalize();
return VersionSpec{ std::move(parser).tree() };
const bool correct = parser.finalize();
if (!correct)
{
return tl::make_unexpected(
ParseError(fmt::format(R"(Version spec "{}" is an incorrect expression)", str))
);
}
return { VersionSpec{ std::move(parser).tree() } };
}
namespace version_spec_literals
{
auto operator""_vs(const char* str, std::size_t len) -> VersionSpec
{
return VersionSpec::parse(std::literals::string_view_literals::operator""sv(str, len));
return VersionSpec::parse(std::literals::string_view_literals::operator""sv(str, len))
.or_else([](ParseError&& error) { throw std::move(error); })
.value();
}
}
}

View File

@ -5,6 +5,7 @@
// The full license is in the file LICENSE, distributed with this software.
#include <cassert>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
@ -37,7 +38,7 @@ namespace mamba::util
*
* Never null, throw exception at construction if creating the handle fails.
*/
class CURLUrl
class CurlUrl
{
public:
@ -46,21 +47,22 @@ namespace mamba::util
using const_pointer = const value_type*;
using flag_type = unsigned int;
CURLUrl();
CURLUrl(const std::string& url, flag_type flags = 0);
~CURLUrl();
static auto parse(const std::string& url, flag_type flags = 0)
-> tl::expected<CurlUrl, URL::ParseError>;
CURLUrl(const CURLUrl&) = delete;
CURLUrl& operator=(const CURLUrl&) = delete;
CURLUrl(CURLUrl&&) = delete;
CURLUrl& operator=(CURLUrl&&) = delete;
CurlUrl();
[[nodiscard]] auto get_part(CURLUPart part, flag_type flags = 0) const
[[nodiscard]] auto get_part(::CURLUPart part, flag_type flags = 0) const
-> std::optional<std::string>;
private:
pointer m_handle = nullptr;
struct CurlDeleter
{
void operator()(pointer ptr);
};
std::unique_ptr<value_type, CurlDeleter> m_handle = nullptr;
};
/**
@ -68,7 +70,7 @@ namespace mamba::util
*
* String can possibly be null, or zero-lenght, depending on the data returned by CURL.
*/
class CURLStr
class CurlStr
{
using value_type = char;
using pointer = value_type*;
@ -78,13 +80,13 @@ namespace mamba::util
public:
explicit CURLStr() = default;
~CURLStr();
explicit CurlStr() = default;
~CurlStr();
CURLStr(const CURLStr&) = delete;
CURLStr& operator=(const CURLStr&) = delete;
CURLStr(CURLStr&&) = delete;
CURLStr& operator=(CURLStr&&) = delete;
CurlStr(const CurlStr&) = delete;
auto operator=(const CurlStr&) -> CurlStr& = delete;
CurlStr(CurlStr&&) = delete;
auto operator=(CurlStr&&) -> CurlStr& = delete;
[[nodiscard]] auto raw_input() -> input_pointer;
@ -97,36 +99,40 @@ namespace mamba::util
size_type m_len = { -1 };
};
CURLUrl::CURLUrl()
auto CurlUrl::parse(const std::string& url, flag_type flags)
-> tl::expected<CurlUrl, URL::ParseError>
{
m_handle = ::curl_url();
if (m_handle == nullptr)
{
throw std::runtime_error("Could not create CURLU handle");
}
}
CURLUrl::CURLUrl(const std::string& url, flag_type flags)
: CURLUrl()
{
const CURLUcode uc = ::curl_url_set(m_handle, CURLUPART_URL, url.c_str(), flags);
auto out = CurlUrl();
const CURLUcode uc = ::curl_url_set(out.m_handle.get(), CURLUPART_URL, url.c_str(), flags);
if (uc != CURLUE_OK)
{
throw std::invalid_argument(
fmt::format(R"(Failed to parse URL "{}": {})", url, ::curl_url_strerror(uc))
);
return tl::make_unexpected(URL::ParseError{
fmt::format(R"(Failed to parse URL "{}": {})", url, ::curl_url_strerror(uc)) });
}
return { std::move(out) };
}
CurlUrl::CurlUrl()
{
m_handle.reset(::curl_url());
if (m_handle == nullptr)
{
throw std::runtime_error("Could not create CurlUrl handle");
}
}
CURLUrl::~CURLUrl()
void CurlUrl::CurlDeleter::operator()(pointer ptr)
{
::curl_url_cleanup(m_handle);
if (ptr)
{
::curl_url_cleanup(ptr);
}
}
auto CURLUrl::get_part(CURLUPart part, flag_type flags) const -> std::optional<std::string>
auto CurlUrl::get_part(CURLUPart part, flag_type flags) const -> std::optional<std::string>
{
CURLStr scheme{};
const auto rc = ::curl_url_get(m_handle, part, scheme.raw_input(), flags);
CurlStr scheme{};
const auto rc = ::curl_url_get(m_handle.get(), part, scheme.raw_input(), flags);
if (!rc)
{
if (auto str = scheme.str())
@ -137,7 +143,7 @@ namespace mamba::util
return std::nullopt;
}
CURLStr::~CURLStr()
CurlStr::~CurlStr()
{
// Even when Curl returns a len along side the data, `curl_free` must be used without
// len.
@ -145,13 +151,13 @@ namespace mamba::util
m_data = nullptr;
}
auto CURLStr::raw_input() -> input_pointer
auto CurlStr::raw_input() -> input_pointer
{
assert(m_data == nullptr); // Otherwise we leak Curl memory
return &m_data;
}
auto CURLStr::str() const -> std::optional<std::string_view>
auto CurlStr::str() const -> std::optional<std::string_view>
{
if (m_data)
{
@ -172,28 +178,31 @@ namespace mamba::util
* URLHandler implementation *
*****************************/
auto URL::parse(std::string_view url) -> URL
auto URL::parse(std::string_view url) -> tl::expected<URL, ParseError>
{
url = util::rstrip(url);
auto out = URL();
if (!url.empty())
if (url.empty())
{
// CURL fails to parse the URL if no scheme is given, unless CURLU_DEFAULT_SCHEME is
// given
const CURLUrl handle = {
file_uri_unc2_to_unc4(url),
CURLU_NON_SUPPORT_SCHEME | CURLU_DEFAULT_SCHEME,
};
out.set_scheme(handle.get_part(CURLUPART_SCHEME).value_or(""));
out.set_user(handle.get_part(CURLUPART_USER).value_or(""), Encode::no);
out.set_password(handle.get_part(CURLUPART_PASSWORD).value_or(""), Encode::no);
out.set_host(handle.get_part(CURLUPART_HOST).value_or(""), Encode::no);
out.set_path(handle.get_part(CURLUPART_PATH).value_or("/"), Encode::no);
out.set_port(handle.get_part(CURLUPART_PORT).value_or(""));
out.set_query(handle.get_part(CURLUPART_QUERY).value_or(""));
out.set_fragment(handle.get_part(CURLUPART_FRAGMENT).value_or(""));
return tl::make_unexpected(ParseError{ "Empty URL" });
}
return out;
return CurlUrl::parse(file_uri_unc2_to_unc4(url), CURLU_NON_SUPPORT_SCHEME | CURLU_DEFAULT_SCHEME)
.transform(
[&](CurlUrl&& handle) -> URL
{
auto out = URL();
// CURL fails to parse the URL if no scheme is given, unless
// CURLU_DEFAULT_SCHEME is given
out.set_scheme(handle.get_part(CURLUPART_SCHEME).value_or(""));
out.set_user(handle.get_part(CURLUPART_USER).value_or(""), Encode::no);
out.set_password(handle.get_part(CURLUPART_PASSWORD).value_or(""), Encode::no);
out.set_host(handle.get_part(CURLUPART_HOST).value_or(""), Encode::no);
out.set_path(handle.get_part(CURLUPART_PATH).value_or("/"), Encode::no);
out.set_port(handle.get_part(CURLUPART_PORT).value_or(""));
out.set_query(handle.get_part(CURLUPART_QUERY).value_or(""));
out.set_fragment(handle.get_part(CURLUPART_FRAGMENT).value_or(""));
return out;
}
);
}
auto URL::scheme_is_defaulted() const -> bool

View File

@ -360,7 +360,7 @@ namespace mamba::validation::v0_6
auto tmp_dir = TemporaryDirectory();
auto tmp_metadata_path = tmp_dir.path() / "pkg_mgr.json";
const auto url = mamba::util::URL::parse(base_url + "/pkg_mgr.json");
const auto url = mamba::util::URL::parse(base_url + "/pkg_mgr.json").value();
if (download::check_resource_exists(url.pretty_str(), context))
{

View File

@ -119,24 +119,22 @@ TEST_SUITE("specs::build_number_spec")
for (const auto& spec : bad_specs)
{
CAPTURE(spec);
// Silence [[nodiscard]] warning
auto parse = [](auto s) { return BuildNumberSpec::parse(s); };
CHECK_THROWS_AS(parse(spec), std::invalid_argument);
CHECK_FALSE(BuildNumberSpec::parse(spec).has_value());
}
}
}
TEST_CASE("BuildNumberSepc::str")
{
CHECK_EQ(BuildNumberSpec::parse("=3").str(), "=3");
CHECK_EQ(BuildNumberSpec::parse("<2").str(), "<2");
CHECK_EQ(BuildNumberSpec::parse("*").str(), "=*");
CHECK_EQ(BuildNumberSpec::parse("=3").value().str(), "=3");
CHECK_EQ(BuildNumberSpec::parse("<2").value().str(), "<2");
CHECK_EQ(BuildNumberSpec::parse("*").value().str(), "=*");
}
TEST_CASE("BuildNumberSepc::is_explicitly_free")
{
CHECK(BuildNumberSpec::parse("*").is_explicitly_free());
CHECK_FALSE(BuildNumberSpec::parse("=3").is_explicitly_free());
CHECK_FALSE(BuildNumberSpec::parse("<2").is_explicitly_free());
CHECK(BuildNumberSpec::parse("*").value().is_explicitly_free());
CHECK_FALSE(BuildNumberSpec::parse("=3").value().is_explicitly_free());
CHECK_FALSE(BuildNumberSpec::parse("<2").value().is_explicitly_free());
}
}

View File

@ -234,7 +234,7 @@ TEST_SUITE("specs::channel")
auto make_typical_params = []() -> ChannelResolveParams
{
auto make_channel = [](std::string_view loc, ChannelResolveParams const& params)
{ return Channel::resolve(UnresolvedChannel::parse(loc), params).at(0); };
{ return Channel::resolve(UnresolvedChannel::parse(loc).value(), params).at(0); };
auto params = ChannelResolveParams{};
params.platforms = { "linux-64", "noarch" };
@ -325,7 +325,8 @@ TEST_SUITE("specs::channel")
};
params.custom_channels.emplace(
"mychan",
Channel::resolve(UnresolvedChannel::parse("file:///home/conda-bld/"), params).at(0)
Channel::resolve(UnresolvedChannel::parse("file:///home/conda-bld/").value(), params)
.at(0)
);
CHECK_EQ(Channel::resolve(uc, params).at(0).display_name(), "mychan");
}
@ -711,7 +712,8 @@ TEST_SUITE("specs::channel")
auto params = make_typical_params();
params.custom_channels.emplace(
"conda-forge",
Channel::resolve(UnresolvedChannel::parse("ftp://mydomain.net/conda"), params).at(0)
Channel::resolve(UnresolvedChannel::parse("ftp://mydomain.net/conda").value(), params)
.at(0)
);
auto channels = Channel::resolve(uc, params);
@ -783,7 +785,7 @@ TEST_SUITE("specs::channel")
params.custom_channels.emplace(
"testchannel",
Channel::resolve(
UnresolvedChannel::parse("https://server.com/private/testchannel"),
UnresolvedChannel::parse("https://server.com/private/testchannel").value(),
params
)
.at(0)
@ -812,7 +814,8 @@ TEST_SUITE("specs::channel")
};
params.custom_channels.emplace(
"prefix",
Channel::resolve(UnresolvedChannel::parse("https://server.com/prefix"), params).at(0)
Channel::resolve(UnresolvedChannel::parse("https://server.com/prefix").value(), params)
.at(0)
);
auto channels = Channel::resolve(uc, params);

View File

@ -21,7 +21,7 @@ TEST_SUITE("specs::repo_data")
{
auto p = RepoDataPackage();
p.name = "mamba";
p.version = Version::parse("1.0.0");
p.version = Version::parse("1.0.0").value();
p.build_string = "bld";
p.build_number = 3;
p.subdir = "linux";

View File

@ -38,21 +38,30 @@ TEST_SUITE("specs::unresolved_channel")
TEST_CASE("Parsing")
{
SUBCASE("Invalid channels")
SUBCASE("Unknown channels")
{
for (std::string_view str : { "", "<unknown>", ":///<unknown>", "none" })
{
CAPTURE(str);
const auto uc = UnresolvedChannel::parse(str);
const auto uc = UnresolvedChannel::parse(str).value();
CHECK_EQ(uc.type(), Type::Unknown);
CHECK_EQ(uc.location(), "<unknown>");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
}
}
SUBCASE("Invalid channels")
{
for (std::string_view str : { "forgelinux-64]" })
{
CAPTURE(str);
CHECK_FALSE(UnresolvedChannel::parse(str).has_value());
}
}
SUBCASE("https://repo.anaconda.com/conda-forge")
{
const auto uc = UnresolvedChannel::parse("https://repo.anaconda.com/conda-forge");
const auto uc = UnresolvedChannel::parse("https://repo.anaconda.com/conda-forge").value();
CHECK_EQ(uc.type(), Type::URL);
CHECK_EQ(uc.location(), "https://repo.anaconda.com/conda-forge");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -60,7 +69,8 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("https://repo.anaconda.com/conda-forge/osx-64")
{
const auto uc = UnresolvedChannel::parse("https://repo.anaconda.com/conda-forge/osx-64");
const auto uc = UnresolvedChannel::parse("https://repo.anaconda.com/conda-forge/osx-64")
.value();
CHECK_EQ(uc.type(), Type::URL);
CHECK_EQ(uc.location(), "https://repo.anaconda.com/conda-forge");
CHECK_EQ(uc.platform_filters(), PlatformSet{ "osx-64" });
@ -69,8 +79,9 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("https://repo.anaconda.com/conda-forge[win-64|noarch]")
{
const auto uc = UnresolvedChannel::parse(
"https://repo.anaconda.com/conda-forge[win-64|noarch]"
);
"https://repo.anaconda.com/conda-forge[win-64|noarch]"
)
.value();
CHECK_EQ(uc.type(), Type::URL);
CHECK_EQ(uc.location(), "https://repo.anaconda.com/conda-forge");
CHECK_EQ(uc.platform_filters(), PlatformSet{ "win-64", "noarch" });
@ -79,8 +90,9 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("https://repo.anaconda.com/conda-forge/linux-64/pkg-0.0-bld.conda")
{
const auto uc = UnresolvedChannel::parse(
"https://repo.anaconda.com/conda-forge/linux-64/pkg-0.0-bld.conda"
);
"https://repo.anaconda.com/conda-forge/linux-64/pkg-0.0-bld.conda"
)
.value();
CHECK_EQ(uc.type(), Type::PackageURL);
CHECK_EQ(uc.location(), "https://repo.anaconda.com/conda-forge/linux-64/pkg-0.0-bld.conda");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -88,7 +100,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("file:///Users/name/conda")
{
const auto uc = UnresolvedChannel::parse("file:///Users/name/conda");
const auto uc = UnresolvedChannel::parse("file:///Users/name/conda").value();
CHECK_EQ(uc.type(), Type::Path);
CHECK_EQ(uc.location(), "file:///Users/name/conda");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -96,7 +108,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("file:///Users/name/conda[linux-64]")
{
const auto uc = UnresolvedChannel::parse("file:///Users/name/conda[linux-64]");
const auto uc = UnresolvedChannel::parse("file:///Users/name/conda[linux-64]").value();
CHECK_EQ(uc.type(), Type::Path);
CHECK_EQ(uc.location(), "file:///Users/name/conda");
CHECK_EQ(uc.platform_filters(), PlatformSet{ "linux-64" });
@ -106,7 +118,7 @@ TEST_SUITE("specs::unresolved_channel")
{
if (util::on_win)
{
const auto uc = UnresolvedChannel::parse("file://C:/Users/name/conda");
const auto uc = UnresolvedChannel::parse("file://C:/Users/name/conda").value();
CHECK_EQ(uc.type(), Type::Path);
CHECK_EQ(uc.location(), "file://C:/Users/name/conda");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -115,7 +127,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("/Users/name/conda")
{
const auto uc = UnresolvedChannel::parse("/Users/name/conda");
const auto uc = UnresolvedChannel::parse("/Users/name/conda").value();
CHECK_EQ(uc.type(), Type::Path);
CHECK_EQ(uc.location(), "/Users/name/conda");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -123,7 +135,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("./folder/../folder/.")
{
const auto uc = UnresolvedChannel::parse("./folder/../folder/.");
const auto uc = UnresolvedChannel::parse("./folder/../folder/.").value();
CHECK_EQ(uc.type(), Type::Path);
CHECK_EQ(uc.location(), "folder");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -131,7 +143,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("~/folder/")
{
const auto uc = UnresolvedChannel::parse("~/folder/");
const auto uc = UnresolvedChannel::parse("~/folder/").value();
CHECK_EQ(uc.type(), Type::Path);
CHECK_EQ(uc.location(), "~/folder");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -139,7 +151,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("/tmp/pkg-0.0-bld.tar.bz2")
{
const auto uc = UnresolvedChannel::parse("/tmp/pkg-0.0-bld.tar.bz2");
const auto uc = UnresolvedChannel::parse("/tmp/pkg-0.0-bld.tar.bz2").value();
CHECK_EQ(uc.type(), Type::PackagePath);
CHECK_EQ(uc.location(), "/tmp/pkg-0.0-bld.tar.bz2");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -147,7 +159,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("C:/tmp//pkg-0.0-bld.tar.bz2")
{
const auto uc = UnresolvedChannel::parse("C:/tmp//pkg-0.0-bld.tar.bz2");
const auto uc = UnresolvedChannel::parse("C:/tmp//pkg-0.0-bld.tar.bz2").value();
CHECK_EQ(uc.type(), Type::PackagePath);
CHECK_EQ(uc.location(), "C:/tmp/pkg-0.0-bld.tar.bz2");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -157,7 +169,7 @@ TEST_SUITE("specs::unresolved_channel")
{
if (util::on_win)
{
const auto uc = UnresolvedChannel::parse(R"(C:\tmp\pkg-0.0-bld.tar.bz2)");
const auto uc = UnresolvedChannel::parse(R"(C:\tmp\pkg-0.0-bld.tar.bz2)").value();
CHECK_EQ(uc.type(), Type::PackagePath);
CHECK_EQ(uc.location(), "C:/tmp/pkg-0.0-bld.tar.bz2");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -166,7 +178,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("conda-forge")
{
const auto uc = UnresolvedChannel::parse("conda-forge");
const auto uc = UnresolvedChannel::parse("conda-forge").value();
CHECK_EQ(uc.type(), Type::Name);
CHECK_EQ(uc.location(), "conda-forge");
CHECK_EQ(uc.platform_filters(), PlatformSet{});
@ -174,7 +186,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("repo.anaconda.com")
{
const auto uc = UnresolvedChannel::parse("repo.anaconda.com");
const auto uc = UnresolvedChannel::parse("repo.anaconda.com").value();
// Unintuitive but correct type, this is not a URL. Better explicit than clever.
CHECK_EQ(uc.type(), Type::Name);
CHECK_EQ(uc.location(), "repo.anaconda.com");
@ -183,7 +195,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("conda-forge/linux-64")
{
const auto uc = UnresolvedChannel::parse("conda-forge/linux-64");
const auto uc = UnresolvedChannel::parse("conda-forge/linux-64").value();
CHECK_EQ(uc.type(), Type::Name);
CHECK_EQ(uc.location(), "conda-forge");
CHECK_EQ(uc.platform_filters(), PlatformSet{ "linux-64" });
@ -191,7 +203,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("conda-forge[linux-avx512]")
{
const auto uc = UnresolvedChannel::parse("conda-forge[linux-avx512]");
const auto uc = UnresolvedChannel::parse("conda-forge[linux-avx512]").value();
CHECK_EQ(uc.type(), Type::Name);
CHECK_EQ(uc.location(), "conda-forge");
CHECK_EQ(uc.platform_filters(), PlatformSet{ "linux-avx512" });
@ -199,7 +211,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("conda-forge[]")
{
const auto uc = UnresolvedChannel::parse("conda-forge[linux-64]");
const auto uc = UnresolvedChannel::parse("conda-forge[linux-64]").value();
CHECK_EQ(uc.type(), Type::Name);
CHECK_EQ(uc.location(), "conda-forge");
CHECK_EQ(uc.platform_filters(), PlatformSet{ "linux-64" });
@ -207,7 +219,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("conda-forge/linux-64/label/foo_dev")
{
const auto uc = UnresolvedChannel::parse("conda-forge/linux-64/label/foo_dev");
const auto uc = UnresolvedChannel::parse("conda-forge/linux-64/label/foo_dev").value();
CHECK_EQ(uc.type(), Type::Name);
CHECK_EQ(uc.location(), "conda-forge/label/foo_dev");
CHECK_EQ(uc.platform_filters(), PlatformSet{ "linux-64" });
@ -215,7 +227,7 @@ TEST_SUITE("specs::unresolved_channel")
SUBCASE("conda-forge/label/foo_dev[linux-64]")
{
const auto uc = UnresolvedChannel::parse("conda-forge/label/foo_dev[linux-64]");
const auto uc = UnresolvedChannel::parse("conda-forge/label/foo_dev[linux-64]").value();
CHECK_EQ(uc.type(), Type::Name);
CHECK_EQ(uc.location(), "conda-forge/label/foo_dev");
CHECK_EQ(uc.platform_filters(), PlatformSet{ "linux-64" });

View File

@ -418,51 +418,51 @@ TEST_SUITE("specs::version")
));
// Default constructed
CHECK_EQ(Version::parse("0.0"), Version());
CHECK_EQ(Version::parse("0.0").value(), Version());
// Lowercase and strip
CHECK_EQ(Version::parse("0.4.1.rc"), Version::parse(" 0.4.1.RC "));
CHECK_EQ(Version::parse(" 0.4.1.RC "), Version::parse("0.4.1.rc"));
CHECK_EQ(Version::parse("0.4.1.rc").value(), Version::parse(" 0.4.1.RC "));
CHECK_EQ(Version::parse(" 0.4.1.RC ").value(), Version::parse("0.4.1.rc"));
// Functional assertions
CHECK_EQ(Version::parse(" 0.4.rc "), Version::parse("0.4.RC"));
CHECK_EQ(Version::parse("0.4"), Version::parse("0.4.0"));
CHECK_NE(Version::parse("0.4"), Version::parse("0.4.1"));
CHECK_EQ(Version::parse("0.4.a1"), Version::parse("0.4.0a1"));
CHECK_NE(Version::parse("0.4.a1"), Version::parse("0.4.1a1"));
CHECK_EQ(Version::parse(" 0.4.rc ").value(), Version::parse("0.4.RC"));
CHECK_EQ(Version::parse("0.4").value(), Version::parse("0.4.0"));
CHECK_NE(Version::parse("0.4").value(), Version::parse("0.4.1"));
CHECK_EQ(Version::parse("0.4.a1").value(), Version::parse("0.4.0a1"));
CHECK_NE(Version::parse("0.4.a1").value(), Version::parse("0.4.1a1"));
}
TEST_CASE("parse_invalid")
{
// Wrong epoch
CHECK_THROWS_AS(Version::parse("!1.1"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("-1!1.1"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("foo!1.1"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("0post1!1.1"), std::invalid_argument);
CHECK_FALSE(Version::parse("!1.1").has_value());
CHECK_FALSE(Version::parse("-1!1.1").has_value());
CHECK_FALSE(Version::parse("foo!1.1").has_value());
CHECK_FALSE(Version::parse("0post1!1.1").has_value());
// Empty parts
CHECK_THROWS_AS(Version::parse(""), std::invalid_argument);
CHECK_THROWS_AS(Version::parse(" "), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("!2.2"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("0!"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("!"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("1."), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("1..1"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("5.5..mw"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("1.2post+"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("1!+1.1"), std::invalid_argument);
CHECK_FALSE(Version::parse("").has_value());
CHECK_FALSE(Version::parse(" ").has_value());
CHECK_FALSE(Version::parse("!2.2").has_value());
CHECK_FALSE(Version::parse("0!").has_value());
CHECK_FALSE(Version::parse("!").has_value());
CHECK_FALSE(Version::parse("1.").has_value());
CHECK_FALSE(Version::parse("1..1").has_value());
CHECK_FALSE(Version::parse("5.5..mw").has_value());
CHECK_FALSE(Version::parse("1.2post+").has_value());
CHECK_FALSE(Version::parse("1!+1.1").has_value());
// Repeated delimiters
CHECK_THROWS_AS(Version::parse("5.5++"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("5.5+1+0.0"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("1!2!3.0"), std::invalid_argument);
CHECK_FALSE(Version::parse("5.5++").has_value());
CHECK_FALSE(Version::parse("5.5+1+0.0").has_value());
CHECK_FALSE(Version::parse("1!2!3.0").has_value());
// '-' and '_' delimiters not allowed together.
CHECK_THROWS_AS(Version::parse("1-1_1"), std::invalid_argument);
CHECK_FALSE(Version::parse("1-1_1").has_value());
// Forbidden characters
CHECK_THROWS_AS(Version::parse("3.5&1"), std::invalid_argument);
CHECK_THROWS_AS(Version::parse("3.5|1"), std::invalid_argument);
CHECK_FALSE(Version::parse("3.5&1").has_value());
CHECK_FALSE(Version::parse("3.5|1").has_value());
}
/**
@ -485,23 +485,23 @@ TEST_SUITE("specs::version")
{
// clang-format off
auto versions = std::vector{
Version::parse("1.0.1dev"),
Version::parse("1.0.1_"), // <- this
Version::parse("1.0.1a"),
Version::parse("1.0.1b"),
Version::parse("1.0.1c"),
Version::parse("1.0.1d"),
Version::parse("1.0.1r"),
Version::parse("1.0.1rc"),
Version::parse("1.0.1rc1"),
Version::parse("1.0.1rc2"),
Version::parse("1.0.1s"),
Version::parse("1.0.1"), // <- compared to this
Version::parse("1.0.1post.a"),
Version::parse("1.0.1post.b"),
Version::parse("1.0.1post.z"),
Version::parse("1.0.1post.za"),
Version::parse("1.0.2"),
Version::parse("1.0.1dev").value(),
Version::parse("1.0.1_").value(), // <- this
Version::parse("1.0.1a").value(),
Version::parse("1.0.1b").value(),
Version::parse("1.0.1c").value(),
Version::parse("1.0.1d").value(),
Version::parse("1.0.1r").value(),
Version::parse("1.0.1rc").value(),
Version::parse("1.0.1rc1").value(),
Version::parse("1.0.1rc2").value(),
Version::parse("1.0.1s").value(),
Version::parse("1.0.1").value(), // <- compared to this
Version::parse("1.0.1post.a").value(),
Version::parse("1.0.1post.b").value(),
Version::parse("1.0.1post.z").value(),
Version::parse("1.0.1post.za").value(),
Version::parse("1.0.2").value(),
};
// clang-format on
@ -521,59 +521,59 @@ TEST_SUITE("specs::version")
{
auto versions = std::vector{
// Implicit epoch of 0
Version::parse("1.0a1"),
Version::parse("1.0a2.dev456"),
Version::parse("1.0a12.dev456"),
Version::parse("1.0a12"),
Version::parse("1.0b1.dev456"),
Version::parse("1.0b2"),
Version::parse("1.0b2.post345.dev456"),
Version::parse("1.0b2.post345"),
Version::parse("1.0c1.dev456"),
Version::parse("1.0c1"),
Version::parse("1.0c3"),
Version::parse("1.0rc2"),
Version::parse("1.0.dev456"),
Version::parse("1.0"),
Version::parse("1.0.post456.dev34"),
Version::parse("1.0.post456"),
Version::parse("1.1.dev1"),
Version::parse("1.2.r32+123456"),
Version::parse("1.2.rev33+123456"),
Version::parse("1.2+abc"),
Version::parse("1.2+abc123def"),
Version::parse("1.2+abc123"),
Version::parse("1.2+123abc"),
Version::parse("1.2+123abc456"),
Version::parse("1.2+1234.abc"),
Version::parse("1.2+123456"),
Version::parse("1.0a1").value(),
Version::parse("1.0a2.dev456").value(),
Version::parse("1.0a12.dev456").value(),
Version::parse("1.0a12").value(),
Version::parse("1.0b1.dev456").value(),
Version::parse("1.0b2").value(),
Version::parse("1.0b2.post345.dev456").value(),
Version::parse("1.0b2.post345").value(),
Version::parse("1.0c1.dev456").value(),
Version::parse("1.0c1").value(),
Version::parse("1.0c3").value(),
Version::parse("1.0rc2").value(),
Version::parse("1.0.dev456").value(),
Version::parse("1.0").value(),
Version::parse("1.0.post456.dev34").value(),
Version::parse("1.0.post456").value(),
Version::parse("1.1.dev1").value(),
Version::parse("1.2.r32+123456").value(),
Version::parse("1.2.rev33+123456").value(),
Version::parse("1.2+abc").value(),
Version::parse("1.2+abc123def").value(),
Version::parse("1.2+abc123").value(),
Version::parse("1.2+123abc").value(),
Version::parse("1.2+123abc456").value(),
Version::parse("1.2+1234.abc").value(),
Version::parse("1.2+123456").value(),
// Explicit epoch of 1
Version::parse("1!1.0a1"),
Version::parse("1!1.0a2.dev456"),
Version::parse("1!1.0a12.dev456"),
Version::parse("1!1.0a12"),
Version::parse("1!1.0b1.dev456"),
Version::parse("1!1.0b2"),
Version::parse("1!1.0b2.post345.dev456"),
Version::parse("1!1.0b2.post345"),
Version::parse("1!1.0c1.dev456"),
Version::parse("1!1.0c1"),
Version::parse("1!1.0c3"),
Version::parse("1!1.0rc2"),
Version::parse("1!1.0.dev456"),
Version::parse("1!1.0"),
Version::parse("1!1.0.post456.dev34"),
Version::parse("1!1.0.post456"),
Version::parse("1!1.1.dev1"),
Version::parse("1!1.2.r32+123456"),
Version::parse("1!1.2.rev33+123456"),
Version::parse("1!1.2+abc"),
Version::parse("1!1.2+abc123def"),
Version::parse("1!1.2+abc123"),
Version::parse("1!1.2+123abc"),
Version::parse("1!1.2+123abc456"),
Version::parse("1!1.2+1234.abc"),
Version::parse("1!1.2+123456"),
Version::parse("1!1.0a1").value(),
Version::parse("1!1.0a2.dev456").value(),
Version::parse("1!1.0a12.dev456").value(),
Version::parse("1!1.0a12").value(),
Version::parse("1!1.0b1.dev456").value(),
Version::parse("1!1.0b2").value(),
Version::parse("1!1.0b2.post345.dev456").value(),
Version::parse("1!1.0b2.post345").value(),
Version::parse("1!1.0c1.dev456").value(),
Version::parse("1!1.0c1").value(),
Version::parse("1!1.0c3").value(),
Version::parse("1!1.0rc2").value(),
Version::parse("1!1.0.dev456").value(),
Version::parse("1!1.0").value(),
Version::parse("1!1.0.post456.dev34").value(),
Version::parse("1!1.0.post456").value(),
Version::parse("1!1.1.dev1").value(),
Version::parse("1!1.2.r32+123456").value(),
Version::parse("1!1.2.rev33+123456").value(),
Version::parse("1!1.2+abc").value(),
Version::parse("1!1.2+abc123def").value(),
Version::parse("1!1.2+abc123").value(),
Version::parse("1!1.2+123abc").value(),
Version::parse("1!1.2+123abc456").value(),
Version::parse("1!1.2+1234.abc").value(),
Version::parse("1!1.2+123456").value(),
};
// clang-format on

View File

@ -5,7 +5,6 @@
// The full license is in the file LICENSE, distributed with this software.
#include <array>
#include <stdexcept>
#include <string_view>
#include <doctest/doctest.h>
@ -16,13 +15,16 @@ using namespace mamba::specs;
TEST_SUITE("specs::version_spec")
{
using namespace mamba::specs::version_literals;
using namespace mamba::specs::version_spec_literals;
TEST_CASE("VersionPredicate")
{
const auto v1 = Version::parse("1.0");
const auto v2 = Version::parse("2.0");
const auto v201 = Version::parse("2.0.1");
const auto v3 = Version::parse("3.0");
const auto v4 = Version::parse("4.0");
const auto v1 = "1.0"_v;
const auto v2 = "2.0"_v;
const auto v201 = "2.0.1"_v;
const auto v3 = "3.0"_v;
const auto v4 = "4.0"_v;
const auto free = VersionPredicate::make_free();
CHECK(free.contains(v1));
@ -135,14 +137,14 @@ TEST_SUITE("specs::version_spec")
const auto v28 = Version(0, { { { 2 } }, { { 8 } }, { { 0 } } });
auto parser = InfixParser<VersionPredicate, BoolOperator>{};
parser.push_variable(VersionPredicate::make_less(v20));
parser.push_operator(BoolOperator::logical_or);
parser.push_left_parenthesis();
parser.push_variable(VersionPredicate::make_greater(v23));
parser.push_operator(BoolOperator::logical_and);
parser.push_variable(VersionPredicate::make_less_equal(v28));
parser.push_right_parenthesis();
parser.finalize();
REQUIRE(parser.push_variable(VersionPredicate::make_less(v20)));
REQUIRE(parser.push_operator(BoolOperator::logical_or));
REQUIRE(parser.push_left_parenthesis());
REQUIRE(parser.push_variable(VersionPredicate::make_greater(v23)));
REQUIRE(parser.push_operator(BoolOperator::logical_and));
REQUIRE(parser.push_variable(VersionPredicate::make_less_equal(v28)));
REQUIRE(parser.push_right_parenthesis());
REQUIRE(parser.finalize());
auto spec = VersionSpec(std::move(parser).tree());
@ -162,9 +164,6 @@ TEST_SUITE("specs::version_spec")
TEST_CASE("VersionSpec::parse")
{
using namespace mamba::specs::version_literals;
using namespace mamba::specs::version_spec_literals;
SUBCASE("Successful")
{
CHECK(""_vs.contains("1.6"_v));
@ -392,9 +391,7 @@ TEST_SUITE("specs::version_spec")
for (const auto& spec : bad_specs)
{
CAPTURE(spec);
// Silence [[nodiscard]] warning
auto parse = [](auto s) { return VersionSpec::parse(s); };
CHECK_THROWS_AS(parse(spec), std::invalid_argument);
CHECK_FALSE(VersionSpec::parse(spec).has_value());
}
}
}
@ -403,14 +400,14 @@ TEST_SUITE("specs::version_spec")
{
SUBCASE("2.3")
{
auto vs = VersionSpec::parse("2.3");
auto vs = VersionSpec::parse("2.3").value();
CHECK_EQ(vs.str(), "==2.3");
CHECK_EQ(vs.str_conda_build(), "==2.3");
}
SUBCASE("=2.3,<3.0")
{
auto vs = VersionSpec::parse("=2.3,<3.0");
auto vs = VersionSpec::parse("=2.3,<3.0").value();
CHECK_EQ(vs.str(), "=2.3,<3.0");
CHECK_EQ(vs.str_conda_build(), "2.3.*,<3.0");
}
@ -422,18 +419,18 @@ TEST_SUITE("specs::version_spec")
using namespace mamba::util;
auto parser = InfixParser<VersionPredicate, BoolOperator>{};
parser.push_variable(VersionPredicate::make_free());
parser.finalize();
REQUIRE(parser.push_variable(VersionPredicate::make_free()));
REQUIRE(parser.finalize());
auto spec = VersionSpec(std::move(parser).tree());
CHECK(spec.is_explicitly_free());
}
CHECK(VersionSpec().is_explicitly_free());
CHECK(VersionSpec::parse("*").is_explicitly_free());
CHECK(VersionSpec::parse("").is_explicitly_free());
CHECK(VersionSpec::parse("*").value().is_explicitly_free());
CHECK(VersionSpec::parse("").value().is_explicitly_free());
CHECK_FALSE(VersionSpec::parse("==2.3|!=2.3").is_explicitly_free());
CHECK_FALSE(VersionSpec::parse("=2.3,<3.0").is_explicitly_free());
CHECK_FALSE(VersionSpec::parse("==2.3|!=2.3").value().is_explicitly_free());
CHECK_FALSE(VersionSpec::parse("=2.3,<3.0").value().is_explicitly_free());
}
}

View File

@ -109,15 +109,15 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("empty")
{
parser.finalize();
CHECK(parser.finalize());
const auto& tree = parser.tree();
CHECK(tree.empty());
}
SUBCASE("a")
{
parser.push_variable('a');
parser.finalize();
CHECK(parser.push_variable('a'));
CHECK(parser.finalize());
const auto& tree = parser.tree();
CHECK_EQ(tree.size(), 1);
@ -129,16 +129,16 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("a b + c d e + * *")
{
// Infix: (a + b) * (c * (d + e))
parser.push_variable('a');
parser.push_variable('b');
parser.push_operator("+");
parser.push_variable('c');
parser.push_variable('d');
parser.push_variable('e');
parser.push_operator("+");
parser.push_operator("*");
parser.push_operator("*");
parser.finalize();
CHECK(parser.push_variable('a'));
CHECK(parser.push_variable('b'));
CHECK(parser.push_operator("+"));
CHECK(parser.push_variable('c'));
CHECK(parser.push_variable('d'));
CHECK(parser.push_variable('e'));
CHECK(parser.push_operator("+"));
CHECK(parser.push_operator("*"));
CHECK(parser.push_operator("*"));
CHECK(parser.finalize());
const auto& tree = parser.tree();
CHECK_EQ(tree.size(), 9);
@ -149,49 +149,31 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("a b")
{
auto bad_parse = [&]()
{
parser.push_variable('a');
parser.push_variable('b');
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_variable('a'));
CHECK(parser.push_variable('b'));
CHECK_FALSE(parser.finalize());
}
SUBCASE("+")
{
auto bad_parse = [&]()
{
parser.push_operator("+");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK_FALSE(parser.push_operator("+"));
}
SUBCASE("a b + *")
{
auto bad_parse = [&]()
{
parser.push_variable('a');
parser.push_variable('b');
parser.push_operator("+");
parser.push_operator("*");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_variable('a'));
CHECK(parser.push_variable('b'));
CHECK(parser.push_operator("+"));
CHECK_FALSE(parser.push_operator("*"));
}
SUBCASE("a b + c")
{
auto bad_parse = [&]()
{
parser.push_variable('a');
parser.push_variable('b');
parser.push_operator("+");
parser.push_variable('c');
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_variable('a'));
CHECK(parser.push_variable('b'));
CHECK(parser.push_operator("+"));
CHECK(parser.push_variable('c'));
CHECK_FALSE(parser.finalize());
}
}
@ -201,21 +183,21 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("empty")
{
parser.finalize();
CHECK(parser.finalize());
const auto& tree = parser.tree();
CHECK(tree.empty());
}
SUBCASE("(((a)))")
{
parser.push_left_parenthesis();
parser.push_left_parenthesis();
parser.push_left_parenthesis();
parser.push_variable('a');
parser.push_right_parenthesis();
parser.push_right_parenthesis();
parser.push_right_parenthesis();
parser.finalize();
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable('a'));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_right_parenthesis());
CHECK(parser.finalize());
const auto& tree = parser.tree();
REQUIRE_EQ(tree.size(), 1);
@ -226,16 +208,16 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("(((a)) + b)")
{
parser.push_left_parenthesis();
parser.push_left_parenthesis();
parser.push_left_parenthesis();
parser.push_variable('a');
parser.push_right_parenthesis();
parser.push_right_parenthesis();
parser.push_operator("+");
parser.push_variable('b');
parser.push_right_parenthesis();
parser.finalize();
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable('a'));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_operator("+"));
CHECK(parser.push_variable('b'));
CHECK(parser.push_right_parenthesis());
CHECK(parser.finalize());
const auto& tree = parser.tree();
REQUIRE_EQ(tree.size(), 3);
@ -250,22 +232,22 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("(a + b) * (c * (d + e))")
{
parser.push_left_parenthesis();
parser.push_variable('a');
parser.push_operator("+");
parser.push_variable('b');
parser.push_right_parenthesis();
parser.push_operator("*");
parser.push_left_parenthesis();
parser.push_variable('c');
parser.push_operator("*");
parser.push_left_parenthesis();
parser.push_variable('d');
parser.push_operator("+");
parser.push_variable('e');
parser.push_right_parenthesis();
parser.push_right_parenthesis();
parser.finalize();
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable('a'));
CHECK(parser.push_operator("+"));
CHECK(parser.push_variable('b'));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_operator("*"));
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable('c'));
CHECK(parser.push_operator("*"));
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable('d'));
CHECK(parser.push_operator("+"));
CHECK(parser.push_variable('e'));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_right_parenthesis());
CHECK(parser.finalize());
const auto& tree = parser.tree();
CHECK_EQ(tree.size(), 9);
@ -276,126 +258,72 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("(")
{
auto bad_parse = [&]()
{
parser.push_left_parenthesis();
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_left_parenthesis());
CHECK_FALSE(parser.finalize());
}
SUBCASE(")")
{
auto bad_parse = [&]()
{
parser.push_right_parenthesis();
// parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK_FALSE(parser.push_right_parenthesis());
}
SUBCASE("(a+b")
{
auto bad_parse = [&]()
{
parser.push_left_parenthesis();
parser.push_variable('a');
parser.push_operator("+");
parser.push_variable('b');
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable('a'));
CHECK(parser.push_operator("+"));
CHECK(parser.push_variable('b'));
CHECK_FALSE(parser.finalize());
}
SUBCASE("a))")
SUBCASE("a)")
{
auto bad_parse = [&]()
{
parser.push_variable('a');
parser.push_right_parenthesis();
parser.push_right_parenthesis();
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_variable('a'));
CHECK_FALSE(parser.push_right_parenthesis());
}
SUBCASE("+")
{
auto bad_parse = [&]()
{
parser.push_operator("+");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK_FALSE(parser.push_operator("+"));
}
SUBCASE("a b +")
{
auto bad_parse = [&]()
{
parser.push_variable('a');
parser.push_variable('b');
parser.push_operator("+");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_variable('a'));
CHECK_FALSE(parser.push_variable('b'));
}
SUBCASE("a + + b")
{
auto bad_parse = [&]()
{
parser.push_variable('a');
parser.push_operator("+");
parser.push_operator("+");
parser.push_variable('b');
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_variable('a'));
CHECK(parser.push_operator("+"));
CHECK_FALSE(parser.push_operator("+"));
}
SUBCASE("a +")
{
auto bad_parse = [&]()
{
parser.push_variable('a');
parser.push_operator("+");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_variable('a'));
CHECK(parser.push_operator("+"));
CHECK_FALSE(parser.finalize());
}
SUBCASE("a + )")
{
auto bad_parse = [&]()
{
parser.push_variable('a');
parser.push_operator("+");
parser.push_right_parenthesis();
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_variable('a'));
CHECK(parser.push_operator("+"));
CHECK_FALSE(parser.push_right_parenthesis());
}
SUBCASE("(((a)) + b (* c")
{
auto bad_parse = [&]()
{
parser.push_left_parenthesis();
parser.push_right_parenthesis();
parser.push_left_parenthesis();
parser.push_left_parenthesis();
parser.push_left_parenthesis();
parser.push_variable('a');
parser.push_right_parenthesis();
parser.push_right_parenthesis();
parser.push_operator("+");
parser.push_variable('b');
parser.push_left_parenthesis();
parser.push_operator("*");
parser.push_variable('c');
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable('a'));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_operator("+"));
CHECK(parser.push_variable('b'));
CHECK_FALSE(parser.push_left_parenthesis());
}
}
@ -404,15 +332,15 @@ TEST_SUITE("util::flat_bool_expr_tree")
// Infix: (false and false) or (false or (false or true))
// Postfix: false true or false or false false and or
auto parser = PostfixParser<bool, BoolOperator>{};
parser.push_variable(false);
parser.push_variable(true);
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(false);
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(false);
parser.push_variable(false);
parser.push_operator(BoolOperator::logical_and);
parser.push_operator(BoolOperator::logical_or);
CHECK(parser.push_variable(false));
CHECK(parser.push_variable(true));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(false));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(false));
CHECK(parser.push_variable(false));
CHECK(parser.push_operator(BoolOperator::logical_and));
CHECK(parser.push_operator(BoolOperator::logical_or));
auto tree = flat_bool_expr_tree(std::move(parser).tree());
SUBCASE("Empty")
@ -468,15 +396,15 @@ TEST_SUITE("util::flat_bool_expr_tree")
// Infix: ((x3 or x4) and x2) and (x0 or x1)
// Postfix: x0 x1 or x2 x3 x4 or and and
auto parser = PostfixParser<std::size_t, BoolOperator>{};
parser.push_variable(0);
parser.push_variable(1);
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(2);
parser.push_variable(3);
parser.push_variable(4);
parser.push_operator(BoolOperator::logical_or);
parser.push_operator(BoolOperator::logical_and);
parser.push_operator(BoolOperator::logical_and);
CHECK(parser.push_variable(0));
CHECK(parser.push_variable(1));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(2));
CHECK(parser.push_variable(3));
CHECK(parser.push_variable(4));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_operator(BoolOperator::logical_and));
CHECK(parser.push_operator(BoolOperator::logical_and));
auto tree = flat_bool_expr_tree(std::move(parser).tree());
static constexpr std::size_t n_vars = 5;
@ -494,26 +422,26 @@ TEST_SUITE("util::flat_bool_expr_tree")
{ return ((x[0] || x[1]) && (x[2] || x[3] || x[4]) && x[5]) || x[6]; };
auto parser = InfixParser<std::size_t, BoolOperator>{};
// Infix: ((x0 or x1) and (x2 or x3 or x4) and x5) or x6
parser.push_left_parenthesis();
parser.push_left_parenthesis();
parser.push_variable(0);
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(1);
parser.push_right_parenthesis();
parser.push_operator(BoolOperator::logical_and);
parser.push_left_parenthesis();
parser.push_variable(2);
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(3);
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(4);
parser.push_right_parenthesis();
parser.push_operator(BoolOperator::logical_and);
parser.push_variable(5);
parser.push_right_parenthesis();
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(6);
parser.finalize();
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable(0));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(1));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_operator(BoolOperator::logical_and));
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable(2));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(3));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(4));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_operator(BoolOperator::logical_and));
CHECK(parser.push_variable(5));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(6));
CHECK(parser.finalize());
auto tree = flat_bool_expr_tree(std::move(parser).tree());
static constexpr std::size_t n_vars = 7;
@ -530,26 +458,26 @@ TEST_SUITE("util::flat_bool_expr_tree")
{
auto parser = InfixParser<std::size_t, BoolOperator>{};
// Infix: ((x0 or x1) and (x2 or x3 or x4) and x5) or x6
parser.push_left_parenthesis();
parser.push_left_parenthesis();
parser.push_variable(0);
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(1);
parser.push_right_parenthesis();
parser.push_operator(BoolOperator::logical_and);
parser.push_left_parenthesis();
parser.push_variable(2);
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(3);
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(4);
parser.push_right_parenthesis();
parser.push_operator(BoolOperator::logical_and);
parser.push_variable(5);
parser.push_right_parenthesis();
parser.push_operator(BoolOperator::logical_or);
parser.push_variable(6);
parser.finalize();
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable(0));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(1));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_operator(BoolOperator::logical_and));
CHECK(parser.push_left_parenthesis());
CHECK(parser.push_variable(2));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(3));
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(4));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_operator(BoolOperator::logical_and));
CHECK(parser.push_variable(5));
CHECK(parser.push_right_parenthesis());
CHECK(parser.push_operator(BoolOperator::logical_or));
CHECK(parser.push_variable(6));
CHECK(parser.finalize());
auto tree = flat_bool_expr_tree(std::move(parser).tree());
auto result = std::string();

View File

@ -204,21 +204,12 @@ TEST_SUITE("util::URL")
{
SUBCASE("Empty")
{
const URL url = URL::parse("");
CHECK_EQ(url.scheme(), URL::https);
CHECK_EQ(url.host(), URL::localhost);
CHECK_EQ(url.path(), "/");
CHECK_EQ(url.pretty_path(), "/");
CHECK_EQ(url.user(), "");
CHECK_EQ(url.password(), "");
CHECK_EQ(url.port(), "");
CHECK_EQ(url.query(), "");
CHECK_EQ(url.fragment(), "");
CHECK_FALSE(URL::parse("").has_value());
}
SUBCASE("mamba.org")
{
const URL url = URL::parse("mamba.org");
const URL url = URL::parse("mamba.org").value();
CHECK_EQ(url.scheme(), URL::https);
CHECK_EQ(url.host(), "mamba.org");
CHECK_EQ(url.path(), "/");
@ -232,7 +223,7 @@ TEST_SUITE("util::URL")
SUBCASE("http://mamba.org")
{
const URL url = URL::parse("http://mamba.org");
const URL url = URL::parse("http://mamba.org").value();
CHECK_EQ(url.scheme(), "http");
CHECK_EQ(url.host(), "mamba.org");
CHECK_EQ(url.path(), "/");
@ -246,7 +237,7 @@ TEST_SUITE("util::URL")
SUBCASE("s3://userx123:üúßsajd@mamba.org")
{
const URL url = URL::parse("s3://userx123:üúßsajd@mamba.org");
const URL url = URL::parse("s3://userx123:üúßsajd@mamba.org").value();
CHECK_EQ(url.scheme(), "s3");
CHECK_EQ(url.host(), "mamba.org");
CHECK_EQ(url.path(), "/");
@ -260,7 +251,7 @@ TEST_SUITE("util::URL")
SUBCASE("http://user%40email.com:test@localhost:8000")
{
const URL url = URL::parse("http://user%40email.com:test@localhost:8000");
const URL url = URL::parse("http://user%40email.com:test@localhost:8000").value();
CHECK_EQ(url.scheme(), "http");
CHECK_EQ(url.host(), "localhost");
CHECK_EQ(url.path(), "/");
@ -276,15 +267,12 @@ TEST_SUITE("util::URL")
{
// Fails before "user@email.com" needs to be percent encoded, otherwise parsing is
// ill defined.
// Silencing [[nodiscard]] warning
auto failure = [](std::string_view str) { [[maybe_unused]] auto url = URL::parse(str); };
CHECK_THROWS_AS(failure("http://user@40email.com:test@localhost"), std::invalid_argument);
CHECK_FALSE(URL::parse("http://user@40email.com:test@localhost").has_value());
}
SUBCASE("http://:pass@localhost:8000")
{
const URL url = URL::parse("http://:pass@localhost:8000");
const URL url = URL::parse("http://:pass@localhost:8000").value();
CHECK_EQ(url.scheme(), "http");
CHECK_EQ(url.host(), "localhost");
CHECK_EQ(url.path(), "/");
@ -300,7 +288,8 @@ TEST_SUITE("util::URL")
{
// Not a valid IETF RFC 3986+ URL, but Curl parses it anyways.
// Undefined behavior, no assumptions are made
const URL url = URL::parse("https://mamba🆒🔬.org/this/is/a/path/?query=123&xyz=3333");
const URL url = URL::parse("https://mamba🆒🔬.org/this/is/a/path/?query=123&xyz=3333")
.value();
CHECK_NE(url.host(URL::Decode::no), "mamba%f0%9f%86%92%f0%9f%94%ac.org");
}
@ -308,7 +297,7 @@ TEST_SUITE("util::URL")
{
if (on_win)
{
const URL url = URL::parse("file://C:/Users/wolfv/test/document.json");
const URL url = URL::parse("file://C:/Users/wolfv/test/document.json").value();
CHECK_EQ(url.scheme(), "file");
CHECK_EQ(url.host(), "");
CHECK_EQ(url.path(), "/C:/Users/wolfv/test/document.json");
@ -324,7 +313,7 @@ TEST_SUITE("util::URL")
SUBCASE("file:///home/wolfv/test/document.json")
{
const URL url = URL::parse("file:///home/wolfv/test/document.json");
const URL url = URL::parse("file:///home/wolfv/test/document.json").value();
CHECK_EQ(url.scheme(), "file");
CHECK_EQ(url.host(), "");
CHECK_EQ(url.path(), "/home/wolfv/test/document.json");
@ -340,13 +329,13 @@ TEST_SUITE("util::URL")
{
// Not a valid IETF RFC 3986+ URL, but Curl parses it anyways.
// Undefined behavior, no assumptions are made
const URL url = URL::parse("file:///home/great:doc.json");
const URL url = URL::parse("file:///home/great:doc.json").value();
CHECK_NE(url.path(URL::Decode::no), "/home/great%3Adoc.json");
}
SUBCASE("file:///home/great%3Adoc.json")
{
const URL url = URL::parse("file:///home/great%3Adoc.json");
const URL url = URL::parse("file:///home/great%3Adoc.json").value();
CHECK_EQ(url.scheme(), "file");
CHECK_EQ(url.host(), "");
CHECK_EQ(url.path(), "/home/great:doc.json");
@ -361,7 +350,7 @@ TEST_SUITE("util::URL")
SUBCASE("https://169.254.0.0/page")
{
const URL url = URL::parse("https://169.254.0.0/page");
const URL url = URL::parse("https://169.254.0.0/page").value();
CHECK_EQ(url.scheme(), "https");
CHECK_EQ(url.host(), "169.254.0.0");
CHECK_EQ(url.path(), "/page");
@ -375,8 +364,8 @@ TEST_SUITE("util::URL")
SUBCASE("ftp://user:pass@[2001:db8:85a3:8d3:1319:0:370:7348]:9999/page")
{
const URL url = URL::parse("ftp://user:pass@[2001:db8:85a3:8d3:1319:0:370:7348]:9999/page"
);
const URL url = URL::parse("ftp://user:pass@[2001:db8:85a3:8d3:1319:0:370:7348]:9999/page")
.value();
CHECK_EQ(url.scheme(), "ftp");
CHECK_EQ(url.host(), "[2001:db8:85a3:8d3:1319:0:370:7348]");
CHECK_EQ(url.path(), "/page");
@ -390,7 +379,7 @@ TEST_SUITE("util::URL")
SUBCASE("https://mamba.org/page#the-fragment")
{
const URL url = URL::parse("https://mamba.org/page#the-fragment");
const URL url = URL::parse("https://mamba.org/page#the-fragment").value();
CHECK_EQ(url.scheme(), "https");
CHECK_EQ(url.host(), "mamba.org");
CHECK_EQ(url.path(), "/page");
@ -672,16 +661,19 @@ TEST_SUITE("util::URL")
TEST_CASE("Equality")
{
CHECK_EQ(URL(), URL());
CHECK_EQ(URL::parse("https://169.254.0.0/page"), URL::parse("https://169.254.0.0/page"));
CHECK_EQ(URL::parse("mamba.org"), URL::parse("mamba.org/"));
CHECK_EQ(URL::parse("mAmba.oRg"), URL::parse("mamba.org/"));
CHECK_EQ(URL::parse("localhost/page"), URL::parse("https://localhost/page"));
CHECK_EQ(
URL::parse("https://169.254.0.0/page").value(),
URL::parse("https://169.254.0.0/page").value()
);
CHECK_EQ(URL::parse("mamba.org").value(), URL::parse("mamba.org/").value());
CHECK_EQ(URL::parse("mAmba.oRg").value(), URL::parse("mamba.org/").value());
CHECK_EQ(URL::parse("localhost/page").value(), URL::parse("https://localhost/page").value());
CHECK_NE(URL::parse("mamba.org/page"), URL::parse("mamba.org/"));
CHECK_NE(URL::parse("mamba.org"), URL::parse("mamba.org:9999"));
CHECK_NE(URL::parse("user@mamba.org"), URL::parse("mamba.org"));
CHECK_NE(URL::parse("mamba.org/page"), URL::parse("mamba.org/page?q=v"));
CHECK_NE(URL::parse("mamba.org/page"), URL::parse("mamba.org/page#there"));
CHECK_NE(URL::parse("mamba.org/page").value(), URL::parse("mamba.org/").value());
CHECK_NE(URL::parse("mamba.org").value(), URL::parse("mamba.org:9999").value());
CHECK_NE(URL::parse("user@mamba.org").value(), URL::parse("mamba.org").value());
CHECK_NE(URL::parse("mamba.org/page").value(), URL::parse("mamba.org/page?q=v").value());
CHECK_NE(URL::parse("mamba.org/page").value(), URL::parse("mamba.org/page#there").value());
}
TEST_CASE("Append path")

View File

@ -4,6 +4,11 @@
//
// The full license is in the file LICENSE, distributed with this software.
#ifndef MAMBA_PY_EXPECTED_CASTER
#define MAMBA_PY_EXPECTED_CASTER
#include <exception>
#include <type_traits>
#include <utility>
#include <pybind11/cast.h>
@ -11,9 +16,6 @@
#include <pybind11/stl.h>
#include <tl/expected.hpp>
#ifndef MAMBA_PY_EXPECTED_CASTER
#define MAMBA_PY_EXPECTED_CASTER
namespace PYBIND11_NAMESPACE
{
namespace detail
@ -47,6 +49,10 @@ namespace PYBIND11_NAMESPACE
}
else
{
// If we use ``expected`` without exception in our API, we need to convert them
// to an exception before throwing it in PyBind11 code.
// This could be done with partial specialization of this ``type_caster``.
static_assert(std::is_base_of_v<std::exception, E>);
throw std::forward<Expected>(src).error();
}
}

View File

@ -20,6 +20,7 @@
#include "mamba/specs/version_spec.hpp"
#include "bindings.hpp"
#include "expected_caster.hpp"
#include "flat_set_caster.hpp"
#include "utils.hpp"
#include "weakening_map_bind.hpp"

View File

@ -36,7 +36,7 @@ read_stdin()
std::string
get_token_base(const std::string& host)
{
const auto url = mamba::util::URL::parse(host);
const auto url = mamba::util::URL::parse(host).value();
std::string maybe_colon_and_port{};
if (!url.port().empty())

View File

@ -52,7 +52,8 @@ namespace
[&](auto pkg)
{
if (!out
|| (specs::Version::parse(pkg.version) > specs::Version::parse(out->version)))
|| (specs::Version::parse(pkg.version).value_or(specs::Version())
> specs::Version::parse(out->version).value_or(specs::Version())))
{
out = std::move(pkg);
}