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:
Antoine Prouvost 2023-10-12 15:56:49 +02:00 committed by GitHub
parent a2b62bfd95
commit 4e93b7967a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 2026 additions and 640 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
};

View File

@ -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

View File

@ -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);

View File

@ -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 = {};

View File

@ -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.
*/

View File

@ -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();
}

View File

@ -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;

View File

@ -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);

View File

@ -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)

View File

@ -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, {});
}
}

View File

@ -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() ? "" : "#",

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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() };
}
};
}

View File

@ -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() };
}
};
}

View File

@ -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() };
}

View File

@ -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" });
}
}
}

View File

@ -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")

View File

@ -16,7 +16,7 @@
using namespace mamba::specs;
TEST_SUITE("version")
TEST_SUITE("specs::version")
{
TEST_CASE("atom_comparison")
{

View File

@ -14,7 +14,7 @@
using namespace mamba::specs;
TEST_SUITE("version_spec")
TEST_SUITE("specs::version_spec")
{
TEST_CASE("VersionPredicate")
{

View File

@ -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>());

View File

@ -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"));
}
}

View File

@ -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("", ""), "");

View File

@ -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")

View File

@ -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")

View File

@ -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:

View File

@ -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

View File

@ -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)
{

View File

@ -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

View File

@ -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)
{