mirror of https://github.com/mamba-org/mamba.git
Clearer output from `micromamba search` (#2782)
* [feat] Show other versions with --pretty printing * [feat] Refine version/build table in search pretty print * [feat] Change table view to contain further aligned build information * [feat] Redirect non-wildcard search to single package view * Fix repoquery test for empty single package view * Cleanup + add string marker for alignment * Fix string marker for libmambapy * Add changes suggested by JH * Move to unordered_map where it makes sense * Move to string_view and switch-case constexpr alignment mapping * Fail lookup for non-existing alignments * Remove unnecessary reference in constructor
This commit is contained in:
parent
7b23f51c75
commit
7dfbf2cf0d
|
@ -37,6 +37,11 @@ namespace mamba
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline FormattedString(const std::string_view i)
|
||||||
|
: s(i)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
inline FormattedString(const char* i)
|
inline FormattedString(const char* i)
|
||||||
: s(i)
|
: s(i)
|
||||||
{
|
{
|
||||||
|
@ -54,6 +59,20 @@ namespace mamba
|
||||||
right,
|
right,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr auto alignmentMarker(alignment a) -> std::string_view
|
||||||
|
{
|
||||||
|
switch (a)
|
||||||
|
{
|
||||||
|
case alignment::right:
|
||||||
|
return "alignment_right";
|
||||||
|
case alignment::left:
|
||||||
|
return "alignment_right";
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Table
|
class Table
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -182,4 +201,4 @@ namespace mamba
|
||||||
#define LOG_ERROR LOG(mamba::log_level::err)
|
#define LOG_ERROR LOG(mamba::log_level::err)
|
||||||
#define LOG_CRITICAL LOG(mamba::log_level::critical)
|
#define LOG_CRITICAL LOG(mamba::log_level::critical)
|
||||||
|
|
||||||
#endif // MAMBA_OUTPUT_HPP
|
#endif // MAMBA_CORE_OUTPUT_HPP
|
||||||
|
|
|
@ -92,7 +92,7 @@ namespace mamba
|
||||||
query_result& reset();
|
query_result& reset();
|
||||||
|
|
||||||
std::ostream& table(std::ostream&) const;
|
std::ostream& table(std::ostream&) const;
|
||||||
std::ostream& table(std::ostream&, const std::vector<std::string>& fmt) const;
|
std::ostream& table(std::ostream&, const std::vector<std::string_view>& fmt) const;
|
||||||
std::ostream& tree(std::ostream&) const;
|
std::ostream& tree(std::ostream&) const;
|
||||||
nlohmann::json json(ChannelContext& channel_context) const;
|
nlohmann::json json(ChannelContext& channel_context) const;
|
||||||
|
|
||||||
|
|
|
@ -144,7 +144,14 @@ namespace mamba
|
||||||
case QueryResultFormat::kRECURSIVETABLE:
|
case QueryResultFormat::kRECURSIVETABLE:
|
||||||
res.sort("name").table(
|
res.sort("name").table(
|
||||||
std::cout,
|
std::cout,
|
||||||
{ "Name", "Version", "Build", util::concat("Depends:", query), "Channel" }
|
{ "Name",
|
||||||
|
"Version",
|
||||||
|
"Build",
|
||||||
|
printers::alignmentMarker(printers::alignment::left),
|
||||||
|
printers::alignmentMarker(printers::alignment::right),
|
||||||
|
util::concat("Depends:", query),
|
||||||
|
"Channel",
|
||||||
|
"Subdir" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (res.empty() && format != QueryResultFormat::kJSON)
|
if (res.empty() && format != QueryResultFormat::kJSON)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
// The full license is in the file LICENSE, distributed with this software.
|
// The full license is in the file LICENSE, distributed with this software.
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
|
||||||
#include <fmt/chrono.h>
|
#include <fmt/chrono.h>
|
||||||
|
@ -156,26 +157,44 @@ namespace mamba
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
auto print_solvable(const PackageInfo& pkg)
|
auto print_solvable(const PackageInfo& pkg, const std::vector<PackageInfo>& otherBuilds)
|
||||||
{
|
{
|
||||||
|
std::map<std::string, std::vector<PackageInfo>> buildsByVersion;
|
||||||
|
auto numOtherBuildsForLatestVersion = 0;
|
||||||
|
for (const auto& p : otherBuilds)
|
||||||
|
{
|
||||||
|
if (p.version != pkg.version)
|
||||||
|
{
|
||||||
|
buildsByVersion[p.version].push_back(p);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++numOtherBuildsForLatestVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
auto out = Console::stream();
|
auto out = Console::stream();
|
||||||
std::string header = fmt::format("{} {} {}", pkg.name, pkg.version, pkg.build_string);
|
std::string additionalBuilds = "";
|
||||||
fmt::print(out, "{:^40}\n{:-^{}}\n\n", header, "", header.size() > 40 ? header.size() : 40);
|
if (numOtherBuildsForLatestVersion > 0)
|
||||||
|
{
|
||||||
|
additionalBuilds = fmt::format(" (+ {} builds)", numOtherBuildsForLatestVersion);
|
||||||
|
}
|
||||||
|
std::string header = fmt::format("{} {} {}", pkg.name, pkg.version, pkg.build_string)
|
||||||
|
+ additionalBuilds;
|
||||||
|
fmt::print(out, "{:^40}\n{:_^{}}\n\n", header, "", header.size() > 40 ? header.size() : 40);
|
||||||
|
|
||||||
static constexpr const char* fmtstring = " {:<15} {}\n";
|
static constexpr const char* fmtstring = " {:<15} {}\n";
|
||||||
fmt::print(out, fmtstring, "File Name", pkg.fn);
|
|
||||||
fmt::print(out, fmtstring, "Name", pkg.name);
|
fmt::print(out, fmtstring, "Name", pkg.name);
|
||||||
fmt::print(out, fmtstring, "Version", pkg.version);
|
fmt::print(out, fmtstring, "Version", pkg.version);
|
||||||
fmt::print(out, fmtstring, "Build", pkg.build_string);
|
fmt::print(out, fmtstring, "Build", pkg.build_string);
|
||||||
fmt::print(out, fmtstring, "Build Number", pkg.build_number);
|
fmt::print(out, " {:<15} {} kB\n", "Size", pkg.size / 1000);
|
||||||
fmt::print(out, " {:<15} {} Kb\n", "Size", pkg.size / 1000);
|
|
||||||
fmt::print(out, fmtstring, "License", pkg.license);
|
fmt::print(out, fmtstring, "License", pkg.license);
|
||||||
fmt::print(out, fmtstring, "Subdir", pkg.subdir);
|
fmt::print(out, fmtstring, "Subdir", pkg.subdir);
|
||||||
|
fmt::print(out, fmtstring, "File Name", pkg.fn);
|
||||||
|
|
||||||
std::string url_remaining, url_scheme, url_auth, url_token;
|
std::string url_remaining, url_scheme, url_auth, url_token;
|
||||||
util::split_scheme_auth_token(pkg.url, url_remaining, url_scheme, url_auth, url_token);
|
util::split_scheme_auth_token(pkg.url, url_remaining, url_scheme, url_auth, url_token);
|
||||||
|
|
||||||
fmt::print(out, " {:<15} {}://{}\n", "URL", url_scheme, url_remaining);
|
fmt::print(out, " {:<15} {}://{}\n", "URL", url_scheme, url_remaining);
|
||||||
|
|
||||||
fmt::print(out, fmtstring, "MD5", pkg.md5.empty() ? "Not available" : pkg.md5);
|
fmt::print(out, fmtstring, "MD5", pkg.md5.empty() ? "Not available" : pkg.md5);
|
||||||
fmt::print(out, fmtstring, "SHA256", pkg.sha256.empty() ? "Not available" : pkg.sha256);
|
fmt::print(out, fmtstring, "SHA256", pkg.sha256.empty() ? "Not available" : pkg.sha256);
|
||||||
|
@ -187,6 +206,15 @@ namespace mamba
|
||||||
// std::cout << fmt::format<char>(
|
// std::cout << fmt::format<char>(
|
||||||
// " {:<15} {:%Y-%m-%d %H:%M:%S} UTC\n", "Timestamp", fmt::gmtime(pkg.timestamp));
|
// " {:<15} {:%Y-%m-%d %H:%M:%S} UTC\n", "Timestamp", fmt::gmtime(pkg.timestamp));
|
||||||
|
|
||||||
|
if (!pkg.constrains.empty())
|
||||||
|
{
|
||||||
|
fmt::print(out, "\n Run Constraints:\n");
|
||||||
|
for (auto& c : pkg.constrains)
|
||||||
|
{
|
||||||
|
fmt::print(out, " - {}\n", c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!pkg.depends.empty())
|
if (!pkg.depends.empty())
|
||||||
{
|
{
|
||||||
fmt::print(out, "\n Dependencies:\n");
|
fmt::print(out, "\n Dependencies:\n");
|
||||||
|
@ -196,12 +224,40 @@ namespace mamba
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pkg.constrains.empty())
|
if (!buildsByVersion.empty())
|
||||||
{
|
{
|
||||||
fmt::print(out, "\n Run Constraints:\n");
|
fmt::print(out, "\n Other Versions ({}):\n\n", buildsByVersion.size());
|
||||||
for (auto& c : pkg.constrains)
|
|
||||||
|
std::stringstream buffer;
|
||||||
|
|
||||||
|
using namespace printers;
|
||||||
|
Table printer({ "Version", "Build", "", "" });
|
||||||
|
printer.set_alignment(
|
||||||
|
{ alignment::left, alignment::left, alignment::left, alignment::right }
|
||||||
|
);
|
||||||
|
// We want the newest version to be on top, therefore we iterate in reverse.
|
||||||
|
for (auto it = buildsByVersion.rbegin(); it != buildsByVersion.rend(); it++)
|
||||||
{
|
{
|
||||||
fmt::print(out, " - {}\n", c);
|
std::vector<FormattedString> row;
|
||||||
|
row.push_back(it->second.front().version);
|
||||||
|
row.push_back(it->second.front().build_string);
|
||||||
|
if (it->second.size() > 1)
|
||||||
|
{
|
||||||
|
row.push_back("(+");
|
||||||
|
row.push_back(fmt::format("{} builds)", it->second.size() - 1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
row.push_back("");
|
||||||
|
row.push_back("");
|
||||||
|
}
|
||||||
|
printer.add_row(row);
|
||||||
|
}
|
||||||
|
printer.print(buffer);
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(buffer, line))
|
||||||
|
{
|
||||||
|
out << " " << line << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,7 +464,16 @@ namespace mamba
|
||||||
|
|
||||||
std::ostream& query_result::table(std::ostream& out) const
|
std::ostream& query_result::table(std::ostream& out) const
|
||||||
{
|
{
|
||||||
return table(out, { "Name", "Version", "Build", "Channel" });
|
return table(
|
||||||
|
out,
|
||||||
|
{ "Name",
|
||||||
|
"Version",
|
||||||
|
"Build",
|
||||||
|
printers::alignmentMarker(printers::alignment::left),
|
||||||
|
printers::alignmentMarker(printers::alignment::right),
|
||||||
|
"Channel",
|
||||||
|
"Subdir" }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -419,9 +484,16 @@ namespace mamba
|
||||||
return util::split(str, "/", 1).front(); // Has at least one element
|
return util::split(str, "/", 1).front(); // Has at least one element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Get subdir from channel name. */
|
||||||
|
auto get_subdir(std::string_view str) -> std::string
|
||||||
|
{
|
||||||
|
return util::split(str, "/").back();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& query_result::table(std::ostream& out, const std::vector<std::string>& fmt) const
|
std::ostream&
|
||||||
|
query_result::table(std::ostream& out, const std::vector<std::string_view>& columns) const
|
||||||
{
|
{
|
||||||
if (m_pkg_id_list.empty())
|
if (m_pkg_id_list.empty())
|
||||||
{
|
{
|
||||||
|
@ -429,25 +501,43 @@ namespace mamba
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<mamba::printers::FormattedString> headers;
|
std::vector<mamba::printers::FormattedString> headers;
|
||||||
std::vector<std::string> cmds, args;
|
std::vector<std::string_view> cmds, args;
|
||||||
for (auto& f : fmt)
|
std::vector<mamba::printers::alignment> alignments;
|
||||||
|
for (auto& col : columns)
|
||||||
{
|
{
|
||||||
if (f.find_first_of(":") == f.npos)
|
if (col == printers::alignmentMarker(printers::alignment::right)
|
||||||
|
|| col == printers::alignmentMarker(printers::alignment::left))
|
||||||
{
|
{
|
||||||
headers.push_back(f);
|
// If an alignment marker is passed, we remove the column name.
|
||||||
cmds.push_back(f);
|
headers.push_back("");
|
||||||
|
cmds.push_back("");
|
||||||
|
args.push_back("");
|
||||||
|
// We only check for the right alignment marker, as left alignment is set the
|
||||||
|
// default.
|
||||||
|
if (col == printers::alignmentMarker(printers::alignment::right))
|
||||||
|
{
|
||||||
|
alignments.push_back(printers::alignment::right);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (col.find_first_of(":") == col.npos)
|
||||||
|
{
|
||||||
|
headers.push_back(col);
|
||||||
|
cmds.push_back(col);
|
||||||
args.push_back("");
|
args.push_back("");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto sfmt = util::split(f, ":", 1);
|
auto sfmt = util::split(col, ":", 1);
|
||||||
headers.push_back(sfmt[0]);
|
headers.push_back(sfmt[0]);
|
||||||
cmds.push_back(sfmt[0]);
|
cmds.push_back(sfmt[0]);
|
||||||
args.push_back(sfmt[1]);
|
args.push_back(sfmt[1]);
|
||||||
}
|
}
|
||||||
|
// By default, columns are left aligned.
|
||||||
|
alignments.push_back(printers::alignment::left);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto format_row = [&](const PackageInfo& pkg)
|
auto format_row = [&](const PackageInfo& pkg, const std::vector<PackageInfo>& builds)
|
||||||
{
|
{
|
||||||
std::vector<mamba::printers::FormattedString> row;
|
std::vector<mamba::printers::FormattedString> row;
|
||||||
for (std::size_t i = 0; i < cmds.size(); ++i)
|
for (std::size_t i = 0; i < cmds.size(); ++i)
|
||||||
|
@ -464,11 +554,25 @@ namespace mamba
|
||||||
else if (cmd == "Build")
|
else if (cmd == "Build")
|
||||||
{
|
{
|
||||||
row.push_back(pkg.build_string);
|
row.push_back(pkg.build_string);
|
||||||
|
if (builds.size() > 1)
|
||||||
|
{
|
||||||
|
row.push_back("(+");
|
||||||
|
row.push_back(fmt::format("{} builds)", builds.size() - 1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
row.push_back("");
|
||||||
|
row.push_back("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (cmd == "Channel")
|
else if (cmd == "Channel")
|
||||||
{
|
{
|
||||||
row.push_back(cut_subdir(cut_repo_name(pkg.channel)));
|
row.push_back(cut_subdir(cut_repo_name(pkg.channel)));
|
||||||
}
|
}
|
||||||
|
else if (cmd == "Subdir")
|
||||||
|
{
|
||||||
|
row.push_back(get_subdir(pkg.channel));
|
||||||
|
}
|
||||||
else if (cmd == "Depends")
|
else if (cmd == "Depends")
|
||||||
{
|
{
|
||||||
std::string depends_qualifier;
|
std::string depends_qualifier;
|
||||||
|
@ -487,14 +591,26 @@ namespace mamba
|
||||||
};
|
};
|
||||||
|
|
||||||
printers::Table printer(headers);
|
printers::Table printer(headers);
|
||||||
|
printer.set_alignment(alignments);
|
||||||
|
|
||||||
if (!m_ordered_pkg_id_list.empty())
|
if (!m_ordered_pkg_id_list.empty())
|
||||||
{
|
{
|
||||||
|
std::map<std::string, std::map<std::string, std::vector<PackageInfo>>> packageBuildsByVersion;
|
||||||
for (auto& entry : m_ordered_pkg_id_list)
|
for (auto& entry : m_ordered_pkg_id_list)
|
||||||
{
|
{
|
||||||
for (const auto& id : entry.second)
|
for (const auto& id : entry.second)
|
||||||
{
|
{
|
||||||
printer.add_row(format_row(m_dep_graph.node(id)));
|
auto package = m_dep_graph.node(id);
|
||||||
|
packageBuildsByVersion[package.name][package.version].push_back(package);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& entry : packageBuildsByVersion)
|
||||||
|
{
|
||||||
|
// We want the newest version to be on top, therefore we iterate in reverse.
|
||||||
|
for (auto it = entry.second.rbegin(); it != entry.second.rend(); ++it)
|
||||||
|
{
|
||||||
|
printer.add_row(format_row(it->second[0], it->second));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -502,9 +618,10 @@ namespace mamba
|
||||||
{
|
{
|
||||||
for (const auto& id : m_pkg_id_list)
|
for (const auto& id : m_pkg_id_list)
|
||||||
{
|
{
|
||||||
printer.add_row(format_row(m_dep_graph.node(id)));
|
printer.add_row(format_row(m_dep_graph.node(id), {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return printer.print(out);
|
return printer.print(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,11 +788,25 @@ namespace mamba
|
||||||
|
|
||||||
std::ostream& query_result::pretty(std::ostream& out) const
|
std::ostream& query_result::pretty(std::ostream& out) const
|
||||||
{
|
{
|
||||||
if (!m_pkg_id_list.empty())
|
if (m_pkg_id_list.empty())
|
||||||
{
|
{
|
||||||
|
out << "No entries matching \"" << m_query << "\" found" << std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::map<std::string, std::vector<PackageInfo>> packages;
|
||||||
for (const auto& id : m_pkg_id_list)
|
for (const auto& id : m_pkg_id_list)
|
||||||
{
|
{
|
||||||
print_solvable(m_dep_graph.node(id));
|
auto package = m_dep_graph.node(id);
|
||||||
|
packages[package.name].push_back(package);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& entry : packages)
|
||||||
|
{
|
||||||
|
print_solvable(
|
||||||
|
entry.second[0],
|
||||||
|
std::vector(entry.second.begin() + 1, entry.second.end())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|
|
@ -408,7 +408,14 @@ PYBIND11_MODULE(bindings, m)
|
||||||
case query::RECURSIVETABLE:
|
case query::RECURSIVETABLE:
|
||||||
res.table(
|
res.table(
|
||||||
res_stream,
|
res_stream,
|
||||||
{ "Name", "Version", "Build", util::concat("Depends:", query), "Channel" }
|
{ "Name",
|
||||||
|
"Version",
|
||||||
|
"Build",
|
||||||
|
printers::alignmentMarker(printers::alignment::left),
|
||||||
|
printers::alignmentMarker(printers::alignment::right),
|
||||||
|
util::concat("Depends:", query),
|
||||||
|
"Channel",
|
||||||
|
"Subdir" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (res.empty() && format != query::JSON)
|
if (res.empty() && format != query::JSON)
|
||||||
|
|
|
@ -106,8 +106,10 @@ set_common_search(CLI::App* subcom, mamba::Configuration& config, bool is_repoqu
|
||||||
{
|
{
|
||||||
format = QueryResultFormat::kTREE;
|
format = QueryResultFormat::kTREE;
|
||||||
}
|
}
|
||||||
|
// Best guess to detect wildcard search; if there's no wildcard search, we want to show
|
||||||
if (qtype == QueryType::kSEARCH && pretty_print)
|
// the pretty single package view.
|
||||||
|
if (qtype == QueryType::kSEARCH
|
||||||
|
&& (pretty_print || specs[0].find("*") == std::string::npos))
|
||||||
{
|
{
|
||||||
format = QueryResultFormat::kPRETTY;
|
format = QueryResultFormat::kPRETTY;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue