diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 30a5ef42..b4a7780c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -238,12 +238,14 @@ add_library(diag STATIC column_namer.cc command_executor.cc curl_looper.cc + base/date_time_scanner.cc db_sub_source.cc elem_to_json.cc environ_vtab.cc extension-functions.cc field_overlay_source.cc file_collection.cc + file_format.cc file_vtab.cc files_sub_source.cc filter_observer.cc @@ -253,6 +255,7 @@ add_library(diag STATIC fstat_vtab.cc fts_fuzzy_match.cc grep_proc.cc + help_text.cc help_text_formatter.cc highlighter.cc hist_source.cc @@ -301,17 +304,21 @@ add_library(diag STATIC sysclip.cc pcrepp/pcrepp.cc piper_proc.cc + spectro_source.cc sql_util.cc state-extension-functions.cc styling.cc base/string_util.cc + string_attr_type.cc strnatcmp.c text_format.cc textfile_highlighters.cc textfile_sub_source.cc textview_curses.cc + base/time_util.cc time-extension-functions.cc timer.cc + unique_path.cc unique_path.hh view_curses.cc view_helpers.cc @@ -350,6 +357,7 @@ add_library(diag STATIC command_executor.hh column_namer.hh curl_looper.hh + base/date_time_scanner.hh doc_status_source.hh elem_to_json.hh base/enum_util.hh @@ -357,6 +365,7 @@ add_library(diag STATIC base/future_util.hh field_overlay_source.hh file_collection.hh + file_format.hh file_vtab.hh files_sub_source.hh filter_observer.hh @@ -378,6 +387,7 @@ add_library(diag STATIC log_data_helper.hh log_data_table.hh log_format.hh + log_format_ext.hh log_format_fwd.hh log_format_impls.cc log_gutter_source.hh @@ -386,6 +396,7 @@ add_library(diag STATIC logfile.hh logfile_fwd.hh logfile_stats.hh + base/math_util.hh optional.hpp papertrail_proc.hh plain_text_source.hh @@ -409,12 +420,15 @@ add_library(diag STATIC simdutf8check.h spectro_source.hh strong_int.hh + string_attr_type.hh sysclip.hh term_extra.hh termios_guard.hh text_format.hh textfile_highlighters.hh textfile_sub_source.hh + textview_curses.hh + textview_curses_fwd.hh time_T.hh timer.hh top_status_source.hh diff --git a/src/Makefile.am b/src/Makefile.am index 9e6b5309..6095cd16 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -269,6 +269,7 @@ noinst_HEADERS = \ environ_vtab.hh \ field_overlay_source.hh \ file_collection.hh \ + file_format.hh \ file_vtab.hh \ files_sub_source.hh \ filter_observer.hh \ @@ -298,6 +299,7 @@ noinst_HEADERS = \ log_data_helper.hh \ log_data_table.hh \ log_format.hh \ + log_format_ext.hh \ log_format_fwd.hh \ log_format_loader.hh \ log_gutter_source.hh \ @@ -340,6 +342,7 @@ noinst_HEADERS = \ sql_util.hh \ sqlite-extension-func.hh \ statusview_curses.hh \ + string_attr_type.hh \ strnatcmp.h \ strong_int.hh \ sysclip.hh \ @@ -349,6 +352,7 @@ noinst_HEADERS = \ textfile_highlighters.hh \ textfile_sub_source.hh \ textview_curses.hh \ + textview_curses_fwd.hh \ time_T.hh \ timer.hh \ top_status_source.hh \ @@ -403,6 +407,7 @@ libdiag_a_SOURCES = \ extension-functions.cc \ field_overlay_source.cc \ file_collection.cc \ + file_format.cc \ file_vtab.cc \ files_sub_source.cc \ filter_observer.cc \ @@ -412,6 +417,7 @@ libdiag_a_SOURCES = \ fs-extension-functions.cc \ fts_fuzzy_match.cc \ grep_proc.cc \ + help_text.cc \ help_text_formatter.cc \ highlighter.cc \ hist_source.cc \ @@ -449,9 +455,11 @@ libdiag_a_SOURCES = \ sequence_matcher.cc \ shared_buffer.cc \ shlex.cc \ + spectro_source.cc \ sqlite-extension-func.cc \ statusview_curses.cc \ string-extension-functions.cc \ + string_attr_type.cc \ styling.cc \ text_format.cc \ textfile_sub_source.cc \ @@ -464,6 +472,7 @@ libdiag_a_SOURCES = \ textfile_highlighters.cc \ textview_curses.cc \ time-extension-functions.cc \ + unique_path.cc \ view_curses.cc \ view_helpers.cc \ views_vtab.cc \ diff --git a/src/all_logs_vtab.cc b/src/all_logs_vtab.cc index f289fb1f..e60935d3 100644 --- a/src/all_logs_vtab.cc +++ b/src/all_logs_vtab.cc @@ -30,6 +30,7 @@ #include "config.h" #include "all_logs_vtab.hh" +#include "string_attr_type.hh" all_logs_vtab::all_logs_vtab() : log_vtab_impl(intern_string::lookup("all_logs")), @@ -57,7 +58,7 @@ void all_logs_vtab::extract(std::shared_ptr lf, uint64_t line_number, this->vi_attrs.clear(); format->annotate(line_number, line, this->vi_attrs, sub_values, false); - auto body = find_string_attr_range(this->vi_attrs, &textview_curses::SA_BODY); + auto body = find_string_attr_range(this->vi_attrs, &SA_BODY); if (body.lr_start == -1) { body.lr_start = 0; body.lr_end = line.length(); diff --git a/src/attr_line.hh b/src/attr_line.hh index 5d5e84c8..e0f577e5 100644 --- a/src/attr_line.hh +++ b/src/attr_line.hh @@ -40,6 +40,7 @@ #include "base/lnav_log.hh" #include "base/string_util.hh" #include "base/intern_string.hh" +#include "string_attr_type.hh" /** * Encapsulates a range in a string. @@ -145,16 +146,6 @@ typedef union { int64_t sav_int; } string_attr_value_t; -class string_attr_type { -public: - explicit string_attr_type(const char *name = nullptr) noexcept - : sat_name(name) { - }; - - const char *sat_name; -}; -typedef string_attr_type *string_attr_type_t; - struct string_attr { string_attr(const struct line_range &lr, string_attr_type_t type, void *val) : sa_range(lr), sa_type(type) { diff --git a/src/base/Makefile.am b/src/base/Makefile.am index 66313742..317e56de 100644 --- a/src/base/Makefile.am +++ b/src/base/Makefile.am @@ -7,6 +7,7 @@ AM_CPPFLAGS = \ noinst_LIBRARIES = libbase.a noinst_HEADERS = \ + date_time_scanner.hh \ enum_util.hh \ file_range.hh \ func_util.hh \ @@ -15,14 +16,17 @@ noinst_HEADERS = \ intern_string.hh \ is_utf8.hh \ lnav_log.hh \ + math_util.hh \ opt_util.hh \ result.h \ string_util.hh \ time_util.hh libbase_a_SOURCES = \ + date_time_scanner.cc \ humanize.cc \ intern_string.cc \ is_utf8.cc \ lnav_log.cc \ - string_util.cc + string_util.cc \ + time_util.cc diff --git a/src/base/date_time_scanner.cc b/src/base/date_time_scanner.cc new file mode 100644 index 00000000..8d22f693 --- /dev/null +++ b/src/base/date_time_scanner.cc @@ -0,0 +1,210 @@ +/** + * 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. + * + * @file date_time_scanner.cc + */ + +#include "config.h" + +#include "date_time_scanner.hh" +#include "ptimec.hh" + +size_t date_time_scanner::ftime(char *dst, size_t len, const exttm &tm) +{ + off_t off = 0; + + PTIMEC_FORMATS[this->dts_fmt_lock].pf_ffunc(dst, off, len, tm); + + return (size_t) off; +} + +bool next_format(const char * const fmt[], int &index, int &locked_index) +{ + bool retval = true; + + if (locked_index == -1) { + index += 1; + if (fmt[index] == nullptr) { + retval = false; + } + } + else if (index == locked_index) { + retval = false; + } + else { + index = locked_index; + } + + return retval; +} + +const char *date_time_scanner::scan(const char *time_dest, + size_t time_len, + const char * const time_fmt[], + struct exttm *tm_out, + struct timeval &tv_out, + bool convert_local) +{ + int curr_time_fmt = -1; + bool found = false; + const char *retval = nullptr; + + if (!time_fmt) { + time_fmt = PTIMEC_FORMAT_STR; + } + + while (next_format(time_fmt, + curr_time_fmt, + this->dts_fmt_lock)) { + *tm_out = this->dts_base_tm; + tm_out->et_flags = 0; + if (time_len > 1 && + time_dest[0] == '+' && + isdigit(time_dest[1])) { + char time_cp[time_len + 1]; + int gmt_int, off; + + retval = nullptr; + memcpy(time_cp, time_dest, time_len); + time_cp[time_len] = '\0'; + if (sscanf(time_cp, "+%d%n", &gmt_int, &off) == 1) { + time_t gmt = gmt_int; + + if (convert_local && this->dts_local_time) { + localtime_r(&gmt, &tm_out->et_tm); +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm_out->et_tm.tm_zone = nullptr; +#endif + tm_out->et_tm.tm_isdst = 0; + gmt = tm2sec(&tm_out->et_tm); + } + tv_out.tv_sec = gmt; + tv_out.tv_usec = 0; + tm_out->et_flags = ETF_DAY_SET|ETF_MONTH_SET|ETF_YEAR_SET|ETF_MACHINE_ORIENTED|ETF_EPOCH_TIME; + + this->dts_fmt_lock = curr_time_fmt; + this->dts_fmt_len = off; + retval = time_dest + off; + found = true; + break; + } + } + else if (time_fmt == PTIMEC_FORMAT_STR) { + ptime_func func = PTIMEC_FORMATS[curr_time_fmt].pf_func; + off_t off = 0; + +#ifdef HAVE_STRUCT_TM_TM_ZONE + if (!this->dts_keep_base_tz) { + tm_out->et_tm.tm_zone = nullptr; + } +#endif + if (func(tm_out, time_dest, off, time_len)) { + retval = &time_dest[off]; + + if (tm_out->et_tm.tm_year < 70) { + tm_out->et_tm.tm_year = 80; + } + if (convert_local && + (this->dts_local_time || tm_out->et_flags & ETF_EPOCH_TIME)) { + time_t gmt = tm2sec(&tm_out->et_tm); + + this->to_localtime(gmt, *tm_out); + } + tv_out.tv_sec = tm2sec(&tm_out->et_tm); + tv_out.tv_usec = tm_out->et_nsec / 1000; + secs2wday(tv_out, &tm_out->et_tm); + + this->dts_fmt_lock = curr_time_fmt; + this->dts_fmt_len = retval - time_dest; + + found = true; + break; + } + } + else { + off_t off = 0; + +#ifdef HAVE_STRUCT_TM_TM_ZONE + if (!this->dts_keep_base_tz) { + tm_out->et_tm.tm_zone = nullptr; + } +#endif + if (ptime_fmt(time_fmt[curr_time_fmt], tm_out, time_dest, off, time_len) && + (time_dest[off] == '.' || time_dest[off] == ',' || off == (off_t)time_len)) { + retval = &time_dest[off]; + if (tm_out->et_tm.tm_year < 70) { + tm_out->et_tm.tm_year = 80; + } + if (convert_local && + (this->dts_local_time || tm_out->et_flags & ETF_EPOCH_TIME)) { + time_t gmt = tm2sec(&tm_out->et_tm); + + this->to_localtime(gmt, *tm_out); +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm_out->et_tm.tm_zone = nullptr; +#endif + tm_out->et_tm.tm_isdst = 0; + } + + tv_out.tv_sec = tm2sec(&tm_out->et_tm); + tv_out.tv_usec = tm_out->et_nsec / 1000; + secs2wday(tv_out, &tm_out->et_tm); + + this->dts_fmt_lock = curr_time_fmt; + this->dts_fmt_len = retval - time_dest; + + found = true; + break; + } + } + } + + if (!found) { + retval = nullptr; + } + + if (retval != nullptr) { + /* Try to pull out the milli/micro-second value. */ + if (retval[0] == '.' || retval[0] == ',') { + off_t off = (retval - time_dest) + 1; + + if (ptime_f(tm_out, time_dest, off, time_len)) { + tv_out.tv_usec = tm_out->et_nsec / 1000; + this->dts_fmt_len += 7; + retval += 7; + } + else if (ptime_L(tm_out, time_dest, off, time_len)) { + tv_out.tv_usec = tm_out->et_nsec / 1000; + this->dts_fmt_len += 4; + retval += 4; + } + } + } + + return retval; +} diff --git a/src/base/date_time_scanner.hh b/src/base/date_time_scanner.hh new file mode 100644 index 00000000..2e8d1312 --- /dev/null +++ b/src/base/date_time_scanner.hh @@ -0,0 +1,154 @@ +/** + * 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. + * + * @file date_time_scanner.hh + */ + +#ifndef lnav_date_time_scanner_hh +#define lnav_date_time_scanner_hh + +#include +#include + +#include + +#include "time_util.hh" + +extern const char *std_time_fmt[]; + +struct date_time_scanner { + date_time_scanner() : dts_keep_base_tz(false), + dts_local_time(false), + dts_local_offset_cache(0), + dts_local_offset_valid(0), + dts_local_offset_expiry(0) { + this->clear(); + }; + + void clear() { + this->dts_base_time = 0; + memset(&this->dts_base_tm, 0, sizeof(this->dts_base_tm)); + this->dts_fmt_lock = -1; + this->dts_fmt_len = -1; + }; + + void unlock() { + this->dts_fmt_lock = -1; + this->dts_fmt_len = -1; + } + + void set_base_time(time_t base_time) { + this->dts_base_time = base_time; + localtime_r(&base_time, &this->dts_base_tm.et_tm); + }; + + /** + * Convert a timestamp to local time. + * + * Calling localtime_r is slow since it wants to lookup the timezone on + * every call, so we cache the result and only call it again if the + * requested time falls outside of a fifteen minute range. + */ + void to_localtime(time_t t, struct exttm &tm_out) { + if (t < (24 * 60 * 60)) { + // Don't convert and risk going past the epoch. + return; + } + + if (t < this->dts_local_offset_valid || + t >= this->dts_local_offset_expiry) { + time_t new_gmt; + + localtime_r(&t, &tm_out.et_tm); +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm_out.et_tm.tm_zone = NULL; +#endif + tm_out.et_tm.tm_isdst = 0; + + new_gmt = tm2sec(&tm_out.et_tm); + this->dts_local_offset_cache = t - new_gmt; + this->dts_local_offset_valid = t; + this->dts_local_offset_expiry = t + (EXPIRE_TIME - 1); + this->dts_local_offset_expiry -= + this->dts_local_offset_expiry % EXPIRE_TIME; + } + else { + time_t adjust_gmt = t - this->dts_local_offset_cache; + gmtime_r(&adjust_gmt, &tm_out.et_tm); + } + }; + + bool dts_keep_base_tz; + bool dts_local_time; + time_t dts_base_time; + struct exttm dts_base_tm; + int dts_fmt_lock; + int dts_fmt_len; + time_t dts_local_offset_cache; + time_t dts_local_offset_valid; + time_t dts_local_offset_expiry; + + static const int EXPIRE_TIME = 15 * 60; + + const char *scan(const char *time_src, + size_t time_len, + const char * const time_fmt[], + struct exttm *tm_out, + struct timeval &tv_out, + bool convert_local = true); + + size_t ftime(char *dst, size_t len, const struct exttm &tm);; + + bool convert_to_timeval(const char *time_src, + ssize_t time_len, + const char * const time_fmt[], + struct timeval &tv_out) { + struct exttm tm; + + if (time_len == -1) { + time_len = strlen(time_src); + } + if (this->scan(time_src, time_len, time_fmt, &tm, tv_out) != NULL) { + return true; + } + return false; + }; + + bool convert_to_timeval(const std::string &time_src, + struct timeval &tv_out) { + struct exttm tm; + + if (this->scan(time_src.c_str(), time_src.size(), + NULL, &tm, tv_out) != NULL) { + return true; + } + return false; + } +}; + +#endif diff --git a/src/base/math_util.hh b/src/base/math_util.hh new file mode 100644 index 00000000..fd5f7204 --- /dev/null +++ b/src/base/math_util.hh @@ -0,0 +1,63 @@ +/** + * 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_math_util_hh +#define lnav_math_util_hh + +#include + +#undef rounddown + +/** + * Round down a number based on a given granularity. + * + * @param + * @param step The granularity. + */ +template +inline int rounddown(Size size, Step step) +{ + return size - (size % step); +} + +inline int rounddown_offset(size_t size, int step, int offset) +{ + return size - ((size - offset) % step); +} + +inline size_t roundup_size(size_t size, int step) +{ + size_t retval = size + step; + + retval -= (retval % step); + + return retval; +} + +#endif diff --git a/src/base/string_util.cc b/src/base/string_util.cc index c1ab2b67..cd2620d4 100644 --- a/src/base/string_util.cc +++ b/src/base/string_util.cc @@ -29,6 +29,9 @@ #include "config.h" +#include +#include + #include "lnav_log.hh" #include "is_utf8.hh" #include "string_util.hh" @@ -102,3 +105,49 @@ void truncate_to(std::string &str, size_t len) str.erase(half_width, str.length() - (half_width * 2)); str.insert(half_width, ELLIPSIS); } + +bool is_url(const char *fn) +{ + static auto url_re = std::regex("^(file|https?|ftps?|scp|sftp):.*"); + + return std::regex_match(fn, url_re); +} + +size_t abbreviate_str(char *str, size_t len, size_t max_len) +{ + size_t last_start = 1; + + if (len < max_len) { + return len; + } + + for (size_t index = 0; index < len; index++) { + switch (str[index]) { + case '.': + case '-': + case '/': + case ':': + memmove(&str[last_start], &str[index], len - index); + len -= (index - last_start); + index = last_start + 1; + last_start = index + 1; + + if (len < max_len) { + return len; + } + break; + } + } + + return len; +} + +void split_ws(const std::string &str, std::vector &toks_out) +{ + std::stringstream ss(str); + std::string buf; + + while (ss >> buf) { + toks_out.push_back(buf); + } +} diff --git a/src/base/string_util.hh b/src/base/string_util.hh index 5c18efe4..60c53406 100644 --- a/src/base/string_util.hh +++ b/src/base/string_util.hh @@ -134,4 +134,10 @@ inline ssize_t utf8_char_to_byte_index(const std::string &str, ssize_t ch_index) return retval; } +bool is_url(const char *fn); + +size_t abbreviate_str(char *str, size_t len, size_t max_len); + +void split_ws(const std::string &str, std::vector &toks_out); + #endif diff --git a/src/base/time_util.cc b/src/base/time_util.cc new file mode 100644 index 00000000..96e2cd6f --- /dev/null +++ b/src/base/time_util.cc @@ -0,0 +1,184 @@ +/** + * 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. + * + * @file time_util.cc + */ + +#include "config.h" + +#include "time_util.hh" + +static time_t BAD_DATE = -1; + +time_t tm2sec(const struct tm *t) +{ + int year; + time_t days, secs; + const int dayoffset[12] = + { 306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275 }; + + year = t->tm_year; + + if (year < 70 || ((sizeof(time_t) <= 4) && (year >= 138))) { + return BAD_DATE; + } + + /* shift new year to 1st March in order to make leap year calc easy */ + + if (t->tm_mon < 2) { + year--; + } + + /* Find number of days since 1st March 1900 (in the Gregorian calendar). */ + + days = year * 365 + year / 4 - year / 100 + (year / 100 + 3) / 4; + days += dayoffset[t->tm_mon] + t->tm_mday - 1; + days -= 25508; /* 1 jan 1970 is 25508 days since 1 mar 1900 */ + + secs = ((days * 24 + t->tm_hour) * 60 + t->tm_min) * 60 + t->tm_sec; + + if (secs < 0) { + return BAD_DATE; + } /* must have overflowed */ + else { +#ifdef HAVE_STRUCT_TM_TM_ZONE + if (t->tm_zone) { + secs -= t->tm_gmtoff; + } +#endif + return secs; + } /* must be a valid time */ +} + +static const int SECSPERMIN = 60; +static const int SECSPERHOUR = 60 * SECSPERMIN; +static const int SECSPERDAY = 24 * SECSPERHOUR; +static const int YEAR_BASE = 1900; +static const int EPOCH_WDAY = 4; +static const int DAYSPERWEEK = 7; +static const int EPOCH_YEAR = 1970; + +#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) + +static const int year_lengths[2] = { + 365, + 366 +}; + +const unsigned short int mon_yday[2][13] = { + /* Normal years. */ + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + /* Leap years. */ + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } +}; + +void secs2wday(const struct timeval &tv, struct tm *res) +{ + long days, rem; + time_t lcltime; + + /* base decision about std/dst time on current time */ + lcltime = tv.tv_sec; + + days = ((long) lcltime) / SECSPERDAY; + rem = ((long) lcltime) % SECSPERDAY; + while (rem < 0) { + rem += SECSPERDAY; + --days; + } + + /* compute day of week */ + if ((res->tm_wday = ((EPOCH_WDAY + days) % DAYSPERWEEK)) < 0) + res->tm_wday += DAYSPERWEEK; +} + +struct tm *secs2tm(time_t *tim_p, struct tm *res) +{ + long days, rem; + time_t lcltime; + int y; + int yleap; + const unsigned short int *ip; + + /* base decision about std/dst time on current time */ + lcltime = *tim_p; + + days = ((long)lcltime) / SECSPERDAY; + rem = ((long)lcltime) % SECSPERDAY; + while (rem < 0) + { + rem += SECSPERDAY; + --days; + } + + /* compute hour, min, and sec */ + res->tm_hour = (int) (rem / SECSPERHOUR); + rem %= SECSPERHOUR; + res->tm_min = (int) (rem / SECSPERMIN); + res->tm_sec = (int) (rem % SECSPERMIN); + + /* compute day of week */ + if ((res->tm_wday = ((EPOCH_WDAY + days) % DAYSPERWEEK)) < 0) + res->tm_wday += DAYSPERWEEK; + + /* compute year & day of year */ + y = EPOCH_YEAR; + if (days >= 0) + { + for (;;) + { + yleap = isleap(y); + if (days < year_lengths[yleap]) + break; + y++; + days -= year_lengths[yleap]; + } + } + else + { + do + { + --y; + yleap = isleap(y); + days += year_lengths[yleap]; + } while (days < 0); + } + + res->tm_year = y - YEAR_BASE; + res->tm_yday = days; + ip = mon_yday[isleap(y)]; + for (y = 11; days < (long int) ip[y]; --y) + continue; + days -= ip[y]; + res->tm_mon = y; + res->tm_mday = days + 1; + + res->tm_isdst = 0; + + return (res); +} diff --git a/src/base/time_util.hh b/src/base/time_util.hh index 3684ae4b..e1e9e8fd 100644 --- a/src/base/time_util.hh +++ b/src/base/time_util.hh @@ -32,27 +32,96 @@ #include #include +#include + +struct tm *secs2tm(time_t *tim_p, struct tm *res); +/** + * Convert the time stored in a 'tm' struct into epoch time. + * + * @param t The 'tm' structure to convert to epoch time. + * @return The given time in seconds since the epoch. + */ +time_t tm2sec(const struct tm *t); +void secs2wday(const struct timeval &tv, struct tm *res); + +constexpr time_t MAX_TIME_T = 4000000000LL; + +enum exttm_bits_t { + ETB_YEAR_SET, + ETB_MONTH_SET, + ETB_DAY_SET, + ETB_MACHINE_ORIENTED, + ETB_EPOCH_TIME, +}; + +enum exttm_flags_t { + ETF_YEAR_SET = (1UL << ETB_YEAR_SET), + ETF_MONTH_SET = (1UL << ETB_MONTH_SET), + ETF_DAY_SET = (1UL << ETB_DAY_SET), + ETF_MACHINE_ORIENTED = (1UL << ETB_MACHINE_ORIENTED), + ETF_EPOCH_TIME = (1UL << ETB_EPOCH_TIME), +}; + +struct exttm { + struct tm et_tm; + int32_t et_nsec; + unsigned int et_flags; + long et_gmtoff; + + bool operator==(const exttm &other) const { + return memcmp(this, &other, sizeof(exttm)) == 0; + }; + + struct timeval to_timeval() const { + struct timeval retval; + + retval.tv_sec = tm2sec(&this->et_tm); + retval.tv_usec = this->et_nsec * 1000; + + return retval; + }; +}; inline bool operator<(const struct timeval &left, time_t right) { return left.tv_sec < right; -}; +} inline bool operator<(time_t left, const struct timeval &right) { return left < right.tv_sec; -}; +} inline bool operator<(const struct timeval &left, const struct timeval &right) { return left.tv_sec < right.tv_sec || ((left.tv_sec == right.tv_sec) && (left.tv_usec < right.tv_usec)); -}; +} inline bool operator!=(const struct timeval &left, const struct timeval &right) { return left.tv_sec != right.tv_sec || left.tv_usec != right.tv_usec; -}; +} + +typedef int64_t mstime_t; + +inline mstime_t getmstime() { + struct timeval tv; + + gettimeofday(&tv, nullptr); + + return tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL; +} + +inline time_t day_num(time_t ti) +{ + return ti / (24 * 60 * 60); +} + +inline time_t hour_num(time_t ti) +{ + return ti / (60 * 60); +} #endif diff --git a/src/big_array.hh b/src/big_array.hh index 5a5bedc4..b8db5705 100644 --- a/src/big_array.hh +++ b/src/big_array.hh @@ -34,7 +34,7 @@ #include -#include "lnav_util.hh" +#include "base/math_util.hh" template struct big_array { diff --git a/src/command_executor.cc b/src/command_executor.cc index dc5a9013..b2c9f0b2 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -37,6 +37,7 @@ #include "lnav.hh" #include "log_format_loader.hh" #include "shlex.hh" +#include "lnav_util.hh" #include "sql_util.hh" #include "command_executor.hh" @@ -827,3 +828,14 @@ void add_global_vars(exec_context &ec) ec.ec_global_vars[iter.first] = str; } } + +std::string exec_context::get_error_prefix() +{ + if (this->ec_source.size() <= 1) { + return "error: "; + } + + std::pair source = this->ec_source.top(); + + return fmt::format("{}:{}: error: ", source.first, source.second); +} diff --git a/src/command_executor.hh b/src/command_executor.hh index 3c43c9e6..c9e18ce0 100644 --- a/src/command_executor.hh +++ b/src/command_executor.hh @@ -63,15 +63,7 @@ struct exec_context { this->ec_output_stack.emplace_back(nonstd::nullopt); } - std::string get_error_prefix() { - if (this->ec_source.size() <= 1) { - return "error: "; - } - - std::pair source = this->ec_source.top(); - - return fmt::format("{}:{}: error: ", source.first, source.second); - } + std::string get_error_prefix(); template Result make_error( diff --git a/src/curl_looper.hh b/src/curl_looper.hh index 2888754b..9b0d92b7 100644 --- a/src/curl_looper.hh +++ b/src/curl_looper.hh @@ -66,7 +66,7 @@ public: #include "auto_mem.hh" #include "base/lnav_log.hh" -#include "lnav_util.hh" +#include "base/time_util.hh" class curl_request { public: diff --git a/src/data_parser.cc b/src/data_parser.cc index 81629a08..8d548d51 100644 --- a/src/data_parser.cc +++ b/src/data_parser.cc @@ -29,6 +29,7 @@ #include "config.h" +#include "spookyhash/SpookyV2.h" #include "data_parser.hh" using namespace std; diff --git a/src/data_parser.hh b/src/data_parser.hh index d66951cd..b9aa0a61 100644 --- a/src/data_parser.hh +++ b/src/data_parser.hh @@ -38,7 +38,6 @@ #include #include "base/lnav_log.hh" -#include "lnav_util.hh" #include "pcrepp/pcrepp.hh" #include "byte_array.hh" #include "data_scanner.hh" @@ -220,7 +219,7 @@ public: static FILE *TRACE_FILE; - typedef byte_array<2, uint64> schema_id_t; + typedef byte_array<2, uint64_t> schema_id_t; struct element; /* typedef std::list element_list_t; */ diff --git a/src/db_sub_source.cc b/src/db_sub_source.cc index 9724b4be..f17833a7 100644 --- a/src/db_sub_source.cc +++ b/src/db_sub_source.cc @@ -29,7 +29,7 @@ #include "config.h" -#include "lnav_util.hh" +#include "base/date_time_scanner.hh" #include "base/time_util.hh" #include "yajlpp/json_ptr.hh" diff --git a/src/db_sub_source.hh b/src/db_sub_source.hh index 4dc7e12e..c14ff05e 100644 --- a/src/db_sub_source.hh +++ b/src/db_sub_source.hh @@ -77,9 +77,9 @@ public: void clear(); - long column_name_to_index(const std::string &name) const;; + long column_name_to_index(const std::string &name) const; - int row_for_time(struct timeval time_bucket);; + int row_for_time(struct timeval time_bucket); struct timeval time_for_row(int row) { if ((row < 0) || (((size_t) row) >= this->dls_time_column.size())) { diff --git a/src/field_overlay_source.cc b/src/field_overlay_source.cc index 72e50eab..3d638856 100644 --- a/src/field_overlay_source.cc +++ b/src/field_overlay_source.cc @@ -29,6 +29,7 @@ #include "config.h" +#include "lnav_util.hh" #include "ansi_scrubber.hh" #include "vtab_module.hh" #include "relative_time.hh" @@ -523,7 +524,7 @@ void field_overlay_source::build_meta_line(const listview_curses &lv, const auto *tc = dynamic_cast(&lv); if (tc) { - const textview_curses::highlight_map_t &hm = tc->get_highlights(); + const auto &hm = tc->get_highlights(); auto hl_iter = hm.find({highlight_source_t::PREVIEW, "search"}); if (hl_iter != hm.end()) { diff --git a/src/file_collection.hh b/src/file_collection.hh index 0f081dc0..e68bcc74 100644 --- a/src/file_collection.hh +++ b/src/file_collection.hh @@ -42,7 +42,7 @@ #include "base/future_util.hh" #include "logfile_fwd.hh" #include "archive_manager.hh" -#include "lnav_util.hh" +#include "file_format.hh" struct scan_progress { std::list sp_extractions; diff --git a/src/file_format.cc b/src/file_format.cc new file mode 100644 index 00000000..f12ac328 --- /dev/null +++ b/src/file_format.cc @@ -0,0 +1,64 @@ +/** + * 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. + * + * @file file_format.hh + */ + +#include "config.h" + +#include "base/intern_string.hh" +#include "auto_fd.hh" +#include "lnav_util.hh" +#include "file_format.hh" +#include "archive_manager.hh" + +file_format_t detect_file_format(const ghc::filesystem::path &filename) +{ + if (archive_manager::is_archive(filename)) { + return file_format_t::FF_ARCHIVE; + } + + file_format_t retval = file_format_t::FF_UNKNOWN; + auto_fd fd; + + if ((fd = openp(filename, O_RDONLY)) != -1) { + char buffer[32]; + ssize_t rc; + + if ((rc = read(fd, buffer, sizeof(buffer))) > 0) { + static auto SQLITE3_HEADER = "SQLite format 3"; + auto header_frag = string_fragment(buffer, 0, rc); + + if (header_frag.startswith(SQLITE3_HEADER)) { + retval = file_format_t::FF_SQLITE_DB; + } + } + } + + return retval; +} diff --git a/src/file_format.hh b/src/file_format.hh new file mode 100644 index 00000000..772788bd --- /dev/null +++ b/src/file_format.hh @@ -0,0 +1,69 @@ +/** + * 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. + * + * @file file_format.hh + */ + +#ifndef lnav_file_format_hh +#define lnav_file_format_hh + +#include "fmt/format.h" +#include "ghc/filesystem.hpp" + +enum class file_format_t { + FF_UNKNOWN, + FF_SQLITE_DB, + FF_ARCHIVE, +}; + +file_format_t detect_file_format(const ghc::filesystem::path& filename); + +namespace fmt { +template<> +struct formatter : formatter { + template + auto format(file_format_t ff, FormatContext &ctx) + { + string_view name = "unknown"; + switch (ff) { + case file_format_t::FF_SQLITE_DB: + name = "SQLite Database"; + break; + case file_format_t::FF_ARCHIVE: + name = "Archive"; + break; + default: + break; + } + return formatter::format(name, ctx); + } +}; +} + + +#endif diff --git a/src/file_vtab.cc b/src/file_vtab.cc index d67dd9f3..2b427f45 100644 --- a/src/file_vtab.cc +++ b/src/file_vtab.cc @@ -33,6 +33,7 @@ #include #include "base/lnav_log.hh" +#include "logfile.hh" #include "file_vtab.hh" #include "session_data.hh" #include "vtab_module.hh" diff --git a/src/filter_observer.hh b/src/filter_observer.hh index 48140164..b1f80e8a 100644 --- a/src/filter_observer.hh +++ b/src/filter_observer.hh @@ -53,7 +53,7 @@ public: logfile::const_iterator ll_end, shared_buffer_ref &sbr); - void logline_eof(const logfile &lf);; + void logline_eof(const logfile &lf); bool excluded(uint32_t filter_in_mask, uint32_t filter_out_mask, size_t offset) const { diff --git a/src/filter_sub_source.cc b/src/filter_sub_source.cc index e42288a0..7a5b0d78 100644 --- a/src/filter_sub_source.cc +++ b/src/filter_sub_source.cc @@ -385,7 +385,7 @@ void filter_sub_source::rl_change(readline_curses *rc) lnav_data.ld_filter_help_status_source.fss_error .set_value("error: %s", errptr); } else { - textview_curses::highlight_map_t &hm = top_view->get_highlights(); + auto &hm = top_view->get_highlights(); highlighter hl(code.release()); int color; diff --git a/src/fs-extension-functions.cc b/src/fs-extension-functions.cc index acddfe78..fdc2338c 100644 --- a/src/fs-extension-functions.cc +++ b/src/fs-extension-functions.cc @@ -31,6 +31,7 @@ #include "config.h" +#include #include #include #include diff --git a/src/help_text.cc b/src/help_text.cc new file mode 100644 index 00000000..c34719ea --- /dev/null +++ b/src/help_text.cc @@ -0,0 +1,98 @@ +/** + * 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. + */ + +#include "config.h" + +#include "help_text.hh" + + +help_text &help_text::with_parameters( + const std::initializer_list ¶ms) noexcept +{ + this->ht_parameters = params; + for (auto ¶m : this->ht_parameters) { + param.ht_context = help_context_t::HC_PARAMETER; + } + return *this; +} + +help_text &help_text::with_parameter(const help_text &ht) noexcept +{ + this->ht_parameters.emplace_back(ht); + this->ht_parameters.back().ht_context = help_context_t::HC_PARAMETER; + return *this; +} + +help_text &help_text::with_result(const help_text &ht) noexcept +{ + this->ht_results.emplace_back(ht); + this->ht_results.back().ht_context = help_context_t::HC_RESULT; + return *this; +} + +help_text &help_text::with_examples( + const std::initializer_list &examples) noexcept +{ + this->ht_example = examples; + return *this; +} + +help_text &help_text::with_example(const help_example &example) noexcept +{ + this->ht_example.emplace_back(example); + return *this; +} + +help_text &help_text::with_enum_values( + const std::initializer_list &enum_values) noexcept +{ + this->ht_enum_values = enum_values; + return *this; +} + +help_text & +help_text::with_tags(const std::initializer_list &tags) noexcept +{ + this->ht_tags = tags; + return *this; +} + +help_text &help_text::with_opposites( + const std::initializer_list &opps) noexcept +{ + this->ht_opposites = opps; + return *this; +} + +void help_text::index_tags() +{ + for (const auto &tag: this->ht_tags) { + TAGGED.insert(std::make_pair(tag, this)); + } +} diff --git a/src/help_text.hh b/src/help_text.hh index 27712803..0a1a911e 100644 --- a/src/help_text.hh +++ b/src/help_text.hh @@ -129,35 +129,15 @@ struct help_text { return *this; } - help_text &with_parameters(const std::initializer_list ¶ms) noexcept { - this->ht_parameters = params; - for (auto ¶m : this->ht_parameters) { - param.ht_context = help_context_t::HC_PARAMETER; - } - return *this; - } + help_text &with_parameters(const std::initializer_list ¶ms) noexcept; - help_text &with_parameter(const help_text &ht) noexcept { - this->ht_parameters.emplace_back(ht); - this->ht_parameters.back().ht_context = help_context_t::HC_PARAMETER; - return *this; - }; + help_text &with_parameter(const help_text &ht) noexcept; - help_text &with_result(const help_text &ht) noexcept { - this->ht_results.emplace_back(ht); - this->ht_results.back().ht_context = help_context_t::HC_RESULT; - return *this; - }; + help_text &with_result(const help_text &ht) noexcept; - help_text &with_examples(const std::initializer_list &examples) noexcept { - this->ht_example = examples; - return *this; - } + help_text &with_examples(const std::initializer_list &examples) noexcept; - help_text &with_example(const help_example &example) noexcept { - this->ht_example.emplace_back(example); - return *this; - } + help_text &with_example(const help_example &example) noexcept; help_text &optional() noexcept { this->ht_nargs = help_nargs_t::HN_OPTIONAL; @@ -179,26 +159,13 @@ struct help_text { return *this; } - help_text &with_enum_values(const std::initializer_list &enum_values) noexcept { - this->ht_enum_values = enum_values; - return *this; - }; + help_text &with_enum_values(const std::initializer_list &enum_values) noexcept; - help_text &with_tags(const std::initializer_list &tags) noexcept { - this->ht_tags = tags; - return *this; - }; + help_text &with_tags(const std::initializer_list &tags) noexcept; - help_text &with_opposites(const std::initializer_list &opps) noexcept { - this->ht_opposites = opps; - return *this; - }; + help_text &with_opposites(const std::initializer_list &opps) noexcept; - void index_tags() { - for (const auto &tag: this->ht_tags) { - TAGGED.insert(std::make_pair(tag, this)); - } - }; + void index_tags(); static std::multimap TAGGED; }; diff --git a/src/hist_source.cc b/src/hist_source.cc index 01efe655..0b465eac 100644 --- a/src/hist_source.cc +++ b/src/hist_source.cc @@ -29,7 +29,7 @@ #include "config.h" -#include "lnav_util.hh" +#include "base/math_util.hh" #include "hist_source.hh" using namespace std; diff --git a/src/hotkeys.cc b/src/hotkeys.cc index 202d9f0c..1ca3cc1f 100644 --- a/src/hotkeys.cc +++ b/src/hotkeys.cc @@ -29,6 +29,7 @@ #include "config.h" +#include "base/math_util.hh" #include "lnav.hh" #include "bookmarks.hh" #include "sql_util.hh" @@ -44,6 +45,7 @@ #include "hotkeys.hh" #include "base/opt_util.hh" #include "shlex.hh" +#include "lnav_util.hh" using namespace std; diff --git a/src/line_buffer.cc b/src/line_buffer.cc index 316e96bc..be9aea7a 100644 --- a/src/line_buffer.cc +++ b/src/line_buffer.cc @@ -47,8 +47,8 @@ #include "simdutf8check.h" #endif +#include "base/math_util.hh" #include "base/is_utf8.hh" -#include "lnav_util.hh" #include "line_buffer.hh" #include "fmtlib/fmt/format.h" @@ -116,6 +116,14 @@ private: }; /* XXX END */ +static int32_t read_le32(const unsigned char *data) +{ + return ( + (data[0] << 0) | + (data[1] << 8) | + (data[2] << 16) | + (data[3] << 24)); +} #define Z_BUFSIZE 65536U #define SYNCPOINT_SIZE (1024 * 1024) diff --git a/src/listview_curses.cc b/src/listview_curses.cc index 04980e02..d517a17e 100644 --- a/src/listview_curses.cc +++ b/src/listview_curses.cc @@ -43,6 +43,11 @@ using namespace std; list_gutter_source listview_curses::DEFAULT_GUTTER_SOURCE; +listview_curses::listview_curses() + : lv_scroll(noop_func{}) +{ +} + void listview_curses::reload_data() { if (this->lv_source == nullptr) { diff --git a/src/listview_curses.hh b/src/listview_curses.hh index 37fbd5ee..1d8226f7 100644 --- a/src/listview_curses.hh +++ b/src/listview_curses.hh @@ -117,6 +117,8 @@ class listview_curses public: using action = std::function; + listview_curses(); + void set_title(const std::string &title) { this->lv_title = title; }; @@ -581,7 +583,7 @@ protected: list_data_source *lv_source{nullptr}; /*< The data source delegate. */ std::list lv_input_delegates; list_overlay_source *lv_overlay_source{nullptr}; - action lv_scroll{noop_func{}}; /*< The scroll action. */ + action lv_scroll; /*< The scroll action. */ WINDOW * lv_window{nullptr}; /*< The window that contains this view. */ unsigned int lv_x{0}; unsigned int lv_y{0}; /*< The y offset of this view. */ diff --git a/src/lnav.cc b/src/lnav.cc index 7fcd89f7..50f1afff 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -2706,7 +2706,7 @@ int main(int argc, char *argv[]) } struct line_range lr = find_string_attr_range( - rows[0].get_attrs(), &textview_curses::SA_ORIGINAL_LINE); + rows[0].get_attrs(), &SA_ORIGINAL_LINE); if (write(STDOUT_FILENO, lr.substr(rows[0].get_string()), lr.sublen(rows[0].get_string())) == -1 || write(STDOUT_FILENO, "\n", 1) == -1) { diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index c902a3e6..b67870e1 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -588,8 +588,7 @@ static void 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, &textview_curses::SA_ORIGINAL_LINE); + auto lr = find_string_attr_range(al_attrs, &SA_ORIGINAL_LINE); const auto& line_meta = find_string_attr(al_attrs, &logline::L_META); if (lr.lr_start > 1) { @@ -1141,7 +1140,7 @@ static Result com_highlight(exec_context &ec, string cmdline, ve } else if (args.size() > 1) { textview_curses *tc = *lnav_data.ld_view_stack.top(); - textview_curses::highlight_map_t &hm = tc->get_highlights(); + auto &hm = tc->get_highlights(); const char *errptr; auto_mem code; int eoff; @@ -1203,11 +1202,10 @@ static Result com_clear_highlight(exec_context &ec, string cmdli } else if (args.size() > 1 && args[1][0] != '$') { textview_curses *tc = *lnav_data.ld_view_stack.top(); - textview_curses::highlight_map_t &hm = tc->get_highlights(); - textview_curses::highlight_map_t::iterator hm_iter; + auto &hm = tc->get_highlights(); args[1] = remaining_args(cmdline, args); - hm_iter = hm.find({highlight_source_t::INTERACTIVE, args[1]}); + auto hm_iter = hm.find({highlight_source_t::INTERACTIVE, args[1]}); if (hm_iter == hm.end()) { return ec.make_error("highlight does not exist -- {}", args[1]); } @@ -1291,7 +1289,7 @@ static Result com_filter(exec_context &ec, string cmdline, vecto .set_value("Match preview for :filter-in only works if there are no other filters"); retval = ""; } else { - textview_curses::highlight_map_t &hm = tc->get_highlights(); + auto &hm = tc->get_highlights(); highlighter hl(code.release()); int color; @@ -1608,7 +1606,7 @@ static Result com_create_search_table(exec_context &ec, string c if (ec.ec_dry_run) { textview_curses *tc = &lnav_data.ld_views[LNV_LOG]; - textview_curses::highlight_map_t &hm = tc->get_highlights(); + auto &hm = tc->get_highlights(); view_colors &vc = view_colors::singleton(); highlighter hl(code.release()); diff --git a/src/lnav_util.cc b/src/lnav_util.cc index 13ba4d7d..70f20731 100644 --- a/src/lnav_util.cc +++ b/src/lnav_util.cc @@ -34,34 +34,16 @@ #include "config.h" #include -#include #include -#include -#include - -#include "auto_fd.hh" #include "lnav_util.hh" -#include "pcrepp/pcrepp.hh" #include "base/opt_util.hh" #include "base/result.h" #include "ansi_scrubber.hh" -#include "view_curses.hh" -#include "archive_manager.hh" #include "fmt/format.h" using namespace std; -bool is_url(const char *fn) -{ - static pcrepp url_re("^(file|https?|ftps?||scp|sftp):"); - - pcre_context_static<30> pc; - pcre_input pi(fn); - - return url_re.match(pc, pi); -} - std::string time_ago(time_t last_time, bool convert_local) { time_t delta, current_time = time(nullptr); @@ -177,358 +159,6 @@ bool change_to_parent_dir() return retval; } -void split_ws(const std::string &str, std::vector &toks_out) -{ - std::stringstream ss(str); - std::string buf; - - while (ss >> buf) { - toks_out.push_back(buf); - } -} - -file_format_t detect_file_format(const ghc::filesystem::path &filename) -{ - if (archive_manager::is_archive(filename)) { - return file_format_t::FF_ARCHIVE; - } - - file_format_t retval = file_format_t::FF_UNKNOWN; - auto_fd fd; - - if ((fd = openp(filename, O_RDONLY)) != -1) { - char buffer[32]; - ssize_t rc; - - if ((rc = read(fd, buffer, sizeof(buffer))) > 0) { - static auto SQLITE3_HEADER = "SQLite format 3"; - auto header_frag = string_fragment(buffer, 0, rc); - - if (header_frag.startswith(SQLITE3_HEADER)) { - retval = file_format_t::FF_SQLITE_DB; - } - } - } - - return retval; -} - -static time_t BAD_DATE = -1; - -time_t tm2sec(const struct tm *t) -{ - int year; - time_t days, secs; - const int dayoffset[12] = - { 306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275 }; - - year = t->tm_year; - - if (year < 70 || ((sizeof(time_t) <= 4) && (year >= 138))) { - return BAD_DATE; - } - - /* shift new year to 1st March in order to make leap year calc easy */ - - if (t->tm_mon < 2) { - year--; - } - - /* Find number of days since 1st March 1900 (in the Gregorian calendar). */ - - days = year * 365 + year / 4 - year / 100 + (year / 100 + 3) / 4; - days += dayoffset[t->tm_mon] + t->tm_mday - 1; - days -= 25508; /* 1 jan 1970 is 25508 days since 1 mar 1900 */ - - secs = ((days * 24 + t->tm_hour) * 60 + t->tm_min) * 60 + t->tm_sec; - - if (secs < 0) { - return BAD_DATE; - } /* must have overflowed */ - else { -#ifdef HAVE_STRUCT_TM_TM_ZONE - if (t->tm_zone) { - secs -= t->tm_gmtoff; - } -#endif - return secs; - } /* must be a valid time */ -} - -static const int SECSPERMIN = 60; -static const int SECSPERHOUR = 60 * SECSPERMIN; -static const int SECSPERDAY = 24 * SECSPERHOUR; -static const int YEAR_BASE = 1900; -static const int EPOCH_WDAY = 4; -static const int DAYSPERWEEK = 7; -static const int EPOCH_YEAR = 1970; - -#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) - -static const int year_lengths[2] = { - 365, - 366 -}; - -const unsigned short int mon_yday[2][13] = { - /* Normal years. */ - { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, - /* Leap years. */ - { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } -}; - -static void secs2wday(const struct timeval &tv, struct tm *res) -{ - long days, rem; - time_t lcltime; - - /* base decision about std/dst time on current time */ - lcltime = tv.tv_sec; - - days = ((long) lcltime) / SECSPERDAY; - rem = ((long) lcltime) % SECSPERDAY; - while (rem < 0) { - rem += SECSPERDAY; - --days; - } - - /* compute day of week */ - if ((res->tm_wday = ((EPOCH_WDAY + days) % DAYSPERWEEK)) < 0) - res->tm_wday += DAYSPERWEEK; -} - -struct tm *secs2tm(time_t *tim_p, struct tm *res) -{ - long days, rem; - time_t lcltime; - int y; - int yleap; - const unsigned short int *ip; - - /* base decision about std/dst time on current time */ - lcltime = *tim_p; - - days = ((long)lcltime) / SECSPERDAY; - rem = ((long)lcltime) % SECSPERDAY; - while (rem < 0) - { - rem += SECSPERDAY; - --days; - } - - /* compute hour, min, and sec */ - res->tm_hour = (int) (rem / SECSPERHOUR); - rem %= SECSPERHOUR; - res->tm_min = (int) (rem / SECSPERMIN); - res->tm_sec = (int) (rem % SECSPERMIN); - - /* compute day of week */ - if ((res->tm_wday = ((EPOCH_WDAY + days) % DAYSPERWEEK)) < 0) - res->tm_wday += DAYSPERWEEK; - - /* compute year & day of year */ - y = EPOCH_YEAR; - if (days >= 0) - { - for (;;) - { - yleap = isleap(y); - if (days < year_lengths[yleap]) - break; - y++; - days -= year_lengths[yleap]; - } - } - else - { - do - { - --y; - yleap = isleap(y); - days += year_lengths[yleap]; - } while (days < 0); - } - - res->tm_year = y - YEAR_BASE; - res->tm_yday = days; - ip = mon_yday[isleap(y)]; - for (y = 11; days < (long int) ip[y]; --y) - continue; - days -= ip[y]; - res->tm_mon = y; - res->tm_mday = days + 1; - - res->tm_isdst = 0; - - return (res); -} - -bool next_format(const char * const fmt[], int &index, int &locked_index) -{ - bool retval = true; - - if (locked_index == -1) { - index += 1; - if (fmt[index] == nullptr) { - retval = false; - } - } - else if (index == locked_index) { - retval = false; - } - else { - index = locked_index; - } - - return retval; -} - -const char *date_time_scanner::scan(const char *time_dest, - size_t time_len, - const char * const time_fmt[], - struct exttm *tm_out, - struct timeval &tv_out, - bool convert_local) -{ - int curr_time_fmt = -1; - bool found = false; - const char *retval = nullptr; - - if (!time_fmt) { - time_fmt = PTIMEC_FORMAT_STR; - } - - while (next_format(time_fmt, - curr_time_fmt, - this->dts_fmt_lock)) { - *tm_out = this->dts_base_tm; - tm_out->et_flags = 0; - if (time_len > 1 && - time_dest[0] == '+' && - isdigit(time_dest[1])) { - char time_cp[time_len + 1]; - int gmt_int, off; - - retval = nullptr; - memcpy(time_cp, time_dest, time_len); - time_cp[time_len] = '\0'; - if (sscanf(time_cp, "+%d%n", &gmt_int, &off) == 1) { - time_t gmt = gmt_int; - - if (convert_local && this->dts_local_time) { - localtime_r(&gmt, &tm_out->et_tm); -#ifdef HAVE_STRUCT_TM_TM_ZONE - tm_out->et_tm.tm_zone = nullptr; -#endif - tm_out->et_tm.tm_isdst = 0; - gmt = tm2sec(&tm_out->et_tm); - } - tv_out.tv_sec = gmt; - tv_out.tv_usec = 0; - tm_out->et_flags = ETF_DAY_SET|ETF_MONTH_SET|ETF_YEAR_SET|ETF_MACHINE_ORIENTED|ETF_EPOCH_TIME; - - this->dts_fmt_lock = curr_time_fmt; - this->dts_fmt_len = off; - retval = time_dest + off; - found = true; - break; - } - } - else if (time_fmt == PTIMEC_FORMAT_STR) { - ptime_func func = PTIMEC_FORMATS[curr_time_fmt].pf_func; - off_t off = 0; - -#ifdef HAVE_STRUCT_TM_TM_ZONE - if (!this->dts_keep_base_tz) { - tm_out->et_tm.tm_zone = nullptr; - } -#endif - if (func(tm_out, time_dest, off, time_len)) { - retval = &time_dest[off]; - - if (tm_out->et_tm.tm_year < 70) { - tm_out->et_tm.tm_year = 80; - } - if (convert_local && - (this->dts_local_time || tm_out->et_flags & ETF_EPOCH_TIME)) { - time_t gmt = tm2sec(&tm_out->et_tm); - - this->to_localtime(gmt, *tm_out); - } - tv_out.tv_sec = tm2sec(&tm_out->et_tm); - tv_out.tv_usec = tm_out->et_nsec / 1000; - secs2wday(tv_out, &tm_out->et_tm); - - this->dts_fmt_lock = curr_time_fmt; - this->dts_fmt_len = retval - time_dest; - - found = true; - break; - } - } - else { - off_t off = 0; - -#ifdef HAVE_STRUCT_TM_TM_ZONE - if (!this->dts_keep_base_tz) { - tm_out->et_tm.tm_zone = nullptr; - } -#endif - if (ptime_fmt(time_fmt[curr_time_fmt], tm_out, time_dest, off, time_len) && - (time_dest[off] == '.' || time_dest[off] == ',' || off == (off_t)time_len)) { - retval = &time_dest[off]; - if (tm_out->et_tm.tm_year < 70) { - tm_out->et_tm.tm_year = 80; - } - if (convert_local && - (this->dts_local_time || tm_out->et_flags & ETF_EPOCH_TIME)) { - time_t gmt = tm2sec(&tm_out->et_tm); - - this->to_localtime(gmt, *tm_out); -#ifdef HAVE_STRUCT_TM_TM_ZONE - tm_out->et_tm.tm_zone = nullptr; -#endif - tm_out->et_tm.tm_isdst = 0; - } - - tv_out.tv_sec = tm2sec(&tm_out->et_tm); - tv_out.tv_usec = tm_out->et_nsec / 1000; - secs2wday(tv_out, &tm_out->et_tm); - - this->dts_fmt_lock = curr_time_fmt; - this->dts_fmt_len = retval - time_dest; - - found = true; - break; - } - } - } - - if (!found) { - retval = nullptr; - } - - if (retval != nullptr) { - /* Try to pull out the milli/micro-second value. */ - if (retval[0] == '.' || retval[0] == ',') { - off_t off = (retval - time_dest) + 1; - - if (ptime_f(tm_out, time_dest, off, time_len)) { - tv_out.tv_usec = tm_out->et_nsec / 1000; - this->dts_fmt_len += 7; - retval += 7; - } - else if (ptime_L(tm_out, time_dest, off, time_len)) { - tv_out.tv_usec = tm_out->et_nsec / 1000; - this->dts_fmt_len += 4; - retval += 4; - } - } - } - - return retval; -} - template size_t strtonum(T &num_out, const char *string, size_t len) { @@ -592,35 +222,6 @@ bool read_file(const ghc::filesystem::path &filename, string &out) return false; } -size_t abbreviate_str(char *str, size_t len, size_t max_len) -{ - size_t last_start = 1; - - if (len < max_len) { - return len; - } - - for (size_t index = 0; index < len; index++) { - switch (str[index]) { - case '.': - case '-': - case '/': - case ':': - memmove(&str[last_start], &str[index], len - index); - len -= (index - last_start); - index = last_start + 1; - last_start = index + 1; - - if (len < max_len) { - return len; - } - break; - } - } - - return len; -} - Result, std::string> open_temp_file(const ghc::filesystem::path &pattern) { diff --git a/src/lnav_util.hh b/src/lnav_util.hh index c3077a93..73904d60 100644 --- a/src/lnav_util.hh +++ b/src/lnav_util.hh @@ -56,67 +56,10 @@ #include "fmt/format.h" #include "ghc/filesystem.hpp" -#undef rounddown - -/** - * Round down a number based on a given granularity. - * - * @param - * @param step The granularity. - */ -template -inline int rounddown(Size size, Step step) -{ - return size - (size % step); -} - -inline int rounddown_offset(size_t size, int step, int offset) -{ - return size - ((size - offset) % step); -} - -inline size_t roundup_size(size_t size, int step) -{ - size_t retval = size + step; - - retval -= (retval % step); - - return retval; -} - -inline int32_t read_le32(const unsigned char *data) -{ - return ( - (data[0] << 0) | - (data[1] << 8) | - (data[2] << 16) | - (data[3] << 24)); -} - -inline time_t day_num(time_t ti) -{ - return ti / (24 * 60 * 60); -} - -inline time_t hour_num(time_t ti) -{ - return ti / (60 * 60); -} - std::string time_ago(time_t last_time, bool convert_local = false); std::string precise_time_ago(const struct timeval &tv, bool convert_local = false); -typedef int64_t mstime_t; - -inline mstime_t getmstime() { - struct timeval tv; - - gettimeofday(&tv, NULL); - - return tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL; -} - #if SIZEOF_OFF_T == 8 #define FORMAT_OFF_T "%qd" #elif SIZEOF_OFF_T == 4 @@ -185,38 +128,6 @@ object_field_t object_field(UnaryFunction &func, bool change_to_parent_dir(); -void split_ws(const std::string &str, std::vector &toks_out); - -enum class file_format_t { - FF_UNKNOWN, - FF_SQLITE_DB, - FF_ARCHIVE, -}; - -file_format_t detect_file_format(const ghc::filesystem::path& filename); - -namespace fmt { -template<> -struct formatter : formatter { - template - auto format(file_format_t ff, FormatContext &ctx) - { - string_view name = "unknown"; - switch (ff) { - case file_format_t::FF_SQLITE_DB: - name = "SQLite Database"; - break; - case file_format_t::FF_ARCHIVE: - name = "Archive"; - break; - default: - break; - } - return formatter::format(name, ctx); - } -}; -} - bool next_format(const char * const fmt[], int &index, int &locked_index); namespace std { @@ -231,20 +142,10 @@ inline bool is_glob(const char *fn) strchr(fn, '[') != nullptr); }; -bool is_url(const char *fn); - std::string build_path(const std::vector &paths); bool read_file(const ghc::filesystem::path &path, std::string &out); -/** - * Convert the time stored in a 'tm' struct into epoch time. - * - * @param t The 'tm' structure to convert to epoch time. - * @return The given time in seconds since the epoch. - */ -time_t tm2sec(const struct tm *t); - inline time_t convert_log_time_to_local(time_t value) { struct tm tm; @@ -257,126 +158,6 @@ time_t convert_log_time_to_local(time_t value) { return tm2sec(&tm); } -struct tm *secs2tm(time_t *tim_p, struct tm *res); - -extern const char *std_time_fmt[]; - -struct date_time_scanner { - date_time_scanner() : dts_keep_base_tz(false), - dts_local_time(false), - dts_local_offset_cache(0), - dts_local_offset_valid(0), - dts_local_offset_expiry(0) { - this->clear(); - }; - - void clear() { - this->dts_base_time = 0; - memset(&this->dts_base_tm, 0, sizeof(this->dts_base_tm)); - this->dts_fmt_lock = -1; - this->dts_fmt_len = -1; - }; - - void unlock() { - this->dts_fmt_lock = -1; - this->dts_fmt_len = -1; - } - - void set_base_time(time_t base_time) { - this->dts_base_time = base_time; - localtime_r(&base_time, &this->dts_base_tm.et_tm); - }; - - /** - * Convert a timestamp to local time. - * - * Calling localtime_r is slow since it wants to lookup the timezone on - * every call, so we cache the result and only call it again if the - * requested time falls outside of a fifteen minute range. - */ - void to_localtime(time_t t, struct exttm &tm_out) { - if (t < (24 * 60 * 60)) { - // Don't convert and risk going past the epoch. - return; - } - - if (t < this->dts_local_offset_valid || - t >= this->dts_local_offset_expiry) { - time_t new_gmt; - - localtime_r(&t, &tm_out.et_tm); -#ifdef HAVE_STRUCT_TM_TM_ZONE - tm_out.et_tm.tm_zone = NULL; -#endif - tm_out.et_tm.tm_isdst = 0; - - new_gmt = tm2sec(&tm_out.et_tm); - this->dts_local_offset_cache = t - new_gmt; - this->dts_local_offset_valid = t; - this->dts_local_offset_expiry = t + (EXPIRE_TIME - 1); - this->dts_local_offset_expiry -= - this->dts_local_offset_expiry % EXPIRE_TIME; - } - else { - time_t adjust_gmt = t - this->dts_local_offset_cache; - gmtime_r(&adjust_gmt, &tm_out.et_tm); - } - }; - - bool dts_keep_base_tz; - bool dts_local_time; - time_t dts_base_time; - struct exttm dts_base_tm; - int dts_fmt_lock; - int dts_fmt_len; - time_t dts_local_offset_cache; - time_t dts_local_offset_valid; - time_t dts_local_offset_expiry; - - static const int EXPIRE_TIME = 15 * 60; - - const char *scan(const char *time_src, - size_t time_len, - const char * const time_fmt[], - struct exttm *tm_out, - struct timeval &tv_out, - bool convert_local = true); - - size_t ftime(char *dst, size_t len, const struct exttm &tm) { - off_t off = 0; - - PTIMEC_FORMATS[this->dts_fmt_lock].pf_ffunc(dst, off, len, tm); - - return (size_t) off; - }; - - bool convert_to_timeval(const char *time_src, - ssize_t time_len, - const char * const time_fmt[], - struct timeval &tv_out) { - struct exttm tm; - - if (time_len == -1) { - time_len = strlen(time_src); - } - if (this->scan(time_src, time_len, time_fmt, &tm, tv_out) != NULL) { - return true; - } - return false; - }; - - bool convert_to_timeval(const std::string &time_src, - struct timeval &tv_out) { - struct exttm tm; - - if (this->scan(time_src.c_str(), time_src.size(), - NULL, &tm, tv_out) != NULL) { - return true; - } - return false; - } -}; - template size_t strtonum(T &num_out, const char *data, size_t len); @@ -427,8 +208,6 @@ inline void rusageadd(const struct rusage &left, const struct rusage &right, str diff_out.ru_nivcsw = left.ru_nivcsw + right.ru_nivcsw; } -size_t abbreviate_str(char *str, size_t len, size_t max_len); - inline int statp(const ghc::filesystem::path &path, struct stat *buf) { return stat(path.c_str(), buf); } diff --git a/src/log_actions.cc b/src/log_actions.cc index 96f2fbdf..26e9cd91 100644 --- a/src/log_actions.cc +++ b/src/log_actions.cc @@ -30,6 +30,7 @@ #include "config.h" #include "lnav.hh" +#include "lnav_util.hh" #include "log_actions.hh" using namespace std; @@ -138,7 +139,7 @@ static string execute_action(log_data_helper &ldh, action.ad_cmdline[0].c_str()); lnav_data.ld_active_files.fc_file_names[desc] .with_fd(pp->get_fd()); - lnav_data.ld_files_to_front.push_back({ desc, 0 }); + lnav_data.ld_files_to_front.emplace_back( desc, 0 ); } return ""; diff --git a/src/log_data_helper.hh b/src/log_data_helper.hh index 7fe5b673..d091e922 100644 --- a/src/log_data_helper.hh +++ b/src/log_data_helper.hh @@ -103,7 +103,7 @@ public: this->ldh_file->read_full_message(ll, this->ldh_msg); format->annotate(this->ldh_line_index, this->ldh_msg, sa, this->ldh_line_values, false); - body = find_string_attr_range(sa, &textview_curses::SA_BODY); + body = find_string_attr_range(sa, &SA_BODY); if (body.lr_start == -1) { body.lr_start = this->ldh_msg.length(); body.lr_end = this->ldh_msg.length(); diff --git a/src/log_data_table.hh b/src/log_data_table.hh index aa39a2d3..9ffee51c 100644 --- a/src/log_data_table.hh +++ b/src/log_data_table.hh @@ -77,7 +77,7 @@ public: this->ldt_parent_column_count = cols.size(); lf->read_full_message(lf->begin() + cl_copy, line); format->annotate(cl_copy, line, sa, line_values, false); - body = find_string_attr_range(sa, &textview_curses::SA_BODY); + body = find_string_attr_range(sa, &SA_BODY); if (body.lr_end == -1) { this->ldt_schema_id.clear(); return; @@ -166,7 +166,7 @@ public: sa, line_values, false); - body = find_string_attr_range(sa, &textview_curses::SA_BODY); + body = find_string_attr_range(sa, &SA_BODY); if (body.lr_end == -1) { return false; } diff --git a/src/log_format.cc b/src/log_format.cc index 4640f641..c3b2fc43 100644 --- a/src/log_format.cc +++ b/src/log_format.cc @@ -38,11 +38,12 @@ #include "yajlpp/yajlpp.hh" #include "yajlpp/yajlpp_def.hh" #include "sql_util.hh" -#include "log_format.hh" +#include "log_format_ext.hh" #include "log_vtab_impl.hh" #include "ptimec.hh" #include "log_search_table.hh" #include "command_executor.hh" +#include "lnav_util.hh" using namespace std; @@ -894,7 +895,7 @@ void external_log_format::annotate(uint64_t line_number, shared_buffer_ref &line // A continued line still needs a body. lr.lr_start = 0; lr.lr_end = line.length(); - sa.emplace_back(lr, &textview_curses::SA_BODY); + sa.emplace_back(lr, &SA_BODY); return; } @@ -932,7 +933,7 @@ void external_log_format::annotate(uint64_t line_number, shared_buffer_ref &line lr.lr_start = line.length(); lr.lr_end = line.length(); } - sa.emplace_back(lr, &textview_curses::SA_BODY); + sa.emplace_back(lr, &SA_BODY); for (size_t lpc = 0; lpc < pat.p_value_by_index.size(); lpc++) { const indexed_value_def &ivd = pat.p_value_by_index[lpc]; @@ -1273,7 +1274,7 @@ void external_log_format::get_subline(const logline &ll, shared_buffer_ref &sbr, } else if (lv_iter->lv_name == this->elf_body_field) { this->jlf_line_attrs.emplace_back( - lr, &textview_curses::SA_BODY); + lr, &SA_BODY); } else if (lv_iter->lv_name == this->elf_opid_field) { this->jlf_line_attrs.emplace_back( @@ -2053,7 +2054,7 @@ public: this->vi_attrs.clear(); format->annotate(cl, line, this->vi_attrs, values, false); - this->elt_container_body = find_string_attr_range(this->vi_attrs, &textview_curses::SA_BODY); + this->elt_container_body = find_string_attr_range(this->vi_attrs, &SA_BODY); if (!this->elt_container_body.is_valid()) { return false; } @@ -2118,6 +2119,32 @@ log_vtab_impl *external_log_format::get_vtab_impl() const return new external_log_table(*this); } +std::unique_ptr external_log_format::specialized(int fmt_lock) +{ + auto retval = std::make_unique(*this); + + retval->lf_specialized = true; + this->lf_pattern_locks.clear(); + if (fmt_lock != -1) { + retval->lf_pattern_locks.emplace_back(0, fmt_lock); + } + + if (this->elf_type == ELF_TYPE_JSON) { + this->jlf_parse_context = std::make_shared(this->elf_name.to_string()); + this->jlf_yajl_handle.reset(yajl_alloc( + &this->jlf_parse_context->ypc_callbacks, + nullptr, + this->jlf_parse_context.get())); + yajl_config(this->jlf_yajl_handle.in(), yajl_dont_validate_strings, 1); + this->jlf_cached_line.reserve(16 * 1024); + } + + this->lf_value_stats.clear(); + this->lf_value_stats.resize(this->elf_numeric_value_defs.size()); + + return retval; +} + int log_format::pattern_index_for_line(uint64_t line_number) const { auto iter = lower_bound(this->lf_pattern_locks.cbegin(), @@ -2135,6 +2162,12 @@ int log_format::pattern_index_for_line(uint64_t line_number) const return iter->pfl_pat_index; } +std::string log_format::get_pattern_name(uint64_t line_number) const +{ + int pat_index = this->pattern_index_for_line(line_number); + return fmt::format("builtin ({})", pat_index); +} + log_format::pattern_for_lines::pattern_for_lines( uint32_t pfl_line, uint32_t pfl_pat_index) : pfl_line(pfl_line), pfl_pat_index(pfl_pat_index) diff --git a/src/log_format.hh b/src/log_format.hh index 11d52bef..35b801ac 100644 --- a/src/log_format.hh +++ b/src/log_format.hh @@ -53,9 +53,8 @@ #include "pcrepp/pcrepp.hh" #include "yajlpp/yajlpp.hh" #include "base/lnav_log.hh" -#include "lnav_util.hh" +#include "base/date_time_scanner.hh" #include "byte_array.hh" -#include "view_curses.hh" #include "base/intern_string.hh" #include "shared_buffer.hh" #include "highlighter.hh" @@ -310,14 +309,7 @@ public: }; }; - log_format() : lf_mod_index(0), - lf_timestamp_field(intern_string::lookup("timestamp", -1)), - lf_timestamp_flags(0), - lf_is_self_describing(false), - lf_time_ordered(true) { - }; - - virtual ~log_format() { }; + virtual ~log_format() = default; virtual void clear() { @@ -420,12 +412,7 @@ public: void check_for_new_year(std::vector &dst, exttm log_tv, timeval timeval1); - virtual std::string get_pattern_name(uint64_t line_number) const { - int pat_index = this->pattern_index_for_line(line_number); - char name[32]; - snprintf(name, sizeof(name), "builtin (%d)", pat_index); - return name; - }; + virtual std::string get_pattern_name(uint64_t line_number) const; virtual std::string get_pattern_regex(uint64_t line_number) const { return ""; @@ -448,17 +435,17 @@ public: int pattern_index_for_line(uint64_t line_number) const; - uint8_t lf_mod_index; + uint8_t lf_mod_index{0}; date_time_scanner lf_date_time; std::vector lf_pattern_locks; - intern_string_t lf_timestamp_field; + intern_string_t lf_timestamp_field{intern_string::lookup("timestamp", -1)}; std::vector lf_timestamp_format; - unsigned int lf_timestamp_flags; + unsigned int lf_timestamp_flags{0}; std::map lf_action_defs; std::vector lf_value_stats; std::vector lf_highlighters; - bool lf_is_self_describing; - bool lf_time_ordered; + bool lf_is_self_describing{false}; + bool lf_time_ordered{true}; bool lf_specialized{false}; protected: static std::vector lf_root_formats; @@ -487,430 +474,4 @@ protected: ...); }; -class module_format; - -class external_log_format : public log_format { - -public: - struct sample { - sample() : s_level(LEVEL_UNKNOWN) {}; - - std::string s_line; - log_level_t s_level; - }; - - struct value_def { - intern_string_t vd_name; - logline_value::kind_t vd_kind{logline_value::VALUE_UNKNOWN}; - std::string vd_collate; - bool vd_identifier{false}; - bool vd_foreign_key{false}; - intern_string_t vd_unit_field; - std::map vd_unit_scaling; - int vd_column{-1}; - ssize_t vd_values_index{-1}; - bool vd_hidden{false}; - bool vd_user_hidden{false}; - bool vd_internal{false}; - std::vector vd_action_list; - std::string vd_rewriter; - std::string vd_description; - }; - - struct indexed_value_def { - indexed_value_def(int index = -1, - int unit_index = -1, - std::shared_ptr vd = nullptr) - : ivd_index(index), - ivd_unit_field_index(unit_index), - ivd_value_def(std::move(vd)) { - } - - int ivd_index; - int ivd_unit_field_index; - std::shared_ptr ivd_value_def; - - bool operator<(const indexed_value_def &rhs) const { - return this->ivd_index < rhs.ivd_index; - } - }; - - struct pattern { - std::string p_config_path; - std::string p_string; - pcrepp *p_pcre{nullptr}; - std::vector p_value_by_index; - std::vector p_numeric_value_indexes; - int p_timestamp_field_index{-1}; - int p_level_field_index{-1}; - int p_module_field_index{-1}; - int p_opid_field_index{-1}; - int p_body_field_index{-1}; - int p_timestamp_end{-1}; - bool p_module_format{false}; - }; - - struct level_pattern { - std::string lp_regex; - pcrepp *lp_pcre{nullptr}; - }; - - external_log_format(const intern_string_t name) - : elf_file_pattern(".*"), - elf_filename_pcre(NULL), - elf_column_count(0), - elf_timestamp_divisor(1.0), - elf_level_field(intern_string::lookup("level", -1)), - elf_body_field(intern_string::lookup("body", -1)), - elf_multiline(true), - elf_container(false), - elf_has_module_format(false), - elf_builtin_format(false), - elf_type(ELF_TYPE_TEXT), - jlf_hide_extra(false), - jlf_cached_offset(-1), - jlf_yajl_handle(yajl_free), - elf_name(name) { - this->jlf_line_offsets.reserve(128); - }; - - const intern_string_t get_name() const { - return this->elf_name; - }; - - bool match_name(const std::string &filename) { - pcre_context_static<10> pc; - pcre_input pi(filename); - - return this->elf_filename_pcre->match(pc, pi); - }; - - scan_result_t scan(logfile &lf, - std::vector &dst, - const line_info &offset, - shared_buffer_ref &sbr); - - bool scan_for_partial(shared_buffer_ref &sbr, size_t &len_out); - - void annotate(uint64_t line_number, shared_buffer_ref &line, string_attrs_t &sa, - std::vector &values, bool annotate_module = true) const; - - void rewrite(exec_context &ec, - shared_buffer_ref &line, - string_attrs_t &sa, - std::string &value_out); - - void build(std::vector &errors); - - void register_vtabs(log_vtab_manager *vtab_manager, - std::vector &errors); - - bool match_samples(const std::vector &samples) const; - - bool hide_field(const intern_string_t field_name, bool val) { - auto vd_iter = this->elf_value_defs.find(field_name); - - if (vd_iter == this->elf_value_defs.end()) { - return false; - } - - vd_iter->second->vd_user_hidden = val; - return true; - }; - - std::unique_ptr specialized(int fmt_lock) { - auto retval = std::make_unique(*this); - - retval->lf_specialized = true; - this->lf_pattern_locks.clear(); - if (fmt_lock != -1) { - retval->lf_pattern_locks.emplace_back(0, fmt_lock); - } - - if (this->elf_type == ELF_TYPE_JSON) { - this->jlf_parse_context = std::make_shared(this->elf_name.to_string()); - this->jlf_yajl_handle.reset(yajl_alloc( - &this->jlf_parse_context->ypc_callbacks, - nullptr, - this->jlf_parse_context.get())); - yajl_config(this->jlf_yajl_handle.in(), yajl_dont_validate_strings, 1); - this->jlf_cached_line.reserve(16 * 1024); - } - - this->lf_value_stats.clear(); - this->lf_value_stats.resize(this->elf_numeric_value_defs.size()); - - return retval; - }; - - const logline_value_stats *stats_for_value(const intern_string_t &name) const { - const logline_value_stats *retval = nullptr; - - for (size_t lpc = 0; lpc < this->elf_numeric_value_defs.size(); lpc++) { - value_def &vd = *this->elf_numeric_value_defs[lpc]; - - if (vd.vd_name == name) { - retval = &this->lf_value_stats[lpc]; - break; - } - } - - return retval; - }; - - void get_subline(const logline &ll, shared_buffer_ref &sbr, bool full_message); - - log_vtab_impl *get_vtab_impl() const; - - const std::vector *get_actions(const logline_value &lv) const { - const std::vector *retval = nullptr; - - const auto iter = this->elf_value_defs.find(lv.lv_name); - if (iter != this->elf_value_defs.end()) { - retval = &iter->second->vd_action_list; - } - - return retval; - }; - - std::set get_source_path() const { - return this->elf_source_path; - }; - - enum json_log_field { - JLF_CONSTANT, - JLF_VARIABLE - }; - - struct json_format_element { - enum class align_t { - LEFT, - RIGHT, - }; - - enum class overflow_t { - ABBREV, - TRUNCATE, - DOTDOT, - }; - - enum class transform_t { - NONE, - UPPERCASE, - LOWERCASE, - CAPITALIZE, - }; - - json_format_element() - : 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; - intern_string_t jfe_value; - std::string jfe_default_value; - long long jfe_min_width; - long long jfe_max_width; - align_t jfe_align; - overflow_t jfe_overflow; - transform_t jfe_text_transform; - std::string jfe_ts_format; - }; - - struct json_field_cmp { - json_field_cmp(json_log_field type, - const intern_string_t name) - : jfc_type(type), jfc_field_name(name) { - }; - - bool operator()(const json_format_element &jfe) const { - return (this->jfc_type == jfe.jfe_type && - this->jfc_field_name == jfe.jfe_value); - }; - - json_log_field jfc_type; - const intern_string_t jfc_field_name; - }; - - struct highlighter_def { - highlighter_def() : hd_underline(false), hd_blink(false) { - } - - std::string hd_pattern; - std::string hd_color; - std::string hd_background_color; - bool hd_underline; - bool hd_blink; - }; - - long value_line_count(const intern_string_t ist, - bool top_level, - const unsigned char *str = nullptr, - 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_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 { - const auto iter = this->elf_value_defs.find(ist); - - return iter != this->elf_value_defs.end(); - }; - - std::string get_pattern_name(uint64_t line_number) const { - if (this->elf_type != ELF_TYPE_TEXT) { - return "structured"; - } - int pat_index = this->pattern_index_for_line(line_number); - return this->elf_pattern_order[pat_index]->p_config_path; - } - - std::string get_pattern_regex(uint64_t line_number) const { - if (this->elf_type != ELF_TYPE_TEXT) { - return ""; - } - int pat_index = this->pattern_index_for_line(line_number); - return this->elf_pattern_order[pat_index]->p_string; - } - - log_level_t convert_level(const pcre_input &pi, 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; - } - - typedef std::map mod_map_t; - static mod_map_t MODULE_FORMATS; - static std::vector GRAPH_ORDERED_FORMATS; - - std::set elf_source_path; - std::list elf_collision; - std::string elf_file_pattern; - pcrepp *elf_filename_pcre; - std::map> elf_patterns; - std::vector> elf_pattern_order; - std::vector elf_samples; - std::unordered_map> - elf_value_defs; - std::vector> elf_value_def_order; - std::vector> elf_numeric_value_defs; - int elf_column_count; - double elf_timestamp_divisor; - intern_string_t elf_level_field; - intern_string_t elf_body_field; - intern_string_t elf_module_id_field; - intern_string_t elf_opid_field; - std::map elf_level_patterns; - std::vector > elf_level_pairs; - bool elf_multiline; - bool elf_container; - bool elf_has_module_format; - bool elf_builtin_format; - std::vector > elf_search_tables; - std::map elf_highlighter_patterns; - - enum elf_type_t { - ELF_TYPE_TEXT, - ELF_TYPE_JSON, - ELF_TYPE_CSV, - }; - - elf_type_t elf_type; - - void json_append_to_cache(const char *value, ssize_t len) { - size_t old_size = this->jlf_cached_line.size(); - if (len == -1) { - len = strlen(value); - } - this->jlf_cached_line.resize(old_size + len); - memcpy(&(this->jlf_cached_line[old_size]), value, len); - }; - - void json_append_to_cache(ssize_t len) { - size_t old_size = this->jlf_cached_line.size(); - this->jlf_cached_line.resize(old_size + len); - memset(&this->jlf_cached_line[old_size], ' ', len); - }; - - void json_append(const 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); - } - } - }; - - bool jlf_hide_extra; - std::vector jlf_line_format; - int jlf_line_format_init_count{0}; - std::vector jlf_line_values; - - off_t jlf_cached_offset; - bool jlf_cached_full{false}; - std::vector jlf_line_offsets; - shared_buffer jlf_share_manager; - std::vector jlf_cached_line; - string_attrs_t jlf_line_attrs; - std::shared_ptr jlf_parse_context; - auto_mem jlf_yajl_handle; -private: - const intern_string_t elf_name; - - static uint8_t module_scan(const pcre_input &pi, - pcre_context::capture_t *body_cap, - const intern_string_t &mod_name); -}; - -class module_format { - -public: - external_log_format *mf_mod_format{nullptr}; -}; - #endif diff --git a/src/log_format_ext.hh b/src/log_format_ext.hh new file mode 100644 index 00000000..ca15d735 --- /dev/null +++ b/src/log_format_ext.hh @@ -0,0 +1,440 @@ +/** + * 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. + * + * @file log_format_ext.hh + */ + +#ifndef lnav_log_format_ext_hh +#define lnav_log_format_ext_hh + +#include "log_format.hh" + +class module_format; + +class external_log_format : public log_format { + +public: + struct sample { + sample() : s_level(LEVEL_UNKNOWN) {}; + + std::string s_line; + log_level_t s_level; + }; + + struct value_def { + intern_string_t vd_name; + logline_value::kind_t vd_kind{logline_value::VALUE_UNKNOWN}; + std::string vd_collate; + bool vd_identifier{false}; + bool vd_foreign_key{false}; + intern_string_t vd_unit_field; + std::map vd_unit_scaling; + int vd_column{-1}; + ssize_t vd_values_index{-1}; + bool vd_hidden{false}; + bool vd_user_hidden{false}; + bool vd_internal{false}; + std::vector vd_action_list; + std::string vd_rewriter; + std::string vd_description; + }; + + struct indexed_value_def { + indexed_value_def(int index = -1, + int unit_index = -1, + std::shared_ptr vd = nullptr) + : ivd_index(index), + ivd_unit_field_index(unit_index), + ivd_value_def(std::move(vd)) { + } + + int ivd_index; + int ivd_unit_field_index; + std::shared_ptr ivd_value_def; + + bool operator<(const indexed_value_def &rhs) const { + return this->ivd_index < rhs.ivd_index; + } + }; + + struct pattern { + std::string p_config_path; + std::string p_string; + pcrepp *p_pcre{nullptr}; + std::vector p_value_by_index; + std::vector p_numeric_value_indexes; + int p_timestamp_field_index{-1}; + int p_level_field_index{-1}; + int p_module_field_index{-1}; + int p_opid_field_index{-1}; + int p_body_field_index{-1}; + int p_timestamp_end{-1}; + bool p_module_format{false}; + }; + + struct level_pattern { + std::string lp_regex; + pcrepp *lp_pcre{nullptr}; + }; + + external_log_format(const intern_string_t name) + : elf_file_pattern(".*"), + elf_filename_pcre(NULL), + elf_column_count(0), + elf_timestamp_divisor(1.0), + elf_level_field(intern_string::lookup("level", -1)), + elf_body_field(intern_string::lookup("body", -1)), + elf_multiline(true), + elf_container(false), + elf_has_module_format(false), + elf_builtin_format(false), + elf_type(ELF_TYPE_TEXT), + jlf_hide_extra(false), + jlf_cached_offset(-1), + jlf_yajl_handle(yajl_free), + elf_name(name) { + this->jlf_line_offsets.reserve(128); + }; + + const intern_string_t get_name() const { + return this->elf_name; + }; + + bool match_name(const std::string &filename) { + pcre_context_static<10> pc; + pcre_input pi(filename); + + return this->elf_filename_pcre->match(pc, pi); + }; + + scan_result_t scan(logfile &lf, + std::vector &dst, + const line_info &offset, + shared_buffer_ref &sbr); + + bool scan_for_partial(shared_buffer_ref &sbr, size_t &len_out); + + void annotate(uint64_t line_number, shared_buffer_ref &line, string_attrs_t &sa, + std::vector &values, bool annotate_module = true) const; + + void rewrite(exec_context &ec, + shared_buffer_ref &line, + string_attrs_t &sa, + std::string &value_out); + + void build(std::vector &errors); + + void register_vtabs(log_vtab_manager *vtab_manager, + std::vector &errors); + + bool match_samples(const std::vector &samples) const; + + bool hide_field(const intern_string_t field_name, bool val) { + auto vd_iter = this->elf_value_defs.find(field_name); + + if (vd_iter == this->elf_value_defs.end()) { + return false; + } + + vd_iter->second->vd_user_hidden = val; + return true; + }; + + std::unique_ptr specialized(int fmt_lock); + + const logline_value_stats *stats_for_value(const intern_string_t &name) const { + const logline_value_stats *retval = nullptr; + + for (size_t lpc = 0; lpc < this->elf_numeric_value_defs.size(); lpc++) { + value_def &vd = *this->elf_numeric_value_defs[lpc]; + + if (vd.vd_name == name) { + retval = &this->lf_value_stats[lpc]; + break; + } + } + + return retval; + }; + + void get_subline(const logline &ll, shared_buffer_ref &sbr, bool full_message); + + log_vtab_impl *get_vtab_impl() const; + + const std::vector *get_actions(const logline_value &lv) const { + const std::vector *retval = nullptr; + + const auto iter = this->elf_value_defs.find(lv.lv_name); + if (iter != this->elf_value_defs.end()) { + retval = &iter->second->vd_action_list; + } + + return retval; + }; + + std::set get_source_path() const { + return this->elf_source_path; + }; + + enum json_log_field { + JLF_CONSTANT, + JLF_VARIABLE + }; + + struct json_format_element { + enum class align_t { + LEFT, + RIGHT, + }; + + enum class overflow_t { + ABBREV, + TRUNCATE, + DOTDOT, + }; + + enum class transform_t { + NONE, + UPPERCASE, + LOWERCASE, + CAPITALIZE, + }; + + json_format_element() + : 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; + intern_string_t jfe_value; + std::string jfe_default_value; + long long jfe_min_width; + long long jfe_max_width; + align_t jfe_align; + overflow_t jfe_overflow; + transform_t jfe_text_transform; + std::string jfe_ts_format; + }; + + struct json_field_cmp { + json_field_cmp(json_log_field type, + const intern_string_t name) + : jfc_type(type), jfc_field_name(name) { + }; + + bool operator()(const json_format_element &jfe) const { + return (this->jfc_type == jfe.jfe_type && + this->jfc_field_name == jfe.jfe_value); + }; + + json_log_field jfc_type; + const intern_string_t jfc_field_name; + }; + + struct highlighter_def { + highlighter_def() : hd_underline(false), hd_blink(false) { + } + + std::string hd_pattern; + std::string hd_color; + std::string hd_background_color; + bool hd_underline; + bool hd_blink; + }; + + long value_line_count(const intern_string_t ist, + bool top_level, + const unsigned char *str = nullptr, + 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_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 { + const auto iter = this->elf_value_defs.find(ist); + + return iter != this->elf_value_defs.end(); + }; + + std::string get_pattern_name(uint64_t line_number) const { + if (this->elf_type != ELF_TYPE_TEXT) { + return "structured"; + } + int pat_index = this->pattern_index_for_line(line_number); + return this->elf_pattern_order[pat_index]->p_config_path; + } + + std::string get_pattern_regex(uint64_t line_number) const { + if (this->elf_type != ELF_TYPE_TEXT) { + return ""; + } + int pat_index = this->pattern_index_for_line(line_number); + return this->elf_pattern_order[pat_index]->p_string; + } + + log_level_t convert_level(const pcre_input &pi, 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; + } + + typedef std::map mod_map_t; + static mod_map_t MODULE_FORMATS; + static std::vector GRAPH_ORDERED_FORMATS; + + std::set elf_source_path; + std::list elf_collision; + std::string elf_file_pattern; + pcrepp *elf_filename_pcre; + std::map> elf_patterns; + std::vector> elf_pattern_order; + std::vector elf_samples; + std::unordered_map> + elf_value_defs; + std::vector> elf_value_def_order; + std::vector> elf_numeric_value_defs; + int elf_column_count; + double elf_timestamp_divisor; + intern_string_t elf_level_field; + intern_string_t elf_body_field; + intern_string_t elf_module_id_field; + intern_string_t elf_opid_field; + std::map elf_level_patterns; + std::vector > elf_level_pairs; + bool elf_multiline; + bool elf_container; + bool elf_has_module_format; + bool elf_builtin_format; + std::vector > elf_search_tables; + std::map elf_highlighter_patterns; + + enum elf_type_t { + ELF_TYPE_TEXT, + ELF_TYPE_JSON, + ELF_TYPE_CSV, + }; + + elf_type_t elf_type; + + void json_append_to_cache(const char *value, ssize_t len) { + size_t old_size = this->jlf_cached_line.size(); + if (len == -1) { + len = strlen(value); + } + this->jlf_cached_line.resize(old_size + len); + memcpy(&(this->jlf_cached_line[old_size]), value, len); + }; + + void json_append_to_cache(ssize_t len) { + size_t old_size = this->jlf_cached_line.size(); + this->jlf_cached_line.resize(old_size + len); + memset(&this->jlf_cached_line[old_size], ' ', len); + }; + + void json_append(const 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); + } + } + }; + + bool jlf_hide_extra; + std::vector jlf_line_format; + int jlf_line_format_init_count{0}; + std::vector jlf_line_values; + + off_t jlf_cached_offset; + bool jlf_cached_full{false}; + std::vector jlf_line_offsets; + shared_buffer jlf_share_manager; + std::vector jlf_cached_line; + string_attrs_t jlf_line_attrs; + std::shared_ptr jlf_parse_context; + auto_mem jlf_yajl_handle; +private: + const intern_string_t elf_name; + + static uint8_t module_scan(const pcre_input &pi, + pcre_context::capture_t *body_cap, + const intern_string_t &mod_name); +}; + +class module_format { + +public: + external_log_format *mf_mod_format{nullptr}; +}; + +#endif diff --git a/src/log_format_impls.cc b/src/log_format_impls.cc index 07fad256..b67a171e 100644 --- a/src/log_format_impls.cc +++ b/src/log_format_impls.cc @@ -207,7 +207,7 @@ class generic_log_format : public log_format { lr.lr_start = prefix_len; lr.lr_end = line.length(); - sa.emplace_back(lr, &textview_curses::SA_BODY); + sa.emplace_back(lr, &SA_BODY); }; unique_ptr specialized(int fmt_lock) diff --git a/src/log_format_loader.cc b/src/log_format_loader.cc index 8b13a2b0..73e30f65 100644 --- a/src/log_format_loader.cc +++ b/src/log_format_loader.cc @@ -46,9 +46,10 @@ #include "yajlpp/yajlpp.hh" #include "yajlpp/yajlpp_def.hh" #include "lnav_config.hh" -#include "log_format.hh" +#include "log_format_ext.hh" #include "auto_fd.hh" #include "sql_util.hh" +#include "lnav_util.hh" #include "builtin-scripts.h" #include "builtin-sh-scripts.h" #include "default-formats.h" diff --git a/src/log_vtab_impl.cc b/src/log_vtab_impl.cc index 62fe4367..125e5f25 100644 --- a/src/log_vtab_impl.cc +++ b/src/log_vtab_impl.cc @@ -550,7 +550,7 @@ static int vt_column(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) struct line_range body_range; body_range = find_string_attr_range( - vt->vi->vi_attrs, &textview_curses::SA_BODY); + vt->vi->vi_attrs, &SA_BODY); if (!body_range.is_valid()) { sqlite3_result_null(ctx); } diff --git a/src/log_vtab_impl.hh b/src/log_vtab_impl.hh index 57026194..af344271 100644 --- a/src/log_vtab_impl.hh +++ b/src/log_vtab_impl.hh @@ -36,9 +36,10 @@ #include #include -#include "textview_curses.hh" #include "logfile_sub_source.hh" +class textview_curses; + enum { VT_COL_LINE_NUMBER, VT_COL_PARTITION, diff --git a/src/logfile_sub_source.cc b/src/logfile_sub_source.cc index 3aa4ab91..8361796a 100644 --- a/src/logfile_sub_source.cc +++ b/src/logfile_sub_source.cc @@ -210,7 +210,7 @@ void logfile_sub_source::text_value_for_line(textview_curses &tc, if (this->lss_token_line->is_continued()) { this->lss_token_attrs.emplace_back( line_range{0, (int) this->lss_token_value.length()}, - &textview_curses::SA_BODY); + &SA_BODY); } else { format->annotate(line, sbr, this->lss_token_attrs, this->lss_token_values, @@ -365,7 +365,7 @@ void logfile_sub_source::text_attrs_for_line(textview_curses &lv, lr.lr_start = 0; lr.lr_end = this->lss_token_value.length(); - value_out.emplace_back(lr, &textview_curses::SA_ORIGINAL_LINE); + value_out.emplace_back(lr, &SA_ORIGINAL_LINE); lr.lr_start = time_offset_end; lr.lr_end = -1; @@ -381,7 +381,7 @@ void logfile_sub_source::text_attrs_for_line(textview_curses &lv, if (line_value.lv_hidden) { value_out.emplace_back( - line_value.lv_origin, &textview_curses::SA_HIDDEN); + line_value.lv_origin, &SA_HIDDEN); } if (!line_value.lv_identifier || !line_value.lv_origin.is_valid()) { @@ -491,7 +491,7 @@ void logfile_sub_source::text_attrs_for_line(textview_curses &lv, lr.lr_start = 0; lr.lr_end = -1; value_out.emplace_back(lr, &logline::L_FILE, this->lss_token_file.get()); - value_out.emplace_back(lr, &textview_curses::SA_FORMAT, + value_out.emplace_back(lr, &SA_FORMAT, this->lss_token_file->get_format()->get_name()); { diff --git a/src/ptimec.hh b/src/ptimec.hh index 7df92ceb..91c61d46 100644 --- a/src/ptimec.hh +++ b/src/ptimec.hh @@ -43,46 +43,7 @@ #include -struct tm *secs2tm(time_t *tim_p, struct tm *res); -time_t tm2sec(const struct tm *t); - -constexpr time_t MAX_TIME_T = 4000000000LL; - -enum exttm_bits_t { - ETB_YEAR_SET, - ETB_MONTH_SET, - ETB_DAY_SET, - ETB_MACHINE_ORIENTED, - ETB_EPOCH_TIME, -}; - -enum exttm_flags_t { - ETF_YEAR_SET = (1UL << ETB_YEAR_SET), - ETF_MONTH_SET = (1UL << ETB_MONTH_SET), - ETF_DAY_SET = (1UL << ETB_DAY_SET), - ETF_MACHINE_ORIENTED = (1UL << ETB_MACHINE_ORIENTED), - ETF_EPOCH_TIME = (1UL << ETB_EPOCH_TIME), -}; - -struct exttm { - struct tm et_tm; - int32_t et_nsec; - unsigned int et_flags; - long et_gmtoff; - - bool operator==(const exttm &other) const { - return memcmp(this, &other, sizeof(exttm)) == 0; - }; - - struct timeval to_timeval() const { - struct timeval retval; - - retval.tv_sec = tm2sec(&this->et_tm); - retval.tv_usec = this->et_nsec * 1000; - - return retval; - }; -}; +#include "base/time_util.hh" #define PTIME_CONSUME(amount, block) \ if ((off_inout + amount) > len) { \ diff --git a/src/readline_curses.cc b/src/readline_curses.cc index 2d724999..5bebb3d5 100644 --- a/src/readline_curses.cc +++ b/src/readline_curses.cc @@ -458,11 +458,14 @@ readline_context::readline_context(const std::string &name, } readline_curses::readline_curses() - : rc_active_context(-1), - rc_child(-1), - rc_value_expiration(0), - rc_matches_remaining(0), - rc_max_match_length(0) + : rc_change(noop_func{}), + rc_perform(noop_func{}), + rc_alt_perform(noop_func{}), + rc_timeout(noop_func{}), + rc_abort(noop_func{}), + rc_display_match(noop_func{}), + rc_display_next(noop_func{}), + rc_blur(noop_func{}) { } diff --git a/src/readline_curses.hh b/src/readline_curses.hh index afacef63..8cd52d8a 100644 --- a/src/readline_curses.hh +++ b/src/readline_curses.hh @@ -402,29 +402,29 @@ private: friend class readline_context; - int rc_active_context; - pid_t rc_child; + int rc_active_context{-1}; + pid_t rc_child{-1}; auto_fd rc_pty[2]; auto_fd rc_command_pipe[2]; std::map rc_contexts; std::string rc_value; std::string rc_line_buffer; - time_t rc_value_expiration; + time_t rc_value_expiration{0}; std::string rc_alt_value; int rc_match_start{0}; - int rc_matches_remaining; - int rc_max_match_length; + int rc_matches_remaining{0}; + int rc_max_match_length{0}; int rc_match_index{0}; std::vector rc_matches; bool rc_is_alt_focus{false}; - action rc_change{noop_func{}}; - action rc_perform{noop_func{}}; - action rc_alt_perform{noop_func{}}; - action rc_timeout{noop_func{}}; - action rc_abort{noop_func{}}; - action rc_display_match{noop_func{}}; - action rc_display_next{noop_func{}}; - action rc_blur{noop_func{}}; + action rc_change; + action rc_perform; + action rc_alt_perform; + action rc_timeout; + action rc_abort; + action rc_display_match; + action rc_display_next; + action rc_blur; }; #endif diff --git a/src/session_data.cc b/src/session_data.cc index b300dc27..6c6fab51 100644 --- a/src/session_data.cc +++ b/src/session_data.cc @@ -49,6 +49,7 @@ #include "lnav_config.hh" #include "session_data.hh" #include "command_executor.hh" +#include "log_format_ext.hh" using namespace std; @@ -1390,17 +1391,13 @@ static void save_session_with_id(const std::string session_id) } } - textview_curses::highlight_map_t &hmap = - lnav_data.ld_views[lpc].get_highlights(); - textview_curses::highlight_map_t::iterator hl_iter; + auto &hmap = lnav_data.ld_views[lpc].get_highlights(); - for (hl_iter = hmap.begin(); - hl_iter != hmap.end(); - ++hl_iter) { - if (hl_iter->first.first != highlight_source_t::INTERACTIVE) { + for (auto & hl : hmap) { + if (hl.first.first != highlight_source_t::INTERACTIVE) { continue; } - cmd_array.gen("highlight " + hl_iter->first.second); + cmd_array.gen("highlight " + hl.first.second); } if (lpc == LNV_LOG) { diff --git a/src/spectro_source.cc b/src/spectro_source.cc new file mode 100644 index 00000000..084f3bb7 --- /dev/null +++ b/src/spectro_source.cc @@ -0,0 +1,393 @@ +/** + * 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. + * + * @file spectro_source.cc + */ + +#include "config.h" + +#include "base/math_util.hh" +#include "spectro_source.hh" + +bool spectrogram_source::list_input_handle_key(listview_curses &lv, int ch) +{ + switch (ch) { + case 'm': { + if (this->ss_cursor_top < 0 || + (size_t) this->ss_cursor_top >= this->text_line_count() || + this->ss_cursor_column == -1 || + this->ss_value_source == nullptr) { + alerter::singleton().chime(); + return true; + } + + unsigned long width; + vis_line_t height; + + lv.get_dimensions(height, width); + + spectrogram_bounds &sb = this->ss_cached_bounds; + struct timeval begin_time = this->time_for_row(this->ss_cursor_top); + struct timeval end_time = begin_time; + + end_time.tv_sec += this->ss_granularity; + double range_min, range_max, column_size; + + column_size = (sb.sb_max_value_out - sb.sb_min_value_out) / + (double) (width - 1); + range_min = sb.sb_min_value_out + this->ss_cursor_column * column_size; + range_max = range_min + column_size + column_size * 0.01; + this->ss_value_source->spectro_mark((textview_curses &) lv, + begin_time.tv_sec, end_time.tv_sec, + range_min, range_max); + this->invalidate(); + lv.reload_data(); + return true; + } + case KEY_LEFT: + case KEY_RIGHT: { + unsigned long width; + vis_line_t height; + string_attrs_t sa; + + this->ss_cursor_top = lv.get_top(); + lv.get_dimensions(height, width); + + this->text_attrs_for_line((textview_curses &) lv, this->ss_cursor_top, sa); + + if (sa.empty()) { + this->ss_cursor_column = -1; + return true; + } + + string_attrs_t::iterator current; + + struct line_range lr(this->ss_cursor_column, this->ss_cursor_column + 1); + + current = find_string_attr(sa, lr); + + if (current != sa.end()) { + if (ch == KEY_LEFT) { + if (current == sa.begin()) { + current = sa.end(); + } + else { + --current; + } + } + else { + ++current; + } + } + + if (current == sa.end()) { + if (ch == KEY_LEFT) { + current = sa.end(); + --current; + } + else { + current = sa.begin(); + } + } + this->ss_cursor_column = current->sa_range.lr_start; + + lv.reload_data(); + + return true; + } + default: + return false; + } +} + +bool +spectrogram_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) { + return false; + } + + std::string &line = value_out.get_string(); + char buf[128]; + vis_line_t height; + unsigned long width; + + lv.get_dimensions(height, width); + + this->cache_bounds(); + + if (this->ss_cached_line_count == 0) { + value_out.with_ansi_string( + ANSI_ROLE("error: no log data"), + view_colors::VCR_ERROR); + return true; + } + + spectrogram_bounds &sb = this->ss_cached_bounds; + spectrogram_thresholds &st = this->ss_cached_thresholds; + + snprintf(buf, sizeof(buf), "Min: %'.10lg", sb.sb_min_value_out); + line = buf; + + snprintf(buf, sizeof(buf), + ANSI_ROLE(" ") " 1-%'d " + ANSI_ROLE(" ") " %'d-%'d " + ANSI_ROLE(" ") " %'d+", + view_colors::VCR_LOW_THRESHOLD, + st.st_green_threshold - 1, + view_colors::VCR_MED_THRESHOLD, + st.st_green_threshold, + st.st_yellow_threshold - 1, + view_colors::VCR_HIGH_THRESHOLD, + st.st_yellow_threshold); + line.append(width / 2 - strlen(buf) / 3 - line.length(), ' '); + line.append(buf); + scrub_ansi_string(line, value_out.get_attrs()); + + snprintf(buf, sizeof(buf), "Max: %'.10lg", sb.sb_max_value_out); + line.append(width - strlen(buf) - line.length() - 2, ' '); + line.append(buf); + + value_out.with_attr(string_attr( + line_range(0, -1), + &view_curses::VC_STYLE, + A_UNDERLINE)); + + return true; +} + +size_t spectrogram_source::text_line_count() +{ + if (this->ss_value_source == nullptr) { + return 0; + } + + this->cache_bounds(); + + return this->ss_cached_line_count; +} + +size_t spectrogram_source::text_line_width(textview_curses &tc) +{ + if (tc.get_window() == nullptr) { + return 80; + } + + unsigned long width; + vis_line_t height; + + tc.get_dimensions(height, width); + return width; +} + +struct timeval spectrogram_source::time_for_row(int row) +{ + struct timeval retval { 0, 0 }; + + this->cache_bounds(); + retval.tv_sec = + rounddown(this->ss_cached_bounds.sb_begin_time, this->ss_granularity) + + row * this->ss_granularity; + + return retval; +} + +int spectrogram_source::row_for_time(struct timeval time_bucket) +{ + if (this->ss_value_source == nullptr) { + return 0; + } + + time_t diff; + int retval; + + this->cache_bounds(); + if (time_bucket.tv_sec < this->ss_cached_bounds.sb_begin_time) { + return 0; + } + + diff = time_bucket.tv_sec - this->ss_cached_bounds.sb_begin_time; + retval = diff / this->ss_granularity; + + return retval; +} + +void spectrogram_source::text_value_for_line(textview_curses &tc, int row, + std::string &value_out, + text_sub_source::line_flags_t flags) +{ + spectrogram_row &s_row = this->load_row(tc, row); + + struct timeval row_time; + char tm_buffer[128]; + struct tm tm; + + row_time = this->time_for_row(row); + + gmtime_r(&row_time.tv_sec, &tm); + strftime(tm_buffer, sizeof(tm_buffer), " %a %b %d %H:%M:%S", &tm); + + value_out = tm_buffer; + value_out.resize(s_row.sr_width, ' '); + + for (size_t lpc = 0; lpc <= s_row.sr_width; lpc++) { + if (s_row.sr_values[lpc].rb_marks) { + value_out[lpc] = 'x'; + } + } + + if (this->ss_cursor_top == row && this->ss_cursor_column != -1) { + if (value_out[this->ss_cursor_column] == 'x') { + value_out[this->ss_cursor_column] = '*'; + } + else { + value_out[this->ss_cursor_column] = '+'; + } + } +} + +void spectrogram_source::text_attrs_for_line(textview_curses &tc, int row, + string_attrs_t &value_out) +{ + if (this->ss_value_source == nullptr) { + return; + } + + view_colors &vc = view_colors::singleton(); + spectrogram_thresholds &st = this->ss_cached_thresholds; + spectrogram_row &s_row = this->load_row(tc, row); + + for (int lpc = 0; lpc <= (int) s_row.sr_width; lpc++) { + int col_value = s_row.sr_values[lpc].rb_counter; + + if (col_value == 0) { + continue; + } + + int color; + + if (col_value < st.st_green_threshold) { + color = COLOR_GREEN; + } + else if (col_value < st.st_yellow_threshold) { + color = COLOR_YELLOW; + } + else { + color = COLOR_RED; + } + value_out.emplace_back( + line_range(lpc, lpc + 1), + &view_curses::VC_STYLE, + vc.ansi_color_pair(COLOR_BLACK, color) + ); + } +} + +void spectrogram_source::cache_bounds() +{ + if (this->ss_value_source == nullptr) { + this->ss_cached_bounds.sb_count = 0; + this->ss_cached_bounds.sb_begin_time = 0; + return; + } + + spectrogram_bounds sb; + + this->ss_value_source->spectro_bounds(sb); + + if (sb.sb_count == this->ss_cached_bounds.sb_count) { + return; + } + + this->ss_cached_bounds = sb; + + if (sb.sb_count == 0) { + this->ss_cached_line_count = 0; + return; + } + + time_t grain_begin_time = rounddown(sb.sb_begin_time, this->ss_granularity); + time_t grain_end_time = roundup_size(sb.sb_end_time, this->ss_granularity); + + time_t diff = std::max((time_t) 1, grain_end_time - grain_begin_time); + this->ss_cached_line_count = + (diff + this->ss_granularity - 1) / this->ss_granularity; + + int64_t samples_per_row = sb.sb_count / this->ss_cached_line_count; + spectrogram_thresholds &st = this->ss_cached_thresholds; + + st.st_yellow_threshold = samples_per_row / 2; + st.st_green_threshold = st.st_yellow_threshold / 2; + + if (st.st_green_threshold <= 1) { + st.st_green_threshold = 2; + } + if (st.st_yellow_threshold <= st.st_green_threshold) { + st.st_yellow_threshold = st.st_green_threshold + 1; + } +} + +spectrogram_row &spectrogram_source::load_row(textview_curses &tc, int row) +{ + this->cache_bounds(); + + unsigned long width; + vis_line_t height; + + tc.get_dimensions(height, width); + width -= 2; + + spectrogram_bounds &sb = this->ss_cached_bounds; + spectrogram_request sr(sb); + time_t row_time; + + sr.sr_width = width; + row_time = rounddown(sb.sb_begin_time, this->ss_granularity) + + row * this->ss_granularity; + sr.sr_begin_time = row_time; + sr.sr_end_time = row_time + this->ss_granularity; + + sr.sr_column_size = (sb.sb_max_value_out - sb.sb_min_value_out) / + (double) (width - 1); + + spectrogram_row &s_row = this->ss_row_cache[row_time]; + + if (s_row.sr_values == nullptr || + s_row.sr_width != width || + s_row.sr_column_size != sr.sr_column_size) { + s_row.sr_width = width; + s_row.sr_column_size = sr.sr_column_size; + delete[] s_row.sr_values; + s_row.sr_values = new spectrogram_row::row_bucket[width + 1]; + this->ss_value_source->spectro_row(sr, s_row); + } + + return s_row; +} diff --git a/src/spectro_source.hh b/src/spectro_source.hh index 3c44106c..831ad4e1 100644 --- a/src/spectro_source.hh +++ b/src/spectro_source.hh @@ -41,44 +41,31 @@ #include "textview_curses.hh" struct spectrogram_bounds { - spectrogram_bounds() - : sb_begin_time(0), - sb_end_time(0), - sb_min_value_out(0.0), - sb_max_value_out(0.0), - sb_count(0) { - - }; - - time_t sb_begin_time; - time_t sb_end_time; - double sb_min_value_out; - double sb_max_value_out; - int64_t sb_count; + time_t sb_begin_time{0}; + time_t sb_end_time{0}; + double sb_min_value_out{0.0}; + double sb_max_value_out{0.0}; + int64_t sb_count{0}; }; struct spectrogram_thresholds { - int st_green_threshold; - int st_yellow_threshold; + int st_green_threshold{0}; + int st_yellow_threshold{0}; }; struct spectrogram_request { - spectrogram_request(spectrogram_bounds &sb) - : sr_bounds(sb), sr_width(0), sr_column_size(0) { + explicit spectrogram_request(spectrogram_bounds &sb) + : sr_bounds(sb) { }; spectrogram_bounds &sr_bounds; - unsigned long sr_width; - time_t sr_begin_time; - time_t sr_end_time; - double sr_column_size; + unsigned long sr_width{0}; + time_t sr_begin_time{0}; + time_t sr_end_time{0}; + double sr_column_size{0}; }; struct spectrogram_row { - spectrogram_row() : sr_values(NULL), sr_width(0) { - - }; - ~spectrogram_row() { delete[] this->sr_values; } @@ -90,9 +77,9 @@ struct spectrogram_row { int rb_marks; }; - row_bucket *sr_values; - unsigned long sr_width; - double sr_column_size; + row_bucket *sr_values{nullptr}; + unsigned long sr_width{0}; + double sr_column_size{0.0}; void add_value(spectrogram_request &sr, double value, bool marked) { long index = lrint((value - sr.sr_bounds.sb_min_value_out) / sr.sr_column_size); @@ -106,7 +93,7 @@ struct spectrogram_row { class spectrogram_value_source { public: - virtual ~spectrogram_value_source() { }; + virtual ~spectrogram_value_source() = default; virtual void spectro_bounds(spectrogram_bounds &sb_out) = 0; @@ -124,382 +111,52 @@ class spectrogram_source public list_overlay_source, public list_input_delegate { public: - - spectrogram_source() - : ss_granularity(60), - ss_value_source(NULL), - ss_cursor_column(-1) { - - }; - void invalidate() { this->ss_cached_bounds.sb_count = 0; this->ss_row_cache.clear(); this->ss_cursor_column = -1; }; - bool list_input_handle_key(listview_curses &lv, int ch) { - switch (ch) { - case 'm': { - if (this->ss_cursor_top < 0 || - (size_t) this->ss_cursor_top >= this->text_line_count() || - this->ss_cursor_column == -1 || - this->ss_value_source == NULL) { - alerter::singleton().chime(); - return true; - } - - unsigned long width; - vis_line_t height; - - lv.get_dimensions(height, width); - - spectrogram_bounds &sb = this->ss_cached_bounds; - struct timeval begin_time = this->time_for_row(this->ss_cursor_top); - struct timeval end_time = begin_time; - - end_time.tv_sec += this->ss_granularity; - double range_min, range_max, column_size; - - column_size = (sb.sb_max_value_out - sb.sb_min_value_out) / - (double) (width - 1); - range_min = sb.sb_min_value_out + this->ss_cursor_column * column_size; - range_max = range_min + column_size + column_size * 0.01; - this->ss_value_source->spectro_mark((textview_curses &) lv, - begin_time.tv_sec, end_time.tv_sec, - range_min, range_max); - this->invalidate(); - lv.reload_data(); - return true; - } - case KEY_LEFT: - case KEY_RIGHT: { - unsigned long width; - vis_line_t height; - string_attrs_t sa; - - this->ss_cursor_top = lv.get_top(); - lv.get_dimensions(height, width); - - this->text_attrs_for_line((textview_curses &) lv, this->ss_cursor_top, sa); - - if (sa.empty()) { - this->ss_cursor_column = -1; - return true; - } - - string_attrs_t::iterator current; - - struct line_range lr(this->ss_cursor_column, this->ss_cursor_column + 1); - - current = find_string_attr(sa, lr); - - if (current != sa.end()) { - if (ch == KEY_LEFT) { - if (current == sa.begin()) { - current = sa.end(); - } - else { - --current; - } - } - else { - ++current; - } - } - - if (current == sa.end()) { - if (ch == KEY_LEFT) { - current = sa.end(); - --current; - } - else { - current = sa.begin(); - } - } - this->ss_cursor_column = current->sa_range.lr_start; - - lv.reload_data(); - - return true; - } - default: - return false; - } - }; + bool list_input_handle_key(listview_curses &lv, int ch) override; bool list_value_for_overlay(const listview_curses &lv, int y, int bottom, vis_line_t row, - attr_line_t &value_out) { - if (y != 0) { - return false; - } - - std::string &line = value_out.get_string(); - char buf[128]; - vis_line_t height; - unsigned long width; - - lv.get_dimensions(height, width); + attr_line_t &value_out) override; - this->cache_bounds(); + size_t text_line_count() override; - if (this->ss_cached_line_count == 0) { - value_out.with_ansi_string( - ANSI_ROLE("error: no log data"), - view_colors::VCR_ERROR); - return true; - } - - spectrogram_bounds &sb = this->ss_cached_bounds; - spectrogram_thresholds &st = this->ss_cached_thresholds; - - snprintf(buf, sizeof(buf), "Min: %'.10lg", sb.sb_min_value_out); - line = buf; - - snprintf(buf, sizeof(buf), - ANSI_ROLE(" ") " 1-%'d " - ANSI_ROLE(" ") " %'d-%'d " - ANSI_ROLE(" ") " %'d+", - view_colors::VCR_LOW_THRESHOLD, - st.st_green_threshold - 1, - view_colors::VCR_MED_THRESHOLD, - st.st_green_threshold, - st.st_yellow_threshold - 1, - view_colors::VCR_HIGH_THRESHOLD, - st.st_yellow_threshold); - line.append(width / 2 - strlen(buf) / 3 - line.length(), ' '); - line.append(buf); - scrub_ansi_string(line, value_out.get_attrs()); - - snprintf(buf, sizeof(buf), "Max: %'.10lg", sb.sb_max_value_out); - line.append(width - strlen(buf) - line.length() - 2, ' '); - line.append(buf); - - value_out.with_attr(string_attr( - line_range(0, -1), - &view_curses::VC_STYLE, - A_UNDERLINE)); - - return true; - }; - - size_t text_line_count() { - if (this->ss_value_source == NULL) { - return 0; - } + size_t text_line_width(textview_curses &tc) override; - this->cache_bounds(); - - return this->ss_cached_line_count; - }; - - size_t text_line_width(textview_curses &tc) { - if (tc.get_window() == NULL) { - return 80; - } - - unsigned long width; - vis_line_t height; - - tc.get_dimensions(height, width); - return width; - }; - - size_t text_size_for_line(textview_curses &tc, int row, line_flags_t flags) { + size_t text_size_for_line(textview_curses &tc, int row, line_flags_t flags) override { return 0; }; - struct timeval time_for_row(int row) { - struct timeval retval { 0, 0 }; + struct timeval time_for_row(int row) override; - this->cache_bounds(); - retval.tv_sec = - rounddown(this->ss_cached_bounds.sb_begin_time, this->ss_granularity) + - row * this->ss_granularity; - - return retval; - } - - int row_for_time(struct timeval time_bucket) { - if (this->ss_value_source == NULL) { - return 0; - } - - time_t diff; - int retval; - - this->cache_bounds(); - if (time_bucket.tv_sec < this->ss_cached_bounds.sb_begin_time) { - return 0; - } - - diff = time_bucket.tv_sec - this->ss_cached_bounds.sb_begin_time; - retval = diff / this->ss_granularity; - - return retval; - } + int row_for_time(struct timeval time_bucket) override; void text_value_for_line(textview_curses &tc, int row, std::string &value_out, - line_flags_t flags) { - spectrogram_row &s_row = this->load_row(tc, row); - - struct timeval row_time; - char tm_buffer[128]; - struct tm tm; - - row_time = this->time_for_row(row); - - gmtime_r(&row_time.tv_sec, &tm); - strftime(tm_buffer, sizeof(tm_buffer), " %a %b %d %H:%M:%S", &tm); - - value_out = tm_buffer; - value_out.resize(s_row.sr_width, ' '); - - for (size_t lpc = 0; lpc <= s_row.sr_width; lpc++) { - if (s_row.sr_values[lpc].rb_marks) { - value_out[lpc] = 'x'; - } - } - - if (this->ss_cursor_top == row && this->ss_cursor_column != -1) { - if (value_out[this->ss_cursor_column] == 'x') { - value_out[this->ss_cursor_column] = '*'; - } - else { - value_out[this->ss_cursor_column] = '+'; - } - } - }; + line_flags_t flags) override; void text_attrs_for_line(textview_curses &tc, int row, - string_attrs_t &value_out) { - if (this->ss_value_source == NULL) { - return; - } - - view_colors &vc = view_colors::singleton(); - spectrogram_thresholds &st = this->ss_cached_thresholds; - spectrogram_row &s_row = this->load_row(tc, row); - - for (int lpc = 0; lpc <= (int) s_row.sr_width; lpc++) { - int col_value = s_row.sr_values[lpc].rb_counter; - - if (col_value == 0) { - continue; - } - - int color; - - if (col_value < st.st_green_threshold) { - color = COLOR_GREEN; - } - else if (col_value < st.st_yellow_threshold) { - color = COLOR_YELLOW; - } - else { - color = COLOR_RED; - } - value_out.emplace_back( - line_range(lpc, lpc + 1), - &view_curses::VC_STYLE, - vc.ansi_color_pair(COLOR_BLACK, color) - ); - } - }; - - void cache_bounds() { - if (this->ss_value_source == NULL) { - this->ss_cached_bounds.sb_count = 0; - this->ss_cached_bounds.sb_begin_time = 0; - return; - } - - spectrogram_bounds sb; - - this->ss_value_source->spectro_bounds(sb); - - if (sb.sb_count == this->ss_cached_bounds.sb_count) { - return; - } - - this->ss_cached_bounds = sb; + string_attrs_t &value_out) override; - if (sb.sb_count == 0) { - this->ss_cached_line_count = 0; - return; - } - - time_t grain_begin_time = rounddown(sb.sb_begin_time, this->ss_granularity); - time_t grain_end_time = roundup_size(sb.sb_end_time, this->ss_granularity); - - time_t diff = std::max((time_t) 1, grain_end_time - grain_begin_time); - this->ss_cached_line_count = - (diff + this->ss_granularity - 1) / this->ss_granularity; - - int64_t samples_per_row = sb.sb_count / this->ss_cached_line_count; - spectrogram_thresholds &st = this->ss_cached_thresholds; - - st.st_yellow_threshold = samples_per_row / 2; - st.st_green_threshold = st.st_yellow_threshold / 2; - - if (st.st_green_threshold <= 1) { - st.st_green_threshold = 2; - } - if (st.st_yellow_threshold <= st.st_green_threshold) { - st.st_yellow_threshold = st.st_green_threshold + 1; - } - }; - - spectrogram_row &load_row(textview_curses &tc, int row) { - this->cache_bounds(); + void cache_bounds(); - unsigned long width; - vis_line_t height; - - tc.get_dimensions(height, width); - width -= 2; - - spectrogram_bounds &sb = this->ss_cached_bounds; - spectrogram_request sr(sb); - time_t row_time; - - sr.sr_width = width; - row_time = rounddown(sb.sb_begin_time, this->ss_granularity) + - row * this->ss_granularity; - sr.sr_begin_time = row_time; - sr.sr_end_time = row_time + this->ss_granularity; - - sr.sr_column_size = (sb.sb_max_value_out - sb.sb_min_value_out) / - (double) (width - 1); - - spectrogram_row &s_row = this->ss_row_cache[row_time]; - - if (s_row.sr_values == nullptr || - s_row.sr_width != width || - s_row.sr_column_size != sr.sr_column_size) { - s_row.sr_width = width; - s_row.sr_column_size = sr.sr_column_size; - delete[] s_row.sr_values; - s_row.sr_values = new spectrogram_row::row_bucket[width + 1]; - this->ss_value_source->spectro_row(sr, s_row); - } - - return s_row; - } + spectrogram_row &load_row(textview_curses &tc, int row); - int ss_granularity; - spectrogram_value_source *ss_value_source; + int ss_granularity{60}; + spectrogram_value_source *ss_value_source{nullptr}; spectrogram_bounds ss_cached_bounds; spectrogram_thresholds ss_cached_thresholds; - size_t ss_cached_line_count; + size_t ss_cached_line_count{0}; std::map ss_row_cache; vis_line_t ss_cursor_top; - int ss_cursor_column; + int ss_cursor_column{-1}; }; #endif diff --git a/src/sql_util.cc b/src/sql_util.cc index 74f1f9da..13c31777 100644 --- a/src/sql_util.cc +++ b/src/sql_util.cc @@ -42,8 +42,8 @@ #include "sql_util.hh" #include "base/string_util.hh" #include "base/lnav_log.hh" +#include "base/time_util.hh" #include "pcrepp/pcrepp.hh" -#include "lnav_util.hh" #include "sqlite-extension-func.hh" using namespace std; diff --git a/src/state-extension-functions.cc b/src/state-extension-functions.cc index de4b7030..9f5d16df 100644 --- a/src/state-extension-functions.cc +++ b/src/state-extension-functions.cc @@ -34,7 +34,6 @@ #include #include -#include #include "sqlite3.h" diff --git a/src/string-extension-functions.cc b/src/string-extension-functions.cc index 59a5b0e7..0b09dc37 100644 --- a/src/string-extension-functions.cc +++ b/src/string-extension-functions.cc @@ -28,6 +28,7 @@ #include "elem_to_json.hh" #include "vtab_module.hh" #include "vtab_module_json.hh" +#include "spookyhash/SpookyV2.h" #include "optional.hpp" #include "mapbox/variant.hpp" diff --git a/src/string_attr_type.cc b/src/string_attr_type.cc new file mode 100644 index 00000000..c2009ad1 --- /dev/null +++ b/src/string_attr_type.cc @@ -0,0 +1,38 @@ +/** + * 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. + */ + +#include "config.h" + +#include "string_attr_type.hh" + +string_attr_type SA_ORIGINAL_LINE("original_line"); +string_attr_type SA_BODY("body"); +string_attr_type SA_HIDDEN("hidden"); +string_attr_type SA_FORMAT("format"); +string_attr_type SA_REMOVED("removed"); diff --git a/src/string_attr_type.hh b/src/string_attr_type.hh new file mode 100644 index 00000000..d945cd7a --- /dev/null +++ b/src/string_attr_type.hh @@ -0,0 +1,49 @@ +/** + * 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_string_attr_type_hh +#define lnav_string_attr_type_hh + +class string_attr_type { +public: + explicit string_attr_type(const char *name = nullptr) noexcept + : sat_name(name) { + }; + + const char *sat_name; +}; +typedef string_attr_type *string_attr_type_t; + +extern string_attr_type SA_ORIGINAL_LINE; +extern string_attr_type SA_BODY; +extern string_attr_type SA_HIDDEN; +extern string_attr_type SA_FORMAT; +extern string_attr_type SA_REMOVED; + +#endif diff --git a/src/textfile_highlighters.cc b/src/textfile_highlighters.cc index cf285ae7..4e7332fd 100644 --- a/src/textfile_highlighters.cc +++ b/src/textfile_highlighters.cc @@ -55,7 +55,7 @@ static pcre *xpcre_compile(const char *pattern, int options = 0) return retval; } -void setup_highlights(textview_curses::highlight_map_t &hm) +void setup_highlights(highlight_map_t &hm) { hm[{highlight_source_t::INTERNAL, "python"}] = highlighter(xpcre_compile( "(?:" diff --git a/src/textfile_highlighters.hh b/src/textfile_highlighters.hh index dde7c31e..e44979a8 100644 --- a/src/textfile_highlighters.hh +++ b/src/textfile_highlighters.hh @@ -30,8 +30,8 @@ #ifndef textfile_highlighters_hh #define textfile_highlighters_hh -#include "textview_curses.hh" +#include "textview_curses_fwd.hh" -void setup_highlights(textview_curses::highlight_map_t &hm); +void setup_highlights(highlight_map_t &hm); #endif diff --git a/src/textview_curses.cc b/src/textview_curses.cc index fbcdd1dc..b7a027f4 100644 --- a/src/textview_curses.cc +++ b/src/textview_curses.cc @@ -114,13 +114,8 @@ bookmark_type_t textview_curses::BM_USER("user"); bookmark_type_t textview_curses::BM_SEARCH("search"); bookmark_type_t textview_curses::BM_META("meta"); -string_attr_type textview_curses::SA_ORIGINAL_LINE("original_line"); -string_attr_type textview_curses::SA_BODY("body"); -string_attr_type textview_curses::SA_HIDDEN("hidden"); -string_attr_type textview_curses::SA_FORMAT("format"); -string_attr_type textview_curses::SA_REMOVED("removed"); - textview_curses::textview_curses() + : tc_search_action(noop_func{}) { this->set_data_source(this); } @@ -556,7 +551,7 @@ void textview_curses::execute_search(const std::string ®ex_orig) hl.with_role(view_colors::VCR_SEARCH); - textview_curses::highlight_map_t &hm = this->get_highlights(); + highlight_map_t &hm = this->get_highlights(); hm[{highlight_source_t::PREVIEW, "search"}] = hl; unique_ptr> gp = make_unique>(code, *this); diff --git a/src/textview_curses.hh b/src/textview_curses.hh index 7c62baa3..449ccef2 100644 --- a/src/textview_curses.hh +++ b/src/textview_curses.hh @@ -44,6 +44,7 @@ #include "text_format.hh" #include "logfile.hh" #include "highlighter.hh" +#include "textview_curses_fwd.hh" class logline; class textview_curses; @@ -558,7 +559,7 @@ private: class text_delegate { public: - virtual ~text_delegate() { }; + virtual ~text_delegate() = default; virtual void text_overlay(textview_curses &tc) { }; @@ -567,14 +568,6 @@ public: }; }; -enum class highlight_source_t { - INTERNAL, - THEME, - PREVIEW, - CONFIGURATION, - INTERACTIVE, -}; - /** * The textview_curses class adds user bookmarks and searching to the standard * list view interface. @@ -593,12 +586,6 @@ public: static bookmark_type_t BM_SEARCH; static bookmark_type_t BM_META; - static string_attr_type SA_ORIGINAL_LINE; - static string_attr_type SA_BODY; - static string_attr_type SA_HIDDEN; - static string_attr_type SA_FORMAT; - static string_attr_type SA_REMOVED; - textview_curses(); void reload_config(error_reporter &reporter); @@ -837,9 +824,6 @@ public: } }; - using highlight_map_t = - std::map, highlighter>; - highlight_map_t &get_highlights() { return this->tc_highlights; }; const highlight_map_t &get_highlights() const { return this->tc_highlights; }; @@ -944,7 +928,7 @@ protected: grep_highlighter(std::unique_ptr> &gp, highlight_source_t source, std::string hl_name, - textview_curses::highlight_map_t &hl_map) + highlight_map_t &hl_map) : gh_grep_proc(std::move(gp)), gh_hl_source(source), gh_hl_name(std::move(hl_name)), @@ -962,7 +946,7 @@ protected: std::unique_ptr> gh_grep_proc; highlight_source_t gh_hl_source; std::string gh_hl_name; - textview_curses::highlight_map_t &gh_hl_map; + highlight_map_t &gh_hl_map; }; text_sub_source *tc_sub_source{nullptr}; @@ -974,7 +958,7 @@ protected: struct timeval tc_follow_deadline{0, 0}; vis_line_t tc_follow_top{-1_vl}; std::function tc_follow_func; - action tc_search_action{noop_func{}}; + action tc_search_action; highlight_map_t tc_highlights; diff --git a/src/textview_curses_fwd.hh b/src/textview_curses_fwd.hh new file mode 100644 index 00000000..d19b0627 --- /dev/null +++ b/src/textview_curses_fwd.hh @@ -0,0 +1,48 @@ +/** + * 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_textview_curses_fwd_hh +#define lnav_textview_curses_fwd_hh + +#include +#include + +#include "highlighter.hh" + +enum class highlight_source_t { + INTERNAL, + THEME, + PREVIEW, + CONFIGURATION, + INTERACTIVE, +}; + +using highlight_map_t = std::map, highlighter>; + +#endif diff --git a/src/time-extension-functions.cc b/src/time-extension-functions.cc index 2c3e3712..79d894f4 100644 --- a/src/time-extension-functions.cc +++ b/src/time-extension-functions.cc @@ -37,7 +37,7 @@ #include -#include "lnav_util.hh" +#include "base/date_time_scanner.hh" #include "sql_util.hh" #include "relative_time.hh" diff --git a/src/unique_path.cc b/src/unique_path.cc new file mode 100644 index 00000000..a4b40e6f --- /dev/null +++ b/src/unique_path.cc @@ -0,0 +1,127 @@ +/** + * 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. + */ + +#include "config.h" + +#include "unique_path.hh" + +void unique_path_generator::add_source( + const std::shared_ptr &path_source) +{ + ghc::filesystem::path path = path_source->get_path(); + + path_source->set_unique_path(path.filename()); + path_source->set_path_prefix(path.parent_path()); + this->upg_unique_paths[path.filename()].push_back(path_source); +} + +void unique_path_generator::generate() +{ + int loop_count = 0; + + while (!this->upg_unique_paths.empty()) { + std::vector> collisions; + + for (auto pair : this->upg_unique_paths) { + if (pair.second.size() == 1) { + if (loop_count > 0) { + std::shared_ptr src = pair.second[0]; + + src->set_unique_path("[" + src->get_unique_path()); + } + + this->upg_max_len = std::max( + this->upg_max_len, + pair.second[0]->get_unique_path().size()); + } else { + bool all_common = true; + + do { + std::string common; + + for (auto &src : pair.second) { + auto &path = src->get_path_prefix(); + + if (common.empty()) { + common = path.filename(); + if (common.empty()) { + all_common = false; + } + } else if (common != path.filename()) { + all_common = false; + } + } + + if (all_common) { + for (auto &src : pair.second) { + auto &path = src->get_path_prefix(); + auto par = path.parent_path(); + + if (path.empty() || path == par) { + all_common = false; + } else { + src->set_path_prefix(path.parent_path()); + } + } + } + } while (all_common); + + collisions.insert(collisions.end(), + pair.second.begin(), + pair.second.end()); + } + } + + this->upg_unique_paths.clear(); + + for (auto &src : collisions) { + const auto unique_path = src->get_unique_path(); + auto &prefix = src->get_path_prefix(); + + if (loop_count == 0) { + src->set_unique_path(prefix.filename().string() + "]/" + unique_path); + } else { + src->set_unique_path(prefix.filename().string() + "/" + unique_path); + } + + ghc::filesystem::path parent = prefix.parent_path(); + + src->set_path_prefix(parent); + + if (parent.empty() || parent == prefix) { + src->set_unique_path("[" + src->get_unique_path()); + } else { + this->upg_unique_paths[src->get_unique_path()].push_back( + src); + } + } + + loop_count += 1; + } +} diff --git a/src/unique_path.hh b/src/unique_path.hh index 2c0ccfae..a8821258 100644 --- a/src/unique_path.hh +++ b/src/unique_path.hh @@ -67,104 +67,12 @@ private: class unique_path_generator { public: - unique_path_generator() : upg_max_len(0) { + void add_source(const std::shared_ptr& path_source); - }; - - void add_source(const std::shared_ptr& path_source) { - ghc::filesystem::path path = path_source->get_path(); - - path_source->set_unique_path(path.filename()); - path_source->set_path_prefix(path.parent_path()); - this->upg_unique_paths[path.filename()].push_back(path_source); - }; - - void generate() { - int loop_count = 0; - - while (!this->upg_unique_paths.empty()) { - std::vector> collisions; - - for (auto pair : this->upg_unique_paths) { - if (pair.second.size() == 1) { - if (loop_count > 0) { - std::shared_ptr src = pair.second[0]; - - src->set_unique_path("[" + src->get_unique_path()); - } - - this->upg_max_len = std::max( - this->upg_max_len, - pair.second[0]->get_unique_path().size()); - } else { - bool all_common = true; - - do { - std::string common; - - for (auto &src : pair.second) { - auto &path = src->get_path_prefix(); - - if (common.empty()) { - common = path.filename(); - if (common.empty()) { - all_common = false; - } - } else if (common != path.filename()) { - all_common = false; - } - } - - if (all_common) { - for (auto &src : pair.second) { - auto &path = src->get_path_prefix(); - auto par = path.parent_path(); - - if (path.empty() || path == par) { - all_common = false; - } else { - src->set_path_prefix(path.parent_path()); - } - } - } - } while (all_common); - - collisions.insert(collisions.end(), - pair.second.begin(), - pair.second.end()); - } - } - - this->upg_unique_paths.clear(); - - for (auto &src : collisions) { - const auto unique_path = src->get_unique_path(); - auto &prefix = src->get_path_prefix(); - - if (loop_count == 0) { - src->set_unique_path(prefix.filename().string() + "]/" + unique_path); - } else { - src->set_unique_path(prefix.filename().string() + "/" + unique_path); - } - - ghc::filesystem::path parent = prefix.parent_path(); - - src->set_path_prefix(parent); - - if (parent.empty() || parent == prefix) { - src->set_unique_path("[" + src->get_unique_path()); - } else { - this->upg_unique_paths[src->get_unique_path()].push_back( - src); - } - } - - loop_count += 1; - } - } + void generate(); std::map>> upg_unique_paths; - size_t upg_max_len; + size_t upg_max_len{0}; }; #endif //LNAV_UNIQUE_PATH_HH diff --git a/src/view_helpers.cc b/src/view_helpers.cc index 58f411fe..f7ebfc73 100644 --- a/src/view_helpers.cc +++ b/src/view_helpers.cc @@ -103,7 +103,7 @@ static void open_pretty_view() lss.text_attrs_for_line(*log_tc, vl, al.get_attrs()); line_range orig_lr = find_string_attr_range( - al.get_attrs(), &textview_curses::SA_ORIGINAL_LINE); + al.get_attrs(), &SA_ORIGINAL_LINE); attr_line_t orig_al = al.subline(orig_lr.lr_start, orig_lr.length()); attr_line_t prefix_al = al.subline(0, orig_lr.lr_start); diff --git a/src/view_helpers.hh b/src/view_helpers.hh index ea5552d9..0548e71b 100644 --- a/src/view_helpers.hh +++ b/src/view_helpers.hh @@ -33,7 +33,9 @@ #define lnav_view_helpers_hh #include "help_text.hh" -#include "textview_curses.hh" +#include "attr_line.hh" + +class textview_curses; /** The different views available. */ typedef enum { diff --git a/src/vtab_module.hh b/src/vtab_module.hh index aae19fa5..6d9823d5 100644 --- a/src/vtab_module.hh +++ b/src/vtab_module.hh @@ -39,7 +39,6 @@ #include "optional.hpp" #include "base/lnav_log.hh" #include "base/string_util.hh" -#include "lnav_util.hh" #include "auto_mem.hh" #include "mapbox/variant.hpp" #include "fmt/format.h" diff --git a/test/drive_data_scanner.cc b/test/drive_data_scanner.cc index 0bcc388c..ca57585c 100644 --- a/test/drive_data_scanner.cc +++ b/test/drive_data_scanner.cc @@ -170,7 +170,7 @@ int main(int argc, char *argv[]) if (format.get() != NULL) { format->annotate(0, sbr, sa, ll_values); - body = find_string_attr_range(sa, &textview_curses::SA_BODY); + body = find_string_attr_range(sa, &SA_BODY); } data_parser::TRACE_FILE = fopen("scanned.dpt", "w"); diff --git a/test/test_abbrev.cc b/test/test_abbrev.cc index d8276fed..cd9e38e4 100644 --- a/test/test_abbrev.cc +++ b/test/test_abbrev.cc @@ -31,7 +31,7 @@ #include -#include "lnav_util.hh" +#include "base/string_util.hh" static struct test_data { const char *str; @@ -43,7 +43,7 @@ static struct test_data { { "com.example.foo.bar", "c.e.foo.bar", 15 }, { "no dots in here", "no dots in here", 5 }, - { NULL } + { nullptr } }; int main(int argc, char *argv[]) diff --git a/test/test_date_time_scanner.cc b/test/test_date_time_scanner.cc index 39a06be6..166eb6b8 100644 --- a/test/test_date_time_scanner.cc +++ b/test/test_date_time_scanner.cc @@ -32,7 +32,7 @@ #include #include -#include "lnav_util.hh" +#include "base/date_time_scanner.hh" #include "../src/lnav_util.hh" static const char *GOOD_TIMES[] = {