mirror of https://github.com/tstack/lnav
[regex101] add an integration with regex101
... and a pile of other changespull/976/head
parent
69b5cb1d58
commit
a27198e8ca
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
srcdir="$1"
|
||||
builddir="$2"
|
||||
|
||||
for fname in "${srcdir}"/expected/*.out; do
|
||||
stem=$(basename "$fname" | sed -e 's/.out$//')
|
||||
|
||||
if ! test -f "${builddir}/$stem.cmd"; then
|
||||
echo "removing $fname"
|
||||
guilt rm "$fname"
|
||||
echo "removing ${srcdir}/expected/${stem}.err"
|
||||
guilt rm "${srcdir}/expected/${stem}.err"
|
||||
fi
|
||||
done
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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 <iostream>
|
||||
|
||||
#include "base/fs_util.hh"
|
||||
|
||||
#include "config.h"
|
||||
#include "doctest/doctest.h"
|
||||
|
||||
TEST_CASE("fs_util::build_path")
|
||||
{
|
||||
auto* old_path = getenv("PATH");
|
||||
unsetenv("PATH");
|
||||
|
||||
CHECK("" == lnav::filesystem::build_path({}));
|
||||
|
||||
CHECK("/bin:/usr/bin"
|
||||
== lnav::filesystem::build_path({"", "/bin", "/usr/bin", ""}));
|
||||
setenv("PATH", "/usr/local/bin", 1);
|
||||
CHECK("/bin:/usr/bin:/usr/local/bin"
|
||||
== lnav::filesystem::build_path({"", "/bin", "/usr/bin", ""}));
|
||||
setenv("PATH", "/usr/local/bin:/opt/bin", 1);
|
||||
CHECK("/usr/local/bin:/opt/bin" == lnav::filesystem::build_path({}));
|
||||
CHECK("/bin:/usr/bin:/usr/local/bin:/opt/bin"
|
||||
== lnav::filesystem::build_path({"", "/bin", "/usr/bin", ""}));
|
||||
if (old_path != nullptr) {
|
||||
setenv("PATH", old_path, 1);
|
||||
}
|
||||
}
|
@ -0,0 +1,400 @@
|
||||
|
||||
#ifndef lnav_itertools_hh
|
||||
#define lnav_itertools_hh
|
||||
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "func_util.hh"
|
||||
#include "optional.hpp"
|
||||
|
||||
namespace lnav {
|
||||
namespace itertools {
|
||||
|
||||
struct empty {};
|
||||
|
||||
struct not_empty {};
|
||||
|
||||
struct full {
|
||||
size_t f_max_size;
|
||||
};
|
||||
|
||||
namespace details {
|
||||
|
||||
template<typename T>
|
||||
struct unwrap_or {
|
||||
T uo_value;
|
||||
};
|
||||
|
||||
template<typename P>
|
||||
struct find_if {
|
||||
P fi_predicate;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct find {
|
||||
T f_value;
|
||||
};
|
||||
|
||||
template<typename F>
|
||||
struct filter_in {
|
||||
F f_func;
|
||||
};
|
||||
|
||||
template<typename F>
|
||||
struct filter_out {
|
||||
F f_func;
|
||||
};
|
||||
|
||||
template<typename C>
|
||||
struct sort_by {
|
||||
C sb_cmp;
|
||||
};
|
||||
|
||||
struct sorted {};
|
||||
|
||||
template<typename F>
|
||||
struct mapper {
|
||||
F m_func;
|
||||
};
|
||||
|
||||
template<typename R, typename T>
|
||||
struct folder {
|
||||
R f_func;
|
||||
T f_init;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct prepend {
|
||||
T p_value;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct append {
|
||||
T p_value;
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
template<typename T>
|
||||
inline details::unwrap_or<T>
|
||||
unwrap_or(T value)
|
||||
{
|
||||
return details::unwrap_or<T>{
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
template<typename P>
|
||||
inline details::find_if<P>
|
||||
find_if(P predicate)
|
||||
{
|
||||
return details::find_if<P>{
|
||||
predicate,
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline details::find<T>
|
||||
find(T value)
|
||||
{
|
||||
return details::find<T>{
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
inline details::filter_in<F>
|
||||
filter_in(F func)
|
||||
{
|
||||
return details::filter_in<F>{
|
||||
func,
|
||||
};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
inline details::filter_out<F>
|
||||
filter_out(F func)
|
||||
{
|
||||
return details::filter_out<F>{
|
||||
func,
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline details::prepend<T>
|
||||
prepend(T value)
|
||||
{
|
||||
return details::prepend<T>{
|
||||
std::move(value),
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline details::append<T>
|
||||
append(T value)
|
||||
{
|
||||
return details::append<T>{
|
||||
std::move(value),
|
||||
};
|
||||
}
|
||||
|
||||
template<typename C>
|
||||
inline details::sort_by<C>
|
||||
sort_with(C cmp)
|
||||
{
|
||||
return details::sort_by<C>{cmp};
|
||||
}
|
||||
|
||||
template<typename C, typename T>
|
||||
inline auto
|
||||
sort_by(T C::*m)
|
||||
{
|
||||
return sort_with(
|
||||
[m](const C& lhs, const C& rhs) { return lhs.*m < rhs.*m; });
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
inline details::mapper<F>
|
||||
map(F func)
|
||||
{
|
||||
return details::mapper<F>{func};
|
||||
}
|
||||
|
||||
template<typename R, typename T>
|
||||
inline details::folder<R, T>
|
||||
fold(R func, T init)
|
||||
{
|
||||
return details::folder<R, T>{func, init};
|
||||
}
|
||||
|
||||
inline details::sorted
|
||||
sorted()
|
||||
{
|
||||
return details::sorted{};
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
T
|
||||
chain(const T& value1, const Args&... args)
|
||||
{
|
||||
T retval;
|
||||
|
||||
for (const auto& arg : {value1, args...}) {
|
||||
for (const auto& elem : arg) {
|
||||
retval.emplace_back(elem);
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
} // namespace itertools
|
||||
} // namespace lnav
|
||||
|
||||
template<typename C, typename P>
|
||||
nonstd::optional<typename C::value_type>
|
||||
operator|(const C& in, const lnav::itertools::details::find_if<P>& finder)
|
||||
{
|
||||
for (const auto& elem : in) {
|
||||
if (lnav::func::invoke(finder.fi_predicate, elem)) {
|
||||
return nonstd::make_optional(elem);
|
||||
}
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
template<typename C, typename T>
|
||||
nonstd::optional<size_t>
|
||||
operator|(const C& in, const lnav::itertools::details::find<T>& finder)
|
||||
{
|
||||
size_t retval = 0;
|
||||
for (const auto& elem : in) {
|
||||
if (elem == finder.f_value) {
|
||||
return nonstd::make_optional(retval);
|
||||
}
|
||||
retval += 1;
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
template<typename C, typename F>
|
||||
C
|
||||
operator|(const C& in, const lnav::itertools::details::filter_in<F>& filterer)
|
||||
{
|
||||
C retval;
|
||||
|
||||
for (const auto& elem : in) {
|
||||
if (lnav::func::invoke(filterer.f_func, elem)) {
|
||||
retval.emplace_back(elem);
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
template<typename C, typename F>
|
||||
C
|
||||
operator|(const C& in, const lnav::itertools::details::filter_out<F>& filterer)
|
||||
{
|
||||
C retval;
|
||||
|
||||
for (const auto& elem : in) {
|
||||
if (!lnav::func::invoke(filterer.f_func, elem)) {
|
||||
retval.emplace_back(elem);
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
template<typename C, typename T>
|
||||
C
|
||||
operator|(C in, const lnav::itertools::details::prepend<T>& prepender)
|
||||
{
|
||||
in.emplace(in.begin(), prepender.p_value);
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
template<typename C, typename T>
|
||||
C
|
||||
operator|(C in, const lnav::itertools::details::append<T>& appender)
|
||||
{
|
||||
in.emplace_back(appender.p_value);
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
template<typename C, typename R, typename T>
|
||||
T
|
||||
operator|(const C& in, const lnav::itertools::details::folder<R, T>& folder)
|
||||
{
|
||||
auto accum = folder.f_init;
|
||||
|
||||
for (const auto& elem : in) {
|
||||
accum = folder.f_func(elem, accum);
|
||||
}
|
||||
|
||||
return accum;
|
||||
}
|
||||
|
||||
template<typename T, typename C>
|
||||
T
|
||||
operator|(T in, const lnav::itertools::details::sort_by<C>& sorter)
|
||||
{
|
||||
std::sort(in.begin(), in.end(), sorter.sb_cmp);
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
operator|(T in, const lnav::itertools::details::sorted& sorter)
|
||||
{
|
||||
std::sort(in.begin(), in.end());
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
template<typename T, typename F>
|
||||
auto
|
||||
operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
|
||||
-> std::vector<decltype(mapper.m_func(typename T::value_type{}))>
|
||||
{
|
||||
using return_type
|
||||
= std::vector<decltype(mapper.m_func(typename T::value_type{}))>;
|
||||
return_type retval;
|
||||
|
||||
retval.reserve(in.size());
|
||||
std::transform(
|
||||
in.begin(), in.end(), std::back_inserter(retval), mapper.m_func);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
template<typename T, typename F>
|
||||
auto
|
||||
operator|(const std::vector<std::shared_ptr<T>>& in,
|
||||
const lnav::itertools::details::mapper<F>& mapper)
|
||||
-> std::vector<typename std::remove_const_t<decltype(((*in.front())
|
||||
.*mapper.m_func)())>>
|
||||
{
|
||||
using return_type = std::vector<typename std::remove_const_t<decltype((
|
||||
(*in.front()).*mapper.m_func)())>>;
|
||||
return_type retval;
|
||||
|
||||
retval.reserve(in.size());
|
||||
std::transform(
|
||||
in.begin(),
|
||||
in.end(),
|
||||
std::back_inserter(retval),
|
||||
[&mapper](const auto& elem) { return ((*elem).*mapper.m_func)(); });
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
template<typename T, typename F>
|
||||
auto
|
||||
operator|(const std::vector<std::shared_ptr<T>>& in,
|
||||
const lnav::itertools::details::mapper<F>& mapper)
|
||||
-> std::vector<typename std::remove_reference_t<
|
||||
typename std::remove_const_t<decltype(((*in.front()).*mapper.m_func))>>>
|
||||
{
|
||||
using return_type = std::vector<
|
||||
typename std::remove_reference_t<typename std::remove_const_t<decltype((
|
||||
(*in.front()).*mapper.m_func))>>>;
|
||||
return_type retval;
|
||||
|
||||
retval.reserve(in.size());
|
||||
for (const auto& elem : in) {
|
||||
retval.template emplace_back(((*elem).*mapper.m_func));
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
template<typename T, typename F>
|
||||
auto
|
||||
operator|(nonstd::optional<T> in,
|
||||
const lnav::itertools::details::mapper<F>& mapper)
|
||||
-> nonstd::optional<typename std::remove_reference_t<
|
||||
typename std::remove_const_t<decltype(((in.value()).*mapper.m_func))>>>
|
||||
{
|
||||
if (!in) {
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
return nonstd::make_optional((in.value()).*mapper.m_func);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
operator|(nonstd::optional<T> in,
|
||||
const lnav::itertools::details::unwrap_or<T>& unwrapper)
|
||||
{
|
||||
return in.value_or(unwrapper.uo_value);
|
||||
}
|
||||
|
||||
template<typename T, typename F>
|
||||
auto
|
||||
operator|(const T& in, const lnav::itertools::details::mapper<F>& mapper)
|
||||
-> std::vector<std::remove_const_t<decltype(((typename T::value_type{})
|
||||
.*mapper.m_func)())>>
|
||||
{
|
||||
using return_type = std::vector<std::remove_const_t<decltype((
|
||||
(typename T::value_type{}).*mapper.m_func)())>>;
|
||||
return_type retval;
|
||||
|
||||
retval.reserve(in.size());
|
||||
for (const auto& elem : in) {
|
||||
retval.template emplace_back((elem.*mapper.m_func)());
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2020, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef lnav_log_level_enum_hh
|
||||
#define lnav_log_level_enum_hh
|
||||
|
||||
/**
|
||||
* The logging level identifiers for a line(s).
|
||||
*/
|
||||
enum log_level_t : int {
|
||||
LEVEL_UNKNOWN,
|
||||
LEVEL_TRACE,
|
||||
LEVEL_DEBUG5,
|
||||
LEVEL_DEBUG4,
|
||||
LEVEL_DEBUG3,
|
||||
LEVEL_DEBUG2,
|
||||
LEVEL_DEBUG,
|
||||
LEVEL_INFO,
|
||||
LEVEL_STATS,
|
||||
LEVEL_NOTICE,
|
||||
LEVEL_WARNING,
|
||||
LEVEL_ERROR,
|
||||
LEVEL_CRITICAL,
|
||||
LEVEL_FATAL,
|
||||
LEVEL_INVALID,
|
||||
|
||||
LEVEL__MAX,
|
||||
|
||||
LEVEL_IGNORE = 0x10, /*< Ignore */
|
||||
LEVEL_TIME_SKEW = 0x20, /*< Received after timestamp. */
|
||||
LEVEL_MARK = 0x40, /*< Bookmarked line. */
|
||||
LEVEL_CONTINUED = 0x80, /*< Continuation of multiline entry. */
|
||||
|
||||
/** Mask of flags for the level field. */
|
||||
LEVEL__FLAGS
|
||||
= (LEVEL_IGNORE | LEVEL_TIME_SKEW | LEVEL_MARK | LEVEL_CONTINUED)
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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 "dump_internals.hh"
|
||||
|
||||
#include "lnav.hh"
|
||||
#include "lnav_config.hh"
|
||||
#include "log_format_loader.hh"
|
||||
#include "sql_help.hh"
|
||||
#include "view_helpers.examples.hh"
|
||||
#include "yajlpp/yajlpp.hh"
|
||||
|
||||
namespace lnav {
|
||||
|
||||
void
|
||||
dump_internals(const char* internals_dir)
|
||||
{
|
||||
dump_schema_to(
|
||||
lnav_config_handlers, internals_dir, "config-v1.schema.json");
|
||||
dump_schema_to(root_format_handler, internals_dir, "format-v1.schema.json");
|
||||
|
||||
execute_examples();
|
||||
|
||||
auto cmd_ref_path = ghc::filesystem::path(internals_dir) / "cmd-ref.rst";
|
||||
auto cmd_file = std::unique_ptr<FILE, decltype(&fclose)>(
|
||||
fopen(cmd_ref_path.c_str(), "w+"), fclose);
|
||||
|
||||
if (cmd_file != nullptr) {
|
||||
std::set<readline_context::command_t*> unique_cmds;
|
||||
|
||||
for (auto& cmd : lnav_commands) {
|
||||
if (unique_cmds.find(cmd.second) != unique_cmds.end()) {
|
||||
continue;
|
||||
}
|
||||
unique_cmds.insert(cmd.second);
|
||||
format_help_text_for_rst(
|
||||
cmd.second->c_help, eval_example, cmd_file.get());
|
||||
}
|
||||
}
|
||||
|
||||
auto sql_ref_path = ghc::filesystem::path(internals_dir) / "sql-ref.rst";
|
||||
auto sql_file = std::unique_ptr<FILE, decltype(&fclose)>(
|
||||
fopen(sql_ref_path.c_str(), "w+"), fclose);
|
||||
std::set<help_text*> unique_sql_help;
|
||||
|
||||
if (sql_file != nullptr) {
|
||||
for (auto& sql : sqlite_function_help) {
|
||||
if (unique_sql_help.find(sql.second) != unique_sql_help.end()) {
|
||||
continue;
|
||||
}
|
||||
unique_sql_help.insert(sql.second);
|
||||
format_help_text_for_rst(*sql.second, eval_example, sql_file.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lnav
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2022, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef dump_internals_hh
|
||||
#define dump_internals_hh
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace lnav {
|
||||
|
||||
void dump_internals(const char* dir);
|
||||
|
||||
} // namespace lnav
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,385 @@
|
||||
/**
|
||||
* 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 "lnav.indexing.hh"
|
||||
|
||||
#include "lnav.hh"
|
||||
#include "service_tags.hh"
|
||||
#include "session_data.hh"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
/**
|
||||
* Observer for loading progress that updates the bottom status bar.
|
||||
*/
|
||||
class loading_observer : public logfile_observer {
|
||||
public:
|
||||
loading_observer() : lo_last_offset(0){};
|
||||
|
||||
indexing_result logfile_indexing(const std::shared_ptr<logfile>& lf,
|
||||
file_off_t off,
|
||||
file_size_t total) override
|
||||
{
|
||||
static sig_atomic_t index_counter = 0;
|
||||
|
||||
if (lnav_data.ld_window == nullptr) {
|
||||
return indexing_result::CONTINUE;
|
||||
}
|
||||
|
||||
/* XXX require(off <= total); */
|
||||
if (off > (off_t) total) {
|
||||
off = total;
|
||||
}
|
||||
|
||||
if ((((size_t) off == total) && (this->lo_last_offset != off))
|
||||
|| ui_periodic_timer::singleton().time_to_update(index_counter))
|
||||
{
|
||||
lnav_data.ld_bottom_source.update_loading(off, total);
|
||||
do_observer_update(lf);
|
||||
this->lo_last_offset = off;
|
||||
}
|
||||
|
||||
if (!lnav_data.ld_looping) {
|
||||
return indexing_result::BREAK;
|
||||
}
|
||||
return indexing_result::CONTINUE;
|
||||
}
|
||||
|
||||
off_t lo_last_offset;
|
||||
};
|
||||
|
||||
void
|
||||
do_observer_update(const std::shared_ptr<logfile>& lf)
|
||||
{
|
||||
if (isendwin()) {
|
||||
return;
|
||||
}
|
||||
lnav_data.ld_top_source.update_time();
|
||||
for (auto& sc : lnav_data.ld_status) {
|
||||
sc.do_update();
|
||||
}
|
||||
if (lf && lnav_data.ld_mode == ln_mode_t::FILES
|
||||
&& !lnav_data.ld_initial_build) {
|
||||
auto& fc = lnav_data.ld_active_files;
|
||||
auto iter = std::find(fc.fc_files.begin(), fc.fc_files.end(), lf);
|
||||
|
||||
if (iter != fc.fc_files.end()) {
|
||||
auto index = std::distance(fc.fc_files.begin(), iter);
|
||||
lnav_data.ld_files_view.set_selection(
|
||||
vis_line_t(fc.fc_other_files.size() + index));
|
||||
lnav_data.ld_files_view.reload_data();
|
||||
lnav_data.ld_files_view.do_update();
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
void
|
||||
rebuild_hist()
|
||||
{
|
||||
logfile_sub_source& lss = lnav_data.ld_log_source;
|
||||
hist_source2& hs = lnav_data.ld_hist_source2;
|
||||
int zoom = lnav_data.ld_zoom_level;
|
||||
|
||||
hs.set_time_slice(ZOOM_LEVELS[zoom]);
|
||||
lss.reload_index_delegate();
|
||||
}
|
||||
|
||||
class textfile_callback {
|
||||
public:
|
||||
void closed_files(const std::vector<std::shared_ptr<logfile>>& files)
|
||||
{
|
||||
for (const auto& lf : files) {
|
||||
log_info("closed text files: %s", lf->get_filename().c_str());
|
||||
}
|
||||
lnav_data.ld_active_files.close_files(files);
|
||||
}
|
||||
|
||||
void promote_file(const std::shared_ptr<logfile>& lf)
|
||||
{
|
||||
if (lnav_data.ld_log_source.insert_file(lf)) {
|
||||
this->did_promotion = true;
|
||||
log_info("promoting text file to log file: %s (%s)",
|
||||
lf->get_filename().c_str(),
|
||||
lf->get_content_id().c_str());
|
||||
auto format = lf->get_format();
|
||||
if (format->lf_is_self_describing) {
|
||||
auto vt = format->get_vtab_impl();
|
||||
|
||||
if (vt != nullptr) {
|
||||
lnav_data.ld_vtab_manager->register_vtab(vt);
|
||||
}
|
||||
}
|
||||
|
||||
auto iter = session_data.sd_file_states.find(lf->get_filename());
|
||||
if (iter != session_data.sd_file_states.end()) {
|
||||
log_debug("found state for log file %d",
|
||||
iter->second.fs_is_visible);
|
||||
|
||||
lnav_data.ld_log_source.find_data(lf) | [&iter](auto ld) {
|
||||
ld->set_visibility(iter->second.fs_is_visible);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
this->closed_files({lf});
|
||||
}
|
||||
}
|
||||
|
||||
void scanned_file(const std::shared_ptr<logfile>& lf)
|
||||
{
|
||||
if (!lnav_data.ld_files_to_front.empty()
|
||||
&& lnav_data.ld_files_to_front.front().first == lf->get_filename())
|
||||
{
|
||||
this->front_file = lf;
|
||||
this->front_top = lnav_data.ld_files_to_front.front().second;
|
||||
|
||||
lnav_data.ld_files_to_front.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<logfile> front_file;
|
||||
int front_top{-1};
|
||||
bool did_promotion{false};
|
||||
};
|
||||
|
||||
size_t
|
||||
rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
|
||||
{
|
||||
logfile_sub_source& lss = lnav_data.ld_log_source;
|
||||
textview_curses& log_view = lnav_data.ld_views[LNV_LOG];
|
||||
textview_curses& text_view = lnav_data.ld_views[LNV_TEXT];
|
||||
vis_line_t old_bottoms[LNV__MAX];
|
||||
bool scroll_downs[LNV__MAX];
|
||||
size_t retval = 0;
|
||||
|
||||
for (int lpc = 0; lpc < LNV__MAX; lpc++) {
|
||||
old_bottoms[lpc] = lnav_data.ld_views[lpc].get_top_for_last_row();
|
||||
scroll_downs[lpc]
|
||||
= (lnav_data.ld_views[lpc].get_top() >= old_bottoms[lpc])
|
||||
&& !(lnav_data.ld_flags & LNF_HEADLESS);
|
||||
}
|
||||
|
||||
{
|
||||
textfile_sub_source* tss = &lnav_data.ld_text_source;
|
||||
textfile_callback cb;
|
||||
|
||||
if (tss->rescan_files(cb, deadline)) {
|
||||
text_view.reload_data();
|
||||
retval += 1;
|
||||
}
|
||||
|
||||
if (cb.front_file != nullptr) {
|
||||
ensure_view(&text_view);
|
||||
|
||||
if (tss->current_file() != cb.front_file) {
|
||||
tss->to_front(cb.front_file);
|
||||
old_bottoms[LNV_TEXT] = -1_vl;
|
||||
}
|
||||
|
||||
if (cb.front_top < 0) {
|
||||
cb.front_top += text_view.get_inner_height();
|
||||
}
|
||||
if (cb.front_top < text_view.get_inner_height()) {
|
||||
text_view.set_top(vis_line_t(cb.front_top));
|
||||
scroll_downs[LNV_TEXT] = false;
|
||||
}
|
||||
}
|
||||
if (cb.did_promotion && deadline) {
|
||||
// If there's a new log file, extend the deadline so it can be
|
||||
// indexed quickly.
|
||||
deadline = deadline.value() + 500ms;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<logfile>> closed_files;
|
||||
for (auto& lf : lnav_data.ld_active_files.fc_files) {
|
||||
if ((!lf->exists() || lf->is_closed())) {
|
||||
log_info("closed log file: %s", lf->get_filename().c_str());
|
||||
lnav_data.ld_text_source.remove(lf);
|
||||
lnav_data.ld_log_source.remove_file(lf);
|
||||
closed_files.emplace_back(lf);
|
||||
}
|
||||
}
|
||||
if (!closed_files.empty()) {
|
||||
lnav_data.ld_active_files.close_files(closed_files);
|
||||
}
|
||||
|
||||
auto result = lss.rebuild_index(deadline);
|
||||
if (result != logfile_sub_source::rebuild_result::rr_no_change) {
|
||||
size_t new_count = lss.text_line_count();
|
||||
bool force
|
||||
= result == logfile_sub_source::rebuild_result::rr_full_rebuild;
|
||||
|
||||
if ((!scroll_downs[LNV_LOG]
|
||||
|| log_view.get_top() > vis_line_t(new_count))
|
||||
&& force)
|
||||
{
|
||||
scroll_downs[LNV_LOG] = false;
|
||||
}
|
||||
|
||||
log_view.reload_data();
|
||||
|
||||
{
|
||||
std::unordered_map<std::string, std::list<std::shared_ptr<logfile>>>
|
||||
id_to_files;
|
||||
bool reload = false;
|
||||
|
||||
for (const auto& lf : lnav_data.ld_active_files.fc_files) {
|
||||
id_to_files[lf->get_content_id()].push_back(lf);
|
||||
}
|
||||
|
||||
for (auto& pair : id_to_files) {
|
||||
if (pair.second.size() == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pair.second.sort([](const auto& left, const auto& right) {
|
||||
return right->get_stat().st_size < left->get_stat().st_size;
|
||||
});
|
||||
|
||||
auto dupe_name = pair.second.front()->get_unique_path();
|
||||
pair.second.pop_front();
|
||||
for_each(pair.second.begin(),
|
||||
pair.second.end(),
|
||||
[&dupe_name](auto& lf) {
|
||||
log_info("Hiding duplicate file: %s",
|
||||
lf->get_filename().c_str());
|
||||
lf->mark_as_duplicate(dupe_name);
|
||||
lnav_data.ld_log_source.find_data(lf) |
|
||||
[](auto ld) { ld->set_visibility(false); };
|
||||
});
|
||||
reload = true;
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
lss.text_filters_changed();
|
||||
}
|
||||
}
|
||||
|
||||
retval += 1;
|
||||
}
|
||||
|
||||
for (int lpc = 0; lpc < LNV__MAX; lpc++) {
|
||||
textview_curses& scroll_view = lnav_data.ld_views[lpc];
|
||||
|
||||
if (scroll_downs[lpc]
|
||||
&& scroll_view.get_top_for_last_row() > scroll_view.get_top())
|
||||
{
|
||||
scroll_view.set_top(scroll_view.get_top_for_last_row());
|
||||
}
|
||||
}
|
||||
|
||||
lnav_data.ld_view_stack.top() | [](auto tc) {
|
||||
lnav_data.ld_filter_status_source.update_filtered(tc->get_sub_source());
|
||||
lnav_data.ld_scroll_broadcaster(tc);
|
||||
};
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void
|
||||
rebuild_indexes_repeatedly()
|
||||
{
|
||||
for (size_t attempt = 0; attempt < 10 && rebuild_indexes() > 0; attempt++) {
|
||||
log_info("continuing to rebuild indexes...");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
update_active_files(file_collection& new_files)
|
||||
{
|
||||
static loading_observer obs;
|
||||
|
||||
if (lnav_data.ld_active_files.fc_invalidate_merge) {
|
||||
lnav_data.ld_active_files.fc_invalidate_merge = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& lf : new_files.fc_files) {
|
||||
lf->set_logfile_observer(&obs);
|
||||
lnav_data.ld_text_source.push_back(lf);
|
||||
}
|
||||
for (const auto& other_pair : new_files.fc_other_files) {
|
||||
switch (other_pair.second.ofd_format) {
|
||||
case file_format_t::SQLITE_DB:
|
||||
attach_sqlite_db(lnav_data.ld_db.in(), other_pair.first);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
lnav_data.ld_active_files.merge(new_files);
|
||||
if (!new_files.fc_files.empty() || !new_files.fc_other_files.empty()
|
||||
|| !new_files.fc_name_to_errors.empty())
|
||||
{
|
||||
lnav_data.ld_active_files.regenerate_unique_file_names();
|
||||
}
|
||||
lnav_data.ld_child_pollers.insert(
|
||||
lnav_data.ld_child_pollers.begin(),
|
||||
std::make_move_iterator(
|
||||
lnav_data.ld_active_files.fc_child_pollers.begin()),
|
||||
std::make_move_iterator(
|
||||
lnav_data.ld_active_files.fc_child_pollers.end()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
rescan_files(bool req)
|
||||
{
|
||||
auto& mlooper = injector::get<main_looper&, services::main_t>();
|
||||
bool done = false;
|
||||
auto delay = 0ms;
|
||||
|
||||
do {
|
||||
auto fc = lnav_data.ld_active_files.rescan_files(req);
|
||||
bool all_synced = true;
|
||||
|
||||
update_active_files(fc);
|
||||
mlooper.get_port().process_for(delay);
|
||||
if (lnav_data.ld_flags & LNF_HEADLESS) {
|
||||
for (const auto& pair : lnav_data.ld_active_files.fc_other_files) {
|
||||
if (pair.second.ofd_format != file_format_t::REMOTE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lnav_data.ld_active_files.fc_synced_files.count(pair.first)
|
||||
== 0) {
|
||||
all_synced = false;
|
||||
}
|
||||
}
|
||||
if (!all_synced) {
|
||||
delay = 30ms;
|
||||
}
|
||||
}
|
||||
done = fc.fc_file_names.empty() && all_synced;
|
||||
} while (!done);
|
||||
return true;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2022, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef lnav_indexing_hh
|
||||
#define lnav_indexing_hh
|
||||
|
||||
#include "file_collection.hh"
|
||||
#include "logfile_fwd.hh"
|
||||
#include "optional.hpp"
|
||||
|
||||
void rebuild_hist();
|
||||
size_t rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline
|
||||
= nonstd::nullopt);
|
||||
void rebuild_indexes_repeatedly();
|
||||
bool rescan_files(bool required = false);
|
||||
bool update_active_files(file_collection& new_files);
|
||||
void do_observer_update(const std::shared_ptr<logfile>& lf);
|
||||
|
||||
#endif
|
@ -0,0 +1,970 @@
|
||||
/**
|
||||
* 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 "fts_fuzzy_match.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 itertools {
|
||||
|
||||
namespace details {
|
||||
|
||||
struct similar_to {
|
||||
std::string st_pattern;
|
||||
size_t st_count;
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
details::similar_to
|
||||
similar_to(std::string pattern, size_t count = 5)
|
||||
{
|
||||
return lnav::itertools::details::similar_to{std::move(pattern), count};
|
||||
}
|
||||
|
||||
} // namespace itertools
|
||||
} // namespace lnav
|
||||
|
||||
template<typename T>
|
||||
std::vector<typename T::value_type>
|
||||
operator|(const T& in, const lnav::itertools::details::similar_to& st)
|
||||
{
|
||||
using score_pair = std::pair<int, typename T::value_type>;
|
||||
|
||||
struct score_cmp {
|
||||
bool operator()(const score_pair& lhs, const score_pair& rhs)
|
||||
{
|
||||
return lhs.first > rhs.first;
|
||||
}
|
||||
};
|
||||
|
||||
std::priority_queue<score_pair, std::vector<score_pair>, score_cmp> pq;
|
||||
|
||||
for (const auto& elem : in) {
|
||||
int score = 0;
|
||||
|
||||
if (!fts::fuzzy_match(st.st_pattern.c_str(), elem.c_str(), score)) {
|
||||
continue;
|
||||
}
|
||||
if (score <= 0) {
|
||||
continue;
|
||||
}
|
||||
pq.push(std::make_pair(score, elem));
|
||||
|
||||
if (pq.size() > st.st_count) {
|
||||
pq.pop();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::remove_const_t<typename T::value_type>> retval;
|
||||
|
||||
while (!pq.empty()) {
|
||||
retval.template emplace_back(pq.top().second);
|
||||
pq.pop();
|
||||
}
|
||||
std::reverse(retval.begin(), retval.end());
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
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::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::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),
|
||||
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 using regex101.com"));
|
||||
|
||||
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 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"));
|
||||
|
||||
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_regex_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,
|
||||
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")
|
||||
.append(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
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2022, Timothy Stack
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Timothy Stack nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef lnav_management_cli_hh
|
||||
#define lnav_management_cli_hh
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "base/lnav.console.hh"
|
||||
#include "CLI/CLI.hpp"
|
||||
|
||||
namespace lnav {
|
||||
namespace management {
|
||||
|
||||
class operations;
|
||||
|
||||
std::shared_ptr<operations> describe_cli(CLI::App& app, int argc, char* argv[]);
|
||||
|
||||
using perform_result_t = std::vector<lnav::console::user_message>;
|
||||
|
||||
perform_result_t perform(std::shared_ptr<operations> opts);
|
||||
|
||||
} // namespace management
|
||||
} // namespace lnav
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue