Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add help and color output. #9

Merged
merged 9 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ subdir('src/uenv')
# the uenv executable
if uenv_cli
uenv_src = [
'src/cli/color.cpp',
'src/cli/help.cpp',
'src/cli/image.cpp',
'src/cli/ls.cpp',
'src/cli/run.cpp',
Expand Down
37 changes: 37 additions & 0 deletions src/cli/color.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <stdio.h>
#include <unistd.h>

#include <fmt/format.h>

#include "color.h"

namespace color {

namespace impl {
bool use = true;
}

void default_color() {
// enable color by default
set_color(true);

// disable color if NO_COLOR env. variable is set
if (std::getenv("NO_COLOR")) {
set_color(false);
}

// disable color if stdout is not a terminal
if (!isatty(fileno(stdout))) {
set_color(false);
}
}

void set_color(bool v) {
impl::use = v;
}

bool use_color() {
return impl::use;
}

} // namespace color
36 changes: 36 additions & 0 deletions src/cli/color.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include <fmt/color.h>

#define MAKE_COLOR(color) \
static auto color() { \
return fmt::emphasis::bold | fg(fmt::terminal_color::color); \
} \
template <typename S> constexpr auto color(const S& s) { \
return use_color() ? fmt::format(color(), "{}", s) : std::string(s); \
}

namespace color {

void default_color();
void set_color(bool v);
bool use_color();

MAKE_COLOR(black)
MAKE_COLOR(red)
MAKE_COLOR(green)
MAKE_COLOR(yellow)
MAKE_COLOR(blue)
MAKE_COLOR(magenta)
MAKE_COLOR(cyan)
MAKE_COLOR(white)
MAKE_COLOR(bright_black)
MAKE_COLOR(bright_red)
MAKE_COLOR(bright_green)
MAKE_COLOR(bright_yellow)
MAKE_COLOR(bright_blue)
MAKE_COLOR(bright_magenta)
MAKE_COLOR(bright_cyan)
MAKE_COLOR(bright_white)

} // namespace color
55 changes: 55 additions & 0 deletions src/cli/help.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <string>

#include "fmt/format.h"

#include "color.h"
#include "help.h"

namespace help {

block::block(std::string msg) : kind(none), lines{std::move(msg)} {
}

std::string render(const linebreak&) {
return "";
}

std::string render(const block& b) {
using enum help::block::admonition;
std::string result{};
switch (b.kind) {
case none:
case code:
break;
case note:
result += fmt::format("{} - ", ::color::cyan("Note"));
break;
case xmpl:
result += fmt::format("{} - ", ::color::blue("Example"));
break;
case info:
result += fmt::format("{} - ", ::color::green("Info"));
break;
case warn:
result += fmt::format("{} - ", ::color::red("Warning"));
break;
case depr:
result += fmt::format("{} - ", ::color::red("Deprecated"));
}
bool first = true;
for (auto& l : b.lines) {
if (!first) {
result += "\n";
}
if (b.kind == code) {
result += fmt::format(" {}", ::color::white(l));
} else {
result += l;
}
first = false;
}

return result;
}

} // namespace help
126 changes: 126 additions & 0 deletions src/cli/help.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#pragma once

#include <memory>
#include <string>
#include <vector>

#include <fmt/color.h>
#include <fmt/format.h>

#include "color.h"

namespace help {

struct lst {
std::string content;
};

struct block {
enum class admonition : std::uint32_t {
none,
note,
xmpl,
code,
info,
warn,
depr
};
using enum admonition;
admonition kind = none;
std::vector<std::string> lines;

block() = default;
block(std::string);
template <typename... Args>
block(admonition k, Args&&... args)
: kind(k), lines{std::forward<Args>(args)...} {
}
};
std::string render(const block&);

struct linebreak {};
std::string render(const linebreak&);

// type erasure for items to print in a help message.
// any type T for which the following is provided will be supported
// std::string render(const T&)
template <typename T>
concept Renderable = requires(T v) {
{ render(v) } -> std::convertible_to<std::string>;
};

class item {
public:
template <Renderable T>
item(T impl) : impl_(std::make_unique<wrap<T>>(std::move(impl))) {
}

item(item&& other) = default;

item(const item& other) : impl_(other.impl_->clone()) {
}

item& operator=(item&& other) = default;
item& operator=(const item& other) {
return *this = item(other);
}

std::string render() const {
return impl_->render();
}

private:
struct interface {
virtual ~interface() {
}
virtual std::unique_ptr<interface> clone() = 0;
virtual std::string render() const = 0;
};

std::unique_ptr<interface> impl_;

template <Renderable T> struct wrap : interface {
explicit wrap(const T& impl) : wrapped(impl) {
}
explicit wrap(T&& impl) : wrapped(std::move(impl)) {
}

virtual std::unique_ptr<interface> clone() override {
return std::unique_ptr<interface>(new wrap<T>(wrapped));
}

virtual std::string render() const override {
return help::render(wrapped);
}

T wrapped;
};
};

} // namespace help

template <> class fmt::formatter<help::item> {
public:
// parse format specification and store it:
constexpr auto parse(format_parse_context& ctx) {
return ctx.end();
}
// format a value using stored specification:
template <typename FmtContext>
constexpr auto format(help::item const& item, FmtContext& ctx) const {
return fmt::format_to(ctx.out(), "{}", item.render());
}
};

template <> class fmt::formatter<help::lst> {
public:
// parse format specification and store it:
constexpr auto parse(format_parse_context& ctx) {
return ctx.end();
}
// format a value using stored specification:
template <typename FmtContext>
constexpr auto format(help::lst const& l, FmtContext& ctx) const {
return fmt::format_to(ctx.out(), "{}", ::color::yellow(l.content));
}
};
59 changes: 55 additions & 4 deletions src/cli/ls.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// vim: ts=4 sts=4 sw=4 et

// #include <string>
#include <string>

#include <fmt/core.h>
#include <fmt/ranges.h>
#include <fmt/std.h>
#include <spdlog/spdlog.h>

Expand All @@ -11,13 +12,12 @@
#include <uenv/repository.h>
#include <util/expected.h>

#include "help.h"
#include "ls.h"

namespace uenv {

void image_ls_help() {
fmt::println("image ls help");
}
std::string image_ls_footer();

void image_ls_args::add_cli(CLI::App& cli,
[[maybe_unused]] global_settings& settings) {
Expand All @@ -28,6 +28,8 @@ void image_ls_args::add_cli(CLI::App& cli,
"print only the matching records, with no header.");
ls_cli->callback(
[&settings]() { settings.mode = uenv::cli_mode::image_ls; });

ls_cli->footer(image_ls_footer);
}

int image_ls(const image_ls_args& args, const global_settings& settings) {
Expand Down Expand Up @@ -105,4 +107,53 @@ int image_ls(const image_ls_args& args, const global_settings& settings) {
return 0;
}

std::string image_ls_footer() {
using enum help::block::admonition;
std::vector<help::item> items{
// clang-format off
help::block{none, "Search for uenv that are available to run." },
help::linebreak{},
help::block{xmpl, "list all uenv"},
help::block{code, "uenv image ls"},
help::linebreak{},
help::block{xmpl, "list all uenv"},
help::block{code, "uenv image ls"},
help::linebreak{},
help::block{xmpl, "list all uenv with the name prgenv-gnu"},
help::block{code, "uenv image ls prgenv-gnu"},
help::linebreak{},
help::block{xmpl, "list all uenv with the name prgenv-gnu and version 24.7"},
help::block{code, "uenv image ls prgenv-gnu/24.7"},
help::linebreak{},
help::block{xmpl, "list all uenv with the name prgenv-gnu, version 24.7 and release v2"},
help::block{code, "uenv image ls prgenv-gnu/24.7:v2"},
help::linebreak{},
help::block{xmpl, "use the @ symbol to specify a target system name"},
help::block{code, "uenv image ls prgenv-gnu@todi"},
help::block{none, "this feature is useful when using images that were built for a different system",
"than the one you are currently working on."},
help::linebreak{},
help::block{xmpl, "use the @ symbol to specify a target system name"},
help::block{code, "uenv image ls prgenv-gnu@todi"},
help::block{none, "this feature is useful when using images that were built for a different system",
"than the one you are currently working on."},
help::linebreak{},
help::block{xmpl, "use the % symbol to specify a target microarchitecture (uarch)"},
help::block{code, "uenv image ls prgenv-gnu%gh200"},
help::block{none, "this feature is useful on a system with multiple uarch."},
help::linebreak{},
help::block{xmpl, "list any uenv with a concrete sha256 checksum"},
help::block{code, "uenv image ls 510094ddb3484e305cb8118e21cbb9c94e9aff2004f0d6499763f42" "bdafccfb5"},
help::linebreak{},
help::block{note, "more than one uenv might be listed if there are two uenv that refer",
"to the same underlying uenv sha256."},
help::linebreak{},
help::block{xmpl, "search for uenv by id (id is the first 16 characters of the sha256):"},
help::block{code, "uenv image ls 510094ddb3484e30"},
// clang-format on
};

return fmt::format("{}", fmt::join(items, "\n"));
}

} // namespace uenv
Loading