diff options
Diffstat (limited to '')
-rw-r--r-- | src/lnav.management_cli.cc | 910 |
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 |