From eb2186cd27c39d9e5649fbda57b80f47ffbfa15e Mon Sep 17 00:00:00 2001 From: Radek Brich Date: Tue, 9 Jul 2024 22:50:21 +0200 Subject: [PATCH 1/7] [core] TermCtl: Replace color and mode placeholders in print/format using a custom parser Do not delegate to fmt. There is a regression in fmt 11, which broke that - named placeholders cannot be mixed with automatically indexed ones. Although I think this should be allowed, using the named args for the TermCtl color placeholders was basically a hack. A custom parser will be more flexible and allow more freedom regarding the syntax. The new placeholders are processed purely in runtime. Unknown placeholders are left untouched. Compile-time checking is still possible, but that doesn't seem as important feature. Actual replacing by escape sequences must be done in runtime, as it depends on what is attached to the output stream (tty or pipe). --- examples/core/demo_termctl.cpp | 24 +++--- src/xci/core/ArgParser.cpp | 14 ++-- src/xci/core/TermCtl.cpp | 76 ++++++++++++++---- src/xci/core/TermCtl.h | 126 ++++++++---------------------- src/xci/core/log_termctl.cpp | 22 +++--- tests/test_core.cpp | 2 +- tools/data_archive/dar.cpp | 10 +-- tools/data_inspect/dati.cpp | 60 +++++++------- tools/find_file/ff.cpp | 4 +- tools/fire_script/Highlighter.cpp | 6 +- tools/fire_script/Options.cpp | 4 +- tools/fire_script/Program.cpp | 14 ++-- tools/fire_script/Repl.cpp | 10 +-- tools/fire_script/ReplCommand.cpp | 22 +++--- tools/shader_editor/shed.cpp | 4 +- tools/term_ctl/tc.cpp | 4 +- 16 files changed, 195 insertions(+), 207 deletions(-) diff --git a/examples/core/demo_termctl.cpp b/examples/core/demo_termctl.cpp index e05ceb10..6792d298 100644 --- a/examples/core/demo_termctl.cpp +++ b/examples/core/demo_termctl.cpp @@ -1,7 +1,7 @@ // demo_termctl.cpp created on 2018-07-11 as part of xcikit project // https://github.com/rbrich/xcikit // -// Copyright 2018, 2020 Radek Brich +// Copyright 2018–2024 Radek Brich // Licensed under the Apache License, Version 2.0 (see LICENSE file) #include @@ -27,17 +27,17 @@ int main() cout << t.move_up().move_right(6).bold().green() << "GREEN" <formatted\n"); + t.print("bold " + "dim " + "italic " + "underlined " + "overlined " + "crossed out " + "framed " + "blinking " + "reversed " + "hidden " "\n"); t.tab_set_all({30, 20}).write(); diff --git a/src/xci/core/ArgParser.cpp b/src/xci/core/ArgParser.cpp index 1ba45655..fea7a6e3 100644 --- a/src/xci/core/ArgParser.cpp +++ b/src/xci/core/ArgParser.cpp @@ -136,12 +136,12 @@ std::string Option::usage() const if (!p) break; if (is_remainder() && p.dashes == 2 && p.len == 0) { - res += t.format("[{fg:green}{}{t:normal}] ", std::string(dp + p.pos, p.dashes)); + res += t.format("[{}] ", std::string(dp + p.pos, p.dashes)); } else if (first) { first = false; - res += t.format("{t:bold}{fg:green}{}{t:normal}", std::string(dp + p.pos, p.dashes + p.len)); + res += t.format("{}", std::string(dp + p.pos, p.dashes + p.len)); } else if (!p.dashes) { - res += t.format(" {fg:green}{}{t:normal}", std::string(dp + p.pos, p.len)); + res += t.format(" {}", std::string(dp + p.pos, p.len)); } dp += p.end(); } @@ -312,7 +312,7 @@ ArgParser& ArgParser::operator()(const char* argv[], bool detect_width, unsigned if (!parse_program_name(argv[0])) { // this should not occur auto& t = TermCtl::stderr_instance(); - t.print("{t:bold}{fg:red}Missing program name (argv[0]){t:normal}\n"); + t.print("Missing program name (argv[0])\n"); exit(1); } try { @@ -326,7 +326,7 @@ ArgParser& ArgParser::operator()(const char* argv[], bool detect_width, unsigned } } catch (const BadArgument& e) { auto& t = TermCtl::stderr_instance(); - t.print("{t:bold}{fg:yellow}Error: {fg:red}{}{t:normal}\n\n", e.what()); + t.print("Error: {}\n\n", e.what()); print_usage(); print_help_notice(); exit(1); @@ -525,7 +525,7 @@ void ArgParser::print_usage() const unsigned indent = 0; { - auto head = t.format("{t:bold}{fg:yellow}Usage:{t:normal} {t:bold}{}{t:normal} ", m_progname); + auto head = t.format("Usage: {} ", m_progname); indent = TermCtl::stripped_width(head); cout << head; } @@ -545,7 +545,7 @@ void ArgParser::print_help() const desc_cols = std::max(desc_cols, (unsigned) opt.desc().size()); print_usage(); auto& t = TermCtl::stdout_instance(); - t.print("\n{t:bold}{fg:yellow}Options:{t:normal}\n"); + t.print("\nOptions:\n"); for (const auto& opt : m_opts) { cout << " " << opt.formatted_desc(desc_cols) << " "; wrapping_print(opt.help(), desc_cols + 4, 0, m_max_width); diff --git a/src/xci/core/TermCtl.cpp b/src/xci/core/TermCtl.cpp index fdd50c3d..1743c2b1 100644 --- a/src/xci/core/TermCtl.cpp +++ b/src/xci/core/TermCtl.cpp @@ -1,7 +1,7 @@ // TermCtl.cpp created on 2018-07-09 as part of xcikit project // https://github.com/rbrich/xcikit // -// Copyright 2018, 2020, 2021 Radek Brich +// Copyright 2018–2024 Radek Brich // Licensed under the Apache License, Version 2.0 (see LICENSE file) // References: @@ -608,21 +608,67 @@ TermCtl& TermCtl::clear_line_to_end() { return TERM_APPEND(clr_eol); } TermCtl& TermCtl::soft_reset() { return XCI_TERM_APPEND(seq::send_soft_reset); } -std::string TermCtl::FgPlaceholder::seq(Color color) const +std::string TermCtl::_format(std::string_view fmt) { - return term_ctl->fg(color).seq(); -} - - -std::string TermCtl::BgPlaceholder::seq(Color color) const -{ - return term_ctl->bg(color).seq(); -} - - -std::string TermCtl::ModePlaceholder::seq(Mode mode) const -{ - return term_ctl->mode(mode).seq(); + std::string r; + r.reserve(fmt.size()); + auto it = fmt.begin(); + while (it != fmt.end()) { + if (*it == '<') { + ++it; + + auto beg = it; + while (std::islower(*it)) + ++it; + std::string_view key (beg, it); + if (*it != ':') { + r.push_back('<'); + r += key; + continue; + } + ++it; + + beg = it; + while (std::islower(*it) || *it == '_' || *it == '*') + ++it; + std::string_view value (beg, it); + if (*it != '>') { + r.push_back('<'); + r += key; + r.push_back(':'); + r += value; + continue; + } + ++it; + + if (key == "t") { + const auto m = _parse_mode(value); + if (m > Mode::_Last) + goto rollback; + r += mode(m).seq(); + continue; + } else if (key == "fg" || key == "bg") { + const auto c = _parse_color(value); + if (c > Color::_Last) + goto rollback; + if (key == "fg") + r += fg(c).seq(); + else + r += bg(c).seq(); + continue; + } + rollback: + r.push_back('<'); + r += key; + r.push_back(':'); + r += value; + r.push_back('>'); + continue; + } + r.push_back(*it); + ++it; + } + return r; } diff --git a/src/xci/core/TermCtl.h b/src/xci/core/TermCtl.h index a6cf056e..a4964010 100644 --- a/src/xci/core/TermCtl.h +++ b/src/xci/core/TermCtl.h @@ -1,7 +1,7 @@ // TermCtl.h created on 2018-07-09 as part of xcikit project // https://github.com/rbrich/xcikit // -// Copyright 2018–2023 Radek Brich +// Copyright 2018–2024 Radek Brich // Licensed under the Apache License, Version 2.0 (see LICENSE file) #ifndef XCI_CORE_TERM_H @@ -70,7 +70,7 @@ class TermCtl { Invalid = 8, Default = 9, BrightBlack = 10, BrightRed, BrightGreen, BrightYellow, BrightBlue, BrightMagenta, BrightCyan, BrightWhite, - Last = BrightWhite + _Last = BrightWhite }; static constexpr const char* c_color_names[] = { "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", @@ -78,7 +78,7 @@ class TermCtl { "*black", "*red", "*green", "*yellow", "*blue", "*magenta", "*cyan", "*white" }; - static_assert(std::size(c_color_names) == size_t(Color::Last) + 1); + static_assert(std::size(c_color_names) == size_t(Color::_Last) + 1); enum class Mode: uint8_t { Normal, // reset all attributes @@ -86,7 +86,7 @@ class TermCtl { Blink, Reverse, Hidden, NormalIntensity, NoItalic, NoUnderline, NoOverline, NoCrossOut, NoFrame, NoBlink, NoReverse, NoHidden, - Last = NoHidden + _Last = NoHidden }; static constexpr const char* c_mode_names[] = { "normal", @@ -95,7 +95,7 @@ class TermCtl { "normal_intensity", "no_italic", "no_underline", "no_overline", "no_cross_out", "no_frame", "no_blink", "no_reverse", "no_hidden" }; - static_assert(std::size(c_mode_names) == size_t(Mode::Last) + 1); + static_assert(std::size(c_mode_names) == size_t(Mode::_Last) + 1); // foreground TermCtl& fg(Color color); @@ -199,70 +199,22 @@ class TermCtl { void write_nl() { m_seq.append(1, '\n'); write(seq()); } friend std::ostream& operator<<(std::ostream& os, TermCtl& t) { return os << t.seq(); } - // Formatting helpers - struct Placeholder { - TermCtl* term_ctl; - }; - struct ColorPlaceholder: Placeholder { - using ValueType = Color; - static constexpr Color parse(std::string_view name) { - Color r = Color::Black; - for (const char* n : c_color_names) { - if (name == n) - return r; - r = static_cast(uint8_t(r) + 1); - } - if (r > Color::Last) // this condition is needed for GCC 10 to allow throw in constexpr - throw fmt::format_error("invalid color name: " + std::string(name)); - XCI_UNREACHABLE; - } - }; - struct FgPlaceholder: ColorPlaceholder { - std::string seq(Color color) const; - }; - struct BgPlaceholder: ColorPlaceholder { - std::string seq(Color color) const; - }; - struct ModePlaceholder: Placeholder { - using ValueType = Mode; - static constexpr Mode parse(std::string_view name) { - Mode r = Mode::Normal; - for (const char* n : c_mode_names) { - if (name == n) - return r; - r = static_cast(uint8_t(r) + 1); - } - if (r > Mode::Last) - throw fmt::format_error("invalid mode name: " + std::string(name)); - XCI_UNREACHABLE; - } - std::string seq(Mode mode) const; - }; - /// Format string, adding colors via special placeholders: - /// {fg:COLOR} where COLOR is default | red | *red ... ("*" = bright) - /// {bg:COLOR} where COLOR is the same as for fg - /// {t:MODE} where MODE is bold | underline | normal ... - #define XCI_TERMCTL_FMT_DECL_ARGS decltype("fg"_a = FgPlaceholder{}), \ - decltype("bg"_a = BgPlaceholder{}), \ - decltype("t"_a = ModePlaceholder{}) - #define XCI_TERMCTL_FMT_ARGS "fg"_a = FgPlaceholder{this}, \ - "bg"_a = BgPlaceholder{this}, \ - "t"_a = ModePlaceholder{this} + /// where COLOR is default | red | *red ... ("*" = bright) + /// where COLOR is the same as for fg + /// where MODE is bold | underline | normal ... template - std::string format(fmt::format_string fmt, T&&... args) { - return _plain_format(fmt, args..., XCI_TERMCTL_FMT_ARGS); + std::string format(fmt::format_string fmt, T&&... args) { + return fmt::vformat(_format({fmt.get().begin(), fmt.get().end()}), + fmt::make_format_args(args...)); } /// Print string with special color/mode placeholders, see `format` above. template - void print(fmt::format_string fmt, T&&... args) { - write(_plain_format(fmt, args..., XCI_TERMCTL_FMT_ARGS)); + void print(fmt::format_string fmt, T&&... args) { + write(format(fmt::runtime(fmt), args...)); } - #undef XCI_TERMCTL_FMT_DECL_ARGS - #undef XCI_TERMCTL_FMT_ARGS - void write(std::string_view buf); void write_raw(std::string_view buf); // doesn't check newline @@ -421,14 +373,30 @@ class TermCtl { TermCtl& _tab_set_all(std::span n_cols); TermCtl& _append_seq(const char* seq) { if (seq) m_seq += seq; return *this; } // needed for TermInfo, which returns NULL for unknown seqs TermCtl& _append_seq(std::string_view seq) { m_seq += seq; return *this; } + std::string _format(std::string_view fmt); + + static constexpr Mode _parse_mode(std::string_view name) { + Mode r = Mode::Normal; + for (const char* n : c_mode_names) { + if (name == n) + return r; + r = static_cast(uint8_t(r) + 1); + } + return r; + } - // helper to avoid fmt error on named arg not being lvalue - template - std::string _plain_format(fmt::string_view fmt, T&&... args) { - return fmt::vformat(fmt, fmt::make_format_args(args...)); + static constexpr Color _parse_color(std::string_view name) { + Color r = Color::Black; + for (const char* n : c_color_names) { + if (name == n) + return r; + r = static_cast(uint8_t(r) + 1); + } + return r; } + std::string m_seq; // cached capability sequences - WriteCallback m_write_cb {}; + WriteCallback m_write_cb; int m_fd; // FD (on Windows mapped to handle) bool m_tty_ok : 1 = false; // tty initialized, will reset the term when destroyed bool m_at_newline : 1 = true; @@ -441,32 +409,6 @@ class TermCtl { } // namespace xci::core -template -concept TermCtlPlaceholder = - std::is_same_v || - std::is_same_v || - std::is_same_v; - -template -struct [[maybe_unused]] fmt::formatter { - typename T::ValueType value; - constexpr auto parse(format_parse_context& ctx) { - auto it = ctx.begin(); // NOLINT - while (it != ctx.end() && *it != '}') { - ++it; - } - value = T::parse({ctx.begin(), size_t(it - ctx.begin())}); - return it; - } - - template - auto format(const T& p, FormatContext& ctx) const { - auto msg = p.seq(value); - return std::copy(msg.begin(), msg.end(), ctx.out()); - } -}; - - // support `term.bold()` etc. directly in format args template <> struct [[maybe_unused]] fmt::formatter { diff --git a/src/xci/core/log_termctl.cpp b/src/xci/core/log_termctl.cpp index 3ea88b3d..55e3197e 100644 --- a/src/xci/core/log_termctl.cpp +++ b/src/xci/core/log_termctl.cpp @@ -19,18 +19,18 @@ namespace xci::core { // 5..9 => multi-line continuation for each log level static constexpr size_t c_cont = 5; static constexpr const char* c_log_format[] = { - "{0:%F %T} {fg:cyan}{1:6x}{t:normal} {t:bold}TRACE{t:normal} {fg:blue}{2}{t:normal}\n", - "{0:%F %T} {fg:cyan}{1:6x}{t:normal} {t:bold}DEBUG{t:normal} {fg:white}{2}{t:normal}\n", - "{0:%F %T} {fg:cyan}{1:6x}{t:normal} {t:bold}INFO {t:normal} {t:bold}{fg:white}{2}{t:normal}\n", - "{0:%F %T} {fg:cyan}{1:6x}{t:normal} {t:bold}WARN {t:normal} {t:bold}{fg:yellow}{2}{t:normal}\n", - "{0:%F %T} {fg:cyan}{1:6x}{t:normal} {t:bold}ERROR{t:normal} {t:bold}{fg:red}{2}{t:normal}\n", - " {t:bold}...{t:normal} {fg:blue}{2}{t:normal}\n", - " {t:bold}...{t:normal} {fg:white}{2}{t:normal}\n", - " {t:bold}...{t:normal} {t:bold}{fg:white}{2}{t:normal}\n", - " {t:bold}...{t:normal} {t:bold}{fg:yellow}{2}{t:normal}\n", - " {t:bold}...{t:normal} {t:bold}{fg:red}{2}{t:normal}\n", + "{0:%F %T} {1:6x} TRACE {2}\n", + "{0:%F %T} {1:6x} DEBUG {2}\n", + "{0:%F %T} {1:6x} INFO {2}\n", + "{0:%F %T} {1:6x} WARN {2}\n", + "{0:%F %T} {1:6x} ERROR {2}\n", + " ... {2}\n", + " ... {2}\n", + " ... {2}\n", + " ... {2}\n", + " ... {2}\n", }; -static constexpr const char* c_log_intro = "{t:underline} Date Time TID Level Message {t:normal}\n"; +static constexpr const char* c_log_intro = " Date Time TID Level Message \n"; Logger::Logger(Level level) : m_level(level) diff --git a/tests/test_core.cpp b/tests/test_core.cpp index 2a018a78..aa903155 100644 --- a/tests/test_core.cpp +++ b/tests/test_core.cpp @@ -297,7 +297,7 @@ TEST_CASE( "stripped_width", "[TermCtl]" ) CHECK(TermCtl::stripped_width("test") == 4); CHECK(TermCtl::stripped_width("❓") == 2); TermCtl t(1, TermCtl::IsTty::Always); - CHECK(TermCtl::stripped_width(t.format("{fg:green}test{t:normal}")) == 4); + CHECK(TermCtl::stripped_width(t.format("test")) == 4); CHECK(TermCtl::stripped_width("\x1b[32mtest\x1b(B\x1b[m") == 4); CHECK(TermCtl::stripped_width("\n") == 1); // newline is 1 column (special handling in EditLine) } diff --git a/tools/data_archive/dar.cpp b/tools/data_archive/dar.cpp index 70666181..105c9fae 100644 --- a/tools/data_archive/dar.cpp +++ b/tools/data_archive/dar.cpp @@ -28,7 +28,7 @@ static void extract_entry(const std::string& name, const VfsFile& file, const fs { const auto entry_path = output_path / name; TermCtl& term = TermCtl::stdout_instance(); - term.print("Extracting file\t{fg:yellow}{}{t:normal} to {}\n", name, entry_path); + term.print("Extracting file\t{} to {}\n", name, entry_path); auto content = file.content(); if (content) { fs::create_directories(entry_path.parent_path()); @@ -152,20 +152,20 @@ int main(int argc, const char* argv[]) } (argv); if (files.empty()) { - term.print("{t:bold}{fg:yellow}No input files.{t:normal}\n"); + term.print("No input files.\n"); } Vfs vfs; for (const auto filename : files) { - term.print("{t:bold}Extracting archive\t{fg:yellow}{}{t:normal}\n", filename); + term.print("Extracting archive\t{}\n", filename); if (!vfs.mount(filename)) { - term.print("{t:bold}{fg:red}Could not mount {}{t:normal}\n", filename); + term.print("Could not mount {}\n", filename); continue; } if (list_entries) { for (const auto& entry : *vfs.mounts().back().vfs_dir) { - term.print("{fg:yellow}{}{t:normal}\n", entry.name()); + term.print("{}\n", entry.name()); } continue; } diff --git a/tools/data_inspect/dati.cpp b/tools/data_inspect/dati.cpp index 43298836..1dce622f 100644 --- a/tools/data_inspect/dati.cpp +++ b/tools/data_inspect/dati.cpp @@ -1,7 +1,7 @@ // data_inspect.cpp created on 2020-08-15 as part of xcikit project // https://github.com/rbrich/xcikit // -// Copyright 2020–2023 Radek Brich +// Copyright 2020–2024 Radek Brich // Licensed under the Apache License, Version 2.0 (see LICENSE file) /// Data Inspector (dati) command line tool @@ -54,33 +54,33 @@ static void print_data(TermCtl& term, uint8_t type, const std::byte* data, size_ { const auto expected_size = BinaryBase::size_by_type(type); if (expected_size != size_t(-1) && size != expected_size) { - term.print("{fg:red}bad size {}{t:normal}", size); + term.print("bad size {}", size); return; } switch (type) { - case BinaryBase::Null: term.print("{fg:yellow}null{t:normal}"); return; - case BinaryBase::BoolFalse: term.print("{fg:yellow}false{t:normal}"); return; - case BinaryBase::BoolTrue: term.print("{fg:yellow}true{t:normal}"); return; - case BinaryBase::Fixed8: term.print("{fg:magenta}{}{t:normal}", unsigned(*data)); return; - case BinaryBase::Fixed16: term.print("{fg:magenta}{}{t:normal}", bit_copy(data)); return; - case BinaryBase::Fixed32: term.print("{fg:magenta}{}{t:normal}", bit_copy(data)); return; - case BinaryBase::Fixed64: term.print("{fg:magenta}{}{t:normal}", bit_copy(data)); return; - case BinaryBase::Fixed128: term.print("{fg:magenta}{}{t:normal}", uint128_to_string(bit_copy(data))); return; - case BinaryBase::Float32: term.print("{fg:magenta}{}{t:normal}", bit_copy(data)); return; - case BinaryBase::Float64: term.print("{fg:magenta}{}{t:normal}", bit_copy(data)); return; - case BinaryBase::VarInt: term.print("{fg:yellow}varint{t:normal}"); return; - case BinaryBase::Array: term.print("{fg:yellow}array{t:normal}"); return; + case BinaryBase::Null: term.print("null"); return; + case BinaryBase::BoolFalse: term.print("false"); return; + case BinaryBase::BoolTrue: term.print("true"); return; + case BinaryBase::Fixed8: term.print("{}", unsigned(*data)); return; + case BinaryBase::Fixed16: term.print("{}", bit_copy(data)); return; + case BinaryBase::Fixed32: term.print("{}", bit_copy(data)); return; + case BinaryBase::Fixed64: term.print("{}", bit_copy(data)); return; + case BinaryBase::Fixed128: term.print("{}", uint128_to_string(bit_copy(data))); return; + case BinaryBase::Float32: term.print("{}", bit_copy(data)); return; + case BinaryBase::Float64: term.print("{}", bit_copy(data)); return; + case BinaryBase::VarInt: term.print("varint"); return; + case BinaryBase::Array: term.print("array"); return; case BinaryBase::String: - term.print("{fg:green}\"{}\"{t:normal}", + term.print("\"{}\"", escape(std::string_view((const char*) data, size))); return; - case BinaryBase::Binary: term.print("{fg:yellow}(size {}){t:normal}", size); return; + case BinaryBase::Binary: term.print("(size {})", size); return; case BinaryBase::Master: - term.print("{fg:yellow}(size {}){t:normal} {t:bold}{{{t:normal}", size); return; - case BinaryBase::Control: term.print("{fg:yellow}control{t:normal}"); return; + term.print("(size {}) {{", size); return; + case BinaryBase::Control: term.print("control"); return; } - term.print("{fg:red}unknown{t:normal}"); + term.print("unknown"); } @@ -110,7 +110,7 @@ int main(int argc, const char* argv[]) } (argv); if (files.empty()) { - term.print("{t:bold}{fg:yellow}No input files.{t:normal}\n"); + term.print("No input files.\n"); } Schema schema; @@ -121,7 +121,7 @@ int main(int argc, const char* argv[]) reader(schema); reader.finish_and_check(); } catch (const ArchiveError& e) { - term.print("{t:bold}{fg:red}Error reading schema: {}{t:normal}\n", e.what()); + term.print("Error reading schema: {}\n", e.what()); } } else if (files.size() == 1 && std::string(files.back()).ends_with(".schema")) { // get Schema of .schema file @@ -129,7 +129,7 @@ int main(int argc, const char* argv[]) } for (const auto& filename : files) { - term.print("{fg:yellow}{t:bold}{}{t:normal}\n", filename); + term.print("{}\n", filename); std::ifstream f(filename, std::ios::binary); try { BinaryReader reader(f); @@ -182,12 +182,12 @@ int main(int argc, const char* argv[]) if (opt_int) last_int_values[schema_member->name] = *opt_int; } - term.print("{}{t:bold}{fg:cyan}{} ({}: {}){t:normal}: {} = ", + term.print("{}{} ({}: {}): {} = ", std::string(indent, ' '), int(it.key), schema_member->name, schema_member->type, type_to_cstr(it.type)); } else { - term.print("{}{t:bold}{fg:cyan}{}{t:normal}: {} = ", + term.print("{}{}: {} = ", std::string(indent, ' '), int(it.key), type_to_cstr(it.type)); } @@ -198,9 +198,9 @@ int main(int argc, const char* argv[]) uint32_t stored_crc = 0; std::memcpy(&stored_crc, it.data.get(), it.size); if (reader.crc() == stored_crc) - term.print(" {t:bold}{fg:green}(CRC32: OK){t:normal}"); + term.print(" (CRC32: OK)"); else - term.print(" {t:bold}{fg:red}(CRC32: expected {}){t:normal}", + term.print(" (CRC32: expected {})", reader.crc()); } } @@ -214,13 +214,13 @@ int main(int argc, const char* argv[]) case What::LeaveGroup: struct_stack.pop_back(); last_int_values.clear(); - term.print("{t:bold}{}}}{t:normal}\n", std::string(indent - 4, ' ')); + term.print("{}}}\n", std::string(indent - 4, ' ')); break; case What::EnterMetadata: - term.print("{t:bold}{}Metadata:{t:normal}\n", std::string(indent, ' ')); + term.print("{}Metadata:\n", std::string(indent, ' ')); break; case What::LeaveMetadata: - term.print("{t:bold}{}Data:{t:normal}\n", std::string(indent, ' ')); + term.print("{}Data:\n", std::string(indent, ' ')); break; case What::EndOfFile: eof = true; @@ -228,7 +228,7 @@ int main(int argc, const char* argv[]) } } } catch (const ArchiveError& e) { - term.print("{t:bold}{fg:red}{}{t:normal}\n", e.what()); + term.print("{}\n", e.what()); } } diff --git a/tools/find_file/ff.cpp b/tools/find_file/ff.cpp index 0a986395..8ec7d93b 100644 --- a/tools/find_file/ff.cpp +++ b/tools/find_file/ff.cpp @@ -979,8 +979,8 @@ int main(int argc, const char* argv[]) } (argv); if (show_version) { - term.print("{t:bold}ff{t:normal} {}\n", c_version); - term.print("using {t:bold}Hyperscan{t:normal} {}\n", hs_version()); + term.print("ff {}\n", c_version); + term.print("using Hyperscan {}\n", hs_version()); return 0; } diff --git a/tools/fire_script/Highlighter.cpp b/tools/fire_script/Highlighter.cpp index 7520fc30..2b1b9478 100644 --- a/tools/fire_script/Highlighter.cpp +++ b/tools/fire_script/Highlighter.cpp @@ -1,7 +1,7 @@ // Highlighter.cpp.cc created on 2021-03-03 as part of xcikit project // https://github.com/rbrich/xcikit // -// Copyright 2021–2023 Radek Brich +// Copyright 2021–2024 Radek Brich // Licensed under the Apache License, Version 2.0 (see LICENSE file) #include "Highlighter.h" @@ -459,13 +459,13 @@ auto Highlighter::highlight(std::string_view input, unsigned cursor) -> HlResult try { auto root = tao::pegtl::parse_tree::parse< Main, Node, HighlightSelector, tao::pegtl::nothing, Control >( in ); if (root->children.size() != 1) - return {std::string{input} + m_term.format("\n{fg:*red}{t:bold}highlighter parse error:{t:normal} {fg:*red}no match{t:normal}"), false}; + return {std::string{input} + m_term.format("\nhighlighter parse error: no match"), false}; auto last_color = highlight_node(*root->children[0], HighlightColor{}, cursor); switch_color(last_color, HighlightColor{}); return {m_output, m_open_bracket}; } catch (tao::pegtl::parse_error& e) { // The grammar is build in a way that parse error should never happen - return {std::string{input} + m_term.format("\n{fg:*red}{t:bold}highlighter parse error:{t:normal} {fg:*red}{}{t:normal}", e.what()), false}; + return {std::string{input} + m_term.format("\nhighlighter parse error: {}", e.what()), false}; } } diff --git a/tools/fire_script/Options.cpp b/tools/fire_script/Options.cpp index f0a3fcf8..d50812a9 100644 --- a/tools/fire_script/Options.cpp +++ b/tools/fire_script/Options.cpp @@ -58,11 +58,11 @@ static Flags pass_name_to_flag(std::string_view name) }) | to< std::vector >(); auto& t = TermCtl::stderr_instance(); if (filtered.empty()) { - t.print("{t:bold}Note:{t:normal} {} did not match any pass name\n", name); + t.print("Note: {} did not match any pass name\n", name); return Flags(~0); } if (filtered.size() > 1) { - t.print("{t:bold}Note:{t:normal} {} matched multiple pass names: {}\n", + t.print("Note: {} matched multiple pass names: {}\n", name, fmt::join(filtered | keys, " ")); return Flags(~0); } diff --git a/tools/fire_script/Program.cpp b/tools/fire_script/Program.cpp index 942fda65..8aa559c5 100644 --- a/tools/fire_script/Program.cpp +++ b/tools/fire_script/Program.cpp @@ -1,7 +1,7 @@ // Program.cpp.cc created on 2021-03-20 as part of xcikit project // https://github.com/rbrich/xcikit // -// Copyright 2021–2023 Radek Brich +// Copyright 2021–2024 Radek Brich // Licensed under the Apache License, Version 2.0 (see LICENSE file) #include "Program.h" @@ -18,9 +18,9 @@ namespace xci::script::tool { using namespace xci::core; static constexpr const char* intro = - "{t:bold}{fg:magenta}🔥 fire script{t:normal} {fg:magenta}v{}{t:normal}\n" - "Type {t:bold}{fg:yellow}.h{t:normal} for help, {t:bold}{fg:yellow}.q{t:normal} to quit.\n"; -static constexpr const char* prompt = "{fg:green}_{}>{t:normal} "; + "🔥 fire script v{}\n" + "Type .h for help, .q to quit.\n"; +static constexpr const char* prompt = "_{}> "; Program::Program(bool log_debug) @@ -171,10 +171,10 @@ void Program::evaluate_input(std::string_view input) repl_command().eval(input.substr(1)); } catch (const ScriptError& e) { auto& tout = ctx.term_out; - tout.print("{fg:red}{}: {t:bold}{}{t:normal}\n", e.code(), e.what()); + tout.print("{}: {}\n", e.code(), e.what()); if (!e.detail().empty()) - tout.print("{fg:magenta}{}{t:normal}\n", e.detail()); - tout.print("{fg:yellow}Help: .h | .help{t:normal}\n"); + tout.print("{}\n", e.detail()); + tout.print("Help: .h | .help\n"); } return; } diff --git a/tools/fire_script/Repl.cpp b/tools/fire_script/Repl.cpp index d50e1776..307c8a22 100644 --- a/tools/fire_script/Repl.cpp +++ b/tools/fire_script/Repl.cpp @@ -150,7 +150,7 @@ bool Repl::evaluate_module(Module& module, EvalMode mode) machine.call(main_fn, [&](TypedValue&& invoked) { if (!invoked.is_void()) { t.sanitize_newline(); - t.print("{t:bold}{fg:yellow}{}{t:normal}\n", invoked); + t.print("{}\n", invoked); } invoked.decref(); }); @@ -163,14 +163,14 @@ bool Repl::evaluate_module(Module& module, EvalMode mode) // REPL mode const auto& module_name = module.name(); if (!result.is_void()) { - t.print("{t:bold}{fg:magenta}{}:{} = {fg:default}{}{t:normal}\n", + t.print("{}:{} = {}\n", module_name, result.type_info(), result); } } else { // single input mode assert(mode == EvalMode::SingleInput); if (!result.is_void()) { - t.print("{t:bold}{}{t:normal}\n", result); + t.print("{}\n", result); } } result.decref(); @@ -191,9 +191,9 @@ void Repl::print_error(const ScriptError& e) if (!e.file().empty()) t.print("{}: ", e.file()); - t.print("{fg:red}{t:bold}{}: {fg:*white}{t:normal_intensity}{}{t:normal}", e.code(), e.what()); + t.print("{}: {}", e.code(), e.what()); if (!e.detail().empty()) - t.print("\n{fg:magenta}{}{t:normal}", e.detail()); + t.print("\n{}", e.detail()); t.write_nl(); } diff --git a/tools/fire_script/ReplCommand.cpp b/tools/fire_script/ReplCommand.cpp index d03be4ef..0a1bee5c 100644 --- a/tools/fire_script/ReplCommand.cpp +++ b/tools/fire_script/ReplCommand.cpp @@ -1,7 +1,7 @@ // ReplCommand.cpp created on 2020-01-11 as part of xcikit project // https://github.com/rbrich/xcikit // -// Copyright 2020–2023 Radek Brich +// Copyright 2020–2024 Radek Brich // Licensed under the Apache License, Version 2.0 (see LICENSE file) #include "ReplCommand.h" @@ -76,7 +76,7 @@ const Module* ReplCommand::module_by_idx(Index mod_idx) { return m_module.get(); if (mod_idx >= m_ctx.input_modules.size()) { - t.print("{t:bold}{fg:red}Error: module index out of range: {}{t:normal}\n", + t.print("Error: module index out of range: {}\n", mod_idx); return nullptr; } @@ -100,7 +100,7 @@ const Module* ReplCommand::module_by_name(std::string_view mod_name) { } TermCtl& t = m_ctx.term_out; - t.print("{t:bold}{fg:red}Error: module not found: {}{t:normal}\n", + t.print("Error: module not found: {}\n", mod_name); return nullptr; } @@ -138,7 +138,7 @@ void ReplCommand::dump_function(const Module& mod, Index fun_idx) { TermCtl& t = m_ctx.term_out; if (fun_idx >= mod.num_functions()) { - t.print("{t:bold}{fg:red}Error: function index out of range: {}{t:normal}\n", + t.print("Error: function index out of range: {}\n", fun_idx); return; } @@ -153,14 +153,14 @@ void ReplCommand::dump_function(const Module& mod, Index fun_idx) { void ReplCommand::cmd_dump_function() { TermCtl& t = m_ctx.term_out; if (m_ctx.input_modules.empty()) { - t.print("{t:bold}{fg:red}Error: no input modules available{t:normal}\n"); + t.print("Error: no input modules available\n"); return; } size_t mod_idx = m_ctx.input_modules.size() - 1; const auto& mod = *m_ctx.input_modules[mod_idx]; if (mod.num_functions() == 0) { - t.print("{t:bold}{fg:red}Error: no functions available{t:normal}\n"); + t.print("Error: no functions available\n"); return; } @@ -171,7 +171,7 @@ void ReplCommand::cmd_dump_function() { void ReplCommand::cmd_dump_function(std::string_view fun_name) { TermCtl& t = m_ctx.term_out; if (m_ctx.input_modules.empty()) { - t.print("{t:bold}{fg:red}Error: no input modules available{t:normal}\n"); + t.print("Error: no input modules available\n"); return; } size_t mod_idx = m_ctx.input_modules.size() - 1; @@ -184,7 +184,7 @@ void ReplCommand::cmd_dump_function(std::string_view fun_name) { return; } } - t.print("{t:bold}{fg:red}Error: function not found: {}{t:normal}\n", + t.print("Error: function not found: {}\n", fun_name); } @@ -205,7 +205,7 @@ void ReplCommand::cmd_dump_function(std::string_view fun_name, std::string_view return; } } - t.print("{t:bold}{fg:red}Error: function not found: {}{t:normal}\n", + t.print("Error: function not found: {}\n", fun_name); } @@ -214,7 +214,7 @@ void ReplCommand::cmd_dump_function(Index fun_idx) { TermCtl& t = m_ctx.term_out; if (m_ctx.input_modules.empty()) { - t.print("{t:bold}{fg:red}Error: no modules available{t:normal}\n"); + t.print("Error: no modules available\n"); return; } size_t mod_idx = m_ctx.input_modules.size() - 1; @@ -274,7 +274,7 @@ void ReplCommand::cmd_describe(std::string_view name) { return; } } - t.print("{t:bold}{fg:red}Error: symbol not found: {}{t:normal}\n", + t.print("Error: symbol not found: {}\n", name); } diff --git a/tools/shader_editor/shed.cpp b/tools/shader_editor/shed.cpp index 5e16275f..79b648cf 100644 --- a/tools/shader_editor/shed.cpp +++ b/tools/shader_editor/shed.cpp @@ -1,7 +1,7 @@ // shed.cpp created on 2023-02-21 as part of xcikit project // https://github.com/rbrich/xcikit // -// Copyright 2023 Radek Brich +// Copyright 2023–2024 Radek Brich // Licensed under the Apache License, Version 2.0 (see LICENSE file) /// Shader Editor (shed) tool @@ -81,7 +81,7 @@ int main(int argc, const char* argv[]) Logger::init(log_debug ? Logger::Level::Trace : Logger::Level::Warning); if (show_version) { - term.print("{t:bold}shed{t:normal} {fg:*white}{}{t:normal}\n", c_version); + term.print("shed {}\n", c_version); return 0; } diff --git a/tools/term_ctl/tc.cpp b/tools/term_ctl/tc.cpp index db2677f0..89af466d 100644 --- a/tools/term_ctl/tc.cpp +++ b/tools/term_ctl/tc.cpp @@ -1,7 +1,7 @@ // tc.cpp created on 2022-10-09 as part of xcikit project // https://github.com/rbrich/xcikit // -// Copyright 2022 Radek Brich +// Copyright 2022–2024 Radek Brich // Licensed under the Apache License, Version 2.0 (see LICENSE file) /// Term Ctl (tc) command line tool @@ -33,7 +33,7 @@ int main(int argc, const char* argv[]) TermCtl term(STDOUT_FILENO, isatty_always ? TermCtl::IsTty::Always : TermCtl::IsTty::Auto); if (show_version) { - term.print("{t:bold}tc{t:normal} {}\n", c_version); + term.print("tc {}\n", c_version); return 0; } From bf1ecba781662a66359d8956d3fca38f06080d28 Mon Sep 17 00:00:00 2001 From: Radek Brich Date: Fri, 12 Jul 2024 21:51:59 +0200 Subject: [PATCH 2/7] [core] TermCtl: Drop the "t:" from mode placeholders, allow single char shortcuts like `` --- examples/core/demo_termctl.cpp | 22 ++++++------ src/xci/core/ArgParser.cpp | 14 ++++---- src/xci/core/TermCtl.cpp | 35 ++++++++++--------- src/xci/core/TermCtl.h | 7 ++-- src/xci/core/log_termctl.cpp | 22 ++++++------ tests/test_core.cpp | 2 +- tools/data_archive/dar.cpp | 10 +++--- tools/data_inspect/dati.cpp | 58 +++++++++++++++---------------- tools/find_file/ff.cpp | 4 +-- tools/fire_script/Highlighter.cpp | 4 +-- tools/fire_script/Options.cpp | 4 +-- tools/fire_script/Program.cpp | 12 +++---- tools/fire_script/Repl.cpp | 10 +++--- tools/fire_script/ReplCommand.cpp | 20 +++++------ tools/shader_editor/shed.cpp | 2 +- tools/term_ctl/tc.cpp | 2 +- 16 files changed, 115 insertions(+), 113 deletions(-) diff --git a/examples/core/demo_termctl.cpp b/examples/core/demo_termctl.cpp index 6792d298..15e78485 100644 --- a/examples/core/demo_termctl.cpp +++ b/examples/core/demo_termctl.cpp @@ -27,17 +27,17 @@ int main() cout << t.move_up().move_right(6).bold().green() << "GREEN" <formatted\n"); - t.print("bold " - "dim " - "italic " - "underlined " - "overlined " - "crossed out " - "framed " - "blinking " - "reversed " - "hidden " + t.print("formatted\n"); + t.print("bold " + "dim " + "italic " + "underlined " + "overlined " + "crossed out " + "framed " + "blinking " + "reversed " + "hidden " "\n"); t.tab_set_all({30, 20}).write(); diff --git a/src/xci/core/ArgParser.cpp b/src/xci/core/ArgParser.cpp index fea7a6e3..20af7438 100644 --- a/src/xci/core/ArgParser.cpp +++ b/src/xci/core/ArgParser.cpp @@ -136,12 +136,12 @@ std::string Option::usage() const if (!p) break; if (is_remainder() && p.dashes == 2 && p.len == 0) { - res += t.format("[{}] ", std::string(dp + p.pos, p.dashes)); + res += t.format("[{}] ", std::string(dp + p.pos, p.dashes)); } else if (first) { first = false; - res += t.format("{}", std::string(dp + p.pos, p.dashes + p.len)); + res += t.format("{}", std::string(dp + p.pos, p.dashes + p.len)); } else if (!p.dashes) { - res += t.format(" {}", std::string(dp + p.pos, p.len)); + res += t.format(" {}", std::string(dp + p.pos, p.len)); } dp += p.end(); } @@ -312,7 +312,7 @@ ArgParser& ArgParser::operator()(const char* argv[], bool detect_width, unsigned if (!parse_program_name(argv[0])) { // this should not occur auto& t = TermCtl::stderr_instance(); - t.print("Missing program name (argv[0])\n"); + t.print("Missing program name (argv[0])\n"); exit(1); } try { @@ -326,7 +326,7 @@ ArgParser& ArgParser::operator()(const char* argv[], bool detect_width, unsigned } } catch (const BadArgument& e) { auto& t = TermCtl::stderr_instance(); - t.print("Error: {}\n\n", e.what()); + t.print("Error: {}\n\n", e.what()); print_usage(); print_help_notice(); exit(1); @@ -525,7 +525,7 @@ void ArgParser::print_usage() const unsigned indent = 0; { - auto head = t.format("Usage: {} ", m_progname); + auto head = t.format("Usage: {} ", m_progname); indent = TermCtl::stripped_width(head); cout << head; } @@ -545,7 +545,7 @@ void ArgParser::print_help() const desc_cols = std::max(desc_cols, (unsigned) opt.desc().size()); print_usage(); auto& t = TermCtl::stdout_instance(); - t.print("\nOptions:\n"); + t.print("\nOptions:\n"); for (const auto& opt : m_opts) { cout << " " << opt.formatted_desc(desc_cols) << " "; wrapping_print(opt.help(), desc_cols + 4, 0, m_max_width); diff --git a/src/xci/core/TermCtl.cpp b/src/xci/core/TermCtl.cpp index 1743c2b1..35d0f45a 100644 --- a/src/xci/core/TermCtl.cpp +++ b/src/xci/core/TermCtl.cpp @@ -618,9 +618,17 @@ std::string TermCtl::_format(std::string_view fmt) ++it; auto beg = it; - while (std::islower(*it)) + while (std::islower(*it) || *it == '_') ++it; std::string_view key (beg, it); + if (*it == '>') { + const auto m = _parse_mode(key); + if (m <= Mode::_Last) { + r += mode(m).seq(); + ++it; + continue; + } + } if (*it != ':') { r.push_back('<'); r += key; @@ -641,29 +649,22 @@ std::string TermCtl::_format(std::string_view fmt) } ++it; - if (key == "t") { - const auto m = _parse_mode(value); - if (m > Mode::_Last) - goto rollback; - r += mode(m).seq(); - continue; - } else if (key == "fg" || key == "bg") { + if (key == "fg" || key == "bg") { const auto c = _parse_color(value); - if (c > Color::_Last) - goto rollback; + if (c > Color::_Last) { + r.push_back('<'); + r += key; + r.push_back(':'); + r += value; + r.push_back('>'); + continue; + } if (key == "fg") r += fg(c).seq(); else r += bg(c).seq(); continue; } - rollback: - r.push_back('<'); - r += key; - r.push_back(':'); - r += value; - r.push_back('>'); - continue; } r.push_back(*it); ++it; diff --git a/src/xci/core/TermCtl.h b/src/xci/core/TermCtl.h index a4964010..0e6185c1 100644 --- a/src/xci/core/TermCtl.h +++ b/src/xci/core/TermCtl.h @@ -202,10 +202,11 @@ class TermCtl { /// Format string, adding colors via special placeholders: /// where COLOR is default | red | *red ... ("*" = bright) /// where COLOR is the same as for fg - /// where MODE is bold | underline | normal ... + /// where MODE is bold | underline | normal ... (shortcuts b | u | n ...) template std::string format(fmt::format_string fmt, T&&... args) { - return fmt::vformat(_format({fmt.get().begin(), fmt.get().end()}), + const auto sv = fmt.get(); + return fmt::vformat(_format(std::string_view(sv.data(), sv.size())), fmt::make_format_args(args...)); } @@ -378,7 +379,7 @@ class TermCtl { static constexpr Mode _parse_mode(std::string_view name) { Mode r = Mode::Normal; for (const char* n : c_mode_names) { - if (name == n) + if (name == n || (name.size() == 1 && name[0] == n[0])) return r; r = static_cast(uint8_t(r) + 1); } diff --git a/src/xci/core/log_termctl.cpp b/src/xci/core/log_termctl.cpp index 55e3197e..153c457b 100644 --- a/src/xci/core/log_termctl.cpp +++ b/src/xci/core/log_termctl.cpp @@ -19,18 +19,18 @@ namespace xci::core { // 5..9 => multi-line continuation for each log level static constexpr size_t c_cont = 5; static constexpr const char* c_log_format[] = { - "{0:%F %T} {1:6x} TRACE {2}\n", - "{0:%F %T} {1:6x} DEBUG {2}\n", - "{0:%F %T} {1:6x} INFO {2}\n", - "{0:%F %T} {1:6x} WARN {2}\n", - "{0:%F %T} {1:6x} ERROR {2}\n", - " ... {2}\n", - " ... {2}\n", - " ... {2}\n", - " ... {2}\n", - " ... {2}\n", + "{0:%F %T} {1:6x} TRACE {2}\n", + "{0:%F %T} {1:6x} DEBUG {2}\n", + "{0:%F %T} {1:6x} INFO {2}\n", + "{0:%F %T} {1:6x} WARN {2}\n", + "{0:%F %T} {1:6x} ERROR {2}\n", + " ... {2}\n", + " ... {2}\n", + " ... {2}\n", + " ... {2}\n", + " ... {2}\n", }; -static constexpr const char* c_log_intro = " Date Time TID Level Message \n"; +static constexpr const char* c_log_intro = " Date Time TID Level Message \n"; Logger::Logger(Level level) : m_level(level) diff --git a/tests/test_core.cpp b/tests/test_core.cpp index aa903155..3f188fb4 100644 --- a/tests/test_core.cpp +++ b/tests/test_core.cpp @@ -297,7 +297,7 @@ TEST_CASE( "stripped_width", "[TermCtl]" ) CHECK(TermCtl::stripped_width("test") == 4); CHECK(TermCtl::stripped_width("❓") == 2); TermCtl t(1, TermCtl::IsTty::Always); - CHECK(TermCtl::stripped_width(t.format("test")) == 4); + CHECK(TermCtl::stripped_width(t.format("test")) == 4); CHECK(TermCtl::stripped_width("\x1b[32mtest\x1b(B\x1b[m") == 4); CHECK(TermCtl::stripped_width("\n") == 1); // newline is 1 column (special handling in EditLine) } diff --git a/tools/data_archive/dar.cpp b/tools/data_archive/dar.cpp index 105c9fae..4db6842b 100644 --- a/tools/data_archive/dar.cpp +++ b/tools/data_archive/dar.cpp @@ -28,7 +28,7 @@ static void extract_entry(const std::string& name, const VfsFile& file, const fs { const auto entry_path = output_path / name; TermCtl& term = TermCtl::stdout_instance(); - term.print("Extracting file\t{} to {}\n", name, entry_path); + term.print("Extracting file\t{} to {}\n", name, entry_path); auto content = file.content(); if (content) { fs::create_directories(entry_path.parent_path()); @@ -152,20 +152,20 @@ int main(int argc, const char* argv[]) } (argv); if (files.empty()) { - term.print("No input files.\n"); + term.print("No input files.\n"); } Vfs vfs; for (const auto filename : files) { - term.print("Extracting archive\t{}\n", filename); + term.print("Extracting archive\t{}\n", filename); if (!vfs.mount(filename)) { - term.print("Could not mount {}\n", filename); + term.print("Could not mount {}\n", filename); continue; } if (list_entries) { for (const auto& entry : *vfs.mounts().back().vfs_dir) { - term.print("{}\n", entry.name()); + term.print("{}\n", entry.name()); } continue; } diff --git a/tools/data_inspect/dati.cpp b/tools/data_inspect/dati.cpp index 1dce622f..ee64dcd9 100644 --- a/tools/data_inspect/dati.cpp +++ b/tools/data_inspect/dati.cpp @@ -54,33 +54,33 @@ static void print_data(TermCtl& term, uint8_t type, const std::byte* data, size_ { const auto expected_size = BinaryBase::size_by_type(type); if (expected_size != size_t(-1) && size != expected_size) { - term.print("bad size {}", size); + term.print("bad size {}", size); return; } switch (type) { - case BinaryBase::Null: term.print("null"); return; - case BinaryBase::BoolFalse: term.print("false"); return; - case BinaryBase::BoolTrue: term.print("true"); return; - case BinaryBase::Fixed8: term.print("{}", unsigned(*data)); return; - case BinaryBase::Fixed16: term.print("{}", bit_copy(data)); return; - case BinaryBase::Fixed32: term.print("{}", bit_copy(data)); return; - case BinaryBase::Fixed64: term.print("{}", bit_copy(data)); return; - case BinaryBase::Fixed128: term.print("{}", uint128_to_string(bit_copy(data))); return; - case BinaryBase::Float32: term.print("{}", bit_copy(data)); return; - case BinaryBase::Float64: term.print("{}", bit_copy(data)); return; - case BinaryBase::VarInt: term.print("varint"); return; - case BinaryBase::Array: term.print("array"); return; + case BinaryBase::Null: term.print("null"); return; + case BinaryBase::BoolFalse: term.print("false"); return; + case BinaryBase::BoolTrue: term.print("true"); return; + case BinaryBase::Fixed8: term.print("{}", unsigned(*data)); return; + case BinaryBase::Fixed16: term.print("{}", bit_copy(data)); return; + case BinaryBase::Fixed32: term.print("{}", bit_copy(data)); return; + case BinaryBase::Fixed64: term.print("{}", bit_copy(data)); return; + case BinaryBase::Fixed128: term.print("{}", uint128_to_string(bit_copy(data))); return; + case BinaryBase::Float32: term.print("{}", bit_copy(data)); return; + case BinaryBase::Float64: term.print("{}", bit_copy(data)); return; + case BinaryBase::VarInt: term.print("varint"); return; + case BinaryBase::Array: term.print("array"); return; case BinaryBase::String: - term.print("\"{}\"", + term.print("\"{}\"", escape(std::string_view((const char*) data, size))); return; - case BinaryBase::Binary: term.print("(size {})", size); return; + case BinaryBase::Binary: term.print("(size {})", size); return; case BinaryBase::Master: - term.print("(size {}) {{", size); return; - case BinaryBase::Control: term.print("control"); return; + term.print("(size {}) {{", size); return; + case BinaryBase::Control: term.print("control"); return; } - term.print("unknown"); + term.print("unknown"); } @@ -110,7 +110,7 @@ int main(int argc, const char* argv[]) } (argv); if (files.empty()) { - term.print("No input files.\n"); + term.print("No input files.\n"); } Schema schema; @@ -121,7 +121,7 @@ int main(int argc, const char* argv[]) reader(schema); reader.finish_and_check(); } catch (const ArchiveError& e) { - term.print("Error reading schema: {}\n", e.what()); + term.print("Error reading schema: {}\n", e.what()); } } else if (files.size() == 1 && std::string(files.back()).ends_with(".schema")) { // get Schema of .schema file @@ -129,7 +129,7 @@ int main(int argc, const char* argv[]) } for (const auto& filename : files) { - term.print("{}\n", filename); + term.print("{}\n", filename); std::ifstream f(filename, std::ios::binary); try { BinaryReader reader(f); @@ -182,12 +182,12 @@ int main(int argc, const char* argv[]) if (opt_int) last_int_values[schema_member->name] = *opt_int; } - term.print("{}{} ({}: {}): {} = ", + term.print("{}{} ({}: {}): {} = ", std::string(indent, ' '), int(it.key), schema_member->name, schema_member->type, type_to_cstr(it.type)); } else { - term.print("{}{}: {} = ", + term.print("{}{}: {} = ", std::string(indent, ' '), int(it.key), type_to_cstr(it.type)); } @@ -198,9 +198,9 @@ int main(int argc, const char* argv[]) uint32_t stored_crc = 0; std::memcpy(&stored_crc, it.data.get(), it.size); if (reader.crc() == stored_crc) - term.print(" (CRC32: OK)"); + term.print(" (CRC32: OK)"); else - term.print(" (CRC32: expected {})", + term.print(" (CRC32: expected {})", reader.crc()); } } @@ -214,13 +214,13 @@ int main(int argc, const char* argv[]) case What::LeaveGroup: struct_stack.pop_back(); last_int_values.clear(); - term.print("{}}}\n", std::string(indent - 4, ' ')); + term.print("{}}}\n", std::string(indent - 4, ' ')); break; case What::EnterMetadata: - term.print("{}Metadata:\n", std::string(indent, ' ')); + term.print("{}Metadata:\n", std::string(indent, ' ')); break; case What::LeaveMetadata: - term.print("{}Data:\n", std::string(indent, ' ')); + term.print("{}Data:\n", std::string(indent, ' ')); break; case What::EndOfFile: eof = true; @@ -228,7 +228,7 @@ int main(int argc, const char* argv[]) } } } catch (const ArchiveError& e) { - term.print("{}\n", e.what()); + term.print("{}\n", e.what()); } } diff --git a/tools/find_file/ff.cpp b/tools/find_file/ff.cpp index 8ec7d93b..5a7fe2bc 100644 --- a/tools/find_file/ff.cpp +++ b/tools/find_file/ff.cpp @@ -979,8 +979,8 @@ int main(int argc, const char* argv[]) } (argv); if (show_version) { - term.print("ff {}\n", c_version); - term.print("using Hyperscan {}\n", hs_version()); + term.print("ff {}\n", c_version); + term.print("using Hyperscan {}\n", hs_version()); return 0; } diff --git a/tools/fire_script/Highlighter.cpp b/tools/fire_script/Highlighter.cpp index 2b1b9478..a234c8a5 100644 --- a/tools/fire_script/Highlighter.cpp +++ b/tools/fire_script/Highlighter.cpp @@ -459,13 +459,13 @@ auto Highlighter::highlight(std::string_view input, unsigned cursor) -> HlResult try { auto root = tao::pegtl::parse_tree::parse< Main, Node, HighlightSelector, tao::pegtl::nothing, Control >( in ); if (root->children.size() != 1) - return {std::string{input} + m_term.format("\nhighlighter parse error: no match"), false}; + return {std::string{input} + m_term.format("\nhighlighter parse error: no match"), false}; auto last_color = highlight_node(*root->children[0], HighlightColor{}, cursor); switch_color(last_color, HighlightColor{}); return {m_output, m_open_bracket}; } catch (tao::pegtl::parse_error& e) { // The grammar is build in a way that parse error should never happen - return {std::string{input} + m_term.format("\nhighlighter parse error: {}", e.what()), false}; + return {std::string{input} + m_term.format("\nhighlighter parse error: {}", e.what()), false}; } } diff --git a/tools/fire_script/Options.cpp b/tools/fire_script/Options.cpp index d50812a9..4f21214a 100644 --- a/tools/fire_script/Options.cpp +++ b/tools/fire_script/Options.cpp @@ -58,11 +58,11 @@ static Flags pass_name_to_flag(std::string_view name) }) | to< std::vector >(); auto& t = TermCtl::stderr_instance(); if (filtered.empty()) { - t.print("Note: {} did not match any pass name\n", name); + t.print("Note: {} did not match any pass name\n", name); return Flags(~0); } if (filtered.size() > 1) { - t.print("Note: {} matched multiple pass names: {}\n", + t.print("Note: {} matched multiple pass names: {}\n", name, fmt::join(filtered | keys, " ")); return Flags(~0); } diff --git a/tools/fire_script/Program.cpp b/tools/fire_script/Program.cpp index 8aa559c5..c1743823 100644 --- a/tools/fire_script/Program.cpp +++ b/tools/fire_script/Program.cpp @@ -18,9 +18,9 @@ namespace xci::script::tool { using namespace xci::core; static constexpr const char* intro = - "🔥 fire script v{}\n" - "Type .h for help, .q to quit.\n"; -static constexpr const char* prompt = "_{}> "; + "🔥 fire script v{}\n" + "Type .h for help, .q to quit.\n"; +static constexpr const char* prompt = "_{}> "; Program::Program(bool log_debug) @@ -171,10 +171,10 @@ void Program::evaluate_input(std::string_view input) repl_command().eval(input.substr(1)); } catch (const ScriptError& e) { auto& tout = ctx.term_out; - tout.print("{}: {}\n", e.code(), e.what()); + tout.print("{}: {}\n", e.code(), e.what()); if (!e.detail().empty()) - tout.print("{}\n", e.detail()); - tout.print("Help: .h | .help\n"); + tout.print("{}\n", e.detail()); + tout.print("Help: .h | .help\n"); } return; } diff --git a/tools/fire_script/Repl.cpp b/tools/fire_script/Repl.cpp index 307c8a22..daf12534 100644 --- a/tools/fire_script/Repl.cpp +++ b/tools/fire_script/Repl.cpp @@ -150,7 +150,7 @@ bool Repl::evaluate_module(Module& module, EvalMode mode) machine.call(main_fn, [&](TypedValue&& invoked) { if (!invoked.is_void()) { t.sanitize_newline(); - t.print("{}\n", invoked); + t.print("{}\n", invoked); } invoked.decref(); }); @@ -163,14 +163,14 @@ bool Repl::evaluate_module(Module& module, EvalMode mode) // REPL mode const auto& module_name = module.name(); if (!result.is_void()) { - t.print("{}:{} = {}\n", + t.print("{}:{} = {}\n", module_name, result.type_info(), result); } } else { // single input mode assert(mode == EvalMode::SingleInput); if (!result.is_void()) { - t.print("{}\n", result); + t.print("{}\n", result); } } result.decref(); @@ -191,9 +191,9 @@ void Repl::print_error(const ScriptError& e) if (!e.file().empty()) t.print("{}: ", e.file()); - t.print("{}: {}", e.code(), e.what()); + t.print("{}: {}", e.code(), e.what()); if (!e.detail().empty()) - t.print("\n{}", e.detail()); + t.print("\n{}", e.detail()); t.write_nl(); } diff --git a/tools/fire_script/ReplCommand.cpp b/tools/fire_script/ReplCommand.cpp index 0a1bee5c..870a52c3 100644 --- a/tools/fire_script/ReplCommand.cpp +++ b/tools/fire_script/ReplCommand.cpp @@ -76,7 +76,7 @@ const Module* ReplCommand::module_by_idx(Index mod_idx) { return m_module.get(); if (mod_idx >= m_ctx.input_modules.size()) { - t.print("Error: module index out of range: {}\n", + t.print("Error: module index out of range: {}\n", mod_idx); return nullptr; } @@ -100,7 +100,7 @@ const Module* ReplCommand::module_by_name(std::string_view mod_name) { } TermCtl& t = m_ctx.term_out; - t.print("Error: module not found: {}\n", + t.print("Error: module not found: {}\n", mod_name); return nullptr; } @@ -138,7 +138,7 @@ void ReplCommand::dump_function(const Module& mod, Index fun_idx) { TermCtl& t = m_ctx.term_out; if (fun_idx >= mod.num_functions()) { - t.print("Error: function index out of range: {}\n", + t.print("Error: function index out of range: {}\n", fun_idx); return; } @@ -153,14 +153,14 @@ void ReplCommand::dump_function(const Module& mod, Index fun_idx) { void ReplCommand::cmd_dump_function() { TermCtl& t = m_ctx.term_out; if (m_ctx.input_modules.empty()) { - t.print("Error: no input modules available\n"); + t.print("Error: no input modules available\n"); return; } size_t mod_idx = m_ctx.input_modules.size() - 1; const auto& mod = *m_ctx.input_modules[mod_idx]; if (mod.num_functions() == 0) { - t.print("Error: no functions available\n"); + t.print("Error: no functions available\n"); return; } @@ -171,7 +171,7 @@ void ReplCommand::cmd_dump_function() { void ReplCommand::cmd_dump_function(std::string_view fun_name) { TermCtl& t = m_ctx.term_out; if (m_ctx.input_modules.empty()) { - t.print("Error: no input modules available\n"); + t.print("Error: no input modules available\n"); return; } size_t mod_idx = m_ctx.input_modules.size() - 1; @@ -184,7 +184,7 @@ void ReplCommand::cmd_dump_function(std::string_view fun_name) { return; } } - t.print("Error: function not found: {}\n", + t.print("Error: function not found: {}\n", fun_name); } @@ -205,7 +205,7 @@ void ReplCommand::cmd_dump_function(std::string_view fun_name, std::string_view return; } } - t.print("Error: function not found: {}\n", + t.print("Error: function not found: {}\n", fun_name); } @@ -214,7 +214,7 @@ void ReplCommand::cmd_dump_function(Index fun_idx) { TermCtl& t = m_ctx.term_out; if (m_ctx.input_modules.empty()) { - t.print("Error: no modules available\n"); + t.print("Error: no modules available\n"); return; } size_t mod_idx = m_ctx.input_modules.size() - 1; @@ -274,7 +274,7 @@ void ReplCommand::cmd_describe(std::string_view name) { return; } } - t.print("Error: symbol not found: {}\n", + t.print("Error: symbol not found: {}\n", name); } diff --git a/tools/shader_editor/shed.cpp b/tools/shader_editor/shed.cpp index 79b648cf..87c9cb12 100644 --- a/tools/shader_editor/shed.cpp +++ b/tools/shader_editor/shed.cpp @@ -81,7 +81,7 @@ int main(int argc, const char* argv[]) Logger::init(log_debug ? Logger::Level::Trace : Logger::Level::Warning); if (show_version) { - term.print("shed {}\n", c_version); + term.print("shed {}\n", c_version); return 0; } diff --git a/tools/term_ctl/tc.cpp b/tools/term_ctl/tc.cpp index 89af466d..9044852f 100644 --- a/tools/term_ctl/tc.cpp +++ b/tools/term_ctl/tc.cpp @@ -33,7 +33,7 @@ int main(int argc, const char* argv[]) TermCtl term(STDOUT_FILENO, isatty_always ? TermCtl::IsTty::Always : TermCtl::IsTty::Auto); if (show_version) { - term.print("tc {}\n", c_version); + term.print("tc {}\n", c_version); return 0; } From bcaf91ae62d9c0a8fc47c98c0864bc2cd14d333c Mon Sep 17 00:00:00 2001 From: Radek Brich Date: Sat, 13 Jul 2024 09:08:09 +0200 Subject: [PATCH 3/7] [core] TermCtl: Fix build with old Clang <= 13 --- src/xci/core/TermCtl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/xci/core/TermCtl.cpp b/src/xci/core/TermCtl.cpp index 35d0f45a..2b36c704 100644 --- a/src/xci/core/TermCtl.cpp +++ b/src/xci/core/TermCtl.cpp @@ -620,7 +620,7 @@ std::string TermCtl::_format(std::string_view fmt) auto beg = it; while (std::islower(*it) || *it == '_') ++it; - std::string_view key (beg, it); + std::string_view key (std::to_address(beg), it - beg); if (*it == '>') { const auto m = _parse_mode(key); if (m <= Mode::_Last) { @@ -639,7 +639,7 @@ std::string TermCtl::_format(std::string_view fmt) beg = it; while (std::islower(*it) || *it == '_' || *it == '*') ++it; - std::string_view value (beg, it); + std::string_view value (std::to_address(beg), it-beg); if (*it != '>') { r.push_back('<'); r += key; From 556088770b1ceabf1fd1fcdfacc338d7629d70e2 Mon Sep 17 00:00:00 2001 From: Radek Brich Date: Sat, 13 Jul 2024 09:55:08 +0200 Subject: [PATCH 4/7] [core] TermCtl: Fix tidy [clang-analyzer-cplusplus.Move] --- src/xci/core/TermCtl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xci/core/TermCtl.h b/src/xci/core/TermCtl.h index 0e6185c1..2378f02c 100644 --- a/src/xci/core/TermCtl.h +++ b/src/xci/core/TermCtl.h @@ -194,7 +194,7 @@ class TermCtl { TermCtl& soft_reset(); // Cached seq - std::string seq() { return std::move(m_seq); } + std::string seq() { auto s = std::move(m_seq); m_seq.clear(); return s; } void write() { write_raw(seq()); } void write_nl() { m_seq.append(1, '\n'); write(seq()); } friend std::ostream& operator<<(std::ostream& os, TermCtl& t) { return os << t.seq(); } From 1c59429547c6699f2af75f6f96c19355f59b9216 Mon Sep 17 00:00:00 2001 From: Radek Brich Date: Sat, 13 Jul 2024 16:02:19 +0200 Subject: [PATCH 5/7] [core] TermCtl: Drop the fg: bg: prefixes in color tags Use '@' for bg, make fg the default: `<@blue>` --- examples/core/demo_termctl.cpp | 2 +- src/xci/core/ArgParser.cpp | 14 ++++---- src/xci/core/TermCtl.cpp | 54 +++++++++++-------------------- src/xci/core/TermCtl.h | 4 +-- src/xci/core/log_termctl.cpp | 20 ++++++------ tests/test_core.cpp | 2 +- tools/data_archive/dar.cpp | 10 +++--- tools/data_inspect/dati.cpp | 52 ++++++++++++++--------------- tools/fire_script/Highlighter.cpp | 4 +-- tools/fire_script/Program.cpp | 12 +++---- tools/fire_script/Repl.cpp | 8 ++--- tools/fire_script/ReplCommand.cpp | 20 ++++++------ tools/shader_editor/shed.cpp | 2 +- 13 files changed, 94 insertions(+), 110 deletions(-) diff --git a/examples/core/demo_termctl.cpp b/examples/core/demo_termctl.cpp index 15e78485..24529c43 100644 --- a/examples/core/demo_termctl.cpp +++ b/examples/core/demo_termctl.cpp @@ -27,7 +27,7 @@ int main() cout << t.move_up().move_right(6).bold().green() << "GREEN" <formatted\n"); + t.print("formatted <*white><@yellow> bg \n"); t.print("bold " "dim " "italic " diff --git a/src/xci/core/ArgParser.cpp b/src/xci/core/ArgParser.cpp index 20af7438..f912a66d 100644 --- a/src/xci/core/ArgParser.cpp +++ b/src/xci/core/ArgParser.cpp @@ -136,12 +136,12 @@ std::string Option::usage() const if (!p) break; if (is_remainder() && p.dashes == 2 && p.len == 0) { - res += t.format("[{}] ", std::string(dp + p.pos, p.dashes)); + res += t.format("[{}] ", std::string(dp + p.pos, p.dashes)); } else if (first) { first = false; - res += t.format("{}", std::string(dp + p.pos, p.dashes + p.len)); + res += t.format("{}", std::string(dp + p.pos, p.dashes + p.len)); } else if (!p.dashes) { - res += t.format(" {}", std::string(dp + p.pos, p.len)); + res += t.format(" {}", std::string(dp + p.pos, p.len)); } dp += p.end(); } @@ -312,7 +312,7 @@ ArgParser& ArgParser::operator()(const char* argv[], bool detect_width, unsigned if (!parse_program_name(argv[0])) { // this should not occur auto& t = TermCtl::stderr_instance(); - t.print("Missing program name (argv[0])\n"); + t.print("Missing program name (argv[0])\n"); exit(1); } try { @@ -326,7 +326,7 @@ ArgParser& ArgParser::operator()(const char* argv[], bool detect_width, unsigned } } catch (const BadArgument& e) { auto& t = TermCtl::stderr_instance(); - t.print("Error: {}\n\n", e.what()); + t.print("Error: {}\n\n", e.what()); print_usage(); print_help_notice(); exit(1); @@ -525,7 +525,7 @@ void ArgParser::print_usage() const unsigned indent = 0; { - auto head = t.format("Usage: {} ", m_progname); + auto head = t.format("Usage: {} ", m_progname); indent = TermCtl::stripped_width(head); cout << head; } @@ -545,7 +545,7 @@ void ArgParser::print_help() const desc_cols = std::max(desc_cols, (unsigned) opt.desc().size()); print_usage(); auto& t = TermCtl::stdout_instance(); - t.print("\nOptions:\n"); + t.print("\nOptions:\n"); for (const auto& opt : m_opts) { cout << " " << opt.formatted_desc(desc_cols) << " "; wrapping_print(opt.help(), desc_cols + 4, 0, m_max_width); diff --git a/src/xci/core/TermCtl.cpp b/src/xci/core/TermCtl.cpp index 2b36c704..fcdc9545 100644 --- a/src/xci/core/TermCtl.cpp +++ b/src/xci/core/TermCtl.cpp @@ -617,8 +617,14 @@ std::string TermCtl::_format(std::string_view fmt) if (*it == '<') { ++it; + bool is_bg = false; + if (*it == '@') { + is_bg = true; + ++it; + } + auto beg = it; - while (std::islower(*it) || *it == '_') + while (std::islower(*it) || *it == '_' || *it == '*') ++it; std::string_view key (std::to_address(beg), it - beg); if (*it == '>') { @@ -628,43 +634,21 @@ std::string TermCtl::_format(std::string_view fmt) ++it; continue; } - } - if (*it != ':') { - r.push_back('<'); - r += key; - continue; - } - ++it; - - beg = it; - while (std::islower(*it) || *it == '_' || *it == '*') - ++it; - std::string_view value (std::to_address(beg), it-beg); - if (*it != '>') { - r.push_back('<'); - r += key; - r.push_back(':'); - r += value; - continue; - } - ++it; - - if (key == "fg" || key == "bg") { - const auto c = _parse_color(value); - if (c > Color::_Last) { - r.push_back('<'); - r += key; - r.push_back(':'); - r += value; - r.push_back('>'); + const auto c = _parse_color(key); + if (c <= Color::_Last) { + if (is_bg) + r += bg(c).seq(); + else + r += fg(c).seq(); + ++it; continue; } - if (key == "fg") - r += fg(c).seq(); - else - r += bg(c).seq(); - continue; } + // rollback + r.push_back('<'); + if (is_bg) + r.push_back('@'); + r += key; } r.push_back(*it); ++it; diff --git a/src/xci/core/TermCtl.h b/src/xci/core/TermCtl.h index 2378f02c..82bbdb52 100644 --- a/src/xci/core/TermCtl.h +++ b/src/xci/core/TermCtl.h @@ -200,8 +200,8 @@ class TermCtl { friend std::ostream& operator<<(std::ostream& os, TermCtl& t) { return os << t.seq(); } /// Format string, adding colors via special placeholders: - /// where COLOR is default | red | *red ... ("*" = bright) - /// where COLOR is the same as for fg + /// where COLOR is default | red | *red ... ("*" = bright) + /// <@BG_COLOR> where BG_COLOR is the same as for COLOR /// where MODE is bold | underline | normal ... (shortcuts b | u | n ...) template std::string format(fmt::format_string fmt, T&&... args) { diff --git a/src/xci/core/log_termctl.cpp b/src/xci/core/log_termctl.cpp index 153c457b..413bc225 100644 --- a/src/xci/core/log_termctl.cpp +++ b/src/xci/core/log_termctl.cpp @@ -19,16 +19,16 @@ namespace xci::core { // 5..9 => multi-line continuation for each log level static constexpr size_t c_cont = 5; static constexpr const char* c_log_format[] = { - "{0:%F %T} {1:6x} TRACE {2}\n", - "{0:%F %T} {1:6x} DEBUG {2}\n", - "{0:%F %T} {1:6x} INFO {2}\n", - "{0:%F %T} {1:6x} WARN {2}\n", - "{0:%F %T} {1:6x} ERROR {2}\n", - " ... {2}\n", - " ... {2}\n", - " ... {2}\n", - " ... {2}\n", - " ... {2}\n", + "{0:%F %T} {1:6x} TRACE {2}\n", + "{0:%F %T} {1:6x} DEBUG {2}\n", + "{0:%F %T} {1:6x} INFO {2}\n", + "{0:%F %T} {1:6x} WARN {2}\n", + "{0:%F %T} {1:6x} ERROR {2}\n", + " ... {2}\n", + " ... {2}\n", + " ... {2}\n", + " ... {2}\n", + " ... {2}\n", }; static constexpr const char* c_log_intro = " Date Time TID Level Message \n"; diff --git a/tests/test_core.cpp b/tests/test_core.cpp index 3f188fb4..42ad3f5a 100644 --- a/tests/test_core.cpp +++ b/tests/test_core.cpp @@ -297,7 +297,7 @@ TEST_CASE( "stripped_width", "[TermCtl]" ) CHECK(TermCtl::stripped_width("test") == 4); CHECK(TermCtl::stripped_width("❓") == 2); TermCtl t(1, TermCtl::IsTty::Always); - CHECK(TermCtl::stripped_width(t.format("test")) == 4); + CHECK(TermCtl::stripped_width(t.format("test")) == 4); CHECK(TermCtl::stripped_width("\x1b[32mtest\x1b(B\x1b[m") == 4); CHECK(TermCtl::stripped_width("\n") == 1); // newline is 1 column (special handling in EditLine) } diff --git a/tools/data_archive/dar.cpp b/tools/data_archive/dar.cpp index 4db6842b..8ca69bfe 100644 --- a/tools/data_archive/dar.cpp +++ b/tools/data_archive/dar.cpp @@ -28,7 +28,7 @@ static void extract_entry(const std::string& name, const VfsFile& file, const fs { const auto entry_path = output_path / name; TermCtl& term = TermCtl::stdout_instance(); - term.print("Extracting file\t{} to {}\n", name, entry_path); + term.print("Extracting file\t{} to {}\n", name, entry_path); auto content = file.content(); if (content) { fs::create_directories(entry_path.parent_path()); @@ -152,20 +152,20 @@ int main(int argc, const char* argv[]) } (argv); if (files.empty()) { - term.print("No input files.\n"); + term.print("No input files.\n"); } Vfs vfs; for (const auto filename : files) { - term.print("Extracting archive\t{}\n", filename); + term.print("Extracting archive\t{}\n", filename); if (!vfs.mount(filename)) { - term.print("Could not mount {}\n", filename); + term.print("Could not mount {}\n", filename); continue; } if (list_entries) { for (const auto& entry : *vfs.mounts().back().vfs_dir) { - term.print("{}\n", entry.name()); + term.print("{}\n", entry.name()); } continue; } diff --git a/tools/data_inspect/dati.cpp b/tools/data_inspect/dati.cpp index ee64dcd9..c47225fd 100644 --- a/tools/data_inspect/dati.cpp +++ b/tools/data_inspect/dati.cpp @@ -54,33 +54,33 @@ static void print_data(TermCtl& term, uint8_t type, const std::byte* data, size_ { const auto expected_size = BinaryBase::size_by_type(type); if (expected_size != size_t(-1) && size != expected_size) { - term.print("bad size {}", size); + term.print("bad size {}", size); return; } switch (type) { - case BinaryBase::Null: term.print("null"); return; - case BinaryBase::BoolFalse: term.print("false"); return; - case BinaryBase::BoolTrue: term.print("true"); return; - case BinaryBase::Fixed8: term.print("{}", unsigned(*data)); return; - case BinaryBase::Fixed16: term.print("{}", bit_copy(data)); return; - case BinaryBase::Fixed32: term.print("{}", bit_copy(data)); return; - case BinaryBase::Fixed64: term.print("{}", bit_copy(data)); return; - case BinaryBase::Fixed128: term.print("{}", uint128_to_string(bit_copy(data))); return; - case BinaryBase::Float32: term.print("{}", bit_copy(data)); return; - case BinaryBase::Float64: term.print("{}", bit_copy(data)); return; - case BinaryBase::VarInt: term.print("varint"); return; - case BinaryBase::Array: term.print("array"); return; + case BinaryBase::Null: term.print("null"); return; + case BinaryBase::BoolFalse: term.print("false"); return; + case BinaryBase::BoolTrue: term.print("true"); return; + case BinaryBase::Fixed8: term.print("{}", unsigned(*data)); return; + case BinaryBase::Fixed16: term.print("{}", bit_copy(data)); return; + case BinaryBase::Fixed32: term.print("{}", bit_copy(data)); return; + case BinaryBase::Fixed64: term.print("{}", bit_copy(data)); return; + case BinaryBase::Fixed128: term.print("{}", uint128_to_string(bit_copy(data))); return; + case BinaryBase::Float32: term.print("{}", bit_copy(data)); return; + case BinaryBase::Float64: term.print("{}", bit_copy(data)); return; + case BinaryBase::VarInt: term.print("varint"); return; + case BinaryBase::Array: term.print("array"); return; case BinaryBase::String: - term.print("\"{}\"", + term.print("\"{}\"", escape(std::string_view((const char*) data, size))); return; - case BinaryBase::Binary: term.print("(size {})", size); return; + case BinaryBase::Binary: term.print("(size {})", size); return; case BinaryBase::Master: - term.print("(size {}) {{", size); return; - case BinaryBase::Control: term.print("control"); return; + term.print("(size {}) {{", size); return; + case BinaryBase::Control: term.print("control"); return; } - term.print("unknown"); + term.print("unknown"); } @@ -110,7 +110,7 @@ int main(int argc, const char* argv[]) } (argv); if (files.empty()) { - term.print("No input files.\n"); + term.print("No input files.\n"); } Schema schema; @@ -121,7 +121,7 @@ int main(int argc, const char* argv[]) reader(schema); reader.finish_and_check(); } catch (const ArchiveError& e) { - term.print("Error reading schema: {}\n", e.what()); + term.print("Error reading schema: {}\n", e.what()); } } else if (files.size() == 1 && std::string(files.back()).ends_with(".schema")) { // get Schema of .schema file @@ -129,7 +129,7 @@ int main(int argc, const char* argv[]) } for (const auto& filename : files) { - term.print("{}\n", filename); + term.print("{}\n", filename); std::ifstream f(filename, std::ios::binary); try { BinaryReader reader(f); @@ -182,12 +182,12 @@ int main(int argc, const char* argv[]) if (opt_int) last_int_values[schema_member->name] = *opt_int; } - term.print("{}{} ({}: {}): {} = ", + term.print("{}{} ({}: {}): {} = ", std::string(indent, ' '), int(it.key), schema_member->name, schema_member->type, type_to_cstr(it.type)); } else { - term.print("{}{}: {} = ", + term.print("{}{}: {} = ", std::string(indent, ' '), int(it.key), type_to_cstr(it.type)); } @@ -198,9 +198,9 @@ int main(int argc, const char* argv[]) uint32_t stored_crc = 0; std::memcpy(&stored_crc, it.data.get(), it.size); if (reader.crc() == stored_crc) - term.print(" (CRC32: OK)"); + term.print(" (CRC32: OK)"); else - term.print(" (CRC32: expected {})", + term.print(" (CRC32: expected {})", reader.crc()); } } @@ -228,7 +228,7 @@ int main(int argc, const char* argv[]) } } } catch (const ArchiveError& e) { - term.print("{}\n", e.what()); + term.print("{}\n", e.what()); } } diff --git a/tools/fire_script/Highlighter.cpp b/tools/fire_script/Highlighter.cpp index a234c8a5..9d2c7787 100644 --- a/tools/fire_script/Highlighter.cpp +++ b/tools/fire_script/Highlighter.cpp @@ -459,13 +459,13 @@ auto Highlighter::highlight(std::string_view input, unsigned cursor) -> HlResult try { auto root = tao::pegtl::parse_tree::parse< Main, Node, HighlightSelector, tao::pegtl::nothing, Control >( in ); if (root->children.size() != 1) - return {std::string{input} + m_term.format("\nhighlighter parse error: no match"), false}; + return {std::string{input} + m_term.format("\n<*red>highlighter parse error: <*red>no match"), false}; auto last_color = highlight_node(*root->children[0], HighlightColor{}, cursor); switch_color(last_color, HighlightColor{}); return {m_output, m_open_bracket}; } catch (tao::pegtl::parse_error& e) { // The grammar is build in a way that parse error should never happen - return {std::string{input} + m_term.format("\nhighlighter parse error: {}", e.what()), false}; + return {std::string{input} + m_term.format("\n<*red>highlighter parse error: <*red>{}", e.what()), false}; } } diff --git a/tools/fire_script/Program.cpp b/tools/fire_script/Program.cpp index c1743823..eb7c5d3e 100644 --- a/tools/fire_script/Program.cpp +++ b/tools/fire_script/Program.cpp @@ -18,9 +18,9 @@ namespace xci::script::tool { using namespace xci::core; static constexpr const char* intro = - "🔥 fire script v{}\n" - "Type .h for help, .q to quit.\n"; -static constexpr const char* prompt = "_{}> "; + "🔥 fire script v{}\n" + "Type .h for help, .q to quit.\n"; +static constexpr const char* prompt = "_{}> "; Program::Program(bool log_debug) @@ -171,10 +171,10 @@ void Program::evaluate_input(std::string_view input) repl_command().eval(input.substr(1)); } catch (const ScriptError& e) { auto& tout = ctx.term_out; - tout.print("{}: {}\n", e.code(), e.what()); + tout.print("{}: {}\n", e.code(), e.what()); if (!e.detail().empty()) - tout.print("{}\n", e.detail()); - tout.print("Help: .h | .help\n"); + tout.print("{}\n", e.detail()); + tout.print("Help: .h | .help\n"); } return; } diff --git a/tools/fire_script/Repl.cpp b/tools/fire_script/Repl.cpp index daf12534..3fc1c289 100644 --- a/tools/fire_script/Repl.cpp +++ b/tools/fire_script/Repl.cpp @@ -150,7 +150,7 @@ bool Repl::evaluate_module(Module& module, EvalMode mode) machine.call(main_fn, [&](TypedValue&& invoked) { if (!invoked.is_void()) { t.sanitize_newline(); - t.print("{}\n", invoked); + t.print("{}\n", invoked); } invoked.decref(); }); @@ -163,7 +163,7 @@ bool Repl::evaluate_module(Module& module, EvalMode mode) // REPL mode const auto& module_name = module.name(); if (!result.is_void()) { - t.print("{}:{} = {}\n", + t.print("{}:{} = {}\n", module_name, result.type_info(), result); } } else { @@ -191,9 +191,9 @@ void Repl::print_error(const ScriptError& e) if (!e.file().empty()) t.print("{}: ", e.file()); - t.print("{}: {}", e.code(), e.what()); + t.print("{}: <*white>{}", e.code(), e.what()); if (!e.detail().empty()) - t.print("\n{}", e.detail()); + t.print("\n{}", e.detail()); t.write_nl(); } diff --git a/tools/fire_script/ReplCommand.cpp b/tools/fire_script/ReplCommand.cpp index 870a52c3..bc55f44f 100644 --- a/tools/fire_script/ReplCommand.cpp +++ b/tools/fire_script/ReplCommand.cpp @@ -76,7 +76,7 @@ const Module* ReplCommand::module_by_idx(Index mod_idx) { return m_module.get(); if (mod_idx >= m_ctx.input_modules.size()) { - t.print("Error: module index out of range: {}\n", + t.print("Error: module index out of range: {}\n", mod_idx); return nullptr; } @@ -100,7 +100,7 @@ const Module* ReplCommand::module_by_name(std::string_view mod_name) { } TermCtl& t = m_ctx.term_out; - t.print("Error: module not found: {}\n", + t.print("Error: module not found: {}\n", mod_name); return nullptr; } @@ -138,7 +138,7 @@ void ReplCommand::dump_function(const Module& mod, Index fun_idx) { TermCtl& t = m_ctx.term_out; if (fun_idx >= mod.num_functions()) { - t.print("Error: function index out of range: {}\n", + t.print("Error: function index out of range: {}\n", fun_idx); return; } @@ -153,14 +153,14 @@ void ReplCommand::dump_function(const Module& mod, Index fun_idx) { void ReplCommand::cmd_dump_function() { TermCtl& t = m_ctx.term_out; if (m_ctx.input_modules.empty()) { - t.print("Error: no input modules available\n"); + t.print("Error: no input modules available\n"); return; } size_t mod_idx = m_ctx.input_modules.size() - 1; const auto& mod = *m_ctx.input_modules[mod_idx]; if (mod.num_functions() == 0) { - t.print("Error: no functions available\n"); + t.print("Error: no functions available\n"); return; } @@ -171,7 +171,7 @@ void ReplCommand::cmd_dump_function() { void ReplCommand::cmd_dump_function(std::string_view fun_name) { TermCtl& t = m_ctx.term_out; if (m_ctx.input_modules.empty()) { - t.print("Error: no input modules available\n"); + t.print("Error: no input modules available\n"); return; } size_t mod_idx = m_ctx.input_modules.size() - 1; @@ -184,7 +184,7 @@ void ReplCommand::cmd_dump_function(std::string_view fun_name) { return; } } - t.print("Error: function not found: {}\n", + t.print("Error: function not found: {}\n", fun_name); } @@ -205,7 +205,7 @@ void ReplCommand::cmd_dump_function(std::string_view fun_name, std::string_view return; } } - t.print("Error: function not found: {}\n", + t.print("Error: function not found: {}\n", fun_name); } @@ -214,7 +214,7 @@ void ReplCommand::cmd_dump_function(Index fun_idx) { TermCtl& t = m_ctx.term_out; if (m_ctx.input_modules.empty()) { - t.print("Error: no modules available\n"); + t.print("Error: no modules available\n"); return; } size_t mod_idx = m_ctx.input_modules.size() - 1; @@ -274,7 +274,7 @@ void ReplCommand::cmd_describe(std::string_view name) { return; } } - t.print("Error: symbol not found: {}\n", + t.print("Error: symbol not found: {}\n", name); } diff --git a/tools/shader_editor/shed.cpp b/tools/shader_editor/shed.cpp index 87c9cb12..810d4878 100644 --- a/tools/shader_editor/shed.cpp +++ b/tools/shader_editor/shed.cpp @@ -81,7 +81,7 @@ int main(int argc, const char* argv[]) Logger::init(log_debug ? Logger::Level::Trace : Logger::Level::Warning); if (show_version) { - term.print("shed {}\n", c_version); + term.print("shed <*white>{}\n", c_version); return 0; } From c12a489f8d6407ce9156e0d5b0c28e20752976c3 Mon Sep 17 00:00:00 2001 From: Radek Brich Date: Sun, 14 Jul 2024 19:19:20 +0200 Subject: [PATCH 6/7] [core] TermCtl: format: Use PEGTL to implement the parser, support escaping the tags (`\`) --- examples/core/demo_termctl.cpp | 1 + src/xci/core/TermCtl.cpp | 94 +++++++++++++++++++--------------- src/xci/core/TermCtl.h | 40 +++++++-------- 3 files changed, 74 insertions(+), 61 deletions(-) diff --git a/examples/core/demo_termctl.cpp b/examples/core/demo_termctl.cpp index 24529c43..588d7637 100644 --- a/examples/core/demo_termctl.cpp +++ b/examples/core/demo_termctl.cpp @@ -39,6 +39,7 @@ int main() "reversed " "hidden " "\n"); + t.print("Escaped \\. Unknown .\n"); t.tab_set_all({30, 20}).write(); t.print("tab stops:\t1\t2\n"); diff --git a/src/xci/core/TermCtl.cpp b/src/xci/core/TermCtl.cpp index fcdc9545..2353c4b4 100644 --- a/src/xci/core/TermCtl.cpp +++ b/src/xci/core/TermCtl.cpp @@ -24,6 +24,9 @@ #include #include +#include +namespace pegtl = tao::pegtl; + #ifdef _WIN32 static_assert(sizeof(unsigned long) == sizeof(DWORD)); #else @@ -608,51 +611,60 @@ TermCtl& TermCtl::clear_line_to_end() { return TERM_APPEND(clr_eol); } TermCtl& TermCtl::soft_reset() { return XCI_TERM_APPEND(seq::send_soft_reset); } -std::string TermCtl::_format(std::string_view fmt) -{ - std::string r; - r.reserve(fmt.size()); - auto it = fmt.begin(); - while (it != fmt.end()) { - if (*it == '<') { - ++it; - - bool is_bg = false; - if (*it == '@') { - is_bg = true; - ++it; - } +namespace format_parser { - auto beg = it; - while (std::islower(*it) || *it == '_' || *it == '*') - ++it; - std::string_view key (std::to_address(beg), it - beg); - if (*it == '>') { - const auto m = _parse_mode(key); - if (m <= Mode::_Last) { - r += mode(m).seq(); - ++it; - continue; - } - const auto c = _parse_color(key); - if (c <= Color::_Last) { - if (is_bg) - r += bg(c).seq(); - else - r += fg(c).seq(); - ++it; - continue; - } - } - // rollback - r.push_back('<'); +struct Char : pegtl::any {}; +struct Escape : pegtl::seq< pegtl::one<'\\'>, Char > {}; +struct Tag : pegtl::seq< pegtl::one<'<'>, pegtl::opt>, pegtl::opt>, pegtl::plus>, pegtl::one<'>'>> {}; +struct Grammar : pegtl::must< pegtl::star>, pegtl::eof > {}; + +template< typename Rule > +struct Action {}; + +template<> +struct Action< Char > { + template< typename ParseInput > + static void apply( const ParseInput& in, TermCtl& t, std::string& r ) { + r.push_back(in.peek_char()); + } +}; + +template<> +struct Action< Tag > { + template< typename ParseInput > + static bool apply( const ParseInput& in, TermCtl& t, std::string& r ) { + auto key = in.string_view().substr(1, in.size() - 2); // strip < > + + const auto m = TermCtl::parse_mode(key); + if (m <= TermCtl::Mode::_Last) { + r += t.mode(m).seq(); + return true; + } + + bool is_bg = key.front() == '@'; + if (is_bg) + key.remove_prefix(1); + + const auto c = TermCtl::parse_color(key); + if (c <= TermCtl::Color::_Last) { if (is_bg) - r.push_back('@'); - r += key; + r += t.bg(c).seq(); + else + r += t.fg(c).seq(); + return true; } - r.push_back(*it); - ++it; + + return false; } +}; + +} // namespace format_parser + +std::string TermCtl::_format(std::string_view fmt) +{ + pegtl::memory_input in( std::to_address(fmt.begin()), std::to_address(fmt.end()) ); + std::string r; + pegtl::parse< format_parser::Grammar, format_parser::Action >( in, *this, r ); return r; } diff --git a/src/xci/core/TermCtl.h b/src/xci/core/TermCtl.h index 82bbdb52..36662ce9 100644 --- a/src/xci/core/TermCtl.h +++ b/src/xci/core/TermCtl.h @@ -80,6 +80,16 @@ class TermCtl { }; static_assert(std::size(c_color_names) == size_t(Color::_Last) + 1); + static constexpr Color parse_color(std::string_view name) { + Color r = Color::Black; + for (const char* n : c_color_names) { + if (name == n) + return r; + r = static_cast(uint8_t(r) + 1); + } + return r; + } + enum class Mode: uint8_t { Normal, // reset all attributes Bold, Dim, Italic, Underline, Overline, CrossOut, Frame, @@ -97,6 +107,16 @@ class TermCtl { }; static_assert(std::size(c_mode_names) == size_t(Mode::_Last) + 1); + static constexpr Mode parse_mode(std::string_view name) { + Mode r = Mode::Normal; + for (const char* n : c_mode_names) { + if (name == n || (name.size() == 1 && name[0] == n[0])) + return r; + r = static_cast(uint8_t(r) + 1); + } + return r; + } + // foreground TermCtl& fg(Color color); TermCtl& black() { return fg(Color::Black); } @@ -376,26 +396,6 @@ class TermCtl { TermCtl& _append_seq(std::string_view seq) { m_seq += seq; return *this; } std::string _format(std::string_view fmt); - static constexpr Mode _parse_mode(std::string_view name) { - Mode r = Mode::Normal; - for (const char* n : c_mode_names) { - if (name == n || (name.size() == 1 && name[0] == n[0])) - return r; - r = static_cast(uint8_t(r) + 1); - } - return r; - } - - static constexpr Color _parse_color(std::string_view name) { - Color r = Color::Black; - for (const char* n : c_color_names) { - if (name == n) - return r; - r = static_cast(uint8_t(r) + 1); - } - return r; - } - std::string m_seq; // cached capability sequences WriteCallback m_write_cb; int m_fd; // FD (on Windows mapped to handle) From e862bf01de26065509f47f86ed58b4edcf8ef403 Mon Sep 17 00:00:00 2001 From: Radek Brich Date: Sun, 14 Jul 2024 20:03:56 +0200 Subject: [PATCH 7/7] [core] TermCtl: Refactoring - add public `render` method --- src/xci/core/TermCtl.cpp | 10 +++++----- src/xci/core/TermCtl.h | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/xci/core/TermCtl.cpp b/src/xci/core/TermCtl.cpp index 2353c4b4..6e5d025a 100644 --- a/src/xci/core/TermCtl.cpp +++ b/src/xci/core/TermCtl.cpp @@ -611,7 +611,7 @@ TermCtl& TermCtl::clear_line_to_end() { return TERM_APPEND(clr_eol); } TermCtl& TermCtl::soft_reset() { return XCI_TERM_APPEND(seq::send_soft_reset); } -namespace format_parser { +namespace render_parser { struct Char : pegtl::any {}; struct Escape : pegtl::seq< pegtl::one<'\\'>, Char > {}; @@ -658,13 +658,13 @@ struct Action< Tag > { } }; -} // namespace format_parser +} // namespace render_parser -std::string TermCtl::_format(std::string_view fmt) +std::string TermCtl::render(std::string_view markup) { - pegtl::memory_input in( std::to_address(fmt.begin()), std::to_address(fmt.end()) ); + pegtl::memory_input in( std::to_address(markup.begin()), std::to_address(markup.end()) ); std::string r; - pegtl::parse< format_parser::Grammar, format_parser::Action >( in, *this, r ); + pegtl::parse< render_parser::Grammar, render_parser::Action >( in, *this, r ); return r; } diff --git a/src/xci/core/TermCtl.h b/src/xci/core/TermCtl.h index 36662ce9..36e751dc 100644 --- a/src/xci/core/TermCtl.h +++ b/src/xci/core/TermCtl.h @@ -219,22 +219,31 @@ class TermCtl { void write_nl() { m_seq.append(1, '\n'); write(seq()); } friend std::ostream& operator<<(std::ostream& os, TermCtl& t) { return os << t.seq(); } - /// Format string, adding colors via special placeholders: + /// Translate special markup language to color/mode control sequences: /// where COLOR is default | red | *red ... ("*" = bright) /// <@BG_COLOR> where BG_COLOR is the same as for COLOR /// where MODE is bold | underline | normal ... (shortcuts b | u | n ...) + std::string render(std::string_view markup); + + /// Format a string, adding colors via special placeholders - see `render` above. + /// Note that these placeholders are applied ahead of the placeholders, + /// so e.g. `{}` cannot expand to `` which would be recognized. If this is + /// intended, call `fmt::format` separately and pass the result to `TermCtl::render`. template std::string format(fmt::format_string fmt, T&&... args) { const auto sv = fmt.get(); - return fmt::vformat(_format(std::string_view(sv.data(), sv.size())), + return fmt::vformat(render(std::string_view(sv.data(), sv.size())), fmt::make_format_args(args...)); } - /// Print string with special color/mode placeholders, see `format` above. + /// Print string with special color/mode placeholders, see `render` above. template void print(fmt::format_string fmt, T&&... args) { write(format(fmt::runtime(fmt), args...)); } + void print(std::string_view markup) { + write(render(markup)); + } void write(std::string_view buf); void write_raw(std::string_view buf); // doesn't check newline @@ -394,7 +403,6 @@ class TermCtl { TermCtl& _tab_set_all(std::span n_cols); TermCtl& _append_seq(const char* seq) { if (seq) m_seq += seq; return *this; } // needed for TermInfo, which returns NULL for unknown seqs TermCtl& _append_seq(std::string_view seq) { m_seq += seq; return *this; } - std::string _format(std::string_view fmt); std::string m_seq; // cached capability sequences WriteCallback m_write_cb;