Skip to content

Commit

Permalink
Add help and color output. (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcumming authored Sep 27, 2024
1 parent 97f8087 commit 71f2ca4
Show file tree
Hide file tree
Showing 10 changed files with 448 additions and 14 deletions.
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

0 comments on commit 71f2ca4

Please sign in to comment.