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)
|
||||
: s(i)
|
||||
{
|
||||
|
@ -54,6 +59,20 @@ namespace mamba
|
|||
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
|
||||
{
|
||||
public:
|
||||
|
@ -182,4 +201,4 @@ namespace mamba
|
|||
#define LOG_ERROR LOG(mamba::log_level::err)
|
||||
#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();
|
||||
|
||||
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;
|
||||
nlohmann::json json(ChannelContext& channel_context) const;
|
||||
|
||||
|
|
|
@ -144,7 +144,14 @@ namespace mamba
|
|||
case QueryResultFormat::kRECURSIVETABLE:
|
||||
res.sort("name").table(
|
||||
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)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
@ -156,26 +157,44 @@ namespace mamba
|
|||
|
||||
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();
|
||||
std::string header = fmt::format("{} {} {}", pkg.name, pkg.version, pkg.build_string);
|
||||
fmt::print(out, "{:^40}\n{:-^{}}\n\n", header, "", header.size() > 40 ? header.size() : 40);
|
||||
std::string additionalBuilds = "";
|
||||
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";
|
||||
fmt::print(out, fmtstring, "File Name", pkg.fn);
|
||||
static constexpr const char* fmtstring = " {:<15} {}\n";
|
||||
fmt::print(out, fmtstring, "Name", pkg.name);
|
||||
fmt::print(out, fmtstring, "Version", pkg.version);
|
||||
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, "Subdir", pkg.subdir);
|
||||
fmt::print(out, fmtstring, "File Name", pkg.fn);
|
||||
|
||||
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);
|
||||
|
||||
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, "SHA256", pkg.sha256.empty() ? "Not available" : pkg.sha256);
|
||||
|
@ -187,6 +206,15 @@ namespace mamba
|
|||
// std::cout << fmt::format<char>(
|
||||
// " {:<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())
|
||||
{
|
||||
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");
|
||||
for (auto& c : pkg.constrains)
|
||||
fmt::print(out, "\n Other Versions ({}):\n\n", buildsByVersion.size());
|
||||
|
||||
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
|
||||
{
|
||||
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
|
||||
|
@ -419,9 +484,16 @@ namespace mamba
|
|||
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())
|
||||
{
|
||||
|
@ -429,25 +501,43 @@ namespace mamba
|
|||
}
|
||||
|
||||
std::vector<mamba::printers::FormattedString> headers;
|
||||
std::vector<std::string> cmds, args;
|
||||
for (auto& f : fmt)
|
||||
std::vector<std::string_view> cmds, args;
|
||||
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);
|
||||
cmds.push_back(f);
|
||||
// If an alignment marker is passed, we remove the column name.
|
||||
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("");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto sfmt = util::split(f, ":", 1);
|
||||
auto sfmt = util::split(col, ":", 1);
|
||||
headers.push_back(sfmt[0]);
|
||||
cmds.push_back(sfmt[0]);
|
||||
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;
|
||||
for (std::size_t i = 0; i < cmds.size(); ++i)
|
||||
|
@ -464,11 +554,25 @@ namespace mamba
|
|||
else if (cmd == "Build")
|
||||
{
|
||||
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")
|
||||
{
|
||||
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")
|
||||
{
|
||||
std::string depends_qualifier;
|
||||
|
@ -487,14 +591,26 @@ namespace mamba
|
|||
};
|
||||
|
||||
printers::Table printer(headers);
|
||||
printer.set_alignment(alignments);
|
||||
|
||||
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 (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)
|
||||
{
|
||||
printer.add_row(format_row(m_dep_graph.node(id)));
|
||||
printer.add_row(format_row(m_dep_graph.node(id), {}));
|
||||
}
|
||||
}
|
||||
|
||||
return printer.print(out);
|
||||
}
|
||||
|
||||
|
@ -671,11 +788,25 @@ namespace mamba
|
|||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
|
|
@ -408,7 +408,14 @@ PYBIND11_MODULE(bindings, m)
|
|||
case query::RECURSIVETABLE:
|
||||
res.table(
|
||||
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)
|
||||
|
|
|
@ -106,8 +106,10 @@ set_common_search(CLI::App* subcom, mamba::Configuration& config, bool is_repoqu
|
|||
{
|
||||
format = QueryResultFormat::kTREE;
|
||||
}
|
||||
|
||||
if (qtype == QueryType::kSEARCH && pretty_print)
|
||||
// Best guess to detect wildcard search; if there's no wildcard search, we want to show
|
||||
// the pretty single package view.
|
||||
if (qtype == QueryType::kSEARCH
|
||||
&& (pretty_print || specs[0].find("*") == std::string::npos))
|
||||
{
|
||||
format = QueryResultFormat::kPRETTY;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue