summaryrefslogtreecommitdiffstats
path: root/src/lnav.management_cli.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lnav.management_cli.cc')
-rw-r--r--src/lnav.management_cli.cc910
1 files changed, 910 insertions, 0 deletions
diff --git a/src/lnav.management_cli.cc b/src/lnav.management_cli.cc
new file mode 100644
index 0000000..71d3994
--- /dev/null
+++ b/src/lnav.management_cli.cc
@@ -0,0 +1,910 @@
+/**
+ * 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 <queue>
+
+#include "lnav.management_cli.hh"
+
+#include "base/itertools.hh"
+#include "base/result.h"
+#include "base/string_util.hh"
+#include "fmt/format.h"
+#include "itertools.similar.hh"
+#include "log_format.hh"
+#include "log_format_ext.hh"
+#include "mapbox/variant.hpp"
+#include "regex101.import.hh"
+#include "session_data.hh"
+
+using namespace lnav::roles::literals;
+
+namespace lnav {
+
+namespace management {
+
+struct no_subcmd_t {
+ CLI::App* ns_root_app{nullptr};
+};
+
+inline attr_line_t&
+symbol_reducer(const std::string& elem, attr_line_t& accum)
+{
+ return accum.append("\n ").append(lnav::roles::symbol(elem));
+}
+
+inline attr_line_t&
+subcmd_reducer(const CLI::App* app, attr_line_t& accum)
+{
+ return accum.append("\n \u2022 ")
+ .append(lnav::roles::keyword(app->get_name()))
+ .append(": ")
+ .append(app->get_description());
+}
+
+struct subcmd_format_t {
+ using action_t = std::function<perform_result_t(const subcmd_format_t&)>;
+
+ CLI::App* sf_format_app{nullptr};
+ std::string sf_name;
+ CLI::App* sf_regex_app{nullptr};
+ std::string sf_regex_name;
+ CLI::App* sf_regex101_app{nullptr};
+ action_t sf_action;
+
+ subcmd_format_t& set_action(action_t act)
+ {
+ if (!this->sf_action) {
+ this->sf_action = std::move(act);
+ }
+ return *this;
+ }
+
+ Result<std::shared_ptr<log_format>, console::user_message> validate_format()
+ const
+ {
+ if (this->sf_name.empty()) {
+ auto um = console::user_message::error(
+ "expecting a format name to operate on");
+ um.with_note(
+ (log_format::get_root_formats()
+ | lnav::itertools::map(&log_format::get_name)
+ | lnav::itertools::sort_with(intern_string_t::case_lt)
+ | lnav::itertools::map(&intern_string_t::to_string)
+ | lnav::itertools::fold(symbol_reducer, attr_line_t{}))
+ .add_header("the available formats are:"));
+
+ return Err(um);
+ }
+
+ auto lformat = log_format::find_root_format(this->sf_name.c_str());
+ if (lformat == nullptr) {
+ auto um = console::user_message::error(
+ attr_line_t("unknown format: ")
+ .append(lnav::roles::symbol(this->sf_name)));
+ um.with_note(
+ (log_format::get_root_formats()
+ | lnav::itertools::map(&log_format::get_name)
+ | lnav::itertools::similar_to(this->sf_name)
+ | lnav::itertools::map(&intern_string_t::to_string)
+ | lnav::itertools::fold(symbol_reducer, attr_line_t{}))
+ .add_header("did you mean one of the following?"));
+
+ return Err(um);
+ }
+
+ return Ok(lformat);
+ }
+
+ Result<external_log_format*, console::user_message>
+ validate_external_format() const
+ {
+ auto lformat = TRY(this->validate_format());
+ auto* ext_lformat = dynamic_cast<external_log_format*>(lformat.get());
+
+ if (ext_lformat == nullptr) {
+ return Err(console::user_message::error(
+ attr_line_t()
+ .append_quoted(lnav::roles::symbol(this->sf_name))
+ .append(" is an internal format that is not defined in a "
+ "configuration file")));
+ }
+
+ return Ok(ext_lformat);
+ }
+
+ Result<std::pair<external_log_format*,
+ std::shared_ptr<external_log_format::pattern>>,
+ console::user_message>
+ validate_regex() const
+ {
+ auto* ext_lformat = TRY(this->validate_external_format());
+
+ if (this->sf_regex_name.empty()) {
+ auto um = console::user_message::error(
+ "expecting a regex name to operate on");
+ um.with_note(
+ ext_lformat->elf_pattern_order
+ | lnav::itertools::map(&external_log_format::pattern::p_name)
+ | lnav::itertools::map(&intern_string_t::to_string)
+ | lnav::itertools::fold(
+ symbol_reducer, attr_line_t{"the available regexes are:"}));
+
+ return Err(um);
+ }
+
+ for (const auto& pat : ext_lformat->elf_pattern_order) {
+ if (pat->p_name == this->sf_regex_name) {
+ return Ok(std::make_pair(ext_lformat, pat));
+ }
+ }
+
+ auto um = console::user_message::error(
+ attr_line_t("unknown regex: ")
+ .append(lnav::roles::symbol(this->sf_regex_name)));
+ um.with_note(
+ (ext_lformat->elf_pattern_order
+ | lnav::itertools::map(&external_log_format::pattern::p_name)
+ | lnav::itertools::map(&intern_string_t::to_string)
+ | lnav::itertools::similar_to(this->sf_regex_name)
+ | lnav::itertools::fold(symbol_reducer, attr_line_t{}))
+ .add_header("did you mean one of the following?"));
+
+ return Err(um);
+ }
+
+ static perform_result_t default_action(const subcmd_format_t& sf)
+ {
+ auto validate_res = sf.validate_format();
+ if (validate_res.isErr()) {
+ return {validate_res.unwrapErr()};
+ }
+
+ auto lformat = validate_res.unwrap();
+ auto* ext_format = dynamic_cast<external_log_format*>(lformat.get());
+
+ attr_line_t ext_details;
+ if (ext_format != nullptr) {
+ ext_details.append("\n ")
+ .append("Regexes"_h3)
+ .append(": ")
+ .join(ext_format->elf_pattern_order
+ | lnav::itertools::map(
+ &external_log_format::pattern::p_name)
+ | lnav::itertools::map(&intern_string_t::to_string),
+ VC_ROLE.value(role_t::VCR_SYMBOL),
+ ", ");
+ }
+
+ auto um = console::user_message::error(
+ attr_line_t("expecting an operation to perform on the ")
+ .append(lnav::roles::symbol(sf.sf_name))
+ .append(" format"));
+ um.with_note(attr_line_t()
+ .append(lnav::roles::symbol(sf.sf_name))
+ .append(": ")
+ .append(lformat->lf_description)
+ .append(ext_details));
+ um.with_help(
+ sf.sf_format_app->get_subcommands({})
+ | lnav::itertools::fold(
+ subcmd_reducer, attr_line_t{"the available operations are:"}));
+
+ return {um};
+ }
+
+ static perform_result_t default_regex_action(const subcmd_format_t& sf)
+ {
+ auto validate_res = sf.validate_regex();
+
+ if (validate_res.isErr()) {
+ return {validate_res.unwrapErr()};
+ }
+
+ auto um = console::user_message::error(
+ attr_line_t("expecting an operation to perform on the ")
+ .append(lnav::roles::symbol(sf.sf_regex_name))
+ .append(" regular expression"));
+
+ um.with_help(attr_line_t{"the available subcommands are:"}.append(
+ sf.sf_regex_app->get_subcommands({})
+ | lnav::itertools::fold(subcmd_reducer, attr_line_t{})));
+
+ return {um};
+ }
+
+ static perform_result_t get_action(const subcmd_format_t& sf)
+ {
+ auto validate_res = sf.validate_format();
+
+ if (validate_res.isErr()) {
+ return {validate_res.unwrapErr()};
+ }
+
+ auto format = validate_res.unwrap();
+
+ auto um = console::user_message::raw(
+ attr_line_t()
+ .append(lnav::roles::symbol(sf.sf_name))
+ .append(": ")
+ .append(on_blank(format->lf_description, "<no description>")));
+
+ return {um};
+ }
+
+ static perform_result_t source_action(const subcmd_format_t& sf)
+ {
+ auto validate_res = sf.validate_external_format();
+
+ if (validate_res.isErr()) {
+ return {validate_res.unwrapErr()};
+ }
+
+ auto* format = validate_res.unwrap();
+
+ if (format->elf_format_source_order.empty()) {
+ return {
+ console::user_message::error(
+ "format is builtin, there is no source file"),
+ };
+ }
+
+ auto um = console::user_message::raw(
+ format->elf_format_source_order[0].string());
+
+ return {um};
+ }
+
+ static perform_result_t sources_action(const subcmd_format_t& sf)
+ {
+ auto validate_res = sf.validate_external_format();
+
+ if (validate_res.isErr()) {
+ return {validate_res.unwrapErr()};
+ }
+
+ auto* format = validate_res.unwrap();
+
+ if (format->elf_format_source_order.empty()) {
+ return {
+ console::user_message::error(
+ "format is builtin, there is no source file"),
+ };
+ }
+
+ auto um = console::user_message::raw(
+ attr_line_t().join(format->elf_format_source_order,
+ VC_ROLE.value(role_t::VCR_TEXT),
+ "\n"));
+
+ return {um};
+ }
+
+ static perform_result_t regex101_pull_action(const subcmd_format_t& sf)
+ {
+ auto validate_res = sf.validate_regex();
+ if (validate_res.isErr()) {
+ return {validate_res.unwrapErr()};
+ }
+
+ auto format_regex_pair = validate_res.unwrap();
+ auto get_meta_res
+ = lnav::session::regex101::get_entry(sf.sf_name, sf.sf_regex_name);
+
+ return get_meta_res.match(
+ [&sf](
+ const lnav::session::regex101::error& err) -> perform_result_t {
+ return {
+ console::user_message::error(
+ attr_line_t("unable to get DB entry for: ")
+ .append(lnav::roles::symbol(sf.sf_name))
+ .append("/")
+ .append(lnav::roles::symbol(sf.sf_regex_name)))
+ .with_reason(err.e_msg),
+ };
+ },
+ [&sf](
+ const lnav::session::regex101::no_entry&) -> perform_result_t {
+ return {
+ console::user_message::error(
+ attr_line_t("regex ")
+ .append_quoted(
+ lnav::roles::symbol(sf.sf_regex_name))
+ .append(" of format ")
+ .append_quoted(lnav::roles::symbol(sf.sf_name))
+ .append(" has not been pushed to regex101.com"))
+ .with_help(
+ attr_line_t("use the ")
+ .append_quoted("push"_keyword)
+ .append(" subcommand to create the regex on "
+ "regex101.com for easy editing")),
+ };
+ },
+ [&](const lnav::session::regex101::entry& en) -> perform_result_t {
+ auto retrieve_res = regex101::client::retrieve(en.re_permalink);
+
+ return retrieve_res.match(
+ [&](const console::user_message& um) -> perform_result_t {
+ return {
+ console::user_message::error(
+ attr_line_t("unable to retrieve entry ")
+ .append_quoted(
+ lnav::roles::symbol(en.re_permalink))
+ .append(" from regex101.com"))
+ .with_reason(um),
+ };
+ },
+ [&](const regex101::client::no_entry&) -> perform_result_t {
+ lnav::session::regex101::delete_entry(sf.sf_name,
+ sf.sf_regex_name);
+ return {
+ console::user_message::error(
+ attr_line_t("entry ")
+ .append_quoted(
+ lnav::roles::symbol(en.re_permalink))
+ .append(
+ " no longer exists on regex101.com"))
+ .with_help(attr_line_t("use the ")
+ .append_quoted("delete"_keyword)
+ .append(" subcommand to delete "
+ "the association")),
+ };
+ },
+ [&](const regex101::client::entry& remote_entry)
+ -> perform_result_t {
+ auto curr_entry = regex101::convert_format_pattern(
+ format_regex_pair.first, format_regex_pair.second);
+
+ if (curr_entry.e_regex == remote_entry.e_regex) {
+ return {
+ console::user_message::ok(
+ attr_line_t("local regex is in sync "
+ "with entry ")
+ .append_quoted(lnav::roles::symbol(
+ en.re_permalink))
+ .append(" on regex101.com"))
+ .with_help(
+ attr_line_t("make edits on ")
+ .append_quoted(lnav::roles::file(
+ regex101::client::to_edit_url(
+ en.re_permalink)))
+ .append(" and then run this "
+ "command again to update "
+ "the local values")),
+ };
+ }
+
+ auto patch_res
+ = regex101::patch(format_regex_pair.first,
+ sf.sf_regex_name,
+ remote_entry);
+
+ if (patch_res.isErr()) {
+ return {
+ console::user_message::error(
+ attr_line_t(
+ "unable to patch format regex: ")
+ .append(lnav::roles::symbol(sf.sf_name))
+ .append("/")
+ .append(lnav::roles::symbol(
+ sf.sf_regex_name)))
+ .with_reason(patch_res.unwrapErr()),
+ };
+ }
+
+ auto um = console::user_message::ok(
+ attr_line_t("format patch file written to: ")
+ .append(lnav::roles::file(
+ patch_res.unwrap().string())));
+ if (!format_regex_pair.first->elf_builtin_format) {
+ um.with_help(
+ attr_line_t("once the regex has been found "
+ "to be working correctly, move the "
+ "contents of the patch file to the "
+ "original file at:\n ")
+ .append(lnav::roles::file(
+ format_regex_pair.first
+ ->elf_format_source_order.front()
+ .string())));
+ }
+
+ return {um};
+ });
+ });
+ }
+
+ static perform_result_t regex101_default_action(const subcmd_format_t& sf)
+ {
+ auto validate_res = sf.validate_regex();
+
+ if (validate_res.isErr()) {
+ return {validate_res.unwrapErr()};
+ }
+
+ auto um = console::user_message::error(
+ attr_line_t("expecting an operation to perform on the ")
+ .append(lnav::roles::symbol(sf.sf_regex_name))
+ .append(" regex using regex101.com"));
+
+ auto get_res
+ = lnav::session::regex101::get_entry(sf.sf_name, sf.sf_regex_name);
+ if (get_res.is<lnav::session::regex101::entry>()) {
+ auto local_entry = get_res.get<lnav::session::regex101::entry>();
+ um.with_note(
+ attr_line_t("this regex is currently associated with the "
+ "following regex101.com entry:\n ")
+ .append(lnav::roles::file(regex101::client::to_edit_url(
+ local_entry.re_permalink))));
+ }
+
+ um.with_help(attr_line_t{"the available subcommands are:"}.append(
+ sf.sf_regex101_app->get_subcommands({})
+ | lnav::itertools::fold(subcmd_reducer, attr_line_t{})));
+
+ return {um};
+ }
+
+ static perform_result_t regex101_push_action(const subcmd_format_t& sf)
+ {
+ auto validate_res = sf.validate_regex();
+ if (validate_res.isErr()) {
+ return {validate_res.unwrapErr()};
+ }
+
+ auto format_regex_pair = validate_res.unwrap();
+ auto entry = regex101::convert_format_pattern(format_regex_pair.first,
+ format_regex_pair.second);
+ auto get_meta_res
+ = lnav::session::regex101::get_entry(sf.sf_name, sf.sf_regex_name);
+
+ if (get_meta_res.is<lnav::session::regex101::entry>()) {
+ auto entry_meta
+ = get_meta_res.get<lnav::session::regex101::entry>();
+ auto retrieve_res
+ = regex101::client::retrieve(entry_meta.re_permalink);
+
+ if (retrieve_res.is<regex101::client::entry>()) {
+ auto remote_entry = retrieve_res.get<regex101::client::entry>();
+
+ if (remote_entry == entry) {
+ return {
+ console::user_message::ok(
+ attr_line_t("regex101 entry ")
+ .append(lnav::roles::symbol(
+ entry_meta.re_permalink))
+ .append(" is already up-to-date")),
+ };
+ }
+ } else if (retrieve_res.is<console::user_message>()) {
+ return {
+ retrieve_res.get<console::user_message>(),
+ };
+ }
+
+ entry.e_permalink_fragment = entry_meta.re_permalink;
+ }
+
+ auto upsert_res = regex101::client::upsert(entry);
+ auto upsert_info = upsert_res.unwrap();
+
+ if (get_meta_res.is<lnav::session::regex101::no_entry>()) {
+ lnav::session::regex101::insert_entry({
+ format_regex_pair.first->get_name().to_string(),
+ format_regex_pair.second->p_name.to_string(),
+ upsert_info.cr_permalink_fragment,
+ upsert_info.cr_delete_code,
+ });
+ }
+
+ return {
+ console::user_message::ok(
+ attr_line_t("pushed regex to -- ")
+ .append(lnav::roles::file(regex101::client::to_edit_url(
+ upsert_info.cr_permalink_fragment))))
+ .with_help(attr_line_t("use the ")
+ .append_quoted("pull"_keyword)
+ .append(" subcommand to update the format after "
+ "you make changes on regex101.com")),
+ };
+ }
+
+ static perform_result_t regex101_delete_action(const subcmd_format_t& sf)
+ {
+ auto get_res
+ = lnav::session::regex101::get_entry(sf.sf_name, sf.sf_regex_name);
+
+ return get_res.match(
+ [&sf](
+ const lnav::session::regex101::entry& en) -> perform_result_t {
+ {
+ auto validate_res = sf.validate_external_format();
+
+ if (validate_res.isOk()) {
+ auto ppath = regex101::patch_path(validate_res.unwrap(),
+ en.re_permalink);
+
+ if (ghc::filesystem::exists(ppath)) {
+ return {
+ console::user_message::error(
+ attr_line_t("cannot delete regex101 entry "
+ "while patch file exists"))
+ .with_note(attr_line_t(" ").append(
+ lnav::roles::file(ppath.string())))
+ .with_help(attr_line_t(
+ "move the contents of the patch file "
+ "to the main log format and then "
+ "delete the file to continue")),
+ };
+ }
+ }
+ }
+
+ perform_result_t retval;
+ if (en.re_delete_code.empty()) {
+ retval.emplace_back(
+ console::user_message::warning(
+ attr_line_t("not deleting regex101 entry ")
+ .append_quoted(
+ lnav::roles::symbol(en.re_permalink)))
+ .with_reason(
+ "delete code is not known for this entry")
+ .with_note(
+ "formats created by importing a regex101.com "
+ "entry will not have a delete code"));
+ } else {
+ auto delete_res
+ = regex101::client::delete_entry(en.re_delete_code);
+
+ if (delete_res.isErr()) {
+ return {
+ console::user_message::error(
+ "unable to delete regex101 entry")
+ .with_reason(delete_res.unwrapErr()),
+ };
+ }
+ }
+
+ lnav::session::regex101::delete_entry(sf.sf_name,
+ sf.sf_regex_name);
+
+ retval.emplace_back(console::user_message::ok(
+ attr_line_t("deleted regex101 entry: ")
+ .append(lnav::roles::symbol(en.re_permalink))));
+
+ return retval;
+ },
+ [&sf](
+ const lnav::session::regex101::no_entry&) -> perform_result_t {
+ return {
+ console::user_message::error(
+ attr_line_t("no regex101 entry for ")
+ .append(lnav::roles::symbol(sf.sf_name))
+ .append("/")
+ .append(lnav::roles::symbol(sf.sf_regex_name))),
+ };
+ },
+ [&sf](
+ const lnav::session::regex101::error& err) -> perform_result_t {
+ return {
+ console::user_message::error(
+ attr_line_t("unable to get regex101 entry for ")
+ .append(lnav::roles::symbol(sf.sf_name))
+ .append("/")
+ .append(lnav::roles::symbol(sf.sf_regex_name)))
+ .with_reason(err.e_msg),
+ };
+ });
+ }
+};
+
+struct subcmd_regex101_t {
+ using action_t = std::function<perform_result_t(const subcmd_regex101_t&)>;
+
+ CLI::App* sr_app{nullptr};
+ action_t sr_action;
+ std::string sr_import_url;
+ std::string sr_import_name;
+ std::string sr_import_regex_name{"std"};
+
+ subcmd_regex101_t& set_action(action_t act)
+ {
+ if (!this->sr_action) {
+ this->sr_action = std::move(act);
+ }
+ return *this;
+ }
+
+ static perform_result_t default_action(const subcmd_regex101_t& sr)
+ {
+ auto um = console::user_message::error(
+ "expecting an operation related to the regex101.com integration");
+ um.with_help(
+ sr.sr_app->get_subcommands({})
+ | lnav::itertools::fold(
+ subcmd_reducer, attr_line_t{"the available operations are:"}));
+
+ return {um};
+ }
+
+ static perform_result_t list_action(const subcmd_regex101_t&)
+ {
+ auto get_res = lnav::session::regex101::get_entries();
+
+ if (get_res.isErr()) {
+ return {
+ console::user_message::error(
+ "unable to read regex101 entries from DB")
+ .with_reason(get_res.unwrapErr()),
+ };
+ }
+
+ auto entries
+ = get_res.unwrap() | lnav::itertools::map([](const auto& elem) {
+ return fmt::format(
+ FMT_STRING(" format {} regex {} regex101\n"),
+ elem.re_format_name,
+ elem.re_regex_name);
+ })
+ | lnav::itertools::fold(
+ [](const auto& elem, auto& accum) {
+ return accum.append(elem);
+ },
+ attr_line_t{});
+
+ auto um = console::user_message::ok(
+ entries.add_header("the following regex101 entries were found:\n")
+ .with_default("no regex101 entries found"));
+
+ return {um};
+ }
+
+ static perform_result_t import_action(const subcmd_regex101_t& sr)
+ {
+ auto import_res = regex101::import(
+ sr.sr_import_url, sr.sr_import_name, sr.sr_import_regex_name);
+
+ if (import_res.isOk()) {
+ return {
+ lnav::console::user_message::ok(
+ attr_line_t("converted regex101 entry to format file: ")
+ .append(lnav::roles::file(import_res.unwrap())))
+ .with_note("the converted format may still have errors")
+ .with_help(
+ attr_line_t(
+ "use the following command to patch the regex as "
+ "more changes are made on regex101.com:\n")
+ .appendf(FMT_STRING(" lnav -m format {} regex {} "
+ "regex101 pull"),
+ sr.sr_import_name,
+ sr.sr_import_regex_name)),
+ };
+ }
+
+ return {
+ import_res.unwrapErr(),
+ };
+ }
+};
+
+using operations_v
+ = mapbox::util::variant<no_subcmd_t, subcmd_format_t, subcmd_regex101_t>;
+
+class operations {
+public:
+ operations_v o_ops;
+};
+
+std::shared_ptr<operations>
+describe_cli(CLI::App& app, int argc, char* argv[])
+{
+ auto retval = std::make_shared<operations>();
+
+ retval->o_ops = no_subcmd_t{
+ &app,
+ };
+
+ app.add_flag("-m", "Switch to the management CLI mode.");
+
+ subcmd_format_t format_args;
+ subcmd_regex101_t regex101_args;
+
+ {
+ auto* subcmd_format
+ = app.add_subcommand("format",
+ "perform operations on log file formats")
+ ->callback([&]() {
+ format_args.set_action(subcmd_format_t::default_action);
+ retval->o_ops = format_args;
+ });
+ format_args.sf_format_app = subcmd_format;
+ subcmd_format
+ ->add_option(
+ "format_name", format_args.sf_name, "the name of the format")
+ ->expected(1);
+
+ {
+ subcmd_format
+ ->add_subcommand("get", "print information about a format")
+ ->callback([&]() {
+ format_args.set_action(subcmd_format_t::get_action);
+ });
+ }
+
+ {
+ subcmd_format
+ ->add_subcommand("source",
+ "print the path of the first source file "
+ "containing this format")
+ ->callback([&]() {
+ format_args.set_action(subcmd_format_t::source_action);
+ });
+ }
+
+ {
+ subcmd_format
+ ->add_subcommand("sources",
+ "print the paths of all source files "
+ "containing this format")
+ ->callback([&]() {
+ format_args.set_action(subcmd_format_t::sources_action);
+ });
+ }
+
+ {
+ auto* subcmd_format_regex
+ = subcmd_format
+ ->add_subcommand(
+ "regex",
+ "operate on the format's regular expressions")
+ ->callback([&]() {
+ format_args.set_action(
+ subcmd_format_t::default_regex_action);
+ });
+ format_args.sf_regex_app = subcmd_format_regex;
+ subcmd_format_regex->add_option(
+ "regex-name",
+ format_args.sf_regex_name,
+ "the name of the regular expression to operate on");
+
+ {
+ auto* subcmd_format_regex_regex101
+ = subcmd_format_regex
+ ->add_subcommand("regex101",
+ "use regex101.com to edit this "
+ "regular expression")
+ ->callback([&]() {
+ format_args.set_action(
+ subcmd_format_t::regex101_default_action);
+ });
+ format_args.sf_regex101_app = subcmd_format_regex_regex101;
+
+ {
+ subcmd_format_regex_regex101
+ ->add_subcommand("push",
+ "create/update an entry for "
+ "this regex on regex101.com")
+ ->callback([&]() {
+ format_args.set_action(
+ subcmd_format_t::regex101_push_action);
+ });
+ subcmd_format_regex_regex101
+ ->add_subcommand(
+ "pull",
+ "create a patch format file for this "
+ "regular expression based on the entry in "
+ "regex101.com")
+ ->callback([&]() {
+ format_args.set_action(
+ subcmd_format_t::regex101_pull_action);
+ });
+ subcmd_format_regex_regex101
+ ->add_subcommand(
+ "delete",
+ "delete the entry regex101.com that was "
+ "created by a push operation")
+ ->callback([&]() {
+ format_args.set_action(
+ subcmd_format_t::regex101_delete_action);
+ });
+ }
+ }
+ }
+ }
+
+ {
+ auto* subcmd_regex101
+ = app.add_subcommand("regex101",
+ "create and edit log message regular "
+ "expressions using regex101.com")
+ ->callback([&]() {
+ regex101_args.set_action(
+ subcmd_regex101_t::default_action);
+ retval->o_ops = regex101_args;
+ });
+ regex101_args.sr_app = subcmd_regex101;
+
+ {
+ subcmd_regex101
+ ->add_subcommand("list",
+ "list the log format regular expression "
+ "linked to entries on regex101.com")
+ ->callback([&]() {
+ regex101_args.set_action(subcmd_regex101_t::list_action);
+ });
+ }
+ {
+ auto* subcmd_regex101_import
+ = subcmd_regex101
+ ->add_subcommand("import",
+ "create a new format from a regular "
+ "expression on regex101.com")
+ ->callback([&]() {
+ regex101_args.set_action(
+ subcmd_regex101_t::import_action);
+ });
+
+ subcmd_regex101_import->add_option(
+ "url",
+ regex101_args.sr_import_url,
+ "The regex101.com url to construct a log format from");
+ subcmd_regex101_import->add_option("name",
+ regex101_args.sr_import_name,
+ "The name for the log format");
+ subcmd_regex101_import
+ ->add_option("regex-name",
+ regex101_args.sr_import_regex_name,
+ "The name for the new regex")
+ ->always_capture_default();
+ }
+ }
+
+ app.parse(argc, argv);
+
+ return retval;
+}
+
+perform_result_t
+perform(std::shared_ptr<operations> opts)
+{
+ return opts->o_ops.match(
+ [](const no_subcmd_t& ns) -> perform_result_t {
+ auto um = console::user_message::error(
+ attr_line_t("expecting an operation to perform"));
+ um.with_help(ns.ns_root_app->get_subcommands({})
+ | lnav::itertools::fold(
+ subcmd_reducer,
+ attr_line_t{"the available operations are:"}));
+
+ return {um};
+ },
+ [](const subcmd_format_t& sf) { return sf.sf_action(sf); },
+ [](const subcmd_regex101_t& sr) { return sr.sr_action(sr); });
+}
+
+} // namespace management
+} // namespace lnav