[sql] do some minimal parsing/annotation of SQL statements

Defect Number:
    Reviewed By:
   Testing Done:
pull/111/merge
Timothy Stack 7 years ago
parent 0b157ff867
commit 8776f6a703

@ -37,6 +37,8 @@ lnav v0.8.2:
string.
Interface Changes:
* Command documentation is now displayed in a section at the bottom of
the screen when a command is being entered.
* The color used for text colored via ":highlight" is now based on the
the regex instead of randomly picked so that colors are consistent
across invocations.

@ -13,6 +13,7 @@ set(diag_STAT_SRCS
file_vtab.cc
fs-extension-functions.cc
grep_proc.cc
help_text_formatter.cc
hist_source.cc
hotkeys.cc
intern_string.cc
@ -52,11 +53,13 @@ set(diag_STAT_SRCS
sysclip.cc
pcrepp.cc
piper_proc.cc
ptimec.cc
sql_util.cc
state-extension-functions.cc
strnatcmp.c
text_format.cc
textview_curses.cc
time-extension-functions.cc
timer.cc
view_curses.cc
views_vtab.cc
@ -83,6 +86,7 @@ set(diag_STAT_SRCS
spookyhash/SpookyV2.cpp
all_logs_vtab.hh
attr_line.hh
auto_fd.hh
auto_mem.hh
auto_pid.hh
@ -93,6 +97,7 @@ set(diag_STAT_SRCS
concise_index.hh
column_namer.hh
curl_looper.hh
doc_status_source.hh
elem_to_json.hh
field_overlay_source.hh
file_vtab.hh
@ -100,6 +105,7 @@ set(diag_STAT_SRCS
format-text-files.hh
grep_highlighter.hh
help.hh
help_text_formatter.hh
hotkeys.hh
init-sql.hh
intern_string.hh

@ -120,6 +120,7 @@ dist_noinst_DATA = \
noinst_HEADERS = \
all_logs_vtab.hh \
ansi_scrubber.hh \
attr_line.hh \
auto_fd.hh \
auto_mem.hh \
auto_pid.hh \
@ -136,6 +137,7 @@ noinst_HEADERS = \
data_parser.hh \
default-log-formats-json.hh \
db_sub_source.hh \
doc_status_source.hh \
elem_to_json.hh \
environ_vtab.hh \
field_overlay_source.hh \
@ -146,6 +148,7 @@ noinst_HEADERS = \
grep_proc.hh \
help.hh \
help.txt \
help_text_formatter.hh \
hist_source.hh \
hotkeys.hh \
init.sql \
@ -252,6 +255,7 @@ libdiag_a_SOURCES = \
file_vtab.cc \
fs-extension-functions.cc \
grep_proc.cc \
help_text_formatter.cc \
hist_source.cc \
hotkeys.cc \
intern_string.cc \

@ -0,0 +1,423 @@
/**
* Copyright (c) 2017, 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 attr_line.hh
*/
#ifndef __attr_line_hh
#define __attr_line_hh
#include <string>
#include <vector>
/**
* Encapsulates a range in a string.
*/
struct line_range {
int lr_start;
int lr_end;
explicit line_range(int start = -1, int end = -1) : lr_start(start), lr_end(end) { };
bool is_valid() const {
return this->lr_start != -1;
}
int length() const
{
return this->lr_end == -1 ? INT_MAX : this->lr_end - this->lr_start;
};
bool contains(int pos) const {
return this->lr_start <= pos && pos < this->lr_end;
};
bool contains(const struct line_range &other) const {
return this->contains(other.lr_start) && other.lr_end <= this->lr_end;
};
bool intersects(const struct line_range &other) const {
return this->contains(other.lr_start) || this->contains(other.lr_end);
};
line_range intersection(const struct line_range &other) const {
return line_range{std::max(this->lr_start, other.lr_start),
std::min(this->lr_end, other.lr_end)};
};
line_range &shift(int32_t start, int32_t amount) {
if (this->lr_start >= start) {
this->lr_start = std::max(0, this->lr_start + amount);
}
if (this->lr_end != -1 && start < this->lr_end) {
this->lr_end += amount;
if (this->lr_end < this->lr_start) {
this->lr_end = this->lr_start;
}
}
return *this;
};
void ltrim(const char *str) {
while (this->lr_start < this->lr_end && isspace(str[this->lr_start])) {
this->lr_start += 1;
}
};
bool operator<(const struct line_range &rhs) const
{
if (this->lr_start < rhs.lr_start) { return true; }
else if (this->lr_start > rhs.lr_start) { return false; }
if (this->lr_end == rhs.lr_end) { return false; }
if (this->lr_end < rhs.lr_end) { return true; }
return false;
};
bool operator==(const struct line_range &rhs) const {
return (this->lr_start == rhs.lr_start && this->lr_end == rhs.lr_end);
};
const char *substr(const std::string &str) const {
if (this->lr_start == -1) {
return str.c_str();
}
return &(str.c_str()[this->lr_start]);
}
size_t sublen(const std::string &str) const {
if (this->lr_start == -1) {
return str.length();
}
if (this->lr_end == -1) {
return str.length() - this->lr_start;
}
return this->length();
}
};
/**
* Container for attribute values for a substring.
*/
typedef union {
void *sav_ptr;
int64_t sav_int;
} string_attr_value_t;
class string_attr_type {
public:
string_attr_type(const char *name = nullptr)
: 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) {
this->sa_value.sav_ptr = val;
};
string_attr(const struct line_range &lr, string_attr_type_t type, int64_t val = 0)
: sa_range(lr), sa_type(type) {
this->sa_value.sav_int = val;
};
string_attr(const struct line_range &lr, string_attr_type_t type, string_attr_value_t val)
: sa_range(lr), sa_type(type), sa_value(val) {
};
string_attr() : sa_type(NULL) { };
bool operator<(const struct string_attr &rhs) const
{
return this->sa_range < rhs.sa_range;
};
struct line_range sa_range;
string_attr_type_t sa_type;
string_attr_value_t sa_value;
};
/** A map of line ranges to attributes for that range. */
typedef std::vector<string_attr> string_attrs_t;
inline string_attrs_t::const_iterator
find_string_attr(const string_attrs_t &sa, string_attr_type_t type, int start = 0)
{
string_attrs_t::const_iterator iter;
for (iter = sa.begin(); iter != sa.end(); ++iter) {
if (iter->sa_type == type && iter->sa_range.lr_start >= start) {
break;
}
}
return iter;
}
inline string_attrs_t::iterator
find_string_attr(string_attrs_t &sa, const struct line_range &lr)
{
string_attrs_t::iterator iter;
for (iter = sa.begin(); iter != sa.end(); ++iter) {
if (lr.contains(iter->sa_range)) {
break;
}
}
return iter;
}
inline string_attrs_t::const_iterator
find_string_attr(const string_attrs_t &sa, size_t near)
{
string_attrs_t::const_iterator iter, nearest = sa.end();
ssize_t last_diff = INT_MAX;
for (iter = sa.begin(); iter != sa.end(); ++iter) {
auto &lr = iter->sa_range;
if (!lr.is_valid() || !lr.contains(near)) {
continue;
}
ssize_t diff = near - lr.lr_start;
if (diff < last_diff) {
last_diff = diff;
nearest = iter;
}
}
return nearest;
}
inline struct line_range
find_string_attr_range(const string_attrs_t &sa, string_attr_type_t type)
{
string_attrs_t::const_iterator iter = find_string_attr(sa, type);
if (iter != sa.end()) {
return iter->sa_range;
}
return line_range();
}
inline void remove_string_attr(string_attrs_t &sa, const struct line_range &lr)
{
string_attrs_t::iterator iter;
while ((iter = find_string_attr(sa, lr)) != sa.end()) {
sa.erase(iter);
}
}
inline void remove_string_attr(string_attrs_t &sa, string_attr_type_t type)
{
string_attrs_t::iterator iter;
for (auto iter = sa.begin(); iter != sa.end();) {
if (iter->sa_type == type) {
iter = sa.erase(iter);
} else {
++iter;
}
}
}
inline void shift_string_attrs(string_attrs_t &sa, int32_t start, int32_t amount)
{
for (string_attrs_t::iterator iter = sa.begin(); iter != sa.end(); ++iter) {
iter->sa_range.shift(start, amount);
}
}
struct text_wrap_settings {
text_wrap_settings() : tws_indent(2), tws_width(80) {};
text_wrap_settings &with_indent(int indent) {
this->tws_indent = indent;
return *this;
};
text_wrap_settings &with_width(int width) {
this->tws_width = width;
return *this;
};
int tws_indent;
int tws_width;
};
/**
* A line that has attributes.
*/
class attr_line_t {
public:
attr_line_t() {
this->al_attrs.reserve(RESERVE_SIZE);
};
attr_line_t(const std::string &str) : al_string(str) {
this->al_attrs.reserve(RESERVE_SIZE);
};
attr_line_t(const char *str) : al_string(str) {
this->al_attrs.reserve(RESERVE_SIZE);
};
static inline attr_line_t from_ansi_str(const char *str) {
attr_line_t retval;
return retval.with_ansi_string(str);
};
/** @return The string itself. */
std::string &get_string() { return this->al_string; };
/** @return The attributes for the string. */
string_attrs_t &get_attrs() { return this->al_attrs; };
attr_line_t &with_string(const std::string &str) {
this->al_string = str;
return *this;
}
attr_line_t &with_ansi_string(const char *str, ...);
attr_line_t &with_attr(const string_attr &sa) {
this->al_attrs.push_back(sa);
return *this;
};
template<typename T = void *>
attr_line_t &append(const char *str,
string_attr_type_t type = nullptr,
T val = T()) {
size_t start_len = this->al_string.length();
this->al_string.append(str);
if (type != nullptr) {
line_range lr{(int) start_len, (int) this->al_string.length()};
this->al_attrs.emplace_back(lr, type, val);
}
return *this;
};
attr_line_t &append(const attr_line_t &al, text_wrap_settings *tws = nullptr);
attr_line_t &append(size_t len, char c) {
this->al_string.append(len, c);
return *this;
};
attr_line_t &insert(size_t index, size_t len, char c) {
this->al_string.insert(index, len, c);
shift_string_attrs(this->al_attrs, index, len);
return *this;
}
attr_line_t &insert(size_t index, const char *str) {
this->al_string.insert(index, str);
shift_string_attrs(this->al_attrs, index, strlen(str));
return *this;
}
attr_line_t &right_justify(unsigned long width) {
long padding = width - this->length();
if (padding > 0) {
this->al_string.insert(0, padding, ' ');
for (std::vector<string_attr>::iterator iter = this->al_attrs.begin();
iter != this->al_attrs.end();
++iter) {
iter->sa_range.lr_start += padding;
iter->sa_range.lr_end += padding;
}
}
return *this;
}
ssize_t length() const {
size_t retval = this->al_string.length();
for (std::vector<string_attr>::const_iterator iter = this->al_attrs.begin();
iter != this->al_attrs.end();
++iter) {
retval = std::max(retval, (size_t) iter->sa_range.lr_start);
if (iter->sa_range.lr_end != -1) {
retval = std::max(retval, (size_t) iter->sa_range.lr_end);
}
}
return retval;
};
std::string get_substring(const line_range &lr) const {
if (!lr.is_valid()) {
return "";
}
return this->al_string.substr(lr.lr_start, lr.length());
};
bool empty() const {
return this->length() == 0;
};
/** Clear the string and the attributes for the string. */
attr_line_t &clear()
{
this->al_string.clear();
this->al_attrs.clear();
return *this;
};
attr_line_t subline(size_t start, size_t len = std::string::npos) const;
void split_lines(std::vector<attr_line_t> &lines) const;
private:
const static size_t RESERVE_SIZE = 128;
std::string al_string;
string_attrs_t al_attrs;
};
#endif

@ -78,8 +78,10 @@ public:
this->bss_fields[BSF_HELP].set_width(14);
this->bss_fields[BSF_HELP].set_value("?:View Help");
this->bss_fields[BSF_HELP].right_justify(true);
this->bss_prompt.set_left_pad(1);
this->bss_prompt.set_min_width(35);
this->bss_prompt.set_share(1);
this->bss_error.set_left_pad(1);
this->bss_error.set_min_width(35);
this->bss_error.set_share(1);
};
@ -134,10 +136,10 @@ public:
status_field &sf = this->bss_fields[BSF_LINE_NUMBER];
if (lc->get_inner_height() == 0) {
sf.set_value("L0");
sf.set_value(" L0");
}
else {
sf.set_value("L%'d", (int)lc->get_top());
sf.set_value(" L%'d", (int)lc->get_top());
}
};

@ -28,42 +28,52 @@
"c_ip" : {
"kind" : "string",
"collate" : "ipaddress",
"identifier" : true
"identifier" : true,
"description" : "The client IP address"
},
"cs_username" : {
"kind" : "string",
"identifier" : true
"identifier" : true,
"description" : "The username passed from the client to the server"
},
"cs_method" : {
"kind" : "string",
"identifier" : true
"identifier" : true,
"description" : "The request method"
},
"cs_uri_stem" : {
"kind" : "string",
"identifier" : true
"identifier" : true,
"description" : "The path part of the request URI"
},
"cs_uri_query" : {
"kind" : "string"
"kind" : "string",
"description" : "The query parameters in the request URI"
},
"cs_version" : {
"kind" : "string",
"identifier" : true
"identifier" : true,
"description" : "The client's HTTP version"
},
"sc_status" : {
"kind" : "integer",
"foreign-key" : true,
"rewriter" : ";SELECT :sc_status || ' (' || (SELECT message FROM http_status_codes WHERE status = :sc_status) || ') '"
"rewriter" : ";SELECT :sc_status || ' (' || (SELECT message FROM http_status_codes WHERE status = :sc_status) || ') '",
"description" : "The status code returned by the server"
},
"sc_bytes" : {
"kind" : "integer"
"kind" : "integer",
"description" : "The number of bytes returned by the server"
},
"cs_referer" : {
"kind" : "string",
"identifier" : true
"identifier" : true,
"description" : "The client's referrer"
},
"cs_user_agent" : {
"kind" : "string",
"identifier" : true
"identifier" : true,
"description" : "The client's HTTP agent"
}
},
"sample" : [
@ -96,11 +106,13 @@
"pid" : {
"kind" : "integer",
"identifier" : true,
"foreign-key" : true
"foreign-key" : true,
"description" : "The ID of the process that generated the message"
},
"module" : {
"kind" : "string",
"identifier" : true
"identifier" : true,
"description" : "The name of the module that generated the message"
}
},
"sample" : [
@ -972,16 +984,19 @@
"log_hostname" : {
"kind" : "string",
"collate" : "ipaddress",
"identifier" : true
"identifier" : true,
"description" : "The name of the host that generated the message"
},
"log_procname" : {
"kind" : "string",
"identifier" : true
"identifier" : true,
"description" : "The name of the process that generated the message"
},
"log_pid" : {
"kind" : "string",
"identifier" : true,
"action-list" : ["dump_pid"]
"action-list" : ["dump_pid"],
"description" : "The ID of the process that generated the message"
}
},
"action" : {

@ -0,0 +1,70 @@
/**
* Copyright (c) 2017, 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 _doc_status_source_hh
#define _doc_status_source_hh
#include <string>
#include "lnav_config.hh"
#include "logfile_sub_source.hh"
#include "statusview_curses.hh"
class doc_status_source
: public status_data_source {
public:
typedef enum {
TSF_TITLE,
TSF_STITCH_TITLE,
TSF__MAX
} field_t;
doc_status_source()
{
this->tss_fields[TSF_TITLE].set_width(14);
this->tss_fields[TSF_TITLE].set_role(view_colors::VCR_VIEW_STATUS);
this->tss_fields[TSF_TITLE].set_value(" Command Help ");
this->tss_fields[TSF_STITCH_TITLE].set_width(2);
this->tss_fields[TSF_STITCH_TITLE].set_stitch_value(
view_colors::ansi_color_pair_index(COLOR_BLUE, COLOR_WHITE));
};
size_t statusview_fields(void) { return TSF__MAX; };
status_field &statusview_value_for_field(int field)
{
return this->tss_fields[field];
};
private:
status_field tss_fields[TSF__MAX];
};
#endif

@ -76,19 +76,12 @@ sql_basename(const char *path_in)
}
}
static void sql_dirname(sqlite3_context *context,
int argc, sqlite3_value **argv)
static
util::variant<const char *, string_fragment>
sql_dirname(const char *path_in)
{
const char *path_in;
ssize_t text_end;
if (sqlite3_value_type(argv[0]) == SQLITE_NULL) {
sqlite3_result_null(context);
return;
}
path_in = (const char *)sqlite3_value_text(argv[0]);
text_end = strlen(path_in) - 1;
while (text_end >= 0 &&
(path_in[text_end] == '/' || path_in[text_end] == '\\')) {
@ -97,18 +90,13 @@ static void sql_dirname(sqlite3_context *context,
while (text_end >= 0) {
if (path_in[text_end] == '/' || path_in[text_end] == '\\') {
sqlite3_result_text(context,
path_in, (int) (text_end == 0 ? 1 : text_end),
SQLITE_TRANSIENT);
return;
return string_fragment(path_in, 0, text_end == 0 ? 1 : text_end);
}
text_end -= 1;
}
sqlite3_result_text(context,
path_in[0] == '/' ? "/" : ".", 1,
SQLITE_STATIC);
return path_in[0] == '/' ? "/" : ".";
}
static void sql_joinpath(sqlite3_context *context,
@ -160,7 +148,13 @@ int fs_extension_functions(const struct FuncDef **basic_funcs,
{"path", "The path"},
}),
{ "dirname", 1, 0, SQLITE_UTF8, 0, sql_dirname },
sqlite_func_adapter<decltype(&sql_dirname), sql_dirname>::builder(
"dirname",
"Extract the directory portion of a pathname",
{
{"path", "The path"},
}),
{ "joinpath", -1, 0, SQLITE_UTF8, 0, sql_joinpath },
/*

@ -0,0 +1,147 @@
/**
* Copyright (c) 2017, 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 <numeric>
#include <algorithm>
#include "ansi_scrubber.hh"
#include "help_text_formatter.hh"
#include "readline_highlighters.hh"
using namespace std;
void format_help_text_for_term(const help_text &ht, int width, attr_line_t &out)
{
static size_t body_indent = 2;
text_wrap_settings tws;
tws.with_width(width);
switch (ht.ht_context) {
case HC_COMMAND: {
out.append("Synopsis", &view_curses::VC_STYLE, A_UNDERLINE)
.append("\n")
.append(body_indent, ' ')
.append(":")
.append(ht.ht_name, &view_curses::VC_STYLE, A_BOLD);
for (auto &param : ht.ht_parameters) {
out.append(" ");
out.append(param.ht_name, &view_curses::VC_STYLE, A_UNDERLINE);
if (param.ht_nargs == HN_ONE_OR_MORE) {
out.append("1", &view_curses::VC_STYLE, A_UNDERLINE);
out.append(" [");
out.append("...", &view_curses::VC_STYLE, A_UNDERLINE);
out.append(" ");
out.append(param.ht_name, &view_curses::VC_STYLE, A_UNDERLINE);
out.append("N", &view_curses::VC_STYLE, A_UNDERLINE);
out.append("]");
}
}
out.append(" - ")
.append(attr_line_t::from_ansi_str(ht.ht_summary),
&tws.with_indent(body_indent + 4))
.append("\n");
break;
}
case HC_SQL_FUNCTION: {
bool needs_comma = false;
out.append(ht.ht_name, &view_curses::VC_STYLE, A_BOLD);
out.append("(");
for (auto &param : ht.ht_parameters) {
if (needs_comma) {
out.append(", ");
}
out.append(param.ht_name, &view_curses::VC_STYLE, A_UNDERLINE);
needs_comma = true;
}
out.append(") -- ")
.append(attr_line_t::from_ansi_str(ht.ht_summary), &tws)
.append("\n");
break;
}
}
if (!ht.ht_parameters.empty()) {
size_t max_param_name_width = 0;
for (auto &param : ht.ht_parameters) {
max_param_name_width = std::max(strlen(param.ht_name), max_param_name_width);
}
out.append(ht.ht_parameters.size() == 1 ? "Parameter" : "Parameters",
&view_curses::VC_STYLE,
A_UNDERLINE)
.append("\n");
for (auto &param : ht.ht_parameters) {
out.append(body_indent, ' ')
.append(param.ht_name,
&view_curses::VC_STYLE,
view_colors::ansi_color_pair(COLOR_CYAN, COLOR_BLACK) | A_BOLD)
.append(max_param_name_width - strlen(param.ht_name), ' ')
.append(" ")
.append(attr_line_t::from_ansi_str(param.ht_summary),
&(tws.with_indent(2 + max_param_name_width + 3)))
.append("\n");
}
}
if (!ht.ht_example.empty()) {
map<string, string> vars;
vars["name"] = ht.ht_name;
add_ansi_vars(vars);
out.append(ht.ht_example.size() == 1 ? "Example" : "Examples",
&view_curses::VC_STYLE,
A_UNDERLINE)
.append("\n");
for (auto &ex : ht.ht_example) {
attr_line_t ex_line(ex.he_cmd);
switch (ht.ht_context) {
case HC_COMMAND:
ex_line.insert(0, 1, ' ');
ex_line.insert(0, 1, ':');
ex_line.insert(1, ht.ht_name);
readline_command_highlighter(ex_line, 0);
break;
}
out.append(body_indent, ' ')
.append(ex_line, &tws.with_indent(body_indent + 2));
out.append("\n");
}
}
}

@ -0,0 +1,156 @@
/**
* Copyright (c) 2017, 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_HELP_TEXT_FORMATTER_HH
#define LNAV_HELP_TEXT_FORMATTER_HH
#include <string>
#include <vector>
#include "attr_line.hh"
enum help_context_t {
HC_NONE,
HC_PARAMETER,
HC_COMMAND,
HC_SQL_FUNCTION,
};
enum help_nargs_t {
HN_REQUIRED,
HN_OPTIONAL,
HN_ZERO_OR_MORE,
HN_ONE_OR_MORE,
};
enum help_parameter_format_t {
HPF_STRING,
HPF_REGEX,
HPF_INTEGER,
HPF_NUMBER,
HPF_DATETIME,
HPF_ENUM,
};
struct help_example {
const char *he_cmd;
const char *he_result;
};
struct help_text {
help_context_t ht_context;
const char *ht_name;
const char *ht_summary;
const char *ht_description;
std::vector<struct help_text> ht_parameters;
std::vector<struct help_example> ht_example;
help_nargs_t ht_nargs;
help_parameter_format_t ht_format;
std::vector<const char *> ht_enum_values;
help_text() : ht_context(HC_NONE) {
};
help_text(const char *name, const char *summary = nullptr)
: ht_context(HC_NONE),
ht_name(name),
ht_summary(summary),
ht_description(nullptr),
ht_nargs(HN_REQUIRED),
ht_format(HPF_STRING) {
if (name[0] == ':') {
this->ht_context = HC_COMMAND;
this->ht_name = &name[1];
}
}
help_text &command() {
this->ht_context = HC_COMMAND;
return *this;
};
help_text &with_summary(const char *summary) {
this->ht_summary = summary;
return *this;
};
help_text &with_parameters(const std::initializer_list<help_text> &params) {
this->ht_parameters = params;
for (auto &param : this->ht_parameters) {
param.ht_context = HC_PARAMETER;
}
return *this;
}
help_text &with_parameter(const help_text &ht) {
this->ht_parameters.emplace_back(ht);
this->ht_parameters.back().ht_context = HC_PARAMETER;
return *this;
};
help_text &with_examples(const std::initializer_list<help_example> &examples) {
this->ht_example = examples;
return *this;
}
help_text &with_example(const help_example &example) {
this->ht_example.emplace_back(example);
return *this;
}
help_text &optional() {
this->ht_nargs = HN_OPTIONAL;
return *this;
};
help_text &zero_or_more() {
this->ht_nargs = HN_ZERO_OR_MORE;
return *this;
};
help_text &one_or_more() {
this->ht_nargs = HN_ONE_OR_MORE;
return *this;
};
help_text &with_format(help_parameter_format_t format) {
this->ht_format = format;
return *this;
}
help_text &with_enum_values(const std::initializer_list<const char*> &enum_values) {
this->ht_enum_values = enum_values;
return *this;
};
};
void format_help_text_for_term(const help_text &ht, int width, attr_line_t &out);
#endif //LNAV_HELP_TEXT_FORMATTER_HH

@ -197,7 +197,12 @@ public:
void set_show_scrollbar(bool ss) { this->lv_show_scrollbar = ss; };
bool get_show_scrollbar() const { return this->lv_show_scrollbar; };
void set_show_bottom_border(bool val) { this->lv_show_bottom_border = val; };
void set_show_bottom_border(bool val) {
if (this->lv_show_bottom_border != val) {
this->lv_show_bottom_border = val;
this->set_needs_update();
}
};
bool get_show_bottom_border() const { return this->lv_show_bottom_border; };
listview_curses &set_word_wrap(bool ww) {
@ -459,7 +464,7 @@ public:
}
else {
getmaxyx(this->lv_window, height, width_out);
if (this->lv_height < 1) {
if (this->lv_height < 0) {
height_out = vis_line_t(height) +
this->lv_height -
vis_line_t(this->lv_y);

@ -407,8 +407,9 @@ private:
void do_update(void)
{
lnav_data.ld_top_source.update_time();
lnav_data.ld_status[LNS_TOP].do_update();
lnav_data.ld_status[LNS_BOTTOM].do_update();
for (auto &sc : lnav_data.ld_status) {
sc.do_update();
}
refresh();
};
@ -1916,17 +1917,19 @@ static void looper(void)
textview_curses::action(update_hits));
}
lnav_data.ld_doc_view.set_window(lnav_data.ld_window);
lnav_data.ld_status[LNS_TOP].set_top(0);
lnav_data.ld_status[LNS_BOTTOM].set_top(-(rlc.get_height() + 1));
for (lpc = 0; lpc < LNS__MAX; lpc++) {
lnav_data.ld_status[lpc].set_window(lnav_data.ld_window);
for (auto &sc : lnav_data.ld_status) {
sc.set_window(lnav_data.ld_window);
}
lnav_data.ld_status[LNS_TOP].set_data_source(
&lnav_data.ld_top_source);
lnav_data.ld_status[LNS_BOTTOM].set_data_source(
&lnav_data.ld_bottom_source);
lnav_data.ld_match_view.set_show_bottom_border(true);
lnav_data.ld_status[LNS_DOC].set_data_source(
&lnav_data.ld_doc_status_source);
vsb.push_back(sb.get_functor());
@ -1939,8 +1942,11 @@ static void looper(void)
sb.push_back(&lnav_data.ld_bottom_source.marks_wire);
sb.push_back(&lnav_data.ld_term_extra.filename_wire);
lnav_data.ld_status[0].window_change();
lnav_data.ld_status[1].window_change();
lnav_data.ld_match_view.set_show_bottom_border(true);
for (auto &sc : lnav_data.ld_status) {
sc.window_change();
}
execute_file(ec, dotlnav_path("session"));
@ -1963,12 +1969,45 @@ static void looper(void)
rebuild_indexes(true);
}
lnav_data.ld_status[LNS_TOP].do_update();
{
unsigned long width, height;
getmaxyx(lnav_data.ld_window, height, width);
bool doc_open = lnav_data.ld_doc_view.get_height() > 0;
int bottom_height =
(doc_open ? 1 : 0)
+ lnav_data.ld_doc_view.get_height()
+ lnav_data.ld_match_view.get_height()
+ 1
+ lnav_data.ld_rl_view->get_height();
for (int lpc = 0; lpc < LNV__MAX; lpc++) {
textview_curses &tc = lnav_data.ld_views[lpc];
tc.set_height(vis_line_t(-bottom_height));
}
lnav_data.ld_status[LNS_BOTTOM].set_top(
-(lnav_data.ld_match_view.get_height() + 2));
lnav_data.ld_status[LNS_DOC].set_top(height - bottom_height);
lnav_data.ld_status[LNS_DOC].set_enabled(doc_open);
lnav_data.ld_doc_view.set_y(height - bottom_height + 1);
lnav_data.ld_match_view.set_y(
height
- bottom_height
+ (doc_open ? 1 : 0)
+ 1
+ lnav_data.ld_doc_view.get_height());
}
if (!lnav_data.ld_view_stack.empty()) {
lnav_data.ld_view_stack.back()->do_update();
}
lnav_data.ld_doc_view.do_update();
lnav_data.ld_match_view.do_update();
lnav_data.ld_status[LNS_BOTTOM].do_update();
for (auto &sc : lnav_data.ld_status) {
sc.do_update();
}
rlc.do_update();
refresh();
@ -2187,11 +2226,14 @@ static void looper(void)
resizeterm(size.ws_row, size.ws_col);
}
rlc.window_change();
lnav_data.ld_status[0].window_change();
lnav_data.ld_status[1].window_change();
for (auto &sc : lnav_data.ld_status) {
sc.window_change();
}
if (!lnav_data.ld_view_stack.empty()) {
lnav_data.ld_view_stack.back()->set_needs_update();
}
lnav_data.ld_doc_view.set_needs_update();
lnav_data.ld_match_view.set_needs_update();
}
if (lnav_data.ld_child_terminated) {
@ -2934,6 +2976,8 @@ int main(int argc, char *argv[])
.add_input_delegate(lnav_data.ld_spectro_source)
.set_tail_space(vis_line_t(2));
lnav_data.ld_doc_view.set_sub_source(&lnav_data.ld_doc_source)
.set_left(0);
lnav_data.ld_match_view.set_left(0);
for (lpc = 0; lpc < LNV__MAX; lpc++) {

@ -49,6 +49,7 @@
#include "listview_curses.hh"
#include "top_status_source.hh"
#include "bottom_status_source.hh"
#include "doc_status_source.hh"
#include "grep_highlighter.hh"
#include "db_sub_source.hh"
#include "textfile_sub_source.hh"
@ -64,6 +65,7 @@
#include "log_format_loader.hh"
#include "spectro_source.hh"
#include "command_executor.hh"
#include "plain_text_source.hh"
/** The command modes that are available while viewing a file. */
typedef enum {
@ -133,6 +135,7 @@ extern const char *lnav_zoom_strings[];
typedef enum {
LNS_TOP,
LNS_BOTTOM,
LNS_DOC,
LNS__MAX
} lnav_status_t;
@ -241,6 +244,7 @@ struct _lnav_data {
statusview_curses ld_status[LNS__MAX];
top_status_source ld_top_source;
bottom_status_source ld_bottom_source;
doc_status_source ld_doc_status_source;
listview_curses::action::broadcaster ld_scroll_broadcaster;
listview_curses::action::broadcaster ld_view_stack_broadcaster;
@ -249,6 +253,8 @@ struct _lnav_data {
time_t ld_bottom_time;
int ld_bottom_time_millis;
plain_text_source ld_doc_source;
textview_curses ld_doc_view;
textview_curses ld_match_view;
std::vector<textview_curses *> ld_view_stack;

@ -2856,7 +2856,13 @@ readline_context::command_t STD_COMMANDS[] = {
"adjust-log-time",
"<date>",
"Change the timestamps of the top file to be relative to the given date",
com_adjust_log_time
com_adjust_log_time,
help_text(":adjust-log-time")
.with_summary("Change the timestamps of the top file to be relative to the given date")
.with_parameter(help_text("timestamp", "The new timestamp for the top line in the view")
.with_format(HPF_DATETIME))
.with_example({"2017-01-02T05:33:00", ""})
},
{
@ -2864,18 +2870,37 @@ readline_context::command_t STD_COMMANDS[] = {
"<seconds>",
"Convert epoch time to a human-readable form",
com_unix_time,
help_text(":unix-time")
.with_summary("Convert epoch time to a human-readable form")
.with_parameter(help_text("seconds", "The epoch timestamp to convert")
.with_format(HPF_INTEGER))
.with_example({"1490191111", "Wed Mar 22 06:58:31 2017 -0700 PDT -- 1490191111"})
},
{
"current-time",
NULL,
"Print the current time in human-readable form and seconds since the epoch",
com_current_time,
help_text(":current-time")
.with_summary("Print the current time in human-readable form and seconds since the epoch")
},
{
"goto",
"<line#|N%|date>",
"Go to the given line number, N percent into the file, or the given timestamp in the log view",
com_goto,
help_text(":goto")
.with_summary("Go to the given location in the top view")
.with_parameter(help_text("line#|N%|date", "A line number, percent into the file, or a timestamp"))
.with_examples(
{
{"22"},
{"75%"},
{"2017-01-01"}
})
},
{
"relative-goto",
@ -2888,180 +2913,336 @@ readline_context::command_t STD_COMMANDS[] = {
NULL,
"Toggle the bookmark state for the top line in the current view",
com_mark,
help_text(":mark")
.with_summary("Toggle the bookmark state for the top line in the current view")
},
{
"next-mark",
"error|warning|search|user|file|partition",
"Move to the next bookmark of the given type in the current view",
com_goto_mark,
help_text(":next-mark")
.with_summary("Move to the next bookmark of the given type in the current view")
.with_parameter(help_text("type", "The type of bookmark -- error, warning, search, user, file, partition"))
.with_example({"error"})
},
{
"prev-mark",
"error|warning|search|user|file|partition",
"Move to the previous bookmark of the given type in the current view",
com_goto_mark,
help_text(":prev-mark")
.with_summary("Move to the previous bookmark of the given type in the current view")
.with_parameter(help_text("type", "The type of bookmark -- error, warning, search, user, file, partition"))
.with_example({"error"})
},
{
"help",
NULL,
"Open the help text view",
com_help,
help_text(":help")
.with_summary("Open the help text view")
},
{
"hide-fields",
"<field-name1> [<field-name2> ... <field-nameN>]",
"Hide log message fields by replacing them with an ellipsis",
com_toggle_field,
help_text(":hide-fields")
.with_summary("Hide log message fields by replacing them with an ellipsis")
.with_parameter(help_text("field-name", "The name of the field to hide")
.one_or_more())
.with_example({"log_procname"})
},
{
"show-fields",
"<field-name> [<field-name2> ... <field-nameN>]",
"Show log message fields that were previously hidden",
com_toggle_field,
help_text(":show-fields")
.with_summary("Show log message fields that were previously hidden")
.with_parameter(help_text("field-name", "The name of the field to show")
.one_or_more())
.with_example({"log_procname"})
},
{
"hide-lines-before",
"<line#|date>",
"Hide lines that come before the given line number or date",
com_hide_line,
help_text(":hide-lines-before")
.with_summary("Hide lines that come before the given date")
.with_parameter(help_text("date", "An absolute or relative date"))
.with_examples(
{
{"here"},
{"6am"},
})
},
{
"hide-lines-after",
"<line#|date>",
"Hide lines that come after the given line number or date",
com_hide_line,
help_text(":hide-lines-after")
.with_summary("Hide lines that come after the given date")
.with_parameter(help_text("date", "An absolute or relative date"))
.with_examples(
{
{"here"},
{"6am"},
})
},
{
"show-lines-before-and-after",
NULL,
"Show lines that were hidden by the 'hide-lines' commands",
com_show_lines,
help_text(":show-lines-before-and-after")
.with_summary("Show lines that were hidden by the 'hide-lines' commands")
},
{
"highlight",
"<regex>",
"<pattern>",
"Add coloring to log messages fragments that match the given regular expression",
com_highlight,
help_text(":highlight")
.with_summary("Add coloring to log messages fragments that match the given regular expression")
.with_parameter(help_text("pattern", "The regular expression to match"))
.with_example({R"(\d{3,})"})
},
{
"clear-highlight",
"<regex>",
"<pattern>",
"Remove a previously set highlight regular expression",
com_clear_highlight,
help_text(":clear-highlight")
.with_summary("Remove a previously set highlight regular expression")
.with_parameter(help_text("pattern", "The regular expression previously used with :highlight"))
.with_example({"foobar"})
},
{
"filter-in",
"<regex>",
"Only show lines that match the given regular expression in the current view",
com_filter,
help_text(":filter-in")
.with_summary("Only show lines that match the given regular expression in the current view")
.with_parameter(help_text("pattern", "The regular expression to match"))
.with_example({"dhclient"})
},
{
"filter-out",
"<regex>",
"Remove lines that match the given regular expression in the current view",
com_filter,
help_text(":filter-out")
.with_summary("Remove lines that match the given regular expression in the current view")
.with_parameter(help_text("pattern", "The regular expression to match"))
.with_example({"last message repeated"})
},
{
"delete-filter",
"<regex>",
"Delete the given filter",
com_delete_filter,
help_text(":filter-out")
.with_summary("Delete the filter created with "
ANSI_BOLD(":filter-in") " or " ANSI_BOLD(":filter-out"))
.with_parameter(help_text("pattern", "The regular expression to match"))
.with_example({"last message repeated"})
},
{
"append-to",
"<filename>",
"Append marked lines in the current view to the given file",
com_save_to,
help_text(":append-to")
.with_summary("Append marked lines in the current view to the given file")
.with_parameter(help_text("path", "The path to the file to append to"))
.with_example({"/tmp/interesting-lines.txt"})
},
{
"write-to",
"<filename>",
"Overwrite the given file with any marked lines in the current view",
com_save_to,
help_text(":write-to")
.with_summary("Overwrite the given file with any marked lines in the current view")
.with_parameter(help_text("path", "The path to the file to write"))
.with_example({"/tmp/interesting-lines.txt"})
},
{
"write-csv-to",
"<filename>",
"Write SQL results to the given file in CSV format",
com_save_to,
help_text(":write-csv-to")
.with_summary("Write SQL results to the given file in CSV format")
.with_parameter(help_text("path", "The path to the file to write"))
.with_example({"/tmp/table.csv"})
},
{
"write-json-to",
"<filename>",
"Write SQL results to the given file in JSON format",
com_save_to,
help_text(":write-json-to")
.with_summary("Write SQL results to the given file in JSON format")
.with_parameter(help_text("path", "The path to the file to write"))
.with_example({"/tmp/table.json"})
},
{
"write-cols-to",
"<filename>",
"Write SQL results to the given file in a columnar format",
com_save_to,
help_text(":write-cols-to")
.with_summary("Write SQL results to the given file in a columnar format")
.with_parameter(help_text("path", "The path to the file to write"))
.with_example({"/tmp/table.txt"})
},
{
"write-raw-to",
"<filename>",
"Write SQL results to the given file without any formatting",
com_save_to,
help_text(":write-raw-to")
.with_summary("Write SQL results to the given file without any formatting")
.with_parameter(help_text("path", "The path to the file to write"))
.with_example({"/tmp/table.txt"})
},
{
"pipe-to",
"<shell-cmd>",
"Pipe the marked lines to the given shell command",
com_pipe_to,
help_text(":pipe-to")
.with_summary("Pipe the marked lines to the given shell command")
.with_parameter(help_text("shell-cmd", "The shell command-line to execute"))
.with_example({"sed -e s/foo/bar/g"})
},
{
"pipe-line-to",
"<shell-cmd>",
"Pipe the top line to the given shell command",
com_pipe_to,
help_text(":pipe-line-to")
.with_summary("Pipe the top line to the given shell command")
.with_parameter(help_text("shell-cmd", "The shell command-line to execute"))
.with_example({"sed -e 's/foo/bar/g'"})
},
{
"enable-filter",
"<regex>",
"Enable a previously created and disabled filter",
com_enable_filter,
help_text(":enable-filter")
.with_summary("Enable a previously created and disabled filter")
.with_parameter(help_text("pattern", "The regular expression used in the filter command"))
.with_example({"last message repeated"})
},
{
"disable-filter",
"<regex>",
"Disable a filter created with filter-in/filter-out",
com_disable_filter,
help_text(":disable-filter")
.with_summary("Disable a filter created with filter-in/filter-out")
.with_parameter(help_text("pattern", "The regular expression used in the filter command"))
.with_example({"last message repeated"})
},
{
"enable-word-wrap",
NULL,
"Enable word-wrapping for the current view",
com_enable_word_wrap,
help_text(":enable-word-wrap")
.with_summary("Enable word-wrapping for the current view")
},
{
"disable-word-wrap",
NULL,
"Disable word-wrapping for the current view",
com_disable_word_wrap,
help_text(":disable-word-wrap")
.with_summary("Disable word-wrapping for the current view")
},
{
"create-logline-table",
"<table-name>",
"Create an SQL table using the top line of the log view as a template",
com_create_logline_table,
help_text(":create-logline-table")
.with_summary("Create an SQL table using the top line of the log view as a template")
.with_parameter(help_text("table-name", "The name for the new table"))
.with_example({"task_durations"})
},
{
"delete-logline-table",
"<table-name>",
"Delete a table created with create-logline-table",
com_delete_logline_table,
help_text(":delete-logline-table")
.with_summary("Delete a table created with create-logline-table")
.with_parameter(help_text("table-name", "The name of the table to delete"))
.with_example({"task_durations"})
},
{
"create-search-table",
"<table-name> [<regex>]",
"Create an SQL table based on a regex search",
com_create_search_table,
help_text(":create-search-table")
.with_summary("Create an SQL table based on a regex search")
.with_parameter(help_text("table-name", "The name of the table to create"))
.with_parameter(help_text(
"pattern",
"The regular expression used to capture the table columns. "
"If not given, the current search pattern is used.")
.optional())
.with_example({R"(task_durations duration=(?<duration>\d+))"})
},
{
"delete-search-table",
"<table-name>",
"Delete a table created with create-search-table",
com_delete_search_table,
help_text(":delete-search-table")
.with_summary("Create an SQL table based on a regex search")
.with_parameter(help_text("table-name", "The name of the table to create"))
.with_example({"task_durations"})
},
{
"open",
@ -3072,24 +3253,48 @@ readline_context::command_t STD_COMMANDS[] = {
"Open the given file(s) in lnav",
#endif
com_open,
help_text(":open")
.with_summary(
#ifdef HAVE_LIBCURL
"Open the given file(s) or URLs in lnav"
#else
"Open the given file(s) in lnav"
#endif
)
.with_parameter(
help_text{"path", "The path to the file to open"}
.one_or_more())
.with_example({"~/.lnav/example", ""})
},
{
"close",
NULL,
"Close the top file",
com_close,
help_text(":close")
.with_summary("Close the top file in the view")
},
{
"partition-name",
"<name>",
"Mark the top line in the log view as the start of a new partition with the given name",
com_partition_name,
help_text(":partition-name")
.with_summary("Mark the top line in the log view as the start of a new partition with the given name")
.with_parameter(help_text("name", "The name for the new partition"))
.with_example({"reboot"})
},
{
"clear-partition",
NULL,
"Clear the partition the top line is a part of",
com_clear_partition,
help_text(":clear-partition")
.with_summary("Clear the partition the top line is a part of")
},
{
"pt-min-time",
@ -3108,96 +3313,174 @@ readline_context::command_t STD_COMMANDS[] = {
"<lnav-command>",
"Add the given command to the session file (~/.lnav/session)",
com_session,
help_text(":session")
.with_summary("Add the given command to the session file (~/.lnav/session)")
.with_parameter(help_text("lnav-command", "The lnav command to save."))
.with_example({":highlight foobar"})
},
{
"summarize",
"<column-name>",
"Execute a SQL query that computes the characteristics of the values in the given column",
com_summarize,
help_text(":summarize")
.with_summary("Execute a SQL query that computes the characteristics of the values in the given column")
.with_parameter(help_text("column-name", "The name of the column to analyze."))
.with_example({"sc_bytes"})
},
{
"switch-to-view",
"<view-name>",
"Switch to the given view",
com_switch_to_view,
help_text(":switch-to-view")
.with_summary("Switch to the given view")
.with_parameter(help_text("view-name", "The name of the view to switch to."))
.with_example({"schema"})
},
{
"reset-session",
NULL,
"Reset the session state, clearing all filters, highlights, and bookmarks",
com_reset_session,
help_text(":reset-session")
.with_summary("Reset the session state, clearing all filters, highlights, and bookmarks")
},
{
"load-session",
NULL,
"Load the latest session state",
com_load_session,
help_text(":load-session")
.with_summary("Load the latest session state")
},
{
"save-session",
NULL,
"Save the current state as a session",
com_save_session,
help_text(":save-session")
.with_summary("Save the current state as a session")
},
{
"set-min-log-level",
"<log-level>",
"Set the minimum log level to display in the log view",
com_set_min_log_level,
help_text(":set-min-log-level")
.with_summary("Set the minimum log level to display in the log view")
.with_parameter(help_text("log-level", "The new minimum log level"))
.with_example({"error"})
},
{
"redraw",
NULL,
"Do a full redraw of the screen",
com_redraw,
help_text(":redraw")
.with_summary("Do a full redraw of the screen")
},
{
"zoom-to",
"<zoom-level>",
"Zoom the histogram view to the given level",
com_zoom_to,
help_text(":zoom-to")
.with_summary("Zoom the histogram view to the given level")
.with_parameter(help_text("zoom-level", "The zoom level"))
.with_example({"1-week"})
},
{
"echo",
"[-n] <msg>",
"Echo the given message",
com_echo,
help_text(":echo")
.with_summary("Echo the given message")
.with_parameter(help_text("msg", "The message to display"))
.with_example({"Hello, World!"})
},
{
"alt-msg",
"<msg>",
"Display a message in the alternate command position",
com_alt_msg,
help_text(":alt-msg")
.with_summary("Display a message in the alternate command position")
.with_parameter(help_text("msg", "The message to display"))
.with_example({"Press t to switch to the text view"})
},
{
"eval",
"<msg>",
"Evaluate the given command/query after doing environment variable substitution",
com_eval,
help_text(":eval")
.with_summary(
"Evaluate the given command/query after doing environment variable substitution")
.with_parameter(help_text("command",
"The command or query to perform substitution on."))
.with_examples(
{
{":echo $HOME"},
{";SELECT * FROM ${table}"}
})
},
{
"config",
"<option> [<value>]",
"Read or write a configuration option",
com_config,
help_text(":config")
.with_summary("Read or write a configuration option")
.with_parameter(help_text("option", "The path to the option to read or write"))
.with_parameter(help_text("value", "The value to write. If not given, the current value is returned")
.optional())
.with_example({"/ui/clock-format"})
},
{
"save-config",
NULL,
"Save the current configuration state",
com_save_config,
help_text(":save-config")
.with_summary("Save the current configuration state")
},
{
"reset-config",
"<option>",
"Reset the configuration option to its default value",
com_reset_config,
help_text(":reset-config")
.with_summary("Reset the configuration option to its default value")
.with_parameter(help_text("option", "The path to the option to reset"))
.with_example({"/ui/clock-format"})
},
{
"spectrogram",
"<field-name>",
"Visualize the given message field using a spectrogram",
com_spectrogram,
help_text(":spectrogram")
.with_summary("Visualize the given message field using a spectrogram")
.with_parameter(help_text("field-name", "The name of the numeric field to visualize."))
.with_example({"sc_bytes"})
},
{ NULL },

@ -73,7 +73,7 @@ public:
lf->read_full_message(lf->begin() + cl_copy, line);
format->annotate(line, sa, line_values);
body = find_string_attr_range(sa, &textview_curses::SA_BODY);
if (body.lr_end == -1 || body.length() == 0) {
if (body.lr_end == -1) {
this->ldt_schema_id.clear();
return;
}
@ -158,7 +158,7 @@ public:
lf->read_full_message(lf_iter, this->ldt_current_line);
lf->get_format()->annotate(this->ldt_current_line, sa, line_values);
body = find_string_attr_range(sa, &textview_curses::SA_BODY);
if (body.lr_end == -1 || body.length() == 0) {
if (body.lr_end == -1) {
return false;
}

@ -269,6 +269,10 @@ public:
memcpy(this->ll_schema, ba.in(), sizeof(this->ll_schema));
};
char get_schema() const {
return this->ll_schema[0];
};
/**
* Perform a partial match of the given schema against this log line.
* Storing the full schema is not practical, so we just keep the first four
@ -894,6 +898,7 @@ public:
bool vd_internal;
std::vector<std::string> vd_action_list;
std::string vd_rewriter;
std::string vd_description;
};
struct indexed_value_def {

@ -490,6 +490,11 @@ static struct json_path_handler value_def_handlers[] = {
.with_description("A command that will rewrite this field when pretty-printing")
.for_field(&nullobj<external_log_format::value_def>()->vd_rewriter),
json_path_handler("description")
.with_synopsis("<string>")
.with_description("A description of the field")
.for_field(&nullobj<external_log_format::value_def>()->vd_description),
json_path_handler()
};

@ -33,9 +33,14 @@
#include <string>
#include <vector>
#include "attr_line.hh"
class plain_text_source
: public text_sub_source {
public:
plain_text_source() {
};
plain_text_source(std::string text)
{
size_t start = 0, end;
@ -52,10 +57,24 @@ public:
};
plain_text_source(const std::vector<std::string> &text_lines) {
for (auto &str : text_lines) {
this->tds_lines.emplace_back(str);
}
this->tds_longest_line = this->compute_longest_line();
};
plain_text_source(const std::vector<attr_line_t> &text_lines) {
this->tds_lines = text_lines;
this->tds_longest_line = this->compute_longest_line();
};
plain_text_source &replace_with(attr_line_t &text_lines) {
this->tds_lines.clear();
text_lines.split_lines(this->tds_lines);
this->tds_longest_line = this->compute_longest_line();
return *this;
};
size_t text_line_count()
{
return this->tds_lines.size();
@ -68,9 +87,13 @@ public:
void text_value_for_line(textview_curses &tc,
int row,
std::string &value_out,
bool no_scrub)
{
value_out = this->tds_lines[row];
bool no_scrub) {
value_out = this->tds_lines[row].get_string();
};
void text_attrs_for_line(textview_curses &tc, int line,
string_attrs_t &value_out) {
value_out = this->tds_lines[line].get_attrs();
};
size_t text_size_for_line(textview_curses &tc, int row, bool raw) {
@ -80,15 +103,13 @@ public:
private:
size_t compute_longest_line() {
size_t retval = 0;
for (std::vector<std::string>::iterator iter = this->tds_lines.begin();
iter != this->tds_lines.end();
++iter) {
retval = std::max(retval, iter->length());
for (auto &iter : this->tds_lines) {
retval = std::max(retval, (size_t) iter.length());
}
return retval;
};
std::vector<std::string> tds_lines;
std::vector<attr_line_t> tds_lines;
size_t tds_longest_line;
};

@ -39,6 +39,7 @@
#include "readline_curses.hh"
#include "log_search_table.hh"
#include "log_format_loader.hh"
#include "help_text_formatter.hh"
using namespace std;
@ -59,6 +60,7 @@ void rl_change(void *dummy, readline_curses *rc)
}
if (iter == lnav_commands.end() ||
iter->second.c_description == NULL) {
lnav_data.ld_doc_view.set_height(vis_line_t(0));
lnav_data.ld_bottom_source.set_prompt(
"Enter an lnav command: " ABORT_MSG);
lnav_data.ld_bottom_source.grep_error("");
@ -89,21 +91,22 @@ void rl_change(void *dummy, readline_curses *rc)
}
else {
readline_context::command_t &cmd = iter->second;
char args_text[128] = {0};
char help_text[1024];
if (cmd.c_args != NULL && strlen(cmd.c_args) > 0) {
snprintf(args_text, sizeof(args_text),
" %s",
cmd.c_args);
help_text &ht = cmd.c_help;
if (ht.ht_name) {
textview_curses &dtc = lnav_data.ld_doc_view;
vector<attr_line_t> lines;
unsigned long width;
vis_line_t height;
attr_line_t al;
dtc.get_dimensions(height, width);
format_help_text_for_term(ht, min(70UL, width), al);
al.split_lines(lines);
lnav_data.ld_doc_source.replace_with(al);
dtc.set_height(vis_line_t(lines.size()));
}
snprintf(help_text, sizeof(help_text),
ANSI_BOLD("%s%s") " -- %s " ABORT_MSG,
cmd.c_name,
args_text,
cmd.c_description);
lnav_data.ld_bottom_source.set_prompt(help_text);
lnav_data.ld_bottom_source.grep_error("");
lnav_data.ld_status[LNS_BOTTOM].window_change();
}
@ -133,6 +136,26 @@ void rl_change(void *dummy, readline_curses *rc)
}
break;
}
case LNM_SQL: {
attr_line_t al(rc->get_line_buffer());
const string_attrs_t &sa = al.get_attrs();
size_t x = rc->get_x() - 1;
annotate_sql_statement(al);
if (x > 0 && x >= al.length()) {
x -= 1;
}
auto iter = find_string_attr(sa, x);
if (iter != sa.end()) {
if (iter->sa_type == &SQL_FUNCTION_ATTR) {
}
}
break;
}
default:
break;
}
@ -218,6 +241,7 @@ void rl_abort(void *dummy, readline_curses *rc)
lnav_view_t index = (lnav_view_t)(tc - lnav_data.ld_views);
lnav_data.ld_bottom_source.set_prompt("");
lnav_data.ld_doc_view.set_height(vis_line_t(0));
lnav_data.ld_bottom_source.grep_error("");
switch (lnav_data.ld_mode) {
@ -241,6 +265,7 @@ void rl_callback(void *dummy, readline_curses *rc)
string alt_msg;
lnav_data.ld_bottom_source.set_prompt("");
lnav_data.ld_doc_view.set_height(vis_line_t(0));
switch (lnav_data.ld_mode) {
case LNM_PAGING:
require(0);
@ -351,7 +376,7 @@ void rl_display_matches(void *dummy, readline_curses *rc)
const std::vector<std::string> &matches = rc->get_matches();
textview_curses &tc = lnav_data.ld_match_view;
unsigned long width, height;
int max_len, cols, rows, match_height, bottom_height;
int max_len, cols, rows, match_height;
getmaxyx(lnav_data.ld_window, height, width);
@ -360,12 +385,6 @@ void rl_display_matches(void *dummy, readline_curses *rc)
rows = (matches.size() + cols - 1) / cols;
match_height = min((unsigned long)rows, (height - 4) / 2);
bottom_height = match_height + 1 + rc->get_height();
for (int lpc = 0; lpc < LNV__MAX; lpc++) {
lnav_data.ld_views[lpc].set_height(vis_line_t(-bottom_height));
}
lnav_data.ld_status[LNS_BOTTOM].set_top(-bottom_height);
delete tc.get_sub_source();
@ -388,12 +407,12 @@ void rl_display_matches(void *dummy, readline_curses *rc)
if (match_height > 0) {
tc.set_window(lnav_data.ld_window);
tc.set_y(height - bottom_height + 1);
tc.set_height(vis_line_t(match_height));
tc.reload_data();
}
else {
tc.set_window(NULL);
tc.set_height(vis_line_t(0));
}
}

@ -61,6 +61,7 @@
#include "lnav_util.hh"
#include "ansi_scrubber.hh"
#include "readline_curses.hh"
#include "spookyhash/SpookyV2.h"
using namespace std;
@ -458,6 +459,8 @@ void readline_curses::start(void)
}
else {
if (FD_ISSET(STDIN_FILENO, &ready_rfds)) {
static uint64_t last_h1, last_h2;
struct itimerval itv;
if (current_context == this->rc_contexts.end()) {
@ -478,13 +481,21 @@ void readline_curses::start(void)
rl_callback_handler_remove();
}
else {
if (sendcmd(this->rc_command_pipe[RCF_SLAVE],
'l',
rl_line_buffer,
rl_end) != 0) {
uint64_t h1 = 1, h2 = 2;
SpookyHash::Hash128(rl_line_buffer, rl_end, &h1, &h2);
if (h1 == last_h1 && h2 == last_h2) {
// do nothing
} else if (sendcmd(this->rc_command_pipe[RCF_SLAVE],
'l',
rl_line_buffer,
rl_end) != 0) {
perror("line: write failed");
_exit(1);
}
last_h1 = h1;
last_h2 = h2;
}
}
if (FD_ISSET(this->rc_command_pipe[RCF_SLAVE], &ready_rfds)) {
@ -749,6 +760,8 @@ void readline_curses::check_poll_set(const vector<struct pollfd> &pollfds)
case 'l':
this->rc_line_buffer = &msg[2];
this->rc_change.invoke(this);
this->rc_matches.clear();
this->rc_display_match.invoke(this);
break;
case 'n':

@ -54,6 +54,7 @@
#include "auto_fd.hh"
#include "vt52_curses.hh"
#include "log_format.hh"
#include "help_text_formatter.hh"
struct exec_context;
@ -76,6 +77,8 @@ public:
const char *c_description;
command_func_t c_func;
struct help_text c_help;
void operator=(command_func_t func) {
this->c_name = "anon";
this->c_args = NULL;

@ -352,7 +352,8 @@ void readline_regex_highlighter(attr_line_t &al, int x)
void readline_command_highlighter(attr_line_t &al, int x)
{
static const pcrepp RE_PREFIXES("^:(filter-in|filter-out|highlight)");
static const pcrepp RE_PREFIXES(
R"(^:(filter-in|filter-out|delete-filter|enable-filter|disable-filter|highlight|clear-highlight|create-search-table\s+[^\s]+\s+))");
static const pcrepp SH_PREFIXES("^:(eval|open|append-to|write-to|write-csv-to|write-json-to)");
static int keyword_attrs = (
A_BOLD|view_colors::ansi_color_pair(COLOR_CYAN, COLOR_BLACK));

@ -797,3 +797,113 @@ int sqlite_authorizer(void *pUserData, int action_code, const char *detail1,
}
return SQLITE_OK;
}
static string sql_keyword_re(void)
{
string retval = "(?:";
for (int lpc = 0; sql_keywords[lpc]; lpc++) {
if (lpc > 0) {
retval.append("|");
}
retval.append("\\b");
retval.append(sql_keywords[lpc]);
retval.append("\\b");
}
retval += ")";
return retval;
}
string_attr_type SQL_KEYWORD_ATTR("sql_keyword");
string_attr_type SQL_IDENTIFIER_ATTR("sql_ident");
string_attr_type SQL_FUNCTION_ATTR("sql_func");
string_attr_type SQL_STRING_ATTR("sql_string");
string_attr_type SQL_OPERATOR_ATTR("sql_oper");
string_attr_type SQL_PAREN_ATTR("sql_paren");
string_attr_type SQL_GARBAGE_ATTR("sql_garbage");
void annotate_sql_statement(attr_line_t &al)
{
static string keyword_re_str =
R"(\A)" + sql_keyword_re() + R"(|\.schema|\.msgformats)";
static struct {
pcrepp re;
string_attr_type_t type;
} PATTERNS[] = {
{ {keyword_re_str.c_str(), PCRE_CASELESS}, &SQL_KEYWORD_ATTR },
{ {R"(\A'[^']*('(?:'[^']*')*|$))"}, &SQL_STRING_ATTR },
{ {R"(\A(\$?\b[a-z_]\w*)|\"([^\"]+)\"|\[([^\]]+)])", PCRE_CASELESS}, &SQL_IDENTIFIER_ATTR },
{ {R"(\A(\*|<|>|=|!|\-|\+|\|\|))"}, &SQL_OPERATOR_ATTR },
{ {R"(\A\(|\))"}, &SQL_PAREN_ATTR },
{ {R"(\A.)"}, &SQL_GARBAGE_ATTR },
};
static pcrepp ws_pattern(R"(\A\s+)");
pcre_context_static<30> pc;
pcre_input pi(al.get_string());
string &line = al.get_string();
string_attrs_t &sa = al.get_attrs();
while (pi.pi_next_offset < line.length()) {
if (ws_pattern.match(pc, pi, PCRE_ANCHORED)) {
continue;
}
for (auto &pat : PATTERNS) {
if (pat.re.match(pc, pi, PCRE_ANCHORED)) {
pcre_context::capture_t *cap = pc.all();
struct line_range lr(cap->c_begin, cap->c_end);
sa.emplace_back(lr, pat.type);
break;
}
}
}
string_attrs_t::const_iterator iter;
int start = 0;
while ((iter = find_string_attr(sa, &SQL_IDENTIFIER_ATTR, start)) != sa.end()) {
string_attrs_t::const_iterator piter;
bool found_open = false;
ssize_t lpc;
for (lpc = iter->sa_range.lr_end; lpc < line.length(); lpc++) {
if (line[lpc] == '(') {
found_open = true;
break;
} else if (!isspace(line[lpc])) {
break;
}
}
if (found_open) {
ssize_t pstart = lpc + 1;
int depth = 1;
while (depth > 0 &&
(piter = find_string_attr(sa, &SQL_PAREN_ATTR, pstart)) != sa.end()) {
if (line[piter->sa_range.lr_start] == '(') {
depth += 1;
} else {
depth -= 1;
}
pstart = piter->sa_range.lr_end;
}
line_range func_range{iter->sa_range.lr_start};
if (piter == sa.end()) {
func_range.lr_end = line.length();
} else {
func_range.lr_end = piter->sa_range.lr_end;
}
sa.emplace_back(func_range, &SQL_FUNCTION_ATTR);
}
start = iter->sa_range.lr_end;
}
remove_string_attr(sa, &SQL_PAREN_ATTR);
}

@ -41,6 +41,8 @@
#include <string>
#include <vector>
#include "attr_line.hh"
extern const char *sql_keywords[];
extern const char *sql_function_names[];
@ -100,4 +102,15 @@ void sqlite_close_wrapper(void *mem);
int sqlite_authorizer(void* pUserData, int action_code, const char *detail1,
const char *detail2, const char *detail3,
const char *detail4);
extern string_attr_type SQL_KEYWORD_ATTR;
extern string_attr_type SQL_IDENTIFIER_ATTR;
extern string_attr_type SQL_FUNCTION_ATTR;
extern string_attr_type SQL_STRING_ATTR;
extern string_attr_type SQL_OPERATOR_ATTR;
extern string_attr_type SQL_PAREN_ATTR;
extern string_attr_type SQL_GARBAGE_ATTR;
void annotate_sql_statement(attr_line_t &al_inout);
#endif

@ -37,10 +37,14 @@ using namespace std;
void statusview_curses::do_update(void)
{
int top, attrs, field, field_count, left = 1, right;
int top, attrs, field, field_count, left = 0, right;
view_colors & vc = view_colors::singleton();
unsigned long width, height;
if (!this->sc_enabled) {
return;
}
getmaxyx(this->sc_window, height, width);
if (width != this->sc_last_width) {
this->window_change();
@ -66,6 +70,7 @@ void statusview_curses::do_update(void)
int x;
val = sf.get_value();
left += sf.get_left_pad();
if (sf.is_right_justified()) {
right -= sf.get_width();
@ -73,7 +78,7 @@ void statusview_curses::do_update(void)
}
else {
x = left;
left += sf.get_width() + 1;
left += sf.get_width();
}
this->mvwattrline(this->sc_window,
top, x,

@ -56,7 +56,8 @@ public:
sf_cylon(false),
sf_cylon_pos(0),
sf_role(role),
sf_share(0) { };
sf_share(0),
sf_left_pad(0) { };
virtual ~status_field() { };
@ -142,6 +143,9 @@ public:
sa.push_back(string_attr(lr, &view_curses::VC_STYLE, COLOR_PAIR(color_pair)));
};
void set_left_pad(size_t val) { this->sf_left_pad = val; };
size_t get_left_pad() const { return this->sf_left_pad; };
/** @return The string value for this field. */
attr_line_t &get_value() { return this->sf_value; };
@ -183,6 +187,7 @@ protected:
attr_line_t sf_value; /*< The value to display for this field. */
view_colors::role_t sf_role; /*< The color role for this field. */
int sf_share;
size_t sf_left_pad;
};
/**
@ -216,7 +221,8 @@ public:
: sc_source(NULL),
sc_window(NULL),
sc_top(0),
sc_last_width(0) {
sc_last_width(0),
sc_enabled(true) {
};
virtual ~statusview_curses() { };
@ -229,6 +235,9 @@ public:
void set_window(WINDOW *win) { this->sc_window = win; };
WINDOW *get_window() { return this->sc_window; };
void set_enabled(bool value) { this->sc_enabled = value; };
bool get_enabled() const { return this->sc_enabled; };
void window_change(void) {
if (this->sc_source == NULL) {
return;
@ -280,6 +289,7 @@ private:
WINDOW * sc_window;
int sc_top;
unsigned long sc_last_width;
bool sc_enabled;
};
#endif

@ -171,18 +171,8 @@ regexp_match(const char *re, const char *str)
}
static
void extract(sqlite3_context *ctx, int argc, sqlite3_value **argv)
json_string extract(const char *str)
{
const char *str;
assert(argc == 1);
str = (const char *)sqlite3_value_text(argv[0]);
if (!str) {
sqlite3_result_null(ctx);
return;
}
data_scanner ds(str);
data_parser dp(&ds);
@ -196,14 +186,7 @@ void extract(sqlite3_context *ctx, int argc, sqlite3_value **argv)
elements_to_json(gen, dp, &dp.dp_pairs);
const unsigned char *buf;
size_t len;
yajl_gen_get_buf(gen, &buf, &len);
sqlite3_result_text(ctx, (const char *) buf, len, SQLITE_TRANSIENT);
#ifdef HAVE_SQLITE3_VALUE_SUBTYPE
sqlite3_result_subtype(ctx, JSON_SUBTYPE);
#endif
return json_string(gen);
}
static
@ -245,7 +228,12 @@ int string_extension_functions(const struct FuncDef **basic_funcs,
{"repl", "The replacement string"},
}),
{ "extract", 1, 0, SQLITE_UTF8, 0, extract },
sqlite_func_adapter<decltype(&extract), extract>::builder(
"extract",
"Automatically Parse and extract data from a string",
{
{"str", "The string to parse"},
}),
sqlite_func_adapter<decltype(
static_cast<bool (*)(const char *, const char *)>(&startswith)),

@ -95,7 +95,8 @@ public:
time_t current_time = time(NULL);
char buffer[32];
strftime(buffer, sizeof(buffer),
buffer[0] = ' ';
strftime(&buffer[1], sizeof(buffer) - 1,
lnav_config.lc_ui_clock_format.c_str(),
localtime(&current_time));
sf.set_value(buffer);

@ -99,6 +99,106 @@ attr_line_t &attr_line_t::with_ansi_string(const char *str, ...)
return *this;
}
attr_line_t &attr_line_t::append(const attr_line_t &al, text_wrap_settings *tws)
{
size_t start_len = this->al_string.length();
this->al_string.append(al.al_string);
for (auto &sa : al.al_attrs) {
this->al_attrs.emplace_back(sa);
this->al_attrs.back().sa_range.shift(0, start_len);
}
if (tws != nullptr && this->al_string.length() > tws->tws_width) {
ssize_t start_pos = start_len;
ssize_t line_start = this->al_string.rfind('\n', start_pos);
if (line_start == string::npos) {
line_start = 0;
} else {
line_start += 1;
}
ssize_t line_len = start_len - line_start;
ssize_t usable_width = tws->tws_width - tws->tws_indent;
ssize_t avail = max((ssize_t) 0, (ssize_t) tws->tws_width - line_len);
while (start_pos < this->al_string.length()) {
ssize_t lpc;
for (lpc = start_pos;
lpc < this->al_string.length() &&
(isalnum(this->al_string[lpc]) ||
this->al_string[lpc] == ',' ||
this->al_string[lpc] == ';');
lpc++) {
if (this->al_string[lpc] == '-') {
lpc += 1;
break;
}
}
if (avail != usable_width && lpc - start_pos > avail) {
this->insert(start_pos, 1, '\n')
.insert(start_pos + 1, tws->tws_indent, ' ');
start_pos += 1 + tws->tws_indent;
avail = tws->tws_width - tws->tws_indent;
} else {
avail -= (lpc - start_pos);
while (lpc < this->al_string.length() && avail) {
if (isalnum(this->al_string[lpc])) {
break;
}
avail -= 1;
lpc += 1;
}
start_pos = lpc;
if (!avail) {
this->insert(start_pos, 1, '\n')
.insert(start_pos + 1, tws->tws_indent, ' ');
start_pos += 1 + tws->tws_indent;
avail = usable_width;
}
}
}
}
return *this;
}
attr_line_t attr_line_t::subline(size_t start, size_t len) const
{
line_range lr{(int) start, (int) (start + len)};
attr_line_t retval;
retval.al_string = this->al_string.substr(start, len);
for (auto &sa : this->al_attrs) {
if (!lr.intersects(sa.sa_range)) {
continue;
}
retval.al_attrs.emplace_back(lr.intersection(sa.sa_range)
.shift(lr.lr_start, -lr.lr_start),
sa.sa_type,
sa.sa_value);
}
return retval;
}
void attr_line_t::split_lines(std::vector<attr_line_t> &lines) const
{
size_t pos = 0, next_line;
while ((next_line = this->al_string.find('\n', pos)) != std::string::npos) {
lines.emplace_back(this->subline(pos, next_line - pos));
pos = next_line + 1;
}
lines.emplace_back(this->subline(pos));
}
void view_curses::mvwattrline(WINDOW *window,
int y,
int x,

@ -61,6 +61,7 @@
#include <algorithm>
#include "lnav_log.hh"
#include "attr_line.hh"
#define KEY_CTRL_G 7
#define KEY_CTRL_L 12
@ -156,266 +157,6 @@ private:
int a_last_input;
};
/**
* Encapsulates a range in a string.
*/
struct line_range {
int lr_start;
int lr_end;
line_range(int start = -1, int end = -1) : lr_start(start), lr_end(end) { };
bool is_valid() const {
return this->lr_start != -1;
}
int length() const
{
return this->lr_end == -1 ? INT_MAX : this->lr_end - this->lr_start;
};
bool contains(int pos) const {
return this->lr_start <= pos && pos < this->lr_end;
};
bool contains(const struct line_range &other) const {
return this->contains(other.lr_start) && other.lr_end <= this->lr_end;
};
bool intersects(const struct line_range &other) const {
return this->contains(other.lr_start) || this->contains(other.lr_end);
};
void shift(int32_t start, int32_t amount) {
if (this->lr_start >= start) {
this->lr_start = std::max(start, this->lr_start + amount);
}
if (this->lr_end != -1 && start < this->lr_end) {
this->lr_end += amount;
if (this->lr_end < this->lr_start) {
this->lr_end = this->lr_start;
}
}
};
void ltrim(const char *str) {
while (this->lr_start < this->lr_end && isspace(str[this->lr_start])) {
this->lr_start += 1;
}
};
bool operator<(const struct line_range &rhs) const
{
if (this->lr_start < rhs.lr_start) { return true; }
else if (this->lr_start > rhs.lr_start) { return false; }
if (this->lr_end == rhs.lr_end) { return false; }
if (this->lr_end < rhs.lr_end) { return true; }
return false;
};
bool operator==(const struct line_range &rhs) const {
return (this->lr_start == rhs.lr_start && this->lr_end == rhs.lr_end);
};
const char *substr(const std::string &str) const {
if (this->lr_start == -1) {
return str.c_str();
}
return &(str.c_str()[this->lr_start]);
}
size_t sublen(const std::string &str) const {
if (this->lr_start == -1) {
return str.length();
}
if (this->lr_end == -1) {
return str.length() - this->lr_start;
}
return this->length();
}
};
/**
* Container for attribute values for a substring.
*/
typedef union {
void *sav_ptr;
int64_t sav_int;
} string_attr_value_t;
class string_attr_type { };
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) {
this->sa_value.sav_ptr = val;
};
string_attr(const struct line_range &lr, string_attr_type_t type, int64_t val = 0)
: sa_range(lr), sa_type(type) {
this->sa_value.sav_int = val;
};
string_attr() : sa_type(NULL) { };
bool operator<(const struct string_attr &rhs) const
{
return this->sa_range < rhs.sa_range;
};
struct line_range sa_range;
string_attr_type_t sa_type;
string_attr_value_t sa_value;
};
/** A map of line ranges to attributes for that range. */
typedef std::vector<string_attr> string_attrs_t;
inline string_attrs_t::const_iterator
find_string_attr(const string_attrs_t &sa, string_attr_type_t type)
{
string_attrs_t::const_iterator iter;
for (iter = sa.begin(); iter != sa.end(); ++iter) {
if (iter->sa_type == type) {
break;
}
}
return iter;
}
inline string_attrs_t::iterator
find_string_attr(string_attrs_t &sa, const struct line_range &lr)
{
string_attrs_t::iterator iter;
struct line_range retval;
for (iter = sa.begin(); iter != sa.end(); ++iter) {
if (lr.contains(iter->sa_range)) {
break;
}
}
return iter;
}
inline struct line_range
find_string_attr_range(const string_attrs_t &sa, string_attr_type_t type)
{
string_attrs_t::const_iterator iter = find_string_attr(sa, type);
if (iter != sa.end()) {
return iter->sa_range;
}
return line_range();
}
inline void remove_string_attr(string_attrs_t &sa, const struct line_range &lr)
{
string_attrs_t::iterator iter;
while ((iter = find_string_attr(sa, lr)) != sa.end()) {
sa.erase(iter);
}
}
inline void shift_string_attrs(string_attrs_t &sa, int32_t start, int32_t amount)
{
for (string_attrs_t::iterator iter = sa.begin(); iter != sa.end(); ++iter) {
iter->sa_range.shift(start, amount);
}
}
/**
* A line that has attributes.
*/
class attr_line_t {
public:
attr_line_t() {
this->al_attrs.reserve(RESERVE_SIZE);
};
attr_line_t(const std::string &str) : al_string(str) {
this->al_attrs.reserve(RESERVE_SIZE);
};
attr_line_t(const char *str) : al_string(str) {
this->al_attrs.reserve(RESERVE_SIZE);
};
/** @return The string itself. */
std::string &get_string() { return this->al_string; };
/** @return The attributes for the string. */
string_attrs_t &get_attrs() { return this->al_attrs; };
attr_line_t &with_string(const std::string &str) {
this->al_string = str;
return *this;
}
attr_line_t &with_ansi_string(const char *str, ...);
attr_line_t &with_attr(const string_attr &sa) {
this->al_attrs.push_back(sa);
return *this;
};
attr_line_t &right_justify(unsigned long width) {
long padding = width - this->length();
if (padding > 0) {
this->al_string.insert(0, padding, ' ');
for (std::vector<string_attr>::iterator iter = this->al_attrs.begin();
iter != this->al_attrs.end();
++iter) {
iter->sa_range.lr_start += padding;
iter->sa_range.lr_end += padding;
}
}
return *this;
}
ssize_t length() const {
size_t retval = this->al_string.length();
for (std::vector<string_attr>::const_iterator iter = this->al_attrs.begin();
iter != this->al_attrs.end();
++iter) {
retval = std::max(retval, (size_t) iter->sa_range.lr_start);
if (iter->sa_range.lr_end != -1) {
retval = std::max(retval, (size_t) iter->sa_range.lr_end);
}
}
return retval;
};
bool empty() const {
return this->length() == 0;
};
/** Clear the string and the attributes for the string. */
attr_line_t &clear()
{
this->al_string.clear();
this->al_attrs.clear();
return *this;
};
private:
const static size_t RESERVE_SIZE = 128;
std::string al_string;
string_attrs_t al_attrs;
};
/**
* Class that encapsulates a method to execute and the object on which to
* execute it.

@ -17,7 +17,7 @@
*
* 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
* WARRANTIES OF MERCHAN`TABILITY 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;

@ -21,8 +21,19 @@ add_executable(test_abbrev test_abbrev.cc
../src/pcrepp.cc
../src/lnav_log.cc
../src/spookyhash/SpookyV2.cpp)
add_executable(test_help_text_formatter test_help_text_formatter.cc
../src/help_text_formatter.cc
../src/view_curses.cc
../src/lnav_log.cc
../src/ansi_scrubber.cc
../src/pcrepp.cc)
add_executable(drive_sql_anno drive_sql_anno.cc ../src/lnav_log.cc ../src/pcrepp.cc)
link_directories(/opt/local/lib)
target_link_libraries(test_pcrepp /opt/local/lib/libpcre.a)
target_link_libraries(test_reltime /opt/local/lib/libpcre.a)
target_link_libraries(test_date_time_scanner /opt/local/lib/libpcre.a)
target_link_libraries(test_abbrev /opt/local/lib/libpcre.a)
target_link_libraries(drive_sql_anno /opt/local/lib/libpcre.a)
target_link_libraries(test_help_text_formatter
/opt/local/lib/libpcre.a
/opt/local/lib/libncurses.a)

@ -27,6 +27,7 @@ check_PROGRAMS = \
drive_sequencer \
drive_shlexer \
drive_sql \
drive_sql_anno \
drive_view_colors \
drive_vt52_curses \
drive_readline_curses \
@ -41,6 +42,7 @@ check_PROGRAMS = \
test_concise \
test_date_time_scanner \
test_grep_proc2 \
test_help_text_formatter \
test_hist_source \
test_json_ptr \
test_line_buffer2 \
@ -83,6 +85,10 @@ test_date_time_scanner_LDADD = ../src/libdiag.a $(SQLITE3_LIBS)
test_grep_proc2_SOURCES = test_grep_proc2.cc
test_grep_proc2_LDADD = ../src/libdiag.a $(PCRE_LIBS) -lz
test_help_text_formatter_SOURCES = test_help_text_formatter.cc
test_help_text_formatter_LDADD = ../src/libdiag.a $(CURSES_LIB) -lz \
$(CONFIG_OBJS) $(SQLITE3_LIBS)
test_hist_source_SOURCES = test_hist_source.cc
test_hist_source_LDADD = ../src/libdiag.a $(CURSES_LIB) -lz \
$(CONFIG_OBJS)
@ -200,6 +206,18 @@ drive_sql_LDADD = \
$(LIBCURL) \
-lpcrecpp
drive_sql_anno_SOURCES = \
drive_sql_anno.cc
drive_sql_anno_LDADD = \
../src/libdiag.a \
$(CONFIG_OBJS) \
$(SQLITE3_LIBS) \
$(PCRE_LIBS) \
$(CURSES_LIB) \
$(READLINE_LIBS) \
$(LIBCURL) \
-lpcrecpp
slicer_SOURCES = slicer.cc
slicer_LDADD = ../src/libdiag.a

@ -88,7 +88,7 @@ public:
int main(int argc, char *argv[])
{
int c, retval = EXIT_SUCCESS;
bool wait_for_input = false;
bool wait_for_input = false, set_height = false;
my_source ms;
WINDOW *win;
@ -104,6 +104,7 @@ int main(int argc, char *argv[])
break;
case 'h':
lv.set_height(vis_line_t(atoi(optarg)));
set_height = true;
break;
case 't':
lv.set_top(vis_line_t(atoi(optarg)));
@ -120,10 +121,17 @@ int main(int argc, char *argv[])
}
}
if (!set_height) {
unsigned long height, width;
getmaxyx(win, height, width);
lv.set_height(vis_line_t(height - lv.get_y()));
}
lv.do_update();
refresh();
if (wait_for_input)
if (wait_for_input) {
getch();
}
endwin();
return retval;

@ -0,0 +1,76 @@
/**
* Copyright (c) 2017, 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 drive_sql_anno.cc
*/
#include <stdio.h>
#include <stdlib.h>
#include "lnav.hh"
#include "sql_util.hh"
int main(int argc, char *argv[])
{
int retval = EXIT_SUCCESS;
log_argv(argc, argv);
if (argc < 2) {
fprintf(stderr, "error: expecting an SQL statement\n");
retval = EXIT_FAILURE;
}
else {
attr_line_t al(argv[1]);
annotate_sql_statement(al);
for (auto &attr : al.get_attrs()) {
auto &lr = attr.sa_range;
printf(" %d:%d (%s) -- %s\n",
lr.lr_start, lr.lr_end,
attr.sa_type->sat_name,
al.get_substring(lr).c_str());
}
if (argc == 3) {
int near;
sscanf(argv[2], "%d", &near);
auto iter = find_string_attr(al.get_attrs(), (size_t) near);
if (iter != al.get_attrs().end()) {
printf("nearest %s\n",
al.get_substring(iter->sa_range).c_str());
}
}
}
return retval;
}

@ -0,0 +1,87 @@
/**
* Copyright (c) 2017, 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 <stdio.h>
#include <assert.h>
#include <view_curses.hh>
#include <attr_line.hh>
#include "lnav_config.hh"
#include "help_text_formatter.hh"
struct _lnav_config lnav_config;
lnav_config_listener *lnav_config_listener::LISTENER_LIST;
int main(int argc, char *argv[])
{
int retval = EXIT_SUCCESS;
static help_text ht = help_text(
"regexp_replace",
"Replace parts of a string that match a regular expression")
.with_parameters(
{
{"str", "The string to perform replacements on"},
{"re", "The regular expression to match"},
{"repl", "The replacement string"},
})
.with_example(
{
";SELECT regexp_replace('abbb bbbc', 'b+', '') AS res",
"a c",
});
{
setenv("TERM", "ansi", 1);
screen_curses sc;
view_colors::init();
attr_line_t al;
format_help_text_for_term(ht, 35, al);
std::vector<attr_line_t> lines;
al.split_lines(lines);
line_range lr{0, 80};
int y = 0;
for (auto &line : lines) {
view_curses::mvwattrline(sc.get_window(), y++, 0, line, lr);
}
getch();
}
return retval;
}

@ -260,6 +260,18 @@ log_line,log_part,log_time,log_idle_msecs,log_level,log_mark,log_hostname,log_pi
1,<NULL>,2006-12-03 09:23:38.000,0,info,0,veridian,16442,automount,0,/auto/opt
EOF
run_test ${lnav_test} -n \
-c ";select sc_bytes from logline" \
-c ':write-csv-to -' \
${test_dir}/logfile_access_log.0
check_output "logline table is not working for defined columns" <<EOF
sc_bytes
134
46210
78929
EOF
run_test ${lnav_test} -n \
-c ':goto 1' \

@ -145,6 +145,35 @@ Row 0:
EOF
run_test ./drive_sql "select extract('foo=1') as result"
check_output "" <<EOF
Row 0:
Column result: {"foo":1}
EOF
run_test ./drive_sql "select extract('foo=1; bar=2') as result"
check_output "" <<EOF
Row 0:
Column result: {"foo":1,"bar":2}
EOF
run_test ./drive_sql "select extract(null) as result"
check_output "" <<EOF
Row 0:
Column result: (null)
EOF
run_test ./drive_sql "select extract(1) as result"
check_output "" <<EOF
Row 0:
Column result: {"col_0":1}
EOF
run_test ./drive_sql "SELECT * FROM regexp_capture('foo bar', '\w+ (\w+)')"
check_output "" <<EOF

@ -79,7 +79,7 @@ int main(int argc, char *argv[])
lnav_config.lc_ui_clock_format = "abc";
tss.update_time();
val = sf.get_value();
assert(val.get_string() == "abc");
assert(val.get_string() == " abc");
}
return retval;

Loading…
Cancel
Save