diff options
Diffstat (limited to '')
-rw-r--r-- | src/command_executor.cc | 392 |
1 files changed, 256 insertions, 136 deletions
diff --git a/src/command_executor.cc b/src/command_executor.cc index 574fefb..ca62818 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -32,9 +32,11 @@ #include "command_executor.hh" #include "base/ansi_scrubber.hh" +#include "base/ansi_vars.hh" #include "base/fs_util.hh" #include "base/injector.hh" #include "base/itertools.hh" +#include "base/paths.hh" #include "base/string_util.hh" #include "bound_tags.hh" #include "config.h" @@ -45,24 +47,22 @@ #include "lnav_config.hh" #include "lnav_util.hh" #include "log_format_loader.hh" +#include "prql-modules.h" #include "readline_highlighters.hh" #include "service_tags.hh" #include "shlex.hh" +#include "sql_help.hh" #include "sql_util.hh" #include "vtab_module.hh" -#include "yajlpp/json_ptr.hh" + +#ifdef HAVE_RUST_DEPS +# include "prqlc.cxx.hh" +#endif using namespace lnav::roles::literals; exec_context INIT_EXEC_CONTEXT; -static const std::string MSG_FORMAT_STMT = R"( -SELECT count(*) AS total, min(log_line) AS log_line, log_msg_format - FROM all_logs - GROUP BY log_msg_format - ORDER BY total DESC -)"; - int sql_progress(const struct log_cursor& lc) { @@ -105,7 +105,7 @@ sql_progress_finished() static Result<std::string, lnav::console::user_message> execute_from_file( exec_context& ec, - const ghc::filesystem::path& path, + const std::string& src, int line_number, const std::string& cmdline); @@ -166,14 +166,7 @@ bind_sql_parameters(exec_context& ec, sqlite3_stmt* stmt) return Err(um); } - ov_iter = ec.ec_override.find(name); - if (ov_iter != ec.ec_override.end()) { - sqlite3_bind_text(stmt, - lpc, - ov_iter->second.c_str(), - ov_iter->second.length(), - SQLITE_TRANSIENT); - } else if (name[0] == '$') { + if (name[0] == '$') { const auto& lvars = ec.ec_local_vars.top(); const auto& gvars = ec.ec_global_vars; std::map<std::string, scoped_value_t>::const_iterator local_var, @@ -263,14 +256,60 @@ execute_search(const std::string& search_cmd) Result<std::string, lnav::console::user_message> execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) { - db_label_source& dls = lnav_data.ld_db_row_source; + db_label_source& dls = *(ec.ec_label_source_stack.back()); auto_mem<sqlite3_stmt> stmt(sqlite3_finalize); struct timeval start_tv, end_tv; std::string stmt_str = trim(sql); std::string retval; int retcode = SQLITE_OK; - log_info("Executing SQL: %s", sql.c_str()); + if (lnav::sql::is_prql(stmt_str)) { + log_info("compiling PRQL: %s", stmt_str.c_str()); + +#if HAVE_RUST_DEPS + auto opts = prqlc::Options{true, "sql.sqlite", true}; + + auto tree = sqlite_extension_prql; + for (const auto& mod : lnav_prql_modules) { + tree.emplace_back(prqlc::SourceTreeElement{ + mod.get_name(), + mod.to_string_fragment().to_string(), + }); + } + tree.emplace_back(prqlc::SourceTreeElement{"", stmt_str}); + auto cr = prqlc::compile_tree(tree, opts); + + for (const auto& msg : cr.messages) { + if (msg.kind != prqlc::MessageKind::Error) { + continue; + } + + auto stmt_al = attr_line_t(stmt_str); + readline_sqlite_highlighter(stmt_al, 0); + auto um + = lnav::console::user_message::error( + attr_line_t("unable to compile PRQL: ").append(stmt_al)) + .with_reason( + attr_line_t::from_ansi_str((std::string) msg.reason)); + if (!msg.display.empty()) { + um.with_note( + attr_line_t::from_ansi_str((std::string) msg.display)); + } + for (const auto& hint : msg.hints) { + um.with_help(attr_line_t::from_ansi_str((std::string) hint)); + break; + } + return Err(um); + } + stmt_str = (std::string) cr.output; +#else + auto um = lnav::console::user_message::error( + attr_line_t("PRQL is not supported in this build")); + return Err(um); +#endif + } + + log_info("Executing SQL: %s", stmt_str.c_str()); auto old_mode = lnav_data.ld_mode; lnav_data.ld_mode = ln_mode_t::BUSY; @@ -281,8 +320,9 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) std::vector<std::string> args; split_ws(stmt_str, args); - auto* sql_cmd_map = injector::get<readline_context::command_map_t*, - sql_cmd_map_tag>(); + const auto* sql_cmd_map + = injector::get<readline_context::command_map_t*, + sql_cmd_map_tag>(); auto cmd_iter = sql_cmd_map->find(args[0]); if (cmd_iter != sql_cmd_map->end()) { @@ -294,10 +334,6 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) } } - if (stmt_str == ".msgformats") { - stmt_str = MSG_FORMAT_STMT; - } - ec.ec_accumulator->clear(); const auto& source = ec.ec_source.back(); @@ -358,27 +394,19 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) bool done = false; auto bound_values = TRY(bind_sql_parameters(ec, stmt.in())); - if (lnav_data.ld_rl_view != nullptr) { - if (lnav_data.ld_rl_view) { - lnav_data.ld_rl_view->set_attr_value( - lnav::console::user_message::info( - attr_line_t("executing SQL statement, press ") - .append("CTRL+]"_hotkey) - .append(" to cancel")) - .to_attr_line()); - lnav_data.ld_rl_view->do_update(); - } - } - ec.ec_sql_callback(ec, stmt.in()); while (!done) { retcode = sqlite3_step(stmt.in()); switch (retcode) { case SQLITE_OK: - case SQLITE_DONE: + case SQLITE_DONE: { + auto changes = sqlite3_changes(lnav_data.ld_db.in()); + + log_info("sqlite3_changes() -> %d", changes); done = true; break; + } case SQLITE_ROW: ec.ec_sql_callback(ec, stmt.in()); @@ -414,7 +442,7 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) log_error("sqlite3_step error code: %d", retcode); auto um = sqlite3_error_to_user_message(lnav_data.ld_db) - .with_snippets(ec.ec_source) + .with_context_snippets(ec.ec_source) .with_note(bound_note); return Err(um); @@ -437,14 +465,14 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) } lnav_data.ld_filter_view.reload_data(); lnav_data.ld_files_view.reload_data(); - lnav_data.ld_views[LNV_DB].reload_data(); - lnav_data.ld_views[LNV_DB].set_left(0); lnav_data.ld_active_files.fc_files | lnav::itertools::for_each(&logfile::dump_stats); if (ec.ec_sql_callback != sql_callback) { retval = ec.ec_accumulator->get_string(); } else if (!dls.dls_rows.empty()) { + lnav_data.ld_views[LNV_DB].reload_data(); + lnav_data.ld_views[LNV_DB].set_left(0); if (lnav_data.ld_flags & LNF_HEADLESS) { if (ec.ec_local_vars.size() == 1) { ensure_view(&lnav_data.ld_views[LNV_DB]); @@ -501,6 +529,7 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) if (lnav_data.ld_flags & LNF_HEADLESS) { if (ec.ec_local_vars.size() == 1) { + lnav_data.ld_views[LNV_DB].reload_data(); ensure_view(&lnav_data.ld_views[LNV_DB]); } } @@ -511,13 +540,73 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) return Ok(retval); } +Result<void, lnav::console::user_message> +multiline_executor::push_back(string_fragment line) +{ + this->me_line_number += 1; + + if (line.trim().empty()) { + if (this->me_cmdline) { + this->me_cmdline = this->me_cmdline.value() + "\n"; + } + return Ok(); + } + if (line[0] == '#') { + return Ok(); + } + + switch (line[0]) { + case ':': + case '/': + case ';': + case '|': + if (this->me_cmdline) { + this->me_last_result + = TRY(execute_from_file(this->me_exec_context, + this->me_source, + this->me_starting_line_number, + trim(this->me_cmdline.value()))); + } + + this->me_starting_line_number = this->me_line_number; + this->me_cmdline = line.to_string(); + break; + default: + if (this->me_cmdline) { + this->me_cmdline = fmt::format( + FMT_STRING("{}{}"), this->me_cmdline.value(), line); + } else { + this->me_last_result = TRY( + execute_from_file(this->me_exec_context, + this->me_source, + this->me_line_number, + fmt::format(FMT_STRING(":{}"), line))); + } + break; + } + + return Ok(); +} + +Result<std::string, lnav::console::user_message> +multiline_executor::final() +{ + if (this->me_cmdline) { + this->me_last_result + = TRY(execute_from_file(this->me_exec_context, + this->me_source, + this->me_starting_line_number, + trim(this->me_cmdline.value()))); + } + + return Ok(this->me_last_result); +} + static Result<std::string, lnav::console::user_message> -execute_file_contents(exec_context& ec, - const ghc::filesystem::path& path, - bool multiline) +execute_file_contents(exec_context& ec, const ghc::filesystem::path& path) { - static ghc::filesystem::path stdin_path("-"); - static ghc::filesystem::path dev_stdin_path("/dev/stdin"); + static const ghc::filesystem::path stdin_path("-"); + static const ghc::filesystem::path dev_stdin_path("/dev/stdin"); std::string retval; FILE* file; @@ -527,59 +616,22 @@ execute_file_contents(exec_context& ec, return ec.make_error("stdin has already been consumed"); } file = stdin; - } else if ((file = fopen(path.c_str(), "r")) == nullptr) { + } else if ((file = fopen(path.c_str(), "re")) == nullptr) { return ec.make_error("unable to open file"); } - int line_number = 0, starting_line_number = 0; auto_mem<char> line; size_t line_max_size; ssize_t line_size; - nonstd::optional<std::string> cmdline; + multiline_executor me(ec, path.string()); ec.ec_path_stack.emplace_back(path.parent_path()); exec_context::output_guard og(ec); while ((line_size = getline(line.out(), &line_max_size, file)) != -1) { - line_number += 1; - - if (trim(line.in()).empty()) { - if (multiline && cmdline) { - cmdline = cmdline.value() + "\n"; - } - continue; - } - if (line[0] == '#') { - continue; - } - - switch (line[0]) { - case ':': - case '/': - case ';': - case '|': - if (cmdline) { - retval = TRY(execute_from_file( - ec, path, starting_line_number, trim(cmdline.value()))); - } - - starting_line_number = line_number; - cmdline = std::string(line); - break; - default: - if (multiline) { - cmdline = fmt::format("{}{}", cmdline.value(), line.in()); - } else { - retval = TRY(execute_from_file( - ec, path, line_number, fmt::format(":{}", line.in()))); - } - break; - } + TRY(me.push_back(string_fragment::from_bytes(line.in(), line_size))); } - if (cmdline) { - retval = TRY(execute_from_file( - ec, path, starting_line_number, trim(cmdline.value()))); - } + retval = TRY(me.final()); if (file == stdin) { if (isatty(STDOUT_FILENO)) { @@ -594,25 +646,35 @@ execute_file_contents(exec_context& ec, } Result<std::string, lnav::console::user_message> -execute_file(exec_context& ec, const std::string& path_and_args, bool multiline) +execute_file(exec_context& ec, const std::string& path_and_args) { + static const intern_string_t SRC = intern_string::lookup("cmdline"); + available_scripts scripts; - std::vector<std::string> split_args; std::string retval, msg; shlex lexer(path_and_args); log_info("Executing file: %s", path_and_args.c_str()); - if (!lexer.split(split_args, ec.ec_local_vars.top())) { - return ec.make_error("unable to parse path"); + auto split_args_res = lexer.split(scoped_resolver{&ec.ec_local_vars.top()}); + if (split_args_res.isErr()) { + auto split_err = split_args_res.unwrapErr(); + auto um = lnav::console::user_message::error( + "unable to parse script command-line") + .with_reason(split_err.te_msg) + .with_snippet(lnav::console::snippet::from( + SRC, lexer.to_attr_line(split_err))); + + return Err(um); } + auto split_args = split_args_res.unwrap(); if (split_args.empty()) { return ec.make_error("no script specified"); } ec.ec_local_vars.push({}); - auto script_name = split_args[0]; + auto script_name = split_args[0].se_value; auto& vars = ec.ec_local_vars.top(); char env_arg_name[32]; std::string star, open_error = "file not found"; @@ -625,13 +687,13 @@ execute_file(exec_context& ec, const std::string& path_and_args, bool multiline) vars["#"] = env_arg_name; for (size_t lpc = 0; lpc < split_args.size(); lpc++) { snprintf(env_arg_name, sizeof(env_arg_name), "%lu", lpc); - vars[env_arg_name] = split_args[lpc]; + vars[env_arg_name] = split_args[lpc].se_value; } for (size_t lpc = 1; lpc < split_args.size(); lpc++) { if (lpc > 1) { star.append(" "); } - star.append(split_args[lpc]); + star.append(split_args[lpc].se_value); } vars["__all__"] = star; @@ -672,8 +734,7 @@ execute_file(exec_context& ec, const std::string& path_and_args, bool multiline) if (!paths_to_exec.empty()) { for (auto& path_iter : paths_to_exec) { - retval - = TRY(execute_file_contents(ec, path_iter.sm_path, multiline)); + retval = TRY(execute_file_contents(ec, path_iter.sm_path)); } } ec.ec_local_vars.pop(); @@ -688,13 +749,13 @@ execute_file(exec_context& ec, const std::string& path_and_args, bool multiline) Result<std::string, lnav::console::user_message> execute_from_file(exec_context& ec, - const ghc::filesystem::path& path, + const std::string& src, int line_number, const std::string& cmdline) { std::string retval, alt_msg; - auto _sg = ec.enter_source( - intern_string::lookup(path.string()), line_number, cmdline); + auto _sg + = ec.enter_source(intern_string::lookup(src), line_number, cmdline); switch (cmdline[0]) { case ':': @@ -715,10 +776,8 @@ execute_from_file(exec_context& ec, break; } - log_info("%s:%d:execute result -- %s", - path.c_str(), - line_number, - retval.c_str()); + log_info( + "%s:%d:execute result -- %s", src.c_str(), line_number, retval.c_str()); return Ok(retval); } @@ -726,6 +785,17 @@ execute_from_file(exec_context& ec, Result<std::string, lnav::console::user_message> execute_any(exec_context& ec, const std::string& cmdline_with_mode) { + if (cmdline_with_mode.empty()) { + auto um = lnav::console::user_message::error("empty command") + .with_help( + "a command should start with ':', ';', '/', '|' and " + "followed by the operation to perform"); + if (!ec.ec_source.empty()) { + um.with_snippet(ec.ec_source.back()); + } + return Err(um); + } + std::string retval, alt_msg, cmdline = cmdline_with_mode.substr(1); auto _cleanup = finally([&ec] { if (ec.is_read_write() && @@ -734,6 +804,7 @@ execute_any(exec_context& ec, const std::string& cmdline_with_mode) (lnav_data.ld_flags & LNF_HEADLESS || ec.ec_path_stack.size() > 1)) { rescan_files(); + wait_for_pipers(nonstd::nullopt); rebuild_indexes_repeatedly(); } }); @@ -785,11 +856,13 @@ execute_init_commands( ""); return; } + fcntl(fileno(tmpout), F_SETFD, FD_CLOEXEC); fd_copy = auto_fd::dup_of(fileno(tmpout)); + fd_copy.close_on_exec(); ec_out = std::make_pair(tmpout.release(), fclose); } - auto& dls = lnav_data.ld_db_row_source; + auto& dls = *(ec.ec_label_source_stack.back()); int option_index = 1; { @@ -804,6 +877,8 @@ execute_init_commands( wait_for_children(); + lnav_data.ld_view_stack.top() | + [&ec](auto* tc) { ec.ec_top_line = tc->get_selection(); }; log_debug("init cmd: %s", cmd.c_str()); { auto _sg @@ -828,9 +903,16 @@ execute_init_commands( } rescan_files(); + auto deadline = current_timeval() + + ((lnav_data.ld_flags & LNF_HEADLESS) + ? timeval{5, 0} + : timeval{0, 500000}); + wait_for_pipers(deadline); rebuild_indexes_repeatedly(); } - if (dls.dls_rows.size() > 1) { + if (dls.dls_rows.size() > 1 && lnav_data.ld_view_stack.size() == 1) + { + lnav_data.ld_views[LNV_DB].reload_data(); ensure_view(LNV_DB); } } @@ -842,15 +924,20 @@ execute_init_commands( if (ec_out && fstat(fd_copy, &st) != -1 && st.st_size > 0) { static const auto OUTPUT_NAME = std::string("Initial command output"); - lnav_data.ld_active_files.fc_file_names[OUTPUT_NAME] - .with_fd(std::move(fd_copy)) - .with_include_in_session(false) - .with_detect_format(false); - lnav_data.ld_files_to_front.emplace_back(OUTPUT_NAME, 0_vl); - - if (lnav_data.ld_rl_view != nullptr) { - lnav_data.ld_rl_view->set_alt_value( - HELP_MSG_1(X, "to close the file")); + auto create_piper_res = lnav::piper::create_looper( + OUTPUT_NAME, std::move(fd_copy), auto_fd{}); + if (create_piper_res.isOk()) { + lnav_data.ld_active_files.fc_file_names[OUTPUT_NAME] + .with_piper(create_piper_res.unwrap()) + .with_include_in_session(false) + .with_detect_format(false) + .with_init_location(0_vl); + lnav_data.ld_files_to_front.emplace_back(OUTPUT_NAME, 0_vl); + + if (lnav_data.ld_rl_view != nullptr) { + lnav_data.ld_rl_view->set_alt_value( + HELP_MSG_1(X, "to close the file")); + } } } @@ -860,7 +947,7 @@ execute_init_commands( int sql_callback(exec_context& ec, sqlite3_stmt* stmt) { - auto& dls = lnav_data.ld_db_row_source; + auto& dls = *(ec.ec_label_source_stack.back()); if (!sqlite3_stmt_busy(stmt)) { dls.clear(); @@ -868,7 +955,6 @@ sql_callback(exec_context& ec, sqlite3_stmt* stmt) return 0; } - auto& chart = dls.dls_chart; auto& vc = view_colors::singleton(); int ncols = sqlite3_column_count(stmt); int row_number; @@ -890,16 +976,17 @@ sql_callback(exec_context& ec, sqlite3_stmt* stmt) dls.push_header(colname, type, graphable); if (graphable) { + auto& hm = dls.dls_headers.back(); auto name_for_ident_attrs = colname; auto attrs = vc.attrs_for_ident(name_for_ident_attrs); for (size_t attempt = 0; - chart.attrs_in_use(attrs) && attempt < 3; + hm.hm_chart.attrs_in_use(attrs) && attempt < 3; attempt++) { name_for_ident_attrs += " "; attrs = vc.attrs_for_ident(name_for_ident_attrs); } - chart.with_attrs_for_ident(colname, attrs); + hm.hm_chart.with_attrs_for_ident(colname, attrs); dls.dls_headers.back().hm_title_attrs = attrs; } } @@ -963,7 +1050,7 @@ pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd) if (out) { FILE* file = *out; - return std::async(std::launch::async, [&fd, file]() { + return std::async(std::launch::async, [fd = std::move(fd), file]() { char buffer[1024]; ssize_t rc; @@ -978,26 +1065,37 @@ pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd) return std::string(); }); } - auto tmp_fd - = lnav::filesystem::open_temp_file( - ghc::filesystem::temp_directory_path() / "lnav.out.XXXXXX") - .map([](auto pair) { - ghc::filesystem::remove(pair.first); - - return std::move(pair.second); - }) - .expect("Cannot create temporary file for callback"); - auto pp - = std::make_shared<piper_proc>(std::move(fd), false, std::move(tmp_fd)); - static int exec_count = 0; + std::error_code errc; + ghc::filesystem::create_directories(lnav::paths::workdir(), errc); + auto open_temp_res = lnav::filesystem::open_temp_file(lnav::paths::workdir() + / "exec.XXXXXX"); + if (open_temp_res.isErr()) { + return lnav::futures::make_ready_future( + fmt::format(FMT_STRING("error: cannot open temp file -- {}"), + open_temp_res.unwrapErr())); + } + + auto tmp_pair = open_temp_res.unwrap(); - lnav_data.ld_pipers.push_back(pp); + auto reader = std::thread( + [in_fd = std::move(fd), out_fd = std::move(tmp_pair.second)]() { + char buffer[1024]; + ssize_t rc; + + while ((rc = read(in_fd, buffer, sizeof(buffer))) > 0) { + write(out_fd, buffer, rc); + } + }); + reader.detach(); + + static int exec_count = 0; auto desc = fmt::format(FMT_STRING("[{}] Output of {}"), exec_count++, cmdline); - lnav_data.ld_active_files.fc_file_names[desc] - .with_fd(pp->get_fd()) + lnav_data.ld_active_files.fc_file_names[tmp_pair.first] + .with_filename(desc) .with_include_in_session(false) - .with_detect_format(false); + .with_detect_format(false) + .with_init_location(0_vl); lnav_data.ld_files_to_front.emplace_back(desc, 0_vl); if (lnav_data.ld_rl_view != nullptr) { lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(X, "to close the file")); @@ -1013,7 +1111,7 @@ add_global_vars(exec_context& ec) shlex subber(iter.second); std::string str; - if (!subber.eval(str, ec.ec_global_vars)) { + if (!subber.eval(str, scoped_resolver{&ec.ec_global_vars})) { log_error("Unable to evaluate global variable value: %s", iter.second.c_str()); continue; @@ -1071,6 +1169,28 @@ exec_context::exec_context(logline_value_vector* line_values, void exec_context::execute(const std::string& cmdline) { + if (this->get_provenance<mouse_input>()) { + require(!lnav_data.ld_rl_view->is_active()); + + int context = 0; + switch (cmdline[0]) { + case '/': + context = lnav::enums::to_underlying(ln_mode_t::SEARCH); + break; + case ':': + context = lnav::enums::to_underlying(ln_mode_t::COMMAND); + break; + case ';': + context = lnav::enums::to_underlying(ln_mode_t::SQL); + break; + case '|': + context = lnav::enums::to_underlying(ln_mode_t::EXEC); + break; + } + + lnav_data.ld_rl_view->append_to_history(context, cmdline.substr(1)); + } + auto exec_res = execute_any(*this, cmdline); if (exec_res.isErr()) { this->ec_error_callback_stack.back()(exec_res.unwrapErr()); |