mirror of
https://github.com/tstack/lnav.git
synced 2024-09-11 13:05:51 +03:00
parent
992d14dcb1
commit
2f9a41dfaf
10
NEWS.md
10
NEWS.md
@ -20,6 +20,16 @@ Features:
|
||||
to specify how a file type can be detected and converted.
|
||||
The built-in PCAP support in **lnav** is implemented using
|
||||
this mechanism.
|
||||
* Added a `shell_exec()` SQLite function that executes a
|
||||
command-line with the user's `$SHELL` and returns the
|
||||
output.
|
||||
* Added support for custom URL schemes that are handled by an
|
||||
lnav script. Schemes can be defined under
|
||||
`/tuning/url-schemes`. See the main docs for more details.
|
||||
* Added a `docker://` URL scheme that can be used to tail
|
||||
the logs for a container (e.g. `docker://my-container`) or
|
||||
files within a container (e.g.
|
||||
`docker://my-serv/var/log/dpkg.log`).
|
||||
|
||||
Bug Fixes:
|
||||
* When piping data into **lnav**'s stdin, the input used to
|
||||
|
@ -199,6 +199,28 @@
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"url-scheme": {
|
||||
"description": "Settings related to custom URL handling",
|
||||
"title": "/tuning/url-scheme",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"(\\w+)": {
|
||||
"description": "Definition of a custom URL scheme",
|
||||
"title": "/tuning/url-scheme/<url_scheme>",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"handler": {
|
||||
"title": "/tuning/url-scheme/<url_scheme>/handler",
|
||||
"description": "The name of the lnav script that can handle URLs with of this scheme. This should not include the '.lnav' suffix.",
|
||||
"type": "string",
|
||||
"pattern": "^[\\w\\-]+(?!\\.lnav)$"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
@ -269,3 +269,7 @@ command.
|
||||
.. jsonschema:: ../schemas/config-v1.schema.json#/properties/tuning/properties/logfile
|
||||
|
||||
.. jsonschema:: ../schemas/config-v1.schema.json#/properties/tuning/properties/remote/properties/ssh
|
||||
|
||||
.. _url_scheme:
|
||||
|
||||
.. jsonschema:: ../schemas/config-v1.schema.json#/properties/tuning/properties/url-scheme
|
||||
|
@ -116,6 +116,52 @@ file.
|
||||
The binary file is named ``tailer.bin.XXXXXX`` where *XXXXXX* is 6 random digits.
|
||||
The file is, under normal circumstancies, deleted immediately.
|
||||
|
||||
Command Output
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
The output of commands can be captured and displayed in **lnav** using
|
||||
the :ref:`:sh<sh>` command or by passing the :option:`-e` option on the
|
||||
command-line. The captured output will be displayed in the TEXT view.
|
||||
The lines from stdout and stderr are recorded separately so that the
|
||||
lines from stderr can be shown in the theme's "error" highlight. The
|
||||
time that the lines were received are also recorded internally so that
|
||||
the "time-offset" display (enabled by pressing :kbd:`Shift` + :kbd:`T`)
|
||||
can be shown and the "jump to slow-down" hotkeys (:kbd:`s` /
|
||||
:kbd:`Shift` + :kbd:`S`) work. Since the line-by-line timestamps are
|
||||
recorded internally, they will not interfere with timestamps that are
|
||||
in the commands output.
|
||||
|
||||
Docker Logs
|
||||
^^^^^^^^^^^
|
||||
|
||||
To make it easier to view
|
||||
`docker logs <https://docs.docker.com/engine/reference/commandline/logs/>`_
|
||||
within **lnav**, a :code:`docker://` URL scheme is available. Passing
|
||||
the container name in the authority field will run the :code:`docker logs`
|
||||
command. If a path is added to the URL, then **lnav** will execute
|
||||
:code:`docker exec <container> tail -F -n +0 /path/to/file` to try and
|
||||
tail the file in the container.
|
||||
|
||||
Custom URL Schemes
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Custom URL schemes can be defined using the :ref:`/tuning/url-schemes<url_scheme>`
|
||||
configuration. By adding a scheme name to the tuning configuration along
|
||||
with the name of an **lnav** handler script, you can control how the URL is
|
||||
interpreted and turned into **lnav** commands. This feature is how the
|
||||
`Docker Logs`_ functionality is implemented.
|
||||
|
||||
Custom URLs can be passed on the command-line or to the :ref:`:open<open>`
|
||||
command. When passed on the command-line, an :code:`:open` command with the
|
||||
URL is added to the list of initial commands. When the :code:`:open` command
|
||||
detects a custom URL, it checks for the definition in the configuration.
|
||||
If found, it will call the associated handler script with the URL as the
|
||||
first parameter. The script can parse the URL using the :ref:`parse_url`
|
||||
SQL function, if needed. The script should then execute whatever commands
|
||||
it needs to open the destination for viewing in **lnav**. For example,
|
||||
the docker URL handler uses the :ref:`:sh<sh>` command to run
|
||||
:code:`docker logs` with the container.
|
||||
|
||||
Searching
|
||||
---------
|
||||
|
||||
|
@ -190,7 +190,9 @@ add_custom_command(
|
||||
list(APPEND GEN_SRCS default-config.h default-config.cc)
|
||||
|
||||
set(BUILTIN_LNAV_SCRIPTS
|
||||
scripts/dhclient-summary.lnav scripts/lnav-pop-view.lnav
|
||||
scripts/dhclient-summary.lnav
|
||||
scripts/docker-url-handler.lnav
|
||||
scripts/lnav-pop-view.lnav
|
||||
scripts/partition-by-boot.lnav scripts/rename-stdin.lnav
|
||||
scripts/search-for.lnav)
|
||||
|
||||
@ -550,6 +552,7 @@ add_library(
|
||||
time_T.hh
|
||||
timer.hh
|
||||
top_status_source.hh
|
||||
url_handler.cfg.hh
|
||||
url_loader.hh
|
||||
view_helpers.hh
|
||||
view_helpers.crumbs.hh
|
||||
|
@ -314,6 +314,7 @@ noinst_HEADERS = \
|
||||
top_status_source.hh \
|
||||
top_status_source.cfg.hh \
|
||||
unique_path.hh \
|
||||
url_handler.cfg.hh \
|
||||
url_loader.hh \
|
||||
view_curses.hh \
|
||||
view_helpers.hh \
|
||||
|
@ -121,6 +121,17 @@ auto_fd::close_on_exec() const
|
||||
log_perror(fcntl(this->af_fd, F_SETFD, FD_CLOEXEC));
|
||||
}
|
||||
|
||||
void
|
||||
auto_fd::non_blocking() const
|
||||
{
|
||||
auto fl = fcntl(this->af_fd, F_GETFL, 0);
|
||||
if (fl < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_perror(fcntl(this->af_fd, F_SETFL, fl | O_NONBLOCK));
|
||||
}
|
||||
|
||||
auto_fd&
|
||||
auto_fd::operator=(int fd)
|
||||
{
|
||||
|
@ -160,6 +160,8 @@ public:
|
||||
|
||||
void close_on_exec() const;
|
||||
|
||||
void non_blocking() const;
|
||||
|
||||
private:
|
||||
int af_fd; /*< The managed file descriptor. */
|
||||
};
|
||||
|
@ -241,6 +241,8 @@ public:
|
||||
|
||||
const char* begin() const { return this->ab_buffer; }
|
||||
|
||||
char* next_available() { return &this->ab_buffer[this->ab_size]; }
|
||||
|
||||
auto_buffer& push_back(char ch)
|
||||
{
|
||||
if (this->ab_size == this->ab_capacity) {
|
||||
|
@ -62,10 +62,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
~auto_pid() noexcept
|
||||
{
|
||||
this->reset();
|
||||
}
|
||||
~auto_pid() noexcept { this->reset(); }
|
||||
|
||||
auto_pid& operator=(auto_pid&& other) noexcept
|
||||
{
|
||||
@ -77,10 +74,7 @@ public:
|
||||
|
||||
auto_pid& operator=(const auto_pid& other) = delete;
|
||||
|
||||
pid_t in() const
|
||||
{
|
||||
return this->ap_child;
|
||||
}
|
||||
pid_t in() const { return this->ap_child; }
|
||||
|
||||
bool in_child() const
|
||||
{
|
||||
@ -89,9 +83,7 @@ public:
|
||||
return this->ap_child == 0;
|
||||
}
|
||||
|
||||
pid_t release() &&
|
||||
{
|
||||
return std::exchange(this->ap_child, -1); }
|
||||
pid_t release() && { return std::exchange(this->ap_child, -1); }
|
||||
|
||||
int status() const
|
||||
{
|
||||
@ -107,6 +99,13 @@ public:
|
||||
return WIFEXITED(this->ap_status);
|
||||
}
|
||||
|
||||
int term_signal() const
|
||||
{
|
||||
static_assert(ProcState == process_state::finished,
|
||||
"wait_for_child() must be called first");
|
||||
return WTERMSIG(this->ap_status);
|
||||
}
|
||||
|
||||
int exit_status() const
|
||||
{
|
||||
static_assert(ProcState == process_state::finished,
|
||||
|
@ -168,14 +168,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,
|
||||
|
@ -120,6 +120,37 @@ struct exec_context {
|
||||
|
||||
void clear_output();
|
||||
|
||||
struct user {};
|
||||
struct file_open {
|
||||
std::string fo_name;
|
||||
};
|
||||
|
||||
using provenance_t = mapbox::util::variant<user, file_open>;
|
||||
|
||||
struct provenance_guard {
|
||||
explicit provenance_guard(exec_context* context, provenance_t prov)
|
||||
: pg_context(context)
|
||||
{
|
||||
this->pg_context->ec_provenance.push_back(prov);
|
||||
}
|
||||
|
||||
provenance_guard(const provenance_guard&) = delete;
|
||||
provenance_guard(provenance_guard&& other)
|
||||
: pg_context(other.pg_context)
|
||||
{
|
||||
other.pg_context = nullptr;
|
||||
}
|
||||
|
||||
~provenance_guard()
|
||||
{
|
||||
if (this->pg_context != nullptr) {
|
||||
this->pg_context->ec_provenance.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
exec_context* pg_context;
|
||||
};
|
||||
|
||||
struct source_guard {
|
||||
source_guard(exec_context* context) : sg_context(context) {}
|
||||
|
||||
@ -214,13 +245,25 @@ struct exec_context {
|
||||
this->ec_local_vars.pop();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
nonstd::optional<T> get_provenance() const
|
||||
{
|
||||
for (const auto& elem : this->ec_provenance) {
|
||||
if (elem.is<T>()) {
|
||||
return elem.get<T>();
|
||||
}
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
vis_line_t ec_top_line{0_vl};
|
||||
bool ec_dry_run{false};
|
||||
perm_t ec_perms{perm_t::READ_WRITE};
|
||||
|
||||
std::map<std::string, std::string> ec_override;
|
||||
logline_value_vector* ec_line_values;
|
||||
std::stack<std::map<std::string, scoped_value_t>> ec_local_vars;
|
||||
std::vector<provenance_t> ec_provenance;
|
||||
std::map<std::string, scoped_value_t> ec_global_vars;
|
||||
std::vector<ghc::filesystem::path> ec_path_stack;
|
||||
std::vector<lnav::console::snippet> ec_source;
|
||||
|
@ -78,7 +78,6 @@ convert(const external_file_format& eff, const std::string& filename)
|
||||
nullptr,
|
||||
};
|
||||
|
||||
setenv("TZ", "UTC", 1);
|
||||
execvp(eff.eff_converter.c_str(), (char**) args);
|
||||
if (errno == ENOENT) {
|
||||
fprintf(stderr,
|
||||
|
@ -29,24 +29,29 @@
|
||||
* @file fs-extension-functions.cc
|
||||
*/
|
||||
|
||||
#include <future>
|
||||
#include <string>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "base/auto_fd.hh"
|
||||
#include "base/auto_mem.hh"
|
||||
#include "base/auto_pid.hh"
|
||||
#include "base/lnav.console.hh"
|
||||
#include "base/opt_util.hh"
|
||||
#include "config.h"
|
||||
#include "lnav.hh"
|
||||
#include "sqlite-extension-func.hh"
|
||||
#include "sqlite3.h"
|
||||
#include "vtab_module.hh"
|
||||
#include "yajlpp/yajlpp_def.hh"
|
||||
|
||||
using namespace mapbox;
|
||||
|
||||
static util::variant<const char*, string_fragment>
|
||||
static mapbox::util::variant<const char*, string_fragment>
|
||||
sql_basename(const char* path_in)
|
||||
{
|
||||
int text_end = -1;
|
||||
@ -72,7 +77,7 @@ sql_basename(const char* path_in)
|
||||
}
|
||||
}
|
||||
|
||||
static util::variant<const char*, string_fragment>
|
||||
static mapbox::util::variant<const char*, string_fragment>
|
||||
sql_dirname(const char* path_in)
|
||||
{
|
||||
ssize_t text_end;
|
||||
@ -161,6 +166,175 @@ sql_realpath(const char* path)
|
||||
return resolved_path;
|
||||
}
|
||||
|
||||
struct shell_exec_options {
|
||||
std::map<std::string, nonstd::optional<std::string>> po_env;
|
||||
};
|
||||
|
||||
static const json_path_container shell_exec_env_handlers = {
|
||||
yajlpp::pattern_property_handler(R"((?<name>[^=]+))")
|
||||
.for_field(&shell_exec_options::po_env),
|
||||
};
|
||||
|
||||
static const typed_json_path_container<shell_exec_options>
|
||||
shell_exec_option_handlers = {
|
||||
yajlpp::property_handler("env").with_children(shell_exec_env_handlers),
|
||||
};
|
||||
|
||||
static blob_auto_buffer
|
||||
sql_shell_exec(const char* cmd,
|
||||
nonstd::optional<string_fragment> input,
|
||||
nonstd::optional<string_fragment> opts_json)
|
||||
{
|
||||
static const intern_string_t SRC = intern_string::lookup("options");
|
||||
|
||||
if (lnav_data.ld_flags & LNF_SECURE_MODE) {
|
||||
throw sqlite_func_error("not available in secure mode");
|
||||
}
|
||||
|
||||
shell_exec_options options;
|
||||
|
||||
if (opts_json) {
|
||||
auto parse_res
|
||||
= shell_exec_option_handlers.parser_for(SRC).of(opts_json.value());
|
||||
|
||||
if (parse_res.isErr()) {
|
||||
throw lnav::console::user_message::error(
|
||||
"invalid options parameter")
|
||||
.with_reason(parse_res.unwrapErr()[0]);
|
||||
}
|
||||
|
||||
options = parse_res.unwrap();
|
||||
}
|
||||
|
||||
auto in_pipe_res = auto_pipe::for_child_fd(STDIN_FILENO);
|
||||
if (in_pipe_res.isErr()) {
|
||||
throw lnav::console::user_message::error("cannot open input pipe")
|
||||
.with_reason(in_pipe_res.unwrapErr());
|
||||
}
|
||||
auto in_pipe = in_pipe_res.unwrap();
|
||||
auto out_pipe_res = auto_pipe::for_child_fd(STDOUT_FILENO);
|
||||
if (out_pipe_res.isErr()) {
|
||||
throw lnav::console::user_message::error("cannot open output pipe")
|
||||
.with_reason(out_pipe_res.unwrapErr());
|
||||
}
|
||||
auto out_pipe = out_pipe_res.unwrap();
|
||||
auto err_pipe_res = auto_pipe::for_child_fd(STDERR_FILENO);
|
||||
if (err_pipe_res.isErr()) {
|
||||
throw lnav::console::user_message::error("cannot open error pipe")
|
||||
.with_reason(err_pipe_res.unwrapErr());
|
||||
}
|
||||
auto err_pipe = err_pipe_res.unwrap();
|
||||
auto child_pid_res = lnav::pid::from_fork();
|
||||
if (child_pid_res.isErr()) {
|
||||
throw lnav::console::user_message::error("cannot fork()")
|
||||
.with_reason(child_pid_res.unwrapErr());
|
||||
}
|
||||
|
||||
auto child_pid = child_pid_res.unwrap();
|
||||
|
||||
in_pipe.after_fork(child_pid.in());
|
||||
out_pipe.after_fork(child_pid.in());
|
||||
err_pipe.after_fork(child_pid.in());
|
||||
|
||||
if (child_pid.in_child()) {
|
||||
const char* args[] = {
|
||||
getenv_opt("SHELL").value_or("bash"),
|
||||
"-c",
|
||||
cmd,
|
||||
nullptr,
|
||||
};
|
||||
|
||||
for (const auto& epair : options.po_env) {
|
||||
if (epair.second.has_value()) {
|
||||
setenv(epair.first.c_str(), epair.second->c_str(), 1);
|
||||
} else {
|
||||
unsetenv(epair.first.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
execvp(args[0], (char**) args);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
auto out_reader = std::async(std::launch::async, [&out_pipe]() {
|
||||
auto buffer = auto_buffer::alloc(4096);
|
||||
|
||||
while (true) {
|
||||
if (buffer.available() < 4096) {
|
||||
buffer.expand_by(4096);
|
||||
}
|
||||
|
||||
auto rc = read(out_pipe.read_end(),
|
||||
buffer.next_available(),
|
||||
buffer.available());
|
||||
if (rc < 0) {
|
||||
break;
|
||||
}
|
||||
if (rc == 0) {
|
||||
break;
|
||||
}
|
||||
buffer.resize_by(rc);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
});
|
||||
|
||||
auto err_reader = std::async(std::launch::async, [&err_pipe]() {
|
||||
auto buffer = auto_buffer::alloc(4096);
|
||||
|
||||
while (true) {
|
||||
if (buffer.available() < 4096) {
|
||||
buffer.expand_by(4096);
|
||||
}
|
||||
|
||||
auto rc = read(err_pipe.read_end(),
|
||||
buffer.next_available(),
|
||||
buffer.available());
|
||||
if (rc < 0) {
|
||||
break;
|
||||
}
|
||||
if (rc == 0) {
|
||||
break;
|
||||
}
|
||||
buffer.resize_by(rc);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
});
|
||||
|
||||
if (input) {
|
||||
auto sf = input.value();
|
||||
|
||||
while (!sf.empty()) {
|
||||
auto rc = write(in_pipe.write_end(), sf.data(), sf.length());
|
||||
if (rc < 0) {
|
||||
break;
|
||||
}
|
||||
sf = sf.substr(rc);
|
||||
}
|
||||
in_pipe.close();
|
||||
}
|
||||
|
||||
auto retval = blob_auto_buffer{out_reader.get()};
|
||||
|
||||
auto finished_child = std::move(child_pid).wait_for_child();
|
||||
|
||||
if (!finished_child.was_normal_exit()) {
|
||||
throw sqlite_func_error("child failed with signal {}",
|
||||
finished_child.term_signal());
|
||||
}
|
||||
|
||||
if (finished_child.exit_status() != EXIT_SUCCESS) {
|
||||
throw lnav::console::user_message::error(
|
||||
attr_line_t("child failed with exit code ")
|
||||
.append(lnav::roles::number(
|
||||
fmt::to_string(finished_child.exit_status()))))
|
||||
.with_reason(err_reader.get().to_string());
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
fs_extension_functions(struct FuncDef** basic_funcs,
|
||||
struct FuncDefAgg** agg_funcs)
|
||||
@ -246,6 +420,28 @@ fs_extension_functions(struct FuncDef** basic_funcs,
|
||||
.with_parameter({"path", "The path to resolve."})
|
||||
.with_tags({"filename"})),
|
||||
|
||||
sqlite_func_adapter<decltype(&sql_shell_exec), sql_shell_exec>::builder(
|
||||
help_text("shell_exec",
|
||||
"Executes a shell command and returns its output.")
|
||||
.sql_function()
|
||||
.with_parameter({"cmd", "The command to execute."})
|
||||
.with_parameter(help_text{
|
||||
"input",
|
||||
"A blob of data to write to the command's standard input."}
|
||||
.optional())
|
||||
.with_parameter(
|
||||
help_text{"options",
|
||||
"A JSON object containing options for the "
|
||||
"execution with the following properties:"}
|
||||
.optional()
|
||||
.with_parameter(help_text{
|
||||
"env",
|
||||
"An object containing the environment variables "
|
||||
"to set or, if NULL, to unset."}
|
||||
.optional()))
|
||||
.with_tags({"shell"}))
|
||||
.with_flags(SQLITE_DIRECTONLY | SQLITE_UTF8),
|
||||
|
||||
/*
|
||||
* TODO: add other functions like normpath, ...
|
||||
*/
|
||||
|
@ -53,7 +53,8 @@ get_related(const help_text& ht)
|
||||
auto tagged = help_text::TAGGED.equal_range(tag);
|
||||
|
||||
for (auto tag_iter = tagged.first; tag_iter != tagged.second;
|
||||
++tag_iter) {
|
||||
++tag_iter)
|
||||
{
|
||||
if (tag_iter->second == &ht) {
|
||||
continue;
|
||||
}
|
||||
@ -377,6 +378,17 @@ format_help_text_for_term(const help_text& ht,
|
||||
.append(attr_line_t::from_ansi_str(param.ht_summary),
|
||||
&(tws.with_indent(2 + max_param_name_width + 3)))
|
||||
.append("\n");
|
||||
if (!param.ht_parameters.empty()) {
|
||||
for (const auto& sub_param : param.ht_parameters) {
|
||||
alb.indent(body_indent + max_param_name_width + 3)
|
||||
.append(lnav::roles::variable(sub_param.ht_name))
|
||||
.append(" - ")
|
||||
.append(
|
||||
attr_line_t::from_ansi_str(sub_param.ht_summary),
|
||||
&(tws.with_indent(2 + max_param_name_width + 5)))
|
||||
.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (htc == help_text_content::full && !ht.ht_results.empty()) {
|
||||
@ -637,6 +649,20 @@ format_help_text_for_rst(const help_text& ht,
|
||||
param.ht_name,
|
||||
param.ht_nargs == help_nargs_t::HN_REQUIRED ? "\\*" : "",
|
||||
param.ht_summary);
|
||||
|
||||
if (!param.ht_parameters.empty()) {
|
||||
fprintf(rst_file, "\n");
|
||||
for (const auto& sub_param : param.ht_parameters) {
|
||||
fmt::fprintf(
|
||||
rst_file,
|
||||
" * **%s%s** --- %s\n",
|
||||
sub_param.ht_name,
|
||||
sub_param.ht_nargs == help_nargs_t::HN_REQUIRED
|
||||
? "\\*"
|
||||
: "",
|
||||
sub_param.ht_summary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt::fprintf(rst_file, "\n");
|
||||
|
@ -127,6 +127,8 @@ CASE *\[base-expr\]* WHEN *cmp-expr* ELSE *\[else-expr\]* END
|
||||
**Parameters**
|
||||
* **base-expr** --- The base expression that is used for comparison in the branches
|
||||
* **cmp-expr** --- The expression to test if this branch should be taken
|
||||
|
||||
* **then-expr\*** --- The result for this branch.
|
||||
* **else-expr** --- The result of this CASE if no branches matched.
|
||||
|
||||
**Examples**
|
||||
@ -395,6 +397,8 @@ UPDATE *table* SET *column-name* WHERE *\[cond\]*
|
||||
**Parameters**
|
||||
* **table\*** --- The table to update
|
||||
* **column-name** --- The columns in the table to update.
|
||||
|
||||
* **expr\*** --- The values to place into the column.
|
||||
* **cond** --- The condition used to determine whether a row should be updated.
|
||||
|
||||
**Examples**
|
||||
@ -2475,14 +2479,14 @@ parse_url(*url*)
|
||||
.. code-block:: custsqlite
|
||||
|
||||
;SELECT parse_url('https://example.com/search?q=hello%20world')
|
||||
{"scheme":"https","user":null,"password":null,"host":"example.com","port":null,"path":"/search","query":"q=hello%20world","parameters":{"q":"hello world"},"fragment":null}
|
||||
{"scheme":"https","username":null,"password":null,"host":"example.com","port":null,"path":"/search","query":"q=hello%20world","parameters":{"q":"hello world"},"fragment":null}
|
||||
|
||||
To parse the URL 'https://alice@[fe80::14ff:4ee5:1215:2fb2]':
|
||||
|
||||
.. code-block:: custsqlite
|
||||
|
||||
;SELECT parse_url('https://alice@[fe80::14ff:4ee5:1215:2fb2]')
|
||||
{"scheme":"https","user":"alice","password":null,"host":"[fe80::14ff:4ee5:1215:2fb2]","port":null,"path":"/","query":null,"parameters":null,"fragment":null}
|
||||
{"scheme":"https","username":"alice","password":null,"host":"[fe80::14ff:4ee5:1215:2fb2]","port":null,"path":"/","query":null,"parameters":null,"fragment":null}
|
||||
|
||||
**See Also**
|
||||
:ref:`anonymize`, :ref:`char`, :ref:`charindex`, :ref:`decode`, :ref:`encode`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`gunzip`, :ref:`gzip`, :ref:`humanize_duration`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`logfmt2json`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture_into_json`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`unparse_url`, :ref:`unparse_url`, :ref:`upper`, :ref:`xpath`
|
||||
@ -3105,6 +3109,26 @@ rtrim(*str*, *\[chars\]*)
|
||||
----
|
||||
|
||||
|
||||
.. _shell_exec:
|
||||
|
||||
shell_exec(*cmd*, *\[input\]*, *\[options\]*)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Executes a shell command and returns its output.
|
||||
|
||||
**Parameters**
|
||||
* **cmd\*** --- The command to execute.
|
||||
* **input** --- A blob of data to write to the command's standard input.
|
||||
* **options** --- A JSON object containing options for the execution with the following properties:
|
||||
|
||||
* **env** --- An object containing the environment variables to set or, if NULL, to unset.
|
||||
|
||||
**See Also**
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
.. _sign:
|
||||
|
||||
sign(*num*)
|
||||
|
@ -2882,6 +2882,9 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
|
||||
.with_filename(file_path);
|
||||
isc::to<curl_looper&, services::curl_streamer_t>().send(
|
||||
[ul](auto& clooper) { clooper.add_request(ul); });
|
||||
} else if (file_path.find("://") != std::string::npos) {
|
||||
lnav_data.ld_commands.emplace_back(
|
||||
fmt::format(FMT_STRING(":open {}"), file_path));
|
||||
}
|
||||
#endif
|
||||
else if (is_glob(file_path))
|
||||
|
@ -80,6 +80,7 @@
|
||||
#include "sysclip.hh"
|
||||
#include "tailer/tailer.looper.hh"
|
||||
#include "text_anonymizer.hh"
|
||||
#include "url_handler.cfg.hh"
|
||||
#include "url_loader.hh"
|
||||
#include "yajl/api/yajl_parse.h"
|
||||
#include "yajlpp/json_op.hh"
|
||||
@ -2478,6 +2479,12 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
|
||||
std::vector<std::pair<std::string, file_location_t>> files_to_front;
|
||||
std::vector<std::string> closed_files;
|
||||
logfile_open_options loo;
|
||||
|
||||
auto prov = ec.get_provenance<exec_context::file_open>();
|
||||
if (prov) {
|
||||
loo.with_filename(prov->fo_name);
|
||||
}
|
||||
|
||||
for (auto fn : split_args) {
|
||||
file_location_t file_loc;
|
||||
@ -2538,12 +2545,59 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
retval = "";
|
||||
}
|
||||
#endif
|
||||
} else if (fn.find("://") != std::string::npos) {
|
||||
const auto& cfg
|
||||
= injector::get<const lnav::url_handler::config&>();
|
||||
|
||||
auto* cu = curl_url();
|
||||
auto set_rc = curl_url_set(
|
||||
cu, CURLUPART_URL, fn.c_str(), CURLU_NON_SUPPORT_SCHEME);
|
||||
if (set_rc != CURLUE_OK) {
|
||||
return Err(lnav::console::user_message::error(
|
||||
attr_line_t("invalid URL: ")
|
||||
.append(lnav::roles::file(fn)))
|
||||
.with_reason(curl_url_strerror(set_rc)));
|
||||
}
|
||||
|
||||
char* scheme_part = nullptr;
|
||||
auto get_rc
|
||||
= curl_url_get(cu, CURLUPART_SCHEME, &scheme_part, 0);
|
||||
if (get_rc != CURLUE_OK) {
|
||||
return Err(lnav::console::user_message::error(
|
||||
attr_line_t("cannot get scheme from URL: ")
|
||||
.append(lnav::roles::file(fn)))
|
||||
.with_reason(curl_url_strerror(set_rc)));
|
||||
}
|
||||
|
||||
auto proto_iter = cfg.c_schemes.find(scheme_part);
|
||||
if (proto_iter == cfg.c_schemes.end()) {
|
||||
return Err(
|
||||
lnav::console::user_message::error(
|
||||
attr_line_t("no defined handler for URL scheme: ")
|
||||
.append(lnav::roles::file(scheme_part)))
|
||||
.with_reason(curl_url_strerror(set_rc)));
|
||||
}
|
||||
|
||||
auto path_and_args
|
||||
= fmt::format(FMT_STRING("{} {}"),
|
||||
proto_iter->second.p_handler.pp_value,
|
||||
fn);
|
||||
|
||||
exec_context::provenance_guard pg(&ec,
|
||||
exec_context::file_open{fn});
|
||||
|
||||
auto exec_res = execute_file(ec, path_and_args);
|
||||
if (exec_res.isErr()) {
|
||||
return exec_res;
|
||||
}
|
||||
|
||||
retval = "info: watching -- " + fn;
|
||||
} else if (is_glob(fn.c_str())) {
|
||||
fc.fc_file_names.emplace(fn, logfile_open_options());
|
||||
fc.fc_file_names.emplace(fn, loo);
|
||||
retval = "info: watching -- " + fn;
|
||||
} else if (stat(fn.c_str(), &st) == -1) {
|
||||
if (fn.find(':') != std::string::npos) {
|
||||
fc.fc_file_names.emplace(fn, logfile_open_options());
|
||||
fc.fc_file_names.emplace(fn, loo);
|
||||
retval = "info: watching -- " + fn;
|
||||
} else {
|
||||
auto um = lnav::console::user_message::error(
|
||||
@ -2573,6 +2627,9 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
} else {
|
||||
auto desc = fmt::format(FMT_STRING("FIFO [{}]"),
|
||||
lnav_data.ld_fifo_counter++);
|
||||
if (prov) {
|
||||
desc = prov->fo_name;
|
||||
}
|
||||
auto create_piper_res = lnav::piper::create_looper(
|
||||
desc, std::move(fifo_fd), auto_fd{});
|
||||
if (create_piper_res.isErr()) {
|
||||
@ -2602,8 +2659,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
if (dir_wild[dir_wild.size() - 1] == '/') {
|
||||
dir_wild.resize(dir_wild.size() - 1);
|
||||
}
|
||||
fc.fc_file_names.emplace(dir_wild + "/*",
|
||||
logfile_open_options());
|
||||
fc.fc_file_names.emplace(dir_wild + "/*", loo);
|
||||
retval = "info: watching -- " + dir_wild;
|
||||
} else if (!S_ISREG(st.st_mode)) {
|
||||
auto um = lnav::console::user_message::error(
|
||||
@ -2627,7 +2683,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
return Err(um);
|
||||
} else {
|
||||
fn = abspath.in();
|
||||
fc.fc_file_names.emplace(fn, logfile_open_options());
|
||||
fc.fc_file_names.emplace(fn, loo);
|
||||
retval = "info: opened -- " + fn;
|
||||
files_to_front.emplace_back(fn, file_loc);
|
||||
|
||||
@ -4173,12 +4229,34 @@ com_sh(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
nullptr,
|
||||
};
|
||||
|
||||
for (const auto& pair : ec.ec_local_vars.top()) {
|
||||
pair.second.match(
|
||||
[&pair](const std::string& val) {
|
||||
setenv(pair.first.c_str(), val.c_str(), 1);
|
||||
},
|
||||
[&pair](const string_fragment& sf) {
|
||||
setenv(pair.first.c_str(), sf.to_string().c_str(), 1);
|
||||
},
|
||||
[](null_value_t) {},
|
||||
[&pair](int64_t val) {
|
||||
setenv(
|
||||
pair.first.c_str(), fmt::to_string(val).c_str(), 1);
|
||||
},
|
||||
[&pair](double val) {
|
||||
setenv(
|
||||
pair.first.c_str(), fmt::to_string(val).c_str(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
execvp(exec_args[0], (char**) exec_args);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
auto display_name = ec.get_provenance<exec_context::file_open>()
|
||||
.value_or(exec_context::file_open{carg})
|
||||
.fo_name;
|
||||
auto create_piper_res
|
||||
= lnav::piper::create_looper(carg,
|
||||
= lnav::piper::create_looper(display_name,
|
||||
std::move(out_pipe.read_end()),
|
||||
std::move(err_pipe.read_end()));
|
||||
|
||||
@ -4190,11 +4268,12 @@ com_sh(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
return Err(um);
|
||||
}
|
||||
|
||||
lnav_data.ld_active_files.fc_file_names[carg].with_piper(
|
||||
lnav_data.ld_active_files.fc_file_names[display_name].with_piper(
|
||||
create_piper_res.unwrap());
|
||||
lnav_data.ld_child_pollers.emplace_back(
|
||||
child_poller{std::move(child), [](auto& fc, auto& child) {}});
|
||||
lnav_data.ld_files_to_front.emplace_back(carg, file_location_t{});
|
||||
lnav_data.ld_files_to_front.emplace_back(display_name,
|
||||
file_location_t{});
|
||||
}
|
||||
|
||||
return Ok(std::string());
|
||||
|
@ -96,6 +96,9 @@ static auto tc = injector::bind<tailer::config>::to_instance(
|
||||
static auto scc = injector::bind<sysclip::config>::to_instance(
|
||||
+[]() { return &lnav_config.lc_sysclip; });
|
||||
|
||||
static auto uh = injector::bind<lnav::url_handler::config>::to_instance(
|
||||
+[]() { return &lnav_config.lc_url_handlers; });
|
||||
|
||||
static auto lsc = injector::bind<logfile_sub_source_ns::config>::to_instance(
|
||||
+[]() { return &lnav_config.lc_log_source; });
|
||||
|
||||
@ -1257,6 +1260,33 @@ static const struct json_path_container log_source_handlers = {
|
||||
.with_children(log_source_watch_handlers),
|
||||
};
|
||||
|
||||
static const struct json_path_container url_scheme_handlers = {
|
||||
yajlpp::property_handler("handler")
|
||||
.with_description(
|
||||
"The name of the lnav script that can handle URLs "
|
||||
"with of this scheme. This should not include the '.lnav' suffix.")
|
||||
.with_pattern(R"(^[\w\-]+(?!\.lnav)$)")
|
||||
.for_field(&lnav::url_handler::scheme::p_handler),
|
||||
};
|
||||
|
||||
static const struct json_path_container url_handlers = {
|
||||
yajlpp::pattern_property_handler(R"((?<url_scheme>\w+))")
|
||||
.with_description("Definition of a custom URL scheme")
|
||||
.with_obj_provider<lnav::url_handler::scheme, _lnav_config>(
|
||||
[](const yajlpp_provider_context& ypc, _lnav_config* root) {
|
||||
auto& retval = root->lc_url_handlers
|
||||
.c_schemes[ypc.get_substr("url_scheme")];
|
||||
return &retval;
|
||||
})
|
||||
.with_path_provider<_lnav_config>(
|
||||
[](struct _lnav_config* cfg, std::vector<std::string>& paths_out) {
|
||||
for (const auto& iter : cfg->lc_url_handlers.c_schemes) {
|
||||
paths_out.emplace_back(iter.first);
|
||||
}
|
||||
})
|
||||
.with_children(url_scheme_handlers),
|
||||
};
|
||||
|
||||
static const struct json_path_container tuning_handlers = {
|
||||
yajlpp::property_handler("archive-manager")
|
||||
.with_description("Settings related to opening archive files")
|
||||
@ -1276,6 +1306,9 @@ static const struct json_path_container tuning_handlers = {
|
||||
yajlpp::property_handler("clipboard")
|
||||
.with_description("Settings related to the clipboard")
|
||||
.with_children(sysclip_handlers),
|
||||
yajlpp::property_handler("url-scheme")
|
||||
.with_description("Settings related to custom URL handling")
|
||||
.with_children(url_handlers),
|
||||
};
|
||||
|
||||
const char* DEFAULT_CONFIG_SCHEMA
|
||||
|
@ -54,6 +54,7 @@
|
||||
#include "sysclip.cfg.hh"
|
||||
#include "tailer/tailer.looper.cfg.hh"
|
||||
#include "top_status_source.cfg.hh"
|
||||
#include "url_handler.cfg.hh"
|
||||
|
||||
/**
|
||||
* Check if an experimental feature should be enabled by
|
||||
@ -115,6 +116,7 @@ struct _lnav_config {
|
||||
lnav::logfile::config lc_logfile;
|
||||
tailer::config lc_tailer;
|
||||
sysclip::config lc_sysclip;
|
||||
lnav::url_handler::config lc_url_handlers;
|
||||
logfile_sub_source_ns::config lc_log_source;
|
||||
};
|
||||
|
||||
|
@ -118,6 +118,9 @@ logfile::open(std::string filename, const logfile_open_options& loo, auto_fd fd)
|
||||
(long long) lf->lf_stat.st_size,
|
||||
(long long) lf->lf_stat.st_mtime,
|
||||
lf->lf_filename.c_str());
|
||||
if (lf->lf_actual_path) {
|
||||
log_info(" actual_path=%s", lf->lf_actual_path->c_str());
|
||||
}
|
||||
|
||||
if (!lf->lf_options.loo_filename.empty()) {
|
||||
lf->set_filename(lf->lf_options.loo_filename);
|
||||
|
@ -98,6 +98,8 @@ enum class read_mode_t {
|
||||
void
|
||||
looper::loop()
|
||||
{
|
||||
static const auto FORCE_MTIME_UPDATE_DURATION = 8h;
|
||||
|
||||
const auto& cfg = injector::get<const config&>();
|
||||
struct pollfd pfd[2];
|
||||
struct {
|
||||
@ -122,11 +124,14 @@ looper::loop()
|
||||
this->l_name.c_str(),
|
||||
this->l_stdout.get(),
|
||||
this->l_stderr.get());
|
||||
this->l_stdout.non_blocking();
|
||||
captured_fds[0].lb.set_fd(this->l_stdout);
|
||||
if (this->l_stderr.has_value()) {
|
||||
this->l_stderr.non_blocking();
|
||||
captured_fds[1].lb.set_fd(this->l_stderr);
|
||||
}
|
||||
captured_fds[1].cf_level = LEVEL_ERROR;
|
||||
auto last_write = std::chrono::system_clock::now();
|
||||
do {
|
||||
static const auto TIMEOUT
|
||||
= std::chrono::duration_cast<std::chrono::milliseconds>(1s).count();
|
||||
@ -156,9 +161,16 @@ looper::loop()
|
||||
// update the timestamp to keep the file alive from any
|
||||
// cleanup processes
|
||||
if (outfd.has_value()) {
|
||||
log_perror(futimes(outfd.get(), nullptr));
|
||||
auto now = std::chrono::system_clock::now();
|
||||
|
||||
if ((now - last_write) >= FORCE_MTIME_UPDATE_DURATION) {
|
||||
last_write = now;
|
||||
log_perror(futimes(outfd.get(), nullptr));
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
last_write = std::chrono::system_clock::now();
|
||||
}
|
||||
for (auto& cap : captured_fds) {
|
||||
while (this->l_looping) {
|
||||
|
@ -78,6 +78,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"url-scheme": {
|
||||
"docker": {
|
||||
"handler": "docker-url-handler"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
src/scripts/docker-url-handler.lnav
Executable file
17
src/scripts/docker-url-handler.lnav
Executable file
@ -0,0 +1,17 @@
|
||||
#
|
||||
# @synopsis: docker-url-handler
|
||||
# @description: Internal script to handle opening docker URLs
|
||||
#
|
||||
|
||||
;SELECT CASE path
|
||||
WHEN '/' THEN
|
||||
'docker logs -f ' || hostname
|
||||
ELSE
|
||||
'docker exec ' || hostname || ' tail -n +0 -F "' || path || '"'
|
||||
END AS cmd
|
||||
FROM (SELECT
|
||||
jget(url, '/host') AS hostname,
|
||||
jget(url, '/path') AS path
|
||||
FROM (SELECT parse_url($1) AS url))
|
||||
|
||||
:sh eval $cmd
|
@ -6,5 +6,8 @@ if ! command -v tshark; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# We want tshark output to come in UTC
|
||||
export TZ=UTC
|
||||
|
||||
# Use tshark to convert the pcap file into a JSON-lines log file
|
||||
exec tshark -T ek -P -V -t ad -r $2
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
BUILTIN_LNAVSCRIPTS = \
|
||||
$(srcdir)/scripts/dhclient-summary.lnav \
|
||||
$(srcdir)/scripts/docker-url-handler.lnav \
|
||||
$(srcdir)/scripts/lnav-pop-view.lnav \
|
||||
$(srcdir)/scripts/partition-by-boot.lnav \
|
||||
$(srcdir)/scripts/rename-stdin.lnav \
|
||||
|
@ -34,14 +34,11 @@
|
||||
|
||||
namespace services {
|
||||
|
||||
struct main_t {
|
||||
};
|
||||
struct ui_t {
|
||||
};
|
||||
struct curl_streamer_t {
|
||||
};
|
||||
struct remote_tailer_t {
|
||||
};
|
||||
struct main_t {};
|
||||
struct ui_t {};
|
||||
struct curl_streamer_t {};
|
||||
struct remote_tailer_t {};
|
||||
struct url_handler_t {};
|
||||
|
||||
} // namespace services
|
||||
|
||||
|
@ -635,17 +635,18 @@ const char* curl_url_strerror(CURLUcode error);
|
||||
#endif
|
||||
|
||||
static json_string
|
||||
sql_parse_url(string_fragment url_frag)
|
||||
sql_parse_url(std::string url)
|
||||
{
|
||||
static auto* CURL_HANDLE = get_curl_easy();
|
||||
|
||||
auto_mem<CURLU> cu(curl_url_cleanup);
|
||||
cu = curl_url();
|
||||
|
||||
auto rc = curl_url_set(cu, CURLUPART_URL, url_frag.data(), 0);
|
||||
auto rc = curl_url_set(
|
||||
cu, CURLUPART_URL, url.c_str(), CURLU_NON_SUPPORT_SCHEME);
|
||||
if (rc != CURLUE_OK) {
|
||||
throw lnav::console::user_message::error(
|
||||
attr_line_t("invalid URL: ").append_quoted(url_frag.to_string()))
|
||||
attr_line_t("invalid URL: ").append(lnav::roles::file(url)))
|
||||
.with_reason(curl_url_strerror(rc));
|
||||
}
|
||||
|
||||
@ -663,7 +664,7 @@ sql_parse_url(string_fragment url_frag)
|
||||
} else {
|
||||
root.gen();
|
||||
}
|
||||
root.gen("user");
|
||||
root.gen("username");
|
||||
rc = curl_url_get(cu, CURLUPART_USER, url_part.out(), CURLU_URLDECODE);
|
||||
if (rc == CURLUE_OK) {
|
||||
root.gen(string_fragment::from_c_str(url_part.in()));
|
||||
@ -708,6 +709,11 @@ sql_parse_url(string_fragment url_frag)
|
||||
robin_hood::unordered_set<std::string> seen_keys;
|
||||
yajlpp_map query_map(gen);
|
||||
|
||||
for (size_t lpc = 0; url_part.in()[lpc]; lpc++) {
|
||||
if (url_part.in()[lpc] == '+') {
|
||||
url_part.in()[lpc] = ' ';
|
||||
}
|
||||
}
|
||||
auto query_frag = string_fragment::from_c_str(url_part.in());
|
||||
auto remaining = query_frag;
|
||||
|
||||
@ -727,6 +733,7 @@ sql_parse_url(string_fragment url_frag)
|
||||
kv_pair_encoded.data(),
|
||||
kv_pair_encoded.length(),
|
||||
&out_len);
|
||||
|
||||
auto kv_pair_frag
|
||||
= string_fragment::from_bytes(kv_pair.in(), out_len);
|
||||
auto eq_index_opt = kv_pair_frag.find('=');
|
||||
@ -788,7 +795,7 @@ struct url_parts {
|
||||
};
|
||||
|
||||
static const json_path_container url_params_handlers = {
|
||||
yajlpp::pattern_property_handler("(?<param>.+)")
|
||||
yajlpp::pattern_property_handler("(?<param>.*)")
|
||||
.for_field(&url_parts::up_parameters),
|
||||
};
|
||||
|
||||
@ -812,7 +819,7 @@ sql_unparse_url(string_fragment in)
|
||||
|
||||
auto parse_res = url_parts_handlers.parser_for(SRC).of(in);
|
||||
if (parse_res.isErr()) {
|
||||
throw parse_res.unwrapErr();
|
||||
throw parse_res.unwrapErr()[0];
|
||||
}
|
||||
|
||||
auto up = parse_res.unwrap();
|
||||
|
@ -51,7 +51,9 @@ textfile_sub_source::text_line_count()
|
||||
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
|
||||
if (rend_iter == this->tss_rendered_files.end()) {
|
||||
auto* lfo = (line_filter_observer*) lf->get_logline_observer();
|
||||
retval = lfo->lfo_filter_state.tfs_index.size();
|
||||
if (lfo != nullptr) {
|
||||
retval = lfo->lfo_filter_state.tfs_index.size();
|
||||
}
|
||||
} else {
|
||||
retval = rend_iter->second.rf_text_source->text_line_count();
|
||||
}
|
||||
@ -72,7 +74,9 @@ textfile_sub_source::text_value_for_line(textview_curses& tc,
|
||||
if (rend_iter == this->tss_rendered_files.end()) {
|
||||
auto* lfo = dynamic_cast<line_filter_observer*>(
|
||||
lf->get_logline_observer());
|
||||
if (line < 0 || line >= lfo->lfo_filter_state.tfs_index.size()) {
|
||||
if (lfo == nullptr || line < 0
|
||||
|| line >= lfo->lfo_filter_state.tfs_index.size())
|
||||
{
|
||||
value_out.clear();
|
||||
} else {
|
||||
auto ll = lf->begin() + lfo->lfo_filter_state.tfs_index[line];
|
||||
@ -119,7 +123,9 @@ textfile_sub_source::text_attrs_for_line(textview_curses& tc,
|
||||
} else {
|
||||
auto* lfo
|
||||
= dynamic_cast<line_filter_observer*>(lf->get_logline_observer());
|
||||
if (row >= 0 && row < lfo->lfo_filter_state.tfs_index.size()) {
|
||||
if (lfo != nullptr && row >= 0
|
||||
&& row < lfo->lfo_filter_state.tfs_index.size())
|
||||
{
|
||||
auto ll = lf->begin() + lfo->lfo_filter_state.tfs_index[row];
|
||||
|
||||
value_out.emplace_back(lr, SA_LEVEL.value(ll->get_msg_level()));
|
||||
@ -171,7 +177,9 @@ textfile_sub_source::text_size_for_line(textview_curses& tc,
|
||||
if (rend_iter == this->tss_rendered_files.end()) {
|
||||
auto* lfo = dynamic_cast<line_filter_observer*>(
|
||||
lf->get_logline_observer());
|
||||
if (line < 0 || line >= lfo->lfo_filter_state.tfs_index.size()) {
|
||||
if (lfo == nullptr || line < 0
|
||||
|| line >= lfo->lfo_filter_state.tfs_index.size())
|
||||
{
|
||||
} else {
|
||||
retval
|
||||
= lf->message_byte_length(
|
||||
|
259
src/url_handler.cc
Normal file
259
src/url_handler.cc
Normal file
@ -0,0 +1,259 @@
|
||||
/**
|
||||
* Copyright (c) 2023, 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 "url_handler.hh"
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include "base/fs_util.hh"
|
||||
#include "base/injector.hh"
|
||||
#include "base/paths.hh"
|
||||
#include "lnav.hh"
|
||||
#include "service_tags.hh"
|
||||
#include "url_handler.cfg.hh"
|
||||
|
||||
namespace lnav {
|
||||
namespace url_handler {
|
||||
|
||||
void
|
||||
looper::handler_looper::loop_body()
|
||||
{
|
||||
pollfd pfd[1];
|
||||
|
||||
pfd[0].events = POLLIN;
|
||||
pfd[0].fd = this->hl_line_buffer.get_fd();
|
||||
pfd[0].revents = 0;
|
||||
|
||||
log_debug("doing url handler poll");
|
||||
auto prc = poll(pfd, 1, 100);
|
||||
log_debug("poll rc %d", prc);
|
||||
if (prc > 0) {
|
||||
auto load_res
|
||||
= this->hl_line_buffer.load_next_line(this->hl_last_range);
|
||||
|
||||
if (load_res.isErr()) {
|
||||
log_error("failed to load next line: %s",
|
||||
load_res.unwrapErr().c_str());
|
||||
this->s_looping = false;
|
||||
} else {
|
||||
auto li = load_res.unwrap();
|
||||
|
||||
log_debug("li %d %d:%d",
|
||||
li.li_partial,
|
||||
li.li_file_range.fr_offset,
|
||||
li.li_file_range.fr_size);
|
||||
if (!li.li_partial && !li.li_file_range.empty()) {
|
||||
auto read_res
|
||||
= this->hl_line_buffer.read_range(li.li_file_range);
|
||||
|
||||
if (read_res.isErr()) {
|
||||
log_error("cannot read line: %s",
|
||||
read_res.unwrapErr().c_str());
|
||||
} else {
|
||||
auto cmd = trim(to_string(read_res.unwrap()));
|
||||
log_debug("url handler command: %s", cmd.c_str());
|
||||
|
||||
isc::to<main_looper&, services::main_t>().send(
|
||||
[cmd](auto& mlooper) {
|
||||
auto exec_res
|
||||
= execute_any(lnav_data.ld_exec_context, cmd);
|
||||
if (exec_res.isErr()) {
|
||||
auto um = exec_res.unwrapErr();
|
||||
log_error(
|
||||
"wtf %s",
|
||||
um.to_attr_line().get_string().c_str());
|
||||
}
|
||||
});
|
||||
}
|
||||
this->hl_last_range = li.li_file_range;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->hl_line_buffer.is_pipe_closed()) {
|
||||
log_info("URL handler finished");
|
||||
this->s_looping = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<void, lnav::console::user_message>
|
||||
looper::open(std::string url)
|
||||
{
|
||||
const auto& cfg = injector::get<const config&>();
|
||||
|
||||
log_info("open request for URL: %s", url.c_str());
|
||||
|
||||
auto* cu = curl_url();
|
||||
auto set_rc = curl_url_set(
|
||||
cu, CURLUPART_URL, url.c_str(), CURLU_NON_SUPPORT_SCHEME);
|
||||
if (set_rc != CURLUE_OK) {
|
||||
return Err(
|
||||
lnav::console::user_message::error(
|
||||
attr_line_t("invalid URL: ").append(lnav::roles::file(url)))
|
||||
.with_reason(curl_url_strerror(set_rc)));
|
||||
}
|
||||
|
||||
char* scheme_part;
|
||||
auto get_rc = curl_url_get(cu, CURLUPART_SCHEME, &scheme_part, 0);
|
||||
if (get_rc != CURLUE_OK) {
|
||||
return Err(lnav::console::user_message::error(
|
||||
attr_line_t("cannot get scheme from URL: ")
|
||||
.append(lnav::roles::file(url)))
|
||||
.with_reason(curl_url_strerror(set_rc)));
|
||||
}
|
||||
|
||||
auto proto_iter = cfg.c_schemes.find(scheme_part);
|
||||
if (proto_iter == cfg.c_schemes.end()) {
|
||||
return Err(lnav::console::user_message::error(
|
||||
attr_line_t("no defined handler for URL scheme: ")
|
||||
.append(lnav::roles::file(scheme_part)))
|
||||
.with_reason(curl_url_strerror(set_rc)));
|
||||
}
|
||||
|
||||
log_info("found URL handler: %s",
|
||||
proto_iter->second.p_handler.pp_value.c_str());
|
||||
auto err_pipe_res = auto_pipe::for_child_fd(STDERR_FILENO);
|
||||
if (err_pipe_res.isErr()) {
|
||||
return Err(
|
||||
lnav::console::user_message::error(
|
||||
attr_line_t("cannot open URL: ").append(lnav::roles::file(url)))
|
||||
.with_reason(err_pipe_res.unwrapErr()));
|
||||
}
|
||||
auto err_pipe = err_pipe_res.unwrap();
|
||||
auto out_pipe_res = auto_pipe::for_child_fd(STDOUT_FILENO);
|
||||
if (out_pipe_res.isErr()) {
|
||||
return Err(
|
||||
lnav::console::user_message::error(
|
||||
attr_line_t("cannot open URL: ").append(lnav::roles::file(url)))
|
||||
.with_reason(out_pipe_res.unwrapErr()));
|
||||
}
|
||||
auto out_pipe = out_pipe_res.unwrap();
|
||||
auto child_pid_res = lnav::pid::from_fork();
|
||||
if (child_pid_res.isErr()) {
|
||||
return Err(
|
||||
lnav::console::user_message::error(
|
||||
attr_line_t("cannot open URL: ").append(lnav::roles::file(url)))
|
||||
.with_reason(child_pid_res.unwrapErr()));
|
||||
}
|
||||
|
||||
auto child_pid = child_pid_res.unwrap();
|
||||
|
||||
out_pipe.after_fork(child_pid.in());
|
||||
err_pipe.after_fork(child_pid.in());
|
||||
|
||||
auto name = proto_iter->second.p_handler.pp_value;
|
||||
if (child_pid.in_child()) {
|
||||
auto dev_null = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
|
||||
|
||||
dup2(dev_null, STDIN_FILENO);
|
||||
|
||||
char* host_part = nullptr;
|
||||
curl_url_get(cu, CURLUPART_HOST, &host_part, 0);
|
||||
std::string host_part_str;
|
||||
if (host_part != nullptr) {
|
||||
host_part_str = host_part;
|
||||
}
|
||||
|
||||
auto source_path = ghc::filesystem::path{
|
||||
proto_iter->second.p_handler.pp_location.sl_source.get()};
|
||||
auto new_path = lnav::filesystem::build_path({
|
||||
source_path.parent_path(),
|
||||
lnav::paths::dotlnav() / "formats/default",
|
||||
});
|
||||
setenv("PATH", new_path.c_str(), 1);
|
||||
setenv("URL_HOSTNAME", host_part_str.c_str(), 1);
|
||||
|
||||
const char* args[] = {
|
||||
name.c_str(),
|
||||
nullptr,
|
||||
};
|
||||
|
||||
execvp(name.c_str(), (char**) args);
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
auto error_queue = std::make_shared<std::vector<std::string>>();
|
||||
std::thread err_reader([err = std::move(err_pipe.read_end()),
|
||||
name,
|
||||
error_queue,
|
||||
child_pid = child_pid.in()]() mutable {
|
||||
line_buffer lb;
|
||||
file_range pipe_range;
|
||||
bool done = false;
|
||||
|
||||
log_debug("error reader");
|
||||
lb.set_fd(err);
|
||||
while (!done) {
|
||||
auto load_res = lb.load_next_line(pipe_range);
|
||||
|
||||
if (load_res.isErr()) {
|
||||
done = true;
|
||||
} else {
|
||||
auto li = load_res.unwrap();
|
||||
|
||||
pipe_range = li.li_file_range;
|
||||
if (li.li_file_range.empty()) {
|
||||
done = true;
|
||||
} else {
|
||||
lb.read_range(li.li_file_range)
|
||||
.then([name, error_queue, child_pid](auto sbr) {
|
||||
auto line_str = string_fragment(
|
||||
sbr.get_data(), 0, sbr.length())
|
||||
.trim("\n");
|
||||
if (error_queue->size() < 5) {
|
||||
error_queue->emplace_back(line_str.to_string());
|
||||
}
|
||||
|
||||
log_debug("%s[%d]: %.*s",
|
||||
name.c_str(),
|
||||
child_pid,
|
||||
line_str.length(),
|
||||
line_str.data());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
err_reader.detach();
|
||||
|
||||
auto child = std::make_shared<handler_looper>(
|
||||
url, std::move(child_pid), std::move(out_pipe.read_end()));
|
||||
this->s_children.add_child_service(child);
|
||||
this->l_children[url] = child;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
void
|
||||
looper::close(std::string url)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace url_handler
|
||||
} // namespace lnav
|
52
src/url_handler.cfg.hh
Normal file
52
src/url_handler.cfg.hh
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2023, 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_url_handler_cfg_hh
|
||||
#define lnav_url_handler_cfg_hh
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "yajlpp/yajlpp.hh"
|
||||
|
||||
namespace lnav {
|
||||
namespace url_handler {
|
||||
|
||||
struct scheme {
|
||||
positioned_property<std::string> p_handler;
|
||||
};
|
||||
|
||||
struct config {
|
||||
std::map<std::string, scheme> c_schemes;
|
||||
};
|
||||
|
||||
} // namespace url_handler
|
||||
} // namespace lnav
|
||||
|
||||
#endif
|
95
src/url_handler.hh
Normal file
95
src/url_handler.hh
Normal file
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2023, 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.
|
||||
*/
|
||||
|
||||
// XXX This code is unused right now, but it's nice and I don't want to delete
|
||||
// it just yet.
|
||||
|
||||
#ifndef lnav_url_handler_hh
|
||||
#define lnav_url_handler_hh
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "base/auto_fd.hh"
|
||||
#include "base/auto_pid.hh"
|
||||
#include "base/isc.hh"
|
||||
#include "base/lnav.console.hh"
|
||||
#include "base/result.h"
|
||||
#include "line_buffer.hh"
|
||||
#include "mapbox/variant.hpp"
|
||||
|
||||
namespace lnav {
|
||||
namespace url_handler {
|
||||
|
||||
class looper : public isc::service<looper> {
|
||||
public:
|
||||
Result<void, lnav::console::user_message> open(std::string url);
|
||||
|
||||
void close(std::string url);
|
||||
|
||||
private:
|
||||
class handler_looper : public isc::service<handler_looper> {
|
||||
public:
|
||||
handler_looper(std::string url,
|
||||
auto_pid<process_state::running> pid,
|
||||
auto_fd infd)
|
||||
: isc::service<handler_looper>(url), hl_state(std::move(pid))
|
||||
{
|
||||
this->hl_line_buffer.set_fd(infd);
|
||||
}
|
||||
|
||||
protected:
|
||||
void loop_body() override;
|
||||
|
||||
std::chrono::milliseconds compute_timeout(
|
||||
mstime_t current_time) const override
|
||||
{
|
||||
return std::chrono::milliseconds{0};
|
||||
}
|
||||
|
||||
public:
|
||||
struct handler_completed {};
|
||||
|
||||
using state_v = mapbox::util::variant<auto_pid<process_state::running>,
|
||||
handler_completed>;
|
||||
|
||||
file_range hl_last_range;
|
||||
line_buffer hl_line_buffer;
|
||||
state_v hl_state;
|
||||
};
|
||||
|
||||
struct child {};
|
||||
|
||||
std::map<std::string, std::shared_ptr<handler_looper>> l_children;
|
||||
};
|
||||
|
||||
} // namespace url_handler
|
||||
} // namespace lnav
|
||||
|
||||
#endif
|
@ -842,6 +842,14 @@ struct json_path_handler : public json_path_handler_base {
|
||||
|
||||
return 1;
|
||||
};
|
||||
this->add_cb(null_field_cb);
|
||||
this->jph_null_cb = [args...](yajlpp_parse_context* ypc) {
|
||||
auto* obj = ypc->ypc_obj_stack.top();
|
||||
|
||||
json_path_handler::get_field(obj, args...) = nonstd::nullopt;
|
||||
|
||||
return 1;
|
||||
};
|
||||
this->jph_gen_callback = [args...](yajlpp_gen_context& ygc,
|
||||
const json_path_handler_base& jph,
|
||||
yajl_gen handle) {
|
||||
@ -1360,7 +1368,9 @@ public:
|
||||
const string_fragment& json)
|
||||
{
|
||||
if (this->yp_parse_context.parse_doc(json)) {
|
||||
return Ok(std::move(this->yp_obj));
|
||||
if (this->yp_errors.empty()) {
|
||||
return Ok(std::move(this->yp_obj));
|
||||
}
|
||||
}
|
||||
|
||||
return Err(std::move(this->yp_errors));
|
||||
|
@ -3761,6 +3761,20 @@ For support questions, email:
|
||||
|
||||
|
||||
|
||||
[1m[4mshell_exec[0m[4m([0m[4mcmd[0m[4m, [[0m[4minput[0m[4m], [[0m[4moptions[0m[4m])[0m
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
Executes a shell command and returns its output.
|
||||
[4mParameters[0m
|
||||
[4mcmd[0m The command to execute.
|
||||
[4minput[0m A blob of data to write to the command's
|
||||
standard input.
|
||||
[4moptions[0m A JSON object containing options for the
|
||||
execution with the following properties:
|
||||
[4menv[0m - An object containing the environment variables to
|
||||
set or, if NULL, to unset.
|
||||
[4mSee Also[0m
|
||||
|
||||
|
||||
[1m[4msign[0m[4m([0m[4mnum[0m[4m)[0m
|
||||
══════════════════════════════════════════════════════════════════════
|
||||
Returns the sign of the given number as -1, 0, or 1
|
||||
@ -4330,6 +4344,7 @@ For support questions, email:
|
||||
comparison in the branches
|
||||
[4mcmp-expr[0m The expression to test if this branch should
|
||||
be taken
|
||||
[4mthen-expr[0m - The result for this branch.
|
||||
[4melse-expr[0m The result of this CASE if no branches
|
||||
matched.
|
||||
|
||||
@ -4424,6 +4439,7 @@ For support questions, email:
|
||||
[4mParameters[0m
|
||||
[4mtable[0m The table to update
|
||||
[4mcolumn-name[0m The columns in the table to update.
|
||||
[4mexpr[0m - The values to place into the column.
|
||||
[4mcond[0m The condition used to determine whether
|
||||
a row should be updated.
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
Row 0:
|
||||
Column parse_url('https://example.com/'): {"scheme":"https","user":null,"password":null,"host":"example.com","port":null,"path":"/","query":null,"parameters":null,"fragment":null}
|
||||
Column parse_url('https://example.com/'): {"scheme":"https","username":null,"password":null,"host":"example.com","port":null,"path":"/","query":null,"parameters":null,"fragment":null}
|
||||
|
@ -1,2 +1,2 @@
|
||||
Row 0:
|
||||
Column parse_url('https://example.com/search?flag&flag2&=def'): {"scheme":"https","user":null,"password":null,"host":"example.com","port":null,"path":"/search","query":"flag&flag2&=def","parameters":{"flag":null,"flag2":null,"":"def"},"fragment":null}
|
||||
Column parse_url('https://example.com/search?flag&flag2&=def'): {"scheme":"https","username":null,"password":null,"host":"example.com","port":null,"path":"/search","query":"flag&flag2&=def","parameters":{"flag":null,"flag2":null,"":"def"},"fragment":null}
|
||||
|
@ -1,2 +1,2 @@
|
||||
Row 0:
|
||||
Column parse_url('https://example.com/sea%26rch?flag&flag2&=def#frag1%20space'): {"scheme":"https","user":null,"password":null,"host":"example.com","port":null,"path":"/sea&rch","query":"flag&flag2&=def","parameters":{"flag":null,"flag2":null,"":"def"},"fragment":"frag1 space"}
|
||||
Column parse_url('https://example.com/sea%26rch?flag&flag2&=def#frag1%20space'): {"scheme":"https","username":null,"password":null,"host":"example.com","port":null,"path":"/sea&rch","query":"flag&flag2&=def","parameters":{"flag":null,"flag2":null,"":"def"},"fragment":"frag1 space"}
|
||||
|
@ -1,2 +1,2 @@
|
||||
Row 0:
|
||||
Column parse_url('https://example.com/search?flag&flag2'): {"scheme":"https","user":null,"password":null,"host":"example.com","port":null,"path":"/search","query":"flag&flag2","parameters":{"flag":null,"flag2":null},"fragment":null}
|
||||
Column parse_url('https://example.com/search?flag&flag2'): {"scheme":"https","username":null,"password":null,"host":"example.com","port":null,"path":"/search","query":"flag&flag2","parameters":{"flag":null,"flag2":null},"fragment":null}
|
||||
|
@ -1 +1 @@
|
||||
error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"invalid URL: “https://example.com:100000”","attrs":[]},"reason":{"str":"Port number was not a decimal number between 0 and 65535","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}}
|
||||
error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"invalid URL: https://example.com:100000","attrs":[{"start":13,"end":39,"type":"role","value":51}]},"reason":{"str":"Port number was not a decimal number between 0 and 65535","attrs":[]},"snippets":[],"help":{"str":"","attrs":[]}}
|
||||
|
@ -1,2 +1,2 @@
|
||||
Row 0:
|
||||
Column parse_url('https://example.com'): {"scheme":"https","user":null,"password":null,"host":"example.com","port":null,"path":"/","query":null,"parameters":null,"fragment":null}
|
||||
Column parse_url('https://example.com'): {"scheme":"https","username":null,"password":null,"host":"example.com","port":null,"path":"/","query":null,"parameters":null,"fragment":null}
|
||||
|
@ -1,2 +1,2 @@
|
||||
Row 0:
|
||||
Column parse_url('https://example.com/search?flag'): {"scheme":"https","user":null,"password":null,"host":"example.com","port":null,"path":"/search","query":"flag","parameters":{"flag":null},"fragment":null}
|
||||
Column parse_url('https://example.com/search?flag'): {"scheme":"https","username":null,"password":null,"host":"example.com","port":null,"path":"/search","query":"flag","parameters":{"flag":null},"fragment":null}
|
||||
|
Loading…
Reference in New Issue
Block a user