[exec] add a prompt to execute lnav scripts

pull/290/head
Timothy Stack 9 years ago
parent 7386ea3864
commit e07b4e448f

@ -14,6 +14,12 @@ lnav v0.8.1:
* Added ":hide-lines-before", ":hide-lines-after", and
":show-lines-before-and-after" commands so that you can filter out
log lines based on time.
* Scripts containing lnav commands/queries can now be executed using
the pipe ('|') hotkey. See the documentation for more information.
* Added an ":eval" command that can be used to execute a command or
query after performing environment variable substitution.
* Added an ":echo" command that can be useful for scripts to message
the user.
Interface Changes:
* The 'o/O' hotkeys have been reassigned to navigate through log

@ -100,3 +100,15 @@ Output
shell command and open the output in lnav.
* pipe-line-to <shell-cmd> - Pipe the top line in the current view to a shell
command and open the output in lnav.
Miscellaneous
-------------
* echo [-n] <msg> - Display the given message in the command prompt. Useful
for scripts to display messages to the user. The '-n' option leaves out the
new line at the end of the message.
* eval <cmd> - Evaluate the given command or SQL query after performing
environment variable substitution. The argument to *eval* must start with a
colon, semi-colon, or pipe character to signify whether the argument is a
command, SQL query, or a script to be executed, respectively.

@ -117,6 +117,7 @@ Spatial Navigation
- Next/previous bookmark
* - |ks| o |ke|
- |ks| Shift |ke| + |ks| o |ke|
-
- Forward/backward through log messages with a matching "opid" field
* - |ks| y |ke|
- |ks| Shift |ke| + |ks| y |ke|
@ -223,5 +224,7 @@ Query
- Execute an SQL query
* - |ks| : |ke|
- Execute an internal command, see :ref:`commands` for more information
* - |ks| \| |ke|
- Execute an lnav script located in a format directory.
* - |ks| Ctrl |ke| + |ks| ] |ke|
- Abort a

@ -42,6 +42,7 @@ set(diag_STAT_SRCS
session_data.cc
sequence_matcher.cc
shared_buffer.cc
shlex.cc
sqlite-extension-func.c
statusview_curses.cc
string-extension-functions.cc
@ -111,6 +112,7 @@ set(diag_STAT_SRCS
readline_possibilities.hh
relative_time.hh
sequence_sink.hh
shlex.hh
status_controllers.hh
strong_int.hh
sysclip.hh

@ -160,6 +160,7 @@ noinst_HEADERS = \
sequence_sink.hh \
session_data.hh \
shared_buffer.hh \
shlex.hh \
sql_util.hh \
sqlite-extension-func.h \
status_controllers.hh \
@ -244,6 +245,7 @@ libdiag_a_SOURCES = \
session_data.cc \
sequence_matcher.cc \
shared_buffer.cc \
shlex.cc \
sqlite-extension-func.c \
statusview_curses.cc \
string-extension-functions.cc \

@ -29,11 +29,14 @@
#include "config.h"
#include <wordexp.h>
#include <vector>
#include "json_ptr.hh"
#include "pcrecpp.h"
#include "lnav.hh"
#include "log_format_loader.hh"
#include "command_executor.hh"
@ -71,7 +74,7 @@ static int sql_progress(const struct log_cursor &lc)
string execute_from_file(const string &path, int line_number, char mode, const string &cmdline);
string execute_command(string cmdline)
string execute_command(const string &cmdline)
{
stringstream ss(cmdline);
@ -99,7 +102,7 @@ string execute_command(string cmdline)
return msg;
}
string execute_sql(string sql, string &alt_msg)
string execute_sql(const string &sql, string &alt_msg)
{
db_label_source &dls = lnav_data.ld_db_row_source;
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
@ -150,9 +153,16 @@ string execute_sql(string sql, string &alt_msg)
name = sqlite3_bind_parameter_name(stmt.in(), lpc + 1);
if (name[0] == '$') {
map<string, string> &vars = lnav_data.ld_local_vars.top();
map<string, string>::iterator local_var;
const char *env_value;
if ((env_value = getenv(&name[1])) != NULL) {
if ((local_var = vars.find(&name[1])) != vars.end()) {
sqlite3_bind_text(stmt.in(), lpc + 1,
local_var->second.c_str(), -1,
SQLITE_TRANSIENT);
}
else if ((env_value = getenv(&name[1])) != NULL) {
sqlite3_bind_text(stmt.in(), lpc + 1, env_value, -1, SQLITE_STATIC);
}
}
@ -246,15 +256,16 @@ string execute_sql(string sql, string &alt_msg)
return retval;
}
void execute_file(string path, bool multiline)
static string execute_file_contents(const string &path, bool multiline)
{
string retval;
FILE *file;
if (path == "-") {
file = stdin;
}
else if ((file = fopen(path.c_str(), "r")) == NULL) {
return;
return "error: unable to open file";
}
int line_number = 0, starting_line_number = 0;
@ -263,7 +274,9 @@ void execute_file(string path, bool multiline)
ssize_t line_size;
string cmdline;
char mode = '\0';
pair<string, string> dir_and_base = split_path(path);
lnav_data.ld_path_stack.push(dir_and_base.first);
while ((line_size = getline(&line, &line_max_size, file)) != -1) {
line_number += 1;
@ -280,7 +293,7 @@ void execute_file(string path, bool multiline)
case ';':
case '|':
if (mode) {
execute_from_file(path, starting_line_number, mode, trim(cmdline));
retval = execute_from_file(path, starting_line_number, mode, trim(cmdline));
}
starting_line_number = line_number;
@ -292,7 +305,7 @@ void execute_file(string path, bool multiline)
cmdline += line;
}
else {
execute_from_file(path, line_number, ':', line);
retval = execute_from_file(path, line_number, ':', line);
}
break;
}
@ -300,12 +313,84 @@ void execute_file(string path, bool multiline)
}
if (mode) {
execute_from_file(path, starting_line_number, mode, trim(cmdline));
retval = execute_from_file(path, starting_line_number, mode, trim(cmdline));
}
if (file != stdin) {
fclose(file);
}
lnav_data.ld_path_stack.pop();
return retval;
}
string execute_file(const string &path_and_args, bool multiline)
{
map<string, vector<string> > scripts;
map<string, vector<string> >::iterator iter;
static_root_mem<wordexp_t, wordfree> wordmem;
string msg, retval;
log_info("Executing file: %s", path_and_args.c_str());
int exp_rc = wordexp(path_and_args.c_str(),
wordmem.inout(),
WRDE_NOCMD | WRDE_UNDEF);
if (!wordexperr(exp_rc, msg)) {
retval = msg;
}
else if (wordmem->we_wordc == 0) {
retval = "error: no script specified";
}
else {
lnav_data.ld_local_vars.push(map<string, string>());
string script_name = wordmem->we_wordv[0];
map<string, string> &vars = lnav_data.ld_local_vars.top();
char env_arg_name[32];
string result;
snprintf(env_arg_name, sizeof(env_arg_name), "%d", (int) wordmem->we_wordc - 1);
vars["#"] = env_arg_name;
for (int lpc = 0; lpc < wordmem->we_wordc; lpc++) {
snprintf(env_arg_name, sizeof(env_arg_name), "%d", lpc);
vars[env_arg_name] = wordmem->we_wordv[lpc];
}
vector<string> paths_to_exec;
find_format_scripts(lnav_data.ld_config_paths, scripts);
if ((iter = scripts.find(script_name)) != scripts.end()) {
paths_to_exec = iter->second;
}
if (access(script_name.c_str(), R_OK) == 0) {
paths_to_exec.push_back(script_name);
}
else {
string local_path = lnav_data.ld_path_stack.top() + "/" + script_name;
if (access(local_path.c_str(), R_OK) == 0) {
paths_to_exec.push_back(local_path);
}
}
if (!paths_to_exec.empty()) {
for (vector<string>::iterator path_iter = paths_to_exec.begin();
path_iter != paths_to_exec.end();
++path_iter) {
result = execute_file_contents(*path_iter, multiline);
}
retval = "Executed: " + script_name + " -- " + result;
}
else {
retval = "error: unknown script -- " + script_name;
}
lnav_data.ld_local_vars.pop();
}
return retval;
}
string execute_from_file(const string &path, int line_number, char mode, const string &cmdline)
@ -322,7 +407,7 @@ string execute_from_file(const string &path, int line_number, char mode, const s
retval = execute_sql(cmdline, alt_msg);
break;
case '|':
execute_file(cmdline);
retval = execute_file(cmdline);
break;
default:
retval = execute_command(cmdline);
@ -362,7 +447,7 @@ void execute_init_commands(vector<pair<string, string> > &msgs)
msg = execute_sql(iter->substr(1), alt_msg);
break;
case '|':
execute_file(iter->substr(1));
msg = execute_file(iter->substr(1));
break;
}

@ -34,10 +34,10 @@
#include <string>
std::string execute_command(std::string cmdline);
std::string execute_command(const std::string &cmdline);
std::string execute_sql(std::string sql, std::string &alt_msg);
void execute_file(std::string path, bool multiline = true);
std::string execute_sql(const std::string &sql, std::string &alt_msg);
std::string execute_file(const std::string &path_and_args, bool multiline = true);
void execute_init_commands(std::vector<std::pair<std::string, std::string> > &msgs);
int sql_callback(sqlite3_stmt *stmt);

@ -115,12 +115,9 @@ static void sql_dirname(sqlite3_context *context,
text_end -= 1;
}
if (text_end == -1) {
sqlite3_result_text(context,
path_in[0] == '/' ? "/" : ".", 1,
SQLITE_STATIC);
return;
}
sqlite3_result_text(context,
path_in[0] == '/' ? "/" : ".", 1,
SQLITE_STATIC);
}
static void sql_joinpath(sqlite3_context *context,

@ -316,7 +316,16 @@ through the file.
that can be used in queries. See the SQL section
below for more information.
CTRL+] Abort command-line entry started with '/', ':', or ';'.
|<script> [arg1 .. argN]
Execute an lnav script contained in a format directory
(e.g. ~/.lnav/formats/default). The script can contain
lines starting with ':', ';', or '|' to execute commands,
SQL queries or execute other files in lnav. Any values
after the script name are treated as arguments can be
referenced in the script using '$1', '$2', and so on, like
in a shell script.
CTRL+] Abort command-line entry started with '/', ':', ';', or '|'.
y/Y Move forward/backward through the log view based on the
"log_line" column in the SQL result view.
@ -569,6 +578,17 @@ COMMANDS
clear-partition
Clear the partition the top line is a part of.
echo [-n] <msg> Display the given message. Useful for scripts to message
the user. The '-n' option leaves out the new line at the
end of the message.
eval <cmd|query|file>
Execute the given command, query, or file after doing
environment variable substitution. The argument to this
command should start with a ':', ';', or '|' signify the
type of action to perform (command, SQL query, execute
script).
pt-min-time [<date>|<relative-time>]
Set/get the minimum time range for any papertrail queries.
Absolute or relative time values can be specified.

@ -43,6 +43,7 @@
#include "readline_possibilities.hh"
#include "field_overlay_source.hh"
#include "hotkeys.hh"
#include "log_format_loader.hh"
using namespace std;
@ -940,6 +941,25 @@ void handle_paging_key(int ch)
}
break;
case '|': {
map<string, vector<string> > scripts;
lnav_data.ld_mode = LNM_EXEC;
lnav_data.ld_rl_view->clear_possibilities(LNM_EXEC, "__command");
find_format_scripts(lnav_data.ld_config_paths, scripts);
for (map<string, vector<string> >::iterator iter = scripts.begin();
iter != scripts.end();
++iter) {
lnav_data.ld_rl_view->add_possibility(LNM_EXEC, "__command", iter->first);
}
lnav_data.ld_rl_view->focus(LNM_EXEC, "|");
lnav_data.ld_bottom_source.set_prompt(
"Enter a script to execute: (Press "
ANSI_BOLD("CTRL+]") " to abort)");
break;
}
case 'p':
if (tc == &lnav_data.ld_views[LNV_LOG]) {
field_overlay_source *fos;

@ -1576,6 +1576,7 @@ static void handle_key(int ch)
case LNM_SEARCH:
case LNM_CAPTURE:
case LNM_SQL:
case LNM_EXEC:
handle_rl_key(ch);
break;
@ -1635,6 +1636,7 @@ static void looper(void)
readline_context search_context("search");
readline_context index_context("capture");
readline_context sql_context("sql", NULL, false);
readline_context exec_context("exec");
readline_curses rlc;
int lpc;
@ -1653,6 +1655,7 @@ static void looper(void)
rlc.add_context(LNM_SEARCH, search_context);
rlc.add_context(LNM_CAPTURE, index_context);
rlc.add_context(LNM_SQL, sql_context);
rlc.add_context(LNM_EXEC, exec_context);
rlc.start();
lnav_data.ld_rl_view = &rlc;
@ -2174,6 +2177,8 @@ int main(int argc, char *argv[])
umask(077);
lnav_data.ld_program_name = argv[0];
lnav_data.ld_local_vars.push(map<string, string>());
lnav_data.ld_path_stack.push(".");
rl_readline_name = "lnav";

@ -70,6 +70,7 @@ typedef enum {
LNM_SEARCH,
LNM_CAPTURE,
LNM_SQL,
LNM_EXEC,
} ln_mode_t;
enum {
@ -251,6 +252,9 @@ struct _lnav_data {
curl_looper ld_curl_looper;
relative_time ld_last_relative_time;
std::stack<std::map<std::string, std::string> > ld_local_vars;
std::stack<std::string> ld_path_stack;
};
extern struct _lnav_data lnav_data;

@ -51,39 +51,10 @@
#include "readline_curses.hh"
#include "relative_time.hh"
#include "log_search_table.hh"
#include "shlex.hh"
using namespace std;
static bool wordexperr(int rc, string &msg)
{
switch (rc) {
case WRDE_BADCHAR:
msg = "error: invalid filename character";
return false;
case WRDE_CMDSUB:
msg = "error: command substitution is not allowed";
return false;
case WRDE_BADVAL:
msg = "error: unknown environment variable in file name";
return false;
case WRDE_NOSPACE:
msg = "error: out of memory";
return false;
case WRDE_SYNTAX:
msg = "error: invalid syntax";
return false;
default:
break;
}
return true;
}
static string remaining_args(const string &cmdline,
const vector<string> &args,
size_t index = 1)
@ -2137,6 +2108,76 @@ static string com_redraw(string cmdline, vector<string> &args)
return "";
}
static string com_echo(string cmdline, vector<string> &args)
{
string retval = "error: expecting a message";
if (args.empty()) {
}
else if (args.size() > 1) {
bool lf = true;
if (args.size() > 2 && args[1] == "-n") {
lf = false;
}
retval = remaining_args(cmdline, args, lf ? 1 : 2);
if (lnav_data.ld_flags & LNF_HEADLESS) {
printf("%s", retval.c_str());
if (lf) {
putc('\n', stdout);
}
fflush(stdout);
}
}
return retval;
}
static string com_eval(string cmdline, vector<string> &args)
{
string retval = "error: expecting a command or query to evaluate";
if (args.empty()) {
}
else if (args.size() > 1) {
string all_args = remaining_args(cmdline, args);
string expanded_cmd;
shlex lexer(all_args.c_str(), all_args.size());
log_debug("Evaluating: %s", all_args.c_str());
if (!lexer.eval(expanded_cmd, lnav_data.ld_local_vars.top())) {
return "error: invalid arguments";
}
log_debug("Expanded command to evaluate: %s", expanded_cmd.c_str());
if (expanded_cmd.empty()) {
return "error: empty result after evaluation";
}
string alt_msg;
switch (expanded_cmd[0]) {
case ':':
retval = execute_command(expanded_cmd.substr(1));
break;
case ';':
retval = execute_sql(expanded_cmd.substr(1), alt_msg);
break;
case '|':
retval = "info: executed file -- " + expanded_cmd.substr(1) +
" -- " + execute_file(expanded_cmd.substr(1));
break;
default:
retval = "error: expecting argument to start with ':', ';', "
"or '|' to signify a command, SQL query, or script to execute";
break;
}
}
return retval;
}
readline_context::command_t STD_COMMANDS[] = {
{
"adjust-log-time",
@ -2413,6 +2454,19 @@ readline_context::command_t STD_COMMANDS[] = {
"Zoom the histogram view to the given level",
com_zoom_to,
},
{
"echo",
"[-n] <msg>",
"Echo the given message",
com_echo,
},
{
"eval",
"<msg>",
"Evaluate the given command/query after doing environment variable substitution",
com_eval,
},
{ NULL },
};

@ -36,6 +36,7 @@
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <wordexp.h>
#include <sqlite3.h>
@ -198,6 +199,27 @@ bool change_to_parent_dir(void)
return retval;
}
std::pair<std::string, std::string> split_path(const char *path, ssize_t len)
{
ssize_t dir_len = len;
while (dir_len >= 0 && (path[dir_len] == '/' || path[dir_len] == '\\')) {
dir_len -= 1;
}
while (dir_len >= 0) {
if (path[dir_len] == '/' || path[dir_len] == '\\') {
return make_pair(string(path, dir_len),
string(&path[dir_len + 1], len - dir_len));
}
dir_len -= 1;
}
return make_pair(path[0] == '/' ? "/" : ".",
path[0] == '/' ? string(&path[1], len - 1) : string(path, len));
}
file_format_t detect_file_format(const std::string &filename)
{
file_format_t retval = FF_UNKNOWN;
@ -587,3 +609,33 @@ bool read_file(const char *filename, string &out)
return false;
}
bool wordexperr(int rc, string &msg)
{
switch (rc) {
case WRDE_BADCHAR:
msg = "error: invalid filename character";
return false;
case WRDE_CMDSUB:
msg = "error: command substitution is not allowed";
return false;
case WRDE_BADVAL:
msg = "error: unknown environment variable in file name";
return false;
case WRDE_NOSPACE:
msg = "error: out of memory";
return false;
case WRDE_SYNTAX:
msg = "error: invalid syntax";
return false;
default:
break;
}
return true;
}

@ -169,6 +169,13 @@ std::string get_current_dir(void);
bool change_to_parent_dir(void);
std::pair<std::string, std::string> split_path(const char *path, ssize_t len);
inline
std::pair<std::string, std::string> split_path(const std::string &path) {
return split_path(path.c_str(), path.size());
};
enum file_format_t {
FF_UNKNOWN,
FF_SQLITE_DB,
@ -321,4 +328,6 @@ inline bool pollfd_ready(const std::vector<struct pollfd> &pollfds, int fd, shor
return false;
};
bool wordexperr(int rc, std::string &msg);
#endif

@ -34,6 +34,7 @@
#include <errno.h>
#include <fcntl.h>
#include <glob.h>
#include <libgen.h>
#include <map>
#include <string>
@ -732,3 +733,33 @@ void load_format_extra(sqlite3 *db,
exec_sql_in_path(db, *path_iter, errors);
}
}
static void find_format_in_path(const string &path,
std::map<std::string, std::vector<std::string> > &scripts)
{
string format_path = path + "/formats/*/*.lnav";
static_root_mem<glob_t, globfree> gl;
if (glob(format_path.c_str(), 0, NULL, gl.inout()) == 0) {
for (int lpc = 0; lpc < (int)gl->gl_pathc; lpc++) {
const char *filename = basename(gl->gl_pathv[lpc]);
string script_name = string(filename, strlen(filename) - 5);
scripts[script_name].push_back(gl->gl_pathv[lpc]);
}
}
}
void find_format_scripts(const std::vector<std::string> &extra_paths,
std::map<std::string, std::vector<std::string> > &scripts)
{
find_format_in_path("/etc/lnav", scripts);
find_format_in_path(SYSCONFDIR "/lnav", scripts);
find_format_in_path(dotlnav_path(""), scripts);
for (vector<string>::const_iterator path_iter = extra_paths.begin();
path_iter != extra_paths.end();
++path_iter) {
find_format_in_path(*path_iter, scripts);
}
}

@ -47,4 +47,7 @@ void load_format_extra(sqlite3 *db,
const std::vector<std::string> &extra_paths,
std::vector<std::string> &errors);
void find_format_scripts(const std::vector<std::string> &extra_paths,
std::map<std::string, std::vector<std::string> > &scripts);
#endif

@ -29,12 +29,16 @@
#include "config.h"
#include <wordexp.h>
#include "lnav.hh"
#include "lnav_util.hh"
#include "sysclip.hh"
#include "plain_text_source.hh"
#include "command_executor.hh"
#include "readline_curses.hh"
#include "log_search_table.hh"
#include "log_format_loader.hh"
using namespace std;
@ -129,6 +133,9 @@ static void rl_search_internal(void *dummy, readline_curses *rc, bool complete =
}
return;
case LNM_EXEC:
return;
default:
require(0);
break;
@ -181,7 +188,6 @@ void rl_callback(void *dummy, readline_curses *rc)
break;
case LNM_COMMAND:
lnav_data.ld_mode = LNM_PAGING;
rc->set_alt_value("");
rc->set_value(execute_command(rc->get_value()));
break;
@ -202,15 +208,19 @@ void rl_callback(void *dummy, readline_curses *rc)
n, N,
"to move forward/backward through search results"));
}
lnav_data.ld_mode = LNM_PAGING;
break;
case LNM_SQL:
rc->set_value(execute_sql(rc->get_value(), alt_msg));
rc->set_alt_value(alt_msg);
lnav_data.ld_mode = LNM_PAGING;
break;
case LNM_EXEC:
rc->set_value(execute_file(rc->get_value()));
break;
}
lnav_data.ld_mode = LNM_PAGING;
}
void rl_display_matches(void *dummy, readline_curses *rc)

@ -322,6 +322,17 @@ public:
}
};
void add_possibility(int context,
const std::string &type,
const std::vector<std::string> &values)
{
for (std::vector<std::string>::const_iterator iter = values.begin();
iter != values.end();
++iter) {
this->add_possibility(context, type, *iter);
}
};
void rem_possibility(int context,
const std::string &type,
const std::string &value);

@ -0,0 +1,34 @@
/**
* Copyright (c) 2015, 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 shlex.cc
*/
#include "config.h"
#include "shlex.hh"

@ -0,0 +1,191 @@
/**
* Copyright (c) 2015, 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 shlex.hh
*/
#ifndef LNAV_SHLEX_HH_H
#define LNAV_SHLEX_HH_H
#include <map>
#include "pcrepp.hh"
enum shlex_token_t {
ST_ERROR,
ST_ESCAPE,
ST_DOUBLE_QUOTE_START,
ST_DOUBLE_QUOTE_END,
ST_SINGLE_QUOTE_START,
ST_SINGLE_QUOTE_END,
ST_VARIABLE_REF,
};
class shlex {
public:
shlex(const char *str, size_t len)
: s_str(str), s_len(len), s_index(0), s_state(STATE_NORMAL) {
};
bool tokenize(pcre_context::capture_t &cap_out, shlex_token_t &token_out) {
while (this->s_index < this->s_len) {
switch (this->s_str[this->s_index]) {
case '\\':
cap_out.c_begin = this->s_index;
if (this->s_index + 1 < this->s_len) {
token_out = ST_ESCAPE;
this->s_index += 2;
cap_out.c_end = this->s_index;
}
else {
this->s_index += 1;
cap_out.c_end = this->s_index;
token_out = ST_ERROR;
}
return true;
case '\"':
cap_out.c_begin = this->s_index;
this->s_index += 1;
cap_out.c_end = this->s_index;
switch (this->s_state) {
case STATE_NORMAL:
token_out = ST_DOUBLE_QUOTE_START;
this->s_state = STATE_IN_DOUBLE_QUOTE;
return true;
case STATE_IN_DOUBLE_QUOTE:
token_out = ST_DOUBLE_QUOTE_END;
this->s_state = STATE_NORMAL;
return true;
default:
break;
}
break;
case '\'':
cap_out.c_begin = this->s_index;
this->s_index += 1;
cap_out.c_end = this->s_index;
switch (this->s_state) {
case STATE_NORMAL:
token_out = ST_SINGLE_QUOTE_START;
this->s_state = STATE_IN_SINGLE_QUOTE;
return true;
case STATE_IN_SINGLE_QUOTE:
token_out = ST_SINGLE_QUOTE_END;
this->s_state = STATE_NORMAL;
return true;
default:
break;
}
break;
case '$':
switch (this->s_state) {
case STATE_NORMAL:
case STATE_IN_DOUBLE_QUOTE:
cap_out.c_begin = this->s_index;
this->s_index += 1;
while (this->s_index < this->s_len &&
(isalnum(this->s_str[this->s_index]) ||
this->s_str[this->s_index] == '#' ||
this->s_str[this->s_index] == '_')) {
this->s_index += 1;
}
cap_out.c_end = this->s_index;
token_out = ST_VARIABLE_REF;
return true;
default:
break;
}
break;
default:
break;
}
this->s_index += 1;
}
return false;
};
bool eval(std::string &result, const std::map<std::string, std::string> &vars) {
result.clear();
pcre_context::capture_t cap;
shlex_token_t token;
int last_index = 0;
while (this->tokenize(cap, token)) {
result.append(&this->s_str[last_index], cap.c_begin - last_index);
switch (token) {
case ST_ERROR:
return false;
case ST_ESCAPE:
result.append(1, this->s_str[cap.c_begin + 1]);
break;
case ST_VARIABLE_REF: {
std::string var_name(&this->s_str[cap.c_begin + 1], cap.length() - 1);
std::map<std::string, std::string>::const_iterator local_var;
const char *var_value = getenv(var_name.c_str());
if ((local_var = vars.find(var_name)) != vars.end()) {
result.append(local_var->second);
}
else if (var_value != NULL) {
result.append(var_value);
}
break;
}
default:
break;
}
last_index = cap.c_end;
}
result.append(&this->s_str[last_index], this->s_len - last_index);
return true;
};
void reset() {
this->s_index = 0;
this->s_state = STATE_NORMAL;
};
enum state_t {
STATE_NORMAL,
STATE_IN_DOUBLE_QUOTE,
STATE_IN_SINGLE_QUOTE,
};
const char *s_str;
size_t s_len;
size_t s_index;
state_t s_state;
};
#endif //LNAV_SHLEX_HH_H

@ -25,6 +25,7 @@ check_PROGRAMS = \
drive_logfile \
drive_mvwattrline \
drive_sequencer \
drive_shlexer \
drive_sql \
drive_view_colors \
drive_vt52_curses \
@ -131,6 +132,9 @@ drive_logfile_LDADD = \
drive_sequencer_SOURCES = drive_sequencer.cc
drive_sequencer_LDADD = ../src/libdiag.a $(CURSES_LIB) $(SQLITE3_LIBS)
drive_shlexer_SOURCES = drive_shlexer.cc
drive_shlexer_LDADD = ../src/libdiag.a
drive_data_scanner_SOURCES = \
drive_data_scanner.cc
drive_data_scanner_LDADD = \
@ -188,6 +192,7 @@ dist_noinst_SCRIPTS = \
test_logfile.sh \
test_mvwattrline.sh \
test_sessions.sh \
test_shlexer.sh \
test_sql.sh \
test_sql_coll_func.sh \
test_sql_json_func.sh \
@ -303,6 +308,7 @@ TESTS = \
test_pcrepp \
test_reltime \
test_sessions.sh \
test_shlexer.sh \
test_sql.sh \
test_sql_coll_func.sh \
test_sql_json_func.sh \

@ -0,0 +1,88 @@
/**
* Copyright (c) 2015, 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 <stdlib.h>
#include "shlex.hh"
using namespace std;
const char *ST_TOKEN_NAMES[] = {
"err",
"esc",
"dst",
"den",
"sst",
"sen",
"ref",
};
int main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(stderr, "error: expecting an argument to parse\n");
exit(EXIT_FAILURE);
}
shlex lexer(argv[1], strlen(argv[1]));
pcre_context::capture_t cap;
shlex_token_t token;
printf(" %s\n", argv[1]);
while (lexer.tokenize(cap, token)) {
int lpc;
printf("%s ", ST_TOKEN_NAMES[token]);
for (lpc = 0; lpc < cap.c_end; lpc++) {
if (lpc == cap.c_begin) {
fputc('^', stdout);
}
else if (lpc == (cap.c_end - 1)) {
fputc('^', stdout);
}
else if (lpc > cap.c_begin) {
fputc('-', stdout);
}
else{
fputc(' ', stdout);
}
}
printf("\n");
}
lexer.reset();
std::string result;
if (lexer.eval(result, map<string, string>())) {
printf("eval -- %s\n", result.c_str());
}
return EXIT_SUCCESS;
}

@ -0,0 +1,2 @@
:eval :echo nested here $0 $1 $2

@ -1,5 +1,30 @@
#! /bin/bash
run_test ${lnav_test} -n \
-c "|${test_dir}/toplevel.lnav 123 456" \
${test_dir}/logfile_access_log.0
check_error_output "include toplevel.lnav" <<EOF
EOF
check_output "include toplevel.lnav" <<EOF
toplevel here 123 456
nested here nested.lnav abc
192.168.202.254 - - [20/Jul/2009:22:59:26 +0000] "GET /vmw/cgi/tramp HTTP/1.0" 200 134 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"
EOF
run_test ${lnav_test} -n \
-f "nonexistent.lnav" \
${test_dir}/logfile_access_log.0
check_error_output "include nonexistent" <<EOF
invalid command file: No such file or directory
EOF
run_test ${lnav_test} -n \
-c ":adjust-log-time 2010-01-01T00:00:00" \
${test_dir}/logfile_access_log.0
@ -653,3 +678,42 @@ check_output "hide-lines-after does not work?" <<EOF
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"
EOF
export XYZ="World"
run_test ${lnav_test} -n \
-c ':echo Hello, $XYZ!' \
${test_dir}/logfile_access_log.0
check_output "echo hello" <<EOF
Hello, \$XYZ!
192.168.202.254 - - [20/Jul/2009:22:59:26 +0000] "GET /vmw/cgi/tramp HTTP/1.0" 200 134 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"
EOF
export XYZ="World"
run_test ${lnav_test} -n \
-c ':echo -n Hello, ' \
-c ':echo World!' \
${test_dir}/logfile_access_log.0
check_output "echo hello" <<EOF
Hello, World!
192.168.202.254 - - [20/Jul/2009:22:59:26 +0000] "GET /vmw/cgi/tramp HTTP/1.0" 200 134 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"
EOF
run_test ${lnav_test} -n \
-c ':eval :echo Hello, $XYZ!' \
${test_dir}/logfile_access_log.0
check_output "eval echo hello" <<EOF
Hello, World!
192.168.202.254 - - [20/Jul/2009:22:59:26 +0000] "GET /vmw/cgi/tramp HTTP/1.0" 200 134 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"
EOF

@ -0,0 +1,65 @@
#! /bin/bash
export FOO='bar'
export DEF='xyz'
run_test drive_shlexer '$FOO'
check_output "var ref" <<EOF
\$FOO
ref ^--^
eval -- bar
EOF
run_test drive_shlexer '\a'
check_output "escape" <<EOF
\a
esc ^^
eval -- a
EOF
run_test drive_shlexer "'abc'"
check_output "single" <<EOF
'abc'
sst ^
sen ^
eval -- abc
EOF
run_test drive_shlexer '"def"'
check_output "double" <<EOF
"def"
dst ^
den ^
eval -- def
EOF
run_test drive_shlexer '"abc $DEF 123"'
check_output "double w/ref" <<EOF
"abc \$DEF 123"
dst ^
ref ^--^
den ^
eval -- abc xyz 123
EOF
run_test drive_shlexer "'abc \$DEF 123'"
check_output "single w/ref" <<EOF
'abc \$DEF 123'
sst ^
sen ^
eval -- abc \$DEF 123
EOF
run_test drive_shlexer 'abc $DEF 123'
check_output "unquoted" <<EOF
abc \$DEF 123
ref ^--^
eval -- abc xyz 123
EOF

@ -0,0 +1,4 @@
:eval :echo toplevel here $1 $2
|nested.lnav abc
Loading…
Cancel
Save