mirror of https://github.com/mamba-org/mamba.git
Add ChannelSpec (#2870)
* Make find_slash_and_platform private-lib * Add ChannelSpec * Add ChannelSpec::Type * Add abs_path_or_url_to_url * Add ChannelSpec path normalization and type detection * Add ChannelSpec::location * Allow any platform string in ChannelSpec * Channel::platforms is a flat_set * Move flat_set_caster to own file * More flat_set in Channel * Regenerate Python stubs * Plug ChannelSpec into Channel * Resolve path in ChannelSpec * Fix env export channels * No resolve symlink in ChannelSpec * Fix tests on Windows * Add ChannelSpec attribute extractors * Add raw domain ChannelSpec test * Use URL in read_channel_configuration * Fix local channel name * Improve path_to_url * Construct from Channel from URL * Properly parse channel alias * Fix wrong move * Channel alias is a CondaURL * Add defaulted scheme to URL * Add defaulted host to URL * CondaURL tokens can only be at the begining * Add CondaURL::path_without_token * Add option to remove credentials in URLs * Add empty cretendial URL tests * Add str Credential options * Add credential option to CondaURL::str * Add split_prefix/suffix * Add ChannelContext::from_package_path * URL::pretty_path only on Windows * Add ChannelContext::from_path * Do not resolve paths in ChannelSpec * Change mapping order in path resolving * Compute URL canonical_name in read_channel_configurations * Add URL::authority credentials * Rename private URL::authentification > URL::authentifaction_elems * Refactor URL::authority * Refactor some read_channel_configurations * Remove empty path URL case * Add path_is_prefix * Fix url_match * Add flat_set doctest printer * Add CondaURL doctest printer * Alias dynamic_platform_set in ChannelSpec * Use util:concat * Restore subdir in matchspecs * Make find_slash_and_platform private
This commit is contained in:
parent
a2b62bfd95
commit
4e93b7967a
|
@ -142,6 +142,7 @@ set(LIBMAMBA_SOURCES
|
|||
${LIBMAMBA_SOURCE_DIR}/specs/conda_url.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/version.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/version_spec.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/channel_spec.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/repo_data.cpp
|
||||
# Core API (low-level)
|
||||
${LIBMAMBA_SOURCE_DIR}/core/singletons.cpp
|
||||
|
@ -236,6 +237,7 @@ set(LIBMAMBA_PUBLIC_HEADERS
|
|||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/conda_url.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version_spec.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/channel_spec.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/repo_data.hpp
|
||||
# Core API (low-level)
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/activation.hpp
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "mamba/specs/conda_url.hpp"
|
||||
#include "mamba/util/flat_set.hpp"
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
|
@ -25,6 +25,10 @@ namespace mamba
|
|||
{
|
||||
class RepoChecker;
|
||||
}
|
||||
namespace specs
|
||||
{
|
||||
class ChannelSpec;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_known_platforms();
|
||||
|
||||
|
@ -40,11 +44,11 @@ namespace mamba
|
|||
|
||||
~Channel();
|
||||
|
||||
const std::string& scheme() const;
|
||||
std::string_view scheme() const;
|
||||
const std::string& location() const;
|
||||
const std::string& name() const;
|
||||
const std::string& canonical_name() const;
|
||||
const std::vector<std::string>& platforms() const;
|
||||
const util::flat_set<std::string>& platforms() const;
|
||||
std::optional<std::string> auth() const;
|
||||
std::optional<std::string> user() const;
|
||||
std::optional<std::string> password() const;
|
||||
|
@ -56,9 +60,9 @@ namespace mamba
|
|||
std::string base_url() const;
|
||||
std::string platform_url(std::string platform, bool with_credential = true) const;
|
||||
// The pairs consist of (platform,url)
|
||||
std::vector<std::pair<std::string, std::string>>
|
||||
util::flat_set<std::pair<std::string, std::string>>
|
||||
platform_urls(bool with_credential = true) const;
|
||||
std::vector<std::string> urls(bool with_credential = true) const;
|
||||
util::flat_set<std::string> urls(bool with_credential = true) const;
|
||||
|
||||
private:
|
||||
|
||||
|
@ -70,14 +74,25 @@ namespace mamba
|
|||
std::string_view user = {},
|
||||
std::string_view password = {},
|
||||
std::string_view token = {},
|
||||
std::string_view package_filename = {}
|
||||
std::string_view package_filename = {},
|
||||
util::flat_set<std::string> platforms = {}
|
||||
);
|
||||
|
||||
Channel(
|
||||
specs::CondaURL url,
|
||||
std::string location,
|
||||
std::string name,
|
||||
std::string canonical_name,
|
||||
util::flat_set<std::string> platforms = {}
|
||||
);
|
||||
|
||||
const specs::CondaURL& url() const;
|
||||
|
||||
specs::CondaURL m_url;
|
||||
std::string m_location;
|
||||
std::string m_name;
|
||||
std::string m_canonical_name;
|
||||
std::vector<std::string> m_platforms;
|
||||
util::flat_set<std::string> m_platforms;
|
||||
|
||||
// This is used to make sure that there is a unique repo for every channel
|
||||
mutable std::unique_ptr<validation::RepoChecker> p_repo_checker;
|
||||
|
@ -116,7 +131,7 @@ namespace mamba
|
|||
const Channel& make_channel(const std::string& value);
|
||||
std::vector<const Channel*> get_channels(const std::vector<std::string>& channel_names);
|
||||
|
||||
const Channel& get_channel_alias() const;
|
||||
const specs::CondaURL& get_channel_alias() const;
|
||||
const channel_map& get_custom_channels() const;
|
||||
|
||||
Context& context() const
|
||||
|
@ -128,7 +143,7 @@ namespace mamba
|
|||
|
||||
Context& m_context;
|
||||
ChannelCache m_channel_cache;
|
||||
Channel m_channel_alias;
|
||||
specs::CondaURL m_channel_alias;
|
||||
channel_map m_custom_channels;
|
||||
multichannel_map m_custom_multichannels;
|
||||
|
||||
|
@ -137,16 +152,18 @@ namespace mamba
|
|||
const multichannel_map& get_custom_multichannels() const;
|
||||
|
||||
Channel make_simple_channel(
|
||||
const Channel& channel_alias,
|
||||
const specs::CondaURL& channel_alias,
|
||||
const std::string& channel_url,
|
||||
const std::string& channel_name,
|
||||
const std::string& channel_canonical_name
|
||||
);
|
||||
|
||||
Channel from_url(std::string_view url);
|
||||
Channel from_any_path(specs::ChannelSpec&& spec);
|
||||
Channel from_package_path(specs::ChannelSpec&& spec);
|
||||
Channel from_path(specs::ChannelSpec&& spec);
|
||||
Channel from_url(specs::ChannelSpec&& spec);
|
||||
Channel from_name(const std::string& name);
|
||||
Channel from_value(const std::string& value);
|
||||
Channel from_alias(std::string_view alias);
|
||||
};
|
||||
|
||||
} // namespace mamba
|
||||
|
|
|
@ -377,11 +377,21 @@ namespace mamba::fs
|
|||
return m_path.extension();
|
||||
}
|
||||
|
||||
u8path lexically_normal() const
|
||||
{
|
||||
return m_path.lexically_normal();
|
||||
}
|
||||
|
||||
u8path lexically_relative(const u8path& base) const
|
||||
{
|
||||
return m_path.lexically_relative(base);
|
||||
}
|
||||
|
||||
u8path lexically_proximate(const u8path& base) const
|
||||
{
|
||||
return m_path.lexically_proximate(base);
|
||||
}
|
||||
|
||||
//---- Modifiers ----
|
||||
|
||||
void clear() noexcept
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) 2023, 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_CHANNEL_SPEC_HPP
|
||||
#define MAMBA_SPECS_CHANNEL_SPEC_HPP
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "mamba/util/flat_set.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
/**
|
||||
* Channel specification.
|
||||
*
|
||||
* This represent the string that is passed by the user to select a channel.
|
||||
* It needs to be resolved in order to get a final URL/path.
|
||||
* This is even true when a full URL or path is given, as some authentification information
|
||||
* may come from login database.
|
||||
*
|
||||
* Note that for a string to be considered a URL, it must have an explicit scheme.
|
||||
* So "repo.anaconda.com" is considered a name, similarily to "conda-forge" and not a URL.
|
||||
* This is because otherwise it is not possible to tell names and URL appart.
|
||||
*/
|
||||
class ChannelSpec
|
||||
{
|
||||
public:
|
||||
|
||||
enum class Type
|
||||
{
|
||||
/**
|
||||
* A URL to a full repo strucuture.
|
||||
*
|
||||
* Example "https://repo.anaconda.com/conda-forge".
|
||||
*/
|
||||
URL,
|
||||
/**
|
||||
* A URL to a single package.
|
||||
*
|
||||
* Example "https://repo.anaconda.com/conda-forge/linux-64/pkg-0.0-bld.conda".
|
||||
*/
|
||||
PackageURL,
|
||||
/**
|
||||
* An (possibly implicit) path to a full repo strucuture.
|
||||
*
|
||||
* Example "/Users/name/conda-bld", "./conda-bld", "~/.conda-bld".
|
||||
*/
|
||||
Path,
|
||||
/**
|
||||
* An (possibly implicit) path to a single-package.
|
||||
*
|
||||
* Example "/tmp/pkg-0.0-bld.conda", "./pkg-0.0-bld.conda", "~/pkg-0.0-bld.tar.bz2".
|
||||
*/
|
||||
PackagePath,
|
||||
/**
|
||||
* A relative name.
|
||||
*
|
||||
* It needs to be resolved using a channel alias, or a custom channel.
|
||||
* Example "conda-forge", "locals", "my-channel/my-label".
|
||||
*/
|
||||
Name,
|
||||
};
|
||||
|
||||
static constexpr std::string_view default_name = "defaults";
|
||||
static constexpr std::string_view platform_separators = "|,;";
|
||||
|
||||
using dynamic_platform_set = util::flat_set<std::string>;
|
||||
|
||||
[[nodiscard]] static auto parse(std::string_view str) -> ChannelSpec;
|
||||
|
||||
ChannelSpec() = default;
|
||||
ChannelSpec(std::string location, dynamic_platform_set filters, Type type);
|
||||
|
||||
[[nodiscard]] auto type() const -> Type;
|
||||
|
||||
[[nodiscard]] auto location() const& -> const std::string&;
|
||||
[[nodiscard]] auto location() && -> std::string;
|
||||
[[nodiscard]] auto clear_location() -> std::string;
|
||||
|
||||
[[nodiscard]] auto platform_filters() const& -> const dynamic_platform_set&;
|
||||
[[nodiscard]] auto platform_filters() && -> dynamic_platform_set;
|
||||
[[nodiscard]] auto clear_platform_filters() -> dynamic_platform_set;
|
||||
|
||||
private:
|
||||
|
||||
std::string m_location = std::string(default_name);
|
||||
dynamic_platform_set m_platform_filters = {};
|
||||
Type m_type = {};
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -21,7 +21,7 @@ namespace mamba::specs
|
|||
public:
|
||||
|
||||
using StripScheme = util::detail::StripScheme;
|
||||
using HideConfidential = util::detail::HideConfidential;
|
||||
using Credentials = util::detail::Credentials;
|
||||
using Encode = util::detail::Encode;
|
||||
using Decode = util::detail::Decode;
|
||||
|
||||
|
@ -34,8 +34,10 @@ namespace mamba::specs
|
|||
explicit CondaURL(util::URL&& url);
|
||||
explicit CondaURL(const util::URL& url);
|
||||
|
||||
using Base::scheme_is_defaulted;
|
||||
using Base::scheme;
|
||||
using Base::set_scheme;
|
||||
using Base::clear_scheme;
|
||||
using Base::user;
|
||||
using Base::set_user;
|
||||
using Base::clear_user;
|
||||
|
@ -43,6 +45,7 @@ namespace mamba::specs
|
|||
using Base::set_password;
|
||||
using Base::clear_password;
|
||||
using Base::authentication;
|
||||
using Base::host_is_defaulted;
|
||||
using Base::host;
|
||||
using Base::set_host;
|
||||
using Base::clear_host;
|
||||
|
@ -52,9 +55,7 @@ namespace mamba::specs
|
|||
using Base::authority;
|
||||
using Base::path;
|
||||
using Base::pretty_path;
|
||||
using Base::set_path;
|
||||
using Base::clear_path;
|
||||
using Base::append_path;
|
||||
using Base::query;
|
||||
using Base::set_query;
|
||||
using Base::clear_query;
|
||||
|
@ -62,6 +63,42 @@ namespace mamba::specs
|
|||
using Base::set_fragment;
|
||||
using Base::clear_fragment;
|
||||
|
||||
/**
|
||||
* Set the path from a not encoded value.
|
||||
*
|
||||
* All '/' are not encoded but interpreted as separators.
|
||||
* On windows with a file scheme, the colon after the drive letter is not encoded.
|
||||
* A leading '/' is added if abscent.
|
||||
* If the path contains only a token, a trailing '/' is added afterwards.
|
||||
*/
|
||||
void set_path(std::string_view path, Encode::yes_type = Encode::yes);
|
||||
|
||||
/** Set the path from an already encoded value.
|
||||
*
|
||||
* A leading '/' is added if abscent.
|
||||
* If the path contains only a token, a trailing '/' is added afterwards.
|
||||
*/
|
||||
void set_path(std::string path, Encode::no_type);
|
||||
|
||||
/**
|
||||
* Append a not encoded sub path to the current path.
|
||||
*
|
||||
* Contrary to `std::filesystem::path::append`, this always append and never replace
|
||||
* the current path, even if @p subpath starts with a '/'.
|
||||
* All '/' are not encoded but interpreted as separators.
|
||||
* If the final path contains only a token, a trailing '/' is added afterwards.
|
||||
*/
|
||||
void append_path(std::string_view path, Encode::yes_type = Encode::yes);
|
||||
|
||||
/**
|
||||
* Append a already encoded sub path to the current path.
|
||||
*
|
||||
* Contrary to `std::filesystem::path::append`, this always append and never replace
|
||||
* the current path, even if @p subpath starts with a '/'.
|
||||
* If the final path contains only a token, a trailing '/' is added afterwards.
|
||||
*/
|
||||
void append_path(std::string_view path, Encode::no_type);
|
||||
|
||||
/** Return the Conda token, as delimited with "/t/", or empty if there isn't any. */
|
||||
[[nodiscard]] auto token() const -> std::string_view;
|
||||
|
||||
|
@ -76,6 +113,29 @@ namespace mamba::specs
|
|||
/** Clear the token and return ``true`` if it exists, otherwise return ``false``. */
|
||||
auto clear_token() -> bool;
|
||||
|
||||
/** Return the encoded part of the path without any Conda token, always start with '/'. */
|
||||
[[nodiscard]] auto path_without_token(Decode::no_type) const -> std::string_view;
|
||||
|
||||
/** Return the decoded part of the path without any Conda token, always start with '/'. */
|
||||
[[nodiscard]] auto path_without_token(Decode::yes_type = Decode::yes) const -> std::string;
|
||||
|
||||
/**
|
||||
* Set the path from an already encoded value, without changing the Conda token.
|
||||
*
|
||||
* A leading '/' is added if abscent.
|
||||
*/
|
||||
void set_path_without_token(std::string_view path, Encode::no_type);
|
||||
|
||||
/**
|
||||
* Set the path from an not yet encoded value, without changing the Conda token.
|
||||
*
|
||||
* A leading '/' is added if abscent.
|
||||
*/
|
||||
void set_path_without_token(std::string_view path, Encode::yes_type = Encode::yes);
|
||||
|
||||
/** Clear the path without changing the Conda token and return ``true`` if it exists. */
|
||||
auto clear_path_without_token() -> bool;
|
||||
|
||||
/** Return the platform if part of the URL path. */
|
||||
[[nodiscard]] auto platform() const -> std::optional<Platform>;
|
||||
|
||||
|
@ -138,7 +198,8 @@ namespace mamba::specs
|
|||
/** Clear the package and return true if it exists, otherwise return ``false``. */
|
||||
auto clear_package() -> bool;
|
||||
|
||||
using Base::str;
|
||||
/** Return the full, exact, encoded URL. */
|
||||
[[nodiscard]] auto str(Credentials credentials = Credentials::Show) const -> std::string;
|
||||
|
||||
/**
|
||||
* Return the full decoded url.
|
||||
|
@ -147,18 +208,20 @@ namespace mamba::specs
|
|||
* asset.
|
||||
* @param strip_scheme If true, remove the scheme and "localhost" on file URI.
|
||||
* @param rstrip_path If non-null, remove the given charaters at the end of the path.
|
||||
* @param hide_confidential If true, hide password and tokens in the decoded string.
|
||||
* @param credentials If true, hide password and tokens in the decoded string.
|
||||
* @param credentials Decide to keep, remove, or hide passwrd, users, and token.
|
||||
*/
|
||||
[[nodiscard]] auto pretty_str(
|
||||
StripScheme strip_scheme = StripScheme::no,
|
||||
char rstrip_path = 0,
|
||||
HideConfidential hide_confidential = HideConfidential::no
|
||||
Credentials credentials = Credentials::Show
|
||||
) const -> std::string;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void set_platform_no_check_input(std::string_view platform);
|
||||
void ensure_path_without_token_leading_slash();
|
||||
|
||||
friend auto operator==(const CondaURL&, const CondaURL&) -> bool;
|
||||
};
|
||||
|
|
|
@ -45,5 +45,11 @@ namespace mamba::util
|
|||
* Convert the Windows path separators to Posix ones on Windows only.
|
||||
*/
|
||||
[[nodiscard]] auto path_to_posix(std::string path) -> std::string;
|
||||
|
||||
/**
|
||||
* Check that a path is a prefix of another path.
|
||||
*/
|
||||
[[nodiscard]] auto
|
||||
path_is_prefix(std::string_view parent, std::string_view child, char sep = '/') -> bool;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
@ -107,12 +106,26 @@ namespace mamba::util
|
|||
std::string_view remove_prefix(std::string_view str, std::string_view prefix);
|
||||
std::string_view remove_prefix(std::string_view str, std::string_view::value_type c);
|
||||
|
||||
/**
|
||||
* Return a view to prefix if present, and a view to the rest of the input.
|
||||
*/
|
||||
std::array<std::string_view, 2> split_prefix(std::string_view str, std::string_view prefix);
|
||||
std::array<std::string_view, 2>
|
||||
split_prefix(std::string_view str, std::string_view::value_type c);
|
||||
|
||||
/**
|
||||
* Return a view to the input without the suffix if present.
|
||||
*/
|
||||
std::string_view remove_suffix(std::string_view str, std::string_view suffix);
|
||||
std::string_view remove_suffix(std::string_view str, std::string_view::value_type c);
|
||||
|
||||
/**
|
||||
* Return a view to the head of the input, and a view to suffix if present.
|
||||
*/
|
||||
std::array<std::string_view, 2> split_suffix(std::string_view str, std::string_view suffix);
|
||||
std::array<std::string_view, 2>
|
||||
split_suffix(std::string_view str, std::string_view::value_type c);
|
||||
|
||||
std::string_view lstrip(std::string_view input, char c);
|
||||
std::wstring_view lstrip(std::wstring_view input, wchar_t c);
|
||||
std::string_view lstrip(std::string_view input, std::string_view chars);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#ifndef MAMBA_UTIL_URL_HPP
|
||||
#define MAMBA_UTIL_URL_HPP
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
|
@ -22,10 +23,11 @@ namespace mamba::util
|
|||
yes
|
||||
};
|
||||
|
||||
enum class HideConfidential : bool
|
||||
enum class Credentials
|
||||
{
|
||||
no,
|
||||
yes
|
||||
Show,
|
||||
Hide,
|
||||
Remove,
|
||||
};
|
||||
|
||||
struct Encode
|
||||
|
@ -59,7 +61,7 @@ namespace mamba::util
|
|||
public:
|
||||
|
||||
using StripScheme = detail::StripScheme;
|
||||
using HideConfidential = detail::HideConfidential;
|
||||
using Credentials = detail::Credentials;
|
||||
using Encode = detail::Encode;
|
||||
using Decode = detail::Decode;
|
||||
|
||||
|
@ -84,12 +86,18 @@ namespace mamba::util
|
|||
/** Create a local URL. */
|
||||
URL() = default;
|
||||
|
||||
/** Return whether the scheme is defaulted, i.e. not explicitly set. */
|
||||
[[nodiscard]] auto scheme_is_defaulted() const -> bool;
|
||||
|
||||
/** Return the scheme, always non-empty. */
|
||||
[[nodiscard]] auto scheme() const -> const std::string&;
|
||||
[[nodiscard]] auto scheme() const -> std::string_view;
|
||||
|
||||
/** Set a non-empty scheme. */
|
||||
void set_scheme(std::string_view scheme);
|
||||
|
||||
/** Clear the scheme back to a defaulted value and return the old value. */
|
||||
auto clear_scheme() -> std::string;
|
||||
|
||||
/** Return the encoded user, or empty if none. */
|
||||
[[nodiscard]] auto user(Decode::no_type) const -> const std::string&;
|
||||
|
||||
|
@ -123,6 +131,9 @@ namespace mamba::util
|
|||
/** Return the encoded basic authentication string. */
|
||||
[[nodiscard]] auto authentication() const -> std::string;
|
||||
|
||||
/** Return whether the host was defaulted, i.e. not explicitly set. */
|
||||
[[nodiscard]] auto host_is_defaulted() const -> bool;
|
||||
|
||||
/** Return the encoded host, always non-empty except for file scheme. */
|
||||
[[nodiscard]] auto host(Decode::no_type) const -> std::string_view;
|
||||
|
||||
|
@ -148,7 +159,7 @@ namespace mamba::util
|
|||
auto clear_port() -> std::string;
|
||||
|
||||
/** Return the encoded autority part of the URL. */
|
||||
[[nodiscard]] auto authority() const -> std::string;
|
||||
[[nodiscard]] auto authority(Credentials = Credentials::Show) const -> std::string;
|
||||
|
||||
/** Return the encoded path, always starts with a '/'. */
|
||||
[[nodiscard]] auto path(Decode::no_type) const -> const std::string&;
|
||||
|
@ -215,32 +226,41 @@ namespace mamba::util
|
|||
auto clear_fragment() -> std::string;
|
||||
|
||||
/** Return the full, exact, encoded URL. */
|
||||
[[nodiscard]] auto str() const -> std::string;
|
||||
[[nodiscard]] auto str(Credentials credentials = Credentials::Show) const -> std::string;
|
||||
|
||||
/**
|
||||
* Return the full decoded url.
|
||||
*
|
||||
* Due to decoding, the outcome may not be understood by parser and usable to reach an
|
||||
* asset.
|
||||
* Due to decoding, the outcome may not be understood by parser and usable to fetch the URL.
|
||||
* @param strip_scheme If true, remove the scheme and "localhost" on file URI.
|
||||
* @param rstrip_path If non-null, remove the given charaters at the end of the path.
|
||||
* @param hide_confidential If true, hide password in the decoded string.
|
||||
* @param credentials Decide to keep, remove, or hide credentials.
|
||||
*/
|
||||
[[nodiscard]] auto pretty_str(
|
||||
StripScheme strip_scheme = StripScheme::no,
|
||||
char rstrip_path = 0,
|
||||
HideConfidential hide_confidential = HideConfidential::no
|
||||
Credentials credentials = Credentials::Show
|
||||
) const -> std::string;
|
||||
|
||||
protected:
|
||||
|
||||
[[nodiscard]] auto authentication_elems(Credentials, Decode::no_type) const
|
||||
-> std::array<std::string_view, 3>;
|
||||
[[nodiscard]] auto authentication_elems(Credentials, Decode::yes_type) const
|
||||
-> std::array<std::string, 3>;
|
||||
|
||||
[[nodiscard]] auto authority_elems(Credentials, Decode::no_type) const
|
||||
-> std::array<std::string_view, 7>;
|
||||
[[nodiscard]] auto authority_elems(Credentials, Decode::yes_type) const
|
||||
-> std::array<std::string, 7>;
|
||||
|
||||
[[nodiscard]] auto
|
||||
pretty_str_path(StripScheme strip_scheme = StripScheme::no, char rstrip_path = 0) const
|
||||
-> std::string;
|
||||
|
||||
private:
|
||||
|
||||
std::string m_scheme = std::string(https);
|
||||
std::string m_scheme = {};
|
||||
std::string m_user = {};
|
||||
std::string m_password = {};
|
||||
std::string m_host = {};
|
||||
|
|
|
@ -73,6 +73,13 @@ namespace mamba::util
|
|||
*/
|
||||
[[nodiscard]] auto abs_path_to_url(std::string_view path) -> std::string;
|
||||
|
||||
/**
|
||||
* Transform an absolute path to a %-encoded "file://" URL.
|
||||
*
|
||||
* Does nothing if the input is already has a URL scheme.
|
||||
*/
|
||||
[[nodiscard]] auto abs_path_or_url_to_url(std::string_view path) -> std::string;
|
||||
|
||||
/**
|
||||
* Transform an absolute or relative path to a %-encoded "file://" URL.
|
||||
*/
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
|
@ -13,9 +12,8 @@
|
|||
#include "mamba/core/context.hpp"
|
||||
#include "mamba/core/environment.hpp"
|
||||
#include "mamba/core/package_cache.hpp"
|
||||
#include "mamba/core/util_os.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/specs/archive.hpp"
|
||||
#include "mamba/specs/channel_spec.hpp"
|
||||
#include "mamba/specs/conda_url.hpp"
|
||||
#include "mamba/util/path_manip.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
@ -25,7 +23,6 @@
|
|||
|
||||
namespace mamba
|
||||
{
|
||||
// Constants used by Channel and ChannelContext
|
||||
namespace
|
||||
{
|
||||
const std::map<std::string, std::string> DEFAULT_CUSTOM_CHANNELS = {
|
||||
|
@ -41,177 +38,20 @@ namespace mamba
|
|||
|
||||
const char LOCAL_CHANNELS_NAME[] = "local";
|
||||
const char DEFAULT_CHANNELS_NAME[] = "defaults";
|
||||
} // namespace
|
||||
|
||||
// Specific functions, used only in this file
|
||||
namespace
|
||||
{
|
||||
std::optional<std::string> nonempty_str(std::string&& s)
|
||||
{
|
||||
return s.empty() ? std::optional<std::string>() : std::make_optional(s);
|
||||
}
|
||||
|
||||
// Channel configuration
|
||||
struct channel_configuration
|
||||
auto channel_alias_location(specs::CondaURL url) -> std::string
|
||||
{
|
||||
std::string location;
|
||||
std::string name;
|
||||
std::string scheme;
|
||||
std::string user;
|
||||
std::string password;
|
||||
std::string token;
|
||||
};
|
||||
|
||||
channel_configuration read_channel_configuration(
|
||||
ChannelContext& channel_context,
|
||||
const std::string& scheme,
|
||||
const std::string& host,
|
||||
const std::string& port,
|
||||
const std::string& path
|
||||
)
|
||||
{
|
||||
auto spath = std::string(util::rstrip(path, '/'));
|
||||
std::string url = [&]()
|
||||
{
|
||||
auto parsed_url = util::URL();
|
||||
parsed_url.set_scheme(scheme);
|
||||
parsed_url.set_host(host);
|
||||
parsed_url.set_port(port);
|
||||
parsed_url.set_path(spath);
|
||||
return parsed_url.pretty_str(util::URL::StripScheme::yes);
|
||||
}();
|
||||
|
||||
// Case 1: No path given, channel name is ""
|
||||
if (spath.empty())
|
||||
{
|
||||
auto l_url = util::URL();
|
||||
l_url.set_host(host);
|
||||
l_url.set_port(port);
|
||||
return channel_configuration{
|
||||
/* .location= */ l_url
|
||||
.pretty_str(util::URL::StripScheme::yes, /* rstrip_path= */ '/'),
|
||||
/* .name= */ "",
|
||||
/* .scheme= */ scheme,
|
||||
/* .user= */ "",
|
||||
/* .password= */ "",
|
||||
/* .token= */ "",
|
||||
};
|
||||
}
|
||||
|
||||
// Case 2: migrated_custom_channels not implemented yet
|
||||
// Case 3: migrated_channel_aliases not implemented yet
|
||||
|
||||
// Case 4: custom_channels matches
|
||||
const auto& custom_channels = channel_context.get_custom_channels();
|
||||
for (const auto& ca : custom_channels)
|
||||
{
|
||||
const Channel& channel = ca.second;
|
||||
std::string test_url = util::join_url(channel.location(), channel.name());
|
||||
if (vector_is_prefix(util::split(test_url, "/"), util::split(url, "/")))
|
||||
{
|
||||
auto subname = std::string(util::strip(url.replace(0u, test_url.size(), ""), '/'));
|
||||
|
||||
return channel_configuration{
|
||||
/* .location= */ channel.location(),
|
||||
/* .name= */ util::join_url(channel.name(), subname),
|
||||
/* .scheme= */ scheme,
|
||||
/* .user= */ channel.user().value_or(""),
|
||||
/* .password= */ channel.password().value_or(""),
|
||||
/* .token= */ channel.token().value_or(""),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Case 5: channel_alias match
|
||||
const Channel& ca = channel_context.get_channel_alias();
|
||||
if (!ca.location().empty() && util::starts_with(url, ca.location()))
|
||||
{
|
||||
auto name = std::string(util::strip(url.replace(0u, ca.location().size(), ""), '/'));
|
||||
return channel_configuration{
|
||||
/* .location= */ ca.location(),
|
||||
/* .name= */ name,
|
||||
/* .scheme= */ scheme,
|
||||
/* .user= */ ca.user().value_or(""),
|
||||
/* .password= */ ca.password().value_or(""),
|
||||
/* .token= */ ca.token().value_or(""),
|
||||
};
|
||||
}
|
||||
|
||||
// Case 6: not-otherwise-specified file://-type urls
|
||||
if (host.empty() || ((host == util::URL::localhost) && port.empty()))
|
||||
{
|
||||
auto sp = util::rsplit(url, "/", 1);
|
||||
return channel_configuration{
|
||||
/* .location= */ sp[0].size() ? sp[0] : "/",
|
||||
/* .name= */ sp[1],
|
||||
/* .scheme= */ "file",
|
||||
/* .user= */ "",
|
||||
/* .password= */ "",
|
||||
/* .token= */ "",
|
||||
};
|
||||
}
|
||||
|
||||
// Case 7: fallback, channel_location = host:port and channel_name = path
|
||||
spath = util::lstrip(spath, '/');
|
||||
auto location = util::URL();
|
||||
location.set_host(host);
|
||||
location.set_port(port);
|
||||
return channel_configuration{
|
||||
/* .location= */ location.pretty_str(util::URL::StripScheme::yes, /* rstrip_path= */ '/'),
|
||||
/* .name= */ spath,
|
||||
/* .scheme= */ scheme,
|
||||
/* .user= */ "",
|
||||
/* .password= */ "",
|
||||
/* .token= */ "",
|
||||
};
|
||||
url.clear_user();
|
||||
url.clear_password();
|
||||
url.clear_token();
|
||||
return url.pretty_str(specs::CondaURL::StripScheme::yes, '/');
|
||||
}
|
||||
|
||||
std::vector<std::string> take_platforms(const Context& context, std::string& value)
|
||||
{
|
||||
std::vector<std::string> platforms;
|
||||
if (!value.empty())
|
||||
{
|
||||
if (value[value.size() - 1] == ']')
|
||||
{
|
||||
const auto end_value = value.find_last_of('[');
|
||||
if (end_value != std::string::npos)
|
||||
{
|
||||
auto ind = end_value + 1;
|
||||
while (ind < value.size() - 1)
|
||||
{
|
||||
auto end = value.find_first_of(", ]", ind);
|
||||
assert(end != std::string::npos);
|
||||
platforms.emplace_back(value.substr(ind, end - ind));
|
||||
ind = end;
|
||||
while (value[ind] == ',' || value[ind] == ' ')
|
||||
{
|
||||
ind++;
|
||||
}
|
||||
}
|
||||
|
||||
value.resize(end_value);
|
||||
}
|
||||
}
|
||||
// This is required because a channel can be instantiated from an URL
|
||||
// that already contains the platform
|
||||
else
|
||||
{
|
||||
std::string platform = "";
|
||||
util::split_platform(get_known_platforms(), value, context.platform, value, platform);
|
||||
if (!platform.empty())
|
||||
{
|
||||
platforms.push_back(std::move(platform));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (platforms.empty())
|
||||
{
|
||||
platforms = context.platforms();
|
||||
}
|
||||
return platforms;
|
||||
}
|
||||
} // namespace
|
||||
}
|
||||
|
||||
std::vector<std::string> get_known_platforms()
|
||||
{
|
||||
|
@ -231,13 +71,14 @@ namespace mamba
|
|||
std::string_view user,
|
||||
std::string_view password,
|
||||
std::string_view token,
|
||||
std::string_view package_filename
|
||||
std::string_view package_filename,
|
||||
util::flat_set<std::string> platforms
|
||||
)
|
||||
: m_url()
|
||||
, m_location(std::move(location))
|
||||
, m_name(std::move(name))
|
||||
, m_canonical_name(std::move(canonical_name))
|
||||
, m_platforms()
|
||||
, m_platforms(std::move(platforms))
|
||||
{
|
||||
if (m_name != UNKNOWN_CHANNEL)
|
||||
{
|
||||
|
@ -264,9 +105,29 @@ namespace mamba
|
|||
}
|
||||
}
|
||||
|
||||
Channel::Channel(
|
||||
specs::CondaURL url,
|
||||
std::string location,
|
||||
std::string name,
|
||||
std::string canonical_name,
|
||||
util::flat_set<std::string> platforms
|
||||
)
|
||||
: m_url(std::move(url))
|
||||
, m_location(std::move(location))
|
||||
, m_name(std::move(name))
|
||||
, m_canonical_name(std::move(canonical_name))
|
||||
, m_platforms(std::move(platforms))
|
||||
{
|
||||
}
|
||||
|
||||
Channel::~Channel() = default;
|
||||
|
||||
const std::string& Channel::scheme() const
|
||||
const specs::CondaURL& Channel::url() const
|
||||
{
|
||||
return m_url;
|
||||
}
|
||||
|
||||
std::string_view Channel::scheme() const
|
||||
{
|
||||
return m_url.scheme();
|
||||
}
|
||||
|
@ -281,7 +142,7 @@ namespace mamba
|
|||
return m_name;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& Channel::platforms() const
|
||||
const util::flat_set<std::string>& Channel::platforms() const
|
||||
{
|
||||
return m_platforms;
|
||||
}
|
||||
|
@ -344,40 +205,42 @@ namespace mamba
|
|||
}
|
||||
else
|
||||
{
|
||||
return util::concat_scheme_url(scheme(), util::join_url(location(), name()));
|
||||
return util::concat_scheme_url(std::string(scheme()), util::join_url(location(), name()));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> Channel::urls(bool with_credential) const
|
||||
util::flat_set<std::string> Channel::urls(bool with_credential) const
|
||||
{
|
||||
if (package_filename())
|
||||
if (auto fn = package_filename())
|
||||
{
|
||||
std::string base = location();
|
||||
std::string base = {};
|
||||
if (with_credential && token())
|
||||
{
|
||||
base = util::join_url(base, "t", *token());
|
||||
base = util::join_url(location(), "t", *token());
|
||||
}
|
||||
else
|
||||
{
|
||||
base = location();
|
||||
}
|
||||
|
||||
std::string platform = m_platforms[0];
|
||||
return { { util::build_url(
|
||||
auth(),
|
||||
scheme(),
|
||||
util::join_url(base, name(), platform, *package_filename()),
|
||||
std::string(scheme()),
|
||||
util::join_url(base, name(), std::move(fn).value()),
|
||||
with_credential
|
||||
) } };
|
||||
}
|
||||
else
|
||||
|
||||
auto out = util::flat_set<std::string>{};
|
||||
for (auto& [_, v] : platform_urls(with_credential))
|
||||
{
|
||||
std::vector<std::string> ret;
|
||||
for (auto& [_, v] : platform_urls(with_credential))
|
||||
{
|
||||
ret.emplace_back(v);
|
||||
}
|
||||
return ret;
|
||||
out.insert(v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> Channel::platform_urls(bool with_credential) const
|
||||
util::flat_set<std::pair<std::string, std::string>>
|
||||
Channel::platform_urls(bool with_credential) const
|
||||
{
|
||||
std::string base = location();
|
||||
if (with_credential && token())
|
||||
|
@ -385,15 +248,20 @@ namespace mamba
|
|||
base = util::join_url(base, "t", *token());
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> ret;
|
||||
auto out = util::flat_set<std::pair<std::string, std::string>>{};
|
||||
for (const auto& platform : platforms())
|
||||
{
|
||||
ret.emplace_back(
|
||||
out.insert({
|
||||
platform,
|
||||
util::build_url(auth(), scheme(), util::join_url(base, name(), platform), with_credential)
|
||||
);
|
||||
util::build_url(
|
||||
auth(),
|
||||
std::string(scheme()),
|
||||
util::join_url(base, name(), platform),
|
||||
with_credential
|
||||
),
|
||||
});
|
||||
}
|
||||
return ret;
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string Channel::platform_url(std::string platform, bool with_credential) const
|
||||
|
@ -403,7 +271,12 @@ namespace mamba
|
|||
{
|
||||
base = util::join_url(base, "t", *token());
|
||||
}
|
||||
return util::build_url(auth(), scheme(), util::join_url(base, name(), platform), with_credential);
|
||||
return util::build_url(
|
||||
auth(),
|
||||
std::string(scheme()),
|
||||
util::join_url(base, name(), platform),
|
||||
with_credential
|
||||
);
|
||||
}
|
||||
|
||||
bool operator==(const Channel& lhs, const Channel& rhs)
|
||||
|
@ -421,7 +294,7 @@ namespace mamba
|
|||
*********************************/
|
||||
|
||||
Channel ChannelContext::make_simple_channel(
|
||||
const Channel& channel_alias,
|
||||
const specs::CondaURL& channel_alias,
|
||||
const std::string& channel_url,
|
||||
const std::string& channel_name,
|
||||
const std::string& channel_canonical_name
|
||||
|
@ -429,14 +302,15 @@ namespace mamba
|
|||
{
|
||||
if (!util::url_has_scheme(channel_url))
|
||||
{
|
||||
auto ca_location = channel_alias_location(channel_alias);
|
||||
return Channel(
|
||||
/* scheme= */ channel_alias.scheme(),
|
||||
/* location= */ channel_alias.location(),
|
||||
/* location= */ std::move(ca_location),
|
||||
/* name= */ std::string(util::strip(channel_name.empty() ? channel_url : channel_name, '/')),
|
||||
/* canonical_name= */ channel_canonical_name,
|
||||
/* user= */ channel_alias.user().value_or(""),
|
||||
/* password= */ channel_alias.password().value_or(""),
|
||||
/* token= */ channel_alias.token().value_or(""),
|
||||
/* user= */ channel_alias.user(),
|
||||
/* password= */ channel_alias.password(),
|
||||
/* token= */ channel_alias.token(),
|
||||
/* package_filename= */ {}
|
||||
);
|
||||
}
|
||||
|
@ -454,12 +328,11 @@ namespace mamba
|
|||
std::string name(channel_name);
|
||||
if (name.empty())
|
||||
{
|
||||
if (!channel_alias.location().empty()
|
||||
&& util::starts_with(location, channel_alias.location()))
|
||||
if (auto ca_location = channel_alias_location(channel_alias);
|
||||
util::starts_with(location, ca_location))
|
||||
{
|
||||
name = location;
|
||||
name.replace(0u, channel_alias.location().size(), "");
|
||||
location = channel_alias.location();
|
||||
name = std::string(util::strip(util::remove_prefix(location, ca_location), '/'));
|
||||
location = std::move(ca_location);
|
||||
}
|
||||
else if (url.scheme() == "file")
|
||||
{
|
||||
|
@ -486,44 +359,233 @@ namespace mamba
|
|||
);
|
||||
}
|
||||
|
||||
Channel ChannelContext::from_url(std::string_view url_str)
|
||||
namespace
|
||||
{
|
||||
auto url = specs::CondaURL::parse(url_str);
|
||||
std::string package_name = url.package();
|
||||
url.clear_package();
|
||||
std::string token = std::string(url.token());
|
||||
url.clear_token();
|
||||
|
||||
auto config = read_channel_configuration(*this, url.scheme(), url.host(), url.port(), url.path());
|
||||
|
||||
auto res_scheme = !config.scheme.empty() ? config.scheme : "https";
|
||||
std::string canonical_name;
|
||||
|
||||
const auto& custom_channels = get_custom_channels();
|
||||
if ((custom_channels.find(config.name) != custom_channels.end())
|
||||
|| (config.location == get_channel_alias().location()))
|
||||
auto url_match(const specs::CondaURL& registered, const specs::CondaURL& candidate) -> bool
|
||||
{
|
||||
canonical_name = config.name;
|
||||
using Decode = typename specs::CondaURL::Decode;
|
||||
|
||||
// Not checking users, passwords, and tokens
|
||||
return /**/
|
||||
// Defaulted scheme matches all, otherwise schemes must be the same
|
||||
(registered.scheme_is_defaulted() || (registered.scheme() == candidate.scheme()))
|
||||
// Hosts must always be the same
|
||||
&& (registered.host(Decode::no) == candidate.host(Decode::no))
|
||||
// Different ports are considered different channels
|
||||
&& (registered.port() == candidate.port())
|
||||
// Registered path must be a prefix
|
||||
&& util::path_is_prefix(
|
||||
registered.path_without_token(Decode::no),
|
||||
candidate.path_without_token(Decode::no)
|
||||
);
|
||||
}
|
||||
else
|
||||
|
||||
auto rsplit_once(std::string_view str, char sep)
|
||||
{
|
||||
canonical_name = util::concat_scheme_url(
|
||||
res_scheme,
|
||||
util::join_url(config.location, config.name)
|
||||
auto [head, tail] = util::rstrip_if_parts(str, [sep](char c) { return c != sep; });
|
||||
if (head.empty())
|
||||
{
|
||||
return std::array{ head, tail };
|
||||
}
|
||||
return std::array{ head.substr(0, head.size() - 1), tail };
|
||||
}
|
||||
|
||||
auto
|
||||
make_platforms(util::flat_set<std::string> filters, const std::vector<std::string>& defaults)
|
||||
{
|
||||
if (filters.empty())
|
||||
{
|
||||
for (const auto& plat : defaults)
|
||||
{
|
||||
filters.insert(plat);
|
||||
}
|
||||
}
|
||||
return filters;
|
||||
};
|
||||
}
|
||||
|
||||
Channel ChannelContext::from_any_path(specs::ChannelSpec&& spec)
|
||||
{
|
||||
auto uri = specs::CondaURL::parse(util::path_or_url_to_url(spec.location()));
|
||||
|
||||
auto path = uri.pretty_path();
|
||||
auto [parent, current] = rsplit_once(path, '/');
|
||||
for (const auto& [canonical_name, chan] : get_custom_channels())
|
||||
{
|
||||
if (url_match(chan.url(), uri))
|
||||
{
|
||||
return Channel(
|
||||
/* url= */ std::move(uri),
|
||||
/* location= */ chan.url().pretty_str(specs::CondaURL::StripScheme::yes),
|
||||
/* name= */ std::string(util::rstrip(parent, '/')),
|
||||
/* canonical_name= */ std::string(canonical_name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto& ca = get_channel_alias(); url_match(ca, uri))
|
||||
{
|
||||
auto name = util::strip(util::remove_prefix(uri.path(), ca.path()), '/');
|
||||
return Channel(
|
||||
/* url= */ std::move(uri),
|
||||
/* location= */ ca.pretty_str(specs::CondaURL::StripScheme::yes),
|
||||
/* name= */ std::string(name),
|
||||
/* canonical_name= */ std::string(name)
|
||||
);
|
||||
}
|
||||
|
||||
std::string user = url.user(); // % encoded
|
||||
std::string password = url.password(); // % encoded
|
||||
auto canonical_name = uri.pretty_str();
|
||||
return Channel(
|
||||
/* scheme= */ res_scheme,
|
||||
/* location= */ config.location,
|
||||
/* name= */ config.name,
|
||||
/* canonical_name= */ canonical_name,
|
||||
/* user= */ user.empty() ? config.user : user,
|
||||
/* password= */ password.empty() ? config.password : password,
|
||||
/* token= */ token.empty() ? config.token : token,
|
||||
/* package_filename= */ package_name
|
||||
/* url= */ std::move(uri),
|
||||
/* location= */ std::string(util::rstrip(parent, '/')),
|
||||
/* name= */ std::string(util::rstrip(current, '/')),
|
||||
/* canonical_name= */ std::move(canonical_name)
|
||||
);
|
||||
}
|
||||
|
||||
Channel ChannelContext::from_package_path(specs::ChannelSpec&& spec)
|
||||
{
|
||||
assert(spec.type() == specs::ChannelSpec::Type::PackagePath);
|
||||
return from_any_path(std::move(spec));
|
||||
}
|
||||
|
||||
Channel ChannelContext::from_path(specs::ChannelSpec&& spec)
|
||||
{
|
||||
assert(spec.type() == specs::ChannelSpec::Type::Path);
|
||||
auto platforms = make_platforms(spec.clear_platform_filters(), m_context.platforms());
|
||||
auto chan = from_any_path(std::move(spec));
|
||||
chan.m_platforms = std::move(platforms);
|
||||
return chan;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// Channel configuration
|
||||
struct channel_configuration
|
||||
{
|
||||
specs::CondaURL url;
|
||||
std::string location;
|
||||
std::string name;
|
||||
std::string canonical_name;
|
||||
};
|
||||
|
||||
channel_configuration
|
||||
read_channel_configuration(ChannelContext& channel_context, specs::CondaURL url)
|
||||
{
|
||||
assert(url.scheme() != "file");
|
||||
|
||||
url.clear_package();
|
||||
url.clear_token();
|
||||
url.clear_password();
|
||||
url.clear_user();
|
||||
|
||||
std::string default_location = url.pretty_str(
|
||||
specs::CondaURL::StripScheme::yes,
|
||||
'/',
|
||||
specs::CondaURL::Credentials::Remove
|
||||
);
|
||||
|
||||
// Case 4: custom_channels matches
|
||||
for (const auto& [canonical_name, chan] : channel_context.get_custom_channels())
|
||||
{
|
||||
std::string test_url = util::join_url(chan.location(), chan.name());
|
||||
if (vector_is_prefix(util::split(test_url, "/"), util::split(default_location, "/")))
|
||||
{
|
||||
auto subname = std::string(
|
||||
util::strip(default_location.replace(0u, test_url.size(), ""), '/')
|
||||
);
|
||||
|
||||
auto location = chan.location();
|
||||
auto name = util::join_url(chan.name(), subname);
|
||||
auto l_url = specs::CondaURL::parse(chan.base_url());
|
||||
l_url.append_path(subname);
|
||||
l_url.set_scheme(url.scheme());
|
||||
l_url.set_user(chan.user().value_or(""));
|
||||
l_url.set_password(chan.password().value_or(""));
|
||||
if (auto token = chan.token().value_or(""); !token.empty())
|
||||
{
|
||||
l_url.set_token(std::move(token));
|
||||
}
|
||||
return channel_configuration{
|
||||
/* .url= */ std::move(l_url),
|
||||
/* .location= */ std::move(location),
|
||||
/* .name= */ std::move(name),
|
||||
/* .canonical_name= */ std::move(canonical_name),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Case 5: channel_alias match
|
||||
const auto& ca = channel_context.get_channel_alias();
|
||||
if (auto ca_location = channel_alias_location(ca);
|
||||
util::starts_with(default_location, ca_location))
|
||||
{
|
||||
auto name = std::string(
|
||||
util::strip(util::remove_prefix(default_location, ca_location), '/')
|
||||
);
|
||||
auto l_url = specs::CondaURL::parse(util::join_url(ca_location, name));
|
||||
l_url.set_scheme(url.scheme());
|
||||
l_url.set_user(ca.user());
|
||||
l_url.set_password(ca.password());
|
||||
if (auto token = ca.token(); !token.empty())
|
||||
{
|
||||
l_url.set_token(std::move(token));
|
||||
}
|
||||
return channel_configuration{
|
||||
/*. .url= */ std::move(l_url),
|
||||
/* .location= */ std::move(ca_location),
|
||||
/* .name= */ name,
|
||||
/* .canonical_name= */ name,
|
||||
};
|
||||
}
|
||||
|
||||
// Case 2: migrated_custom_channels not implemented yet
|
||||
// Case 3: migrated_channel_aliases not implemented yet
|
||||
|
||||
// Case 1: No path given, channel name is ""
|
||||
// Case 7: fallback, channel_location = host:port and channel_name = path
|
||||
auto name = std::string(util::strip(url.path_without_token(), '/'));
|
||||
auto location = url.authority(specs::CondaURL::Credentials::Remove);
|
||||
auto canonical_name = url.pretty_str(
|
||||
util::URL::StripScheme::no,
|
||||
/* rstrip_path/ */ '/',
|
||||
specs::CondaURL::Credentials::Remove
|
||||
);
|
||||
return channel_configuration{
|
||||
/* .url= */ std::move(url),
|
||||
/* .location= */ std::move(location),
|
||||
/* .name= */ std::move(name),
|
||||
/* .canonical_name= */ std::move(canonical_name),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Channel ChannelContext::from_url(specs::ChannelSpec&& spec)
|
||||
{
|
||||
assert(util::url_has_scheme(spec.location()));
|
||||
auto url = specs::CondaURL::parse(spec.location());
|
||||
|
||||
auto config = read_channel_configuration(*this, url);
|
||||
|
||||
using Decode = typename specs::CondaURL::Decode;
|
||||
using Encode = typename specs::CondaURL::Encode;
|
||||
if (url.user(Decode::no).empty() && !config.url.user(Decode::no).empty())
|
||||
{
|
||||
url.set_user(config.url.clear_user(), Encode::no);
|
||||
}
|
||||
if (url.password(Decode::no).empty() && !config.url.password(Decode::no).empty())
|
||||
{
|
||||
url.set_password(config.url.clear_password(), Encode::no);
|
||||
}
|
||||
if (url.token().empty() && !config.url.token().empty())
|
||||
{
|
||||
url.set_token(config.url.token());
|
||||
}
|
||||
return Channel(
|
||||
/* url= */ std::move(url),
|
||||
/* location= */ std::move(config.location),
|
||||
/* name= */ std::move(config.name),
|
||||
/* canonical_name= */ std::move(config.canonical_name)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -586,15 +648,15 @@ namespace mamba
|
|||
}
|
||||
else
|
||||
{
|
||||
const Channel& alias = get_channel_alias();
|
||||
const auto& alias = get_channel_alias();
|
||||
return Channel(
|
||||
/* scheme= */ alias.scheme(),
|
||||
/* location= */ alias.location(),
|
||||
/* location= */ channel_alias_location(alias),
|
||||
/* name= */ name,
|
||||
/* canonical_name= */ name,
|
||||
/* user= */ alias.user().value_or(""),
|
||||
/* password= */ alias.password().value_or(""),
|
||||
/* token= */ alias.token().value_or("")
|
||||
/* user= */ alias.user(),
|
||||
/* password= */ alias.password(),
|
||||
/* token= */ alias.token()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -611,42 +673,56 @@ namespace mamba
|
|||
);
|
||||
}
|
||||
|
||||
std::string value = in_value;
|
||||
auto platforms = take_platforms(m_context, value);
|
||||
auto spec = specs::ChannelSpec::parse(in_value);
|
||||
|
||||
auto chan = util::url_has_scheme(value) ? from_url(fix_win_path(value))
|
||||
: util::is_explicit_path(value) ? from_url(util::path_or_url_to_url(value))
|
||||
: specs::has_archive_extension(value) ? from_url(fix_win_path(value))
|
||||
: from_name(value);
|
||||
auto get_platforms = [&]()
|
||||
{
|
||||
auto out = spec.platform_filters();
|
||||
|
||||
chan.m_platforms = std::move(platforms);
|
||||
if (out.empty())
|
||||
{
|
||||
for (const auto& plat : m_context.platforms())
|
||||
{
|
||||
out.insert(plat);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
return chan;
|
||||
return [&](specs::ChannelSpec&& l_spec) -> Channel
|
||||
{
|
||||
switch (l_spec.type())
|
||||
{
|
||||
case specs::ChannelSpec::Type::PackagePath:
|
||||
{
|
||||
return from_package_path(std::move(l_spec));
|
||||
}
|
||||
case specs::ChannelSpec::Type::Path:
|
||||
{
|
||||
return from_path(std::move(l_spec));
|
||||
}
|
||||
case specs::ChannelSpec::Type::PackageURL:
|
||||
{
|
||||
return from_url(std::move(l_spec));
|
||||
}
|
||||
case specs::ChannelSpec::Type::URL:
|
||||
{
|
||||
auto plats = get_platforms();
|
||||
auto chan = from_url(std::move(l_spec));
|
||||
chan.m_platforms = std::move(plats);
|
||||
return chan;
|
||||
}
|
||||
case specs::ChannelSpec::Type::Name:
|
||||
{
|
||||
auto chan = from_name(l_spec.location());
|
||||
chan.m_platforms = get_platforms();
|
||||
return chan;
|
||||
}
|
||||
}
|
||||
throw std::invalid_argument("Invalid ChannelSpec::Type");
|
||||
}(std::move(spec));
|
||||
}
|
||||
|
||||
Channel ChannelContext::from_alias(std::string_view alias)
|
||||
{
|
||||
auto url = specs::CondaURL::parse(alias);
|
||||
|
||||
std::string token = std::string(url.token());
|
||||
std::string user = url.user(); // % encoded
|
||||
url.clear_user();
|
||||
std::string password = url.password(); // % encoded
|
||||
url.clear_password();
|
||||
url.clear_token();
|
||||
|
||||
return Channel(
|
||||
/* scheme= */ url.scheme(),
|
||||
/* location= */ url.pretty_str(specs::CondaURL::StripScheme::yes, '/'),
|
||||
/* name= */ "<alias>",
|
||||
/* canonical_name= */ "<alias>",
|
||||
/* user= */ user,
|
||||
/* password= */ password,
|
||||
/* token= */ token
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const Channel& ChannelContext::make_channel(const std::string& value)
|
||||
{
|
||||
auto res = m_channel_cache.find(value);
|
||||
|
@ -723,7 +799,7 @@ namespace mamba
|
|||
return result;
|
||||
}
|
||||
|
||||
const Channel& ChannelContext::get_channel_alias() const
|
||||
const specs::CondaURL& ChannelContext::get_channel_alias() const
|
||||
{
|
||||
return m_channel_alias;
|
||||
}
|
||||
|
@ -740,7 +816,7 @@ namespace mamba
|
|||
|
||||
ChannelContext::ChannelContext(Context& context)
|
||||
: m_context(context)
|
||||
, m_channel_alias(from_alias(m_context.channel_alias))
|
||||
, m_channel_alias(specs::CondaURL::parse(util::path_or_url_to_url(m_context.channel_alias)))
|
||||
{
|
||||
init_custom_channels();
|
||||
}
|
||||
|
|
|
@ -102,7 +102,10 @@ namespace mamba
|
|||
|
||||
channel = parsed_channel.canonical_name();
|
||||
// TODO how to handle this with multiple platforms?
|
||||
subdir = parsed_channel.platforms()[0];
|
||||
if (const auto& plats = parsed_channel.platforms(); !plats.empty())
|
||||
{
|
||||
subdir = plats.front();
|
||||
}
|
||||
fn = *parsed_channel.package_filename();
|
||||
url = spec_str;
|
||||
is_file = true;
|
||||
|
|
|
@ -186,7 +186,7 @@ namespace mamba
|
|||
out,
|
||||
" {:<15} {}\n",
|
||||
"URL",
|
||||
url.pretty_str(CondaURL::StripScheme::no, '/', CondaURL::HideConfidential::yes)
|
||||
url.pretty_str(CondaURL::StripScheme::no, '/', CondaURL::Credentials::Hide)
|
||||
);
|
||||
|
||||
fmt::print(out, fmtstring, "MD5", pkg.md5.empty() ? "Not available" : pkg.md5);
|
||||
|
|
|
@ -1621,14 +1621,16 @@ namespace mamba
|
|||
|
||||
if (host.empty())
|
||||
{
|
||||
options = {
|
||||
scheme,
|
||||
"all",
|
||||
};
|
||||
options = { std::string(scheme), "all" };
|
||||
}
|
||||
else
|
||||
{
|
||||
options = { scheme + "://" + host, scheme, "all://" + host, "all" };
|
||||
options = {
|
||||
util::concat(scheme, "://", host),
|
||||
std::string(scheme),
|
||||
util::concat("all://", host),
|
||||
"all",
|
||||
};
|
||||
}
|
||||
|
||||
for (auto& option : options)
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
// Copyright (c) 2023, 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.
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
#include "mamba/specs/archive.hpp"
|
||||
#include "mamba/specs/channel_spec.hpp"
|
||||
#include "mamba/specs/platform.hpp"
|
||||
#include "mamba/util/path_manip.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/util/url_manip.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
// Defined in conda_url.cpp
|
||||
[[nodiscard]] auto find_slash_and_platform(std::string_view path)
|
||||
-> std::tuple<std::size_t, std::size_t, std::optional<Platform>>;
|
||||
|
||||
namespace
|
||||
{
|
||||
using dynamic_platform_set = ChannelSpec::dynamic_platform_set;
|
||||
|
||||
auto parse_platform_list(std::string_view plats) -> dynamic_platform_set
|
||||
{
|
||||
static constexpr auto is_not_sep = [](char c) -> bool
|
||||
{ return !util::contains(ChannelSpec::platform_separators, c); };
|
||||
|
||||
auto out = dynamic_platform_set{};
|
||||
auto head_rest = util::lstrip_if_parts(plats, is_not_sep);
|
||||
while (!head_rest.front().empty())
|
||||
{
|
||||
// Accepting all strings, so that user can dynamically register new platforms
|
||||
out.insert(util::to_lower(util::strip(head_rest.front())));
|
||||
head_rest = util::lstrip_if_parts(
|
||||
util::lstrip(head_rest.back(), ChannelSpec::platform_separators),
|
||||
is_not_sep
|
||||
);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
auto parse_platform_path(std::string_view str) -> std::pair<std::string, std::string>
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
auto [start, len, plat] = find_slash_and_platform(str);
|
||||
if (plat.has_value())
|
||||
{
|
||||
const auto end = (len == npos) ? str.size() : start + len;
|
||||
return {
|
||||
util::concat(str.substr(0, start), str.substr(end)),
|
||||
std::string(platform_name(plat.value())),
|
||||
};
|
||||
}
|
||||
return { {}, {} };
|
||||
}
|
||||
|
||||
auto split_location_platform(std::string_view str)
|
||||
-> std::pair<std::string, dynamic_platform_set>
|
||||
{
|
||||
if (util::ends_with(str, ']'))
|
||||
{
|
||||
// Parsing platforms in "something[linux-64,noarch]"
|
||||
const auto start_pos = str.find_last_of('[');
|
||||
if ((start_pos != std::string_view::npos) && (start_pos != 0))
|
||||
{
|
||||
return {
|
||||
std::string(util::rstrip(str.substr(0, start_pos))),
|
||||
parse_platform_list(str.substr(start_pos + 1, str.size() - start_pos - 2)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_archive_extension(str))
|
||||
{
|
||||
// Paring a platform inside a path name.
|
||||
// This is required because a channel can be instantiated from a value that already
|
||||
// contains the platform.
|
||||
auto [rest, plat] = parse_platform_path(str);
|
||||
if (!plat.empty())
|
||||
{
|
||||
rest = util::rstrip(rest, '/');
|
||||
return {
|
||||
std::move(rest),
|
||||
{ std::move(plat) },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// For single archive channel specs, we don't need to compute platform filters
|
||||
// since they are not needed to compute URLs.
|
||||
return { std::string(util::rstrip(str)), {} };
|
||||
}
|
||||
|
||||
auto parse_path(std::string_view str) -> std::string
|
||||
{
|
||||
auto out = util::path_to_posix(fs::u8path(str).lexically_normal().string());
|
||||
out = util::rstrip(out, '/');
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
auto ChannelSpec::parse(std::string_view str) -> ChannelSpec
|
||||
{
|
||||
str = util::strip(str);
|
||||
if (str.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto [location, filters] = split_location_platform(str);
|
||||
|
||||
const std::string_view scheme = util::url_get_scheme(location);
|
||||
Type type = {};
|
||||
if (scheme == "file")
|
||||
{
|
||||
type = has_archive_extension(location) ? Type::PackagePath : Type::Path;
|
||||
}
|
||||
else if (!scheme.empty())
|
||||
{
|
||||
type = has_archive_extension(location) ? Type::PackageURL : Type::URL;
|
||||
}
|
||||
else if (util::is_explicit_path(location))
|
||||
{
|
||||
location = parse_path(location);
|
||||
type = has_archive_extension(location) ? Type::PackagePath : Type::Path;
|
||||
}
|
||||
else
|
||||
{
|
||||
type = Type::Name;
|
||||
}
|
||||
|
||||
return { std::move(location), std::move(filters), type };
|
||||
}
|
||||
|
||||
ChannelSpec::ChannelSpec(std::string location, dynamic_platform_set filters, Type type)
|
||||
: m_location(std::move(location))
|
||||
, m_platform_filters(std::move(filters))
|
||||
, m_type(type)
|
||||
{
|
||||
if (m_location.empty())
|
||||
{
|
||||
m_location = std::string(default_name);
|
||||
}
|
||||
}
|
||||
|
||||
auto ChannelSpec::type() const -> Type
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
auto ChannelSpec::location() const& -> const std::string&
|
||||
{
|
||||
return m_location;
|
||||
}
|
||||
|
||||
auto ChannelSpec::location() && -> std::string
|
||||
{
|
||||
return std::move(m_location);
|
||||
}
|
||||
|
||||
auto ChannelSpec::clear_location() -> std::string
|
||||
{
|
||||
return std::exchange(m_location, "");
|
||||
}
|
||||
|
||||
auto ChannelSpec::platform_filters() const& -> const dynamic_platform_set&
|
||||
{
|
||||
return m_platform_filters;
|
||||
}
|
||||
|
||||
auto ChannelSpec::platform_filters() && -> dynamic_platform_set
|
||||
{
|
||||
return std::move(m_platform_filters);
|
||||
}
|
||||
|
||||
auto ChannelSpec::clear_platform_filters() -> dynamic_platform_set
|
||||
{
|
||||
return std::exchange(m_platform_filters, {});
|
||||
}
|
||||
}
|
|
@ -18,6 +18,33 @@
|
|||
|
||||
namespace mamba::specs
|
||||
{
|
||||
/**
|
||||
* Find the location of "/os-arch"-like subsring.
|
||||
*
|
||||
* Not a static function, it is needed in "channel_spec.cpp".
|
||||
*/
|
||||
auto find_slash_and_platform(std::string_view path)
|
||||
-> std::tuple<std::size_t, std::size_t, std::optional<Platform>>
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
auto start = std::size_t(0);
|
||||
auto end = path.find('/', start + 1);
|
||||
while (start != npos)
|
||||
{
|
||||
assert(start < end);
|
||||
const auto count = (end == npos) ? npos : end - start;
|
||||
const auto count_minus_1 = (end == npos) ? npos : end - start - 1;
|
||||
if (auto plat = platform_parse(path.substr(start + 1, count_minus_1)))
|
||||
{
|
||||
return { start, count, plat };
|
||||
}
|
||||
start = end;
|
||||
end = path.find('/', start + 1);
|
||||
}
|
||||
return { npos, 0, std::nullopt };
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
[[nodiscard]] auto is_token_char(char c) -> bool
|
||||
|
@ -45,37 +72,40 @@ namespace mamba::specs
|
|||
&& std::all_of(token_rest.cbegin(), token_rest.cend(), &is_token_char);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto find_token_and_prefix(std::string_view path)
|
||||
-> std::pair<std::size_t, std::size_t>
|
||||
[[nodiscard]] auto token_and_prefix_len(std::string_view path) -> std::size_t
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
static constexpr auto prefix = CondaURL::token_prefix;
|
||||
|
||||
const auto prefix_pos = path.find(CondaURL::token_prefix);
|
||||
if (prefix_pos == npos)
|
||||
if ((path.size() <= prefix.size()) || !util::starts_with(path, prefix))
|
||||
{
|
||||
return std::pair{ std::string_view::npos, 0ul };
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto token_pos = prefix_pos + CondaURL::token_prefix.size();
|
||||
const auto token_end_pos = path.find('/', token_pos);
|
||||
assert(token_pos < token_end_pos);
|
||||
const auto token_len = (token_end_pos == npos) ? npos : token_end_pos - token_pos;
|
||||
if (is_token(path.substr(token_pos, token_len)))
|
||||
const auto token_end_pos = path.find('/', prefix.size());
|
||||
assert(prefix.size() < token_end_pos);
|
||||
const auto token_len = (token_end_pos == npos) ? npos : token_end_pos - prefix.size();
|
||||
if (is_token(path.substr(prefix.size(), token_len)))
|
||||
{
|
||||
const auto token_and_prefix_len = (token_end_pos == npos)
|
||||
? npos
|
||||
: token_end_pos - token_pos
|
||||
+ CondaURL::token_prefix.size();
|
||||
return std::pair{ prefix_pos, token_and_prefix_len };
|
||||
return token_end_pos;
|
||||
}
|
||||
|
||||
return std::pair{ std::string_view::npos, 0ul };
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CondaURL::ensure_path_without_token_leading_slash()
|
||||
{
|
||||
if (path_without_token().empty())
|
||||
{
|
||||
set_path_without_token("/", Encode::no);
|
||||
}
|
||||
}
|
||||
|
||||
CondaURL::CondaURL(URL&& url)
|
||||
: Base(std::move(url))
|
||||
{
|
||||
ensure_path_without_token_leading_slash();
|
||||
}
|
||||
|
||||
CondaURL::CondaURL(const util::URL& url)
|
||||
|
@ -88,19 +118,43 @@ namespace mamba::specs
|
|||
return CondaURL(URL::parse(url));
|
||||
}
|
||||
|
||||
void CondaURL::set_path(std::string_view path, Encode::yes_type)
|
||||
{
|
||||
Base::set_path(path, Encode::yes);
|
||||
ensure_path_without_token_leading_slash();
|
||||
}
|
||||
|
||||
void CondaURL::set_path(std::string path, Encode::no_type)
|
||||
{
|
||||
Base::set_path(path, Encode::no);
|
||||
ensure_path_without_token_leading_slash();
|
||||
}
|
||||
|
||||
void CondaURL::append_path(std::string_view path, Encode::yes_type)
|
||||
{
|
||||
Base::append_path(path, Encode::yes);
|
||||
ensure_path_without_token_leading_slash();
|
||||
}
|
||||
|
||||
void CondaURL::append_path(std::string_view path, Encode::no_type)
|
||||
{
|
||||
Base::append_path(path, Encode::no);
|
||||
ensure_path_without_token_leading_slash();
|
||||
}
|
||||
|
||||
auto CondaURL::token() const -> std::string_view
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
const auto& l_path = path(Decode::no);
|
||||
const auto [pos, len] = find_token_and_prefix(l_path);
|
||||
if ((pos == npos) || (len == 0))
|
||||
const auto len = token_and_prefix_len(l_path);
|
||||
if (len == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
assert(token_prefix.size() < len);
|
||||
const auto token_len = (len != npos) ? len - token_prefix.size() : npos;
|
||||
return std::string_view(l_path).substr(pos + token_prefix.size(), token_len);
|
||||
return std::string_view(l_path).substr(token_prefix.size(), token_len);
|
||||
}
|
||||
|
||||
namespace
|
||||
|
@ -109,27 +163,25 @@ namespace mamba::specs
|
|||
std::string& path,
|
||||
std::size_t pos,
|
||||
std::size_t len,
|
||||
std::string_view token
|
||||
std::string_view new_token
|
||||
)
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
assert(CondaURL::token_prefix.size() < len);
|
||||
const auto token_len = (len != npos) ? len - CondaURL::token_prefix.size() : npos;
|
||||
path.replace(pos + CondaURL::token_prefix.size(), token_len, token);
|
||||
path.replace(pos + CondaURL::token_prefix.size(), token_len, new_token);
|
||||
}
|
||||
}
|
||||
|
||||
void CondaURL::set_token(std::string_view token)
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
if (!is_token(token))
|
||||
{
|
||||
throw std::invalid_argument(fmt::format(R"(Invalid CondaURL token "{}")", token));
|
||||
}
|
||||
const auto [pos, len] = find_token_and_prefix(path(Decode::no));
|
||||
if ((pos == npos) || (len == 0))
|
||||
const auto len = token_and_prefix_len(path(Decode::no));
|
||||
if (len == 0)
|
||||
{
|
||||
std::string l_path = clear_path(); // percent encoded
|
||||
assert(util::starts_with(l_path, '/'));
|
||||
|
@ -138,54 +190,72 @@ namespace mamba::specs
|
|||
else
|
||||
{
|
||||
std::string l_path = clear_path(); // percent encoded
|
||||
set_token_no_check_input_impl(l_path, pos, len, token);
|
||||
set_token_no_check_input_impl(l_path, 0, len, token);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
}
|
||||
}
|
||||
|
||||
auto CondaURL::clear_token() -> bool
|
||||
{
|
||||
const auto [pos, len] = find_token_and_prefix(path(Decode::no));
|
||||
if ((pos == std::string::npos) || (len == 0))
|
||||
const auto len = token_and_prefix_len(path(Decode::no));
|
||||
if (len == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
assert(token_prefix.size() < len);
|
||||
std::string l_path = clear_path(); // percent encoded
|
||||
l_path.erase(pos, len);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
l_path.erase(0, len);
|
||||
Base::set_path(std::move(l_path), Encode::no);
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
auto CondaURL::path_without_token(Decode::no_type) const -> std::string_view
|
||||
{
|
||||
[[nodiscard]] auto find_slash_and_platform(std::string_view path)
|
||||
-> std::tuple<std::size_t, std::size_t, std::optional<Platform>>
|
||||
const auto& full_path = path(Decode::no);
|
||||
if (const auto len = token_and_prefix_len(full_path); len > 0)
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
assert(!path.empty() && (path.front() == '/'));
|
||||
auto start = std::size_t(0);
|
||||
auto end = path.find('/', start + 1);
|
||||
while (start != npos)
|
||||
{
|
||||
assert(start < end);
|
||||
const auto count = (end == npos) ? npos : end - start;
|
||||
const auto count_minus_1 = (end == npos) ? npos : end - start - 1;
|
||||
if (auto plat = platform_parse(path.substr(start + 1, count_minus_1)))
|
||||
{
|
||||
return { start, count, plat };
|
||||
}
|
||||
start = end;
|
||||
end = path.find('/', start + 1);
|
||||
}
|
||||
return { npos, 0, std::nullopt };
|
||||
return std::string_view(full_path).substr(std::min(len, full_path.size()));
|
||||
}
|
||||
return full_path;
|
||||
}
|
||||
|
||||
auto CondaURL::path_without_token(Decode::yes_type) const -> std::string
|
||||
{
|
||||
return util::url_decode(path_without_token(Decode::no));
|
||||
}
|
||||
|
||||
void CondaURL::set_path_without_token(std::string_view new_path, Encode::no_type)
|
||||
{
|
||||
if (const auto len = token_and_prefix_len(path(Decode::no)); len > 0)
|
||||
{
|
||||
auto old_path = clear_path();
|
||||
old_path.erase(std::min(len, old_path.size()));
|
||||
Base::set_path(std::move(old_path), Encode::no);
|
||||
Base::append_path(new_path.empty() ? "/" : new_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Base::set_path(std::string(new_path), Encode::no);
|
||||
}
|
||||
}
|
||||
|
||||
void CondaURL::set_path_without_token(std::string_view new_path, Encode::yes_type)
|
||||
{
|
||||
clear_path_without_token();
|
||||
Base::append_path(new_path.empty() ? "/" : new_path, Encode::yes);
|
||||
}
|
||||
|
||||
auto CondaURL::clear_path_without_token() -> bool
|
||||
{
|
||||
const std::size_t old_len = path(Decode::no).size();
|
||||
set_path_without_token("", Encode::no);
|
||||
return path(Decode::no).size() != old_len;
|
||||
}
|
||||
|
||||
auto CondaURL::platform() const -> std::optional<Platform>
|
||||
{
|
||||
const auto& l_path = path(Decode::no);
|
||||
assert(!l_path.empty() && (l_path.front() == '/'));
|
||||
const auto [pos, count, plat] = find_slash_and_platform(l_path);
|
||||
return plat;
|
||||
}
|
||||
|
@ -195,6 +265,7 @@ namespace mamba::specs
|
|||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
const auto& l_path = path(Decode::no);
|
||||
assert(!l_path.empty() && (l_path.front() == '/'));
|
||||
const auto [pos, len, plat] = find_slash_and_platform(l_path);
|
||||
if (!plat.has_value())
|
||||
{
|
||||
|
@ -209,6 +280,7 @@ namespace mamba::specs
|
|||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
assert(!path(Decode::no).empty() && (path(Decode::no).front() == '/'));
|
||||
const auto [pos, len, plat] = find_slash_and_platform(path(Decode::no));
|
||||
if (!plat.has_value())
|
||||
{
|
||||
|
@ -220,7 +292,7 @@ namespace mamba::specs
|
|||
std::string l_path = clear_path(); // percent encoded
|
||||
const auto plat_len = (len != npos) ? len - 1 : npos;
|
||||
l_path.replace(pos + 1, plat_len, platform);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
Base::set_path(std::move(l_path), Encode::no);
|
||||
}
|
||||
|
||||
void CondaURL::set_platform(std::string_view platform)
|
||||
|
@ -239,6 +311,7 @@ namespace mamba::specs
|
|||
|
||||
auto CondaURL::clear_platform() -> bool
|
||||
{
|
||||
assert(!path(Decode::no).empty() && (path(Decode::no).front() == '/'));
|
||||
const auto [pos, count, plat] = find_slash_and_platform(path(Decode::no));
|
||||
if (!plat.has_value())
|
||||
{
|
||||
|
@ -247,7 +320,7 @@ namespace mamba::specs
|
|||
assert(1 < count);
|
||||
std::string l_path = clear_path(); // percent encoded
|
||||
l_path.erase(pos, count);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
Base::set_path(std::move(l_path), Encode::no);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -287,11 +360,11 @@ namespace mamba::specs
|
|||
auto l_path = clear_path();
|
||||
const auto pos = std::min(std::min(l_path.rfind('/'), l_path.size()) + 1ul, l_path.size());
|
||||
l_path.replace(pos, std::string::npos, pkg);
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
Base::set_path(std::move(l_path), Encode::no);
|
||||
}
|
||||
else
|
||||
{
|
||||
append_path(pkg, Encode::no);
|
||||
Base::append_path(pkg, Encode::no);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,40 +375,113 @@ namespace mamba::specs
|
|||
{
|
||||
auto l_path = clear_path();
|
||||
l_path.erase(l_path.rfind('/'));
|
||||
set_path(std::move(l_path), Encode::no);
|
||||
Base::set_path(std::move(l_path), Encode::no);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto CondaURL::str(Credentials credentials) const -> std::string
|
||||
{
|
||||
std::string_view l_path = "";
|
||||
std::string_view l_token = "";
|
||||
switch (credentials)
|
||||
{
|
||||
case (Credentials::Show):
|
||||
{
|
||||
l_path = path(Decode::no);
|
||||
break;
|
||||
}
|
||||
case (Credentials::Hide):
|
||||
{
|
||||
if (token().empty())
|
||||
{
|
||||
l_path = path(Decode::no);
|
||||
}
|
||||
else
|
||||
{
|
||||
l_path = path_without_token(Decode::no);
|
||||
l_token = "*****";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case (Credentials::Remove):
|
||||
{
|
||||
l_path = path_without_token(Decode::no);
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::array<std::string_view, 7> authority = authority_elems(credentials, Decode::no);
|
||||
return util::concat(
|
||||
scheme(),
|
||||
"://",
|
||||
authority[0],
|
||||
authority[1],
|
||||
authority[2],
|
||||
authority[3],
|
||||
authority[4],
|
||||
authority[5],
|
||||
authority[6],
|
||||
l_token.empty() ? "" : token_prefix,
|
||||
l_token,
|
||||
l_path,
|
||||
query().empty() ? "" : "?",
|
||||
query(),
|
||||
fragment().empty() ? "" : "#",
|
||||
fragment()
|
||||
);
|
||||
}
|
||||
|
||||
auto
|
||||
CondaURL::pretty_str(StripScheme strip_scheme, char rstrip_path, HideConfidential hide_confifential) const
|
||||
CondaURL::pretty_str(StripScheme strip_scheme, char rstrip_path, Credentials credentials) const
|
||||
-> std::string
|
||||
{
|
||||
std::string computed_path = pretty_str_path(strip_scheme, rstrip_path);
|
||||
|
||||
if (hide_confifential == HideConfidential::yes)
|
||||
std::string l_path = {};
|
||||
switch (credentials)
|
||||
{
|
||||
const auto [pos, len] = find_token_and_prefix(computed_path);
|
||||
if ((pos < std::string::npos) && (len > 0))
|
||||
case (Credentials::Show):
|
||||
{
|
||||
set_token_no_check_input_impl(computed_path, pos, len, "*****");
|
||||
l_path = pretty_str_path(strip_scheme, rstrip_path);
|
||||
break;
|
||||
}
|
||||
case (Credentials::Hide):
|
||||
{
|
||||
if (token().empty())
|
||||
{
|
||||
l_path = pretty_str_path(strip_scheme, rstrip_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
l_path = util::concat("/t/*****", path_without_token(Decode::yes));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case (Credentials::Remove):
|
||||
{
|
||||
if (token().empty())
|
||||
{
|
||||
l_path = pretty_str_path(strip_scheme, rstrip_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
l_path = path_without_token(Decode::yes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::array<std::string, 7> authority = authority_elems(credentials, Decode::yes);
|
||||
return util::concat(
|
||||
(strip_scheme == StripScheme::no) ? scheme() : "",
|
||||
(strip_scheme == StripScheme::no) ? "://" : "",
|
||||
user(Decode::yes),
|
||||
password(Decode::no).empty() ? "" : ":",
|
||||
password(Decode::no).empty()
|
||||
? ""
|
||||
: ((hide_confifential == HideConfidential::no) ? password(Decode::yes) : "*****"),
|
||||
user(Decode::no).empty() ? "" : "@",
|
||||
host(Decode::yes),
|
||||
port().empty() ? "" : ":",
|
||||
port(),
|
||||
computed_path,
|
||||
authority[0],
|
||||
authority[1],
|
||||
authority[2],
|
||||
authority[3],
|
||||
authority[4],
|
||||
authority[5],
|
||||
authority[6],
|
||||
l_path,
|
||||
query().empty() ? "" : "?",
|
||||
query(),
|
||||
fragment().empty() ? "" : "#",
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "mamba/util/build.hpp"
|
||||
#include "mamba/util/path_manip.hpp"
|
||||
|
@ -72,4 +71,30 @@ namespace mamba::util
|
|||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// TODO(C++20): Use std::ranges::split_view
|
||||
auto path_is_prefix(std::string_view parent, std::string_view child, char sep) -> bool
|
||||
{
|
||||
static constexpr auto npos = std::string_view::npos;
|
||||
|
||||
std::size_t parent_start = 0;
|
||||
std::size_t parent_end = parent.find(sep);
|
||||
std::size_t child_start = 0;
|
||||
std::size_t child_end = child.find(sep);
|
||||
auto parent_item = [&]() { return parent.substr(parent_start, parent_end); };
|
||||
auto child_item = [&]() { return child.substr(child_start, child_end); };
|
||||
while ((parent_end != npos) && (child_end != npos))
|
||||
{
|
||||
if (parent_item() != child_item())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
parent_start = parent_end + 1;
|
||||
parent_end = parent.find(sep, parent_start);
|
||||
child_start = child_end + 1;
|
||||
child_end = child.find(sep, child_start);
|
||||
}
|
||||
// Last item comparison
|
||||
return parent_item().empty() || (parent_item() == child_item());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -285,40 +285,62 @@ namespace mamba::util
|
|||
* Implementation of remove prefix/suffix functions *
|
||||
******************************************************/
|
||||
|
||||
std::string_view remove_prefix(std::string_view str, std::string_view prefix)
|
||||
std::array<std::string_view, 2> split_prefix(std::string_view str, std::string_view prefix)
|
||||
{
|
||||
if (starts_with(str, prefix))
|
||||
{
|
||||
return str.substr(prefix.size());
|
||||
return { str.substr(0, prefix.size()), str.substr(prefix.size()) };
|
||||
}
|
||||
return str;
|
||||
return { std::string_view(), str };
|
||||
}
|
||||
|
||||
std::array<std::string_view, 2> split_prefix(std::string_view str, std::string_view::value_type c)
|
||||
{
|
||||
if (starts_with(str, c))
|
||||
{
|
||||
return { str.substr(0, 1), str.substr(1) };
|
||||
}
|
||||
return { std::string_view(), str };
|
||||
}
|
||||
|
||||
std::string_view remove_prefix(std::string_view str, std::string_view prefix)
|
||||
{
|
||||
return std::get<1>(split_prefix(str, prefix));
|
||||
}
|
||||
|
||||
std::string_view remove_prefix(std::string_view str, std::string_view::value_type c)
|
||||
{
|
||||
if (starts_with(str, c))
|
||||
return std::get<1>(split_prefix(str, c));
|
||||
}
|
||||
|
||||
std::array<std::string_view, 2> split_suffix(std::string_view str, std::string_view suffix)
|
||||
{
|
||||
if (ends_with(str, suffix))
|
||||
{
|
||||
return str.substr(1);
|
||||
auto suffix_pos = str.size() - suffix.size();
|
||||
return { str.substr(0, suffix_pos), str.substr(suffix_pos) };
|
||||
}
|
||||
return str;
|
||||
return { str, std::string_view() };
|
||||
}
|
||||
|
||||
std::array<std::string_view, 2> split_suffix(std::string_view str, std::string_view::value_type c)
|
||||
{
|
||||
if (ends_with(str, c))
|
||||
{
|
||||
auto suffix_pos = str.size() - 1;
|
||||
return { str.substr(0, suffix_pos), str.substr(suffix_pos) };
|
||||
}
|
||||
return { str, std::string_view() };
|
||||
}
|
||||
|
||||
std::string_view remove_suffix(std::string_view str, std::string_view suffix)
|
||||
{
|
||||
if (ends_with(str, suffix))
|
||||
{
|
||||
return str.substr(0, str.size() - suffix.size());
|
||||
}
|
||||
return str;
|
||||
return std::get<0>(split_suffix(str, suffix));
|
||||
}
|
||||
|
||||
std::string_view remove_suffix(std::string_view str, std::string_view::value_type c)
|
||||
{
|
||||
if (ends_with(str, c))
|
||||
{
|
||||
return str.substr(0, str.size() - 1);
|
||||
}
|
||||
return str;
|
||||
return std::get<0>(split_suffix(str, c));
|
||||
}
|
||||
|
||||
/***************************************
|
||||
|
@ -432,7 +454,7 @@ namespace mamba::util
|
|||
std::array<std::basic_string_view<Char>, 2>
|
||||
lstrip_parts_impl(std::basic_string_view<Char> input, CharOrStrView chars)
|
||||
{
|
||||
std::size_t const start = input.find_first_not_of(chars);
|
||||
const std::size_t start = input.find_first_not_of(chars);
|
||||
if (start == std::basic_string_view<Char>::npos)
|
||||
{
|
||||
return { input, std::basic_string_view<Char>{} };
|
||||
|
@ -469,7 +491,7 @@ namespace mamba::util
|
|||
std::array<std::basic_string_view<Char>, 2>
|
||||
rstrip_parts_impl(std::basic_string_view<Char> input, CharOrStrView chars)
|
||||
{
|
||||
std::size_t const end = input.find_last_not_of(chars);
|
||||
const std::size_t end = input.find_last_not_of(chars);
|
||||
if (end == std::basic_string_view<Char>::npos)
|
||||
{
|
||||
return { std::basic_string_view<Char>{}, input };
|
||||
|
@ -506,13 +528,13 @@ namespace mamba::util
|
|||
std::array<std::basic_string_view<Char>, 3>
|
||||
strip_parts_impl(std::basic_string_view<Char> input, CharOrStrView chars)
|
||||
{
|
||||
std::size_t const start = input.find_first_not_of(chars);
|
||||
const std::size_t start = input.find_first_not_of(chars);
|
||||
if (start == std::basic_string_view<Char>::npos)
|
||||
{
|
||||
return { input, {}, {} };
|
||||
}
|
||||
std::size_t const end = input.find_last_not_of(chars) + 1;
|
||||
std::size_t const length = end - start;
|
||||
const std::size_t end = input.find_last_not_of(chars) + 1;
|
||||
const std::size_t length = end - start;
|
||||
return { input.substr(0, start), input.substr(start, length), input.substr(end) };
|
||||
}
|
||||
}
|
||||
|
@ -554,8 +576,8 @@ namespace mamba::util
|
|||
|
||||
std::vector<std::basic_string<Char>> result;
|
||||
|
||||
std::size_t const len = input.size();
|
||||
std::size_t const n = sep.size();
|
||||
const std::size_t len = input.size();
|
||||
const std::size_t n = sep.size();
|
||||
std::size_t i = 0;
|
||||
std::size_t j = 0;
|
||||
|
||||
|
@ -597,8 +619,8 @@ namespace mamba::util
|
|||
|
||||
std::vector<std::basic_string<Char>> result;
|
||||
|
||||
std::size_t const len = input.size();
|
||||
std::size_t const n = sep.size();
|
||||
const std::size_t len = input.size();
|
||||
const std::size_t n = sep.size();
|
||||
std::size_t i = len;
|
||||
std::size_t j = len;
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
|
||||
#include <curl/urlapi.h>
|
||||
#include <fmt/format.h>
|
||||
|
@ -180,7 +182,7 @@ namespace mamba::util
|
|||
file_uri_unc2_to_unc4(url),
|
||||
CURLU_NON_SUPPORT_SCHEME | CURLU_DEFAULT_SCHEME,
|
||||
};
|
||||
out.set_scheme(handle.get_part(CURLUPART_SCHEME).value_or(std::string(URL::https)));
|
||||
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(""));
|
||||
|
@ -192,20 +194,34 @@ namespace mamba::util
|
|||
return out;
|
||||
}
|
||||
|
||||
auto URL::scheme() const -> const std::string&
|
||||
auto URL::scheme_is_defaulted() const -> bool
|
||||
{
|
||||
return m_scheme.empty();
|
||||
}
|
||||
|
||||
auto URL::scheme() const -> std::string_view
|
||||
{
|
||||
if (scheme_is_defaulted())
|
||||
{
|
||||
return https;
|
||||
}
|
||||
return m_scheme;
|
||||
}
|
||||
|
||||
void URL::set_scheme(std::string_view scheme)
|
||||
{
|
||||
if (scheme.empty())
|
||||
{
|
||||
throw std::invalid_argument("Cannot set empty scheme");
|
||||
}
|
||||
m_scheme = util::to_lower(util::rstrip(scheme));
|
||||
}
|
||||
|
||||
auto URL::clear_scheme() -> std::string
|
||||
{
|
||||
if (scheme_is_defaulted())
|
||||
{
|
||||
return std::string(https);
|
||||
}
|
||||
return std::exchange(m_scheme, "");
|
||||
}
|
||||
|
||||
auto URL::user(Decode::no_type) const -> const std::string&
|
||||
{
|
||||
return m_user;
|
||||
|
@ -256,16 +272,74 @@ namespace mamba::util
|
|||
return std::exchange(m_password, "");
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename Str, typename UGetter, typename PGetter>
|
||||
auto
|
||||
authentication_elems_impl(URL::Credentials credentials, UGetter&& get_user, PGetter&& get_password)
|
||||
{
|
||||
switch (credentials)
|
||||
{
|
||||
case (URL::Credentials::Show):
|
||||
{
|
||||
Str user = get_user();
|
||||
Str pass = user.empty() ? "" : get_password();
|
||||
Str sep = pass.empty() ? "" : ":";
|
||||
return std::array<Str, 3>{ std::move(user), std::move(sep), std::move(pass) };
|
||||
}
|
||||
case (URL::Credentials::Hide):
|
||||
{
|
||||
Str user = get_user();
|
||||
Str pass = user.empty() ? "" : "*****";
|
||||
Str sep = user.empty() ? "" : ":";
|
||||
return std::array<Str, 3>{ std::move(user), std::move(sep), std::move(pass) };
|
||||
}
|
||||
case (URL::Credentials::Remove):
|
||||
{
|
||||
return std::array<Str, 3>{ "", "", "" };
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
throw std::invalid_argument("Invalid enum number");
|
||||
}
|
||||
}
|
||||
|
||||
auto URL::authentication_elems(Credentials credentials, Decode::no_type) const
|
||||
-> std::array<std::string_view, 3>
|
||||
{
|
||||
return authentication_elems_impl<std::string_view>(
|
||||
credentials,
|
||||
[&]() -> std::string_view { return user(Decode::no); },
|
||||
[&]() -> std::string_view { return password(Decode::no); }
|
||||
);
|
||||
}
|
||||
|
||||
auto URL::authentication_elems(Credentials credentials, Decode::yes_type) const
|
||||
-> std::array<std::string, 3>
|
||||
{
|
||||
return authentication_elems_impl<std::string>(
|
||||
credentials,
|
||||
[&]() -> std::string { return user(Decode::yes); },
|
||||
[&]() -> std::string { return password(Decode::yes); }
|
||||
);
|
||||
}
|
||||
|
||||
auto URL::authentication() const -> std::string
|
||||
{
|
||||
const auto& u = user(Decode::no);
|
||||
const auto& p = password(Decode::no);
|
||||
return p.empty() ? u : util::concat(u, ':', p);
|
||||
return std::apply(
|
||||
[](auto&&... elem) { return util::concat(std::forward<decltype(elem)>(elem)...); },
|
||||
authentication_elems(Credentials::Show, Decode::no)
|
||||
);
|
||||
}
|
||||
|
||||
auto URL::host_is_defaulted() const -> bool
|
||||
{
|
||||
return m_host.empty();
|
||||
}
|
||||
|
||||
auto URL::host(Decode::no_type) const -> std::string_view
|
||||
{
|
||||
if ((m_scheme != "file") && m_host.empty())
|
||||
if ((scheme() != "file") && host_is_defaulted())
|
||||
{
|
||||
return localhost;
|
||||
}
|
||||
|
@ -295,12 +369,9 @@ namespace mamba::util
|
|||
|
||||
auto URL::clear_host() -> std::string
|
||||
{
|
||||
// Cheap == comparison that works because of class invariant
|
||||
if (auto l_host = host(Decode::no); l_host.data() != m_host.data())
|
||||
if (host_is_defaulted())
|
||||
{
|
||||
auto out = std::string(l_host);
|
||||
set_host("", Encode::no);
|
||||
return out;
|
||||
return std::string(host(Decode::no));
|
||||
}
|
||||
return std::exchange(m_host, "");
|
||||
}
|
||||
|
@ -324,19 +395,50 @@ namespace mamba::util
|
|||
return std::exchange(m_port, "");
|
||||
}
|
||||
|
||||
auto URL::authority() const -> std::string
|
||||
namespace
|
||||
{
|
||||
const auto& l_user = user(Decode::no);
|
||||
const auto& l_pass = password(Decode::no);
|
||||
const auto& l_host = host(Decode::no);
|
||||
return util::concat(
|
||||
l_user,
|
||||
l_pass.empty() ? "" : ":",
|
||||
l_pass,
|
||||
l_user.empty() ? "" : "@",
|
||||
l_host,
|
||||
m_port.empty() ? "" : ":",
|
||||
m_port
|
||||
template <typename Str>
|
||||
auto authority_elems_impl(std::array<Str, 3> user_sep_pass, Str host, Str port)
|
||||
{
|
||||
const bool has_auth = !user_sep_pass[0].empty();
|
||||
const bool has_port = !port.empty();
|
||||
return std::array<Str, 7>{
|
||||
std::move(user_sep_pass[0]),
|
||||
std::move(user_sep_pass[1]),
|
||||
std::move(user_sep_pass[2]),
|
||||
Str(has_auth ? "@" : ""),
|
||||
std::move(host),
|
||||
Str(has_port ? ":" : ""),
|
||||
std::move(port),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
auto URL::authority_elems(Credentials credentials, Decode::no_type) const
|
||||
-> std::array<std::string_view, 7>
|
||||
{
|
||||
return authority_elems_impl<std::string_view>(
|
||||
authentication_elems(credentials, Decode::no),
|
||||
host(Decode::no),
|
||||
port()
|
||||
);
|
||||
}
|
||||
|
||||
auto URL::authority_elems(Credentials credentials, Decode::yes_type) const
|
||||
-> std::array<std::string, 7>
|
||||
{
|
||||
return authority_elems_impl<std::string>(
|
||||
authentication_elems(credentials, Decode::yes),
|
||||
host(Decode::yes),
|
||||
port()
|
||||
);
|
||||
}
|
||||
|
||||
auto URL::authority(Credentials credentials) const -> std::string
|
||||
{
|
||||
return std::apply(
|
||||
[](auto&&... elem) { return util::concat(std::forward<decltype(elem)>(elem)...); },
|
||||
authority_elems(credentials, Decode::no)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -396,7 +498,7 @@ namespace mamba::util
|
|||
auto URL::pretty_path() const -> std::string
|
||||
{
|
||||
// All paths start with a '/' except those like "file:///C:/folder/file.txt"
|
||||
if (m_scheme == "file")
|
||||
if (on_win && scheme() == "file")
|
||||
{
|
||||
assert(util::starts_with(m_path, '/'));
|
||||
auto path_no_slash = url_decode(std::string_view(m_path).substr(1));
|
||||
|
@ -464,18 +566,19 @@ namespace mamba::util
|
|||
return std::exchange(m_fragment, "");
|
||||
}
|
||||
|
||||
auto URL::str() const -> std::string
|
||||
auto URL::str(Credentials credentials) const -> std::string
|
||||
{
|
||||
std::array<std::string_view, 7> authority = authority_elems(credentials, Decode::no);
|
||||
return util::concat(
|
||||
scheme(),
|
||||
"://",
|
||||
user(Decode::no),
|
||||
m_password.empty() ? "" : ":",
|
||||
password(Decode::no),
|
||||
m_user.empty() ? "" : "@",
|
||||
host(Decode::no),
|
||||
m_port.empty() ? "" : ":",
|
||||
port(),
|
||||
authority[0],
|
||||
authority[1],
|
||||
authority[2],
|
||||
authority[3],
|
||||
authority[4],
|
||||
authority[5],
|
||||
authority[6],
|
||||
path(Decode::no),
|
||||
m_query.empty() ? "" : "?",
|
||||
m_query,
|
||||
|
@ -488,7 +591,7 @@ namespace mamba::util
|
|||
{
|
||||
std::string computed_path = {};
|
||||
// When stripping file scheme, not showing leading '/' for Windows path with drive
|
||||
if ((m_scheme == "file") && (strip_scheme == StripScheme::yes) && host(Decode::no).empty())
|
||||
if ((scheme() == "file") && (strip_scheme == StripScheme::yes) && host(Decode::no).empty())
|
||||
{
|
||||
computed_path = pretty_path();
|
||||
}
|
||||
|
@ -500,22 +603,20 @@ namespace mamba::util
|
|||
return computed_path;
|
||||
}
|
||||
|
||||
auto
|
||||
URL::pretty_str(StripScheme strip_scheme, char rstrip_path, HideConfidential hide_confidential) const
|
||||
auto URL::pretty_str(StripScheme strip_scheme, char rstrip_path, Credentials credentials) const
|
||||
-> std::string
|
||||
{
|
||||
std::array<std::string, 7> authority = authority_elems(credentials, Decode::yes);
|
||||
return util::concat(
|
||||
(strip_scheme == StripScheme::no) ? m_scheme : "",
|
||||
(strip_scheme == StripScheme::no) ? scheme() : "",
|
||||
(strip_scheme == StripScheme::no) ? "://" : "",
|
||||
user(Decode::yes),
|
||||
m_password.empty() ? "" : ":",
|
||||
password(Decode::no).empty()
|
||||
? ""
|
||||
: ((hide_confidential == HideConfidential::no) ? password(Decode::yes) : "*****"),
|
||||
m_user.empty() ? "" : "@",
|
||||
host(Decode::yes),
|
||||
m_port.empty() ? "" : ":",
|
||||
m_port,
|
||||
authority[0],
|
||||
authority[1],
|
||||
authority[2],
|
||||
authority[3],
|
||||
authority[4],
|
||||
authority[5],
|
||||
authority[6],
|
||||
pretty_str_path(strip_scheme, rstrip_path),
|
||||
m_query.empty() ? "" : "?",
|
||||
m_query,
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <fmt/format.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "mamba/core/environment.hpp"
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
#include "mamba/util/build.hpp"
|
||||
#include "mamba/util/path_manip.hpp"
|
||||
|
@ -256,9 +257,18 @@ namespace mamba::util
|
|||
return util::concat(file_scheme, url_encode(path, '/'));
|
||||
}
|
||||
|
||||
auto abs_path_or_url_to_url(std::string_view path) -> std::string
|
||||
{
|
||||
if (url_has_scheme(path))
|
||||
{
|
||||
return std::string(path);
|
||||
}
|
||||
return abs_path_to_url(path);
|
||||
}
|
||||
|
||||
auto path_to_url(std::string_view path) -> std::string
|
||||
{
|
||||
return abs_path_to_url(fs::absolute(path).string());
|
||||
return abs_path_to_url(fs::absolute(env::expand_user(path)).lexically_normal().string());
|
||||
}
|
||||
|
||||
auto path_or_url_to_url(std::string_view path) -> std::string
|
||||
|
|
|
@ -45,6 +45,7 @@ set(LIBMAMBA_TEST_SRCS
|
|||
src/specs/test_conda_url.cpp
|
||||
src/specs/test_version.cpp
|
||||
src/specs/test_version_spec.cpp
|
||||
src/specs/test_channel_spec.cpp
|
||||
src/specs/test_repo_data.cpp
|
||||
|
||||
../longpath.manifest
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
// Copyright (c) 2023, 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.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
@ -5,7 +11,7 @@
|
|||
#include "mamba/core/channel.hpp"
|
||||
#include "mamba/core/context.hpp"
|
||||
#include "mamba/core/environment.hpp"
|
||||
#include "mamba/specs/platform.hpp"
|
||||
#include "mamba/util/flat_set.hpp"
|
||||
|
||||
#include "mambatests.hpp"
|
||||
|
||||
|
@ -13,6 +19,8 @@ namespace mamba
|
|||
{
|
||||
|
||||
static const std::string platform = std::string(specs::build_platform_name());
|
||||
using PlatformSet = typename util::flat_set<std::string>;
|
||||
using UrlSet = typename util::flat_set<std::string>;
|
||||
|
||||
static_assert(std::is_move_constructible_v<mamba::Channel>);
|
||||
static_assert(std::is_move_assignable_v<mamba::Channel>);
|
||||
|
@ -25,10 +33,7 @@ namespace mamba
|
|||
// make_simple_channel
|
||||
ChannelContext channel_context{ mambatests::context() };
|
||||
const auto& ch = channel_context.get_channel_alias();
|
||||
CHECK_EQ(ch.scheme(), "https");
|
||||
CHECK_EQ(ch.location(), "conda.anaconda.org");
|
||||
CHECK_EQ(ch.name(), "<alias>");
|
||||
CHECK_EQ(ch.canonical_name(), "<alias>");
|
||||
CHECK_EQ(ch.str(), "https://conda.anaconda.org/");
|
||||
|
||||
const auto& custom = channel_context.get_custom_channels();
|
||||
|
||||
|
@ -61,10 +66,7 @@ namespace mamba
|
|||
ChannelContext channel_context{ mambatests::context() };
|
||||
|
||||
const auto& ch = channel_context.get_channel_alias();
|
||||
CHECK_EQ(ch.scheme(), "https");
|
||||
CHECK_EQ(ch.location(), "mydomain.com/channels");
|
||||
CHECK_EQ(ch.name(), "<alias>");
|
||||
CHECK_EQ(ch.canonical_name(), "<alias>");
|
||||
CHECK_EQ(ch.str(), "https://mydomain.com/channels/");
|
||||
|
||||
const auto& custom = channel_context.get_custom_channels();
|
||||
|
||||
|
@ -81,7 +83,7 @@ namespace mamba
|
|||
CHECK_EQ(c.name(), "conda-forge");
|
||||
CHECK_EQ(c.canonical_name(), "conda-forge");
|
||||
// CHECK_EQ(c.url(), "conda-forge");
|
||||
CHECK_EQ(c.platforms(), std::vector<std::string>({ platform, "noarch" }));
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
|
||||
ctx.channel_alias = "https://conda.anaconda.org";
|
||||
}
|
||||
|
@ -98,8 +100,7 @@ namespace mamba
|
|||
ChannelContext channel_context{ ctx };
|
||||
auto base = std::string("https://ali.as/prefix-and-more/");
|
||||
auto& chan = channel_context.make_channel(base);
|
||||
std::vector<std::string> expected_urls = { base + platform, base + "noarch" };
|
||||
CHECK_EQ(chan.urls(), expected_urls);
|
||||
CHECK_EQ(chan.urls(), UrlSet{ base + platform, base + "noarch" });
|
||||
|
||||
ctx.channel_alias = "https://conda.anaconda.org";
|
||||
ctx.custom_channels.clear();
|
||||
|
@ -119,10 +120,7 @@ namespace mamba
|
|||
|
||||
ChannelContext channel_context{ ctx };
|
||||
const auto& ch = channel_context.get_channel_alias();
|
||||
CHECK_EQ(ch.scheme(), "https");
|
||||
CHECK_EQ(ch.location(), "mydomain.com/channels");
|
||||
CHECK_EQ(ch.name(), "<alias>");
|
||||
CHECK_EQ(ch.canonical_name(), "<alias>");
|
||||
CHECK_EQ(ch.str(), "https://mydomain.com/channels/");
|
||||
|
||||
{
|
||||
std::string value = "test_channel";
|
||||
|
@ -131,9 +129,11 @@ namespace mamba
|
|||
CHECK_EQ(c.location(), "/tmp");
|
||||
CHECK_EQ(c.name(), "test_channel");
|
||||
CHECK_EQ(c.canonical_name(), "test_channel");
|
||||
CHECK_EQ(c.platforms(), std::vector<std::string>({ platform, "noarch" }));
|
||||
std::vector<std::string> exp_urls({ std::string("file:///tmp/test_channel/") + platform,
|
||||
std::string("file:///tmp/test_channel/noarch") });
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
std::string("file:///tmp/test_channel/") + platform,
|
||||
"file:///tmp/test_channel/noarch",
|
||||
});
|
||||
CHECK_EQ(c.urls(), exp_urls);
|
||||
}
|
||||
|
||||
|
@ -144,11 +144,11 @@ namespace mamba
|
|||
CHECK_EQ(c.location(), "conda.mydomain.xyz");
|
||||
CHECK_EQ(c.name(), "some_channel");
|
||||
CHECK_EQ(c.canonical_name(), "some_channel");
|
||||
CHECK_EQ(c.platforms(), std::vector<std::string>({ platform, "noarch" }));
|
||||
std::vector<std::string> exp_urls(
|
||||
{ std::string("https://conda.mydomain.xyz/some_channel/") + platform,
|
||||
std::string("https://conda.mydomain.xyz/some_channel/noarch") }
|
||||
);
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
std::string("https://conda.mydomain.xyz/some_channel/") + platform,
|
||||
"https://conda.mydomain.xyz/some_channel/noarch",
|
||||
});
|
||||
CHECK_EQ(c.urls(), exp_urls);
|
||||
}
|
||||
|
||||
|
@ -164,12 +164,12 @@ namespace mamba
|
|||
ctx.custom_multichannels["xtest"] = std::vector<std::string>{
|
||||
"https://mydomain.com/conda-forge",
|
||||
"https://mydomain.com/bioconda",
|
||||
"https://mydomain.com/snakepit"
|
||||
"https://mydomain.com/snakepit",
|
||||
};
|
||||
ctx.custom_multichannels["ytest"] = std::vector<std::string>{
|
||||
"https://otherdomain.com/conda-forge",
|
||||
"https://otherdomain.com/bioconda",
|
||||
"https://otherdomain.com/snakepit"
|
||||
"https://otherdomain.com/snakepit",
|
||||
};
|
||||
|
||||
ChannelContext channel_context{ ctx };
|
||||
|
@ -179,17 +179,17 @@ namespace mamba
|
|||
CHECK_EQ(x.size(), 3);
|
||||
auto* c1 = x[0];
|
||||
|
||||
std::vector<std::string> exp_urls(
|
||||
{ std::string("https://mydomain.com/conda-forge/") + platform,
|
||||
std::string("https://mydomain.com/conda-forge/noarch") }
|
||||
);
|
||||
const UrlSet exp_urls({
|
||||
std::string("https://mydomain.com/conda-forge/") + platform,
|
||||
"https://mydomain.com/conda-forge/noarch",
|
||||
});
|
||||
|
||||
CHECK_EQ(c1->urls(), exp_urls);
|
||||
|
||||
std::vector<std::string> exp_urlsy3(
|
||||
{ std::string("https://otherdomain.com/snakepit/") + platform,
|
||||
std::string("https://otherdomain.com/snakepit/noarch") }
|
||||
);
|
||||
const UrlSet exp_urlsy3({
|
||||
std::string("https://otherdomain.com/snakepit/") + platform,
|
||||
"https://otherdomain.com/snakepit/noarch",
|
||||
});
|
||||
|
||||
auto y = channel_context.get_channels({ "ytest" });
|
||||
auto* y3 = y[2];
|
||||
|
@ -225,24 +225,24 @@ namespace mamba
|
|||
auto* c2 = x[1];
|
||||
auto* c3 = x[2];
|
||||
|
||||
std::vector<std::string> exp_urls(
|
||||
{ std::string("https://condaforge.org/channels/conda-forge/") + platform,
|
||||
std::string("https://condaforge.org/channels/conda-forge/noarch") }
|
||||
);
|
||||
const UrlSet exp_urls({
|
||||
std::string("https://condaforge.org/channels/conda-forge/") + platform,
|
||||
"https://condaforge.org/channels/conda-forge/noarch",
|
||||
});
|
||||
|
||||
CHECK_EQ(c1->urls(), exp_urls);
|
||||
|
||||
std::vector<std::string> exp_urls2(
|
||||
{ std::string("https://mydomain.com/bioconda/") + platform,
|
||||
std::string("https://mydomain.com/bioconda/noarch") }
|
||||
);
|
||||
const UrlSet exp_urls2({
|
||||
std::string("https://mydomain.com/bioconda/") + platform,
|
||||
"https://mydomain.com/bioconda/noarch",
|
||||
});
|
||||
|
||||
CHECK_EQ(c2->urls(), exp_urls2);
|
||||
|
||||
std::vector<std::string> exp_urls3(
|
||||
{ std::string("https://mydomain.xyz/xyzchannel/xyz/") + platform,
|
||||
std::string("https://mydomain.xyz/xyzchannel/xyz/noarch") }
|
||||
);
|
||||
const UrlSet exp_urls3({
|
||||
std::string("https://mydomain.xyz/xyzchannel/xyz/") + platform,
|
||||
"https://mydomain.xyz/xyzchannel/xyz/noarch",
|
||||
});
|
||||
|
||||
CHECK_EQ(c3->urls(), exp_urls3);
|
||||
|
||||
|
@ -262,17 +262,17 @@ namespace mamba
|
|||
const Channel* c2 = x[1];
|
||||
|
||||
CHECK_EQ(c1->name(), "pkgs/main");
|
||||
std::vector<std::string> exp_urls(
|
||||
{ std::string("https://repo.anaconda.com/pkgs/main/") + platform,
|
||||
std::string("https://repo.anaconda.com/pkgs/main/noarch") }
|
||||
);
|
||||
const UrlSet exp_urls({
|
||||
std::string("https://repo.anaconda.com/pkgs/main/") + platform,
|
||||
"https://repo.anaconda.com/pkgs/main/noarch",
|
||||
});
|
||||
CHECK_EQ(c1->urls(), exp_urls);
|
||||
|
||||
CHECK_EQ(c2->name(), "pkgs/r");
|
||||
std::vector<std::string> exp_urls2(
|
||||
{ std::string("https://repo.anaconda.com/pkgs/r/") + platform,
|
||||
std::string("https://repo.anaconda.com/pkgs/r/noarch") }
|
||||
);
|
||||
const UrlSet exp_urls2({
|
||||
std::string("https://repo.anaconda.com/pkgs/r/") + platform,
|
||||
"https://repo.anaconda.com/pkgs/r/noarch",
|
||||
});
|
||||
CHECK_EQ(c2->urls(), exp_urls2);
|
||||
|
||||
CHECK_EQ(c1->location(), "repo.anaconda.com");
|
||||
|
@ -285,8 +285,10 @@ namespace mamba
|
|||
TEST_CASE("custom_default_channels")
|
||||
{
|
||||
auto& ctx = mambatests::context();
|
||||
ctx.default_channels = { "https://mamba.com/test/channel",
|
||||
"https://mamba.com/stable/channel" };
|
||||
ctx.default_channels = {
|
||||
"https://mamba.com/test/channel",
|
||||
"https://mamba.com/stable/channel",
|
||||
};
|
||||
ChannelContext channel_context{ ctx };
|
||||
|
||||
auto x = channel_context.get_channels({ "defaults" });
|
||||
|
@ -294,15 +296,15 @@ namespace mamba
|
|||
const Channel* c2 = x[1];
|
||||
|
||||
CHECK_EQ(c1->name(), "test/channel");
|
||||
std::vector<std::string> exp_urls(
|
||||
{ std::string("https://mamba.com/test/channel/") + platform,
|
||||
std::string("https://mamba.com/test/channel/noarch") }
|
||||
);
|
||||
const UrlSet exp_urls({
|
||||
std::string("https://mamba.com/test/channel/") + platform,
|
||||
"https://mamba.com/test/channel/noarch",
|
||||
});
|
||||
CHECK_EQ(c1->urls(), exp_urls);
|
||||
std::vector<std::string> exp_urls2(
|
||||
{ std::string("https://mamba.com/stable/channel/") + platform,
|
||||
std::string("https://mamba.com/stable/channel/noarch") }
|
||||
);
|
||||
const UrlSet exp_urls2({
|
||||
std::string("https://mamba.com/stable/channel/") + platform,
|
||||
"https://mamba.com/stable/channel/noarch",
|
||||
});
|
||||
CHECK_EQ(c2->urls(), exp_urls2);
|
||||
|
||||
CHECK_EQ(c2->name(), "stable/channel");
|
||||
|
@ -360,11 +362,11 @@ namespace mamba
|
|||
CHECK_EQ(c.location(), "server.com/private/channels");
|
||||
CHECK_EQ(c.name(), "test_channel");
|
||||
CHECK_EQ(c.canonical_name(), "test_channel");
|
||||
CHECK_EQ(c.platforms(), std::vector<std::string>({ platform, "noarch" }));
|
||||
std::vector<std::string> exp_urls(
|
||||
{ std::string("https://server.com/private/channels/test_channel/") + platform,
|
||||
std::string("https://server.com/private/channels/test_channel/noarch") }
|
||||
);
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
std::string("https://server.com/private/channels/test_channel/") + platform,
|
||||
"https://server.com/private/channels/test_channel/noarch",
|
||||
});
|
||||
CHECK_EQ(c.urls(), exp_urls);
|
||||
}
|
||||
|
||||
|
@ -375,13 +377,12 @@ namespace mamba
|
|||
CHECK_EQ(c.location(), "server.com/private/channels");
|
||||
CHECK_EQ(c.name(), "test_channel/mylabel/xyz");
|
||||
CHECK_EQ(c.canonical_name(), "test_channel/mylabel/xyz");
|
||||
CHECK_EQ(c.platforms(), std::vector<std::string>({ platform, "noarch" }));
|
||||
std::vector<std::string> exp_urls(
|
||||
{ std::string("https://server.com/private/channels/test_channel/mylabel/xyz/")
|
||||
+ platform,
|
||||
std::string("https://server.com/private/channels/test_channel/mylabel/xyz/noarch"
|
||||
) }
|
||||
);
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
std::string("https://server.com/private/channels/test_channel/mylabel/xyz/")
|
||||
+ platform,
|
||||
"https://server.com/private/channels/test_channel/mylabel/xyz/noarch",
|
||||
});
|
||||
CHECK_EQ(c.urls(), exp_urls);
|
||||
}
|
||||
|
||||
|
@ -392,12 +393,12 @@ namespace mamba
|
|||
CHECK_EQ(c.location(), "server.com/random/channels");
|
||||
CHECK_EQ(c.name(), "random/test_channel/pkg");
|
||||
CHECK_EQ(c.canonical_name(), "random/test_channel/pkg");
|
||||
CHECK_EQ(c.platforms(), std::vector<std::string>({ platform, "noarch" }));
|
||||
std::vector<std::string> exp_urls(
|
||||
{ std::string("https://server.com/random/channels/random/test_channel/pkg/")
|
||||
+ platform,
|
||||
std::string("https://server.com/random/channels/random/test_channel/pkg/noarch") }
|
||||
);
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
const UrlSet exp_urls({
|
||||
std::string("https://server.com/random/channels/random/test_channel/pkg/")
|
||||
+ platform,
|
||||
"https://server.com/random/channels/random/test_channel/pkg/noarch",
|
||||
});
|
||||
CHECK_EQ(c.urls(), exp_urls);
|
||||
}
|
||||
|
||||
|
@ -417,7 +418,7 @@ namespace mamba
|
|||
CHECK_EQ(c.location(), "repo.mamba.pm");
|
||||
CHECK_EQ(c.name(), "conda-forge");
|
||||
CHECK_EQ(c.canonical_name(), "https://repo.mamba.pm/conda-forge");
|
||||
CHECK_EQ(c.platforms(), std::vector<std::string>({ platform, "noarch" }));
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
}
|
||||
|
||||
TEST_CASE("make_channel")
|
||||
|
@ -429,7 +430,7 @@ namespace mamba
|
|||
CHECK_EQ(c.location(), "conda.anaconda.org");
|
||||
CHECK_EQ(c.name(), "conda-forge");
|
||||
CHECK_EQ(c.canonical_name(), "conda-forge");
|
||||
CHECK_EQ(c.platforms(), std::vector<std::string>({ platform, "noarch" }));
|
||||
CHECK_EQ(c.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
|
||||
std::string value2 = "https://repo.anaconda.com/pkgs/main[" + platform + "]";
|
||||
const Channel& c2 = channel_context.make_channel(value2);
|
||||
|
@ -437,7 +438,7 @@ namespace mamba
|
|||
CHECK_EQ(c2.location(), "repo.anaconda.com");
|
||||
CHECK_EQ(c2.name(), "pkgs/main");
|
||||
CHECK_EQ(c2.canonical_name(), "https://repo.anaconda.com/pkgs/main");
|
||||
CHECK_EQ(c2.platforms(), std::vector<std::string>({ platform }));
|
||||
CHECK_EQ(c2.platforms(), PlatformSet({ platform }));
|
||||
|
||||
std::string value3 = "https://conda.anaconda.org/conda-forge[" + platform + "]";
|
||||
const Channel& c3 = channel_context.make_channel(value3);
|
||||
|
@ -445,7 +446,7 @@ namespace mamba
|
|||
CHECK_EQ(c3.location(), c.location());
|
||||
CHECK_EQ(c3.name(), c.name());
|
||||
CHECK_EQ(c3.canonical_name(), c.canonical_name());
|
||||
CHECK_EQ(c3.platforms(), std::vector<std::string>({ platform }));
|
||||
CHECK_EQ(c3.platforms(), PlatformSet({ platform }));
|
||||
|
||||
std::string value4 = "/home/mamba/test/channel_b";
|
||||
const Channel& c4 = channel_context.make_channel(value4);
|
||||
|
@ -459,7 +460,7 @@ namespace mamba
|
|||
CHECK_EQ(c4.canonical_name(), "file:///home/mamba/test/channel_b");
|
||||
#endif
|
||||
CHECK_EQ(c4.name(), "channel_b");
|
||||
CHECK_EQ(c4.platforms(), std::vector<std::string>({ platform, "noarch" }));
|
||||
CHECK_EQ(c4.platforms(), PlatformSet({ platform, "noarch" }));
|
||||
|
||||
std::string value5 = "/home/mamba/test/channel_b[" + platform + "]";
|
||||
const Channel& c5 = channel_context.make_channel(value5);
|
||||
|
@ -472,25 +473,22 @@ namespace mamba
|
|||
CHECK_EQ(c5.canonical_name(), "file:///home/mamba/test/channel_b");
|
||||
#endif
|
||||
CHECK_EQ(c5.name(), "channel_b");
|
||||
CHECK_EQ(c5.platforms(), std::vector<std::string>({ platform }));
|
||||
CHECK_EQ(c5.platforms(), PlatformSet({ platform }));
|
||||
|
||||
std::string value6a = "http://localhost:8000/conda-forge[noarch]";
|
||||
const Channel& c6a = channel_context.make_channel(value6a);
|
||||
CHECK_EQ(
|
||||
c6a.urls(false),
|
||||
std::vector<std::string>({ "http://localhost:8000/conda-forge/noarch" })
|
||||
);
|
||||
CHECK_EQ(c6a.urls(false), UrlSet({ "http://localhost:8000/conda-forge/noarch" }));
|
||||
|
||||
std::string value6b = "http://localhost:8000/conda_mirror/conda-forge[noarch]";
|
||||
const Channel& c6b = channel_context.make_channel(value6b);
|
||||
CHECK_EQ(
|
||||
c6b.urls(false),
|
||||
std::vector<std::string>({ "http://localhost:8000/conda_mirror/conda-forge/noarch" })
|
||||
UrlSet({ "http://localhost:8000/conda_mirror/conda-forge/noarch" })
|
||||
);
|
||||
|
||||
std::string value7 = "conda-forge[noarch,arbitrary]";
|
||||
const Channel& c7 = channel_context.make_channel(value7);
|
||||
CHECK_EQ(c7.platforms(), std::vector<std::string>({ "noarch", "arbitrary" }));
|
||||
CHECK_EQ(c7.platforms(), PlatformSet({ "noarch", "arbitrary" }));
|
||||
}
|
||||
|
||||
TEST_CASE("urls")
|
||||
|
@ -500,16 +498,20 @@ namespace mamba
|
|||
const Channel& c = channel_context.make_channel(value);
|
||||
CHECK_EQ(
|
||||
c.urls(),
|
||||
std::vector<std::string>({ "https://conda.anaconda.org/conda-forge/noarch",
|
||||
"https://conda.anaconda.org/conda-forge/win-64",
|
||||
"https://conda.anaconda.org/conda-forge/arbitrary" })
|
||||
UrlSet({
|
||||
"https://conda.anaconda.org/conda-forge/arbitrary",
|
||||
"https://conda.anaconda.org/conda-forge/noarch",
|
||||
"https://conda.anaconda.org/conda-forge/win-64",
|
||||
})
|
||||
);
|
||||
|
||||
const Channel& c1 = channel_context.make_channel("https://conda.anaconda.org/conda-forge");
|
||||
CHECK_EQ(
|
||||
c1.urls(),
|
||||
std::vector<std::string>({ "https://conda.anaconda.org/conda-forge/" + platform,
|
||||
"https://conda.anaconda.org/conda-forge/noarch" })
|
||||
UrlSet({
|
||||
"https://conda.anaconda.org/conda-forge/" + platform,
|
||||
"https://conda.anaconda.org/conda-forge/noarch",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -524,13 +526,9 @@ namespace mamba
|
|||
CHECK_EQ(chan.token(), "my-12345-token");
|
||||
CHECK_EQ(
|
||||
chan.urls(true),
|
||||
std::vector<std::string>{
|
||||
{ "https://conda.anaconda.org/t/my-12345-token/conda-forge/noarch" } }
|
||||
);
|
||||
CHECK_EQ(
|
||||
chan.urls(false),
|
||||
std::vector<std::string>{ { "https://conda.anaconda.org/conda-forge/noarch" } }
|
||||
UrlSet({ "https://conda.anaconda.org/t/my-12345-token/conda-forge/noarch" })
|
||||
);
|
||||
CHECK_EQ(chan.urls(false), UrlSet({ "https://conda.anaconda.org/conda-forge/noarch" }));
|
||||
}
|
||||
|
||||
TEST_CASE("add_multiple_tokens")
|
||||
|
@ -555,8 +553,7 @@ namespace mamba
|
|||
const Channel& c = channel_context.make_channel("C:\\test\\channel");
|
||||
CHECK_EQ(
|
||||
c.urls(false),
|
||||
std::vector<std::string>({ "file:///C:/test/channel/win-64",
|
||||
"file:///C:/test/channel/noarch" })
|
||||
UrlSet({ "file:///C:/test/channel/win-64", "file:///C:/test/channel/noarch" })
|
||||
);
|
||||
}
|
||||
else
|
||||
|
@ -564,8 +561,8 @@ namespace mamba
|
|||
const Channel& c = channel_context.make_channel("/test/channel");
|
||||
CHECK_EQ(
|
||||
c.urls(false),
|
||||
std::vector<std::string>({ std::string("file:///test/channel/") + platform,
|
||||
"file:///test/channel/noarch" })
|
||||
UrlSet({ std::string("file:///test/channel/") + platform,
|
||||
"file:///test/channel/noarch" })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -576,8 +573,8 @@ namespace mamba
|
|||
const Channel& c = channel_context.make_channel("http://localhost:8000/");
|
||||
CHECK_EQ(c.platform_url("win-64", false), "http://localhost:8000/win-64");
|
||||
CHECK_EQ(c.base_url(), "http://localhost:8000");
|
||||
std::vector<std::string> expected_urls({ std::string("http://localhost:8000/") + platform,
|
||||
"http://localhost:8000/noarch" });
|
||||
const UrlSet expected_urls({ std::string("http://localhost:8000/") + platform,
|
||||
"http://localhost:8000/noarch" });
|
||||
CHECK_EQ(c.urls(true), expected_urls);
|
||||
const Channel& c4 = channel_context.make_channel("http://localhost:8000");
|
||||
CHECK_EQ(c4.platform_url("linux-64", false), "http://localhost:8000/linux-64");
|
||||
|
@ -593,11 +590,11 @@ namespace mamba
|
|||
"https://localhost:8000/t/xy-12345678-1234-1234-1234-123456789012/win-64"
|
||||
);
|
||||
|
||||
std::vector<std::string> expected_urls2(
|
||||
{ std::string("https://localhost:8000/t/xy-12345678-1234-1234-1234-123456789012/")
|
||||
+ platform,
|
||||
"https://localhost:8000/t/xy-12345678-1234-1234-1234-123456789012/noarch" }
|
||||
);
|
||||
const UrlSet expected_urls2({
|
||||
std::string("https://localhost:8000/t/xy-12345678-1234-1234-1234-123456789012/")
|
||||
+ platform,
|
||||
"https://localhost:8000/t/xy-12345678-1234-1234-1234-123456789012/noarch",
|
||||
});
|
||||
|
||||
CHECK_EQ(c3.urls(true), expected_urls2);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) 2023, 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.
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "mamba/specs/conda_url.hpp"
|
||||
|
||||
namespace doctest
|
||||
{
|
||||
template <>
|
||||
struct StringMaker<mamba::specs::CondaURL>
|
||||
{
|
||||
static auto convert(const mamba::specs::CondaURL& value) -> String
|
||||
{
|
||||
return { value.str().c_str() };
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2023, 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.
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "mamba/util/flat_set.hpp"
|
||||
|
||||
namespace doctest
|
||||
{
|
||||
template <typename K, typename C, typename A>
|
||||
struct StringMaker<mamba::util::flat_set<K, C, A>>
|
||||
{
|
||||
static auto convert(const mamba::util::flat_set<K, C, A>& value) -> String
|
||||
{
|
||||
return { fmt::format("std::flat_set{{{}}}", fmt::join(value, ", ")).c_str() };
|
||||
}
|
||||
};
|
||||
}
|
|
@ -11,10 +11,10 @@
|
|||
|
||||
namespace doctest
|
||||
{
|
||||
template <typename T>
|
||||
struct StringMaker<std::vector<T>>
|
||||
template <typename T, typename A>
|
||||
struct StringMaker<std::vector<T, A>>
|
||||
{
|
||||
static auto convert(const std::vector<T>& value) -> String
|
||||
static auto convert(const std::vector<T, A>& value) -> String
|
||||
{
|
||||
return { fmt::format("std::vector{{{}}}", fmt::join(value, ", ")).c_str() };
|
||||
}
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright (c) 2023, 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.
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
#include "mamba/specs/channel_spec.hpp"
|
||||
#include "mamba/util/build.hpp"
|
||||
#include "mamba/util/path_manip.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
using namespace mamba;
|
||||
using namespace mamba::specs;
|
||||
|
||||
TEST_SUITE("specs::channel_spec")
|
||||
{
|
||||
TEST_CASE("Parsing")
|
||||
{
|
||||
using Type = typename ChannelSpec::Type;
|
||||
using PlatformSet = typename util::flat_set<std::string>;
|
||||
|
||||
SUBCASE("https://repo.anaconda.com/conda-forge")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("https://repo.anaconda.com/conda-forge");
|
||||
CHECK_EQ(spec.type(), Type::URL);
|
||||
CHECK_EQ(spec.location(), "https://repo.anaconda.com/conda-forge");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.anaconda.com/conda-forge/osx-64")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("https://repo.anaconda.com/conda-forge/osx-64");
|
||||
CHECK_EQ(spec.type(), Type::URL);
|
||||
CHECK_EQ(spec.location(), "https://repo.anaconda.com/conda-forge");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{ "osx-64" });
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.anaconda.com/conda-forge[win-64|noarch]")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("https://repo.anaconda.com/conda-forge[win-64|noarch]"
|
||||
);
|
||||
CHECK_EQ(spec.type(), Type::URL);
|
||||
CHECK_EQ(spec.location(), "https://repo.anaconda.com/conda-forge");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{ "win-64", "noarch" });
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.anaconda.com/conda-forge/linux-64/pkg-0.0-bld.conda")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse(
|
||||
"https://repo.anaconda.com/conda-forge/linux-64/pkg-0.0-bld.conda"
|
||||
);
|
||||
CHECK_EQ(spec.type(), Type::PackageURL);
|
||||
CHECK_EQ(spec.location(), "https://repo.anaconda.com/conda-forge/linux-64/pkg-0.0-bld.conda");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE("file:///Users/name/conda")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("file:///Users/name/conda");
|
||||
CHECK_EQ(spec.type(), Type::Path);
|
||||
CHECK_EQ(spec.location(), "file:///Users/name/conda");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE("file:///Users/name/conda[linux-64]")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("file:///Users/name/conda[linux-64]");
|
||||
CHECK_EQ(spec.type(), Type::Path);
|
||||
CHECK_EQ(spec.location(), "file:///Users/name/conda");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{ "linux-64" });
|
||||
}
|
||||
|
||||
SUBCASE("file://C:/Users/name/conda")
|
||||
{
|
||||
if (util::on_win)
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("file://C:/Users/name/conda");
|
||||
CHECK_EQ(spec.type(), Type::Path);
|
||||
CHECK_EQ(spec.location(), "file://C:/Users/name/conda");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("/Users/name/conda")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("/Users/name/conda");
|
||||
CHECK_EQ(spec.type(), Type::Path);
|
||||
CHECK_EQ(spec.location(), "/Users/name/conda");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE("./folder/../folder/.")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("./folder/../folder/.");
|
||||
CHECK_EQ(spec.type(), Type::Path);
|
||||
CHECK_EQ(spec.location(), "folder");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE("~/folder/")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("~/folder/");
|
||||
CHECK_EQ(spec.type(), Type::Path);
|
||||
CHECK_EQ(spec.location(), "~/folder");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE("/tmp/pkg-0.0-bld.tar.bz2")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("/tmp/pkg-0.0-bld.tar.bz2");
|
||||
CHECK_EQ(spec.type(), Type::PackagePath);
|
||||
CHECK_EQ(spec.location(), "/tmp/pkg-0.0-bld.tar.bz2");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE("C:/tmp//pkg-0.0-bld.tar.bz2")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("C:/tmp//pkg-0.0-bld.tar.bz2");
|
||||
CHECK_EQ(spec.type(), Type::PackagePath);
|
||||
CHECK_EQ(spec.location(), "C:/tmp/pkg-0.0-bld.tar.bz2");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE(R"(C:\tmp\pkg-0.0-bld.tar.bz2)")
|
||||
{
|
||||
if (util::on_win)
|
||||
{
|
||||
const auto spec = ChannelSpec::parse(R"(C:\tmp\pkg-0.0-bld.tar.bz2)");
|
||||
CHECK_EQ(spec.type(), Type::PackagePath);
|
||||
CHECK_EQ(spec.location(), "C:/tmp/pkg-0.0-bld.tar.bz2");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("conda-forge");
|
||||
CHECK_EQ(spec.type(), Type::Name);
|
||||
CHECK_EQ(spec.location(), "conda-forge");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE("repo.anaconda.com")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("repo.anaconda.com");
|
||||
// Unintuitive but correct type, this is not a URL. Better explicit than clever.
|
||||
CHECK_EQ(spec.type(), Type::Name);
|
||||
CHECK_EQ(spec.location(), "repo.anaconda.com");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{});
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge/linux-64")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("conda-forge/linux-64");
|
||||
CHECK_EQ(spec.type(), Type::Name);
|
||||
CHECK_EQ(spec.location(), "conda-forge");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{ "linux-64" });
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge[linux-avx512]")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("conda-forge[linux-avx512]");
|
||||
CHECK_EQ(spec.type(), Type::Name);
|
||||
CHECK_EQ(spec.location(), "conda-forge");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{ "linux-avx512" });
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge[]")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("conda-forge[linux-64]");
|
||||
CHECK_EQ(spec.type(), Type::Name);
|
||||
CHECK_EQ(spec.location(), "conda-forge");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{ "linux-64" });
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge/linux-64/label/foo_dev")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("conda-forge/linux-64/label/foo_dev");
|
||||
CHECK_EQ(spec.type(), Type::Name);
|
||||
CHECK_EQ(spec.location(), "conda-forge/label/foo_dev");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{ "linux-64" });
|
||||
}
|
||||
|
||||
SUBCASE("conda-forge/label/foo_dev[linux-64]")
|
||||
{
|
||||
const auto spec = ChannelSpec::parse("conda-forge/label/foo_dev[linux-64]");
|
||||
CHECK_EQ(spec.type(), Type::Name);
|
||||
CHECK_EQ(spec.location(), "conda-forge/label/foo_dev");
|
||||
CHECK_EQ(spec.platform_filters(), PlatformSet{ "linux-64" });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,12 +23,15 @@ TEST_SUITE("specs::CondaURL")
|
|||
{
|
||||
url.set_path("/folder/file.txt");
|
||||
CHECK_EQ(url.token(), "");
|
||||
CHECK_EQ(url.path_without_token(), "/folder/file.txt");
|
||||
|
||||
url.set_token("mytoken");
|
||||
CHECK_EQ(url.token(), "mytoken");
|
||||
CHECK_EQ(url.path_without_token(), "/folder/file.txt");
|
||||
CHECK_EQ(url.path(), "/t/mytoken/folder/file.txt");
|
||||
|
||||
CHECK(url.clear_token());
|
||||
CHECK_EQ(url.path_without_token(), "/folder/file.txt");
|
||||
CHECK_EQ(url.path(), "/folder/file.txt");
|
||||
}
|
||||
|
||||
|
@ -36,12 +39,14 @@ TEST_SUITE("specs::CondaURL")
|
|||
{
|
||||
url.set_path("/t/xy-12345678-1234/conda-forge/linux-64");
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234");
|
||||
CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64");
|
||||
|
||||
SUBCASE("Cannot set invalid token")
|
||||
{
|
||||
CHECK_THROWS_AS(url.set_token(""), std::invalid_argument);
|
||||
CHECK_THROWS_AS(url.set_token("?fds:g"), std::invalid_argument);
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234");
|
||||
CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64");
|
||||
CHECK_EQ(url.path(), "/t/xy-12345678-1234/conda-forge/linux-64");
|
||||
}
|
||||
|
||||
|
@ -49,6 +54,7 @@ TEST_SUITE("specs::CondaURL")
|
|||
{
|
||||
CHECK(url.clear_token());
|
||||
CHECK_EQ(url.token(), "");
|
||||
CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64");
|
||||
CHECK_EQ(url.path(), "/conda-forge/linux-64");
|
||||
}
|
||||
|
||||
|
@ -56,6 +62,7 @@ TEST_SUITE("specs::CondaURL")
|
|||
{
|
||||
url.set_token("abcd");
|
||||
CHECK_EQ(url.token(), "abcd");
|
||||
CHECK_EQ(url.path_without_token(), "/conda-forge/linux-64");
|
||||
CHECK_EQ(url.path(), "/t/abcd/conda-forge/linux-64");
|
||||
}
|
||||
}
|
||||
|
@ -67,23 +74,61 @@ TEST_SUITE("specs::CondaURL")
|
|||
|
||||
url.set_token("abcd");
|
||||
CHECK_EQ(url.token(), "abcd");
|
||||
CHECK_EQ(url.path(), "/t/abcd");
|
||||
CHECK_EQ(url.path_without_token(), "/");
|
||||
CHECK_EQ(url.path(), "/t/abcd/");
|
||||
|
||||
CHECK(url.clear_token());
|
||||
CHECK_EQ(url.token(), "");
|
||||
CHECK_EQ(url.path_without_token(), "/");
|
||||
CHECK_EQ(url.path(), "/");
|
||||
}
|
||||
|
||||
SUBCASE("https://repo.mamba.pm/bar/t/xy-12345678-1234-1234-1234-123456789012/")
|
||||
{
|
||||
url.set_path("/bar/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234-1234-1234-123456789012");
|
||||
CHECK_EQ(url.token(), ""); // Not at begining of path
|
||||
|
||||
url.set_token("abcd");
|
||||
CHECK_EQ(url.token(), "abcd");
|
||||
CHECK_EQ(url.path(), "/bar/t/abcd/");
|
||||
CHECK_EQ(url.path_without_token(), "/bar/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
CHECK_EQ(url.path(), "/t/abcd/bar/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
|
||||
CHECK(url.clear_token());
|
||||
CHECK_EQ(url.path(), "/bar/");
|
||||
CHECK_EQ(url.path_without_token(), "/bar/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
CHECK_EQ(url.path(), "/bar/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Path without token")
|
||||
{
|
||||
CondaURL url{};
|
||||
url.set_scheme("https");
|
||||
url.set_host("repo.mamba.pm");
|
||||
|
||||
SUBCASE("Setters")
|
||||
{
|
||||
url.set_path_without_token("foo");
|
||||
CHECK_EQ(url.path_without_token(), "/foo");
|
||||
url.set_token("mytoken");
|
||||
CHECK_EQ(url.path_without_token(), "/foo");
|
||||
CHECK(url.clear_path_without_token());
|
||||
CHECK_EQ(url.path_without_token(), "/");
|
||||
}
|
||||
|
||||
SUBCASE("Parse")
|
||||
{
|
||||
url = CondaURL::parse("mamba.org/t/xy-12345678-1234-1234-1234-123456789012");
|
||||
CHECK_EQ(url.token(), "xy-12345678-1234-1234-1234-123456789012");
|
||||
CHECK_EQ(url.path_without_token(), "/");
|
||||
CHECK_EQ(url.path(), "/t/xy-12345678-1234-1234-1234-123456789012/");
|
||||
}
|
||||
|
||||
SUBCASE("Encoding")
|
||||
{
|
||||
url.set_token("mytoken");
|
||||
url.set_path_without_token("some / weird/path %");
|
||||
CHECK_EQ(url.path_without_token(), "/some / weird/path %");
|
||||
CHECK_EQ(url.path_without_token(CondaURL::Decode::no), "/some%20/%20weird/path%20%25");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,6 +144,7 @@ TEST_SUITE("specs::CondaURL")
|
|||
CHECK_EQ(url.platform_name(), "");
|
||||
|
||||
CHECK_THROWS_AS(url.set_platform(Platform::linux_64), std::invalid_argument);
|
||||
CHECK_EQ(url.path_without_token(), "/");
|
||||
CHECK_EQ(url.path(), "/");
|
||||
|
||||
CHECK_FALSE(url.clear_platform());
|
||||
|
@ -251,6 +297,42 @@ TEST_SUITE("specs::CondaURL")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("str options")
|
||||
{
|
||||
CondaURL url = {};
|
||||
|
||||
SUBCASE("without credentials")
|
||||
{
|
||||
CHECK_EQ(url.str(CondaURL::Credentials::Show), "https://localhost/");
|
||||
CHECK_EQ(url.str(CondaURL::Credentials::Hide), "https://localhost/");
|
||||
CHECK_EQ(url.str(CondaURL::Credentials::Remove), "https://localhost/");
|
||||
}
|
||||
|
||||
SUBCASE("with some credentials")
|
||||
{
|
||||
url.set_user("user@mamba.org");
|
||||
url.set_password("pass");
|
||||
|
||||
CHECK_EQ(url.str(CondaURL::Credentials::Show), "https://user%40mamba.org:pass@localhost/");
|
||||
CHECK_EQ(url.str(CondaURL::Credentials::Hide), "https://user%40mamba.org:*****@localhost/");
|
||||
CHECK_EQ(url.str(CondaURL::Credentials::Remove), "https://localhost/");
|
||||
|
||||
SUBCASE("and token")
|
||||
{
|
||||
url.set_path("/t/abcd1234/linux-64");
|
||||
CHECK_EQ(
|
||||
url.str(CondaURL::Credentials::Show),
|
||||
"https://user%40mamba.org:pass@localhost/t/abcd1234/linux-64"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.str(CondaURL::Credentials::Hide),
|
||||
"https://user%40mamba.org:*****@localhost/t/*****/linux-64"
|
||||
);
|
||||
CHECK_EQ(url.str(CondaURL::Credentials::Remove), "https://localhost/linux-64");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pretty_str options")
|
||||
{
|
||||
SUBCASE("scheme option")
|
||||
|
@ -283,30 +365,60 @@ TEST_SUITE("specs::CondaURL")
|
|||
CHECK_EQ(url.pretty_str(CondaURL::StripScheme::no, '/'), "https://mamba.org/page");
|
||||
}
|
||||
|
||||
SUBCASE("Hide confidential option")
|
||||
SUBCASE("Credentail option")
|
||||
{
|
||||
CondaURL url = {};
|
||||
url.set_user("user");
|
||||
url.set_password("pass");
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::HideConfidential::no),
|
||||
"https://user:pass@localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::HideConfidential::yes),
|
||||
"https://user:*****@localhost/"
|
||||
);
|
||||
|
||||
url.set_path("/custom/t/abcd1234/linux-64");
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::HideConfidential::no),
|
||||
"https://user:pass@localhost/custom/t/abcd1234/linux-64"
|
||||
);
|
||||
SUBCASE("without credentials")
|
||||
{
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::Credentials::Show),
|
||||
"https://localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::Credentials::Hide),
|
||||
"https://localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::Credentials::Remove),
|
||||
"https://localhost/"
|
||||
);
|
||||
}
|
||||
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::HideConfidential::yes),
|
||||
"https://user:*****@localhost/custom/t/*****/linux-64"
|
||||
);
|
||||
SUBCASE("with user:password")
|
||||
{
|
||||
url.set_user("user");
|
||||
url.set_password("pass");
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::Credentials::Show),
|
||||
"https://user:pass@localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::Credentials::Hide),
|
||||
"https://user:*****@localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::Credentials::Remove),
|
||||
"https://localhost/"
|
||||
);
|
||||
|
||||
SUBCASE("and token")
|
||||
{
|
||||
url.set_path("/t/abcd1234/linux-64");
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::Credentials::Show),
|
||||
"https://user:pass@localhost/t/abcd1234/linux-64"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::Credentials::Hide),
|
||||
"https://user:*****@localhost/t/*****/linux-64"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(CondaURL::StripScheme::no, 0, CondaURL::Credentials::Remove),
|
||||
"https://localhost/linux-64"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("https://user:password@mamba.org:8080/folder/file.html?param=value#fragment")
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
using namespace mamba::specs;
|
||||
|
||||
TEST_SUITE("version")
|
||||
TEST_SUITE("specs::version")
|
||||
{
|
||||
TEST_CASE("atom_comparison")
|
||||
{
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
using namespace mamba::specs;
|
||||
|
||||
TEST_SUITE("version_spec")
|
||||
TEST_SUITE("specs::version_spec")
|
||||
{
|
||||
TEST_CASE("VersionPredicate")
|
||||
{
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
#include "mamba/util/flat_set.hpp"
|
||||
|
||||
#include "doctest-printer/flat_set.hpp"
|
||||
|
||||
using namespace mamba::util;
|
||||
|
||||
TEST_SUITE("util::flat_set")
|
||||
|
@ -35,7 +37,6 @@ TEST_SUITE("util::flat_set")
|
|||
static_assert(std::is_same_v<decltype(s6)::value_type, decltype(s5)::value_type>);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE("equality")
|
||||
{
|
||||
CHECK_EQ(flat_set<int>(), flat_set<int>());
|
||||
|
|
|
@ -69,4 +69,24 @@ TEST_SUITE("util::path_manip")
|
|||
CHECK_EQ(path_to_posix(R"(folder/weird\file)"), R"(folder/weird\file)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("path_is_prefix")
|
||||
{
|
||||
CHECK(path_is_prefix("", ""));
|
||||
CHECK(path_is_prefix("", "folder"));
|
||||
|
||||
CHECK(path_is_prefix("folder", "folder"));
|
||||
CHECK(path_is_prefix("/", "/folder"));
|
||||
CHECK(path_is_prefix("/folder", "/folder"));
|
||||
CHECK(path_is_prefix("/folder/file.txt", "/folder/file.txt"));
|
||||
CHECK(path_is_prefix("folder/file.txt", "folder/file.txt"));
|
||||
|
||||
CHECK_FALSE(path_is_prefix("/folder", "/"));
|
||||
CHECK_FALSE(path_is_prefix("/folder", "/folder-more"));
|
||||
CHECK_FALSE(path_is_prefix("/folder/file.json", "/folder/file.txt"));
|
||||
CHECK_FALSE(path_is_prefix("folder/file.json", "folder/file.txt"));
|
||||
|
||||
// Debatable
|
||||
CHECK_FALSE(path_is_prefix("/folder/", "/folder"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,6 +74,28 @@ namespace mamba::util
|
|||
CHECK(contains("", "")); // same as Python ``"" in ""``
|
||||
}
|
||||
|
||||
TEST_CASE("split_prefix")
|
||||
{
|
||||
using PrefixTail = decltype(split_prefix("", ""));
|
||||
CHECK_EQ(split_prefix("", ""), PrefixTail{ "", "" });
|
||||
CHECK_EQ(split_prefix("hello", ""), PrefixTail{ "", "hello" });
|
||||
CHECK_EQ(split_prefix("hello", "hello"), PrefixTail{ "hello", "" });
|
||||
CHECK_EQ(split_prefix("", "hello"), PrefixTail{ "", "" });
|
||||
CHECK_EQ(
|
||||
split_prefix("https://localhost", "https://"),
|
||||
PrefixTail{ "https://", "localhost" }
|
||||
);
|
||||
CHECK_EQ(
|
||||
split_prefix("https://localhost", "http://"),
|
||||
PrefixTail{ "", "https://localhost" }
|
||||
);
|
||||
CHECK_EQ(split_prefix("aabb", "a"), PrefixTail{ "a", "abb" });
|
||||
CHECK_EQ(split_prefix("", 'a'), PrefixTail{ "", "" });
|
||||
CHECK_EQ(split_prefix("a", 'a'), PrefixTail{ "a", "" });
|
||||
CHECK_EQ(split_prefix("aaa", 'a'), PrefixTail{ "a", "aa" });
|
||||
CHECK_EQ(split_prefix("aabb", 'b'), PrefixTail{ "", "aabb" });
|
||||
}
|
||||
|
||||
TEST_CASE("remove_prefix")
|
||||
{
|
||||
CHECK_EQ(remove_prefix("", ""), "");
|
||||
|
@ -89,6 +111,22 @@ namespace mamba::util
|
|||
CHECK_EQ(remove_prefix("aabb", 'b'), "aabb");
|
||||
}
|
||||
|
||||
TEST_CASE("split_suffix")
|
||||
{
|
||||
using HeadSuffix = decltype(split_suffix("", ""));
|
||||
CHECK_EQ(split_suffix("", ""), HeadSuffix{ "", "" });
|
||||
CHECK_EQ(split_suffix("hello", ""), HeadSuffix{ "hello", "" });
|
||||
CHECK_EQ(split_suffix("hello", "hello"), HeadSuffix{ "", "hello" });
|
||||
CHECK_EQ(split_suffix("", "hello"), HeadSuffix{ "", "" });
|
||||
CHECK_EQ(split_suffix("localhost:8080", ":8080"), HeadSuffix{ "localhost", ":8080" });
|
||||
CHECK_EQ(split_suffix("localhost:8080", ":80"), HeadSuffix{ "localhost:8080", "" });
|
||||
CHECK_EQ(split_suffix("aabb", "b"), HeadSuffix{ "aab", "b" });
|
||||
CHECK_EQ(split_suffix("", 'b'), HeadSuffix{ "", "" });
|
||||
CHECK_EQ(split_suffix("b", 'b'), HeadSuffix{ "", "b" });
|
||||
CHECK_EQ(split_suffix("bbb", 'b'), HeadSuffix{ "bb", "b" });
|
||||
CHECK_EQ(split_suffix("aabb", 'a'), HeadSuffix{ "aabb", "" });
|
||||
}
|
||||
|
||||
TEST_CASE("remove_suffix")
|
||||
{
|
||||
CHECK_EQ(remove_suffix("", ""), "");
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <doctest/doctest.h>
|
||||
|
@ -109,7 +108,14 @@ TEST_SUITE("util::URL")
|
|||
url.set_scheme("file");
|
||||
url.set_path("C:/folder/file.txt");
|
||||
CHECK_EQ(url.path(), "/C:/folder/file.txt");
|
||||
CHECK_EQ(url.pretty_path(), "C:/folder/file.txt");
|
||||
if (on_win)
|
||||
{
|
||||
CHECK_EQ(url.pretty_path(), "C:/folder/file.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(url.pretty_path(), "/C:/folder/file.txt");
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Case")
|
||||
|
@ -121,10 +127,55 @@ TEST_SUITE("util::URL")
|
|||
CHECK_EQ(url.host(), "some_host.com");
|
||||
}
|
||||
|
||||
SUBCASE("Default scheme")
|
||||
{
|
||||
URL url{};
|
||||
CHECK(url.scheme_is_defaulted());
|
||||
CHECK_EQ(url.scheme(), "https");
|
||||
|
||||
url.set_scheme("https");
|
||||
CHECK_FALSE(url.scheme_is_defaulted());
|
||||
CHECK_EQ(url.scheme(), "https");
|
||||
|
||||
url.set_scheme("");
|
||||
CHECK(url.scheme_is_defaulted());
|
||||
url.set_scheme("https");
|
||||
|
||||
url.set_scheme("ftp");
|
||||
CHECK_FALSE(url.scheme_is_defaulted());
|
||||
CHECK_EQ(url.scheme(), "ftp");
|
||||
|
||||
CHECK_EQ(url.clear_scheme(), "ftp");
|
||||
CHECK(url.scheme_is_defaulted());
|
||||
url.set_scheme("https");
|
||||
}
|
||||
|
||||
SUBCASE("Default host")
|
||||
{
|
||||
URL url{};
|
||||
CHECK(url.host_is_defaulted());
|
||||
CHECK_EQ(url.host(), "localhost");
|
||||
|
||||
url.set_host("localhost");
|
||||
CHECK_FALSE(url.host_is_defaulted());
|
||||
CHECK_EQ(url.host(), "localhost");
|
||||
|
||||
url.set_host("");
|
||||
CHECK(url.host_is_defaulted());
|
||||
url.set_host("localhost");
|
||||
|
||||
url.set_host("test.org");
|
||||
CHECK_FALSE(url.host_is_defaulted());
|
||||
CHECK_EQ(url.host(), "test.org");
|
||||
|
||||
CHECK_EQ(url.clear_host(), "test.org");
|
||||
CHECK(url.host_is_defaulted());
|
||||
url.set_host("localhost");
|
||||
}
|
||||
|
||||
SUBCASE("Invalid")
|
||||
{
|
||||
URL url{};
|
||||
CHECK_THROWS_AS(url.set_scheme(""), std::invalid_argument);
|
||||
CHECK_THROWS_AS(url.set_port("not-a-number"), std::invalid_argument);
|
||||
}
|
||||
|
||||
|
@ -261,7 +312,14 @@ TEST_SUITE("util::URL")
|
|||
CHECK_EQ(url.scheme(), "file");
|
||||
CHECK_EQ(url.host(), "");
|
||||
CHECK_EQ(url.path(), "/C:/Users/wolfv/test/document.json");
|
||||
CHECK_EQ(url.pretty_path(), "C:/Users/wolfv/test/document.json");
|
||||
if (on_win)
|
||||
{
|
||||
CHECK_EQ(url.pretty_path(), "C:/Users/wolfv/test/document.json");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(url.pretty_path(), "/C:/Users/wolfv/test/document.json");
|
||||
}
|
||||
CHECK_EQ(url.user(), "");
|
||||
CHECK_EQ(url.password(), "");
|
||||
CHECK_EQ(url.port(), "");
|
||||
|
@ -331,6 +389,28 @@ TEST_SUITE("util::URL")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("str options")
|
||||
{
|
||||
URL url = {};
|
||||
|
||||
SUBCASE("without credentials")
|
||||
{
|
||||
CHECK_EQ(url.str(URL::Credentials::Show), "https://localhost/");
|
||||
CHECK_EQ(url.str(URL::Credentials::Hide), "https://localhost/");
|
||||
CHECK_EQ(url.str(URL::Credentials::Remove), "https://localhost/");
|
||||
}
|
||||
|
||||
SUBCASE("with some credentials")
|
||||
{
|
||||
url.set_user("user@mamba.org");
|
||||
url.set_password("pass");
|
||||
|
||||
CHECK_EQ(url.str(URL::Credentials::Show), "https://user%40mamba.org:pass@localhost/");
|
||||
CHECK_EQ(url.str(URL::Credentials::Hide), "https://user%40mamba.org:*****@localhost/");
|
||||
CHECK_EQ(url.str(URL::Credentials::Remove), "https://localhost/");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("pretty_str options")
|
||||
{
|
||||
SUBCASE("scheme option")
|
||||
|
@ -363,19 +443,44 @@ TEST_SUITE("util::URL")
|
|||
CHECK_EQ(url.pretty_str(URL::StripScheme::no, '/'), "https://mamba.org/page");
|
||||
}
|
||||
|
||||
SUBCASE("Hide password option")
|
||||
SUBCASE("Credential option")
|
||||
{
|
||||
URL url = {};
|
||||
url.set_user("user");
|
||||
url.set_password("pass");
|
||||
CHECK_EQ(
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::HideConfidential::no),
|
||||
"https://user:pass@localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::HideConfidential::yes),
|
||||
"https://user:*****@localhost/"
|
||||
);
|
||||
|
||||
SUBCASE("without credentials")
|
||||
{
|
||||
CHECK_EQ(
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::Credentials::Show),
|
||||
"https://localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::Credentials::Hide),
|
||||
"https://localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::Credentials::Remove),
|
||||
"https://localhost/"
|
||||
);
|
||||
}
|
||||
|
||||
SUBCASE("with some credentials")
|
||||
{
|
||||
url.set_user("user");
|
||||
url.set_password("pass");
|
||||
|
||||
CHECK_EQ(
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::Credentials::Show),
|
||||
"https://user:pass@localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::Credentials::Hide),
|
||||
"https://user:*****@localhost/"
|
||||
);
|
||||
CHECK_EQ(
|
||||
url.pretty_str(URL::StripScheme::no, 0, URL::Credentials::Remove),
|
||||
"https://localhost/"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -457,7 +562,14 @@ TEST_SUITE("util::URL")
|
|||
CHECK_EQ(url.str(), "file:///C%3A/folder%26/file.txt");
|
||||
}
|
||||
CHECK_EQ(url.pretty_str(), "file:///C:/folder&/file.txt");
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "C:/folder&/file.txt");
|
||||
if (on_win)
|
||||
{
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "C:/folder&/file.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(url.pretty_str(URL::StripScheme::yes), "/C:/folder&/file.txt");
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("https://user@email.com:pw%rd@mamba.org/some /path$/")
|
||||
|
@ -492,12 +604,27 @@ TEST_SUITE("util::URL")
|
|||
url.set_query("param=value");
|
||||
url.set_fragment("fragment");
|
||||
CHECK_EQ(url.authority(), "mamba.org");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Show), "mamba.org");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Hide), "mamba.org");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Remove), "mamba.org");
|
||||
|
||||
url.set_port("8000");
|
||||
CHECK_EQ(url.authority(), "mamba.org:8000");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Show), "mamba.org:8000");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Hide), "mamba.org:8000");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Remove), "mamba.org:8000");
|
||||
|
||||
url.set_user("user@email.com");
|
||||
CHECK_EQ(url.authority(), "user%40email.com@mamba.org:8000");
|
||||
url.set_password("password");
|
||||
CHECK_EQ(url.authority(), "user%40email.com:password@mamba.org:8000");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Show), "user%40email.com@mamba.org:8000");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Hide), "user%40email.com:*****@mamba.org:8000");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Remove), "mamba.org:8000");
|
||||
|
||||
url.set_password("pass");
|
||||
CHECK_EQ(url.authority(), "user%40email.com:pass@mamba.org:8000");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Show), "user%40email.com:pass@mamba.org:8000");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Hide), "user%40email.com:*****@mamba.org:8000");
|
||||
CHECK_EQ(url.authority(URL::Credentials::Remove), "mamba.org:8000");
|
||||
}
|
||||
|
||||
TEST_CASE("Equality")
|
||||
|
|
|
@ -179,6 +179,19 @@ TEST_SUITE("util::url_manip")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("abs_path_or_url_to_url")
|
||||
{
|
||||
SUBCASE("/users/test/miniconda3")
|
||||
{
|
||||
CHECK_EQ(abs_path_or_url_to_url("/users/test/miniconda3"), "file:///users/test/miniconda3");
|
||||
}
|
||||
|
||||
SUBCASE("file:///tmp/bar")
|
||||
{
|
||||
CHECK_EQ(abs_path_or_url_to_url("file:///tmp/bar"), "file:///tmp/bar");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("path_to_url")
|
||||
{
|
||||
const std::string win_drive = fs::absolute(fs::u8path("/")).string().substr(0, 1);
|
||||
|
@ -216,6 +229,21 @@ TEST_SUITE("util::url_manip")
|
|||
CHECK_EQ(url, "file:///tmp/foo%20bar");
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("./folder/./../folder")
|
||||
{
|
||||
auto url = path_to_url("./folder/./../folder");
|
||||
if (on_win)
|
||||
{
|
||||
CHECK(starts_with(url, concat("file://", win_drive, ":/")));
|
||||
CHECK(ends_with(url, "/folder"));
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto expected_folder = fs::absolute("folder").lexically_normal();
|
||||
CHECK_EQ(url, concat("file://", expected_folder.string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("path_or_url_to_url")
|
||||
|
|
|
@ -136,8 +136,8 @@ class Channel:
|
|||
def platform_url(self, platform: str, with_credentials: bool = True) -> str: ...
|
||||
def platform_urls(
|
||||
self, with_credentials: bool = True
|
||||
) -> typing.List[typing.Tuple[str, str]]: ...
|
||||
def urls(self, with_credentials: bool = True) -> typing.List[str]: ...
|
||||
) -> typing.Set[typing.Tuple[str, str]]: ...
|
||||
def urls(self, with_credentials: bool = True) -> typing.Set[str]: ...
|
||||
@property
|
||||
def auth(self) -> typing.Optional[str]:
|
||||
"""
|
||||
|
@ -164,9 +164,9 @@ class Channel:
|
|||
:type: typing.Optional[str]
|
||||
"""
|
||||
@property
|
||||
def platforms(self) -> typing.List[str]:
|
||||
def platforms(self) -> typing.Set[str]:
|
||||
"""
|
||||
:type: typing.List[str]
|
||||
:type: typing.Set[str]
|
||||
"""
|
||||
@property
|
||||
def scheme(self) -> str:
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) 2023, 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.
|
||||
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include "mamba/util/flat_set.hpp"
|
||||
|
||||
#ifndef MAMBA_PY_SET_CASTER_HPP
|
||||
#define MAMBA_PY_SET_CASTER_HPP
|
||||
|
||||
namespace PYBIND11_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <typename Key, typename Compare, typename Alloc>
|
||||
struct type_caster<mamba::util::flat_set<Key, Compare, Alloc>>
|
||||
: set_caster<std::set<Key, Compare, Alloc>, Key>
|
||||
{
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -38,6 +38,8 @@
|
|||
#include "mamba/util/flat_set.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
|
||||
#include "flat_set_caster.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace query
|
||||
|
@ -52,18 +54,6 @@ namespace query
|
|||
};
|
||||
}
|
||||
|
||||
namespace PYBIND11_NAMESPACE
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <typename Key, typename Compare, typename Allocator>
|
||||
struct type_caster<mamba::util::flat_set<Key, Compare, Allocator>>
|
||||
: set_caster<mamba::util::flat_set<Key, Compare, Allocator>, Key>
|
||||
{
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
deprecated(const char* message)
|
||||
{
|
||||
|
|
|
@ -81,6 +81,6 @@ python "${reposerver}" \
|
|||
-d "${repo}" -n defaults --token private-token -- \
|
||||
-d "${channel_a}" -n channel_a --user user@email.com --password test -- \
|
||||
-d "${channel_b}" -n channel_b --auth none & PID=$!
|
||||
mamba create -y -q -n "env-${RANDOM}" --override-channels -c http://localhost:8000/defaults/t/private-token test-package --json
|
||||
mamba create -y -q -n "env-${RANDOM}" --override-channels -c http://localhost:8000/t/private-token/defaults test-package --json
|
||||
mamba create -y -q -n "env-${RANDOM}" --override-channels -c http://user%40email.com:test@localhost:8000/channel_a _r-mutex --json
|
||||
kill -TERM $PID
|
||||
|
|
|
@ -173,7 +173,7 @@ set_env_command(CLI::App* com, Configuration& config)
|
|||
continue;
|
||||
}
|
||||
|
||||
const Channel& channel = channel_context.make_channel(v.url);
|
||||
const Channel& channel = channel_context.make_channel(v.channel);
|
||||
|
||||
if (from_history)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue