From bfd49178ca97b31229f27549bde43c0c13c406bf Mon Sep 17 00:00:00 2001 From: bcumming Date: Mon, 16 Sep 2024 18:29:52 +0200 Subject: [PATCH 1/9] add support for help messages and color; add help messages to image ls --- src/cli/color.h | 32 +++++++++++ src/cli/help.h | 135 +++++++++++++++++++++++++++++++++++++++++++++++ src/cli/ls.cpp | 57 ++++++++++++++++++-- src/cli/uenv.cpp | 19 +++++++ 4 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 src/cli/color.h create mode 100644 src/cli/help.h diff --git a/src/cli/color.h b/src/cli/color.h new file mode 100644 index 0000000..a49685e --- /dev/null +++ b/src/cli/color.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#define MAKE_COLOR(color) \ + constexpr auto color() { \ + return fmt::emphasis::bold | fg(fmt::terminal_color::color); \ + } \ + template constexpr auto color(const S& s) { \ + return fmt::format(color(), "{}", s); \ + } + +namespace 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 diff --git a/src/cli/help.h b/src/cli/help.h new file mode 100644 index 0000000..8d02dc4 --- /dev/null +++ b/src/cli/help.h @@ -0,0 +1,135 @@ +#pragma once + +#include +#include + +#include +#include + +#include "color.h" + +namespace help { + +struct lst { + std::string content; +}; + +struct block { + enum class admonition : std::uint32_t { + none, + note, + info, + warn, + deprecated + }; + using enum admonition; + admonition kind = none; + std::vector lines; +}; + +struct example { + std::vector description; + std::vector code; + std::vector blocks; +}; + +} // namespace help + +template <> class fmt::formatter { + public: + // parse format specification and store it: + constexpr auto parse(format_parse_context& ctx) { + return ctx.end(); + } + // format a value using stored specification: + template + constexpr auto format(help::lst const& listing, FmtContext& ctx) const { + return fmt::format_to(ctx.out(), "{}", ::color::white(listing.content)); + } +}; + +template <> class fmt::formatter { + public: + // parse format specification and store it: + constexpr auto parse(format_parse_context& ctx) { + return ctx.end(); + } + + // format a value using stored specification: + template + constexpr auto format(help::block const& block, FmtContext& ctx) const { + using enum help::block::admonition; + auto ctx_ = ctx.out(); + switch (block.kind) { + case none: + break; + case note: + ctx_ = fmt::format_to(ctx.out(), "{} - ", ::color::cyan("note")); + break; + case info: + ctx_ = fmt::format_to(ctx.out(), "{} - ", + ::color::bright_green("info")); + break; + case warn: + ctx_ = fmt::format_to(ctx.out(), "{} - ", + ::color::bright_red("warning")); + break; + case deprecated: + ctx_ = fmt::format_to(ctx.out(), "{} - ", + ::color::bright_red("deprecated")); + } + bool first = true; + for (auto& l : block.lines) { + if (!first) { + ctx_ = fmt::format_to(ctx_, "\n"); + } + ctx_ = fmt::format_to(ctx_, "{}", l); + first = false; + } + + return ctx_; + } +}; + +template +constexpr auto format_lines(FmtContext& ctx, + const std::vector& lines, + std::string_view prefix = "") { + auto ctx_ = ctx; + bool first = true; + for (auto& l : lines) { + if (!first) { + ctx_ = fmt::format_to(ctx_, "\n"); + } + ctx_ = fmt::format_to(ctx_, "{}{}", prefix, l); + } + + return ctx_; +} + +template <> class fmt::formatter { + public: + // parse format specification and store it: + constexpr auto parse(format_parse_context& ctx) { + return ctx.end(); + } + // format a value using stored specification: + template + constexpr auto format(help::example const& E, FmtContext& ctx) const { + using namespace color; + auto ctx_ = fmt::format_to(ctx.out(), "{} - ", blue("Example")); + ctx_ = format_lines(ctx_, E.description); + ctx_ = fmt::format_to(ctx_, ":"); + for (auto& l : E.code) { + ctx_ = fmt::format_to(ctx_, "\n {}", white(l)); + } + if (!E.blocks.empty()) { + ctx_ = fmt::format_to(ctx_, "\n"); + } + for (auto& b : E.blocks) { + ctx_ = fmt::format_to(ctx_, "{}", b); + } + + return ctx_; + } +}; diff --git a/src/cli/ls.cpp b/src/cli/ls.cpp index 0413c22..2ea600c 100644 --- a/src/cli/ls.cpp +++ b/src/cli/ls.cpp @@ -1,6 +1,6 @@ // vim: ts=4 sts=4 sw=4 et -// #include +#include #include #include @@ -11,13 +11,12 @@ #include #include +#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) { @@ -28,6 +27,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) { @@ -105,4 +106,52 @@ int image_ls(const image_ls_args& args, const global_settings& settings) { return 0; } +std::string image_ls_footer() { + using enum help::block::admonition; + help::block prelude = {none, + {"Search for uenv that are available to run."}}; + help::example list_all = {{"list all uenv"}, {"uenv image ls"}, {}}; + help::example list_all_prg = {{"list all uenv with the name prgenv-gnu"}, + {"uenv image ls prgenv-gnu"}, + {}}; + help::example list_all_prg2 = { + {"list all uenv with the name prgenv-gnu and version 24.7"}, + {"uenv image ls prgenv-gnu/24.7"}, + {}}; + help::example list_all_prg3 = { + {"list all uenv with the name prgenv-gnu, version 24.7 and release v2"}, + {"uenv image ls prgenv-gnu/24.7:v2"}, + {}}; + help::example list_system = { + {"use the @ symbol to specify a target system name"}, + {"uenv image ls prgenv-gnu@todi"}, + {{none, + {"this feature is useful when using images that were built for a " + "different system", + "than the one you are currently working on."}}}}; + help::example list_uarch = { + {"use the % symbol to specify a target microarchitecture (uarch)"}, + {"uenv image ls prgenv-gnu%gh200"}, + {{none, {"this feature is useful on a system with multiple uarch."}}}}; + help::example list_sha = { + {"list any uenv with a concrete sha256 checksum"}, + {"uenv image ls " + "510094ddb3484e305cb8118e21cbb9c94e9aff2004f0d6499763f42bdafccfb5"}, + {}}; + help::block unique_note = { + note, + {"more than one uenv might be listed if there are two uenv that " + "refer", + "to the same underlying uenv sha256."}}; + help::example list_id = {{"search for uenv by id (id is the first 16 " + "characters of the sha256):"}, + {"uenv image ls 510094ddb3484e30"}, + {}}; + + return fmt::format( + "{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}", prelude, + list_all, list_all_prg, list_all_prg2, list_all_prg3, list_system, + list_uarch, list_sha, unique_note, list_id); +} + } // namespace uenv diff --git a/src/cli/uenv.cpp b/src/cli/uenv.cpp index dda3dc5..0cda342 100644 --- a/src/cli/uenv.cpp +++ b/src/cli/uenv.cpp @@ -12,11 +12,14 @@ #include #include +#include "help.h" #include "image.h" #include "run.h" #include "start.h" #include "uenv.h" +std::string help_footer(); + int main(int argc, char** argv) { uenv::global_settings settings; bool print_version = false; @@ -27,6 +30,8 @@ int main(int argc, char** argv) { cli.add_flag("--repo", settings.repo_, "the uenv repository"); cli.add_flag("--version", print_version, "print version"); + cli.footer(help_footer); + uenv::start_args start; uenv::run_args run; uenv::image_args image; @@ -103,3 +108,17 @@ int main(int argc, char** argv) { return 0; } + +std::string help_footer() { + using enum help::block::admonition; + help::block prelude = { + none, + {"Use the --help flag in with sub-commands for more information."}}; + help::example run_example = { + {"get help with the run command"}, {"uenv run --help"}, {}}; + help::example ls_example = { + {fmt::format("get help with the {} command", help::lst("image ls"))}, + {"uenv image ls --help"}, + {}}; + return fmt::format("{}\n\n{}\n\n{}", prelude, run_example, ls_example); +} From 1866274a0fdfcd6a9cca12e5653f0d6d3e019b71 Mon Sep 17 00:00:00 2001 From: bcumming Date: Wed, 18 Sep 2024 21:56:01 +0200 Subject: [PATCH 2/9] tidy up help helpers --- meson.build | 1 + src/cli/help.cpp | 69 ++++++++++++++++++++++++++ src/cli/help.h | 125 +++++++++++++++++------------------------------ src/cli/ls.cpp | 110 ++++++++++++++++++++++++----------------- src/cli/uenv.cpp | 31 ++++++++---- 5 files changed, 202 insertions(+), 134 deletions(-) create mode 100644 src/cli/help.cpp diff --git a/meson.build b/meson.build index 29c747c..bbbe9ea 100644 --- a/meson.build +++ b/meson.build @@ -51,6 +51,7 @@ subdir('src/uenv') # the uenv executable if uenv_cli uenv_src = [ + 'src/cli/help.cpp', 'src/cli/image.cpp', 'src/cli/ls.cpp', 'src/cli/run.cpp', diff --git a/src/cli/help.cpp b/src/cli/help.cpp new file mode 100644 index 0000000..7a9bbe5 --- /dev/null +++ b/src/cli/help.cpp @@ -0,0 +1,69 @@ +#include + +#include "fmt/format.h" + +#include "color.h" +#include "help.h" + +namespace help { + +std::string render(const lst& l) { + return fmt::format("{}", ::color::white(l.content)); +} + +std::string render(const block& b) { + using enum help::block::admonition; + std::string result{}; + switch (b.kind) { + case none: + break; + case note: + result += fmt::format("{} - ", ::color::cyan("note")); + break; + case info: + result += fmt::format("{} - ", ::color::bright_green("info")); + break; + case warn: + result += fmt::format("{} - ", ::color::bright_red("warning")); + break; + case deprecated: + result += fmt::format("{} - ", ::color::bright_red("deprecated")); + } + bool first = true; + for (auto& l : b.lines) { + if (!first) { + result += "\n"; + } + result += l; + first = false; + } + + return result; +} + +std::string render(const example& e) { + using namespace color; + auto result = fmt::format("{} - ", blue("Example")); + bool first = true; + for (auto& l : e.description) { + if (!first) { + result += "\n"; + } + result += l; + first = false; + } + result += ":"; + for (auto& l : e.code) { + result += fmt::format("\n {}", white(l)); + } + if (!e.blocks.empty()) { + result += "\n"; + } + for (auto& b : e.blocks) { + result += render(b); + } + + return result; +} + +} // namespace help diff --git a/src/cli/help.h b/src/cli/help.h index 8d02dc4..4b0dd30 100644 --- a/src/cli/help.h +++ b/src/cli/help.h @@ -1,18 +1,18 @@ #pragma once +#include #include #include #include #include -#include "color.h" - namespace help { struct lst { std::string content; }; +std::string render(const lst&); struct block { enum class admonition : std::uint32_t { @@ -26,88 +26,67 @@ struct block { admonition kind = none; std::vector lines; }; +std::string render(const block&); struct example { std::vector description; std::vector code; std::vector blocks; + std::string string() const; }; +std::string render(const example&); -} // namespace help - -template <> class fmt::formatter { +class item { public: - // parse format specification and store it: - constexpr auto parse(format_parse_context& ctx) { - return ctx.end(); + template + item(T impl) : impl_(std::make_unique>(std::move(impl))) { } - // format a value using stored specification: - template - constexpr auto format(help::lst const& listing, FmtContext& ctx) const { - return fmt::format_to(ctx.out(), "{}", ::color::white(listing.content)); + + item(item&& other) = default; + + item(const item& other) : impl_(other.impl_->clone()) { } -}; -template <> class fmt::formatter { - public: - // parse format specification and store it: - constexpr auto parse(format_parse_context& ctx) { - return ctx.end(); + item& operator=(item&& other) = default; + item& operator=(const item& other) { + return *this = item(other); } - // format a value using stored specification: - template - constexpr auto format(help::block const& block, FmtContext& ctx) const { - using enum help::block::admonition; - auto ctx_ = ctx.out(); - switch (block.kind) { - case none: - break; - case note: - ctx_ = fmt::format_to(ctx.out(), "{} - ", ::color::cyan("note")); - break; - case info: - ctx_ = fmt::format_to(ctx.out(), "{} - ", - ::color::bright_green("info")); - break; - case warn: - ctx_ = fmt::format_to(ctx.out(), "{} - ", - ::color::bright_red("warning")); - break; - case deprecated: - ctx_ = fmt::format_to(ctx.out(), "{} - ", - ::color::bright_red("deprecated")); + std::string render() const { + return impl_->render(); + } + + private: + struct interface { + virtual ~interface() { + } + virtual std::unique_ptr clone() = 0; + virtual std::string render() const = 0; + }; + + std::unique_ptr impl_; + + template struct wrap : interface { + explicit wrap(const T& impl) : wrapped(impl) { } - bool first = true; - for (auto& l : block.lines) { - if (!first) { - ctx_ = fmt::format_to(ctx_, "\n"); - } - ctx_ = fmt::format_to(ctx_, "{}", l); - first = false; + explicit wrap(T&& impl) : wrapped(std::move(impl)) { } - return ctx_; - } -}; + virtual std::unique_ptr clone() override { + return std::unique_ptr(new wrap(wrapped)); + } -template -constexpr auto format_lines(FmtContext& ctx, - const std::vector& lines, - std::string_view prefix = "") { - auto ctx_ = ctx; - bool first = true; - for (auto& l : lines) { - if (!first) { - ctx_ = fmt::format_to(ctx_, "\n"); + virtual std::string render() const override { + return help::render(wrapped); } - ctx_ = fmt::format_to(ctx_, "{}{}", prefix, l); - } - return ctx_; -} + T wrapped; + }; +}; + +} // namespace help -template <> class fmt::formatter { +template <> class fmt::formatter { public: // parse format specification and store it: constexpr auto parse(format_parse_context& ctx) { @@ -115,21 +94,7 @@ template <> class fmt::formatter { } // format a value using stored specification: template - constexpr auto format(help::example const& E, FmtContext& ctx) const { - using namespace color; - auto ctx_ = fmt::format_to(ctx.out(), "{} - ", blue("Example")); - ctx_ = format_lines(ctx_, E.description); - ctx_ = fmt::format_to(ctx_, ":"); - for (auto& l : E.code) { - ctx_ = fmt::format_to(ctx_, "\n {}", white(l)); - } - if (!E.blocks.empty()) { - ctx_ = fmt::format_to(ctx_, "\n"); - } - for (auto& b : E.blocks) { - ctx_ = fmt::format_to(ctx_, "{}", b); - } - - return ctx_; + constexpr auto format(help::item const& item, FmtContext& ctx) const { + return fmt::format_to(ctx.out(), "{}", item.render()); } }; diff --git a/src/cli/ls.cpp b/src/cli/ls.cpp index 2ea600c..af2883f 100644 --- a/src/cli/ls.cpp +++ b/src/cli/ls.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -108,50 +109,71 @@ int image_ls(const image_ls_args& args, const global_settings& settings) { std::string image_ls_footer() { using enum help::block::admonition; - help::block prelude = {none, - {"Search for uenv that are available to run."}}; - help::example list_all = {{"list all uenv"}, {"uenv image ls"}, {}}; - help::example list_all_prg = {{"list all uenv with the name prgenv-gnu"}, - {"uenv image ls prgenv-gnu"}, - {}}; - help::example list_all_prg2 = { - {"list all uenv with the name prgenv-gnu and version 24.7"}, - {"uenv image ls prgenv-gnu/24.7"}, - {}}; - help::example list_all_prg3 = { - {"list all uenv with the name prgenv-gnu, version 24.7 and release v2"}, - {"uenv image ls prgenv-gnu/24.7:v2"}, - {}}; - help::example list_system = { - {"use the @ symbol to specify a target system name"}, - {"uenv image ls prgenv-gnu@todi"}, - {{none, - {"this feature is useful when using images that were built for a " - "different system", - "than the one you are currently working on."}}}}; - help::example list_uarch = { - {"use the % symbol to specify a target microarchitecture (uarch)"}, - {"uenv image ls prgenv-gnu%gh200"}, - {{none, {"this feature is useful on a system with multiple uarch."}}}}; - help::example list_sha = { - {"list any uenv with a concrete sha256 checksum"}, - {"uenv image ls " - "510094ddb3484e305cb8118e21cbb9c94e9aff2004f0d6499763f42bdafccfb5"}, - {}}; - help::block unique_note = { - note, - {"more than one uenv might be listed if there are two uenv that " - "refer", - "to the same underlying uenv sha256."}}; - help::example list_id = {{"search for uenv by id (id is the first 16 " - "characters of the sha256):"}, - {"uenv image ls 510094ddb3484e30"}, - {}}; - - return fmt::format( - "{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}", prelude, - list_all, list_all_prg, list_all_prg2, list_all_prg3, list_system, - list_uarch, list_sha, unique_note, list_id); + std::vector items{ + // clang-format off + help::block{ + none, + {"Search for uenv that are available to run."} + }, + help::example{ + {"list all uenv"}, + {"uenv image ls"}, + {} + }, + help::example{ + {"list all uenv with the name prgenv-gnu"}, + {"uenv image ls prgenv-gnu"}, + {} + }, + help::example{ + {"list all uenv with the name prgenv-gnu and version 24.7"}, + {"uenv image ls prgenv-gnu/24.7"}, + {} + }, + help::example{ + {"list all uenv with the name prgenv-gnu, version 24.7 and release v2"}, + {"uenv image ls prgenv-gnu/24.7:v2"}, + {} + }, + help::example{ + {"use the @ symbol to specify a target system name"}, + {"uenv image ls prgenv-gnu@todi"}, + { + { + none, { + "this feature is useful when using images that were built for a different system", + "than the one you are currently working on." + } + } + } + }, + help::example { + {"use the % symbol to specify a target microarchitecture (uarch)"}, + {"uenv image ls prgenv-gnu%gh200"}, + { + { none, {"this feature is useful on a system with multiple uarch."} } + } + }, + help::example { + {"list any uenv with a concrete sha256 checksum"}, + {"uenv image ls 510094ddb3484e305cb8118e21cbb9c94e9aff2004f0d6499763f42" "bdafccfb5"}, + {} + }, + help::block { + note, { + "more than one uenv might be listed if there are two uenv that refer", + "to the same underlying uenv sha256." + } + }, + help::example { + {"search for uenv by id (id is the first 16 characters of the sha256):"}, + {"uenv image ls 510094ddb3484e30"}, + {} + } + // clang-format on + }; + + return fmt::format("{}", fmt::join(items, "\n\n")); } } // namespace uenv diff --git a/src/cli/uenv.cpp b/src/cli/uenv.cpp index 0cda342..28770af 100644 --- a/src/cli/uenv.cpp +++ b/src/cli/uenv.cpp @@ -111,14 +111,25 @@ int main(int argc, char** argv) { std::string help_footer() { using enum help::block::admonition; - help::block prelude = { - none, - {"Use the --help flag in with sub-commands for more information."}}; - help::example run_example = { - {"get help with the run command"}, {"uenv run --help"}, {}}; - help::example ls_example = { - {fmt::format("get help with the {} command", help::lst("image ls"))}, - {"uenv image ls --help"}, - {}}; - return fmt::format("{}\n\n{}\n\n{}", prelude, run_example, ls_example); + + // clang-format off + std::vector items{ + help::block{ + none, + {"Use the --help flag in with sub-commands for more information."} + }, + help::example{ + {"get help with the run command"}, + {"uenv run --help"}, + {} + }, + help::example{ + {fmt::format("get help with the {} command", render(help::lst("image ls")))}, + {"uenv image ls --help"}, + {} + } + }; + // clang-format on + + return fmt::format("{}", fmt::join(items, "\n\n")); } From 6207eb0605b2c609295c7880db88a44e560ccf99 Mon Sep 17 00:00:00 2001 From: bcumming Date: Mon, 23 Sep 2024 08:55:14 +0200 Subject: [PATCH 3/9] refactor help formatting to be more modular --- src/cli/help.cpp | 50 +++++++++--------------- src/cli/help.h | 37 +++++++++++++----- src/cli/ls.cpp | 99 +++++++++++++++++++---------------------------- src/cli/start.cpp | 54 ++++++++++++++++++++++++-- src/cli/uenv.cpp | 28 +++++++------- 5 files changed, 149 insertions(+), 119 deletions(-) diff --git a/src/cli/help.cpp b/src/cli/help.cpp index 7a9bbe5..03248c4 100644 --- a/src/cli/help.cpp +++ b/src/cli/help.cpp @@ -7,8 +7,11 @@ namespace help { -std::string render(const lst& l) { - return fmt::format("{}", ::color::white(l.content)); +block::block(std::string msg) : kind(none), lines{std::move(msg)} { +} + +std::string render(const linebreak&) { + return ""; } std::string render(const block& b) { @@ -16,52 +19,35 @@ std::string render(const block& b) { std::string result{}; switch (b.kind) { case none: + case code: break; case note: - result += fmt::format("{} - ", ::color::cyan("note")); + result += fmt::format("{} - ", ::color::cyan("Note")); + break; + case xmpl: + result += fmt::format("{} - ", ::color::blue("Example")); break; case info: - result += fmt::format("{} - ", ::color::bright_green("info")); + result += fmt::format("{} - ", ::color::green("Info")); break; case warn: - result += fmt::format("{} - ", ::color::bright_red("warning")); + result += fmt::format("{} - ", ::color::red("Warning")); break; - case deprecated: - result += fmt::format("{} - ", ::color::bright_red("deprecated")); + case depr: + result += fmt::format("{} - ", ::color::red("Deprecated")); } bool first = true; for (auto& l : b.lines) { if (!first) { result += "\n"; } - result += l; - first = false; - } - - return result; -} - -std::string render(const example& e) { - using namespace color; - auto result = fmt::format("{} - ", blue("Example")); - bool first = true; - for (auto& l : e.description) { - if (!first) { - result += "\n"; + if (b.kind == code) { + result += fmt::format(" {}", ::color::white(l)); + } else { + result += l; } - result += l; first = false; } - result += ":"; - for (auto& l : e.code) { - result += fmt::format("\n {}", white(l)); - } - if (!e.blocks.empty()) { - result += "\n"; - } - for (auto& b : e.blocks) { - result += render(b); - } return result; } diff --git a/src/cli/help.h b/src/cli/help.h index 4b0dd30..483c967 100644 --- a/src/cli/help.h +++ b/src/cli/help.h @@ -7,34 +7,40 @@ #include #include +#include "color.h" + namespace help { struct lst { std::string content; }; -std::string render(const lst&); struct block { enum class admonition : std::uint32_t { none, note, + xmpl, + code, info, warn, - deprecated + depr }; using enum admonition; admonition kind = none; std::vector lines; + + block() = default; + block(std::string); + template + block(admonition k, Args&&... args) + : kind(k), lines{std::forward(args)...} { + } }; std::string render(const block&); -struct example { - std::vector description; - std::vector code; - std::vector blocks; - std::string string() const; -}; -std::string render(const example&); +// linebreak +struct linebreak {}; +std::string render(const linebreak&); class item { public: @@ -98,3 +104,16 @@ template <> class fmt::formatter { return fmt::format_to(ctx.out(), "{}", item.render()); } }; + +template <> class fmt::formatter { + public: + // parse format specification and store it: + constexpr auto parse(format_parse_context& ctx) { + return ctx.end(); + } + // format a value using stored specification: + template + constexpr auto format(help::lst const& l, FmtContext& ctx) const { + return fmt::format_to(ctx.out(), "{}", ::color::yellow(l.content)); + } +}; diff --git a/src/cli/ls.cpp b/src/cli/ls.cpp index af2883f..7da48d2 100644 --- a/src/cli/ls.cpp +++ b/src/cli/ls.cpp @@ -111,69 +111,48 @@ std::string image_ls_footer() { using enum help::block::admonition; std::vector items{ // clang-format off - help::block{ - none, - {"Search for uenv that are available to run."} - }, - help::example{ - {"list all uenv"}, - {"uenv image ls"}, - {} - }, - help::example{ - {"list all uenv with the name prgenv-gnu"}, - {"uenv image ls prgenv-gnu"}, - {} - }, - help::example{ - {"list all uenv with the name prgenv-gnu and version 24.7"}, - {"uenv image ls prgenv-gnu/24.7"}, - {} - }, - help::example{ - {"list all uenv with the name prgenv-gnu, version 24.7 and release v2"}, - {"uenv image ls prgenv-gnu/24.7:v2"}, - {} - }, - help::example{ - {"use the @ symbol to specify a target system name"}, - {"uenv image ls prgenv-gnu@todi"}, - { - { - none, { - "this feature is useful when using images that were built for a different system", - "than the one you are currently working on." - } - } - } - }, - help::example { - {"use the % symbol to specify a target microarchitecture (uarch)"}, - {"uenv image ls prgenv-gnu%gh200"}, - { - { none, {"this feature is useful on a system with multiple uarch."} } - } - }, - help::example { - {"list any uenv with a concrete sha256 checksum"}, - {"uenv image ls 510094ddb3484e305cb8118e21cbb9c94e9aff2004f0d6499763f42" "bdafccfb5"}, - {} - }, - help::block { - note, { - "more than one uenv might be listed if there are two uenv that refer", - "to the same underlying uenv sha256." - } - }, - help::example { - {"search for uenv by id (id is the first 16 characters of the sha256):"}, - {"uenv image ls 510094ddb3484e30"}, - {} - } + help::block{none, "Search for uenv that are available to run." }, + 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\n")); + return fmt::format("{}", fmt::join(items, "\n")); } } // namespace uenv diff --git a/src/cli/start.cpp b/src/cli/start.cpp index afe6996..8c1a2b6 100644 --- a/src/cli/start.cpp +++ b/src/cli/start.cpp @@ -13,14 +13,13 @@ #include #include +#include "help.h" #include "start.h" #include "uenv.h" namespace uenv { -void start_help() { - fmt::println("start help"); -} +std::string start_footer(); void start_args::add_cli(CLI::App& cli, [[maybe_unused]] global_settings& settings) { @@ -33,6 +32,7 @@ void start_args::add_cli(CLI::App& cli, ->required(); start_cli->callback( [&settings]() { settings.mode = uenv::cli_mode::start; }); + start_cli->footer(start_footer); } int start(const start_args& args, @@ -76,4 +76,52 @@ int start(const start_args& args, return util::exec(commands); } +std::string start_footer() { + using enum help::block::admonition; + using help::lst; + std::vector items{ + // clang-format off + help::block{none, "Start a new shell with a uenv environment. The shell will be", + fmt::format("the default shell set using the SHELL envronment variable ({}).", lst("echo $SHELL"))}, + help::linebreak{}, + help::block{note, + "the uenv must have been pulled before it can be used. See the list", + fmt::format("of available uenv using {}.", lst("uenv image ls")), + "If using a path to a squashfs file, you need to have read rights in", + "the path where the file is stored.", + }, + help::linebreak{}, + help::block{xmpl, "start a uenv"}, + help::block{code, "uenv start prgenv-gnu/24.7:v3"}, + help::block{none, "use the full name/version:tag format to disambiguate fully the image "}, + help::linebreak{}, + help::block{info, "uenv will mount the image at the correct location, which for most uenv", + "is /user-enviroment."}, + help::linebreak{}, + help::block{xmpl, fmt::format("start an image built for the system daint using {}", lst("@daint"))}, + help::block{code, "uenv start prgenv-gnu/24.7:v1@daint"}, + help::linebreak{}, + help::block{xmpl, "use the @ symbol to specify a target system name"}, + help::block{code, "uenv start 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, "two uenv images can be used at the same time"}, + help::block{code, "uenv start prgenv-gnu/24.7:v3,editors/24.7:v1"}, + help::linebreak{}, + help::block{info, "to start two uenv at the same time, they must be mounted at different mount.", + "points. uenv provided by CSCS are designed to be mounted at two locations:", + fmt::format(" - {} programming environments and applications", help::lst("/user-enviroment")), + fmt::format(" - {} tools like debuggers and profilers that are used", help::lst("/user-tools")), + " alongside PE and applications"}, + help::linebreak{}, + help::block{xmpl, "example of using the full specification:"}, + help::block{code, "uenv start prgenv-gnu/24.7:v3:/user-environemnt,editors/24.7:v1:user-tools \\", + " --view=prgenv-gnu:default,editors:modules"}, + help::linebreak{}, + // clang-format on + }; + + return fmt::format("{}", fmt::join(items, "\n")); +} } // namespace uenv diff --git a/src/cli/uenv.cpp b/src/cli/uenv.cpp index 28770af..3e1c824 100644 --- a/src/cli/uenv.cpp +++ b/src/cli/uenv.cpp @@ -111,25 +111,23 @@ int main(int argc, char** argv) { std::string help_footer() { using enum help::block::admonition; + using help::lst; // clang-format off std::vector items{ - help::block{ - none, - {"Use the --help flag in with sub-commands for more information."} - }, - help::example{ - {"get help with the run command"}, - {"uenv run --help"}, - {} - }, - help::example{ - {fmt::format("get help with the {} command", render(help::lst("image ls")))}, - {"uenv image ls --help"}, - {} - } + help::block{none, "Use the --help flag in with sub-commands for more information."}, + help::linebreak{}, + help::block{xmpl, fmt::format("use the {} flag to generate more verbose output", lst{"-v"})}, + help::block{code, "uenv -v image ls # info level logging"}, + help::block{code, "uenv -vv image ls # debug level logging"}, + help::linebreak{}, + help::block{xmpl, "get help with the run command"}, + help::block{code, "uenv run --help"}, + help::linebreak{}, + help::block{xmpl, fmt::format("get help with the {} command", lst("image ls"))}, + help::block{code, "uenv image ls --help"}, }; // clang-format on - return fmt::format("{}", fmt::join(items, "\n\n")); + return fmt::format("{}", fmt::join(items, "\n")); } From e28f8d26989dabe38bcad1ad31d08ed8b770e937 Mon Sep 17 00:00:00 2001 From: bcumming Date: Fri, 27 Sep 2024 18:10:42 +0200 Subject: [PATCH 4/9] add support for enabling/disabling color output --- meson.build | 1 + src/cli/color.cpp | 34 ++++++++++++++++++++++++++++++++++ src/cli/color.h | 12 ++++++++---- src/cli/uenv.cpp | 15 +++++++++++++-- src/cli/uenv.h | 2 -- 5 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 src/cli/color.cpp diff --git a/meson.build b/meson.build index bbbe9ea..b9818c2 100644 --- a/meson.build +++ b/meson.build @@ -51,6 +51,7 @@ 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', diff --git a/src/cli/color.cpp b/src/cli/color.cpp new file mode 100644 index 0000000..3550160 --- /dev/null +++ b/src/cli/color.cpp @@ -0,0 +1,34 @@ +#include +#include + +#include + +#include "color.h" + +namespace color { + +namespace impl { +bool use = true; +} + +void default_color() { + // check whether NO_COLOR has been set + if (std::getenv("NO_COLOR")) { + set_color(false); + return; + } + + if (!isatty(fileno(stdout))) { + set_color(false); + } +} + +void set_color(bool v) { + impl::use = v; +} + +bool use_color() { + return impl::use; +} + +} // namespace color diff --git a/src/cli/color.h b/src/cli/color.h index a49685e..a49d56e 100644 --- a/src/cli/color.h +++ b/src/cli/color.h @@ -2,16 +2,20 @@ #include -#define MAKE_COLOR(color) \ - constexpr auto color() { \ - return fmt::emphasis::bold | fg(fmt::terminal_color::color); \ +#define MAKE_COLOR(color) \ + static auto color() { \ + return fmt::emphasis::bold | fg(fmt::terminal_color::color); \ } \ template constexpr auto color(const S& s) { \ - return fmt::format(color(), "{}", 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) diff --git a/src/cli/uenv.cpp b/src/cli/uenv.cpp index 3e1c824..fe55731 100644 --- a/src/cli/uenv.cpp +++ b/src/cli/uenv.cpp @@ -1,7 +1,6 @@ // vim: ts=4 sts=4 sw=4 et #include -#include #include #include @@ -12,6 +11,7 @@ #include #include +#include "color.h" #include "help.h" #include "image.h" #include "run.h" @@ -24,9 +24,18 @@ int main(int argc, char** argv) { uenv::global_settings settings; bool print_version = false; + // enable/disable color depending on NOCOLOR env. var + // and tty terminal status. + color::default_color(); + CLI::App cli(fmt::format("uenv {}", UENV_VERSION)); cli.add_flag("-v,--verbose", settings.verbose, "enable verbose output"); - cli.add_flag("--no-color", settings.no_color, "disable color output"); + cli.add_flag_callback("--no-color", + []() -> void {color::set_color(false);}, + "disable color output"); + cli.add_flag_callback("--color", + []() -> void {color::set_color(true);}, + "enable color output"); cli.add_flag("--repo", settings.repo_, "the uenv repository"); cli.add_flag("--version", print_version, "print version"); @@ -42,6 +51,8 @@ int main(int argc, char** argv) { CLI11_PARSE(cli, argc, argv); + //color::set_color(!settings.no_color); + // Warnings and errors are always logged. The verbosity level is increased // with repeated uses of --verbose. spdlog::level::level_enum console_log_level = spdlog::level::warn; diff --git a/src/cli/uenv.h b/src/cli/uenv.h index c4d9ace..a4015e7 100644 --- a/src/cli/uenv.h +++ b/src/cli/uenv.h @@ -18,8 +18,6 @@ struct global_settings { using enum cli_mode; int verbose = 0; - bool no_color = false; - // int mode = mode_none; cli_mode mode = unset; // repo_ is the unverified string description of the repo path that is From 9ae924223920d9684e7ea21ee03f6eb25ce64dfe Mon Sep 17 00:00:00 2001 From: bcumming Date: Fri, 27 Sep 2024 18:11:01 +0200 Subject: [PATCH 5/9] add draft help for uenv run command --- src/cli/run.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/cli/run.cpp b/src/cli/run.cpp index 22df5d5..3a3a3e4 100644 --- a/src/cli/run.cpp +++ b/src/cli/run.cpp @@ -13,14 +13,13 @@ #include #include +#include "help.h" #include "run.h" #include "uenv.h" namespace uenv { -void run_help() { - fmt::println("run help"); -} +std::string run_footer(); void run_args::add_cli(CLI::App& cli, global_settings& settings) { auto* run_cli = cli.add_subcommand("run", "run a uenv session"); @@ -35,6 +34,7 @@ void run_args::add_cli(CLI::App& cli, global_settings& settings) { "the command to run, including with arguments") ->required(); run_cli->callback([&settings]() { settings.mode = uenv::cli_mode::run; }); + run_cli->footer(run_footer); } int run(const run_args& args, const global_settings& globals) { @@ -68,4 +68,46 @@ int run(const run_args& args, const global_settings& globals) { return util::exec(commands); } +std::string run_footer() { + using enum help::block::admonition; + using help::block; + using help::lst; + using help::linebreak; + std::vector items{ + // clang-format off + block{none, "Run a command in an environment."}, + linebreak{}, + block{xmpl, "run the script job.sh in an evironmnent"}, + block{code, "uenv run prgenv-gnu/24.2:v1 -- ./job.sh"}, + block{none, "This will mount prgenv-gnu, execute job.sh, then return to the calling shell."}, + block{note, "how the command to execute comes after the two dashes '--'."}, + linebreak{}, + block{xmpl, "run the script job.sh in an evironmnent with a view loaded"}, + block{code, "uenv run prgenv-gnu/24.2:v1 --view=default -- ./job.sh"}, + linebreak{}, + block{note, "the spec must uniquely identify the uenv. To ensure this, always use a"}, + block{none, "fully qualified spec in the form of name/version:tag, the unique 16 digit id,"}, + block{none, "or sha256 of a uenv. If more than one uenv match the spec, an error message"}, + block{none, "is printed."}, + linebreak{}, + block{xmpl, "run the job.sh script with two images mounted"}, + block{code, "uenv run prgenv-gnu/24.2:v1 ddt/23.1 -- ./job.sh"}, + linebreak{}, + block{xmpl, "run the job.sh script with two images mounted at specific mount points"}, + block{code, "uenv run prgenv-gnu/24.2:v1:$SCRATCH/pe ddt/23.1:/user-tools -- ./job.sh"}, + block{none, "Here the mount point for each image is specified using a ':'."}, + linebreak{}, + block{note, "uenv must be mounted at the mount point for which they were built."}, + block{none, "If mounted at the wrong location, a warning message will be printed, and"}, + block{none, "views will be disabled."}, + linebreak{}, + block{xmpl, "the run command can be used to execute workflow steps with", "separate environments"}, + block{code, "uenv run gromacs/23.1 -- ./simulation.sh"}, + block{code, "uenv run paraview/5.11 -- ./render.sh"}, + // clang-format on + }; + + return fmt::format("{}", fmt::join(items, "\n")); +} + } // namespace uenv From 6e03d9cf12c51951cc6693729da0890463bb38a0 Mon Sep 17 00:00:00 2001 From: bcumming Date: Fri, 27 Sep 2024 19:27:57 +0200 Subject: [PATCH 6/9] small formatting change --- src/cli/ls.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cli/ls.cpp b/src/cli/ls.cpp index 7da48d2..c3ec174 100644 --- a/src/cli/ls.cpp +++ b/src/cli/ls.cpp @@ -112,6 +112,7 @@ std::string image_ls_footer() { std::vector 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{}, From 746f7170c3df3f3905dfeaaca5846d9ddde744d2 Mon Sep 17 00:00:00 2001 From: bcumming Date: Fri, 27 Sep 2024 19:29:47 +0200 Subject: [PATCH 7/9] reformat code --- src/cli/color.cpp | 2 +- src/cli/color.h | 8 ++++---- src/cli/run.cpp | 2 +- src/cli/uenv.cpp | 14 +++++++------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/cli/color.cpp b/src/cli/color.cpp index 3550160..3d5f622 100644 --- a/src/cli/color.cpp +++ b/src/cli/color.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include diff --git a/src/cli/color.h b/src/cli/color.h index a49d56e..92cfa8b 100644 --- a/src/cli/color.h +++ b/src/cli/color.h @@ -2,12 +2,12 @@ #include -#define MAKE_COLOR(color) \ - static auto color() { \ - return fmt::emphasis::bold | fg(fmt::terminal_color::color); \ +#define MAKE_COLOR(color) \ + static auto color() { \ + return fmt::emphasis::bold | fg(fmt::terminal_color::color); \ } \ template constexpr auto color(const S& s) { \ - return use_color()? fmt::format(color(), "{}", s): std::string(s); \ + return use_color() ? fmt::format(color(), "{}", s) : std::string(s); \ } namespace color { diff --git a/src/cli/run.cpp b/src/cli/run.cpp index 3a3a3e4..074ad19 100644 --- a/src/cli/run.cpp +++ b/src/cli/run.cpp @@ -71,8 +71,8 @@ int run(const run_args& args, const global_settings& globals) { std::string run_footer() { using enum help::block::admonition; using help::block; - using help::lst; using help::linebreak; + using help::lst; std::vector items{ // clang-format off block{none, "Run a command in an environment."}, diff --git a/src/cli/uenv.cpp b/src/cli/uenv.cpp index fe55731..7f90299 100644 --- a/src/cli/uenv.cpp +++ b/src/cli/uenv.cpp @@ -30,12 +30,12 @@ int main(int argc, char** argv) { CLI::App cli(fmt::format("uenv {}", UENV_VERSION)); cli.add_flag("-v,--verbose", settings.verbose, "enable verbose output"); - cli.add_flag_callback("--no-color", - []() -> void {color::set_color(false);}, - "disable color output"); - cli.add_flag_callback("--color", - []() -> void {color::set_color(true);}, - "enable color output"); + cli.add_flag_callback( + "--no-color", []() -> void { color::set_color(false); }, + "disable color output"); + cli.add_flag_callback( + "--color", []() -> void { color::set_color(true); }, + "enable color output"); cli.add_flag("--repo", settings.repo_, "the uenv repository"); cli.add_flag("--version", print_version, "print version"); @@ -51,7 +51,7 @@ int main(int argc, char** argv) { CLI11_PARSE(cli, argc, argv); - //color::set_color(!settings.no_color); + // color::set_color(!settings.no_color); // Warnings and errors are always logged. The verbosity level is increased // with repeated uses of --verbose. From d8d8f67dea50f331891f94fe781a25ff7cee3480 Mon Sep 17 00:00:00 2001 From: bcumming Date: Fri, 27 Sep 2024 19:54:07 +0200 Subject: [PATCH 8/9] improve comments --- src/cli/color.cpp | 7 +++++-- src/cli/help.h | 13 ++++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/cli/color.cpp b/src/cli/color.cpp index 3d5f622..3eb8278 100644 --- a/src/cli/color.cpp +++ b/src/cli/color.cpp @@ -12,12 +12,15 @@ bool use = true; } void default_color() { - // check whether NO_COLOR has been set + // enable color by default + set_color(true); + + // disable color if NO_COLOR env. variable is set if (std::getenv("NO_COLOR")) { set_color(false); - return; } + // disable color if stdout is not a terminal if (!isatty(fileno(stdout))) { set_color(false); } diff --git a/src/cli/help.h b/src/cli/help.h index 483c967..7683211 100644 --- a/src/cli/help.h +++ b/src/cli/help.h @@ -38,13 +38,20 @@ struct block { }; std::string render(const block&); -// linebreak 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 +concept Renderable = requires(T v) { + {render(v)} -> std::convertible_to; +}; + class item { public: - template + template item(T impl) : impl_(std::make_unique>(std::move(impl))) { } @@ -72,7 +79,7 @@ class item { std::unique_ptr impl_; - template struct wrap : interface { + template struct wrap : interface { explicit wrap(const T& impl) : wrapped(impl) { } explicit wrap(T&& impl) : wrapped(std::move(impl)) { From 8b20ecfe63acd54643ddf2ef2fcd4f2f90b1e3fb Mon Sep 17 00:00:00 2001 From: bcumming Date: Fri, 27 Sep 2024 20:08:02 +0200 Subject: [PATCH 9/9] formatting --- src/cli/help.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/help.h b/src/cli/help.h index 7683211..fbc4f3e 100644 --- a/src/cli/help.h +++ b/src/cli/help.h @@ -46,7 +46,7 @@ std::string render(const linebreak&); // std::string render(const T&) template concept Renderable = requires(T v) { - {render(v)} -> std::convertible_to; + { render(v) } -> std::convertible_to; }; class item {