summaryrefslogtreecommitdiffstats
path: root/src/lnav_config.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lnav_config.cc')
-rw-r--r--src/lnav_config.cc1629
1 files changed, 1629 insertions, 0 deletions
diff --git a/src/lnav_config.cc b/src/lnav_config.cc
new file mode 100644
index 0000000..4e9396a
--- /dev/null
+++ b/src/lnav_config.cc
@@ -0,0 +1,1629 @@
+/**
+ * Copyright (c) 2013, 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.
+ *
+ * @file lnav_config.cc
+ */
+
+#include <chrono>
+#include <iostream>
+#include <regex>
+#include <stdexcept>
+
+#include "lnav_config.hh"
+
+#include <fcntl.h>
+#include <fmt/format.h>
+#include <glob.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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"
+#include "base/injector.hh"
+#include "base/lnav_log.hh"
+#include "base/paths.hh"
+#include "base/string_util.hh"
+#include "bin2c.hh"
+#include "config.h"
+#include "default-config.h"
+#include "styling.hh"
+#include "view_curses.hh"
+#include "yajlpp/yajlpp.hh"
+#include "yajlpp/yajlpp_def.hh"
+
+using namespace std::chrono_literals;
+
+static const int MAX_CRASH_LOG_COUNT = 16;
+static const auto STDIN_CAPTURE_RETENTION = 24h;
+
+static auto intern_lifetime = intern_string::get_table_lifetime();
+
+struct _lnav_config lnav_config;
+struct _lnav_config rollback_lnav_config;
+static struct _lnav_config lnav_default_config;
+
+std::map<intern_string_t, source_location> lnav_config_locations;
+
+lnav_config_listener* lnav_config_listener::LISTENER_LIST;
+
+static auto a = injector::bind<archive_manager::config>::to_instance(
+ +[]() { return &lnav_config.lc_archive_manager; });
+
+static auto fvc = injector::bind<file_vtab::config>::to_instance(
+ +[]() { return &lnav_config.lc_file_vtab; });
+
+static auto lc = injector::bind<lnav::logfile::config>::to_instance(
+ +[]() { return &lnav_config.lc_logfile; });
+
+static auto tc = injector::bind<tailer::config>::to_instance(
+ +[]() { return &lnav_config.lc_tailer; });
+
+static auto scc = injector::bind<sysclip::config>::to_instance(
+ +[]() { return &lnav_config.lc_sysclip; });
+
+static auto lsc = injector::bind<logfile_sub_source_ns::config>::to_instance(
+ +[]() { return &lnav_config.lc_log_source; });
+
+static auto tssc = injector::bind<top_status_source_cfg>::to_instance(
+ +[]() { return &lnav_config.lc_top_status_cfg; });
+
+bool
+check_experimental(const char* feature_name)
+{
+ const char* env_value = getenv("LNAV_EXP");
+
+ require(feature_name != nullptr);
+
+ if (env_value && strcasestr(env_value, feature_name)) {
+ return true;
+ }
+
+ return false;
+}
+
+void
+ensure_dotlnav()
+{
+ static const char* subdirs[] = {
+ "",
+ "configs",
+ "configs/default",
+ "configs/installed",
+ "formats",
+ "formats/default",
+ "formats/installed",
+ "staging",
+ "stdin-captures",
+ "crash",
+ };
+
+ auto path = lnav::paths::dotlnav();
+
+ for (const auto* sub_path : subdirs) {
+ auto full_path = path / sub_path;
+
+ log_perror(mkdir(full_path.c_str(), 0755));
+ }
+
+ lnav_log_crash_dir = strdup(path.c_str());
+
+ {
+ static_root_mem<glob_t, globfree> gl;
+ auto crash_glob = path / "crash/*";
+
+ if (glob(crash_glob.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
+ for (int lpc = 0; lpc < ((int) gl->gl_pathc - MAX_CRASH_LOG_COUNT);
+ lpc++)
+ {
+ log_perror(remove(gl->gl_pathv[lpc]));
+ }
+ }
+ }
+
+ {
+ static_root_mem<glob_t, globfree> gl;
+ auto cap_glob = path / "stdin-captures/*";
+
+ if (glob(cap_glob.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
+ auto old_time
+ = std::chrono::system_clock::now() - STDIN_CAPTURE_RETENTION;
+
+ for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
+ struct stat st;
+
+ if (stat(gl->gl_pathv[lpc], &st) == -1) {
+ continue;
+ }
+
+ if (std::chrono::system_clock::from_time_t(st.st_mtime)
+ > old_time)
+ {
+ continue;
+ }
+
+ log_info("Removing old stdin capture: %s", gl->gl_pathv[lpc]);
+ log_perror(remove(gl->gl_pathv[lpc]));
+ }
+ }
+ }
+}
+
+bool
+install_from_git(const std::string& repo)
+{
+ static const std::regex repo_name_converter("[^\\w]");
+
+ auto formats_path = lnav::paths::dotlnav() / "formats";
+ auto configs_path = lnav::paths::dotlnav() / "configs";
+ auto staging_path = lnav::paths::dotlnav() / "staging";
+ auto local_name = std::regex_replace(repo, repo_name_converter, "_");
+
+ auto local_formats_path = formats_path / local_name;
+ auto local_configs_path = configs_path / local_name;
+ auto local_staging_path = staging_path / local_name;
+
+ auto fork_res = lnav::pid::from_fork();
+ if (fork_res.isErr()) {
+ fprintf(stderr,
+ "error: cannot fork() to run git: %s\n",
+ fork_res.unwrapErr().c_str());
+ _exit(1);
+ }
+
+ auto git_cmd = fork_res.unwrap();
+ if (git_cmd.in_child()) {
+ if (ghc::filesystem::is_directory(local_formats_path)) {
+ fmt::print("Updating format repo: {}\n", repo);
+ log_perror(chdir(local_formats_path.c_str()));
+ execlp("git", "git", "pull", nullptr);
+ } else if (ghc::filesystem::is_directory(local_configs_path)) {
+ fmt::print("Updating config repo: {}\n", repo);
+ log_perror(chdir(local_configs_path.c_str()));
+ execlp("git", "git", "pull", nullptr);
+ } else {
+ execlp("git",
+ "git",
+ "clone",
+ repo.c_str(),
+ local_staging_path.c_str(),
+ nullptr);
+ }
+ _exit(1);
+ }
+
+ auto finished_child = std::move(git_cmd).wait_for_child();
+
+ if (!finished_child.was_normal_exit() || finished_child.exit_status() != 0)
+ {
+ return false;
+ }
+
+ if (!ghc::filesystem::is_directory(local_staging_path)) {
+ auto um
+ = lnav::console::user_message::error(
+ attr_line_t("failed to install git repo: ")
+ .append(lnav::roles::file(repo)))
+ .with_reason(
+ attr_line_t("git failed to create the local directory")
+ .append(
+ lnav::roles::file(local_staging_path.string())));
+ lnav::console::print(stderr, um);
+ return false;
+ }
+
+ auto config_path = local_staging_path / "*";
+ static_root_mem<glob_t, globfree> gl;
+ int found_config_file = 0;
+ int found_format_file = 0;
+ int found_sql_file = 0;
+ int found_lnav_file = 0;
+
+ if (glob(config_path.c_str(), 0, nullptr, gl.inout()) == 0) {
+ for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
+ auto file_path = ghc::filesystem::path{gl->gl_pathv[lpc]};
+
+ if (file_path.extension() == ".lnav") {
+ found_lnav_file += 1;
+ continue;
+ }
+ if (file_path.extension() == ".sql") {
+ found_sql_file += 1;
+ continue;
+ }
+ if (file_path.extension() != ".json") {
+ found_sql_file += 1;
+ continue;
+ }
+
+ auto file_type_result = detect_config_file_type(file_path);
+
+ if (file_type_result.isErr()) {
+ fprintf(stderr,
+ "error: %s\n",
+ file_type_result.unwrapErr().c_str());
+ return false;
+ }
+ if (file_type_result.unwrap() == config_file_type::CONFIG) {
+ found_config_file += 1;
+ } else {
+ found_format_file += 1;
+ }
+ }
+ }
+
+ if (found_config_file == 0 && found_format_file == 0 && found_sql_file == 0
+ && found_lnav_file == 0)
+ {
+ auto um = lnav::console::user_message::error(
+ attr_line_t("invalid lnav repo: ")
+ .append(lnav::roles::file(repo)))
+ .with_reason("no .json, .sql, or .lnav files were found");
+ lnav::console::print(stderr, um);
+ return false;
+ }
+
+ auto dest_path = local_formats_path;
+ attr_line_t notes;
+ if (found_format_file > 0) {
+ notes.append("found ")
+ .append(lnav::roles::number(fmt::to_string(found_format_file)))
+ .append(" format file(s)\n");
+ }
+ if (found_config_file > 0) {
+ if (found_format_file == 0) {
+ dest_path = local_configs_path;
+ }
+ notes.append("found ")
+ .append(lnav::roles::number(fmt::to_string(found_config_file)))
+ .append(" configuration file(s)\n");
+ }
+ if (found_sql_file > 0) {
+ notes.append("found ")
+ .append(lnav::roles::number(fmt::to_string(found_sql_file)))
+ .append(" SQL file(s)\n");
+ }
+ if (found_lnav_file > 0) {
+ notes.append("found ")
+ .append(lnav::roles::number(fmt::to_string(found_lnav_file)))
+ .append(" lnav-script file(s)\n");
+ }
+ rename(local_staging_path.c_str(), dest_path.c_str());
+ auto um = lnav::console::user_message::ok(
+ attr_line_t("installed lnav repo at: ")
+ .append(lnav::roles::file(local_configs_path.string())))
+ .with_note(notes);
+ lnav::console::print(stdout, um);
+
+ return true;
+}
+
+bool
+update_installs_from_git()
+{
+ static_root_mem<glob_t, globfree> gl;
+ auto git_formats = lnav::paths::dotlnav() / "formats/*/.git";
+ bool found = false, retval = true;
+
+ if (glob(git_formats.c_str(), GLOB_NOCHECK, nullptr, gl.inout()) == 0) {
+ for (int lpc = 0; lpc < (int) gl->gl_pathc; lpc++) {
+ auto git_dir
+ = ghc::filesystem::path(gl->gl_pathv[lpc]).parent_path();
+
+ printf("Updating formats in %s\n", git_dir.c_str());
+ auto pull_cmd = fmt::format(FMT_STRING("cd '{}' && git pull"),
+ git_dir.string());
+ int ret = system(pull_cmd.c_str());
+ if (ret == -1) {
+ std::cerr << "Failed to spawn command "
+ << "\"" << pull_cmd << "\": " << strerror(errno)
+ << std::endl;
+ retval = false;
+ } else if (ret > 0) {
+ std::cerr << "Command "
+ << "\"" << pull_cmd
+ << "\" failed: " << strerror(errno) << std::endl;
+ retval = false;
+ }
+ found = true;
+ }
+ }
+
+ if (!found) {
+ printf(
+ "No formats from git repositories found, "
+ "use 'lnav -i extra' to install third-party foramts\n");
+ }
+
+ return retval;
+}
+
+static int
+read_repo_path(yajlpp_parse_context* ypc, const unsigned char* str, size_t len)
+{
+ auto path = std::string((const char*) str, len);
+
+ install_from_git(path.c_str());
+
+ return 1;
+}
+
+static const struct json_path_container format_handlers = {
+ json_path_handler("format-repos#", read_repo_path),
+};
+
+void
+install_extra_formats()
+{
+ auto config_root = lnav::paths::dotlnav() / "remote-config";
+ auto_fd fd;
+
+ if (access(config_root.c_str(), R_OK) == 0) {
+ printf("Updating lnav remote config repo...\n");
+ auto pull_cmd = fmt::format(FMT_STRING("cd '{}' && git pull"),
+ config_root.string());
+ log_perror(system(pull_cmd.c_str()));
+ } else {
+ printf("Cloning lnav remote config repo...\n");
+ auto clone_cmd = fmt::format(
+ FMT_STRING(
+ "git clone https://github.com/tstack/lnav-config.git {}"),
+ config_root.string());
+ log_perror(system(clone_cmd.c_str()));
+ }
+
+ auto config_json = config_root / "remote-config.json";
+ if ((fd = lnav::filesystem::openp(config_json, O_RDONLY)) == -1) {
+ perror("Unable to open remote-config.json");
+ } else {
+ yajlpp_parse_context ypc_config(
+ intern_string::lookup(config_root.string()), &format_handlers);
+ auto_mem<yajl_handle_t> jhandle(yajl_free);
+ unsigned char buffer[4096];
+ ssize_t rc;
+
+ jhandle = yajl_alloc(&ypc_config.ypc_callbacks, nullptr, &ypc_config);
+ 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);
+ fprintf(
+ stderr, "Unable to parse remote-config.json -- %s", msg);
+ yajl_free_error(jhandle, msg);
+ return;
+ }
+ }
+ if (yajl_complete_parse(jhandle) != yajl_status_ok) {
+ auto* msg = yajl_get_error(jhandle, 1, buffer, rc);
+
+ fprintf(stderr, "Unable to parse remote-config.json -- %s", msg);
+ yajl_free_error(jhandle, msg);
+ }
+ }
+}
+
+struct config_userdata {
+ explicit config_userdata(std::vector<lnav::console::user_message>& errors)
+ : ud_errors(errors)
+ {
+ }
+
+ std::vector<lnav::console::user_message>& ud_errors;
+};
+
+static void
+config_error_reporter(const yajlpp_parse_context& ypc,
+ const lnav::console::user_message& msg)
+{
+ auto* ud = (config_userdata*) ypc.ypc_userdata;
+
+ ud->ud_errors.emplace_back(msg);
+}
+
+static const struct json_path_container key_command_handlers = {
+ yajlpp::property_handler("command")
+ .with_synopsis("<command>")
+ .with_description(
+ "The command to execute for the given key sequence. Use a script "
+ "to execute more complicated operations.")
+ .with_pattern("^[:|;].*")
+ .with_example(":goto next hour")
+ .for_field(&key_command::kc_cmd),
+ yajlpp::property_handler("alt-msg")
+ .with_synopsis("<msg>")
+ .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("(?<key_seq>(?:x[0-9a-f]{2})+)")
+ .with_synopsis("<utf8-key-code-in-hex>")
+ .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<key_command, key_map>(
+ [](const yajlpp_provider_context& ypc, key_map* km) {
+ auto& retval = km->km_seq_to_cmd[ypc.get_substr("key_seq")];
+
+ return &retval;
+ })
+ .with_path_provider<key_map>(
+ [](key_map* km, std::vector<std::string>& 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("(?<keymap_name>[\\w\\-]+)")
+ .with_description("The keymap definitions")
+ .with_obj_provider<key_map, _lnav_config>(
+ [](const yajlpp_provider_context& ypc, _lnav_config* root) {
+ key_map& retval
+ = root->lc_ui_keymaps[ypc.get_substr("keymap_name")];
+ return &retval;
+ })
+ .with_path_provider<_lnav_config>(
+ [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
+ for (const auto& iter : cfg->lc_ui_keymaps) {
+ paths_out.emplace_back(iter.first);
+ }
+ })
+ .with_children(keymap_def_handlers),
+};
+
+static const struct json_path_container global_var_handlers = {
+ yajlpp::pattern_property_handler("(?<var_name>\\w+)")
+ .with_synopsis("<name>")
+ .with_description(
+ "A global variable definition. Global variables can be referenced "
+ "in scripts, SQL statements, or commands.")
+ .with_path_provider<_lnav_config>(
+ [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
+ for (const auto& iter : cfg->lc_global_vars) {
+ paths_out.emplace_back(iter.first);
+ }
+ })
+ .for_field(&_lnav_config::lc_global_vars),
+};
+
+static const struct json_path_container style_config_handlers =
+ json_path_container{
+ yajlpp::property_handler("color")
+ .with_synopsis("#hex|color_name")
+ .with_description(
+ "The foreground color value for this style. The value can be "
+ "the name of an xterm color, the hexadecimal value, or a theme "
+ "variable reference.")
+ .with_example("#fff")
+ .with_example("Green")
+ .with_example("$black")
+ .for_field(&style_config::sc_color),
+ yajlpp::property_handler("background-color")
+ .with_synopsis("#hex|color_name")
+ .with_description(
+ "The background color value for this style. The value can be "
+ "the name of an xterm color, the hexadecimal value, or a theme "
+ "variable reference.")
+ .with_example("#2d2a2e")
+ .with_example("Green")
+ .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),
+ yajlpp::property_handler("bold")
+ .with_description("Indicates that the text should be bolded.")
+ .for_field(&style_config::sc_bold),
+ }
+ .with_definition_id("style");
+
+static const struct json_path_container theme_styles_handlers = {
+ yajlpp::property_handler("identifier")
+ .with_description("Styling for identifiers in logs")
+ .for_child(&lnav_theme::lt_style_identifier)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("text")
+ .with_description("Styling for plain text")
+ .for_child(&lnav_theme::lt_style_text)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("alt-text")
+ .with_description("Styling for plain text when alternating")
+ .for_child(&lnav_theme::lt_style_alt_text)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("error")
+ .with_description("Styling for error messages")
+ .for_child(&lnav_theme::lt_style_error)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("ok")
+ .with_description("Styling for success messages")
+ .for_child(&lnav_theme::lt_style_ok)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("info")
+ .with_description("Styling for informational messages")
+ .for_child(&lnav_theme::lt_style_info)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("warning")
+ .with_description("Styling for warning messages")
+ .for_child(&lnav_theme::lt_style_warning)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("hidden")
+ .with_description("Styling for hidden fields in logs")
+ .for_child(&lnav_theme::lt_style_hidden)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("adjusted-time")
+ .with_description("Styling for timestamps that have been adjusted")
+ .for_child(&lnav_theme::lt_style_adjusted_time)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("skewed-time")
+ .with_description(
+ "Styling for timestamps that are different from the received time")
+ .for_child(&lnav_theme::lt_style_skewed_time)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("offset-time")
+ .with_description("Styling for hidden fields")
+ .for_child(&lnav_theme::lt_style_offset_time)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("invalid-msg")
+ .with_description("Styling for invalid log messages")
+ .for_child(&lnav_theme::lt_style_invalid_msg)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("popup")
+ .with_description("Styling for popup windows")
+ .for_child(&lnav_theme::lt_style_popup)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("focused")
+ .with_description("Styling for a focused row in a list view")
+ .for_child(&lnav_theme::lt_style_focused)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("disabled-focused")
+ .with_description("Styling for a disabled focused row in a list view")
+ .for_child(&lnav_theme::lt_style_disabled_focused)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("scrollbar")
+ .with_description("Styling for scrollbars")
+ .for_child(&lnav_theme::lt_style_scrollbar)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("h1")
+ .with_description("Styling for top-level headers")
+ .with_obj_provider<style_config, lnav_theme>(
+ [](const yajlpp_provider_context& ypc, lnav_theme* root) {
+ return &root->lt_style_header[0].pp_value;
+ })
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("h2")
+ .with_description("Styling for 2nd-level headers")
+ .with_obj_provider<style_config, lnav_theme>(
+ [](const yajlpp_provider_context& ypc, lnav_theme* root) {
+ return &root->lt_style_header[1].pp_value;
+ })
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("h3")
+ .with_description("Styling for 3rd-level headers")
+ .with_obj_provider<style_config, lnav_theme>(
+ [](const yajlpp_provider_context& ypc, lnav_theme* root) {
+ return &root->lt_style_header[2].pp_value;
+ })
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("h4")
+ .with_description("Styling for 4th-level headers")
+ .with_obj_provider<style_config, lnav_theme>(
+ [](const yajlpp_provider_context& ypc, lnav_theme* root) {
+ return &root->lt_style_header[3].pp_value;
+ })
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("h5")
+ .with_description("Styling for 5th-level headers")
+ .with_obj_provider<style_config, lnav_theme>(
+ [](const yajlpp_provider_context& ypc, lnav_theme* root) {
+ return &root->lt_style_header[4].pp_value;
+ })
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("h6")
+ .with_description("Styling for 6th-level headers")
+ .with_obj_provider<style_config, lnav_theme>(
+ [](const yajlpp_provider_context& ypc, lnav_theme* root) {
+ return &root->lt_style_header[5].pp_value;
+ })
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("hr")
+ .with_description("Styling for horizontal rules")
+ .for_child(&lnav_theme::lt_style_hr)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("hyperlink")
+ .with_description("Styling for hyperlinks")
+ .for_child(&lnav_theme::lt_style_hyperlink)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("list-glyph")
+ .with_description("Styling for glyphs that prefix a list item")
+ .for_child(&lnav_theme::lt_style_list_glyph)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("breadcrumb")
+ .with_description("Styling for the separator between breadcrumbs")
+ .for_child(&lnav_theme::lt_style_breadcrumb)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("table-border")
+ .with_description("Styling for table borders")
+ .for_child(&lnav_theme::lt_style_table_border)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("table-header")
+ .with_description("Styling for table headers")
+ .for_child(&lnav_theme::lt_style_table_header)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("quote-border")
+ .with_description("Styling for quoted-block borders")
+ .for_child(&lnav_theme::lt_style_quote_border)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("quoted-text")
+ .with_description("Styling for quoted text blocks")
+ .for_child(&lnav_theme::lt_style_quoted_text)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("footnote-border")
+ .with_description("Styling for footnote borders")
+ .for_child(&lnav_theme::lt_style_footnote_border)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("footnote-text")
+ .with_description("Styling for footnote text")
+ .for_child(&lnav_theme::lt_style_footnote_text)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("snippet-border")
+ .with_description("Styling for snippet borders")
+ .for_child(&lnav_theme::lt_style_snippet_border)
+ .with_children(style_config_handlers),
+};
+
+static const struct json_path_container theme_syntax_styles_handlers = {
+ yajlpp::property_handler("quoted-code")
+ .with_description("Styling for quoted code blocks")
+ .for_child(&lnav_theme::lt_style_quoted_code)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("code-border")
+ .with_description("Styling for quoted-code borders")
+ .for_child(&lnav_theme::lt_style_code_border)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("keyword")
+ .with_description("Styling for keywords in source files")
+ .for_child(&lnav_theme::lt_style_keyword)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("string")
+ .with_description("Styling for single/double-quoted strings in text")
+ .for_child(&lnav_theme::lt_style_string)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("comment")
+ .with_description("Styling for comments in source files")
+ .for_child(&lnav_theme::lt_style_comment)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("doc-directive")
+ .with_description(
+ "Styling for documentation directives in source files")
+ .for_child(&lnav_theme::lt_style_doc_directive)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("variable")
+ .with_description("Styling for variables in text")
+ .for_child(&lnav_theme::lt_style_variable)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("symbol")
+ .with_description("Styling for symbols in source files")
+ .for_child(&lnav_theme::lt_style_symbol)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("number")
+ .with_description("Styling for numbers in source files")
+ .for_child(&lnav_theme::lt_style_number)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("re-special")
+ .with_description(
+ "Styling for special characters in regular expressions")
+ .for_child(&lnav_theme::lt_style_re_special)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("re-repeat")
+ .with_description("Styling for repeats in regular expressions")
+ .for_child(&lnav_theme::lt_style_re_repeat)
+ .with_children(style_config_handlers),
+
+ yajlpp::property_handler("diff-delete")
+ .with_description("Styling for deleted lines in diffs")
+ .for_child(&lnav_theme::lt_style_diff_delete)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("diff-add")
+ .with_description("Styling for added lines in diffs")
+ .for_child(&lnav_theme::lt_style_diff_add)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("diff-section")
+ .with_description("Styling for diffs")
+ .for_child(&lnav_theme::lt_style_diff_section)
+ .with_children(style_config_handlers),
+
+ yajlpp::property_handler("spectrogram-low")
+ .with_description(
+ "Styling for the lower threshold values in the spectrogram view")
+ .for_child(&lnav_theme::lt_style_low_threshold)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("spectrogram-medium")
+ .with_description(
+ "Styling for the medium threshold values in the spectrogram view")
+ .for_child(&lnav_theme::lt_style_med_threshold)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("spectrogram-high")
+ .with_description(
+ "Styling for the high threshold values in the spectrogram view")
+ .for_child(&lnav_theme::lt_style_high_threshold)
+ .with_children(style_config_handlers),
+
+ yajlpp::property_handler("file")
+ .with_description("Styling for file names in source files")
+ .for_child(&lnav_theme::lt_style_file)
+ .with_children(style_config_handlers),
+};
+
+static const struct json_path_container theme_status_styles_handlers = {
+ yajlpp::property_handler("text")
+ .with_description("Styling for status bars")
+ .for_child(&lnav_theme::lt_style_status)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("warn")
+ .with_description("Styling for warnings in status bars")
+ .for_child(&lnav_theme::lt_style_warn_status)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("alert")
+ .with_description("Styling for alerts in status bars")
+ .for_child(&lnav_theme::lt_style_alert_status)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("active")
+ .with_description("Styling for activity in status bars")
+ .for_child(&lnav_theme::lt_style_active_status)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("inactive-alert")
+ .with_description("Styling for inactive alert status bars")
+ .for_child(&lnav_theme::lt_style_inactive_alert_status)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("inactive")
+ .with_description("Styling for inactive status bars")
+ .for_child(&lnav_theme::lt_style_inactive_status)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("title-hotkey")
+ .with_description("Styling for hotkey highlights in titles")
+ .for_child(&lnav_theme::lt_style_status_title_hotkey)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("title")
+ .with_description("Styling for title sections of status bars")
+ .for_child(&lnav_theme::lt_style_status_title)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("disabled-title")
+ .with_description("Styling for title sections of status bars")
+ .for_child(&lnav_theme::lt_style_status_disabled_title)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("subtitle")
+ .with_description("Styling for subtitle sections of status bars")
+ .for_child(&lnav_theme::lt_style_status_subtitle)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("info")
+ .with_description("Styling for informational messages in status bars")
+ .for_child(&lnav_theme::lt_style_status_info)
+ .with_children(style_config_handlers),
+ yajlpp::property_handler("hotkey")
+ .with_description("Styling for hotkey highlights of status bars")
+ .for_child(&lnav_theme::lt_style_status_hotkey)
+ .with_children(style_config_handlers),
+};
+
+static const struct json_path_container theme_log_level_styles_handlers = {
+ yajlpp::pattern_property_handler(
+ "(?<level>trace|debug5|debug4|debug3|debug2|debug|info|stats|notice|"
+ "warning|error|critical|fatal|invalid)")
+ .with_obj_provider<style_config, lnav_theme>(
+ [](const yajlpp_provider_context& ypc, lnav_theme* root) {
+ auto& sc = root->lt_level_styles[string2level(
+ ypc.get_substr_i("level").get())];
+
+ if (ypc.ypc_parse_context != nullptr && sc.pp_path.empty()) {
+ sc.pp_path = ypc.ypc_parse_context->get_full_path();
+ }
+
+ return &sc.pp_value;
+ })
+ .with_path_provider<lnav_theme>(
+ [](struct lnav_theme* cfg, std::vector<std::string>& 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")
+ .with_synopsis("regular expression")
+ .with_description("The regular expression to highlight")
+ .for_field(&highlighter_config::hc_regex),
+
+ yajlpp::property_handler("style")
+ .with_description(
+ "The styling for the text that matches the associated pattern")
+ .for_child(&highlighter_config::hc_style)
+ .with_children(style_config_handlers),
+};
+
+static const struct json_path_container theme_highlights_handlers = {
+ yajlpp::pattern_property_handler("(?<highlight_name>[\\w\\-]+)")
+ .with_obj_provider<highlighter_config,
+ lnav_theme>([](const yajlpp_provider_context& ypc,
+ lnav_theme* root) {
+ highlighter_config& hc
+ = root->lt_highlights[ypc.get_substr_i("highlight_name").get()];
+
+ return &hc;
+ })
+ .with_path_provider<lnav_theme>(
+ [](struct lnav_theme* cfg, std::vector<std::string>& 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("(?<var_name>\\w+)")
+ .with_synopsis("name")
+ .with_description("A theme variable definition")
+ .with_path_provider<lnav_theme>(
+ [](struct lnav_theme* lt, std::vector<std::string>& 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")
+ .with_description("Variables definitions that are used in this theme.")
+ .with_children(theme_vars_handlers),
+
+ yajlpp::property_handler("styles")
+ .with_description("Styles for log messages.")
+ .with_children(theme_styles_handlers),
+
+ yajlpp::property_handler("syntax-styles")
+ .with_description("Styles for syntax highlighting in text files.")
+ .with_children(theme_syntax_styles_handlers),
+
+ yajlpp::property_handler("status-styles")
+ .with_description("Styles for the user-interface components.")
+ .with_children(theme_status_styles_handlers),
+
+ yajlpp::property_handler("log-level-styles")
+ .with_description("Styles for each log message level.")
+ .with_children(theme_log_level_styles_handlers),
+
+ yajlpp::property_handler("highlights")
+ .with_description("Styles for text highlights.")
+ .with_children(theme_highlights_handlers),
+};
+
+static const struct json_path_container theme_defs_handlers = {
+ yajlpp::pattern_property_handler("(?<theme_name>[\\w\\-]+)")
+ .with_description("Theme definitions")
+ .with_obj_provider<lnav_theme, _lnav_config>(
+ [](const yajlpp_provider_context& ypc, _lnav_config* root) {
+ lnav_theme& lt
+ = root->lc_ui_theme_defs[ypc.get_substr("theme_name")];
+
+ return &lt;
+ })
+ .with_path_provider<_lnav_config>(
+ [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
+ for (const auto& iter : cfg->lc_ui_theme_defs) {
+ paths_out.emplace_back(iter.first);
+ }
+ })
+ .with_obj_deleter(
+ +[](const yajlpp_provider_context& ypc, _lnav_config* root) {
+ root->lc_ui_theme_defs.erase(ypc.get_substr("theme_name"));
+ })
+ .with_children(theme_def_handlers),
+};
+
+static const struct json_path_container ui_handlers = {
+ yajlpp::property_handler("clock-format")
+ .with_synopsis("format")
+ .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_top_status_cfg,
+ &top_status_source_cfg::tssc_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),
+ yajlpp::property_handler("default-colors")
+ .with_synopsis("bool")
+ .with_description(
+ "Use default terminal background and foreground colors "
+ "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),
+ yajlpp::property_handler("keymap")
+ .with_synopsis("keymap_name")
+ .with_description("The name of the keymap to use.")
+ .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),
+ yajlpp::property_handler("theme-defs")
+ .with_description("Theme definitions.")
+ .with_children(theme_defs_handlers),
+ yajlpp::property_handler("keymap-defs")
+ .with_description("Keymap definitions.")
+ .with_children(keymap_defs_handlers),
+};
+
+static const struct json_path_container archive_handlers = {
+ yajlpp::property_handler("min-free-space")
+ .with_synopsis("<bytes>")
+ .with_description(
+ "The minimum free space, in bytes, to maintain when unpacking "
+ "archives")
+ .with_min_value(0)
+ .for_field(&_lnav_config::lc_archive_manager,
+ &archive_manager::config::amc_min_free_space),
+ yajlpp::property_handler("cache-ttl")
+ .with_synopsis("<duration>")
+ .with_description(
+ "The time-to-live for unpacked archives, expressed as a duration "
+ "(e.g. '3d' for three days)")
+ .with_example("3d")
+ .with_example("12h")
+ .for_field(&_lnav_config::lc_archive_manager,
+ &archive_manager::config::amc_cache_ttl),
+};
+
+static const struct json_path_container file_vtab_handlers = {
+ yajlpp::property_handler("max-content-size")
+ .with_synopsis("<bytes>")
+ .with_description(
+ "The maximum allowed file size for the content column")
+ .with_min_value(0)
+ .for_field(&_lnav_config::lc_file_vtab,
+ &file_vtab::config::fvc_max_content_size),
+};
+
+static const struct json_path_container logfile_handlers = {
+ yajlpp::property_handler("max-unrecognized-lines")
+ .with_synopsis("<lines>")
+ .with_description("The maximum number of lines in a file to use when "
+ "detecting the format")
+ .with_min_value(1)
+ .for_field(&_lnav_config::lc_logfile,
+ &lnav::logfile::config::lc_max_unrecognized_lines),
+};
+
+static const struct json_path_container ssh_config_handlers = {
+ yajlpp::pattern_property_handler("(?<config_name>\\w+)")
+ .with_synopsis("name")
+ .with_description("Set an SSH configuration value")
+ .with_path_provider<_lnav_config>(
+ [](auto* m, std::vector<std::string>& paths_out) {
+ for (const auto& pair : m->lc_tailer.c_ssh_config) {
+ paths_out.emplace_back(pair.first);
+ }
+ })
+ .for_field(&_lnav_config::lc_tailer, &tailer::config::c_ssh_config),
+};
+
+static const struct json_path_container ssh_option_handlers = {
+ yajlpp::pattern_property_handler("(?<option_name>\\w+)")
+ .with_synopsis("name")
+ .with_description("Set an option to be passed to the SSH command")
+ .for_field(&_lnav_config::lc_tailer, &tailer::config::c_ssh_options),
+};
+
+static const struct json_path_container ssh_handlers = {
+ yajlpp::property_handler("command")
+ .with_synopsis("ssh-command")
+ .with_description("The SSH command to execute")
+ .for_field(&_lnav_config::lc_tailer, &tailer::config::c_ssh_cmd),
+ yajlpp::property_handler("transfer-command")
+ .with_synopsis("command")
+ .with_description(
+ "Command executed on the remote host when transferring the file")
+ .for_field(&_lnav_config::lc_tailer, &tailer::config::c_transfer_cmd),
+ yajlpp::property_handler("start-command")
+ .with_synopsis("command")
+ .with_description(
+ "Command executed on the remote host to start the tailer")
+ .for_field(&_lnav_config::lc_tailer, &tailer::config::c_start_cmd),
+ yajlpp::property_handler("flags")
+ .with_description("The flags to pass to the SSH command")
+ .for_field(&_lnav_config::lc_tailer, &tailer::config::c_ssh_flags),
+ yajlpp::property_handler("options")
+ .with_description("The options to pass to the SSH command")
+ .with_children(ssh_option_handlers),
+ yajlpp::property_handler("config")
+ .with_description(
+ "The ssh_config options to pass to SSH with the -o option")
+ .with_children(ssh_config_handlers),
+};
+
+static const struct json_path_container remote_handlers = {
+ yajlpp::property_handler("cache-ttl")
+ .with_synopsis("<duration>")
+ .with_description("The time-to-live for files copied from remote "
+ "hosts, expressed as a duration "
+ "(e.g. '3d' for three days)")
+ .with_example("3d")
+ .with_example("12h")
+ .for_field(&_lnav_config::lc_tailer, &tailer::config::c_cache_ttl),
+ yajlpp::property_handler("ssh")
+ .with_description(
+ "Settings related to the ssh command used to contact remote "
+ "machines")
+ .with_children(ssh_handlers),
+};
+
+static const struct json_path_container sysclip_impl_cmd_handlers = json_path_container{
+ yajlpp::property_handler("write")
+ .with_synopsis("<command>")
+ .with_description("The command used to write to the clipboard")
+ .with_example("pbcopy")
+ .for_field(&sysclip::clip_commands::cc_write),
+ yajlpp::property_handler("read")
+ .with_synopsis("<command>")
+ .with_description("The command used to read from the clipboard")
+ .with_example("pbpaste")
+ .for_field(&sysclip::clip_commands::cc_read),
+}
+ .with_description("Container for the commands used to read from and write to the system clipboard")
+ .with_definition_id("clip-commands");
+
+static const struct json_path_container sysclip_impl_handlers = {
+ yajlpp::property_handler("test")
+ .with_synopsis("<command>")
+ .with_description("The command that checks")
+ .with_example("command -v pbcopy")
+ .for_field(&sysclip::clipboard::c_test_command),
+ yajlpp::property_handler("general")
+ .with_description("Commands to work with the general clipboard")
+ .for_child(&sysclip::clipboard::c_general)
+ .with_children(sysclip_impl_cmd_handlers),
+ yajlpp::property_handler("find")
+ .with_description("Commands to work with the find clipboard")
+ .for_child(&sysclip::clipboard::c_find)
+ .with_children(sysclip_impl_cmd_handlers),
+};
+
+static const struct json_path_container sysclip_impls_handlers = {
+ yajlpp::pattern_property_handler("(?<clipboard_impl_name>[\\w\\-]+)")
+ .with_synopsis("<name>")
+ .with_description("Clipboard implementation")
+ .with_obj_provider<sysclip::clipboard, _lnav_config>(
+ [](const yajlpp_provider_context& ypc, _lnav_config* root) {
+ auto& retval
+ = root->lc_sysclip.c_clipboard_impls[ypc.get_substr(
+ "clipboard_impl_name")];
+ return &retval;
+ })
+ .with_path_provider<_lnav_config>(
+ [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
+ for (const auto& iter : cfg->lc_sysclip.c_clipboard_impls) {
+ paths_out.emplace_back(iter.first);
+ }
+ })
+ .with_children(sysclip_impl_handlers),
+};
+
+static const struct json_path_container sysclip_handlers = {
+ yajlpp::property_handler("impls")
+ .with_description("Clipboard implementations")
+ .with_children(sysclip_impls_handlers),
+};
+
+static const struct json_path_container log_source_watch_expr_handlers = {
+ yajlpp::property_handler("expr")
+ .with_synopsis("<SQL-expression>")
+ .with_description("The SQL expression to execute for each input line. "
+ "If expression evaluates to true, a 'log message "
+ "detected' event will be published.")
+ .for_field(&logfile_sub_source_ns::watch_expression::we_expr),
+ yajlpp::property_handler("enabled")
+ .with_description("Indicates whether or not this expression should be "
+ "evaluated during log processing.")
+ .for_field(&logfile_sub_source_ns::watch_expression::we_enabled),
+};
+
+static const struct json_path_container log_source_watch_handlers = {
+ yajlpp::pattern_property_handler("(?<watch_name>[\\w\\-]+)")
+ .with_synopsis("<name>")
+ .with_description("A log message watch expression")
+ .with_obj_provider<logfile_sub_source_ns::watch_expression,
+ _lnav_config>(
+ [](const yajlpp_provider_context& ypc, _lnav_config* root) {
+ auto& retval = root->lc_log_source
+ .c_watch_exprs[ypc.get_substr("watch_name")];
+ return &retval;
+ })
+ .with_path_provider<_lnav_config>(
+ [](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
+ for (const auto& iter : cfg->lc_log_source.c_watch_exprs) {
+ paths_out.emplace_back(iter.first);
+ }
+ })
+ .with_obj_deleter(
+ +[](const yajlpp_provider_context& ypc, _lnav_config* root) {
+ root->lc_log_source.c_watch_exprs.erase(
+ ypc.get_substr("watch_name"));
+ })
+ .with_children(log_source_watch_expr_handlers),
+};
+
+static const struct json_path_container log_source_handlers = {
+ yajlpp::property_handler("watch-expressions")
+ .with_description("Log message watch expressions")
+ .with_children(log_source_watch_handlers),
+};
+
+static const struct json_path_container tuning_handlers = {
+ yajlpp::property_handler("archive-manager")
+ .with_description("Settings related to opening archive files")
+ .with_children(archive_handlers),
+ yajlpp::property_handler("file-vtab")
+ .with_description("Settings related to the lnav_file virtual-table")
+ .with_children(file_vtab_handlers),
+ yajlpp::property_handler("logfile")
+ .with_description("Settings related to log files")
+ .with_children(logfile_handlers),
+ yajlpp::property_handler("remote")
+ .with_description("Settings related to remote file support")
+ .with_children(remote_handlers),
+ yajlpp::property_handler("clipboard")
+ .with_description("Settings related to the clipboard")
+ .with_children(sysclip_handlers),
+};
+
+const char* DEFAULT_CONFIG_SCHEMA
+ = "https://lnav.org/schemas/config-v1.schema.json";
+
+static const std::set<std::string> SUPPORTED_CONFIG_SCHEMAS = {
+ DEFAULT_CONFIG_SCHEMA,
+};
+
+const char* DEFAULT_FORMAT_SCHEMA
+ = "https://lnav.org/schemas/format-v1.schema.json";
+
+const std::set<std::string> SUPPORTED_FORMAT_SCHEMAS = {
+ DEFAULT_FORMAT_SCHEMA,
+};
+
+static int
+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::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;
+}
+
+const json_path_container lnav_config_handlers = json_path_container {
+ json_path_handler("$schema", read_id)
+ .with_synopsis("<schema-uri>")
+ .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")
+ .with_children(tuning_handlers),
+
+ yajlpp::property_handler("ui")
+ .with_description("User-interface settings")
+ .with_children(ui_handlers),
+
+ yajlpp::property_handler("log")
+ .with_description("Log message settings")
+ .with_children(log_source_handlers),
+
+ yajlpp::property_handler("global")
+ .with_description("Global variable definitions")
+ .with_children(global_var_handlers),
+}
+ .with_schema_id(*SUPPORTED_CONFIG_SCHEMAS.cbegin());
+
+class active_key_map_listener : public lnav_config_listener {
+public:
+ void reload_config(error_reporter& reporter) override
+ {
+ lnav_config.lc_active_keymap = lnav_config.lc_ui_keymaps["default"];
+ for (const auto& pair :
+ lnav_config.lc_ui_keymaps[lnav_config.lc_ui_keymap].km_seq_to_cmd)
+ {
+ lnav_config.lc_active_keymap.km_seq_to_cmd[pair.first]
+ = pair.second;
+ }
+ }
+};
+
+static active_key_map_listener KEYMAP_LISTENER;
+
+Result<config_file_type, std::string>
+detect_config_file_type(const ghc::filesystem::path& path)
+{
+ static const char* id_path[] = {"$schema", nullptr};
+
+ auto content = TRY(lnav::filesystem::read_file(path));
+ if (startswith(content, "#")) {
+ content.insert(0, "//");
+ }
+
+ char error_buffer[1024];
+ auto content_tree = std::unique_ptr<yajl_val_s, decltype(&yajl_tree_free)>(
+ yajl_tree_parse(content.c_str(), error_buffer, sizeof(error_buffer)),
+ yajl_tree_free);
+ if (content_tree == nullptr) {
+ 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);
+ if (id_val != nullptr) {
+ if (SUPPORTED_CONFIG_SCHEMAS.count(id_val->u.string)) {
+ return Ok(config_file_type::CONFIG);
+ }
+ if (SUPPORTED_FORMAT_SCHEMAS.count(id_val->u.string)) {
+ return Ok(config_file_type::FORMAT);
+ }
+ return Err(fmt::format(
+ FMT_STRING("unsupported configuration version in file -- {}"),
+ id_val->u.string));
+ }
+ return Ok(config_file_type::FORMAT);
+}
+
+static void
+load_config_from(_lnav_config& lconfig,
+ const ghc::filesystem::path& path,
+ std::vector<lnav::console::user_message>& errors)
+{
+ yajlpp_parse_context ypc(intern_string::lookup(path.string()),
+ &lnav_config_handlers);
+ struct config_userdata ud(errors);
+ auto_fd fd;
+
+ ypc.ypc_locations = &lnav_config_locations;
+ ypc.with_obj(lconfig);
+ ypc.ypc_userdata = &ud;
+ ypc.with_error_reporter(config_error_reporter);
+ if ((fd = lnav::filesystem::openp(path, O_RDONLY)) == -1) {
+ if (errno != ENOENT) {
+ 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<yajl_handle_t> handle(yajl_free);
+ char buffer[2048];
+ ssize_t rc = -1;
+
+ handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
+ yajl_config(handle, yajl_allow_comments, 1);
+ yajl_config(handle, yajl_allow_multiple_values, 1);
+ ypc.ypc_handle = handle;
+ while (true) {
+ rc = read(fd, buffer, sizeof(buffer));
+ if (rc == 0) {
+ break;
+ }
+ if (rc == -1) {
+ errors.emplace_back(
+ 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;
+ }
+ }
+ if (rc == 0) {
+ ypc.complete_parse();
+ }
+ }
+}
+
+static bool
+load_default_config(struct _lnav_config& config_obj,
+ const std::string& path,
+ const bin_src_file& bsf,
+ std::vector<lnav::console::user_message>& errors)
+{
+ yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()),
+ &lnav_config_handlers);
+ auto_mem<yajl_handle_t> handle(yajl_free);
+ struct config_userdata ud(errors);
+
+ handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin);
+ ypc_builtin.ypc_locations = &lnav_config_locations;
+ ypc_builtin.with_handle(handle);
+ ypc_builtin.with_obj(config_obj);
+ ypc_builtin.with_error_reporter(config_error_reporter);
+ ypc_builtin.ypc_userdata = &ud;
+
+ if (path != "*") {
+ ypc_builtin.ypc_ignore_unused = true;
+ ypc_builtin.ypc_active_paths.insert(path);
+ }
+
+ yajl_config(handle, yajl_allow_comments, 1);
+ yajl_config(handle, yajl_allow_multiple_values, 1);
+ if (ypc_builtin.parse(bsf.to_string_fragment()) == yajl_status_ok) {
+ ypc_builtin.complete_parse();
+ }
+
+ return path == "*" || ypc_builtin.ypc_active_paths.empty();
+}
+
+static bool
+load_default_configs(struct _lnav_config& config_obj,
+ const std::string& path,
+ std::vector<lnav::console::user_message>& errors)
+{
+ auto retval = false;
+
+ for (auto& bsf : lnav_config_json) {
+ retval = load_default_config(config_obj, path, bsf, errors) || retval;
+ }
+
+ return retval;
+}
+
+void
+load_config(const std::vector<ghc::filesystem::path>& extra_paths,
+ std::vector<lnav::console::user_message>& errors)
+{
+ auto user_config = lnav::paths::dotlnav() / "config.json";
+
+ for (auto& bsf : lnav_config_json) {
+ auto sample_path = lnav::paths::dotlnav() / "configs" / "default"
+ / fmt::format(FMT_STRING("{}.sample"), bsf.get_name());
+
+ auto write_res = lnav::filesystem::write_file(sample_path,
+ bsf.to_string_fragment());
+ if (write_res.isErr()) {
+ fprintf(stderr,
+ "error:unable to write default config file: %s -- %s\n",
+ sample_path.c_str(),
+ write_res.unwrapErr().c_str());
+ }
+ }
+
+ {
+ log_info("loading builtin configuration into default");
+ load_default_configs(lnav_default_config, "*", errors);
+ log_info("loading builtin configuration into base");
+ load_default_configs(lnav_config, "*", errors);
+
+ log_info("loading user configuration files");
+ for (const auto& extra_path : extra_paths) {
+ auto config_path = extra_path / "configs/*/*.json";
+ static_root_mem<glob_t, globfree> gl;
+
+ if (glob(config_path.c_str(), 0, nullptr, gl.inout()) == 0) {
+ for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
+ load_config_from(lnav_config, gl->gl_pathv[lpc], errors);
+ if (errors.empty()) {
+ load_config_from(
+ lnav_default_config, gl->gl_pathv[lpc], errors);
+ }
+ }
+ }
+ }
+ for (const auto& extra_path : extra_paths) {
+ auto config_path = extra_path / "formats/*/config.*.json";
+ static_root_mem<glob_t, globfree> gl;
+
+ if (glob(config_path.c_str(), 0, nullptr, gl.inout()) == 0) {
+ for (size_t lpc = 0; lpc < gl->gl_pathc; lpc++) {
+ load_config_from(lnav_config, gl->gl_pathv[lpc], errors);
+ if (errors.empty()) {
+ load_config_from(
+ lnav_default_config, gl->gl_pathv[lpc], errors);
+ }
+ }
+ }
+ }
+
+ log_info("loading user configuration");
+ load_config_from(lnav_config, user_config, errors);
+ }
+
+ reload_config(errors);
+
+ rollback_lnav_config = lnav_config;
+}
+
+void
+reset_config(const std::string& path)
+{
+ std::vector<lnav::console::user_message> errors;
+
+ load_default_configs(lnav_config, path, errors);
+ if (path != "*") {
+ static const auto INPUT_SRC = intern_string::lookup("input");
+
+ yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
+ ypc.set_path(path)
+ .with_obj(lnav_config)
+ .with_error_reporter([&errors](const auto& ypc, auto msg) {
+ errors.push_back(msg);
+ });
+ ypc.ypc_active_paths.insert(path);
+ ypc.update_callbacks();
+ const json_path_handler_base* jph = ypc.ypc_current_handler;
+
+ if (!ypc.ypc_handler_stack.empty()) {
+ jph = ypc.ypc_handler_stack.back();
+ }
+
+ if (jph != nullptr && jph->jph_children && jph->jph_obj_deleter) {
+ auto key_start = ypc.ypc_path_index_stack.back();
+ auto path_frag = string_fragment::from_byte_range(
+ ypc.ypc_path.data(), key_start + 1, ypc.ypc_path.size());
+ auto md = jph->jph_regex->create_match_data();
+ yajlpp_provider_context provider_ctx{&md, static_cast<size_t>(-1)};
+ jph->jph_regex->capture_from(path_frag).into(md).matches();
+
+ jph->jph_obj_deleter(provider_ctx, ypc.ypc_obj_stack.top());
+ }
+ }
+
+ reload_config(errors);
+
+ for (const auto& err : errors) {
+ log_debug("reset %s", err.um_message.get_string().c_str());
+ }
+}
+
+std::string
+save_config()
+{
+ auto user_config = lnav::paths::dotlnav() / "config.json";
+
+ yajlpp_gen gen;
+ yajlpp_gen_context ygc(gen, lnav_config_handlers);
+
+ ygc.with_default_obj(lnav_default_config).with_obj(lnav_config);
+ ygc.gen();
+
+ auto config_str = gen.to_string_fragment().to_string();
+ char errbuf[1024];
+ auto_mem<yajl_val_s> tree(yajl_tree_free);
+
+ tree = yajl_tree_parse(config_str.c_str(), errbuf, sizeof(errbuf));
+
+ if (tree == nullptr) {
+ return fmt::format(
+ FMT_STRING("error: unable to save configuration -- {}"), errbuf);
+ }
+
+ yajl_cleanup_tree(tree);
+
+ yajlpp_gen clean_gen;
+
+ yajl_gen_config(clean_gen, yajl_gen_beautify, true);
+ yajl_gen_tree(clean_gen, tree);
+
+ auto write_res = lnav::filesystem::write_file(
+ user_config, clean_gen.to_string_fragment());
+ if (write_res.isErr()) {
+ return fmt::format(
+ FMT_STRING("error: unable to write configuration file: {} -- {}"),
+ user_config.string(),
+ write_res.unwrapErr());
+ }
+
+ return "info: configuration saved";
+}
+
+void
+reload_config(std::vector<lnav::console::user_message>& errors)
+{
+ lnav_config_listener* curr = lnav_config_listener::LISTENER_LIST;
+
+ while (curr != nullptr) {
+ auto reporter = [&errors](const void* cfg_value,
+ const lnav::console::user_message& errmsg) {
+ auto cb = [&cfg_value, &errors, &errmsg](
+ const json_path_handler_base& jph,
+ const std::string& path,
+ void* mem) {
+ if (mem != cfg_value) {
+ return;
+ }
+
+ auto loc_iter
+ = lnav_config_locations.find(intern_string::lookup(path));
+ if (loc_iter == lnav_config_locations.end()) {
+ return;
+ }
+
+ 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, "")
+ .with_line(loc_iter->second.sl_line_number))
+ .with_help(jph.get_help_text(path)));
+ };
+
+ for (const auto& jph : lnav_config_handlers.jpc_children) {
+ jph.walk(cb, &lnav_config);
+ }
+ };
+
+ curr->reload_config(reporter);
+ curr = curr->lcl_next;
+ }
+}