mirror of https://github.com/tstack/lnav
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
911 lines
35 KiB
C++
911 lines
35 KiB
C++
/**
|
|
* 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
|