diff --git a/docs/schemas/config-v1.schema.json b/docs/schemas/config-v1.schema.json index 363d7006..024bf419 100644 --- a/docs/schemas/config-v1.schema.json +++ b/docs/schemas/config-v1.schema.json @@ -4,8 +4,11 @@ "properties": { "$schema": { "title": "/$schema", - "description": "Specifies the type of this file", - "type": "string" + "description": "The URI that specifies the schema that describes this type of file", + "type": "string", + "examples": [ + "https://lnav.org/schemas/config-v1.schema.json" + ] }, "tuning": { "description": "Internal settings", @@ -314,6 +317,36 @@ "description": "Styling for scrollbars", "title": "/ui/theme-defs//styles/scrollbar", "$ref": "#/definitions/style" + }, + "h1": { + "description": "Styling for top-level headers", + "title": "/ui/theme-defs//styles/h1", + "$ref": "#/definitions/style" + }, + "h2": { + "description": "Styling for 2nd-level headers", + "title": "/ui/theme-defs//styles/h2", + "$ref": "#/definitions/style" + }, + "h3": { + "description": "Styling for 3rd-level headers", + "title": "/ui/theme-defs//styles/h3", + "$ref": "#/definitions/style" + }, + "h4": { + "description": "Styling for 4th-level headers", + "title": "/ui/theme-defs//styles/h4", + "$ref": "#/definitions/style" + }, + "h5": { + "description": "Styling for 5th-level headers", + "title": "/ui/theme-defs//styles/h5", + "$ref": "#/definitions/style" + }, + "h6": { + "description": "Styling for 6th-level headers", + "title": "/ui/theme-defs//styles/h6", + "$ref": "#/definitions/style" } }, "additionalProperties": false @@ -516,7 +549,7 @@ "title": "/ui/keymap-defs///command", "description": "The command to execute for the given key sequence. Use a script to execute more complicated operations.", "type": "string", - "pattern": "[:|;].*", + "pattern": "^[:|;].*", "examples": [ ":goto next hour" ] diff --git a/docs/schemas/format-v1.schema.json b/docs/schemas/format-v1.schema.json index 7c38e501..766fe163 100644 --- a/docs/schemas/format-v1.schema.json +++ b/docs/schemas/format-v1.schema.json @@ -419,18 +419,15 @@ "search-table": { "description": "Search tables to automatically define for this log format", "title": "//search-table", - "type": [ - "string", - "object" - ], + "type": "object", "patternProperties": { - "\\w+": { + "(\\w+)": { "description": "The set of search tables to be automatically defined", - "title": "//search-table/<>", + "title": "//search-table/", "type": "object", "properties": { "pattern": { - "title": "//search-table/<>/pattern", + "title": "//search-table//pattern", "description": "The regular expression for this search table.", "type": "string" } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 75cdfe87..7fa0a961 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -244,9 +244,7 @@ add_library( ${GEN_SRCS} config.h.in all_logs_vtab.cc - ansi_scrubber.cc archive_manager.cc - attr_line.cc bin2c.hh bookmarks.cc bottom_status_source.cc @@ -317,7 +315,6 @@ add_library( sql_util.cc state-extension-functions.cc styling.cc - string_attr_type.cc text_format.cc textfile_highlighters.cc textfile_sub_source.cc @@ -342,8 +339,6 @@ add_library( all_logs_vtab.hh archive_manager.hh archive_manager.cfg.hh - attr_line.hh - auto_mem.hh big_array.hh bottom_status_source.hh bound_tags.hh @@ -369,6 +364,8 @@ add_library( hotkeys.hh input_dispatcher.hh k_merge_tree.h + lnav_config.hh + lnav_config_fwd.hh log_actions.hh log_data_helper.hh log_data_table.hh @@ -410,7 +407,6 @@ add_library( sql_help.hh sql_util.hh strong_int.hh - string_attr_type.hh sysclip.hh sysclip.cfg.hh term_extra.hh diff --git a/src/Makefile.am b/src/Makefile.am index bdf2ff08..e7618970 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -154,11 +154,8 @@ dist_noinst_DATA = \ noinst_HEADERS = \ all_logs_vtab.hh \ - ansi_scrubber.hh \ archive_manager.hh \ archive_manager.cfg.hh \ - attr_line.hh \ - auto_mem.hh \ big_array.hh \ bin2c.hh \ bookmarks.hh \ @@ -256,7 +253,6 @@ noinst_HEADERS = \ sqlite-extension-func.hh \ styling.hh \ statusview_curses.hh \ - string_attr_type.hh \ strong_int.hh \ sysclip.hh \ sysclip.cfg.hh \ @@ -306,9 +302,7 @@ THIRD_PARTY_SRCS = \ libdiag_a_SOURCES = \ $(THIRD_PARTY_SRCS) \ all_logs_vtab.cc \ - ansi_scrubber.cc \ archive_manager.cc \ - attr_line.cc \ bookmarks.cc \ bottom_status_source.cc \ collation-functions.cc \ @@ -374,7 +368,6 @@ libdiag_a_SOURCES = \ sqlite-extension-func.cc \ statusview_curses.cc \ string-extension-functions.cc \ - string_attr_type.cc \ styling.cc \ text_format.cc \ textfile_sub_source.cc \ diff --git a/src/all_logs_vtab.cc b/src/all_logs_vtab.cc index bf0c2040..7fa112ef 100644 --- a/src/all_logs_vtab.cc +++ b/src/all_logs_vtab.cc @@ -30,7 +30,7 @@ #include "all_logs_vtab.hh" #include "config.h" -#include "string_attr_type.hh" +#include "base/attr_line.hh" static auto intern_lifetime = intern_string::get_table_lifetime(); diff --git a/src/archive_manager.cc b/src/archive_manager.cc index 5e1fc191..9c1b1534 100644 --- a/src/archive_manager.cc +++ b/src/archive_manager.cc @@ -40,8 +40,8 @@ #include "archive_manager.cfg.hh" #include "archive_manager.hh" -#include "auto_mem.hh" #include "base/auto_fd.hh" +#include "base/auto_mem.hh" #include "base/fs_util.hh" #include "base/humanize.hh" #include "base/injector.hh" @@ -227,8 +227,10 @@ copy_data(const std::string& filename, FMT_STRING("available space on disk ({}) is below the " "minimum-free threshold ({}). Unable to unpack " "'{}' to '{}'"), - humanize::file_size(tmp_space.available), - humanize::file_size(cfg.amc_min_free_space), + humanize::file_size(tmp_space.available, + humanize::alignment::none), + humanize::file_size(cfg.amc_min_free_space, + humanize::alignment::none), entry_path.filename().string(), entry_path.parent_path().string())); } @@ -240,11 +242,11 @@ copy_data(const std::string& filename, return Ok(); } if (r != ARCHIVE_OK) { - return Err( - fmt::format(FMT_STRING("failed to read file: {} >> {} -- {}"), - filename, - archive_entry_pathname_utf8(entry), - archive_error_string(ar))); + return Err(fmt::format( + FMT_STRING("failed to extract '{}' from archive '{}' -- {}"), + archive_entry_pathname_utf8(entry), + filename, + archive_error_string(ar))); } r = archive_write_data_block(aw, buff, size, offset); if (r != ARCHIVE_OK) { @@ -269,7 +271,7 @@ extract(const std::string& filename, const extract_cb& cb) fs::create_directories(tmp_path.parent_path(), ec); if (ec) { - return Err(fmt::format("Unable to create directory: {} -- {}", + return Err(fmt::format("unable to create directory: {} -- {}", tmp_path.parent_path().string(), ec.message())); } diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index ac7e5791..c1fa9a15 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -1,6 +1,8 @@ add_library( base STATIC ../config.h.in + ansi_scrubber.cc + attr_line.cc auto_pid.cc date_time_scanner.cc fs_util.cc @@ -10,14 +12,20 @@ add_library( intern_string.cc is_utf8.cc isc.cc + lnav.console.cc lnav.gzip.cc lnav_log.cc network.tcp.cc paths.cc + string_attr_type.cc string_util.cc strnatcmp.c time_util.cc + + ansi_scrubber.hh + attr_line.hh auto_fd.hh + auto_mem.hh auto_pid.hh date_time_scanner.hh enum_util.hh @@ -32,11 +40,13 @@ add_library( intern_string.hh is_utf8.hh isc.hh + lnav.console.hh lrucache.hpp math_util.hh network.tcp.hh paths.hh result.h + string_attr_type.hh strnatcmp.h time_util.hh) diff --git a/src/base/Makefile.am b/src/base/Makefile.am index 7de7aa24..aad3eabd 100644 --- a/src/base/Makefile.am +++ b/src/base/Makefile.am @@ -19,7 +19,10 @@ AM_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS) noinst_LIBRARIES = libbase.a noinst_HEADERS = \ + ansi_scrubber.hh \ + attr_line.hh \ auto_fd.hh \ + auto_mem.hh \ auto_pid.hh \ date_time_scanner.hh \ enum_util.hh \ @@ -36,6 +39,7 @@ noinst_HEADERS = \ is_utf8.hh \ isc.hh \ lnav_log.hh \ + lnav.console.hh \ lnav.gzip.hh \ lrucache.hpp \ math_util.hh \ @@ -43,11 +47,14 @@ noinst_HEADERS = \ opt_util.hh \ paths.hh \ result.h \ + string_attr_type.hh \ string_util.hh \ strnatcmp.h \ time_util.hh libbase_a_SOURCES = \ + ansi_scrubber.cc \ + attr_line.cc \ auto_pid.cc \ date_time_scanner.cc \ fs_util.cc \ @@ -57,10 +64,12 @@ libbase_a_SOURCES = \ intern_string.cc \ is_utf8.cc \ isc.cc \ + lnav.console.cc \ lnav.gzip.cc \ lnav_log.cc \ network.tcp.cc \ paths.cc \ + string_attr_type.cc \ string_util.cc \ strnatcmp.c \ time_util.cc diff --git a/src/ansi_scrubber.cc b/src/base/ansi_scrubber.cc similarity index 92% rename from src/ansi_scrubber.cc rename to src/base/ansi_scrubber.cc index 844e3a64..8b61a44f 100644 --- a/src/ansi_scrubber.cc +++ b/src/base/ansi_scrubber.cc @@ -61,7 +61,7 @@ scrub_ansi_string(std::string& str, string_attrs_t& sa) attr_t attrs = 0; auto bg = nonstd::optional(); auto fg = nonstd::optional(); - auto role = nonstd::optional(); + auto role = nonstd::optional(); size_t lpc; switch (pi.get_substr_start(&caps[2])[0]) { @@ -138,8 +138,10 @@ scrub_ansi_string(std::string& str, string_attrs_t& sa) int role_int; if (sscanf(&(str[caps[1].c_begin]), "%d", &role_int) == 1) { - if (role_int >= 0 && role_int < view_colors::VCR__MAX) { - role = role_int; + role_t role_tmp = (role_t) role_int; + if (role_tmp > role_t::VCR_NONE + && role_tmp < role_t::VCR__MAX) { + role = role_tmp; has_attrs = true; } } @@ -158,16 +160,15 @@ scrub_ansi_string(std::string& str, string_attrs_t& sa) lr.lr_start = caps[0].c_begin; lr.lr_end = -1; if (attrs) { - sa.emplace_back(lr, view_curses::VC_STYLE.value(attrs)); + sa.emplace_back(lr, VC_STYLE.value(attrs)); } - role | [&lr, &sa](int r) { - sa.emplace_back(lr, view_curses::VC_ROLE.value(r)); - }; + role | + [&lr, &sa](role_t r) { sa.emplace_back(lr, VC_ROLE.value(r)); }; fg | [&lr, &sa](int color) { - sa.emplace_back(lr, view_curses::VC_FOREGROUND.value(color)); + sa.emplace_back(lr, VC_FOREGROUND.value(color)); }; bg | [&lr, &sa](int color) { - sa.emplace_back(lr, view_curses::VC_BACKGROUND.value(color)); + sa.emplace_back(lr, VC_BACKGROUND.value(color)); }; } diff --git a/src/ansi_scrubber.hh b/src/base/ansi_scrubber.hh similarity index 100% rename from src/ansi_scrubber.hh rename to src/base/ansi_scrubber.hh diff --git a/src/attr_line.cc b/src/base/attr_line.cc similarity index 89% rename from src/attr_line.cc rename to src/base/attr_line.cc index d395bb1f..0fc0bd92 100644 --- a/src/attr_line.cc +++ b/src/base/attr_line.cc @@ -34,7 +34,7 @@ #include "ansi_scrubber.hh" #include "auto_mem.hh" #include "config.h" -#include "view_curses.hh" +#include "lnav_log.hh" attr_line_t& attr_line_t::with_ansi_string(const char* str, ...) @@ -257,21 +257,49 @@ attr_line_t::apply_hide() struct line_range& lr = sattr.sa_range; std::for_each(sa.begin(), sa.end(), [&](string_attr& attr) { - if (attr.sa_type == &view_curses::VC_STYLE - && lr.contains(attr.sa_range)) { + if (attr.sa_type == &VC_STYLE && lr.contains(attr.sa_range)) { attr.sa_type = &SA_REMOVED; } }); this->al_string.replace(lr.lr_start, lr.length(), "\xE2\x8B\xAE"); shift_string_attrs(sa, lr.lr_start + 1, -(lr.length() - 3)); - sattr.sa_type = &view_curses::VC_ROLE; - sattr.sa_value = view_colors::VCR_HIDDEN; + sattr.sa_type = &VC_ROLE; + sattr.sa_value = role_t::VCR_HIDDEN; lr.lr_end = lr.lr_start + 3; } } } +attr_line_t& +attr_line_t::rtrim() +{ + auto index = this->al_string.length(); + + for (; index > 0; index--) { + if (!isspace(this->al_string[index - 1])) { + break; + } + } + if (index > 0 && index < this->al_string.length()) { + this->erase(index); + } + return *this; +} + +attr_line_t& +attr_line_t::erase(size_t pos, size_t len) +{ + if (len == std::string::npos) { + len = this->al_string.size() - pos; + } + this->al_string.erase(pos, len); + + shift_string_attrs(this->al_attrs, pos, -((int32_t) len)); + + return *this; +} + line_range line_range::intersection(const line_range& other) const { @@ -290,13 +318,14 @@ line_range::intersection(const line_range& other) const line_range& line_range::shift(int32_t start, int32_t amount) { - if (this->lr_start >= start) { + if (start <= this->lr_start) { this->lr_start = std::max(0, this->lr_start + amount); - } - if (this->lr_end != -1 && start <= this->lr_end) { - this->lr_end += amount; - if (this->lr_end < this->lr_start) { - this->lr_end = this->lr_start; + if (this->lr_end != -1) { + this->lr_end = std::max(0, this->lr_end + amount); + } + } else if (this->lr_end != -1) { + if (start < this->lr_end) { + this->lr_end = std::max(this->lr_start, this->lr_end + amount); } } diff --git a/src/attr_line.hh b/src/base/attr_line.hh similarity index 86% rename from src/attr_line.hh rename to src/base/attr_line.hh index f6c52db7..404a8c38 100644 --- a/src/attr_line.hh +++ b/src/base/attr_line.hh @@ -38,10 +38,10 @@ #include -#include "base/intern_string.hh" -#include "base/lnav_log.hh" -#include "base/string_util.hh" +#include "fmt/format.h" +#include "intern_string.hh" #include "string_attr_type.hh" +#include "string_util.hh" /** * Encapsulates a range in a string. @@ -137,18 +137,8 @@ struct line_range { } }; -/** - * Container for attribute values for a substring. - */ -typedef union { - const void* sav_ptr; - int64_t sav_int; -} string_attr_value_t; - struct string_attr { - string_attr( - const struct line_range& lr, - const std::pair& value) + string_attr(const struct line_range& lr, const string_attr_pair& value) : sa_range(lr), sa_type(value.first), sa_value(value.second) { } @@ -444,9 +434,7 @@ public: } template - attr_line_t& append( - S str, - const std::pair& value) + attr_line_t& append(S str, const string_attr_pair& value) { size_t start_len = this->al_string.length(); @@ -459,6 +447,63 @@ public: return *this; } + template + attr_line_t& append(const std::pair& value) + { + size_t start_len = this->al_string.length(); + + this->al_string.append(std::move(value.first)); + + line_range lr{(int) start_len, (int) this->al_string.length()}; + + this->al_attrs.emplace_back(lr, value.second); + + return *this; + } + + template + attr_line_t& append_quoted(const std::pair& value) + { + this->al_string.append("\u201c"); + + size_t start_len = this->al_string.length(); + + this->al_string.append(std::move(value.first)); + + line_range lr{(int) start_len, (int) this->al_string.length()}; + + this->al_attrs.emplace_back(lr, value.second); + + this->al_string.append("\u201d"); + + return *this; + } + + attr_line_t& append_quoted(const intern_string_t str) + { + this->al_string.append("\u201c"); + this->al_string.append(str.get(), str.size()); + this->al_string.append("\u201d"); + + return *this; + } + + template + attr_line_t& append_quoted(S s) + { + this->al_string.append("\u201c"); + this->al_string.append(std::move(s)); + this->al_string.append("\u201d"); + + return *this; + } + + attr_line_t& append(const intern_string_t str) + { + this->al_string.append(str.get(), str.size()); + return *this; + } + template attr_line_t& append(S str) { @@ -466,6 +511,14 @@ public: return *this; } + template + attr_line_t& append(fmt::string_view format_str, const Args&... args) + { + this->template append(fmt::vformat( + format_str, fmt::make_args_checked(format_str, args...))); + return *this; + } + attr_line_t& append(const char* str, size_t len) { this->al_string.append(str, len); @@ -507,14 +560,9 @@ public: return *this; } - attr_line_t& erase(size_t pos, size_t len = std::string::npos) - { - this->al_string.erase(pos, len); + attr_line_t& erase(size_t pos, size_t len = std::string::npos); - shift_string_attrs(this->al_attrs, pos, -((int32_t) len)); - - return *this; - } + attr_line_t& rtrim(); attr_line_t& erase_utf8_chars(size_t start) { @@ -548,7 +596,8 @@ public: return this->al_string.substr(lr.lr_start, lr.sublen(this->al_string)); } - string_fragment to_string_fragment(string_attrs_t::const_iterator iter) + string_fragment to_string_fragment( + string_attrs_t::const_iterator iter) const { return string_fragment(this->al_string.c_str(), iter->sa_range.lr_start, @@ -571,6 +620,11 @@ public: return this->length() == 0; } + bool blank() const + { + return is_blank(this->al_string); + } + /** Clear the string and the attributes for the string. */ attr_line_t& clear() { @@ -584,11 +638,18 @@ public: void split_lines(std::vector& lines) const; + std::vector split_lines() const + { + std::vector retval; + + this->split_lines(retval); + return retval; + } + size_t nearest_text(size_t x) const; void apply_hide(); -private: const static size_t RESERVE_SIZE = 128; std::string al_string; diff --git a/src/auto_mem.hh b/src/base/auto_mem.hh similarity index 99% rename from src/auto_mem.hh rename to src/base/auto_mem.hh index 6c1b3d60..36068693 100644 --- a/src/auto_mem.hh +++ b/src/base/auto_mem.hh @@ -40,7 +40,7 @@ #include "base/result.h" -typedef void (*free_func_t)(void*); +using free_func_t = void (*)(void*); /** * Resource management class for memory allocated by a custom allocator. diff --git a/src/base/fs_util.cc b/src/base/fs_util.cc index 1a7e307a..ff1dcfc1 100644 --- a/src/base/fs_util.cc +++ b/src/base/fs_util.cc @@ -108,5 +108,19 @@ build_path(const std::vector& paths) return retval; } +Result +stat_file(const ghc::filesystem::path& path) +{ + struct stat retval; + + if (statp(path, &retval) == 0) { + return Ok(retval); + } + + return Err(fmt::format(FMT_STRING("failed to find file: {} -- {}"), + path.string(), + strerror(errno))); +} + } // namespace filesystem } // namespace lnav diff --git a/src/base/fs_util.hh b/src/base/fs_util.hh index 1fd28d68..61243998 100644 --- a/src/base/fs_util.hh +++ b/src/base/fs_util.hh @@ -62,6 +62,8 @@ Result open_file(const ghc::filesystem::path& path, int flags, mode_t mode); +Result stat_file(const ghc::filesystem::path& path); + Result, std::string> open_temp_file( const ghc::filesystem::path& pattern); diff --git a/src/base/humanize.cc b/src/base/humanize.cc index af63f78a..5e96bf38 100644 --- a/src/base/humanize.cc +++ b/src/base/humanize.cc @@ -38,7 +38,7 @@ namespace humanize { std::string -file_size(file_ssize_t value) +file_size(file_ssize_t value, alignment align) { static const double LN1024 = log(1024.0); static const std::vector UNITS = { @@ -56,13 +56,21 @@ file_size(file_ssize_t value) } if (value == 0) { - return "0.0 B"; + switch (align) { + case alignment::none: + return "0B"; + case alignment::columnar: + return "0.0 B"; + } } auto exp = floor(std::min(log(value) / LN1024, (double) (UNITS.size() - 1))); auto divisor = pow(1024, exp); + if (align == alignment::none && divisor <= 1) { + return fmt::format(FMT_STRING("{}B"), value, UNITS[exp]); + } return fmt::format(FMT_STRING("{:.1f}{}B"), divisor == 0 ? value : value / divisor, UNITS[exp]); diff --git a/src/base/humanize.file_size.tests.cc b/src/base/humanize.file_size.tests.cc index 133b9a12..ff70c7e3 100644 --- a/src/base/humanize.file_size.tests.cc +++ b/src/base/humanize.file_size.tests.cc @@ -36,11 +36,16 @@ TEST_CASE("humanize::file_size") { - CHECK(humanize::file_size(0) == "0.0 B"); - CHECK(humanize::file_size(1) == "1.0 B"); - CHECK(humanize::file_size(1024) == "1.0KB"); - CHECK(humanize::file_size(1500) == "1.5KB"); - CHECK(humanize::file_size(55LL * 784LL * 1024LL * 1024LL) == "42.1GB"); - CHECK(humanize::file_size(-1LL) == "Unknown"); - CHECK(humanize::file_size(std::numeric_limits::max()) == "8.0EB"); + CHECK(humanize::file_size(0, humanize::alignment::columnar) == "0.0 B"); + CHECK(humanize::file_size(1, humanize::alignment::columnar) == "1.0 B"); + CHECK(humanize::file_size(1024, humanize::alignment::columnar) == "1.0KB"); + CHECK(humanize::file_size(1500, humanize::alignment::columnar) == "1.5KB"); + CHECK(humanize::file_size(55LL * 784LL * 1024LL * 1024LL, + humanize::alignment::columnar) + == "42.1GB"); + CHECK(humanize::file_size(-1LL, humanize::alignment::columnar) + == "Unknown"); + CHECK(humanize::file_size(std::numeric_limits::max(), + humanize::alignment::columnar) + == "8.0EB"); } diff --git a/src/base/humanize.hh b/src/base/humanize.hh index 306c88df..3f9f66cd 100644 --- a/src/base/humanize.hh +++ b/src/base/humanize.hh @@ -38,13 +38,18 @@ namespace humanize { +enum class alignment { + none, + columnar, +}; + /** * Format the given size as a human-friendly string. * * @param value The value to format. * @return The formatted string. */ -std::string file_size(file_ssize_t value); +std::string file_size(file_ssize_t value, alignment align); const std::string& sparkline(double value, nonstd::optional upper); diff --git a/src/base/lnav.console.cc b/src/base/lnav.console.cc new file mode 100644 index 00000000..6d4d0ffc --- /dev/null +++ b/src/base/lnav.console.cc @@ -0,0 +1,265 @@ +/** + * Copyright (c) 2022, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "lnav.console.hh" + +#include "config.h" +#include "fmt/color.h" +#include "view_curses.hh" + +namespace lnav { +namespace console { + +user_message +user_message::error(const attr_line_t& al) +{ + user_message retval; + + retval.um_level = level::error; + retval.um_message.append(al); + return retval; +} + +user_message +user_message::info(const attr_line_t& al) +{ + user_message retval; + + retval.um_level = level::info; + retval.um_message.append(al); + return retval; +} + +user_message +user_message::ok(const attr_line_t& al) +{ + user_message retval; + + retval.um_level = level::ok; + retval.um_message.append(al); + return retval; +} + +user_message +user_message::warning(const attr_line_t& al) +{ + user_message retval; + + retval.um_level = level::warning; + retval.um_message.append(al); + return retval; +} + +attr_line_t +user_message::to_attr_line(std::set flags) const +{ + attr_line_t retval; + + if (flags.count(render_flags::prefix)) { + switch (this->um_level) { + case level::ok: + retval.append(lnav::roles::ok("\u2714 ")); + break; + case level::info: + retval.append(lnav::roles::status("\u24d8 info")).append(": "); + break; + case level::warning: + retval.append(lnav::roles::warning("\u26a0 warning")) + .append(": "); + break; + case level::error: + retval.append(lnav::roles::error("\u2718 error")).append(": "); + break; + } + } + + retval.append(this->um_message).append("\n"); + if (!this->um_reason.empty()) { + bool first_line = true; + for (const auto& line : this->um_reason.split_lines()) { + attr_line_t prefix; + + if (first_line) { + prefix.append(lnav::roles::error(" reason")).append(": "); + first_line = false; + } else { + prefix.append(lnav::roles::error(" | ")); + } + retval.append(prefix).append(line).append("\n"); + } + } + if (!this->um_snippets.empty()) { + for (const auto& snip : this->um_snippets) { + attr_line_t header; + + header.append(lnav::roles::comment(" --> ")) + .append(lnav::roles::file(snip.s_source)); + if (snip.s_line > 0) { + header.append(":").append(FMT_STRING("{}"), snip.s_line); + if (snip.s_column > 0) { + header.append(":").append(FMT_STRING("{}"), snip.s_column); + } + } + retval.append(header).append("\n"); + if (!snip.s_content.blank()) { + for (const auto& line : snip.s_content.split_lines()) { + retval.append(lnav::roles::comment(" | ")) + .append(line) + .append("\n"); + } + } + } + } + if (!this->um_notes.empty()) { + bool first_line = true; + for (const auto& note : this->um_notes) { + for (const auto& line : note.split_lines()) { + attr_line_t prefix; + + if (first_line) { + prefix.append(lnav::roles::comment(" = note")).append(": "); + first_line = false; + } else { + prefix.append(" "); + } + + retval.append(prefix).append(line).append("\n"); + } + } + } + if (!this->um_help.empty()) { + bool first_line = true; + for (const auto& line : this->um_help.split_lines()) { + attr_line_t prefix; + + if (first_line) { + prefix.append(lnav::roles::comment(" = help")).append(": "); + first_line = false; + } else { + prefix.append(" "); + } + + retval.append(prefix).append(line).append("\n"); + } + } + + return retval; +} + +void +println(FILE* file, const attr_line_t& al) +{ + const auto& str = al.get_string(); + + if (!isatty(fileno(file))) { + fmt::print(file, "{}\n", str); + return; + } + + string_attrs_t style_attrs; + + for (const auto& sa : al.get_attrs()) { + if (sa.sa_type != &VC_ROLE) { + continue; + } + + style_attrs.emplace_back(sa); + } + + std::sort(style_attrs.begin(), style_attrs.end(), [](auto lhs, auto rhs) { + return lhs.sa_range < rhs.sa_range; + }); + + auto start = size_t{0}; + for (const auto& attr : style_attrs) { + fmt::print( + file, "{}", str.substr(start, attr.sa_range.lr_start - start)); + if (attr.sa_type == &VC_ROLE) { + auto saw = string_attr_wrapper(&attr); + auto role = saw.get(); + auto line_style = fmt::text_style(); + + switch (role) { + case role_t::VCR_ERROR: + line_style = fmt::fg(fmt::terminal_color::red); + break; + case role_t::VCR_WARNING: + line_style = fmt::fg(fmt::terminal_color::yellow); + break; + case role_t::VCR_COMMENT: + line_style = fmt::fg(fmt::terminal_color::cyan); + break; + case role_t::VCR_OK: + line_style = fmt::emphasis::bold + | fmt::fg(fmt::terminal_color::red); + break; + case role_t::VCR_STATUS: + line_style = fmt::emphasis::bold + | fmt::fg(fmt::terminal_color::magenta); + break; + case role_t::VCR_VARIABLE: + line_style = fmt::emphasis::underline; + break; + case role_t::VCR_SYMBOL: + case role_t::VCR_NUMBER: + case role_t::VCR_FILE: + line_style = fmt::emphasis::bold; + break; + case role_t::VCR_H1: + case role_t::VCR_H2: + case role_t::VCR_H3: + case role_t::VCR_H4: + case role_t::VCR_H5: + case role_t::VCR_H6: + line_style = fmt::emphasis::underline; + break; + default: + break; + } + fmt::print( + file, + line_style, + "{}", + str.substr(attr.sa_range.lr_start, attr.sa_range.length())); + } + start = attr.sa_range.lr_end; + } + fmt::print(file, "{}\n", str.substr(start)); +} + +void +print(FILE* file, const user_message& um) +{ + println(file, um.to_attr_line().rtrim()); +} + +} // namespace console +} // namespace lnav diff --git a/src/base/lnav.console.hh b/src/base/lnav.console.hh new file mode 100644 index 00000000..b03f8600 --- /dev/null +++ b/src/base/lnav.console.hh @@ -0,0 +1,149 @@ +/** + * Copyright (c) 2022, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef lnav_console_hh +#define lnav_console_hh + +#include +#include + +#include "base/attr_line.hh" + +namespace lnav { +namespace console { + +void println(FILE* file, const attr_line_t& al); + +struct snippet { + static snippet from(std::string src, const attr_line_t& content) + { + snippet retval; + + retval.s_source = std::move(src); + retval.s_content = content; + return retval; + } + + snippet& with_line(int32_t line) + { + this->s_line = line; + return *this; + } + + snippet& with_column(int32_t column) + { + this->s_column = column; + return *this; + } + + std::string s_source; + int32_t s_line{0}; + int32_t s_column{0}; + attr_line_t s_content; +}; + +struct user_message { + enum class level { + ok, + info, + warning, + error, + }; + + static user_message error(const attr_line_t& al); + + static user_message warning(const attr_line_t& al); + + static user_message info(const attr_line_t& al); + + static user_message ok(const attr_line_t& al); + + user_message& with_reason(const attr_line_t& al) + { + this->um_reason = al; + this->um_reason.rtrim(); + return *this; + } + + user_message& with_errno_reason() + { + this->um_reason = strerror(errno); + return *this; + } + + user_message& with_snippet(const snippet& sn) + { + this->um_snippets.emplace_back(sn); + return *this; + } + + user_message& with_snippets(std::vector snippets) + { + this->um_snippets.insert(this->um_snippets.end(), + std::make_move_iterator(snippets.begin()), + std::make_move_iterator(snippets.end())); + return *this; + } + + user_message& with_note(const attr_line_t& al) + { + this->um_notes.emplace_back(al); + + return *this; + } + + user_message& with_help(const attr_line_t& al) + { + this->um_help = al; + this->um_help.rtrim(); + + return *this; + } + + enum class render_flags { + prefix, + }; + + attr_line_t to_attr_line(std::set flags + = {render_flags::prefix}) const; + + level um_level{level::ok}; + attr_line_t um_message; + std::vector um_snippets; + attr_line_t um_reason; + std::vector um_notes; + attr_line_t um_help; +}; + +void print(FILE* file, const user_message& um); + +} // namespace console +} // namespace lnav + +#endif diff --git a/src/string_attr_type.cc b/src/base/string_attr_type.cc similarity index 86% rename from src/string_attr_type.cc rename to src/base/string_attr_type.cc index c8e4c485..956003f1 100644 --- a/src/string_attr_type.cc +++ b/src/base/string_attr_type.cc @@ -38,3 +38,10 @@ string_attr_type SA_FORMAT("format"); string_attr_type SA_REMOVED("removed"); string_attr_type SA_INVALID("invalid"); string_attr_type SA_ERROR("error"); + +string_attr_type VC_ROLE("role"); +string_attr_type VC_ROLE_FG("role-fg"); +string_attr_type VC_STYLE("style"); +string_attr_type VC_GRAPHIC("graphic"); +string_attr_type VC_FOREGROUND("foreground"); +string_attr_type VC_BACKGROUND("background"); diff --git a/src/base/string_attr_type.hh b/src/base/string_attr_type.hh new file mode 100644 index 00000000..88b0b35b --- /dev/null +++ b/src/base/string_attr_type.hh @@ -0,0 +1,346 @@ +/** + * Copyright (c) 2020, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef lnav_string_attr_type_hh +#define lnav_string_attr_type_hh + +#include +#include + +#include + +#include "base/intern_string.hh" +#include "mapbox/variant.hpp" + +class logfile; +struct bookmark_metadata; + +/** Roles that can be mapped to curses attributes using attrs_for_role() */ +enum class role_t : int32_t { + VCR_NONE = -1, + + VCR_TEXT, /*< Raw text. */ + VCR_IDENTIFIER, + VCR_SEARCH, /*< A search hit. */ + VCR_OK, + VCR_ERROR, /*< An error message. */ + VCR_WARNING, /*< A warning message. */ + VCR_ALT_ROW, /*< Highlight for alternating rows in a list */ + VCR_HIDDEN, + VCR_ADJUSTED_TIME, + VCR_SKEWED_TIME, + VCR_OFFSET_TIME, + VCR_INVALID_MSG, + VCR_STATUS, /*< Normal status line text. */ + VCR_WARN_STATUS, + VCR_ALERT_STATUS, /*< Alert status line text. */ + VCR_ACTIVE_STATUS, /*< */ + VCR_ACTIVE_STATUS2, /*< */ + VCR_STATUS_TITLE, + VCR_STATUS_SUBTITLE, + VCR_STATUS_STITCH_TITLE_TO_SUB, + VCR_STATUS_STITCH_SUB_TO_TITLE, + VCR_STATUS_STITCH_SUB_TO_NORMAL, + VCR_STATUS_STITCH_NORMAL_TO_SUB, + VCR_STATUS_STITCH_TITLE_TO_NORMAL, + VCR_STATUS_STITCH_NORMAL_TO_TITLE, + VCR_STATUS_TITLE_HOTKEY, + VCR_STATUS_DISABLED_TITLE, + VCR_STATUS_HOTKEY, + VCR_INACTIVE_STATUS, + VCR_INACTIVE_ALERT_STATUS, + VCR_SCROLLBAR, + VCR_SCROLLBAR_ERROR, + VCR_SCROLLBAR_WARNING, + VCR_FOCUSED, + VCR_DISABLED_FOCUSED, + VCR_POPUP, + VCR_COLOR_HINT, + + VCR_KEYWORD, + VCR_STRING, + VCR_COMMENT, + VCR_DOC_DIRECTIVE, + VCR_VARIABLE, + VCR_SYMBOL, + VCR_NUMBER, + VCR_RE_SPECIAL, + VCR_RE_REPEAT, + VCR_FILE, + + VCR_DIFF_DELETE, /*< Deleted line in a diff. */ + VCR_DIFF_ADD, /*< Added line in a diff. */ + VCR_DIFF_SECTION, /*< Section marker in a diff. */ + + VCR_LOW_THRESHOLD, + VCR_MED_THRESHOLD, + VCR_HIGH_THRESHOLD, + + VCR_H1, + VCR_H2, + VCR_H3, + VCR_H4, + VCR_H5, + VCR_H6, + + VCR__MAX +}; + +using string_attr_value = mapbox::util::variant, + bookmark_metadata*>; + +class string_attr_type_base { +public: + explicit string_attr_type_base(const char* name) noexcept : sat_name(name) + { + } + + const char* const sat_name; +}; + +using string_attr_pair + = std::pair; + +template +class string_attr_type : public string_attr_type_base { +public: + using value_type = T; + + explicit string_attr_type(const char* name) noexcept + : string_attr_type_base(name) + { + } + + template + std::enable_if_t::value, string_attr_pair> value( + const U& val) const + { + return std::make_pair(this, val); + } + + template + std::enable_if_t::value, string_attr_pair> value() const + { + return std::make_pair(this, string_attr_value{}); + } +}; + +extern string_attr_type SA_ORIGINAL_LINE; +extern string_attr_type SA_BODY; +extern string_attr_type SA_HIDDEN; +extern string_attr_type SA_FORMAT; +extern string_attr_type SA_REMOVED; +extern string_attr_type SA_INVALID; +extern string_attr_type SA_ERROR; + +extern string_attr_type VC_ROLE; +extern string_attr_type VC_ROLE_FG; +extern string_attr_type VC_STYLE; +extern string_attr_type VC_GRAPHIC; +extern string_attr_type VC_FOREGROUND; +extern string_attr_type VC_BACKGROUND; + +namespace lnav { +namespace roles { + +template +inline std::pair +error(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_ERROR)); +} + +template +inline std::pair +warning(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_WARNING)); +} + +template +inline std::pair +status(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_STATUS)); +} + +template +inline std::pair +ok(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_OK)); +} + +template +inline std::pair +file(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_FILE)); +} + +template +inline std::pair +symbol(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_SYMBOL)); +} + +template +inline std::pair +keyword(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_KEYWORD)); +} + +template +inline std::pair +variable(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_VARIABLE)); +} + +template +inline std::pair +number(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_NUMBER)); +} + +template +inline std::pair +comment(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_COMMENT)); +} + +template +inline std::pair +h1(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_H1)); +} + +template +inline std::pair +h2(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_H2)); +} + +template +inline std::pair +h3(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_H3)); +} + +template +inline std::pair +h4(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_H4)); +} + +template +inline std::pair +h5(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_H5)); +} + +template +inline std::pair +h6(S str) +{ + return std::make_pair(std::move(str), + VC_ROLE.template value(role_t::VCR_H6)); +} + +namespace literals { + +inline std::pair operator"" _symbol( + const char* str, std::size_t len) +{ + return std::make_pair(std::string(str, len), + VC_ROLE.template value(role_t::VCR_SYMBOL)); +} + +inline std::pair operator"" _variable( + const char* str, std::size_t len) +{ + return std::make_pair(std::string(str, len), + VC_ROLE.template value(role_t::VCR_VARIABLE)); +} + +inline std::pair operator"" _h1(const char* str, + std::size_t len) +{ + return std::make_pair(std::string(str, len), + VC_ROLE.template value(role_t::VCR_H1)); +} + +inline std::pair operator"" _h2(const char* str, + std::size_t len) +{ + return std::make_pair(std::string(str, len), + VC_ROLE.template value(role_t::VCR_H2)); +} + +inline std::pair operator"" _h3(const char* str, + std::size_t len) +{ + return std::make_pair(std::string(str, len), + VC_ROLE.template value(role_t::VCR_H3)); +} + +} // namespace literals + +} // namespace roles +} // namespace lnav + +#endif diff --git a/src/base/string_util.cc b/src/base/string_util.cc index 329e5b34..9f6b5019 100644 --- a/src/base/string_util.cc +++ b/src/base/string_util.cc @@ -27,6 +27,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -253,6 +254,13 @@ strtonum(T& num_out, const char* string, size_t len) return retval; } +bool +is_blank(const std::string& str) +{ + return std::all_of( + str.begin(), str.end(), [](const auto ch) { return isspace(ch); }); +} + template size_t strtonum(long long& num_out, const char* string, size_t len); diff --git a/src/base/string_util.hh b/src/base/string_util.hh index 43e21a04..fe5efc7b 100644 --- a/src/base/string_util.hh +++ b/src/base/string_util.hh @@ -188,6 +188,8 @@ utf8_string_length(const std::string& str) bool is_url(const char* fn); +bool is_blank(const std::string& str); + size_t abbreviate_str(char* str, size_t len, size_t max_len); void split_ws(const std::string& str, std::vector& toks_out); diff --git a/src/bookmarks.cc b/src/bookmarks.cc index f26f7dcb..19dcff5f 100644 --- a/src/bookmarks.cc +++ b/src/bookmarks.cc @@ -33,4 +33,53 @@ #include "config.h" -std::set bookmark_metadata::KNOWN_TAGS; +std::unordered_set bookmark_metadata::KNOWN_TAGS; + +void +bookmark_metadata::add_tag(const std::string& tag) +{ + if (std::find(this->bm_tags.begin(), this->bm_tags.end(), tag) + == this->bm_tags.end()) + { + this->bm_tags.push_back(tag); + } +} + +bool +bookmark_metadata::remove_tag(const std::string& tag) +{ + auto iter = std::find(this->bm_tags.begin(), this->bm_tags.end(), tag); + bool retval = false; + + if (iter != this->bm_tags.end()) { + this->bm_tags.erase(iter); + retval = true; + } + return retval; +} + +bool +bookmark_metadata::empty() const +{ + return this->bm_name.empty() && this->bm_comment.empty() + && this->bm_tags.empty(); +} + +void +bookmark_metadata::clear() +{ + this->bm_comment.clear(); + this->bm_tags.clear(); +} + +bookmark_type_t* +bookmark_type_t::find_type(const std::string& name) +{ + auto iter = std::find_if(type_begin(), type_end(), mark_eq(name)); + bookmark_type_t* retval = nullptr; + + if (iter != type_end()) { + retval = (*iter); + } + return retval; +} diff --git a/src/bookmarks.hh b/src/bookmarks.hh index 8a1e6fb2..d72bdfc5 100644 --- a/src/bookmarks.hh +++ b/src/bookmarks.hh @@ -34,51 +34,26 @@ #include #include -#include +#include #include #include #include "base/lnav_log.hh" struct bookmark_metadata { - static std::set KNOWN_TAGS; + static std::unordered_set KNOWN_TAGS; std::string bm_name; std::string bm_comment; std::vector bm_tags; - void add_tag(const std::string& tag) - { - if (std::find(this->bm_tags.begin(), this->bm_tags.end(), tag) - == this->bm_tags.end()) - { - this->bm_tags.push_back(tag); - } - }; + void add_tag(const std::string& tag); - bool remove_tag(const std::string& tag) - { - auto iter = std::find(this->bm_tags.begin(), this->bm_tags.end(), tag); - bool retval = false; - - if (iter != this->bm_tags.end()) { - this->bm_tags.erase(iter); - retval = true; - } - return retval; - }; + bool remove_tag(const std::string& tag); - bool empty() const - { - return this->bm_name.empty() && this->bm_comment.empty() - && this->bm_tags.empty(); - }; + bool empty() const; - void clear() - { - this->bm_comment.clear(); - this->bm_tags.clear(); - }; + void clear(); }; /** @@ -96,12 +71,12 @@ struct bookmark_metadata { */ template class bookmark_vector : public std::vector { - typedef std::vector base_vector; + using base_vector = std::vector; public: - typedef typename base_vector::size_type size_type; - typedef typename base_vector::iterator iterator; - typedef typename base_vector::const_iterator const_iterator; + using size_type = typename base_vector::size_type; + using iterator = typename base_vector::iterator; + using const_iterator = typename base_vector::const_iterator; /** * Insert a bookmark into this vector, but only if it is not already in the @@ -111,11 +86,11 @@ public: */ iterator insert_once(LineType vl) { - iterator lb, retval; + iterator retval; require(vl >= 0); - lb = std::lower_bound(this->begin(), this->end(), vl); + auto lb = std::lower_bound(this->begin(), this->end(), vl); if (lb == this->end() || *lb != vl) { this->insert(lb, vl); retval = this->end(); @@ -132,11 +107,11 @@ public: if (stop == LineType(-1)) { return std::make_pair(lb, this->end()); - } else { - auto up = std::upper_bound(this->begin(), this->end(), stop); - - return std::make_pair(lb, up); } + + auto up = std::upper_bound(this->begin(), this->end(), stop); + + return std::make_pair(lb, up); }; /** @@ -164,45 +139,36 @@ public: */ class bookmark_type_t { public: - typedef std::vector::iterator type_iterator; + using type_iterator = std::vector::iterator; static type_iterator type_begin() { return get_all_types().begin(); - }; + } static type_iterator type_end() { return get_all_types().end(); - }; - - static bookmark_type_t* find_type(const std::string& name) - { - auto iter = find_if(type_begin(), type_end(), mark_eq(name)); - bookmark_type_t* retval = nullptr; + } - if (iter != type_end()) { - retval = (*iter); - } - return retval; - }; + static bookmark_type_t* find_type(const std::string& name); static std::vector& get_all_types() { static std::vector all_types; return all_types; - }; + } explicit bookmark_type_t(std::string name) : bt_name(std::move(name)) { get_all_types().push_back(this); - }; + } const std::string& get_name() const { return this->bt_name; - }; + } private: struct mark_eq { @@ -227,7 +193,7 @@ bookmark_vector::next(LineType start) const require(start >= -1); - auto ub = upper_bound(this->cbegin(), this->cend(), start); + auto ub = std::upper_bound(this->cbegin(), this->cend(), start); if (ub != this->cend()) { retval = *ub; } @@ -245,7 +211,7 @@ bookmark_vector::prev(LineType start) const require(start >= 0); - auto lb = lower_bound(this->cbegin(), this->cend(), start); + auto lb = std::lower_bound(this->cbegin(), this->cend(), start); if (lb != this->cbegin()) { lb -= 1; retval = *lb; @@ -261,7 +227,7 @@ bookmark_vector::prev(LineType start) const */ template struct bookmarks { - typedef std::map > type; + using type = std::map>; }; #endif diff --git a/src/bottom_status_source.cc b/src/bottom_status_source.cc index 2a0b4e3d..d7d06df9 100644 --- a/src/bottom_status_source.cc +++ b/src/bottom_status_source.cc @@ -158,18 +158,18 @@ void bottom_status_source::update_hits(textview_curses* tc) { status_field& sf = this->bss_fields[BSF_HITS]; - view_colors::role_t new_role; + role_t new_role; if (tc->is_searching()) { this->bss_hit_spinner += 1; if (this->bss_hit_spinner % 2) { - new_role = view_colors::VCR_ACTIVE_STATUS; + new_role = role_t::VCR_ACTIVE_STATUS; } else { - new_role = view_colors::VCR_ACTIVE_STATUS2; + new_role = role_t::VCR_ACTIVE_STATUS2; } sf.set_cylon(true); } else { - new_role = view_colors::VCR_STATUS; + new_role = role_t::VCR_STATUS; sf.set_cylon(false); } // this->bss_error.clear(); @@ -186,7 +186,7 @@ bottom_status_source::update_loading(file_off_t off, file_size_t total) if (total == 0 || (size_t) off == total) { sf.set_cylon(false); - sf.set_role(view_colors::VCR_STATUS); + sf.set_role(role_t::VCR_STATUS); if (this->bss_paused) { sf.set_value("\xE2\x80\x96 Paused"); } else { @@ -199,7 +199,7 @@ bottom_status_source::update_loading(file_off_t off, file_size_t total) this->bss_load_percent = pct; sf.set_cylon(true); - sf.set_role(view_colors::VCR_ACTIVE_STATUS2); + sf.set_role(role_t::VCR_ACTIVE_STATUS2); sf.set_value(" Loading %2d%% ", pct); } } diff --git a/src/bottom_status_source.hh b/src/bottom_status_source.hh index caaebc7c..d25fd066 100644 --- a/src/bottom_status_source.hh +++ b/src/bottom_status_source.hh @@ -85,9 +85,9 @@ public: void update_loading(file_off_t off, file_size_t total); private: - status_field bss_prompt{1024, view_colors::VCR_STATUS}; - status_field bss_error{1024, view_colors::VCR_ALERT_STATUS}; - status_field bss_line_error{1024, view_colors::VCR_ALERT_STATUS}; + status_field bss_prompt{1024, role_t::VCR_STATUS}; + status_field bss_error{1024, role_t::VCR_ALERT_STATUS}; + status_field bss_line_error{1024, role_t::VCR_ALERT_STATUS}; status_field bss_fields[BSF__MAX]; int bss_hit_spinner{0}; int bss_load_percent{0}; diff --git a/src/command_executor.cc b/src/command_executor.cc index 3384ec1c..86e9441e 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -31,12 +31,14 @@ #include "command_executor.hh" +#include "base/ansi_scrubber.hh" #include "base/fs_util.hh" #include "base/injector.hh" #include "base/string_util.hh" #include "bound_tags.hh" #include "config.h" #include "db_sub_source.hh" +#include "help_text_formatter.hh" #include "lnav.hh" #include "lnav_config.hh" #include "lnav_util.hh" @@ -101,14 +103,14 @@ sql_progress_finished() lnav_data.ld_views[LNV_DB].redo_search(); } -Result execute_from_file( +Result execute_from_file( exec_context& ec, const ghc::filesystem::path& path, int line_number, char mode, const std::string& cmdline); -Result +Result execute_command(exec_context& ec, const std::string& cmdline) { std::vector args; @@ -122,15 +124,18 @@ execute_command(exec_context& ec, const std::string& cmdline) if ((iter = lnav_commands.find(args[0])) == lnav_commands.end()) { return ec.make_error("unknown command - {}", args[0]); - } else { - return iter->second->c_func(ec, cmdline, args); } + + ec.ec_current_help = &iter->second->c_help; + auto retval = iter->second->c_func(ec, cmdline, args); + ec.ec_current_help = nullptr; + return retval; } return ec.make_error("no command to execute"); } -Result +Result execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) { db_label_source& dls = lnav_data.ld_db_row_source; @@ -153,7 +158,11 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) auto cmd_iter = sql_cmd_map->find(args[0]); if (cmd_iter != sql_cmd_map->end()) { - return cmd_iter->second->c_func(ec, stmt_str, args); + ec.ec_current_help = &cmd_iter->second->c_help; + auto retval = cmd_iter->second->c_func(ec, stmt_str, args); + ec.ec_current_help = nullptr; + + return retval; } } @@ -163,9 +172,12 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) ec.ec_accumulator->clear(); - std::pair source = ec.ec_source.top(); - sql_progress_guard progress_guard( - sql_progress, sql_progress_finished, source.first, source.second); + auto source = ec.ec_source.top(); + sql_progress_guard progress_guard(sql_progress, + sql_progress_finished, + source.s_source, + source.s_line, + source.s_content); gettimeofday(&start_tv, nullptr); retcode = sqlite3_prepare_v2( lnav_data.ld_db.in(), stmt_str.c_str(), -1, stmt.out(), nullptr); @@ -174,7 +186,8 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) alt_msg = ""; return ec.make_error("{}", errmsg); - } else if (stmt == nullptr) { + } + if (stmt == nullptr) { alt_msg = ""; return ec.make_error("No statement given"); } @@ -301,8 +314,11 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) log_error("sqlite3_step error code: %d", retcode); errmsg = sqlite3_errmsg(lnav_data.ld_db); + if (startswith(errmsg, "lnav-error:")) { + return Err(lnav::from_json( + &errmsg[11])); + } return ec.make_error("{}", errmsg); - break; } } } @@ -409,7 +425,7 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) return Ok(retval); } -static Result +static Result execute_file_contents(exec_context& ec, const ghc::filesystem::path& path, bool multiline) @@ -490,7 +506,7 @@ execute_file_contents(exec_context& ec, return Ok(retval); } -Result +Result execute_file(exec_context& ec, const std::string& path_and_args, bool multiline) { available_scripts scripts; @@ -583,7 +599,7 @@ execute_file(exec_context& ec, const std::string& path_and_args, bool multiline) return Ok(retval); } -Result +Result execute_from_file(exec_context& ec, const ghc::filesystem::path& path, int line_number, @@ -591,7 +607,7 @@ execute_from_file(exec_context& ec, const std::string& cmdline) { std::string retval, alt_msg; - auto _sg = ec.enter_source(path.string(), line_number); + auto _sg = ec.enter_source(path.string(), line_number, cmdline); switch (mode) { case ':': @@ -621,7 +637,7 @@ execute_from_file(exec_context& ec, return Ok(retval); } -Result +Result execute_any(exec_context& ec, const std::string& cmdline_with_mode) { std::string retval, alt_msg, cmdline = cmdline_with_mode.substr(1); @@ -663,7 +679,8 @@ execute_any(exec_context& ec, const std::string& cmdline_with_mode) void execute_init_commands( exec_context& ec, - std::vector, std::string>>& msgs) + std::vector, + std::string>>& msgs) { if (lnav_data.ld_cmd_init_done) { return; @@ -678,29 +695,30 @@ execute_init_commands( wait_for_children(); - ec.ec_source.emplace("command-option", option_index++); - switch (cmd.at(0)) { - case ':': - msgs.emplace_back(execute_command(ec, cmd.substr(1)), alt_msg); - break; - case '/': - lnav_data.ld_view_stack.top() | - [cmd](auto tc) { tc->execute_search(cmd.substr(1)); }; - break; - case ';': - setup_logline_table(ec); - msgs.emplace_back(execute_sql(ec, cmd.substr(1), alt_msg), - alt_msg); - break; - case '|': - msgs.emplace_back(execute_file(ec, cmd.substr(1)), alt_msg); - break; - } - - rescan_files(); - rebuild_indexes_repeatedly(); + { + auto _sg = ec.enter_source("command-option", option_index++, cmd); + switch (cmd.at(0)) { + case ':': + msgs.emplace_back(execute_command(ec, cmd.substr(1)), + alt_msg); + break; + case '/': + lnav_data.ld_view_stack.top() | + [cmd](auto tc) { tc->execute_search(cmd.substr(1)); }; + break; + case ';': + setup_logline_table(ec); + msgs.emplace_back(execute_sql(ec, cmd.substr(1), alt_msg), + alt_msg); + break; + case '|': + msgs.emplace_back(execute_file(ec, cmd.substr(1)), alt_msg); + break; + } - ec.ec_source.pop(); + rescan_files(); + rebuild_indexes_repeatedly(); + } } lnav_data.ld_commands.clear(); @@ -854,19 +872,6 @@ add_global_vars(exec_context& ec) } } -std::string -exec_context::get_error_prefix() -{ - if (this->ec_source.size() <= 1) { - return "error: "; - } - - std::pair source = this->ec_source.top(); - - return fmt::format( - FMT_STRING("{}:{}: error: "), source.first, source.second); -} - void exec_context::set_output(const std::string& name, FILE* file, @@ -903,10 +908,28 @@ exec_context::exec_context(std::vector* line_values, { this->ec_local_vars.push(std::map()); this->ec_path_stack.emplace_back("."); - this->ec_source.emplace("command", 1); + this->ec_source.emplace( + lnav::console::snippet::from("command", "").with_line(1)); this->ec_output_stack.emplace_back("screen", nonstd::nullopt); } +void +exec_context::add_error_context(lnav::console::user_message& um) +{ + if (!this->ec_source.empty()) { + auto source = this->ec_source.top(); + + um.with_snippet(source); + } + + if (this->ec_current_help != nullptr) { + attr_line_t help; + + format_help_text_for_term(*this->ec_current_help, -1, help, true); + um.with_help(help); + } +} + exec_context::output_guard::output_guard(exec_context& context, std::string name, const nonstd::optional& file) diff --git a/src/command_executor.hh b/src/command_executor.hh index b8969777..b3f767d2 100644 --- a/src/command_executor.hh +++ b/src/command_executor.hh @@ -37,9 +37,11 @@ #include #include "base/auto_fd.hh" +#include "base/lnav.console.hh" #include "bookmarks.hh" #include "fmt/format.h" #include "ghc/filesystem.hpp" +#include "help_text.hh" #include "optional.hpp" #include "shlex.resolver.hh" #include "vis_line.hh" @@ -82,14 +84,18 @@ struct exec_context { return *this; } - std::string get_error_prefix(); + void add_error_context(lnav::console::user_message& um); template - Result make_error(fmt::string_view format_str, - const Args&... args) + Result make_error( + fmt::string_view format_str, const Args&... args) { - return Err(this->get_error_prefix() - + fmt::vformat(format_str, fmt::make_format_args(args...))); + auto retval = lnav::console::user_message::error( + fmt::vformat(format_str, fmt::make_format_args(args...))); + + this->add_error_context(retval); + + return Err(retval); } nonstd::optional get_output() @@ -132,9 +138,12 @@ struct exec_context { exec_context& sg_context; }; - source_guard enter_source(const std::string& path, int line_number) + source_guard enter_source(const std::string& path, + int line_number, + const std::string& content) { - this->ec_source.emplace(path, line_number); + this->ec_source.emplace( + lnav::console::snippet::from(path, content).with_line(line_number)); return {*this}; } @@ -155,7 +164,8 @@ struct exec_context { std::stack> ec_local_vars; std::map ec_global_vars; std::vector ec_path_stack; - std::stack> ec_source; + std::stack ec_source; + help_text* ec_current_help{nullptr}; std::vector>> ec_output_stack; @@ -166,21 +176,19 @@ struct exec_context { pipe_callback_t ec_pipe_callback; }; -Result execute_command(exec_context& ec, - const std::string& cmdline); - -Result execute_sql(exec_context& ec, - const std::string& sql, - std::string& alt_msg); -Result execute_file(exec_context& ec, - const std::string& path_and_args, - bool multiline = true); -Result execute_any(exec_context& ec, - const std::string& cmdline); +Result execute_command( + exec_context& ec, const std::string& cmdline); + +Result execute_sql( + exec_context& ec, const std::string& sql, std::string& alt_msg); +Result execute_file( + exec_context& ec, const std::string& path_and_args, bool multiline = true); +Result execute_any( + exec_context& ec, const std::string& cmdline); void execute_init_commands( exec_context& ec, - std::vector, std::string>>& - msgs); + std::vector, + std::string>>& msgs); std::future pipe_callback(exec_context& ec, const std::string& cmdline, diff --git a/src/curl_looper.hh b/src/curl_looper.hh index 484c2dcf..f751b325 100644 --- a/src/curl_looper.hh +++ b/src/curl_looper.hh @@ -32,8 +32,6 @@ #ifndef curl_looper_hh #define curl_looper_hh -#include "config.h" - #include #include #include @@ -41,6 +39,7 @@ #include #include "base/isc.hh" +#include "config.h" #if !defined(HAVE_LIBCURL) @@ -67,7 +66,7 @@ public: # include -# include "auto_mem.hh" +# include "base/auto_mem.hh" # include "base/lnav_log.hh" # include "base/time_util.hh" diff --git a/src/db_sub_source.cc b/src/db_sub_source.cc index 9b73c25c..d993c084 100644 --- a/src/db_sub_source.cc +++ b/src/db_sub_source.cc @@ -112,11 +112,11 @@ db_label_source::text_attrs_for_line(textview_curses& tc, } for (size_t lpc = 0; lpc < this->dls_headers.size() - 1; lpc++) { if (row % 2 == 0) { - sa.emplace_back(lr2, view_curses::VC_STYLE.value(A_BOLD)); + sa.emplace_back(lr2, VC_STYLE.value(A_BOLD)); } lr.lr_start += this->dls_cell_width[lpc]; lr.lr_end = lr.lr_start + 1; - sa.emplace_back(lr, view_curses::VC_GRAPHIC.value(ACS_VLINE)); + sa.emplace_back(lr, VC_GRAPHIC.value(ACS_VLINE)); lr.lr_start += 1; } @@ -344,10 +344,10 @@ db_overlay_source::list_overlay_count(const listview_curses& lv) string_attrs_t& sa = this->dos_lines.back().get_attrs(); struct line_range lr(1, 2); - sa.emplace_back(lr, view_curses::VC_GRAPHIC.value(ACS_LTEE)); + sa.emplace_back(lr, VC_GRAPHIC.value(ACS_LTEE)); lr.lr_start = 3 + jpw_value.wt_ptr.size() + 3; lr.lr_end = -1; - sa.emplace_back(lr, view_curses::VC_STYLE.value(A_BOLD)); + sa.emplace_back(lr, VC_STYLE.value(A_BOLD)); double num_value = 0.0; @@ -390,10 +390,10 @@ db_overlay_source::list_overlay_count(const listview_curses& lv) string_attrs_t& sa = this->dos_lines.back().get_attrs(); struct line_range lr(1, 2); - sa.emplace_back(lr, view_curses::VC_GRAPHIC.value(ACS_LLCORNER)); + sa.emplace_back(lr, VC_GRAPHIC.value(ACS_LLCORNER)); lr.lr_start = 2; lr.lr_end = -1; - sa.emplace_back(lr, view_curses::VC_GRAPHIC.value(ACS_HLINE)); + sa.emplace_back(lr, VC_GRAPHIC.value(ACS_HLINE)); retval += 1; } @@ -443,12 +443,12 @@ db_overlay_source::list_value_for_overlay(const listview_curses& lv, if (!this->dos_labels->dls_headers[lpc].hm_graphable) { attrs = A_UNDERLINE; } - sa.emplace_back(header_range, view_curses::VC_STYLE.value(attrs)); + sa.emplace_back(header_range, VC_STYLE.value(attrs)); } struct line_range lr(0); - sa.emplace_back(lr, view_curses::VC_STYLE.value(A_BOLD | A_UNDERLINE)); + sa.emplace_back(lr, VC_STYLE.value(A_BOLD | A_UNDERLINE)); return true; } else if (this->dos_active && y >= 2 && ((size_t) y) < (this->dos_lines.size() + 2)) diff --git a/src/doc_status_source.hh b/src/doc_status_source.hh index 475f8fdb..79cb4c32 100644 --- a/src/doc_status_source.hh +++ b/src/doc_status_source.hh @@ -48,13 +48,13 @@ public: { this->tss_fields[TSF_TITLE].set_width(14); this->tss_fields[TSF_TITLE].set_left_pad(1); - this->tss_fields[TSF_TITLE].set_role(view_colors::VCR_STATUS_TITLE); + this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE); this->tss_fields[TSF_STITCH_TITLE].set_width(2); this->tss_fields[TSF_STITCH_TITLE].set_stitch_value( - view_colors::VCR_STATUS_STITCH_TITLE_TO_NORMAL, - view_colors::VCR_STATUS_STITCH_NORMAL_TO_TITLE); + role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL, + role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE); this->tss_fields[TSF_DESCRIPTION].set_share(1); - this->tss_fields[TSF_DESCRIPTION].set_role(view_colors::VCR_STATUS); + this->tss_fields[TSF_DESCRIPTION].set_role(role_t::VCR_STATUS); }; size_t statusview_fields() override diff --git a/src/environ_vtab.cc b/src/environ_vtab.cc index d8b77928..3211f801 100644 --- a/src/environ_vtab.cc +++ b/src/environ_vtab.cc @@ -32,7 +32,7 @@ #include #include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/lnav_log.hh" #include "config.h" diff --git a/src/field_overlay_source.cc b/src/field_overlay_source.cc index 3cef0fba..c2bc3cd9 100644 --- a/src/field_overlay_source.cc +++ b/src/field_overlay_source.cc @@ -29,10 +29,9 @@ #include "field_overlay_source.hh" -#include "ansi_scrubber.hh" +#include "base/ansi_scrubber.hh" #include "base/humanize.time.hh" #include "config.h" -#include "lnav_util.hh" #include "log_format_ext.hh" #include "log_vtab_impl.hh" #include "readline_highlighters.hh" @@ -145,7 +144,7 @@ field_overlay_source::build_summary_lines(const listview_curses& lv) "%'.2lf") "/min; " "Time span: " ANSI_BOLD("%s"), lss.file_count(), - view_colors::VCR_ERROR, + role_t::VCR_ERROR, error_rate, time_span.c_str()); } else { @@ -158,7 +157,7 @@ field_overlay_source::build_summary_lines(const listview_curses& lv) "Time span: " ANSI_BOLD("%s"), lss.file_count(), tss.size(), - view_colors::VCR_ERROR, + role_t::VCR_ERROR, error_rate, time_span.c_str()); } @@ -167,18 +166,18 @@ field_overlay_source::build_summary_lines(const listview_curses& lv) .with_attr(string_attr( line_range(sum_msg.find("Error rate"), sum_msg.find("Error rate") + rate_len), - view_curses::VC_STYLE.value(A_REVERSE))) + VC_STYLE.value(A_REVERSE))) .with_attr( string_attr(line_range(1, 2), - view_curses::VC_GRAPHIC.value(ACS_ULCORNER))) + VC_GRAPHIC.value(ACS_ULCORNER))) .with_attr(string_attr( - line_range(2, 6), view_curses::VC_GRAPHIC.value(ACS_HLINE))) + line_range(2, 6), VC_GRAPHIC.value(ACS_HLINE))) .with_attr(string_attr( line_range(sum_msg.length() + 1, sum_msg.length() + 5), - view_curses::VC_GRAPHIC.value(ACS_HLINE))) + VC_GRAPHIC.value(ACS_HLINE))) .with_attr(string_attr( line_range(sum_msg.length() + 5, sum_msg.length() + 6), - view_curses::VC_GRAPHIC.value(ACS_URCORNER))) + VC_GRAPHIC.value(ACS_URCORNER))) .right_justify(width - 2); } } @@ -234,11 +233,11 @@ field_overlay_source::build_field_lines(const listview_curses& lv) auto al = attr_line_t(emsg) .with_attr(string_attr( line_range{1, 2}, - view_curses::VC_GRAPHIC.value(ACS_LLCORNER))) + VC_GRAPHIC.value(ACS_LLCORNER))) .with_attr( string_attr(line_range{0, 22}, - view_curses::VC_ROLE.value( - view_colors::VCR_INVALID_MSG))); + VC_ROLE.value( + role_t::VCR_INVALID_MSG))); this->fos_lines.emplace_back(al); } } @@ -259,12 +258,12 @@ field_overlay_source::build_field_lines(const listview_curses& lv) time_lr.lr_start = 1; time_lr.lr_end = 2; time_line.with_attr( - string_attr(time_lr, view_curses::VC_GRAPHIC.value(ACS_LLCORNER))); + string_attr(time_lr, VC_GRAPHIC.value(ACS_LLCORNER))); time_str.append(" Out-Of-Time-Order Message"); time_lr.lr_start = 3; time_lr.lr_end = time_str.length(); time_line.with_attr(string_attr( - time_lr, view_curses::VC_ROLE.value(view_colors::VCR_SKEWED_TIME))); + time_lr, VC_ROLE.value(role_t::VCR_SKEWED_TIME))); time_str.append(" --"); } @@ -273,7 +272,7 @@ field_overlay_source::build_field_lines(const listview_curses& lv) time_str.append(curr_timestamp); time_lr.lr_end = time_str.length(); time_line.with_attr( - string_attr(time_lr, view_curses::VC_STYLE.value(A_BOLD))); + string_attr(time_lr, VC_STYLE.value(A_BOLD))); time_str.append(" -- "); time_lr.lr_start = time_str.length(); time_str.append(humanize::time::point::from_tv(ll->get_timeval()) @@ -281,7 +280,7 @@ field_overlay_source::build_field_lines(const listview_curses& lv) .as_precise_time_ago()); time_lr.lr_end = time_str.length(); time_line.with_attr( - string_attr(time_lr, view_curses::VC_STYLE.value(A_BOLD))); + string_attr(time_lr, VC_STYLE.value(A_BOLD))); struct line_range time_range = find_string_attr_range( this->fos_log_helper.ldh_line_attrs, &logline::L_TIMESTAMP); @@ -312,7 +311,7 @@ field_overlay_source::build_field_lines(const listview_curses& lv) time_lr.lr_end = time_str.length(); time_line.with_attr(string_attr( time_lr, - view_curses::VC_ROLE.value(view_colors::VCR_SKEWED_TIME))); + VC_ROLE.value(role_t::VCR_SKEWED_TIME))); timersub(&curr_tv, &actual_tv, &diff_tv); time_str.append("; Diff: "); @@ -321,7 +320,7 @@ field_overlay_source::build_field_lines(const listview_curses& lv) humanize::time::duration::from_tv(diff_tv).to_string()); time_lr.lr_end = time_str.length(); time_line.with_attr( - string_attr(time_lr, view_curses::VC_STYLE.value(A_BOLD))); + string_attr(time_lr, VC_STYLE.value(A_BOLD))); } } @@ -419,7 +418,7 @@ field_overlay_source::build_field_lines(const listview_curses& lv) + format_name + ":"); this->fos_lines.back().with_attr(string_attr( line_range(32, 32 + format_name.length()), - view_curses::VC_STYLE.value(vc.attrs_for_ident(format_name) + VC_STYLE.value(vc.attrs_for_ident(format_name) | A_BOLD))); last_format = curr_format; } @@ -455,12 +454,12 @@ field_overlay_source::build_field_lines(const listview_curses& lv) auto prefix_len = field_name.length() - orig_field_name.length(); al.with_attr(string_attr( line_range(3 + prefix_len, 3 + prefix_len + field_name.size()), - view_curses::VC_STYLE.value( + VC_STYLE.value( vc.attrs_for_ident(orig_field_name)))); } else { al.with_attr(string_attr( line_range(8, 8 + lv.lv_meta.lvm_struct_name.size()), - view_curses::VC_STYLE.value( + VC_STYLE.value( vc.attrs_for_ident(lv.lv_meta.lvm_struct_name)))); } @@ -473,7 +472,7 @@ field_overlay_source::build_field_lines(const listview_curses& lv) al.clear() .append(" extract(") .append(lv.lv_meta.lvm_name.get(), - view_curses::VC_STYLE.value( + VC_STYLE.value( vc.attrs_for_ident(lv.lv_meta.lvm_name))) .append(")") .append(this->fos_known_key_size - lv.lv_meta.lvm_name.size() @@ -536,12 +535,12 @@ field_overlay_source::build_field_lines(const listview_curses& lv) " Discovered fields for logline table from message format: "); this->fos_lines.back().with_attr(string_attr( line_range(23, 23 + 7), - view_curses::VC_STYLE.value(vc.attrs_for_ident("logline")))); + VC_STYLE.value(vc.attrs_for_ident("logline")))); auto& al = this->fos_lines.back(); auto& disc_str = al.get_string(); al.with_attr(string_attr(line_range(disc_str.length(), -1), - view_curses::VC_STYLE.value(A_BOLD))); + VC_STYLE.value(A_BOLD))); disc_str.append(this->fos_log_helper.ldh_msg_format); } @@ -556,7 +555,7 @@ field_overlay_source::build_field_lines(const listview_curses& lv) al.with_attr( string_attr(line_range(3, 3 + name.length()), - view_curses::VC_STYLE.value(vc.attrs_for_ident(name)))); + VC_STYLE.value(vc.attrs_for_ident(name)))); this->fos_lines.emplace_back(al); this->add_key_line_attrs( @@ -585,7 +584,7 @@ field_overlay_source::build_meta_line(const listview_curses& lv, al.with_string(" + ") .with_attr(string_attr( line_range(1, 2), - view_curses::VC_GRAPHIC.value( + VC_GRAPHIC.value( line_meta.bm_tags.empty() ? ACS_LLCORNER : ACS_LTEE))) .append(line_meta.bm_comment); al.insert(0, filename_width, ' '); @@ -595,10 +594,10 @@ field_overlay_source::build_meta_line(const listview_curses& lv, attr_line_t al; al.with_string(" +").with_attr(string_attr( - line_range(1, 2), view_curses::VC_GRAPHIC.value(ACS_LLCORNER))); + line_range(1, 2), VC_GRAPHIC.value(ACS_LLCORNER))); for (const auto& str : line_meta.bm_tags) { al.append(1, ' ').append( - str, view_curses::VC_STYLE.value(vc.attrs_for_ident(str))); + str, VC_STYLE.value(vc.attrs_for_ident(str))); } const auto* tc = dynamic_cast(&lv); diff --git a/src/field_overlay_source.hh b/src/field_overlay_source.hh index e9b75c77..8e7b23e9 100644 --- a/src/field_overlay_source.hh +++ b/src/field_overlay_source.hh @@ -51,11 +51,11 @@ public: string_attrs_t& sa = this->fos_lines.back().get_attrs(); struct line_range lr(1, 2); int64_t graphic = (int64_t) (last_line ? ACS_LLCORNER : ACS_LTEE); - sa.emplace_back(lr, view_curses::VC_GRAPHIC.value(graphic)); + sa.emplace_back(lr, VC_GRAPHIC.value(graphic)); lr.lr_start = 3 + key_size + 3; lr.lr_end = -1; - sa.emplace_back(lr, view_curses::VC_STYLE.value(A_BOLD)); + sa.emplace_back(lr, VC_STYLE.value(A_BOLD)); }; bool list_value_for_overlay(const listview_curses& lv, diff --git a/src/file_collection.cc b/src/file_collection.cc index 60007b37..f8eefc92 100644 --- a/src/file_collection.cc +++ b/src/file_collection.cc @@ -38,6 +38,7 @@ #include "base/humanize.network.hh" #include "base/isc.hh" #include "base/opt_util.hh" +#include "base/string_util.hh" #include "config.h" #include "lnav_util.hh" #include "logfile.hh" diff --git a/src/files_sub_source.cc b/src/files_sub_source.cc index 543428d8..a0941e7c 100644 --- a/src/files_sub_source.cc +++ b/src/files_sub_source.cc @@ -29,6 +29,7 @@ #include "files_sub_source.hh" +#include "base/ansi_scrubber.hh" #include "base/humanize.hh" #include "base/humanize.network.hh" #include "base/opt_util.hh" @@ -284,7 +285,8 @@ files_sub_source::text_value_for_line(textview_curses& tc, value_out = fmt::format(FMT_STRING(" {:<{}} {:>8} {} \u2014 {} {}"), fn, filename_width, - humanize::file_size(lf->get_index_size()), + humanize::file_size(lf->get_index_size(), + humanize::alignment::columnar), start_time, end_time, fmt::join(file_notes, "; ")); @@ -308,19 +310,19 @@ files_sub_source::text_attrs_for_line(textview_curses& tc, if (selected) { value_out.emplace_back(line_range{0, 1}, - view_curses::VC_GRAPHIC.value(ACS_RARROW)); + VC_GRAPHIC.value(ACS_RARROW)); } if (line < fc.fc_name_to_errors.size()) { if (selected) { value_out.emplace_back( line_range{0, -1}, - view_curses::VC_ROLE.value(view_colors::VCR_DISABLED_FOCUSED)); + VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED)); } value_out.emplace_back( line_range{4 + (int) filename_width, -1}, - view_curses::VC_ROLE_FG.value(view_colors::VCR_ERROR)); + VC_ROLE_FG.value(role_t::VCR_ERROR)); return; } line -= fc.fc_name_to_errors.size(); @@ -329,11 +331,11 @@ files_sub_source::text_attrs_for_line(textview_curses& tc, if (selected) { value_out.emplace_back( line_range{0, -1}, - view_curses::VC_ROLE.value(view_colors::VCR_DISABLED_FOCUSED)); + VC_ROLE.value(role_t::VCR_DISABLED_FOCUSED)); } if (line == fc.fc_other_files.size() - 1) { value_out.emplace_back(line_range{0, -1}, - view_curses::VC_STYLE.value(A_UNDERLINE)); + VC_STYLE.value(A_UNDERLINE)); } return; } @@ -343,7 +345,7 @@ files_sub_source::text_attrs_for_line(textview_curses& tc, if (selected) { value_out.emplace_back( line_range{0, -1}, - view_curses::VC_ROLE.value(view_colors::VCR_FOCUSED)); + VC_ROLE.value(role_t::VCR_FOCUSED)); } auto& lss = lnav_data.ld_log_source; @@ -355,10 +357,10 @@ files_sub_source::text_attrs_for_line(textview_curses& tc, visible = ' '; } value_out.emplace_back(line_range{2, 3}, - view_curses::VC_GRAPHIC.value(visible)); + VC_GRAPHIC.value(visible)); if (visible == ACS_DIAMOND) { value_out.emplace_back(line_range{2, 3}, - view_curses::VC_FOREGROUND.value( + VC_FOREGROUND.value( vcolors.ansi_to_theme_color(COLOR_GREEN))); } @@ -366,12 +368,12 @@ files_sub_source::text_attrs_for_line(textview_curses& tc, (int) filename_width + 3 + 4, (int) filename_width + 3 + 10, }; - value_out.emplace_back(lr, view_curses::VC_STYLE.value(A_BOLD)); + value_out.emplace_back(lr, VC_STYLE.value(A_BOLD)); lr.lr_start = this->fss_last_line_len; lr.lr_end = -1; value_out.emplace_back(lr, - view_curses::VC_FOREGROUND.value( + VC_FOREGROUND.value( vcolors.ansi_to_theme_color(COLOR_YELLOW))); } @@ -417,8 +419,10 @@ files_overlay_source::list_value_for_overlay(const listview_curses& lv, "... {:>8}/{}", PROG[spinner_index() % PROG_SIZE], prog.ep_path.filename().string(), - humanize::file_size(prog.ep_out_size), - humanize::file_size(prog.ep_total_size))); + humanize::file_size(prog.ep_out_size, + humanize::alignment::none), + humanize::file_size(prog.ep_total_size, + humanize::alignment::none))); return true; } if (!sp->sp_tailers.empty()) { diff --git a/src/filter_status_source.cc b/src/filter_status_source.cc index 723317f7..00c77151 100644 --- a/src/filter_status_source.cc +++ b/src/filter_status_source.cc @@ -29,6 +29,7 @@ #include "filter_status_source.hh" +#include "base/ansi_scrubber.hh" #include "base/opt_util.hh" #include "config.h" #include "files_sub_source.hh" @@ -49,32 +50,32 @@ static auto CLOSE_HELP = ANSI_BOLD("X") ": Close"; filter_status_source::filter_status_source() { this->tss_fields[TSF_TITLE].set_width(14); - this->tss_fields[TSF_TITLE].set_role(view_colors::VCR_STATUS_TITLE); + this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE); this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ", - view_colors::VCR_STATUS_TITLE_HOTKEY); + role_t::VCR_STATUS_TITLE_HOTKEY); this->tss_fields[TSF_STITCH_TITLE].set_width(2); this->tss_fields[TSF_STITCH_TITLE].set_stitch_value( - view_colors::VCR_STATUS_STITCH_TITLE_TO_NORMAL, - view_colors::VCR_STATUS_STITCH_NORMAL_TO_TITLE); + role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL, + role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE); this->tss_fields[TSF_COUNT].set_min_width(16); this->tss_fields[TSF_COUNT].set_share(1); - this->tss_fields[TSF_COUNT].set_role(view_colors::VCR_STATUS); + this->tss_fields[TSF_COUNT].set_role(role_t::VCR_STATUS); this->tss_fields[TSF_FILTERED].set_min_width(20); this->tss_fields[TSF_FILTERED].set_share(1); - this->tss_fields[TSF_FILTERED].set_role(view_colors::VCR_STATUS); + this->tss_fields[TSF_FILTERED].set_role(role_t::VCR_STATUS); this->tss_fields[TSF_FILES_TITLE].set_width(7); this->tss_fields[TSF_FILES_TITLE].set_role( - view_colors::VCR_STATUS_DISABLED_TITLE); + role_t::VCR_STATUS_DISABLED_TITLE); this->tss_fields[TSF_FILES_TITLE].set_value(" " ANSI_ROLE("F") "iles ", - view_colors::VCR_STATUS_HOTKEY); + role_t::VCR_STATUS_HOTKEY); this->tss_fields[TSF_FILES_RIGHT_STITCH].set_width(2); this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value( - view_colors::VCR_STATUS, view_colors::VCR_STATUS); + role_t::VCR_STATUS, role_t::VCR_STATUS); this->tss_fields[TSF_HELP].right_justify(true); this->tss_fields[TSF_HELP].set_width(20); @@ -83,7 +84,7 @@ filter_status_source::filter_status_source() this->tss_error.set_min_width(20); this->tss_error.set_share(1); - this->tss_error.set_role(view_colors::VCR_ALERT_STATUS); + this->tss_error.set_role(role_t::VCR_ALERT_STATUS); } size_t @@ -106,27 +107,27 @@ filter_status_source::statusview_fields() if (lnav_data.ld_mode == LNM_FILES || lnav_data.ld_mode == LNM_SEARCH_FILES) { this->tss_fields[TSF_FILES_TITLE].set_value( - " " ANSI_ROLE("F") "iles ", view_colors::VCR_STATUS_TITLE_HOTKEY); + " " ANSI_ROLE("F") "iles ", role_t::VCR_STATUS_TITLE_HOTKEY); this->tss_fields[TSF_FILES_TITLE].set_role( - view_colors::VCR_STATUS_TITLE); + role_t::VCR_STATUS_TITLE); this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value( - view_colors::VCR_STATUS_STITCH_TITLE_TO_NORMAL, - view_colors::VCR_STATUS_STITCH_NORMAL_TO_TITLE); + role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL, + role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE); this->tss_fields[TSF_TITLE].set_value(" " ANSI_ROLE("T") "ext Filters ", - view_colors::VCR_STATUS_HOTKEY); + role_t::VCR_STATUS_HOTKEY); this->tss_fields[TSF_TITLE].set_role( - view_colors::VCR_STATUS_DISABLED_TITLE); + role_t::VCR_STATUS_DISABLED_TITLE); this->tss_fields[TSF_STITCH_TITLE].set_stitch_value( - view_colors::VCR_STATUS, view_colors::VCR_STATUS); + role_t::VCR_STATUS, role_t::VCR_STATUS); } else { this->tss_fields[TSF_FILES_TITLE].set_value( - " " ANSI_ROLE("F") "iles ", view_colors::VCR_STATUS_HOTKEY); + " " ANSI_ROLE("F") "iles ", role_t::VCR_STATUS_HOTKEY); if (lnav_data.ld_active_files.fc_name_to_errors.empty()) { this->tss_fields[TSF_FILES_TITLE].set_role( - view_colors::VCR_STATUS_DISABLED_TITLE); + role_t::VCR_STATUS_DISABLED_TITLE); } else { this->tss_fields[TSF_FILES_TITLE].set_role( - view_colors::VCR_ALERT_STATUS); + role_t::VCR_ALERT_STATUS); auto& fc = lnav_data.ld_active_files; if (fc.fc_name_to_errors.size() == 1) { @@ -138,15 +139,15 @@ filter_status_source::statusview_fields() } } this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value( - view_colors::VCR_STATUS_STITCH_NORMAL_TO_TITLE, - view_colors::VCR_STATUS_STITCH_TITLE_TO_NORMAL); + role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE, + role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL); this->tss_fields[TSF_TITLE].set_value( " " ANSI_ROLE("T") "ext Filters ", - view_colors::VCR_STATUS_TITLE_HOTKEY); - this->tss_fields[TSF_TITLE].set_role(view_colors::VCR_STATUS_TITLE); + role_t::VCR_STATUS_TITLE_HOTKEY); + this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE); this->tss_fields[TSF_STITCH_TITLE].set_stitch_value( - view_colors::VCR_STATUS_STITCH_TITLE_TO_NORMAL, - view_colors::VCR_STATUS_STITCH_NORMAL_TO_TITLE); + role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL, + role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE); } lnav_data.ld_view_stack.top() | [this](auto tc) { @@ -209,13 +210,13 @@ filter_status_source::update_filtered(text_sub_source* tss) if (tss->get_filtered_count() == this->bss_last_filtered_count) { if (timer.fade_diff(this->bss_filter_counter) == 0) { this->tss_fields[TSF_FILTERED].set_role( - view_colors::VCR_STATUS); + role_t::VCR_STATUS); al.with_attr(string_attr(line_range{0, -1}, - view_curses::VC_STYLE.value(A_BOLD))); + VC_STYLE.value(A_BOLD))); } } else { this->tss_fields[TSF_FILTERED].set_role( - view_colors::VCR_ALERT_STATUS); + role_t::VCR_ALERT_STATUS); this->bss_last_filtered_count = tss->get_filtered_count(); timer.start_fade(this->bss_filter_counter, 3); } diff --git a/src/filter_status_source.hh b/src/filter_status_source.hh index 619f07d8..7d62966b 100644 --- a/src/filter_status_source.hh +++ b/src/filter_status_source.hh @@ -72,8 +72,8 @@ public: status_field& statusview_value_for_field(int field) override; - status_field fss_prompt{1024, view_colors::VCR_STATUS}; - status_field fss_error{1024, view_colors::VCR_ALERT_STATUS}; + status_field fss_prompt{1024, role_t::VCR_STATUS}; + status_field fss_error{1024, role_t::VCR_ALERT_STATUS}; private: status_field fss_help; diff --git a/src/filter_sub_source.cc b/src/filter_sub_source.cc index fe0ae96b..431a3f4d 100644 --- a/src/filter_sub_source.cc +++ b/src/filter_sub_source.cc @@ -61,7 +61,7 @@ filter_sub_source::filter_sub_source() this->fss_match_view.set_sub_source(&this->fss_match_source); this->fss_match_view.set_height(0_vl); this->fss_match_view.set_show_scrollbar(true); - this->fss_match_view.set_default_role(view_colors::VCR_POPUP); + this->fss_match_view.set_default_role(role_t::VCR_POPUP); } bool @@ -353,36 +353,35 @@ filter_sub_source::text_attrs_for_line(textview_curses& tc, if (selected) { value_out.emplace_back(line_range{0, 1}, - view_curses::VC_GRAPHIC.value(ACS_RARROW)); + VC_GRAPHIC.value(ACS_RARROW)); } chtype enabled = tf->is_enabled() ? ACS_DIAMOND : ' '; line_range lr{2, 3}; - value_out.emplace_back(lr, view_curses::VC_GRAPHIC.value(enabled)); + value_out.emplace_back(lr, VC_GRAPHIC.value(enabled)); if (tf->is_enabled()) { value_out.emplace_back(lr, - view_curses::VC_FOREGROUND.value( + VC_FOREGROUND.value( vcolors.ansi_to_theme_color(COLOR_GREEN))); } - int fg_role = tf->get_type() == text_filter::INCLUDE - ? view_colors::VCR_OK - : view_colors::VCR_ERROR; + role_t fg_role = tf->get_type() == text_filter::INCLUDE ? role_t::VCR_OK + : role_t::VCR_ERROR; value_out.emplace_back(line_range{4, 7}, - view_curses::VC_ROLE_FG.value(fg_role)); + VC_ROLE_FG.value(fg_role)); value_out.emplace_back(line_range{4, 7}, - view_curses::VC_STYLE.value(A_BOLD)); + VC_STYLE.value(A_BOLD)); value_out.emplace_back(line_range{8, 17}, - view_curses::VC_STYLE.value(A_BOLD)); + VC_STYLE.value(A_BOLD)); value_out.emplace_back(line_range{23, 24}, - view_curses::VC_GRAPHIC.value(ACS_VLINE)); + VC_GRAPHIC.value(ACS_VLINE)); if (selected) { value_out.emplace_back( line_range{0, -1}, - view_curses::VC_ROLE.value(view_colors::VCR_FOCUSED)); + VC_ROLE.value(role_t::VCR_FOCUSED)); } attr_line_t content{tf->get_id()}; @@ -624,7 +623,7 @@ filter_sub_source::rl_display_matches(readline_curses* rc) for (auto& match : matches) { if (match == current_match) { - al.append(match, view_curses::VC_STYLE.value(A_REVERSE)); + al.append(match, VC_STYLE.value(A_REVERSE)); selected_line = line; } else { al.append(match); diff --git a/src/fstat_vtab.cc b/src/fstat_vtab.cc index a5de4f41..f2930d54 100644 --- a/src/fstat_vtab.cc +++ b/src/fstat_vtab.cc @@ -35,6 +35,7 @@ #include #include +#include "base/auto_mem.hh" #include "base/injector.hh" #include "base/lnav_log.hh" #include "config.h" diff --git a/src/grep_proc.hh b/src/grep_proc.hh index ff9a7322..9a77a908 100644 --- a/src/grep_proc.hh +++ b/src/grep_proc.hh @@ -32,18 +32,18 @@ #ifndef grep_proc_hh #define grep_proc_hh -#include -#include -#include -#include - #include #include #include #include +#include +#include +#include +#include + #include "base/auto_fd.hh" -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/lnav_log.hh" #include "line_buffer.hh" #include "pcrepp/pcrepp.hh" diff --git a/src/help_text_formatter.cc b/src/help_text_formatter.cc index 720b1379..33bc008e 100644 --- a/src/help_text_formatter.cc +++ b/src/help_text_formatter.cc @@ -32,13 +32,15 @@ #include "help_text_formatter.hh" -#include "ansi_scrubber.hh" +#include "base/ansi_scrubber.hh" #include "base/string_util.hh" #include "config.h" #include "fmt/format.h" #include "fmt/printf.h" #include "readline_highlighters.hh" +using namespace lnav::roles::literals; + std::multimap help_text::TAGGED; static std::vector @@ -83,7 +85,6 @@ format_help_text_for_term(const help_text& ht, { static const size_t body_indent = 2; - view_colors& vc = view_colors::singleton(); text_wrap_settings tws; size_t start_index = out.get_string().length(); @@ -91,29 +92,27 @@ format_help_text_for_term(const help_text& ht, switch (ht.ht_context) { case help_context_t::HC_COMMAND: { - out.append("Synopsis", view_curses::VC_STYLE.value(A_UNDERLINE)) + out.append("Synopsis"_h2) .append("\n") .append(body_indent, ' ') .append(":") - .append(ht.ht_name, view_curses::VC_STYLE.value(A_BOLD)); + .append(lnav::roles::symbol(ht.ht_name)); for (const auto& param : ht.ht_parameters) { out.append(" "); if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) { out.append("["); } - out.append(param.ht_name, - view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append(lnav::roles::variable(param.ht_name)); if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) { out.append("]"); } if (param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE) { - out.append("1", view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append("1"_variable); out.append(" ["); - out.append("...", view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append("..."_variable); out.append(" "); - out.append(param.ht_name, - view_curses::VC_STYLE.value(A_UNDERLINE)); - out.append("N", view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append(lnav::roles::variable(param.ht_name)); + out.append("N"_variable); out.append("]"); } } @@ -130,13 +129,12 @@ format_help_text_for_term(const help_text& ht, bool needs_comma = false; if (!synopsis_only) { - out.append("Synopsis", view_curses::VC_STYLE.value(A_UNDERLINE)) - .append("\n"); + out.append("Synopsis"_h2).append("\n"); } line_start = out.length(); out.append(body_indent, ' ') - .append(ht.ht_name, view_curses::VC_STYLE.value(A_BOLD)) + .append(lnav::roles::symbol(ht.ht_name)) .append("("); for (const auto& param : ht.ht_parameters) { if (!param.ht_flag_name && needs_comma) { @@ -153,15 +151,13 @@ format_help_text_for_term(const help_text& ht, } if (param.ht_flag_name) { out.append(" ") - .append(param.ht_flag_name, - view_curses::VC_STYLE.value(A_BOLD)) + .append(lnav::roles::symbol(param.ht_flag_name)) .append(" "); } if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) { out.append("["); } - out.append(param.ht_name, - view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append(lnav::roles::variable(param.ht_name)); if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) { out.append("]"); } @@ -183,29 +179,27 @@ format_help_text_for_term(const help_text& ht, break; } case help_context_t::HC_SQL_COMMAND: { - out.append("Synopsis", view_curses::VC_STYLE.value(A_UNDERLINE)) + out.append("Synopsis"_h2) .append("\n") .append(body_indent, ' ') .append(";") - .append(ht.ht_name, view_curses::VC_STYLE.value(A_BOLD)); + .append(lnav::roles::symbol(ht.ht_name)); for (const auto& param : ht.ht_parameters) { out.append(" "); if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) { out.append("["); } - out.append(param.ht_name, - view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append(lnav::roles::variable(param.ht_name)); if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) { out.append("]"); } if (param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE) { - out.append("1", view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append("1"_variable); out.append(" ["); - out.append("...", view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append("..."_variable); out.append(" "); - out.append(param.ht_name, - view_curses::VC_STYLE.value(A_UNDERLINE)); - out.append("N", view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append(lnav::roles::variable(param.ht_name)); + out.append("N"_variable); out.append("]"); } } @@ -222,12 +216,14 @@ format_help_text_for_term(const help_text& ht, bool is_infix = ht.ht_context == help_context_t::HC_SQL_INFIX; if (!synopsis_only) { - out.append("Synopsis", view_curses::VC_STYLE.value(A_UNDERLINE)) - .append("\n"); + out.append("Synopsis"_h2).append("\n"); + } + out.append(body_indent, ' '); + if (is_infix) { + out.append(ht.ht_name); + } else { + out.append(lnav::roles::keyword(ht.ht_name)); } - out.append(body_indent, ' ') - .append(ht.ht_name, - view_curses::VC_STYLE.value(is_infix ? 0 : A_BOLD)); for (const auto& param : ht.ht_parameters) { if (break_all || (int) (out.get_string().length() - start_index @@ -249,33 +245,29 @@ format_help_text_for_term(const help_text& ht, } if (param.ht_flag_name) { out.ensure_space().append( - param.ht_flag_name, - view_curses::VC_STYLE.value(A_BOLD)); + lnav::roles::keyword(param.ht_flag_name)); } if (param.ht_group_start) { out.ensure_space().append( - param.ht_group_start, - view_curses::VC_STYLE.value(A_BOLD)); + lnav::roles::keyword(param.ht_group_start)); } if (param.ht_name[0]) { out.ensure_space().append( - param.ht_name, - view_curses::VC_STYLE.value(A_UNDERLINE)); + lnav::roles::variable(param.ht_name)); if (!param.ht_parameters.empty()) { if (param.ht_nargs == help_nargs_t::HN_ZERO_OR_MORE || param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE) { - out.append( - "1", view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append("1"_variable); } if (param.ht_parameters[0].ht_flag_name) { out.append(" ") - .append(param.ht_parameters[0].ht_flag_name, - view_curses::VC_STYLE.value(A_BOLD)) + .append(lnav::roles::keyword( + param.ht_parameters[0].ht_flag_name)) .append(" "); } - out.append(param.ht_parameters[0].ht_name, - view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append(lnav::roles::variable( + param.ht_parameters[0].ht_name)); } } if (param.ht_nargs == help_nargs_t::HN_ZERO_OR_MORE @@ -284,38 +276,35 @@ format_help_text_for_term(const help_text& ht, bool needs_comma = param.ht_parameters.empty() || !param.ht_flag_name; - out.append("1", view_curses::VC_STYLE.value(A_UNDERLINE)) + out.append("1"_variable) .append(" [") .append(needs_comma ? ", " : "") .append("...") .append(needs_comma ? "" : " ") - .append((needs_comma || !param.ht_flag_name) - ? "" - : param.ht_flag_name, - view_curses::VC_STYLE.value(A_BOLD)) + .append(lnav::roles::keyword( + (needs_comma || !param.ht_flag_name) + ? "" + : param.ht_flag_name)) .append(" ") - .append(param.ht_name, - view_curses::VC_STYLE.value(A_UNDERLINE)) - .append("N", view_curses::VC_STYLE.value(A_UNDERLINE)); + .append(lnav::roles::variable(param.ht_name)) + .append("N"_variable); if (!param.ht_parameters.empty()) { if (param.ht_parameters[0].ht_flag_name) { out.append(" ") - .append(param.ht_parameters[0].ht_flag_name, - view_curses::VC_STYLE.value(A_BOLD)) + .append(lnav::roles::keyword( + param.ht_parameters[0].ht_flag_name)) .append(" "); } - out.append(param.ht_parameters[0].ht_name, - view_curses::VC_STYLE.value(A_UNDERLINE)) - .append("N", - view_curses::VC_STYLE.value(A_UNDERLINE)); + out.append(lnav::roles::variable( + param.ht_parameters[0].ht_name)) + .append("N"_variable); } out.append("]"); } if (param.ht_group_end) { out.ensure_space().append( - param.ht_group_end, - view_curses::VC_STYLE.value(A_BOLD)); + lnav::roles::keyword(param.ht_group_end)); } if (param.ht_nargs == help_nargs_t::HN_ZERO_OR_MORE || param.ht_nargs == help_nargs_t::HN_OPTIONAL) @@ -345,8 +334,8 @@ format_help_text_for_term(const help_text& ht, = std::max(strlen(param.ht_name), max_param_name_width); } - out.append(ht.ht_parameters.size() == 1 ? "Parameter" : "Parameters", - view_curses::VC_STYLE.value(A_UNDERLINE)) + out.append(ht.ht_parameters.size() == 1 ? "Parameter"_h2 + : "Parameters"_h2) .append("\n"); for (const auto& param : ht.ht_parameters) { @@ -355,10 +344,7 @@ format_help_text_for_term(const help_text& ht, } out.append(body_indent, ' ') - .append( - param.ht_name, - view_curses::VC_STYLE.value( - vc.attrs_for_role(view_colors::VCR_VARIABLE) | A_BOLD)) + .append(lnav::roles::variable(param.ht_name)) .append(max_param_name_width - strlen(param.ht_name), ' ') .append(" ") .append(attr_line_t::from_ansi_str(param.ht_summary), @@ -374,8 +360,7 @@ format_help_text_for_term(const help_text& ht, = std::max(strlen(result.ht_name), max_result_name_width); } - out.append(ht.ht_results.size() == 1 ? "Result" : "Results", - view_curses::VC_STYLE.value(A_UNDERLINE)) + out.append(ht.ht_results.size() == 1 ? "Result"_h2 : "Results"_h2) .append("\n"); for (const auto& result : ht.ht_results) { @@ -384,10 +369,7 @@ format_help_text_for_term(const help_text& ht, } out.append(body_indent, ' ') - .append( - result.ht_name, - view_curses::VC_STYLE.value( - vc.attrs_for_role(view_colors::VCR_VARIABLE) | A_BOLD)) + .append(lnav::roles::variable(result.ht_name)) .append(max_result_name_width - strlen(result.ht_name), ' ') .append(" ") .append(attr_line_t::from_ansi_str(result.ht_summary), @@ -416,9 +398,7 @@ format_help_text_for_term(const help_text& ht, } stable_sort(related_refs.begin(), related_refs.end()); - out.append("See Also", view_curses::VC_STYLE.value(A_UNDERLINE)) - .append("\n") - .append(body_indent, ' '); + out.append("See Also"_h2).append("\n").append(body_indent, ' '); bool first = true; size_t line_start = out.length(); @@ -430,7 +410,7 @@ format_help_text_for_term(const help_text& ht, out.append("\n").append(body_indent, ' '); line_start = out.length(); } - out.append(ref, view_curses::VC_STYLE.value(A_BOLD)); + out.append(lnav::roles::symbol(ref)); first = false; } } @@ -449,8 +429,7 @@ format_example_text_for_term(const help_text& ht, if (!ht.ht_example.empty()) { int count = 1; - out.append(ht.ht_example.size() == 1 ? "Example" : "Examples", - view_curses::VC_STYLE.value(A_UNDERLINE)) + out.append(ht.ht_example.size() == 1 ? "Example"_h2 : "Examples"_h2) .append("\n"); for (const auto& ex : ht.ht_example) { attr_line_t ex_line(ex.he_cmd); diff --git a/src/help_text_formatter.hh b/src/help_text_formatter.hh index 1ddf133b..d4d1842b 100644 --- a/src/help_text_formatter.hh +++ b/src/help_text_formatter.hh @@ -32,7 +32,7 @@ #include -#include "attr_line.hh" +#include "base/attr_line.hh" #include "help_text.hh" using help_example_to_attr_line_fun_t diff --git a/src/highlighter.cc b/src/highlighter.cc index 69623183..5a7e0509 100644 --- a/src/highlighter.cc +++ b/src/highlighter.cc @@ -132,7 +132,7 @@ highlighter::annotate(attr_line_t& al, int start) const if (lr.lr_end > lr.lr_start && (this->h_nestable || find_string_attr_containing( - sa, &view_curses::VC_STYLE, lr) + sa, &VC_STYLE, lr) == sa.end())) { int attrs = 0; @@ -142,20 +142,20 @@ highlighter::annotate(attr_line_t& al, int start) const } if (!this->h_fg.empty()) { sa.emplace_back(lr, - view_curses::VC_FOREGROUND.value( + VC_FOREGROUND.value( vc.match_color(this->h_fg))); } if (!this->h_bg.empty()) { sa.emplace_back(lr, - view_curses::VC_BACKGROUND.value( + VC_BACKGROUND.value( vc.match_color(this->h_bg))); } - if (this->h_role != view_colors::VCR_NONE) { + if (this->h_role != role_t::VCR_NONE) { sa.emplace_back(lr, - view_curses::VC_ROLE.value(this->h_role)); + VC_ROLE.value(this->h_role)); } if (attrs) { - sa.emplace_back(lr, view_curses::VC_STYLE.value(attrs)); + sa.emplace_back(lr, VC_STYLE.value(attrs)); } off = matches[1]; diff --git a/src/highlighter.hh b/src/highlighter.hh index 48330462..9be4ac4e 100644 --- a/src/highlighter.hh +++ b/src/highlighter.hh @@ -70,7 +70,7 @@ struct highlighter { return *this; } - highlighter& with_role(view_colors::role_t role) + highlighter& with_role(role_t role) { this->h_role = role; @@ -123,7 +123,7 @@ struct highlighter { void annotate(attr_line_t& al, int start) const; std::string h_pattern; - view_colors::role_t h_role{view_colors::VCR_NONE}; + role_t h_role{role_t::VCR_NONE}; styling::color_unit h_fg{styling::color_unit::make_empty()}; styling::color_unit h_bg{styling::color_unit::make_empty()}; pcre* h_code; diff --git a/src/hist_source.cc b/src/hist_source.cc index 163d94c5..54177912 100644 --- a/src/hist_source.cc +++ b/src/hist_source.cc @@ -135,13 +135,13 @@ hist_source2::init() this->hs_chart .with_attrs_for_ident(HT_NORMAL, - vc.attrs_for_role(view_colors::VCR_TEXT)) + vc.attrs_for_role(role_t::VCR_TEXT)) .with_attrs_for_ident(HT_WARNING, - vc.attrs_for_role(view_colors::VCR_WARNING)) + vc.attrs_for_role(role_t::VCR_WARNING)) .with_attrs_for_ident(HT_ERROR, - vc.attrs_for_role(view_colors::VCR_ERROR)) + vc.attrs_for_role(role_t::VCR_ERROR)) .with_attrs_for_ident(HT_MARK, - vc.attrs_for_role(view_colors::VCR_KEYWORD)); + vc.attrs_for_role(role_t::VCR_KEYWORD)); } void diff --git a/src/hist_source.hh b/src/hist_source.hh index 7b59bd2c..28dedef8 100644 --- a/src/hist_source.hh +++ b/src/hist_source.hh @@ -62,7 +62,7 @@ struct stacked_bar_chart_base { explicit show_one(int so_index) : so_index(so_index) {} }; - typedef mapbox::util::variant show_state; + using show_state = mapbox::util::variant; enum class direction { forward, @@ -228,7 +228,7 @@ public: if (ci.ci_attrs != 0) { value_out.emplace_back( - lr, view_curses::VC_STYLE.value(ci.ci_attrs | A_REVERSE)); + lr, VC_STYLE.value(ci.ci_attrs | A_REVERSE)); } }; @@ -328,7 +328,7 @@ public: hist_source2() { this->clear(); - }; + } ~hist_source2() override = default; @@ -337,22 +337,22 @@ public: void set_time_slice(int64_t slice) { this->hs_time_slice = slice; - }; + } int64_t get_time_slice() const { return this->hs_time_slice; - }; + } size_t text_line_count() override { return this->hs_line_count; - }; + } size_t text_line_width(textview_curses& curses) override { return 48 + 8 * 4; - }; + } void clear(); @@ -374,7 +374,7 @@ public: line_flags_t flags) override { return 0; - }; + } nonstd::optional time_for_row(vis_line_t row) override; @@ -397,7 +397,7 @@ private: bucket_block() { memset(this->bb_buckets, 0, sizeof(this->bb_buckets)); - }; + } unsigned int bb_used{0}; bucket_t bb_buckets[BLOCK_SIZE]; diff --git a/src/hotkeys.cc b/src/hotkeys.cc index 53316210..492cc5f3 100644 --- a/src/hotkeys.cc +++ b/src/hotkeys.cc @@ -29,6 +29,7 @@ #include "hotkeys.hh" +#include "base/ansi_scrubber.hh" #include "base/injector.hh" #include "base/math_util.hh" #include "base/opt_util.hh" diff --git a/src/internals/sql-ref.rst b/src/internals/sql-ref.rst index 7f30af57..fa109f7d 100644 --- a/src/internals/sql-ref.rst +++ b/src/internals/sql-ref.rst @@ -318,11 +318,12 @@ SELECT *result-column* FROM *table* WHERE *\[cond\]* GROUP BY *grouping-expr* OR Query the database and return zero or more rows of data. **Parameters** + * **result-column** --- The expression used to generate a result for this column. * **table** --- The table(s) to query for data * **cond** --- The conditions used to select the rows to return. * **grouping-expr** --- The expression to use when grouping rows. * **ordering-term** --- The values to use when ordering the result set. - * **limit-expr** --- The maximum number of rows to return + * **limit-expr** --- The maximum number of rows to return. **Examples** To select all of the columns from the table 'syslog_log': diff --git a/src/line_buffer.hh b/src/line_buffer.hh index 72d17109..4591b11a 100644 --- a/src/line_buffer.hh +++ b/src/line_buffer.hh @@ -40,8 +40,8 @@ #include #include -#include "auto_mem.hh" #include "base/auto_fd.hh" +#include "base/auto_mem.hh" #include "base/file_range.hh" #include "base/lnav_log.hh" #include "base/result.h" diff --git a/src/listview_curses.cc b/src/listview_curses.cc index 9d1252f8..6d4a0e76 100644 --- a/src/listview_curses.cc +++ b/src/listview_curses.cc @@ -275,8 +275,8 @@ listview_curses::do_update() gutter_y++) { int range_start = 0, range_end; - view_colors::role_t role = this->vc_default_role; - view_colors::role_t bar_role = view_colors::VCR_SCROLLBAR; + role_t role = this->vc_default_role; + role_t bar_role = role_t::VCR_SCROLLBAR; int attrs; chtype ch = ACS_VLINE; diff --git a/src/listview_curses.hh b/src/listview_curses.hh index 9bad0115..07bdb51f 100644 --- a/src/listview_curses.hh +++ b/src/listview_curses.hh @@ -90,8 +90,8 @@ public: int start, int end, chtype& ch_out, - view_colors::role_t& role_out, - view_colors::role_t& bar_role_out) + role_t& role_out, + role_t& bar_role_out) { ch_out = ACS_VLINE; }; diff --git a/src/lnav.cc b/src/lnav.cc index 6d6cdd9f..4f02dfb0 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -74,14 +74,16 @@ #endif #include "all_logs_vtab.hh" -#include "ansi_scrubber.hh" +#include "base/ansi_scrubber.hh" #include "base/fs_util.hh" #include "base/func_util.hh" #include "base/future_util.hh" +#include "base/humanize.hh" #include "base/humanize.network.hh" #include "base/humanize.time.hh" #include "base/injector.bind.hh" #include "base/isc.hh" +#include "base/lnav.console.hh" #include "base/lnav_log.hh" #include "base/paths.hh" #include "base/string_util.hh" @@ -155,6 +157,7 @@ #endif using namespace std::literals::chrono_literals; +using namespace lnav::roles::literals; static bool initial_build = false; static std::multimap DEFAULT_FILES; @@ -177,18 +180,20 @@ const int ZOOM_LEVELS[] = { const ssize_t ZOOM_COUNT = sizeof(ZOOM_LEVELS) / sizeof(int); -const char* lnav_zoom_strings[] = {"1-second", - "30-second", - "1-minute", - "5-minute", - "15-minute", - "1-hour", - "4-hour", - "8-hour", - "1-day", - "1-week", - - nullptr}; +const char* lnav_zoom_strings[] = { + "1-second", + "30-second", + "1-minute", + "5-minute", + "15-minute", + "1-hour", + "4-hour", + "8-hour", + "1-day", + "1-week", + + nullptr, +}; static const char* view_titles[LNV__MAX] = { "LOG", @@ -299,12 +304,14 @@ bool setup_logline_table(exec_context& ec) { // Hidden columns don't show up in the table_info pragma. - static const char* hidden_table_columns[] = {"log_time_msecs", - "log_path", - "log_text", - "log_body", + static const char* hidden_table_columns[] = { + "log_time_msecs", + "log_path", + "log_text", + "log_body", - nullptr}; + nullptr, + }; textview_curses& log_view = lnav_data.ld_views[LNV_LOG]; bool retval = false; @@ -751,15 +758,11 @@ append_default_files(lnav_flags_t flag) if (lnav_data.ld_flags & flag) { auto cwd = ghc::filesystem::current_path(); - std::pair::iterator, - std::multimap::iterator> - range; - for (range = DEFAULT_FILES.equal_range(flag); + for (auto range = DEFAULT_FILES.equal_range(flag); range.first != range.second; range.first++) { - std::string path = range.first->second; - struct stat st; + auto path = range.first->second; if (access(path.c_str(), R_OK) == 0) { auto_mem abspath; @@ -770,11 +773,13 @@ append_default_files(lnav_flags_t flag) } else { lnav_data.ld_active_files.fc_file_names[abspath.in()]; } - } else if (stat(path.c_str(), &st) == 0) { - fprintf(stderr, - "error: cannot read -- %s%s\n", - cwd.c_str(), - path.c_str()); + } else if (lnav::filesystem::stat_file(path).isOk()) { + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("default syslog file is not readable -- ") + .append(lnav::roles::file(cwd)) + .append(lnav::roles::file(path)))); retval = false; } } @@ -842,6 +847,27 @@ rl_blur(readline_curses* rc) readline_context::command_map_t lnav_commands; +static attr_line_t +command_arg_help() +{ + return attr_line_t() + .append( + "command arguments must start with one of the following symbols " + "to denote the type of command:\n") + .append(" ") + .append(":"_symbol) + .append(" - ") + .append("an lnav command (e.g. :goto 42)\n") + .append(" ") + .append(";"_symbol) + .append(" - ") + .append("an SQL statement (e.g. SELECT * FROM syslog_log)\n") + .append(" ") + .append("|"_symbol) + .append(" - ") + .append("an lnav script (e.g. |rename-stdin foo)\n"); +} + static void usage() { @@ -1045,9 +1071,7 @@ public: me.me_y = y - tc->get_y() - 1; tc->handle_mouse(me); - }; - -private: + } }; static bool @@ -1512,6 +1536,8 @@ looper() highlight_source_t::THEME); lnav_data.ld_files_view.set_overlay_source(&lnav_data.ld_files_overlay); + lnav_data.ld_user_message_view.set_window(lnav_data.ld_window); + lnav_data.ld_status[LNS_TOP].set_top(0); lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc.get_height() + 1)); for (auto& sc : lnav_data.ld_status) { @@ -1530,6 +1556,7 @@ looper() &lnav_data.ld_preview_status_source); lnav_data.ld_match_view.set_show_bottom_border(true); + lnav_data.ld_user_message_view.set_show_bottom_border(true); for (auto& sc : lnav_data.ld_status) { sc.window_change(); @@ -1713,6 +1740,7 @@ looper() lnav_data.ld_example_view.do_update(); lnav_data.ld_match_view.do_update(); lnav_data.ld_preview_view.do_update(); + lnav_data.ld_user_message_view.do_update(); if (ui_clock::now() >= next_status_update_time) { for (auto& sc : lnav_data.ld_status) { sc.do_update(); @@ -1824,6 +1852,8 @@ looper() ch, tc->get_top()); }; + lnav_data.ld_user_message_source.clear(); + if (!lnav_data.ld_looping) { // No reason to keep processing input after the // user has quit. The view stack will also be @@ -1953,8 +1983,9 @@ looper() if (initial_build) { static bool ran_cleanup = false; - std::vector, - std::string>> + std::vector, + std::string>> cmd_results; execute_init_commands(ec, cmd_results); @@ -2024,6 +2055,7 @@ looper() lnav_data.ld_match_view.set_needs_update(); lnav_data.ld_filter_view.set_needs_update(); lnav_data.ld_files_view.set_needs_update(); + lnav_data.ld_user_message_view.set_needs_update(); } if (lnav_data.ld_child_terminated) { @@ -2127,20 +2159,18 @@ get_textview_for_mode(ln_mode_t mode) } static void -print_errors(std::vector error_list) +print_errors(std::vector error_list) { for (auto& iter : error_list) { - fprintf(stderr, - "%s%s", - iter.c_str(), - iter[iter.size() - 1] == '\n' ? "" : "\n"); + lnav::console::print(stderr, iter); } } int main(int argc, char* argv[]) { - std::vector config_errors, loader_errors; + std::vector config_errors; + std::vector loader_errors; exec_context& ec = lnav_data.ld_exec_context; int lpc, c, retval = EXIT_SUCCESS; @@ -2223,15 +2253,22 @@ main(int argc, char* argv[]) } break; default: - fprintf( + lnav::console::print( stderr, - "error: command arguments should start with a " - "colon, semi-colon, or pipe-symbol to denote:\n"); - fprintf(stderr, - "error: a built-in command, SQL query, " - "or a file path that contains commands to " - "execute\n"); - usage(); + lnav::console::user_message::error( + attr_line_t("invalid value for ") + .append_quoted("-c"_symbol) + .append(" option")) + .with_snippet(lnav::console::snippet::from( + "arg", + attr_line_t(" -c ") + .append(optarg) + .append("\n") + .append(4, ' ') + .append(lnav::roles::error( + "^ command type prefix " + "is missing")))) + .with_help(command_arg_help())); exit(EXIT_FAILURE); break; } @@ -2244,12 +2281,18 @@ main(int argc, char* argv[]) || strcmp("/dev/stdin", optarg) == 0) { exec_stdin = true; } - lnav_data.ld_commands.push_back("|" + std::string(optarg)); + lnav_data.ld_commands.emplace_back( + fmt::format(FMT_STRING("|{}"), optarg)); break; case 'I': if (access(optarg, X_OK) != 0) { - perror("invalid config path"); + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("invalid configuration directory: ") + .append(lnav::roles::file(optarg))) + .with_errno_reason()); exit(EXIT_FAILURE); } lnav_data.ld_config_paths.emplace_back(optarg); @@ -2305,14 +2348,15 @@ main(int argc, char* argv[]) if (isatty(STDIN_FILENO) && read(STDIN_FILENO, &b, 1) == -1) { perror("Read key from STDIN"); } - } break; + break; + } case 'v': lnav_data.ld_flags |= LNF_VERBOSE; break; case 'V': - printf("%s\n", VCS_PACKAGE_STRING); + fmt::print("{}\n", VCS_PACKAGE_STRING); exit(0); break; @@ -2361,7 +2405,26 @@ main(int argc, char* argv[]) = lnav::paths::dotlnav() / "configs/installed"; if (argc == 0) { - fprintf(stderr, "error: expecting file format paths\n"); + const auto install_reason + = attr_line_t("the ") + .append("-i"_symbol) + .append( + " option expects one or more log format definition " + "files to install in your lnav configuration " + "directory"); + const auto install_help + = attr_line_t( + "log format definitions are JSON files that tell lnav " + "how to understand log files\n") + .append( + "See: https://docs.lnav.org/en/latest/formats.html"); + + lnav::console::print(stderr, + lnav::console::user_message::error( + "missing format files to install") + .with_reason(install_reason) + .with_help(install_help)); + usage(); return EXIT_FAILURE; } @@ -2380,27 +2443,35 @@ main(int argc, char* argv[]) auto file_type_result = detect_config_file_type(argv[lpc]); if (file_type_result.isErr()) { - fprintf(stderr, - "error: %s\n", - file_type_result.unwrapErr().c_str()); + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("unable to open configuration file: ") + .append(lnav::roles::file(argv[lpc]))) + .with_reason(file_type_result.unwrapErr())); return EXIT_FAILURE; } auto file_type = file_type_result.unwrap(); - std::string dst_name; + auto src_path = ghc::filesystem::path(argv[lpc]); + ghc::filesystem::path dst_name; if (file_type == config_file_type::CONFIG) { - dst_name = basename(argv[lpc]); + dst_name = src_path.filename(); } else { - std::vector format_list - = load_format_file(argv[lpc], loader_errors); + auto format_list = load_format_file(src_path, loader_errors); if (!loader_errors.empty()) { print_errors(loader_errors); return EXIT_FAILURE; } if (format_list.empty()) { - fprintf( - stderr, "error: format file is empty: %s\n", argv[lpc]); + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("invalid format file: ") + .append(lnav::roles::file(src_path.string()))) + .with_reason("there must be at least one format " + "definition in the file")); return EXIT_FAILURE; } @@ -2442,7 +2513,11 @@ main(int argc, char* argv[]) } } - fprintf(stderr, "info: installed: %s\n", dst_path.c_str()); + lnav::console::print( + stderr, + lnav::console::user_message::ok( + attr_line_t("installed -- ") + .append(lnav::roles::file(dst_path)))); } } return EXIT_SUCCESS; @@ -2541,6 +2616,8 @@ main(int argc, char* argv[]) .add_child_view(&lnav_data.ld_filter_source.fss_editor); lnav_data.ld_files_view.set_sub_source(&lnav_data.ld_files_source) .add_input_delegate(lnav_data.ld_files_source); + lnav_data.ld_user_message_view.set_sub_source( + &lnav_data.ld_user_message_source); for (lpc = 0; lpc < LNV__MAX; lpc++) { lnav_data.ld_views[lpc].set_gutter_source(new log_gutter_source()); @@ -2775,26 +2852,31 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' lnav_data.ld_active_files.fc_file_names[argv[lpc]].with_tail( !(lnav_data.ld_flags & LNF_HEADLESS)); } else { - fprintf(stderr, - "Cannot stat file: %s -- %s\n", - argv[lpc], - strerror(errno)); + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("unable to open file: ") + .append(lnav::roles::file(argv[lpc]))) + .with_errno_reason()); retval = EXIT_FAILURE; } } else if (access(argv[lpc], R_OK) == -1) { - fprintf(stderr, - "Cannot read file: %s -- %s\n", - argv[lpc], - strerror(errno)); + lnav::console::print(stderr, + lnav::console::user_message::error( + attr_line_t("cannot read file: ") + .append(lnav::roles::file(argv[lpc]))) + .with_errno_reason()); retval = EXIT_FAILURE; } else if (S_ISFIFO(st.st_mode)) { auto_fd fifo_fd; if ((fifo_fd = open(argv[lpc], O_RDONLY)) == -1) { - fprintf(stderr, - "Cannot open fifo: %s -- %s\n", - argv[lpc], - strerror(errno)); + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("cannot open fifo: ") + .append(lnav::roles::file(argv[lpc]))) + .with_errno_reason()); retval = EXIT_FAILURE; } else { auto fifo_tmp_fd @@ -2901,7 +2983,15 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' if (!(lnav_data.ld_flags & (LNF_HEADLESS | LNF_CHECK_CONFIG)) && !isatty(STDOUT_FILENO)) { - fprintf(stderr, "error: stdout is not a tty.\n"); + lnav::console::print( + stderr, + lnav::console::user_message::error( + "unable to display interactive text UI") + .with_reason("stdout is not a TTY") + .with_help(attr_line_t("pass the ") + .append("-n"_symbol) + .append(" option to run lnav in headless mode " + "or don't redirect stdout"))); retval = EXIT_FAILURE; } @@ -2955,7 +3045,9 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' && lnav_data.ld_commands.empty() && lnav_data.ld_pt_search.empty() && !(lnav_data.ld_flags & (LNF_HELP | LNF_NO_DEFAULT))) { - fprintf(stderr, "error: no log files given/found.\n"); + lnav::console::print( + stderr, + lnav::console::user_message::error("no log files given/found")); retval = EXIT_FAILURE; } @@ -3002,7 +3094,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' if (lnav_data.ld_flags & LNF_HEADLESS) { std::vector< - std::pair, std::string>> + std::pair, + std::string>> cmd_results; textview_curses *log_tc, *text_tc, *tc; bool output_view = true; @@ -3012,10 +3105,12 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' if (!lnav_data.ld_active_files.fc_name_to_errors.empty()) { for (const auto& pair : lnav_data.ld_active_files.fc_name_to_errors) { - fprintf(stderr, - "error: unable to open file: %s -- %s\n", - pair.first.c_str(), - pair.second.fei_description.c_str()); + lnav::console::print( + stderr, + lnav::console::user_message::error( + attr_line_t("unable to open file: ") + .append(lnav::roles::file(pair.first))) + .with_reason(pair.second.fei_description)); } return EXIT_FAILURE; @@ -3064,7 +3159,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' for (auto& pair : cmd_results) { if (pair.first.isErr()) { - fprintf(stderr, "%s\n", pair.first.unwrapErr().c_str()); + lnav::console::print(stderr, pair.first.unwrapErr()); output_view = false; } else { auto msg = pair.first.unwrap(); @@ -3182,27 +3277,36 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' && !(lnav_data.ld_flags & LNF_QUIET) && !(lnav_data.ld_flags & LNF_HEADLESS)) { - if (ghc::filesystem::file_size(stdin_tmp_path) - > MAX_STDIN_CAPTURE_SIZE) { + ghc::filesystem::permissions(stdin_tmp_path, + ghc::filesystem::perms::owner_read); + auto stdin_size = ghc::filesystem::file_size(stdin_tmp_path); + if (stdin_size > MAX_STDIN_CAPTURE_SIZE) { log_info("not saving large stdin capture -- %s", stdin_tmp_path.c_str()); ghc::filesystem::remove(stdin_tmp_path); } else { - auto home = getenv("HOME"); + auto home = getenv_opt("HOME"); auto path_str = stdin_tmp_path.string(); - if (home != nullptr && startswith(path_str, home)) { - path_str = path_str.substr(strlen(home)); + if (home && startswith(path_str, home.value())) { + path_str = path_str.substr(strlen(home.value())); if (path_str[0] != '/') { path_str.insert(0, 1, '/'); } path_str.insert(0, 1, '~'); } - fprintf(stderr, - "info: stdin was captured, you can reopen it using -- " - "lnav %s\n", - path_str.c_str()); + lnav::console::print( + stderr, + lnav::console::user_message::info( + attr_line_t() + .append(lnav::roles::number(humanize::file_size( + stdin_size, humanize::alignment::none))) + .append(" of data from stdin was captured and " + "will be saved for one day. You can " + "reopen it by running:\n") + .append(" {} ", lnav_data.ld_program_name) + .append(lnav::roles::file(path_str)))); } } } diff --git a/src/lnav.hh b/src/lnav.hh index 1aace0ab..2f24d758 100644 --- a/src/lnav.hh +++ b/src/lnav.hh @@ -43,6 +43,7 @@ #include #include "archive_manager.hh" +#include "base/ansi_scrubber.hh" #include "base/future_util.hh" #include "base/isc.hh" #include "bottom_status_source.hh" @@ -256,6 +257,10 @@ struct lnav_data_t { textview_curses ld_match_view; plain_text_source ld_preview_source; textview_curses ld_preview_view; + plain_text_source ld_user_message_source; + textview_curses ld_user_message_view; + std::chrono::time_point + ld_user_message_expiration; view_stack ld_view_stack; textview_curses* ld_last_view; diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index f152e4d9..93e82e51 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -40,9 +40,8 @@ #include #include #include -#include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/fs_util.hh" #include "base/humanize.network.hh" #include "base/injector.hh" @@ -181,7 +180,7 @@ refresh_pt_search() return retval; } -static Result +static Result com_adjust_log_time(exec_context& ec, std::string cmdline, std::vector& args) @@ -251,7 +250,7 @@ com_adjust_log_time(exec_context& ec, return Ok(retval); } -static Result +static Result com_unix_time(exec_context& ec, std::string cmdline, std::vector& args) @@ -313,7 +312,7 @@ com_unix_time(exec_context& ec, return Ok(retval); } -static Result +static Result com_current_time(exec_context& ec, std::string cmdline, std::vector& args) @@ -337,7 +336,7 @@ com_current_time(exec_context& ec, return Ok(retval); } -static Result +static Result com_goto(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; @@ -445,7 +444,7 @@ com_goto(exec_context& ec, std::string cmdline, std::vector& args) return Ok(retval); } -static Result +static Result com_relative_goto(exec_context& ec, std::string cmdline, std::vector& args) @@ -484,7 +483,7 @@ com_relative_goto(exec_context& ec, return Ok(retval); } -static Result +static Result com_mark(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; @@ -501,7 +500,7 @@ com_mark(exec_context& ec, std::string cmdline, std::vector& args) return Ok(retval); } -static Result +static Result com_mark_expr(exec_context& ec, std::string cmdline, std::vector& args) @@ -576,7 +575,7 @@ com_mark_expr_prompt(exec_context& ec, const std::string& cmdline) trim(lnav_data.ld_log_source.get_sql_marker_text())); } -static Result +static Result com_clear_mark_expr(exec_context& ec, std::string cmdline, std::vector& args) @@ -593,7 +592,7 @@ com_clear_mark_expr(exec_context& ec, return Ok(retval); } -static Result +static Result com_goto_mark(exec_context& ec, std::string cmdline, std::vector& args) @@ -673,7 +672,7 @@ com_goto_mark(exec_context& ec, return Ok(retval); } -static Result +static Result com_goto_location(exec_context& ec, std::string cmdline, std::vector& args) @@ -855,7 +854,7 @@ write_line_to(FILE* outfile, const attr_line_t& al) } } -static Result +static Result com_save_to(exec_context& ec, std::string cmdline, std::vector& args) @@ -1276,7 +1275,7 @@ com_save_to(exec_context& ec, return Ok(retval); } -static Result +static Result com_pipe_to(exec_context& ec, std::string cmdline, std::vector& args) @@ -1439,7 +1438,7 @@ com_pipe_to(exec_context& ec, return Ok(retval); } -static Result +static Result com_redirect_to(exec_context& ec, std::string cmdline, std::vector& args) @@ -1503,7 +1502,7 @@ com_redirect_to(exec_context& ec, return Ok("info: redirecting output to file -- " + split_args[0]); } -static Result +static Result com_highlight(exec_context& ec, std::string cmdline, std::vector& args) @@ -1566,7 +1565,7 @@ com_highlight(exec_context& ec, return Ok(retval); } -static Result +static Result com_clear_highlight(exec_context& ec, std::string cmdline, std::vector& args) @@ -1602,7 +1601,7 @@ com_clear_highlight(exec_context& ec, return Ok(retval); } -static Result +static Result com_help(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; @@ -1615,10 +1614,10 @@ com_help(exec_context& ec, std::string cmdline, std::vector& args) return Ok(retval); } -static Result com_enable_filter( +static Result com_enable_filter( exec_context& ec, std::string cmdline, std::vector& args); -static Result +static Result com_filter(exec_context& ec, std::string cmdline, std::vector& args) @@ -1715,7 +1714,7 @@ com_filter(exec_context& ec, return Ok(retval); } -static Result +static Result com_delete_filter(exec_context& ec, std::string cmdline, std::vector& args) @@ -1745,7 +1744,7 @@ com_delete_filter(exec_context& ec, return Ok(retval); } -static Result +static Result com_enable_filter(exec_context& ec, std::string cmdline, std::vector& args) @@ -1780,7 +1779,7 @@ com_enable_filter(exec_context& ec, return Ok(retval); } -static Result +static Result com_disable_filter(exec_context& ec, std::string cmdline, std::vector& args) @@ -1815,7 +1814,7 @@ com_disable_filter(exec_context& ec, return Ok(retval); } -static Result +static Result com_filter_expr(exec_context& ec, std::string cmdline, std::vector& args) @@ -1899,7 +1898,7 @@ com_filter_expr_prompt(exec_context& ec, const std::string& cmdline) trim(lnav_data.ld_log_source.get_sql_filter_text())); } -static Result +static Result com_clear_filter_expr(exec_context& ec, std::string cmdline, std::vector& args) @@ -1917,7 +1916,7 @@ com_clear_filter_expr(exec_context& ec, return Ok(retval); } -static Result +static Result com_enable_word_wrap(exec_context& ec, std::string cmdline, std::vector& args) @@ -1934,7 +1933,7 @@ com_enable_word_wrap(exec_context& ec, return Ok(retval); } -static Result +static Result com_disable_word_wrap(exec_context& ec, std::string cmdline, std::vector& args) @@ -1953,7 +1952,7 @@ com_disable_word_wrap(exec_context& ec, static std::set custom_logline_tables; -static Result +static Result com_create_logline_table(exec_context& ec, std::string cmdline, std::vector& args) @@ -2008,7 +2007,7 @@ com_create_logline_table(exec_context& ec, return Ok(retval); } -static Result +static Result com_delete_logline_table(exec_context& ec, std::string cmdline, std::vector& args) @@ -2048,7 +2047,7 @@ com_delete_logline_table(exec_context& ec, static std::set custom_search_tables; -static Result +static Result com_create_search_table(exec_context& ec, std::string cmdline, std::vector& args) @@ -2117,7 +2116,7 @@ com_create_search_table(exec_context& ec, return Ok(retval); } -static Result +static Result com_delete_search_table(exec_context& ec, std::string cmdline, std::vector& args) @@ -2154,7 +2153,7 @@ com_delete_search_table(exec_context& ec, return Ok(retval); } -static Result +static Result com_session(exec_context& ec, std::string cmdline, std::vector& args) @@ -2218,7 +2217,7 @@ com_session(exec_context& ec, return Ok(retval); } -static Result +static Result com_open(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; @@ -2424,7 +2423,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector& args) if (gl->gl_pathc > 10) { al.append(" ... ") .append(std::to_string(gl->gl_pathc - 10), - view_curses::VC_STYLE.value(A_BOLD)) + VC_STYLE.value(A_BOLD)) .append(" files not shown ..."); } lnav_data.ld_preview_status_source.get_description() @@ -2482,7 +2481,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector& args) return Ok(retval); } -static Result +static Result com_close(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; @@ -2546,7 +2545,7 @@ com_close(exec_context& ec, std::string cmdline, std::vector& args) return Ok(retval); } -static Result +static Result com_file_visibility(exec_context& ec, std::string cmdline, std::vector& args) @@ -2662,7 +2661,7 @@ com_file_visibility(exec_context& ec, return Ok(retval); } -static Result +static Result com_hide_file(exec_context& ec, std::string cmdline, std::vector& args) @@ -2678,7 +2677,7 @@ com_hide_file(exec_context& ec, return Ok(retval); } -static Result +static Result com_show_file(exec_context& ec, std::string cmdline, std::vector& args) @@ -2694,7 +2693,7 @@ com_show_file(exec_context& ec, return Ok(retval); } -static Result +static Result com_show_only_this_file(exec_context& ec, std::string cmdline, std::vector& args) @@ -2709,7 +2708,7 @@ com_show_only_this_file(exec_context& ec, return Ok(retval); } -static Result +static Result com_comment(exec_context& ec, std::string cmdline, std::vector& args) @@ -2772,7 +2771,7 @@ com_comment_prompt(exec_context& ec, const std::string& cmdline) return ""; } -static Result +static Result com_clear_comment(exec_context& ec, std::string cmdline, std::vector& args) @@ -2817,7 +2816,7 @@ com_clear_comment(exec_context& ec, return Ok(retval); } -static Result +static Result com_tag(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; @@ -2862,7 +2861,7 @@ com_tag(exec_context& ec, std::string cmdline, std::vector& args) return Ok(retval); } -static Result +static Result com_untag(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; @@ -2914,7 +2913,7 @@ com_untag(exec_context& ec, std::string cmdline, std::vector& args) return Ok(retval); } -static Result +static Result com_delete_tags(exec_context& ec, std::string cmdline, std::vector& args) @@ -2935,7 +2934,7 @@ com_delete_tags(exec_context& ec, "The :delete-tag command only works in the log view"); } - std::set& known_tags = bookmark_metadata::KNOWN_TAGS; + auto& known_tags = bookmark_metadata::KNOWN_TAGS; std::vector tags; for (size_t lpc = 1; lpc < args.size(); lpc++) { @@ -2989,7 +2988,7 @@ com_delete_tags(exec_context& ec, return Ok(retval); } -static Result +static Result com_partition_name(exec_context& ec, std::string cmdline, std::vector& args) @@ -3023,7 +3022,7 @@ com_partition_name(exec_context& ec, return Ok(retval); } -static Result +static Result com_clear_partition(exec_context& ec, std::string cmdline, std::vector& args) @@ -3064,7 +3063,7 @@ com_clear_partition(exec_context& ec, return Ok(retval); } -static Result +static Result com_pt_time(exec_context& ec, std::string cmdline, std::vector& args) @@ -3136,7 +3135,7 @@ com_pt_time(exec_context& ec, return Ok(retval); } -static Result +static Result com_summarize(exec_context& ec, std::string cmdline, std::vector& args) @@ -3154,10 +3153,12 @@ com_summarize(exec_context& ec, auto_mem query_frag; std::vector other_columns; std::vector num_columns; + const auto& top_source = ec.ec_source.top(); sql_progress_guard progress_guard(sql_progress, sql_progress_finished, - ec.ec_source.top().first, - ec.ec_source.top().second); + top_source.s_source, + top_source.s_line, + top_source.s_content); auto_mem stmt(sqlite3_finalize); int retcode; std::string query; @@ -3334,7 +3335,7 @@ com_summarize(exec_context& ec, return Ok(retval); } -static Result +static Result com_add_test(exec_context& ec, std::string cmdline, std::vector& args) @@ -3378,7 +3379,7 @@ com_add_test(exec_context& ec, return Ok(retval); } -static Result +static Result com_switch_to_view(exec_context& ec, std::string cmdline, std::vector& args) @@ -3411,7 +3412,7 @@ com_switch_to_view(exec_context& ec, return Ok(retval); } -static Result +static Result com_toggle_filtering(exec_context& ec, std::string cmdline, std::vector& args) @@ -3429,7 +3430,7 @@ com_toggle_filtering(exec_context& ec, return Ok(retval); } -static Result +static Result com_zoom_to(exec_context& ec, std::string cmdline, std::vector& args) @@ -3497,7 +3498,7 @@ com_zoom_to(exec_context& ec, return Ok(retval); } -static Result +static Result com_reset_session(exec_context& ec, std::string cmdline, std::vector& args) @@ -3511,7 +3512,7 @@ com_reset_session(exec_context& ec, return Ok(std::string()); } -static Result +static Result com_load_session(exec_context& ec, std::string cmdline, std::vector& args) @@ -3525,7 +3526,7 @@ com_load_session(exec_context& ec, return Ok(std::string()); } -static Result +static Result com_save_session(exec_context& ec, std::string cmdline, std::vector& args) @@ -3538,7 +3539,7 @@ com_save_session(exec_context& ec, return Ok(std::string()); } -static Result +static Result com_set_min_log_level(exec_context& ec, std::string cmdline, std::vector& args) @@ -3565,7 +3566,7 @@ com_set_min_log_level(exec_context& ec, return Ok(retval); } -static Result +static Result com_toggle_field(exec_context& ec, std::string cmdline, std::vector& args) @@ -3643,7 +3644,7 @@ com_toggle_field(exec_context& ec, return Ok(retval); } -static Result +static Result com_hide_line(exec_context& ec, std::string cmdline, std::vector& args) @@ -3744,7 +3745,7 @@ com_hide_line(exec_context& ec, return Ok(retval); } -static Result +static Result com_show_lines(exec_context& ec, std::string cmdline, std::vector& args) @@ -3765,7 +3766,7 @@ com_show_lines(exec_context& ec, return Ok(retval); } -static Result +static Result com_hide_unmarked(exec_context& ec, std::string cmdline, std::vector& args) @@ -3791,7 +3792,7 @@ com_hide_unmarked(exec_context& ec, return Ok(retval); } -static Result +static Result com_show_unmarked(exec_context& ec, std::string cmdline, std::vector& args) @@ -3807,7 +3808,7 @@ com_show_unmarked(exec_context& ec, return Ok(retval); } -static Result +static Result com_rebuild(exec_context& ec, std::string cmdline, std::vector& args) @@ -3820,7 +3821,7 @@ com_rebuild(exec_context& ec, return Ok(std::string()); } -static Result +static Result com_shexec(exec_context& ec, std::string cmdline, std::vector& args) @@ -3833,7 +3834,7 @@ com_shexec(exec_context& ec, return Ok(std::string()); } -static Result +static Result com_poll_now(exec_context& ec, std::string cmdline, std::vector& args) @@ -3847,7 +3848,7 @@ com_poll_now(exec_context& ec, return Ok(std::string()); } -static Result +static Result com_redraw(exec_context& ec, std::string cmdline, std::vector& args) @@ -3861,7 +3862,7 @@ com_redraw(exec_context& ec, return Ok(std::string()); } -static Result +static Result com_echo(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval = "error: expecting a message"; @@ -3911,7 +3912,7 @@ com_echo(exec_context& ec, std::string cmdline, std::vector& args) return Ok(retval); } -static Result +static Result com_alt_msg(exec_context& ec, std::string cmdline, std::vector& args) @@ -3939,7 +3940,7 @@ com_alt_msg(exec_context& ec, return Ok(retval); } -static Result +static Result com_eval(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; @@ -4003,7 +4004,7 @@ com_eval(exec_context& ec, std::string cmdline, std::vector& args) return Ok(retval); } -static Result +static Result com_config(exec_context& ec, std::string cmdline, std::vector& args) @@ -4014,16 +4015,15 @@ com_config(exec_context& ec, args.emplace_back("config-option"); } else if (args.size() > 1) { yajlpp_parse_context ypc("input", &lnav_config_handlers); - std::vector errors; + std::vector errors; std::string option = args[1]; lnav_config = rollback_lnav_config; ypc.set_path(option) .with_obj(lnav_config) - .with_error_reporter( - [&errors](const auto& ypc, auto level, auto* msg) { - errors.push_back(msg); - }); + .with_error_reporter([&errors](const auto& ypc, auto msg) { + errors.push_back(msg); + }); ypc.ypc_active_paths.insert(option); ypc.update_callbacks(); @@ -4103,7 +4103,6 @@ com_config(exec_context& ec, auto consumed = strtonum(val, value.c_str(), value.length()); - log_debug("got val %d", (int) val); if (consumed != value.length()) { return ec.make_error("expecting an integer, found: {}", value); @@ -4124,22 +4123,24 @@ com_config(exec_context& ec, } if (!errors.empty()) { - return ec.make_error(errors[0]); + return Err(errors[0]); } if (changed) { intern_string_t path = intern_string::lookup(option); - lnav_config_locations[path] - = {intern_string::lookup(ec.ec_source.top().first), - ec.ec_source.top().second}; + lnav_config_locations[path] = { + intern_string::lookup(ec.ec_source.top().s_source), + ec.ec_source.top().s_line, + }; reload_config(errors); if (!errors.empty()) { lnav_config = rollback_lnav_config; reload_config(errors); - return Err("error: " + errors[0]); - } else if (!ec.ec_dry_run) { + return Err(errors[0]); + } + if (!ec.ec_dry_run) { retval = "info: changed config option -- " + option; rollback_lnav_config = lnav_config; save_config(); @@ -4157,7 +4158,7 @@ com_config(exec_context& ec, return Ok(retval); } -static Result +static Result com_reset_config(exec_context& ec, std::string cmdline, std::vector& args) @@ -4497,7 +4498,7 @@ public: std::string dsvs_error_msg; }; -static Result +static Result com_spectrogram(exec_context& ec, std::string cmdline, std::vector& args) @@ -4560,7 +4561,7 @@ com_spectrogram(exec_context& ec, return Ok(retval); } -static Result +static Result com_quit(exec_context& ec, std::string cmdline, std::vector& args) { if (args.empty()) { @@ -4802,7 +4803,7 @@ user_prompt(std::vector& args) lnav_data.ld_status[LNS_BOTTOM].do_update(); } -static Result +static Result com_prompt(exec_context& ec, std::string cmdline, std::vector& args) diff --git a/src/lnav_config.cc b/src/lnav_config.cc index 5bd51a74..539bbb50 100644 --- a/src/lnav_config.cc +++ b/src/lnav_config.cc @@ -46,8 +46,8 @@ #include #include -#include "auto_mem.hh" #include "base/auto_fd.hh" +#include "base/auto_mem.hh" #include "base/auto_pid.hh" #include "base/fs_util.hh" #include "base/injector.bind.hh" @@ -59,6 +59,7 @@ #include "config.h" #include "default-config.h" #include "styling.hh" +#include "view_curses.hh" #include "yajlpp/yajlpp.hh" #include "yajlpp/yajlpp_def.hh" @@ -124,7 +125,7 @@ ensure_dotlnav() auto path = lnav::paths::dotlnav(); - for (auto sub_path : subdirs) { + for (const auto* sub_path : subdirs) { auto full_path = path / sub_path; log_perror(mkdir(full_path.c_str(), 0755)); @@ -164,7 +165,7 @@ ensure_dotlnav() continue; } - log_debug("Removing old stdin capture: %s", gl->gl_pathv[lpc]); + log_info("Removing old stdin capture: %s", gl->gl_pathv[lpc]); log_perror(remove(gl->gl_pathv[lpc])); } } @@ -315,8 +316,9 @@ read_repo_path(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) return 1; } -static const struct json_path_container format_handlers - = {json_path_handler("format-repos#", read_repo_path)}; +static const struct json_path_container format_handlers = { + json_path_handler("format-repos#", read_repo_path), +}; void install_extra_formats() @@ -351,7 +353,7 @@ install_extra_formats() yajl_config(jhandle, yajl_allow_comments, 1); while ((rc = read(fd, buffer, sizeof(buffer))) > 0) { if (yajl_parse(jhandle, buffer, rc) != yajl_status_ok) { - auto msg = yajl_get_error(jhandle, 1, buffer, rc); + auto* msg = yajl_get_error(jhandle, 1, buffer, rc); fprintf( stderr, "Unable to parse remote-config.json -- %s", msg); yajl_free_error(jhandle, msg); @@ -359,7 +361,7 @@ install_extra_formats() } } if (yajl_complete_parse(jhandle) != yajl_status_ok) { - auto msg = yajl_get_error(jhandle, 1, buffer, rc); + auto* msg = yajl_get_error(jhandle, 1, buffer, rc); fprintf(stderr, "Unable to parse remote-config.json -- %s", msg); yajl_free_error(jhandle, msg); @@ -368,23 +370,19 @@ install_extra_formats() } struct userdata { - userdata(std::vector& errors) : ud_errors(errors){}; + explicit userdata(std::vector& errors) + : ud_errors(errors){}; - std::vector& ud_errors; + std::vector& ud_errors; }; static void config_error_reporter(const yajlpp_parse_context& ypc, - lnav_log_level_t level, - const char* msg) + const lnav::console::user_message& msg) { - if (level >= lnav_log_level_t::ERROR) { - struct userdata* ud = (userdata*) ypc.ypc_userdata; + auto* ud = (userdata*) ypc.ypc_userdata; - ud->ud_errors.emplace_back(msg); - } else { - fprintf(stderr, "warning:%s\n", msg); - } + ud->ud_errors.emplace_back(msg); } static const struct json_path_container key_command_handlers = { @@ -393,38 +391,40 @@ static const struct json_path_container key_command_handlers = { .with_description( "The command to execute for the given key sequence. Use a script " "to execute more complicated operations.") - .with_pattern("[:|;].*") + .with_pattern("^[:|;].*") .with_example(":goto next hour") - .FOR_FIELD(key_command, kc_cmd), + .for_field(&key_command::kc_cmd), yajlpp::property_handler("alt-msg") .with_synopsis("") .with_description( "The help message to display after the key is pressed.") - .FOR_FIELD(key_command, kc_alt_msg)}; - -static const struct json_path_container keymap_def_handlers - = {yajlpp::pattern_property_handler("(?(?:x[0-9a-f]{2})+)") - .with_synopsis("") - .with_description( - "Map of key codes to commands to execute. The field names are " - "the keys to be mapped using as a hexadecimal representation of " - "the UTF-8 encoding. Each byte of the UTF-8 should start with " - "an 'x' followed by the hexadecimal representation of the byte.") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, key_map* km) { - key_command& retval - = km->km_seq_to_cmd[ypc.ypc_extractor.get_substr( - "key_seq")]; - - return &retval; - }) - .with_path_provider( - [](key_map* km, std::vector& paths_out) { - for (const auto& iter : km->km_seq_to_cmd) { - paths_out.emplace_back(iter.first); - } - }) - .with_children(key_command_handlers)}; + .for_field<>(&key_command::kc_alt_msg), +}; + +static const struct json_path_container keymap_def_handlers = { + yajlpp::pattern_property_handler("(?(?:x[0-9a-f]{2})+)") + .with_synopsis("") + .with_description( + "Map of key codes to commands to execute. The field names are " + "the keys to be mapped using as a hexadecimal representation of " + "the UTF-8 encoding. Each byte of the UTF-8 should start with " + "an 'x' followed by the hexadecimal representation of the byte.") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, key_map* km) { + key_command& retval + = km->km_seq_to_cmd[ypc.ypc_extractor.get_substr( + "key_seq")]; + + return &retval; + }) + .with_path_provider( + [](key_map* km, std::vector& paths_out) { + for (const auto& iter : km->km_seq_to_cmd) { + paths_out.emplace_back(iter.first); + } + }) + .with_children(key_command_handlers), +}; static const struct json_path_container keymap_defs_handlers = { yajlpp::pattern_property_handler("(?[\\w\\-]+)") @@ -442,7 +442,8 @@ static const struct json_path_container keymap_defs_handlers = { paths_out.emplace_back(iter.first); } }) - .with_children(keymap_def_handlers)}; + .with_children(keymap_def_handlers), +}; static const struct json_path_container global_var_handlers = { yajlpp::pattern_property_handler("(?\\w+)") @@ -456,7 +457,8 @@ static const struct json_path_container global_var_handlers = { paths_out.emplace_back(iter.first); } }) - .FOR_FIELD(_lnav_config, lc_global_vars)}; + .FOR_FIELD(_lnav_config, lc_global_vars), +}; static const struct json_path_container style_config_handlers = json_path_container{ @@ -469,7 +471,7 @@ static const struct json_path_container style_config_handlers = .with_example("#fff") .with_example("Green") .with_example("$black") - .FOR_FIELD(style_config, sc_color), + .for_field(&style_config::sc_color), yajlpp::property_handler("background-color") .with_synopsis("#hex|color_name") .with_description( @@ -478,13 +480,13 @@ static const struct json_path_container style_config_handlers = "variable reference.") .with_example("#2d2a2e") .with_example("Green") - .FOR_FIELD(style_config, sc_background_color), + .for_field(&style_config::sc_background_color), yajlpp::property_handler("underline") .with_description("Indicates that the text should be underlined.") - .FOR_FIELD(style_config, sc_underline), + .for_field(&style_config::sc_underline), yajlpp::property_handler("bold") .with_description("Indicates that the text should be bolded.") - .FOR_FIELD(style_config, sc_bold), + .for_field(&style_config::sc_bold), } .with_definition_id("style"); @@ -594,103 +596,147 @@ static const struct json_path_container theme_styles_handlers = { [](const yajlpp_provider_context& ypc, lnav_theme* root) { return &root->lt_style_scrollbar; }) - .with_children(style_config_handlers)}; - -static const struct json_path_container theme_syntax_styles_handlers - = {yajlpp::property_handler("keyword") - .with_description("Styling for keywords in source files") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_keyword; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("string") - .with_description("Styling for single/double-quoted strings in text") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_string; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("comment") - .with_description("Styling for comments in source files") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_comment; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("doc-directive") - .with_description( - "Styling for documentation directives in source files") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_doc_directive; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("variable") - .with_description("Styling for variables in text") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_variable; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("symbol") - .with_description("Styling for symbols in source files") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_symbol; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("number") - .with_description("Styling for numbers in source files") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_number; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("re-special") - .with_description( - "Styling for special characters in regular expressions") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_re_special; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("re-repeat") - .with_description("Styling for repeats in regular expressions") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_re_repeat; - }) - .with_children(style_config_handlers), - - yajlpp::property_handler("diff-delete") - .with_description("Styling for deleted lines in diffs") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_diff_delete; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("diff-add") - .with_description("Styling for added lines in diffs") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_diff_add; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("diff-section") - .with_description("Styling for diffs") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_diff_section; - }) - .with_children(style_config_handlers), - yajlpp::property_handler("file") - .with_description("Styling for file names in source files") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - return &root->lt_style_file; - }) - .with_children(style_config_handlers)}; + .with_children(style_config_handlers), + yajlpp::property_handler("h1") + .with_description("Styling for top-level headers") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_header[0]; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("h2") + .with_description("Styling for 2nd-level headers") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_header[1]; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("h3") + .with_description("Styling for 3rd-level headers") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_header[2]; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("h4") + .with_description("Styling for 4th-level headers") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_header[3]; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("h5") + .with_description("Styling for 5th-level headers") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_header[4]; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("h6") + .with_description("Styling for 6th-level headers") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_header[5]; + }) + .with_children(style_config_handlers), +}; + +static const struct json_path_container theme_syntax_styles_handlers = { + yajlpp::property_handler("keyword") + .with_description("Styling for keywords in source files") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_keyword; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("string") + .with_description("Styling for single/double-quoted strings in text") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_string; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("comment") + .with_description("Styling for comments in source files") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_comment; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("doc-directive") + .with_description( + "Styling for documentation directives in source files") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_doc_directive; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("variable") + .with_description("Styling for variables in text") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_variable; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("symbol") + .with_description("Styling for symbols in source files") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_symbol; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("number") + .with_description("Styling for numbers in source files") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_number; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("re-special") + .with_description( + "Styling for special characters in regular expressions") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_re_special; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("re-repeat") + .with_description("Styling for repeats in regular expressions") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_re_repeat; + }) + .with_children(style_config_handlers), + + yajlpp::property_handler("diff-delete") + .with_description("Styling for deleted lines in diffs") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_diff_delete; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("diff-add") + .with_description("Styling for added lines in diffs") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_diff_add; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("diff-section") + .with_description("Styling for diffs") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_diff_section; + }) + .with_children(style_config_handlers), + yajlpp::property_handler("file") + .with_description("Styling for file names in source files") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + return &root->lt_style_file; + }) + .with_children(style_config_handlers), +}; static const struct json_path_container theme_status_styles_handlers = { yajlpp::property_handler("text") @@ -772,24 +818,25 @@ static const struct json_path_container theme_status_styles_handlers = { .with_children(style_config_handlers), }; -static const struct json_path_container theme_log_level_styles_handlers - = {yajlpp::pattern_property_handler( - "(?trace|debug5|debug4|debug3|debug2|debug|info|stats|notice|" - "warning|error|critical|fatal|invalid)") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - style_config& sc = root->lt_level_styles[string2level( - ypc.ypc_extractor.get_substr_i("level").get())]; - - return ≻ - }) - .with_path_provider( - [](struct lnav_theme* cfg, std::vector& paths_out) { - for (int lpc = LEVEL_TRACE; lpc < LEVEL__MAX; lpc++) { - paths_out.emplace_back(level_names[lpc]); - } - }) - .with_children(style_config_handlers)}; +static const struct json_path_container theme_log_level_styles_handlers = { + yajlpp::pattern_property_handler( + "(?trace|debug5|debug4|debug3|debug2|debug|info|stats|notice|" + "warning|error|critical|fatal|invalid)") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + style_config& sc = root->lt_level_styles[string2level( + ypc.ypc_extractor.get_substr_i("level").get())]; + + return ≻ + }) + .with_path_provider( + [](struct lnav_theme* cfg, std::vector& paths_out) { + for (int lpc = LEVEL_TRACE; lpc < LEVEL__MAX; lpc++) { + paths_out.emplace_back(level_names[lpc]); + } + }) + .with_children(style_config_handlers), +}; static const struct json_path_container highlighter_handlers = { yajlpp::property_handler("pattern") @@ -807,36 +854,38 @@ static const struct json_path_container highlighter_handlers = { .with_children(style_config_handlers), }; -static const struct json_path_container theme_highlights_handlers - = {yajlpp::pattern_property_handler("(?\\w+)") - .with_obj_provider( - [](const yajlpp_provider_context& ypc, lnav_theme* root) { - highlighter_config& hc - = root->lt_highlights[ypc.ypc_extractor - .get_substr_i("highlight_name") - .get()]; - - return &hc; - }) - .with_path_provider( - [](struct lnav_theme* cfg, std::vector& paths_out) { - for (const auto& pair : cfg->lt_highlights) { - paths_out.emplace_back(pair.first); - } - }) - .with_children(highlighter_handlers)}; - -static const struct json_path_container theme_vars_handlers - = {yajlpp::pattern_property_handler("(?\\w+)") - .with_synopsis("name") - .with_description("A theme variable definition") - .with_path_provider( - [](struct lnav_theme* lt, std::vector& paths_out) { - for (const auto& iter : lt->lt_vars) { - paths_out.emplace_back(iter.first); - } - }) - .FOR_FIELD(lnav_theme, lt_vars)}; +static const struct json_path_container theme_highlights_handlers = { + yajlpp::pattern_property_handler("(?\\w+)") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, lnav_theme* root) { + highlighter_config& hc + = root->lt_highlights[ypc.ypc_extractor + .get_substr_i("highlight_name") + .get()]; + + return &hc; + }) + .with_path_provider( + [](struct lnav_theme* cfg, std::vector& paths_out) { + for (const auto& pair : cfg->lt_highlights) { + paths_out.emplace_back(pair.first); + } + }) + .with_children(highlighter_handlers), +}; + +static const struct json_path_container theme_vars_handlers = { + yajlpp::pattern_property_handler("(?\\w+)") + .with_synopsis("name") + .with_description("A theme variable definition") + .with_path_provider( + [](struct lnav_theme* lt, std::vector& paths_out) { + for (const auto& iter : lt->lt_vars) { + paths_out.emplace_back(iter.first); + } + }) + .FOR_FIELD(lnav_theme, lt_vars), +}; static const struct json_path_container theme_def_handlers = { yajlpp::property_handler("vars") @@ -881,7 +930,8 @@ static const struct json_path_container theme_defs_handlers = { paths_out.emplace_back(iter.first); } }) - .with_children(theme_def_handlers)}; + .with_children(theme_def_handlers), +}; static const struct json_path_container ui_handlers = { yajlpp::property_handler("clock-format") @@ -889,13 +939,13 @@ static const struct json_path_container ui_handlers = { .with_description("The format for the clock displayed in " "the top-left corner using strftime(3) conversions") .with_example("%a %b %d %H:%M:%S %Z") - .FOR_FIELD(_lnav_config, lc_ui_clock_format), + .for_field(&_lnav_config::lc_ui_clock_format), yajlpp::property_handler("dim-text") .with_synopsis("bool") .with_description("Reduce the brightness of text (useful for xterms). " "This setting can be useful when running in an xterm " "where the white color is very bright.") - .FOR_FIELD(_lnav_config, lc_ui_dim_text), + .for_field(&_lnav_config::lc_ui_dim_text), yajlpp::property_handler("default-colors") .with_synopsis("bool") .with_description( @@ -903,15 +953,15 @@ static const struct json_path_container ui_handlers = { "instead of black and white for all text coloring. This setting " "can be useful when transparent background or alternate color " "theme terminal is used.") - .FOR_FIELD(_lnav_config, lc_ui_default_colors), + .for_field(&_lnav_config::lc_ui_default_colors), yajlpp::property_handler("keymap") .with_synopsis("keymap_name") .with_description("The name of the keymap to use.") - .FOR_FIELD(_lnav_config, lc_ui_keymap), + .for_field(&_lnav_config::lc_ui_keymap), yajlpp::property_handler("theme") .with_synopsis("theme_name") .with_description("The name of the theme to use.") - .FOR_FIELD(_lnav_config, lc_ui_theme), + .for_field(&_lnav_config::lc_ui_theme), yajlpp::property_handler("theme-defs") .with_description("Theme definitions.") .with_children(theme_defs_handlers), @@ -1105,8 +1155,11 @@ static const struct json_path_container tuning_handlers = { .with_children(sysclip_handlers), }; +const char* DEFAULT_CONFIG_SCHEMA + = "https://lnav.org/schemas/config-v1.schema.json"; + static const std::set SUPPORTED_CONFIG_SCHEMAS = { - "https://lnav.org/schemas/config-v1.schema.json", + DEFAULT_CONFIG_SCHEMA, }; const char* DEFAULT_FORMAT_SCHEMA @@ -1122,13 +1175,22 @@ read_id(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) auto file_id = std::string((const char*) str, len); if (SUPPORTED_CONFIG_SCHEMAS.count(file_id) == 0) { + const auto* handler = ypc->ypc_current_handler; + attr_line_t notes{"expecting one of the following $schema values:"}; + + for (const auto& schema : SUPPORTED_CONFIG_SCHEMAS) { + notes.append("\n").append( + lnav::roles::symbol(fmt::format(FMT_STRING(" {}"), schema))); + } ypc->report_error( - lnav_log_level_t::ERROR, - "%s:%d: error: unsupported configuration $schema -- %s\n", - ypc->ypc_source.c_str(), - ypc->get_line_number(), - file_id.c_str()); - return 0; + lnav::console::user_message::error( + attr_line_t("'") + .append(lnav::roles::symbol(file_id)) + .append( + "' is not a supported configuration $schema version")) + .with_snippet(ypc->get_snippet()) + .with_note(notes) + .with_help(handler->get_help_text(ypc))); } return 1; @@ -1136,8 +1198,9 @@ read_id(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) const json_path_container lnav_config_handlers = json_path_container { json_path_handler("$schema", read_id) - .with_synopsis("The URI of the schema for this file") - .with_description("Specifies the type of this file"), + .with_synopsis("") + .with_description("The URI that specifies the schema that describes this type of file") + .with_example(DEFAULT_CONFIG_SCHEMA), yajlpp::property_handler("tuning") .with_description("Internal settings") @@ -1174,15 +1237,7 @@ detect_config_file_type(const ghc::filesystem::path& path) { static const char* id_path[] = {"$schema", nullptr}; - auto read_res = lnav::filesystem::read_file(path); - - if (read_res.isErr()) { - return Err(fmt::format(FMT_STRING("unable to open file4: {} -- {}"), - path.string(), - read_res.unwrapErr())); - } - - auto content = read_res.unwrap(); + auto content = TRY(lnav::filesystem::read_file(path)); if (startswith(content, "#")) { content.insert(0, "//"); } @@ -1192,9 +1247,8 @@ detect_config_file_type(const ghc::filesystem::path& path) yajl_tree_parse(content.c_str(), error_buffer, sizeof(error_buffer)), yajl_tree_free); if (content_tree == nullptr) { - return Err(fmt::format(FMT_STRING("unable to parse file: {} -- {}"), - path.string(), - error_buffer)); + return Err( + fmt::format(FMT_STRING("JSON parsing failed -- {}"), error_buffer)); } auto* id_val = yajl_tree_get(content_tree.get(), id_path, yajl_t_string); @@ -1206,18 +1260,16 @@ detect_config_file_type(const ghc::filesystem::path& path) return Ok(config_file_type::FORMAT); } return Err(fmt::format( - FMT_STRING("unsupported configuration version in file: {} -- {}"), - path.string(), + FMT_STRING("unsupported configuration version in file -- {}"), id_val->u.string)); - } else { - return Ok(config_file_type::FORMAT); } + return Ok(config_file_type::FORMAT); } static void load_config_from(_lnav_config& lconfig, const ghc::filesystem::path& path, - std::vector& errors) + std::vector& errors) { yajlpp_parse_context ypc(path.string(), &lnav_config_handlers); struct userdata ud(errors); @@ -1229,14 +1281,15 @@ load_config_from(_lnav_config& lconfig, ypc.with_error_reporter(config_error_reporter); if ((fd = lnav::filesystem::openp(path, O_RDONLY)) == -1) { if (errno != ENOENT) { - errors.emplace_back(fmt::format( - FMT_STRING("error: unable to open format file -- {}"), - path.string())); + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("unable to open configuration file: ") + .append(lnav::roles::file(path))) + .with_errno_reason()); } } else { auto_mem handle(yajl_free); char buffer[2048]; - off_t offset = 0; ssize_t rc = -1; handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc); @@ -1247,18 +1300,19 @@ load_config_from(_lnav_config& lconfig, rc = read(fd, buffer, sizeof(buffer)); if (rc == 0) { break; - } else if (rc == -1) { + } + if (rc == -1) { errors.emplace_back( - fmt::format(FMT_STRING("{}:unable to read file -- {}"), - path.string(), - strerror(errno))); + lnav::console::user_message::error( + attr_line_t("unable to read format file: ") + .append(lnav::roles::file(path))) + .with_errno_reason()); break; } if (ypc.parse((const unsigned char*) buffer, rc) != yajl_status_ok) { break; } - offset += rc; } if (rc == 0) { ypc.complete_parse(); @@ -1270,7 +1324,7 @@ static void load_default_config(struct _lnav_config& config_obj, const std::string& path, const bin_src_file& bsf, - std::vector& errors) + std::vector& errors) { yajlpp_parse_context ypc_builtin(bsf.get_name(), &lnav_config_handlers); auto_mem handle(yajl_free); @@ -1298,7 +1352,7 @@ load_default_config(struct _lnav_config& config_obj, static void load_default_configs(struct _lnav_config& config_obj, const std::string& path, - std::vector& errors) + std::vector& errors) { for (auto& bsf : lnav_config_json) { load_default_config(config_obj, path, bsf, errors); @@ -1307,7 +1361,7 @@ load_default_configs(struct _lnav_config& config_obj, void load_config(const std::vector& extra_paths, - std::vector& errors) + std::vector& errors) { auto user_config = lnav::paths::dotlnav() / "config.json"; @@ -1370,14 +1424,14 @@ load_config(const std::vector& extra_paths, void reset_config(const std::string& path) { - std::vector errors; + std::vector errors; load_default_configs(lnav_config, path, errors); reload_config(errors); - for (auto& err : errors) { - log_debug("reset %s", err.c_str()); + for (const auto& err : errors) { + log_debug("reset %s", err.um_message.get_string().c_str()); } } @@ -1418,7 +1472,7 @@ save_config() } void -reload_config(std::vector& errors) +reload_config(std::vector& errors) { lnav_config_listener* curr = lnav_config_listener::LISTENER_LIST; @@ -1439,10 +1493,16 @@ reload_config(std::vector& errors) return; } - errors.emplace_back(fmt::format(FMT_STRING("{}:{}:{}"), - loc_iter->second.sl_source, - loc_iter->second.sl_line_number, - errmsg)); + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t() + .append("invalid value for property ") + .append_quoted(lnav::roles::symbol(path))) + .with_reason(errmsg) + .with_snippet( + lnav::console::snippet::from( + loc_iter->second.sl_source.to_string(), "") + .with_line(loc_iter->second.sl_line_number))); }; for (const auto& jph : lnav_config_handlers.jpc_children) { diff --git a/src/lnav_config.hh b/src/lnav_config.hh index 58162077..4e27c57c 100644 --- a/src/lnav_config.hh +++ b/src/lnav_config.hh @@ -41,6 +41,7 @@ #include "archive_manager.cfg.hh" #include "base/file_range.hh" +#include "base/lnav.console.hh" #include "base/result.h" #include "file_vtab.cfg.hh" #include "ghc/filesystem.hpp" @@ -117,11 +118,11 @@ Result detect_config_file_type( const ghc::filesystem::path& path); void load_config(const std::vector& extra_paths, - std::vector& errors); + std::vector& errors); void reset_config(const std::string& path); -void reload_config(std::vector& errors); +void reload_config(std::vector& errors); std::string save_config(); diff --git a/src/lnav_util.cc b/src/lnav_util.cc index 7904e9b2..a6b67d0c 100644 --- a/src/lnav_util.cc +++ b/src/lnav_util.cc @@ -36,11 +36,13 @@ #include #include -#include "ansi_scrubber.hh" +#include "base/ansi_scrubber.hh" #include "base/result.h" #include "config.h" #include "fmt/format.h" #include "view_curses.hh" +#include "yajlpp/yajlpp.hh" +#include "yajlpp/yajlpp_def.hh" bool change_to_parent_dir() @@ -101,8 +103,249 @@ err_prefix(const std::string msg) return std::string(ANSI_COLOR(COLOR_RED) "\u2718" ANSI_NORM " ") + msg; } -Result -err_to_ok(const std::string msg) +Result +err_to_ok(const lnav::console::user_message msg) { - return Ok(err_prefix(msg)); + return Ok(msg.to_attr_line().get_string()); } + +namespace lnav { + +static void +to_json(yajlpp_gen& gen, const attr_line_t& al) +{ + { + yajlpp_map root_map(gen); + + root_map.gen("str"); + root_map.gen(al.get_string()); + + root_map.gen("attrs"); + { + yajlpp_array attr_array(gen); + + for (const auto& sa : al.get_attrs()) { + yajlpp_map elem_map(gen); + + elem_map.gen("start"); + elem_map.gen(sa.sa_range.lr_start); + elem_map.gen("end"); + elem_map.gen(sa.sa_range.lr_end); + elem_map.gen("type"); + elem_map.gen(sa.sa_type->sat_name); + elem_map.gen("value"); + sa.sa_value.match( + [&](int64_t i) { elem_map.gen(i); }, + [&](role_t r) { + elem_map.gen(lnav::enums::to_underlying(r)); + }, + [&](const intern_string_t& str) { elem_map.gen(str); }, + [&](const std::string& str) { elem_map.gen(str); }, + [&](const std::shared_ptr& lf) { + elem_map.gen(""); + }, + [&](const bookmark_metadata* bm) { elem_map.gen(""); }); + } + } + } +} + +std::string +to_json(const attr_line_t& al) +{ + yajlpp_gen gen; + + yajl_gen_config(gen, yajl_gen_beautify, false); + to_json(gen, al); + + return gen.to_string_fragment().to_string(); +} + +std::string +to_json(const lnav::console::user_message& um) +{ + yajlpp_gen gen; + + yajl_gen_config(gen, yajl_gen_beautify, false); + + { + yajlpp_map root_map(gen); + + root_map.gen("level"); + switch (um.um_level) { + case console::user_message::level::ok: + root_map.gen("ok"); + break; + case console::user_message::level::info: + root_map.gen("info"); + break; + case console::user_message::level::warning: + root_map.gen("warning"); + break; + case console::user_message::level::error: + root_map.gen("error"); + break; + } + + root_map.gen("message"); + to_json(gen, um.um_message); + root_map.gen("reason"); + to_json(gen, um.um_reason); + root_map.gen("snippets"); + { + yajlpp_array snippet_array(gen); + + for (const auto& snip : um.um_snippets) { + yajlpp_map snip_map(gen); + + snip_map.gen("source"); + snip_map.gen(snip.s_source); + snip_map.gen("line"); + snip_map.gen(snip.s_line); + snip_map.gen("column"); + snip_map.gen(snip.s_column); + snip_map.gen("content"); + to_json(gen, snip.s_content); + } + } + root_map.gen("help"); + to_json(gen, um.um_help); + } + + return gen.to_string_fragment().to_string(); +} + +static int +read_string_attr_type(yajlpp_parse_context* ypc, + const unsigned char* str, + size_t len) +{ + auto* sa = (string_attr*) ypc->ypc_obj_stack.top(); + auto type = std::string((const char*) str, len); + + if (type == "role") { + sa->sa_type = &VC_ROLE; + } else { + ensure(false); + } + return 1; +} + +static int +read_string_attr_int_value(yajlpp_parse_context* ypc, int64_t in) +{ + auto sa = (string_attr*) ypc->ypc_obj_stack.top(); + + if (sa->sa_type == &VC_ROLE) { + sa->sa_value = (role_t) in; + } + return 1; +} + +static const struct json_path_container string_attr_handlers = { + yajlpp::property_handler("start").for_field(&string_attr::sa_range, + &line_range::lr_start), + yajlpp::property_handler("end").for_field(&string_attr::sa_range, + &line_range::lr_end), + yajlpp::property_handler("type").add_cb(read_string_attr_type), + yajlpp::property_handler("value").add_cb(read_string_attr_int_value), +}; + +static const struct json_path_container attr_line_handlers = { + yajlpp::property_handler("str").for_field(&attr_line_t::al_string), + yajlpp::property_handler("attrs#") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, attr_line_t* root) { + root->al_attrs.resize(ypc.ypc_index + 1); + + return &root->al_attrs[ypc.ypc_index]; + }) + .with_children(string_attr_handlers), +}; + +template<> +attr_line_t +from_json(const std::string& json) +{ + yajlpp_parse_context ypc("string", &attr_line_handlers); + auto_mem handle(yajl_free); + attr_line_t retval; + + handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc); + ypc.with_handle(handle); + ypc.with_obj(retval); + ypc.parse(json); + ypc.complete_parse(); + + return retval; +} + +static const json_path_container snippet_handlers = { + yajlpp::property_handler("source").for_field(&console::snippet::s_source), + yajlpp::property_handler("line").for_field(&console::snippet::s_line), + yajlpp::property_handler("column").for_field(&console::snippet::s_column), + yajlpp::property_handler("content") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, console::snippet* snip) { + return &snip->s_content; + }) + .with_children(attr_line_handlers), +}; + +static const json_path_handler_base::enum_value_t LEVEL_ENUM[] = { + {"ok", lnav::console::user_message::level::ok}, + {"info", lnav::console::user_message::level::info}, + {"warning", lnav::console::user_message::level::warning}, + {"error", lnav::console::user_message::level::error}, + + json_path_handler_base::ENUM_TERMINATOR, +}; + +static const struct json_path_container user_message_handlers = { + yajlpp::property_handler("level") + .with_enum_values(LEVEL_ENUM) + .for_field(&console::user_message::um_level), + yajlpp::property_handler("message") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, + console::user_message* root) { return &root->um_message; }) + .with_children(attr_line_handlers), + yajlpp::property_handler("reason") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, + console::user_message* root) { return &root->um_reason; }) + .with_children(attr_line_handlers), + yajlpp::property_handler("snippets#") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, + console::user_message* root) { + root->um_snippets.resize(ypc.ypc_index + 1); + + return &root->um_snippets[ypc.ypc_index]; + }) + .with_children(snippet_handlers), + yajlpp::property_handler("help") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, + console::user_message* root) { return &root->um_help; }) + .with_children(attr_line_handlers), +}; + +template<> +lnav::console::user_message +from_json(const std::string& json) +{ + yajlpp_parse_context ypc("string", &user_message_handlers); + auto_mem handle(yajl_free); + lnav::console::user_message retval; + + handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc); + ypc.with_handle(handle); + ypc.with_obj(retval); + ypc.parse(json); + ypc.complete_parse(); + + return retval; +} + +} // namespace lnav diff --git a/src/lnav_util.hh b/src/lnav_util.hh index f0bdda52..2bb53f25 100644 --- a/src/lnav_util.hh +++ b/src/lnav_util.hh @@ -48,6 +48,7 @@ #include #include "base/intern_string.hh" +#include "base/lnav.console.hh" #include "base/result.h" #include "byte_array.hh" #include "config.h" @@ -229,6 +230,17 @@ finally(A act) // deduce action type std::string ok_prefix(std::string msg); std::string err_prefix(std::string msg); -Result err_to_ok(std::string msg); +Result err_to_ok( + lnav::console::user_message msg); + +namespace lnav { + +std::string to_json(const lnav::console::user_message& um); +std::string to_json(const attr_line_t& al); + +template +T from_json(const std::string& str); + +} // namespace lnav #endif diff --git a/src/log_format.cc b/src/log_format.cc index 1562e704..4fe2d9b3 100644 --- a/src/log_format.cc +++ b/src/log_format.cc @@ -638,7 +638,7 @@ bool external_log_format::scan_for_partial(shared_buffer_ref& sbr, size_t& len_out) const { - if (this->elf_type != ELF_TYPE_TEXT) { + if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) { return false; } @@ -660,13 +660,26 @@ external_log_format::scan_for_partial(shared_buffer_ref& sbr, return (int) len_out > pat->p_timestamp_end; } +std::vector +external_log_format::get_snippets() const +{ + std::vector retval; + + for (const auto& src_pair : this->elf_format_sources) { + retval.emplace_back(lnav::console::snippet::from(src_pair.first, "") + .with_line(src_pair.second)); + } + + return retval; +} + log_format::scan_result_t external_log_format::scan(logfile& lf, std::vector& dst, const line_info& li, shared_buffer_ref& sbr) { - if (this->elf_type == ELF_TYPE_JSON) { + if (this->elf_type == elf_type_t::ELF_TYPE_JSON) { yajlpp_parse_context& ypc = *(this->jlf_parse_context); logline ll(li.li_file_range.fr_offset, 0, 0, LEVEL_INFO); yajl_handle handle = this->jlf_yajl_handle.get(); @@ -852,7 +865,7 @@ external_log_format::scan(logfile& lf, = *mod_elf->elf_pattern_order[mod_pat_index]; if (mod_pat.p_pcre->match(mod_pc, mod_pi)) { - auto mod_level_cap + auto* mod_level_cap = mod_pc[mod_pat.p_level_field_index]; level = mod_elf->convert_level(mod_pi, mod_level_cap); @@ -991,7 +1004,7 @@ external_log_format::annotate(uint64_t line_number, struct line_range lr; pcre_context::capture_t *cap, *body_cap, *module_cap = nullptr; - if (this->elf_type != ELF_TYPE_TEXT) { + if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) { values = this->jlf_line_values; sa = this->jlf_line_attrs; return; @@ -1152,7 +1165,9 @@ external_log_format::rewrite(exec_context& ec, } auto _sg = ec.enter_source( - this->elf_name.to_string() + ":" + vd_iter->first.to_string(), 1); + this->elf_name.to_string() + ":" + vd_iter->first.to_string(), + 1, + vd.vd_rewriter); auto field_value = execute_any(ec, vd.vd_rewriter).orElse(err_to_ok).unwrap(); struct line_range adj_origin @@ -1297,7 +1312,7 @@ external_log_format::get_subline(const logline& ll, shared_buffer_ref& sbr, bool full_message) { - if (this->elf_type == ELF_TYPE_TEXT) { + if (this->elf_type == elf_type_t::ELF_TYPE_TEXT) { return; } @@ -1382,9 +1397,10 @@ external_log_format::get_subline(const logline& ll, jfe.jfe_default_value.size()); break; case JLF_VARIABLE: - lv_iter = find_if(this->jlf_line_values.begin(), - this->jlf_line_values.end(), - logline_value_cmp(&jfe.jfe_value)); + lv_iter = find_if( + this->jlf_line_values.begin(), + this->jlf_line_values.end(), + logline_value_cmp(&jfe.jfe_value.pp_value)); if (lv_iter != this->jlf_line_values.end()) { auto str = lv_iter->to_string(); size_t nl_pos = str.find('\n'); @@ -1458,7 +1474,7 @@ external_log_format::get_subline(const logline& ll, used_values[distance(this->jlf_line_values.begin(), lv_iter)] = true; - } else if (jfe.jfe_value == ts_field) { + } else if (jfe.jfe_value.pp_value == ts_field) { struct line_range lr; ssize_t ts_len; char ts[64]; @@ -1490,7 +1506,10 @@ external_log_format::get_subline(const logline& ll, this->jlf_line_values.begin(), lv_iter)] = true; } - } else if (jfe.jfe_value == level_field) { + } else if (jfe.jfe_value.pp_value == level_field + || jfe.jfe_value.pp_value + == this->elf_level_field) + { this->json_append(jfe, ll.get_level_name(), -1); } else { this->json_append(jfe, @@ -1623,7 +1642,7 @@ external_log_format::get_subline(const logline& ll, } void -external_log_format::build(std::vector& errors) +external_log_format::build(std::vector& errors) { if (!this->lf_timestamp_field.empty()) { auto& vd = this->elf_value_defs[this->lf_timestamp_field]; @@ -1635,6 +1654,10 @@ external_log_format::build(std::vector& errors) vd->vd_meta.lvm_kind = value_kind_t::VALUE_TEXT; vd->vd_internal = true; } + if (startswith(this->elf_level_field.get(), "/")) { + this->elf_level_field + = intern_string::lookup(this->elf_level_field.get() + 1); + } if (!this->elf_level_field.empty() && this->elf_value_defs.find(this->elf_level_field) == this->elf_value_defs.end()) @@ -1662,35 +1685,20 @@ external_log_format::build(std::vector& errors) if (!this->lf_timestamp_format.empty()) { this->lf_timestamp_format.push_back(nullptr); } - try { - this->elf_filename_pcre - = std::make_shared(this->elf_file_pattern); - } catch (const pcrepp::error& e) { - errors.push_back("error:" + this->elf_name.to_string() - + ".file-pattern:" + e.what()); - } for (auto iter = this->elf_patterns.begin(); iter != this->elf_patterns.end(); ++iter) { pattern& pat = *iter->second; + if (pat.p_pcre == nullptr) { + continue; + } + if (pat.p_module_format) { this->elf_has_module_format = true; } - try { - pat.p_pcre = std::make_unique(pat.p_string, PCRE_DOTALL); - } catch (const pcrepp::error& e) { - errors.push_back("error:" + this->elf_name.to_string() + ".regex[" - + iter->first + "]" + ":" + e.what()); - errors.push_back("error:" + this->elf_name.to_string() + ".regex[" - + iter->first + "]" + ":" + pat.p_string); - errors.push_back("error:" + this->elf_name.to_string() + ".regex[" - + iter->first + "]" + ":" - + std::string(e.e_offset, ' ') + "^"); - continue; - } for (pcre_named_capture::iterator name_iter = pat.p_pcre->named_begin(); name_iter != pat.p_pcre->named_end(); ++name_iter) @@ -1772,12 +1780,18 @@ external_log_format::build(std::vector& errors) this->elf_pattern_order.push_back(iter->second); } - if (this->elf_type != ELF_TYPE_TEXT) { + if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) { if (!this->elf_patterns.empty()) { - errors.push_back("error:" + this->elf_name.to_string() - + ": structured logs cannot have regexes"); - } - if (this->elf_type == ELF_TYPE_JSON) { + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t() + .append_quoted( + lnav::roles::symbol(this->elf_name.to_string())) + .append(" is not a valid log format")) + .with_reason("structured logs cannot have regexes") + .with_snippets(this->get_snippets())); + } + if (this->elf_type == elf_type_t::ELF_TYPE_JSON) { this->jlf_parse_context = std::make_shared( this->elf_name.to_string()); this->jlf_yajl_handle.reset( @@ -1791,18 +1805,13 @@ external_log_format::build(std::vector& errors) } else { if (this->elf_patterns.empty()) { - errors.push_back("error:" + this->elf_name.to_string() - + ": no regexes specified for format"); - } - } - - for (auto& elf_level_pattern : this->elf_level_patterns) { - try { - elf_level_pattern.second.lp_pcre = std::make_shared( - elf_level_pattern.second.lp_regex.c_str()); - } catch (const pcrepp::error& e) { - errors.push_back("error:" + this->elf_name.to_string() - + ".level:" + e.what()); + errors.emplace_back(lnav::console::user_message::error( + attr_line_t() + .append_quoted(lnav::roles::symbol( + this->elf_name.to_string())) + .append(" is not a valid log format")) + .with_reason("no regexes specified") + .with_snippets(this->get_snippets())); } } @@ -1825,22 +1834,31 @@ external_log_format::build(std::vector& errors) { if (this->lf_action_defs.find(*act_iter) == this->lf_action_defs.end()) { +#if 0 errors.push_back("error:" + this->elf_name.to_string() + ":" + vd->vd_meta.lvm_name.get() + ": cannot find action -- " + (*act_iter)); +#endif } } } - if (this->elf_type == ELF_TYPE_TEXT && this->elf_samples.empty()) { - errors.push_back( - "error:" + this->elf_name.to_string() - + ":no sample logs provided, all formats must have samples"); + if (this->elf_type == elf_type_t::ELF_TYPE_TEXT + && this->elf_samples.empty()) { + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t() + .append_quoted( + lnav::roles::symbol(this->elf_name.to_string())) + .append(" is not a valid log format")) + .with_reason("log message samples must be included in a format " + "definition") + .with_snippets(this->get_snippets())); } for (auto& elf_sample : this->elf_samples) { pcre_context_static<128> pc; - pcre_input pi(elf_sample.s_line); + pcre_input pi(elf_sample.s_line.pp_value); bool found = false; for (auto pat_iter = this->elf_pattern_order.begin(); @@ -1853,100 +1871,136 @@ external_log_format::build(std::vector& errors) continue; } - if (!pat.p_module_format - && pat.p_pcre->name_index(this->lf_timestamp_field.to_string()) - < 0) - { - errors.push_back("error:" + this->elf_name.to_string() - + ":timestamp field '" - + this->lf_timestamp_field.get() - + "' not found in pattern -- " + pat.p_string); + if (!pat.p_pcre->match(pc, pi)) { continue; } + found = true; - if (pat.p_pcre->match(pc, pi)) { - if (pat.p_module_format) { - found = true; - continue; - } - pcre_context::capture_t* ts_cap - = pc[this->lf_timestamp_field.get()]; - pcre_context::capture_t* level_cap - = pc[pat.p_level_field_index]; - const char* ts = pi.get_substr_start(ts_cap); - ssize_t ts_len = pc[this->lf_timestamp_field.get()]->length(); - const char* const* custom_formats - = this->get_timestamp_formats(); - date_time_scanner dts; - struct timeval tv; - struct exttm tm; - - if (ts_cap->c_begin == 0) { - pat.p_timestamp_end = ts_cap->c_end; + if (pat.p_module_format) { + continue; + } + + if (pat.p_pcre->name_index(this->lf_timestamp_field.to_string()) + < 0) { + attr_line_t notes; + bool first_note = true; + + if (pat.p_pcre->p_named_count > 0) { + notes.append("the following captures are available:\n "); } - found = true; - if (ts_len == -1 - || dts.scan(ts, ts_len, custom_formats, &tm, tv) == nullptr) + for (auto name_iter = pat.p_pcre->named_begin(); + name_iter != pat.p_pcre->named_end(); + ++name_iter) { - errors.push_back("error:" + this->elf_name.to_string() - + ":invalid sample -- " - + elf_sample.s_line); - errors.push_back("error:" + this->elf_name.to_string() - + ":unrecognized timestamp format -- " - + ts); - - if (custom_formats == nullptr) { - for (int lpc = 0; PTIMEC_FORMATS[lpc].pf_fmt != nullptr; - lpc++) { - off_t off = 0; - - PTIMEC_FORMATS[lpc].pf_func(&tm, ts, off, ts_len); - errors.push_back( - " format: " - + std::string(PTIMEC_FORMATS[lpc].pf_fmt) - + "; matched: " + std::string(ts, off)); - } - } else { - for (int lpc = 0; custom_formats[lpc] != nullptr; lpc++) - { - off_t off = 0; - - ptime_fmt( - custom_formats[lpc], &tm, ts, off, ts_len); - errors.push_back( - " format: " + std::string(custom_formats[lpc]) - + "; matched: " + std::string(ts, off)); - } + if (!first_note) { + notes.append(", "); } + notes.append(lnav::roles::symbol(name_iter->pnc_name)); + first_note = false; } + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("invalid value for property ") + .append_quoted(lnav::roles::symbol( + fmt::format(FMT_STRING("/{}/timestamp-field"), + this->elf_name)))) + .with_reason( + attr_line_t() + .append_quoted(this->lf_timestamp_field) + .append(" was not found in the pattern at ") + .append(lnav::roles::symbol(pat.p_config_path))) + .with_note(notes) + .with_snippets(this->get_snippets())); + continue; + } + + const auto* ts_cap = pc[this->lf_timestamp_field.get()]; + const auto* level_cap = pc[pat.p_level_field_index]; + const char* ts = pi.get_substr_start(ts_cap); + ssize_t ts_len = pc[this->lf_timestamp_field.get()]->length(); + const char* const* custom_formats = this->get_timestamp_formats(); + date_time_scanner dts; + struct timeval tv; + struct exttm tm; - log_level_t level = this->convert_level(pi, level_cap); - - if (elf_sample.s_level != LEVEL_UNKNOWN) { - if (elf_sample.s_level != level) { - errors.push_back("error:" + this->elf_name.to_string() - + ":invalid sample -- " - + elf_sample.s_line); - errors.push_back( - "error:" + this->elf_name.to_string() - + ":parsed level '" + level_names[level] - + "' does not match expected level of '" - + level_names[elf_sample.s_level] + "'"); + if (ts_cap->c_begin == 0) { + pat.p_timestamp_end = ts_cap->c_end; + } + if (ts_len == -1 + || dts.scan(ts, ts_len, custom_formats, &tm, tv) == nullptr) { + attr_line_t notes; + + notes.append("the following formats were tried:"); + if (custom_formats == nullptr) { + for (int lpc = 0; PTIMEC_FORMATS[lpc].pf_fmt != nullptr; + lpc++) { + off_t off = 0; + + PTIMEC_FORMATS[lpc].pf_func(&tm, ts, off, ts_len); + notes.append("\n ") + .append(ts, (size_t) ts_len) + .append("\n") + .append(2 + off, ' ') + .append(lnav::roles::comment("^ ")) + .append_quoted( + lnav::roles::symbol(PTIMEC_FORMATS[lpc].pf_fmt)) + .append( + lnav::roles::comment(" matched up to here")); + } + } else { + for (int lpc = 0; custom_formats[lpc] != nullptr; lpc++) { + off_t off = 0; + + ptime_fmt(custom_formats[lpc], &tm, ts, off, ts_len); + notes.append("\n ") + .append(ts, (size_t) ts_len) + .append("\n") + .append(2 + off, ' ') + .append(lnav::roles::comment("^ ")) + .append_quoted( + lnav::roles::symbol(custom_formats[lpc])) + .append( + lnav::roles::comment(" matched up to here")); } } + + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("invalid sample log message ") + .append_quoted(elf_sample.s_line.pp_value)) + .with_reason(attr_line_t("unrecognized timestamp -- ") + .append(ts, (size_t) ts_len)) + .with_snippet(elf_sample.s_line.to_snippet()) + .with_note(notes)); + } + + log_level_t level = this->convert_level(pi, level_cap); + + if (elf_sample.s_level != LEVEL_UNKNOWN + && elf_sample.s_level != level) { + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("invalid sample log message ") + .append_quoted(elf_sample.s_line.pp_value)) + .with_reason(attr_line_t() + .append_quoted(lnav::roles::symbol( + level_names[level])) + .append(" does not match the expected " + "level of ") + .append_quoted(lnav::roles::symbol( + level_names[elf_sample.s_level]))) + .with_snippet(elf_sample.s_line.to_snippet())); } } - if (!found) { - errors.push_back("error:" + this->elf_name.to_string() - + ":invalid sample -- " - + elf_sample.s_line); + if (!found && !this->elf_pattern_order.empty()) { + attr_line_t notes( + "the following shows how each pattern matched this sample:\n"); + size_t max_name_width = 0; - for (auto pat_iter = this->elf_pattern_order.begin(); - pat_iter != this->elf_pattern_order.end(); - ++pat_iter) - { - pattern& pat = *(*pat_iter); + notes.append(" ").append_quoted(elf_sample.s_line.pp_value); + for (const auto& pat_iter : this->elf_pattern_order) { + pattern& pat = *pat_iter; if (!pat.p_pcre) { continue; @@ -1954,19 +2008,42 @@ external_log_format::build(std::vector& errors) size_t partial_len = pat.p_pcre->match_partial(pi); - if (partial_len > 0) { - errors.push_back( - "error:" + this->elf_name.to_string() - + ":partial sample matched -- " - + elf_sample.s_line.substr(0, partial_len)); - errors.push_back("error: against pattern " - + (*pat_iter)->p_config_path + " -- " - + (*pat_iter)->p_string); - } else { - errors.push_back("error:" + this->elf_name.to_string() - + ":no partial match found"); + notes.append("\n ") + .append(partial_len, ' ') + .append(lnav::roles::comment("^ ")) + .append(lnav::roles::symbol(pat.p_name)) + .append(lnav::roles::comment(" matched up to here")); + + max_name_width = std::max(max_name_width, pat.p_name.size()); + } + + attr_line_t help; + for (const auto& pat_iter : this->elf_pattern_order) { + if (!pat_iter->p_pcre) { + help.append( + lnav::roles::symbol(fmt::format(FMT_STRING("{:{}}"), + pat_iter->p_name, + max_name_width))) + .append(" is invalid"); + continue; } + + help + .append(lnav::roles::symbol(fmt::format( + FMT_STRING("{:{}}"), pat_iter->p_name, max_name_width))) + .append(" = ") + .append(pat_iter->p_pcre->get_pattern()) + .append("\n"); } + + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("invalid sample log message ") + .append_quoted(elf_sample.s_line.pp_value)) + .with_reason("sample does not match any patterns") + .with_snippet(elf_sample.s_line.to_snippet()) + .with_note(notes) + .with_help(help)); } } @@ -2002,41 +2079,48 @@ external_log_format::build(std::vector& errors) = intern_string::lookup("__level__"); json_format_element& jfe = *iter; - if (startswith(jfe.jfe_value.get(), "/")) { - jfe.jfe_value = intern_string::lookup(jfe.jfe_value.get() + 1); + if (startswith(jfe.jfe_value.pp_value.get(), "/")) { + jfe.jfe_value.pp_value + = intern_string::lookup(jfe.jfe_value.pp_value.get() + 1); } if (!jfe.jfe_ts_format.empty()) { - if (!jfe.jfe_value.empty() && jfe.jfe_value != ts) { + if (!jfe.jfe_value.pp_value.empty() && jfe.jfe_value.pp_value != ts) + { log_warning( "%s:line-format[%d]:ignoring field '%s' since " "timestamp-format was used", this->elf_name.get(), format_index, - jfe.jfe_value.get()); + jfe.jfe_value.pp_value.get()); } - jfe.jfe_value = ts; + jfe.jfe_value.pp_value = ts; } switch (jfe.jfe_type) { case JLF_VARIABLE: { - auto vd_iter = this->elf_value_defs.find(jfe.jfe_value); - if (jfe.jfe_value == ts) { + auto vd_iter + = this->elf_value_defs.find(jfe.jfe_value.pp_value); + if (jfe.jfe_value.pp_value == ts) { this->elf_value_defs[this->lf_timestamp_field] ->vd_meta.lvm_hidden = true; - } else if (jfe.jfe_value == level_field) { + } else if (jfe.jfe_value.pp_value == level_field) { this->elf_value_defs[this->elf_level_field] ->vd_meta.lvm_hidden = true; } else if (vd_iter == this->elf_value_defs.end()) { - char index_str[32]; - - snprintf(index_str, sizeof(index_str), "%d", format_index); - errors.push_back( - "error:" + this->elf_name.to_string() + ":line-format[" - + index_str - + "]:line format variable is not defined -- " - + jfe.jfe_value.to_string()); + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("invalid line format element ") + .append_quoted(lnav::roles::symbol(fmt::format( + FMT_STRING("/{}/line-format/{}/field"), + this->elf_name, + format_index)))) + .with_reason( + attr_line_t() + .append_quoted(jfe.jfe_value.pp_value) + .append(" is not a defined value")) + .with_snippet(jfe.jfe_value.to_snippet())); } break; } @@ -2053,30 +2137,44 @@ external_log_format::build(std::vector& errors) for (auto& hd_pair : this->elf_highlighter_patterns) { external_log_format::highlighter_def& hd = hd_pair.second; - const std::string& pattern = hd.hd_pattern; const char* errptr; auto fg = styling::color_unit::make_empty(); auto bg = styling::color_unit::make_empty(); int eoff, attrs = 0; - if (!hd.hd_color.empty()) { - fg = styling::color_unit::from_str(hd.hd_color) + if (!hd.hd_color.pp_value.empty()) { + fg = styling::color_unit::from_str(hd.hd_color.pp_value) .unwrapOrElse([&](const auto& msg) { - errors.push_back("error:" + this->elf_name.to_string() - + ":highlighters/" - + hd_pair.first.to_string() - + "/color:" + msg); + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t() + .append_quoted(hd.hd_color.pp_value) + .append(" is not a valid color value for " + "property ") + .append_quoted(lnav::roles::symbol( + hd.hd_color.pp_path.to_string()))) + .with_reason(msg) + .with_snippet(hd.hd_color.to_snippet())); return styling::color_unit::make_empty(); }); } - if (!hd.hd_background_color.empty()) { - bg = styling::color_unit::from_str(hd.hd_background_color) + if (!hd.hd_background_color.pp_value.empty()) { + bg = styling::color_unit::from_str(hd.hd_background_color.pp_value) .unwrapOrElse([&](const auto& msg) { - errors.push_back("error:" + this->elf_name.to_string() - + ":highlighters/" - + hd_pair.first.to_string() - + "/color:" + msg); + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t() + .append_quoted( + hd.hd_background_color.pp_value) + .append(" is not a valid color value for " + "property ") + .append_quoted(lnav::roles::symbol( + hd.hd_background_color.pp_path + .to_string()))) + .with_reason(msg) + .with_snippet( + hd.hd_background_color.to_snippet())); return styling::color_unit::make_empty(); }); } @@ -2088,58 +2186,46 @@ external_log_format::build(std::vector& errors) attrs |= A_BLINK; } - pcre* code = pcre_compile( - pattern.c_str(), PCRE_CASELESS, &errptr, &eoff, nullptr); + if (hd.hd_pattern != nullptr) { + pcre* code = pcre_compile(hd.hd_pattern->get_pattern().c_str(), + PCRE_CASELESS, + &errptr, + &eoff, + nullptr); - if (code == nullptr) { - errors.push_back("error:" + this->elf_name.to_string() - + ":highlighters/" + hd_pair.first.to_string() - + ":" + std::string(errptr)); - errors.push_back("error:" + this->elf_name.to_string() - + ":highlighters/" + hd_pair.first.to_string() - + ":" + pattern); - errors.push_back("error:" + this->elf_name.to_string() - + ":highlighters/" + hd_pair.first.to_string() - + ":" + std::string(eoff, ' ') + "^"); - } else { - this->lf_highlighters.emplace_back(code); - this->lf_highlighters.back() - .with_pattern(pattern) - .with_format_name(this->elf_name) - .with_color(fg, bg) - .with_attrs(attrs); + if (code == nullptr) { + log_error("unable to recompile highlighter pattern"); + } else { + this->lf_highlighters.emplace_back(code); + this->lf_highlighters.back() + .with_pattern(hd.hd_pattern->get_pattern()) + .with_format_name(this->elf_name) + .with_color(fg, bg) + .with_attrs(attrs); + } } } } void -external_log_format::register_vtabs(log_vtab_manager* vtab_manager, - std::vector& errors) +external_log_format::register_vtabs( + log_vtab_manager* vtab_manager, + std::vector& errors) { - for (auto search_iter = this->elf_search_tables.begin(); - search_iter != this->elf_search_tables.end(); - ++search_iter) - { - auto re_res = pcrepp::from_str(search_iter->second, - log_search_table::pattern_options()); - - if (re_res.isErr()) { - errors.emplace_back(fmt::format( - FMT_STRING("error:{}:{}:unable to compile regex '{}': {}"), - this->elf_name.get(), - search_iter->first.get(), - search_iter->second, - re_res.unwrapErr().ce_msg)); + for (auto& elf_search_table : this->elf_search_tables) { + if (elf_search_table.second.std_pattern == nullptr) { continue; } - auto lst = std::make_shared(re_res.unwrap(), - search_iter->first); + auto lst = std::make_shared( + *elf_search_table.second.std_pattern, elf_search_table.first); auto errmsg = vtab_manager->register_vtab(lst); if (!errmsg.empty()) { +#if 0 errors.push_back("error:" + this->elf_name.to_string() + ":" + search_iter->first.to_string() + ":unable to register table -- " + errmsg); +#endif } } } @@ -2156,7 +2242,7 @@ external_log_format::match_samples(const std::vector& samples) const } pcre_context_static<128> pc; - pcre_input pi(sample_iter.s_line); + pcre_input pi(sample_iter.s_line.pp_value); if (pat.p_pcre->match(pc, pi)) { return true; @@ -2317,7 +2403,7 @@ external_log_format::specialized(int fmt_lock) retval->lf_pattern_locks.emplace_back(0, fmt_lock); } - if (this->elf_type == ELF_TYPE_JSON) { + if (this->elf_type == elf_type_t::ELF_TYPE_JSON) { this->jlf_parse_context = std::make_shared( this->elf_name.to_string()); this->jlf_yajl_handle.reset( diff --git a/src/log_format_ext.hh b/src/log_format_ext.hh index 63dc2e70..56311865 100644 --- a/src/log_format_ext.hh +++ b/src/log_format_ext.hh @@ -42,9 +42,9 @@ class module_format; class external_log_format : public log_format { public: struct sample { - sample() : s_level(LEVEL_UNKNOWN){}; + sample() : s_level(LEVEL_UNKNOWN) {} - std::string s_line; + positioned_property s_line; log_level_t s_level; }; @@ -87,9 +87,9 @@ public: }; struct pattern { + std::string p_name; std::string p_config_path; - std::string p_string; - std::unique_ptr p_pcre; + std::shared_ptr p_pcre; std::vector p_value_by_index; std::vector p_numeric_value_indexes; int p_timestamp_field_index{-1}; @@ -116,12 +116,8 @@ public: }; external_log_format(const intern_string_t name) - : elf_column_count(0), elf_timestamp_divisor(1.0), - elf_level_field(intern_string::lookup("level", -1)), + : elf_level_field(intern_string::lookup("level", -1)), elf_body_field(intern_string::lookup("body", -1)), - elf_container(false), elf_has_module_format(false), - elf_builtin_format(false), elf_type(ELF_TYPE_TEXT), - jlf_hide_extra(false), jlf_cached_offset(-1), jlf_yajl_handle(nullptr, yajl_handle_deleter()), elf_name(name) { this->jlf_line_offsets.reserve(128); @@ -154,10 +150,10 @@ public: string_attrs_t& sa, std::string& value_out); - void build(std::vector& errors); + void build(std::vector& errors); void register_vtabs(log_vtab_manager* vtab_manager, - std::vector& errors); + std::vector& errors); bool match_samples(const std::vector& samples) const; @@ -246,7 +242,7 @@ public: jfe_text_transform(transform_t::NONE){}; json_log_field jfe_type; - intern_string_t jfe_value; + positioned_property jfe_value; std::string jfe_default_value; long long jfe_min_width; long long jfe_max_width; @@ -263,7 +259,7 @@ public: bool operator()(const json_format_element& jfe) const { return (this->jfc_type == jfe.jfe_type - && this->jfc_field_name == jfe.jfe_value); + && this->jfc_field_name == jfe.jfe_value.pp_value); }; json_log_field jfc_type; @@ -273,9 +269,9 @@ public: struct highlighter_def { highlighter_def() : hd_underline(false), hd_blink(false) {} - std::string hd_pattern; - std::string hd_color; - std::string hd_background_color; + std::shared_ptr hd_pattern; + positioned_property hd_color; + positioned_property hd_background_color; bool hd_underline; bool hd_blink; }; @@ -317,7 +313,7 @@ public: std::string get_pattern_name(uint64_t line_number) const { - if (this->elf_type != ELF_TYPE_TEXT) { + if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) { return "structured"; } int pat_index = this->pattern_index_for_line(line_number); @@ -326,15 +322,15 @@ public: std::string get_pattern_regex(uint64_t line_number) const { - if (this->elf_type != ELF_TYPE_TEXT) { + if (this->elf_type != elf_type_t::ELF_TYPE_TEXT) { return ""; } int pat_index = this->pattern_index_for_line(line_number); - return this->elf_pattern_order[pat_index]->p_string; + return this->elf_pattern_order[pat_index]->p_pcre->get_pattern(); } log_level_t convert_level(const pcre_input& pi, - pcre_context::capture_t* level_cap) const + const pcre_context::capture_t* level_cap) const { log_level_t retval = LEVEL_INFO; @@ -360,12 +356,13 @@ public: return retval; } - typedef std::map mod_map_t; + using mod_map_t = std::map; static mod_map_t MODULE_FORMATS; static std::vector> GRAPH_ORDERED_FORMATS; std::set elf_source_path; + std::unordered_map elf_format_sources; std::list elf_collision; std::string elf_file_pattern; std::set elf_mime_types; @@ -377,8 +374,8 @@ public: elf_value_defs; std::vector> elf_value_def_order; std::vector> elf_numeric_value_defs; - int elf_column_count; - double elf_timestamp_divisor; + int elf_column_count{0}; + double elf_timestamp_divisor{1.0}; intern_string_t elf_level_field; pcrepp elf_level_pointer; intern_string_t elf_body_field; @@ -386,19 +383,24 @@ public: intern_string_t elf_opid_field; std::map elf_level_patterns; std::vector> elf_level_pairs; - bool elf_container; - bool elf_has_module_format; - bool elf_builtin_format; - std::vector> elf_search_tables; + bool elf_container{false}; + bool elf_has_module_format{false}; + bool elf_builtin_format{false}; + + struct search_table_def { + std::shared_ptr std_pattern; + }; + + std::map elf_search_tables; std::map elf_highlighter_patterns; - enum elf_type_t { + enum class elf_type_t { ELF_TYPE_TEXT, ELF_TYPE_JSON, ELF_TYPE_CSV, }; - elf_type_t elf_type; + elf_type_t elf_type{elf_type_t::ELF_TYPE_TEXT}; void json_append_to_cache(const char* value, ssize_t len) { @@ -455,12 +457,14 @@ public: return lvm; } - bool jlf_hide_extra; + std::vector get_snippets() const; + + bool jlf_hide_extra{false}; std::vector jlf_line_format; int jlf_line_format_init_count{0}; std::vector jlf_line_values; - off_t jlf_cached_offset; + off_t jlf_cached_offset{-1}; bool jlf_cached_full{false}; std::vector jlf_line_offsets; shared_buffer jlf_share_manager; diff --git a/src/log_format_fwd.hh b/src/log_format_fwd.hh index d6fce685..10961de3 100644 --- a/src/log_format_fwd.hh +++ b/src/log_format_fwd.hh @@ -34,7 +34,7 @@ #include -#include "attr_line.hh" +#include "base/string_attr_type.hh" #include "byte_array.hh" #include "log_level.hh" #include "ptimec.hh" diff --git a/src/log_format_loader.cc b/src/log_format_loader.cc index 7edcd909..6d82cafb 100644 --- a/src/log_format_loader.cc +++ b/src/log_format_loader.cc @@ -61,16 +61,17 @@ static void extract_metadata(const char* contents, size_t len, struct script_metadata& meta_out); -typedef std::map> - log_formats_map_t; +using log_formats_map_t + = std::map>; static auto intern_lifetime = intern_string::get_table_lifetime(); static log_formats_map_t LOG_FORMATS; struct userdata { + yajlpp_parse_context* ud_parse_context{nullptr}; ghc::filesystem::path ud_format_path; std::vector* ud_format_names{nullptr}; - std::vector* ud_errors{nullptr}; + std::vector* ud_errors{nullptr}; }; static external_log_format* @@ -86,12 +87,19 @@ ensure_format(const yajlpp_provider_context& ypc, userdata* ud) retval = LOG_FORMATS[name].get(); log_debug("Loading format -- %s", name.get()); } - retval->elf_source_path.insert(ud->ud_format_path.filename().string()); + retval->elf_source_path.insert(ud->ud_format_path.parent_path().string()); if (find(formats->begin(), formats->end(), name) == formats->end()) { formats->push_back(name); } + auto srcs_iter + = retval->elf_format_sources.find(ud->ud_format_path.string()); + if (srcs_iter == retval->elf_format_sources.end()) { + retval->elf_format_sources[ud->ud_format_path.string()] + = ud->ud_parse_context->get_line_number(); + } + if (ud->ud_format_path.empty()) { retval->elf_builtin_format = true; } @@ -110,8 +118,9 @@ pattern_provider(const yajlpp_provider_context& ypc, external_log_format* elf) } if (pat->p_config_path.empty()) { - pat->p_config_path - = elf->get_name().to_string() + "/regex/" + regex_name; + pat->p_name = regex_name; + pat->p_config_path = fmt::format( + FMT_STRING("/{}/regex/{}"), elf->get_name(), regex_name); } return pat.get(); @@ -172,16 +181,13 @@ read_format_bool(yajlpp_parse_context* ypc, int val) auto elf = (external_log_format*) ypc->ypc_obj_stack.top(); auto field_name = ypc->get_path_fragment(1); - if (field_name == "convert-to-local-time") + if (field_name == "convert-to-local-time") { elf->lf_date_time.dts_local_time = val; - else if (field_name == "json") { + } else if (field_name == "json") { if (val) { - elf->elf_type = external_log_format::ELF_TYPE_JSON; + elf->elf_type = external_log_format::elf_type_t::ELF_TYPE_JSON; } - } else if (field_name == "hide-extra") - elf->jlf_hide_extra = val; - else if (field_name == "multiline") - elf->lf_multiline = val; + } return 1; } @@ -194,11 +200,16 @@ read_format_double(yajlpp_parse_context* ypc, double val) if (field_name == "timestamp-divisor") { if (val <= 0) { - fprintf(stderr, - "error:%s: timestamp-divisor cannot be less " - "than or equal to zero\n", - ypc->get_path_fragment(0).c_str()); - return 0; + ypc->report_error( + lnav::console::user_message::error( + attr_line_t() + .append_quoted(fmt::to_string(val)) + .append(" is not a valid value for ") + .append_quoted(lnav::roles::symbol( + ypc->get_full_path().to_string()))) + .with_reason("value cannot be less than or equal to zero") + .with_snippet(ypc->get_snippet()) + .with_help(ypc->ypc_current_handler->get_help_text(ypc))); } elf->elf_timestamp_divisor = val; } @@ -214,11 +225,16 @@ read_format_int(yajlpp_parse_context* ypc, long long val) if (field_name == "timestamp-divisor") { if (val <= 0) { - fprintf(stderr, - "error:%s: timestamp-divisor cannot be less " - "than or equal to zero\n", - ypc->get_path_fragment(0).c_str()); - return 0; + ypc->report_error( + lnav::console::user_message::error( + attr_line_t() + .append_quoted(fmt::to_string(val)) + .append(" is not a valid value for ") + .append_quoted(lnav::roles::symbol( + ypc->get_full_path().to_string()))) + .with_reason("value cannot be less than or equal to zero") + .with_snippet(ypc->get_snippet()) + .with_help(ypc->ypc_current_handler->get_help_text(ypc))); } elf->elf_timestamp_divisor = val; } @@ -238,37 +254,33 @@ read_format_field(yajlpp_parse_context* ypc, auto field_name = ypc->get_path_fragment(1); if (field_name == "file-pattern") { - elf->elf_file_pattern = value; - } else if (field_name == "level-field") { - elf->elf_level_field = intern_string::lookup(value); + try { + elf->elf_file_pattern = value; + elf->elf_filename_pcre + = std::make_shared(elf->elf_file_pattern); + } catch (const pcrepp::error& e) { + pcrepp::compile_error ce; + + ce.ce_msg = e.what(); + ce.ce_offset = e.e_offset; + ypc->ypc_current_handler->report_regex_value_error(ypc, value, ce); + } } else if (field_name == "level-pointer") { auto pcre_res = pcrepp::from_str(value); if (pcre_res.isErr()) { - ypc->ypc_error_reporter( - *ypc, - lnav_log_level_t::ERROR, - fmt::format( - FMT_STRING("error:{}:{}:invalid regular expression for " - "level-pointer -- {}"), - ypc->ypc_source, - ypc->get_line_number(), - pcre_res.unwrapErr().ce_msg) - .c_str()); + auto pcre_error = pcre_res.unwrapErr(); + + ypc->ypc_current_handler->report_regex_value_error( + ypc, value, pcre_error); } else { elf->elf_level_pointer = pcre_res.unwrap(); } - } else if (field_name == "timestamp-field") { - elf->lf_timestamp_field = intern_string::lookup(value); - } else if (field_name == "body-field") { - elf->elf_body_field = intern_string::lookup(value); } else if (field_name == "timestamp-format") { elf->lf_timestamp_format.push_back(intern_string::lookup(value)->get()); } else if (field_name == "module-field") { elf->elf_module_id_field = intern_string::lookup(value); elf->elf_container = true; - } else if (field_name == "opid-field") { - elf->elf_opid_field = intern_string::lookup(value); } else if (field_name == "mime-types") { auto value_opt = ypc->ypc_current_handler->to_enum_value(value); if (value_opt) { @@ -288,6 +300,17 @@ read_levels(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) log_level_t level = string2level(level_name_or_number.c_str()); elf->elf_level_patterns[level].lp_regex = regex; + try { + elf->elf_level_patterns[level].lp_pcre + = std::make_shared(regex); + } catch (const pcrepp::error& e) { + pcrepp::compile_error ce; + + ce.ce_msg = e.what(); + ce.ce_offset = e.e_offset; + ypc->ypc_current_handler->report_regex_value_error(ypc, regex, ce); + } + return 1; } @@ -312,8 +335,9 @@ read_action_def(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) auto val = std::string((const char*) str, len); elf->lf_action_defs[action_name].ad_name = action_name; - if (field_name == "label") + if (field_name == "label") { elf->lf_action_defs[action_name].ad_label = val; + } return 1; } @@ -376,39 +400,27 @@ read_json_constant(yajlpp_parse_context* ypc, return 1; } -static int -create_search_table(yajlpp_parse_context* ypc, - const unsigned char* str, - size_t len) -{ - auto elf = (external_log_format*) ypc->ypc_obj_stack.top(); - const intern_string_t table_name = ypc->get_path_fragment_i(2); - auto regex = std::string((const char*) str, len); - - elf->elf_search_tables.emplace_back(table_name, regex); - - return 1; -} - static struct json_path_container pattern_handlers = { yajlpp::property_handler("pattern") .with_synopsis("") .with_description( "The regular expression to match a log message and capture fields.") .with_min_length(1) - .FOR_FIELD(external_log_format::pattern, p_string), + .for_field(&external_log_format::pattern::p_pcre), yajlpp::property_handler("module-format") .with_synopsis("") .with_description( "If true, this pattern will only be used to parse message bodies " "of container formats, like syslog") - .for_field(&external_log_format::pattern::p_module_format)}; + .for_field(&external_log_format::pattern::p_module_format), +}; -static const json_path_handler_base::enum_value_t ALIGN_ENUM[] - = {{"left", external_log_format::json_format_element::align_t::LEFT}, - {"right", external_log_format::json_format_element::align_t::RIGHT}, +static const json_path_handler_base::enum_value_t ALIGN_ENUM[] = { + {"left", external_log_format::json_format_element::align_t::LEFT}, + {"right", external_log_format::json_format_element::align_t::RIGHT}, - json_path_handler_base::ENUM_TERMINATOR}; + json_path_handler_base::ENUM_TERMINATOR, +}; static const json_path_handler_base::enum_value_t OVERFLOW_ENUM[] = { {"abbrev", external_log_format::json_format_element::overflow_t::ABBREV}, @@ -416,18 +428,20 @@ static const json_path_handler_base::enum_value_t OVERFLOW_ENUM[] = { external_log_format::json_format_element::overflow_t::TRUNCATE}, {"dot-dot", external_log_format::json_format_element::overflow_t::DOTDOT}, - json_path_handler_base::ENUM_TERMINATOR}; + json_path_handler_base::ENUM_TERMINATOR, +}; -static const json_path_handler_base::enum_value_t TRANSFORM_ENUM[] - = {{"none", external_log_format::json_format_element::transform_t::NONE}, - {"uppercase", - external_log_format::json_format_element::transform_t::UPPERCASE}, - {"lowercase", - external_log_format::json_format_element::transform_t::LOWERCASE}, - {"capitalize", - external_log_format::json_format_element::transform_t::CAPITALIZE}, +static const json_path_handler_base::enum_value_t TRANSFORM_ENUM[] = { + {"none", external_log_format::json_format_element::transform_t::NONE}, + {"uppercase", + external_log_format::json_format_element::transform_t::UPPERCASE}, + {"lowercase", + external_log_format::json_format_element::transform_t::LOWERCASE}, + {"capitalize", + external_log_format::json_format_element::transform_t::CAPITALIZE}, - json_path_handler_base::ENUM_TERMINATOR}; + json_path_handler_base::ENUM_TERMINATOR, +}; static struct json_path_container line_format_handlers = { yajlpp::property_handler("field") @@ -435,78 +449,83 @@ static struct json_path_container line_format_handlers = { .with_description( "The name of the field to substitute at this position") .with_min_length(1) - .FOR_FIELD(external_log_format::json_format_element, jfe_value), + .for_field(&external_log_format::json_format_element::jfe_value), yajlpp::property_handler("default-value") .with_synopsis("") .with_description( "The default value for this position if the field is null") - .FOR_FIELD(external_log_format::json_format_element, jfe_default_value), + .for_field( + &external_log_format::json_format_element::jfe_default_value), yajlpp::property_handler("timestamp-format") .with_synopsis("") .with_min_length(1) .with_description("The strftime(3) format for this field") - .FOR_FIELD(external_log_format::json_format_element, jfe_ts_format), + .for_field(&external_log_format::json_format_element::jfe_ts_format), yajlpp::property_handler("min-width") .with_min_value(0) .with_synopsis("") .with_description("The minimum width of the field") - .FOR_FIELD(external_log_format::json_format_element, jfe_min_width), + .for_field(&external_log_format::json_format_element::jfe_min_width), yajlpp::property_handler("max-width") .with_min_value(0) .with_synopsis("") .with_description("The maximum width of the field") - .FOR_FIELD(external_log_format::json_format_element, jfe_max_width), + .for_field(&external_log_format::json_format_element::jfe_max_width), yajlpp::property_handler("align") .with_synopsis("left|right") .with_description( "Align the text in the column to the left or right side") .with_enum_values(ALIGN_ENUM) - .FOR_FIELD(external_log_format::json_format_element, jfe_align), + .for_field(&external_log_format::json_format_element::jfe_align), yajlpp::property_handler("overflow") .with_synopsis("abbrev|truncate|dot-dot") .with_description("Overflow style") .with_enum_values(OVERFLOW_ENUM) - .FOR_FIELD(external_log_format::json_format_element, jfe_overflow), + .for_field(&external_log_format::json_format_element::jfe_overflow), yajlpp::property_handler("text-transform") .with_synopsis("none|uppercase|lowercase|capitalize") .with_description("Text transformation") .with_enum_values(TRANSFORM_ENUM) - .FOR_FIELD(external_log_format::json_format_element, - jfe_text_transform)}; - -static const json_path_handler_base::enum_value_t KIND_ENUM[] - = {{"string", value_kind_t::VALUE_TEXT}, - {"integer", value_kind_t::VALUE_INTEGER}, - {"float", value_kind_t::VALUE_FLOAT}, - {"boolean", value_kind_t::VALUE_BOOLEAN}, - {"json", value_kind_t::VALUE_JSON}, - {"struct", value_kind_t::VALUE_STRUCT}, - {"quoted", value_kind_t::VALUE_QUOTED}, - {"xml", value_kind_t::VALUE_XML}, + .for_field( + &external_log_format::json_format_element::jfe_text_transform), +}; - json_path_handler_base::ENUM_TERMINATOR}; +static const json_path_handler_base::enum_value_t KIND_ENUM[] = { + {"string", value_kind_t::VALUE_TEXT}, + {"integer", value_kind_t::VALUE_INTEGER}, + {"float", value_kind_t::VALUE_FLOAT}, + {"boolean", value_kind_t::VALUE_BOOLEAN}, + {"json", value_kind_t::VALUE_JSON}, + {"struct", value_kind_t::VALUE_STRUCT}, + {"quoted", value_kind_t::VALUE_QUOTED}, + {"xml", value_kind_t::VALUE_XML}, + + json_path_handler_base::ENUM_TERMINATOR, +}; -static const json_path_handler_base::enum_value_t SCALE_OP_ENUM[] - = {{"identity", scale_op_t::SO_IDENTITY}, - {"multiply", scale_op_t::SO_MULTIPLY}, - {"divide", scale_op_t::SO_DIVIDE}, +static const json_path_handler_base::enum_value_t SCALE_OP_ENUM[] = { + {"identity", scale_op_t::SO_IDENTITY}, + {"multiply", scale_op_t::SO_MULTIPLY}, + {"divide", scale_op_t::SO_DIVIDE}, - json_path_handler_base::ENUM_TERMINATOR}; + json_path_handler_base::ENUM_TERMINATOR, +}; -static struct json_path_container scaling_factor_handlers - = {yajlpp::pattern_property_handler("op") - .with_enum_values(SCALE_OP_ENUM) - .FOR_FIELD(scaling_factor, sf_op), +static struct json_path_container scaling_factor_handlers = { + yajlpp::pattern_property_handler("op") + .with_enum_values(SCALE_OP_ENUM) + .for_field(&scaling_factor::sf_op), - yajlpp::pattern_property_handler("value").FOR_FIELD(scaling_factor, - sf_value)}; + yajlpp::pattern_property_handler("value").FOR_FIELD(scaling_factor, + sf_value), +}; static struct json_path_container scale_handlers = { yajlpp::pattern_property_handler("(?[^/]+)") @@ -519,7 +538,7 @@ static struct json_path_container unit_handlers = { .with_synopsis("") .with_description( "The name of the field that contains the units for this field") - .FOR_FIELD(external_log_format::value_def, vd_unit_field), + .for_field(&external_log_format::value_def::vd_unit_field), yajlpp::property_handler("scaling-factor") .with_description("Transforms the numeric value by the given factor") @@ -528,7 +547,7 @@ static struct json_path_container unit_handlers = { static struct json_path_container value_def_handlers = { yajlpp::property_handler("kind") - .with_synopsis("string|integer|float|boolean|json|quoted") + .with_synopsis("") .with_description("The type of data in the field") .with_enum_values(KIND_ENUM) .for_field(&external_log_format::value_def::vd_meta, @@ -537,7 +556,7 @@ static struct json_path_container value_def_handlers = { yajlpp::property_handler("collate") .with_synopsis("") .with_description("The collating function to use for this column") - .FOR_FIELD(external_log_format::value_def, vd_collate), + .for_field(&external_log_format::value_def::vd_collate), yajlpp::property_handler("unit") .with_description("Unit definitions for this field") @@ -572,30 +591,31 @@ static struct json_path_container value_def_handlers = { .with_synopsis("") .with_description( "A command that will rewrite this field when pretty-printing") - .FOR_FIELD(external_log_format::value_def, vd_rewriter), + .for_field(&external_log_format::value_def::vd_rewriter), yajlpp::property_handler("description") .with_synopsis("") .with_description("A description of the field") - .FOR_FIELD(external_log_format::value_def, vd_description)}; + .for_field(&external_log_format::value_def::vd_description), +}; static struct json_path_container highlighter_def_handlers = { yajlpp::property_handler("pattern") .with_synopsis("") .with_description( "A regular expression to highlight in logs of this format.") - .FOR_FIELD(external_log_format::highlighter_def, hd_pattern), + .for_field(&external_log_format::highlighter_def::hd_pattern), yajlpp::property_handler("color") .with_synopsis("#|") .with_description("The color to use when highlighting this pattern.") - .FOR_FIELD(external_log_format::highlighter_def, hd_color), + .for_field(&external_log_format::highlighter_def::hd_color), yajlpp::property_handler("background-color") .with_synopsis("#|") .with_description( "The background color to use when highlighting this pattern.") - .FOR_FIELD(external_log_format::highlighter_def, hd_background_color), + .for_field(&external_log_format::highlighter_def::hd_background_color), yajlpp::property_handler("underline") .with_synopsis("") @@ -605,43 +625,47 @@ static struct json_path_container highlighter_def_handlers = { yajlpp::property_handler("blink") .with_synopsis("") .with_description("Highlight this pattern by blinking.") - .for_field(&external_log_format::highlighter_def::hd_blink)}; - -static const json_path_handler_base::enum_value_t LEVEL_ENUM[] - = {{level_names[LEVEL_TRACE], LEVEL_TRACE}, - {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5}, - {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4}, - {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3}, - {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2}, - {level_names[LEVEL_DEBUG], LEVEL_DEBUG}, - {level_names[LEVEL_INFO], LEVEL_INFO}, - {level_names[LEVEL_STATS], LEVEL_STATS}, - {level_names[LEVEL_NOTICE], LEVEL_NOTICE}, - {level_names[LEVEL_WARNING], LEVEL_WARNING}, - {level_names[LEVEL_ERROR], LEVEL_ERROR}, - {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL}, - {level_names[LEVEL_FATAL], LEVEL_FATAL}, - - json_path_handler_base::ENUM_TERMINATOR}; - -static struct json_path_container sample_handlers - = {yajlpp::property_handler("line") - .with_synopsis("") - .with_description( - "A sample log line that should match a pattern in this format.") - .FOR_FIELD(external_log_format::sample, s_line), - - yajlpp::property_handler("level") - .with_enum_values(LEVEL_ENUM) - .with_description("The expected level for this sample log line.") - .FOR_FIELD(external_log_format::sample, s_level)}; - -static const json_path_handler_base::enum_value_t TYPE_ENUM[] - = {{"text", external_log_format::elf_type_t::ELF_TYPE_TEXT}, - {"json", external_log_format::elf_type_t::ELF_TYPE_JSON}, - {"csv", external_log_format::elf_type_t::ELF_TYPE_CSV}, - - json_path_handler_base::ENUM_TERMINATOR}; + .for_field(&external_log_format::highlighter_def::hd_blink), +}; + +static const json_path_handler_base::enum_value_t LEVEL_ENUM[] = { + {level_names[LEVEL_TRACE], LEVEL_TRACE}, + {level_names[LEVEL_DEBUG5], LEVEL_DEBUG5}, + {level_names[LEVEL_DEBUG4], LEVEL_DEBUG4}, + {level_names[LEVEL_DEBUG3], LEVEL_DEBUG3}, + {level_names[LEVEL_DEBUG2], LEVEL_DEBUG2}, + {level_names[LEVEL_DEBUG], LEVEL_DEBUG}, + {level_names[LEVEL_INFO], LEVEL_INFO}, + {level_names[LEVEL_STATS], LEVEL_STATS}, + {level_names[LEVEL_NOTICE], LEVEL_NOTICE}, + {level_names[LEVEL_WARNING], LEVEL_WARNING}, + {level_names[LEVEL_ERROR], LEVEL_ERROR}, + {level_names[LEVEL_CRITICAL], LEVEL_CRITICAL}, + {level_names[LEVEL_FATAL], LEVEL_FATAL}, + + json_path_handler_base::ENUM_TERMINATOR, +}; + +static struct json_path_container sample_handlers = { + yajlpp::property_handler("line") + .with_synopsis("") + .with_description( + "A sample log line that should match a pattern in this format.") + .for_field(&external_log_format::sample::s_line), + + yajlpp::property_handler("level") + .with_enum_values(LEVEL_ENUM) + .with_description("The expected level for this sample log line.") + .for_field(&external_log_format::sample::s_level), +}; + +static const json_path_handler_base::enum_value_t TYPE_ENUM[] = { + {"text", external_log_format::elf_type_t::ELF_TYPE_TEXT}, + {"json", external_log_format::elf_type_t::ELF_TYPE_JSON}, + {"csv", external_log_format::elf_type_t::ELF_TYPE_CSV}, + + json_path_handler_base::ENUM_TERMINATOR, +}; static struct json_path_container regex_handlers = { yajlpp::pattern_property_handler(R"((?[^/]+))") @@ -659,14 +683,16 @@ static struct json_path_container level_handlers = { .with_description("The regular expression used to match the log text " "for this level. " "For JSON logs with numeric levels, this should be " - "the number for the corresponding level.")}; + "the number for the corresponding level."), +}; -static struct json_path_container value_handlers - = {yajlpp::pattern_property_handler("(?[^/]+)") - .with_description( - "The set of values captured by the log message patterns") - .with_obj_provider(value_def_provider) - .with_children(value_def_handlers)}; +static struct json_path_container value_handlers = { + yajlpp::pattern_property_handler("(?[^/]+)") + .with_description( + "The set of values captured by the log message patterns") + .with_obj_provider(value_def_provider) + .with_children(value_def_handlers), +}; static struct json_path_container highlight_handlers = { yajlpp::pattern_property_handler(R"((?[^/]+))") @@ -674,38 +700,54 @@ static struct json_path_container highlight_handlers = { .with_obj_provider( [](const yajlpp_provider_context& ypc, external_log_format* root) { - return &(root->elf_highlighter_patterns[ypc.get_substr_i(0)]); + auto* retval + = &(root->elf_highlighter_patterns[ypc.get_substr_i(0)]); + + return retval; }) - .with_children(highlighter_def_handlers)}; + .with_children(highlighter_def_handlers), +}; -static struct json_path_container action_def_handlers - = {json_path_handler("label", read_action_def), - json_path_handler("capture-output", read_action_bool), - json_path_handler("cmd#", read_action_cmd)}; +static struct json_path_container action_def_handlers = { + json_path_handler("label", read_action_def), + json_path_handler("capture-output", read_action_bool), + json_path_handler("cmd#", read_action_cmd), +}; -static struct json_path_container action_handlers - = {json_path_handler(pcrepp("(?\\w+)"), read_action_def) - .with_children(action_def_handlers)}; +static struct json_path_container action_handlers = { + json_path_handler(pcrepp("(?\\w+)"), read_action_def) + .with_children(action_def_handlers), +}; static struct json_path_container search_table_def_handlers = { - json_path_handler("pattern", create_search_table) + json_path_handler("pattern") .with_synopsis("") - .with_description("The regular expression for this search table."), + .with_description("The regular expression for this search table.") + .for_field(&external_log_format::search_table_def::std_pattern), }; -static struct json_path_container search_table_handlers - = {yajlpp::pattern_property_handler("\\w+") - .with_description( - "The set of search tables to be automatically defined") - .with_children(search_table_def_handlers)}; +static struct json_path_container search_table_handlers = { + yajlpp::pattern_property_handler("(?\\w+)") + .with_description( + "The set of search tables to be automatically defined") + .with_obj_provider( + [](const yajlpp_provider_context& ypc, external_log_format* root) { + auto* retval = &(root->elf_search_tables[ypc.get_substr_i(0)]); + + return retval; + }) + .with_children(search_table_def_handlers), +}; -static const json_path_handler_base::enum_value_t MIME_TYPE_ENUM[] - = {{ - "application/vnd.tcpdump.pcap", - file_format_t::PCAP, - }, +static const json_path_handler_base::enum_value_t MIME_TYPE_ENUM[] = { + { + "application/vnd.tcpdump.pcap", + file_format_t::PCAP, + }, - json_path_handler_base::ENUM_TERMINATOR}; + json_path_handler_base::ENUM_TERMINATOR, +}; struct json_path_container format_handlers = { yajlpp::property_handler("regex") @@ -721,10 +763,11 @@ struct json_path_container format_handlers = { "automatically be converted to local time"), json_path_handler("hide-extra", read_format_bool) .with_description( - "Specifies whether extra values in JSON logs should be displayed"), + "Specifies whether extra values in JSON logs should be displayed") + .for_field(&external_log_format::jlf_hide_extra), json_path_handler("multiline", read_format_bool) - .with_description( - "Indicates that log messages can span multiple lines"), + .with_description("Indicates that log messages can span multiple lines") + .for_field(&log_format::lf_multiline), json_path_handler("timestamp-divisor", read_format_double) .add_cb(read_format_int) .with_synopsis("") @@ -738,16 +781,19 @@ struct json_path_container format_handlers = { .with_enum_values(MIME_TYPE_ENUM), json_path_handler("level-field", read_format_field) .with_description( - "The name of the level field in the log message pattern"), + "The name of the level field in the log message pattern") + .for_field(&external_log_format::elf_level_field), json_path_handler("level-pointer", read_format_field) .with_description("A regular-expression that matches the JSON-pointer " "of the level property"), json_path_handler("timestamp-field", read_format_field) .with_description( - "The name of the timestamp field in the log message pattern"), + "The name of the timestamp field in the log message pattern") + .for_field(&log_format::lf_timestamp_field), json_path_handler("body-field", read_format_field) .with_description( - "The name of the body field in the log message pattern"), + "The name of the body field in the log message pattern") + .for_field(&external_log_format::elf_body_field), json_path_handler("url", pcrepp("url#?")) .add_cb(read_format_field) .with_description("A URL with more information about this log format"), @@ -762,7 +808,8 @@ struct json_path_container format_handlers = { "The name of the module field in the log message pattern"), json_path_handler("opid-field", read_format_field) .with_description( - "The name of the operation-id field in the log message pattern"), + "The name of the operation-id field in the log message pattern") + .for_field(&external_log_format::elf_opid_field), yajlpp::property_handler("ordered-by-time") .with_synopsis("") .with_description( @@ -789,7 +836,7 @@ struct json_path_container format_handlers = { .with_obj_provider(line_format_provider) .add_cb(read_json_constant) .with_children(line_format_handlers), - json_path_handler("search-table", create_search_table) + json_path_handler("search-table") .with_description( "Search tables to automatically define for this log format") .with_children(search_table_handlers), @@ -802,7 +849,8 @@ struct json_path_container format_handlers = { .with_synopsis("text|json|csv") .with_description("The type of file that contains the log messages") .with_enum_values(TYPE_ENUM) - .FOR_FIELD(external_log_format, elf_type)}; + .for_field(&external_log_format::elf_type), +}; static int read_id(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) @@ -814,12 +862,21 @@ read_id(yajlpp_parse_context* ypc, const unsigned char* str, size_t len) file_id) == SUPPORTED_FORMAT_SCHEMAS.end()) { - fprintf(stderr, - "%s:%d: error: unsupported format $schema -- %s\n", - ypc->ypc_source.c_str(), - ypc->get_line_number(), - file_id.c_str()); - return 0; + const auto* handler = ypc->ypc_current_handler; + attr_line_t notes{"expecting one of the following $schema values:"}; + + for (const auto& schema : SUPPORTED_FORMAT_SCHEMAS) { + notes.append("\n").append( + lnav::roles::symbol(fmt::format(FMT_STRING(" {}"), schema))); + } + ypc->report_error( + lnav::console::user_message::error( + attr_line_t("'") + .append(lnav::roles::symbol(file_id)) + .append("' is not a supported log format $schema version")) + .with_snippet(ypc->get_snippet()) + .with_note(notes) + .with_help(handler->get_help_text(ypc))); } return 1; @@ -910,38 +967,35 @@ write_sample_file() static void format_error_reporter(const yajlpp_parse_context& ypc, - lnav_log_level_t level, - const char* msg) + const lnav::console::user_message& msg) { - if (level >= lnav_log_level_t::ERROR) { - struct userdata* ud = (userdata*) ypc.ypc_userdata; + struct userdata* ud = (userdata*) ypc.ypc_userdata; - ud->ud_errors->push_back(msg); - } else { - fprintf(stderr, "warning:%s\n", msg); - } + ud->ud_errors->emplace_back(msg); } std::vector load_format_file(const ghc::filesystem::path& filename, - std::vector& errors) + std::vector& errors) { std::vector retval; struct userdata ud; auto_fd fd; log_info("loading formats from file: %s", filename.c_str()); + yajlpp_parse_context ypc(filename, &root_format_handler); + ud.ud_parse_context = &ypc; ud.ud_format_path = filename; ud.ud_format_names = &retval; ud.ud_errors = &errors; - yajlpp_parse_context ypc(filename, &root_format_handler); ypc.ypc_userdata = &ud; ypc.with_obj(ud); if ((fd = lnav::filesystem::openp(filename, O_RDONLY)) == -1) { - errors.emplace_back(fmt::format( - FMT_STRING("error: unable to open format file '{}' -- {}"), - filename.string(), - strerror(errno))); + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("unable to open format file: ") + .append(lnav::roles::file(filename.string()))) + .with_errno_reason()); } else { auto_mem handle(yajl_free); char buffer[2048]; @@ -955,11 +1009,13 @@ load_format_file(const ghc::filesystem::path& filename, rc = read(fd, buffer, sizeof(buffer)); if (rc == 0) { break; - } else if (rc == -1) { - errors.emplace_back(fmt::format( - FMT_STRING("error:{}:unable to read file -- {}"), - filename.string(), - strerror(errno))); + } + if (rc == -1) { + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("unable to read format file: ") + .append(lnav::roles::file(filename.string()))) + .with_errno_reason()); break; } if (offset == 0 && (rc > 2) && (buffer[0] == '#') @@ -983,7 +1039,7 @@ load_format_file(const ghc::filesystem::path& filename, static void load_from_path(const ghc::filesystem::path& path, - std::vector& errors) + std::vector& errors) { auto format_path = path / "formats/*/*.json"; static_root_mem gl; @@ -1015,11 +1071,9 @@ load_from_path(const ghc::filesystem::path& path, void load_formats(const std::vector& extra_paths, - std::vector& errors) + std::vector& errors) { auto default_source = lnav::paths::dotlnav() / "default"; - yajlpp_parse_context ypc_builtin(default_source.string(), - &root_format_handler); std::vector retval; struct userdata ud; yajl_handle handle; @@ -1028,7 +1082,9 @@ load_formats(const std::vector& extra_paths, log_debug("Loading default formats"); for (const auto& bsf : lnav_format_json) { + yajlpp_parse_context ypc_builtin(bsf.get_name(), &root_format_handler); handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin); + ud.ud_parse_context = &ypc_builtin; ud.ud_format_names = &retval; ud.ud_errors = &errors; ypc_builtin.with_obj(ud) @@ -1043,8 +1099,10 @@ load_formats(const std::vector& extra_paths, handle, 1, (const unsigned char*) sf.data(), sf.length()); errors.emplace_back( - fmt::format(FMT_STRING("builtin: invalid json -- {}"), - reinterpret_cast(msg))); + lnav::console::user_message::error("invalid json") + .with_snippet(lnav::console::snippet::from( + ypc_builtin.ypc_source, attr_line_t((const char*) msg))) + .with_errno_reason()); yajl_free_error(handle, msg); } ypc_builtin.complete_parse(); @@ -1154,7 +1212,7 @@ load_formats(const std::vector& extra_paths, static void exec_sql_in_path(sqlite3* db, const ghc::filesystem::path& path, - std::vector& errors) + std::vector& errors) { auto format_path = path / "formats/*/*.sql"; static_root_mem gl; @@ -1172,10 +1230,11 @@ exec_sql_in_path(sqlite3* db, sql_execute_script( db, filename.c_str(), content.c_str(), errors); } else { - errors.emplace_back(fmt::format( - FMT_STRING("error:unable to read file '{}' -- {}"), - filename.string(), - read_res.unwrapErr())); + errors.emplace_back( + lnav::console::user_message::error( + attr_line_t("unable to read format file: ") + .append(lnav::roles::file(filename.string()))) + .with_reason(read_res.unwrapErr())); } } } @@ -1184,7 +1243,7 @@ exec_sql_in_path(sqlite3* db, void load_format_extra(sqlite3* db, const std::vector& extra_paths, - std::vector& errors) + std::vector& errors) { for (const auto& extra_path : extra_paths) { exec_sql_in_path(db, extra_path, errors); @@ -1275,7 +1334,7 @@ find_format_scripts(const std::vector& extra_paths, void load_format_vtabs(log_vtab_manager* vtab_manager, - std::vector& errors) + std::vector& errors) { auto& root_formats = LOG_FORMATS; diff --git a/src/log_format_loader.hh b/src/log_format_loader.hh index 5f8326f9..3c495db5 100644 --- a/src/log_format_loader.hh +++ b/src/log_format_loader.hh @@ -38,22 +38,24 @@ #include #include "base/intern_string.hh" +#include "base/lnav.console.hh" #include "ghc/filesystem.hpp" class log_vtab_manager; std::vector load_format_file( - const ghc::filesystem::path& filename, std::vector& errors); + const ghc::filesystem::path& filename, + std::vector& errors); void load_formats(const std::vector& extra_paths, - std::vector& errors); + std::vector& errors); void load_format_vtabs(log_vtab_manager* vtab_manager, - std::vector& errors); + std::vector& errors); void load_format_extra(sqlite3* db, const std::vector& extra_paths, - std::vector& errors); + std::vector& errors); struct script_metadata { ghc::filesystem::path sm_path; diff --git a/src/log_gutter_source.hh b/src/log_gutter_source.hh index 1120b2f9..496f8241 100644 --- a/src/log_gutter_source.hh +++ b/src/log_gutter_source.hh @@ -39,8 +39,8 @@ public: int start, int end, chtype& ch, - view_colors::role_t& role_out, - view_colors::role_t& bar_role_out) + role_t& role_out, + role_t& bar_role_out) { textview_curses* tc = (textview_curses*) &lv; vis_bookmarks& bm = tc->get_bookmarks(); @@ -63,13 +63,13 @@ public: } next = bm[&logfile_sub_source::BM_ERRORS].next(vis_line_t(start)); if (next != -1 && next <= end) { - role_out = view_colors::VCR_ERROR; - bar_role_out = view_colors::VCR_SCROLLBAR_ERROR; + role_out = role_t::VCR_ERROR; + bar_role_out = role_t::VCR_SCROLLBAR_ERROR; } else { next = bm[&logfile_sub_source::BM_WARNINGS].next(vis_line_t(start)); if (next != -1 && next <= end) { - role_out = view_colors::VCR_WARNING; - bar_role_out = view_colors::VCR_SCROLLBAR_WARNING; + role_out = role_t::VCR_WARNING; + bar_role_out = role_t::VCR_SCROLLBAR_WARNING; } } }; diff --git a/src/log_vtab_impl.cc b/src/log_vtab_impl.cc index 1239dc4d..70079b27 100644 --- a/src/log_vtab_impl.cc +++ b/src/log_vtab_impl.cc @@ -32,17 +32,20 @@ #include "base/lnav_log.hh" #include "base/string_util.hh" #include "config.h" +#include "lnav_util.hh" #include "logfile_sub_source.hh" #include "sql_util.hh" #include "vtab_module.hh" #include "yajlpp/json_op.hh" #include "yajlpp/yajlpp_def.hh" +using namespace lnav::roles::literals; + static auto intern_lifetime = intern_string::get_table_lifetime(); static struct log_cursor log_cursor_latest; -struct _log_vtab_data log_vtab_data; +thread_local _log_vtab_data log_vtab_data; static const char* LOG_COLUMNS = R"( ( log_line INTEGER PRIMARY KEY, -- The line number for the log message @@ -947,12 +950,13 @@ vt_best_index(sqlite3_vtab* tab, sqlite3_index_info* p_info) return SQLITE_OK; } -static struct json_path_container tags_handler - = {json_path_handler("#") - .with_synopsis("") - .with_description("A tag for the log line") - .with_pattern(R"(^#[^\s]+$)") - .FOR_FIELD(bookmark_metadata, bm_tags)}; +static struct json_path_container tags_handler = { + json_path_handler("#") + .with_synopsis("tag") + .with_description("A tag for the log line") + .with_pattern(R"(^#[^\s]+$)") + .FOR_FIELD(bookmark_metadata, bm_tags), +}; static int vt_update(sqlite3_vtab* tab, @@ -972,17 +976,17 @@ vt_update(sqlite3_vtab* tab, std::map& bm = vt->lss->get_user_bookmark_metadata(); - const unsigned char* part_name - = sqlite3_value_text(argv[2 + VT_COL_PARTITION]); - const unsigned char* log_comment + const auto* part_name = sqlite3_value_text(argv[2 + VT_COL_PARTITION]); + const auto* log_comment = sqlite3_value_text(argv[2 + VT_COL_LOG_COMMENT]); - const unsigned char* log_tags - = sqlite3_value_text(argv[2 + VT_COL_LOG_TAGS]); + const auto* log_tags = sqlite3_value_text(argv[2 + VT_COL_LOG_TAGS]); bookmark_metadata tmp_bm; if (log_tags) { - std::vector errors; - yajlpp_parse_context ypc(log_vtab_data.lvd_source, &tags_handler); + std::vector errors; + yajlpp_parse_context ypc( + fmt::format(FMT_STRING("{}.log_tags"), vt->vi->get_name()), + &tags_handler); auto_mem handle(yajl_free); handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc); @@ -990,19 +994,31 @@ vt_update(sqlite3_vtab* tab, ypc.ypc_line_number = log_vtab_data.lvd_line_number; ypc.with_handle(handle) .with_error_reporter([](const yajlpp_parse_context& ypc, - lnav_log_level_t level, - const char* msg) { - auto& errors - = *((std::vector*) ypc.ypc_userdata); + auto msg) { + auto& errors = *((std::vector*) + ypc.ypc_userdata); errors.emplace_back(msg); }) .with_obj(tmp_bm); - ypc.parse(log_tags, strlen((const char*) log_tags)); - ypc.complete_parse(); + ypc.parse_doc(string_fragment{log_tags}); if (!errors.empty()) { - auto all_errors - = fmt::format(FMT_STRING("{}"), fmt::join(errors, "\n")); - tab->zErrMsg = sqlite3_mprintf("%s", all_errors.c_str()); + auto top_error + = lnav::console::user_message::error( + attr_line_t("invalid value for ") + .append_quoted("log_tags"_symbol) + .append(" column of table ") + .append_quoted(lnav::roles::symbol( + vt->vi->get_name().to_string()))) + .with_reason(errors[0].to_attr_line({})) + .with_snippet( + lnav::console::snippet::from( + log_vtab_data.lvd_source, + log_vtab_data.lvd_content) + .with_line(log_vtab_data.lvd_line_number)); + auto json_error = lnav::to_json(top_error); + tab->zErrMsg + = sqlite3_mprintf("lnav-error:%s", json_error.c_str()); + log_debug("dump %s", json_error.c_str()); return SQLITE_ERROR; } } diff --git a/src/log_vtab_impl.hh b/src/log_vtab_impl.hh index fb6bd8f8..61eb4698 100644 --- a/src/log_vtab_impl.hh +++ b/src/log_vtab_impl.hh @@ -207,25 +207,30 @@ protected: typedef int (*sql_progress_callback_t)(const log_cursor& lc); typedef void (*sql_progress_finished_callback_t)(); -extern struct _log_vtab_data { +struct _log_vtab_data { sql_progress_callback_t lvd_progress; sql_progress_finished_callback_t lvd_finished; std::string lvd_source; int lvd_line_number{0}; -} log_vtab_data; + attr_line_t lvd_content; +}; + +extern thread_local _log_vtab_data log_vtab_data; class sql_progress_guard { public: sql_progress_guard(sql_progress_callback_t cb, sql_progress_finished_callback_t fcb, const std::string& source, - int line_number) + int line_number, + const attr_line_t& content) { log_vtab_data.lvd_progress = cb; log_vtab_data.lvd_finished = fcb; log_vtab_data.lvd_source = source; log_vtab_data.lvd_line_number = line_number; - }; + log_vtab_data.lvd_content = content; + } ~sql_progress_guard() { @@ -236,7 +241,8 @@ public: log_vtab_data.lvd_finished = nullptr; log_vtab_data.lvd_source.clear(); log_vtab_data.lvd_line_number = 0; - }; + log_vtab_data.lvd_content.clear(); + } }; class log_vtab_manager { diff --git a/src/logfile_sub_source.cc b/src/logfile_sub_source.cc index d48291a3..a067c712 100644 --- a/src/logfile_sub_source.cc +++ b/src/logfile_sub_source.cc @@ -34,7 +34,7 @@ #include -#include "ansi_scrubber.hh" +#include "base/ansi_scrubber.hh" #include "base/humanize.time.hh" #include "base/string_util.hh" #include "command_executor.hh" @@ -371,7 +371,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, lr.lr_start = time_offset_end; lr.lr_end = -1; - value_out.emplace_back(lr, view_curses::VC_STYLE.value(attrs)); + value_out.emplace_back(lr, VC_STYLE.value(attrs)); if (this->lss_token_line->get_msg_level() == log_level_t::LEVEL_INVALID) { for (auto& token_attr : this->lss_token_attrs) { @@ -381,7 +381,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, value_out.emplace_back( token_attr.sa_range, - view_curses::VC_ROLE.value(view_colors::VCR_INVALID_MSG)); + VC_ROLE.value(role_t::VCR_INVALID_MSG)); } } @@ -411,7 +411,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, value_out.emplace_back( ident_range, - view_curses::VC_ROLE.value(view_colors::VCR_IDENTIFIER)); + VC_ROLE.value(role_t::VCR_IDENTIFIER)); } if (this->lss_token_shift_size) { @@ -441,7 +441,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, } else if (is_last_for_file) { graph = ACS_LLCORNER; } - value_out.emplace_back(lr, view_curses::VC_GRAPHIC.value(graph)); + value_out.emplace_back(lr, VC_GRAPHIC.value(graph)); if (!(this->lss_token_flags & RF_FULL)) { bookmark_vector& bv_search @@ -453,13 +453,13 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, lr.lr_start = 0; lr.lr_end = 1; value_out.emplace_back(lr, - view_curses::VC_STYLE.value(A_REVERSE)); + VC_STYLE.value(A_REVERSE)); } } } value_out.emplace_back(lr, - view_curses::VC_STYLE.value(vc.attrs_for_ident( + VC_STYLE.value(vc.attrs_for_ident( this->lss_token_file->get_filename()))); if (this->lss_flags & F_FILENAME || this->lss_flags & F_BASENAME) { @@ -472,7 +472,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, lr.lr_start = 0; lr.lr_end = file_offset_end + 1; value_out.emplace_back(lr, - view_curses::VC_STYLE.value(vc.attrs_for_ident( + VC_STYLE.value(vc.attrs_for_ident( this->lss_token_file->get_filename()))); } @@ -484,25 +484,25 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, shift_string_attrs(value_out, 0, time_offset_end); value_out.emplace_back( - lr, view_curses::VC_ROLE.value(view_colors::VCR_OFFSET_TIME)); + lr, VC_ROLE.value(role_t::VCR_OFFSET_TIME)); value_out.emplace_back(line_range(12, 13), - view_curses::VC_GRAPHIC.value(ACS_VLINE)); + VC_GRAPHIC.value(ACS_VLINE)); - view_colors::role_t bar_role = view_colors::VCR_NONE; + role_t bar_role = role_t::VCR_NONE; switch (this->get_line_accel_direction(vis_line_t(row))) { case log_accel::A_STEADY: break; case log_accel::A_DECEL: - bar_role = view_colors::VCR_DIFF_DELETE; + bar_role = role_t::VCR_DIFF_DELETE; break; case log_accel::A_ACCEL: - bar_role = view_colors::VCR_DIFF_ADD; + bar_role = role_t::VCR_DIFF_ADD; break; } - if (bar_role != view_colors::VCR_NONE) { + if (bar_role != role_t::VCR_NONE) { value_out.emplace_back(line_range(12, 13), - view_curses::VC_ROLE.value(bar_role)); + VC_ROLE.value(bar_role)); } } @@ -550,7 +550,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, if (time_range.lr_end != -1) { value_out.emplace_back( time_range, - view_curses::VC_ROLE.value(view_colors::VCR_ADJUSTED_TIME)); + VC_ROLE.value(role_t::VCR_ADJUSTED_TIME)); } } @@ -561,7 +561,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, if (time_range.lr_end != -1) { value_out.emplace_back( time_range, - view_curses::VC_ROLE.value(view_colors::VCR_SKEWED_TIME)); + VC_ROLE.value(role_t::VCR_SKEWED_TIME)); } } @@ -584,11 +584,11 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, } else { color = COLOR_RED; value_out.emplace_back( - line_range{0, 1}, view_curses::VC_STYLE.value(A_BLINK)); + line_range{0, 1}, VC_STYLE.value(A_BLINK)); } } value_out.emplace_back(line_range{0, 1}, - view_curses::VC_BACKGROUND.value(color)); + VC_BACKGROUND.value(color)); } auto sql_filter_opt = this->get_sql_filter(); @@ -605,7 +605,7 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv, auto color = COLOR_YELLOW; value_out.emplace_back(line_range{0, -1}, SA_ERROR.value(msg)); value_out.emplace_back(line_range{0, 1}, - view_curses::VC_BACKGROUND.value(color)); + VC_BACKGROUND.value(color)); } } } diff --git a/src/network-extension-functions.cc b/src/network-extension-functions.cc index fe8ada3a..b30c8bbb 100644 --- a/src/network-extension-functions.cc +++ b/src/network-extension-functions.cc @@ -35,7 +35,7 @@ #include #include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "config.h" #include "sqlite-extension-func.hh" #include "sqlite3.h" diff --git a/src/papertrail_proc.hh b/src/papertrail_proc.hh index 78e68b08..184f97f6 100644 --- a/src/papertrail_proc.hh +++ b/src/papertrail_proc.hh @@ -45,8 +45,8 @@ # include # include -# include "auto_mem.hh" # include "base/auto_fd.hh" +# include "base/auto_mem.hh" # include "curl_looper.hh" # include "line_buffer.hh" # include "yajlpp/yajlpp.hh" diff --git a/src/pcrepp/pcrepp.hh b/src/pcrepp/pcrepp.hh index 5104bea4..b99a7cc0 100644 --- a/src/pcrepp/pcrepp.hh +++ b/src/pcrepp/pcrepp.hh @@ -59,7 +59,7 @@ #include #include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/intern_string.hh" #include "base/result.h" diff --git a/src/plain_text_source.hh b/src/plain_text_source.hh index d679c802..52fd5a04 100644 --- a/src/plain_text_source.hh +++ b/src/plain_text_source.hh @@ -33,7 +33,7 @@ #include #include -#include "attr_line.hh" +#include "base/attr_line.hh" #include "textview_curses.hh" class plain_text_source @@ -55,18 +55,18 @@ public: this->tds_lines.emplace_back(text.substr(start)); } this->tds_longest_line = this->compute_longest_line(); - }; + } plain_text_source(const std::vector& text_lines) { this->replace_with(text_lines); - }; + } plain_text_source(const std::vector& text_lines) { this->tds_lines = text_lines; this->tds_longest_line = this->compute_longest_line(); - }; + } plain_text_source& replace_with(const attr_line_t& text_lines) { @@ -74,23 +74,23 @@ public: text_lines.split_lines(this->tds_lines); this->tds_longest_line = this->compute_longest_line(); return *this; - }; + } plain_text_source& replace_with(const std::vector& text_lines) { - for (auto& str : text_lines) { + for (const auto& str : text_lines) { this->tds_lines.emplace_back(str); } this->tds_longest_line = this->compute_longest_line(); return *this; - }; + } void clear() { this->tds_lines.clear(); this->tds_longest_line = 0; this->tds_text_format = text_format_t::TF_UNKNOWN; - }; + } plain_text_source& truncate_to(size_t max_lines) { @@ -98,22 +98,22 @@ public: this->tds_lines.pop_back(); } return *this; - }; + } size_t text_line_count() { return this->tds_lines.size(); - }; + } bool empty() const { return this->tds_lines.empty(); - }; + } size_t text_line_width(textview_curses& curses) { return this->tds_longest_line; - }; + } void text_value_for_line(textview_curses& tc, int row, @@ -121,30 +121,35 @@ public: line_flags_t flags) { value_out = this->tds_lines[row].get_string(); - }; + } void text_attrs_for_line(textview_curses& tc, int line, string_attrs_t& value_out) { value_out = this->tds_lines[line].get_attrs(); - }; + } size_t text_size_for_line(textview_curses& tc, int row, line_flags_t flags) { return this->tds_lines[row].length(); - }; + } text_format_t get_text_format() const { return this->tds_text_format; - }; + } + + const std::vector& get_lines() const + { + return this->tds_lines; + } plain_text_source& set_text_format(text_format_t format) { this->tds_text_format = format; return *this; - }; + } nonstd::optional get_location_history() { diff --git a/src/pretty_printer.hh b/src/pretty_printer.hh index 929f601b..66bb0533 100644 --- a/src/pretty_printer.hh +++ b/src/pretty_printer.hh @@ -37,7 +37,7 @@ #include -#include "attr_line.hh" +#include "base/attr_line.hh" #include "data_scanner.hh" class pretty_printer { diff --git a/src/preview_status_source.hh b/src/preview_status_source.hh index 065a22e4..25622b07 100644 --- a/src/preview_status_source.hh +++ b/src/preview_status_source.hh @@ -50,12 +50,12 @@ public: static const char TOGGLE_MSG[] = "Press CTRL+P to show/hide"; this->tss_fields[TSF_TITLE].set_width(14); - this->tss_fields[TSF_TITLE].set_role(view_colors::VCR_STATUS_TITLE); + this->tss_fields[TSF_TITLE].set_role(role_t::VCR_STATUS_TITLE); this->tss_fields[TSF_TITLE].set_value(" Preview Data "); this->tss_fields[TSF_STITCH_TITLE].set_width(2); this->tss_fields[TSF_STITCH_TITLE].set_stitch_value( - view_colors::VCR_STATUS_STITCH_TITLE_TO_NORMAL, - view_colors::VCR_STATUS_STITCH_NORMAL_TO_TITLE); + role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL, + role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE); this->tss_fields[TSF_DESCRIPTION].set_share(1); this->tss_fields[TSF_TOGGLE].set_width(strlen(TOGGLE_MSG) + 1); this->tss_fields[TSF_TOGGLE].set_value(TOGGLE_MSG); diff --git a/src/readline_callbacks.cc b/src/readline_callbacks.cc index df65a643..3b7eaea6 100644 --- a/src/readline_callbacks.cc +++ b/src/readline_callbacks.cc @@ -49,6 +49,8 @@ #include "vtab_module.hh" #include "yajlpp/yajlpp.hh" +using namespace std::chrono_literals; + #define ABORT_MSG "(Press " ANSI_BOLD("CTRL+]") " to abort)" #define STR_HELPER(x) #x @@ -126,7 +128,7 @@ rl_set_help() } case LNM_SQL: { textview_curses& log_view = lnav_data.ld_views[LNV_LOG]; - auto lss = (logfile_sub_source*) log_view.get_sub_source(); + auto* lss = (logfile_sub_source*) log_view.get_sub_source(); attr_line_t example_al; if (log_view.get_inner_height() > 0) { @@ -241,6 +243,7 @@ rl_change(readline_curses* rc) tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"}); tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"}); lnav_data.ld_log_source.set_preview_sql_filter(nullptr); + lnav_data.ld_user_message_source.clear(); lnav_data.ld_preview_source.clear(); lnav_data.ld_preview_status_source.get_description() .set_cylon(false) @@ -266,9 +269,9 @@ rl_change(readline_curses* rc) generation += 1; } - auto os = tc->get_overlay_source(); + auto* os = tc->get_overlay_source(); if (!args.empty() && os != nullptr) { - auto fos = dynamic_cast(os); + auto* fos = dynamic_cast(os); if (fos != nullptr) { if (generation == 0) { @@ -400,6 +403,7 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false) tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"}); lnav_data.ld_log_source.set_preview_sql_filter(nullptr); tc->reload_data(); + lnav_data.ld_user_message_source.clear(); switch (mode) { case LNM_SEARCH: @@ -436,7 +440,8 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false) } } else { lnav_data.ld_bottom_source.set_prompt(""); - lnav_data.ld_bottom_source.grep_error(result.unwrapErr()); + lnav_data.ld_bottom_source.grep_error( + result.unwrapErr().um_message.get_string()); } lnav_data.ld_preview_view.reload_data(); @@ -518,7 +523,7 @@ lnav_rl_abort(readline_curses* rc) tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"}); lnav_data.ld_log_source.set_preview_sql_filter(nullptr); - std::vector errors; + std::vector errors; lnav_config = rollback_lnav_config; reload_config(errors); @@ -577,13 +582,30 @@ rl_callback_int(readline_curses* rc, bool is_alt) require(0); break; - case LNM_COMMAND: + case LNM_COMMAND: { rc->set_alt_value(""); - rc->set_value(execute_command(ec, rc->get_value()) - .map(ok_prefix) - .orElse(err_to_ok) - .unwrap()); + ec.ec_source.top().s_content + = fmt::format(FMT_STRING(":{}"), rc->get_value()); + auto exec_res = execute_command(ec, rc->get_value()); + if (exec_res.isOk()) { + rc->set_value(exec_res.unwrap()); + } else { + auto um = exec_res.unwrapErr(); + + lnav_data.ld_user_message_source.replace_with( + um.to_attr_line().rtrim()); + for (const auto& line : + lnav_data.ld_user_message_source.get_lines()) { + log_debug("line -- %s", lnav::to_json(line).c_str()); + } + lnav_data.ld_user_message_view.reload_data(); + lnav_data.ld_user_message_expiration + = std::chrono::steady_clock::now() + 20s; + rc->set_value(""); + } + ec.ec_source.top().s_content.clear(); break; + } case LNM_USER: rc->set_alt_value(""); @@ -645,6 +667,7 @@ rl_callback_int(readline_curses* rc, bool is_alt) break; case LNM_SQL: { + ec.ec_source.top().s_content = rc->get_value(); auto result = execute_sql(ec, rc->get_value(), alt_msg); db_label_source& dls = lnav_data.ld_db_row_source; std::string prompt; @@ -659,8 +682,14 @@ rl_callback_int(readline_curses* rc, bool is_alt) } } } else { - prompt = result.orElse(err_to_ok).unwrap(); + auto um = result.unwrapErr(); + lnav_data.ld_user_message_source.replace_with( + um.to_attr_line().rtrim()); + lnav_data.ld_user_message_view.reload_data(); + lnav_data.ld_user_message_expiration + = std::chrono::steady_clock::now() + 20s; } + ec.ec_source.top().s_content.clear(); rc->set_value(prompt); rc->set_alt_value(alt_msg); @@ -766,7 +795,7 @@ rl_display_matches(readline_curses* rc) add_nl = false; } if (match == current_match) { - al.append(match, view_curses::VC_STYLE.value(A_REVERSE)); + al.append(match, VC_STYLE.value(A_REVERSE)); } else { al.append(match); } diff --git a/src/readline_context.hh b/src/readline_context.hh index d4172f68..86dcbf35 100644 --- a/src/readline_context.hh +++ b/src/readline_context.hh @@ -37,6 +37,7 @@ #include +#include "base/lnav.console.hh" #include "base/result.h" #include "help_text.hh" @@ -52,7 +53,7 @@ typedef void (*readline_highlighter_t)(attr_line_t& line, int x); */ class readline_context { public: - typedef Result (*command_func_t)( + typedef Result (*command_func_t)( exec_context& ec, std::string cmdline, std::vector& args); typedef std::string (*prompt_func_t)(exec_context& ec, const std::string& cmdline); diff --git a/src/readline_curses.cc b/src/readline_curses.cc index b4d4f7e2..5b3a86b7 100644 --- a/src/readline_curses.cc +++ b/src/readline_curses.cc @@ -54,8 +54,8 @@ #include #include -#include "ansi_scrubber.hh" -#include "auto_mem.hh" +#include "base/ansi_scrubber.hh" +#include "base/auto_mem.hh" #include "base/lnav_log.hh" #include "base/paths.hh" #include "base/string_util.hh" @@ -1381,7 +1381,7 @@ readline_curses::do_update() view_colors& vc = view_colors::singleton(); wmove(this->vc_window, this->get_actual_y(), this->vc_left); - wattron(this->vc_window, vc.attrs_for_role(view_colors::VCR_TEXT)); + wattron(this->vc_window, vc.attrs_for_role(role_t::VCR_TEXT)); whline(this->vc_window, ' ', this->vc_width); if (time(nullptr) > this->rc_value_expiration) { diff --git a/src/readline_curses.hh b/src/readline_curses.hh index bc29303d..85698e90 100644 --- a/src/readline_curses.hh +++ b/src/readline_curses.hh @@ -90,44 +90,53 @@ public: void add_context(int id, readline_context& rc) { this->rc_contexts[id] = &rc; - }; + } void set_focus_action(const action& va) { this->rc_focus = va; - }; + } + void set_change_action(const action& va) { this->rc_change = va; - }; + } + void set_perform_action(const action& va) { this->rc_perform = va; - }; + } + void set_alt_perform_action(const action& va) { this->rc_alt_perform = va; - }; + } + void set_timeout_action(const action& va) { this->rc_timeout = va; - }; + } + void set_abort_action(const action& va) { this->rc_abort = va; - }; + } + void set_display_match_action(const action& va) { this->rc_display_match = va; - }; + } + void set_display_next_action(const action& va) { this->rc_display_next = va; - }; + } + void set_blur_action(const action& va) { this->rc_blur = va; - }; + } + void set_completion_request_action(const action& va) { this->rc_completion_request = va; @@ -141,32 +150,34 @@ public: } this->rc_value_expiration = time(nullptr) + VALUE_EXPIRATION; this->set_needs_update(); - }; + } + std::string get_value() const { return this->rc_value; - }; + } std::string get_line_buffer() const { return this->rc_line_buffer; - }; + } void set_alt_value(const std::string& value) { this->rc_alt_value = value; - }; + } + std::string get_alt_value() const { return this->rc_alt_value; - }; + } void update_poll_set(std::vector& pollfds) { pollfds.push_back((struct pollfd){this->rc_pty[RCF_MASTER], POLLIN, 0}); pollfds.push_back( (struct pollfd){this->rc_command_pipe[RCF_MASTER], POLLIN, 0}); - }; + } void handle_key(int ch); @@ -190,7 +201,7 @@ public: std::map::const_iterator iter; iter = this->rc_contexts.find(this->rc_active_context); return iter->second; - }; + } void abort(); @@ -208,7 +219,7 @@ public: if (ioctl(this->rc_pty[RCF_MASTER], TIOCSWINSZ, &ws) == -1) { throw error(errno); } - }; + } void line_ready(const char* line); @@ -229,7 +240,7 @@ public: for (int lpc = 0; values[lpc]; lpc++) { this->add_possibility(context, type, values[lpc]); } - }; + } void add_possibility(int context, const std::string& type, @@ -239,7 +250,7 @@ public: for (; first < last; first++) { this->add_possibility(context, type, *first); } - }; + } template class Container> void add_possibility(int context, @@ -249,7 +260,7 @@ public: for (const auto& str : values) { this->add_possibility(context, type, str); } - }; + } void rem_possibility(int context, const std::string& type, @@ -259,7 +270,7 @@ public: const std::vector& get_matches() const { return this->rc_matches; - }; + } int get_match_start() const { @@ -271,7 +282,7 @@ public: int get_max_match_length() const { return this->rc_max_match_length; - }; + } bool consume_ready_for_input() { @@ -327,4 +338,5 @@ private: action rc_blur; action rc_completion_request; }; + #endif diff --git a/src/readline_highlighters.cc b/src/readline_highlighters.cc index 9cc121f0..c79fcc85 100644 --- a/src/readline_highlighters.cc +++ b/src/readline_highlighters.cc @@ -37,6 +37,7 @@ #include "shlex.hh" #include "sql_help.hh" #include "sql_util.hh" +#include "view_curses.hh" static void readline_sqlite_highlighter_int(attr_line_t& al, int x, int skip); @@ -72,9 +73,9 @@ find_matching_bracket(attr_line_t& al, int x, char left, char right) { view_colors& vc = view_colors::singleton(); int matching_bracket_attrs - = A_BOLD | A_REVERSE | vc.attrs_for_role(view_colors::VCR_OK); + = A_BOLD | A_REVERSE | vc.attrs_for_role(role_t::VCR_OK); int missing_bracket_attrs - = A_BOLD | A_REVERSE | vc.attrs_for_role(view_colors::VCR_ERROR); + = A_BOLD | A_REVERSE | vc.attrs_for_role(role_t::VCR_ERROR); bool is_lit = (left == 'Q'); const std::string& line = al.get_string(); int depth = 0; @@ -91,7 +92,7 @@ find_matching_bracket(attr_line_t& al, int x, char left, char right) if (depth == 0) { al.get_attrs().emplace_back( line_range(lpc, lpc + 1), - view_curses::VC_STYLE.value(matching_bracket_attrs)); + VC_STYLE.value(matching_bracket_attrs)); break; } else { depth -= 1; @@ -108,7 +109,7 @@ find_matching_bracket(attr_line_t& al, int x, char left, char right) if (depth == 0) { al.get_attrs().emplace_back( line_range(lpc, lpc + 1), - view_curses::VC_STYLE.value(matching_bracket_attrs)); + VC_STYLE.value(matching_bracket_attrs)); break; } else { depth -= 1; @@ -133,7 +134,7 @@ find_matching_bracket(attr_line_t& al, int x, char left, char right) } else { al.get_attrs().emplace_back( line_range(is_lit ? lpc - 1 : lpc, lpc + 1), - view_curses::VC_STYLE.value(missing_bracket_attrs)); + VC_STYLE.value(missing_bracket_attrs)); } } } @@ -141,7 +142,7 @@ find_matching_bracket(attr_line_t& al, int x, char left, char right) if (depth > 0) { al.get_attrs().emplace_back( line_range(is_lit ? first_left - 1 : first_left, first_left + 1), - view_curses::VC_STYLE.value(missing_bracket_attrs)); + VC_STYLE.value(missing_bracket_attrs)); } } @@ -159,20 +160,21 @@ static void readline_regex_highlighter_int(attr_line_t& al, int x, int skip) { view_colors& vc = view_colors::singleton(); - int special_char - = (A_BOLD | vc.attrs_for_role(view_colors::VCR_RE_SPECIAL)); - int class_attrs = (A_BOLD | vc.attrs_for_role(view_colors::VCR_SYMBOL)); - int repeated_char_attrs = vc.attrs_for_role(view_colors::VCR_RE_REPEAT); - int bracket_attrs = vc.attrs_for_role(view_colors::VCR_OK); + int special_char = (A_BOLD | vc.attrs_for_role(role_t::VCR_RE_SPECIAL)); + int class_attrs = (A_BOLD | vc.attrs_for_role(role_t::VCR_SYMBOL)); + int repeated_char_attrs = vc.attrs_for_role(role_t::VCR_RE_REPEAT); + int bracket_attrs = vc.attrs_for_role(role_t::VCR_OK); int error_attrs - = (A_BOLD | A_REVERSE | vc.attrs_for_role(view_colors::VCR_ERROR)); + = (A_BOLD | A_REVERSE | vc.attrs_for_role(role_t::VCR_ERROR)); - static const char* brackets[] = {"[]", - "{}", - "()", - "QE", + static const char* brackets[] = { + "[]", + "{}", + "()", + "QE", - nullptr}; + nullptr, + }; auto& line = al.get_string(); bool backslash_is_quoted = false; @@ -186,15 +188,14 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip) case '+': case '|': case '.': - al.get_attrs().emplace_back( - line_range(lpc, lpc + 1), - view_curses::VC_STYLE.value(special_char)); + al.get_attrs().emplace_back(line_range(lpc, lpc + 1), + VC_STYLE.value(special_char)); if ((line[lpc] == '*' || line[lpc] == '+') && check_re_prev(line, lpc)) { al.get_attrs().emplace_back( line_range(lpc - 1, lpc), - view_curses::VC_STYLE.value(repeated_char_attrs)); + VC_STYLE.value(repeated_char_attrs)); } break; case '?': { @@ -211,16 +212,15 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip) break; } al.get_attrs().emplace_back( - lr, view_curses::VC_STYLE.value(bracket_attrs)); + lr, VC_STYLE.value(bracket_attrs)); } else { al.get_attrs().emplace_back( - lr, view_curses::VC_STYLE.value(special_char)); + lr, VC_STYLE.value(special_char)); if (check_re_prev(line, lpc)) { al.get_attrs().emplace_back( line_range(lpc - 1, lpc), - view_curses::VC_STYLE.value( - repeated_char_attrs)); + VC_STYLE.value(repeated_char_attrs)); } } break; @@ -232,9 +232,8 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip) case '}': case '[': case ']': - al.get_attrs().emplace_back( - line_range(lpc, lpc + 1), - view_curses::VC_STYLE.value(bracket_attrs)); + al.get_attrs().emplace_back(line_range(lpc, lpc + 1), + VC_STYLE.value(bracket_attrs)); break; } } @@ -246,9 +245,8 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip) switch (line[lpc]) { case '\\': backslash_is_quoted = true; - al.with_attr( - string_attr(line_range(lpc - 1, lpc + 1), - view_curses::VC_STYLE.value(special_char))); + al.with_attr(string_attr(line_range(lpc - 1, lpc + 1), + VC_STYLE.value(special_char))); break; case 'd': case 'D': @@ -270,44 +268,38 @@ readline_regex_highlighter_int(attr_line_t& al, int x, int skip) case 'G': case 'Z': case 'z': - al.get_attrs().emplace_back( - line_range(lpc - 1, lpc + 1), - view_curses::VC_STYLE.value(class_attrs)); + al.get_attrs().emplace_back(line_range(lpc - 1, lpc + 1), + VC_STYLE.value(class_attrs)); break; case ' ': - al.get_attrs().emplace_back( - line_range(lpc - 1, lpc + 1), - view_curses::VC_STYLE.value(error_attrs)); + al.get_attrs().emplace_back(line_range(lpc - 1, lpc + 1), + VC_STYLE.value(error_attrs)); break; case '0': case 'x': if (safe_read(line, lpc + 1) == '{') { - al.with_attr(string_attr( - line_range(lpc - 1, lpc + 1), - view_curses::VC_STYLE.value(special_char))); + al.with_attr(string_attr(line_range(lpc - 1, lpc + 1), + VC_STYLE.value(special_char))); } else if (isdigit(safe_read(line, lpc + 1)) && isdigit(safe_read(line, lpc + 2))) { - al.with_attr(string_attr( - line_range(lpc - 1, lpc + 3), - view_curses::VC_STYLE.value(special_char))); + al.with_attr(string_attr(line_range(lpc - 1, lpc + 3), + VC_STYLE.value(special_char))); } else { - al.with_attr(string_attr( - line_range(lpc - 1, lpc + 1), - view_curses::VC_STYLE.value(error_attrs))); + al.with_attr(string_attr(line_range(lpc - 1, lpc + 1), + VC_STYLE.value(error_attrs))); } break; case 'Q': case 'E': - al.with_attr(string_attr( - line_range(lpc - 1, lpc + 1), - view_curses::VC_STYLE.value(bracket_attrs))); + al.with_attr(string_attr(line_range(lpc - 1, lpc + 1), + VC_STYLE.value(bracket_attrs))); break; default: if (isdigit(line[lpc])) { al.get_attrs().emplace_back( line_range(lpc - 1, lpc + 1), - view_curses::VC_STYLE.value(special_char)); + VC_STYLE.value(special_char)); } break; } @@ -338,7 +330,7 @@ readline_command_highlighter(attr_line_t& al, int x) static const pcrepp COLOR_RE("(#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3}))"); view_colors& vc = view_colors::singleton(); - int keyword_attrs = (A_BOLD | vc.attrs_for_role(view_colors::VCR_KEYWORD)); + int keyword_attrs = (A_BOLD | vc.attrs_for_role(role_t::VCR_KEYWORD)); const auto& line = al.get_string(); pcre_context_static<30> pc; @@ -349,7 +341,7 @@ readline_command_highlighter(attr_line_t& al, int x) auto command = line.substr(0, ws_index); if (ws_index != std::string::npos) { al.get_attrs().emplace_back(line_range(1, ws_index), - view_curses::VC_STYLE.value(keyword_attrs)); + VC_STYLE.value(keyword_attrs)); } if (RE_PREFIXES.match(pc, pi)) { readline_regex_highlighter_int(al, x, 1 + pc[0]->length()); @@ -373,8 +365,7 @@ readline_command_highlighter(attr_line_t& al, int x) .then([&](const auto& rgb_fg) { al.get_attrs().emplace_back( line_range{cap->c_begin, cap->c_begin + 1}, - view_curses::VC_ROLE.value( - view_colors::VCR_COLOR_HINT)); + VC_ROLE.value(role_t::VCR_COLOR_HINT)); }); } } @@ -402,7 +393,7 @@ readline_command_highlighter(attr_line_t& al, int x) value = "#" + value; } al.get_attrs().emplace_back( - lr, view_curses::VC_STYLE.value(vc.attrs_for_ident(value))); + lr, VC_STYLE.value(vc.attrs_for_ident(value))); } start = last; @@ -426,10 +417,10 @@ readline_sqlite_highlighter_int(attr_line_t& al, int x, int skip) auto& vc = view_colors::singleton(); - int keyword_attrs = vc.attrs_for_role(view_colors::VCR_KEYWORD); - int symbol_attrs = vc.attrs_for_role(view_colors::VCR_SYMBOL); - int string_attrs = vc.attrs_for_role(view_colors::VCR_STRING); - int error_attrs = vc.attrs_for_role(view_colors::VCR_ERROR) | A_REVERSE; + int keyword_attrs = vc.attrs_for_role(role_t::VCR_KEYWORD); + int symbol_attrs = vc.attrs_for_role(role_t::VCR_SYMBOL); + int string_attrs = vc.attrs_for_role(role_t::VCR_STRING); + int error_attrs = vc.attrs_for_role(role_t::VCR_ERROR) | A_REVERSE; pcre_context_static<30> pc; pcre_input pi(al.get_string(), skip); @@ -444,8 +435,7 @@ readline_sqlite_highlighter_int(attr_line_t& al, int x, int skip) if (space != std::string::npos) { lr.lr_end = space; } - al.get_attrs().emplace_back(lr, - view_curses::VC_STYLE.value(keyword_attrs)); + al.get_attrs().emplace_back(lr, VC_STYLE.value(keyword_attrs)); return; } @@ -456,7 +446,7 @@ readline_sqlite_highlighter_int(attr_line_t& al, int x, int skip) if (line[cap->c_end] == '(') { } else if (!lr.contains(x) && !lr.contains(x - 1)) { - al.get_attrs().emplace_back(lr, view_curses::VC_STYLE.value(attrs)); + al.get_attrs().emplace_back(lr, VC_STYLE.value(attrs)); } } @@ -466,7 +456,7 @@ readline_sqlite_highlighter_int(attr_line_t& al, int x, int skip) pcre_context::capture_t* cap = pc.all(); al.get_attrs().emplace_back(line_range(cap->c_begin, cap->c_end), - view_curses::VC_STYLE.value(keyword_attrs)); + VC_STYLE.value(keyword_attrs)); } for (size_t lpc = skip; lpc < line.length(); lpc++) { @@ -478,9 +468,8 @@ readline_sqlite_highlighter_int(attr_line_t& al, int x, int skip) case '!': case '-': case '+': - al.get_attrs().emplace_back( - line_range(lpc, lpc + 1), - view_curses::VC_STYLE.value(symbol_attrs)); + al.get_attrs().emplace_back(line_range(lpc, lpc + 1), + VC_STYLE.value(symbol_attrs)); break; } } @@ -496,10 +485,10 @@ readline_sqlite_highlighter_int(attr_line_t& al, int x, int skip) if (line[cap->c_end - 1] != '\'') { sa.emplace_back(line_range(cap->c_begin, cap->c_begin + 1), - view_curses::VC_STYLE.value(error_attrs)); + VC_STYLE.value(error_attrs)); lr.lr_start += 1; } - sa.emplace_back(lr, view_curses::VC_STYLE.value(string_attrs)); + sa.emplace_back(lr, VC_STYLE.value(string_attrs)); } for (int lpc = 0; brackets[lpc]; lpc++) { @@ -517,9 +506,9 @@ void readline_shlex_highlighter(attr_line_t& al, int x) { view_colors& vc = view_colors::singleton(); - int special_char = (A_BOLD | vc.attrs_for_role(view_colors::VCR_SYMBOL)); - int error_attrs = vc.attrs_for_role(view_colors::VCR_ERROR) | A_REVERSE; - int string_attrs = vc.attrs_for_role(view_colors::VCR_STRING); + int special_char = (A_BOLD | vc.attrs_for_role(role_t::VCR_SYMBOL)); + int error_attrs = vc.attrs_for_role(role_t::VCR_ERROR) | A_REVERSE; + int string_attrs = vc.attrs_for_role(role_t::VCR_STRING); const auto& str = al.get_string(); pcre_context::capture_t cap; shlex_token_t token; @@ -529,15 +518,13 @@ readline_shlex_highlighter(attr_line_t& al, int x) while (lexer.tokenize(cap, token)) { switch (token) { case shlex_token_t::ST_ERROR: - al.with_attr( - string_attr(line_range(cap.c_begin, cap.c_end), - view_curses::VC_STYLE.value(error_attrs))); + al.with_attr(string_attr(line_range(cap.c_begin, cap.c_end), + VC_STYLE.value(error_attrs))); break; case shlex_token_t::ST_TILDE: case shlex_token_t::ST_ESCAPE: - al.with_attr( - string_attr(line_range(cap.c_begin, cap.c_end), - view_curses::VC_STYLE.value(special_char))); + al.with_attr(string_attr(line_range(cap.c_begin, cap.c_end), + VC_STYLE.value(special_char))); break; case shlex_token_t::ST_DOUBLE_QUOTE_START: case shlex_token_t::ST_SINGLE_QUOTE_START: @@ -545,9 +532,8 @@ readline_shlex_highlighter(attr_line_t& al, int x) break; case shlex_token_t::ST_DOUBLE_QUOTE_END: case shlex_token_t::ST_SINGLE_QUOTE_END: - al.with_attr( - string_attr(line_range(quote_start, cap.c_end), - view_curses::VC_STYLE.value(string_attrs))); + al.with_attr(string_attr(line_range(quote_start, cap.c_end), + VC_STYLE.value(string_attrs))); quote_start = -1; break; case shlex_token_t::ST_VARIABLE_REF: @@ -559,16 +545,16 @@ readline_shlex_highlighter(attr_line_t& al, int x) al.with_attr(string_attr( line_range(cap.c_begin, cap.c_begin + 1 + extra), - view_curses::VC_STYLE.value(special_char))); + VC_STYLE.value(special_char))); al.with_attr(string_attr( line_range(cap.c_begin + 1 + extra, cap.c_end - extra), - view_curses::VC_STYLE.value( - x == cap.c_end || cap.contains(x) ? special_char - : attrs))); + VC_STYLE.value(x == cap.c_end || cap.contains(x) + ? special_char + : attrs))); if (extra) { al.with_attr( string_attr(line_range(cap.c_end - 1, cap.c_end), - view_curses::VC_STYLE.value(special_char))); + VC_STYLE.value(special_char))); } break; } @@ -579,6 +565,6 @@ readline_shlex_highlighter(attr_line_t& al, int x) if (quote_start != -1) { al.with_attr(string_attr(line_range(quote_start, quote_start + 1), - view_curses::VC_STYLE.value(error_attrs))); + VC_STYLE.value(error_attrs))); } } diff --git a/src/readline_highlighters.hh b/src/readline_highlighters.hh index 5d6dd065..ab537713 100644 --- a/src/readline_highlighters.hh +++ b/src/readline_highlighters.hh @@ -32,7 +32,7 @@ #ifndef readline_highlighters_hh #define readline_highlighters_hh -#include "view_curses.hh" +#include "base/attr_line.hh" void readline_regex_highlighter(attr_line_t& line, int x); diff --git a/src/relative_time.cc b/src/relative_time.cc index e184fb8d..99235a08 100644 --- a/src/relative_time.cc +++ b/src/relative_time.cc @@ -114,7 +114,7 @@ relative_time::from_str(const char* str, size_t len) parse_error pe_out; std::unordered_set seen_tokens; - pe_out.pe_column = -1; + pe_out.pe_column = 0; pe_out.pe_msg.clear(); while (true) { diff --git a/src/session_data.cc b/src/session_data.cc index 4a4946d1..ddfadc24 100644 --- a/src/session_data.cc +++ b/src/session_data.cc @@ -913,7 +913,7 @@ static struct json_path_container view_handlers static struct json_path_container file_state_handlers = { yajlpp::property_handler("visible") .with_description("Indicates whether the file is visible or not") - .FOR_FIELD(file_state, fs_is_visible), + .for_field(&file_state::fs_is_visible), }; static struct json_path_container file_states_handlers = { @@ -928,9 +928,9 @@ static struct json_path_container file_states_handlers = { static struct json_path_container view_info_handlers = { yajlpp::property_handler("save-time") - .FOR_FIELD(session_data_t, sd_save_time), + .for_field(&session_data_t::sd_save_time), yajlpp::property_handler("time-offset") - .FOR_FIELD(session_data_t, sd_time_offset), + .for_field(&session_data_t::sd_time_offset), json_path_handler("files#", read_files), yajlpp::property_handler("file-states").with_children(file_states_handlers), yajlpp::property_handler("views").with_children(view_handlers), diff --git a/src/session_data.hh b/src/session_data.hh index 85779648..04398251 100644 --- a/src/session_data.hh +++ b/src/session_data.hh @@ -33,6 +33,7 @@ #define lnav_session_data_hh #include +#include #include #include "view_helpers.hh" diff --git a/src/shared_buffer.hh b/src/shared_buffer.hh index ad29a502..d3df1f50 100644 --- a/src/shared_buffer.hh +++ b/src/shared_buffer.hh @@ -39,7 +39,7 @@ #include #include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/lnav_log.hh" class shared_buffer; diff --git a/src/spectro_source.cc b/src/spectro_source.cc index de20b216..ee9dfb91 100644 --- a/src/spectro_source.cc +++ b/src/spectro_source.cc @@ -31,6 +31,7 @@ #include "spectro_source.hh" +#include "base/ansi_scrubber.hh" #include "base/math_util.hh" #include "config.h" @@ -155,7 +156,7 @@ spectrogram_source::list_value_for_overlay(const listview_curses& lv, if (this->ss_cached_line_count == 0) { value_out.with_ansi_string(ANSI_ROLE("error: no log data"), - view_colors::VCR_ERROR); + role_t::VCR_ERROR); return true; } @@ -169,12 +170,12 @@ spectrogram_source::list_value_for_overlay(const listview_curses& lv, sizeof(buf), ANSI_ROLE(" ") " 1-%'d " ANSI_ROLE(" ") " %'d-%'d " ANSI_ROLE( " ") " %'d+", - view_colors::VCR_LOW_THRESHOLD, + role_t::VCR_LOW_THRESHOLD, st.st_green_threshold - 1, - view_colors::VCR_MED_THRESHOLD, + role_t::VCR_MED_THRESHOLD, st.st_green_threshold, st.st_yellow_threshold - 1, - view_colors::VCR_HIGH_THRESHOLD, + role_t::VCR_HIGH_THRESHOLD, st.st_yellow_threshold); line.append(width / 2 - strlen(buf) / 3 - line.length(), ' '); line.append(buf); @@ -185,7 +186,7 @@ spectrogram_source::list_value_for_overlay(const listview_curses& lv, line.append(buf); value_out.with_attr(string_attr(line_range(0, -1), - view_curses::VC_STYLE.value(A_UNDERLINE))); + VC_STYLE.value(A_UNDERLINE))); return true; } @@ -321,7 +322,7 @@ spectrogram_source::text_attrs_for_line(textview_curses& tc, color = COLOR_RED; } value_out.emplace_back(line_range(lpc, lpc + 1), - view_curses::VC_STYLE.value( + VC_STYLE.value( vc.ansi_color_pair(COLOR_BLACK, color))); } } diff --git a/src/spectro_source.hh b/src/spectro_source.hh index 8c70cb19..1057777c 100644 --- a/src/spectro_source.hh +++ b/src/spectro_source.hh @@ -38,7 +38,6 @@ #include #include -#include "ansi_scrubber.hh" #include "textview_curses.hh" struct spectrogram_bounds { diff --git a/src/sql_commands.cc b/src/sql_commands.cc index 0d901481..739b9b60 100644 --- a/src/sql_commands.cc +++ b/src/sql_commands.cc @@ -27,7 +27,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/fs_util.hh" #include "base/injector.bind.hh" #include "base/lnav_log.hh" @@ -40,7 +40,7 @@ #include "sqlitepp.hh" #include "view_helpers.hh" -static Result +static Result sql_cmd_dump(exec_context& ec, std::string cmdline, std::vector& args) @@ -80,7 +80,7 @@ sql_cmd_dump(exec_context& ec, return Ok(retval); } -static Result +static Result sql_cmd_read(exec_context& ec, std::string cmdline, std::vector& args) @@ -143,7 +143,7 @@ sql_cmd_read(exec_context& ec, return Ok(retval); } -static Result +static Result sql_cmd_schema(exec_context& ec, std::string cmdline, std::vector& args) @@ -159,7 +159,7 @@ sql_cmd_schema(exec_context& ec, return Ok(retval); } -static Result +static Result sql_cmd_generic(exec_context& ec, std::string cmdline, std::vector& args) diff --git a/src/sql_help.hh b/src/sql_help.hh index 90c4b135..203d053c 100644 --- a/src/sql_help.hh +++ b/src/sql_help.hh @@ -34,7 +34,7 @@ #include -#include "attr_line.hh" +#include "base/attr_line.hh" #include "help_text.hh" extern string_attr_type SQL_COMMAND_ATTR; diff --git a/src/sql_util.cc b/src/sql_util.cc index 51531064..e06dc6d5 100644 --- a/src/sql_util.cc +++ b/src/sql_util.cc @@ -39,7 +39,7 @@ #include #include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/injector.hh" #include "base/lnav_log.hh" #include "base/string_util.hh" @@ -655,11 +655,11 @@ sql_compile_script(sqlite3* db, const char* src_name, const char* script_orig, std::vector& stmts, - std::vector& errors) + std::vector& errors) { const char* script = script_orig; - while (script != NULL && script[0]) { + while (script != nullptr && script[0]) { auto_mem stmt(sqlite3_finalize); int line_number = 1; const char* tail; @@ -678,23 +678,23 @@ sql_compile_script(sqlite3* db, log_debug("retcode %d %p %p", retcode, script, tail); if (retcode != SQLITE_OK) { const char* errmsg = sqlite3_errmsg(db); - auto_mem full_msg; - - if (asprintf(full_msg.out(), - "error:%s:%d:%s", - src_name, - line_number, - errmsg) - == -1) - { - log_error("unable to allocate error message"); - break; + attr_line_t sql_content; + + if (tail != nullptr) { + sql_content.append(script, (size_t) (tail - script)); + } else { + sql_content.append(script); } - errors.emplace_back(full_msg.in()); + errors.emplace_back(lnav::console::user_message::error( + "failed to compile SQL statement") + .with_reason(errmsg) + .with_snippet(lnav::console::snippet::from( + src_name, sql_content) + .with_line(line_number))); break; } else if (script == tail) { break; - } else if (stmt == NULL) { + } else if (stmt == nullptr) { } else { stmts.push_back(stmt.release()); } @@ -703,10 +703,11 @@ sql_compile_script(sqlite3* db, } } -void +static void sql_execute_script(sqlite3* db, + const char* src_name, const std::vector& stmts, - std::vector& errors) + std::vector& errors) { std::map lvars; @@ -766,7 +767,13 @@ sql_execute_script(sqlite3* db, const char* errmsg; errmsg = sqlite3_errmsg(db); - errors.emplace_back(errmsg); + errors.emplace_back( + lnav::console::user_message::error( + "failed to execute SQL statement") + .with_reason(errmsg) + .with_snippet(lnav::console::snippet::from( + src_name, sqlite3_sql(stmt)))); + done = true; break; } } @@ -780,13 +787,14 @@ void sql_execute_script(sqlite3* db, const char* src_name, const char* script, - std::vector& errors) + std::vector& errors) { std::vector stmts; + auto init_error_count = errors.size(); sql_compile_script(db, src_name, script, stmts, errors); - if (errors.empty()) { - sql_execute_script(db, stmts, errors); + if (errors.size() == init_error_count) { + sql_execute_script(db, src_name, stmts, errors); } for (sqlite3_stmt* stmt : stmts) { diff --git a/src/sql_util.hh b/src/sql_util.hh index 9580c123..c7ba9cd9 100644 --- a/src/sql_util.hh +++ b/src/sql_util.hh @@ -41,6 +41,7 @@ #include #include "base/intern_string.hh" +#include "base/lnav.console.hh" #include "base/time_util.hh" #include "sqlitepp.hh" @@ -94,16 +95,12 @@ void sql_compile_script(sqlite3* db, const char* src_name, const char* script, std::vector& stmts, - std::vector& errors); - -void sql_execute_script(sqlite3* db, - const std::vector& stmts, - std::vector& errors); + std::vector& errors); void sql_execute_script(sqlite3* db, const char* src_name, const char* script, - std::vector& errors); + std::vector& errors); int guess_type_from_pcre(const std::string& pattern, std::string& collator); diff --git a/src/sqlite-extension-func.cc b/src/sqlite-extension-func.cc index 08dfbc54..6d0c14f4 100644 --- a/src/sqlite-extension-func.cc +++ b/src/sqlite-extension-func.cc @@ -31,7 +31,7 @@ #include "sqlite-extension-func.hh" -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/lnav_log.hh" #include "base/string_util.hh" #include "config.h" @@ -942,7 +942,11 @@ register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs) help_text("SELECT", "Query the database and return zero or more rows of data.") .sql_keyword() - .with_parameter(help_text("result-column", "").one_or_more()) + .with_parameter( + help_text( + "result-column", + "The expression used to generate a result for this column.") + .one_or_more()) .with_parameter(help_text("table", "The table(s) to query for data") .with_flag_name("FROM") .zero_or_more()) @@ -962,7 +966,7 @@ register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs) .with_flag_name("ORDER BY") .zero_or_more()) .with_parameter( - help_text("limit-expr", "The maximum number of rows to return") + help_text("limit-expr", "The maximum number of rows to return.") .with_flag_name("LIMIT") .zero_or_more()) .with_example( diff --git a/src/statusview_curses.cc b/src/statusview_curses.cc index 8d9680b1..90fe8589 100644 --- a/src/statusview_curses.cc +++ b/src/statusview_curses.cc @@ -34,6 +34,7 @@ #include "statusview_curses.hh" +#include "base/ansi_scrubber.hh" #include "config.h" void @@ -56,15 +57,15 @@ status_field::do_cylon() { string_attrs_t& sa = this->sf_value.get_attrs(); - remove_string_attr(sa, &view_curses::VC_STYLE); + remove_string_attr(sa, &VC_STYLE); struct line_range lr(this->sf_cylon_pos, this->sf_width); view_colors& vc = view_colors::singleton(); sa.emplace_back( lr, - view_curses::VC_STYLE.value( - vc.attrs_for_role(view_colors::VCR_ACTIVE_STATUS) | A_REVERSE)); + VC_STYLE.value( + vc.attrs_for_role(role_t::VCR_ACTIVE_STATUS) | A_REVERSE)); this->sf_cylon_pos += 1; if (this->sf_cylon_pos > this->sf_width) { @@ -73,18 +74,18 @@ status_field::do_cylon() } void -status_field::set_stitch_value(view_colors::role_t left, - view_colors::role_t right) +status_field::set_stitch_value(role_t left, + role_t right) { string_attrs_t& sa = this->sf_value.get_attrs(); struct line_range lr(0, 1); this->sf_value.get_string() = "::"; sa.clear(); - sa.emplace_back(lr, view_curses::VC_ROLE.value(left)); + sa.emplace_back(lr, VC_ROLE.value(left)); lr.lr_start = 1; lr.lr_end = 2; - sa.emplace_back(lr, view_curses::VC_ROLE.value(right)); + sa.emplace_back(lr, VC_ROLE.value(right)); } void @@ -104,8 +105,8 @@ statusview_curses::do_update() top = this->sc_top < 0 ? height + this->sc_top : this->sc_top; right = width; attrs = vc.attrs_for_role(this->sc_enabled - ? view_colors::VCR_STATUS - : view_colors::VCR_INACTIVE_STATUS); + ? role_t::VCR_STATUS + : role_t::VCR_INACTIVE_STATUS); wattron(this->sc_window, attrs); wmove(this->sc_window, top, 0); @@ -125,16 +126,16 @@ statusview_curses::do_update() val = sf.get_value(); if (!this->sc_enabled) { for (auto& sa : val.get_attrs()) { - if (sa.sa_type == &view_curses::VC_STYLE) { + if (sa.sa_type == &VC_STYLE) { sa.sa_value = sa.sa_value.get() & ~(A_REVERSE | A_COLOR); - } else if (sa.sa_type == &view_curses::VC_ROLE) { - if (sa.sa_value.get() - == view_colors::VCR_ALERT_STATUS) { - sa.sa_value.get() - = view_colors::VCR_INACTIVE_ALERT_STATUS; + } else if (sa.sa_type == &VC_ROLE) { + if (sa.sa_value.get() + == role_t::VCR_ALERT_STATUS) { + sa.sa_value.get() + = role_t::VCR_INACTIVE_ALERT_STATUS; } else { - sa.sa_value = view_colors::VCR_NONE; + sa.sa_value = role_t::VCR_NONE; } } } @@ -171,10 +172,10 @@ statusview_curses::do_update() auto default_role = sf.get_role(); if (!this->sc_enabled) { - if (default_role == view_colors::VCR_ALERT_STATUS) { - default_role = view_colors::VCR_INACTIVE_ALERT_STATUS; + if (default_role == role_t::VCR_ALERT_STATUS) { + default_role = role_t::VCR_INACTIVE_ALERT_STATUS; } else { - default_role = view_colors::VCR_INACTIVE_STATUS; + default_role = role_t::VCR_INACTIVE_STATUS; } } diff --git a/src/statusview_curses.hh b/src/statusview_curses.hh index 88c2355c..8107845b 100644 --- a/src/statusview_curses.hh +++ b/src/statusview_curses.hh @@ -35,7 +35,6 @@ #include #include -#include "ansi_scrubber.hh" #include "view_curses.hh" /** @@ -48,7 +47,7 @@ public: * @param role The color role for this field, defaults to VCR_STATUS. */ status_field(int width = 1, - view_colors::role_t role = view_colors::VCR_STATUS) + role_t role = role_t::VCR_STATUS) : sf_width(width), sf_role(role){}; virtual ~status_field() = default; @@ -75,7 +74,7 @@ public: return *this; }; - void set_stitch_value(view_colors::role_t left, view_colors::role_t right); + void set_stitch_value(role_t left, role_t right); void set_left_pad(size_t val) { @@ -125,12 +124,12 @@ public: }; /** @param role The color role for this field. */ - void set_role(view_colors::role_t role) + void set_role(role_t role) { this->sf_role = role; }; /** @return The color role for this field. */ - view_colors::role_t get_role() const + role_t get_role() const { return this->sf_role; }; @@ -173,7 +172,7 @@ protected: bool sf_cylon{false}; ssize_t sf_cylon_pos{0}; attr_line_t sf_value; /*< The value to display for this field. */ - view_colors::role_t sf_role; /*< The color role for this field. */ + role_t sf_role; /*< The color role for this field. */ int sf_share{0}; size_t sf_left_pad{0}; }; diff --git a/src/string-extension-functions.cc b/src/string-extension-functions.cc index 4c8f25ff..26934ee7 100644 --- a/src/string-extension-functions.cc +++ b/src/string-extension-functions.cc @@ -443,6 +443,12 @@ sql_gzip(sqlite3_value* val) return nonstd::nullopt; } +std::string +sql_humanize_file_size(file_ssize_t value) +{ + return humanize::file_size(value, humanize::alignment::columnar); +} + int string_extension_functions(struct FuncDef** basic_funcs, struct FuncDefAgg** agg_funcs) @@ -511,8 +517,8 @@ string_extension_functions(struct FuncDef** basic_funcs, "SELECT regexp_replace('123 abc', '(\\w+)', '<\\1>')", })), - sqlite_func_adapter:: + sqlite_func_adapter:: builder(help_text( "humanize_file_size", "Format the given file size as a human-friendly string") diff --git a/src/string_attr_type.hh b/src/string_attr_type.hh deleted file mode 100644 index a2331911..00000000 --- a/src/string_attr_type.hh +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2020, Timothy Stack - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * * Neither the name of Timothy Stack nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef lnav_string_attr_type_hh -#define lnav_string_attr_type_hh - -#include -#include - -#include - -#include "base/intern_string.hh" -#include "mapbox/variant.hpp" - -class logfile; -struct bookmark_metadata; - -using string_attr_value = mapbox::util::variant, - bookmark_metadata*>; - -class string_attr_type_base { -public: - explicit string_attr_type_base(const char* name) noexcept : sat_name(name) - { - } - - const char* const sat_name; -}; - -template -class string_attr_type : public string_attr_type_base { -public: - using value_type = T; - - explicit string_attr_type(const char* name) noexcept - : string_attr_type_base(name) - { - } - - template - std::enable_if_t::value, - std::pair> - value(const U& val) const - { - return std::make_pair(this, val); - } - - template - std::enable_if_t::value, - std::pair> - value() const - { - return std::make_pair(this, string_attr_value{}); - } -}; - -extern string_attr_type SA_ORIGINAL_LINE; -extern string_attr_type SA_BODY; -extern string_attr_type SA_HIDDEN; -extern string_attr_type SA_FORMAT; -extern string_attr_type SA_REMOVED; -extern string_attr_type SA_INVALID; -extern string_attr_type SA_ERROR; - -#endif diff --git a/src/styling.cc b/src/styling.cc index 71dc6974..9684bf11 100644 --- a/src/styling.cc +++ b/src/styling.cc @@ -39,15 +39,15 @@ #include "yajlpp/yajlpp_def.hh" static const struct json_path_container term_color_rgb_handler = { - yajlpp::property_handler("r").FOR_FIELD(rgb_color, rc_r), - yajlpp::property_handler("g").FOR_FIELD(rgb_color, rc_g), - yajlpp::property_handler("b").FOR_FIELD(rgb_color, rc_b), + yajlpp::property_handler("r").for_field(&rgb_color::rc_r), + yajlpp::property_handler("g").for_field(&rgb_color::rc_g), + yajlpp::property_handler("b").for_field(&rgb_color::rc_b), }; static const struct json_path_container term_color_handler = { - yajlpp::property_handler("colorId").FOR_FIELD(term_color, xc_id), - yajlpp::property_handler("name").FOR_FIELD(term_color, xc_name), - yajlpp::property_handler("hexString").FOR_FIELD(term_color, xc_hex), + yajlpp::property_handler("colorId").for_field(&term_color::xc_id), + yajlpp::property_handler("name").for_field(&term_color::xc_name), + yajlpp::property_handler("hexString").for_field(&term_color::xc_hex), yajlpp::property_handler("rgb") .with_obj_provider( [](const auto& pc, term_color* xc) { return &xc->xc_color; }) diff --git a/src/styling.hh b/src/styling.hh index 5362eae6..ea11a48e 100644 --- a/src/styling.hh +++ b/src/styling.hh @@ -206,6 +206,7 @@ struct lnav_theme { style_config lt_style_inactive_status; style_config lt_style_inactive_alert_status; style_config lt_style_file; + style_config lt_style_header[6]; std::map lt_level_styles; std::map lt_highlights; }; diff --git a/src/tailer/tailerpp.cc b/src/tailer/tailerpp.cc index 18f62701..1ea0a9e3 100644 --- a/src/tailer/tailerpp.cc +++ b/src/tailer/tailerpp.cc @@ -29,6 +29,8 @@ #include "tailerpp.hh" +#include + namespace tailer { int diff --git a/src/tailer/tailerpp.hh b/src/tailer/tailerpp.hh index a9cf43eb..a895d83a 100644 --- a/src/tailer/tailerpp.hh +++ b/src/tailer/tailerpp.hh @@ -33,7 +33,6 @@ #include #include -#include "auto_mem.hh" #include "base/result.h" #include "fmt/format.h" #include "mapbox/variant.hpp" diff --git a/src/textfile_highlighters.cc b/src/textfile_highlighters.cc index 3ff3cab4..5c2ee650 100644 --- a/src/textfile_highlighters.cc +++ b/src/textfile_highlighters.cc @@ -94,7 +94,7 @@ setup_highlights(highlight_map_t& hm) ")")) .with_nestable(false) .with_text_format(text_format_t::TF_PYTHON) - .with_role(view_colors::VCR_KEYWORD); + .with_role(role_t::VCR_KEYWORD); hm[{highlight_source_t::INTERNAL, "rust"}] = highlighter(xpcre_compile("(?:" @@ -159,7 +159,7 @@ setup_highlights(highlight_map_t& hm) ")")) .with_nestable(false) .with_text_format(text_format_t::TF_RUST) - .with_role(view_colors::VCR_KEYWORD); + .with_role(role_t::VCR_KEYWORD); hm[{highlight_source_t::INTERNAL, "clike"}] = highlighter(xpcre_compile("(?:" @@ -245,12 +245,12 @@ setup_highlights(highlight_map_t& hm) .with_nestable(false) .with_text_format(text_format_t::TF_C_LIKE) .with_text_format(text_format_t::TF_JAVA) - .with_role(view_colors::VCR_KEYWORD); + .with_role(role_t::VCR_KEYWORD); hm[{highlight_source_t::INTERNAL, "sql.0.comment"}] = highlighter(xpcre_compile("(?:(?<=[\\s;])|^)--.*")) .with_text_format(text_format_t::TF_SQL) - .with_role(view_colors::VCR_COMMENT); + .with_role(role_t::VCR_COMMENT); hm[{highlight_source_t::INTERNAL, "sql.9.keyword"}] = highlighter(xpcre_compile("(?:" "\\bABORT\\b|" @@ -387,42 +387,42 @@ setup_highlights(highlight_map_t& hm) PCRE_CASELESS)) .with_nestable(false) .with_text_format(text_format_t::TF_SQL) - .with_role(view_colors::VCR_KEYWORD); + .with_role(role_t::VCR_KEYWORD); hm[{highlight_source_t::INTERNAL, "srcfile"}] = highlighter(xpcre_compile( "[\\w\\-_]+\\." "(?:java|a|o|so|c|cc|cpp|cxx|h|hh|hpp|hxx|py|pyc|rb):" "\\d+")) - .with_role(view_colors::VCR_FILE); + .with_role(role_t::VCR_FILE); hm[{highlight_source_t::INTERNAL, "1.stringd"}] = highlighter(xpcre_compile(R"("(?:\\.|[^"])*")")) - .with_role(view_colors::VCR_STRING); + .with_role(role_t::VCR_STRING); hm[{highlight_source_t::INTERNAL, "1.strings"}] = highlighter(xpcre_compile(R"((? key_map_t; + typedef std::map key_map_t; static key_map_t key_roles; data_scanner ds(str); @@ -546,7 +546,7 @@ textview_curses::textview_value_for_row(vis_line_t row, attr_line_t& value_out) || binary_search(user_expr_marks.begin(), user_expr_marks.end(), row)) { sa.emplace_back(line_range{orig_line.lr_start, -1}, - view_curses::VC_STYLE.value(A_REVERSE)); + VC_STYLE.value(A_REVERSE)); } } @@ -590,7 +590,7 @@ textview_curses::execute_search(const std::string& regex_orig) if (code != nullptr) { highlighter hl(code); - hl.with_role(view_colors::VCR_SEARCH); + hl.with_role(role_t::VCR_SEARCH); highlight_map_t& hm = this->get_highlights(); hm[{highlight_source_t::PREVIEW, "search"}] = hl; diff --git a/src/themes/default-theme.json b/src/themes/default-theme.json index 0eac149d..e56849f8 100644 --- a/src/themes/default-theme.json +++ b/src/themes/default-theme.json @@ -58,6 +58,24 @@ "disabled-focused": { "color": "Black", "background-color": "#888" + }, + "h1": { + "underline": true + }, + "h2": { + "underline": true + }, + "h3": { + "underline": true + }, + "h4": { + "underline": true + }, + "h5": { + "underline": true + }, + "h6": { + "underline": true } }, "syntax-styles": { diff --git a/src/themes/eldar.json b/src/themes/eldar.json index 1ff4e865..0f368663 100644 --- a/src/themes/eldar.json +++ b/src/themes/eldar.json @@ -59,6 +59,24 @@ "popup": { "color": "$black", "background-color": "Grey37" + }, + "h1": { + "underline": true + }, + "h2": { + "underline": true + }, + "h3": { + "underline": true + }, + "h4": { + "underline": true + }, + "h5": { + "underline": true + }, + "h6": { + "underline": true } }, "syntax-styles": { diff --git a/src/themes/grayscale.json b/src/themes/grayscale.json index 0c88a25d..2de65c32 100644 --- a/src/themes/grayscale.json +++ b/src/themes/grayscale.json @@ -72,6 +72,24 @@ "scrollbar": { "color": "$black", "background-color": "#888" + }, + "h1": { + "underline": true + }, + "h2": { + "underline": true + }, + "h3": { + "underline": true + }, + "h4": { + "underline": true + }, + "h5": { + "underline": true + }, + "h6": { + "underline": true } }, "status-styles": { diff --git a/src/themes/monocai.json b/src/themes/monocai.json index ad5bf7e2..a67a8186 100644 --- a/src/themes/monocai.json +++ b/src/themes/monocai.json @@ -71,6 +71,24 @@ "scrollbar": { "color": "$black", "background-color": "#888" + }, + "h1": { + "underline": true + }, + "h2": { + "underline": true + }, + "h3": { + "underline": true + }, + "h4": { + "underline": true + }, + "h5": { + "underline": true + }, + "h6": { + "underline": true } }, "syntax-styles": { diff --git a/src/themes/night-owl.json b/src/themes/night-owl.json index 3e3dd912..aca21033 100644 --- a/src/themes/night-owl.json +++ b/src/themes/night-owl.json @@ -67,6 +67,24 @@ "popup": { "color": "$base00", "background-color": "$base3" + }, + "h1": { + "underline": true + }, + "h2": { + "underline": true + }, + "h3": { + "underline": true + }, + "h4": { + "underline": true + }, + "h5": { + "underline": true + }, + "h6": { + "underline": true } }, "syntax-styles": { diff --git a/src/themes/solarized-dark.json b/src/themes/solarized-dark.json index 7e619aad..a611a9ca 100644 --- a/src/themes/solarized-dark.json +++ b/src/themes/solarized-dark.json @@ -76,6 +76,24 @@ "disabled-focused": { "color": "$base0", "background-color": "$base02" + }, + "h1": { + "underline": true + }, + "h2": { + "underline": true + }, + "h3": { + "underline": true + }, + "h4": { + "underline": true + }, + "h5": { + "underline": true + }, + "h6": { + "underline": true } }, "syntax-styles": { diff --git a/src/themes/solarized-light.json b/src/themes/solarized-light.json index a0c34982..28f92897 100644 --- a/src/themes/solarized-light.json +++ b/src/themes/solarized-light.json @@ -76,6 +76,24 @@ "disabled-focused": { "color": "$base0", "background-color": "$base02" + }, + "h1": { + "underline": true + }, + "h2": { + "underline": true + }, + "h3": { + "underline": true + }, + "h4": { + "underline": true + }, + "h5": { + "underline": true + }, + "h6": { + "underline": true } }, "syntax-styles": { diff --git a/src/top_status_source.cc b/src/top_status_source.cc index 3ff86db7..22bb0281 100644 --- a/src/top_status_source.cc +++ b/src/top_status_source.cc @@ -39,20 +39,20 @@ top_status_source::top_status_source() this->tss_fields[TSF_PARTITION_NAME].set_width(34); this->tss_fields[TSF_PARTITION_NAME].set_left_pad(1); this->tss_fields[TSF_VIEW_NAME].set_width(8); - this->tss_fields[TSF_VIEW_NAME].set_role(view_colors::VCR_STATUS_TITLE); + this->tss_fields[TSF_VIEW_NAME].set_role(role_t::VCR_STATUS_TITLE); this->tss_fields[TSF_VIEW_NAME].right_justify(true); this->tss_fields[TSF_STITCH_VIEW_FORMAT].set_width(2); this->tss_fields[TSF_STITCH_VIEW_FORMAT].set_stitch_value( - view_colors::VCR_STATUS_STITCH_SUB_TO_TITLE, - view_colors::VCR_STATUS_STITCH_TITLE_TO_SUB); + role_t::VCR_STATUS_STITCH_SUB_TO_TITLE, + role_t::VCR_STATUS_STITCH_TITLE_TO_SUB); this->tss_fields[TSF_STITCH_VIEW_FORMAT].right_justify(true); this->tss_fields[TSF_FORMAT].set_width(20); - this->tss_fields[TSF_FORMAT].set_role(view_colors::VCR_STATUS_SUBTITLE); + this->tss_fields[TSF_FORMAT].set_role(role_t::VCR_STATUS_SUBTITLE); this->tss_fields[TSF_FORMAT].right_justify(true); this->tss_fields[TSF_STITCH_FORMAT_FILENAME].set_width(2); this->tss_fields[TSF_STITCH_FORMAT_FILENAME].set_stitch_value( - view_colors::VCR_STATUS_STITCH_NORMAL_TO_SUB, - view_colors::VCR_STATUS_STITCH_SUB_TO_NORMAL); + role_t::VCR_STATUS_STITCH_NORMAL_TO_SUB, + role_t::VCR_STATUS_STITCH_SUB_TO_NORMAL); this->tss_fields[TSF_STITCH_FORMAT_FILENAME].right_justify(true); this->tss_fields[TSF_FILENAME].set_min_width(35); /* XXX */ this->tss_fields[TSF_FILENAME].set_share(1); diff --git a/src/top_sys_status_source.hh b/src/top_sys_status_source.hh index 100d4d6e..10f60647 100644 --- a/src/top_sys_status_source.hh +++ b/src/top_sys_status_source.hh @@ -59,9 +59,9 @@ public: this->tss_fields[lpc].set_width(5); this->tss_fields[lpc].set_value(names[lpc]); } - this->tss_fields[TSF_CPU].set_role(view_colors::VCR_WARN_STATUS); - this->tss_fields[TSF_MEM].set_role(view_colors::VCR_ALERT_STATUS); - this->tss_fields[TSF_TRAF].set_role(view_colors::VCR_ACTIVE_STATUS); + this->tss_fields[TSF_CPU].set_role(role_t::VCR_WARN_STATUS); + this->tss_fields[TSF_MEM].set_role(role_t::VCR_ALERT_STATUS); + this->tss_fields[TSF_TRAF].set_role(role_t::VCR_ACTIVE_STATUS); }; size_t statusview_fields() diff --git a/src/view_curses.cc b/src/view_curses.cc index 410582b0..9aff1bb4 100644 --- a/src/view_curses.cc +++ b/src/view_curses.cc @@ -37,9 +37,8 @@ #include #include -#include "ansi_scrubber.hh" -#include "attr_line.hh" -#include "auto_mem.hh" +#include "base/ansi_scrubber.hh" +#include "base/attr_line.hh" #include "base/lnav_log.hh" #include "config.h" #include "lnav_config.hh" @@ -48,13 +47,6 @@ using namespace std::chrono_literals; -string_attr_type view_curses::VC_ROLE("role"); -string_attr_type view_curses::VC_ROLE_FG("role-fg"); -string_attr_type view_curses::VC_STYLE("style"); -string_attr_type view_curses::VC_GRAPHIC("graphic"); -string_attr_type view_curses::VC_FOREGROUND("foreground"); -string_attr_type view_curses::VC_BACKGROUND("background"); - const struct itimerval ui_periodic_timer::INTERVAL = { {0, std::chrono::duration_cast(350ms).count()}, {0, std::chrono::duration_cast(350ms).count()}}; @@ -121,7 +113,7 @@ view_curses::mvwattrline(WINDOW* window, int x, attr_line_t& al, const struct line_range& lr_chars, - view_colors::role_t base_role) + role_t base_role) { auto& sa = al.get_attrs(); auto& line = al.get_string(); @@ -296,14 +288,12 @@ view_curses::mvwattrline(WINDOW* window, attrs = iter->sa_value.get() & ~A_COLOR; color_pair = PAIR_NUMBER(iter->sa_value.get()); } else if (iter->sa_type == &VC_ROLE) { - attrs = vc.attrs_for_role( - (view_colors::role_t) iter->sa_value.get()); + attrs = vc.attrs_for_role(iter->sa_value.get()); color_pair = PAIR_NUMBER(attrs); attrs = attrs & ~A_COLOR; } else if (iter->sa_type == &VC_ROLE_FG) { short role_fg, role_bg; - attrs = vc.attrs_for_role( - (view_colors::role_t) iter->sa_value.get()); + attrs = vc.attrs_for_role(iter->sa_value.get()); color_pair = PAIR_NUMBER(attrs); pair_content(color_pair, &role_fg, &role_bg); attrs = attrs & ~A_COLOR; @@ -663,11 +653,14 @@ view_colors::init_roles(const lnav_theme& lt, } /* Setup the mappings from roles to actual colors. */ - this->vc_role_colors[VCR_TEXT] = this->to_attrs( - color_pair_base, lt, lt.lt_style_text, lt.lt_style_text, reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_TEXT)] + = this->to_attrs( + color_pair_base, lt, lt.lt_style_text, lt.lt_style_text, reporter); { - int pnum = PAIR_NUMBER(this->vc_role_colors[VCR_TEXT].first); + int pnum = PAIR_NUMBER( + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_TEXT)] + .first); short text_fg, text_bg; pair_content(pnum, &text_fg, &text_bg); @@ -717,114 +710,184 @@ view_colors::init_roles(const lnav_theme& lt, } } if (lnav_config.lc_ui_dim_text) { - this->vc_role_colors[VCR_TEXT].first |= A_DIM; - this->vc_role_colors[VCR_TEXT].second |= A_DIM; + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_TEXT)].first + |= A_DIM; + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_TEXT)] + .second + |= A_DIM; } - this->vc_role_colors[VCR_SEARCH] = std::make_pair(A_REVERSE, A_REVERSE); - this->vc_role_colors[VCR_IDENTIFIER] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_SEARCH)] + = std::make_pair(A_REVERSE, A_REVERSE); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_IDENTIFIER)] = this->to_attrs(color_pair_base, lt, lt.lt_style_identifier, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_OK] = this->to_attrs( - color_pair_base, lt, lt.lt_style_ok, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_ERROR] = this->to_attrs( - color_pair_base, lt, lt.lt_style_error, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_WARNING] = this->to_attrs( - color_pair_base, lt, lt.lt_style_warning, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_ALT_ROW] = this->to_attrs( - color_pair_base, lt, lt.lt_style_alt_text, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_HIDDEN] = this->to_attrs( - color_pair_base, lt, lt.lt_style_hidden, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_ADJUSTED_TIME] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_OK)] + = this->to_attrs( + color_pair_base, lt, lt.lt_style_ok, lt.lt_style_text, reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_ERROR)] + = this->to_attrs( + color_pair_base, lt, lt.lt_style_error, lt.lt_style_text, reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_WARNING)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_warning, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_ALT_ROW)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_alt_text, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_HIDDEN)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_hidden, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_ADJUSTED_TIME)] = this->to_attrs(color_pair_base, lt, lt.lt_style_adjusted_time, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_SKEWED_TIME] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_SKEWED_TIME)] = this->to_attrs(color_pair_base, lt, lt.lt_style_skewed_time, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_OFFSET_TIME] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_OFFSET_TIME)] = this->to_attrs(color_pair_base, lt, lt.lt_style_offset_time, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_INVALID_MSG] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_INVALID_MSG)] = this->to_attrs(color_pair_base, lt, lt.lt_style_invalid_msg, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_STATUS] = this->to_attrs( - color_pair_base, lt, lt.lt_style_status, lt.lt_style_status, reporter); - this->vc_role_colors[VCR_WARN_STATUS] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_STATUS)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_status, + lt.lt_style_status, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_WARN_STATUS)] = this->to_attrs(color_pair_base, lt, lt.lt_style_warn_status, lt.lt_style_status, reporter); - this->vc_role_colors[VCR_ALERT_STATUS] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_ALERT_STATUS)] = this->to_attrs(color_pair_base, lt, lt.lt_style_alert_status, lt.lt_style_status, reporter); - this->vc_role_colors[VCR_ACTIVE_STATUS] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_ACTIVE_STATUS)] = this->to_attrs(color_pair_base, lt, lt.lt_style_active_status, lt.lt_style_status, reporter); - this->vc_role_colors[VCR_ACTIVE_STATUS2] = std::make_pair( - this->vc_role_colors[VCR_ACTIVE_STATUS].first | A_BOLD, - this->vc_role_colors[VCR_ACTIVE_STATUS].second | A_BOLD); - this->vc_role_colors[VCR_STATUS_TITLE] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_ACTIVE_STATUS2)] + = std::make_pair(this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_ACTIVE_STATUS)] + .first + | A_BOLD, + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_ACTIVE_STATUS)] + .second + | A_BOLD); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_STATUS_TITLE)] = this->to_attrs(color_pair_base, lt, lt.lt_style_status_title, lt.lt_style_status, reporter); - this->vc_role_colors[VCR_STATUS_SUBTITLE] + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_STATUS_SUBTITLE)] = this->to_attrs(color_pair_base, lt, lt.lt_style_status_subtitle, lt.lt_style_status, reporter); - this->vc_role_colors[VCR_STATUS_HOTKEY] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_STATUS_HOTKEY)] = this->to_attrs(color_pair_base, lt, lt.lt_style_status_hotkey, lt.lt_style_status, reporter); - this->vc_role_colors[VCR_STATUS_TITLE_HOTKEY] + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_STATUS_TITLE_HOTKEY)] = this->to_attrs(color_pair_base, lt, lt.lt_style_status_title_hotkey, lt.lt_style_status, reporter); - this->vc_role_colors[VCR_STATUS_DISABLED_TITLE] + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_STATUS_DISABLED_TITLE)] = this->to_attrs(color_pair_base, lt, lt.lt_style_status_disabled_title, lt.lt_style_status, reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_H1)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_header[0], + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_H2)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_header[1], + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_H3)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_header[2], + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_H4)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_header[3], + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_H5)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_header[4], + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_H6)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_header[5], + lt.lt_style_text, + reporter); + { style_config stitch_sc; stitch_sc.sc_color = lt.lt_style_status_subtitle.sc_background_color; stitch_sc.sc_background_color = lt.lt_style_status_title.sc_background_color; - this->vc_role_colors[VCR_STATUS_STITCH_TITLE_TO_SUB] = this->to_attrs( - color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_STATUS_STITCH_TITLE_TO_SUB)] + = this->to_attrs( + color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); } { style_config stitch_sc; @@ -832,8 +895,10 @@ view_colors::init_roles(const lnav_theme& lt, stitch_sc.sc_color = lt.lt_style_status_title.sc_background_color; stitch_sc.sc_background_color = lt.lt_style_status_subtitle.sc_background_color; - this->vc_role_colors[VCR_STATUS_STITCH_SUB_TO_TITLE] = this->to_attrs( - color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_STATUS_STITCH_SUB_TO_TITLE)] + = this->to_attrs( + color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); } { @@ -842,16 +907,20 @@ view_colors::init_roles(const lnav_theme& lt, stitch_sc.sc_color = lt.lt_style_status.sc_background_color; stitch_sc.sc_background_color = lt.lt_style_status_subtitle.sc_background_color; - this->vc_role_colors[VCR_STATUS_STITCH_SUB_TO_NORMAL] = this->to_attrs( - color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_STATUS_STITCH_SUB_TO_NORMAL)] + = this->to_attrs( + color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); } { style_config stitch_sc; stitch_sc.sc_color = lt.lt_style_status_subtitle.sc_background_color; stitch_sc.sc_background_color = lt.lt_style_status.sc_background_color; - this->vc_role_colors[VCR_STATUS_STITCH_NORMAL_TO_SUB] = this->to_attrs( - color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_STATUS_STITCH_NORMAL_TO_SUB)] + = this->to_attrs( + color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); } { @@ -860,7 +929,8 @@ view_colors::init_roles(const lnav_theme& lt, stitch_sc.sc_color = lt.lt_style_status.sc_background_color; stitch_sc.sc_background_color = lt.lt_style_status_title.sc_background_color; - this->vc_role_colors[VCR_STATUS_STITCH_TITLE_TO_NORMAL] + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL)] = this->to_attrs( color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); } @@ -869,121 +939,169 @@ view_colors::init_roles(const lnav_theme& lt, stitch_sc.sc_color = lt.lt_style_status_title.sc_background_color; stitch_sc.sc_background_color = lt.lt_style_status.sc_background_color; - this->vc_role_colors[VCR_STATUS_STITCH_NORMAL_TO_TITLE] + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE)] = this->to_attrs( color_pair_base, lt, stitch_sc, lt.lt_style_status, reporter); } - this->vc_role_colors[VCR_INACTIVE_STATUS] + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_INACTIVE_STATUS)] = this->to_attrs(color_pair_base, lt, lt.lt_style_inactive_status, lt.lt_style_status, reporter); - this->vc_role_colors[VCR_INACTIVE_ALERT_STATUS] + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_INACTIVE_ALERT_STATUS)] = this->to_attrs(color_pair_base, lt, lt.lt_style_inactive_alert_status, lt.lt_style_alert_status, reporter); - this->vc_role_colors[VCR_POPUP] = this->to_attrs( - color_pair_base, lt, lt.lt_style_popup, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_FOCUSED] = this->to_attrs(color_pair_base, - lt, - lt.lt_style_focused, - lt.lt_style_focused, - reporter); - this->vc_role_colors[VCR_DISABLED_FOCUSED] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_POPUP)] + = this->to_attrs( + color_pair_base, lt, lt.lt_style_popup, lt.lt_style_text, reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_FOCUSED)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_focused, + lt.lt_style_focused, + reporter); + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_DISABLED_FOCUSED)] = this->to_attrs(color_pair_base, lt, lt.lt_style_disabled_focused, lt.lt_style_disabled_focused, reporter); - this->vc_role_colors[VCR_COLOR_HINT] = std::make_pair( - COLOR_PAIR(color_pair_base), COLOR_PAIR(color_pair_base + 1)); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_COLOR_HINT)] + = std::make_pair(COLOR_PAIR(color_pair_base), + COLOR_PAIR(color_pair_base + 1)); color_pair_base += 2; - this->vc_role_colors[VCR_SCROLLBAR] = this->to_attrs(color_pair_base, - lt, - lt.lt_style_scrollbar, - lt.lt_style_status, - reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_SCROLLBAR)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_scrollbar, + lt.lt_style_status, + reporter); { style_config bar_sc; bar_sc.sc_color = lt.lt_style_error.sc_color; bar_sc.sc_background_color = lt.lt_style_scrollbar.sc_background_color; - this->vc_role_colors[VCR_SCROLLBAR_ERROR] = this->to_attrs( - color_pair_base, lt, bar_sc, lt.lt_style_alert_status, reporter); + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_SCROLLBAR_ERROR)] + = this->to_attrs(color_pair_base, + lt, + bar_sc, + lt.lt_style_alert_status, + reporter); } { style_config bar_sc; bar_sc.sc_color = lt.lt_style_warning.sc_color; bar_sc.sc_background_color = lt.lt_style_scrollbar.sc_background_color; - this->vc_role_colors[VCR_SCROLLBAR_WARNING] = this->to_attrs( - color_pair_base, lt, bar_sc, lt.lt_style_warn_status, reporter); + this->vc_role_colors[lnav::enums::to_underlying( + role_t::VCR_SCROLLBAR_WARNING)] + = this->to_attrs( + color_pair_base, lt, bar_sc, lt.lt_style_warn_status, reporter); } - this->vc_role_colors[VCR_KEYWORD] = this->to_attrs( - color_pair_base, lt, lt.lt_style_keyword, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_STRING] = this->to_attrs( - color_pair_base, lt, lt.lt_style_string, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_COMMENT] = this->to_attrs( - color_pair_base, lt, lt.lt_style_comment, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_DOC_DIRECTIVE] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_KEYWORD)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_keyword, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_STRING)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_string, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_COMMENT)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_comment, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_DOC_DIRECTIVE)] = this->to_attrs(color_pair_base, lt, lt.lt_style_doc_directive, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_VARIABLE] = this->to_attrs( - color_pair_base, lt, lt.lt_style_variable, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_SYMBOL] = this->to_attrs( - color_pair_base, lt, lt.lt_style_symbol, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_NUMBER] = this->to_attrs( - color_pair_base, lt, lt.lt_style_number, lt.lt_style_text, reporter); - - this->vc_role_colors[VCR_RE_SPECIAL] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_VARIABLE)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_variable, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_SYMBOL)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_symbol, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_NUMBER)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_number, + lt.lt_style_text, + reporter); + + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_RE_SPECIAL)] = this->to_attrs(color_pair_base, lt, lt.lt_style_re_special, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_RE_REPEAT] = this->to_attrs( - color_pair_base, lt, lt.lt_style_re_repeat, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_FILE] = this->to_attrs( - color_pair_base, lt, lt.lt_style_file, lt.lt_style_text, reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_RE_REPEAT)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_re_repeat, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_FILE)] + = this->to_attrs( + color_pair_base, lt, lt.lt_style_file, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_DIFF_DELETE] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_DIFF_DELETE)] = this->to_attrs(color_pair_base, lt, lt.lt_style_diff_delete, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_DIFF_ADD] = this->to_attrs( - color_pair_base, lt, lt.lt_style_diff_add, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_DIFF_SECTION] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_DIFF_ADD)] + = this->to_attrs(color_pair_base, + lt, + lt.lt_style_diff_add, + lt.lt_style_text, + reporter); + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_DIFF_SECTION)] = this->to_attrs(color_pair_base, lt, lt.lt_style_diff_section, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_LOW_THRESHOLD] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_LOW_THRESHOLD)] = this->to_attrs(color_pair_base, lt, lt.lt_style_low_threshold, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_MED_THRESHOLD] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_MED_THRESHOLD)] = this->to_attrs(color_pair_base, lt, lt.lt_style_med_threshold, lt.lt_style_text, reporter); - this->vc_role_colors[VCR_HIGH_THRESHOLD] + this->vc_role_colors[lnav::enums::to_underlying(role_t::VCR_HIGH_THRESHOLD)] = this->to_attrs(color_pair_base, lt, lt.lt_style_high_threshold, @@ -1032,7 +1150,7 @@ view_colors::ensure_color_pair(short fg, short bg) return retval; } - short def_pair = PAIR_NUMBER(this->attrs_for_role(VCR_TEXT)); + short def_pair = PAIR_NUMBER(this->attrs_for_role(role_t::VCR_TEXT)); short def_fg, def_bg; pair_content(def_pair, &def_fg, &def_bg); @@ -1101,7 +1219,7 @@ view_colors::color_for_ident(const char* str, size_t len) const attr_t view_colors::attrs_for_ident(const char* str, size_t len) { - auto retval = this->attrs_for_role(VCR_IDENTIFIER); + auto retval = this->attrs_for_role(role_t::VCR_IDENTIFIER); if (retval & (A_LEFT | A_RIGHT)) { auto color_pair = PAIR_NUMBER(retval); @@ -1208,3 +1326,9 @@ lab_color::operator!=(const lab_color& rhs) const { return !(rhs == *this); } + +string_attr_pair +view_colors::roles::file() +{ + return VC_ROLE.value(role_t::VCR_FILE); +} diff --git a/src/view_curses.hh b/src/view_curses.hh index 75392d0a..baf86514 100644 --- a/src/view_curses.hh +++ b/src/view_curses.hh @@ -59,7 +59,8 @@ #include #include -#include "attr_line.hh" +#include "base/attr_line.hh" +#include "base/enum_util.hh" #include "base/lnav_log.hh" #include "base/lrucache.hpp" #include "base/opt_util.hh" @@ -193,70 +194,6 @@ class view_colors { public: static constexpr unsigned long HI_COLOR_COUNT = 6 * 3 * 3; - /** Roles that can be mapped to curses attributes using attrs_for_role() */ - typedef enum { - VCR_NONE = -1, - - VCR_TEXT, /*< Raw text. */ - VCR_IDENTIFIER, - VCR_SEARCH, /*< A search hit. */ - VCR_OK, - VCR_ERROR, /*< An error message. */ - VCR_WARNING, /*< A warning message. */ - VCR_ALT_ROW, /*< Highlight for alternating rows in a list */ - VCR_HIDDEN, - VCR_ADJUSTED_TIME, - VCR_SKEWED_TIME, - VCR_OFFSET_TIME, - VCR_INVALID_MSG, - VCR_STATUS, /*< Normal status line text. */ - VCR_WARN_STATUS, - VCR_ALERT_STATUS, /*< Alert status line text. */ - VCR_ACTIVE_STATUS, /*< */ - VCR_ACTIVE_STATUS2, /*< */ - VCR_STATUS_TITLE, - VCR_STATUS_SUBTITLE, - VCR_STATUS_STITCH_TITLE_TO_SUB, - VCR_STATUS_STITCH_SUB_TO_TITLE, - VCR_STATUS_STITCH_SUB_TO_NORMAL, - VCR_STATUS_STITCH_NORMAL_TO_SUB, - VCR_STATUS_STITCH_TITLE_TO_NORMAL, - VCR_STATUS_STITCH_NORMAL_TO_TITLE, - VCR_STATUS_TITLE_HOTKEY, - VCR_STATUS_DISABLED_TITLE, - VCR_STATUS_HOTKEY, - VCR_INACTIVE_STATUS, - VCR_INACTIVE_ALERT_STATUS, - VCR_SCROLLBAR, - VCR_SCROLLBAR_ERROR, - VCR_SCROLLBAR_WARNING, - VCR_FOCUSED, - VCR_DISABLED_FOCUSED, - VCR_POPUP, - VCR_COLOR_HINT, - - VCR_KEYWORD, - VCR_STRING, - VCR_COMMENT, - VCR_DOC_DIRECTIVE, - VCR_VARIABLE, - VCR_SYMBOL, - VCR_NUMBER, - VCR_RE_SPECIAL, - VCR_RE_REPEAT, - VCR_FILE, - - VCR_DIFF_DELETE, /*< Deleted line in a diff. */ - VCR_DIFF_ADD, /*< Added line in a diff. */ - VCR_DIFF_SECTION, /*< Section marker in a diff. */ - - VCR_LOW_THRESHOLD, - VCR_MED_THRESHOLD, - VCR_HIGH_THRESHOLD, - - VCR__MAX - } role_t; - /** @return A reference to the singleton. */ static view_colors& singleton(); @@ -276,23 +213,24 @@ public: */ attr_t attrs_for_role(role_t role, bool selected = false) const { - if (role == VCR_NONE) { + if (role == role_t::VCR_NONE) { return 0; } - require(role >= 0); - require(role < VCR__MAX); + require(role > role_t::VCR_NONE); + require(role < role_t::VCR__MAX); - return selected ? this->vc_role_colors[role].second - : this->vc_role_colors[role].first; + return selected + ? this->vc_role_colors[lnav::enums::to_underlying(role)].second + : this->vc_role_colors[lnav::enums::to_underlying(role)].first; }; attr_t reverse_attrs_for_role(role_t role) const { - require(role >= 0); - require(role < VCR__MAX); + require(role > role_t::VCR_NONE); + require(role < role_t::VCR__MAX); - return this->vc_role_reverse_colors[role]; + return this->vc_role_reverse_colors[lnav::enums::to_underlying(role)]; }; int color_for_ident(const char* str, size_t len) const; @@ -351,6 +289,10 @@ public: return this->vc_ansi_to_theme[ansi_fg]; } + struct roles { + static string_attr_pair file(); + }; + static bool initialized; private: @@ -364,9 +306,10 @@ private: }; /** Map of role IDs to attribute values. */ - std::pair vc_role_colors[VCR__MAX]; + std::pair + vc_role_colors[lnav::enums::to_underlying(role_t::VCR__MAX)]; /** Map of role IDs to reverse-video attribute values. */ - attr_t vc_role_reverse_colors[VCR__MAX]; + attr_t vc_role_reverse_colors[lnav::enums::to_underlying(role_t::VCR__MAX)]; short vc_ansi_to_theme[8]; short vc_highlight_colors[HI_COLOR_COUNT]; int vc_color_pair_end{0}; @@ -447,7 +390,7 @@ public: return *this; } - void set_default_role(view_colors::role_t role) + void set_default_role(role_t role) { this->vc_default_role = role; } @@ -472,13 +415,6 @@ public: return this->vc_width; } - static string_attr_type VC_ROLE; - static string_attr_type VC_ROLE_FG; - static string_attr_type VC_STYLE; - static string_attr_type VC_GRAPHIC; - static string_attr_type VC_FOREGROUND; - static string_attr_type VC_BACKGROUND; - static void awaiting_user_input(); static void mvwattrline(WINDOW* window, @@ -486,8 +422,7 @@ public: int x, attr_line_t& al, const struct line_range& lr, - view_colors::role_t base_role - = view_colors::VCR_TEXT); + role_t base_role = role_t::VCR_TEXT); protected: bool vc_visible{true}; @@ -495,7 +430,7 @@ protected: bool vc_needs_update{true}; long vc_width; std::vector vc_children; - view_colors::role_t vc_default_role{view_colors::VCR_TEXT}; + role_t vc_default_role{role_t::VCR_TEXT}; }; template @@ -507,9 +442,8 @@ public: { if (this->vs_views.empty()) { return nonstd::nullopt; - } else { - return this->vs_views.back(); } + return this->vs_views.back(); } void do_update() override @@ -563,12 +497,12 @@ public: return this->vs_views.end(); } - size_t size() + size_t size() const { return this->vs_views.size(); } - bool empty() + bool empty() const { return this->vs_views.empty(); } diff --git a/src/view_helpers.cc b/src/view_helpers.cc index 8dd2b12e..7c537387 100644 --- a/src/view_helpers.cc +++ b/src/view_helpers.cc @@ -297,14 +297,28 @@ layout_views() lnav_data.ld_match_view.set_height(vis_line_t(match_height)); - if (doc_height + 14 > ((int) height - match_height - preview_height - 2)) { + int um_rows = lnav_data.ld_user_message_source.text_line_count(); + if (um_rows > 0 + && std::chrono::steady_clock::now() + > lnav_data.ld_user_message_expiration) + { + lnav_data.ld_user_message_source.clear(); + um_rows = 0; + } + int um_height = std::min((unsigned long) um_rows, (height - 4) / 2); + + lnav_data.ld_user_message_view.set_height(vis_line_t(um_height)); + + if (doc_height + 14 + > ((int) height - match_height - um_height - preview_height - 2)) + { preview_height = 0; preview_status_open = false; } - if (doc_height + 14 > ((int) height - match_height - 2)) { + if (doc_height + 14 > ((int) height - match_height - um_height - 2)) { doc_height = lnav_data.ld_doc_source.text_line_count(); - if (doc_height + 14 > ((int) height - match_height - 2)) { + if (doc_height + 14 > ((int) height - match_height - um_height - 2)) { doc_height = 0; } } @@ -319,7 +333,7 @@ layout_views() int bottom_height = (doc_open ? 1 : 0) + doc_height + (preview_status_open ? 1 : 0) + preview_height + 1 // bottom status - + match_height + lnav_data.ld_rl_view->get_height(); + + match_height + um_height + lnav_data.ld_rl_view->get_height(); for (auto& tc : lnav_data.ld_views) { tc.set_height(vis_line_t(-(bottom_height + (filter_status_open ? 1 : 0) @@ -333,7 +347,7 @@ layout_views() lnav_data.ld_status[LNS_FILTER_HELP].set_visible(filters_open); lnav_data.ld_status[LNS_FILTER_HELP].set_top( -(bottom_height + filter_height + 1)); - lnav_data.ld_status[LNS_BOTTOM].set_top(-(match_height + 2)); + lnav_data.ld_status[LNS_BOTTOM].set_top(-(match_height + um_height + 2)); lnav_data.ld_status[LNS_DOC].set_top(height - bottom_height); lnav_data.ld_status[LNS_DOC].set_visible(doc_open); lnav_data.ld_status[LNS_PREVIEW].set_top(height - bottom_height @@ -371,6 +385,8 @@ layout_views() lnav_data.ld_preview_view.set_height(vis_line_t(preview_height)); lnav_data.ld_preview_view.set_y(height - bottom_height + 1 + (doc_open ? 1 : 0) + doc_height); + lnav_data.ld_user_message_view.set_y( + height - lnav_data.ld_rl_view->get_height() - match_height - um_height); lnav_data.ld_match_view.set_y(height - lnav_data.ld_rl_view->get_height() - match_height); lnav_data.ld_rl_view->set_width(width); diff --git a/src/view_helpers.examples.hh b/src/view_helpers.examples.hh index ff43484d..12287c72 100644 --- a/src/view_helpers.examples.hh +++ b/src/view_helpers.examples.hh @@ -32,7 +32,7 @@ #ifndef lnav_view_helpers_examples_hh #define lnav_view_helpers_examples_hh -#include "attr_line.hh" +#include "base/attr_line.hh" #include "help_text.hh" void execute_examples(); diff --git a/src/vtab_module.hh b/src/vtab_module.hh index 7bef184a..366a659a 100644 --- a/src/vtab_module.hh +++ b/src/vtab_module.hh @@ -36,7 +36,7 @@ #include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/intern_string.hh" #include "base/lnav_log.hh" #include "base/string_util.hh" diff --git a/src/yajlpp/CMakeLists.txt b/src/yajlpp/CMakeLists.txt index 099e7d8d..042a29ba 100644 --- a/src/yajlpp/CMakeLists.txt +++ b/src/yajlpp/CMakeLists.txt @@ -13,7 +13,7 @@ add_library( target_include_directories(yajlpp PUBLIC . .. ../fmtlib ${CMAKE_CURRENT_BINARY_DIR}/..) -target_link_libraries(yajlpp pcrepp yajl) +target_link_libraries(yajlpp pcrepp yajl ncurses::libcurses) add_executable(test_yajlpp test_yajlpp.cc) target_link_libraries(test_yajlpp yajlpp base ${lnav_LIBS}) diff --git a/src/yajlpp/json_ptr.hh b/src/yajlpp/json_ptr.hh index f2c5706f..e3734e9b 100644 --- a/src/yajlpp/json_ptr.hh +++ b/src/yajlpp/json_ptr.hh @@ -40,7 +40,7 @@ #include #include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "yajl/api/yajl_parse.h" #include "yajl/api/yajl_tree.h" diff --git a/src/yajlpp/yajlpp.cc b/src/yajlpp/yajlpp.cc index eb60b880..9a71c328 100644 --- a/src/yajlpp/yajlpp.cc +++ b/src/yajlpp/yajlpp.cc @@ -353,8 +353,9 @@ json_path_handler_base::walk( if (this->jph_children) { full_path += "/"; } - json_path_container dummy - = {json_path_handler(this->jph_property)}; + json_path_container dummy{ + json_path_handler(this->jph_property), + }; dummy.jpc_children[0].jph_callbacks = this->jph_callbacks; yajlpp_parse_context ypc("possibilities", &dummy); @@ -683,74 +684,51 @@ yajlpp_parse_context::handle_unused(void* ctx) } const json_path_handler_base* handler = ypc->ypc_current_handler; - - int line_number = ypc->get_line_number(); + lnav::console::user_message msg; if (handler != nullptr && strlen(handler->jph_synopsis) > 0 && strlen(handler->jph_description) > 0) { - ypc->report_error(lnav_log_level_t::WARNING, - "%s:line %d", - ypc->ypc_source.c_str(), - line_number); - ypc->report_error(lnav_log_level_t::WARNING, - " unexpected data for path"); - - ypc->report_error(lnav_log_level_t::WARNING, - " %s %s -- %s", - &ypc->ypc_path[0], - handler->jph_synopsis, - handler->jph_description); - } else if (ypc->ypc_path[1]) { - ypc->report_error(lnav_log_level_t::WARNING, - "%s:line %d", - ypc->ypc_source.c_str(), - line_number); - ypc->report_error(lnav_log_level_t::WARNING, " unexpected path --"); - - ypc->report_error( - lnav_log_level_t::WARNING, " %s", &ypc->ypc_path[0]); - } else { - ypc->report_error(lnav_log_level_t::WARNING, - "%s:line %d\n unexpected JSON value", - ypc->ypc_source.c_str(), - line_number); - } + auto help_text = handler->get_help_text(ypc); + std::vector expected_types; - if (ypc->ypc_callbacks.yajl_boolean - != (int (*)(void*, int)) yajlpp_parse_context::handle_unused - || ypc->ypc_callbacks.yajl_integer - != (int (*)(void*, long long)) yajlpp_parse_context::handle_unused - || ypc->ypc_callbacks.yajl_double - != (int (*)(void*, double)) yajlpp_parse_context::handle_unused - || ypc->ypc_callbacks.yajl_string + if (ypc->ypc_callbacks.yajl_boolean + != (int (*)(void*, int)) yajlpp_parse_context::handle_unused) + { + expected_types.emplace_back("boolean"); + } + if (ypc->ypc_callbacks.yajl_integer + != (int (*)(void*, long long)) yajlpp_parse_context::handle_unused) + { + expected_types.emplace_back("integer"); + } + if (ypc->ypc_callbacks.yajl_double + != (int (*)(void*, double)) yajlpp_parse_context::handle_unused) + { + expected_types.emplace_back("float"); + } + if (ypc->ypc_callbacks.yajl_string != (int (*)(void*, const unsigned char*, size_t)) yajlpp_parse_context::handle_unused) - { - ypc->report_error(lnav_log_level_t::WARNING, - " expecting one of the following data types --"); - } - - if (ypc->ypc_callbacks.yajl_boolean - != (int (*)(void*, int)) yajlpp_parse_context::handle_unused) - { - ypc->report_error(lnav_log_level_t::WARNING, " boolean"); - } - if (ypc->ypc_callbacks.yajl_integer - != (int (*)(void*, long long)) yajlpp_parse_context::handle_unused) - { - ypc->report_error(lnav_log_level_t::WARNING, " integer"); - } - if (ypc->ypc_callbacks.yajl_double - != (int (*)(void*, double)) yajlpp_parse_context::handle_unused) - { - ypc->report_error(lnav_log_level_t::WARNING, " float"); - } - if (ypc->ypc_callbacks.yajl_string - != (int (*)(void*, const unsigned char*, size_t)) - yajlpp_parse_context::handle_unused) - { - ypc->report_error(lnav_log_level_t::WARNING, " string"); + { + expected_types.emplace_back("string"); + } + if (!expected_types.empty()) { + help_text.append(" expecting one of the following types: {}", + fmt::join(expected_types, ", ")); + } + msg = lnav::console::user_message::warning( + attr_line_t("unexpected data for property ") + .append_quoted(lnav::roles::symbol( + ypc->get_full_path().to_string()))) + .with_help(help_text); + } else if (ypc->ypc_path[1]) { + msg = lnav::console::user_message::warning( + attr_line_t("unexpected value for property ") + .append_quoted( + lnav::roles::symbol(ypc->get_full_path().to_string()))); + } else { + msg = lnav::console::user_message::error("unexpected JSON value"); } if (handler == nullptr) { @@ -762,16 +740,36 @@ yajlpp_parse_context::handle_unused(void* ctx) accepted_handlers = ypc->ypc_handlers; } - ypc->report_error(lnav_log_level_t::WARNING, " accepted paths --"); - for (auto& jph : accepted_handlers->jpc_children) { - ypc->report_error(lnav_log_level_t::WARNING, - " %s %s -- %s", - jph.jph_property.c_str(), - jph.jph_synopsis, - jph.jph_description); + attr_line_t help_text; + + if (accepted_handlers->jpc_children.size() == 1 + && accepted_handlers->jpc_children.front().jph_is_array) + { + const auto& jph = accepted_handlers->jpc_children.front(); + + help_text.append("expecting an array of ") + .append(lnav::roles::variable(jph.jph_synopsis)) + .append(" values"); + } else { + help_text.append(lnav::roles::h2("Available Properties")) + .append("\n"); + for (const auto& jph : accepted_handlers->jpc_children) { + help_text.append(" ") + .append(lnav::roles::symbol(jph.jph_property)) + .append(lnav::roles::symbol( + jph.jph_children != nullptr ? "/" : "")) + .append(" ") + .append(lnav::roles::variable(jph.jph_synopsis)) + .append("\n"); + } } + msg.with_help(help_text); } + msg.with_snippet(ypc->get_snippet()); + + ypc->report_error(msg); + return 1; } @@ -794,6 +792,7 @@ yajl_status yajlpp_parse_context::parse(const unsigned char* jsonText, size_t jsonTextLen) { this->ypc_json_text = jsonText; + this->ypc_json_text_len = jsonTextLen; yajl_status retval = yajl_parse(this->ypc_handle, jsonText, jsonTextLen); @@ -807,14 +806,11 @@ yajlpp_parse_context::parse(const unsigned char* jsonText, size_t jsonTextLen) if (retval != yajl_status_ok && this->ypc_error_reporter) { auto* msg = yajl_get_error(this->ypc_handle, 1, jsonText, jsonTextLen); - this->ypc_error_reporter( - *this, - lnav_log_level_t::ERROR, - fmt::format(FMT_STRING("error:{}:{}:invalid json -- {}"), - this->ypc_source, - this->get_line_number(), - reinterpret_cast(msg)) - .c_str()); + this->report_error( + lnav::console::user_message::error("invalid JSON") + .with_snippet(lnav::console::snippet::from(this->ypc_source, + (const char*) msg) + .with_line(this->get_line_number()))); yajl_free_error(this->ypc_handle, msg); } @@ -829,19 +825,51 @@ yajlpp_parse_context::complete_parse() if (retval != yajl_status_ok && this->ypc_error_reporter) { auto* msg = yajl_get_error(this->ypc_handle, 0, nullptr, 0); - this->ypc_error_reporter( - *this, - lnav_log_level_t::ERROR, - fmt::format(FMT_STRING("error:{}:invalid json -- {}"), - this->ypc_source, - reinterpret_cast(msg)) - .c_str()); + this->report_error(lnav::console::user_message::error("invalid JSON") + .with_reason((const char*) msg) + .with_snippet(this->get_snippet())); yajl_free_error(this->ypc_handle, msg); } return retval; } +bool +yajlpp_parse_context::parse_doc(const string_fragment& sf) +{ + bool retval = true; + + this->ypc_json_text = (const unsigned char*) sf.data(); + this->ypc_json_text_len = sf.length(); + + auto rc = yajl_parse(this->ypc_handle, this->ypc_json_text, sf.length()); + size_t consumed = yajl_get_bytes_consumed(this->ypc_handle); + this->ypc_line_number += std::count( + &this->ypc_json_text[0], &this->ypc_json_text[consumed], '\n'); + + if (rc != yajl_status_ok) { + if (this->ypc_error_reporter) { + auto* msg = yajl_get_error(this->ypc_handle, + 1, + this->ypc_json_text, + this->ypc_json_text_len); + + this->report_error( + lnav::console::user_message::error("invalid JSON") + .with_reason((const char*) msg) + .with_snippet(this->get_snippet())); + yajl_free_error(this->ypc_handle, msg); + } + retval = false; + } else if (this->complete_parse() != yajl_status_ok) { + retval = false; + } + + this->ypc_json_text = nullptr; + + return retval; +} + const intern_string_t yajlpp_parse_context::get_path() const { @@ -1039,6 +1067,180 @@ json_path_handler::with_children(const json_path_container& container) return *this; } +lnav::console::snippet +yajlpp_parse_context::get_snippet() const +{ + auto line_number = this->get_line_number(); + attr_line_t content; + + if (this->ypc_json_text != nullptr) { + auto in_text_line = line_number - this->ypc_line_number; + const auto* line_start = this->ypc_json_text; + auto text_len_remaining = this->ypc_json_text_len; + + while (in_text_line > 0) { + const auto* line_end = (const unsigned char*) memchr( + line_start, '\n', text_len_remaining); + if (line_end == nullptr) { + break; + } + + text_len_remaining -= (line_end - line_start) + 1; + line_start = line_end + 1; + in_text_line -= 1; + } + + if (text_len_remaining > 0) { + const auto* line_end = (const unsigned char*) memchr( + line_start, '\n', text_len_remaining); + if (line_end) { + text_len_remaining = (line_end - line_start); + } + content.append((const char*) line_start, text_len_remaining); + } + } + + return lnav::console::snippet::from(this->ypc_source, content) + .with_line(line_number); +} + +void +json_path_handler_base::report_pattern_error(yajlpp_parse_context* ypc, + const std::string& value_str) const +{ + ypc->report_error( + lnav::console::user_message::error( + attr_line_t() + .append_quoted(value_str) + .append(" is not a valid value for option ") + .append_quoted( + lnav::roles::symbol(ypc->get_full_path().to_string()))) + .with_snippet(ypc->get_snippet()) + .with_reason(attr_line_t("value does not match pattern: ") + .append(lnav::roles::symbol(this->jph_pattern_re))) + .with_help(this->get_help_text(ypc))); +} + +attr_line_t +json_path_handler_base::get_help_text(yajlpp_parse_context* ypc) const +{ + attr_line_t retval; + + retval.append(lnav::roles::h2("Property Synopsis")) + .append("\n ") + .append(lnav::roles::symbol(ypc->get_full_path().to_string())) + .append(" ") + .append(lnav::roles::variable(this->jph_synopsis)) + .append("\n") + .append(lnav::roles::h2("Description")) + .append("\n ") + .append(this->jph_description) + .append("\n"); + + if (this->jph_enum_values != nullptr) { + retval.append(lnav::roles::h2("Allowed Values")).append("\n "); + + for (int lpc = 0; this->jph_enum_values[lpc].first; lpc++) { + const auto& ev = this->jph_enum_values[lpc]; + + retval.append(lpc == 0 ? "" : ", ") + .append(lnav::roles::symbol(ev.first)); + } + } + + if (!this->jph_examples.empty()) { + retval + .append(lnav::roles::h2( + this->jph_examples.size() == 1 ? "Example" : "Examples")) + .append("\n"); + + for (const auto& ex : this->jph_examples) { + retval.append(" {}\n", ex); + } + } + + return retval; +} + +void +json_path_handler_base::report_min_value_error(yajlpp_parse_context* ypc, + long long value) const +{ + const auto* jph = ypc->ypc_current_handler; + + ypc->report_error( + lnav::console::user_message::error( + attr_line_t() + .append_quoted(fmt::to_string(value)) + .append(" is not a valid value for option ") + .append_quoted( + lnav::roles::symbol(ypc->get_full_path().to_string()))) + .with_reason(attr_line_t("value must be greater than or equal to ") + .append(lnav::roles::number( + fmt::to_string(jph->jph_min_value)))) + .with_snippet(ypc->get_snippet()) + .with_help(jph->get_help_text(ypc))); +} + +void +json_path_handler_base::report_duration_error( + yajlpp_parse_context* ypc, + const std::string& value_str, + const relative_time::parse_error& pe) const +{ + ypc->report_error(lnav::console::user_message::error( + attr_line_t() + .append_quoted(value_str) + .append(" is not a valid duration value " + "for option ") + .append_quoted(lnav::roles::symbol( + ypc->get_full_path().to_string()))) + .with_snippet(ypc->get_snippet()) + .with_reason(pe.pe_msg) + .with_help(this->get_help_text(ypc))); +} + +void +json_path_handler_base::report_enum_error(yajlpp_parse_context* ypc, + const std::string& value_str) const +{ + ypc->report_error(lnav::console::user_message::error( + attr_line_t() + .append_quoted(value_str) + .append(" is not a valid value for option ") + .append_quoted(lnav::roles::symbol( + ypc->get_full_path().to_string()))) + .with_snippet(ypc->get_snippet()) + .with_help(this->get_help_text(ypc))); +} + +void +json_path_handler_base::report_regex_value_error( + yajlpp_parse_context* ypc, + const std::string& value, + const pcrepp::compile_error& pcre_error) const +{ + attr_line_t pcre_error_content{value}; + + pcre_error_content.append("\n") + .append(pcre_error.ce_offset, ' ') + .append(lnav::roles::error("^ ")) + .append(lnav::roles::error(pcre_error.ce_msg)); + ypc->report_error( + lnav::console::user_message::error( + attr_line_t() + .append_quoted(value) + .append(" is not a valid regular expression for " + "property ") + .append_quoted( + lnav::roles::symbol(ypc->get_full_path().to_string()))) + .with_reason(pcre_error.ce_msg) + .with_snippet(ypc->get_snippet()) + .with_snippet(lnav::console::snippet::from( + ypc->get_full_path().to_string(), pcre_error_content)) + .with_help(this->get_help_text(ypc))); +} + void json_path_container::gen_schema(yajlpp_gen_context& ygc) const { diff --git a/src/yajlpp/yajlpp.hh b/src/yajlpp/yajlpp.hh index dbdf0abd..8558cd04 100644 --- a/src/yajlpp/yajlpp.hh +++ b/src/yajlpp/yajlpp.hh @@ -47,10 +47,12 @@ #include "base/file_range.hh" #include "base/intern_string.hh" +#include "base/lnav.console.hh" #include "base/lnav_log.hh" #include "json_ptr.hh" #include "optional.hpp" #include "pcrepp/pcrepp.hh" +#include "relative_time.hh" #include "yajl/api/yajl_gen.h" #include "yajl/api/yajl_parse.h" @@ -70,6 +72,20 @@ yajl_gen_string(yajl_gen hand, const std::string& str) hand, (const unsigned char*) str.c_str(), str.length()); } +template +struct positioned_property { + intern_string_t pp_path; + std::string pp_src; + int32_t pp_line_number{0}; + T pp_value; + + lnav::console::snippet to_snippet() const + { + return lnav::console::snippet::from(this->pp_src, "") + .with_line(this->pp_line_number); + } +}; + class yajlpp_gen_context; class yajlpp_parse_context; @@ -209,15 +225,29 @@ struct json_path_handler_base { std::function jph_str_cb; + + void report_pattern_error(yajlpp_parse_context* ypc, + const std::string& value_str) const; + void report_min_value_error(yajlpp_parse_context* ypc, + long long value) const; + void report_duration_error(yajlpp_parse_context* ypc, + const std::string& value_str, + const relative_time::parse_error& pe) const; + void report_enum_error(yajlpp_parse_context* ypc, + const std::string& value_str) const; + void report_regex_value_error(yajlpp_parse_context* ypc, + const std::string& value_str, + const pcrepp::compile_error& ce) const; + + attr_line_t get_help_text(yajlpp_parse_context* ypc) const; }; struct json_path_handler; class yajlpp_parse_context { public: - using error_reporter_t = std::function; + using error_reporter_t = std::function; yajlpp_parse_context(std::string source, const struct json_path_container* handlers = nullptr); @@ -297,21 +327,17 @@ public: yajl_status complete_parse(); - void report_error(lnav_log_level_t level, const char* format, ...) const - { - va_list args; + bool parse_doc(const string_fragment& sf); - va_start(args, format); + void report_error(const lnav::console::user_message& msg) const + { if (this->ypc_error_reporter) { - char buffer[1024]; - - vsnprintf(buffer, sizeof(buffer), format, args); - - this->ypc_error_reporter(*this, level, buffer); + this->ypc_error_reporter(*this, msg); } - va_end(args); } + lnav::console::snippet get_snippet() const; + template std::vector& get_lvalue(std::map>& value) { @@ -369,6 +395,7 @@ public: void* ypc_userdata{nullptr}; yajl_handle ypc_handle{nullptr}; const unsigned char* ypc_json_text{nullptr}; + size_t ypc_json_text_len{0}; yajl_callbacks ypc_callbacks; yajl_callbacks ypc_alt_callbacks; std::vector ypc_path; diff --git a/src/yajlpp/yajlpp_def.hh b/src/yajlpp/yajlpp_def.hh index 7cd482c2..31b85490 100644 --- a/src/yajlpp/yajlpp_def.hh +++ b/src/yajlpp/yajlpp_def.hh @@ -34,7 +34,9 @@ #include +#include "config.h" #include "relative_time.hh" +#include "view_curses.hh" #include "yajlpp.hh" #define FOR_FIELD(T, FIELD) for_field() @@ -182,13 +184,6 @@ struct json_path_handler : public json_path_handler_base { return *this; }; - json_path_handler& with_string_validator( - std::function val) - { - this->jph_string_validator = val; - return *this; - }; - json_path_handler& with_min_value(long long val) { this->jph_min_value = val; @@ -260,27 +255,8 @@ struct json_path_handler : public json_path_handler_base { if (res) { obj->*ENUM = (ENUM_T) res.value(); } else { - ypc->report_error(lnav_log_level_t::ERROR, - "error:%s:line %d\n " - "Invalid value, '%.*s', for option:", - ypc->ypc_source.c_str(), - ypc->get_line_number(), - len, - str); - - ypc->report_error(lnav_log_level_t::ERROR, - " %s %s -- %s\n", - &ypc->ypc_path[0], - handler->jph_synopsis, - handler->jph_description); - ypc->report_error(lnav_log_level_t::ERROR, " Allowed values: "); - for (int lpc = 0; handler->jph_enum_values[lpc].first; lpc++) { - const json_path_handler::enum_value_t& ev - = handler->jph_enum_values[lpc]; - - ypc->report_error( - lnav_log_level_t::ERROR, " %s\n", ev.first); - } + handler->report_enum_error(ypc, + std::string((const char*) str, len)); } return 1; @@ -330,32 +306,39 @@ struct json_path_handler : public json_path_handler_base { auto& field_ptr = ypc.get_rvalue(ypc.get_obj_member()); if (jph.jph_pattern) { - string_fragment sf = to_string_fragment(field_ptr); + auto sf = to_string_fragment(field_ptr); pcre_input pi(sf); pcre_context_static<30> pc; if (!jph.jph_pattern->match(pc, pi)) { - ypc.report_error(lnav_log_level_t::ERROR, - "Value does not match pattern: %s", - jph.jph_pattern_re); - } - } - if (jph.jph_string_validator) { - try { - jph.jph_string_validator(to_string_fragment(field_ptr)); - } catch (const std::exception& e) { - ypc.report_error(lnav_log_level_t::ERROR, "%s", e.what()); + jph.report_pattern_error(&ypc, sf.to_string()); } } if (field_ptr.empty() && jph.jph_min_length > 0) { - ypc.report_error(lnav_log_level_t::ERROR, - "value must not be empty"); + ypc.report_error( + lnav::console::user_message::error( + attr_line_t("invalid value for option ") + .template append_quoted(lnav::roles::symbol( + ypc.get_full_path().to_string()))) + .with_reason("empty values are not allowed") + .with_snippet(ypc.get_snippet()) + .with_help(jph.get_help_text(&ypc))); } else if (field_ptr.size() < jph.jph_min_length) { - ypc.report_error(lnav_log_level_t::ERROR, - "value must be at least %lu characters long", - jph.jph_min_length); + ypc.report_error( + lnav::console::user_message::error( + attr_line_t() + .template append_quoted(field_ptr) + .append(" is not a valid value for option ") + .append_quoted(lnav::roles::symbol( + ypc.get_full_path().to_string()))) + .with_reason(attr_line_t("value must be at least ") + .append(lnav::roles::number( + fmt::to_string(jph.jph_min_length))) + .append(" characters long")) + .with_snippet(ypc.get_snippet()) + .with_help(jph.get_help_text(&ypc))); } - }; + } template static void number_field_validator(yajlpp_parse_context& ypc, @@ -364,9 +347,7 @@ struct json_path_handler : public json_path_handler_base { auto& field_ptr = ypc.get_rvalue(ypc.get_obj_member()); if (field_ptr < jph.jph_min_value) { - ypc.report_error(lnav_log_level_t::ERROR, - "value must be greater than %lld", - jph.jph_min_value); + jph.report_min_value_error(&ypc, field_ptr); } } @@ -589,6 +570,11 @@ struct json_path_handler : public json_path_handler_base { return gen(field); }; + this->jph_field_getter + = [args...](void* root, nonstd::optional name) { + return (void*) &json_path_handler::get_field(root, args...); + }; + return *this; } @@ -648,9 +634,134 @@ struct json_path_handler : public json_path_handler_base { const unsigned char* str, size_t len) { auto obj = ypc->ypc_obj_stack.top(); + auto value_str = std::string((const char*) str, len); + auto jph = ypc->ypc_current_handler; + + if (jph->jph_pattern) { + pcre_input pi(value_str); + pcre_context_static<30> pc; + + if (!jph->jph_pattern->match(pc, pi)) { + jph->report_pattern_error(ypc, value_str); + } + } + + json_path_handler::get_field(obj, args...) = std::move(value_str); + + return 1; + }; + this->jph_gen_callback = [args...](yajlpp_gen_context& ygc, + const json_path_handler_base& jph, + yajl_gen handle) { + const auto& field = json_path_handler::get_field( + ygc.ygc_obj_stack.top(), args...); + + if (!ygc.ygc_default_stack.empty()) { + const auto& field_def = json_path_handler::get_field( + ygc.ygc_default_stack.top(), args...); + + if (field == field_def) { + return yajl_gen_status_ok; + } + } + + if (ygc.ygc_depth) { + yajl_gen_string(handle, jph.jph_property); + } + + yajlpp_generator gen(handle); + + return gen(field); + }; + this->jph_field_getter + = [args...](void* root, nonstd::optional name) { + return (void*) &json_path_handler::get_field(root, args...); + }; + return *this; + } + + template, Args...>::value, + bool> = true> + json_path_handler& for_field(Args... args) + { + this->add_cb(str_field_cb2); + this->jph_str_cb = [args...](yajlpp_parse_context* ypc, + const unsigned char* str, + size_t len) { + auto obj = ypc->ypc_obj_stack.top(); + auto value_str = std::string((const char*) str, len); + auto jph = ypc->ypc_current_handler; + + if (jph->jph_pattern) { + pcre_input pi(value_str); + pcre_context_static<30> pc; + + if (!jph->jph_pattern->match(pc, pi)) { + jph->report_pattern_error(ypc, value_str); + } + } + + auto& field = json_path_handler::get_field(obj, args...); + + field.pp_path = ypc->get_full_path(); + field.pp_src = ypc->ypc_source; + field.pp_line_number = ypc->get_line_number(); + field.pp_value = std::move(value_str); + + return 1; + }; + this->jph_gen_callback = [args...](yajlpp_gen_context& ygc, + const json_path_handler_base& jph, + yajl_gen handle) { + const auto& field = json_path_handler::get_field( + ygc.ygc_obj_stack.top(), args...); + + if (!ygc.ygc_default_stack.empty()) { + const auto& field_def = json_path_handler::get_field( + ygc.ygc_default_stack.top(), args...); + + if (field.pp_value == field_def.pp_value) { + return yajl_gen_status_ok; + } + } + + if (ygc.ygc_depth) { + yajl_gen_string(handle, jph.jph_property); + } + + yajlpp_generator gen(handle); + + return gen(field.pp_value); + }; + return *this; + } + + template< + typename... Args, + std::enable_if_t::value, bool> = true> + json_path_handler& for_field(Args... args) + { + this->add_cb(str_field_cb2); + this->jph_str_cb = [args...](yajlpp_parse_context* ypc, + const unsigned char* str, + size_t len) { + auto obj = ypc->ypc_obj_stack.top(); + auto value_str = std::string((const char*) str, len); + auto jph = ypc->ypc_current_handler; + + if (jph->jph_pattern) { + pcre_input pi(value_str); + pcre_context_static<30> pc; + + if (!jph->jph_pattern->match(pc, pi)) { + jph->report_pattern_error(ypc, value_str); + } + } json_path_handler::get_field(obj, args...) - = std::string((const char*) str, len); + = intern_string::lookup(value_str); return 1; }; @@ -681,27 +792,133 @@ struct json_path_handler : public json_path_handler_base { } template::value, bool> = true> + std::enable_if_t< + LastIs, Args...>::value, + bool> = true> json_path_handler& for_field(Args... args) { - this->add_cb(int_field_cb); - this->jph_integer_cb = [args...](yajlpp_parse_context* ypc, - long long val) { + this->add_cb(str_field_cb2); + this->jph_str_cb = [args...](yajlpp_parse_context* ypc, + const unsigned char* str, + size_t len) { auto obj = ypc->ypc_obj_stack.top(); + auto value_str = std::string((const char*) str, len); + auto jph = ypc->ypc_current_handler; - if (val < ypc->ypc_current_handler->jph_min_value) { - ypc->report_error( - lnav_log_level_t::ERROR, - "value must be greater than or equal to %lld, found %lld", - ypc->ypc_current_handler->jph_min_value, - val); - return 1; + if (jph->jph_pattern) { + pcre_input pi(value_str); + pcre_context_static<30> pc; + + if (!jph->jph_pattern->match(pc, pi)) { + jph->report_pattern_error(ypc, value_str); + } } - json_path_handler::get_field(obj, args...) = val; + auto& field = json_path_handler::get_field(obj, args...); + field.pp_path = ypc->get_full_path(); + field.pp_src = ypc->ypc_source; + field.pp_line_number = ypc->get_line_number(); + field.pp_value = intern_string::lookup(value_str); return 1; }; + this->jph_gen_callback = [args...](yajlpp_gen_context& ygc, + const json_path_handler_base& jph, + yajl_gen handle) { + const auto& field = json_path_handler::get_field( + ygc.ygc_obj_stack.top(), args...); + + if (!ygc.ygc_default_stack.empty()) { + const auto& field_def = json_path_handler::get_field( + ygc.ygc_default_stack.top(), args...); + + if (field.pp_value == field_def.pp_value) { + return yajl_gen_status_ok; + } + } + + if (ygc.ygc_depth) { + yajl_gen_string(handle, jph.jph_property); + } + + yajlpp_generator gen(handle); + + return gen(field.pp_value); + }; + return *this; + } + + template, Args...>::value, + bool> = true> + json_path_handler& for_field(Args... args) + { + this->add_cb(str_field_cb2); + this->jph_str_cb = [args...](yajlpp_parse_context* ypc, + const unsigned char* str, + size_t len) { + auto obj = ypc->ypc_obj_stack.top(); + auto value_str = std::string((const char*) str, len); + auto jph = ypc->ypc_current_handler; + + try { + auto re = std::make_unique(value_str); + json_path_handler::get_field(obj, args...) = std::move(re); + } catch (const pcrepp::error& e) { + pcrepp::compile_error ce; + + ce.ce_msg = e.what(); + ce.ce_offset = e.e_offset; + jph->report_regex_value_error(ypc, value_str, ce); + } + + return 1; + }; + this->jph_gen_callback = [args...](yajlpp_gen_context& ygc, + const json_path_handler_base& jph, + yajl_gen handle) { + const auto& field = json_path_handler::get_field( + ygc.ygc_obj_stack.top(), args...); + + if (!ygc.ygc_default_stack.empty()) { + const auto& field_def = json_path_handler::get_field( + ygc.ygc_default_stack.top(), args...); + + if (field == field_def) { + return yajl_gen_status_ok; + } + } + + if (ygc.ygc_depth) { + yajl_gen_string(handle, jph.jph_property); + } + + yajlpp_generator gen(handle); + + return gen(field->get_pattern()); + }; + return *this; + } + + template::value, bool> = true> + json_path_handler& for_field(Args... args) + { + this->add_cb(int_field_cb); + this->jph_integer_cb + = [args...](yajlpp_parse_context* ypc, long long val) { + auto jph = ypc->ypc_current_handler; + auto* obj = ypc->ypc_obj_stack.top(); + + if (val < jph->jph_min_value) { + jph->report_min_value_error(ypc, val); + return 1; + } + + json_path_handler::get_field(obj, args...) = val; + + return 1; + }; this->jph_gen_callback = [args...](yajlpp_gen_context& ygc, const json_path_handler_base& jph, yajl_gen handle) { @@ -725,6 +942,10 @@ struct json_path_handler : public json_path_handler_base { return gen(field); }; + this->jph_field_getter + = [args...](void* root, nonstd::optional name) { + return (void*) &json_path_handler::get_field(root, args...); + }; return *this; } @@ -743,19 +964,10 @@ struct json_path_handler : public json_path_handler_base { auto parse_res = relative_time::from_str((const char*) str, len); if (parse_res.isErr()) { - ypc->report_error(lnav_log_level_t::ERROR, - "error:%s:line %d\n" - " Invalid duration: '%.*s' -- %s", - ypc->ypc_source.c_str(), - ypc->get_line_number(), - len, - str, - parse_res.unwrapErr().pe_msg.c_str()); - ypc->report_error(lnav_log_level_t::ERROR, - " for option: %s %s -- %s\n", - &ypc->ypc_path[0], - handler->jph_synopsis, - handler->jph_description); + auto parse_error = parse_res.unwrapErr(); + auto value_str = std::string((const char*) str, len); + + handler->report_duration_error(ypc, value_str, parse_error); return 1; } @@ -813,32 +1025,16 @@ struct json_path_handler : public json_path_handler_base { = (decltype(json_path_handler::get_field( obj, args...))) res.value(); } else { - ypc->report_error(lnav_log_level_t::ERROR, - "error:%s:line %d\n " - "Invalid value, '%.*s', for option:", - ypc->ypc_source.c_str(), - ypc->get_line_number(), - len, - str); - - ypc->report_error(lnav_log_level_t::ERROR, - " %s %s -- %s\n", - &ypc->ypc_path[0], - handler->jph_synopsis, - handler->jph_description); - ypc->report_error(lnav_log_level_t::ERROR, - " Allowed values: "); - for (int lpc = 0; handler->jph_enum_values[lpc].first; lpc++) { - const json_path_handler::enum_value_t& ev - = handler->jph_enum_values[lpc]; - - ypc->report_error( - lnav_log_level_t::ERROR, " %s\n", ev.first); - } + handler->report_enum_error(ypc, + std::string((const char*) str, len)); } return 1; }; + this->jph_field_getter + = [args...](void* root, nonstd::optional name) { + return (void*) &json_path_handler::get_field(root, args...); + }; return *this; }; diff --git a/test/bad-config-json/formats/invalid-key/format.json b/test/bad-config-json/formats/invalid-key/format.json index 463ab0c0..11a3bdd6 100644 --- a/test/bad-config-json/formats/invalid-key/format.json +++ b/test/bad-config-json/formats/invalid-key/format.json @@ -1,9 +1,27 @@ { - "invalid_key_log" : { - "value" : { + "$schema": "https://lnav.org/schemas/format-v1.schema.json", + "invalid_key_log": { + "level-pointer": "abc(", + "file-pattern": "def[ghi", + "json": true, + "regex": { + "foo": { + "pattern": "jkl" + } + }, + "value": { "test": { - "identifiers" : true + "identifiers": true + }, + "body": { + "kind": "string" + } + }, + "line-format": [ + { + "field": "non-existent" } - } + ], + "timestamp-divisor": -1.2 } } \ No newline at end of file diff --git a/test/bad-config/formats/invalid-properties/format.json b/test/bad-config/formats/invalid-properties/format.json new file mode 100644 index 00000000..db547bcd --- /dev/null +++ b/test/bad-config/formats/invalid-properties/format.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://lnav.org/schemas/format-v1.schema.json", + "invalid_props_log": { + "title": "invalid properties", + "regex": { + "std": { + "pattern": "^(?\\d+): (?\\w+) (?.*)$" + } + }, + "timestamp-field": "ts", + "sample": [ + { + "line": "1428634687123: 1234 abc" + } + ], + "highlights": { + "hl1": { + "color": "not a color", + "background-color": "also not a color" + } + }, + "search-table": { + "bad_table_regex": { + "pattern": "abc(def" + } + } + } +} \ No newline at end of file diff --git a/test/bad-config/formats/invalid-sample/format.json b/test/bad-config/formats/invalid-sample/format.json index de473fb3..a08bc1a8 100644 --- a/test/bad-config/formats/invalid-sample/format.json +++ b/test/bad-config/formats/invalid-sample/format.json @@ -8,9 +8,15 @@ }, "semi": { "pattern": "^(?\\d+); (?\\w+)$" + }, + "bad-time": { + "pattern": "^(?\\w+): (?\\w+)$" + }, + "with-level": { + "pattern": "^(?\\d+)\\| (?\\w+) (?\\w+)$" } }, - "timestamp-format" : [ + "timestamp-format": [ "%i" ], "value": { @@ -18,7 +24,15 @@ "kind": "foo" } }, + "level-field": "level", "sample": [ + { + "line": "abc: foo" + }, + { + "line": "1428634687123| debug hello", + "level": "info" + }, { "line": "1428634687123; foo bar" } diff --git a/test/bad-config/formats/invalid-schema/format.json b/test/bad-config/formats/invalid-schema/format.json new file mode 100644 index 00000000..b0111649 --- /dev/null +++ b/test/bad-config/formats/invalid-schema/format.json @@ -0,0 +1,3 @@ +{ + "$schema": "bad" +} \ No newline at end of file diff --git a/test/bad-config/formats/invalid-sql/init.sql b/test/bad-config/formats/invalid-sql/init.sql index e29dc75f..341e675b 100644 --- a/test/bad-config/formats/invalid-sql/init.sql +++ b/test/bad-config/formats/invalid-sql/init.sql @@ -1,3 +1,5 @@ +SELECT * FROM sqlite_master; + -- comment test CREATE TALE invalid (x y z); diff --git a/test/bad-config/formats/invalid-sql/init2.sql b/test/bad-config/formats/invalid-sql/init2.sql new file mode 100644 index 00000000..9810ea01 --- /dev/null +++ b/test/bad-config/formats/invalid-sql/init2.sql @@ -0,0 +1,2 @@ +SELECT regexp_match('abc(', '123') +FROM sqlite_master; diff --git a/test/bad-config/formats/no-regexes/format.json b/test/bad-config/formats/no-regexes/format.json new file mode 100644 index 00000000..f20cde16 --- /dev/null +++ b/test/bad-config/formats/no-regexes/format.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://lnav.org/schemas/format-v1.schema.json", + "no_regexes_log": { + "value": { + } + } +} \ No newline at end of file diff --git a/test/bad-config/formats/no-samples/format.json b/test/bad-config/formats/no-samples/format.json index fd4165a0..5c062943 100644 --- a/test/bad-config/formats/no-samples/format.json +++ b/test/bad-config/formats/no-samples/format.json @@ -1,4 +1,5 @@ { + "$schema": "https://lnav.org/schemas/format-v1.schema.json", "no_sample_log": { "title": "invalid sample test", "regex": { @@ -9,7 +10,7 @@ "pattern": "^(?\\d+); (?\\w+)$" } }, - "timestamp-format" : [ + "timestamp-format": [ "%i" ] } diff --git a/test/bad-config2/formats/invalid-config/config.bad-schema.json b/test/bad-config2/formats/invalid-config/config.bad-schema.json new file mode 100644 index 00000000..b0111649 --- /dev/null +++ b/test/bad-config2/formats/invalid-config/config.bad-schema.json @@ -0,0 +1,3 @@ +{ + "$schema": "bad" +} \ No newline at end of file diff --git a/test/drive_data_scanner.cc b/test/drive_data_scanner.cc index 1de28d74..2ee202ac 100644 --- a/test/drive_data_scanner.cc +++ b/test/drive_data_scanner.cc @@ -69,7 +69,7 @@ main(int argc, char* argv[]) { std::vector paths; - std::vector errors; + std::vector errors; load_formats(paths, errors); } diff --git a/test/drive_logfile.cc b/test/drive_logfile.cc index da105786..d93e198b 100644 --- a/test/drive_logfile.cc +++ b/test/drive_logfile.cc @@ -79,7 +79,7 @@ main(int argc, char* argv[]) } { - std::vector errors; + std::vector errors; vector paths; getenv_opt("test_dir") | diff --git a/test/drive_mvwattrline.cc b/test/drive_mvwattrline.cc index e60912f8..7ceb6036 100644 --- a/test/drive_mvwattrline.cc +++ b/test/drive_mvwattrline.cc @@ -69,44 +69,44 @@ main(int argc, char* argv[]) al.clear() .with_string("\tLeading tab") .with_attr(string_attr(line_range(0, 1), - view_curses::VC_STYLE.value(A_REVERSE))); + VC_STYLE.value(A_REVERSE))); view_curses::mvwattrline(win, y++, 0, al, lr); al.clear() .with_string("Tab\twith text") .with_attr(string_attr(line_range(1, 4), - view_curses::VC_STYLE.value(A_REVERSE))); + VC_STYLE.value(A_REVERSE))); view_curses::mvwattrline(win, y++, 0, al, lr); al.clear() .with_string("Tab\twith text #2") .with_attr(string_attr(line_range(3, 4), - view_curses::VC_STYLE.value(A_REVERSE))); + VC_STYLE.value(A_REVERSE))); view_curses::mvwattrline(win, y++, 0, al, lr); al.clear() .with_string("Two\ttabs\twith text") .with_attr(string_attr(line_range(4, 6), - view_curses::VC_STYLE.value(A_REVERSE))) + VC_STYLE.value(A_REVERSE))) .with_attr(string_attr(line_range(9, 13), - view_curses::VC_STYLE.value(A_REVERSE))); + VC_STYLE.value(A_REVERSE))); view_curses::mvwattrline(win, y++, 0, al, lr); al.clear() .with_string("Text with mixed attributes.") .with_attr(string_attr( line_range(5, 9), - view_curses::VC_STYLE.value( + VC_STYLE.value( view_colors::ansi_color_pair(COLOR_RED, COLOR_BLACK)))) .with_attr(string_attr(line_range(7, 12), - view_curses::VC_STYLE.value(A_REVERSE))); + VC_STYLE.value(A_REVERSE))); view_curses::mvwattrline(win, y++, 0, al, lr); const char* text = u8"Text with unicode ▶ characters"; int offset = strstr(text, "char") - text; al.clear().with_string(text).with_attr( string_attr(line_range(offset, offset + 4), - view_curses::VC_STYLE.value(A_REVERSE))); + VC_STYLE.value(A_REVERSE))); view_curses::mvwattrline(win, y++, 0, al, lr); wmove(win, y, 0); diff --git a/test/drive_sql.cc b/test/drive_sql.cc index 0936a733..243e78a3 100644 --- a/test/drive_sql.cc +++ b/test/drive_sql.cc @@ -6,7 +6,7 @@ #include #include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/injector.hh" #include "regexp_vtab.hh" #include "sqlite-extension-func.hh" diff --git a/test/drive_view_colors.cc b/test/drive_view_colors.cc index 6f7574fd..6d55f196 100644 --- a/test/drive_view_colors.cc +++ b/test/drive_view_colors.cc @@ -53,7 +53,7 @@ public: attrs = vc.attrs_for_ident(label); al = label; al.get_attrs().emplace_back(line_range(0, -1), - view_curses::VC_STYLE.value(attrs)); + VC_STYLE.value(attrs)); lr.lr_start = 0; lr.lr_end = 40; test_colors::mvwattrline(this->tc_window, lpc, 0, al, lr); diff --git a/test/expected_help.txt b/test/expected_help.txt index 473a64f6..dac71d4c 100644 --- a/test/expected_help.txt +++ b/test/expected_help.txt @@ -3121,12 +3121,12 @@ Synopsis Query the database and return zero or more rows of data. Parameters - result-column + result-column The expression used to generate a result for this column. table The table(s) to query for data cond The conditions used to select the rows to return. grouping-expr The expression to use when grouping rows. ordering-term The values to use when ordering the result set. - limit-expr The maximum number of rows to return + limit-expr The maximum number of rows to return. Example #1 To select all of the columns from the table 'syslog_log': diff --git a/test/formats/collision/format.json b/test/formats/collision/format.json index 824da8a0..35c25c00 100644 --- a/test/formats/collision/format.json +++ b/test/formats/collision/format.json @@ -1,14 +1,15 @@ { - "zblued_log" : { - "title" : "blued", - "regex" : { - "std" : { - "pattern" : "^(?\\w{3}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2})(?: (?[a-zA-Z0-9:][^ ]+[a-zA-Z0-9]))? blued(?:\\[(?\\d+)])?:(?(?:.|\\n)*)$" + "$schema": "https://lnav.org/schemas/format-v1.schema.json", + "zblued_log": { + "title": "blued", + "regex": { + "std": { + "pattern": "^(?\\w{3}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2})(?: (?[a-zA-Z0-9:][^ ]+[a-zA-Z0-9]))? blued(?:\\[(?\\d+)])?:(?(?:.|\\n)*)$" } }, - "level-field" : "body", - "level" : { - "error" : "(?:failed|failure|error)", + "level-field": "body", + "level": { + "error": "(?:failed|failure|error)", "warning" : "(?:warn|not responding|init: cannot execute)" }, "value" : { diff --git a/test/formats/customlevel/format.json b/test/formats/customlevel/format.json index b777c508..dde41fee 100644 --- a/test/formats/customlevel/format.json +++ b/test/formats/customlevel/format.json @@ -1,4 +1,5 @@ { + "$schema": "https://lnav.org/schemas/format-v1.schema.json", "leveltest_log": { "description": "Log format used for testing levels", "regex": { diff --git a/test/formats/jsontest/lnav-logstash.json b/test/formats/jsontest/lnav-logstash.json index 9e16236a..27f92398 100644 --- a/test/formats/jsontest/lnav-logstash.json +++ b/test/formats/jsontest/lnav-logstash.json @@ -1,14 +1,14 @@ { + "$schema": "https://lnav.org/schemas/format-v1.schema.json", "logstash_dam": { - "title" : "Logstash Java JSON", - "url" : "https://github.com/logstash/logstash-logback-encoder", - "description" : "Log format for DAM Logstash JSON", - "json" : true, - "hide-extra" : false, - "file-pattern" : "\\.clog.*", + "title": "Logstash Java JSON", + "url": "https://github.com/logstash/logstash-logback-encoder", + "description": "Log format for DAM Logstash JSON", + "json": true, + "hide-extra": false, + "file-pattern": "\\.clog.*", "multiline": false, - "line-format" : - [ + "line-format": [ { "field" : "@timestamp" }, " ", { "field" : "ipaddress" }, diff --git a/test/formats/jsontest2/format.json b/test/formats/jsontest2/format.json index 04cce6f1..95781f7f 100644 --- a/test/formats/jsontest2/format.json +++ b/test/formats/jsontest2/format.json @@ -1,13 +1,19 @@ { - "json_log2" : { - "title" : "Test JSON Log with integer levels", - "json" : true, - "file-pattern" : "logfile_json2\\.json", - "description" : "Test config", - "line-format" : [ - { "field" : "ts" }, + "$schema": "https://lnav.org/schemas/format-v1.schema.json", + "json_log2": { + "title": "Test JSON Log with integer levels", + "json": true, + "file-pattern": "logfile_json2\\.json", + "description": "Test config", + "line-format": [ + { + "field": "ts" + }, " ", - { "field" : "ts", "timestamp-format" : "abc %S def" }, + { + "field": "ts", + "timestamp-format": "abc %S def" + }, " ", { "field" : "lvl", "min-width": 5 }, " ", diff --git a/test/formats/jsontest3/format.json b/test/formats/jsontest3/format.json index 14773221..efa984c6 100644 --- a/test/formats/jsontest3/format.json +++ b/test/formats/jsontest3/format.json @@ -1,8 +1,9 @@ { + "$schema": "https://lnav.org/schemas/format-v1.schema.json", "json_log3": { "title": "Test JSON Log Format", "description": "Test JSON Log Format", - "file-pattern" : "logfile_json3\\.json", + "file-pattern": "logfile_json3\\.json", "json": true, "hide-extra": true, "line-format": [ diff --git a/test/formats/nestedjson/format.json b/test/formats/nestedjson/format.json index eeee5295..afa6b080 100644 --- a/test/formats/nestedjson/format.json +++ b/test/formats/nestedjson/format.json @@ -1,13 +1,18 @@ { - "ntest_log" : { - "title" : "Test JSON Log", - "json" : true, - "file-pattern" : "logfile_(nested|invalid)_json\\d*\\.json", - "description" : "Test config", - "line-format" : [ - { "field" : "ts" }, + "$schema": "https://lnav.org/schemas/format-v1.schema.json", + "ntest_log": { + "title": "Test JSON Log", + "json": true, + "file-pattern": "logfile_(nested|invalid)_json\\d*\\.json", + "description": "Test config", + "line-format": [ + { + "field": "ts" + }, " ", - { "field" : "/@fields/lvl" }, + { + "field": "/@fields/lvl" + }, " ", { "field" : "@fields/msg" } ], diff --git a/test/formats/timestamp/format.json b/test/formats/timestamp/format.json index 86545f8c..f02a1166 100644 --- a/test/formats/timestamp/format.json +++ b/test/formats/timestamp/format.json @@ -1,4 +1,5 @@ { + "$schema": "https://lnav.org/schemas/format-v1.schema.json", "epoch_log": { "title": "epoch timestamp test", "regex": { diff --git a/test/formats/xmlmsg/format.json b/test/formats/xmlmsg/format.json index 9ee1eabe..d664871a 100644 --- a/test/formats/xmlmsg/format.json +++ b/test/formats/xmlmsg/format.json @@ -1,4 +1,5 @@ { + "$schema": "https://lnav.org/schemas/format-v1.schema.json", "xml_msg_log": { "title": "", "description": "", diff --git a/test/lnav_doctests.cc b/test/lnav_doctests.cc index 1c66d2eb..d556fe11 100644 --- a/test/lnav_doctests.cc +++ b/test/lnav_doctests.cc @@ -33,6 +33,7 @@ #include "byte_array.hh" #include "doctest/doctest.h" #include "lnav_config.hh" +#include "lnav_util.hh" #include "relative_time.hh" #include "unique_path.hh" @@ -180,3 +181,32 @@ TEST_CASE("unique_path") CHECK(log1->get_unique_path() == "[machine1]/syslog.log"); CHECK(log2->get_unique_path() == "[machine2]/syslog.log"); } + +TEST_CASE("attr_line to json") +{ + attr_line_t al; + + al.append("Hello, ").append(lnav::roles::symbol("World")).append("!"); + + auto json = lnav::to_json(al); + auto al2 = lnav::from_json(json); + auto json2 = lnav::to_json(al2); + + CHECK(json == json2); +} + +TEST_CASE("user_message to json") +{ + auto um + = lnav::console::user_message::error("testing") + .with_reason("because") + .with_snippet(lnav::console::snippet::from("hello.c", "printf(") + .with_line(1)) + .with_help("close it"); + + auto json = lnav::to_json(um); + auto um2 = lnav::from_json(json); + auto json2 = lnav::to_json(um2); + + CHECK(json == json2); +} diff --git a/test/scripty.cc b/test/scripty.cc index 07623b69..2f7bb940 100644 --- a/test/scripty.cc +++ b/test/scripty.cc @@ -78,7 +78,7 @@ #include #include "base/auto_fd.hh" -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "base/string_util.hh" #include "fmt/format.h" #include "ghc/filesystem.hpp" diff --git a/test/test_ansi_scrubber.cc b/test/test_ansi_scrubber.cc index a6ee5d84..6676c778 100644 --- a/test/test_ansi_scrubber.cc +++ b/test/test_ansi_scrubber.cc @@ -36,7 +36,7 @@ #include -#include "ansi_scrubber.hh" +#include "base/ansi_scrubber.hh" #include "config.h" #include "view_curses.hh" @@ -60,11 +60,11 @@ main(int argc, char* argv[]) assert(sa[0].sa_range.lr_start == 5); assert(sa[0].sa_range.lr_end == 7); - assert(sa[0].sa_type == &view_curses::VC_BACKGROUND); + assert(sa[0].sa_type == &VC_BACKGROUND); assert(sa[0].sa_value.get() == COLOR_BLUE); assert(sa[1].sa_range.lr_start == 7); assert(sa[1].sa_range.lr_end == 12); - assert(sa[1].sa_type == &view_curses::VC_FOREGROUND); + assert(sa[1].sa_type == &VC_FOREGROUND); assert(sa[1].sa_value.get() == COLOR_YELLOW); } diff --git a/test/test_auto_mem.cc b/test/test_auto_mem.cc index 2024238b..2603c631 100644 --- a/test/test_auto_mem.cc +++ b/test/test_auto_mem.cc @@ -30,7 +30,7 @@ #include #include -#include "auto_mem.hh" +#include "base/auto_mem.hh" #include "config.h" struct my_data { diff --git a/test/test_cli.sh b/test/test_cli.sh index ca3c4f35..72c4f146 100644 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -1,6 +1,20 @@ #! /bin/bash export TZ="UTC" + +run_test ${lnav_test} -n -c 'foo' + +check_error_output "invalid command not detected?" < arg + | -c foo + | ^ command type prefix is missing + = help: command arguments must start with one of the following symbols to denote the type of command: + : - an lnav command (e.g. :goto 42) + ; - an SQL statement (e.g. SELECT * FROM syslog_log) + | - an lnav script (e.g. |rename-stdin foo) +EOF + run_test ${lnav_test} -d /tmp/lnav.err -t -n < command-option:1 + | :unix-time + = help: Synopsis + :unix-time seconds - Convert epoch time to a human-readable form EOF @@ -25,7 +29,11 @@ run_test ${lnav_test} -n \ "${test_dir}/logfile_access_log.*" check_error_output ":unix-time works without arg?" < command-option:1 + | :unix-time abc + = help: Synopsis + :unix-time seconds - Convert epoch time to a human-readable form EOF @@ -52,7 +60,11 @@ run_test ${lnav_test} -n -d /tmp/lnav.err \ "${test_dir}/logfile_access_log.*" check_error_output "able to write without a file name" < command-option:1 + | :write-to + = help: Synopsis + :write-to path - Overwrite the given file with any marked lines in the current view EOF @@ -108,7 +120,11 @@ run_test ${lnav_test} -n -d /tmp/lnav.err \ "${test_dir}/logfile_multiline.0" check_error_output "filter-expr with bad SQL works?" < command-option:1 + | :filter-expr timeslice(:log_time_msecs, 'bad') is not null + = help: Synopsis + :filter-expr expr - Set the filter expression EOF @@ -147,7 +163,11 @@ run_test ${lnav_test} -n -d /tmp/lnav.err \ "${test_dir}/logfile_access_log.*" check_error_output "filter-expr error not working" < command-option:1 + | :filter-expr :sc_bytes # ff + = help: Synopsis + :filter-expr expr - Set the filter expression EOF @@ -244,7 +264,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "hide-fields with unknown" < command-option:1 + | :hide-fields foobar + = help: Synopsis + :hide-fields field-name1 [... field-nameN] - Hide log message fields by replacing them with an ellipsis EOF run_test ${lnav_test} -n \ @@ -279,7 +303,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "config bad option" < command-option:1 + | :config /bad/option + = help: Synopsis + :config option [value] - Read or write a configuration option EOF check_output "config bad option" < command-option:1 + | |nonexistent.lnav EOF @@ -427,7 +457,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "goto invalid is working" < command-option:1 + | :goto invalid + = help: Synopsis + :goto line#|N%|date - Go to the given location in the top view EOF check_output "goto invalid is not working" < command-option:2 + | :next-mark foobar + = help: Synopsis + :next-mark type1 [... typeN] - Move to the next bookmark of the given type in the current view EOF check_output "invalid mark-type is working" < command-option:32 + | :filter-out 32 + = help: Synopsis + :filter-out pattern - Remove lines that match the given regular expression in the current view EOF @@ -640,7 +682,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "double close works" < command-option:2 + | :close + = help: Synopsis + :close - Close the top file in the view EOF check_output "double close is working" < command-option:1 + | :open + = help: Synopsis + :open path1 [... pathN] - Open the given file(s) in lnav. Opening files on machines accessible via SSH can be done using the syntax: [user@]host:/path/to/logs EOF run_test ${lnav_test} -n \ @@ -672,7 +722,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "open non-existent is working" < command-option:2 + | :open /non-existent + = help: Synopsis + :open path1 [... pathN] - Open the given file(s) in lnav. Opening files on machines accessible via SSH can be done using the syntax: [user@]host:/path/to/logs EOF check_output "open non-existent is not working" < command-option:2 + | :write-json-to - + = help: Synopsis + :write-json-to path - Write SQL results to the given file in JSON format EOF unset LNAVSECURE @@ -990,7 +1048,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "clear-highlight did not report an error?" < command-option:1 + | :clear-highlight foobar + = help: Synopsis + :clear-highlight pattern - Remove a previously set highlight regular expression EOF run_test ${lnav_test} -n \ @@ -1075,7 +1137,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_syslog.0 check_error_output ":mark-expr works without an expression?" < command-option:1 + | :mark-expr + = help: Synopsis + :mark-expr expr - Set the bookmark expression EOF @@ -1084,7 +1150,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_syslog.0 check_error_output ":mark-expr works with a bad expression?" < command-option:1 + | :mark-expr :log_procname lik + = help: Synopsis + :mark-expr expr - Set the bookmark expression EOF @@ -1115,7 +1185,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "bad zoom level is not rejected?" < command-option:1 + | :zoom-to bad + = help: Synopsis + :zoom-to zoom-level - Zoom the histogram view to the given level EOF diff --git a/test/test_config.sh b/test/test_config.sh index ec18934b..b1724404 100755 --- a/test/test_config.sh +++ b/test/test_config.sh @@ -23,7 +23,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "config bad color" < command-option:1 EOF run_test env TMPDIR=tmp ${lnav_test} -n \ @@ -31,7 +33,11 @@ run_test env TMPDIR=tmp ${lnav_test} -n \ ${srcdir}/logfile_syslog.0 check_error_output "invalid min-free-space allowed?" < command-option:1 + | :config /tuning/archive-manager/min-free-space abc + = help: Synopsis + :config option [value] - Read or write a configuration option EOF run_test ${lnav_test} -n \ @@ -39,7 +45,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "config bad theme" < command-option:1 EOF run_test ${lnav_test} -n \ @@ -47,24 +55,40 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "config bad theme" < {test_dir}/bad-config2/formats/invalid-config/config.bad-schema.json:2 + | "\$schema": "bad" + = note: expecting one of the following \$schema values: + https://lnav.org/schemas/config-v1.schema.json + = help: Property Synopsis + /\$schema + Description + The URI that specifies the schema that describes this type of file + Example + https://lnav.org/schemas/config-v1.schema.json +⚠ warning: unexpected value for property “/ui” + --> {test_dir}/bad-config2/formats/invalid-config/config.malformed.json:2 + | "ui": "theme", + = help: Available Properties + \$schema + tuning/ + ui/ + global/ +✘ error: invalid JSON + --> {test_dir}/bad-config2/formats/invalid-config/config.malformed.json:3 + | parse error: object key and value must be separated by a colon (':') + | "ui": "theme", "abc", "def": "" } + | (right here) ------^ + | +⚠ warning: unexpected value for property “/ui” + --> {test_dir}/bad-config2/formats/invalid-config/config.truncated.json:2 + | "ui": "theme" + = help: Available Properties + \$schema + tuning/ + ui/ + global/ +✘ error: invalid JSON + reason: parse error: premature EOF + --> {test_dir}/bad-config2/formats/invalid-config/config.truncated.json:3 EOF diff --git a/test/test_format_loader.sh b/test/test_format_loader.sh index 4fa9465b..b99a26df 100644 --- a/test/test_format_loader.sh +++ b/test/test_format_loader.sh @@ -7,59 +7,187 @@ run_test ${lnav_test} -C \ -I ${test_dir}/bad-config-json check_error_output "invalid format not detected?" < -- The collating function to use for this column -warning: unit -- Unit definitions for this field -warning: identifier -- Indicates whether or not this field contains an identifier that should be highlighted -warning: foreign-key -- Indicates whether or not this field should be treated as a foreign key for row in another table -warning: hidden -- Indicates whether or not this field should be hidden -warning: action-list -- Actions to execute when this field is clicked on -warning: rewriter -- A command that will rewrite this field when pretty-printing -warning: description -- A description of the field -error:{test_dir}/bad-config-json/formats/invalid-json/format.json:4:invalid json -- parse error: object key and value must be separated by a colon (':') - ar_log": { "abc" } } - (right here) ------^ -error:foobar_log: no regexes specified for format -error:foobar_log:no sample logs provided, all formats must have samples -error:invalid_key_log: no regexes specified for format -error:invalid_key_log:no sample logs provided, all formats must have samples +✘ error: invalid JSON + --> {test_dir}/bad-config-json/formats/invalid-json/format.json:4 + | parse error: object key and value must be separated by a colon (':') + | ar_log": { "abc" } } + | (right here) ------^ + | +✘ error: “abc(” is not a valid regular expression for property “/invalid_key_log/level-pointer” + reason: missing ) + --> {test_dir}/bad-config-json/formats/invalid-key/format.json:4 + | "level-pointer": "abc(", + --> /invalid_key_log/level-pointer + | abc( + | ^ missing ) + = help: Property Synopsis + /invalid_key_log/level-pointer + Description + A regular-expression that matches the JSON-pointer of the level property +✘ error: “def[ghi” is not a valid regular expression for property “/invalid_key_log/file-pattern” + reason: missing terminating ] for character class + --> {test_dir}/bad-config-json/formats/invalid-key/format.json:5 + | "file-pattern": "def[ghi", + --> /invalid_key_log/file-pattern + | def[ghi + | ^ missing terminating ] for character class + = help: Property Synopsis + /invalid_key_log/file-pattern + Description + A regular expression that restricts this format to log files with a matching name +⚠ warning: unexpected value for property “/invalid_key_log/value/test/identifiers” + --> {test_dir}/bad-config-json/formats/invalid-key/format.json:14 + | "identifiers": true + = help: Available Properties + kind + collate + unit/ + identifier + foreign-key + hidden + action-list + rewriter + description +✘ error: “-1.2” is not a valid value for “/invalid_key_log/timestamp-divisor” + reason: value cannot be less than or equal to zero + --> {test_dir}/bad-config-json/formats/invalid-key/format.json:25 + | "timestamp-divisor": -1.2 + = help: Property Synopsis + /invalid_key_log/timestamp-divisor + Description + The value to divide a numeric timestamp by in a JSON log. +✘ error: “foobar_log” is not a valid log format + reason: no regexes specified + --> {test_dir}/bad-config-json/formats/invalid-json/format.json:3 +✘ error: “foobar_log” is not a valid log format + reason: log message samples must be included in a format definition + --> {test_dir}/bad-config-json/formats/invalid-json/format.json:3 +✘ error: “invalid_key_log” is not a valid log format + reason: structured logs cannot have regexes + --> {test_dir}/bad-config-json/formats/invalid-key/format.json:4 +✘ error: invalid line format element “/invalid_key_log/line-format/0/field” + reason: “non-existent” is not a defined value + --> {test_dir}/bad-config-json/formats/invalid-key/format.json:22 EOF run_test ${lnav_test} -C \ -I ${test_dir}/bad-config check_error_output "invalid format not detected?" <\d+: (?.*)$ -error:bad_regex_log.regex[std]: ^ -error:bad_regex_log.level:missing ) -error:bad_regex_log:invalid sample -- 1428634687123; foo -error:bad_regex_log:highlighters/foobar:missing ) -error:bad_regex_log:highlighters/foobar:abc( -error:bad_regex_log:highlighters/foobar: ^ -error:bad_sample_log:invalid sample -- 1428634687123; foo bar -error:bad_sample_log:partial sample matched -- 1428634687123; foo -error: against pattern bad_sample_log/regex/semi -- ^(?\d+); (?\w+)$ -error:bad_sample_log:partial sample matched -- 1428634687123 -error: against pattern bad_sample_log/regex/std -- ^(?\d+): (?\w+) (?.*)$ -error:no_sample_log:no sample logs provided, all formats must have samples -error:{test_dir}/bad-config/formats/invalid-sql/init.sql:2:near "TALE": syntax error +✘ error: “abc(def” is not a valid regular expression for property “/invalid_props_log/search-table/bad_table_regex/pattern” + reason: missing ) + --> {test_dir}/bad-config/formats/invalid-properties/format.json:24 + | "pattern": "abc(def" + --> /invalid_props_log/search-table/bad_table_regex/pattern + | abc(def + | ^ missing ) + = help: Property Synopsis + /invalid_props_log/search-table/bad_table_regex/pattern + Description + The regular expression for this search table. +✘ error: “^(?\d+: (?.*)\$” is not a valid regular expression for property “/bad_regex_log/regex/std/pattern” + reason: missing ) + --> {test_dir}/bad-config/formats/invalid-regex/format.json:6 + | "pattern": "^(?\\\\d+: (?.*)$" + --> /bad_regex_log/regex/std/pattern + | ^(?\d+: (?.*)$ + | ^ missing ) + = help: Property Synopsis + /bad_regex_log/regex/std/pattern + Description + The regular expression to match a log message and capture fields. +✘ error: “(foo” is not a valid regular expression for property “/bad_regex_log/level/error” + reason: missing ) + --> {test_dir}/bad-config/formats/invalid-regex/format.json:10 + | "error" : "(foo" + --> /bad_regex_log/level/error + | (foo + | ^ missing ) + = help: Property Synopsis + /bad_regex_log/level/error + Description + The regular expression used to match the log text for this level. For JSON logs with numeric levels, this should be the number for the corresponding level. +✘ error: “abc(” is not a valid regular expression for property “/bad_regex_log/highlights/foobar/pattern” + reason: missing ) + --> {test_dir}/bad-config/formats/invalid-regex/format.json:22 + | "pattern": "abc(" + --> /bad_regex_log/highlights/foobar/pattern + | abc( + | ^ missing ) + = help: Property Synopsis + /bad_regex_log/highlights/foobar/pattern + Description + A regular expression to highlight in logs of this format. +✘ error: “foo” is not a valid value for option “/bad_sample_log/value/pid/kind” + --> {test_dir}/bad-config/formats/invalid-sample/format.json:24 + | "kind": "foo" + = help: Property Synopsis + /bad_sample_log/value/pid/kind + Description + The type of data in the field + Allowed Values + string, integer, float, boolean, json, struct, quoted, xml +✘ error: 'bad' is not a supported log format \$schema version + --> {test_dir}/bad-config/formats/invalid-schema/format.json:2 + | "\$schema": "bad" + = note: expecting one of the following \$schema values: + https://lnav.org/schemas/format-v1.schema.json + = help: Property Synopsis + /\$schema The URI of the schema for this file + Description + Specifies the type of this file +✘ error: invalid sample log message “abc: foo” + reason: unrecognized timestamp -- abc + --> {test_dir}/bad-config/formats/invalid-sample/format.json:30 + = note: the following formats were tried: + abc + ^ “%i” matched up to here +✘ error: invalid sample log message “1428634687123| debug hello” + reason: “debug” does not match the expected level of “info” + --> {test_dir}/bad-config/formats/invalid-sample/format.json:33 +✘ error: invalid sample log message “1428634687123; foo bar” + reason: sample does not match any patterns + --> {test_dir}/bad-config/formats/invalid-sample/format.json:37 + = note: the following shows how each pattern matched this sample: + “1428634687123; foo bar” + ^ bad-time matched up to here + ^ semi matched up to here + ^ std matched up to here + ^ with-level matched up to here + = help: bad-time = ^(?\w+): (?\w+)$ + semi = ^(?\d+); (?\w+)$ + std = ^(?\d+): (?\w+) (?.*)$ + with-level = ^(?\d+)\| (?\w+) (?\w+)$ +✘ error: invalid value for property “/invalid_props_log/timestamp-field” + reason: “ts” was not found in the pattern at /invalid_props_log/regex/std + --> {test_dir}/bad-config/formats/invalid-properties/format.json:4 + = note: the following captures are available: + body, pid, timestamp +✘ error: “not a color” is not a valid color value for property “/invalid_props_log/highlights/hl1/color” + reason: Unknown color: 'not a color'. See https://jonasjacek.github.io/colors/ for a list of supported color names + --> {test_dir}/bad-config/formats/invalid-properties/format.json:18 +✘ error: “also not a color” is not a valid color value for property “/invalid_props_log/highlights/hl1/background-color” + reason: Unknown color: 'also not a color'. See https://jonasjacek.github.io/colors/ for a list of supported color names + --> {test_dir}/bad-config/formats/invalid-properties/format.json:19 +✘ error: “no_regexes_log” is not a valid log format + reason: no regexes specified + --> {test_dir}/bad-config/formats/no-regexes/format.json:4 +✘ error: “no_regexes_log” is not a valid log format + reason: log message samples must be included in a format definition + --> {test_dir}/bad-config/formats/no-regexes/format.json:4 +✘ error: “no_sample_log” is not a valid log format + reason: log message samples must be included in a format definition + --> {test_dir}/bad-config/formats/no-samples/format.json:4 +✘ error: failed to compile SQL statement + reason: near "TALE": syntax error + --> {test_dir}/bad-config/formats/invalid-sql/init.sql:4 + | -- comment test + | CREATE TALE +✘ error: failed to execute SQL statement + reason: missing ) + --> {test_dir}/bad-config/formats/invalid-sql/init2.sql + | SELECT regexp_match('abc(', '123') + | FROM sqlite_master; EOF run_test ${lnav_test} -n \ diff --git a/test/test_json_format.sh b/test/test_json_format.sh index 7df2a831..fe7f00ee 100644 --- a/test/test_json_format.sh +++ b/test/test_json_format.sh @@ -68,7 +68,7 @@ user: mailto:steve@example.com [2013-09-06T22:01:49.124] STATS 1 beat per secondbork bork bork [2013-09-06T22:01:49.124] WARNING not looking goodbork bork bork [2013-09-06T22:01:49.124] ERROR looking badbork bork bork -[2013-09-06T22:01:49.124] CRITICAL sooo badbork bork bor +[2013-09-06T22:01:49.124] CRITICAL sooo badbork bork bork EOF diff --git a/test/test_logfile.sh b/test/test_logfile.sh index e562d1fe..b3f87ece 100644 --- a/test/test_logfile.sh +++ b/test/test_logfile.sh @@ -65,7 +65,13 @@ if test x"${LIBARCHIVE_LIBS}" != x""; then ${srcdir}/logfile_syslog.0 check_error_output "invalid min-free-space allowed?" < input:1 + = help: Property Synopsis + /tuning/archive-manager/min-free-space + Description + The minimum free space, in bytes, to maintain when unpacking archives EOF rm -rf tmp/lnav-* @@ -87,7 +93,8 @@ EOF `test_err_filename` > test_logfile.big.out mv test_logfile.big.out `test_err_filename` check_error_output "decompression worked?" < test_logfile.trunc.out mv test_logfile.trunc.out `test_err_filename` check_error_output "truncated tgz not reported correctly" <> src/lnav -- truncated gzip input +✘ error: unable to open file: /test-logs-trunc.tgz + reason: failed to extract 'src/lnav' from archive '/test-logs-trunc.tgz' -- truncated gzip input EOF mkdir -p rotmp @@ -168,11 +176,12 @@ EOF -e 's|log\.0 -- .*|log\.0 -- ...|g' \ -e "s|arc-[0-9a-z]*-test|arc-NNN-test|g" \ -e "s|${builddir}||g" \ - `test_err_filename` | head -1 \ + `test_err_filename` | head -2 \ > test_logfile.rotmp.out cp test_logfile.rotmp.out `test_err_filename` check_error_output "archive not unpacked" < test_logfile.unreadable.out mv test_logfile.unreadable.out `test_err_filename` check_error_output "able to read an unreadable log file?" < -- A tag for the log line +✘ error: invalid value for “log_tags” column of table “access_log” + reason: unexpected JSON value + | --> access_log.log_tags:1 + | | 1 + | = help: expecting an array of tag values + --> command-option:1 + | ;UPDATE access_log SET log_tags = 1 WHERE log_line = 1 EOF run_test ${lnav_test} -n \ @@ -138,7 +141,17 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "updating log_tags is not working?" < access_log.log_tags:1 + | | ["foo"] + | = help: Property Synopsis + | /# tag + | Description + | A tag for the log line + --> command-option:1 + | ;UPDATE access_log SET log_tags = json_array('foo') WHERE log_line = 1 EOF run_test ${lnav_test} -n \ diff --git a/test/test_sql.sh b/test/test_sql.sh index 52569527..85e900bb 100644 --- a/test/test_sql.sh +++ b/test/test_sql.sh @@ -8,7 +8,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_empty.0 check_error_output "read worked with a nonexistent file?" < command-option:1 + | ;.read nonexistent-file + = help: Synopsis + ;.read path - Execute the SQLite statements in the given file EOF run_test ${lnav_test} -n \ @@ -150,7 +154,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "generate_series() works without params?" < command-option:1 + | ;SELECT * FROM generate_series() EOF run_test ${lnav_test} -n \ @@ -158,7 +164,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "generate_series() works without stop param?" < command-option:1 + | ;SELECT * FROM generate_series(1) EOF run_test ${lnav_test} -n \ @@ -166,7 +174,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "raise_error() does not work?" < command-option:1 + | ;SELECT raise_error('oops!') EOF run_test ${lnav_test} -n \ @@ -231,19 +241,14 @@ ts 2013-09-06T22:01:49.124817Z EOF -run_test ${lnav_test} -n \ - -c ";UPDATE lnav_file SET visible=0" \ - ${test_dir}/logfile_access_log.0 - -check_output "file is not hidden?" < command-option:1 + | ;UPDATE lnav_file SET filepath='foo' WHERE endswith(filepath, '_log.0') EOF run_test ${lnav_test} -n \ @@ -251,7 +256,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 < /dev/null check_error_output "rename-stdin works without an argument?" < ../test/.lnav/formats/default/rename-stdin.lnav:6 + | SELECT raise_error('expecting the new name for stdin as the first argument') WHERE \$1 IS NULL EOF run_test ${lnav_test} -n \ @@ -259,7 +266,10 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 < /dev/null check_error_output "rename-stdin when there is no stdin file?" < ../test/.lnav/formats/default/rename-stdin.lnav:7 + | SELECT raise_error('no data was redirected to lnav''s standard-input') + | WHERE (SELECT count(1) FROM lnav_file WHERE filepath='stdin') = 0 EOF run_test ${lnav_test} -n \ @@ -340,7 +350,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "able to update lnav_view_stack?" < command-option:1 + | ;UPDATE lnav_view_stack SET name = 'foo' EOF run_test ${lnav_test} -n \ @@ -349,7 +361,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "able to delete a view in the middle of lnav_view_stack?" < command-option:2 + | ;DELETE FROM lnav_view_stack WHERE name = 'log' EOF run_test ${lnav_test} -n \ @@ -357,7 +371,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "inserted filter with an empty pattern?" < command-option:1 + | ;INSERT INTO lnav_view_filters VALUES ('log', 0, 1, 'out', 'regex', '') EOF run_test ${lnav_test} -n \ @@ -365,7 +381,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "inserted filter with an invalid pattern?" < command-option:1 + | ;INSERT INTO lnav_view_filters VALUES ('log', 0, 1, 'out', 'regex', 'abc(') EOF run_test ${lnav_test} -n \ @@ -373,7 +391,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "inserted filter with an invalid view name?" < command-option:1 + | ;INSERT INTO lnav_view_filters VALUES ('bad', 0, 1, 'out', 'regex', 'abc') EOF run_test ${lnav_test} -n \ @@ -381,7 +401,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "inserted filter with a null view name?" < command-option:1 + | ;INSERT INTO lnav_view_filters VALUES (NULL, 0, 1, 'out', 'regex', 'abc') EOF run_test ${lnav_test} -n \ @@ -389,7 +411,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "inserted filter with an invalid filter type?" < command-option:1 + | ;INSERT INTO lnav_view_filters VALUES ('log', 0 , 1, 'bad', 'regex', 'abc') EOF run_test ${lnav_test} -n \ @@ -639,7 +663,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "spectrogram worked without log_time?" < command-option:2 + | :spectrogram sc_bytes + = help: Synopsis + :spectrogram field-name - Visualize the given message field using a spectrogram EOF run_test ${lnav_test} -n \ @@ -648,7 +676,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "spectrogram worked with bad column?" < command-option:2 + | :spectrogram sc_byes + = help: Synopsis + :spectrogram field-name - Visualize the given message field using a spectrogram EOF run_test ${lnav_test} -n \ @@ -657,7 +689,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "spectrogram worked with non-numeric column?" < command-option:2 + | :spectrogram c_ip + = help: Synopsis + :spectrogram field-name - Visualize the given message field using a spectrogram EOF run_test ${lnav_test} -n \ @@ -666,7 +702,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "spectrogram worked with unordered log_time?" < command-option:2 + | :spectrogram sc_bytes + = help: Synopsis + :spectrogram field-name - Visualize the given message field using a spectrogram EOF cp ${srcdir}/logfile_syslog_with_mixed_times.0 logfile_syslog_with_mixed_times_test.0 @@ -896,7 +936,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "insert into environ table works" < command-option:1 + | ;INSERT INTO environ (name) VALUES (null) EOF check_output "insert into environ table works" < command-option:1 + | ;INSERT INTO environ (name, value) VALUES (null, null) EOF check_output "insert into environ table works" < command-option:1 + | ;INSERT INTO environ (name, value) VALUES ("", null) EOF check_output "insert into environ table works" < command-option:1 + | ;INSERT INTO environ (name, value) VALUES ("foo=bar", "bar") EOF check_output "insert into environ table works" < command-option:1 + | ;INSERT INTO environ (name, value) VALUES ("SQL_ENV_VALUE", "bar") EOF check_output "insert into environ table works" < command-option:1 + | ;UPDATE lnav_views SET top_time = 'bad-time' WHERE name = 'log' EOF @@ -1134,7 +1186,9 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_access_log.0 check_error_output "errors are not reported" < command-option:1 + | ;select * from nonexistent_table EOF check_output "errors are not reported" < command-option:1 + | ;delete from access_log EOF check_output "errors are not reported" < command-option:1 + | ;attach database 'simple-db.db' as 'db' EOF run_test ${lnav_test} -n \ @@ -1368,7 +1427,9 @@ run_test ${lnav_test} -n \ empty check_error_output "LNAVSECURE mode bypassed (':' adorned)" < command-option:1 + | ;attach database ':memdb:' as 'db' EOF run_test ${lnav_test} -n \ @@ -1376,7 +1437,9 @@ run_test ${lnav_test} -n \ empty check_error_output "LNAVSECURE mode bypassed (filepath)" < command-option:1 + | ;attach database '/tmp/memdb' as 'db' EOF run_test ${lnav_test} -n \ @@ -1384,7 +1447,9 @@ run_test ${lnav_test} -n \ empty check_error_output "LNAVSECURE mode bypassed (URI)" < command-option:1 + | ;attach database 'file:memdb?cache=shared' as 'db' EOF unset LNAVSECURE @@ -1481,7 +1546,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_multiline.0 check_error_output "able to delete unknown table?" < command-option:1 + | :delete-search-table search_test1 + = help: Synopsis + :delete-search-table table-name - Create an SQL table based on a regex search EOF run_test ${lnav_test} -n \ @@ -1490,7 +1559,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_multiline.0 check_error_output "able to delete logline table?" < command-option:2 + | :delete-search-table search_test1 + = help: Synopsis + :delete-search-table table-name - Create an SQL table based on a regex search EOF run_test ${lnav_test} -n \ @@ -1498,7 +1571,11 @@ run_test ${lnav_test} -n \ ${test_dir}/logfile_multiline.0 check_error_output "able to create table with a bad regex?" < command-option:1 + | :create-search-table search_test1 bad( + = help: Synopsis + :create-search-table table-name [pattern] - Create an SQL table based on a regex search EOF NULL_GRAPH_SELECT_1=$(cat < errors; - vector paths; + std::vector errors; + std::vector paths; load_config(paths, errors);