[cmds] add :redirect-to command for redirecting the output of commands in scripts

Related to #551
This commit is contained in:
Timothy Stack 2018-10-12 07:12:35 -07:00
parent ea5ac46c0e
commit ac7ae1275a
12 changed files with 174 additions and 42 deletions

4
NEWS
View File

@ -3,6 +3,10 @@ lnav v0.8.5:
Features:
* The ":write-*" commands will now accept "/dev/clipboard" as a file name
that writes to the system clipboard.
* Added a ":redirect-to <path>" command to redirect command output to the
given file. This command is mostly useful in scripts where one might
want to redirect all output from commands like ":echo" and ":write-to -"
to a single file.
Interface Changes:
* The auto-complete behavior in the prompt has been modified to fall back

View File

@ -143,6 +143,10 @@ 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.
* redirect-to [path] - If a path is given, all output from commands, like
":echo" and when writing to stdout (e.g. :write-to -), will be sent to the
given file. If no path is specified, the current redirect will be cleared
and output will be captured as it was before the redirect was done.
.. _misc-cmd:

View File

@ -385,6 +385,7 @@ static string execute_file_contents(exec_context &ec, const string &path, bool m
pair<string, string> dir_and_base = split_path(path);
ec.ec_path_stack.push_back(dir_and_base.first);
ec.ec_output_stack.emplace_back(nonstd::nullopt);
while ((line_size = getline(&line, &line_max_size, file)) != -1) {
line_number += 1;
@ -431,6 +432,7 @@ static string execute_file_contents(exec_context &ec, const string &path, bool m
} else {
fclose(file);
}
ec.ec_output_stack.pop_back();
ec.ec_path_stack.pop_back();
return retval;
@ -731,7 +733,26 @@ int sql_callback(exec_context &ec, sqlite3_stmt *stmt)
future<string> pipe_callback(exec_context &ec, const string &cmdline, auto_fd &fd)
{
if (lnav_data.ld_output_stack.empty()) {
auto out = ec.get_output();
if (out) {
FILE *file = *out;
return std::async(std::launch::async, [&fd, file]() {
char buffer[1024];
ssize_t rc;
if (file == stdout) {
lnav_data.ld_stdout_used = true;
}
while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
fwrite(buffer, rc, 1, file);
}
return string();
});
} else {
auto pp = make_shared<piper_proc>(fd, false);
static int exec_count = 0;
char desc[128];
@ -755,22 +776,6 @@ future<string> pipe_callback(exec_context &ec, const string &cmdline, auto_fd &f
task();
return task.get_future();
} else {
return std::async(std::launch::async, [&]() {
FILE *file = lnav_data.ld_output_stack.top();
char buffer[1024];
ssize_t rc;
if (file == stdout) {
lnav_data.ld_stdout_used = true;
}
while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
fwrite(buffer, rc, 1, file);
}
return string();
});
}
}

View File

@ -35,6 +35,7 @@
#include <future>
#include <string>
#include "optional.hpp"
#include "auto_fd.hh"
#include "attr_line.hh"
#include "textview_curses.hh"
@ -47,9 +48,9 @@ typedef std::future<std::string> (*pipe_callback_t)(
exec_context &ec, const std::string &cmdline, auto_fd &fd);
struct exec_context {
exec_context(std::vector<logline_value> *line_values = NULL,
sql_callback_t sql_callback = NULL,
pipe_callback_t pipe_callback = NULL)
exec_context(std::vector<logline_value> *line_values = nullptr,
sql_callback_t sql_callback = nullptr,
pipe_callback_t pipe_callback = nullptr)
: ec_top_line(vis_line_t(0)),
ec_dry_run(false),
ec_line_values(line_values),
@ -58,6 +59,7 @@ struct exec_context {
this->ec_local_vars.push(std::map<std::string, std::string>());
this->ec_path_stack.emplace_back(".");
this->ec_source.emplace("unknown", 0);
this->ec_output_stack.emplace_back(nonstd::nullopt);
}
std::string get_error_prefix() {
@ -70,6 +72,18 @@ struct exec_context {
return "error:" + source.first + ":" + std::to_string(source.second) + ":";
}
nonstd::optional<FILE *> get_output() {
for (auto iter = this->ec_output_stack.rbegin();
iter != this->ec_output_stack.rend();
++iter) {
if (*iter) {
return *iter;
}
}
return nonstd::nullopt;
}
vis_line_t ec_top_line;
bool ec_dry_run;
@ -79,6 +93,7 @@ struct exec_context {
std::map<std::string, std::string> ec_global_vars;
std::vector<std::string> ec_path_stack;
std::stack<std::pair<std::string, int>> ec_source;
std::vector<nonstd::optional<FILE *>> ec_output_stack;
attr_line_t ec_accumulator;

View File

@ -3302,10 +3302,8 @@ int main(int argc, char *argv[])
lnav_data.ld_vtab_manager->register_vtab(new log_format_vtab_impl(
*log_format::find_root_format("generic_log")));
for (std::vector<log_format *>::iterator iter = log_format::get_root_formats().begin();
iter != log_format::get_root_formats().end();
++iter) {
log_vtab_impl *lvi = (*iter)->get_vtab_impl();
for (auto &iter : log_format::get_root_formats()) {
log_vtab_impl *lvi = iter->get_vtab_impl();
if (lvi != NULL) {
lnav_data.ld_vtab_manager->register_vtab(lvi);
@ -3481,10 +3479,8 @@ int main(int argc, char *argv[])
if (lnav_data.ld_flags & LNF_CHECK_CONFIG) {
rescan_files(true);
for (auto file_iter = lnav_data.ld_files.begin();
file_iter != lnav_data.ld_files.end();
++file_iter) {
auto lf = (*file_iter);
for (auto &ld_file : lnav_data.ld_files) {
auto lf = ld_file;
lf->rebuild_index();
@ -3496,7 +3492,7 @@ int main(int argc, char *argv[])
retval = EXIT_FAILURE;
continue;
}
for (logfile::iterator line_iter = lf->begin();
for (auto line_iter = lf->begin();
line_iter != lf->end();
++line_iter) {
if (!line_iter->is_continued()) {
@ -3587,14 +3583,14 @@ int main(int argc, char *argv[])
log_info("lnav_data:");
log_info(" flags=%x", lnav_data.ld_flags);
log_info(" commands:");
for (std::list<string>::iterator cmd_iter =
for (auto cmd_iter =
lnav_data.ld_commands.begin();
cmd_iter != lnav_data.ld_commands.end();
++cmd_iter) {
log_info(" %s", cmd_iter->c_str());
}
log_info(" files:");
for (map<string, logfile_open_options>::iterator file_iter =
for (auto file_iter =
lnav_data.ld_file_names.begin();
file_iter != lnav_data.ld_file_names.end();
++file_iter) {
@ -3608,7 +3604,7 @@ int main(int argc, char *argv[])
bool found_error = false;
init_session();
lnav_data.ld_output_stack.push(stdout);
lnav_data.ld_exec_context.ec_output_stack.back() = stdout;
alerter::singleton().enabled(false);
log_tc = &lnav_data.ld_views[LNV_LOG];

View File

@ -305,8 +305,6 @@ struct _lnav_data {
relative_time ld_last_relative_time;
std::stack<FILE *> ld_output_stack;
std::map<std::string, std::vector<script_metadata> > ld_scripts;
exec_context ld_exec_context;

View File

@ -568,7 +568,9 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
toclose = outfile;
}
else if (split_args[0] == "-" || split_args[0] == "/dev/stdout") {
if (lnav_data.ld_output_stack.empty()) {
auto ec_out = ec.get_output();
if (!ec_out) {
outfile = stdout;
nodelay(lnav_data.ld_window, 0);
endwin();
@ -583,7 +585,7 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
"----------------\n\n");
}
else {
outfile = lnav_data.ld_output_stack.top();
outfile = *ec_out;
}
if (outfile == stdout) {
lnav_data.ld_stdout_used = true;
@ -673,7 +675,7 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
if ((handle = yajl_gen_alloc(NULL)) == NULL) {
if (outfile != stdout) {
fclose(outfile);
closer(outfile);
}
return "error: unable to allocate memory";
}
@ -945,6 +947,52 @@ static string com_pipe_to(exec_context &ec, string cmdline, vector<string> &args
return retval;
}
static string com_redirect_to(exec_context &ec, string cmdline, vector<string> &args)
{
if (args.empty()) {
args.emplace_back("filename");
return "";
}
if (args.size() == 1) {
if (ec.ec_dry_run) {
return "info: redirect will be cleared";
}
ec.ec_output_stack.back() = nonstd::nullopt;
return "info: cleared redirect";
}
string fn = trim(remaining_args(cmdline, args));
vector<string> split_args;
shlex lexer(fn);
scoped_resolver scopes = {
&ec.ec_local_vars.top(),
&ec.ec_global_vars,
};
if (!lexer.split(split_args, scopes)) {
return "error: unable to parse arguments";
}
if (split_args.size() > 1) {
return "error: more than one file name was matched";
}
if (ec.ec_dry_run) {
return "info: output will be redirected to -- " + split_args[0];
}
FILE *file = fopen(split_args[0].c_str(), "w");
if (file == nullptr) {
return "error: unable to open file -- " + split_args[0];
}
ec.ec_output_stack.back() = file;
return "info: redirecting output to file -- " + split_args[0];
}
static string com_highlight(exec_context &ec, string cmdline, vector<string> &args)
{
string retval = "error: expecting regular expression to highlight";
@ -2933,14 +2981,15 @@ static string com_echo(exec_context &ec, string cmdline, vector<string> &args)
retval = "";
}
auto ec_out = ec.get_output();
if (ec.ec_dry_run) {
lnav_data.ld_preview_status_source.get_description()
.set_value("The text to output:");
lnav_data.ld_preview_source.replace_with(attr_line_t(retval));
retval = "";
}
else if (!lnav_data.ld_output_stack.empty()) {
FILE *outfile = lnav_data.ld_output_stack.top();
else if (ec_out) {
FILE *outfile = *ec_out;
if (outfile == stdout) {
lnav_data.ld_stdout_used = true;
@ -2951,6 +3000,8 @@ static string com_echo(exec_context &ec, string cmdline, vector<string> &args)
putc('\n', outfile);
}
fflush(outfile);
retval = "";
}
}
@ -3838,6 +3889,19 @@ readline_context::command_t STD_COMMANDS[] = {
.with_tags({"io"})
.with_example({"sed -e 's/foo/bar/g'"})
},
{
"redirect-to",
com_redirect_to,
help_text(":redirect-to")
.with_summary("Redirect the output of commands to the given file")
.with_parameter(help_text(
"path", "The path to the file to write."
" If not specified, the current redirect will be cleared")
.optional())
.with_tags({"io", "scripting"})
.with_example({"/tmp/script-output.txt"})
},
{
"enable-filter",
com_enable_filter,

View File

@ -484,14 +484,14 @@ void rl_callback(void *dummy, readline_curses *rc)
time_t current_time = time(NULL);
string path_and_args = rc->get_value();
lnav_data.ld_output_stack.push(tmpout);
ec.ec_output_stack.back() = tmpout.in();
string result = execute_file(ec, path_and_args);
string::size_type lf_index = result.find('\n');
if (lf_index != string::npos) {
result = result.substr(0, lf_index);
}
rc->set_value(result);
lnav_data.ld_output_stack.pop();
ec.ec_output_stack.back() = nonstd::nullopt;
struct stat st;
@ -506,7 +506,7 @@ void rl_callback(void *dummy, readline_curses *rc)
lnav_data.ld_file_names[desc]
.with_fd(fd_copy)
.with_detect_format(false);
lnav_data.ld_files_to_front.push_back(make_pair(desc, 0));
lnav_data.ld_files_to_front.emplace_back(desc, 0);
if (lnav_data.ld_rl_view != NULL) {
lnav_data.ld_rl_view->set_alt_value(

View File

@ -348,6 +348,8 @@ dist_noinst_DATA = \
formats/jsontest3/format.json \
formats/nestedjson/format.json \
formats/scripts/multiline-echo.lnav \
formats/scripts/redirecting.lnav \
formats/scripts/nested-redirecting.lnav \
formats/sqldir/init.sql \
formats/timestamp/format.json \
log-samples/sample-27353a72ba4025448f261dcfa6ea16e474187795.txt \

View File

@ -0,0 +1,5 @@
:echo HOWDY!
:redirect-to hw2.txt
:echo HELLO, WORLD!
:redirect-to
:echo GOODBYE, WORLD!

View File

@ -0,0 +1,6 @@
:echo Howdy!
:redirect-to hw.txt
:echo Hello, World!
|nested-redirecting
:redirect-to
:echo Goodbye, World!

View File

@ -16,3 +16,36 @@ check_output "multiline-echo is not working?" <<EOF
Hello, World!
Goodbye, World!
EOF
run_test ${lnav_test} -n -d /tmp/lnav.err \
-I ${test_dir} \
-f 'redirecting' \
scripts-empty
check_error_output "redirecting has errors?" <<EOF
EOF
check_output "redirecting is not working?" <<EOF
Howdy!
Goodbye, World!
EOF
diff -w -u - hw.txt <<EOF
Hello, World!
HOWDY!
GOODBYE, WORLD!
EOF
if test $? -ne 0; then
echo "Script output was not redirected?"
exit 1
fi
diff -w -u - hw2.txt <<EOF
HELLO, WORLD!
EOF
if test $? -ne 0; then
echo "Script output was not redirected?"
exit 1
fi