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/build_number_spec.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/channel.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/channel.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/conda_url.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/glob_spec.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/match_spec.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/match_spec.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/package_info.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/package_info.hpp

View File

@ -14,6 +14,8 @@
#include <fmt/format.h> #include <fmt/format.h>
#include "mamba/specs/error.hpp"
namespace mamba::specs namespace mamba::specs
{ {
/** /**
@ -97,7 +99,7 @@ namespace mamba::specs
static constexpr std::string_view less_str = "<"; static constexpr std::string_view less_str = "<";
static constexpr std::string_view less_equal_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. */ /** Construct BuildNumberSpec that match all versions. */
BuildNumberSpec(); 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: public:
inline static constexpr std::string_view free_pattern = "*"; inline static constexpr std::string_view free_pattern = "*";
inline static constexpr char glob_pattern = '*';
GlobSpec() = default; GlobSpec() = default;
explicit GlobSpec(std::string pattern); explicit GlobSpec(std::string pattern);

View File

@ -14,6 +14,7 @@
#include <fmt/core.h> #include <fmt/core.h>
#include <fmt/format.h> #include <fmt/format.h>
#include "mamba/specs/error.hpp"
#include "mamba/util/flat_set.hpp" #include "mamba/util/flat_set.hpp"
namespace mamba::specs namespace mamba::specs
@ -88,7 +89,7 @@ namespace mamba::specs
using dynamic_platform_set = util::flat_set<std::string>; 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() = default;
UnresolvedChannel(std::string location, dynamic_platform_set filters, Type type); UnresolvedChannel(std::string location, dynamic_platform_set filters, Type type);

View File

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

View File

@ -14,6 +14,7 @@
#include <fmt/format.h> #include <fmt/format.h>
#include "mamba/specs/error.hpp"
#include "mamba/specs/version.hpp" #include "mamba/specs/version.hpp"
#include "mamba/util/flat_bool_expr_tree.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 std::string_view glob_suffix_str = ".*";
static constexpr char glob_suffix_token = '*'; 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. */ /** Construct VersionSpec that match all versions. */
VersionSpec() = default; VersionSpec() = default;

View File

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

View File

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

View File

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

View File

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

View File

@ -67,7 +67,10 @@ namespace mamba
out.set_name(specs::MatchSpec::NameSpec(pkg.name)); out.set_name(specs::MatchSpec::NameSpec(pkg.name));
if (!pkg.version.empty()) 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()) if (!pkg.build_string.empty())
{ {

View File

@ -1568,7 +1568,7 @@ namespace mamba
return std::nullopt; 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 scheme = url_parsed.scheme();
auto host = url_parsed.host(); auto host = url_parsed.host();
std::vector<std::string> options; std::vector<std::string> options;

View File

@ -323,7 +323,7 @@ namespace mamba::download
p_handle->add_header(user_agent); p_handle->add_header(user_agent);
// get url host // 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(); auto host = url_handler.host();
const auto port = url_handler.port(); const auto port = url_handler.port();
if (port.size()) if (port.size())

View File

@ -1059,10 +1059,10 @@ namespace mamba::solver::libsolv
{ {
if (auto newer = find_new_python_in_solution(solution)) if (auto newer = find_new_python_in_solution(solution))
{ {
return !python_binary_compatible( auto installed_ver = specs::Version::parse(installed->version());
specs::Version::parse(installed->version()), auto newer_ver = specs::Version::parse(newer->get().version);
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; return false;
@ -1182,8 +1182,31 @@ namespace mamba::solver::libsolv
} }
auto ms_modified = ms; auto ms_modified = ms;
ms_modified.set_channel(specs::UnresolvedChannel::parse(solvable->channel())); auto unresolved_chan = specs::UnresolvedChannel::parse(solvable->channel());
ms_modified.set_version(specs::VersionSpec::parse(solvable->version())); 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()))); ms_modified.set_build_string(specs::GlobSpec(std::string(solvable->build_string())));
LOG_INFO << "Reinstall " << ms_modified.conda_build_form() << " from channel " 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 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); str = util::strip(str);
if (std::find(all_free_strs.cbegin(), all_free_strs.cend(), str) != all_free_strs.cend()) 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()))) 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) 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)); 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() BuildNumberSpec::BuildNumberSpec()
@ -245,7 +245,9 @@ namespace mamba::specs
{ {
auto operator""_bs(const char* str, std::size_t len) -> BuildNumberSpec 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/archive.hpp"
#include "mamba/specs/conda_url.hpp" #include "mamba/specs/conda_url.hpp"
#include "mamba/specs/error.hpp"
#include "mamba/util/encoding.hpp" #include "mamba/util/encoding.hpp"
#include "mamba/util/string.hpp" #include "mamba/util/string.hpp"
@ -120,7 +121,14 @@ namespace mamba::specs
auto CondaURL::parse(std::string_view url) -> CondaURL 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) 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 auto GlobSpec::contains(std::string_view str) const -> bool
{ {
// is_free is not required but returns faster in the default case. // 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 auto GlobSpec::is_free() const -> bool
@ -35,7 +35,7 @@ namespace mamba::specs
auto GlobSpec::is_exact() const -> bool 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& auto GlobSpec::str() const -> const std::string&

View File

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

View File

@ -68,7 +68,7 @@ namespace mamba::specs
} }
auto split_location_platform(std::string_view str) 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, ']')) if (util::ends_with(str, ']'))
{ {
@ -76,10 +76,14 @@ namespace mamba::specs
const auto start_pos = str.find_last_of('['); const auto start_pos = str.find_last_of('[');
if ((start_pos != std::string_view::npos) && (start_pos != 0)) if ((start_pos != std::string_view::npos) && (start_pos != 0))
{ {
return { return { {
std::string(util::rstrip(str.substr(0, start_pos))), std::string(util::rstrip(str.substr(0, start_pos))),
parse_platform_list(str.substr(start_pos + 1, str.size() - start_pos - 2)), 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()) if (!plat.empty())
{ {
rest = util::rstrip(rest, '/'); rest = util::rstrip(rest, '/');
return { return { {
std::move(rest), std::move(rest),
{ std::move(plat) }, { std::move(plat) },
}; } };
} }
} }
// For single archive channel specs, we don't need to compute platform filters // For single archive channel specs, we don't need to compute platform filters
// since they are not needed to compute URLs. // 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 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); str = util::strip(str);
if (is_unknown_channel(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); const std::string_view scheme = util::url_get_scheme(location);
Type type = {}; Type type = {};
@ -152,7 +168,7 @@ namespace mamba::specs
type = Type::Name; 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) UnresolvedChannel::UnresolvedChannel(std::string location, dynamic_platform_set filters, Type type)

View File

@ -534,17 +534,18 @@ namespace mamba::specs
} }
template <typename Int> 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); const auto delim_pos = str.find(Version::epoch_delim);
// No epoch is specified // No epoch is specified
if (delim_pos == std::string_view::npos) // TODO(C++20) [[likely]] if (delim_pos == std::string_view::npos) // TODO(C++20) [[likely]]
{ {
return { Int(0), str }; return { { Int(0), str } };
} }
if (delim_pos == 0) if (delim_pos == 0)
{ {
throw std::invalid_argument( return make_unexpected_parse(
fmt::format("Empty epoch delimited by '{}'.", Version::epoch_delim) fmt::format("Empty epoch delimited by '{}'.", Version::epoch_delim)
); );
} }
@ -554,12 +555,12 @@ namespace mamba::specs
// Epoch is not a number (or empty) // Epoch is not a number (or empty)
if (!maybe_int.has_value()) if (!maybe_int.has_value())
{ {
throw std::invalid_argument( return make_unexpected_parse(
fmt::format("Epoch should be a number, got '{}'.", epoch_str) fmt::format("Epoch should be a number, got '{}'.", epoch_str)
); );
} }
// Found an epoch // Found an epoch
return { maybe_int.value(), str.substr(delim_pos + 1) }; return { { maybe_int.value(), str.substr(delim_pos + 1) } };
} }
template <typename Int> template <typename Int>
@ -617,7 +618,7 @@ namespace mamba::specs
return atoms; 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. // `_` and `-` delimiter cannot be used together.
// Special meaning for `_` at the end of the string. // 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) && (str.find(Version::part_delim_special) < str.size() - 1)) // TODO(C++20)
// [[unlikely]] // [[unlikely]]
{ {
throw std::invalid_argument(fmt::format( return make_unexpected_parse(fmt::format(
"Cannot use both '{}' and '{}' delimiters in {}'.", "Cannot use both '{}' and '{}' delimiters in {}'.",
Version::part_delim_alt, Version::part_delim_alt,
Version::part_delim_special, Version::part_delim_special,
@ -643,16 +644,20 @@ namespace mamba::specs
}; };
if (std::find_if_not(str.cbegin(), str.cend(), allowed_char) != str.cend()) 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) 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()); 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{ static constexpr auto delims_buf = std::array{
Version::part_delim, Version::part_delim,
@ -678,7 +683,7 @@ namespace mamba::specs
if ((tail_delim_pos == 0) || (tail_delim_pos == tail.size() - 1)) // TODO(C++20) if ((tail_delim_pos == 0) || (tail_delim_pos == tail.size() - 1)) // TODO(C++20)
// [[unlikely]] // [[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))); parts.push_back(parse_part(tail.substr(0, tail_delim_pos)));
if (tail_delim_pos == std::string_view::npos) if (tail_delim_pos == std::string_view::npos)
@ -687,63 +692,86 @@ namespace mamba::specs
} }
tail = tail.substr(tail_delim_pos + 1); tail = tail.substr(tail_delim_pos + 1);
} }
return parts; return { std::move(parts) };
} }
auto parse_trailing_local_version(std::string_view str) 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); const auto delim_pos = str.rfind(Version::local_delim);
// No local is specified // No local is specified
if (delim_pos == std::string_view::npos) // TODO(C++20) [[likely]] if (delim_pos == std::string_view::npos) // TODO(C++20) [[likely]]
{ {
return { str, {} }; return { { str, {} } };
} }
// local specified but empty // local specified but empty
if (delim_pos + 1 == str.size()) if (delim_pos + 1 == str.size())
{ {
throw std::invalid_argument( return make_unexpected_parse(
fmt::format("Empty local version delimited by '{}'.", Version::local_delim) 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()) if (str.empty())
{ {
throw std::invalid_argument("Empty version."); return make_unexpected_parse("Empty version.");
} }
return parse_common_version(str); 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); 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); return make_unexpected_parse(
auto [version_str, local] = parse_trailing_local_version(version_and_local_str); fmt::format(R"(Error parsing version "{}". {})", str, error.what())
auto version = parse_version(version_str); );
return { };
/* .epoch= */ epoch,
/* .version= */ std::move(version), auto epoch_rest = parse_leading_epoch<std::size_t>(str);
/* .local= */ std::move(local), if (!epoch_rest)
};
}
catch (const std::invalid_argument& ia)
{ {
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 namespace version_literals
{ {
auto operator""_v(const char* str, std::size_t len) -> Version 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 <algorithm>
#include <array> #include <array>
#include <stdexcept>
#include <type_traits> #include <type_traits>
#include <fmt/format.h> #include <fmt/format.h>
@ -320,7 +319,7 @@ namespace mamba::specs
return std::find(range.cbegin(), range.cend(), val) != range.cend(); 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); str = util::strip(str);
// WARNING order is important since some operator are prefix of others. // 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)) if (util::starts_with(str, VersionSpec::greater_equal_str))
{ {
return VersionPredicate::make_greater_equal( return Version::parse(str.substr(VersionSpec::greater_equal_str.size()))
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)) if (util::starts_with(str, VersionSpec::greater_str))
{ {
return VersionPredicate::make_greater( return Version::parse(str.substr(VersionSpec::greater_str.size()))
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)) if (util::starts_with(str, VersionSpec::less_equal_str))
{ {
return VersionPredicate::make_less_equal( return Version::parse(str.substr(VersionSpec::less_equal_str.size()))
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)) if (util::starts_with(str, VersionSpec::less_str))
{ {
return VersionPredicate::make_less( return Version::parse(str.substr(VersionSpec::less_str.size()))
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)) if (util::starts_with(str, VersionSpec::compatible_str))
{ {
auto ver = Version::parse(str.substr(VersionSpec::compatible_str.size())); return Version::parse(str.substr(VersionSpec::compatible_str.size()))
// in ``~=1.1`` level is assumed to be 1, in ``~=1.1.1`` level 2, etc. .transform(
static constexpr auto one = std::size_t(1); // MSVC [](specs::Version&& ver)
const std::size_t level = std::max(ver.version().size(), one) - one; {
return VersionPredicate::make_compatible_with(std::move(ver), level); // 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 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(); 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.* // Glob suffix changes meaning for ==1.3.*
if (has_glob_suffix) if (has_glob_suffix)
{ {
return VersionPredicate::make_starts_with( return Version::parse(str.substr(VersionSpec::less_equal_str.size()))
Version::parse(str.substr(start, str.size() - glob_len - start)) .transform([](specs::Version&& ver)
); { return VersionPredicate::make_starts_with(std::move(ver)); });
} }
else 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)) if (util::starts_with(str, VersionSpec::not_equal_str))
@ -383,22 +389,25 @@ namespace mamba::specs
// Glob suffix changes meaning for !=1.3.* // Glob suffix changes meaning for !=1.3.*
if (has_glob_suffix) if (has_glob_suffix)
{ {
return VersionPredicate::make_not_starts_with( return Version::parse(str.substr(start, str.size() - glob_len - start))
Version::parse(str.substr(start, str.size() - glob_len - start)) .transform([](specs::Version&& ver)
); { return VersionPredicate::make_not_starts_with(std::move(ver)); }
);
} }
else 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)) if (util::starts_with(str, VersionSpec::starts_with_str))
{ {
const std::size_t start = VersionSpec::starts_with_str.size(); const std::size_t start = VersionSpec::starts_with_str.size();
// Glob suffix does not change meaning for =1.3.* // Glob suffix does not change meaning for =1.3.*
return VersionPredicate::make_starts_with( return Version::parse(str.substr(start, str.size() - glob_len - start))
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 if (util::is_digit(str.front())) // All versions must start with a digit
{ {
@ -408,19 +417,25 @@ namespace mamba::specs
// either ".*" or "*" // either ".*" or "*"
static constexpr auto one = std::size_t(1); // MSVC static constexpr auto one = std::size_t(1); // MSVC
const std::size_t len = str.size() - std::max(glob_len, one); 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 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{ static constexpr auto all_tokens = std::array{
VersionSpec::and_token, VersionSpec::and_token,
@ -447,40 +462,95 @@ namespace mamba::specs
{ {
if (str.front() == VersionSpec::and_token) 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); str = str.substr(1);
} }
else if (str.front() == VersionSpec::or_token) 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); str = str.substr(1);
} }
else if (str.front() == VersionSpec::left_parenthesis_token) 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)); str = util::lstrip(str.substr(1));
} }
else if (str.front() == VersionSpec::right_parenthesis_token) 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)); str = util::lstrip(str.substr(1));
} }
else else
{ {
auto [op_ver, rest] = util::lstrip_if_parts(str, [&](auto c) { return !is_token(c); }); 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; 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 namespace version_spec_literals
{ {
auto operator""_vs(const char* str, std::size_t len) -> VersionSpec 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. // The full license is in the file LICENSE, distributed with this software.
#include <cassert> #include <cassert>
#include <memory>
#include <optional> #include <optional>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
@ -37,7 +38,7 @@ namespace mamba::util
* *
* Never null, throw exception at construction if creating the handle fails. * Never null, throw exception at construction if creating the handle fails.
*/ */
class CURLUrl class CurlUrl
{ {
public: public:
@ -46,21 +47,22 @@ namespace mamba::util
using const_pointer = const value_type*; using const_pointer = const value_type*;
using flag_type = unsigned int; using flag_type = unsigned int;
CURLUrl(); static auto parse(const std::string& url, flag_type flags = 0)
CURLUrl(const std::string& url, flag_type flags = 0); -> tl::expected<CurlUrl, URL::ParseError>;
~CURLUrl();
CURLUrl(const CURLUrl&) = delete; CurlUrl();
CURLUrl& operator=(const CURLUrl&) = delete;
CURLUrl(CURLUrl&&) = delete;
CURLUrl& operator=(CURLUrl&&) = delete;
[[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>; -> std::optional<std::string>;
private: 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. * String can possibly be null, or zero-lenght, depending on the data returned by CURL.
*/ */
class CURLStr class CurlStr
{ {
using value_type = char; using value_type = char;
using pointer = value_type*; using pointer = value_type*;
@ -78,13 +80,13 @@ namespace mamba::util
public: public:
explicit CURLStr() = default; explicit CurlStr() = default;
~CURLStr(); ~CurlStr();
CURLStr(const CURLStr&) = delete; CurlStr(const CurlStr&) = delete;
CURLStr& operator=(const CURLStr&) = delete; auto operator=(const CurlStr&) -> CurlStr& = delete;
CURLStr(CURLStr&&) = delete; CurlStr(CurlStr&&) = delete;
CURLStr& operator=(CURLStr&&) = delete; auto operator=(CurlStr&&) -> CurlStr& = delete;
[[nodiscard]] auto raw_input() -> input_pointer; [[nodiscard]] auto raw_input() -> input_pointer;
@ -97,36 +99,40 @@ namespace mamba::util
size_type m_len = { -1 }; 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(); auto out = CurlUrl();
if (m_handle == nullptr) const CURLUcode uc = ::curl_url_set(out.m_handle.get(), CURLUPART_URL, url.c_str(), flags);
{
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);
if (uc != CURLUE_OK) if (uc != CURLUE_OK)
{ {
throw std::invalid_argument( return tl::make_unexpected(URL::ParseError{
fmt::format(R"(Failed to parse URL "{}": {})", url, ::curl_url_strerror(uc)) 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{}; CurlStr scheme{};
const auto rc = ::curl_url_get(m_handle, part, scheme.raw_input(), flags); const auto rc = ::curl_url_get(m_handle.get(), part, scheme.raw_input(), flags);
if (!rc) if (!rc)
{ {
if (auto str = scheme.str()) if (auto str = scheme.str())
@ -137,7 +143,7 @@ namespace mamba::util
return std::nullopt; return std::nullopt;
} }
CURLStr::~CURLStr() CurlStr::~CurlStr()
{ {
// Even when Curl returns a len along side the data, `curl_free` must be used without // Even when Curl returns a len along side the data, `curl_free` must be used without
// len. // len.
@ -145,13 +151,13 @@ namespace mamba::util
m_data = nullptr; m_data = nullptr;
} }
auto CURLStr::raw_input() -> input_pointer auto CurlStr::raw_input() -> input_pointer
{ {
assert(m_data == nullptr); // Otherwise we leak Curl memory assert(m_data == nullptr); // Otherwise we leak Curl memory
return &m_data; return &m_data;
} }
auto CURLStr::str() const -> std::optional<std::string_view> auto CurlStr::str() const -> std::optional<std::string_view>
{ {
if (m_data) if (m_data)
{ {
@ -172,28 +178,31 @@ namespace mamba::util
* URLHandler implementation * * 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); 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 return tl::make_unexpected(ParseError{ "Empty URL" });
// 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 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 auto URL::scheme_is_defaulted() const -> bool

View File

@ -360,7 +360,7 @@ namespace mamba::validation::v0_6
auto tmp_dir = TemporaryDirectory(); auto tmp_dir = TemporaryDirectory();
auto tmp_metadata_path = tmp_dir.path() / "pkg_mgr.json"; 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)) 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) for (const auto& spec : bad_specs)
{ {
CAPTURE(spec); CAPTURE(spec);
// Silence [[nodiscard]] warning CHECK_FALSE(BuildNumberSpec::parse(spec).has_value());
auto parse = [](auto s) { return BuildNumberSpec::parse(s); };
CHECK_THROWS_AS(parse(spec), std::invalid_argument);
} }
} }
} }
TEST_CASE("BuildNumberSepc::str") TEST_CASE("BuildNumberSepc::str")
{ {
CHECK_EQ(BuildNumberSpec::parse("=3").str(), "=3"); CHECK_EQ(BuildNumberSpec::parse("=3").value().str(), "=3");
CHECK_EQ(BuildNumberSpec::parse("<2").str(), "<2"); CHECK_EQ(BuildNumberSpec::parse("<2").value().str(), "<2");
CHECK_EQ(BuildNumberSpec::parse("*").str(), "=*"); CHECK_EQ(BuildNumberSpec::parse("*").value().str(), "=*");
} }
TEST_CASE("BuildNumberSepc::is_explicitly_free") TEST_CASE("BuildNumberSepc::is_explicitly_free")
{ {
CHECK(BuildNumberSpec::parse("*").is_explicitly_free()); CHECK(BuildNumberSpec::parse("*").value().is_explicitly_free());
CHECK_FALSE(BuildNumberSpec::parse("=3").is_explicitly_free()); CHECK_FALSE(BuildNumberSpec::parse("=3").value().is_explicitly_free());
CHECK_FALSE(BuildNumberSpec::parse("<2").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_typical_params = []() -> ChannelResolveParams
{ {
auto make_channel = [](std::string_view loc, ChannelResolveParams const& params) 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{}; auto params = ChannelResolveParams{};
params.platforms = { "linux-64", "noarch" }; params.platforms = { "linux-64", "noarch" };
@ -325,7 +325,8 @@ TEST_SUITE("specs::channel")
}; };
params.custom_channels.emplace( params.custom_channels.emplace(
"mychan", "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"); CHECK_EQ(Channel::resolve(uc, params).at(0).display_name(), "mychan");
} }
@ -711,7 +712,8 @@ TEST_SUITE("specs::channel")
auto params = make_typical_params(); auto params = make_typical_params();
params.custom_channels.emplace( params.custom_channels.emplace(
"conda-forge", "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); auto channels = Channel::resolve(uc, params);
@ -783,7 +785,7 @@ TEST_SUITE("specs::channel")
params.custom_channels.emplace( params.custom_channels.emplace(
"testchannel", "testchannel",
Channel::resolve( Channel::resolve(
UnresolvedChannel::parse("https://server.com/private/testchannel"), UnresolvedChannel::parse("https://server.com/private/testchannel").value(),
params params
) )
.at(0) .at(0)
@ -812,7 +814,8 @@ TEST_SUITE("specs::channel")
}; };
params.custom_channels.emplace( params.custom_channels.emplace(
"prefix", "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); auto channels = Channel::resolve(uc, params);

View File

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

View File

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

View File

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

View File

@ -5,7 +5,6 @@
// The full license is in the file LICENSE, distributed with this software. // The full license is in the file LICENSE, distributed with this software.
#include <array> #include <array>
#include <stdexcept>
#include <string_view> #include <string_view>
#include <doctest/doctest.h> #include <doctest/doctest.h>
@ -16,13 +15,16 @@ using namespace mamba::specs;
TEST_SUITE("specs::version_spec") TEST_SUITE("specs::version_spec")
{ {
using namespace mamba::specs::version_literals;
using namespace mamba::specs::version_spec_literals;
TEST_CASE("VersionPredicate") TEST_CASE("VersionPredicate")
{ {
const auto v1 = Version::parse("1.0"); const auto v1 = "1.0"_v;
const auto v2 = Version::parse("2.0"); const auto v2 = "2.0"_v;
const auto v201 = Version::parse("2.0.1"); const auto v201 = "2.0.1"_v;
const auto v3 = Version::parse("3.0"); const auto v3 = "3.0"_v;
const auto v4 = Version::parse("4.0"); const auto v4 = "4.0"_v;
const auto free = VersionPredicate::make_free(); const auto free = VersionPredicate::make_free();
CHECK(free.contains(v1)); CHECK(free.contains(v1));
@ -135,14 +137,14 @@ TEST_SUITE("specs::version_spec")
const auto v28 = Version(0, { { { 2 } }, { { 8 } }, { { 0 } } }); const auto v28 = Version(0, { { { 2 } }, { { 8 } }, { { 0 } } });
auto parser = InfixParser<VersionPredicate, BoolOperator>{}; auto parser = InfixParser<VersionPredicate, BoolOperator>{};
parser.push_variable(VersionPredicate::make_less(v20)); REQUIRE(parser.push_variable(VersionPredicate::make_less(v20)));
parser.push_operator(BoolOperator::logical_or); REQUIRE(parser.push_operator(BoolOperator::logical_or));
parser.push_left_parenthesis(); REQUIRE(parser.push_left_parenthesis());
parser.push_variable(VersionPredicate::make_greater(v23)); REQUIRE(parser.push_variable(VersionPredicate::make_greater(v23)));
parser.push_operator(BoolOperator::logical_and); REQUIRE(parser.push_operator(BoolOperator::logical_and));
parser.push_variable(VersionPredicate::make_less_equal(v28)); REQUIRE(parser.push_variable(VersionPredicate::make_less_equal(v28)));
parser.push_right_parenthesis(); REQUIRE(parser.push_right_parenthesis());
parser.finalize(); REQUIRE(parser.finalize());
auto spec = VersionSpec(std::move(parser).tree()); auto spec = VersionSpec(std::move(parser).tree());
@ -162,9 +164,6 @@ TEST_SUITE("specs::version_spec")
TEST_CASE("VersionSpec::parse") TEST_CASE("VersionSpec::parse")
{ {
using namespace mamba::specs::version_literals;
using namespace mamba::specs::version_spec_literals;
SUBCASE("Successful") SUBCASE("Successful")
{ {
CHECK(""_vs.contains("1.6"_v)); CHECK(""_vs.contains("1.6"_v));
@ -392,9 +391,7 @@ TEST_SUITE("specs::version_spec")
for (const auto& spec : bad_specs) for (const auto& spec : bad_specs)
{ {
CAPTURE(spec); CAPTURE(spec);
// Silence [[nodiscard]] warning CHECK_FALSE(VersionSpec::parse(spec).has_value());
auto parse = [](auto s) { return VersionSpec::parse(s); };
CHECK_THROWS_AS(parse(spec), std::invalid_argument);
} }
} }
} }
@ -403,14 +400,14 @@ TEST_SUITE("specs::version_spec")
{ {
SUBCASE("2.3") 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(), "==2.3");
CHECK_EQ(vs.str_conda_build(), "==2.3"); CHECK_EQ(vs.str_conda_build(), "==2.3");
} }
SUBCASE("=2.3,<3.0") 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(), "=2.3,<3.0");
CHECK_EQ(vs.str_conda_build(), "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; using namespace mamba::util;
auto parser = InfixParser<VersionPredicate, BoolOperator>{}; auto parser = InfixParser<VersionPredicate, BoolOperator>{};
parser.push_variable(VersionPredicate::make_free()); REQUIRE(parser.push_variable(VersionPredicate::make_free()));
parser.finalize(); REQUIRE(parser.finalize());
auto spec = VersionSpec(std::move(parser).tree()); auto spec = VersionSpec(std::move(parser).tree());
CHECK(spec.is_explicitly_free()); CHECK(spec.is_explicitly_free());
} }
CHECK(VersionSpec().is_explicitly_free()); CHECK(VersionSpec().is_explicitly_free());
CHECK(VersionSpec::parse("*").is_explicitly_free()); CHECK(VersionSpec::parse("*").value().is_explicitly_free());
CHECK(VersionSpec::parse("").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|!=2.3").value().is_explicitly_free());
CHECK_FALSE(VersionSpec::parse("=2.3,<3.0").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") SUBCASE("empty")
{ {
parser.finalize(); CHECK(parser.finalize());
const auto& tree = parser.tree(); const auto& tree = parser.tree();
CHECK(tree.empty()); CHECK(tree.empty());
} }
SUBCASE("a") SUBCASE("a")
{ {
parser.push_variable('a'); CHECK(parser.push_variable('a'));
parser.finalize(); CHECK(parser.finalize());
const auto& tree = parser.tree(); const auto& tree = parser.tree();
CHECK_EQ(tree.size(), 1); CHECK_EQ(tree.size(), 1);
@ -129,16 +129,16 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("a b + c d e + * *") SUBCASE("a b + c d e + * *")
{ {
// Infix: (a + b) * (c * (d + e)) // Infix: (a + b) * (c * (d + e))
parser.push_variable('a'); CHECK(parser.push_variable('a'));
parser.push_variable('b'); CHECK(parser.push_variable('b'));
parser.push_operator("+"); CHECK(parser.push_operator("+"));
parser.push_variable('c'); CHECK(parser.push_variable('c'));
parser.push_variable('d'); CHECK(parser.push_variable('d'));
parser.push_variable('e'); CHECK(parser.push_variable('e'));
parser.push_operator("+"); CHECK(parser.push_operator("+"));
parser.push_operator("*"); CHECK(parser.push_operator("*"));
parser.push_operator("*"); CHECK(parser.push_operator("*"));
parser.finalize(); CHECK(parser.finalize());
const auto& tree = parser.tree(); const auto& tree = parser.tree();
CHECK_EQ(tree.size(), 9); CHECK_EQ(tree.size(), 9);
@ -149,49 +149,31 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("a b") SUBCASE("a b")
{ {
auto bad_parse = [&]() CHECK(parser.push_variable('a'));
{ CHECK(parser.push_variable('b'));
parser.push_variable('a'); CHECK_FALSE(parser.finalize());
parser.push_variable('b');
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("+") SUBCASE("+")
{ {
auto bad_parse = [&]() CHECK_FALSE(parser.push_operator("+"));
{
parser.push_operator("+");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("a b + *") SUBCASE("a b + *")
{ {
auto bad_parse = [&]() CHECK(parser.push_variable('a'));
{ CHECK(parser.push_variable('b'));
parser.push_variable('a'); CHECK(parser.push_operator("+"));
parser.push_variable('b'); CHECK_FALSE(parser.push_operator("*"));
parser.push_operator("+");
parser.push_operator("*");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("a b + c") SUBCASE("a b + c")
{ {
auto bad_parse = [&]() CHECK(parser.push_variable('a'));
{ CHECK(parser.push_variable('b'));
parser.push_variable('a'); CHECK(parser.push_operator("+"));
parser.push_variable('b'); CHECK(parser.push_variable('c'));
parser.push_operator("+"); CHECK_FALSE(parser.finalize());
parser.push_variable('c');
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
} }
@ -201,21 +183,21 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("empty") SUBCASE("empty")
{ {
parser.finalize(); CHECK(parser.finalize());
const auto& tree = parser.tree(); const auto& tree = parser.tree();
CHECK(tree.empty()); CHECK(tree.empty());
} }
SUBCASE("(((a)))") SUBCASE("(((a)))")
{ {
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_variable('a'); CHECK(parser.push_variable('a'));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.finalize(); CHECK(parser.finalize());
const auto& tree = parser.tree(); const auto& tree = parser.tree();
REQUIRE_EQ(tree.size(), 1); REQUIRE_EQ(tree.size(), 1);
@ -226,16 +208,16 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("(((a)) + b)") SUBCASE("(((a)) + b)")
{ {
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_variable('a'); CHECK(parser.push_variable('a'));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_operator("+"); CHECK(parser.push_operator("+"));
parser.push_variable('b'); CHECK(parser.push_variable('b'));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.finalize(); CHECK(parser.finalize());
const auto& tree = parser.tree(); const auto& tree = parser.tree();
REQUIRE_EQ(tree.size(), 3); REQUIRE_EQ(tree.size(), 3);
@ -250,22 +232,22 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("(a + b) * (c * (d + e))") SUBCASE("(a + b) * (c * (d + e))")
{ {
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_variable('a'); CHECK(parser.push_variable('a'));
parser.push_operator("+"); CHECK(parser.push_operator("+"));
parser.push_variable('b'); CHECK(parser.push_variable('b'));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_operator("*"); CHECK(parser.push_operator("*"));
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_variable('c'); CHECK(parser.push_variable('c'));
parser.push_operator("*"); CHECK(parser.push_operator("*"));
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_variable('d'); CHECK(parser.push_variable('d'));
parser.push_operator("+"); CHECK(parser.push_operator("+"));
parser.push_variable('e'); CHECK(parser.push_variable('e'));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.finalize(); CHECK(parser.finalize());
const auto& tree = parser.tree(); const auto& tree = parser.tree();
CHECK_EQ(tree.size(), 9); CHECK_EQ(tree.size(), 9);
@ -276,126 +258,72 @@ TEST_SUITE("util::flat_bool_expr_tree")
SUBCASE("(") SUBCASE("(")
{ {
auto bad_parse = [&]() CHECK(parser.push_left_parenthesis());
{ CHECK_FALSE(parser.finalize());
parser.push_left_parenthesis();
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE(")") SUBCASE(")")
{ {
auto bad_parse = [&]() CHECK_FALSE(parser.push_right_parenthesis());
{
parser.push_right_parenthesis();
// parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("(a+b") SUBCASE("(a+b")
{ {
auto bad_parse = [&]() CHECK(parser.push_left_parenthesis());
{ CHECK(parser.push_variable('a'));
parser.push_left_parenthesis(); CHECK(parser.push_operator("+"));
parser.push_variable('a'); CHECK(parser.push_variable('b'));
parser.push_operator("+"); CHECK_FALSE(parser.finalize());
parser.push_variable('b');
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("a))") SUBCASE("a)")
{ {
auto bad_parse = [&]() CHECK(parser.push_variable('a'));
{ CHECK_FALSE(parser.push_right_parenthesis());
parser.push_variable('a');
parser.push_right_parenthesis();
parser.push_right_parenthesis();
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("+") SUBCASE("+")
{ {
auto bad_parse = [&]() CHECK_FALSE(parser.push_operator("+"));
{
parser.push_operator("+");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("a b +") SUBCASE("a b +")
{ {
auto bad_parse = [&]() CHECK(parser.push_variable('a'));
{ CHECK_FALSE(parser.push_variable('b'));
parser.push_variable('a');
parser.push_variable('b');
parser.push_operator("+");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("a + + b") SUBCASE("a + + b")
{ {
auto bad_parse = [&]() CHECK(parser.push_variable('a'));
{ CHECK(parser.push_operator("+"));
parser.push_variable('a'); CHECK_FALSE(parser.push_operator("+"));
parser.push_operator("+");
parser.push_operator("+");
parser.push_variable('b');
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("a +") SUBCASE("a +")
{ {
auto bad_parse = [&]() CHECK(parser.push_variable('a'));
{ CHECK(parser.push_operator("+"));
parser.push_variable('a'); CHECK_FALSE(parser.finalize());
parser.push_operator("+");
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("a + )") SUBCASE("a + )")
{ {
auto bad_parse = [&]() CHECK(parser.push_variable('a'));
{ CHECK(parser.push_operator("+"));
parser.push_variable('a'); CHECK_FALSE(parser.push_right_parenthesis());
parser.push_operator("+");
parser.push_right_parenthesis();
parser.finalize();
};
CHECK_THROWS_AS(bad_parse(), std::invalid_argument);
} }
SUBCASE("(((a)) + b (* c") SUBCASE("(((a)) + b (* c")
{ {
auto bad_parse = [&]() CHECK(parser.push_left_parenthesis());
{ CHECK(parser.push_left_parenthesis());
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_right_parenthesis(); CHECK(parser.push_variable('a'));
parser.push_left_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_left_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_left_parenthesis(); CHECK(parser.push_operator("+"));
parser.push_variable('a'); CHECK(parser.push_variable('b'));
parser.push_right_parenthesis(); CHECK_FALSE(parser.push_left_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);
} }
} }
@ -404,15 +332,15 @@ TEST_SUITE("util::flat_bool_expr_tree")
// Infix: (false and false) or (false or (false or true)) // Infix: (false and false) or (false or (false or true))
// Postfix: false true or false or false false and or // Postfix: false true or false or false false and or
auto parser = PostfixParser<bool, BoolOperator>{}; auto parser = PostfixParser<bool, BoolOperator>{};
parser.push_variable(false); CHECK(parser.push_variable(false));
parser.push_variable(true); CHECK(parser.push_variable(true));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(false); CHECK(parser.push_variable(false));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(false); CHECK(parser.push_variable(false));
parser.push_variable(false); CHECK(parser.push_variable(false));
parser.push_operator(BoolOperator::logical_and); CHECK(parser.push_operator(BoolOperator::logical_and));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
auto tree = flat_bool_expr_tree(std::move(parser).tree()); auto tree = flat_bool_expr_tree(std::move(parser).tree());
SUBCASE("Empty") SUBCASE("Empty")
@ -468,15 +396,15 @@ TEST_SUITE("util::flat_bool_expr_tree")
// Infix: ((x3 or x4) and x2) and (x0 or x1) // Infix: ((x3 or x4) and x2) and (x0 or x1)
// Postfix: x0 x1 or x2 x3 x4 or and and // Postfix: x0 x1 or x2 x3 x4 or and and
auto parser = PostfixParser<std::size_t, BoolOperator>{}; auto parser = PostfixParser<std::size_t, BoolOperator>{};
parser.push_variable(0); CHECK(parser.push_variable(0));
parser.push_variable(1); CHECK(parser.push_variable(1));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(2); CHECK(parser.push_variable(2));
parser.push_variable(3); CHECK(parser.push_variable(3));
parser.push_variable(4); CHECK(parser.push_variable(4));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_operator(BoolOperator::logical_and); CHECK(parser.push_operator(BoolOperator::logical_and));
parser.push_operator(BoolOperator::logical_and); CHECK(parser.push_operator(BoolOperator::logical_and));
auto tree = flat_bool_expr_tree(std::move(parser).tree()); auto tree = flat_bool_expr_tree(std::move(parser).tree());
static constexpr std::size_t n_vars = 5; 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]; }; { return ((x[0] || x[1]) && (x[2] || x[3] || x[4]) && x[5]) || x[6]; };
auto parser = InfixParser<std::size_t, BoolOperator>{}; auto parser = InfixParser<std::size_t, BoolOperator>{};
// Infix: ((x0 or x1) and (x2 or x3 or x4) and x5) or x6 // Infix: ((x0 or x1) and (x2 or x3 or x4) and x5) or x6
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_variable(0); CHECK(parser.push_variable(0));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(1); CHECK(parser.push_variable(1));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_operator(BoolOperator::logical_and); CHECK(parser.push_operator(BoolOperator::logical_and));
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_variable(2); CHECK(parser.push_variable(2));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(3); CHECK(parser.push_variable(3));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(4); CHECK(parser.push_variable(4));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_operator(BoolOperator::logical_and); CHECK(parser.push_operator(BoolOperator::logical_and));
parser.push_variable(5); CHECK(parser.push_variable(5));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(6); CHECK(parser.push_variable(6));
parser.finalize(); CHECK(parser.finalize());
auto tree = flat_bool_expr_tree(std::move(parser).tree()); auto tree = flat_bool_expr_tree(std::move(parser).tree());
static constexpr std::size_t n_vars = 7; 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>{}; auto parser = InfixParser<std::size_t, BoolOperator>{};
// Infix: ((x0 or x1) and (x2 or x3 or x4) and x5) or x6 // Infix: ((x0 or x1) and (x2 or x3 or x4) and x5) or x6
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_variable(0); CHECK(parser.push_variable(0));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(1); CHECK(parser.push_variable(1));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_operator(BoolOperator::logical_and); CHECK(parser.push_operator(BoolOperator::logical_and));
parser.push_left_parenthesis(); CHECK(parser.push_left_parenthesis());
parser.push_variable(2); CHECK(parser.push_variable(2));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(3); CHECK(parser.push_variable(3));
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(4); CHECK(parser.push_variable(4));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_operator(BoolOperator::logical_and); CHECK(parser.push_operator(BoolOperator::logical_and));
parser.push_variable(5); CHECK(parser.push_variable(5));
parser.push_right_parenthesis(); CHECK(parser.push_right_parenthesis());
parser.push_operator(BoolOperator::logical_or); CHECK(parser.push_operator(BoolOperator::logical_or));
parser.push_variable(6); CHECK(parser.push_variable(6));
parser.finalize(); CHECK(parser.finalize());
auto tree = flat_bool_expr_tree(std::move(parser).tree()); auto tree = flat_bool_expr_tree(std::move(parser).tree());
auto result = std::string(); auto result = std::string();

View File

@ -204,21 +204,12 @@ TEST_SUITE("util::URL")
{ {
SUBCASE("Empty") SUBCASE("Empty")
{ {
const URL url = URL::parse(""); CHECK_FALSE(URL::parse("").has_value());
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(), "");
} }
SUBCASE("mamba.org") 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.scheme(), URL::https);
CHECK_EQ(url.host(), "mamba.org"); CHECK_EQ(url.host(), "mamba.org");
CHECK_EQ(url.path(), "/"); CHECK_EQ(url.path(), "/");
@ -232,7 +223,7 @@ TEST_SUITE("util::URL")
SUBCASE("http://mamba.org") 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.scheme(), "http");
CHECK_EQ(url.host(), "mamba.org"); CHECK_EQ(url.host(), "mamba.org");
CHECK_EQ(url.path(), "/"); CHECK_EQ(url.path(), "/");
@ -246,7 +237,7 @@ TEST_SUITE("util::URL")
SUBCASE("s3://userx123:üúßsajd@mamba.org") 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.scheme(), "s3");
CHECK_EQ(url.host(), "mamba.org"); CHECK_EQ(url.host(), "mamba.org");
CHECK_EQ(url.path(), "/"); CHECK_EQ(url.path(), "/");
@ -260,7 +251,7 @@ TEST_SUITE("util::URL")
SUBCASE("http://user%40email.com:test@localhost:8000") 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.scheme(), "http");
CHECK_EQ(url.host(), "localhost"); CHECK_EQ(url.host(), "localhost");
CHECK_EQ(url.path(), "/"); 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 // Fails before "user@email.com" needs to be percent encoded, otherwise parsing is
// ill defined. // ill defined.
CHECK_FALSE(URL::parse("http://user@40email.com:test@localhost").has_value());
// 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);
} }
SUBCASE("http://:pass@localhost:8000") 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.scheme(), "http");
CHECK_EQ(url.host(), "localhost"); CHECK_EQ(url.host(), "localhost");
CHECK_EQ(url.path(), "/"); CHECK_EQ(url.path(), "/");
@ -300,7 +288,8 @@ TEST_SUITE("util::URL")
{ {
// Not a valid IETF RFC 3986+ URL, but Curl parses it anyways. // Not a valid IETF RFC 3986+ URL, but Curl parses it anyways.
// Undefined behavior, no assumptions are made // 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"); 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) 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.scheme(), "file");
CHECK_EQ(url.host(), ""); CHECK_EQ(url.host(), "");
CHECK_EQ(url.path(), "/C:/Users/wolfv/test/document.json"); 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") 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.scheme(), "file");
CHECK_EQ(url.host(), ""); CHECK_EQ(url.host(), "");
CHECK_EQ(url.path(), "/home/wolfv/test/document.json"); 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. // Not a valid IETF RFC 3986+ URL, but Curl parses it anyways.
// Undefined behavior, no assumptions are made // 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"); CHECK_NE(url.path(URL::Decode::no), "/home/great%3Adoc.json");
} }
SUBCASE("file:///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.scheme(), "file");
CHECK_EQ(url.host(), ""); CHECK_EQ(url.host(), "");
CHECK_EQ(url.path(), "/home/great:doc.json"); CHECK_EQ(url.path(), "/home/great:doc.json");
@ -361,7 +350,7 @@ TEST_SUITE("util::URL")
SUBCASE("https://169.254.0.0/page") 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.scheme(), "https");
CHECK_EQ(url.host(), "169.254.0.0"); CHECK_EQ(url.host(), "169.254.0.0");
CHECK_EQ(url.path(), "/page"); 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") 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.scheme(), "ftp");
CHECK_EQ(url.host(), "[2001:db8:85a3:8d3:1319:0:370:7348]"); CHECK_EQ(url.host(), "[2001:db8:85a3:8d3:1319:0:370:7348]");
CHECK_EQ(url.path(), "/page"); CHECK_EQ(url.path(), "/page");
@ -390,7 +379,7 @@ TEST_SUITE("util::URL")
SUBCASE("https://mamba.org/page#the-fragment") 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.scheme(), "https");
CHECK_EQ(url.host(), "mamba.org"); CHECK_EQ(url.host(), "mamba.org");
CHECK_EQ(url.path(), "/page"); CHECK_EQ(url.path(), "/page");
@ -672,16 +661,19 @@ TEST_SUITE("util::URL")
TEST_CASE("Equality") TEST_CASE("Equality")
{ {
CHECK_EQ(URL(), URL()); CHECK_EQ(URL(), URL());
CHECK_EQ(URL::parse("https://169.254.0.0/page"), URL::parse("https://169.254.0.0/page")); CHECK_EQ(
CHECK_EQ(URL::parse("mamba.org"), URL::parse("mamba.org/")); URL::parse("https://169.254.0.0/page").value(),
CHECK_EQ(URL::parse("mAmba.oRg"), URL::parse("mamba.org/")); URL::parse("https://169.254.0.0/page").value()
CHECK_EQ(URL::parse("localhost/page"), URL::parse("https://localhost/page")); );
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/page").value(), URL::parse("mamba.org/").value());
CHECK_NE(URL::parse("mamba.org"), URL::parse("mamba.org:9999")); CHECK_NE(URL::parse("mamba.org").value(), URL::parse("mamba.org:9999").value());
CHECK_NE(URL::parse("user@mamba.org"), URL::parse("mamba.org")); CHECK_NE(URL::parse("user@mamba.org").value(), URL::parse("mamba.org").value());
CHECK_NE(URL::parse("mamba.org/page"), URL::parse("mamba.org/page?q=v")); CHECK_NE(URL::parse("mamba.org/page").value(), URL::parse("mamba.org/page?q=v").value());
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/page#there").value());
} }
TEST_CASE("Append path") TEST_CASE("Append path")

View File

@ -4,6 +4,11 @@
// //
// The full license is in the file LICENSE, distributed with this software. // 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 <utility>
#include <pybind11/cast.h> #include <pybind11/cast.h>
@ -11,9 +16,6 @@
#include <pybind11/stl.h> #include <pybind11/stl.h>
#include <tl/expected.hpp> #include <tl/expected.hpp>
#ifndef MAMBA_PY_EXPECTED_CASTER
#define MAMBA_PY_EXPECTED_CASTER
namespace PYBIND11_NAMESPACE namespace PYBIND11_NAMESPACE
{ {
namespace detail namespace detail
@ -47,6 +49,10 @@ namespace PYBIND11_NAMESPACE
} }
else 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(); throw std::forward<Expected>(src).error();
} }
} }

View File

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

View File

@ -36,7 +36,7 @@ read_stdin()
std::string std::string
get_token_base(const std::string& host) 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{}; std::string maybe_colon_and_port{};
if (!url.port().empty()) if (!url.port().empty())

View File

@ -52,7 +52,8 @@ namespace
[&](auto pkg) [&](auto pkg)
{ {
if (!out 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); out = std::move(pkg);
} }