[regex101] add an integration with regex101

... and a pile of other changes
pull/976/head
Timothy Stack 2 years ago
parent 69b5cb1d58
commit a27198e8ca

2
.gitignore vendored

@ -2,7 +2,7 @@
.lnav .lnav
*.dat *.dat
*.diff *.diff
*.err test/*.err
*.index *.index
*.log *.log
*.o *.o

@ -1,5 +1,10 @@
lnav v0.10.2: lnav v0.10.2:
Features: Features:
* Added an integration with regex101.com to make it easier to edit
log message regular expressions. Using the new "management CLI"
(activated by the -m option), a log format can be created from
a regular expression entry on regex101.com and existing patterns
can be edited.
* Add initial support for pcap(3) files using tshark(1). * Add initial support for pcap(3) files using tshark(1).
* Add format for UniFi gateway. * Add format for UniFi gateway.
@ -13,6 +18,7 @@ lnav v0.10.2:
Fixes: Fixes:
* Toggling enabled/disabled filters when there is a SQL expression * Toggling enabled/disabled filters when there is a SQL expression
no longer causes a crash. no longer causes a crash.
* Fix a crash related to long lines that are word wrapped.
lnav v0.10.1: lnav v0.10.1:
Features: Features:

@ -42,6 +42,8 @@ test_file_base=`basename $1`
# The current test number for shell based tests. # The current test number for shell based tests.
test_num=0 test_num=0
test_hash=""
lnav_test="${top_builddir}/src/lnav-test" lnav_test="${top_builddir}/src/lnav-test"
export lnav_test export lnav_test
@ -61,6 +63,10 @@ export HAVE_SQLITE3_VALUE_SUBTYPE
LAST_TEST="" LAST_TEST=""
LAST_CAP_TEST=()
has_errors=""
# #
# Run a test case and capture its standard out and standard err. # Run a test case and capture its standard out and standard err.
# #
@ -73,11 +79,68 @@ LAST_TEST=""
# run_test rktimes -V # run_test rktimes -V
# #
run_test() { run_test() {
LAST_TEST="test: $@" printf "%s \033[0;35m=============================================================\033[0m\n" $(date -Iseconds)
LAST_TEST=("test: $@")
echo "${LAST_TEST[@]}"
export test_num=`expr ${test_num} \+ 1` export test_num=`expr ${test_num} \+ 1`
"$@" > ${test_file_base}_${test_num}.tmp 2> ${test_file_base}_${test_num}.err "$@" > ${test_file_base}_${test_num}.tmp 2> ${test_file_base}_${test_num}.err
} }
run_cap_test() {
LAST_CAP_TEST=("test: $@")
local full_cmd=$(echo "${LAST_CAP_TEST[@]}" | sed -e "s;${test_dir};{test_dir};g")
export test_hash=$(echo "${full_cmd}" | shasum | cut -f 1 -d ' ')
echo "${full_cmd}" > ${test_file_base}_${test_hash}.cmd
"$@" > ${test_file_base}_${test_hash}.out 2> ${test_file_base}_${test_hash}.err
sed -ibak \
-e "s;${builddir};{builddir};g" \
-e "s;${test_dir};{test_dir};g" \
-e "s;${top_srcdir};{top_srcdir};g" \
${test_file_base}_${test_hash}.out
echo
printf "%s \033[0;35m=============================================================\033[0m\n" $(date -Iseconds)
printf '\033[0;35mCommand\033[0m: %s\n' "${full_cmd}"
printf '\033[0;32mBEGIN\033[0m %s\n' "${test_file_base}_${test_hash}.out"
cat "${test_file_base}_${test_hash}.out"
printf '\033[0;32mEND\033[0m %s\n' "${test_file_base}_${test_hash}.out"
if test -f ${srcdir}/expected/${test_file_base}_${test_hash}.out; then
diff -w -u \
${srcdir}/expected/${test_file_base}_${test_hash}.out \
${test_file_base}_${test_hash}.out \
> ${test_file_base}_${test_hash}.diff
if test $? -ne 0; then
echo OUT: "${full_cmd}"
cat ${test_file_base}_${test_hash}.diff
export has_errors="yes"
fi
else
export has_errors="yes"
fi
sed -ibak -E \
-e "s;${builddir};{builddir};g" \
-e "s;${test_dir};{test_dir};g" \
-e "s;${top_srcdir};{top_srcdir};g" \
-e 's;"errorId":".+";;g' \
${test_file_base}_${test_hash}.err
printf '\033[0;31mBEGIN\033[0m %s\n' "${test_file_base}_${test_hash}.err"
cat "${test_file_base}_${test_hash}.err"
printf '\033[0;31mEND\033[0m %s\n' "${test_file_base}_${test_hash}.err"
if test -f ${srcdir}/expected/${test_file_base}_${test_hash}.err; then
diff -w -u ${srcdir}/expected/${test_file_base}_${test_hash}.err \
${test_file_base}_${test_hash}.err \
> ${test_file_base}_${test_hash}.err.diff
if test $? -ne 0; then
echo ERR: "${full_cmd}"
cat ${test_file_base}_${test_hash}.err.diff
export has_errors="yes"
fi
else
export has_errors="yes"
fi
}
# #
# Check the output generated by a run_test() call. # Check the output generated by a run_test() call.
# #
@ -100,7 +163,7 @@ check_output() {
${test_file_base}_${test_num}.tmp ${test_file_base}_${test_num}.tmp
diff -w -u - ${test_file_base}_${test_num}.tmp > ${test_file_base}_${test_num}.diff diff -w -u - ${test_file_base}_${test_num}.tmp > ${test_file_base}_${test_num}.diff
if test $? -ne 0; then if test $? -ne 0; then
echo $LAST_TEST echo "${LAST_TEST[@]}"
echo $1 echo $1
cat ${test_file_base}_${test_num}.diff cat ${test_file_base}_${test_num}.diff
exit 1 exit 1
@ -110,7 +173,7 @@ check_output() {
check_output_ws() { check_output_ws() {
diff -u - ${test_file_base}_${test_num}.tmp > ${test_file_base}_${test_num}.diff diff -u - ${test_file_base}_${test_num}.tmp > ${test_file_base}_${test_num}.diff
if test $? -ne 0; then if test $? -ne 0; then
echo $LAST_TEST echo "${LAST_TEST[@]}"
echo $1 echo $1
cat ${test_file_base}_${test_num}.diff cat ${test_file_base}_${test_num}.diff
exit 1 exit 1
@ -133,7 +196,7 @@ check_error_output() {
diff -w -u - ${test_file_base}_${test_num}.err \ diff -w -u - ${test_file_base}_${test_num}.err \
> ${test_file_base}_${test_num}.err.diff > ${test_file_base}_${test_num}.err.diff
if test $? -ne 0; then if test $? -ne 0; then
echo $LAST_TEST echo "${LAST_TEST[@]}"
echo $1 echo $1
cat ${test_file_base}_${test_num}.err.diff cat ${test_file_base}_${test_num}.err.diff
exit 1 exit 1
@ -191,3 +254,11 @@ else
shift shift
. ${test_file} . ${test_file}
fi fi
cleanup() {
if test "${has_errors}"x = "yes"x; then
exit 1
fi
}
trap "cleanup" EXIT

@ -1,6 +1,6 @@
# aminclude_static.am generated automatically by Autoconf # aminclude_static.am generated automatically by Autoconf
# from AX_AM_MACROS_STATIC on Thu Mar 31 15:28:54 PDT 2022 # from AX_AM_MACROS_STATIC on Fri Apr 22 09:39:38 PDT 2022
# Code coverage # Code coverage

@ -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

@ -347,6 +347,11 @@
"description": "Styling for 6th-level headers", "description": "Styling for 6th-level headers",
"title": "/ui/theme-defs/<theme_name>/styles/h6", "title": "/ui/theme-defs/<theme_name>/styles/h6",
"$ref": "#/definitions/style" "$ref": "#/definitions/style"
},
"list-glyph": {
"description": "Styling for glyphs that prefix a list item",
"title": "/ui/theme-defs/<theme_name>/styles/list-glyph",
"$ref": "#/definitions/style"
} }
}, },
"additionalProperties": false "additionalProperties": false

@ -314,6 +314,11 @@
"items": { "items": {
"type": "object", "type": "object",
"properties": { "properties": {
"description": {
"title": "/<format_name>/sample/description",
"description": "A description of this sample.",
"type": "string"
},
"line": { "line": {
"title": "/<format_name>/sample/line", "title": "/<format_name>/sample/line",
"description": "A sample log line that should match a pattern in this format.", "description": "A sample log line that should match a pattern in this format.",

@ -1,16 +1,24 @@
.. _cli: .. _cli:
Command Line Interface Command Line Interface
====================== ======================
There are two command-line interfaces provided by **lnav**, one for viewing
files and one for managing **lnav**'s configuration. The file viewing mode is
the default and is all that most people will need. The management mode can
be useful for those that are developing log file formats and is activated by
passing the :option:`-m` option as the first argument.
File Viewing Mode
-----------------
The following options can be used when starting **lnav**. There are not The following options can be used when starting **lnav**. There are not
many flags because the majority of the functionality is accessed using many flags because the majority of the functionality is accessed using
the :option:`-c` option to execute :ref:`commands<commands>` or the :option:`-c` option to execute :ref:`commands<commands>` or
:ref:`SQL queries<sql-ext>`. :ref:`SQL queries<sql-ext>`.
Options Options
------- ^^^^^^^
.. option:: -h .. option:: -h
@ -85,6 +93,32 @@ Options
Do not print the log messages after executing all of the commands. Do not print the log messages after executing all of the commands.
Management Mode (v0.10.2+)
--------------------------
Options
^^^^^^^
.. option:: -m
Switch to management mode. This must be the first option passed on the
command-line.
Subcommands
^^^^^^^^^^^
.. option:: regex101 import <regex101-url> <format-name> [<regex-name>]
Convert a regex101.com entry into a skeleton log format file.
.. option:: format <format-name> regex <regex-name> push
Push a log format regular expression to regex101.com .
.. option:: format <format-name> regex <regex-name> pull
Pull changes to a regex that was previously pushed to regex101.com .
Environment Variables Environment Variables
--------------------- ---------------------

@ -55,6 +55,35 @@ own formats or if you need to modify existing ones. Format directories can
also contain '.sql' and '.lnav' script files that can be used automate log file also contain '.sql' and '.lnav' script files that can be used automate log file
analysis. analysis.
Creating a Format Using Regex101.com (v0.10.2+)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For plain-text log files, the easiest way to create a log format definition is
to create the regular expression that recognizes log messages using
https://regex101.com . Simply copy a log line into the test string input box
on the site and then start editing the regular expression. When building the
regular expression, you'll want to use named captures for the structured parts
of the log message. Any raw message text should be matched by a captured named
"body". Once you have a regex that matches the whole log message, you can use
**lnav**'s "management CLI" to create a skeleton format file. The skeleton
will be populated with the regular expression from the site and the test
string, along with any unit tests, will be added to the "samples" list. The
"regex101 import" management command is used to create the skeleton and has
the following form:
.. prompt:: bash
lnav -m regex101 import <regex101-url> <format-name> [<regex-name>]
If the import was successful, the path to the new format file should be
printed out. The skeleton will most likely need some changes to make it
fully functional. For example, the :code:`kind` properties for captured values
default to :code:`string`, but you'll want to change them to the appropriate
type.
Format File Reference
^^^^^^^^^^^^^^^^^^^^^
An **lnav** format file must contain a single JSON object, preferably with a An **lnav** format file must contain a single JSON object, preferably with a
:code:`$schema` property that refers to the :code:`$schema` property that refers to the
`format-v1.schema <https://lnav.org/schemas/format-v1.schema.json>`_, `format-v1.schema <https://lnav.org/schemas/format-v1.schema.json>`_,
@ -309,8 +338,8 @@ Example format:
} }
} }
Modifying an Existing Format Patching an Existing Format
---------------------------- ---------------------------
When loading log formats from files, **lnav** will overlay any new data over When loading log formats from files, **lnav** will overlay any new data over
previously loaded data. This feature allows you to override existing value or previously loaded data. This feature allows you to override existing value or

@ -68,8 +68,10 @@ set(TIME_FORMATS
"%m/%d/%Y %l:%M:%S %p %Z" "%m/%d/%Y %l:%M:%S %p %Z"
"%m/%e/%Y %I:%M:%S %p" "%m/%e/%Y %I:%M:%S %p"
"%m/%e/%Y %l:%M:%S %p" "%m/%e/%Y %l:%M:%S %p"
"%m/%d/%Y %H:%M:%S"
"%d/%b/%y %H:%M:%S" "%d/%b/%y %H:%M:%S"
"%m%d %H:%M:%S" "%m%d %H:%M:%S"
"%Y%m%d.%H%M%S"
"%H:%M:%S" "%H:%M:%S"
"%M:%S" "%M:%S"
"%m/%d %H:%M:%S" "%m/%d %H:%M:%S"
@ -253,6 +255,7 @@ add_library(
command_executor.cc command_executor.cc
curl_looper.cc curl_looper.cc
db_sub_source.cc db_sub_source.cc
dump_internals.cc
elem_to_json.cc elem_to_json.cc
environ_vtab.cc environ_vtab.cc
extension-functions.cc extension-functions.cc
@ -275,6 +278,8 @@ add_library(
input_dispatcher.cc input_dispatcher.cc
json-extension-functions.cc json-extension-functions.cc
listview_curses.cc listview_curses.cc
lnav.indexing.cc
lnav.management_cli.cc
lnav_commands.cc lnav_commands.cc
lnav_config.cc lnav_config.cc
lnav_util.cc lnav_util.cc
@ -301,6 +306,8 @@ add_library(
readline_highlighters.cc readline_highlighters.cc
readline_possibilities.cc readline_possibilities.cc
regexp_vtab.cc regexp_vtab.cc
regex101.client.cc
regex101.import.cc
relative_time.cc relative_time.cc
session_data.cc session_data.cc
sequence_matcher.cc sequence_matcher.cc
@ -347,6 +354,7 @@ add_library(
column_namer.hh column_namer.hh
curl_looper.hh curl_looper.hh
doc_status_source.hh doc_status_source.hh
dump_internals.hh
elem_to_json.hh elem_to_json.hh
field_overlay_source.hh field_overlay_source.hh
file_collection.hh file_collection.hh
@ -364,6 +372,8 @@ add_library(
hotkeys.hh hotkeys.hh
input_dispatcher.hh input_dispatcher.hh
k_merge_tree.h k_merge_tree.h
lnav.indexing.hh
lnav.management_cli.hh
lnav_config.hh lnav_config.hh
lnav_config_fwd.hh lnav_config_fwd.hh
log_actions.hh log_actions.hh
@ -390,6 +400,8 @@ add_library(
readline_callbacks.hh readline_callbacks.hh
readline_context.hh readline_context.hh
readline_possibilities.hh readline_possibilities.hh
regex101.client.hh
regex101.import.hh
regexp_vtab.hh regexp_vtab.hh
relative_time.hh relative_time.hh
styling.hh styling.hh
@ -422,6 +434,7 @@ add_library(
url_loader.hh url_loader.hh
view_helpers.hh view_helpers.hh
view_helpers.examples.hh view_helpers.examples.hh
view_helpers.hist.hh
views_vtab.hh views_vtab.hh
vis_line.hh vis_line.hh
vtab_module.hh vtab_module.hh
@ -439,7 +452,24 @@ add_library(
ghc/fs_std_fwd.hpp ghc/fs_std_fwd.hpp
ghc/fs_std_impl.hpp ghc/fs_std_impl.hpp
ww898/cp_utf8.hpp ww898/cp_utf8.hpp
log_level_re.cc) log_level_re.cc
third-party/CLI/StringTools.hpp
third-party/CLI/App.hpp
third-party/CLI/Macros.hpp
third-party/CLI/Option.hpp
third-party/CLI/Config.hpp
third-party/CLI/CLI.hpp
third-party/CLI/Formatter.hpp
third-party/CLI/Error.hpp
third-party/CLI/Version.hpp
third-party/CLI/Timer.hpp
third-party/CLI/FormatterFwd.hpp
third-party/CLI/Validators.hpp
third-party/CLI/Split.hpp
third-party/CLI/TypeTools.hpp
third-party/CLI/ConfigFwd.hpp
)
set(lnav_SRCS lnav.cc) set(lnav_SRCS lnav.cc)

@ -170,6 +170,7 @@ noinst_HEADERS = \
data_parser.hh \ data_parser.hh \
db_sub_source.hh \ db_sub_source.hh \
doc_status_source.hh \ doc_status_source.hh \
dump_internals.hh \
elem_to_json.hh \ elem_to_json.hh \
environ_vtab.hh \ environ_vtab.hh \
field_overlay_source.hh \ field_overlay_source.hh \
@ -196,6 +197,8 @@ noinst_HEADERS = \
line_buffer.hh \ line_buffer.hh \
listview_curses.hh \ listview_curses.hh \
lnav.hh \ lnav.hh \
lnav.indexing.hh \
lnav.management_cli.hh \
lnav_commands.hh \ lnav_commands.hh \
lnav_config.hh \ lnav_config.hh \
lnav_config_fwd.hh \ lnav_config_fwd.hh \
@ -233,6 +236,8 @@ noinst_HEADERS = \
readline_curses.hh \ readline_curses.hh \
readline_highlighters.hh \ readline_highlighters.hh \
readline_possibilities.hh \ readline_possibilities.hh \
regex101.client.hh \
regex101.import.hh \
regexp_vtab.hh \ regexp_vtab.hh \
relative_time.hh \ relative_time.hh \
ring_span.hh \ ring_span.hh \
@ -271,6 +276,7 @@ noinst_HEADERS = \
view_curses.hh \ view_curses.hh \
view_helpers.hh \ view_helpers.hh \
view_helpers.examples.hh \ view_helpers.examples.hh \
view_helpers.hist.hh \
views_vtab.hh \ views_vtab.hh \
vis_line.hh \ vis_line.hh \
vt52_curses.hh \ vt52_curses.hh \
@ -295,6 +301,21 @@ nodist_libdiag_a_SOURCES = \
THIRD_PARTY_SRCS = \ THIRD_PARTY_SRCS = \
third-party/backward-cpp/backward.hpp \ third-party/backward-cpp/backward.hpp \
third-party/CLI/StringTools.hpp \
third-party/CLI/App.hpp \
third-party/CLI/Macros.hpp \
third-party/CLI/Option.hpp \
third-party/CLI/Config.hpp \
third-party/CLI/CLI.hpp \
third-party/CLI/Formatter.hpp \
third-party/CLI/Error.hpp \
third-party/CLI/Version.hpp \
third-party/CLI/Timer.hpp \
third-party/CLI/FormatterFwd.hpp \
third-party/CLI/Validators.hpp \
third-party/CLI/Split.hpp \
third-party/CLI/TypeTools.hpp \
third-party/CLI/ConfigFwd.hpp \
third-party/doctest-root/doctest/doctest.h \ third-party/doctest-root/doctest/doctest.h \
third-party/sqlite/ext/dbdump.c \ third-party/sqlite/ext/dbdump.c \
third-party/sqlite/ext/series.c third-party/sqlite/ext/series.c
@ -312,6 +333,7 @@ libdiag_a_SOURCES = \
data_scanner.cc \ data_scanner.cc \
data_scanner_re.cc \ data_scanner_re.cc \
db_sub_source.cc \ db_sub_source.cc \
dump_internals.cc \
elem_to_json.cc \ elem_to_json.cc \
environ_vtab.cc \ environ_vtab.cc \
extension-functions.cc \ extension-functions.cc \
@ -359,6 +381,8 @@ libdiag_a_SOURCES = \
readline_curses.cc \ readline_curses.cc \
readline_highlighters.cc \ readline_highlighters.cc \
readline_possibilities.cc \ readline_possibilities.cc \
regex101.client.cc \
regex101.import.cc \
regexp_vtab.cc \ regexp_vtab.cc \
relative_time.cc \ relative_time.cc \
session_data.cc \ session_data.cc \
@ -398,9 +422,18 @@ PLUGIN_SRCS = \
lnav.$(OBJEXT): help-txt.h init-sql.h lnav.$(OBJEXT): help-txt.h init-sql.h
lnav_SOURCES = lnav.cc $(PLUGIN_SRCS) lnav_SOURCES = \
lnav.cc \
lnav.indexing.cc \
lnav.management_cli.cc \
$(PLUGIN_SRCS)
lnav_test_SOURCES = lnav.cc test_override.c $(PLUGIN_SRCS) lnav_test_SOURCES = \
lnav.cc \
lnav.indexing.cc \
lnav.management_cli.cc \
test_override.c \
$(PLUGIN_SRCS)
ptimec$(BUILD_EXEEXT): ptimec.c ptimec$(BUILD_EXEEXT): ptimec.c
$(AM_V_CC) $(CC_FOR_BUILD) $(CPPFLAGS_FOR_BUILD) $(LDFLAGS_FOR_BUILD) -g3 -o $@ $? $(AM_V_CC) $(CC_FOR_BUILD) $(CPPFLAGS_FOR_BUILD) $(LDFLAGS_FOR_BUILD) -g3 -o $@ $?
@ -419,6 +452,9 @@ DISTCLEANFILES = \
$(LNAV_BUILT_FILES) \ $(LNAV_BUILT_FILES) \
$(RE2C_FILES) $(RE2C_FILES)
distclean-local:
$(RM_V)rm -rf *.dSYM
uncrusty: uncrusty:
(cd $(srcdir) && uncrustify -c ../lnav.cfg --replace $(SOURCES) \ (cd $(srcdir) && uncrustify -c ../lnav.cfg --replace $(SOURCES) \
$(HEADERS)) $(HEADERS))

@ -40,7 +40,9 @@ add_library(
intern_string.hh intern_string.hh
is_utf8.hh is_utf8.hh
isc.hh isc.hh
itertools.hh
lnav.console.hh lnav.console.hh
log_level_enum.hh
lrucache.hpp lrucache.hpp
math_util.hh math_util.hh
network.tcp.hh network.tcp.hh
@ -56,6 +58,7 @@ target_link_libraries(base cppfmt pcre::libpcre ncurses::libcurses pthread)
add_executable( add_executable(
test_base test_base
fs_util.tests.cc
humanize.file_size.tests.cc humanize.file_size.tests.cc
humanize.network.tests.cc humanize.network.tests.cc
humanize.time.tests.cc humanize.time.tests.cc

@ -38,9 +38,11 @@ noinst_HEADERS = \
intern_string.hh \ intern_string.hh \
is_utf8.hh \ is_utf8.hh \
isc.hh \ isc.hh \
itertools.hh \
lnav_log.hh \ lnav_log.hh \
lnav.console.hh \ lnav.console.hh \
lnav.gzip.hh \ lnav.gzip.hh \
log_level_enum.hh \
lrucache.hpp \ lrucache.hpp \
math_util.hh \ math_util.hh \
network.tcp.hh \ network.tcp.hh \
@ -78,6 +80,7 @@ check_PROGRAMS = \
test_base test_base
test_base_SOURCES = \ test_base_SOURCES = \
fs_util.tests.cc \
humanize.file_size.tests.cc \ humanize.file_size.tests.cc \
humanize.network.tests.cc \ humanize.network.tests.cc \
humanize.time.tests.cc \ humanize.time.tests.cc \

@ -190,7 +190,7 @@ attr_line_t::subline(size_t start, size_t len) const
lr.intersection(sa.sa_range).shift(lr.lr_start, -lr.lr_start), lr.intersection(sa.sa_range).shift(lr.lr_start, -lr.lr_start),
std::make_pair(sa.sa_type, sa.sa_value)); std::make_pair(sa.sa_type, sa.sa_value));
line_range& last_lr = retval.al_attrs.back().sa_range; const auto& last_lr = retval.al_attrs.back().sa_range;
ensure(last_lr.lr_end <= (int) retval.al_string.length()); ensure(last_lr.lr_end <= (int) retval.al_string.length());
} }
@ -300,6 +300,18 @@ attr_line_t::erase(size_t pos, size_t len)
return *this; return *this;
} }
attr_line_t&
attr_line_t::pad_to(size_t size)
{
const auto curr_len = this->length();
if (curr_len < size) {
this->append((size - curr_len), ' ');
}
return *this;
}
line_range line_range
line_range::intersection(const line_range& other) const line_range::intersection(const line_range& other) const
{ {

@ -526,6 +526,23 @@ public:
return *this; return *this;
} }
template<typename C>
attr_line_t& join(const C& container,
const string_attr_pair& sap,
const char* fill)
{
bool init = true;
for (const auto& elem : container) {
if (!init) {
this->append(fill);
}
this->append(std::make_pair(elem, sap));
init = false;
}
return *this;
}
attr_line_t& insert(size_t index, attr_line_t& insert(size_t index,
const attr_line_t& al, const attr_line_t& al,
text_wrap_settings* tws = nullptr); text_wrap_settings* tws = nullptr);
@ -560,6 +577,26 @@ public:
return *this; return *this;
} }
template<typename... Args>
attr_line_t& add_header(Args... args)
{
if (!this->blank()) {
this->insert(0, args...);
}
return *this;
}
template<typename... Args>
attr_line_t& with_default(Args... args)
{
if (this->blank()) {
this->clear();
this->append(args...);
}
return *this;
}
attr_line_t& erase(size_t pos, size_t len = std::string::npos); attr_line_t& erase(size_t pos, size_t len = std::string::npos);
attr_line_t& rtrim(); attr_line_t& rtrim();
@ -574,6 +611,8 @@ public:
attr_line_t& right_justify(unsigned long width); attr_line_t& right_justify(unsigned long width);
attr_line_t& pad_to(size_t size);
ssize_t length() const ssize_t length() const
{ {
size_t retval = this->al_string.length(); size_t retval = this->al_string.length();

@ -62,15 +62,17 @@ public:
struct source_location { struct source_location {
source_location() source_location()
: sl_source(intern_string::lookup("unknown")), sl_line_number(-1) : sl_source(intern_string::lookup("unknown")), sl_line_number(0)
{ {
} }
source_location(intern_string_t source, int line) explicit source_location(intern_string_t source, int32_t line = 0)
: sl_source(source), sl_line_number(line){}; : sl_source(source), sl_line_number(line)
{
}
intern_string_t sl_source; intern_string_t sl_source;
int sl_line_number; int32_t sl_line_number;
}; };
#endif #endif

@ -31,6 +31,7 @@
#include "config.h" #include "config.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "itertools.hh"
#include "opt_util.hh" #include "opt_util.hh"
namespace lnav { namespace lnav {
@ -87,25 +88,56 @@ read_file(const ghc::filesystem::path& path)
} }
} }
std::string Result<void, std::string>
build_path(const std::vector<ghc::filesystem::path>& paths) write_file(const ghc::filesystem::path& path, const string_fragment& content)
{ {
std::string retval; auto tmp_pattern = path;
tmp_pattern += ".XXXXXX";
for (const auto& path : paths) { auto tmp_pair = TRY(open_temp_file(tmp_pattern));
if (path.empty()) { auto bytes_written
continue; = write(tmp_pair.second.get(), content.data(), content.length());
} if (bytes_written < 0) {
if (!retval.empty()) { return Err(
retval += ":"; fmt::format(FMT_STRING("unable to write to temporary file {}: {}"),
} tmp_pair.first.string(),
retval += path.string(); strerror(errno)));
}
if (bytes_written != content.length()) {
return Err(fmt::format(FMT_STRING("short write to file {}: {} < {}"),
tmp_pair.first.string(),
bytes_written,
content.length()));
} }
auto env_path = getenv_opt("PATH"); std::error_code ec;
if (env_path) { ghc::filesystem::rename(tmp_pair.first, path, ec);
retval += ":" + std::string(*env_path); if (ec) {
return Err(
fmt::format(FMT_STRING("unable to move temporary file {}: {}"),
tmp_pair.first.string(),
ec.message()));
} }
return retval;
return Ok();
}
std::string
build_path(const std::vector<ghc::filesystem::path>& paths)
{
return paths
| lnav::itertools::map(
static_cast<std::string (ghc::filesystem::path::*)() const>(
&ghc::filesystem::path::string))
| lnav::itertools::append(getenv_opt("PATH").value_or(""))
| lnav::itertools::filter_out(&std::string::empty)
| lnav::itertools::fold(
[](const auto& elem, auto& accum) {
if (!accum.empty()) {
accum.push_back(':');
}
return accum.append(elem);
},
std::string());
} }
Result<struct stat, std::string> Result<struct stat, std::string>

@ -35,6 +35,7 @@
#include "auto_fd.hh" #include "auto_fd.hh"
#include "ghc/filesystem.hpp" #include "ghc/filesystem.hpp"
#include "intern_string.hh"
#include "result.h" #include "result.h"
namespace lnav { namespace lnav {
@ -69,6 +70,9 @@ Result<std::pair<ghc::filesystem::path, auto_fd>, std::string> open_temp_file(
Result<std::string, std::string> read_file(const ghc::filesystem::path& path); Result<std::string, std::string> read_file(const ghc::filesystem::path& path);
Result<void, std::string> write_file(const ghc::filesystem::path& path,
const string_fragment& content);
std::string build_path(const std::vector<ghc::filesystem::path>& paths); std::string build_path(const std::vector<ghc::filesystem::path>& paths);
} // namespace filesystem } // namespace filesystem

@ -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);
}
}

@ -64,4 +64,30 @@ struct noop_func {
} }
}; };
namespace lnav {
namespace func {
template<typename Fn,
typename... Args,
std::enable_if_t<std::is_member_pointer<std::decay_t<Fn>>{}, int> = 0>
constexpr decltype(auto)
invoke(Fn&& f, Args&&... args) noexcept(
noexcept(std::mem_fn(f)(std::forward<Args>(args)...)))
{
return std::mem_fn(f)(std::forward<Args>(args)...);
}
template<typename Fn,
typename... Args,
std::enable_if_t<!std::is_member_pointer<std::decay_t<Fn>>{}, int> = 0>
constexpr decltype(auto)
invoke(Fn&& f, Args&&... args) noexcept(
noexcept(std::forward<Fn>(f)(std::forward<Args>(args)...)))
{
return std::forward<Fn>(f)(std::forward<Args>(args)...);
}
} // namespace func
} // namespace lnav
#endif #endif

@ -185,3 +185,20 @@ string_fragment::consume_n(int amount) const
this->sf_end, this->sf_end,
}; };
} }
std::vector<string_fragment>
string_fragment::split_lines() const
{
std::vector<string_fragment> retval;
int start = this->sf_begin;
for (auto index = start; index < this->sf_end; index++) {
if ((*this)[index] == '\n') {
retval.emplace_back(this->sf_string, start, index + 1);
start = index + 1;
}
}
retval.emplace_back(this->sf_string, start, this->sf_end);
return retval;
}

@ -33,6 +33,7 @@
#define intern_string_hh #define intern_string_hh
#include <string> #include <string>
#include <vector>
#include <string.h> #include <string.h>
#include <sys/types.h> #include <sys/types.h>
@ -239,13 +240,12 @@ struct string_fragment {
}); });
} }
std::vector<string_fragment> split_lines() const;
struct tag1 { struct tag1 {
const char t_value; const char t_value;
bool operator()(char ch) const bool operator()(char ch) const { return this->t_value == ch; }
{
return this->t_value == ch;
}
}; };
struct quoted_string_body { struct quoted_string_body {
@ -367,20 +367,11 @@ public:
{ {
} }
const intern_string* unwrap() const const intern_string* unwrap() const { return this->ist_interned_string; }
{
return this->ist_interned_string;
}
void clear() void clear() { this->ist_interned_string = nullptr; };
{
this->ist_interned_string = nullptr;
};
bool empty() const bool empty() const { return this->ist_interned_string == nullptr; }
{
return this->ist_interned_string == nullptr;
}
const char* get() const const char* get() const
{ {
@ -390,15 +381,11 @@ public:
return this->ist_interned_string->get(); return this->ist_interned_string->get();
} }
iterator begin() const const char* c_str() const { return this->get(); }
{
return this->get();
}
iterator end() const iterator begin() const { return this->get(); }
{
return this->get() + this->size(); iterator end() const { return this->get() + this->size(); }
}
size_t size() const size_t size() const
{ {
@ -456,6 +443,11 @@ public:
return strcmp(this->get(), rhs) != 0; return strcmp(this->get(), rhs) != 0;
} }
static bool case_lt(const intern_string_t& lhs, const intern_string_t& rhs)
{
return strnatcasecmp(lhs.size(), lhs.get(), rhs.size(), rhs.get()) < 0;
}
private: private:
const intern_string* ist_interned_string; const intern_string* ist_interned_string;
}; };

@ -35,6 +35,29 @@
#include "config.h" #include "config.h"
#include "doctest/doctest.h" #include "doctest/doctest.h"
TEST_CASE("split_lines")
{
std::string in1 = "Hello, World!";
std::string in2 = "Hello, World!\nGoodbye, World!";
{
auto sf = string_fragment(in1);
auto split = sf.split_lines();
CHECK(1 == split.size());
CHECK(in1 == split[0].to_string());
}
{
auto sf = string_fragment(in2);
auto split = sf.split_lines();
CHECK(2 == split.size());
CHECK("Hello, World!\n" == split[0].to_string());
CHECK("Goodbye, World!" == split[1].to_string());
}
}
TEST_CASE("consume") TEST_CASE("consume")
{ {
auto is_eq = string_fragment::tag1{'='}; auto is_eq = string_fragment::tag1{'='};

@ -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

@ -33,11 +33,25 @@
#include "config.h" #include "config.h"
#include "fmt/color.h" #include "fmt/color.h"
#include "itertools.hh"
#include "log_level_enum.hh"
#include "view_curses.hh" #include "view_curses.hh"
using namespace lnav::roles::literals;
namespace lnav { namespace lnav {
namespace console { namespace console {
user_message
user_message::raw(const attr_line_t& al)
{
user_message retval;
retval.um_level = level::raw;
retval.um_message.append(al);
return retval;
}
user_message user_message
user_message::error(const attr_line_t& al) user_message::error(const attr_line_t& al)
{ {
@ -81,10 +95,17 @@ user_message::warning(const attr_line_t& al)
attr_line_t attr_line_t
user_message::to_attr_line(std::set<render_flags> flags) const user_message::to_attr_line(std::set<render_flags> flags) const
{ {
auto indent = 1;
attr_line_t retval; attr_line_t retval;
if (this->um_level == level::warning) {
indent = 3;
}
if (flags.count(render_flags::prefix)) { if (flags.count(render_flags::prefix)) {
switch (this->um_level) { switch (this->um_level) {
case level::raw:
break;
case level::ok: case level::ok:
retval.append(lnav::roles::ok("\u2714 ")); retval.append(lnav::roles::ok("\u2714 "));
break; break;
@ -105,13 +126,18 @@ user_message::to_attr_line(std::set<render_flags> flags) const
if (!this->um_reason.empty()) { if (!this->um_reason.empty()) {
bool first_line = true; bool first_line = true;
for (const auto& line : this->um_reason.split_lines()) { for (const auto& line : this->um_reason.split_lines()) {
auto role = this->um_level == level::error ? role_t::VCR_ERROR
: role_t::VCR_WARNING;
attr_line_t prefix; attr_line_t prefix;
if (first_line) { if (first_line) {
prefix.append(lnav::roles::error(" reason")).append(": "); prefix.append(indent, ' ')
.append("reason", VC_ROLE.value(role))
.append(": ");
first_line = false; first_line = false;
} else { } else {
prefix.append(lnav::roles::error(" | ")); prefix.append(" | ", VC_ROLE.value(role))
.append(indent, ' ');
} }
retval.append(prefix).append(line).append("\n"); retval.append(prefix).append(line).append("\n");
} }
@ -120,35 +146,34 @@ user_message::to_attr_line(std::set<render_flags> flags) const
for (const auto& snip : this->um_snippets) { for (const auto& snip : this->um_snippets) {
attr_line_t header; attr_line_t header;
header.append(lnav::roles::comment(" --> ")) header.append(" --> "_comment)
.append(lnav::roles::file(snip.s_source)); .append(lnav::roles::file(snip.s_location.sl_source.get()));
if (snip.s_line > 0) { if (snip.s_location.sl_line_number > 0) {
header.append(":").append(FMT_STRING("{}"), snip.s_line); header.append(":").append(FMT_STRING("{}"),
if (snip.s_column > 0) { snip.s_location.sl_line_number);
header.append(":").append(FMT_STRING("{}"), snip.s_column);
}
} }
retval.append(header).append("\n"); retval.append(header).append("\n");
if (!snip.s_content.blank()) { if (!snip.s_content.blank()) {
for (const auto& line : snip.s_content.split_lines()) { for (const auto& line : snip.s_content.split_lines()) {
retval.append(lnav::roles::comment(" | ")) retval.append(" | "_comment).append(line).append("\n");
.append(line)
.append("\n");
} }
} }
} }
} }
if (!this->um_notes.empty()) { if (!this->um_notes.empty()) {
bool first_line = true;
for (const auto& note : this->um_notes) { for (const auto& note : this->um_notes) {
bool first_line = true;
for (const auto& line : note.split_lines()) { for (const auto& line : note.split_lines()) {
attr_line_t prefix; attr_line_t prefix;
if (first_line) { if (first_line) {
prefix.append(lnav::roles::comment(" = note")).append(": "); prefix.append(" ="_comment)
.append(indent, ' ')
.append("note"_comment)
.append(": ");
first_line = false; first_line = false;
} else { } else {
prefix.append(" "); prefix.append(" ").append(indent, ' ');
} }
retval.append(prefix).append(line).append("\n"); retval.append(prefix).append(line).append("\n");
@ -161,7 +186,10 @@ user_message::to_attr_line(std::set<render_flags> flags) const
attr_line_t prefix; attr_line_t prefix;
if (first_line) { if (first_line) {
prefix.append(lnav::roles::comment(" = help")).append(": "); prefix.append(" ="_comment)
.append(indent, ' ')
.append("help"_comment)
.append(": ");
first_line = false; first_line = false;
} else { } else {
prefix.append(" "); prefix.append(" ");
@ -174,85 +202,152 @@ user_message::to_attr_line(std::set<render_flags> flags) const
return retval; return retval;
} }
fmt::terminal_color
curses_color_to_terminal_color(int curses_color)
{
switch (curses_color) {
case COLOR_BLACK:
return fmt::terminal_color::black;
case COLOR_CYAN:
return fmt::terminal_color::cyan;
case COLOR_WHITE:
return fmt::terminal_color::white;
case COLOR_MAGENTA:
return fmt::terminal_color::magenta;
case COLOR_BLUE:
return fmt::terminal_color::blue;
case COLOR_YELLOW:
return fmt::terminal_color::yellow;
case COLOR_GREEN:
return fmt::terminal_color::green;
case COLOR_RED:
return fmt::terminal_color::red;
}
ensure(false);
}
void void
println(FILE* file, const attr_line_t& al) println(FILE* file, const attr_line_t& al)
{ {
const auto& str = al.get_string(); const auto& str = al.get_string();
if (!isatty(fileno(file))) { if (getenv("NO_COLOR") != nullptr
|| (!isatty(fileno(file)) && getenv("YES_COLOR") == nullptr))
{
fmt::print(file, "{}\n", str); fmt::print(file, "{}\n", str);
return; return;
} }
string_attrs_t style_attrs; std::set<int> points = {0, (int) al.length()};
for (const auto& sa : al.get_attrs()) { for (const auto& attr : al.get_attrs()) {
if (sa.sa_type != &VC_ROLE) { if (!attr.sa_range.is_valid()) {
continue; continue;
} }
points.insert(attr.sa_range.lr_start);
style_attrs.emplace_back(sa); if (attr.sa_range.lr_end > 0) {
points.insert(attr.sa_range.lr_end);
}
} }
std::sort(style_attrs.begin(), style_attrs.end(), [](auto lhs, auto rhs) { nonstd::optional<int> last_point;
return lhs.sa_range < rhs.sa_range; for (const auto& point : points) {
}); if (last_point) {
auto line_style = fmt::text_style{};
auto start = size_t{0}; auto fg_style = fmt::text_style{};
for (const auto& attr : style_attrs) { auto start = last_point.value();
fmt::print(
file, "{}", str.substr(start, attr.sa_range.lr_start - start)); for (const auto& attr : al.get_attrs()) {
if (attr.sa_type == &VC_ROLE) { if (!attr.sa_range.contains(start)
auto saw = string_attr_wrapper<role_t>(&attr); && !attr.sa_range.contains(point - 1)) {
auto role = saw.get(); continue;
auto line_style = fmt::text_style(); }
switch (role) { if (attr.sa_type == &VC_BACKGROUND) {
case role_t::VCR_ERROR: auto saw = string_attr_wrapper<int64_t>(&attr);
line_style = fmt::fg(fmt::terminal_color::red); auto color = saw.get();
break;
case role_t::VCR_WARNING: if (color >= 0) {
line_style = fmt::fg(fmt::terminal_color::yellow); line_style
break; |= fmt::bg(curses_color_to_terminal_color(color));
case role_t::VCR_COMMENT: }
line_style = fmt::fg(fmt::terminal_color::cyan); } else if (attr.sa_type == &VC_FOREGROUND) {
break; auto saw = string_attr_wrapper<int64_t>(&attr);
case role_t::VCR_OK: auto color = saw.get();
line_style = fmt::emphasis::bold
| fmt::fg(fmt::terminal_color::red); if (color >= 0) {
break; fg_style
case role_t::VCR_STATUS: = fmt::fg(curses_color_to_terminal_color(color));
line_style = fmt::emphasis::bold }
| fmt::fg(fmt::terminal_color::magenta); } else if (attr.sa_type == &VC_ROLE) {
break; auto saw = string_attr_wrapper<role_t>(&attr);
case role_t::VCR_VARIABLE: auto role = saw.get();
line_style = fmt::emphasis::underline;
break; switch (role) {
case role_t::VCR_SYMBOL: case role_t::VCR_ERROR:
case role_t::VCR_NUMBER: line_style |= fmt::fg(fmt::terminal_color::red);
case role_t::VCR_FILE: break;
line_style = fmt::emphasis::bold; case role_t::VCR_WARNING:
break; line_style |= fmt::fg(fmt::terminal_color::yellow);
case role_t::VCR_H1: break;
case role_t::VCR_H2: case role_t::VCR_COMMENT:
case role_t::VCR_H3: line_style |= fmt::fg(fmt::terminal_color::cyan);
case role_t::VCR_H4: break;
case role_t::VCR_H5: case role_t::VCR_OK:
case role_t::VCR_H6: line_style |= fmt::emphasis::bold
line_style = fmt::emphasis::underline; | fmt::fg(fmt::terminal_color::green);
break; break;
default: case role_t::VCR_STATUS:
break; line_style |= fmt::emphasis::bold
| fmt::fg(fmt::terminal_color::magenta);
break;
case role_t::VCR_KEYWORD:
line_style |= fmt::emphasis::bold
| fmt::fg(fmt::terminal_color::blue);
break;
case role_t::VCR_VARIABLE:
line_style |= fmt::emphasis::underline;
break;
case role_t::VCR_SYMBOL:
case role_t::VCR_NUMBER:
case role_t::VCR_FILE:
line_style |= fmt::emphasis::bold;
break;
case role_t::VCR_H1:
line_style |= fmt::emphasis::bold
| fmt::fg(fmt::terminal_color::magenta);
break;
case role_t::VCR_H2:
line_style |= fmt::emphasis::bold;
break;
case role_t::VCR_H3:
case role_t::VCR_H4:
case role_t::VCR_H5:
case role_t::VCR_H6:
line_style |= fmt::emphasis::underline;
break;
case role_t::VCR_LIST_GLYPH:
line_style |= fmt::fg(fmt::terminal_color::yellow);
break;
default:
break;
}
}
} }
fmt::print(
file, if (!line_style.has_foreground() && fg_style.has_foreground()) {
line_style, line_style |= fg_style;
"{}", }
str.substr(attr.sa_range.lr_start, attr.sa_range.length()));
fmt::print(file,
line_style,
FMT_STRING("{}"),
str.substr(start, point - start));
} }
start = attr.sa_range.lr_end; last_point = point;
} }
fmt::print(file, "{}\n", str.substr(start)); fmt::print(file, "\n");
} }
void void

@ -34,6 +34,7 @@
#include <vector> #include <vector>
#include "base/attr_line.hh" #include "base/attr_line.hh"
#include "base/file_range.hh"
namespace lnav { namespace lnav {
namespace console { namespace console {
@ -41,41 +42,45 @@ namespace console {
void println(FILE* file, const attr_line_t& al); void println(FILE* file, const attr_line_t& al);
struct snippet { struct snippet {
static snippet from(std::string src, const attr_line_t& content) static snippet from(intern_string_t src, const attr_line_t& content)
{ {
snippet retval; snippet retval;
retval.s_source = std::move(src); retval.s_location.sl_source = src;
retval.s_content = content; retval.s_content = content;
return retval; return retval;
} }
snippet& with_line(int32_t line) static snippet from(source_location loc, const attr_line_t& content)
{ {
this->s_line = line; snippet retval;
return *this;
retval.s_location = loc;
retval.s_content = content;
return retval;
} }
snippet& with_column(int32_t column) snippet& with_line(int32_t line)
{ {
this->s_column = column; this->s_location.sl_line_number = line;
return *this; return *this;
} }
std::string s_source; source_location s_location;
int32_t s_line{0};
int32_t s_column{0};
attr_line_t s_content; attr_line_t s_content;
}; };
struct user_message { struct user_message {
enum class level { enum class level {
raw,
ok, ok,
info, info,
warning, warning,
error, error,
}; };
static user_message raw(const attr_line_t& al);
static user_message error(const attr_line_t& al); static user_message error(const attr_line_t& al);
static user_message warning(const attr_line_t& al); static user_message warning(const attr_line_t& al);
@ -91,6 +96,11 @@ struct user_message {
return *this; return *this;
} }
user_message& with_reason(const user_message& um)
{
return this->with_reason(um.to_attr_line({}));
}
user_message& with_errno_reason() user_message& with_errno_reason()
{ {
this->um_reason = strerror(errno); this->um_reason = strerror(errno);
@ -113,15 +123,21 @@ struct user_message {
user_message& with_note(const attr_line_t& al) user_message& with_note(const attr_line_t& al)
{ {
this->um_notes.emplace_back(al); if (!al.blank()) {
this->um_notes.emplace_back(al);
}
return *this; return *this;
} }
user_message& with_help(const attr_line_t& al) user_message& with_help(const attr_line_t& al)
{ {
this->um_help = al; if (al.blank()) {
this->um_help.rtrim(); this->um_help.clear();
} else {
this->um_help = al;
this->um_help.rtrim();
}
return *this; return *this;
} }

@ -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

@ -38,6 +38,7 @@ string_attr_type<const intern_string_t> SA_FORMAT("format");
string_attr_type<void> SA_REMOVED("removed"); string_attr_type<void> SA_REMOVED("removed");
string_attr_type<std::string> SA_INVALID("invalid"); string_attr_type<std::string> SA_INVALID("invalid");
string_attr_type<std::string> SA_ERROR("error"); string_attr_type<std::string> SA_ERROR("error");
string_attr_type<int64_t> SA_LEVEL("level");
string_attr_type<role_t> VC_ROLE("role"); string_attr_type<role_t> VC_ROLE("role");
string_attr_type<role_t> VC_ROLE_FG("role-fg"); string_attr_type<role_t> VC_ROLE_FG("role-fg");

@ -109,6 +109,8 @@ enum class role_t : int32_t {
VCR_H5, VCR_H5,
VCR_H6, VCR_H6,
VCR_LIST_GLYPH,
VCR__MAX VCR__MAX
}; };
@ -162,6 +164,7 @@ extern string_attr_type<const intern_string_t> SA_FORMAT;
extern string_attr_type<void> SA_REMOVED; extern string_attr_type<void> SA_REMOVED;
extern string_attr_type<std::string> SA_INVALID; extern string_attr_type<std::string> SA_INVALID;
extern string_attr_type<std::string> SA_ERROR; extern string_attr_type<std::string> SA_ERROR;
extern string_attr_type<int64_t> SA_LEVEL;
extern string_attr_type<role_t> VC_ROLE; extern string_attr_type<role_t> VC_ROLE;
extern string_attr_type<role_t> VC_ROLE_FG; extern string_attr_type<role_t> VC_ROLE_FG;
@ -310,6 +313,13 @@ inline std::pair<std::string, string_attr_pair> operator"" _symbol(
VC_ROLE.template value(role_t::VCR_SYMBOL)); VC_ROLE.template value(role_t::VCR_SYMBOL));
} }
inline std::pair<std::string, string_attr_pair> operator"" _keyword(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_KEYWORD));
}
inline std::pair<std::string, string_attr_pair> operator"" _variable( inline std::pair<std::string, string_attr_pair> operator"" _variable(
const char* str, std::size_t len) const char* str, std::size_t len)
{ {
@ -317,6 +327,13 @@ inline std::pair<std::string, string_attr_pair> operator"" _variable(
VC_ROLE.template value(role_t::VCR_VARIABLE)); VC_ROLE.template value(role_t::VCR_VARIABLE));
} }
inline std::pair<std::string, string_attr_pair> operator"" _comment(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_COMMENT));
}
inline std::pair<std::string, string_attr_pair> operator"" _h1(const char* str, inline std::pair<std::string, string_attr_pair> operator"" _h1(const char* str,
std::size_t len) std::size_t len)
{ {
@ -338,6 +355,13 @@ inline std::pair<std::string, string_attr_pair> operator"" _h3(const char* str,
VC_ROLE.template value(role_t::VCR_H3)); VC_ROLE.template value(role_t::VCR_H3));
} }
inline std::pair<std::string, string_attr_pair> operator"" _list_glyph(
const char* str, std::size_t len)
{
return std::make_pair(std::string(str, len),
VC_ROLE.template value(role_t::VCR_LIST_GLYPH));
}
} // namespace literals } // namespace literals
} // namespace roles } // namespace roles

@ -153,7 +153,7 @@ truncate_to(std::string& str, size_t max_char_len)
} }
bool bool
is_url(const char* fn) is_url(const std::string& fn)
{ {
static const auto url_re = std::regex("^(file|https?|ftps?|scp|sftp):.*"); static const auto url_re = std::regex("^(file|https?|ftps?|scp|sftp):.*");

@ -105,6 +105,17 @@ trim(const std::string& str)
return str.substr(start, end - start); return str.substr(start, end - start);
} }
inline std::string
rtrim(const std::string& str)
{
std::string::size_type end;
for (end = str.size(); end > 0 && isspace(str[end - 1]); end--)
;
return str.substr(0, end);
}
inline std::string inline std::string
tolower(const char* str) tolower(const char* str)
{ {
@ -186,7 +197,7 @@ utf8_string_length(const std::string& str)
return utf8_string_length(str.c_str(), str.length()); return utf8_string_length(str.c_str(), str.length());
} }
bool is_url(const char* fn); bool is_url(const std::string& fn);
bool is_blank(const std::string& str); bool is_blank(const std::string& str);
@ -201,4 +212,14 @@ std::string center_str(const std::string& subject, size_t width);
template<typename T> template<typename T>
size_t strtonum(T& num_out, const char* data, size_t len); size_t strtonum(T& num_out, const char* data, size_t len);
inline std::string
on_blank(const std::string& str, const std::string& def)
{
if (is_blank(str)) {
return def;
}
return str;
}
#endif #endif

@ -31,6 +31,7 @@
#include "bookmarks.hh" #include "bookmarks.hh"
#include "base/itertools.hh"
#include "config.h" #include "config.h"
std::unordered_set<std::string> bookmark_metadata::KNOWN_TAGS; std::unordered_set<std::string> bookmark_metadata::KNOWN_TAGS;
@ -38,10 +39,8 @@ std::unordered_set<std::string> bookmark_metadata::KNOWN_TAGS;
void void
bookmark_metadata::add_tag(const std::string& tag) bookmark_metadata::add_tag(const std::string& tag)
{ {
if (std::find(this->bm_tags.begin(), this->bm_tags.end(), tag) if (!(this->bm_tags | lnav::itertools::find(tag))) {
== this->bm_tags.end()) this->bm_tags.emplace_back(tag);
{
this->bm_tags.push_back(tag);
} }
} }
@ -72,14 +71,18 @@ bookmark_metadata::clear()
this->bm_tags.clear(); this->bm_tags.clear();
} }
bookmark_type_t* nonstd::optional<bookmark_type_t*>
bookmark_type_t::find_type(const std::string& name) bookmark_type_t::find_type(const std::string& name)
{ {
auto iter = std::find_if(type_begin(), type_end(), mark_eq(name)); return get_all_types()
bookmark_type_t* retval = nullptr; | lnav::itertools::find_if(
[&name](const auto& elem) { return elem->bt_name == name; });
}
if (iter != type_end()) { std::vector<bookmark_type_t*>&
retval = (*iter); bookmark_type_t::get_all_types()
} {
return retval; static std::vector<bookmark_type_t*> all_types;
return all_types;
} }

@ -141,47 +141,23 @@ class bookmark_type_t {
public: public:
using type_iterator = std::vector<bookmark_type_t*>::iterator; using type_iterator = std::vector<bookmark_type_t*>::iterator;
static type_iterator type_begin() static type_iterator type_begin() { return get_all_types().begin(); }
{
return get_all_types().begin();
}
static type_iterator type_end() static type_iterator type_end() { return get_all_types().end(); }
{
return get_all_types().end();
}
static bookmark_type_t* find_type(const std::string& name); static nonstd::optional<bookmark_type_t*> find_type(
const std::string& name);
static std::vector<bookmark_type_t*>& get_all_types() static std::vector<bookmark_type_t*>& get_all_types();
{
static std::vector<bookmark_type_t*> all_types;
return all_types;
}
explicit bookmark_type_t(std::string name) : bt_name(std::move(name)) explicit bookmark_type_t(std::string name) : bt_name(std::move(name))
{ {
get_all_types().push_back(this); get_all_types().push_back(this);
} }
const std::string& get_name() const const std::string& get_name() const { return this->bt_name; }
{
return this->bt_name;
}
private: private:
struct mark_eq {
explicit mark_eq(const std::string& name) : me_name(name){};
bool operator()(bookmark_type_t* bt)
{
return bt->bt_name == this->me_name;
};
const std::string& me_name;
};
const std::string bt_name; const std::string bt_name;
}; };

@ -53,20 +53,17 @@ public:
bottom_status_source(); bottom_status_source();
status_field& get_field(field_t id) status_field& get_field(field_t id) { return this->bss_fields[id]; }
{
return this->bss_fields[id];
};
void set_prompt(const std::string& prompt) void set_prompt(const std::string& prompt)
{ {
this->bss_prompt.set_value(prompt); this->bss_prompt.set_value(prompt);
}; }
void grep_error(const std::string& msg) override void grep_error(const std::string& msg) override
{ {
this->bss_error.set_value(msg); this->bss_error.set_value(msg);
}; }
size_t statusview_fields() override; size_t statusview_fields() override;

@ -44,32 +44,29 @@ struct byte_array {
static constexpr size_t BYTE_COUNT = COUNT * sizeof(T); static constexpr size_t BYTE_COUNT = COUNT * sizeof(T);
static constexpr size_t STRING_SIZE = BYTE_COUNT * 2 + 1; static constexpr size_t STRING_SIZE = BYTE_COUNT * 2 + 1;
byte_array(){}; byte_array() {}
byte_array(const byte_array& other) byte_array(const byte_array& other)
{ {
memcpy(this->ba_data, other.ba_data, BYTE_COUNT); memcpy(this->ba_data, other.ba_data, BYTE_COUNT);
}; }
bool operator<(const byte_array& other) const bool operator<(const byte_array& other) const
{ {
return memcmp(this->ba_data, other.ba_data, BYTE_COUNT) < 0; return memcmp(this->ba_data, other.ba_data, BYTE_COUNT) < 0;
}; }
bool operator!=(const byte_array& other) const bool operator!=(const byte_array& other) const
{ {
return memcmp(this->ba_data, other.ba_data, BYTE_COUNT) != 0; return memcmp(this->ba_data, other.ba_data, BYTE_COUNT) != 0;
}; }
bool operator==(const byte_array& other) const bool operator==(const byte_array& other) const
{ {
return memcmp(this->ba_data, other.ba_data, BYTE_COUNT) == 0; return memcmp(this->ba_data, other.ba_data, BYTE_COUNT) == 0;
}; }
void clear() void clear() { memset(this->ba_data, 0, BYTE_COUNT); }
{
memset(this->ba_data, 0, BYTE_COUNT);
};
template<typename OutputIt> template<typename OutputIt>
void to_string(OutputIt out) const void to_string(OutputIt out) const
@ -88,17 +85,14 @@ struct byte_array {
return retval; return retval;
} }
const unsigned char* in() const const unsigned char* in() const { return this->ba_data; }
{
return this->ba_data;
};
T* out(int offset = 0) T* out(int offset = 0)
{ {
T* ptr = (T*) this->ba_data; T* ptr = (T*) this->ba_data;
return &ptr[offset]; return &ptr[offset];
}; }
unsigned char ba_data[BYTE_COUNT]; unsigned char ba_data[BYTE_COUNT];
}; };

@ -33,6 +33,7 @@
#include "column_namer.hh" #include "column_namer.hh"
#include "base/itertools.hh"
#include "base/lnav_log.hh" #include "base/lnav_log.hh"
#include "base/string_util.hh" #include "base/string_util.hh"
#include "config.h" #include "config.h"
@ -47,17 +48,11 @@ column_namer::existing_name(const std::string& in_name) const
return true; return true;
} }
if (std::find(this->cn_builtin_names.begin(), if (this->cn_builtin_names | lnav::itertools::find(in_name)) {
this->cn_builtin_names.end(),
in_name)
!= this->cn_builtin_names.end())
{
return true; return true;
} }
if (std::find(this->cn_names.begin(), this->cn_names.end(), in_name) if (this->cn_names | lnav::itertools::find(in_name)) {
!= this->cn_names.end())
{
return true; return true;
} }

@ -40,6 +40,7 @@
#include "db_sub_source.hh" #include "db_sub_source.hh"
#include "help_text_formatter.hh" #include "help_text_formatter.hh"
#include "lnav.hh" #include "lnav.hh"
#include "lnav.indexing.hh"
#include "lnav_config.hh" #include "lnav_config.hh"
#include "lnav_util.hh" #include "lnav_util.hh"
#include "log_format_loader.hh" #include "log_format_loader.hh"
@ -175,8 +176,7 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg)
auto source = ec.ec_source.top(); auto source = ec.ec_source.top();
sql_progress_guard progress_guard(sql_progress, sql_progress_guard progress_guard(sql_progress,
sql_progress_finished, sql_progress_finished,
source.s_source, source.s_location,
source.s_line,
source.s_content); source.s_content);
gettimeofday(&start_tv, nullptr); gettimeofday(&start_tv, nullptr);
retcode = sqlite3_prepare_v2( retcode = sqlite3_prepare_v2(
@ -315,8 +315,17 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg)
log_error("sqlite3_step error code: %d", retcode); log_error("sqlite3_step error code: %d", retcode);
errmsg = sqlite3_errmsg(lnav_data.ld_db); errmsg = sqlite3_errmsg(lnav_data.ld_db);
if (startswith(errmsg, "lnav-error:")) { if (startswith(errmsg, "lnav-error:")) {
return Err(lnav::from_json<lnav::console::user_message>( auto from_res
&errmsg[11])); = lnav::from_json<lnav::console::user_message>(
&errmsg[11]);
if (from_res.isOk()) {
return Err(from_res.unwrap());
}
return ec.make_error(
"internal error: {}",
from_res.unwrapErr()[0].um_message.get_string());
} }
return ec.make_error("{}", errmsg); return ec.make_error("{}", errmsg);
} }
@ -344,7 +353,7 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg)
} }
if (lnav_data.ld_rl_view != nullptr) { if (lnav_data.ld_rl_view != nullptr) {
lnav_data.ld_rl_view->set_value(""); lnav_data.ld_rl_view->clear_value();
} }
} }
@ -607,7 +616,8 @@ execute_from_file(exec_context& ec,
const std::string& cmdline) const std::string& cmdline)
{ {
std::string retval, alt_msg; std::string retval, alt_msg;
auto _sg = ec.enter_source(path.string(), line_number, cmdline); auto _sg = ec.enter_source(
intern_string::lookup(path.string()), line_number, cmdline);
switch (mode) { switch (mode) {
case ':': case ':':
@ -691,12 +701,15 @@ execute_init_commands(
log_info("Executing initial commands"); log_info("Executing initial commands");
for (auto& cmd : lnav_data.ld_commands) { for (auto& cmd : lnav_data.ld_commands) {
static const auto COMMAND_OPTION_SRC
= intern_string::lookup("command-option");
std::string alt_msg; std::string alt_msg;
wait_for_children(); wait_for_children();
{ {
auto _sg = ec.enter_source("command-option", option_index++, cmd); auto _sg = ec.enter_source(COMMAND_OPTION_SRC, option_index++, cmd);
switch (cmd.at(0)) { switch (cmd.at(0)) {
case ':': case ':':
msgs.emplace_back(execute_command(ec, cmd.substr(1)), msgs.emplace_back(execute_command(ec, cmd.substr(1)),
@ -906,10 +919,12 @@ exec_context::exec_context(std::vector<logline_value>* line_values,
ec_accumulator(std::make_unique<attr_line_t>()), ec_accumulator(std::make_unique<attr_line_t>()),
ec_sql_callback(sql_callback), ec_pipe_callback(pipe_callback) ec_sql_callback(sql_callback), ec_pipe_callback(pipe_callback)
{ {
static const auto COMMAND_SRC = intern_string::lookup("command");
this->ec_local_vars.push(std::map<std::string, std::string>()); this->ec_local_vars.push(std::map<std::string, std::string>());
this->ec_path_stack.emplace_back("."); this->ec_path_stack.emplace_back(".");
this->ec_source.emplace( this->ec_source.emplace(
lnav::console::snippet::from("command", "").with_line(1)); lnav::console::snippet::from(COMMAND_SRC, "").with_line(1));
this->ec_output_stack.emplace_back("screen", nonstd::nullopt); this->ec_output_stack.emplace_back("screen", nonstd::nullopt);
} }

@ -138,7 +138,7 @@ struct exec_context {
exec_context& sg_context; exec_context& sg_context;
}; };
source_guard enter_source(const std::string& path, source_guard enter_source(intern_string_t path,
int line_number, int line_number,
const std::string& content) const std::string& content)
{ {

@ -91,6 +91,17 @@ curl_request::debug_cb(
return 0; return 0;
} }
size_t
curl_request::string_cb(void* data, size_t size, size_t nmemb, void* userp)
{
auto realsize = size * nmemb;
auto& vec = *static_cast<std::string*>(userp);
vec.append((char*) data, ((char*) data) + realsize);
return realsize;
}
void void
curl_looper::loop_body() curl_looper::loop_body()
{ {
@ -245,4 +256,18 @@ curl_looper::compute_timeout(mstime_t current_time) const
return retval; return retval;
} }
void
curl_looper::process_all()
{
this->check_for_new_requests();
this->requeue_requests(LONG_MAX);
while (!this->cl_handle_to_request.empty()) {
this->perform_io();
this->check_for_finished_requests();
}
}
#endif #endif

@ -39,6 +39,8 @@
#include <vector> #include <vector>
#include "base/isc.hh" #include "base/isc.hh"
#include "base/lnav.console.hh"
#include "base/result.h"
#include "config.h" #include "config.h"
#if !defined(HAVE_LIBCURL) #if !defined(HAVE_LIBCURL)
@ -73,8 +75,7 @@ public:
class curl_request { class curl_request {
public: public:
curl_request(std::string name) curl_request(std::string name)
: cr_name(std::move(name)), cr_open(true), cr_handle(curl_easy_cleanup), : cr_name(std::move(name)), cr_handle(curl_easy_cleanup)
cr_completions(0)
{ {
this->cr_handle.reset(curl_easy_init()); this->cr_handle.reset(curl_easy_init());
curl_easy_setopt(this->cr_handle, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(this->cr_handle, CURLOPT_NOSIGNAL, 1);
@ -91,34 +92,39 @@ public:
# endif # endif
CURLSSH_AUTH_PASSWORD); CURLSSH_AUTH_PASSWORD);
} }
}; }
virtual ~curl_request() = default; virtual ~curl_request() = default;
const std::string& get_name() const const std::string& get_name() const
{ {
return this->cr_name; return this->cr_name;
}; }
virtual void close() virtual void close()
{ {
this->cr_open = false; this->cr_open = false;
}; }
bool is_open() const bool is_open() const
{ {
return this->cr_open; return this->cr_open;
}; }
CURL* get_handle() const CURL* get_handle() const
{ {
return this->cr_handle; return this->cr_handle;
}; }
operator CURL*() const
{
return this->cr_handle;
}
int get_completions() const int get_completions() const
{ {
return this->cr_completions; return this->cr_completions;
}; }
virtual long complete(CURLcode result) virtual long complete(CURLcode result)
{ {
@ -136,17 +142,42 @@ public:
"%s: download_speed=%f", this->cr_name.c_str(), download_speed); "%s: download_speed=%f", this->cr_name.c_str(), download_speed);
return -1; return -1;
}; }
Result<std::string, CURLcode> perform()
{
std::string response;
curl_easy_setopt(this->get_handle(), CURLOPT_WRITEFUNCTION, string_cb);
curl_easy_setopt(this->get_handle(), CURLOPT_WRITEDATA, &response);
auto rc = curl_easy_perform(this->get_handle());
if (rc == CURLE_OK) {
return Ok(response);
}
return Err(rc);
}
long get_response_code() const
{
long retval;
curl_easy_getinfo(this->get_handle(), CURLINFO_RESPONSE_CODE, &retval);
return retval;
}
protected: protected:
static int debug_cb( static int debug_cb(
CURL* handle, curl_infotype type, char* data, size_t size, void* userp); CURL* handle, curl_infotype type, char* data, size_t size, void* userp);
static size_t string_cb(void* data, size_t size, size_t nmemb, void* userp);
const std::string cr_name; const std::string cr_name;
bool cr_open; bool cr_open{true};
auto_mem<CURL> cr_handle; auto_mem<CURL> cr_handle;
char cr_error_buffer[CURL_ERROR_SIZE]; char cr_error_buffer[CURL_ERROR_SIZE];
int cr_completions; int cr_completions{0};
}; };
class curl_looper : public isc::service<curl_looper> { class curl_looper : public isc::service<curl_looper> {
@ -154,20 +185,9 @@ public:
curl_looper() : cl_curl_multi(curl_multi_cleanup) curl_looper() : cl_curl_multi(curl_multi_cleanup)
{ {
this->cl_curl_multi.reset(curl_multi_init()); this->cl_curl_multi.reset(curl_multi_init());
}; }
void process_all() void process_all();
{
this->check_for_new_requests();
this->requeue_requests(LONG_MAX);
while (!this->cl_handle_to_request.empty()) {
this->perform_io();
this->check_for_finished_requests();
}
};
void add_request(const std::shared_ptr<curl_request>& cr) void add_request(const std::shared_ptr<curl_request>& cr)
{ {
@ -175,12 +195,12 @@ public:
this->cl_all_requests.emplace_back(cr); this->cl_all_requests.emplace_back(cr);
this->cl_new_requests.emplace_back(cr); this->cl_new_requests.emplace_back(cr);
}; }
void close_request(const std::string& name) void close_request(const std::string& name)
{ {
this->cl_close_requests.emplace_back(name); this->cl_close_requests.emplace_back(name);
}; }
protected: protected:
void loop_body() override; void loop_body() override;

@ -32,6 +32,7 @@
#include "db_sub_source.hh" #include "db_sub_source.hh"
#include "base/date_time_scanner.hh" #include "base/date_time_scanner.hh"
#include "base/itertools.hh"
#include "base/time_util.hh" #include "base/time_util.hh"
#include "config.h" #include "config.h"
#include "yajlpp/json_ptr.hh" #include "yajlpp/json_ptr.hh"
@ -261,17 +262,10 @@ db_label_source::clear()
this->dls_cell_width.clear(); this->dls_cell_width.clear();
} }
long nonstd::optional<size_t>
db_label_source::column_name_to_index(const std::string& name) const db_label_source::column_name_to_index(const std::string& name) const
{ {
std::vector<header_meta>::const_iterator iter; return this->dls_headers | lnav::itertools::find(name);
iter = std::find(this->dls_headers.begin(), this->dls_headers.end(), name);
if (iter == this->dls_headers.end()) {
return -1;
}
return std::distance(this->dls_headers.begin(), iter);
} }
nonstd::optional<vis_line_t> nonstd::optional<vis_line_t>

@ -48,20 +48,14 @@ public:
this->clear(); this->clear();
} }
bool has_log_time_column() const bool has_log_time_column() const { return !this->dls_time_column.empty(); }
{
return !this->dls_time_column.empty();
};
size_t text_line_count() size_t text_line_count() { return this->dls_rows.size(); }
{
return this->dls_rows.size();
};
size_t text_size_for_line(textview_curses& tc, int line, line_flags_t flags) size_t text_size_for_line(textview_curses& tc, int line, line_flags_t flags)
{ {
return this->text_line_width(tc); return this->text_line_width(tc);
}; }
size_t text_line_width(textview_curses& curses) size_t text_line_width(textview_curses& curses)
{ {
@ -71,7 +65,7 @@ public:
retval += dls_header.hm_column_size + 1; retval += dls_header.hm_column_size + 1;
} }
return retval; return retval;
}; }
void text_value_for_line(textview_curses& tc, void text_value_for_line(textview_curses& tc,
int row, int row,
@ -86,7 +80,8 @@ public:
void clear(); void clear();
long column_name_to_index(const std::string& name) const; nonstd::optional<size_t> column_name_to_index(
const std::string& name) const;
nonstd::optional<vis_line_t> row_for_time(struct timeval time_bucket); nonstd::optional<vis_line_t> row_for_time(struct timeval time_bucket);
@ -97,26 +92,22 @@ public:
} }
return this->dls_time_column[row]; return this->dls_time_column[row];
}; }
struct header_meta { struct header_meta {
explicit header_meta(std::string name) explicit header_meta(std::string name) : hm_name(std::move(name)) {}
: hm_name(std::move(name)), hm_column_type(SQLITE3_TEXT),
hm_graphable(false), hm_log_time(false), hm_column_size(0)
{
}
bool operator==(const std::string& name) const bool operator==(const std::string& name) const
{ {
return this->hm_name == name; return this->hm_name == name;
}; }
std::string hm_name; std::string hm_name;
int hm_column_type; int hm_column_type{SQLITE3_TEXT};
unsigned int hm_sub_type{0}; unsigned int hm_sub_type{0};
bool hm_graphable; bool hm_graphable{false};
bool hm_log_time; bool hm_log_time{false};
size_t hm_column_size; size_t hm_column_size{0};
}; };
stacked_bar_chart<std::string> dls_chart; stacked_bar_chart<std::string> dls_chart;

@ -55,17 +55,14 @@ public:
role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE); role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE);
this->tss_fields[TSF_DESCRIPTION].set_share(1); this->tss_fields[TSF_DESCRIPTION].set_share(1);
this->tss_fields[TSF_DESCRIPTION].set_role(role_t::VCR_STATUS); this->tss_fields[TSF_DESCRIPTION].set_role(role_t::VCR_STATUS);
}; }
size_t statusview_fields() override size_t statusview_fields() override { return TSF__MAX; }
{
return TSF__MAX;
};
status_field& statusview_value_for_field(int field) override status_field& statusview_value_for_field(int field) override
{ {
return this->tss_fields[field]; return this->tss_fields[field];
}; }
void set_title(const std::string& title) void set_title(const std::string& title)
{ {

@ -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

@ -31,6 +31,7 @@
#include "elem_to_json.hh" #include "elem_to_json.hh"
#include "base/itertools.hh"
#include "config.h" #include "config.h"
#include "yajlpp/yajlpp.hh" #include "yajlpp/yajlpp.hh"
@ -150,7 +151,7 @@ map_elements_to_json(yajl_gen gen,
if (key_str.empty()) { if (key_str.empty()) {
continue; continue;
} }
if (find(names.begin(), names.end(), key_str) != names.end()) { if (names | lnav::itertools::find(key_str)) {
unique_names = false; unique_names = false;
break; break;
} else { } else {

@ -579,22 +579,19 @@ field_overlay_source::build_meta_line(const listview_curses& lv,
size_t filename_width = this->fos_lss.get_filename_offset(); size_t filename_width = this->fos_lss.get_filename_offset();
if (!line_meta.bm_comment.empty()) { if (!line_meta.bm_comment.empty()) {
const auto* lead = line_meta.bm_tags.empty() ? " \u2514 "
: " \u251c ";
attr_line_t al; attr_line_t al;
al.with_string(" + ") al.with_string(lead).append(
.with_attr(string_attr( lnav::roles::comment(line_meta.bm_comment));
line_range(1, 2),
VC_GRAPHIC.value(
line_meta.bm_tags.empty() ? ACS_LLCORNER : ACS_LTEE)))
.append(line_meta.bm_comment);
al.insert(0, filename_width, ' '); al.insert(0, filename_width, ' ');
dst.emplace_back(al); dst.emplace_back(al);
} }
if (!line_meta.bm_tags.empty()) { if (!line_meta.bm_tags.empty()) {
attr_line_t al; attr_line_t al;
al.with_string(" +").with_attr(string_attr( al.with_string(" \u2514");
line_range(1, 2), VC_GRAPHIC.value(ACS_LLCORNER)));
for (const auto& str : line_meta.bm_tags) { for (const auto& str : line_meta.bm_tags) {
al.append(1, ' ').append( al.append(1, ' ').append(
str, VC_STYLE.value(vc.attrs_for_ident(str))); str, VC_STYLE.value(vc.attrs_for_ident(str)));
@ -614,3 +611,53 @@ field_overlay_source::build_meta_line(const listview_curses& lv,
} }
} }
} }
void
field_overlay_source::add_key_line_attrs(int key_size, bool last_line)
{
string_attrs_t& sa = this->fos_lines.back().get_attrs();
struct line_range lr(1, 2);
int64_t graphic = (int64_t) (last_line ? ACS_LLCORNER : ACS_LTEE);
sa.emplace_back(lr, VC_GRAPHIC.value(graphic));
lr.lr_start = 3 + key_size + 3;
lr.lr_end = -1;
sa.emplace_back(lr, VC_STYLE.value(A_BOLD));
}
bool
field_overlay_source::list_value_for_overlay(const listview_curses& lv,
int y,
int bottom,
vis_line_t row,
attr_line_t& value_out)
{
if (y == 0) {
this->build_field_lines(lv);
this->build_summary_lines(lv);
return false;
}
if (1 <= y && y <= (int) this->fos_lines.size()) {
value_out = this->fos_lines[y - 1];
return true;
}
if (!this->fos_summary_lines.empty() && y == (bottom - 1)) {
value_out = this->fos_summary_lines[0];
return true;
}
if (!this->fos_meta_lines.empty()) {
value_out = this->fos_meta_lines.front();
this->fos_meta_lines.erase(this->fos_meta_lines.begin());
return true;
}
if (row < lv.get_inner_height()) {
this->build_meta_line(lv, this->fos_meta_lines, row);
}
return false;
}

@ -42,57 +42,17 @@ class field_overlay_source : public list_overlay_source {
public: public:
explicit field_overlay_source(logfile_sub_source& lss, explicit field_overlay_source(logfile_sub_source& lss,
textfile_sub_source& tss) textfile_sub_source& tss)
: fos_lss(lss), fos_tss(tss), fos_log_helper(lss){ : fos_lss(lss), fos_tss(tss), fos_log_helper(lss)
};
void add_key_line_attrs(int key_size, bool last_line = false)
{ {
string_attrs_t& sa = this->fos_lines.back().get_attrs(); }
struct line_range lr(1, 2);
int64_t graphic = (int64_t) (last_line ? ACS_LLCORNER : ACS_LTEE);
sa.emplace_back(lr, VC_GRAPHIC.value(graphic));
lr.lr_start = 3 + key_size + 3; void add_key_line_attrs(int key_size, bool last_line = false);
lr.lr_end = -1;
sa.emplace_back(lr, VC_STYLE.value(A_BOLD));
};
bool list_value_for_overlay(const listview_curses& lv, bool list_value_for_overlay(const listview_curses& lv,
int y, int y,
int bottom, int bottom,
vis_line_t row, vis_line_t row,
attr_line_t& value_out) override attr_line_t& value_out) override;
{
if (y == 0) {
this->build_field_lines(lv);
this->build_summary_lines(lv);
return false;
}
if (1 <= y && y <= (int) this->fos_lines.size()) {
value_out = this->fos_lines[y - 1];
return true;
}
if (!this->fos_summary_lines.empty() && y == (bottom - 1)) {
value_out = this->fos_summary_lines[0];
return true;
}
if (!this->fos_meta_lines.empty()) {
value_out = this->fos_meta_lines.front();
this->fos_meta_lines.erase(this->fos_meta_lines.begin());
return true;
}
if (row < lv.get_inner_height()) {
this->build_meta_line(lv, this->fos_meta_lines, row);
}
return false;
};
void build_field_lines(const listview_curses& lv); void build_field_lines(const listview_curses& lv);
void build_summary_lines(const listview_curses& lv); void build_summary_lines(const listview_curses& lv);

@ -37,6 +37,7 @@
#include "base/humanize.network.hh" #include "base/humanize.network.hh"
#include "base/isc.hh" #include "base/isc.hh"
#include "base/itertools.hh"
#include "base/opt_util.hh" #include "base/opt_util.hh"
#include "base/string_util.hh" #include "base/string_util.hh"
#include "config.h" #include "config.h"
@ -280,21 +281,19 @@ file_collection::watch_logfile(const std::string& filename,
return lnav::futures::make_ready_future(std::move(retval)); return lnav::futures::make_ready_future(std::move(retval));
} }
auto stat_iter = find_if(this->fc_new_stats.begin(), if (this->fc_new_stats | lnav::itertools::find_if([&st](const auto& elem) {
this->fc_new_stats.end(), return st.st_ino == elem.st_ino && st.st_dev == elem.st_dev;
[&st](auto& elem) { }))
return st.st_ino == elem.st_ino {
&& st.st_dev == elem.st_dev;
});
if (stat_iter != this->fc_new_stats.end()) {
// this file is probably a link that we have already scanned in this // this file is probably a link that we have already scanned in this
// pass. // pass.
return lnav::futures::make_ready_future(std::move(retval)); return lnav::futures::make_ready_future(std::move(retval));
} }
this->fc_new_stats.emplace_back(st); this->fc_new_stats.emplace_back(st);
auto file_iter
= find_if(this->fc_files.begin(), this->fc_files.end(), same_file(st)); auto file_iter = std::find_if(
this->fc_files.begin(), this->fc_files.end(), same_file(st));
if (file_iter == this->fc_files.end()) { if (file_iter == this->fc_files.end()) {
if (this->fc_other_files.find(filename) != this->fc_other_files.end()) { if (this->fc_other_files.find(filename) != this->fc_other_files.end()) {
@ -313,7 +312,8 @@ file_collection::watch_logfile(const std::string& filename,
return retval; return retval;
} }
auto ff = detect_file_format(filename); auto ff = loo2.loo_temp_file ? file_format_t::UNKNOWN
: detect_file_format(filename);
loo2.loo_file_format = ff; loo2.loo_file_format = ff;
switch (ff) { switch (ff) {
@ -619,7 +619,7 @@ file_collection::rescan_files(bool required)
this->expand_filename(fq, path, pair.second, false); this->expand_filename(fq, path, pair.second, false);
} }
} else { } else if (pair.second.loo_fd.get() != -1) {
fq.push_back(watch_logfile(pair.first, pair.second, required)); fq.push_back(watch_logfile(pair.first, pair.second, required));
} }

@ -113,7 +113,7 @@ files_sub_source::list_input_handle_key(listview_curses& lv, int ch)
} }
lv.reload_data(); lv.reload_data();
lnav_data.ld_mode = LNM_PAGING; lnav_data.ld_mode = ln_mode_t::PAGING;
}); });
return true; return true;
@ -205,7 +205,7 @@ files_sub_source::list_input_handle_key(listview_curses& lv, int ch)
void void
files_sub_source::list_input_handle_scroll_out(listview_curses& lv) files_sub_source::list_input_handle_scroll_out(listview_curses& lv)
{ {
lnav_data.ld_mode = LNM_PAGING; lnav_data.ld_mode = ln_mode_t::PAGING;
lnav_data.ld_filter_view.reload_data(); lnav_data.ld_filter_view.reload_data();
} }
@ -300,7 +300,7 @@ files_sub_source::text_attrs_for_line(textview_curses& tc,
string_attrs_t& value_out) string_attrs_t& value_out)
{ {
bool selected bool selected
= lnav_data.ld_mode == LNM_FILES && line == tc.get_selection(); = lnav_data.ld_mode == ln_mode_t::FILES && line == tc.get_selection();
const auto& fc = lnav_data.ld_active_files; const auto& fc = lnav_data.ld_active_files;
auto& vcolors = view_colors::singleton(); auto& vcolors = view_colors::singleton();
const auto dim = tc.get_dimensions(); const auto dim = tc.get_dimensions();

@ -91,12 +91,12 @@ size_t
filter_status_source::statusview_fields() filter_status_source::statusview_fields()
{ {
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
case LNM_SEARCH_FILTERS: case ln_mode_t::SEARCH_FILTERS:
case LNM_SEARCH_FILES: case ln_mode_t::SEARCH_FILES:
this->tss_fields[TSF_HELP].set_value(""); this->tss_fields[TSF_HELP].set_value("");
break; break;
case LNM_FILTER: case ln_mode_t::FILTER:
case LNM_FILES: case ln_mode_t::FILES:
this->tss_fields[TSF_HELP].set_value(EXIT_MSG); this->tss_fields[TSF_HELP].set_value(EXIT_MSG);
break; break;
default: default:
@ -104,12 +104,12 @@ filter_status_source::statusview_fields()
break; break;
} }
if (lnav_data.ld_mode == LNM_FILES || lnav_data.ld_mode == LNM_SEARCH_FILES) if (lnav_data.ld_mode == ln_mode_t::FILES
|| lnav_data.ld_mode == ln_mode_t::SEARCH_FILES)
{ {
this->tss_fields[TSF_FILES_TITLE].set_value( this->tss_fields[TSF_FILES_TITLE].set_value(
" " ANSI_ROLE("F") "iles ", role_t::VCR_STATUS_TITLE_HOTKEY); " " ANSI_ROLE("F") "iles ", role_t::VCR_STATUS_TITLE_HOTKEY);
this->tss_fields[TSF_FILES_TITLE].set_role( this->tss_fields[TSF_FILES_TITLE].set_role(role_t::VCR_STATUS_TITLE);
role_t::VCR_STATUS_TITLE);
this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value( this->tss_fields[TSF_FILES_RIGHT_STITCH].set_stitch_value(
role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL, role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL,
role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE); role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE);
@ -245,7 +245,7 @@ filter_help_status_source::statusview_fields()
return; return;
} }
if (lnav_data.ld_mode == LNM_FILTER) { if (lnav_data.ld_mode == ln_mode_t::FILTER) {
auto& editor = lnav_data.ld_filter_source; auto& editor = lnav_data.ld_filter_source;
auto& lv = lnav_data.ld_filter_view; auto& lv = lnav_data.ld_filter_view;
auto& fs = tss->get_filters(); auto& fs = tss->get_filters();
@ -285,7 +285,7 @@ filter_help_status_source::statusview_fields()
tss->tss_apply_filters ? "Disable Filtering" tss->tss_apply_filters ? "Disable Filtering"
: "Enable Filtering"); : "Enable Filtering");
} }
} else if (lnav_data.ld_mode == LNM_FILES } else if (lnav_data.ld_mode == ln_mode_t::FILES
&& lnav_data.ld_session_loaded) { && lnav_data.ld_session_loaded) {
auto& lv = lnav_data.ld_files_view; auto& lv = lnav_data.ld_files_view;
auto sel = files_model::from_selection(lv.get_selection()); auto sel = files_model::from_selection(lv.get_selection());

@ -41,13 +41,10 @@ filter_sub_source::filter_sub_source()
{ {
this->fss_regex_context.set_highlighter(readline_regex_highlighter) this->fss_regex_context.set_highlighter(readline_regex_highlighter)
.set_append_character(0); .set_append_character(0);
this->fss_editor.add_context( this->fss_editor.add_context(filter_lang_t::REGEX, this->fss_regex_context);
lnav::enums::to_underlying(filter_lang_t::REGEX),
this->fss_regex_context);
this->fss_sql_context.set_highlighter(readline_sqlite_highlighter) this->fss_sql_context.set_highlighter(readline_sqlite_highlighter)
.set_append_character(0); .set_append_character(0);
this->fss_editor.add_context(lnav::enums::to_underlying(filter_lang_t::SQL), this->fss_editor.add_context(filter_lang_t::SQL, this->fss_sql_context);
this->fss_sql_context);
this->fss_editor.set_change_action( this->fss_editor.set_change_action(
bind_mem(&filter_sub_source::rl_change, this)); bind_mem(&filter_sub_source::rl_change, this));
this->fss_editor.set_perform_action( this->fss_editor.set_perform_action(
@ -80,8 +77,8 @@ filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
switch (ch) { switch (ch) {
case 'f': { case 'f': {
auto top_view = *lnav_data.ld_view_stack.top(); auto* top_view = *lnav_data.ld_view_stack.top();
auto tss = top_view->get_sub_source(); auto* tss = top_view->get_sub_source();
tss->toggle_apply_filters(); tss->toggle_apply_filters();
break; break;
@ -159,19 +156,18 @@ filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
this->fss_editing = true; this->fss_editing = true;
add_view_text_possibilities( add_view_text_possibilities(&this->fss_editor,
&this->fss_editor, filter_lang_t::REGEX,
lnav::enums::to_underlying(filter_lang_t::REGEX), "*",
"*", top_view,
top_view); text_quoting::regex);
this->fss_editor.set_window(lv.get_window()); this->fss_editor.set_window(lv.get_window());
this->fss_editor.set_visible(true); this->fss_editor.set_visible(true);
this->fss_editor.set_y(lv.get_y() this->fss_editor.set_y(lv.get_y()
+ (int) (lv.get_selection() - lv.get_top())); + (int) (lv.get_selection() - lv.get_top()));
this->fss_editor.set_left(25); this->fss_editor.set_left(25);
this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1); this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1);
this->fss_editor.focus( this->fss_editor.focus(filter_lang_t::REGEX, "", "");
lnav::enums::to_underlying(filter_lang_t::REGEX), "", "");
this->fss_filter_state = true; this->fss_filter_state = true;
ef->disable(); ef->disable();
return true; return true;
@ -196,19 +192,18 @@ filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
this->fss_editing = true; this->fss_editing = true;
add_view_text_possibilities( add_view_text_possibilities(&this->fss_editor,
&this->fss_editor, filter_lang_t::REGEX,
lnav::enums::to_underlying(filter_lang_t::REGEX), "*",
"*", top_view,
top_view); text_quoting::regex);
this->fss_editor.set_window(lv.get_window()); this->fss_editor.set_window(lv.get_window());
this->fss_editor.set_visible(true); this->fss_editor.set_visible(true);
this->fss_editor.set_y(lv.get_y() this->fss_editor.set_y(lv.get_y()
+ (int) (lv.get_selection() - lv.get_top())); + (int) (lv.get_selection() - lv.get_top()));
this->fss_editor.set_left(25); this->fss_editor.set_left(25);
this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1); this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1);
this->fss_editor.focus( this->fss_editor.focus(filter_lang_t::REGEX, "", "");
lnav::enums::to_underlying(filter_lang_t::REGEX), "", "");
this->fss_filter_state = true; this->fss_filter_state = true;
ef->disable(); ef->disable();
return true; return true;
@ -227,23 +222,20 @@ filter_sub_source::list_input_handle_key(listview_curses& lv, int ch)
this->fss_editing = true; this->fss_editing = true;
auto tq = tf->get_lang() == filter_lang_t::SQL
? text_quoting::sql
: text_quoting::regex;
add_view_text_possibilities( add_view_text_possibilities(
&this->fss_editor, &this->fss_editor, tf->get_lang(), "*", top_view, tq);
lnav::enums::to_underlying(filter_lang_t::REGEX),
"*",
top_view);
add_filter_expr_possibilities( add_filter_expr_possibilities(
&this->fss_editor, &this->fss_editor, filter_lang_t::SQL, "*");
lnav::enums::to_underlying(filter_lang_t::SQL),
"*");
this->fss_editor.set_window(lv.get_window()); this->fss_editor.set_window(lv.get_window());
this->fss_editor.set_visible(true); this->fss_editor.set_visible(true);
this->fss_editor.set_y(lv.get_y() this->fss_editor.set_y(lv.get_y()
+ (int) (lv.get_selection() - lv.get_top())); + (int) (lv.get_selection() - lv.get_top()));
this->fss_editor.set_left(25); this->fss_editor.set_left(25);
this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1); this->fss_editor.set_width(this->tss_view->get_width() - 8 - 1);
this->fss_editor.focus(lnav::enums::to_underlying(tf->get_lang()), this->fss_editor.focus(tf->get_lang(), "");
"");
this->fss_editor.rewrite_line(0, tf->get_id().c_str()); this->fss_editor.rewrite_line(0, tf->get_id().c_str());
this->fss_filter_state = tf->is_enabled(); this->fss_filter_state = tf->is_enabled();
tf->disable(); tf->disable();
@ -349,7 +341,7 @@ filter_sub_source::text_attrs_for_line(textview_curses& tc,
filter_stack& fs = tss->get_filters(); filter_stack& fs = tss->get_filters();
auto tf = *(fs.begin() + line); auto tf = *(fs.begin() + line);
bool selected bool selected
= lnav_data.ld_mode == LNM_FILTER && line == tc.get_selection(); = lnav_data.ld_mode == ln_mode_t::FILTER && line == tc.get_selection();
if (selected) { if (selected) {
value_out.emplace_back(line_range{0, 1}, value_out.emplace_back(line_range{0, 1},
@ -509,7 +501,7 @@ filter_sub_source::rl_perform(readline_curses* rc)
filter_stack& fs = tss->get_filters(); filter_stack& fs = tss->get_filters();
auto iter = fs.begin() + this->tss_view->get_selection(); auto iter = fs.begin() + this->tss_view->get_selection();
auto tf = *iter; auto tf = *iter;
auto new_value = rc->get_value(); auto new_value = rc->get_value().get_string();
if (new_value.empty()) { if (new_value.empty()) {
this->rl_abort(rc); this->rl_abort(rc);
@ -663,6 +655,6 @@ filter_sub_source::rl_display_next(readline_curses* rc)
void void
filter_sub_source::list_input_handle_scroll_out(listview_curses& lv) filter_sub_source::list_input_handle_scroll_out(listview_curses& lv)
{ {
lnav_data.ld_mode = LNM_PAGING; lnav_data.ld_mode = ln_mode_t::PAGING;
lnav_data.ld_filter_view.reload_data(); lnav_data.ld_filter_view.reload_data();
} }

@ -5,15 +5,15 @@
"description": "A generic format for logs, like cron, that have a date at the start of a block.", "description": "A generic format for logs, like cron, that have a date at the start of a block.",
"regex": { "regex": {
"std": { "std": {
"pattern": "^(?<timestamp>\\S{3,8} \\w{3}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2} \\w+ \\d{4})(?<body>.*)$" "pattern": "^(?<timestamp>\\S{3,8} \\w{3}\\s+\\d{1,2} \\d{2}:\\d{2}:\\d{2} \\w+ \\d{4})\\s*(?<body>.*)$"
}, },
"sq-brackets": { "sq-brackets": {
"pattern": "^\\[(?<timestamp>\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3,6})?)Z?\\](?<body>.*)$" "pattern": "^\\[(?<timestamp>\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3,6})?)Z?\\]\\s*(?<body>.*)$"
} }
}, },
"sample": [ "sample": [
{ {
"line": "Sat Apr 27 03:33:07 PDT 2013" "line": "Sat Apr 27 03:33:07 PDT 2013\nHello, World"
}, },
{ {
"line": "[2021-05-21T21:58:57.022497Z]" "line": "[2021-05-21T21:58:57.022497Z]"

@ -4,8 +4,12 @@
// license: you are granted a perpetual, irrevocable license to copy, modify, // license: you are granted a perpetual, irrevocable license to copy, modify,
// publish, and distribute this file as you see fit. // publish, and distribute this file as you see fit.
#include <cstring> // memcpy
#include "fts_fuzzy_match.hh" #include "fts_fuzzy_match.hh"
#include <ctype.h> // ::tolower, ::toupper
#include "config.h" #include "config.h"
namespace fts { namespace fts {

@ -35,10 +35,6 @@
#define FTS_FUZZY_MATCH_H #define FTS_FUZZY_MATCH_H
#include <cstdint> // uint8_t #include <cstdint> // uint8_t
#include <cstdio>
#include <cstring> // memcpy
#include <ctype.h> // ::tolower, ::toupper
// Public interface // Public interface
namespace fts { namespace fts {

@ -79,12 +79,9 @@ public:
return highest; return highest;
} }
return start; return start;
}; }
virtual void grep_next_line(LineType& line) virtual void grep_next_line(LineType& line) { line = line + LineType(1); }
{
line = line + LineType(1);
};
grep_proc<LineType>* gps_proc; grep_proc<LineType>* gps_proc;
}; };
@ -97,7 +94,7 @@ public:
virtual ~grep_proc_control() = default; virtual ~grep_proc_control() = default;
/** @param msg The error encountered while attempting the grep. */ /** @param msg The error encountered while attempting the grep. */
virtual void grep_error(const std::string& msg){}; virtual void grep_error(const std::string& msg) {}
}; };
/** /**
@ -114,10 +111,10 @@ public:
LineType stop){}; LineType stop){};
/** Called periodically between grep_begin and grep_end. */ /** Called periodically between grep_begin and grep_end. */
virtual void grep_end_batch(grep_proc<LineType>& gp){}; virtual void grep_end_batch(grep_proc<LineType>& gp) {}
/** Called at the end of a grep run. */ /** Called at the end of a grep run. */
virtual void grep_end(grep_proc<LineType>& gp){}; virtual void grep_end(grep_proc<LineType>& gp) {}
/** /**
* Called when a match is found on 'line' and between [start, end). * Called when a match is found on 'line' and between [start, end).
@ -183,24 +180,15 @@ public:
virtual ~grep_proc(); virtual ~grep_proc();
/** @param gpd The sink to send resuls to. */ /** @param gpd The sink to send resuls to. */
void set_sink(grep_proc_sink<LineType>* gpd) void set_sink(grep_proc_sink<LineType>* gpd) { this->gp_sink = gpd; }
{
this->gp_sink = gpd;
};
grep_proc& invalidate(); grep_proc& invalidate();
/** @param gpd The sink to send results to. */ /** @param gpd The sink to send results to. */
void set_control(grep_proc_control* gpc) void set_control(grep_proc_control* gpc) { this->gp_control = gpc; }
{
this->gp_control = gpc;
};
/** @return The sink to send results to. */ /** @return The sink to send results to. */
grep_proc_sink<LineType>* get_sink() grep_proc_sink<LineType>* get_sink() { return this->gp_sink; }
{
return this->gp_sink;
};
/** /**
* Queue a request to search the input between the given line numbers. * Queue a request to search the input between the given line numbers.
@ -221,7 +209,7 @@ public:
} }
return *this; return *this;
}; }
/** /**
* Start the search requests that have been queued up with queue_request. * Start the search requests that have been queued up with queue_request.
@ -237,7 +225,7 @@ public:
if (this->gp_err_pipe != -1) { if (this->gp_err_pipe != -1) {
pollfds.push_back((struct pollfd){this->gp_err_pipe, POLLIN, 0}); pollfds.push_back((struct pollfd){this->gp_err_pipe, POLLIN, 0});
} }
}; }
/** /**
* Check the fd_set to see if there is any new data to be processed. * Check the fd_set to see if there is any new data to be processed.
@ -259,7 +247,7 @@ public:
} }
return true; return true;
}; }
protected: protected:
/** /**
@ -277,15 +265,9 @@ protected:
virtual void child_init(){}; virtual void child_init(){};
virtual void child_batch() virtual void child_batch() { fflush(stdout); }
{
fflush(stdout);
};
virtual void child_term() virtual void child_term() { fflush(stdout); }
{
fflush(stdout);
};
virtual void handle_match( virtual void handle_match(
int line, std::string& line_value, int off, int* matches, int count); int line, std::string& line_value, int off, int* matches, int count);
@ -319,4 +301,5 @@ protected:
grep_proc_sink<LineType>* gp_sink{nullptr}; /*< The sink delegate. */ grep_proc_sink<LineType>* gp_sink{nullptr}; /*< The sink delegate. */
grep_proc_control* gp_control{nullptr}; /*< The control delegate. */ grep_proc_control* gp_control{nullptr}; /*< The control delegate. */
}; };
#endif #endif

@ -100,56 +100,56 @@ struct help_text {
this->ht_context = help_context_t::HC_COMMAND; this->ht_context = help_context_t::HC_COMMAND;
this->ht_name = &name[1]; this->ht_name = &name[1];
} }
}; }
help_text& command() noexcept help_text& command() noexcept
{ {
this->ht_context = help_context_t::HC_COMMAND; this->ht_context = help_context_t::HC_COMMAND;
return *this; return *this;
}; }
help_text& sql_function() noexcept help_text& sql_function() noexcept
{ {
this->ht_context = help_context_t::HC_SQL_FUNCTION; this->ht_context = help_context_t::HC_SQL_FUNCTION;
return *this; return *this;
}; }
help_text& sql_agg_function() noexcept help_text& sql_agg_function() noexcept
{ {
this->ht_context = help_context_t::HC_SQL_FUNCTION; this->ht_context = help_context_t::HC_SQL_FUNCTION;
this->ht_function_type = help_function_type_t::HFT_AGGREGATE; this->ht_function_type = help_function_type_t::HFT_AGGREGATE;
return *this; return *this;
}; }
help_text& sql_table_valued_function() noexcept help_text& sql_table_valued_function() noexcept
{ {
this->ht_context = help_context_t::HC_SQL_TABLE_VALUED_FUNCTION; this->ht_context = help_context_t::HC_SQL_TABLE_VALUED_FUNCTION;
return *this; return *this;
}; }
help_text& sql_command() noexcept help_text& sql_command() noexcept
{ {
this->ht_context = help_context_t::HC_SQL_COMMAND; this->ht_context = help_context_t::HC_SQL_COMMAND;
return *this; return *this;
}; }
help_text& sql_keyword() noexcept help_text& sql_keyword() noexcept
{ {
this->ht_context = help_context_t::HC_SQL_KEYWORD; this->ht_context = help_context_t::HC_SQL_KEYWORD;
return *this; return *this;
}; }
help_text& sql_infix() noexcept help_text& sql_infix() noexcept
{ {
this->ht_context = help_context_t::HC_SQL_INFIX; this->ht_context = help_context_t::HC_SQL_INFIX;
return *this; return *this;
}; }
help_text& with_summary(const char* summary) noexcept help_text& with_summary(const char* summary) noexcept
{ {
this->ht_summary = summary; this->ht_summary = summary;
return *this; return *this;
}; }
help_text& with_flag_name(const char* flag) noexcept help_text& with_flag_name(const char* flag) noexcept
{ {
@ -181,19 +181,19 @@ struct help_text {
{ {
this->ht_nargs = help_nargs_t::HN_OPTIONAL; this->ht_nargs = help_nargs_t::HN_OPTIONAL;
return *this; return *this;
}; }
help_text& zero_or_more() noexcept help_text& zero_or_more() noexcept
{ {
this->ht_nargs = help_nargs_t::HN_ZERO_OR_MORE; this->ht_nargs = help_nargs_t::HN_ZERO_OR_MORE;
return *this; return *this;
}; }
help_text& one_or_more() noexcept help_text& one_or_more() noexcept
{ {
this->ht_nargs = help_nargs_t::HN_ONE_OR_MORE; this->ht_nargs = help_nargs_t::HN_ONE_OR_MORE;
return *this; return *this;
}; }
help_text& with_format(help_parameter_format_t format) noexcept help_text& with_format(help_parameter_format_t format) noexcept
{ {

@ -46,7 +46,7 @@ struct highlighter {
{ {
pcre_refcount(this->h_code, 1); pcre_refcount(this->h_code, 1);
this->study(); this->study();
}; }
highlighter(const highlighter& other); highlighter(const highlighter& other);
@ -59,7 +59,7 @@ struct highlighter {
this->h_code = nullptr; this->h_code = nullptr;
} }
free(this->h_code_extra); free(this->h_code_extra);
}; }
void study(); void study();
@ -75,14 +75,14 @@ struct highlighter {
this->h_role = role; this->h_role = role;
return *this; return *this;
}; }
highlighter& with_attrs(int attrs) highlighter& with_attrs(int attrs)
{ {
this->h_attrs = attrs; this->h_attrs = attrs;
return *this; return *this;
}; }
highlighter& with_text_format(text_format_t tf) highlighter& with_text_format(text_format_t tf)
{ {
@ -96,7 +96,7 @@ struct highlighter {
this->h_format_name = name; this->h_format_name = name;
return *this; return *this;
}; }
highlighter& with_color(const styling::color_unit& fg, highlighter& with_color(const styling::color_unit& fg,
const styling::color_unit& bg) const styling::color_unit& bg)
@ -105,7 +105,7 @@ struct highlighter {
this->h_bg = bg; this->h_bg = bg;
return *this; return *this;
}; }
highlighter& with_nestable(bool val) highlighter& with_nestable(bool val)
{ {
@ -118,7 +118,7 @@ struct highlighter {
ensure(this->h_attrs != -1); ensure(this->h_attrs != -1);
return this->h_attrs; return this->h_attrs;
}; }
void annotate(attr_line_t& al, int start) const; void annotate(attr_line_t& al, int start) const;

@ -73,31 +73,25 @@ struct stacked_bar_chart_base {
template<typename T> template<typename T>
class stacked_bar_chart : public stacked_bar_chart_base { class stacked_bar_chart : public stacked_bar_chart_base {
public: public:
stacked_bar_chart()
: sbc_do_stacking(true), sbc_left(0), sbc_right(0),
sbc_show_state(show_all()){
};
stacked_bar_chart& with_stacking_enabled(bool enabled) stacked_bar_chart& with_stacking_enabled(bool enabled)
{ {
this->sbc_do_stacking = enabled; this->sbc_do_stacking = enabled;
return *this; return *this;
}; }
stacked_bar_chart& with_attrs_for_ident(const T& ident, int attrs) stacked_bar_chart& with_attrs_for_ident(const T& ident, int attrs)
{ {
struct chart_ident& ci = this->find_ident(ident); struct chart_ident& ci = this->find_ident(ident);
ci.ci_attrs = attrs; ci.ci_attrs = attrs;
return *this; return *this;
}; }
stacked_bar_chart& with_margins(unsigned long left, unsigned long right) stacked_bar_chart& with_margins(unsigned long left, unsigned long right)
{ {
this->sbc_left = left; this->sbc_left = left;
this->sbc_right = right; this->sbc_right = right;
return *this; return *this;
}; }
show_state show_next_ident(direction dir) show_state show_next_ident(direction dir)
{ {
@ -152,7 +146,7 @@ public:
}); });
return this->sbc_show_state; return this->sbc_show_state;
}; }
void get_ident_to_show(T& ident_out) const void get_ident_to_show(T& ident_out) const
{ {
@ -162,7 +156,7 @@ public:
[&](const show_one& one) { [&](const show_one& one) {
ident_out = this->sbc_idents[one.so_index].ci_ident; ident_out = this->sbc_idents[one.so_index].ci_ident;
}); });
}; }
void chart_attrs_for_value(const listview_curses& lc, void chart_attrs_for_value(const listview_curses& lc,
int& left, int& left,
@ -230,20 +224,20 @@ public:
value_out.emplace_back( value_out.emplace_back(
lr, VC_STYLE.value(ci.ci_attrs | A_REVERSE)); lr, VC_STYLE.value(ci.ci_attrs | A_REVERSE));
} }
}; }
void clear() void clear()
{ {
this->sbc_idents.clear(); this->sbc_idents.clear();
this->sbc_ident_lookup.clear(); this->sbc_ident_lookup.clear();
this->sbc_show_state = show_all(); this->sbc_show_state = show_all();
}; }
void add_value(const T& ident, double amount = 1.0) void add_value(const T& ident, double amount = 1.0)
{ {
struct chart_ident& ci = this->find_ident(ident); struct chart_ident& ci = this->find_ident(ident);
ci.ci_stats.update(amount); ci.ci_stats.update(amount);
}; }
struct bucket_stats_t { struct bucket_stats_t {
bucket_stats_t() bucket_stats_t()
@ -281,11 +275,11 @@ public:
const chart_ident& ci = this->find_ident(ident); const chart_ident& ci = this->find_ident(ident);
return ci.ci_stats; return ci.ci_stats;
}; }
protected: protected:
struct chart_ident { struct chart_ident {
chart_ident(const T& ident) : ci_ident(ident){}; chart_ident(const T& ident) : ci_ident(ident) {}
T ci_ident; T ci_ident;
int ci_attrs{0}; int ci_attrs{0};
@ -303,13 +297,13 @@ protected:
return this->sbc_idents.back(); return this->sbc_idents.back();
} }
return this->sbc_idents[iter->second]; return this->sbc_idents[iter->second];
}; }
bool sbc_do_stacking; bool sbc_do_stacking{true};
unsigned long sbc_left, sbc_right; unsigned long sbc_left{0}, sbc_right{0};
std::vector<struct chart_ident> sbc_idents; std::vector<struct chart_ident> sbc_idents;
std::map<T, unsigned int> sbc_ident_lookup; std::map<T, unsigned int> sbc_ident_lookup;
show_state sbc_show_state; show_state sbc_show_state{show_all()};
}; };
class hist_source2 class hist_source2

@ -31,6 +31,7 @@
#include "base/ansi_scrubber.hh" #include "base/ansi_scrubber.hh"
#include "base/injector.hh" #include "base/injector.hh"
#include "base/itertools.hh"
#include "base/math_util.hh" #include "base/math_util.hh"
#include "base/opt_util.hh" #include "base/opt_util.hh"
#include "bookmarks.hh" #include "bookmarks.hh"
@ -160,8 +161,17 @@ handle_keyseq(const char* keyseq)
log_debug("executing key sequence %s: %s", keyseq, kc.kc_cmd.c_str()); log_debug("executing key sequence %s: %s", keyseq, kc.kc_cmd.c_str());
auto result = execute_any(ec, kc.kc_cmd); auto result = execute_any(ec, kc.kc_cmd);
lnav_data.ld_rl_view->set_value( if (result.isOk()) {
result.map(ok_prefix).orElse(err_to_ok).unwrap()); lnav_data.ld_rl_view->set_value(result.unwrap());
} else {
auto um = result.unwrapErr();
um.um_snippets.clear();
um.um_reason.clear();
um.um_notes.clear();
um.um_help.clear();
lnav_data.ld_rl_view->set_attr_value(um.to_attr_line());
}
if (!kc.kc_alt_msg.empty()) { if (!kc.kc_alt_msg.empty()) {
shlex lexer(kc.kc_alt_msg); shlex lexer(kc.kc_alt_msg);
@ -731,13 +741,13 @@ handle_paging_key(int ch)
db_label_source& dls = lnav_data.ld_db_row_source; db_label_source& dls = lnav_data.ld_db_row_source;
if (toggle_view(db_tc)) { if (toggle_view(db_tc)) {
long log_line_index = dls.column_name_to_index("log_line"); auto log_line_index = dls.column_name_to_index("log_line");
if (log_line_index == -1) { if (!log_line_index) {
log_line_index = dls.column_name_to_index("min(log_line)"); log_line_index = dls.column_name_to_index("min(log_line)");
} }
if (log_line_index != -1) { if (log_line_index) {
fmt::memory_buffer linestr; fmt::memory_buffer linestr;
int line_number = (int) tc->get_top(); int line_number = (int) tc->get_top();
unsigned int row; unsigned int row;
@ -747,7 +757,7 @@ handle_paging_key(int ch)
line_number); line_number);
linestr.push_back('\0'); linestr.push_back('\0');
for (row = 0; row < dls.dls_rows.size(); row++) { for (row = 0; row < dls.dls_rows.size(); row++) {
if (strcmp(dls.dls_rows[row][log_line_index], if (strcmp(dls.dls_rows[row][log_line_index.value()],
linestr.data()) linestr.data())
== 0) { == 0) {
vis_line_t db_line(row); vis_line_t db_line(row);
@ -761,16 +771,16 @@ handle_paging_key(int ch)
} else if (db_tc->get_inner_height() > 0) { } else if (db_tc->get_inner_height() > 0) {
int db_row = db_tc->get_top(); int db_row = db_tc->get_top();
tc = &lnav_data.ld_views[LNV_LOG]; tc = &lnav_data.ld_views[LNV_LOG];
long log_line_index = dls.column_name_to_index("log_line"); auto log_line_index = dls.column_name_to_index("log_line");
if (log_line_index == -1) { if (!log_line_index) {
log_line_index = dls.column_name_to_index("min(log_line)"); log_line_index = dls.column_name_to_index("min(log_line)");
} }
if (log_line_index != -1) { if (log_line_index) {
unsigned int line_number; unsigned int line_number;
if (sscanf(dls.dls_rows[db_row][log_line_index], if (sscanf(dls.dls_rows[db_row][log_line_index.value()],
"%d", "%d",
&line_number) &line_number)
&& line_number < tc->listview_rows(*tc)) && line_number < tc->listview_rows(*tc))
@ -839,11 +849,10 @@ handle_paging_key(int ch)
if (line_attr_opt) { if (line_attr_opt) {
const auto& fc = lnav_data.ld_active_files; const auto& fc = lnav_data.ld_active_files;
auto lf = line_attr_opt.value().get(); auto lf = line_attr_opt.value().get();
auto iter auto index_opt
= find(fc.fc_files.begin(), fc.fc_files.end(), lf); = fc.fc_files | lnav::itertools::find(lf);
if (iter != fc.fc_files.end()) { if (index_opt) {
auto index = distance(fc.fc_files.begin(), iter); auto index_vl = vis_line_t(index_opt.value());
auto index_vl = vis_line_t(index);
lnav_data.ld_files_view.set_top(index_vl); lnav_data.ld_files_view.set_top(index_vl);
lnav_data.ld_files_view.set_selection(index_vl); lnav_data.ld_files_view.set_selection(index_vl);

@ -185,7 +185,7 @@ gen_handle_null(void* ctx)
sql_json_op* sjo = (sql_json_op*) ctx; sql_json_op* sjo = (sql_json_op*) ctx;
yajl_gen gen = (yajl_gen) sjo->jo_ptr_data; yajl_gen gen = (yajl_gen) sjo->jo_ptr_data;
if (sjo->jo_ptr.jp_state == json_ptr::MS_DONE) { if (sjo->jo_ptr.jp_state == json_ptr::match_state_t::DONE) {
sjo->sjo_type = SQLITE_NULL; sjo->sjo_type = SQLITE_NULL;
} else { } else {
sjo->jo_ptr_error_code = yajl_gen_null(gen); sjo->jo_ptr_error_code = yajl_gen_null(gen);
@ -200,7 +200,7 @@ gen_handle_boolean(void* ctx, int boolVal)
sql_json_op* sjo = (sql_json_op*) ctx; sql_json_op* sjo = (sql_json_op*) ctx;
yajl_gen gen = (yajl_gen) sjo->jo_ptr_data; yajl_gen gen = (yajl_gen) sjo->jo_ptr_data;
if (sjo->jo_ptr.jp_state == json_ptr::MS_DONE) { if (sjo->jo_ptr.jp_state == json_ptr::match_state_t::DONE) {
sjo->sjo_type = SQLITE_INTEGER; sjo->sjo_type = SQLITE_INTEGER;
sjo->sjo_int = boolVal; sjo->sjo_int = boolVal;
} else { } else {
@ -216,7 +216,7 @@ gen_handle_string(void* ctx, const unsigned char* stringVal, size_t len)
sql_json_op* sjo = (sql_json_op*) ctx; sql_json_op* sjo = (sql_json_op*) ctx;
yajl_gen gen = (yajl_gen) sjo->jo_ptr_data; yajl_gen gen = (yajl_gen) sjo->jo_ptr_data;
if (sjo->jo_ptr.jp_state == json_ptr::MS_DONE) { if (sjo->jo_ptr.jp_state == json_ptr::match_state_t::DONE) {
sjo->sjo_type = SQLITE3_TEXT; sjo->sjo_type = SQLITE3_TEXT;
sjo->sjo_str = std::string((char*) stringVal, len); sjo->sjo_str = std::string((char*) stringVal, len);
} else { } else {
@ -232,7 +232,7 @@ gen_handle_number(void* ctx, const char* numval, size_t numlen)
sql_json_op* sjo = (sql_json_op*) ctx; sql_json_op* sjo = (sql_json_op*) ctx;
yajl_gen gen = (yajl_gen) sjo->jo_ptr_data; yajl_gen gen = (yajl_gen) sjo->jo_ptr_data;
if (sjo->jo_ptr.jp_state == json_ptr::MS_DONE) { if (sjo->jo_ptr.jp_state == json_ptr::match_state_t::DONE) {
if (strtonum(sjo->sjo_int, numval, numlen) == numlen) { if (strtonum(sjo->sjo_int, numval, numlen) == numlen) {
sjo->sjo_type = SQLITE_INTEGER; sjo->sjo_type = SQLITE_INTEGER;
} else { } else {
@ -298,7 +298,8 @@ sql_jget(sqlite3_context* context, int argc, sqlite3_value** argv)
return; return;
} }
case yajl_status_client_canceled: case yajl_status_client_canceled:
if (jo.jo_ptr.jp_state == json_ptr::MS_ERR_INVALID_ESCAPE) { if (jo.jo_ptr.jp_state
== json_ptr::match_state_t::ERR_INVALID_ESCAPE) {
sqlite3_result_error( sqlite3_result_error(
context, jo.jo_ptr.error_msg().c_str(), -1); context, jo.jo_ptr.error_msg().c_str(), -1);
} else { } else {
@ -320,7 +321,8 @@ sql_jget(sqlite3_context* context, int argc, sqlite3_value** argv)
return; return;
} }
case yajl_status_client_canceled: case yajl_status_client_canceled:
if (jo.jo_ptr.jp_state == json_ptr::MS_ERR_INVALID_ESCAPE) { if (jo.jo_ptr.jp_state
== json_ptr::match_state_t::ERR_INVALID_ESCAPE) {
sqlite3_result_error( sqlite3_result_error(
context, jo.jo_ptr.error_msg().c_str(), -1); context, jo.jo_ptr.error_msg().c_str(), -1);
} else { } else {

@ -800,3 +800,40 @@ line_buffer::get_available()
{ {
return {this->lb_file_offset, this->lb_buffer_size}; return {this->lb_file_offset, this->lb_buffer_size};
} }
line_buffer::gz_indexed::indexDict::indexDict(const z_stream& s,
const file_size_t size)
{
assert((s.data_type & GZ_END_OF_BLOCK_MASK));
assert(!(s.data_type & GZ_END_OF_FILE_MASK));
assert(size >= s.avail_out + GZ_WINSIZE);
this->bits = s.data_type & GZ_BORROW_BITS_MASK;
this->in = s.total_in;
this->out = s.total_out;
auto last_byte_in = s.next_in[-1];
this->in_bits = last_byte_in >> (8 - this->bits);
// Copy the last 32k uncompressed data (sliding window) to our
// index
memcpy(this->index, s.next_out - GZ_WINSIZE, GZ_WINSIZE);
}
int
line_buffer::gz_indexed::indexDict::apply(z_streamp s)
{
s->zalloc = Z_NULL;
s->zfree = Z_NULL;
s->opaque = Z_NULL;
s->avail_in = 0;
s->next_in = Z_NULL;
auto ret = inflateInit2(s, GZ_RAW_MODE);
if (ret != Z_OK) {
return ret;
}
if (this->bits) {
inflatePrime(s, this->bits, this->in_bits);
}
s->total_in = this->in;
s->total_out = this->out;
inflateSetDictionary(s, this->index, GZ_WINSIZE);
return ret;
}

@ -99,7 +99,7 @@ public:
return this->gz_fd != -1; return this->gz_fd != -1;
} }
uLong get_source_offset() uLong get_source_offset() const
{ {
return !!*this ? this->strm.total_in + this->strm.avail_in : 0; return !!*this ? this->strm.total_in + this->strm.avail_in : 0;
} }
@ -123,40 +123,9 @@ public:
unsigned char bits = 0; unsigned char bits = 0;
unsigned char in_bits = 0; unsigned char in_bits = 0;
Bytef index[GZ_WINSIZE]; Bytef index[GZ_WINSIZE];
indexDict(z_stream const& s, const file_size_t size) indexDict(z_stream const& s, const file_size_t size);
{
assert((s.data_type & GZ_END_OF_BLOCK_MASK)); int apply(z_streamp s);
assert(!(s.data_type & GZ_END_OF_FILE_MASK));
assert(size >= s.avail_out + GZ_WINSIZE);
this->bits = s.data_type & GZ_BORROW_BITS_MASK;
this->in = s.total_in;
this->out = s.total_out;
auto last_byte_in = s.next_in[-1];
this->in_bits = last_byte_in >> (8 - this->bits);
// Copy the last 32k uncompressed data (sliding window) to our
// index
memcpy(this->index, s.next_out - GZ_WINSIZE, GZ_WINSIZE);
}
int apply(z_streamp s)
{
s->zalloc = Z_NULL;
s->zfree = Z_NULL;
s->opaque = Z_NULL;
s->avail_in = 0;
s->next_in = Z_NULL;
auto ret = inflateInit2(s, GZ_RAW_MODE);
if (ret != Z_OK) {
return ret;
}
if (this->bits) {
inflatePrime(s, this->bits, this->in_bits);
}
s->total_in = this->in;
s->total_out = this->out;
inflateSetDictionary(s, this->index, GZ_WINSIZE);
return ret;
}
}; };
private: private:
@ -181,12 +150,12 @@ public:
int get_fd() const int get_fd() const
{ {
return this->lb_fd; return this->lb_fd;
}; }
time_t get_file_time() const time_t get_file_time() const
{ {
return this->lb_file_time; return this->lb_file_time;
}; }
/** /**
* @return The size of the file or the amount of data pulled from a pipe. * @return The size of the file or the amount of data pulled from a pipe.
@ -194,22 +163,22 @@ public:
file_ssize_t get_file_size() const file_ssize_t get_file_size() const
{ {
return this->lb_file_size; return this->lb_file_size;
}; }
bool is_pipe() const bool is_pipe() const
{ {
return !this->lb_seekable; return !this->lb_seekable;
}; }
bool is_pipe_closed() const bool is_pipe_closed() const
{ {
return !this->lb_seekable && (this->lb_file_size != -1); return !this->lb_seekable && (this->lb_file_size != -1);
}; }
bool is_compressed() const bool is_compressed() const
{ {
return this->lb_gz_file || this->lb_bz_file; return this->lb_gz_file || this->lb_bz_file;
}; }
file_off_t get_read_offset(file_off_t off) const file_off_t get_read_offset(file_off_t off) const
{ {
@ -218,7 +187,7 @@ public:
} else { } else {
return off; return off;
} }
}; }
bool is_data_available(file_off_t off, file_off_t stat_size) const bool is_data_available(file_off_t off, file_off_t stat_size) const
{ {
@ -226,7 +195,7 @@ public:
return (this->lb_file_size == -1 || off < this->lb_file_size); return (this->lb_file_size == -1 || off < this->lb_file_size);
} }
return off < stat_size; return off < stat_size;
}; }
/** /**
* Attempt to load the next line into the buffer. * Attempt to load the next line into the buffer.
@ -244,7 +213,7 @@ public:
void clear() void clear()
{ {
this->lb_buffer_size = 0; this->lb_buffer_size = 0;
}; }
/** Release any resources held by this object. */ /** Release any resources held by this object. */
void reset() void reset()
@ -255,16 +224,16 @@ public:
this->lb_file_size = (ssize_t) -1; this->lb_file_size = (ssize_t) -1;
this->lb_buffer_size = 0; this->lb_buffer_size = 0;
this->lb_last_line_offset = -1; this->lb_last_line_offset = -1;
}; }
/** Check the invariants for this object. */ /** Check the invariants for this object. */
bool invariant() bool invariant() const
{ {
require(this->lb_buffer != nullptr); require(this->lb_buffer != nullptr);
require(this->lb_buffer_size <= this->lb_buffer_max); require(this->lb_buffer_size <= this->lb_buffer_max);
return true; return true;
}; }
private: private:
/** /**
@ -275,7 +244,7 @@ private:
{ {
return this->lb_file_offset <= off return this->lb_file_offset <= off
&& off < (this->lb_file_offset + this->lb_buffer_size); && off < (this->lb_file_offset + this->lb_buffer_size);
}; }
void resize_buffer(size_t new_max); void resize_buffer(size_t new_max);
@ -325,7 +294,7 @@ private:
avail_out = this->lb_buffer_size - buffer_offset; avail_out = this->lb_buffer_size - buffer_offset;
return retval; return retval;
}; }
shared_buffer lb_share_manager; shared_buffer lb_share_manager;

@ -397,14 +397,15 @@ listview_curses::handle_mouse(mouse_event& me)
this->lv_mouse_time = me.me_time; this->lv_mouse_time = me.me_time;
if (me.me_button != mouse_button_t::BUTTON_LEFT || inner_height == 0 if (me.me_button != mouse_button_t::BUTTON_LEFT || inner_height == 0
|| (this->lv_mouse_mode != LV_MODE_DRAG && me.me_x < (int) (width - 2))) || (this->lv_mouse_mode != lv_mode_t::DRAG
&& me.me_x < (int) (width - 2)))
{ {
return false; return false;
} }
if (me.me_state == mouse_button_state_t::BUTTON_STATE_RELEASED) { if (me.me_state == mouse_button_state_t::BUTTON_STATE_RELEASED) {
this->lv_mouse_y = -1; this->lv_mouse_y = -1;
this->lv_mouse_mode = LV_MODE_NONE; this->lv_mouse_mode = lv_mode_t::NONE;
return true; return true;
} }
@ -416,35 +417,35 @@ listview_curses::handle_mouse(mouse_event& me)
scroll_top = (this->get_y() + (int) (top_pct * (double) height)); scroll_top = (this->get_y() + (int) (top_pct * (double) height));
scroll_bottom = (this->get_y() + (int) (bot_pct * (double) height)); scroll_bottom = (this->get_y() + (int) (bot_pct * (double) height));
if (this->lv_mouse_mode == LV_MODE_NONE) { if (this->lv_mouse_mode == lv_mode_t::NONE) {
if ((scroll_top - 1) <= me.me_y && me.me_y <= (scroll_bottom + 1)) { if ((scroll_top - 1) <= me.me_y && me.me_y <= (scroll_bottom + 1)) {
this->lv_mouse_mode = LV_MODE_DRAG; this->lv_mouse_mode = lv_mode_t::DRAG;
this->lv_mouse_y = me.me_y - scroll_top; this->lv_mouse_y = me.me_y - scroll_top;
} else if (me.me_y < scroll_top) { } else if (me.me_y < scroll_top) {
this->lv_mouse_mode = LV_MODE_UP; this->lv_mouse_mode = lv_mode_t::UP;
} else { } else {
this->lv_mouse_mode = LV_MODE_DOWN; this->lv_mouse_mode = lv_mode_t::DOWN;
} }
} }
switch (this->lv_mouse_mode) { switch (this->lv_mouse_mode) {
case LV_MODE_NONE: case lv_mode_t::NONE:
require(0); require(0);
break; break;
case LV_MODE_UP: case lv_mode_t::UP:
if (me.me_y < scroll_top) { if (me.me_y < scroll_top) {
shift_amount = -1 * height; shift_amount = -1 * height;
} }
break; break;
case LV_MODE_DOWN: case lv_mode_t::DOWN:
if (me.me_y > scroll_bottom) { if (me.me_y > scroll_bottom) {
shift_amount = height; shift_amount = height;
} }
break; break;
case LV_MODE_DRAG: case lv_mode_t::DRAG:
pct = (double) inner_height / (double) height; pct = (double) inner_height / (double) height;
new_top = me.me_y - this->get_y() - this->lv_mouse_y; new_top = me.me_y - this->get_y() - this->lv_mouse_y;
new_top = (int) floor(((double) new_top * pct) + 0.5); new_top = (int) floor(((double) new_top * pct) + 0.5);
@ -490,3 +491,57 @@ listview_curses::set_top(vis_line_t top, bool suppress_flash)
this->set_needs_update(); this->set_needs_update();
} }
} }
vis_line_t
listview_curses::get_bottom() const
{
auto retval = this->lv_top;
auto avail = this->rows_available(retval, RD_DOWN);
if (avail > 0) {
retval += vis_line_t(avail - 1);
}
return retval;
}
vis_line_t
listview_curses::rows_available(vis_line_t line,
listview_curses::row_direction_t dir) const
{
unsigned long width;
vis_line_t height;
vis_line_t retval(0);
this->get_dimensions(height, width);
if (this->lv_word_wrap) {
size_t row_count = this->lv_source->listview_rows(*this);
width -= 1;
while ((height > 0) && (line >= 0) && ((size_t) line < row_count)) {
size_t len = this->lv_source->listview_size_for_row(*this, line);
do {
len -= std::min((size_t) width, len);
--height;
} while (len > 0);
line += vis_line_t(dir);
if (height >= 0) {
++retval;
}
}
} else {
switch (dir) {
case RD_UP:
retval = std::min(height, line + 1_vl);
break;
case RD_DOWN:
retval = std::min(
height,
vis_line_t(this->lv_source->listview_rows(*this) - line));
break;
}
}
return retval;
}

@ -55,10 +55,7 @@ public:
/** @return The number of rows in the list. */ /** @return The number of rows in the list. */
virtual size_t listview_rows(const listview_curses& lv) = 0; virtual size_t listview_rows(const listview_curses& lv) = 0;
virtual size_t listview_width(const listview_curses& lv) virtual size_t listview_width(const listview_curses& lv) { return INT_MAX; }
{
return INT_MAX;
};
/** /**
* Get the string value for a row in the list. * Get the string value for a row in the list.
@ -78,7 +75,7 @@ public:
virtual std::string listview_source_name(const listview_curses& lv) virtual std::string listview_source_name(const listview_curses& lv)
{ {
return ""; return "";
}; }
}; };
class list_gutter_source { class list_gutter_source {
@ -94,7 +91,7 @@ public:
role_t& bar_role_out) role_t& bar_role_out)
{ {
ch_out = ACS_VLINE; ch_out = ACS_VLINE;
}; }
}; };
class list_overlay_source { class list_overlay_source {
@ -115,7 +112,7 @@ public:
virtual bool list_input_handle_key(listview_curses& lv, int ch) = 0; virtual bool list_input_handle_key(listview_curses& lv, int ch) = 0;
virtual void list_input_handle_scroll_out(listview_curses& lv){}; virtual void list_input_handle_scroll_out(listview_curses& lv) {}
}; };
/** /**
@ -129,15 +126,9 @@ public:
listview_curses(); listview_curses();
void set_title(const std::string& title) void set_title(const std::string& title) { this->lv_title = title; }
{
this->lv_title = title;
};
const std::string& get_title() const const std::string& get_title() const { return this->lv_title; }
{
return this->lv_title;
};
/** @param src The data source delegate. */ /** @param src The data source delegate. */
void set_data_source(list_data_source* src) void set_data_source(list_data_source* src)
@ -145,18 +136,15 @@ public:
this->lv_source = src; this->lv_source = src;
this->invoke_scroll(); this->invoke_scroll();
this->reload_data(); this->reload_data();
}; }
/** @return The data source delegate. */ /** @return The data source delegate. */
list_data_source* get_data_source() const list_data_source* get_data_source() const { return this->lv_source; }
{
return this->lv_source;
};
void set_gutter_source(list_gutter_source* src) void set_gutter_source(list_gutter_source* src)
{ {
this->lv_gutter_source = src; this->lv_gutter_source = src;
}; }
/** @param src The data source delegate. */ /** @param src The data source delegate. */
listview_curses& set_overlay_source(list_overlay_source* src) listview_curses& set_overlay_source(list_overlay_source* src)
@ -165,38 +153,30 @@ public:
this->reload_data(); this->reload_data();
return *this; return *this;
}; }
/** @return The overlay source delegate. */ /** @return The overlay source delegate. */
list_overlay_source* get_overlay_source() list_overlay_source* get_overlay_source()
{ {
return this->lv_overlay_source; return this->lv_overlay_source;
}; }
listview_curses& add_input_delegate(list_input_delegate& lid) listview_curses& add_input_delegate(list_input_delegate& lid)
{ {
this->lv_input_delegates.push_back(&lid); this->lv_input_delegates.push_back(&lid);
return *this; return *this;
}; }
/** /**
* @param va The action to invoke when the view is scrolled. * @param va The action to invoke when the view is scrolled.
* @todo Allow multiple observers. * @todo Allow multiple observers.
*/ */
void set_scroll_action(action va) void set_scroll_action(action va) { this->lv_scroll = std::move(va); }
{
this->lv_scroll = std::move(va);
};
void set_show_scrollbar(bool ss) void set_show_scrollbar(bool ss) { this->lv_show_scrollbar = ss; }
{
this->lv_show_scrollbar = ss; bool get_show_scrollbar() const { return this->lv_show_scrollbar; }
};
bool get_show_scrollbar() const
{
return this->lv_show_scrollbar;
};
void set_show_bottom_border(bool val) void set_show_bottom_border(bool val)
{ {
@ -204,21 +184,12 @@ public:
this->lv_show_bottom_border = val; this->lv_show_bottom_border = val;
this->set_needs_update(); this->set_needs_update();
} }
}; }
bool get_show_bottom_border() const bool get_show_bottom_border() const { return this->lv_show_bottom_border; }
{
return this->lv_show_bottom_border;
};
void set_selectable(bool sel) void set_selectable(bool sel) { this->lv_selectable = sel; }
{
this->lv_selectable = sel;
};
bool is_selectable() const bool is_selectable() const { return this->lv_selectable; }
{
return this->lv_selectable;
};
void set_selection(vis_line_t sel) void set_selection(vis_line_t sel)
{ {
@ -266,58 +237,16 @@ public:
this->set_needs_update(); this->set_needs_update();
return *this; return *this;
}; }
bool get_word_wrap() const bool get_word_wrap() const { return this->lv_word_wrap; }
{
return this->lv_word_wrap;
};
enum row_direction_t { enum row_direction_t {
RD_UP = -1, RD_UP = -1,
RD_DOWN = 1, RD_DOWN = 1,
}; };
vis_line_t rows_available(vis_line_t line, row_direction_t dir) const vis_line_t rows_available(vis_line_t line, row_direction_t dir) const;
{
unsigned long width;
vis_line_t height;
vis_line_t retval(0);
this->get_dimensions(height, width);
if (this->lv_word_wrap) {
size_t row_count = this->lv_source->listview_rows(*this);
width -= 1;
while ((height > 0) && (line >= 0) && ((size_t) line < row_count)) {
size_t len
= this->lv_source->listview_size_for_row(*this, line);
do {
len -= std::min((size_t) width, len);
--height;
} while (len > 0);
line += vis_line_t(dir);
if (height >= 0) {
++retval;
}
}
} else {
switch (dir) {
case RD_UP:
retval = std::min(height, line + 1_vl);
break;
case RD_DOWN:
retval = std::min(
height,
vis_line_t(this->lv_source->listview_rows(*this)
- line));
break;
}
}
return retval;
};
template<typename F> template<typename F>
auto map_top_row(F func) -> auto map_top_row(F func) ->
@ -334,16 +263,10 @@ public:
} }
/** @param win The curses window this view is attached to. */ /** @param win The curses window this view is attached to. */
void set_window(WINDOW* win) void set_window(WINDOW* win) { this->lv_window = win; }
{
this->lv_window = win;
};
/** @return The curses window this view is attached to. */ /** @return The curses window this view is attached to. */
WINDOW* get_window() const WINDOW* get_window() const { return this->lv_window; }
{
return this->lv_window;
};
void set_y(unsigned int y) void set_y(unsigned int y)
{ {
@ -351,11 +274,9 @@ public:
this->lv_y = y; this->lv_y = y;
this->set_needs_update(); this->set_needs_update();
} }
}; }
unsigned int get_y() const
{ unsigned int get_y() const { return this->lv_y; }
return this->lv_y;
};
void set_x(unsigned int x) void set_x(unsigned int x)
{ {
@ -363,11 +284,9 @@ public:
this->lv_x = x; this->lv_x = x;
this->set_needs_update(); this->set_needs_update();
} }
}; }
unsigned int get_x() const
{ unsigned int get_x() const { return this->lv_x; }
return this->lv_x;
};
/** /**
* Set the line number to be displayed at the top of the view. If the * Set the line number to be displayed at the top of the view. If the
@ -380,10 +299,7 @@ public:
void set_top(vis_line_t top, bool suppress_flash = false); void set_top(vis_line_t top, bool suppress_flash = false);
/** @return The line number that is displayed at the top. */ /** @return The line number that is displayed at the top. */
vis_line_t get_top() const vis_line_t get_top() const { return this->lv_top; }
{
return this->lv_top;
};
nonstd::optional<vis_line_t> get_top_opt() const nonstd::optional<vis_line_t> get_top_opt() const
{ {
@ -395,19 +311,7 @@ public:
} }
/** @return The line number that is displayed at the bottom. */ /** @return The line number that is displayed at the bottom. */
vis_line_t get_bottom() const vis_line_t get_bottom() const;
{
auto retval = this->lv_top;
auto avail = this->rows_available(retval, RD_DOWN);
if (avail > 0) {
retval += vis_line_t(avail - 1);
} else {
retval = -1_vl;
}
return retval;
};
vis_line_t get_top_for_last_row() vis_line_t get_top_for_last_row()
{ {
@ -424,13 +328,13 @@ public:
} }
return retval; return retval;
}; }
/** @return True if the given line is visible. */ /** @return True if the given line is visible. */
bool is_line_visible(vis_line_t line) const bool is_line_visible(vis_line_t line) const
{ {
return this->get_top() <= line && line <= this->get_bottom(); return this->get_top() <= line && line <= this->get_bottom();
}; }
/** /**
* Shift the value of top by the given value. * Shift the value of top by the given value.
@ -451,7 +355,7 @@ public:
} }
return this->lv_top; return this->lv_top;
}; }
/** /**
* Set the column number to be displayed at the left of the view. If the * Set the column number to be displayed at the left of the view. If the
@ -480,13 +384,10 @@ public:
this->lv_left = left; this->lv_left = left;
this->invoke_scroll(); this->invoke_scroll();
this->set_needs_update(); this->set_needs_update();
}; }
/** @return The column number that is displayed at the left. */ /** @return The column number that is displayed at the left. */
unsigned int get_left() const unsigned int get_left() const { return this->lv_left; }
{
return this->lv_left;
};
/** /**
* Shift the value of left by the given value. * Shift the value of left by the given value.
@ -505,7 +406,7 @@ public:
} }
return this->lv_left; return this->lv_left;
}; }
/** /**
* Set the height of the view. A value greater than one is considered to * Set the height of the view. A value greater than one is considered to
@ -520,13 +421,10 @@ public:
this->lv_height = height; this->lv_height = height;
this->set_needs_update(); this->set_needs_update();
} }
}; }
/** @return The absolute or relative height of the window. */ /** @return The absolute or relative height of the window. */
vis_line_t get_height() const vis_line_t get_height() const { return this->lv_height; }
{
return this->lv_height;
};
/** @return The number of rows of data in this view's source data. */ /** @return The number of rows of data in this view's source data. */
vis_line_t get_inner_height() const vis_line_t get_inner_height() const
@ -534,19 +432,16 @@ public:
return vis_line_t(this->lv_source == nullptr return vis_line_t(this->lv_source == nullptr
? 0 ? 0
: this->lv_source->listview_rows(*this)); : this->lv_source->listview_rows(*this));
}; }
size_t get_inner_width() const size_t get_inner_width() const
{ {
return this->lv_source == nullptr return this->lv_source == nullptr
? 0 ? 0
: this->lv_source->listview_width(*this); : this->lv_source->listview_width(*this);
}; }
void set_overlay_needs_update() void set_overlay_needs_update() { this->lv_overlay_needs_update = true; }
{
this->lv_overlay_needs_update = true;
};
/** /**
* Get the actual dimensions of the view. * Get the actual dimensions of the view.
@ -579,7 +474,7 @@ public:
} else { } else {
width_out = 0; width_out = 0;
} }
}; }
std::pair<vis_line_t, unsigned long> get_dimensions() const std::pair<vis_line_t, unsigned long> get_dimensions() const
{ {
@ -619,13 +514,10 @@ public:
log_debug(" lv_title=%s", this->lv_title.c_str()); log_debug(" lv_title=%s", this->lv_title.c_str());
log_debug(" lv_y=%u", this->lv_y); log_debug(" lv_y=%u", this->lv_y);
log_debug(" lv_top=%d", (int) this->lv_top); log_debug(" lv_top=%d", (int) this->lv_top);
};
virtual void invoke_scroll()
{
this->lv_scroll(this);
} }
virtual void invoke_scroll() { this->lv_scroll(this); }
protected: protected:
void delegate_scroll_out() void delegate_scroll_out()
{ {
@ -634,11 +526,11 @@ protected:
} }
} }
enum lv_mode_t { enum class lv_mode_t {
LV_MODE_NONE, NONE,
LV_MODE_DOWN, DOWN,
LV_MODE_UP, UP,
LV_MODE_DRAG DRAG
}; };
static list_gutter_source DEFAULT_GUTTER_SOURCE; static list_gutter_source DEFAULT_GUTTER_SOURCE;
@ -667,7 +559,7 @@ protected:
int lv_scroll_accel{1}; int lv_scroll_accel{1};
int lv_scroll_velo; int lv_scroll_velo;
int lv_mouse_y{-1}; int lv_mouse_y{-1};
lv_mode_t lv_mouse_mode{LV_MODE_NONE}; lv_mode_t lv_mouse_mode{lv_mode_t::NONE};
vis_line_t lv_tail_space{1}; vis_line_t lv_tail_space{1};
}; };
#endif #endif

File diff suppressed because it is too large Load Diff

@ -75,53 +75,17 @@
#include "top_status_source.hh" #include "top_status_source.hh"
#include "view_helpers.hh" #include "view_helpers.hh"
/** The command modes that are available while viewing a file. */
typedef enum {
LNM_PAGING,
LNM_FILTER,
LNM_FILES,
LNM_COMMAND,
LNM_SEARCH,
LNM_SEARCH_FILTERS,
LNM_SEARCH_FILES,
LNM_CAPTURE,
LNM_SQL,
LNM_EXEC,
LNM_USER,
} ln_mode_t;
enum { enum {
LNB_SYSLOG,
LNB__MAX,
LNB_TIMESTAMP,
LNB_HELP,
LNB_HEADLESS, LNB_HEADLESS,
LNB_QUIET, LNB_MANAGEMENT,
LNB_CHECK_CONFIG,
LNB_INSTALL,
LNB_UPDATE_FORMATS,
LNB_VERBOSE,
LNB_SECURE_MODE, LNB_SECURE_MODE,
LNB_NO_DEFAULT,
}; };
/** Flags set on the lnav command-line. */ /** Flags set on the lnav command-line. */
typedef enum { typedef enum {
LNF_SYSLOG = (1L << LNB_SYSLOG),
LNF_TIMESTAMP = (1L << LNB_TIMESTAMP),
LNF_HELP = (1L << LNB_HELP),
LNF_HEADLESS = (1L << LNB_HEADLESS), LNF_HEADLESS = (1L << LNB_HEADLESS),
LNF_QUIET = (1L << LNB_QUIET), LNF_MANAGEMENT = (1L << LNB_MANAGEMENT),
LNF_CHECK_CONFIG = (1L << LNB_CHECK_CONFIG),
LNF_INSTALL = (1L << LNB_INSTALL),
LNF_UPDATE_FORMATS = (1L << LNB_UPDATE_FORMATS),
LNF_VERBOSE = (1L << LNB_VERBOSE),
LNF_SECURE_MODE = (1L << LNB_SECURE_MODE), LNF_SECURE_MODE = (1L << LNB_SECURE_MODE),
LNF_NO_DEFAULT = (1L << LNB_NO_DEFAULT),
LNF__ALL = (LNF_SYSLOG | LNF_HELP),
} lnav_flags_t; } lnav_flags_t;
extern const char* lnav_zoom_strings[]; extern const char* lnav_zoom_strings[];
@ -138,17 +102,17 @@ typedef enum {
LNS__MAX LNS__MAX
} lnav_status_t; } lnav_status_t;
typedef std::pair<int, int> ppid_time_pair_t; using ppid_time_pair_t = std::pair<int, int>;
typedef std::pair<ppid_time_pair_t, ghc::filesystem::path> session_pair_t; using session_pair_t = std::pair<ppid_time_pair_t, ghc::filesystem::path>;
class input_state_tracker : public log_state_dumper { class input_state_tracker : public log_state_dumper {
public: public:
input_state_tracker() : ist_index(0) input_state_tracker()
{ {
memset(this->ist_recent_key_presses, memset(this->ist_recent_key_presses,
0, 0,
sizeof(this->ist_recent_key_presses)); sizeof(this->ist_recent_key_presses));
}; }
void log_state() override void log_state() override
{ {
@ -159,19 +123,19 @@ public:
this->ist_recent_key_presses[lpc]); this->ist_recent_key_presses[lpc]);
} }
log_msg_extra_complete(); log_msg_extra_complete();
}; }
void push_back(int ch) void push_back(int ch)
{ {
this->ist_recent_key_presses[this->ist_index % COUNT] = ch; this->ist_recent_key_presses[this->ist_index % COUNT] = ch;
this->ist_index = (this->ist_index + 1) % COUNT; this->ist_index = (this->ist_index + 1) % COUNT;
}; }
private: private:
static const int COUNT = 10; static const int COUNT = 10;
int ist_recent_key_presses[COUNT]; int ist_recent_key_presses[COUNT];
size_t ist_index; size_t ist_index{0};
}; };
struct key_repeat_history { struct key_repeat_history {
@ -209,7 +173,7 @@ struct lnav_data_t {
time_t ld_session_time; time_t ld_session_time;
time_t ld_session_load_time; time_t ld_session_load_time;
const char* ld_program_name; const char* ld_program_name;
const char* ld_debug_log_name; std::string ld_debug_log_name;
std::list<std::string> ld_commands; std::list<std::string> ld_commands;
bool ld_cmd_init_done; bool ld_cmd_init_done;
@ -228,7 +192,7 @@ struct lnav_data_t {
unsigned long ld_flags; unsigned long ld_flags;
WINDOW* ld_window; WINDOW* ld_window;
ln_mode_t ld_mode; ln_mode_t ld_mode;
ln_mode_t ld_last_config_mode{LNM_FILTER}; ln_mode_t ld_last_config_mode{ln_mode_t::FILTER};
statusview_curses ld_status[LNS__MAX]; statusview_curses ld_status[LNS__MAX];
top_status_source ld_top_source; top_status_source ld_top_source;
@ -301,6 +265,9 @@ struct lnav_data_t {
int ld_fifo_counter; int ld_fifo_counter;
struct key_repeat_history ld_key_repeat_history; struct key_repeat_history ld_key_repeat_history;
bool ld_initial_build{false};
bool ld_show_help_view{false};
}; };
struct static_service { struct static_service {
@ -322,18 +289,7 @@ extern const ssize_t ZOOM_COUNT;
#define HELP_MSG_2(x, y, msg) "Press " ANSI_BOLD(#x) "/" ANSI_BOLD(#y) " " msg #define HELP_MSG_2(x, y, msg) "Press " ANSI_BOLD(#x) "/" ANSI_BOLD(#y) " " msg
void rebuild_hist();
size_t rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline
= nonstd::nullopt);
void rebuild_indexes_repeatedly();
bool setup_logline_table(exec_context& ec); bool setup_logline_table(exec_context& ec);
bool rescan_files(bool required = false);
bool update_active_files(file_collection& new_files);
void wait_for_children(); void wait_for_children();
textview_curses* get_textview_for_mode(ln_mode_t mode);
#endif #endif

@ -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

@ -55,6 +55,7 @@
#include "db_sub_source.hh" #include "db_sub_source.hh"
#include "field_overlay_source.hh" #include "field_overlay_source.hh"
#include "fmt/printf.h" #include "fmt/printf.h"
#include "lnav.indexing.hh"
#include "lnav_commands.hh" #include "lnav_commands.hh"
#include "lnav_config.hh" #include "lnav_config.hh"
#include "lnav_util.hh" #include "lnav_util.hh"
@ -613,11 +614,12 @@ com_goto_mark(exec_context& ec,
if (args.size() > 1) { if (args.size() > 1) {
for (size_t lpc = 1; lpc < args.size(); lpc++) { for (size_t lpc = 1; lpc < args.size(); lpc++) {
auto bt = bookmark_type_t::find_type(args[lpc]); auto bt_opt = bookmark_type_t::find_type(args[lpc]);
if (bt == nullptr) { if (!bt_opt) {
return ec.make_error("unknown bookmark type"); return ec.make_error("unknown bookmark type: {}",
args[lpc]);
} }
mark_types.insert(bt); mark_types.insert(bt_opt.value());
} }
} else { } else {
mark_types = DEFAULT_TYPES; mark_types = DEFAULT_TYPES;
@ -826,34 +828,6 @@ json_write_row(yajl_gen handle, int row)
} }
} }
static void
write_line_to(FILE* outfile, const attr_line_t& al)
{
const auto& al_attrs = al.get_attrs();
auto lr = find_string_attr_range(al_attrs, &SA_ORIGINAL_LINE);
const auto line_meta_opt = get_string_attr(al_attrs, logline::L_META);
if (lr.lr_start > 1) {
// If the line is prefixed with some extra information, include that
// in the output. For example, the log file name or time offset.
lr = line_range{0, -1};
}
fwrite(lr.substr(al.get_string()), 1, lr.sublen(al.get_string()), outfile);
fwrite("\n", 1, 1, outfile);
if (line_meta_opt) {
auto bm = line_meta_opt.value().get();
if (!bm->bm_comment.empty()) {
fprintf(outfile, " // %s\n", bm->bm_comment.c_str());
}
if (!bm->bm_tags.empty()) {
fmt::print(outfile, " -- {}\n", fmt::join(bm->bm_tags, " "));
}
}
}
static Result<std::string, lnav::console::user_message> static Result<std::string, lnav::console::user_message>
com_save_to(exec_context& ec, com_save_to(exec_context& ec,
std::string cmdline, std::string cmdline,
@ -1129,13 +1103,33 @@ com_save_to(exec_context& ec,
vis_line_t top = tc->get_top(); vis_line_t top = tc->get_top();
vis_line_t bottom = tc->get_bottom(); vis_line_t bottom = tc->get_bottom();
auto y = 0_vl;
std::vector<attr_line_t> rows(bottom - top + 1); std::vector<attr_line_t> rows(bottom - top + 1);
attr_line_t ov_al;
auto* los = tc->get_overlay_source();
tc->listview_value_for_rows(*tc, top, rows); tc->listview_value_for_rows(*tc, top, rows);
for (auto& al : rows) { for (const auto& al : rows) {
while (los != nullptr
&& los->list_value_for_overlay(
*tc, y, tc->get_inner_height(), top, ov_al))
{
write_line_to(outfile, ov_al);
++y;
}
write_line_to(outfile, al); write_line_to(outfile, al);
line_count += 1; line_count += 1;
++top;
++y;
}
while (los != nullptr
&& los->list_value_for_overlay(
*tc, y, tc->get_inner_height(), top, ov_al)
&& !ov_al.empty())
{
write_line_to(outfile, ov_al);
++y;
} }
tc->set_word_wrap(wrapped); tc->set_word_wrap(wrapped);
@ -1225,9 +1219,10 @@ com_save_to(exec_context& ec,
tc->set_word_wrap(wrapped); tc->set_word_wrap(wrapped);
} else { } else {
auto* los = tc->get_overlay_source();
std::vector<attr_line_t> rows(1); std::vector<attr_line_t> rows(1);
attr_line_t ov_al;
size_t count = 0; size_t count = 0;
std::string line;
for (auto iter = all_user_marks.begin(); iter != all_user_marks.end(); for (auto iter = all_user_marks.begin(); iter != all_user_marks.end();
iter++, count++) iter++, count++)
@ -1238,6 +1233,15 @@ com_save_to(exec_context& ec,
tc->listview_value_for_rows(*tc, *iter, rows); tc->listview_value_for_rows(*tc, *iter, rows);
write_line_to(outfile, rows[0]); write_line_to(outfile, rows[0]);
auto y = 1_vl;
while (los != nullptr
&& los->list_value_for_overlay(
*tc, y, tc->get_inner_height(), *iter, ov_al))
{
write_line_to(outfile, ov_al);
++y;
}
line_count += 1; line_count += 1;
} }
} }
@ -1551,7 +1555,7 @@ com_highlight(exec_context& ec,
if (lnav_data.ld_rl_view != nullptr) { if (lnav_data.ld_rl_view != nullptr) {
lnav_data.ld_rl_view->add_possibility( lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "highlight", args[1]); ln_mode_t::COMMAND, "highlight", args[1]);
} }
retval = "info: highlight pattern now active"; retval = "info: highlight pattern now active";
@ -1591,7 +1595,7 @@ com_clear_highlight(exec_context& ec,
if (lnav_data.ld_rl_view != NULL) { if (lnav_data.ld_rl_view != NULL) {
lnav_data.ld_rl_view->rem_possibility( lnav_data.ld_rl_view->rem_possibility(
LNM_COMMAND, "highlight", args[1]); ln_mode_t::COMMAND, "highlight", args[1]);
} }
} }
} else { } else {
@ -1991,7 +1995,7 @@ com_create_logline_table(exec_context& ec,
custom_logline_tables.insert(args[1]); custom_logline_tables.insert(args[1]);
if (lnav_data.ld_rl_view != NULL) { if (lnav_data.ld_rl_view != NULL) {
lnav_data.ld_rl_view->add_possibility( lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "custom-table", args[1]); ln_mode_t::COMMAND, "custom-table", args[1]);
} }
retval = "info: created new log table -- " + args[1]; retval = "info: created new log table -- " + args[1];
} else { } else {
@ -2032,7 +2036,7 @@ com_delete_logline_table(exec_context& ec,
if (rc.empty()) { if (rc.empty()) {
if (lnav_data.ld_rl_view != NULL) { if (lnav_data.ld_rl_view != NULL) {
lnav_data.ld_rl_view->rem_possibility( lnav_data.ld_rl_view->rem_possibility(
LNM_COMMAND, "custom-table", args[1]); ln_mode_t::COMMAND, "custom-table", args[1]);
} }
retval = "info: deleted logline table"; retval = "info: deleted logline table";
} else { } else {
@ -2103,7 +2107,7 @@ com_create_search_table(exec_context& ec,
custom_search_tables.insert(args[1]); custom_search_tables.insert(args[1]);
if (lnav_data.ld_rl_view != NULL) { if (lnav_data.ld_rl_view != NULL) {
lnav_data.ld_rl_view->add_possibility( lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "search-table", args[1]); ln_mode_t::COMMAND, "search-table", args[1]);
} }
retval = "info: created new search table -- " + args[1]; retval = "info: created new search table -- " + args[1];
} else { } else {
@ -2140,7 +2144,7 @@ com_delete_search_table(exec_context& ec,
if (rc.empty()) { if (rc.empty()) {
if (lnav_data.ld_rl_view != NULL) { if (lnav_data.ld_rl_view != NULL) {
lnav_data.ld_rl_view->rem_possibility( lnav_data.ld_rl_view->rem_possibility(
LNM_COMMAND, "search-table", args[1]); ln_mode_t::COMMAND, "search-table", args[1]);
} }
retval = "info: deleted search table"; retval = "info: deleted search table";
} else { } else {
@ -3156,8 +3160,7 @@ com_summarize(exec_context& ec,
const auto& top_source = ec.ec_source.top(); const auto& top_source = ec.ec_source.top();
sql_progress_guard progress_guard(sql_progress, sql_progress_guard progress_guard(sql_progress,
sql_progress_finished, sql_progress_finished,
top_source.s_source, top_source.s_location,
top_source.s_line,
top_source.s_content); top_source.s_content);
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize); auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
int retcode; int retcode;
@ -3848,6 +3851,14 @@ com_poll_now(exec_context& ec,
return Ok(std::string()); return Ok(std::string());
} }
static Result<std::string, lnav::console::user_message>
com_test_comment(exec_context& ec,
std::string cmdline,
std::vector<std::string>& args)
{
return Ok(std::string());
}
static Result<std::string, lnav::console::user_message> static Result<std::string, lnav::console::user_message>
com_redraw(exec_context& ec, com_redraw(exec_context& ec,
std::string cmdline, std::string cmdline,
@ -4014,7 +4025,9 @@ com_config(exec_context& ec,
if (args.empty()) { if (args.empty()) {
args.emplace_back("config-option"); args.emplace_back("config-option");
} else if (args.size() > 1) { } else if (args.size() > 1) {
yajlpp_parse_context ypc("input", &lnav_config_handlers); static const auto INPUT_SRC = intern_string::lookup("input");
yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
std::vector<lnav::console::user_message> errors; std::vector<lnav::console::user_message> errors;
std::string option = args[1]; std::string option = args[1];
@ -4129,10 +4142,7 @@ com_config(exec_context& ec,
if (changed) { if (changed) {
intern_string_t path = intern_string::lookup(option); intern_string_t path = intern_string::lookup(option);
lnav_config_locations[path] = { lnav_config_locations[path] = ec.ec_source.top().s_location;
intern_string::lookup(ec.ec_source.top().s_source),
ec.ec_source.top().s_line,
};
reload_config(errors); reload_config(errors);
if (!errors.empty()) { if (!errors.empty()) {
@ -4170,7 +4180,9 @@ com_reset_config(exec_context& ec,
} else if (args.size() == 1) { } else if (args.size() == 1) {
return ec.make_error("expecting a configuration option to reset"); return ec.make_error("expecting a configuration option to reset");
} else { } else {
yajlpp_parse_context ypc("input", &lnav_config_handlers); static const auto INPUT_SRC = intern_string::lookup("input");
yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
std::string option = args[1]; std::string option = args[1];
lnav_config = rollback_lnav_config; lnav_config = rollback_lnav_config;
@ -4401,10 +4413,10 @@ public:
class db_spectro_value_source : public spectrogram_value_source { class db_spectro_value_source : public spectrogram_value_source {
public: public:
db_spectro_value_source(std::string colname) db_spectro_value_source(std::string colname)
: dsvs_colname(std::move(colname)), dsvs_begin_time(0), dsvs_end_time(0) : dsvs_colname(std::move(colname))
{ {
this->update_stats(); this->update_stats();
}; }
void update_stats() void update_stats()
{ {
@ -4425,12 +4437,12 @@ public:
return; return;
} }
if (this->dsvs_column_index == -1) { if (!this->dsvs_column_index) {
this->dsvs_error_msg = "unknown column -- " + this->dsvs_colname; this->dsvs_error_msg = "unknown column -- " + this->dsvs_colname;
return; return;
} }
if (!dls.dls_headers[this->dsvs_column_index].hm_graphable) { if (!dls.dls_headers[this->dsvs_column_index.value()].hm_graphable) {
this->dsvs_error_msg this->dsvs_error_msg
= "column is not numeric -- " + this->dsvs_colname; = "column is not numeric -- " + this->dsvs_colname;
return; return;
@ -4449,9 +4461,9 @@ public:
this->dsvs_stats.lvs_min_value = bs.bs_min_value; this->dsvs_stats.lvs_min_value = bs.bs_min_value;
this->dsvs_stats.lvs_max_value = bs.bs_max_value; this->dsvs_stats.lvs_max_value = bs.bs_max_value;
this->dsvs_stats.lvs_count = dls.dls_rows.size(); this->dsvs_stats.lvs_count = dls.dls_rows.size();
}; }
void spectro_bounds(spectrogram_bounds& sb_out) void spectro_bounds(spectrogram_bounds& sb_out) override
{ {
db_label_source& dls = lnav_data.ld_db_row_source; db_label_source& dls = lnav_data.ld_db_row_source;
@ -4466,9 +4478,9 @@ public:
sb_out.sb_min_value_out = this->dsvs_stats.lvs_min_value; sb_out.sb_min_value_out = this->dsvs_stats.lvs_min_value;
sb_out.sb_max_value_out = this->dsvs_stats.lvs_max_value; sb_out.sb_max_value_out = this->dsvs_stats.lvs_max_value;
sb_out.sb_count = this->dsvs_stats.lvs_count; sb_out.sb_count = this->dsvs_stats.lvs_count;
}; }
void spectro_row(spectrogram_request& sr, spectrogram_row& row_out) void spectro_row(spectrogram_request& sr, spectrogram_row& row_out) override
{ {
db_label_source& dls = lnav_data.ld_db_row_source; db_label_source& dls = lnav_data.ld_db_row_source;
auto begin_row = dls.row_for_time({sr.sr_begin_time, 0}).value_or(0_vl); auto begin_row = dls.row_for_time({sr.sr_begin_time, 0}).value_or(0_vl);
@ -4478,23 +4490,25 @@ public:
for (auto lpc = begin_row; lpc < end_row; ++lpc) { for (auto lpc = begin_row; lpc < end_row; ++lpc) {
double value = 0.0; double value = 0.0;
sscanf(dls.dls_rows[lpc][this->dsvs_column_index], "%lf", &value); sscanf(dls.dls_rows[lpc][this->dsvs_column_index.value()],
"%lf",
&value);
row_out.add_value(sr, value, false); row_out.add_value(sr, value, false);
} }
}; }
void spectro_mark(textview_curses& tc, void spectro_mark(textview_curses& tc,
time_t begin_time, time_t begin_time,
time_t end_time, time_t end_time,
double range_min, double range_min,
double range_max){}; double range_max) override{};
std::string dsvs_colname; std::string dsvs_colname;
logline_value_stats dsvs_stats; logline_value_stats dsvs_stats;
time_t dsvs_begin_time; time_t dsvs_begin_time{0};
time_t dsvs_end_time; time_t dsvs_end_time{0};
int dsvs_column_index; nonstd::optional<size_t> dsvs_column_index;
std::string dsvs_error_msg; std::string dsvs_error_msg;
}; };
@ -4589,9 +4603,10 @@ command_prompt(std::vector<std::string>& args)
lnav_data.ld_exec_context.ec_top_line = tc->get_top(); lnav_data.ld_exec_context.ec_top_line = tc->get_top();
lnav_data.ld_rl_view->clear_possibilities(LNM_COMMAND, lnav_data.ld_rl_view->clear_possibilities(ln_mode_t::COMMAND,
"numeric-colname"); "numeric-colname");
lnav_data.ld_rl_view->clear_possibilities(LNM_COMMAND, "colname"); lnav_data.ld_rl_view->clear_possibilities(ln_mode_t::COMMAND,
"colname");
ldh.parse_line(log_view.get_top(), true); ldh.parse_line(log_view.get_top(), true);
@ -4604,7 +4619,7 @@ command_prompt(std::vector<std::string>& args)
} }
lnav_data.ld_rl_view->add_possibility( lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "numeric-colname", dls_header.hm_name); ln_mode_t::COMMAND, "numeric-colname", dls_header.hm_name);
} }
} else { } else {
for (auto& ldh_line_value : ldh.ldh_line_values) { for (auto& ldh_line_value : ldh.ldh_line_values) {
@ -4622,67 +4637,74 @@ command_prompt(std::vector<std::string>& args)
} }
lnav_data.ld_rl_view->add_possibility( lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "numeric-colname", meta.lvm_name.to_string()); ln_mode_t::COMMAND,
"numeric-colname",
meta.lvm_name.to_string());
} }
} }
for (auto& cn_name : ldh.ldh_namer->cn_names) { for (auto& cn_name : ldh.ldh_namer->cn_names) {
lnav_data.ld_rl_view->add_possibility( lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "colname", cn_name); ln_mode_t::COMMAND, "colname", cn_name);
} }
for (const auto& iter : ldh.ldh_namer->cn_builtin_names) { for (const auto& iter : ldh.ldh_namer->cn_builtin_names) {
if (iter == "col") { if (iter == "col") {
continue; continue;
} }
lnav_data.ld_rl_view->add_possibility(LNM_COMMAND, "colname", iter); lnav_data.ld_rl_view->add_possibility(
ln_mode_t::COMMAND, "colname", iter);
} }
ldh.clear(); ldh.clear();
readline_curses* rlc = lnav_data.ld_rl_view; readline_curses* rlc = lnav_data.ld_rl_view;
rlc->clear_possibilities(LNM_COMMAND, "move-time"); rlc->clear_possibilities(ln_mode_t::COMMAND, "move-time");
rlc->add_possibility(LNM_COMMAND, "move-time", MOVE_TIMES); rlc->add_possibility(ln_mode_t::COMMAND, "move-time", MOVE_TIMES);
rlc->clear_possibilities(LNM_COMMAND, "line-time"); rlc->clear_possibilities(ln_mode_t::COMMAND, "line-time");
{ {
struct timeval tv = lf->get_time_offset(); struct timeval tv = lf->get_time_offset();
char buffer[64]; char buffer[64];
sql_strftime( sql_strftime(
buffer, sizeof(buffer), ll->get_time(), ll->get_millis(), 'T'); buffer, sizeof(buffer), ll->get_time(), ll->get_millis(), 'T');
rlc->add_possibility(LNM_COMMAND, "line-time", buffer); rlc->add_possibility(ln_mode_t::COMMAND, "line-time", buffer);
rlc->add_possibility(LNM_COMMAND, "move-time", buffer); rlc->add_possibility(ln_mode_t::COMMAND, "move-time", buffer);
sql_strftime(buffer, sql_strftime(buffer,
sizeof(buffer), sizeof(buffer),
ll->get_time() - tv.tv_sec, ll->get_time() - tv.tv_sec,
ll->get_millis() - (tv.tv_usec / 1000), ll->get_millis() - (tv.tv_usec / 1000),
'T'); 'T');
rlc->add_possibility(LNM_COMMAND, "line-time", buffer); rlc->add_possibility(ln_mode_t::COMMAND, "line-time", buffer);
rlc->add_possibility(LNM_COMMAND, "move-time", buffer); rlc->add_possibility(ln_mode_t::COMMAND, "move-time", buffer);
} }
} }
rollback_lnav_config = lnav_config; rollback_lnav_config = lnav_config;
lnav_data.ld_doc_status_source.set_title("Command Help"); lnav_data.ld_doc_status_source.set_title("Command Help");
add_view_text_possibilities( add_view_text_possibilities(lnav_data.ld_rl_view,
lnav_data.ld_rl_view, LNM_COMMAND, "filter", tc); ln_mode_t::COMMAND,
"filter",
tc,
text_quoting::none);
lnav_data.ld_rl_view->add_possibility( lnav_data.ld_rl_view->add_possibility(
LNM_COMMAND, "filter", tc->get_current_search()); ln_mode_t::COMMAND, "filter", tc->get_current_search());
add_filter_possibilities(tc); add_filter_possibilities(tc);
add_mark_possibilities(); add_mark_possibilities();
add_config_possibilities(); add_config_possibilities();
add_env_possibilities(LNM_COMMAND); add_env_possibilities(ln_mode_t::COMMAND);
add_tag_possibilities(); add_tag_possibilities();
add_file_possibilities(); add_file_possibilities();
add_recent_netlocs_possibilities(); add_recent_netlocs_possibilities();
if (tc == &lnav_data.ld_views[LNV_LOG]) { if (tc == &lnav_data.ld_views[LNV_LOG]) {
add_filter_expr_possibilities( add_filter_expr_possibilities(
lnav_data.ld_rl_view, LNM_COMMAND, "filter-expr-syms"); lnav_data.ld_rl_view, ln_mode_t::COMMAND, "filter-expr-syms");
} }
lnav_data.ld_mode = LNM_COMMAND; lnav_data.ld_mode = ln_mode_t::COMMAND;
lnav_data.ld_rl_view->focus( lnav_data.ld_rl_view->focus(ln_mode_t::COMMAND,
LNM_COMMAND, cget(args, 2).value_or(":"), cget(args, 3).value_or("")); cget(args, 2).value_or(":"),
cget(args, 3).value_or(""));
} }
static void static void
@ -4691,19 +4713,21 @@ script_prompt(std::vector<std::string>& args)
textview_curses* tc = *lnav_data.ld_view_stack.top(); textview_curses* tc = *lnav_data.ld_view_stack.top();
auto& scripts = injector::get<available_scripts&>(); auto& scripts = injector::get<available_scripts&>();
lnav_data.ld_mode = LNM_EXEC; lnav_data.ld_mode = ln_mode_t::EXEC;
lnav_data.ld_exec_context.ec_top_line = tc->get_top(); lnav_data.ld_exec_context.ec_top_line = tc->get_top();
lnav_data.ld_rl_view->clear_possibilities(LNM_EXEC, "__command"); lnav_data.ld_rl_view->clear_possibilities(ln_mode_t::EXEC, "__command");
find_format_scripts(lnav_data.ld_config_paths, scripts); find_format_scripts(lnav_data.ld_config_paths, scripts);
for (const auto& iter : scripts.as_scripts) { for (const auto& iter : scripts.as_scripts) {
lnav_data.ld_rl_view->add_possibility( lnav_data.ld_rl_view->add_possibility(
LNM_EXEC, "__command", iter.first); ln_mode_t::EXEC, "__command", iter.first);
} }
add_view_text_possibilities(lnav_data.ld_rl_view, LNM_EXEC, "*", tc); add_view_text_possibilities(
add_env_possibilities(LNM_EXEC); lnav_data.ld_rl_view, ln_mode_t::EXEC, "*", tc, text_quoting::regex);
lnav_data.ld_rl_view->focus( add_env_possibilities(ln_mode_t::EXEC);
LNM_EXEC, cget(args, 2).value_or("|"), cget(args, 3).value_or("")); lnav_data.ld_rl_view->focus(ln_mode_t::EXEC,
cget(args, 2).value_or("|"),
cget(args, 3).value_or(""));
lnav_data.ld_bottom_source.set_prompt( lnav_data.ld_bottom_source.set_prompt(
"Enter a script to execute: (Press " ANSI_BOLD("CTRL+]") " to abort)"); "Enter a script to execute: (Press " ANSI_BOLD("CTRL+]") " to abort)");
} }
@ -4713,11 +4737,13 @@ search_prompt(std::vector<std::string>& args)
{ {
textview_curses* tc = *lnav_data.ld_view_stack.top(); textview_curses* tc = *lnav_data.ld_view_stack.top();
lnav_data.ld_mode = LNM_SEARCH; lnav_data.ld_mode = ln_mode_t::SEARCH;
lnav_data.ld_search_start_line = tc->get_top(); lnav_data.ld_search_start_line = tc->get_top();
add_view_text_possibilities(lnav_data.ld_rl_view, LNM_SEARCH, "*", tc); add_view_text_possibilities(
lnav_data.ld_rl_view->focus( lnav_data.ld_rl_view, ln_mode_t::SEARCH, "*", tc, text_quoting::regex);
LNM_SEARCH, cget(args, 2).value_or("/"), cget(args, 3).value_or("")); lnav_data.ld_rl_view->focus(ln_mode_t::SEARCH,
cget(args, 2).value_or("/"),
cget(args, 3).value_or(""));
lnav_data.ld_doc_status_source.set_title("Syntax Help"); lnav_data.ld_doc_status_source.set_title("Syntax Help");
rl_set_help(); rl_set_help();
lnav_data.ld_bottom_source.set_prompt( lnav_data.ld_bottom_source.set_prompt(
@ -4729,13 +4755,14 @@ search_prompt(std::vector<std::string>& args)
static void static void
search_filters_prompt(std::vector<std::string>& args) search_filters_prompt(std::vector<std::string>& args)
{ {
lnav_data.ld_mode = LNM_SEARCH_FILTERS; lnav_data.ld_mode = ln_mode_t::SEARCH_FILTERS;
lnav_data.ld_filter_view.reload_data(); lnav_data.ld_filter_view.reload_data();
add_view_text_possibilities(lnav_data.ld_rl_view, add_view_text_possibilities(lnav_data.ld_rl_view,
LNM_SEARCH_FILTERS, ln_mode_t::SEARCH_FILTERS,
"*", "*",
&lnav_data.ld_filter_view); &lnav_data.ld_filter_view,
lnav_data.ld_rl_view->focus(LNM_SEARCH_FILTERS, text_quoting::regex);
lnav_data.ld_rl_view->focus(ln_mode_t::SEARCH_FILTERS,
cget(args, 2).value_or("/"), cget(args, 2).value_or("/"),
cget(args, 3).value_or("")); cget(args, 3).value_or(""));
lnav_data.ld_bottom_source.set_prompt( lnav_data.ld_bottom_source.set_prompt(
@ -4749,12 +4776,13 @@ search_files_prompt(std::vector<std::string>& args)
{ {
static const std::regex re_escape(R"(([.\^$*+?()\[\]{}\\|]))"); static const std::regex re_escape(R"(([.\^$*+?()\[\]{}\\|]))");
lnav_data.ld_mode = LNM_SEARCH_FILES; lnav_data.ld_mode = ln_mode_t::SEARCH_FILES;
for (const auto& lf : lnav_data.ld_active_files.fc_files) { for (const auto& lf : lnav_data.ld_active_files.fc_files) {
auto path = pcrepp::quote(lf->get_unique_path()); auto path = pcrepp::quote(lf->get_unique_path());
lnav_data.ld_rl_view->add_possibility(LNM_SEARCH_FILES, "*", path); lnav_data.ld_rl_view->add_possibility(
ln_mode_t::SEARCH_FILES, "*", path);
} }
lnav_data.ld_rl_view->focus(LNM_SEARCH_FILES, lnav_data.ld_rl_view->focus(ln_mode_t::SEARCH_FILES,
cget(args, 2).value_or("/"), cget(args, 2).value_or("/"),
cget(args, 3).value_or("")); cget(args, 3).value_or(""));
lnav_data.ld_bottom_source.set_prompt( lnav_data.ld_bottom_source.set_prompt(
@ -4771,10 +4799,11 @@ sql_prompt(std::vector<std::string>& args)
lnav_data.ld_exec_context.ec_top_line = tc->get_top(); lnav_data.ld_exec_context.ec_top_line = tc->get_top();
lnav_data.ld_mode = LNM_SQL; lnav_data.ld_mode = ln_mode_t::SQL;
setup_logline_table(lnav_data.ld_exec_context); setup_logline_table(lnav_data.ld_exec_context);
lnav_data.ld_rl_view->focus( lnav_data.ld_rl_view->focus(ln_mode_t::SQL,
LNM_SQL, cget(args, 2).value_or(";"), cget(args, 3).value_or("")); cget(args, 2).value_or(";"),
cget(args, 3).value_or(""));
lnav_data.ld_doc_status_source.set_title("Query Help"); lnav_data.ld_doc_status_source.set_title("Query Help");
rl_set_help(); rl_set_help();
@ -4794,10 +4823,11 @@ user_prompt(std::vector<std::string>& args)
textview_curses* tc = *lnav_data.ld_view_stack.top(); textview_curses* tc = *lnav_data.ld_view_stack.top();
lnav_data.ld_exec_context.ec_top_line = tc->get_top(); lnav_data.ld_exec_context.ec_top_line = tc->get_top();
lnav_data.ld_mode = LNM_USER; lnav_data.ld_mode = ln_mode_t::USER;
setup_logline_table(lnav_data.ld_exec_context); setup_logline_table(lnav_data.ld_exec_context);
lnav_data.ld_rl_view->focus( lnav_data.ld_rl_view->focus(ln_mode_t::USER,
LNM_USER, cget(args, 2).value_or("? "), cget(args, 3).value_or("")); cget(args, 2).value_or("? "),
cget(args, 3).value_or(""));
lnav_data.ld_bottom_source.update_loading(0, 0); lnav_data.ld_bottom_source.update_loading(0, 0);
lnav_data.ld_status[LNS_BOTTOM].do_update(); lnav_data.ld_status[LNS_BOTTOM].do_update();
@ -5700,10 +5730,12 @@ init_lnav_commands(readline_context::command_map_t& cmd_map)
} }
if (getenv("lnav_test") != nullptr) { if (getenv("lnav_test") != nullptr) {
static readline_context::command_t rebuild(com_rebuild), static readline_context::command_t rebuild(com_rebuild),
shexec(com_shexec), poll_now(com_poll_now); shexec(com_shexec), poll_now(com_poll_now),
test_comment(com_test_comment);
cmd_map["rebuild"] = &rebuild; cmd_map["rebuild"] = &rebuild;
cmd_map["shexec"] = &shexec; cmd_map["shexec"] = &shexec;
cmd_map["poll-now"] = &poll_now; cmd_map["poll-now"] = &poll_now;
cmd_map["test-comment"] = &test_comment;
} }
} }

@ -173,7 +173,7 @@ ensure_dotlnav()
} }
bool bool
install_from_git(const char* repo) install_from_git(const std::string& repo)
{ {
static const std::regex repo_name_converter("[^\\w]"); static const std::regex repo_name_converter("[^\\w]");
@ -197,18 +197,18 @@ install_from_git(const char* repo)
auto git_cmd = fork_res.unwrap(); auto git_cmd = fork_res.unwrap();
if (git_cmd.in_child()) { if (git_cmd.in_child()) {
if (ghc::filesystem::is_directory(local_formats_path)) { if (ghc::filesystem::is_directory(local_formats_path)) {
printf("Updating format repo: %s\n", repo); fmt::print("Updating format repo: {}\n", repo);
log_perror(chdir(local_formats_path.c_str())); log_perror(chdir(local_formats_path.c_str()));
execlp("git", "git", "pull", nullptr); execlp("git", "git", "pull", nullptr);
} else if (ghc::filesystem::is_directory(local_configs_path)) { } else if (ghc::filesystem::is_directory(local_configs_path)) {
printf("Updating config repo: %s\n", repo); fmt::print("Updating config repo: {}\n", repo);
log_perror(chdir(local_configs_path.c_str())); log_perror(chdir(local_configs_path.c_str()));
execlp("git", "git", "pull", nullptr); execlp("git", "git", "pull", nullptr);
} else { } else {
execlp("git", execlp("git",
"git", "git",
"clone", "clone",
repo, repo.c_str(),
local_staging_path.c_str(), local_staging_path.c_str(),
nullptr); nullptr);
} }
@ -344,7 +344,8 @@ install_extra_formats()
if ((fd = lnav::filesystem::openp(config_json, O_RDONLY)) == -1) { if ((fd = lnav::filesystem::openp(config_json, O_RDONLY)) == -1) {
perror("Unable to open remote-config.json"); perror("Unable to open remote-config.json");
} else { } else {
yajlpp_parse_context ypc_config(config_root.string(), &format_handlers); yajlpp_parse_context ypc_config(
intern_string::lookup(config_root.string()), &format_handlers);
auto_mem<yajl_handle_t> jhandle(yajl_free); auto_mem<yajl_handle_t> jhandle(yajl_free);
unsigned char buffer[4096]; unsigned char buffer[4096];
ssize_t rc; ssize_t rc;
@ -639,6 +640,13 @@ static const struct json_path_container theme_styles_handlers = {
return &root->lt_style_header[5]; return &root->lt_style_header[5];
}) })
.with_children(style_config_handlers), .with_children(style_config_handlers),
yajlpp::property_handler("list-glyph")
.with_description("Styling for glyphs that prefix a list item")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_list_glyph;
})
.with_children(style_config_handlers),
}; };
static const struct json_path_container theme_syntax_styles_handlers = { static const struct json_path_container theme_syntax_styles_handlers = {
@ -1271,7 +1279,8 @@ load_config_from(_lnav_config& lconfig,
const ghc::filesystem::path& path, const ghc::filesystem::path& path,
std::vector<lnav::console::user_message>& errors) std::vector<lnav::console::user_message>& errors)
{ {
yajlpp_parse_context ypc(path.string(), &lnav_config_handlers); yajlpp_parse_context ypc(intern_string::lookup(path.string()),
&lnav_config_handlers);
struct userdata ud(errors); struct userdata ud(errors);
auto_fd fd; auto_fd fd;
@ -1326,7 +1335,8 @@ load_default_config(struct _lnav_config& config_obj,
const bin_src_file& bsf, const bin_src_file& bsf,
std::vector<lnav::console::user_message>& errors) std::vector<lnav::console::user_message>& errors)
{ {
yajlpp_parse_context ypc_builtin(bsf.get_name(), &lnav_config_handlers); yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()),
&lnav_config_handlers);
auto_mem<yajl_handle_t> handle(yajl_free); auto_mem<yajl_handle_t> handle(yajl_free);
struct userdata ud(errors); struct userdata ud(errors);
@ -1501,7 +1511,7 @@ reload_config(std::vector<lnav::console::user_message>& errors)
.with_reason(errmsg) .with_reason(errmsg)
.with_snippet( .with_snippet(
lnav::console::snippet::from( lnav::console::snippet::from(
loc_iter->second.sl_source.to_string(), "") loc_iter->second.sl_source, "")
.with_line(loc_iter->second.sl_line_number))); .with_line(loc_iter->second.sl_line_number)));
}; };

@ -69,7 +69,7 @@ bool check_experimental(const char* feature_name);
*/ */
void ensure_dotlnav(); void ensure_dotlnav();
bool install_from_git(const char* repo); bool install_from_git(const std::string& repo);
bool update_installs_from_git(); bool update_installs_from_git();
void install_extra_formats(); void install_extra_formats();

@ -37,9 +37,12 @@
#include <sys/stat.h> #include <sys/stat.h>
#include "base/ansi_scrubber.hh" #include "base/ansi_scrubber.hh"
#include "base/itertools.hh"
#include "base/result.h" #include "base/result.h"
#include "bookmarks.hh"
#include "config.h" #include "config.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "log_format_fwd.hh"
#include "view_curses.hh" #include "view_curses.hh"
#include "yajlpp/yajlpp.hh" #include "yajlpp/yajlpp.hh"
#include "yajlpp/yajlpp_def.hh" #include "yajlpp/yajlpp_def.hh"
@ -109,8 +112,44 @@ err_to_ok(const lnav::console::user_message msg)
return Ok(msg.to_attr_line().get_string()); return Ok(msg.to_attr_line().get_string());
} }
short
pollfd_revents(const std::vector<struct pollfd>& pollfds, int fd)
{
return pollfds | lnav::itertools::find_if([fd](const auto& entry) {
return entry.fd == fd;
})
| lnav::itertools::map(&pollfd::revents)
| lnav::itertools::unwrap_or((short) 0);
}
void
write_line_to(FILE* outfile, const attr_line_t& al)
{
const auto& al_attrs = al.get_attrs();
auto lr = find_string_attr_range(al_attrs, &SA_ORIGINAL_LINE);
if (!lr.is_valid() || lr.lr_start > 1) {
// If the line is prefixed with some extra information, include that
// in the output. For example, the log file name or time offset.
lnav::console::println(outfile, al);
} else {
lnav::console::println(outfile, al.subline(lr.lr_start, lr.length()));
}
}
namespace lnav { namespace lnav {
std::string
to_json(const std::string& str)
{
yajlpp_gen gen;
yajl_gen_config(gen, yajl_gen_beautify, false);
yajl_gen_string(gen, str);
return gen.to_string_fragment().to_string();
}
static void static void
to_json(yajlpp_gen& gen, const attr_line_t& al) to_json(yajlpp_gen& gen, const attr_line_t& al)
{ {
@ -173,6 +212,9 @@ to_json(const lnav::console::user_message& um)
root_map.gen("level"); root_map.gen("level");
switch (um.um_level) { switch (um.um_level) {
case console::user_message::level::raw:
root_map.gen("raw");
break;
case console::user_message::level::ok: case console::user_message::level::ok:
root_map.gen("ok"); root_map.gen("ok");
break; break;
@ -199,11 +241,9 @@ to_json(const lnav::console::user_message& um)
yajlpp_map snip_map(gen); yajlpp_map snip_map(gen);
snip_map.gen("source"); snip_map.gen("source");
snip_map.gen(snip.s_source); snip_map.gen(snip.s_location.sl_source);
snip_map.gen("line"); snip_map.gen("line");
snip_map.gen(snip.s_line); snip_map.gen(snip.s_location.sl_line_number);
snip_map.gen("column");
snip_map.gen(snip.s_column);
snip_map.gen("content"); snip_map.gen("content");
to_json(gen, snip.s_content); to_json(gen, snip.s_content);
} }
@ -234,10 +274,10 @@ read_string_attr_type(yajlpp_parse_context* ypc,
static int static int
read_string_attr_int_value(yajlpp_parse_context* ypc, long long in) read_string_attr_int_value(yajlpp_parse_context* ypc, long long in)
{ {
auto sa = (string_attr*) ypc->ypc_obj_stack.top(); auto* sa = (string_attr*) ypc->ypc_obj_stack.top();
if (sa->sa_type == &VC_ROLE) { if (sa->sa_type == &VC_ROLE) {
sa->sa_value = (role_t) in; sa->sa_value = static_cast<role_t>(in);
} }
return 1; return 1;
} }
@ -251,39 +291,27 @@ static const struct json_path_container string_attr_handlers = {
yajlpp::property_handler("value").add_cb(read_string_attr_int_value), yajlpp::property_handler("value").add_cb(read_string_attr_int_value),
}; };
static const struct json_path_container attr_line_handlers = { static const typed_json_path_container<attr_line_t> attr_line_handlers = {
yajlpp::property_handler("str").for_field(&attr_line_t::al_string), yajlpp::property_handler("str").for_field(&attr_line_t::al_string),
yajlpp::property_handler("attrs#") yajlpp::property_handler("attrs#")
.with_obj_provider<string_attr, attr_line_t>( .for_field(&attr_line_t::al_attrs)
[](const yajlpp_provider_context& ypc, attr_line_t* root) {
root->al_attrs.resize(ypc.ypc_index + 1);
return &root->al_attrs[ypc.ypc_index];
})
.with_children(string_attr_handlers), .with_children(string_attr_handlers),
}; };
template<> template<>
attr_line_t Result<attr_line_t, std::vector<console::user_message>>
from_json(const std::string& json) from_json(const std::string& json)
{ {
yajlpp_parse_context ypc("string", &attr_line_handlers); static const auto STRING_SRC = intern_string::lookup("string");
auto_mem<yajl_handle_t> handle(yajl_free);
attr_line_t retval;
handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc); return attr_line_handlers.parser_for(STRING_SRC).of(json);
ypc.with_handle(handle);
ypc.with_obj(retval);
ypc.parse(json);
ypc.complete_parse();
return retval;
} }
static const json_path_container snippet_handlers = { static const json_path_container snippet_handlers = {
yajlpp::property_handler("source").for_field(&console::snippet::s_source), yajlpp::property_handler("source").for_field(&console::snippet::s_location,
yajlpp::property_handler("line").for_field(&console::snippet::s_line), &source_location::sl_source),
yajlpp::property_handler("column").for_field(&console::snippet::s_column), yajlpp::property_handler("line").for_field(
&console::snippet::s_location, &source_location::sl_line_number),
yajlpp::property_handler("content") yajlpp::property_handler("content")
.with_obj_provider<attr_line_t, console::snippet>( .with_obj_provider<attr_line_t, console::snippet>(
[](const yajlpp_provider_context& ypc, console::snippet* snip) { [](const yajlpp_provider_context& ypc, console::snippet* snip) {
@ -293,6 +321,7 @@ static const json_path_container snippet_handlers = {
}; };
static const json_path_handler_base::enum_value_t LEVEL_ENUM[] = { static const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
{"raw", lnav::console::user_message::level::raw},
{"ok", lnav::console::user_message::level::ok}, {"ok", lnav::console::user_message::level::ok},
{"info", lnav::console::user_message::level::info}, {"info", lnav::console::user_message::level::info},
{"warning", lnav::console::user_message::level::warning}, {"warning", lnav::console::user_message::level::warning},
@ -301,51 +330,44 @@ static const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
json_path_handler_base::ENUM_TERMINATOR, json_path_handler_base::ENUM_TERMINATOR,
}; };
static const struct json_path_container user_message_handlers = { static const typed_json_path_container<console::user_message>
yajlpp::property_handler("level") user_message_handlers = {
.with_enum_values(LEVEL_ENUM) yajlpp::property_handler("level")
.for_field(&console::user_message::um_level), .with_enum_values(LEVEL_ENUM)
yajlpp::property_handler("message") .for_field(&console::user_message::um_level),
.with_obj_provider<attr_line_t, console::user_message>( yajlpp::property_handler("message")
[](const yajlpp_provider_context& ypc, .with_obj_provider<attr_line_t, console::user_message>(
console::user_message* root) { return &root->um_message; }) [](const yajlpp_provider_context& ypc,
.with_children(attr_line_handlers), console::user_message* root) { return &root->um_message; })
yajlpp::property_handler("reason") .with_children(attr_line_handlers),
.with_obj_provider<attr_line_t, console::user_message>( yajlpp::property_handler("reason")
[](const yajlpp_provider_context& ypc, .with_obj_provider<attr_line_t, console::user_message>(
console::user_message* root) { return &root->um_reason; }) [](const yajlpp_provider_context& ypc,
.with_children(attr_line_handlers), console::user_message* root) { return &root->um_reason; })
yajlpp::property_handler("snippets#") .with_children(attr_line_handlers),
.with_obj_provider<console::snippet, console::user_message>( yajlpp::property_handler("snippets#")
[](const yajlpp_provider_context& ypc, .with_obj_provider<console::snippet, console::user_message>(
console::user_message* root) { [](const yajlpp_provider_context& ypc,
root->um_snippets.resize(ypc.ypc_index + 1); console::user_message* root) {
root->um_snippets.resize(ypc.ypc_index + 1);
return &root->um_snippets[ypc.ypc_index];
}) return &root->um_snippets[ypc.ypc_index];
.with_children(snippet_handlers), })
yajlpp::property_handler("help") .with_children(snippet_handlers),
.with_obj_provider<attr_line_t, console::user_message>( yajlpp::property_handler("help")
[](const yajlpp_provider_context& ypc, .with_obj_provider<attr_line_t, console::user_message>(
console::user_message* root) { return &root->um_help; }) [](const yajlpp_provider_context& ypc,
.with_children(attr_line_handlers), console::user_message* root) { return &root->um_help; })
.with_children(attr_line_handlers),
}; };
template<> template<>
lnav::console::user_message Result<lnav::console::user_message, std::vector<console::user_message>>
from_json(const std::string& json) from_json(const std::string& json)
{ {
yajlpp_parse_context ypc("string", &user_message_handlers); static const auto STRING_SRC = intern_string::lookup("string");
auto_mem<yajl_handle_t> handle(yajl_free);
lnav::console::user_message retval;
handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
ypc.with_handle(handle);
ypc.with_obj(retval);
ypc.parse(json);
ypc.complete_parse();
return retval; return user_message_handlers.parser_for(STRING_SRC).of(json);
} }
} // namespace lnav } // namespace lnav

@ -132,26 +132,14 @@ to_string(const char* s)
} // namespace std } // namespace std
inline bool inline bool
is_glob(const char* fn) is_glob(const std::string& fn)
{ {
return (strchr(fn, '*') != nullptr || strchr(fn, '?') != nullptr return (fn.find('*') != std::string::npos
|| strchr(fn, '[') != nullptr); || fn.find('?') != std::string::npos
|| fn.find('[') != std::string::npos);
}; };
inline short short pollfd_revents(const std::vector<struct pollfd>& pollfds, int fd);
pollfd_revents(const std::vector<struct pollfd>& pollfds, int fd)
{
auto iter
= std::find_if(pollfds.begin(), pollfds.end(), [fd](const auto& entry) {
return entry.fd == fd;
});
if (iter == pollfds.end()) {
return 0;
}
return iter->revents;
}
inline bool inline bool
pollfd_ready(const std::vector<struct pollfd>& pollfds, pollfd_ready(const std::vector<struct pollfd>& pollfds,
@ -233,13 +221,17 @@ std::string err_prefix(std::string msg);
Result<std::string, lnav::console::user_message> err_to_ok( Result<std::string, lnav::console::user_message> err_to_ok(
lnav::console::user_message msg); lnav::console::user_message msg);
void write_line_to(FILE* outfile, const attr_line_t& al);
namespace lnav { namespace lnav {
std::string to_json(const std::string& str);
std::string to_json(const lnav::console::user_message& um); std::string to_json(const lnav::console::user_message& um);
std::string to_json(const attr_line_t& al); std::string to_json(const attr_line_t& al);
template<typename T> template<typename T>
T from_json(const std::string& str); Result<T, std::vector<lnav::console::user_message>> from_json(
const std::string& json);
} // namespace lnav } // namespace lnav

@ -46,9 +46,9 @@ public:
std::function<void(const std::string&, std::shared_ptr<piper_proc>)> std::function<void(const std::string&, std::shared_ptr<piper_proc>)>
piper_cb) piper_cb)
: ad_log_helper(lss), ad_child_cb(std::move(child_cb)), : ad_log_helper(lss), ad_child_cb(std::move(child_cb)),
ad_piper_cb(std::move(piper_cb)){ ad_piper_cb(std::move(piper_cb))
{
}; }
bool text_handle_mouse(textview_curses& tc, mouse_event& me) override; bool text_handle_mouse(textview_curses& tc, mouse_event& me) override;

@ -48,10 +48,7 @@
class log_data_helper { class log_data_helper {
public: public:
explicit log_data_helper(logfile_sub_source& lss) explicit log_data_helper(logfile_sub_source& lss) : ldh_log_source(lss) {}
: ldh_log_source(lss){
};
void clear(); void clear();
@ -70,7 +67,7 @@ public:
return std::count(this->ldh_msg.get_data(), return std::count(this->ldh_msg.get_data(),
this->ldh_msg.get_data() + lv.lv_origin.lr_start, this->ldh_msg.get_data() + lv.lv_origin.lr_start,
'\n'); '\n');
}; }
std::string format_json_getter(const intern_string_t field, int index); std::string format_json_getter(const intern_string_t field, int index);

@ -53,13 +53,13 @@ public:
void get_columns(std::vector<vtab_column>& cols) const override void get_columns(std::vector<vtab_column>& cols) const override
{ {
cols = this->ldt_cols; cols = this->ldt_cols;
}; }
void get_foreign_keys(std::vector<std::string>& keys_inout) const override void get_foreign_keys(std::vector<std::string>& keys_inout) const override
{ {
log_vtab_impl::get_foreign_keys(keys_inout); log_vtab_impl::get_foreign_keys(keys_inout);
keys_inout.emplace_back("log_msg_instance"); keys_inout.emplace_back("log_msg_instance");
}; }
bool next(log_cursor& lc, logfile_sub_source& lss) override; bool next(log_cursor& lc, logfile_sub_source& lss) override;

@ -46,6 +46,8 @@
#include "yajlpp/yajlpp.hh" #include "yajlpp/yajlpp.hh"
#include "yajlpp/yajlpp_def.hh" #include "yajlpp/yajlpp_def.hh"
using namespace lnav::roles::literals;
static auto intern_lifetime = intern_string::get_table_lifetime(); static auto intern_lifetime = intern_string::get_table_lifetime();
string_attr_type<void> logline::L_PREFIX("prefix"); string_attr_type<void> logline::L_PREFIX("prefix");
@ -1165,9 +1167,7 @@ external_log_format::rewrite(exec_context& ec,
} }
auto _sg = ec.enter_source( auto _sg = ec.enter_source(
this->elf_name.to_string() + ":" + vd_iter->first.to_string(), vd_iter->second->vd_rewrite_src_name, 1, vd.vd_rewriter);
1,
vd.vd_rewriter);
auto field_value auto field_value
= execute_any(ec, vd.vd_rewriter).orElse(err_to_ok).unwrap(); = execute_any(ec, vd.vd_rewriter).orElse(err_to_ok).unwrap();
struct line_range adj_origin struct line_range adj_origin
@ -1391,12 +1391,12 @@ external_log_format::get_subline(const logline& ll,
size_t begin_size = this->jlf_cached_line.size(); size_t begin_size = this->jlf_cached_line.size();
switch (jfe.jfe_type) { switch (jfe.jfe_type) {
case JLF_CONSTANT: case json_log_field::CONSTANT:
this->json_append_to_cache( this->json_append_to_cache(
jfe.jfe_default_value.c_str(), jfe.jfe_default_value.c_str(),
jfe.jfe_default_value.size()); jfe.jfe_default_value.size());
break; break;
case JLF_VARIABLE: case json_log_field::VARIABLE:
lv_iter = find_if( lv_iter = find_if(
this->jlf_line_values.begin(), this->jlf_line_values.begin(),
this->jlf_line_values.end(), this->jlf_line_values.end(),
@ -1792,8 +1792,8 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
.with_snippets(this->get_snippets())); .with_snippets(this->get_snippets()));
} }
if (this->elf_type == elf_type_t::ELF_TYPE_JSON) { if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
this->jlf_parse_context = std::make_shared<yajlpp_parse_context>( this->jlf_parse_context
this->elf_name.to_string()); = std::make_shared<yajlpp_parse_context>(this->elf_name);
this->jlf_yajl_handle.reset( this->jlf_yajl_handle.reset(
yajl_alloc(&this->jlf_parse_context->ypc_callbacks, yajl_alloc(&this->jlf_parse_context->ypc_callbacks,
nullptr, nullptr,
@ -1820,6 +1820,7 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
for (auto& vd : this->elf_value_def_order) { for (auto& vd : this->elf_value_def_order) {
std::vector<std::string>::iterator act_iter; std::vector<std::string>::iterator act_iter;
vd->vd_meta.lvm_format = this;
if (!vd->vd_internal && vd->vd_meta.lvm_column == -1) { if (!vd->vd_internal && vd->vd_meta.lvm_column == -1) {
vd->vd_meta.lvm_column = this->elf_column_count++; vd->vd_meta.lvm_column = this->elf_column_count++;
} }
@ -1841,6 +1842,8 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
#endif #endif
} }
} }
vd->set_rewrite_src_name();
} }
if (this->elf_type == elf_type_t::ELF_TYPE_TEXT if (this->elf_type == elf_type_t::ELF_TYPE_TEXT
@ -1857,8 +1860,10 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
} }
for (auto& elf_sample : this->elf_samples) { for (auto& elf_sample : this->elf_samples) {
auto sample_lines
= string_fragment(elf_sample.s_line.pp_value).split_lines();
pcre_context_static<128> pc; pcre_context_static<128> pc;
pcre_input pi(elf_sample.s_line.pp_value); pcre_input pi(sample_lines[0]);
bool found = false; bool found = false;
for (auto pat_iter = this->elf_pattern_order.begin(); for (auto pat_iter = this->elf_pattern_order.begin();
@ -1930,8 +1935,8 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
|| dts.scan(ts, ts_len, custom_formats, &tm, tv) == nullptr) { || dts.scan(ts, ts_len, custom_formats, &tm, tv) == nullptr) {
attr_line_t notes; attr_line_t notes;
notes.append("the following formats were tried:");
if (custom_formats == nullptr) { if (custom_formats == nullptr) {
notes.append("the following built-in formats were tried:");
for (int lpc = 0; PTIMEC_FORMATS[lpc].pf_fmt != nullptr; for (int lpc = 0; PTIMEC_FORMATS[lpc].pf_fmt != nullptr;
lpc++) { lpc++) {
off_t off = 0; off_t off = 0;
@ -1941,13 +1946,13 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
.append(ts, (size_t) ts_len) .append(ts, (size_t) ts_len)
.append("\n") .append("\n")
.append(2 + off, ' ') .append(2 + off, ' ')
.append(lnav::roles::comment("^ ")) .append("^ "_comment)
.append_quoted( .append_quoted(
lnav::roles::symbol(PTIMEC_FORMATS[lpc].pf_fmt)) lnav::roles::symbol(PTIMEC_FORMATS[lpc].pf_fmt))
.append( .append(" matched up to here"_comment);
lnav::roles::comment(" matched up to here"));
} }
} else { } else {
notes.append("the following custom formats were tried:");
for (int lpc = 0; custom_formats[lpc] != nullptr; lpc++) { for (int lpc = 0; custom_formats[lpc] != nullptr; lpc++) {
off_t off = 0; off_t off = 0;
@ -1956,22 +1961,26 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
.append(ts, (size_t) ts_len) .append(ts, (size_t) ts_len)
.append("\n") .append("\n")
.append(2 + off, ' ') .append(2 + off, ' ')
.append(lnav::roles::comment("^ ")) .append("^ "_comment)
.append_quoted( .append_quoted(
lnav::roles::symbol(custom_formats[lpc])) lnav::roles::symbol(custom_formats[lpc]))
.append( .append(" matched up to here"_comment);
lnav::roles::comment(" matched up to here"));
} }
} }
errors.emplace_back( errors.emplace_back(
lnav::console::user_message::error( lnav::console::user_message::error(
attr_line_t("invalid sample log message ") attr_line_t("invalid sample log message: ")
.append_quoted(elf_sample.s_line.pp_value)) .append(lnav::to_json(elf_sample.s_line.pp_value)))
.with_reason(attr_line_t("unrecognized timestamp -- ") .with_reason(attr_line_t("unrecognized timestamp -- ")
.append(ts, (size_t) ts_len)) .append(ts, (size_t) ts_len))
.with_snippet(elf_sample.s_line.to_snippet()) .with_snippet(elf_sample.s_line.to_snippet())
.with_note(notes)); .with_note(notes)
.with_help(attr_line_t("If the timestamp format is not "
"supported by default, you can "
"add a custom format with the ")
.append_quoted("timestamp-format"_symbol)
.append(" property")));
} }
log_level_t level = this->convert_level(pi, level_cap); log_level_t level = this->convert_level(pi, level_cap);
@ -1980,8 +1989,8 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
&& elf_sample.s_level != level) { && elf_sample.s_level != level) {
errors.emplace_back( errors.emplace_back(
lnav::console::user_message::error( lnav::console::user_message::error(
attr_line_t("invalid sample log message ") attr_line_t("invalid sample log message: ")
.append_quoted(elf_sample.s_line.pp_value)) .append(lnav::to_json(elf_sample.s_line.pp_value)))
.with_reason(attr_line_t() .with_reason(attr_line_t()
.append_quoted(lnav::roles::symbol( .append_quoted(lnav::roles::symbol(
level_names[level])) level_names[level]))
@ -1991,14 +2000,35 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
level_names[elf_sample.s_level]))) level_names[elf_sample.s_level])))
.with_snippet(elf_sample.s_line.to_snippet())); .with_snippet(elf_sample.s_line.to_snippet()));
} }
{
pcre_context_static<128> pc_full;
pcre_input pi_full(elf_sample.s_line.pp_value);
if (!pat.p_pcre->match(pc_full, pi_full)
|| pc_full.all()->length()
!= elf_sample.s_line.pp_value.length())
{
errors.emplace_back(
lnav::console::user_message::error(
attr_line_t("invalid pattern: ")
.append_quoted(lnav::roles::symbol(pat.p_name)))
.with_reason("pattern does not match entire "
"multiline message")
.with_snippet(elf_sample.s_line.to_snippet())
.with_help(attr_line_t("using ")
.append_quoted(".*")
.append(" when capturing the body "
"will match new-lines")));
}
}
} }
if (!found && !this->elf_pattern_order.empty()) { if (!found && !this->elf_pattern_order.empty()) {
attr_line_t notes( std::vector<std::pair<ssize_t, std::string>> partial_indexes;
"the following shows how each pattern matched this sample:\n"); attr_line_t notes;
size_t max_name_width = 0; size_t max_name_width = 0;
notes.append(" ").append_quoted(elf_sample.s_line.pp_value);
for (const auto& pat_iter : this->elf_pattern_order) { for (const auto& pat_iter : this->elf_pattern_order) {
pattern& pat = *pat_iter; pattern& pat = *pat_iter;
@ -2006,21 +2036,37 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
continue; continue;
} }
size_t partial_len = pat.p_pcre->match_partial(pi); partial_indexes.emplace_back(pat.p_pcre->match_partial(pi),
pat.p_name);
notes.append("\n ")
.append(partial_len, ' ')
.append(lnav::roles::comment("^ "))
.append(lnav::roles::symbol(pat.p_name))
.append(lnav::roles::comment(" matched up to here"));
max_name_width = std::max(max_name_width, pat.p_name.size()); max_name_width = std::max(max_name_width, pat.p_name.size());
} }
for (const auto& line_frag : sample_lines) {
auto src_line = line_frag.to_string();
if (!endswith(src_line, "\n")) {
src_line.append("\n");
}
notes.append(" ").append(src_line);
for (auto& part_pair : partial_indexes) {
if (part_pair.first >= 0
&& part_pair.first < line_frag.length()) {
notes.append(" ")
.append(part_pair.first, ' ')
.append("^ "_comment)
.append(lnav::roles::symbol(part_pair.second))
.append(" matched up to here"_comment)
.append("\n");
}
part_pair.first -= line_frag.length();
}
}
notes.add_header(
"the following shows how each pattern matched this sample:\n");
attr_line_t help; attr_line_t regex_note;
for (const auto& pat_iter : this->elf_pattern_order) { for (const auto& pat_iter : this->elf_pattern_order) {
if (!pat_iter->p_pcre) { if (!pat_iter->p_pcre) {
help.append( regex_note
.append(
lnav::roles::symbol(fmt::format(FMT_STRING("{:{}}"), lnav::roles::symbol(fmt::format(FMT_STRING("{:{}}"),
pat_iter->p_name, pat_iter->p_name,
max_name_width))) max_name_width)))
@ -2028,22 +2074,22 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
continue; continue;
} }
help regex_note
.append(lnav::roles::symbol(fmt::format( .append(lnav::roles::symbol(fmt::format(
FMT_STRING("{:{}}"), pat_iter->p_name, max_name_width))) FMT_STRING("{:{}}"), pat_iter->p_name, max_name_width)))
.append(" = ") .append(" = ")
.append(pat_iter->p_pcre->get_pattern()) .append_quoted(pat_iter->p_pcre->get_pattern())
.append("\n"); .append("\n");
} }
errors.emplace_back( errors.emplace_back(
lnav::console::user_message::error( lnav::console::user_message::error(
attr_line_t("invalid sample log message ") attr_line_t("invalid sample log message: ")
.append_quoted(elf_sample.s_line.pp_value)) .append(lnav::to_json(elf_sample.s_line.pp_value)))
.with_reason("sample does not match any patterns") .with_reason("sample does not match any patterns")
.with_snippet(elf_sample.s_line.to_snippet()) .with_snippet(elf_sample.s_line.to_snippet())
.with_note(notes) .with_note(notes.rtrim())
.with_help(help)); .with_note(regex_note));
} }
} }
@ -2097,7 +2143,7 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
} }
switch (jfe.jfe_type) { switch (jfe.jfe_type) {
case JLF_VARIABLE: { case json_log_field::VARIABLE: {
auto vd_iter auto vd_iter
= this->elf_value_defs.find(jfe.jfe_value.pp_value); = this->elf_value_defs.find(jfe.jfe_value.pp_value);
if (jfe.jfe_value.pp_value == ts) { if (jfe.jfe_value.pp_value == ts) {
@ -2124,7 +2170,7 @@ external_log_format::build(std::vector<lnav::console::user_message>& errors)
} }
break; break;
} }
case JLF_CONSTANT: case json_log_field::CONSTANT:
this->jlf_line_format_init_count this->jlf_line_format_init_count
+= std::count(jfe.jfe_default_value.begin(), += std::count(jfe.jfe_default_value.begin(),
jfe.jfe_default_value.end(), jfe.jfe_default_value.end(),
@ -2404,8 +2450,8 @@ external_log_format::specialized(int fmt_lock)
} }
if (this->elf_type == elf_type_t::ELF_TYPE_JSON) { if (this->elf_type == elf_type_t::ELF_TYPE_JSON) {
this->jlf_parse_context = std::make_shared<yajlpp_parse_context>( this->jlf_parse_context
this->elf_name.to_string()); = std::make_shared<yajlpp_parse_context>(this->elf_name);
this->jlf_yajl_handle.reset( this->jlf_yajl_handle.reset(
yajl_alloc(&this->jlf_parse_context->ypc_callbacks, yajl_alloc(&this->jlf_parse_context->ypc_callbacks,
nullptr, nullptr,
@ -2444,6 +2490,103 @@ external_log_format::match_mime_type(const file_format_t ff) const
return this->elf_mime_types.count(ff) == 1; return this->elf_mime_types.count(ff) == 1;
} }
long
external_log_format::value_line_count(const intern_string_t ist,
bool top_level,
const unsigned char* str,
ssize_t len) const
{
const auto iter = this->elf_value_defs.find(ist);
long line_count
= (str != nullptr) ? std::count(&str[0], &str[len], '\n') + 1 : 1;
if (iter == this->elf_value_defs.end()) {
return (this->jlf_hide_extra || !top_level) ? 0 : line_count;
}
if (iter->second->vd_meta.lvm_hidden) {
return 0;
}
if (std::find_if(this->jlf_line_format.begin(),
this->jlf_line_format.end(),
json_field_cmp(json_log_field::VARIABLE, ist))
!= this->jlf_line_format.end())
{
return line_count - 1;
}
return line_count;
}
log_level_t
external_log_format::convert_level(
const pcre_input& pi, const pcre_context::capture_t* level_cap) const
{
log_level_t retval = LEVEL_INFO;
if (level_cap != nullptr && level_cap->is_valid()) {
pcre_context_static<128> pc_level;
pcre_input pi_level(
pi.get_substr_start(level_cap), 0, level_cap->length());
if (this->elf_level_patterns.empty()) {
retval = string2level(pi_level.get_string(), level_cap->length());
} else {
for (const auto& elf_level_pattern : this->elf_level_patterns) {
if (elf_level_pattern.second.lp_pcre->match(pc_level, pi_level))
{
retval = elf_level_pattern.first;
break;
}
}
}
}
return retval;
}
logline_value_meta
external_log_format::get_value_meta(intern_string_t field_name,
value_kind_t kind)
{
auto iter = this->elf_value_defs.find(field_name);
if (iter == this->elf_value_defs.end()) {
auto retval = logline_value_meta(field_name, kind, -1, this);
retval.lvm_hidden = this->jlf_hide_extra;
return retval;
}
auto lvm = iter->second->vd_meta;
lvm.lvm_kind = kind;
return lvm;
}
void
external_log_format::json_append(
const external_log_format::json_format_element& jfe,
const char* value,
ssize_t len)
{
if (len == -1) {
len = strlen(value);
}
if (jfe.jfe_align == json_format_element::align_t::RIGHT) {
if (len < jfe.jfe_min_width) {
this->json_append_to_cache(jfe.jfe_min_width - len);
}
}
this->json_append_to_cache(value, len);
if (jfe.jfe_align == json_format_element::align_t::LEFT) {
if (len < jfe.jfe_min_width) {
this->json_append_to_cache(jfe.jfe_min_width - len);
}
}
}
int int
log_format::pattern_index_for_line(uint64_t line_number) const log_format::pattern_index_for_line(uint64_t line_number) const
{ {
@ -2468,11 +2611,58 @@ log_format::get_pattern_name(uint64_t line_number) const
return fmt::format(FMT_STRING("builtin ({})"), pat_index); return fmt::format(FMT_STRING("builtin ({})"), pat_index);
} }
std::shared_ptr<log_format>
log_format::find_root_format(const char* name)
{
auto& fmts = get_root_formats();
for (auto& lf : fmts) {
if (lf->get_name() == name) {
return lf;
}
}
return nullptr;
}
log_format::pattern_for_lines::pattern_for_lines(uint32_t pfl_line, log_format::pattern_for_lines::pattern_for_lines(uint32_t pfl_line,
uint32_t pfl_pat_index) uint32_t pfl_pat_index)
: pfl_line(pfl_line), pfl_pat_index(pfl_pat_index) : pfl_line(pfl_line), pfl_pat_index(pfl_pat_index)
{ {
} }
void
logline_value_stats::merge(const logline_value_stats& other)
{
if (other.lvs_count == 0) {
return;
}
require(other.lvs_min_value <= other.lvs_max_value);
if (other.lvs_min_value < this->lvs_min_value) {
this->lvs_min_value = other.lvs_min_value;
}
if (other.lvs_max_value > this->lvs_max_value) {
this->lvs_max_value = other.lvs_max_value;
}
this->lvs_count += other.lvs_count;
this->lvs_total += other.lvs_total;
ensure(this->lvs_count >= 0);
ensure(this->lvs_min_value <= this->lvs_max_value);
}
void
logline_value_stats::add_value(double value)
{
if (value < this->lvs_min_value) {
this->lvs_min_value = value;
}
if (value > this->lvs_max_value) {
this->lvs_max_value = value;
}
this->lvs_count += 1;
this->lvs_total += value;
}
/* XXX */ /* XXX */
#include "log_format_impls.cc" #include "log_format_impls.cc"

@ -51,7 +51,6 @@
#include "base/date_time_scanner.hh" #include "base/date_time_scanner.hh"
#include "base/intern_string.hh" #include "base/intern_string.hh"
#include "base/lnav_log.hh" #include "base/lnav_log.hh"
#include "byte_array.hh"
#include "file_format.hh" #include "file_format.hh"
#include "highlighter.hh" #include "highlighter.hh"
#include "line_buffer.hh" #include "line_buffer.hh"
@ -73,8 +72,6 @@ enum class scale_op_t {
}; };
struct scaling_factor { struct scaling_factor {
scaling_factor() : sf_op(scale_op_t::SO_IDENTITY), sf_value(1){};
template<typename T> template<typename T>
void scale(T& val) const void scale(T& val) const
{ {
@ -90,8 +87,8 @@ struct scaling_factor {
} }
} }
scale_op_t sf_op; scale_op_t sf_op{scale_op_t::SO_IDENTITY};
double sf_value; double sf_value{1};
}; };
enum class value_kind_t : int { enum class value_kind_t : int {
@ -227,10 +224,7 @@ public:
}; };
struct logline_value_stats { struct logline_value_stats {
logline_value_stats() logline_value_stats() { this->clear(); }
{
this->clear();
};
void clear() void clear()
{ {
@ -238,40 +232,11 @@ struct logline_value_stats {
this->lvs_total = 0; this->lvs_total = 0;
this->lvs_min_value = std::numeric_limits<double>::max(); this->lvs_min_value = std::numeric_limits<double>::max();
this->lvs_max_value = -std::numeric_limits<double>::max(); this->lvs_max_value = -std::numeric_limits<double>::max();
}; }
void merge(const logline_value_stats& other)
{
if (other.lvs_count == 0) {
return;
}
require(other.lvs_min_value <= other.lvs_max_value);
if (other.lvs_min_value < this->lvs_min_value) { void merge(const logline_value_stats& other);
this->lvs_min_value = other.lvs_min_value;
}
if (other.lvs_max_value > this->lvs_max_value) {
this->lvs_max_value = other.lvs_max_value;
}
this->lvs_count += other.lvs_count;
this->lvs_total += other.lvs_total;
ensure(this->lvs_count >= 0); void add_value(double value);
ensure(this->lvs_min_value <= this->lvs_max_value);
};
void add_value(double value)
{
if (value < this->lvs_min_value) {
this->lvs_min_value = value;
}
if (value > this->lvs_max_value) {
this->lvs_max_value = value;
}
this->lvs_count += 1;
this->lvs_total += value;
};
int64_t lvs_count; int64_t lvs_count;
double lvs_total; double lvs_total;
@ -282,9 +247,9 @@ struct logline_value_stats {
struct logline_value_cmp { struct logline_value_cmp {
explicit logline_value_cmp(const intern_string_t* name = nullptr, explicit logline_value_cmp(const intern_string_t* name = nullptr,
int col = -1) int col = -1)
: lvc_name(name), lvc_column(col){ : lvc_name(name), lvc_column(col)
{
}; }
bool operator()(const logline_value& lv) const bool operator()(const logline_value& lv) const
{ {
@ -298,7 +263,7 @@ struct logline_value_cmp {
} }
return retval; return retval;
}; }
const intern_string_t* lvc_name; const intern_string_t* lvc_name;
int lvc_column; int lvc_column;
@ -316,29 +281,18 @@ public:
*/ */
static std::vector<std::shared_ptr<log_format>>& get_root_formats(); static std::vector<std::shared_ptr<log_format>>& get_root_formats();
static std::shared_ptr<log_format> find_root_format(const char* name) static std::shared_ptr<log_format> find_root_format(const char* name);
{
auto& fmts = get_root_formats();
for (auto& lf : fmts) {
if (lf->get_name() == name) {
return lf;
}
}
return nullptr;
}
struct action_def { struct action_def {
std::string ad_name; std::string ad_name;
std::string ad_label; std::string ad_label;
std::vector<std::string> ad_cmdline; std::vector<std::string> ad_cmdline;
bool ad_capture_output; bool ad_capture_output{false};
action_def() : ad_capture_output(false){};
bool operator<(const action_def& rhs) const bool operator<(const action_def& rhs) const
{ {
return this->ad_name < rhs.ad_name; return this->ad_name < rhs.ad_name;
}; }
}; };
virtual ~log_format() = default; virtual ~log_format() = default;
@ -347,7 +301,7 @@ public:
{ {
this->lf_pattern_locks.clear(); this->lf_pattern_locks.clear();
this->lf_date_time.clear(); this->lf_date_time.clear();
}; }
/** /**
* Get the name of this log format. * Get the name of this log format.
@ -356,10 +310,7 @@ public:
*/ */
virtual const intern_string_t get_name() const = 0; virtual const intern_string_t get_name() const = 0;
virtual bool match_name(const std::string& filename) virtual bool match_name(const std::string& filename) { return true; }
{
return true;
};
virtual bool match_mime_type(const file_format_t ff) const virtual bool match_mime_type(const file_format_t ff) const
{ {
@ -367,7 +318,7 @@ public:
return true; return true;
} }
return false; return false;
}; }
enum scan_result_t { enum scan_result_t {
SCAN_MATCH, SCAN_MATCH,
@ -393,7 +344,7 @@ public:
virtual bool scan_for_partial(shared_buffer_ref& sbr, size_t& len_out) const virtual bool scan_for_partial(shared_buffer_ref& sbr, size_t& len_out) const
{ {
return false; return false;
}; }
/** /**
* Remove redundant data from the log line string. * Remove redundant data from the log line string.
@ -409,7 +360,9 @@ public:
shared_buffer_ref& sbr, shared_buffer_ref& sbr,
string_attrs_t& sa, string_attrs_t& sa,
std::vector<logline_value>& values, std::vector<logline_value>& values,
bool annotate_module = true) const {}; bool annotate_module = true) const
{
}
virtual void rewrite(exec_context& ec, virtual void rewrite(exec_context& ec,
shared_buffer_ref& line, shared_buffer_ref& line,
@ -417,20 +370,20 @@ public:
std::string& value_out) std::string& value_out)
{ {
value_out.assign(line.get_data(), line.length()); value_out.assign(line.get_data(), line.length());
}; }
virtual const logline_value_stats* stats_for_value( virtual const logline_value_stats* stats_for_value(
const intern_string_t& name) const const intern_string_t& name) const
{ {
return nullptr; return nullptr;
}; }
virtual std::shared_ptr<log_format> specialized(int fmt_lock = -1) = 0; virtual std::shared_ptr<log_format> specialized(int fmt_lock = -1) = 0;
virtual std::shared_ptr<log_vtab_impl> get_vtab_impl() const virtual std::shared_ptr<log_vtab_impl> get_vtab_impl() const
{ {
return nullptr; return nullptr;
}; }
virtual void get_subline(const logline& ll, virtual void get_subline(const logline& ll,
shared_buffer_ref& sbr, shared_buffer_ref& sbr,
@ -440,7 +393,7 @@ public:
const logline_value& lv) const const logline_value& lv) const
{ {
return nullptr; return nullptr;
}; }
virtual std::set<std::string> get_source_path() const virtual std::set<std::string> get_source_path() const
{ {
@ -449,12 +402,12 @@ public:
retval.insert("default"); retval.insert("default");
return retval; return retval;
}; }
virtual bool hide_field(const intern_string_t field_name, bool val) virtual bool hide_field(const intern_string_t field_name, bool val)
{ {
return false; return false;
}; }
const char* const* get_timestamp_formats() const const char* const* get_timestamp_formats() const
{ {
@ -463,7 +416,7 @@ public:
} }
return &this->lf_timestamp_format[0]; return &this->lf_timestamp_format[0];
}; }
void check_for_new_year(std::vector<logline>& dst, void check_for_new_year(std::vector<logline>& dst,
exttm log_tv, exttm log_tv,
@ -474,7 +427,7 @@ public:
virtual std::string get_pattern_regex(uint64_t line_number) const virtual std::string get_pattern_regex(uint64_t line_number) const
{ {
return ""; return "";
}; }
struct pattern_for_lines { struct pattern_for_lines {
pattern_for_lines(uint32_t pfl_line, uint32_t pfl_pat_index); pattern_for_lines(uint32_t pfl_line, uint32_t pfl_pat_index);
@ -494,6 +447,18 @@ public:
int pattern_index_for_line(uint64_t line_number) const; int pattern_index_for_line(uint64_t line_number) const;
bool operator<(const log_format& rhs) const
{
return this->get_name() < rhs.get_name();
}
static bool name_lt(const std::shared_ptr<const log_format>& lhs,
const std::shared_ptr<const log_format>& rhs)
{
return intern_string_t::case_lt(lhs->get_name(), rhs->get_name());
}
std::string lf_description;
uint8_t lf_mod_index{0}; uint8_t lf_mod_index{0};
bool lf_multiline{true}; bool lf_multiline{true};
date_time_scanner lf_date_time; date_time_scanner lf_date_time;
@ -515,9 +480,9 @@ protected:
pcre_format(const char* regex) : name(regex), pcre(regex) pcre_format(const char* regex) : name(regex), pcre(regex)
{ {
this->pf_timestamp_index = this->pcre.name_index("timestamp"); this->pf_timestamp_index = this->pcre.name_index("timestamp");
}; }
pcre_format() : name(nullptr), pcre(""){}; pcre_format() : name(nullptr), pcre("") {}
const char* name; const char* name;
pcrepp pcre; pcrepp pcre;

@ -42,10 +42,9 @@ class module_format;
class external_log_format : public log_format { class external_log_format : public log_format {
public: public:
struct sample { struct sample {
sample() : s_level(LEVEL_UNKNOWN) {}
positioned_property<std::string> s_line; positioned_property<std::string> s_line;
log_level_t s_level; std::string s_description;
log_level_t s_level{LEVEL_UNKNOWN};
}; };
struct value_def { struct value_def {
@ -53,7 +52,17 @@ public:
value_kind_t kind, value_kind_t kind,
int col, int col,
log_format* format) log_format* format)
: vd_meta(name, kind, col, format){}; : vd_meta(name, kind, col, format)
{
}
void set_rewrite_src_name()
{
this->vd_rewrite_src_name = intern_string::lookup(
fmt::format(FMT_STRING("{}:{}"),
this->vd_meta.lvm_format.value()->get_name(),
this->vd_meta.lvm_name));
}
logline_value_meta vd_meta; logline_value_meta vd_meta;
std::string vd_collate; std::string vd_collate;
@ -65,6 +74,7 @@ public:
std::vector<std::string> vd_action_list; std::vector<std::string> vd_action_list;
std::string vd_rewriter; std::string vd_rewriter;
std::string vd_description; std::string vd_description;
intern_string_t vd_rewrite_src_name;
}; };
struct indexed_value_def { struct indexed_value_def {
@ -89,7 +99,7 @@ public:
struct pattern { struct pattern {
std::string p_name; std::string p_name;
std::string p_config_path; std::string p_config_path;
std::shared_ptr<pcrepp> p_pcre; std::shared_ptr<pcrepp_with_options<PCRE_DOTALL>> p_pcre;
std::vector<indexed_value_def> p_value_by_index; std::vector<indexed_value_def> p_value_by_index;
std::vector<int> p_numeric_value_indexes; std::vector<int> p_numeric_value_indexes;
int p_timestamp_field_index{-1}; int p_timestamp_field_index{-1};
@ -204,16 +214,16 @@ public:
} }
return retval; return retval;
}; }
std::set<std::string> get_source_path() const std::set<std::string> get_source_path() const
{ {
return this->elf_source_path; return this->elf_source_path;
}; }
enum json_log_field { enum class json_log_field {
JLF_CONSTANT, CONSTANT,
JLF_VARIABLE VARIABLE
}; };
struct json_format_element { struct json_format_element {
@ -235,81 +245,52 @@ public:
CAPITALIZE, CAPITALIZE,
}; };
json_format_element() json_log_field jfe_type{json_log_field::CONSTANT};
: jfe_type(JLF_CONSTANT), jfe_default_value("-"), jfe_min_width(0),
jfe_max_width(LLONG_MAX), jfe_align(align_t::LEFT),
jfe_overflow(overflow_t::ABBREV),
jfe_text_transform(transform_t::NONE){};
json_log_field jfe_type;
positioned_property<intern_string_t> jfe_value; positioned_property<intern_string_t> jfe_value;
std::string jfe_default_value; std::string jfe_default_value{"-"};
long long jfe_min_width; long long jfe_min_width{0};
long long jfe_max_width; long long jfe_max_width{LLONG_MAX};
align_t jfe_align; align_t jfe_align{align_t::LEFT};
overflow_t jfe_overflow; overflow_t jfe_overflow{overflow_t::ABBREV};
transform_t jfe_text_transform; transform_t jfe_text_transform{transform_t::NONE};
std::string jfe_ts_format; std::string jfe_ts_format;
}; };
struct json_field_cmp { struct json_field_cmp {
json_field_cmp(json_log_field type, const intern_string_t name) json_field_cmp(json_log_field type, const intern_string_t name)
: jfc_type(type), jfc_field_name(name){}; : jfc_type(type), jfc_field_name(name)
{
}
bool operator()(const json_format_element& jfe) const bool operator()(const json_format_element& jfe) const
{ {
return (this->jfc_type == jfe.jfe_type return (this->jfc_type == jfe.jfe_type
&& this->jfc_field_name == jfe.jfe_value.pp_value); && this->jfc_field_name == jfe.jfe_value.pp_value);
}; }
json_log_field jfc_type; json_log_field jfc_type;
const intern_string_t jfc_field_name; const intern_string_t jfc_field_name;
}; };
struct highlighter_def { struct highlighter_def {
highlighter_def() : hd_underline(false), hd_blink(false) {}
std::shared_ptr<pcrepp> hd_pattern; std::shared_ptr<pcrepp> hd_pattern;
positioned_property<std::string> hd_color; positioned_property<std::string> hd_color;
positioned_property<std::string> hd_background_color; positioned_property<std::string> hd_background_color;
bool hd_underline; bool hd_underline{false};
bool hd_blink; bool hd_blink{false};
}; };
long value_line_count(const intern_string_t ist, long value_line_count(const intern_string_t ist,
bool top_level, bool top_level,
const unsigned char* str = nullptr, const unsigned char* str = nullptr,
ssize_t len = -1) const ssize_t len = -1) const;
{
const auto iter = this->elf_value_defs.find(ist);
long line_count
= (str != NULL) ? std::count(&str[0], &str[len], '\n') + 1 : 1;
if (iter == this->elf_value_defs.end()) {
return (this->jlf_hide_extra || !top_level) ? 0 : line_count;
}
if (iter->second->vd_meta.lvm_hidden) {
return 0;
}
if (std::find_if(this->jlf_line_format.begin(),
this->jlf_line_format.end(),
json_field_cmp(JLF_VARIABLE, ist))
!= this->jlf_line_format.end())
{
return line_count - 1;
}
return line_count;
};
bool has_value_def(const intern_string_t ist) const bool has_value_def(const intern_string_t ist) const
{ {
const auto iter = this->elf_value_defs.find(ist); const auto iter = this->elf_value_defs.find(ist);
return iter != this->elf_value_defs.end(); return iter != this->elf_value_defs.end();
}; }
std::string get_pattern_name(uint64_t line_number) const std::string get_pattern_name(uint64_t line_number) const
{ {
@ -330,31 +311,7 @@ public:
} }
log_level_t convert_level(const pcre_input& pi, log_level_t convert_level(const pcre_input& pi,
const pcre_context::capture_t* level_cap) const const pcre_context::capture_t* level_cap) const;
{
log_level_t retval = LEVEL_INFO;
if (level_cap != nullptr && level_cap->is_valid()) {
pcre_context_static<128> pc_level;
pcre_input pi_level(
pi.get_substr_start(level_cap), 0, level_cap->length());
if (this->elf_level_patterns.empty()) {
retval
= string2level(pi_level.get_string(), level_cap->length());
} else {
for (const auto& elf_level_pattern : this->elf_level_patterns) {
if (elf_level_pattern.second.lp_pcre->match(pc_level,
pi_level)) {
retval = elf_level_pattern.first;
break;
}
}
}
}
return retval;
}
using mod_map_t = std::map<intern_string_t, module_format>; using mod_map_t = std::map<intern_string_t, module_format>;
static mod_map_t MODULE_FORMATS; static mod_map_t MODULE_FORMATS;
@ -362,7 +319,8 @@ public:
GRAPH_ORDERED_FORMATS; GRAPH_ORDERED_FORMATS;
std::set<std::string> elf_source_path; std::set<std::string> elf_source_path;
std::unordered_map<std::string, int> elf_format_sources; std::vector<ghc::filesystem::path> elf_format_source_order;
std::map<intern_string_t, int> elf_format_sources;
std::list<intern_string_t> elf_collision; std::list<intern_string_t> elf_collision;
std::string elf_file_pattern; std::string elf_file_pattern;
std::set<file_format_t> elf_mime_types; std::set<file_format_t> elf_mime_types;
@ -410,52 +368,21 @@ public:
} }
this->jlf_cached_line.resize(old_size + len); this->jlf_cached_line.resize(old_size + len);
memcpy(&(this->jlf_cached_line[old_size]), value, len); memcpy(&(this->jlf_cached_line[old_size]), value, len);
}; }
void json_append_to_cache(ssize_t len) void json_append_to_cache(ssize_t len)
{ {
size_t old_size = this->jlf_cached_line.size(); size_t old_size = this->jlf_cached_line.size();
this->jlf_cached_line.resize(old_size + len); this->jlf_cached_line.resize(old_size + len);
memset(&this->jlf_cached_line[old_size], ' ', len); memset(&this->jlf_cached_line[old_size], ' ', len);
}; }
void json_append(const json_format_element& jfe, void json_append(const json_format_element& jfe,
const char* value, const char* value,
ssize_t len) ssize_t len);
{
if (len == -1) {
len = strlen(value);
}
if (jfe.jfe_align == json_format_element::align_t::RIGHT) {
if (len < jfe.jfe_min_width) {
this->json_append_to_cache(jfe.jfe_min_width - len);
}
}
this->json_append_to_cache(value, len);
if (jfe.jfe_align == json_format_element::align_t::LEFT) {
if (len < jfe.jfe_min_width) {
this->json_append_to_cache(jfe.jfe_min_width - len);
}
}
};
logline_value_meta get_value_meta(intern_string_t field_name, logline_value_meta get_value_meta(intern_string_t field_name,
value_kind_t kind) value_kind_t kind);
{
auto iter = this->elf_value_defs.find(field_name);
if (iter == this->elf_value_defs.end()) {
auto retval = logline_value_meta(field_name, kind, -1, this);
retval.lvm_hidden = this->jlf_hide_extra;
return retval;
}
auto lvm = iter->second->vd_meta;
lvm.lvm_kind = kind;
return lvm;
}
std::vector<lnav::console::snippet> get_snippets() const; std::vector<lnav::console::snippet> get_snippets() const;

@ -73,7 +73,7 @@ public:
ll_expr_mark(0) ll_expr_mark(0)
{ {
memset(this->ll_schema, 0, sizeof(this->ll_schema)); memset(this->ll_schema, 0, sizeof(this->ll_schema));
}; }
logline(file_off_t off, logline(file_off_t off,
const struct timeval& tv, const struct timeval& tv,
@ -85,69 +85,48 @@ public:
{ {
this->set_time(tv); this->set_time(tv);
memset(this->ll_schema, 0, sizeof(this->ll_schema)); memset(this->ll_schema, 0, sizeof(this->ll_schema));
}; }
/** @return The offset of the line in the file. */ /** @return The offset of the line in the file. */
file_off_t get_offset() const file_off_t get_offset() const { return this->ll_offset; }
{
return this->ll_offset;
};
uint16_t get_sub_offset() const uint16_t get_sub_offset() const { return this->ll_sub_offset; }
{
return this->ll_sub_offset;
};
void set_sub_offset(uint16_t suboff) void set_sub_offset(uint16_t suboff) { this->ll_sub_offset = suboff; }
{
this->ll_sub_offset = suboff;
};
/** @return The timestamp for the line. */ /** @return The timestamp for the line. */
time_t get_time() const time_t get_time() const { return this->ll_time; }
{
return this->ll_time;
};
void to_exttm(struct exttm& tm_out) const void to_exttm(struct exttm& tm_out) const
{ {
tm_out.et_tm = *gmtime(&this->ll_time); tm_out.et_tm = *gmtime(&this->ll_time);
tm_out.et_nsec = this->ll_millis * 1000 * 1000; tm_out.et_nsec = this->ll_millis * 1000 * 1000;
}; }
void set_time(time_t t) void set_time(time_t t) { this->ll_time = t; }
{
this->ll_time = t;
};
/** @return The millisecond timestamp for the line. */ /** @return The millisecond timestamp for the line. */
uint16_t get_millis() const uint16_t get_millis() const { return this->ll_millis; }
{
return this->ll_millis;
};
void set_millis(uint16_t m) void set_millis(uint16_t m) { this->ll_millis = m; }
{
this->ll_millis = m;
};
uint64_t get_time_in_millis() const uint64_t get_time_in_millis() const
{ {
return (this->ll_time * 1000ULL + (uint64_t) this->ll_millis); return (this->ll_time * 1000ULL + (uint64_t) this->ll_millis);
}; }
struct timeval get_timeval() const struct timeval get_timeval() const
{ {
struct timeval retval = {this->ll_time, this->ll_millis * 1000}; struct timeval retval = {this->ll_time, this->ll_millis * 1000};
return retval; return retval;
}; }
void set_time(const struct timeval& tv) void set_time(const struct timeval& tv)
{ {
this->ll_time = tv.tv_sec; this->ll_time = tv.tv_sec;
this->ll_millis = tv.tv_usec / 1000; this->ll_millis = tv.tv_usec / 1000;
}; }
void set_ignore(bool val) void set_ignore(bool val)
{ {
@ -156,7 +135,7 @@ public:
} else { } else {
this->ll_level &= ~LEVEL_IGNORE; this->ll_level &= ~LEVEL_IGNORE;
} }
}; }
bool is_ignored() const bool is_ignored() const
{ {
@ -170,22 +149,13 @@ public:
} else { } else {
this->ll_level &= ~LEVEL_MARK; this->ll_level &= ~LEVEL_MARK;
} }
}; }
bool is_marked() const bool is_marked() const { return this->ll_level & LEVEL_MARK; }
{
return this->ll_level & LEVEL_MARK;
};
void set_expr_mark(bool val) void set_expr_mark(bool val) { this->ll_expr_mark = val; }
{
this->ll_expr_mark = val;
};
bool is_expr_marked() const bool is_expr_marked() const { return this->ll_expr_mark; }
{
return this->ll_expr_mark;
};
void set_time_skew(bool val) void set_time_skew(bool val)
{ {
@ -194,22 +164,13 @@ public:
} else { } else {
this->ll_level &= ~LEVEL_TIME_SKEW; this->ll_level &= ~LEVEL_TIME_SKEW;
} }
}; }
bool is_time_skewed() const bool is_time_skewed() const { return this->ll_level & LEVEL_TIME_SKEW; }
{
return this->ll_level & LEVEL_TIME_SKEW;
};
void set_valid_utf(bool v) void set_valid_utf(bool v) { this->ll_valid_utf = v; }
{
this->ll_valid_utf = v;
}
bool is_valid_utf() const bool is_valid_utf() const { return this->ll_valid_utf; }
{
return this->ll_valid_utf;
}
/** @param l The logging level. */ /** @param l The logging level. */
void set_level(log_level_t l) void set_level(log_level_t l)
@ -221,42 +182,30 @@ public:
log_level_t get_level_and_flags() const log_level_t get_level_and_flags() const
{ {
return (log_level_t) this->ll_level; return (log_level_t) this->ll_level;
}; }
log_level_t get_msg_level() const log_level_t get_msg_level() const
{ {
return (log_level_t) (this->ll_level & ~LEVEL__FLAGS); return (log_level_t) (this->ll_level & ~LEVEL__FLAGS);
}; }
const char* get_level_name() const const char* get_level_name() const
{ {
return level_names[this->ll_level & ~LEVEL__FLAGS]; return level_names[this->ll_level & ~LEVEL__FLAGS];
}; }
bool is_message() const bool is_message() const
{ {
return (this->ll_level & (LEVEL_IGNORE | LEVEL_CONTINUED)) == 0; return (this->ll_level & (LEVEL_IGNORE | LEVEL_CONTINUED)) == 0;
} }
bool is_continued() const bool is_continued() const { return this->ll_level & LEVEL_CONTINUED; }
{
return this->ll_level & LEVEL_CONTINUED;
};
uint8_t get_module_id() const uint8_t get_module_id() const { return this->ll_module_id; }
{
return this->ll_module_id;
};
void set_opid(uint8_t opid) void set_opid(uint8_t opid) { this->ll_opid = opid; }
{
this->ll_opid = opid;
};
uint8_t get_opid() const uint8_t get_opid() const { return this->ll_opid; }
{
return this->ll_opid;
};
/** /**
* @return True if there is a schema value set for this log line. * @return True if there is a schema value set for this log line.
@ -264,7 +213,7 @@ public:
bool has_schema() const bool has_schema() const
{ {
return (this->ll_schema[0] != 0 || this->ll_schema[1] != 0); return (this->ll_schema[0] != 0 || this->ll_schema[1] != 0);
}; }
/** /**
* Set the "schema" for this log line. The schema ID is used to match log * Set the "schema" for this log line. The schema ID is used to match log
@ -276,12 +225,9 @@ public:
void set_schema(const byte_array<2, uint64_t>& ba) void set_schema(const byte_array<2, uint64_t>& ba)
{ {
memcpy(this->ll_schema, ba.in(), sizeof(this->ll_schema)); memcpy(this->ll_schema, ba.in(), sizeof(this->ll_schema));
}; }
char get_schema() const char get_schema() const { return this->ll_schema[0]; }
{
return this->ll_schema[0];
};
/** /**
* Perform a partial match of the given schema against this log line. * Perform a partial match of the given schema against this log line.
@ -309,26 +255,23 @@ public:
|| (this->ll_time == rhs.ll_time && this->ll_millis == rhs.ll_millis || (this->ll_time == rhs.ll_time && this->ll_millis == rhs.ll_millis
&& this->ll_offset == rhs.ll_offset && this->ll_offset == rhs.ll_offset
&& this->ll_sub_offset < rhs.ll_sub_offset); && this->ll_sub_offset < rhs.ll_sub_offset);
}; }
bool operator<(const time_t& rhs) const bool operator<(const time_t& rhs) const { return this->ll_time < rhs; }
{
return this->ll_time < rhs;
};
bool operator<(const struct timeval& rhs) const bool operator<(const struct timeval& rhs) const
{ {
return ((this->ll_time < rhs.tv_sec) return ((this->ll_time < rhs.tv_sec)
|| ((this->ll_time == rhs.tv_sec) || ((this->ll_time == rhs.tv_sec)
&& (this->ll_millis < (rhs.tv_usec / 1000)))); && (this->ll_millis < (rhs.tv_usec / 1000))));
}; }
bool operator<=(const struct timeval& rhs) const bool operator<=(const struct timeval& rhs) const
{ {
return ((this->ll_time < rhs.tv_sec) return ((this->ll_time < rhs.tv_sec)
|| ((this->ll_time == rhs.tv_sec) || ((this->ll_time == rhs.tv_sec)
&& (this->ll_millis <= (rhs.tv_usec / 1000)))); && (this->ll_millis <= (rhs.tv_usec / 1000))));
}; }
private: private:
file_off_t ll_offset; file_off_t ll_offset;

@ -93,11 +93,14 @@ ensure_format(const yajlpp_provider_context& ypc, userdata* ud)
formats->push_back(name); formats->push_back(name);
} }
auto srcs_iter if (!ud->ud_format_path.empty()) {
= retval->elf_format_sources.find(ud->ud_format_path.string()); auto i_src_path = intern_string::lookup(ud->ud_format_path.string());
if (srcs_iter == retval->elf_format_sources.end()) { auto srcs_iter = retval->elf_format_sources.find(i_src_path);
retval->elf_format_sources[ud->ud_format_path.string()] if (srcs_iter == retval->elf_format_sources.end()) {
= ud->ud_parse_context->get_line_number(); retval->elf_format_source_order.emplace_back(ud->ud_format_path);
retval->elf_format_sources[i_src_path]
= ud->ud_parse_context->get_line_number();
}
} }
if (ud->ud_format_path.empty()) { if (ud->ud_format_path.empty()) {
@ -170,7 +173,7 @@ line_format_provider(const yajlpp_provider_context& ypc,
{ {
auto& jfe = ensure_json_format_element(elf, ypc.ypc_index); auto& jfe = ensure_json_format_element(elf, ypc.ypc_index);
jfe.jfe_type = external_log_format::JLF_VARIABLE; jfe.jfe_type = external_log_format::json_log_field::VARIABLE;
return &jfe; return &jfe;
} }
@ -394,7 +397,7 @@ read_json_constant(yajlpp_parse_context* ypc,
ypc->ypc_array_index.back() += 1; ypc->ypc_array_index.back() += 1;
auto& jfe = ensure_json_format_element(elf, ypc->ypc_array_index.back()); auto& jfe = ensure_json_format_element(elf, ypc->ypc_array_index.back());
jfe.jfe_type = external_log_format::JLF_CONSTANT; jfe.jfe_type = external_log_format::json_log_field::CONSTANT;
jfe.jfe_default_value = val; jfe.jfe_default_value = val;
return 1; return 1;
@ -647,6 +650,10 @@ static const json_path_handler_base::enum_value_t LEVEL_ENUM[] = {
}; };
static struct json_path_container sample_handlers = { static struct json_path_container sample_handlers = {
yajlpp::property_handler("description")
.with_synopsis("<text>")
.with_description("A description of this sample.")
.for_field(&external_log_format::sample::s_description),
yajlpp::property_handler("line") yajlpp::property_handler("line")
.with_synopsis("<log-line>") .with_synopsis("<log-line>")
.with_description( .with_description(
@ -800,7 +807,8 @@ struct json_path_container format_handlers = {
json_path_handler("title", read_format_field) json_path_handler("title", read_format_field)
.with_description("The human-readable name for this log format"), .with_description("The human-readable name for this log format"),
json_path_handler("description", read_format_field) json_path_handler("description", read_format_field)
.with_description("A longer description of this log format"), .with_description("A longer description of this log format")
.for_field(&external_log_format::lf_description),
json_path_handler("timestamp-format#", read_format_field) json_path_handler("timestamp-format#", read_format_field)
.with_description("An array of strptime(3)-like timestamp formats"), .with_description("An array of strptime(3)-like timestamp formats"),
json_path_handler("module-field", read_format_field) json_path_handler("module-field", read_format_field)
@ -983,7 +991,8 @@ load_format_file(const ghc::filesystem::path& filename,
auto_fd fd; auto_fd fd;
log_info("loading formats from file: %s", filename.c_str()); log_info("loading formats from file: %s", filename.c_str());
yajlpp_parse_context ypc(filename, &root_format_handler); yajlpp_parse_context ypc(intern_string::lookup(filename.string()),
&root_format_handler);
ud.ud_parse_context = &ypc; ud.ud_parse_context = &ypc;
ud.ud_format_path = filename; ud.ud_format_path = filename;
ud.ud_format_names = &retval; ud.ud_format_names = &retval;
@ -1082,7 +1091,8 @@ load_formats(const std::vector<ghc::filesystem::path>& extra_paths,
log_debug("Loading default formats"); log_debug("Loading default formats");
for (const auto& bsf : lnav_format_json) { for (const auto& bsf : lnav_format_json) {
yajlpp_parse_context ypc_builtin(bsf.get_name(), &root_format_handler); yajlpp_parse_context ypc_builtin(intern_string::lookup(bsf.get_name()),
&root_format_handler);
handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin); handle = yajl_alloc(&ypc_builtin.ypc_callbacks, nullptr, &ypc_builtin);
ud.ud_parse_context = &ypc_builtin; ud.ud_parse_context = &ypc_builtin;
ud.ud_format_names = &retval; ud.ud_format_names = &retval;
@ -1140,13 +1150,7 @@ load_formats(const std::vector<ghc::filesystem::path>& extra_paths,
} }
} }
if (errors.empty()) { alpha_ordered_formats.push_back(elf);
alpha_ordered_formats.push_back(elf);
}
}
if (!errors.empty()) {
return;
} }
auto& graph_ordered_formats = external_log_format::GRAPH_ORDERED_FORMATS; auto& graph_ordered_formats = external_log_format::GRAPH_ORDERED_FORMATS;

@ -72,7 +72,7 @@ public:
bar_role_out = role_t::VCR_SCROLLBAR_WARNING; bar_role_out = role_t::VCR_SCROLLBAR_WARNING;
} }
} }
}; }
}; };
#endif #endif

@ -34,37 +34,7 @@
#include <sys/types.h> #include <sys/types.h>
/** #include "base/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)
};
extern const char* level_names[LEVEL__MAX + 1]; extern const char* level_names[LEVEL__MAX + 1];

@ -168,6 +168,41 @@ log_vtab_impl::logline_value_to_sqlite_type(value_kind_t kind)
return std::make_pair(type, subtype); return std::make_pair(type, subtype);
} }
void
log_vtab_impl::get_foreign_keys(std::vector<std::string>& keys_inout) const
{
keys_inout.emplace_back("log_line");
keys_inout.emplace_back("min(log_line)");
keys_inout.emplace_back("log_mark");
keys_inout.emplace_back("log_time_msecs");
}
void
log_vtab_impl::extract(std::shared_ptr<logfile> lf,
uint64_t line_number,
shared_buffer_ref& line,
std::vector<logline_value>& values)
{
auto format = lf->get_format();
this->vi_attrs.clear();
format->annotate(line_number, line, this->vi_attrs, values, false);
}
bool
log_vtab_impl::is_valid(log_cursor& lc, logfile_sub_source& lss)
{
content_line_t cl(lss.at(lc.lc_curr_line));
std::shared_ptr<logfile> lf = lss.find(cl);
auto lf_iter = lf->begin() + cl;
if (!lf_iter->is_message()) {
return false;
}
return true;
}
struct vtab { struct vtab {
sqlite3_vtab base; sqlite3_vtab base;
sqlite3* db; sqlite3* db;
@ -984,14 +1019,12 @@ vt_update(sqlite3_vtab* tab,
if (log_tags) { if (log_tags) {
std::vector<lnav::console::user_message> errors; std::vector<lnav::console::user_message> errors;
yajlpp_parse_context ypc( yajlpp_parse_context ypc(vt->vi->get_tags_name(), &tags_handler);
fmt::format(FMT_STRING("{}.log_tags"), vt->vi->get_name()),
&tags_handler);
auto_mem<yajl_handle_t> handle(yajl_free); auto_mem<yajl_handle_t> handle(yajl_free);
handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc); handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
ypc.ypc_userdata = &errors; ypc.ypc_userdata = &errors;
ypc.ypc_line_number = log_vtab_data.lvd_line_number; ypc.ypc_line_number = log_vtab_data.lvd_location.sl_line_number;
ypc.with_handle(handle) ypc.with_handle(handle)
.with_error_reporter([](const yajlpp_parse_context& ypc, .with_error_reporter([](const yajlpp_parse_context& ypc,
auto msg) { auto msg) {
@ -1002,19 +1035,16 @@ vt_update(sqlite3_vtab* tab,
.with_obj(tmp_bm); .with_obj(tmp_bm);
ypc.parse_doc(string_fragment{log_tags}); ypc.parse_doc(string_fragment{log_tags});
if (!errors.empty()) { if (!errors.empty()) {
auto top_error auto top_error = lnav::console::user_message::error(
= lnav::console::user_message::error( attr_line_t("invalid value for ")
attr_line_t("invalid value for ") .append_quoted("log_tags"_symbol)
.append_quoted("log_tags"_symbol) .append(" column of table ")
.append(" column of table ") .append_quoted(lnav::roles::symbol(
.append_quoted(lnav::roles::symbol( vt->vi->get_name().to_string())))
vt->vi->get_name().to_string()))) .with_reason(errors[0].to_attr_line({}))
.with_reason(errors[0].to_attr_line({})) .with_snippet(lnav::console::snippet::from(
.with_snippet( log_vtab_data.lvd_location,
lnav::console::snippet::from( log_vtab_data.lvd_content));
log_vtab_data.lvd_source,
log_vtab_data.lvd_content)
.with_line(log_vtab_data.lvd_line_number));
auto json_error = lnav::to_json(top_error); auto json_error = lnav::to_json(top_error);
tab->zErrMsg tab->zErrMsg
= sqlite3_mprintf("lnav-error:%s", json_error.c_str()); = sqlite3_mprintf("lnav-error:%s", json_error.c_str());
@ -1185,3 +1215,33 @@ log_vtab_manager::unregister_vtab(intern_string_t name)
return retval; return retval;
} }
bool
log_format_vtab_impl::next(log_cursor& lc, logfile_sub_source& lss)
{
lc.lc_curr_line = lc.lc_curr_line + vis_line_t(1);
lc.lc_sub_index = 0;
if (lc.is_eof()) {
return true;
}
auto cl = content_line_t(lss.at(lc.lc_curr_line));
auto lf = lss.find(cl);
auto lf_iter = lf->begin() + cl;
uint8_t mod_id = lf_iter->get_module_id();
if (!lf_iter->is_message()) {
return false;
}
auto format = lf->get_format();
if (format->get_name() == this->lfvi_format.get_name()) {
return true;
} else if (mod_id && mod_id == this->lfvi_format.lf_mod_index) {
// XXX
return true;
}
return false;
}

@ -107,61 +107,40 @@ public:
value_kind_t kind); value_kind_t kind);
log_vtab_impl(const intern_string_t name) log_vtab_impl(const intern_string_t name)
: vi_supports_indexes(true), vi_name(name) : vi_name(name), vi_tags_name(intern_string::lookup(
fmt::format(FMT_STRING("{}.log_tags"), name)))
{ {
this->vi_attrs.resize(128); this->vi_attrs.resize(128);
}; }
virtual ~log_vtab_impl() = default;
const intern_string_t get_name() const virtual ~log_vtab_impl() = default;
{
return this->vi_name;
};
std::string get_table_statement(); const intern_string_t get_name() const { return this->vi_name; }
virtual bool is_valid(log_cursor& lc, logfile_sub_source& lss) intern_string_t get_tags_name() const { return this->vi_tags_name; }
{
content_line_t cl(lss.at(lc.lc_curr_line));
std::shared_ptr<logfile> lf = lss.find(cl);
auto lf_iter = lf->begin() + cl;
if (!lf_iter->is_message()) { std::string get_table_statement();
return false;
}
return true; virtual bool is_valid(log_cursor& lc, logfile_sub_source& lss);
};
virtual bool next(log_cursor& lc, logfile_sub_source& lss) = 0; virtual bool next(log_cursor& lc, logfile_sub_source& lss) = 0;
virtual void get_columns(std::vector<vtab_column>& cols) const {}; virtual void get_columns(std::vector<vtab_column>& cols) const {};
virtual void get_foreign_keys(std::vector<std::string>& keys_inout) const virtual void get_foreign_keys(std::vector<std::string>& keys_inout) const;
{
keys_inout.emplace_back("log_line");
keys_inout.emplace_back("min(log_line)");
keys_inout.emplace_back("log_mark");
keys_inout.emplace_back("log_time_msecs");
};
virtual void extract(std::shared_ptr<logfile> lf, virtual void extract(std::shared_ptr<logfile> lf,
uint64_t line_number, uint64_t line_number,
shared_buffer_ref& line, shared_buffer_ref& line,
std::vector<logline_value>& values) std::vector<logline_value>& values);
{
auto format = lf->get_format();
this->vi_attrs.clear(); bool vi_supports_indexes{true};
format->annotate(line_number, line, this->vi_attrs, values, false);
};
bool vi_supports_indexes;
int vi_column_count; int vi_column_count;
string_attrs_t vi_attrs; string_attrs_t vi_attrs;
protected: protected:
const intern_string_t vi_name; const intern_string_t vi_name;
const intern_string_t vi_tags_name;
}; };
class log_format_vtab_impl : public log_vtab_impl { class log_format_vtab_impl : public log_vtab_impl {
@ -171,34 +150,7 @@ public:
{ {
} }
virtual bool next(log_cursor& lc, logfile_sub_source& lss) virtual bool next(log_cursor& lc, logfile_sub_source& lss);
{
lc.lc_curr_line = lc.lc_curr_line + vis_line_t(1);
lc.lc_sub_index = 0;
if (lc.is_eof()) {
return true;
}
auto cl = content_line_t(lss.at(lc.lc_curr_line));
auto lf = lss.find(cl);
auto lf_iter = lf->begin() + cl;
uint8_t mod_id = lf_iter->get_module_id();
if (!lf_iter->is_message()) {
return false;
}
auto format = lf->get_format();
if (format->get_name() == this->lfvi_format.get_name()) {
return true;
} else if (mod_id && mod_id == this->lfvi_format.lf_mod_index) {
// XXX
return true;
}
return false;
};
protected: protected:
const log_format& lfvi_format; const log_format& lfvi_format;
@ -210,8 +162,7 @@ typedef void (*sql_progress_finished_callback_t)();
struct _log_vtab_data { struct _log_vtab_data {
sql_progress_callback_t lvd_progress; sql_progress_callback_t lvd_progress;
sql_progress_finished_callback_t lvd_finished; sql_progress_finished_callback_t lvd_finished;
std::string lvd_source; source_location lvd_location;
int lvd_line_number{0};
attr_line_t lvd_content; attr_line_t lvd_content;
}; };
@ -221,14 +172,12 @@ class sql_progress_guard {
public: public:
sql_progress_guard(sql_progress_callback_t cb, sql_progress_guard(sql_progress_callback_t cb,
sql_progress_finished_callback_t fcb, sql_progress_finished_callback_t fcb,
const std::string& source, source_location loc,
int line_number,
const attr_line_t& content) const attr_line_t& content)
{ {
log_vtab_data.lvd_progress = cb; log_vtab_data.lvd_progress = cb;
log_vtab_data.lvd_finished = fcb; log_vtab_data.lvd_finished = fcb;
log_vtab_data.lvd_source = source; log_vtab_data.lvd_location = loc;
log_vtab_data.lvd_line_number = line_number;
log_vtab_data.lvd_content = content; log_vtab_data.lvd_content = content;
} }
@ -239,8 +188,7 @@ public:
} }
log_vtab_data.lvd_progress = nullptr; log_vtab_data.lvd_progress = nullptr;
log_vtab_data.lvd_finished = nullptr; log_vtab_data.lvd_finished = nullptr;
log_vtab_data.lvd_source.clear(); log_vtab_data.lvd_location = source_location{};
log_vtab_data.lvd_line_number = 0;
log_vtab_data.lvd_content.clear(); log_vtab_data.lvd_content.clear();
} }
}; };
@ -253,15 +201,9 @@ public:
log_vtab_manager(sqlite3* db, textview_curses& tc, logfile_sub_source& lss); log_vtab_manager(sqlite3* db, textview_curses& tc, logfile_sub_source& lss);
~log_vtab_manager(); ~log_vtab_manager();
textview_curses* get_view() const textview_curses* get_view() const { return &this->vm_textview; }
{
return &this->vm_textview;
};
logfile_sub_source* get_source() logfile_sub_source* get_source() { return &this->vm_source; }
{
return &this->vm_source;
};
std::string register_vtab(std::shared_ptr<log_vtab_impl> vi); std::string register_vtab(std::shared_ptr<log_vtab_impl> vi);
std::string unregister_vtab(intern_string_t name); std::string unregister_vtab(intern_string_t name);
@ -274,17 +216,11 @@ public:
return iter->second; return iter->second;
} }
return nullptr; return nullptr;
}; }
iterator begin() const iterator begin() const { return this->vm_impls.begin(); }
{
return this->vm_impls.begin();
};
iterator end() const iterator end() const { return this->vm_impls.end(); }
{
return this->vm_impls.end();
};
private: private:
sqlite3* vm_db; sqlite3* vm_db;

@ -796,3 +796,49 @@ logfile::mark_as_duplicate(const std::string& name)
note_type::duplicate, note_type::duplicate,
fmt::format(FMT_STRING("hiding duplicate of {}"), name)); fmt::format(FMT_STRING("hiding duplicate of {}"), name));
} }
void
logfile::adjust_content_time(int line, const timeval& tv, bool abs_offset)
{
struct timeval old_time = this->lf_time_offset;
this->lf_time_offset_line = line;
if (abs_offset) {
this->lf_time_offset = tv;
} else {
timeradd(&old_time, &tv, &this->lf_time_offset);
}
for (auto& iter : *this) {
struct timeval curr, diff, new_time;
curr = iter.get_timeval();
timersub(&curr, &old_time, &diff);
timeradd(&diff, &this->lf_time_offset, &new_time);
iter.set_time(new_time);
}
this->lf_sort_needed = true;
}
void
logfile::set_filename(const std::string& filename)
{
if (this->lf_filename != filename) {
this->lf_filename = filename;
ghc::filesystem::path p(filename);
this->lf_basename = p.filename();
}
}
struct timeval
logfile::original_line_time(logfile::iterator ll)
{
if (this->is_time_adjusted()) {
struct timeval line_time = ll->get_timeval();
struct timeval retval;
timersub(&line_time, &this->lf_time_offset, &retval);
return retval;
}
return ll->get_timeval();
}

@ -111,10 +111,7 @@ public:
~logfile() override; ~logfile() override;
const logfile_activity& get_activity() const const logfile_activity& get_activity() const { return this->lf_activity; }
{
return this->lf_activity;
};
nonstd::optional<ghc::filesystem::path> get_actual_path() const nonstd::optional<ghc::filesystem::path> get_actual_path() const
{ {
@ -122,128 +119,63 @@ public:
} }
/** @return The filename as given in the constructor. */ /** @return The filename as given in the constructor. */
const std::string& get_filename() const const std::string& get_filename() const { return this->lf_filename; }
{
return this->lf_filename;
};
/** @return The filename as given in the constructor, excluding the path /** @return The filename as given in the constructor, excluding the path
* prefix. */ * prefix. */
const std::string& get_basename() const const std::string& get_basename() const { return this->lf_basename; }
{
return this->lf_basename;
};
int get_fd() const int get_fd() const { return this->lf_line_buffer.get_fd(); }
{
return this->lf_line_buffer.get_fd();
};
/** @param filename The new filename for this log file. */ /** @param filename The new filename for this log file. */
void set_filename(const std::string& filename) void set_filename(const std::string& filename);
{
if (this->lf_filename != filename) {
this->lf_filename = filename;
ghc::filesystem::path p(filename);
this->lf_basename = p.filename();
}
};
const std::string& get_content_id() const const std::string& get_content_id() const { return this->lf_content_id; }
{
return this->lf_content_id;
};
/** @return The inode for this log file. */ /** @return The inode for this log file. */
const struct stat& get_stat() const const struct stat& get_stat() const { return this->lf_stat; }
{
return this->lf_stat;
};
size_t get_longest_line_length() const size_t get_longest_line_length() const { return this->lf_longest_line; }
{
return this->lf_longest_line;
}
bool is_compressed() const bool is_compressed() const { return this->lf_line_buffer.is_compressed(); }
{
return this->lf_line_buffer.is_compressed();
};
bool is_valid_filename() const bool is_valid_filename() const { return this->lf_valid_filename; }
{
return this->lf_valid_filename;
};
file_off_t get_index_size() const file_off_t get_index_size() const { return this->lf_index_size; }
{
return this->lf_index_size;
}
/** /**
* @return The detected format, rebuild_index() must be called before this * @return The detected format, rebuild_index() must be called before this
* will return a value other than NULL. * will return a value other than NULL.
*/ */
std::shared_ptr<log_format> get_format() const std::shared_ptr<log_format> get_format() const { return this->lf_format; }
{
return this->lf_format;
};
intern_string_t get_format_name() const; intern_string_t get_format_name() const;
text_format_t get_text_format() const text_format_t get_text_format() const { return this->lf_text_format; }
{
return this->lf_text_format;
}
/** /**
* @return The last modified time of the file when the file was last * @return The last modified time of the file when the file was last
* indexed. * indexed.
*/ */
time_t get_modified_time() const time_t get_modified_time() const { return this->lf_index_time; }
{
return this->lf_index_time;
};
int get_time_offset_line() const int get_time_offset_line() const { return this->lf_time_offset_line; }
{
return this->lf_time_offset_line;
};
const struct timeval& get_time_offset() const const struct timeval& get_time_offset() const
{ {
return this->lf_time_offset; return this->lf_time_offset;
}; }
void adjust_content_time(int line, void adjust_content_time(int line,
const struct timeval& tv, const struct timeval& tv,
bool abs_offset = true) bool abs_offset = true);
{
struct timeval old_time = this->lf_time_offset;
this->lf_time_offset_line = line;
if (abs_offset) {
this->lf_time_offset = tv;
} else {
timeradd(&old_time, &tv, &this->lf_time_offset);
}
for (auto& iter : *this) {
struct timeval curr, diff, new_time;
curr = iter.get_timeval();
timersub(&curr, &old_time, &diff);
timeradd(&diff, &this->lf_time_offset, &new_time);
iter.set_time(new_time);
}
this->lf_sort_needed = true;
};
void clear_time_offset() void clear_time_offset()
{ {
struct timeval tv = {0, 0}; struct timeval tv = {0, 0};
this->adjust_content_time(-1, tv); this->adjust_content_time(-1, tv);
}; }
void mark_as_duplicate(const std::string& name); void mark_as_duplicate(const std::string& name);
@ -299,46 +231,20 @@ public:
nonstd::optional<const_iterator> find_from_time( nonstd::optional<const_iterator> find_from_time(
const struct timeval& tv) const; const struct timeval& tv) const;
logline& operator[](int index) logline& operator[](int index) { return this->lf_index[index]; }
{
return this->lf_index[index];
};
logline& front() logline& front() { return this->lf_index.front(); }
{
return this->lf_index.front();
}
logline& back() logline& back() { return this->lf_index.back(); }
{
return this->lf_index.back();
};
/** @return True if this log file still exists. */ /** @return True if this log file still exists. */
bool exists() const; bool exists() const;
void close() void close() { this->lf_is_closed = true; }
{
this->lf_is_closed = true;
};
bool is_closed() const
{
return this->lf_is_closed;
};
struct timeval original_line_time(iterator ll)
{
if (this->is_time_adjusted()) {
struct timeval line_time = ll->get_timeval();
struct timeval retval;
timersub(&line_time, &this->lf_time_offset, &retval); bool is_closed() const { return this->lf_is_closed; }
return retval;
}
return ll->get_timeval(); struct timeval original_line_time(iterator ll);
};
Result<shared_buffer_ref, std::string> read_line(iterator ll); Result<shared_buffer_ref, std::string> read_line(iterator ll);
@ -351,7 +257,7 @@ public:
} }
return retval; return retval;
}; }
iterator message_start(iterator ll) iterator message_start(iterator ll)
{ {
@ -370,8 +276,10 @@ public:
file_range get_file_range(const_iterator ll, bool include_continues = true) file_range get_file_range(const_iterator ll, bool include_continues = true)
{ {
return {ll->get_offset(), return {
(file_ssize_t) this->line_length(ll, include_continues)}; ll->get_offset(),
(file_ssize_t) this->line_length(ll, include_continues),
};
} }
void read_full_message(const_iterator ll, void read_full_message(const_iterator ll,
@ -402,14 +310,14 @@ public:
void set_logfile_observer(logfile_observer* lo) void set_logfile_observer(logfile_observer* lo)
{ {
this->lf_logfile_observer = lo; this->lf_logfile_observer = lo;
}; }
void set_logline_observer(logline_observer* llo); void set_logline_observer(logline_observer* llo);
logline_observer* get_logline_observer() const logline_observer* get_logline_observer() const
{ {
return this->lf_logline_observer; return this->lf_logline_observer;
}; }
bool operator<(const logfile& rhs) const bool operator<(const logfile& rhs) const
{ {
@ -424,7 +332,7 @@ public:
} }
return retval; return retval;
}; }
bool is_indexing() const bool is_indexing() const
{ {

@ -36,6 +36,7 @@
#include "base/ansi_scrubber.hh" #include "base/ansi_scrubber.hh"
#include "base/humanize.time.hh" #include "base/humanize.time.hh"
#include "base/itertools.hh"
#include "base/string_util.hh" #include "base/string_util.hh"
#include "command_executor.hh" #include "command_executor.hh"
#include "config.h" #include "config.h"
@ -349,8 +350,6 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
value_out = this->lss_token_attrs; value_out = this->lss_token_attrs;
attrs = vc.vc_level_attrs[this->lss_token_line->get_msg_level()].first;
if ((row + 1) < (int) this->lss_filtered_index.size()) { if ((row + 1) < (int) this->lss_filtered_index.size()) {
next_line = this->find_line(this->at(vis_line_t(row + 1))); next_line = this->find_line(this->at(vis_line_t(row + 1)));
} }
@ -367,6 +366,8 @@ logfile_sub_source::text_attrs_for_line(textview_curses& lv,
lr.lr_start = 0; lr.lr_start = 0;
lr.lr_end = this->lss_token_value.length(); lr.lr_end = this->lss_token_value.length();
value_out.emplace_back(lr, SA_ORIGINAL_LINE.value()); value_out.emplace_back(lr, SA_ORIGINAL_LINE.value());
value_out.emplace_back(
lr, SA_LEVEL.value(this->lss_token_line->get_msg_level()));
lr.lr_start = time_offset_end; lr.lr_start = time_offset_end;
lr.lr_end = -1; lr.lr_end = -1;
@ -1731,15 +1732,9 @@ logfile_sub_source::reload_index_delegate()
nonstd::optional<std::shared_ptr<text_filter>> nonstd::optional<std::shared_ptr<text_filter>>
logfile_sub_source::get_sql_filter() logfile_sub_source::get_sql_filter()
{ {
auto iter return this->tss_filters | lnav::itertools::find_if([](const auto& filt) {
= std::find_if(this->tss_filters.begin(), return filt->get_index() == 0;
this->tss_filters.end(), });
[](const auto& filt) { return filt->get_index() == 0; });
if (iter != this->tss_filters.end()) {
return *iter;
}
return nonstd::nullopt;
} }
void void

@ -65,19 +65,15 @@ class index_delegate {
public: public:
virtual ~index_delegate() = default; virtual ~index_delegate() = default;
virtual void index_start(logfile_sub_source& lss){ virtual void index_start(logfile_sub_source& lss) {}
};
virtual void index_line(logfile_sub_source& lss, virtual void index_line(logfile_sub_source& lss,
logfile* lf, logfile* lf,
logfile::iterator ll){ logfile::iterator ll)
{
}; }
virtual void index_complete(logfile_sub_source& lss){
}; virtual void index_complete(logfile_sub_source& lss) {}
}; };
class pcre_filter : public text_filter { class pcre_filter : public text_filter {
@ -95,14 +91,14 @@ public:
pcre_input pi(line.get_data(), 0, line.length()); pcre_input pi(line.get_data(), 0, line.length());
return this->pf_pcre.match(pc, pi); return this->pf_pcre.match(pc, pi);
}; }
std::string to_command() const override std::string to_command() const override
{ {
return (this->lf_type == text_filter::INCLUDE ? "filter-in " return (this->lf_type == text_filter::INCLUDE ? "filter-in "
: "filter-out ") : "filter-out ")
+ this->lf_id; + this->lf_id;
}; }
protected: protected:
pcrepp pf_pcre; pcrepp pf_pcre;
@ -177,7 +173,7 @@ public:
{ {
this->lss_flags ^= F_TIME_OFFSET; this->lss_flags ^= F_TIME_OFFSET;
this->clear_line_size_cache(); this->clear_line_size_cache();
}; }
void increase_line_context() void increase_line_context()
{ {
@ -194,7 +190,7 @@ public:
if (old_flags != this->lss_flags) { if (old_flags != this->lss_flags) {
this->clear_line_size_cache(); this->clear_line_size_cache();
} }
}; }
bool decrease_line_context() bool decrease_line_context()
{ {
@ -213,7 +209,7 @@ public:
} }
return false; return false;
}; }
size_t get_filename_offset() const size_t get_filename_offset() const
{ {
@ -233,27 +229,24 @@ public:
else else
this->lss_flags &= ~F_TIME_OFFSET; this->lss_flags &= ~F_TIME_OFFSET;
this->clear_line_size_cache(); this->clear_line_size_cache();
}; }
bool is_time_offset_enabled() const bool is_time_offset_enabled() const
{ {
return (bool) (this->lss_flags & F_TIME_OFFSET); return (bool) (this->lss_flags & F_TIME_OFFSET);
}; }
bool is_filename_enabled() const bool is_filename_enabled() const
{ {
return (bool) (this->lss_flags & F_FILENAME); return (bool) (this->lss_flags & F_FILENAME);
}; }
bool is_basename_enabled() const bool is_basename_enabled() const
{ {
return (bool) (this->lss_flags & F_BASENAME); return (bool) (this->lss_flags & F_BASENAME);
}; }
log_level_t get_min_log_level() const log_level_t get_min_log_level() const { return this->lss_min_log_level; }
{
return this->lss_min_log_level;
};
void set_force_rebuild() void set_force_rebuild()
{ {
@ -266,14 +259,14 @@ public:
this->lss_min_log_level = level; this->lss_min_log_level = level;
this->text_filters_changed(); this->text_filters_changed();
} }
}; }
bool get_min_log_time(struct timeval& tv_out) const bool get_min_log_time(struct timeval& tv_out) const
{ {
tv_out = this->lss_min_log_time; tv_out = this->lss_min_log_time;
return (this->lss_min_log_time.tv_sec != 0 return (this->lss_min_log_time.tv_sec != 0
|| this->lss_min_log_time.tv_usec != 0); || this->lss_min_log_time.tv_usec != 0);
}; }
void set_min_log_time(const struct timeval& tv) void set_min_log_time(const struct timeval& tv)
{ {
@ -281,7 +274,7 @@ public:
this->lss_min_log_time = tv; this->lss_min_log_time = tv;
this->text_filters_changed(); this->text_filters_changed();
} }
}; }
bool get_max_log_time(struct timeval& tv_out) const bool get_max_log_time(struct timeval& tv_out) const
{ {
@ -289,7 +282,7 @@ public:
return (this->lss_max_log_time.tv_sec return (this->lss_max_log_time.tv_sec
!= std::numeric_limits<time_t>::max() != std::numeric_limits<time_t>::max()
|| this->lss_max_log_time.tv_usec != 0); || this->lss_max_log_time.tv_usec != 0);
}; }
void set_max_log_time(struct timeval& tv) void set_max_log_time(struct timeval& tv)
{ {
@ -297,7 +290,7 @@ public:
this->lss_max_log_time = tv; this->lss_max_log_time = tv;
this->text_filters_changed(); this->text_filters_changed();
} }
}; }
void clear_min_max_log_times() void clear_min_max_log_times()
{ {
@ -312,7 +305,7 @@ public:
this->lss_max_log_time.tv_usec = 0; this->lss_max_log_time.tv_usec = 0;
this->text_filters_changed(); this->text_filters_changed();
} }
}; }
bool list_input_handle_key(listview_curses& lv, int ch); bool list_input_handle_key(listview_curses& lv, int ch);
@ -322,22 +315,16 @@ public:
this->lss_marked_only = val; this->lss_marked_only = val;
this->text_filters_changed(); this->text_filters_changed();
} }
}; }
bool get_marked_only() bool get_marked_only() { return this->lss_marked_only; }
{
return this->lss_marked_only;
};
size_t text_line_count() size_t text_line_count() { return this->lss_filtered_index.size(); }
{
return this->lss_filtered_index.size();
};
size_t text_line_width(textview_curses& curses) size_t text_line_width(textview_curses& curses)
{ {
return this->lss_longest_line; return this->lss_longest_line;
}; }
size_t file_count() const size_t file_count() const
{ {
@ -353,10 +340,7 @@ public:
return retval; return retval;
} }
bool empty() const bool empty() const { return this->lss_filtered_index.empty(); }
{
return this->lss_filtered_index.empty();
};
void text_value_for_line(textview_curses& tc, void text_value_for_line(textview_curses& tc,
int row, int row,
@ -404,22 +388,22 @@ public:
void set_user_mark(const bookmark_type_t* bm, content_line_t cl) void set_user_mark(const bookmark_type_t* bm, content_line_t cl)
{ {
this->lss_user_marks[bm].insert_once(cl); this->lss_user_marks[bm].insert_once(cl);
}; }
bookmarks<content_line_t>::type& get_user_bookmarks() bookmarks<content_line_t>::type& get_user_bookmarks()
{ {
return this->lss_user_marks; return this->lss_user_marks;
}; }
std::map<content_line_t, bookmark_metadata>& get_user_bookmark_metadata() std::map<content_line_t, bookmark_metadata>& get_user_bookmark_metadata()
{ {
return this->lss_user_mark_metadata; return this->lss_user_mark_metadata;
}; }
int get_filtered_count() const int get_filtered_count() const
{ {
return this->lss_index.size() - this->lss_filtered_index.size(); return this->lss_index.size() - this->lss_filtered_index.size();
}; }
int get_filtered_count_for(size_t filter_index) const int get_filtered_count_for(size_t filter_index) const
{ {
@ -468,7 +452,7 @@ public:
line = content_line_t(line % MAX_LINES_PER_FILE); line = content_line_t(line % MAX_LINES_PER_FILE);
return retval; return retval;
}; }
logfile* find_file_ptr(content_line_t& line) logfile* find_file_ptr(content_line_t& line)
{ {
@ -477,7 +461,7 @@ public:
line = content_line_t(line % MAX_LINES_PER_FILE); line = content_line_t(line % MAX_LINES_PER_FILE);
return retval; return retval;
}; }
logline* find_line(content_line_t line) const logline* find_line(content_line_t line) const
{ {
@ -491,7 +475,7 @@ public:
} }
return retval; return retval;
}; }
nonstd::optional<vis_line_t> find_from_time( nonstd::optional<vis_line_t> find_from_time(
const struct timeval& start) const; const struct timeval& start) const;
@ -501,12 +485,12 @@ public:
struct timeval tv = {start, 0}; struct timeval tv = {start, 0};
return this->find_from_time(tv); return this->find_from_time(tv);
}; }
nonstd::optional<vis_line_t> find_from_time(const exttm& etm) const nonstd::optional<vis_line_t> find_from_time(const exttm& etm) const
{ {
return this->find_from_time(etm.to_timeval()); return this->find_from_time(etm.to_timeval());
}; }
nonstd::optional<vis_line_t> find_from_content(content_line_t cl); nonstd::optional<vis_line_t> find_from_content(content_line_t cl);
@ -516,17 +500,17 @@ public:
return this->find_line(this->at(row))->get_timeval(); return this->find_line(this->at(row))->get_timeval();
} }
return nonstd::nullopt; return nonstd::nullopt;
}; }
nonstd::optional<vis_line_t> row_for_time(struct timeval time_bucket) nonstd::optional<vis_line_t> row_for_time(struct timeval time_bucket)
{ {
return this->find_from_time(time_bucket); return this->find_from_time(time_bucket);
}; }
content_line_t at(vis_line_t vl) content_line_t at(vis_line_t vl)
{ {
return this->lss_index[this->lss_filtered_index[vl]]; return this->lss_index[this->lss_filtered_index[vl]];
}; }
content_line_t at_base(vis_line_t vl) content_line_t at_base(vis_line_t vl)
{ {
@ -535,7 +519,7 @@ public:
} }
return this->at(vl); return this->at(vl);
}; }
log_accel::direction_t get_line_accel_direction(vis_line_t vl); log_accel::direction_t get_line_accel_direction(vis_line_t vl);
@ -551,28 +535,25 @@ public:
ld_visible(lf->is_indexing()) ld_visible(lf->is_indexing())
{ {
lf->set_logline_observer(&this->ld_filter_state); lf->set_logline_observer(&this->ld_filter_state);
}; }
void clear() void clear() { this->ld_filter_state.lfo_filter_state.clear(); }
{
this->ld_filter_state.lfo_filter_state.clear();
};
void set_file(const std::shared_ptr<logfile>& lf) void set_file(const std::shared_ptr<logfile>& lf)
{ {
this->ld_filter_state.lfo_filter_state.tfs_logfile = lf; this->ld_filter_state.lfo_filter_state.tfs_logfile = lf;
lf->set_logline_observer(&this->ld_filter_state); lf->set_logline_observer(&this->ld_filter_state);
}; }
std::shared_ptr<logfile> get_file() const std::shared_ptr<logfile> get_file() const
{ {
return this->ld_filter_state.lfo_filter_state.tfs_logfile; return this->ld_filter_state.lfo_filter_state.tfs_logfile;
}; }
logfile* get_file_ptr() const logfile* get_file_ptr() const
{ {
return this->ld_filter_state.lfo_filter_state.tfs_logfile.get(); return this->ld_filter_state.lfo_filter_state.tfs_logfile.get();
}; }
bool is_visible() const bool is_visible() const
{ {
@ -594,25 +575,13 @@ public:
using const_iterator using const_iterator
= std::vector<std::unique_ptr<logfile_data>>::const_iterator; = std::vector<std::unique_ptr<logfile_data>>::const_iterator;
iterator begin() iterator begin() { return this->lss_files.begin(); }
{
return this->lss_files.begin();
};
iterator end() iterator end() { return this->lss_files.end(); }
{
return this->lss_files.end();
};
const_iterator cbegin() const const_iterator cbegin() const { return this->lss_files.begin(); }
{
return this->lss_files.begin();
};
const_iterator cend() const const_iterator cend() const { return this->lss_files.end(); }
{
return this->lss_files.end();
};
iterator find_data(content_line_t& line) iterator find_data(content_line_t& line)
{ {
@ -621,7 +590,7 @@ public:
line = content_line_t(line % MAX_LINES_PER_FILE); line = content_line_t(line % MAX_LINES_PER_FILE);
return retval; return retval;
}; }
iterator find_data(content_line_t line, uint64_t& offset_out) iterator find_data(content_line_t line, uint64_t& offset_out)
{ {
@ -630,7 +599,7 @@ public:
offset_out = line % MAX_LINES_PER_FILE; offset_out = line % MAX_LINES_PER_FILE;
return retval; return retval;
}; }
nonstd::optional<logfile_data*> find_data( nonstd::optional<logfile_data*> find_data(
const std::shared_ptr<logfile>& lf) const std::shared_ptr<logfile>& lf)
@ -659,7 +628,7 @@ public:
ssize_t index = std::distance(this->begin(), iter); ssize_t index = std::distance(this->begin(), iter);
return content_line_t(index * MAX_LINES_PER_FILE); return content_line_t(index * MAX_LINES_PER_FILE);
}; }
void set_index_delegate(index_delegate* id) void set_index_delegate(index_delegate* id)
{ {
@ -667,12 +636,12 @@ public:
this->lss_index_delegate = id; this->lss_index_delegate = id;
this->reload_index_delegate(); this->reload_index_delegate();
} }
}; }
index_delegate* get_index_delegate() const index_delegate* get_index_delegate() const
{ {
return this->lss_index_delegate; return this->lss_index_delegate;
}; }
void reload_index_delegate(); void reload_index_delegate();
@ -712,7 +681,7 @@ public:
nonstd::optional<location_history*> get_location_history() nonstd::optional<location_history*> get_location_history()
{ {
return &this->lss_location_history; return &this->lss_location_history;
}; }
Result<bool, std::string> eval_sql_filter(sqlite3_stmt* stmt, Result<bool, std::string> eval_sql_filter(sqlite3_stmt* stmt,
iterator ld, iterator ld,
@ -757,19 +726,14 @@ private:
}; };
struct __attribute__((__packed__)) indexed_content { struct __attribute__((__packed__)) indexed_content {
indexed_content(){ indexed_content() {}
};
indexed_content(content_line_t cl)
: ic_value(cl){
}; indexed_content(content_line_t cl) : ic_value(cl) {}
operator content_line_t() const operator content_line_t() const
{ {
return content_line_t(this->ic_value); return content_line_t(this->ic_value);
}; }
uint64_t ic_value : 40; uint64_t ic_value : 40;
}; };
@ -783,7 +747,7 @@ private:
logline* ll_rhs = this->llss_controller.find_line(rhs); logline* ll_rhs = this->llss_controller.find_line(rhs);
return (*ll_lhs) < (*ll_rhs); return (*ll_lhs) < (*ll_rhs);
}; }
bool operator()(const uint32_t& lhs, const uint32_t& rhs) const bool operator()(const uint32_t& lhs, const uint32_t& rhs) const
{ {
@ -795,7 +759,7 @@ private:
logline* ll_rhs = this->llss_controller.find_line(cl_rhs); logline* ll_rhs = this->llss_controller.find_line(cl_rhs);
return (*ll_lhs) < (*ll_rhs); return (*ll_lhs) < (*ll_rhs);
}; }
#if 0 #if 0
bool operator()(const indexed_content &lhs, const indexed_content &rhs) bool operator()(const indexed_content &lhs, const indexed_content &rhs)
{ {
@ -803,21 +767,23 @@ private:
logline *ll_rhs = this->llss_controller.find_line(rhs.ic_value); logline *ll_rhs = this->llss_controller.find_line(rhs.ic_value);
return (*ll_lhs) < (*ll_rhs); return (*ll_lhs) < (*ll_rhs);
}; }
#endif #endif
bool operator()(const content_line_t& lhs, const time_t& rhs) const bool operator()(const content_line_t& lhs, const time_t& rhs) const
{ {
logline* ll_lhs = this->llss_controller.find_line(lhs); logline* ll_lhs = this->llss_controller.find_line(lhs);
return *ll_lhs < rhs; return *ll_lhs < rhs;
}; }
bool operator()(const content_line_t& lhs, bool operator()(const content_line_t& lhs,
const struct timeval& rhs) const const struct timeval& rhs) const
{ {
logline* ll_lhs = this->llss_controller.find_line(lhs); logline* ll_lhs = this->llss_controller.find_line(lhs);
return *ll_lhs < rhs; return *ll_lhs < rhs;
}; }
logfile_sub_source& llss_controller; logfile_sub_source& llss_controller;
}; };
@ -835,7 +801,7 @@ private:
logline* ll_rhs = this->llss_controller.find_line(cl_rhs); logline* ll_rhs = this->llss_controller.find_line(cl_rhs);
return (*ll_lhs) < (*ll_rhs); return (*ll_lhs) < (*ll_rhs);
}; }
bool operator()(const uint32_t& lhs, const struct timeval& rhs) const bool operator()(const uint32_t& lhs, const struct timeval& rhs) const
{ {
@ -844,7 +810,7 @@ private:
logline* ll_lhs = this->llss_controller.find_line(cl_lhs); logline* ll_lhs = this->llss_controller.find_line(cl_lhs);
return (*ll_lhs) < rhs; return (*ll_lhs) < rhs;
}; }
const logfile_sub_source& llss_controller; const logfile_sub_source& llss_controller;
}; };

@ -55,11 +55,8 @@ class papertrail_proc : public curl_request {
public: public:
papertrail_proc(const std::string& search, time_t min_time, time_t max_time) papertrail_proc(const std::string& search, time_t min_time, time_t max_time)
: curl_request("papertrailapp.com"), : curl_request("papertrailapp.com"),
ptp_jcontext(this->cr_name, &FORMAT_HANDLERS), ptp_jhandle(yajl_free), ptp_jcontext(intern_string::lookup(this->cr_name), &FORMAT_HANDLERS),
ptp_gen(yajl_gen_free), ptp_search(search), ptp_search(search), ptp_min_time(min_time), ptp_max_time(max_time)
ptp_quoted_search(curl_free), ptp_header_list(curl_slist_free_all),
ptp_partial_read(false), ptp_min_time(min_time),
ptp_max_time(max_time)
{ {
char piper_tmpname[PATH_MAX]; char piper_tmpname[PATH_MAX];
const char* tmpdir; const char* tmpdir;
@ -111,14 +108,11 @@ public:
this->cr_handle, CURLOPT_HTTPHEADER, this->ptp_header_list.in()); this->cr_handle, CURLOPT_HTTPHEADER, this->ptp_header_list.in());
this->set_url(); this->set_url();
}; }
~papertrail_proc() {} ~papertrail_proc() {}
auto_fd copy_fd() const auto_fd copy_fd() const { return this->ptp_fd.dup(); }
{
return this->ptp_fd.dup();
};
long complete(CURLcode result); long complete(CURLcode result);
@ -149,7 +143,7 @@ public:
base_url, base_url,
this->ptp_quoted_search.in())); this->ptp_quoted_search.in()));
curl_easy_setopt(this->cr_handle, CURLOPT_URL, this->ptp_url.in()); curl_easy_setopt(this->cr_handle, CURLOPT_URL, this->ptp_url.in());
}; }
static size_t write_cb(void* contents, static size_t write_cb(void* contents,
size_t size, size_t size,
@ -164,17 +158,17 @@ public:
static const char* PT_SEARCH_URL; static const char* PT_SEARCH_URL;
yajlpp_parse_context ptp_jcontext; yajlpp_parse_context ptp_jcontext;
auto_mem<yajl_handle_t> ptp_jhandle; auto_mem<yajl_handle_t> ptp_jhandle{yajl_free};
auto_mem<yajl_gen_t> ptp_gen; auto_mem<yajl_gen_t> ptp_gen{yajl_gen_free};
const char* ptp_api_key; const char* ptp_api_key;
const std::string ptp_search; const std::string ptp_search;
auto_mem<const char> ptp_quoted_search; auto_mem<const char> ptp_quoted_search{curl_free};
auto_mem<char> ptp_url; auto_mem<char> ptp_url;
auto_mem<char> ptp_token_header; auto_mem<char> ptp_token_header;
auto_mem<struct curl_slist> ptp_header_list; auto_mem<struct curl_slist> ptp_header_list{curl_slist_free_all};
auto_fd ptp_fd; auto_fd ptp_fd;
std::string ptp_last_max_id; std::string ptp_last_max_id;
bool ptp_partial_read; bool ptp_partial_read{false};
std::string ptp_error; std::string ptp_error;
time_t ptp_min_time; time_t ptp_min_time;
time_t ptp_max_time; time_t ptp_max_time;

@ -38,9 +38,7 @@ pcre_context::capture_t*
pcre_context::operator[](const char* name) const pcre_context::operator[](const char* name) const
{ {
capture_t* retval = nullptr; capture_t* retval = nullptr;
int index; auto index = this->pc_pcre->name_index(name);
index = this->pc_pcre->name_index(name);
if (index != PCRE_ERROR_NOSUBSTRING) { if (index != PCRE_ERROR_NOSUBSTRING) {
retval = &this->pc_captures[index + 1]; retval = &this->pc_captures[index + 1];
} }
@ -48,6 +46,18 @@ pcre_context::operator[](const char* name) const
return retval; return retval;
} }
pcre_context::capture_t*
pcre_context::first_valid() const
{
for (int lpc = 1; lpc < this->pc_count; lpc++) {
if (this->pc_captures[lpc].is_valid()) {
return &this->pc_captures[lpc];
}
}
return nullptr;
}
std::string std::string
pcrepp::quote(const char* unquoted) pcrepp::quote(const char* unquoted)
{ {
@ -85,7 +95,7 @@ void
pcrepp::find_captures(const char* pattern) pcrepp::find_captures(const char* pattern)
{ {
bool in_class = false, in_escape = false, in_literal = false; bool in_class = false, in_escape = false, in_literal = false;
std::vector<pcre_context::capture> cap_in_progress; std::vector<pcre_context::capture_t> cap_in_progress;
for (int lpc = 0; pattern[lpc]; lpc++) { for (int lpc = 0; pattern[lpc]; lpc++) {
if (in_escape) { if (in_escape) {
@ -118,7 +128,7 @@ pcrepp::find_captures(const char* pattern)
break; break;
case ')': { case ')': {
if (!cap_in_progress.empty()) { if (!cap_in_progress.empty()) {
pcre_context::capture& cap = cap_in_progress.back(); auto& cap = cap_in_progress.back();
char first = '\0', second = '\0', third = '\0'; char first = '\0', second = '\0', third = '\0';
bool is_cap = false; bool is_cap = false;
@ -342,6 +352,60 @@ pcrepp::jit_stack()
return retval; return retval;
} }
size_t
pcrepp::match_partial(pcre_input& pi) const
{
size_t length = pi.pi_length;
int rc;
do {
rc = pcre_exec(this->p_code,
this->p_code_extra.in(),
pi.get_string(),
length,
pi.pi_offset,
PCRE_PARTIAL,
nullptr,
0);
switch (rc) {
case 0:
case PCRE_ERROR_PARTIAL:
return length;
}
if (length > 0) {
length -= 1;
}
} while (length > 0);
return length;
}
const char*
pcrepp::name_for_capture(int index) const
{
for (pcre_named_capture::iterator iter = this->named_begin();
iter != this->named_end();
++iter)
{
if (iter->index() == index) {
return iter->pnc_name;
}
}
return "";
}
int
pcrepp::name_index(const char* name) const
{
int retval = pcre_get_stringnumber(this->p_code, name);
if (retval == PCRE_ERROR_NOSUBSTRING) {
return retval;
}
return retval - 1;
}
#else #else
# warning "pcrejit is not available, search performance will be degraded" # warning "pcrejit is not available, search performance will be degraded"
@ -351,3 +415,11 @@ pcrepp::pcre_free_study(pcre_extra* extra)
free(extra); free(extra);
} }
#endif #endif
void
pcre_context::capture_t::ltrim(const char* str)
{
while (this->c_begin < this->c_end && isspace(str[this->c_begin])) {
this->c_begin += 1;
}
}

@ -72,88 +72,53 @@ class pcrepp;
*/ */
class pcre_context { class pcre_context {
public: public:
typedef struct capture { struct capture_t {
capture(){ capture_t()
/* We don't initialize anything since it's a perf hit. */ { /* We don't initialize anything since it's a perf hit. */
}; }
capture(int begin, int end) : c_begin(begin), c_end(end) capture_t(int begin, int end) : c_begin(begin), c_end(end)
{ {
assert(begin <= end); assert(begin <= end);
}; }
int c_begin; int c_begin;
int c_end; int c_end;
void ltrim(const char* str) void ltrim(const char* str);
{
while (this->c_begin < this->c_end && isspace(str[this->c_begin])) {
this->c_begin += 1;
}
};
bool contains(int pos) const bool contains(int pos) const
{ {
return this->c_begin <= pos && pos < this->c_end; return this->c_begin <= pos && pos < this->c_end;
}; }
bool is_valid() const bool is_valid() const { return this->c_begin != -1; }
{
return this->c_begin != -1;
};
int length() const int length() const { return this->c_end - this->c_begin; }
{
return this->c_end - this->c_begin;
};
bool empty() const bool empty() const { return this->c_begin == this->c_end; }
{ };
return this->c_begin == this->c_end; using iterator = capture_t*;
}; using const_iterator = const capture_t*;
} capture_t;
typedef capture_t* iterator;
typedef const capture_t* const_iterator;
/** @return The maximum number of strings this context can capture. */ /** @return The maximum number of strings this context can capture. */
int get_max_count() const int get_max_count() const { return this->pc_max_count; }
{
return this->pc_max_count;
};
void set_count(int count) void set_count(int count) { this->pc_count = count; }
{
this->pc_count = count;
};
int get_count() const int get_count() const { return this->pc_count; }
{
return this->pc_count;
};
void set_pcrepp(const pcrepp* src) void set_pcrepp(const pcrepp* src) { this->pc_pcre = src; }
{
this->pc_pcre = src;
};
/** /**
* @return a capture_t that covers all of the text that was matched. * @return a capture_t that covers all of the text that was matched.
*/ */
capture_t* all() const capture_t* all() const { return pc_captures; }
{
return pc_captures;
};
/** @return An iterator to the first capture. */ /** @return An iterator to the first capture. */
iterator begin() iterator begin() { return pc_captures + 1; }
{
return pc_captures + 1;
};
/** @return An iterator that refers to the end of the capture array. */ /** @return An iterator that refers to the end of the capture array. */
iterator end() iterator end() { return pc_captures + pc_count; };
{
return pc_captures + pc_count;
};
capture_t* operator[](int offset) const capture_t* operator[](int offset) const
{ {
@ -161,39 +126,31 @@ public:
return nullptr; return nullptr;
} }
return &this->pc_captures[offset + 1]; return &this->pc_captures[offset + 1];
}; }
capture_t* operator[](const char* name) const; capture_t* operator[](const char* name) const;
capture_t* operator[](const std::string& name) const capture_t* operator[](const std::string& name) const
{ {
return (*this)[name.c_str()]; return (*this)[name.c_str()];
}; }
capture_t* first_valid() const
{
for (int lpc = 1; lpc < this->pc_count; lpc++) {
if (this->pc_captures[lpc].is_valid()) {
return &this->pc_captures[lpc];
}
}
return nullptr; capture_t* first_valid() const;
};
protected: protected:
pcre_context(capture_t* captures, int max_count) pcre_context(capture_t* captures, int max_count)
: pc_pcre(nullptr), pc_captures(captures), pc_max_count(max_count), : pc_captures(captures), pc_max_count(max_count)
pc_count(0){}; {
}
const pcrepp* pc_pcre; const pcrepp* pc_pcre{nullptr};
capture_t* pc_captures; capture_t* pc_captures;
int pc_max_count; int pc_max_count;
int pc_count; int pc_count{0};
}; };
struct capture_if_not { struct capture_if_not {
capture_if_not(int begin) : cin_begin(begin){}; capture_if_not(int begin) : cin_begin(begin) {}
bool operator()(const pcre_context::capture_t& cap) const bool operator()(const pcre_context::capture_t& cap) const
{ {
@ -203,19 +160,6 @@ struct capture_if_not {
int cin_begin; int cin_begin;
}; };
inline pcre_context::iterator
skip_invalid_captures(pcre_context::iterator iter,
pcre_context::iterator pc_end)
{
for (; iter != pc_end; ++iter) {
if (iter->c_begin == -1) {
continue;
}
}
return iter;
}
/** /**
* A pcre_context that allocates storage for the capture array within the object * A pcre_context that allocates storage for the capture array within the object
* itself. * itself.
@ -241,7 +185,7 @@ public:
if (this->pi_length == (size_t) -1) { if (this->pi_length == (size_t) -1) {
this->pi_length = strlen(str); this->pi_length = strlen(str);
} }
}; }
pcre_input(const string_fragment& s) pcre_input(const string_fragment& s)
: pi_offset(0), pi_next_offset(0), pi_length(s.length()), : pi_offset(0), pi_next_offset(0), pi_length(s.length()),
@ -259,20 +203,17 @@ public:
pcre_input(const std::string&&, size_t off = 0) = delete; pcre_input(const std::string&&, size_t off = 0) = delete;
const char* get_string() const const char* get_string() const { return this->pi_string; }
{
return this->pi_string;
};
const char* get_substr_start(pcre_context::const_iterator iter) const const char* get_substr_start(pcre_context::const_iterator iter) const
{ {
return &this->pi_string[iter->c_begin]; return &this->pi_string[iter->c_begin];
}; }
size_t get_substr_len(pcre_context::const_iterator iter) const size_t get_substr_len(pcre_context::const_iterator iter) const
{ {
return iter->length(); return iter->length();
}; }
std::string get_substr(pcre_context::const_iterator iter) const std::string get_substr(pcre_context::const_iterator iter) const
{ {
@ -280,13 +221,13 @@ public:
return ""; return "";
} }
return std::string(&this->pi_string[iter->c_begin], iter->length()); return std::string(&this->pi_string[iter->c_begin], iter->length());
}; }
intern_string_t get_substr_i(pcre_context::const_iterator iter) const intern_string_t get_substr_i(pcre_context::const_iterator iter) const
{ {
return intern_string::lookup(&this->pi_string[iter->c_begin], return intern_string::lookup(&this->pi_string[iter->c_begin],
iter->length()); iter->length());
}; }
nonstd::optional<std::string> get_substr_opt( nonstd::optional<std::string> get_substr_opt(
pcre_context::const_iterator iter) const pcre_context::const_iterator iter) const
@ -302,12 +243,9 @@ public:
{ {
memcpy(dst, &this->pi_string[iter->c_begin], iter->length()); memcpy(dst, &this->pi_string[iter->c_begin], iter->length());
dst[iter->length()] = '\0'; dst[iter->length()] = '\0';
}; }
void reset_next_offset() void reset_next_offset() { this->pi_next_offset = this->pi_offset; }
{
this->pi_next_offset = this->pi_offset;
};
void reset(const char* str, size_t off = 0, size_t len = -1) void reset(const char* str, size_t off = 0, size_t len = -1)
{ {
@ -324,7 +262,7 @@ public:
void reset(const std::string& str, size_t off = 0) void reset(const std::string& str, size_t off = 0)
{ {
this->reset(str.c_str(), off, str.length()); this->reset(str.c_str(), off, str.length());
}; }
size_t pi_offset; size_t pi_offset;
size_t pi_next_offset; size_t pi_next_offset;
@ -345,17 +283,17 @@ struct pcre_named_capture {
const pcre_named_capture& operator*() const const pcre_named_capture& operator*() const
{ {
return *this->i_named_capture; return *this->i_named_capture;
}; }
const pcre_named_capture* operator->() const const pcre_named_capture* operator->() const
{ {
return this->i_named_capture; return this->i_named_capture;
}; }
bool operator!=(const iterator& rhs) const bool operator!=(const iterator& rhs) const
{ {
return this->i_named_capture != rhs.i_named_capture; return this->i_named_capture != rhs.i_named_capture;
}; }
iterator& operator++() iterator& operator++()
{ {
@ -364,7 +302,7 @@ struct pcre_named_capture {
ptr += this->i_name_len; ptr += this->i_name_len;
this->i_named_capture = (pcre_named_capture*) ptr; this->i_named_capture = (pcre_named_capture*) ptr;
return *this; return *this;
}; }
private: private:
pcre_named_capture* i_named_capture; pcre_named_capture* i_named_capture;
@ -374,7 +312,7 @@ struct pcre_named_capture {
int index() const int index() const
{ {
return (this->pnc_index_msb << 8 | this->pnc_index_lsb) - 1; return (this->pnc_index_msb << 8 | this->pnc_index_lsb) - 1;
}; }
char pnc_index_msb; char pnc_index_msb;
char pnc_index_lsb; char pnc_index_lsb;
@ -389,13 +327,13 @@ struct pcre_extractor {
intern_string_t get_substr_i(T name) const intern_string_t get_substr_i(T name) const
{ {
return this->pe_input.get_substr_i(this->pe_context[name]); return this->pe_input.get_substr_i(this->pe_context[name]);
}; }
template<typename T> template<typename T>
std::string get_substr(T name) const std::string get_substr(T name) const
{ {
return this->pe_input.get_substr(this->pe_context[name]); return this->pe_input.get_substr(this->pe_context[name]);
}; }
}; };
class pcrepp { class pcrepp {
@ -422,8 +360,8 @@ public:
} }
struct compile_error { struct compile_error {
const char* ce_msg; const char* ce_msg{nullptr};
int ce_offset; int ce_offset{0};
}; };
static Result<pcrepp, compile_error> from_str(std::string pattern, static Result<pcrepp, compile_error> from_str(std::string pattern,
@ -433,7 +371,7 @@ public:
{ {
pcre_refcount(this->p_code, 1); pcre_refcount(this->p_code, 1);
this->study(); this->study();
}; }
pcrepp(std::string pattern, pcre* code) pcrepp(std::string pattern, pcre* code)
: p_code(code), p_pattern(std::move(pattern)), : p_code(code), p_pattern(std::move(pattern)),
@ -442,7 +380,7 @@ public:
pcre_refcount(this->p_code, 1); pcre_refcount(this->p_code, 1);
this->study(); this->study();
this->find_captures(this->p_pattern.c_str()); this->find_captures(this->p_pattern.c_str());
}; }
explicit pcrepp(const char* pattern, int options = 0) explicit pcrepp(const char* pattern, int options = 0)
: p_pattern(pattern), p_code_extra(pcre_free_study) : p_pattern(pattern), p_code_extra(pcre_free_study)
@ -460,7 +398,7 @@ public:
pcre_refcount(this->p_code, 1); pcre_refcount(this->p_code, 1);
this->study(); this->study();
this->find_captures(pattern); this->find_captures(pattern);
}; }
explicit pcrepp(const std::string& pattern, int options = 0) explicit pcrepp(const std::string& pattern, int options = 0)
: p_pattern(pattern), p_code_extra(pcre_free_study) : p_pattern(pattern), p_code_extra(pcre_free_study)
@ -478,7 +416,7 @@ public:
pcre_refcount(this->p_code, 1); pcre_refcount(this->p_code, 1);
this->study(); this->study();
this->find_captures(pattern.c_str()); this->find_captures(pattern.c_str());
}; }
pcrepp() {} pcrepp() {}
@ -488,7 +426,7 @@ public:
{ {
pcre_refcount(this->p_code, 1); pcre_refcount(this->p_code, 1);
this->study(); this->study();
}; }
pcrepp(pcrepp&& other) pcrepp(pcrepp&& other)
: p_code(other.p_code), p_pattern(std::move(other.p_pattern)), : p_code(other.p_code), p_pattern(std::move(other.p_pattern)),
@ -501,10 +439,7 @@ public:
this->p_code_extra = std::move(other.p_code_extra); this->p_code_extra = std::move(other.p_code_extra);
} }
virtual ~pcrepp() virtual ~pcrepp() { this->clear(); }
{
this->clear();
};
pcrepp& operator=(pcrepp&& other) noexcept pcrepp& operator=(pcrepp&& other) noexcept
{ {
@ -555,7 +490,7 @@ public:
pcre_named_capture::iterator named_begin() const pcre_named_capture::iterator named_begin() const
{ {
return {this->p_named_entries, static_cast<size_t>(this->p_name_len)}; return {this->p_named_entries, static_cast<size_t>(this->p_name_len)};
}; }
pcre_named_capture::iterator named_end() const pcre_named_capture::iterator named_end() const
{ {
@ -564,56 +499,33 @@ public:
ptr += this->p_named_count * this->p_name_len; ptr += this->p_named_count * this->p_name_len;
return {(pcre_named_capture*) ptr, return {(pcre_named_capture*) ptr,
static_cast<size_t>(this->p_name_len)}; static_cast<size_t>(this->p_name_len)};
}; }
const std::vector<pcre_context::capture>& captures() const const std::vector<pcre_context::capture_t>& captures() const
{ {
return this->p_captures; return this->p_captures;
}; }
std::vector<pcre_context::capture>::const_iterator cap_begin() const std::vector<pcre_context::capture_t>::const_iterator cap_begin() const
{ {
return this->p_captures.begin(); return this->p_captures.begin();
}; }
std::vector<pcre_context::capture>::const_iterator cap_end() const std::vector<pcre_context::capture_t>::const_iterator cap_end() const
{ {
return this->p_captures.end(); return this->p_captures.end();
}; }
int name_index(const std::string& name) const int name_index(const std::string& name) const
{ {
return this->name_index(name.c_str()); return this->name_index(name.c_str());
}; }
int name_index(const char* name) const
{
int retval = pcre_get_stringnumber(this->p_code, name);
if (retval == PCRE_ERROR_NOSUBSTRING) {
return retval;
}
return retval - 1; int name_index(const char* name) const;
};
const char* name_for_capture(int index) const const char* name_for_capture(int index) const;
{
for (pcre_named_capture::iterator iter = this->named_begin();
iter != this->named_end();
++iter)
{
if (iter->index() == index) {
return iter->pnc_name;
}
}
return "";
};
int get_capture_count() const int get_capture_count() const { return this->p_capture_count; }
{
return this->p_capture_count;
};
bool match(pcre_context& pc, pcre_input& pi, int options = 0) const; bool match(pcre_context& pc, pcre_input& pi, int options = 0) const;
@ -633,30 +545,7 @@ public:
std::string replace(const char* str, const char* repl) const; std::string replace(const char* str, const char* repl) const;
size_t match_partial(pcre_input& pi) const size_t match_partial(pcre_input& pi) const;
{
size_t length = pi.pi_length;
int rc;
do {
rc = pcre_exec(this->p_code,
this->p_code_extra.in(),
pi.get_string(),
length,
pi.pi_offset,
PCRE_PARTIAL,
nullptr,
0);
switch (rc) {
case 0:
case PCRE_ERROR_PARTIAL:
return length;
}
length -= 1;
} while (length > 0);
return length;
};
// #undef PCRE_STUDY_JIT_COMPILE // #undef PCRE_STUDY_JIT_COMPILE
#ifdef PCRE_STUDY_JIT_COMPILE #ifdef PCRE_STUDY_JIT_COMPILE
@ -678,7 +567,16 @@ public:
int p_name_len{0}; int p_name_len{0};
unsigned long p_options{0}; unsigned long p_options{0};
pcre_named_capture* p_named_entries{nullptr}; pcre_named_capture* p_named_entries{nullptr};
std::vector<pcre_context::capture> p_captures; std::vector<pcre_context::capture_t> p_captures;
};
template<int options = 0>
class pcrepp_with_options : public pcrepp {
public:
template<typename... Args>
pcrepp_with_options(Args... args) : pcrepp(args..., options)
{
}
}; };
#endif #endif

@ -98,18 +98,20 @@ main(int argc, char* argv[])
} }
{ {
pcre_context::capture cap(1, 4); pcre_context::capture_t cap(1, 4);
pcre_input pi("\0foo", 0, 4); pcre_input pi("\0foo", 0, 4);
assert("foo" == pi.get_substr(&cap)); assert("foo" == pi.get_substr(&cap));
} }
const char* empty_cap_regexes[] = {"foo (?:bar)", const char* empty_cap_regexes[] = {
"foo [(]", "foo (?:bar)",
"foo \\Q(bar)\\E", "foo [(]",
"(?i)", "foo \\Q(bar)\\E",
"(?i)",
NULL}; nullptr,
};
for (int lpc = 0; empty_cap_regexes[lpc]; lpc++) { for (int lpc = 0; empty_cap_regexes[lpc]; lpc++) {
pcrepp re(empty_cap_regexes[lpc]); pcrepp re(empty_cap_regexes[lpc]);

@ -48,7 +48,7 @@ class piper_proc {
public: public:
class error : public std::exception { class error : public std::exception {
public: public:
error(int err) : e_err(err){}; error(int err) : e_err(err) {}
int e_err; int e_err;
}; };
@ -72,15 +72,9 @@ public:
virtual ~piper_proc(); virtual ~piper_proc();
/** @return The file descriptor for the temporary file. */ /** @return The file descriptor for the temporary file. */
auto_fd get_fd() auto_fd get_fd() { return this->pp_fd.dup(); }
{
return this->pp_fd.dup();
};
pid_t get_child_pid() const pid_t get_child_pid() const { return this->pp_child; }
{
return this->pp_child;
};
private: private:
/** A file descriptor that refers to the temporary file. */ /** A file descriptor that refers to the temporary file. */

@ -121,12 +121,12 @@ void
rl_set_help() rl_set_help()
{ {
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
case LNM_SEARCH: { case ln_mode_t::SEARCH: {
lnav_data.ld_doc_source.replace_with(RE_HELP); lnav_data.ld_doc_source.replace_with(RE_HELP);
lnav_data.ld_example_source.replace_with(RE_EXAMPLE); lnav_data.ld_example_source.replace_with(RE_EXAMPLE);
break; break;
} }
case LNM_SQL: { case ln_mode_t::SQL: {
textview_curses& log_view = lnav_data.ld_views[LNV_LOG]; textview_curses& log_view = lnav_data.ld_views[LNV_LOG];
auto* lss = (logfile_sub_source*) log_view.get_sub_source(); auto* lss = (logfile_sub_source*) log_view.get_sub_source();
attr_line_t example_al; attr_line_t example_al;
@ -250,7 +250,7 @@ rl_change(readline_curses* rc)
.clear(); .clear();
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
case LNM_COMMAND: { case ln_mode_t::COMMAND: {
static std::string last_command; static std::string last_command;
static int generation = 0; static int generation = 0;
@ -302,7 +302,8 @@ rl_change(readline_curses* rc)
lnav_data.ld_bottom_source.set_prompt(LNAV_CMD_PROMPT); lnav_data.ld_bottom_source.set_prompt(LNAV_CMD_PROMPT);
lnav_data.ld_bottom_source.grep_error(""); lnav_data.ld_bottom_source.grep_error("");
} else if (args[0] == "config" && args.size() > 1) { } else if (args[0] == "config" && args.size() > 1) {
yajlpp_parse_context ypc("input", &lnav_config_handlers); static const auto INPUT_SRC = intern_string::lookup("input");
yajlpp_parse_context ypc(INPUT_SRC, &lnav_config_handlers);
ypc.set_path(args[1]).with_obj(lnav_config); ypc.set_path(args[1]).with_obj(lnav_config);
ypc.update_callbacks(); ypc.update_callbacks();
@ -363,7 +364,7 @@ rl_change(readline_curses* rc)
} }
break; break;
} }
case LNM_EXEC: { case ln_mode_t::EXEC: {
const auto line = rc->get_line_buffer(); const auto line = rc->get_line_buffer();
size_t name_end = line.find(' '); size_t name_end = line.find(' ');
const auto script_name = line.substr(0, name_end); const auto script_name = line.substr(0, name_end);
@ -406,18 +407,18 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
lnav_data.ld_user_message_source.clear(); lnav_data.ld_user_message_source.clear();
switch (mode) { switch (mode) {
case LNM_SEARCH: case ln_mode_t::SEARCH:
case LNM_SEARCH_FILTERS: case ln_mode_t::SEARCH_FILTERS:
case LNM_SEARCH_FILES: case ln_mode_t::SEARCH_FILES:
name = "$search"; name = "$search";
break; break;
case LNM_CAPTURE: case ln_mode_t::CAPTURE:
require(0); require(0);
name = "$capture"; name = "$capture";
break; break;
case LNM_COMMAND: { case ln_mode_t::COMMAND: {
lnav_data.ld_exec_context.ec_dry_run = true; lnav_data.ld_exec_context.ec_dry_run = true;
lnav_data.ld_preview_generation += 1; lnav_data.ld_preview_generation += 1;
@ -425,8 +426,8 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
.set_cylon(false) .set_cylon(false)
.clear(); .clear();
lnav_data.ld_preview_source.clear(); lnav_data.ld_preview_source.clear();
auto result auto result = execute_command(lnav_data.ld_exec_context,
= execute_command(lnav_data.ld_exec_context, rc->get_value()); rc->get_value().get_string());
if (result.isOk()) { if (result.isOk()) {
auto msg = result.unwrap(); auto msg = result.unwrap();
@ -450,8 +451,8 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
return; return;
} }
case LNM_SQL: { case ln_mode_t::SQL: {
term_val = trim(rc->get_value() + ";"); term_val = trim(rc->get_value().get_string() + ";");
if (!term_val.empty() && term_val[0] == '.') { if (!term_val.empty() && term_val[0] == '.') {
lnav_data.ld_bottom_source.grep_error(""); lnav_data.ld_bottom_source.grep_error("");
@ -462,11 +463,12 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize); auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
int retcode; int retcode;
retcode = sqlite3_prepare_v2(lnav_data.ld_db, retcode
rc->get_value().c_str(), = sqlite3_prepare_v2(lnav_data.ld_db,
-1, rc->get_value().get_string().c_str(),
stmt.out(), -1,
nullptr); stmt.out(),
nullptr);
if (retcode != SQLITE_OK) { if (retcode != SQLITE_OK) {
const char* errmsg = sqlite3_errmsg(lnav_data.ld_db); const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
@ -484,18 +486,18 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
return; return;
} }
case LNM_PAGING: case ln_mode_t::PAGING:
case LNM_FILTER: case ln_mode_t::FILTER:
case LNM_FILES: case ln_mode_t::FILES:
case LNM_EXEC: case ln_mode_t::EXEC:
case LNM_USER: case ln_mode_t::USER:
return; return;
} }
if (!complete) { if (!complete) {
tc->set_top(lnav_data.ld_search_start_line); tc->set_top(lnav_data.ld_search_start_line);
} }
tc->execute_search(rc->get_value()); tc->execute_search(rc->get_value().get_string());
} }
void void
@ -529,18 +531,18 @@ lnav_rl_abort(readline_curses* rc)
lnav_data.ld_bottom_source.grep_error(""); lnav_data.ld_bottom_source.grep_error("");
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
case LNM_SEARCH: case ln_mode_t::SEARCH:
tc->set_top(lnav_data.ld_search_start_line); tc->set_top(lnav_data.ld_search_start_line);
tc->revert_search(); tc->revert_search();
break; break;
case LNM_SQL: case ln_mode_t::SQL:
tc->reload_data(); tc->reload_data();
break; break;
default: default:
break; break;
} }
lnav_data.ld_rl_view->set_value(""); lnav_data.ld_rl_view->set_value("");
lnav_data.ld_mode = LNM_PAGING; lnav_data.ld_mode = ln_mode_t::PAGING;
} }
static void static void
@ -561,14 +563,14 @@ rl_callback_int(readline_curses* rc, bool is_alt)
tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"}); tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
lnav_data.ld_log_source.set_preview_sql_filter(nullptr); lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
auto new_mode = LNM_PAGING; auto new_mode = ln_mode_t::PAGING;
switch (lnav_data.ld_mode) { switch (lnav_data.ld_mode) {
case LNM_SEARCH_FILTERS: case ln_mode_t::SEARCH_FILTERS:
new_mode = LNM_FILTER; new_mode = ln_mode_t::FILTER;
break; break;
case LNM_SEARCH_FILES: case ln_mode_t::SEARCH_FILES:
new_mode = LNM_FILES; new_mode = ln_mode_t::FILES;
break; break;
default: default:
break; break;
@ -576,17 +578,17 @@ rl_callback_int(readline_curses* rc, bool is_alt)
auto old_mode = std::exchange(lnav_data.ld_mode, new_mode); auto old_mode = std::exchange(lnav_data.ld_mode, new_mode);
switch (old_mode) { switch (old_mode) {
case LNM_PAGING: case ln_mode_t::PAGING:
case LNM_FILTER: case ln_mode_t::FILTER:
case LNM_FILES: case ln_mode_t::FILES:
require(0); require(0);
break; break;
case LNM_COMMAND: { case ln_mode_t::COMMAND: {
rc->set_alt_value(""); rc->set_alt_value("");
ec.ec_source.top().s_content ec.ec_source.top().s_content
= fmt::format(FMT_STRING(":{}"), rc->get_value()); = fmt::format(FMT_STRING(":{}"), rc->get_value().get_string());
auto exec_res = execute_command(ec, rc->get_value()); auto exec_res = execute_command(ec, rc->get_value().get_string());
if (exec_res.isOk()) { if (exec_res.isOk()) {
rc->set_value(exec_res.unwrap()); rc->set_value(exec_res.unwrap());
} else { } else {
@ -607,16 +609,16 @@ rl_callback_int(readline_curses* rc, bool is_alt)
break; break;
} }
case LNM_USER: case ln_mode_t::USER:
rc->set_alt_value(""); rc->set_alt_value("");
ec.ec_local_vars.top()["value"] = rc->get_value(); ec.ec_local_vars.top()["value"] = rc->get_value().get_string();
rc->set_value(""); rc->set_value("");
break; break;
case LNM_SEARCH: case ln_mode_t::SEARCH:
case LNM_SEARCH_FILTERS: case ln_mode_t::SEARCH_FILTERS:
case LNM_SEARCH_FILES: case ln_mode_t::SEARCH_FILES:
case LNM_CAPTURE: case ln_mode_t::CAPTURE:
rl_search_internal(rc, old_mode, true); rl_search_internal(rc, old_mode, true);
if (!rc->get_value().empty()) { if (!rc->get_value().empty()) {
auto_mem<FILE> pfile(pclose); auto_mem<FILE> pfile(pclose);
@ -627,7 +629,8 @@ rl_callback_int(readline_curses* rc, bool is_alt)
pfile = sysclip::open(sysclip::type_t::FIND); pfile = sysclip::open(sysclip::type_t::FIND);
if (pfile.in() != nullptr) { if (pfile.in() != nullptr) {
fprintf(pfile, "%s", rc->get_value().c_str()); fmt::print(
pfile, FMT_STRING("{}"), rc->get_value().get_string());
} }
if (vl != -1_vl) { if (vl != -1_vl) {
tc->set_top(vl); tc->set_top(vl);
@ -660,15 +663,17 @@ rl_callback_int(readline_curses* rc, bool is_alt)
return true; return true;
}); });
} }
rc->set_value("search: " + rc->get_value()); rc->set_attr_value(
attr_line_t("search: ").append(rc->get_value()));
rc->set_alt_value(HELP_MSG_2( rc->set_alt_value(HELP_MSG_2(
n, N, "to move forward/backward through search results")); n, N, "to move forward/backward through search results"));
} }
break; break;
case LNM_SQL: { case ln_mode_t::SQL: {
ec.ec_source.top().s_content = rc->get_value(); ec.ec_source.top().s_content = rc->get_value();
auto result = execute_sql(ec, rc->get_value(), alt_msg); auto result
= execute_sql(ec, rc->get_value().get_string(), alt_msg);
db_label_source& dls = lnav_data.ld_db_row_source; db_label_source& dls = lnav_data.ld_db_row_source;
std::string prompt; std::string prompt;
@ -696,7 +701,7 @@ rl_callback_int(readline_curses* rc, bool is_alt)
break; break;
} }
case LNM_EXEC: { case ln_mode_t::EXEC: {
auto_mem<FILE> tmpout(fclose); auto_mem<FILE> tmpout(fclose);
tmpout = std::tmpfile(); tmpout = std::tmpfile();
@ -715,7 +720,7 @@ rl_callback_int(readline_curses* rc, bool is_alt)
exec_context::output_guard og( exec_context::output_guard og(
ec, "tmp", std::make_pair(tmpout.release(), fclose)); ec, "tmp", std::make_pair(tmpout.release(), fclose));
auto result = execute_file(ec, path_and_args) auto result = execute_file(ec, path_and_args.get_string())
.map(ok_prefix) .map(ok_prefix)
.orElse(err_to_ok) .orElse(err_to_ok)
.unwrap(); .unwrap();
@ -736,7 +741,7 @@ rl_callback_int(readline_curses* rc, bool is_alt)
snprintf(desc, snprintf(desc,
sizeof(desc), sizeof(desc),
"Output of %s (%s)", "Output of %s (%s)",
path_and_args.c_str(), path_and_args.get_string().c_str(),
timestamp); timestamp);
lnav_data.ld_active_files.fc_file_names[desc] lnav_data.ld_active_files.fc_file_names[desc]
.with_fd(std::move(fd_copy)) .with_fd(std::move(fd_copy))
@ -839,3 +844,22 @@ rl_completion_request(readline_curses* rc)
} }
}); });
} }
void
rl_focus(readline_curses* rc)
{
auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
.get_overlay_source();
fos->fos_contexts.emplace("", false, true);
}
void
rl_blur(readline_curses* rc)
{
auto fos = (field_overlay_source*) lnav_data.ld_views[LNV_LOG]
.get_overlay_source();
fos->fos_contexts.pop();
lnav_data.ld_preview_generation += 1;
}

@ -39,6 +39,8 @@ void rl_alt_callback(readline_curses* rc);
void rl_display_matches(readline_curses* rc); void rl_display_matches(readline_curses* rc);
void rl_display_next(readline_curses* rc); void rl_display_next(readline_curses* rc);
void rl_completion_request(readline_curses* rc); void rl_completion_request(readline_curses* rc);
void rl_focus(readline_curses* rc);
void rl_blur(readline_curses* rc);
extern const char* RE_HELP; extern const char* RE_HELP;
extern const char* RE_EXAMPLE; extern const char* RE_EXAMPLE;

@ -69,10 +69,13 @@ public:
help_text help = {}, help_text help = {},
prompt_func_t prompt = nullptr) noexcept prompt_func_t prompt = nullptr) noexcept
: c_name(name), c_func(func), c_help(std::move(help)), : c_name(name), c_func(func), c_help(std::move(help)),
c_prompt(prompt){}; c_prompt(prompt)
{
}
_command_t(command_func_t func) noexcept _command_t(command_func_t func) noexcept : c_name("anon"), c_func(func)
: c_name("anon"), c_func(func){}; {
}
} command_t; } command_t;
typedef std::map<std::string, command_t*> command_map_t; typedef std::map<std::string, command_t*> command_map_t;
@ -80,10 +83,7 @@ public:
command_map_t* commands = nullptr, command_map_t* commands = nullptr,
bool case_sensitive = true); bool case_sensitive = true);
const std::string& get_name() const const std::string& get_name() const { return this->rc_name; }
{
return this->rc_name;
};
void load(); void load();
@ -92,29 +92,26 @@ public:
void add_possibility(const std::string& type, const std::string& value) void add_possibility(const std::string& type, const std::string& value)
{ {
this->rc_possibilities[type].insert(value); this->rc_possibilities[type].insert(value);
}; }
void rem_possibility(const std::string& type, const std::string& value) void rem_possibility(const std::string& type, const std::string& value)
{ {
this->rc_possibilities[type].erase(value); this->rc_possibilities[type].erase(value);
}; }
void clear_possibilities(const std::string& type) void clear_possibilities(const std::string& type)
{ {
this->rc_possibilities[type].clear(); this->rc_possibilities[type].clear();
}; }
bool is_case_sensitive() const bool is_case_sensitive() const { return this->rc_case_sensitive; }
{
return this->rc_case_sensitive;
};
readline_context& set_append_character(int ch) readline_context& set_append_character(int ch)
{ {
this->rc_append_character = ch; this->rc_append_character = ch;
return *this; return *this;
}; }
int get_append_character() const int get_append_character() const
{ {
@ -125,26 +122,26 @@ public:
{ {
this->rc_highlighter = hl; this->rc_highlighter = hl;
return *this; return *this;
}; }
readline_context& set_quote_chars(const char* qc) readline_context& set_quote_chars(const char* qc)
{ {
this->rc_quote_chars = qc; this->rc_quote_chars = qc;
return *this; return *this;
}; }
readline_context& with_readline_var(char** var, const char* val) readline_context& with_readline_var(char** var, const char* val)
{ {
this->rc_vars.emplace_back(var, val); this->rc_vars.emplace_back(var, val);
return *this; return *this;
}; }
readline_highlighter_t get_highlighter() const readline_highlighter_t get_highlighter() const
{ {
return this->rc_highlighter; return this->rc_highlighter;
}; }
static int command_complete(int, int); static int command_complete(int, int);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save