diff --git a/NEWS b/NEWS index cacf95fd..db6e1c61 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,13 @@ lnav v0.9.1: * Themes can now include definitions for text highlights under: /ui/theme-defs//highlights * Added a "grayscale" theme that isn't so colorful. + * Added the humanize_file_size() SQL function that converts a numeric size + to a human-friendly string. + * Added the sparkline() SQL function that returns a "sparkline" bar made + out of unicode characters. It can be used with a single value or as + an aggregator. + * Added a "log_time_msecs" hidden column to the log tables that returns + the timestamp as the number of milliseconds from the epoch. Interface Changes: * When copying log lines, the file name and time offset will be included @@ -42,9 +49,15 @@ lnav v0.9.1: * The range_start and range_stop values of the regexp_capture() results now start at 1 instead of zero to match with what the other SQL string functions expect. + * The ":write-cols-to" command has been renamed to ":write-table-to". + * The DB view will limit the maximum column width to 120 characters. + * The ":echo" command now evaluates its message to do variable + substitution. Fixes: * Unicode text can now be entered in prompts. + * The replicate() SQL function would cause a crash if the number of + replications was zero. lnav v0.9.0: Features: diff --git a/docs/source/conf.py b/docs/source/conf.py index b132f3f5..a1d5f932 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -41,6 +41,7 @@ class CustSqliteLexer(RegexLexer): 'root': [ (r'\s+', Text), (r'--.*\n?', Comment.Single), + (r'#.*\n?', Comment.Single), (r'/\*', Comment.Multiline, 'multiline-comments'), (words(( 'ABORT', diff --git a/docs/source/cookbook.rst b/docs/source/cookbook.rst index 44a8e5a3..3eb0ffda 100644 --- a/docs/source/cookbook.rst +++ b/docs/source/cookbook.rst @@ -19,6 +19,40 @@ Defining a New Format TBD + +Annotating Logs +--------------- + +Log messages can be annotated in a couple of different ways in **lnav** to help +you get organized. + +Create partitions for Linux boots +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When digging through logs that can be broken up into multiple sections, +**lnav**'s :ref:`partitioning feature` can be used to keep track +of which section you are in. For example, if a collection of Linux logs +covered multiple boots, the following script could be used to create partitions +for each boot. After the partition name is set for the log messages, the +current name will show up in the top status bar next to the current time. + +.. literalinclude:: ../../src/scripts/partition-by-boot.lnav + :language: custsqlite + :caption: partition-by-boot.lnav + :linenos: + +Tagging SSH log messages +^^^^^^^^^^^^^^^^^^^^^^^^ + +Log messages can be tagged interactively with the :ref:`:tag` command or +programmatically using the :ref:`sql-ext`. This example uses a script to +search for interesting SSH messages and automatically adds an appropriate tag. + +.. literalinclude:: ../../example-scripts/tag-ssh-msgs.lnav + :language: custsqlite + :caption: tag-ssh-msgs.lnav + :linenos: + Log Analysis ------------ @@ -50,3 +84,21 @@ between 10,000 and 40,000 bytes like so: .. code-block:: custsqlite :filter-expr :sc_bytes BETWEEN 10000 AND 40000 + + +Generating a Report +^^^^^^^^^^^^^^^^^^^ + +Reports can be generated by writing an **lnav** :ref:`script` that +uses SQL queries and commands to format a document. A basic script can simply +execute a SQL query that is shown in the DB view. More sophisticated scripts +can use the following commands to generate customized output for a report: + +* The :ref:`:echo` command to write plain text +* :ref:`SQL queries` followed by a "write" command, like + :ref:`:write-table-to`. + +.. literalinclude:: ../../example-scripts/report-demo.lnav + :language: custsqlite + :caption: report-demo.lnav + :linenos: diff --git a/docs/source/formats.rst b/docs/source/formats.rst index f96512ba..995cb5f9 100644 --- a/docs/source/formats.rst +++ b/docs/source/formats.rst @@ -333,9 +333,25 @@ can have a mix of SQL and **lnav** commands, as well as include other scripts. The type of statement to execute is determined by the leading character on a line: a semi-colon begins a SQL statement; a colon starts an **lnav** command; and a pipe (|) denotes another script to be executed. Lines beginning with a -hash are treated as comments. Any arguments passed to a script can be -referenced using '$N' where 'N' is the index of the argument. Remember that -you need to use the :ref:`:eval` command when referencing +hash are treated as comments. The following variables are defined in a script: + +.. envvar:: # + + The number of arguments passed to the script. + +.. envvar:: __all__ + + A string containing all the arguments joined by a single space. + +.. envvar:: 0 + + The path to the script being executed. + +.. envvar:: 1-N + + The arguments passed to the script. + +Remember that you need to use the :ref:`:eval` command when referencing variables in most **lnav** commands. Scripts can provide help text to be displayed during interactive usage by adding the following tags in a comment header: @@ -350,6 +366,17 @@ header: # @description: Say hello to the given names. + +.. tip:: + + The :ref:`:eval` command can be used to do variable substitution for + commands that do not natively support it. For example, to substitute the + variable, :code:`pattern`, in a :ref:`:filter-out` command: + + .. code-block:: lnav + + :eval :filter-out ${pattern} + Installing Formats ------------------ diff --git a/docs/source/sqlext.rst b/docs/source/sqlext.rst index 38326cd2..abfa01c6 100644 --- a/docs/source/sqlext.rst +++ b/docs/source/sqlext.rst @@ -84,69 +84,6 @@ the `sqlite.org `_ web site. **lnav**'s interface to perform queries. The database will be attached with a name based on the database file name. -Taking Notes ------------- - -A few of the columns in the log tables can be updated on a row-by-row basis to -allow you to take notes. The majority of the columns in a log table are -read-only since they are backed by the log files themselves. However, the -following columns can be changed by an :code:`UPDATE` statement: - -* **log_part** - The "partition" the log message belongs to. This column can - also be changed by the :ref:`:partition-name` command. -* **log_mark** - Indicates whether the line has been bookmarked. -* **log_comment** - A free-form text field for storing commentary. This - column can also be changed by the :ref:`:comment` command. -* **log_tags** - A JSON list of tags associated with the log message. This - column can also be changed by the :ref:`:tag` command. - -While these columns can be updated by through other means, using the SQL -interface allows you to make changes automatically and en masse. For example, -to bookmark all lines that have the text "something interesting" in the log -message body, you can execute: - -.. code-block:: custsqlite - - ;UPDATE all_logs SET log_mark = 1 WHERE log_body LIKE '%something interesting%' - -As a more advanced example of the power afforded by SQL and **lnav**'s virtual -tables, we will tag log messages where the IP address bound by dhclient has -changed. For example, if dhclient reports "bound to 10.0.0.1" initially and -then reports "bound to 10.0.0.2", we want to tag only the messages where the -IP address was different from the previous message. While this can be done -with a single SQL statement [#]_, we will break things down into a few steps for -this example. First, we will use the :ref:`:create-search-table` -command to match the dhclient message and extract the IP address: - -.. code-block:: lnav - - :create-search-table dhclient_ip bound to (?[^ ]+) - -The above command will create a new table named :code:`dhclient_ip` with the -standard log columns and an :code:`ip` column that contains the IP address. -Next, we will create a view over the :code:`dhclient_ip` table that returns -the log message line number, the IP address from the current row and the IP -address from the previous row: - -.. code-block:: custsqlite - - ;CREATE VIEW IF NOT EXISTS dhclient_ip_changes AS SELECT log_line, ip, lag(ip) OVER (ORDER BY log_line) AS prev_ip FROM dhclient_ip - -Finally, the following :code:`UPDATE` statement will concatenate the tag -"#ipchanged" onto the :code:`log_tags` column for any rows in the view where -the current IP is different from the previous IP: - -.. code-block:: custsqlite - - ;UPDATE syslog_log SET log_tags = json_concat(log_tags, '#ipchanged') WHERE log_line IN (SELECT log_line FROM dhclient_ip_changes WHERE ip != prev_ip) - -Since the above can be a lot to type out interactively, you can put these -commands into a :ref:`script` and execute that script with the -:kbd:`\|` hotkey. - -.. [#] The expression :code:`regexp_match('bound to ([^ ]+)', log_body) as ip` - can be used to extract the IP address from the log message body. - Commands -------- diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 32258527..4a37379f 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -132,4 +132,70 @@ To hide messages below a certain log level, you can use the Search Tables ------------- -TBD \ No newline at end of file +TBD + + +.. _taking_notes: + +Taking Notes +------------ + +A few of the columns in the log tables can be updated on a row-by-row basis to +allow you to take notes. The majority of the columns in a log table are +read-only since they are backed by the log files themselves. However, the +following columns can be changed by an :code:`UPDATE` statement: + +* **log_part** - The "partition" the log message belongs to. This column can + also be changed by the :ref:`:partition-name` command. +* **log_mark** - Indicates whether the line has been bookmarked. +* **log_comment** - A free-form text field for storing commentary. This + column can also be changed by the :ref:`:comment` command. +* **log_tags** - A JSON list of tags associated with the log message. This + column can also be changed by the :ref:`:tag` command. + +While these columns can be updated by through other means, using the SQL +interface allows you to make changes automatically and en masse. For example, +to bookmark all lines that have the text "something interesting" in the log +message body, you can execute: + +.. code-block:: custsqlite + + ;UPDATE all_logs SET log_mark = 1 WHERE log_body LIKE '%something interesting%' + +As a more advanced example of the power afforded by SQL and **lnav**'s virtual +tables, we will tag log messages where the IP address bound by dhclient has +changed. For example, if dhclient reports "bound to 10.0.0.1" initially and +then reports "bound to 10.0.0.2", we want to tag only the messages where the +IP address was different from the previous message. While this can be done +with a single SQL statement [#]_, we will break things down into a few steps for +this example. First, we will use the :ref:`:create-search-table` +command to match the dhclient message and extract the IP address: + +.. code-block:: lnav + + :create-search-table dhclient_ip bound to (?[^ ]+) + +The above command will create a new table named :code:`dhclient_ip` with the +standard log columns and an :code:`ip` column that contains the IP address. +Next, we will create a view over the :code:`dhclient_ip` table that returns +the log message line number, the IP address from the current row and the IP +address from the previous row: + +.. code-block:: custsqlite + + ;CREATE VIEW IF NOT EXISTS dhclient_ip_changes AS SELECT log_line, ip, lag(ip) OVER (ORDER BY log_line) AS prev_ip FROM dhclient_ip + +Finally, the following :code:`UPDATE` statement will concatenate the tag +"#ipchanged" onto the :code:`log_tags` column for any rows in the view where +the current IP is different from the previous IP: + +.. code-block:: custsqlite + + ;UPDATE syslog_log SET log_tags = json_concat(log_tags, '#ipchanged') WHERE log_line IN (SELECT log_line FROM dhclient_ip_changes WHERE ip != prev_ip) + +Since the above can be a lot to type out interactively, you can put these +commands into a :ref:`script` and execute that script with the +:kbd:`\|` hotkey. + +.. [#] The expression :code:`regexp_match('bound to ([^ ]+)', log_body) as ip` + can be used to extract the IP address from the log message body. diff --git a/example-scripts/report-demo.lnav b/example-scripts/report-demo.lnav new file mode 100644 index 00000000..aeb00401 --- /dev/null +++ b/example-scripts/report-demo.lnav @@ -0,0 +1,83 @@ +# +# @synopsis: report-demo [] +# @description: Generate a report for requests in access_log files +# + +# Figure out the file path where the report should be written to, default is +# stdout +;SELECT CASE + WHEN $1 IS NULL THEN '-' + ELSE $1 + END AS out_path + +# Redirect output from commands to $out_path +:redirect-to $out_path + +# Print an introductory message +;SELECT printf('\n%d total requests', count(1)) AS msg FROM access_log +:echo $msg + +;WITH top_paths AS ( + SELECT + cs_uri_stem, + count(1) AS total_hits, + sum(sc_bytes) as bytes, + count(distinct c_ip) as visitors + FROM access_log + WHERE sc_status BETWEEN 200 AND 300 + GROUP BY cs_uri_stem + ORDER BY total_hits DESC + LIMIT 50), + weekly_hits_with_gaps AS ( + SELECT timeslice(log_time_msecs, '1w') AS week, + cs_uri_stem, + count(1) AS weekly_hits + FROM access_log + WHERE cs_uri_stem IN (SELECT cs_uri_stem FROM top_paths) AND + sc_status BETWEEN 200 AND 300 + GROUP BY week, cs_uri_stem), + all_weeks AS ( + SELECT week + FROM weekly_hits_with_gaps + GROUP BY week + ORDER BY week ASC), + weekly_hits AS ( + SELECT all_weeks.week, + top_paths.cs_uri_stem, + ifnull(weekly_hits, 0) AS hits + FROM all_weeks + CROSS JOIN top_paths + LEFT JOIN weekly_hits_with_gaps + ON all_weeks.week = weekly_hits_with_gaps.week AND + top_paths.cs_uri_stem = weekly_hits_with_gaps.cs_uri_stem) + SELECT weekly_hits.cs_uri_stem AS Path, + printf('%,9d', total_hits) AS Hits, + printf('%,9d', visitors) AS Visitors, + printf('%9s', humanize_file_size(bytes)) as Amount, + sparkline(hits) AS Weeks + FROM weekly_hits + LEFT JOIN top_paths ON top_paths.cs_uri_stem = weekly_hits.cs_uri_stem + GROUP BY weekly_hits.cs_uri_stem + ORDER BY Hits DESC + LIMIT 10 + +:write-table-to - + +:echo +:echo Failed Requests +:echo + +;SELECT printf('%,9d', count(1)) AS Hits, + printf('%,9d', count(distinct c_ip)) AS Visitors, + sc_status AS Status, + cs_method AS Method, + group_concat(distinct cs_version) AS Versions, + cs_uri_stem AS Path, + replicate('|', (cast(count(1) AS REAL) / $total_requests) * 100.0) AS "% of Requests" + FROM access_log + WHERE sc_status >= 400 + GROUP BY cs_method, cs_uri_stem + ORDER BY Hits DESC + LIMIT 10 + +:write-table-to - diff --git a/example-scripts/tag-ssh-msgs.lnav b/example-scripts/tag-ssh-msgs.lnav new file mode 100644 index 00000000..9ffad6ac --- /dev/null +++ b/example-scripts/tag-ssh-msgs.lnav @@ -0,0 +1,10 @@ +# +# @synopsis: tag-ssh-msgs +# @description: Tag interesting SSH log messages +# + +;UPDATE all_logs + SET log_tags = json_concat(log_tags, '#ssh.invalid-user') + WHERE log_text LIKE '%Invalid user from%' + +;SELECT 'Tagged ' || changes() || ' messages'; diff --git a/src/archive_manager.cc b/src/archive_manager.cc index d4643ff1..ec020192 100644 --- a/src/archive_manager.cc +++ b/src/archive_manager.cc @@ -89,6 +89,22 @@ public: auto_fd lh_fd; }; +#if HAVE_ARCHIVE_H +/** + * Enables a subset of the supported archive formats to speed up detection, + * since some formats, like xar are unlikely to be used. + */ +static void enable_desired_archive_formats(archive *arc) +{ + archive_read_support_format_7zip(arc); + archive_read_support_format_cpio(arc); + archive_read_support_format_lha(arc); + archive_read_support_format_rar(arc); + archive_read_support_format_tar(arc); + archive_read_support_format_zip(arc); +} +#endif + bool is_archive(const fs::path& filename) { #if HAVE_ARCHIVE_H @@ -97,7 +113,7 @@ bool is_archive(const fs::path& filename) arc = archive_read_new(); archive_read_support_filter_all(arc); - archive_read_support_format_all(arc); + enable_desired_archive_formats(arc); archive_read_support_format_raw(arc); log_debug("read open %s", filename.c_str()); auto r = archive_read_open_filename(arc, filename.c_str(), 128 * 1024); @@ -254,7 +270,7 @@ static walk_result_t extract(const std::string &filename, const extract_cb &cb) auto_mem ext(archive_free); arc = archive_read_new(); - archive_read_support_format_all(arc); + enable_desired_archive_formats(arc); archive_read_support_format_raw(arc); archive_read_support_filter_all(arc); ext = archive_write_disk_new(); diff --git a/src/auto_fd.hh b/src/auto_fd.hh index a2d64abb..fa8e3eb0 100644 --- a/src/auto_fd.hh +++ b/src/auto_fd.hh @@ -72,6 +72,26 @@ public: return retval; }; + /** + * dup(2) the given file descriptor and wrap it in an auto_fd. + * + * @param fd The file descriptor to duplicate. + * @return A new auto_fd that contains the duplicated file descriptor. + */ + static auto_fd dup_of(int fd) { + if (fd == -1) { + return auto_fd{}; + } + + auto new_fd = dup(fd); + + if (new_fd == -1) { + throw std::bad_alloc(); + } + + return auto_fd(new_fd); + }; + /** * Construct an auto_fd to manage the given file descriptor. * diff --git a/src/base/humanize.cc b/src/base/humanize.cc index bd1d8de3..12e6d19e 100644 --- a/src/base/humanize.cc +++ b/src/base/humanize.cc @@ -37,7 +37,7 @@ namespace humanize { -std::string file_size(ssize_t value) +std::string file_size(file_ssize_t value) { static const double LN1024 = log(1024.0); static const std::vector UNITS = { @@ -61,4 +61,34 @@ std::string file_size(ssize_t value) UNITS[exp]); } +const std::string& sparkline(double value, nonstd::optional upper_opt) +{ + static const std::string ZERO = " "; + static const std::string BARS[] = { + "\u2581", + "\u2582", + "\u2583", + "\u2584", + "\u2585", + "\u2586", + "\u2587", + "\u2588", + }; + static const double BARS_COUNT = std::distance(begin(BARS), end(BARS)); + + if (value <= 0.0) { + return ZERO; + } + + auto upper = upper_opt.value_or(100.0); + + if (value >= upper) { + return BARS[(size_t) BARS_COUNT - 1]; + } + + size_t index = ceil((value / upper) * BARS_COUNT) - 1; + + return BARS[index]; +} + } diff --git a/src/base/humanize.hh b/src/base/humanize.hh index fc8dbffe..985662a4 100644 --- a/src/base/humanize.hh +++ b/src/base/humanize.hh @@ -34,6 +34,8 @@ #include +#include "file_range.hh" + namespace humanize { /** @@ -42,7 +44,9 @@ namespace humanize { * @param value The value to format. * @return The formatted string. */ -std::string file_size(ssize_t value); +std::string file_size(file_ssize_t value); + +const std::string& sparkline(double value, nonstd::optional upper); } diff --git a/src/base/string_util.cc b/src/base/string_util.cc index e93b2914..eaa845a8 100644 --- a/src/base/string_util.cc +++ b/src/base/string_util.cc @@ -113,6 +113,11 @@ size_t unquote_w3c(char *dst, const char *str, size_t len) void truncate_to(std::string &str, size_t max_char_len) { static const std::string ELLIPSIS = "\u22ef"; + + if (str.length() < max_char_len) { + return; + } + auto str_char_len_res = utf8_string_length(str); if (str_char_len_res.isErr()) { @@ -130,12 +135,16 @@ void truncate_to(std::string &str, size_t max_char_len) return; } - auto to_remove = (str_char_len - max_char_len) + 1; + auto chars_to_remove = (str_char_len - max_char_len) + 1; auto midpoint = str_char_len / 2; - auto to_keep_at_front = midpoint - (to_remove / 2); - - str.erase(to_keep_at_front, to_remove); - str.insert(to_keep_at_front, ELLIPSIS); + auto chars_to_keep_at_front = midpoint - (chars_to_remove / 2); + auto bytes_to_keep_at_front = + utf8_char_to_byte_index(str, chars_to_keep_at_front); + auto remove_up_to_bytes = + utf8_char_to_byte_index(str, chars_to_keep_at_front + chars_to_remove); + auto bytes_to_remove = remove_up_to_bytes - bytes_to_keep_at_front; + str.erase(bytes_to_keep_at_front, bytes_to_remove); + str.insert(bytes_to_keep_at_front, ELLIPSIS); } bool is_url(const char *fn) @@ -190,3 +199,20 @@ std::string repeat(const std::string& input, size_t num) std::fill_n(std::ostream_iterator(os), num, input); return os.str(); } + +std::string center_str(const std::string &subject, size_t width) +{ + std::string retval = subject; + + truncate_to(retval, width); + + auto retval_length = utf8_string_length(retval).unwrapOr(retval.length()); + auto total_fill = width - retval_length; + auto before = total_fill / 2; + auto after = total_fill - before; + + retval.insert(0, before, ' '); + retval.append(after, ' '); + + return retval; +} diff --git a/src/base/string_util.hh b/src/base/string_util.hh index 95c2dfa5..c8c5f7fc 100644 --- a/src/base/string_util.hh +++ b/src/base/string_util.hh @@ -137,13 +137,17 @@ inline ssize_t utf8_char_to_byte_index(const std::string &str, ssize_t ch_index) return retval; } -inline Result utf8_string_length(const std::string &str) +inline Result utf8_string_length(const char *str, ssize_t len = -1) { size_t retval = 0; - for (size_t byte_index = 0; byte_index < str.length();) { - auto ch_size = TRY(ww898::utf::utf8::char_size([&str, byte_index]() { - return std::make_pair(str[byte_index], str.length() - byte_index); + if (len == -1) { + len = strlen(str); + } + + for (ssize_t byte_index = 0; byte_index < len;) { + auto ch_size = TRY(ww898::utf::utf8::char_size([str, len, byte_index]() { + return std::make_pair(str[byte_index], len - byte_index); })); byte_index += ch_size; retval += 1; @@ -152,6 +156,11 @@ inline Result utf8_string_length(const std::string &str) return Ok(retval); } +inline Result utf8_string_length(const std::string& str) +{ + return utf8_string_length(str.c_str(), str.length()); +} + bool is_url(const char *fn); size_t abbreviate_str(char *str, size_t len, size_t max_len); @@ -160,4 +169,6 @@ void split_ws(const std::string &str, std::vector &toks_out); std::string repeat(const std::string& input, size_t num); +std::string center_str(const std::string& subject, size_t width); + #endif diff --git a/src/column_namer.cc b/src/column_namer.cc index 404b7cf7..25b036d4 100644 --- a/src/column_namer.cc +++ b/src/column_namer.cc @@ -84,11 +84,10 @@ std::string column_namer::add_column(const std::string &in_name) while (this->existing_name(retval)) { if (num == 0) { - log_debug("existing!"); this->cn_name_counters[retval] = num; } - log_debug("dup %s", retval.c_str()); + log_debug("column name already exists: %s", retval.c_str()); snprintf(buffer, buf_size, "%s_%d", base_name.c_str(), num); retval = buffer; num += 1; diff --git a/src/command_executor.cc b/src/command_executor.cc index 5428e534..62750331 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -424,7 +424,7 @@ static Result execute_file_contents(exec_context &ec, const ghc: char mode = '\0'; ec.ec_path_stack.emplace_back(path.parent_path()); - ec.ec_output_stack.emplace_back(nonstd::nullopt); + exec_context::output_guard og(ec); while ((line_size = getline(line.out(), &line_max_size, file)) != -1) { line_number += 1; @@ -471,7 +471,6 @@ static Result execute_file_contents(exec_context &ec, const ghc: } else { fclose(file); } - ec.ec_output_stack.pop_back(); ec.ec_path_stack.pop_back(); return Ok(retval); @@ -846,3 +845,41 @@ std::string exec_context::get_error_prefix() return fmt::format("{}:{}: error: ", source.first, source.second); } + +void exec_context::set_output(const string &name, FILE *file) +{ + log_info("redirecting command output to: %s", name.c_str()); + this->ec_output_stack.back().second | [](auto file) { + if (file != stdout) { + fclose(file); + } + }; + this->ec_output_stack.back() = std::make_pair(name, file); +} + +void exec_context::clear_output() +{ + log_info("redirecting command output to screen"); + this->ec_output_stack.back().second | [](auto file) { + if (file != stdout) { + fclose(file); + } + }; + this->ec_output_stack.back() = std::make_pair("default", nonstd::nullopt); +} + +exec_context::output_guard::output_guard(exec_context &context, + std::string name, + const nonstd::optional &file) + : sg_context(context) { + if (file) { + log_info("redirecting command output to: %s", name.c_str()); + } + context.ec_output_stack.emplace_back(std::move(name), file); +} + +exec_context::output_guard::~output_guard() +{ + this->sg_context.clear_output(); + this->sg_context.ec_output_stack.pop_back(); +} diff --git a/src/command_executor.hh b/src/command_executor.hh index 42eeb4e3..3f2941fb 100644 --- a/src/command_executor.hh +++ b/src/command_executor.hh @@ -63,7 +63,7 @@ struct exec_context { this->ec_local_vars.push(std::map()); this->ec_path_stack.emplace_back("."); this->ec_source.emplace("command", 1); - this->ec_output_stack.emplace_back(nonstd::nullopt); + this->ec_output_stack.emplace_back("screen", nonstd::nullopt); } std::string get_error_prefix(); @@ -79,14 +79,18 @@ struct exec_context { for (auto iter = this->ec_output_stack.rbegin(); iter != this->ec_output_stack.rend(); ++iter) { - if (*iter) { - return *iter; + if (iter->second && *iter->second) { + return *iter->second; } } return nonstd::nullopt; } + void set_output(const std::string& name, FILE *file); + + void clear_output(); + struct source_guard { source_guard(exec_context &context) : sg_context(context) { @@ -99,6 +103,16 @@ struct exec_context { exec_context &sg_context; }; + struct output_guard { + explicit output_guard(exec_context &context, + std::string name = "default", + const nonstd::optional& file = nonstd::nullopt); + + ~output_guard(); + + exec_context &sg_context; + }; + source_guard enter_source(const std::string& path, int line_number) { this->ec_source.emplace(path, line_number); return source_guard(*this); @@ -120,7 +134,7 @@ struct exec_context { std::map ec_global_vars; std::vector ec_path_stack; std::stack> ec_source; - std::vector> ec_output_stack; + std::vector>> ec_output_stack; attr_line_t ec_accumulator; diff --git a/src/data_parser.cc b/src/data_parser.cc index 0dc43602..8bf25136 100644 --- a/src/data_parser.cc +++ b/src/data_parser.cc @@ -356,8 +356,7 @@ void data_parser::pairup(data_parser::schema_id_t *schema, struct element blank; blank.e_capture.c_begin = blank.e_capture.c_end = - free_row.front().e_capture. - c_begin; + free_row.front().e_capture.c_begin; blank.e_token = DNT_KEY; pair_subs.PUSH_BACK(blank); pair_subs.PUSH_BACK(free_row.front()); @@ -445,8 +444,8 @@ void data_parser::discover_format() elem.e_capture = *pc_iter; - require(elem.e_capture.c_begin != -1); - require(elem.e_capture.c_end != -1); + require(elem.e_capture.c_begin >= 0); + require(elem.e_capture.c_end >= 0); state_stack.top().update_for_element(elem); switch (elem.e_token) { diff --git a/src/data_parser.hh b/src/data_parser.hh index b9aa0a61..c4b783d9 100644 --- a/src/data_parser.hh +++ b/src/data_parser.hh @@ -257,6 +257,7 @@ public: { ELEMENT_TRACE; + require(elem.e_capture.c_end >= -1); this->std::list::push_front(elem); }; @@ -264,6 +265,7 @@ public: { ELEMENT_TRACE; + require(elem.e_capture.c_end >= -1); this->std::list::push_back(elem); }; diff --git a/src/data_scanner_re.cc b/src/data_scanner_re.cc index 9154d30b..88c74efa 100644 --- a/src/data_scanner_re.cc +++ b/src/data_scanner_re.cc @@ -1,5 +1,5 @@ -/* Generated by re2c 2.0.3 on Sat Feb 13 21:35:28 2021 */ -#line 1 "../../lnav/src/data_scanner_re.re" +/* Generated by re2c 2.0.3 on Thu Feb 25 11:01:38 2021 */ +#line 1 "../../lnav2/src/data_scanner_re.re" /** * Copyright (c) 2015, Timothy Stack * @@ -41,7 +41,11 @@ bool data_scanner::tokenize2(pcre_context &pc, data_token_t &token_out) { # define YYCTYPE unsigned char # define CAPTURE(tok) { \ - pi.pi_next_offset = YYCURSOR.val - (const unsigned char *) pi.get_string(); \ + if (YYCURSOR.val == EMPTY) { \ + pi.pi_next_offset = pi.pi_length; \ + } else { \ + pi.pi_next_offset = YYCURSOR.val - (const unsigned char *) pi.get_string(); \ + } \ cap[0].c_end = pi.pi_next_offset; \ cap[1].c_end = pi.pi_next_offset; \ token_out = tok; \ @@ -102,10 +106,12 @@ bool data_scanner::tokenize2(pcre_context &pc, data_token_t &token_out) pc.set_count(2); cap[0].c_begin = pi.pi_next_offset; + cap[0].c_end = pi.pi_next_offset; cap[1].c_begin = pi.pi_next_offset; + cap[1].c_end = pi.pi_next_offset; -#line 109 "../../lnav/src/data_scanner_re.cc" +#line 115 "../../lnav2/src/data_scanner_re.cc" { YYCTYPE yych; unsigned int yyaccept = 0; @@ -360,9 +366,9 @@ yy2: } yy3: ++YYCURSOR; -#line 132 "../../lnav/src/data_scanner_re.re" +#line 138 "../../lnav2/src/data_scanner_re.re" { return false; } -#line 366 "../../lnav/src/data_scanner_re.cc" +#line 372 "../../lnav2/src/data_scanner_re.cc" yy5: yyaccept = 0; yych = *(YYMARKER = ++YYCURSOR); @@ -520,11 +526,11 @@ yy6: default: goto yy7; } yy7: -#line 226 "../../lnav/src/data_scanner_re.re" +#line 232 "../../lnav2/src/data_scanner_re.re" { RET(DT_SYMBOL); } -#line 528 "../../lnav/src/data_scanner_re.cc" +#line 534 "../../lnav2/src/data_scanner_re.cc" yy8: yyaccept = 1; yych = *(YYMARKER = ++YYCURSOR); @@ -543,14 +549,14 @@ yy8: default: goto yy74; } yy9: -#line 231 "../../lnav/src/data_scanner_re.re" +#line 237 "../../lnav2/src/data_scanner_re.re" { RET(DT_WHITE); } -#line 549 "../../lnav/src/data_scanner_re.cc" +#line 555 "../../lnav2/src/data_scanner_re.cc" yy10: ++YYCURSOR; -#line 230 "../../lnav/src/data_scanner_re.re" +#line 236 "../../lnav2/src/data_scanner_re.re" { RET(DT_LINE); } -#line 554 "../../lnav/src/data_scanner_re.cc" +#line 560 "../../lnav2/src/data_scanner_re.cc" yy12: yyaccept = 1; yych = *(YYMARKER = ++YYCURSOR); @@ -572,9 +578,9 @@ yy12: yy13: ++YYCURSOR; yy14: -#line 233 "../../lnav/src/data_scanner_re.re" +#line 239 "../../lnav2/src/data_scanner_re.re" { RET(DT_GARBAGE); } -#line 578 "../../lnav/src/data_scanner_re.cc" +#line 584 "../../lnav2/src/data_scanner_re.cc" yy15: yyaccept = 2; yych = *(YYMARKER = ++YYCURSOR); @@ -1024,19 +1030,19 @@ yy18: default: goto yy19; } yy19: -#line 198 "../../lnav/src/data_scanner_re.re" +#line 204 "../../lnav2/src/data_scanner_re.re" { RET(DT_LPAREN); } -#line 1030 "../../lnav/src/data_scanner_re.cc" +#line 1036 "../../lnav2/src/data_scanner_re.cc" yy20: ++YYCURSOR; -#line 199 "../../lnav/src/data_scanner_re.re" +#line 205 "../../lnav2/src/data_scanner_re.re" { RET(DT_RPAREN); } -#line 1035 "../../lnav/src/data_scanner_re.cc" +#line 1041 "../../lnav2/src/data_scanner_re.cc" yy22: ++YYCURSOR; -#line 191 "../../lnav/src/data_scanner_re.re" +#line 197 "../../lnav2/src/data_scanner_re.re" { RET(DT_COMMA); } -#line 1040 "../../lnav/src/data_scanner_re.cc" +#line 1046 "../../lnav2/src/data_scanner_re.cc" yy24: yyaccept = 0; yych = *(YYMARKER = ++YYCURSOR); @@ -1351,9 +1357,9 @@ yy26: default: goto yy28; } yy28: -#line 162 "../../lnav/src/data_scanner_re.re" +#line 168 "../../lnav2/src/data_scanner_re.re" { RET(DT_PATH); } -#line 1357 "../../lnav/src/data_scanner_re.cc" +#line 1363 "../../lnav2/src/data_scanner_re.cc" yy29: yyaccept = 4; yych = *(YYMARKER = ++YYCURSOR); @@ -1513,9 +1519,9 @@ yy29: default: goto yy30; } yy30: -#line 217 "../../lnav/src/data_scanner_re.re" +#line 223 "../../lnav2/src/data_scanner_re.re" { RET(DT_NUMBER); } -#line 1519 "../../lnav/src/data_scanner_re.cc" +#line 1525 "../../lnav2/src/data_scanner_re.cc" yy31: yyaccept = 4; yych = *(YYMARKER = ++YYCURSOR); @@ -1998,14 +2004,14 @@ yy34: default: goto yy35; } yy35: -#line 189 "../../lnav/src/data_scanner_re.re" +#line 195 "../../lnav2/src/data_scanner_re.re" { RET(DT_COLON); } -#line 2004 "../../lnav/src/data_scanner_re.cc" +#line 2010 "../../lnav2/src/data_scanner_re.cc" yy36: ++YYCURSOR; -#line 192 "../../lnav/src/data_scanner_re.re" +#line 198 "../../lnav2/src/data_scanner_re.re" { RET(DT_SEMI); } -#line 2009 "../../lnav/src/data_scanner_re.cc" +#line 2015 "../../lnav2/src/data_scanner_re.cc" yy38: yyaccept = 6; yych = *(YYMARKER = ++YYCURSOR); @@ -2080,19 +2086,19 @@ yy38: default: goto yy39; } yy39: -#line 200 "../../lnav/src/data_scanner_re.re" +#line 206 "../../lnav2/src/data_scanner_re.re" { RET(DT_LANGLE); } -#line 2086 "../../lnav/src/data_scanner_re.cc" +#line 2092 "../../lnav2/src/data_scanner_re.cc" yy40: ++YYCURSOR; -#line 190 "../../lnav/src/data_scanner_re.re" +#line 196 "../../lnav2/src/data_scanner_re.re" { RET(DT_EQUALS); } -#line 2091 "../../lnav/src/data_scanner_re.cc" +#line 2097 "../../lnav2/src/data_scanner_re.cc" yy42: ++YYCURSOR; -#line 201 "../../lnav/src/data_scanner_re.re" +#line 207 "../../lnav2/src/data_scanner_re.re" { RET(DT_RANGLE); } -#line 2096 "../../lnav/src/data_scanner_re.cc" +#line 2102 "../../lnav2/src/data_scanner_re.cc" yy44: yyaccept = 0; yych = *(YYMARKER = ++YYCURSOR); @@ -2556,9 +2562,9 @@ yy50: default: goto yy51; } yy51: -#line 196 "../../lnav/src/data_scanner_re.re" +#line 202 "../../lnav2/src/data_scanner_re.re" { RET(DT_LSQUARE); } -#line 2562 "../../lnav/src/data_scanner_re.cc" +#line 2568 "../../lnav2/src/data_scanner_re.cc" yy52: yych = *++YYCURSOR; switch (yych) { @@ -2567,9 +2573,9 @@ yy52: } yy53: ++YYCURSOR; -#line 197 "../../lnav/src/data_scanner_re.re" +#line 203 "../../lnav2/src/data_scanner_re.re" { RET(DT_RSQUARE); } -#line 2573 "../../lnav/src/data_scanner_re.cc" +#line 2579 "../../lnav2/src/data_scanner_re.cc" yy55: yyaccept = 0; yych = *(YYMARKER = ++YYCURSOR); @@ -3116,14 +3122,14 @@ yy62: default: goto yy63; } yy63: -#line 194 "../../lnav/src/data_scanner_re.re" +#line 200 "../../lnav2/src/data_scanner_re.re" { RET(DT_LCURLY); } -#line 3122 "../../lnav/src/data_scanner_re.cc" +#line 3128 "../../lnav2/src/data_scanner_re.cc" yy64: ++YYCURSOR; -#line 195 "../../lnav/src/data_scanner_re.re" +#line 201 "../../lnav2/src/data_scanner_re.re" { RET(DT_RCURLY); } -#line 3127 "../../lnav/src/data_scanner_re.cc" +#line 3133 "../../lnav2/src/data_scanner_re.cc" yy66: yych = *++YYCURSOR; switch (yych) { @@ -3670,7 +3676,7 @@ yy79: default: goto yy80; } yy80: -#line 134 "../../lnav/src/data_scanner_re.re" +#line 140 "../../lnav2/src/data_scanner_re.re" { CAPTURE(DT_QUOTED_STRING); switch (pi.get_string()[cap[1].c_begin]) { @@ -3683,7 +3689,7 @@ yy80: cap[1].c_end -= 1; return true; } -#line 3687 "../../lnav/src/data_scanner_re.cc" +#line 3693 "../../lnav2/src/data_scanner_re.cc" yy81: yych = *++YYCURSOR; switch (yych) { @@ -5152,9 +5158,9 @@ yy102: } yy103: ++YYCURSOR; -#line 193 "../../lnav/src/data_scanner_re.re" +#line 199 "../../lnav2/src/data_scanner_re.re" { RET(DT_EMPTY_CONTAINER); } -#line 5158 "../../lnav/src/data_scanner_re.cc" +#line 5164 "../../lnav2/src/data_scanner_re.cc" yy105: yyaccept = 4; yych = *(YYMARKER = ++YYCURSOR); @@ -5780,9 +5786,9 @@ yy114: default: goto yy115; } yy115: -#line 216 "../../lnav/src/data_scanner_re.re" +#line 222 "../../lnav2/src/data_scanner_re.re" { RET(DT_PERCENTAGE); } -#line 5786 "../../lnav/src/data_scanner_re.cc" +#line 5792 "../../lnav2/src/data_scanner_re.cc" yy116: yyaccept = 0; yych = *(YYMARKER = ++YYCURSOR); @@ -6016,9 +6022,9 @@ yy117: default: goto yy118; } yy118: -#line 215 "../../lnav/src/data_scanner_re.re" +#line 221 "../../lnav2/src/data_scanner_re.re" { RET(DT_OCTAL_NUMBER); } -#line 6022 "../../lnav/src/data_scanner_re.cc" +#line 6028 "../../lnav2/src/data_scanner_re.cc" yy119: yyaccept = 4; yych = *(YYMARKER = ++YYCURSOR); @@ -6364,9 +6370,9 @@ yy121: default: goto yy122; } yy122: -#line 218 "../../lnav/src/data_scanner_re.re" +#line 224 "../../lnav2/src/data_scanner_re.re" { RET(DT_HEX_NUMBER); } -#line 6370 "../../lnav/src/data_scanner_re.cc" +#line 6376 "../../lnav2/src/data_scanner_re.cc" yy123: yyaccept = 10; yych = *(YYMARKER = ++YYCURSOR); @@ -7489,11 +7495,11 @@ yy133: default: goto yy134; } yy134: -#line 146 "../../lnav/src/data_scanner_re.re" +#line 152 "../../lnav2/src/data_scanner_re.re" { CAPTURE(DT_WORD); } -#line 7497 "../../lnav/src/data_scanner_re.cc" +#line 7503 "../../lnav2/src/data_scanner_re.cc" yy135: yyaccept = 0; yych = *(YYMARKER = ++YYCURSOR); @@ -9296,7 +9302,7 @@ yy160: yyt1 = yyt2; yy161: YYCURSOR = yyt1; -#line 149 "../../lnav/src/data_scanner_re.re" +#line 155 "../../lnav2/src/data_scanner_re.re" { CAPTURE(DT_QUOTED_STRING); switch (pi.get_string()[cap[1].c_begin]) { @@ -9309,7 +9315,7 @@ yy161: cap[1].c_end -= 1; return true; } -#line 9313 "../../lnav/src/data_scanner_re.cc" +#line 9319 "../../lnav2/src/data_scanner_re.cc" yy162: yyaccept = 12; yych = *(YYMARKER = ++YYCURSOR); @@ -12758,9 +12764,9 @@ yy200: ++YYCURSOR; yy201: YYCURSOR = yyt2; -#line 175 "../../lnav/src/data_scanner_re.re" +#line 181 "../../lnav2/src/data_scanner_re.re" { RET(DT_IPV6_ADDRESS); } -#line 12764 "../../lnav/src/data_scanner_re.cc" +#line 12770 "../../lnav2/src/data_scanner_re.cc" yy202: yych = *++YYCURSOR; switch (yych) { @@ -14038,11 +14044,11 @@ yy217: yy218: ++YYCURSOR; yy219: -#line 181 "../../lnav/src/data_scanner_re.re" +#line 187 "../../lnav2/src/data_scanner_re.re" { RET(DT_XML_OPEN_TAG); } -#line 14046 "../../lnav/src/data_scanner_re.cc" +#line 14052 "../../lnav2/src/data_scanner_re.cc" yy220: yych = *++YYCURSOR; switch (yych) { @@ -14191,9 +14197,9 @@ yy223: yyt3 = yyt4; yy224: YYCURSOR = yyt3; -#line 224 "../../lnav/src/data_scanner_re.re" +#line 230 "../../lnav2/src/data_scanner_re.re" { RET(DT_WORD); } -#line 14197 "../../lnav/src/data_scanner_re.cc" +#line 14203 "../../lnav2/src/data_scanner_re.cc" yy225: yych = *++YYCURSOR; switch (yych) { @@ -21698,11 +21704,11 @@ yy310: yy311: ++YYCURSOR; yy312: -#line 177 "../../lnav/src/data_scanner_re.re" +#line 183 "../../lnav2/src/data_scanner_re.re" { RET(DT_XML_EMPTY_TAG); } -#line 21706 "../../lnav/src/data_scanner_re.cc" +#line 21712 "../../lnav2/src/data_scanner_re.cc" yy313: yych = *++YYCURSOR; switch (yych) { @@ -21714,11 +21720,11 @@ yy313: } yy315: ++YYCURSOR; -#line 185 "../../lnav/src/data_scanner_re.re" +#line 191 "../../lnav2/src/data_scanner_re.re" { RET(DT_XML_CLOSE_TAG); } -#line 21722 "../../lnav/src/data_scanner_re.cc" +#line 21728 "../../lnav2/src/data_scanner_re.cc" yy317: yych = *++YYCURSOR; yy318: @@ -22883,9 +22889,9 @@ yy332: default: goto yy334; } yy334: -#line 220 "../../lnav/src/data_scanner_re.re" +#line 226 "../../lnav2/src/data_scanner_re.re" { RET(DT_EMAIL); } -#line 22889 "../../lnav/src/data_scanner_re.cc" +#line 22895 "../../lnav2/src/data_scanner_re.cc" yy335: yyaccept = 0; yych = *(YYMARKER = ++YYCURSOR); @@ -23273,11 +23279,11 @@ yy338: default: goto yy340; } yy340: -#line 211 "../../lnav/src/data_scanner_re.re" +#line 217 "../../lnav2/src/data_scanner_re.re" { RET(DT_VERSION_NUMBER); } -#line 23281 "../../lnav/src/data_scanner_re.cc" +#line 23287 "../../lnav2/src/data_scanner_re.cc" yy341: yyaccept = 18; yych = *(YYMARKER = ++YYCURSOR); @@ -25711,7 +25717,7 @@ yy362: default: goto yy363; } yy363: -#line 165 "../../lnav/src/data_scanner_re.re" +#line 171 "../../lnav2/src/data_scanner_re.re" { if ((YYCURSOR - (const unsigned char *) pi.get_string()) == 17) { RET(DT_MAC_ADDRESS); @@ -25719,7 +25725,7 @@ yy363: RET(DT_HEX_DUMP); } } -#line 25723 "../../lnav/src/data_scanner_re.cc" +#line 25729 "../../lnav2/src/data_scanner_re.cc" yy364: yyaccept = 19; yych = *(YYMARKER = ++YYCURSOR); @@ -29755,9 +29761,9 @@ yy414: ++YYCURSOR; yy415: YYCURSOR = yyt1; -#line 222 "../../lnav/src/data_scanner_re.re" +#line 228 "../../lnav2/src/data_scanner_re.re" { RET(DT_CONSTANT); } -#line 29761 "../../lnav/src/data_scanner_re.cc" +#line 29767 "../../lnav2/src/data_scanner_re.cc" yy416: yych = *++YYCURSOR; switch (yych) { @@ -30086,9 +30092,9 @@ yy422: ++YYCURSOR; yy423: YYCURSOR = yyt1; -#line 163 "../../lnav/src/data_scanner_re.re" +#line 169 "../../lnav2/src/data_scanner_re.re" { RET(DT_TIME); } -#line 30092 "../../lnav/src/data_scanner_re.cc" +#line 30098 "../../lnav2/src/data_scanner_re.cc" yy424: yych = *++YYCURSOR; switch (yych) { @@ -31042,9 +31048,9 @@ yy436: default: goto yy438; } yy438: -#line 209 "../../lnav/src/data_scanner_re.re" +#line 215 "../../lnav2/src/data_scanner_re.re" { RET(DT_NUMBER); } -#line 31048 "../../lnav/src/data_scanner_re.cc" +#line 31054 "../../lnav2/src/data_scanner_re.cc" yy439: yyaccept = 21; yych = *(YYMARKER = ++YYCURSOR); @@ -32454,9 +32460,9 @@ yy453: default: goto yy455; } yy455: -#line 161 "../../lnav/src/data_scanner_re.re" +#line 167 "../../lnav2/src/data_scanner_re.re" { RET(DT_URL); } -#line 32460 "../../lnav/src/data_scanner_re.cc" +#line 32466 "../../lnav2/src/data_scanner_re.cc" yy456: yych = *++YYCURSOR; switch (yych) { @@ -46940,9 +46946,9 @@ yy625: yyt1 = yyt2; yy626: YYCURSOR = yyt1; -#line 164 "../../lnav/src/data_scanner_re.re" +#line 170 "../../lnav2/src/data_scanner_re.re" { RET(DT_TIME); } -#line 46946 "../../lnav/src/data_scanner_re.cc" +#line 46952 "../../lnav2/src/data_scanner_re.cc" yy627: yyaccept = 26; yych = *(YYMARKER = ++YYCURSOR); @@ -47282,11 +47288,11 @@ yy634: ++YYCURSOR; yy635: YYCURSOR = yyt1; -#line 203 "../../lnav/src/data_scanner_re.re" +#line 209 "../../lnav2/src/data_scanner_re.re" { RET(DT_IPV4_ADDRESS); } -#line 47290 "../../lnav/src/data_scanner_re.cc" +#line 47296 "../../lnav2/src/data_scanner_re.cc" yy636: yyaccept = 27; yych = *(YYMARKER = ++YYCURSOR); @@ -49067,11 +49073,11 @@ yy652: default: goto yy653; } yy653: -#line 172 "../../lnav/src/data_scanner_re.re" +#line 178 "../../lnav2/src/data_scanner_re.re" { RET(DT_DATE); } -#line 49075 "../../lnav/src/data_scanner_re.cc" +#line 49081 "../../lnav2/src/data_scanner_re.cc" yy654: yyaccept = 28; yych = *(YYMARKER = ++YYCURSOR); @@ -82935,10 +82941,10 @@ yy990: default: goto yy991; } yy991: -#line 207 "../../lnav/src/data_scanner_re.re" +#line 213 "../../lnav2/src/data_scanner_re.re" { RET(DT_UUID); } -#line 82941 "../../lnav/src/data_scanner_re.cc" +#line 82947 "../../lnav2/src/data_scanner_re.cc" } -#line 235 "../../lnav/src/data_scanner_re.re" +#line 241 "../../lnav2/src/data_scanner_re.re" } diff --git a/src/data_scanner_re.re b/src/data_scanner_re.re index 3b3e5e51..ee4ceb40 100644 --- a/src/data_scanner_re.re +++ b/src/data_scanner_re.re @@ -39,7 +39,11 @@ bool data_scanner::tokenize2(pcre_context &pc, data_token_t &token_out) { # define YYCTYPE unsigned char # define CAPTURE(tok) { \ - pi.pi_next_offset = YYCURSOR.val - (const unsigned char *) pi.get_string(); \ + if (YYCURSOR.val == EMPTY) { \ + pi.pi_next_offset = pi.pi_length; \ + } else { \ + pi.pi_next_offset = YYCURSOR.val - (const unsigned char *) pi.get_string(); \ + } \ cap[0].c_end = pi.pi_next_offset; \ cap[1].c_end = pi.pi_next_offset; \ token_out = tok; \ @@ -100,7 +104,9 @@ bool data_scanner::tokenize2(pcre_context &pc, data_token_t &token_out) pc.set_count(2); cap[0].c_begin = pi.pi_next_offset; + cap[0].c_end = pi.pi_next_offset; cap[1].c_begin = pi.pi_next_offset; + cap[1].c_end = pi.pi_next_offset; /*!re2c re2c:yyfill:enable = 0; diff --git a/src/db_sub_source.cc b/src/db_sub_source.cc index f17833a7..b0ab0f61 100644 --- a/src/db_sub_source.cc +++ b/src/db_sub_source.cc @@ -37,6 +37,8 @@ const char *db_label_source::NULL_STR = ""; +constexpr size_t MAX_COLUMN_WIDTH = 120; + void db_label_source::text_value_for_line(textview_curses &tc, int row, std::string &label_out, text_sub_source::line_flags_t flags) @@ -51,14 +53,20 @@ void db_label_source::text_value_for_line(textview_curses &tc, int row, return; } for (int lpc = 0; lpc < (int)this->dls_rows[row].size(); lpc++) { - int padding = (this->dls_headers[lpc].hm_column_size - - strlen(this->dls_rows[row][lpc]) - - 1); + auto actual_col_size = std::min(MAX_COLUMN_WIDTH, + this->dls_headers[lpc].hm_column_size); + auto cell_str = std::string(this->dls_rows[row][lpc]); + + truncate_to(cell_str, MAX_COLUMN_WIDTH); + auto cell_length = utf8_string_length(cell_str) + .unwrapOr(MAX_COLUMN_WIDTH); + auto padding = actual_col_size - cell_length; + this->dls_cell_width[lpc] = cell_str.length() + padding; if (this->dls_headers[lpc].hm_column_type != SQLITE3_TEXT) { label_out.append(padding, ' '); } - label_out.append(this->dls_rows[row][lpc]); + label_out.append(cell_str); if (this->dls_headers[lpc].hm_column_type == SQLITE3_TEXT) { label_out.append(padding, ' '); } @@ -79,7 +87,7 @@ void db_label_source::text_attrs_for_line(textview_curses &tc, int row, if (row % 2 == 0) { sa.emplace_back(lr2, &view_curses::VC_STYLE, A_BOLD); } - lr.lr_start += this->dls_headers[lpc].hm_column_size - 1; + lr.lr_start += this->dls_cell_width[lpc]; lr.lr_end = lr.lr_start + 1; sa.emplace_back(lr, &view_curses::VC_GRAPHIC, ACS_VLINE); lr.lr_start += 1; @@ -122,10 +130,11 @@ void db_label_source::push_header(const std::string &colstr, int type, bool graphable) { this->dls_headers.emplace_back(colstr); + this->dls_cell_width.push_back(0); header_meta &hm = this->dls_headers.back(); - hm.hm_column_size = colstr.length() + 1; + hm.hm_column_size = utf8_string_length(colstr).unwrapOr(colstr.length()); hm.hm_column_type = type; hm.hm_graphable = graphable; if (colstr == "log_time") { @@ -140,12 +149,6 @@ void db_label_source::push_column(const char *colstr) double num_value = 0.0; size_t value_len; - if (colstr == nullptr) { - value_len = 0; - } - else { - value_len = strlen(colstr); - } if (colstr == nullptr) { colstr = NULL_STR; } @@ -155,6 +158,7 @@ void db_label_source::push_column(const char *colstr) throw "out of memory"; } } + value_len = strlen(colstr); if (index == this->dls_time_column_index) { date_time_scanner dts; @@ -175,7 +179,8 @@ void db_label_source::push_column(const char *colstr) this->dls_rows.back().push_back(colstr); this->dls_headers[index].hm_column_size = - std::max(this->dls_headers[index].hm_column_size, strlen(colstr) + 1); + std::max(this->dls_headers[index].hm_column_size, + utf8_string_length(colstr, value_len).unwrapOr(value_len)); if (colstr != nullptr && this->dls_headers[index].hm_graphable) { if (sscanf(colstr, "%lf", &num_value) != 1) { @@ -215,6 +220,7 @@ void db_label_source::clear() } this->dls_rows.clear(); this->dls_time_column.clear(); + this->dls_cell_width.clear(); } long db_label_source::column_name_to_index(const std::string &name) const @@ -365,13 +371,25 @@ bool db_overlay_source::list_value_for_overlay(const listview_curses &lv, int y, for (size_t lpc = 0; lpc < this->dos_labels->dls_headers.size(); lpc++) { - int before, total_fill = - dls->dls_headers[lpc].hm_column_size - - dls->dls_headers[lpc].hm_name.length(); + auto actual_col_size = std::min( + MAX_COLUMN_WIDTH, dls->dls_headers[lpc].hm_column_size); + std::string cell_title = dls->dls_headers[lpc].hm_name; + + truncate_to(cell_title, MAX_COLUMN_WIDTH); + + auto cell_length = utf8_string_length(cell_title) + .unwrapOr(actual_col_size); + int before, total_fill = actual_col_size - cell_length; + auto line_len_before = line.length(); + + before = total_fill / 2; + total_fill -= before; + line.append(before, ' '); + line.append(cell_title); + line.append(total_fill, ' '); + line.append(1, ' '); - struct line_range header_range(line.length(), - line.length() + - dls->dls_headers[lpc].hm_column_size); + struct line_range header_range(line_len_before, line.length()); int attrs = vc.attrs_for_ident(dls->dls_headers[lpc].hm_name) | A_REVERSE; @@ -379,12 +397,6 @@ bool db_overlay_source::list_value_for_overlay(const listview_curses &lv, int y, attrs = A_UNDERLINE; } sa.emplace_back(header_range, &view_curses::VC_STYLE, attrs); - - before = total_fill / 2; - total_fill -= before; - line.append(before, ' '); - line.append(dls->dls_headers[lpc].hm_name); - line.append(total_fill, ' '); } struct line_range lr(0); diff --git a/src/db_sub_source.hh b/src/db_sub_source.hh index 196e5786..3351aba1 100644 --- a/src/db_sub_source.hh +++ b/src/db_sub_source.hh @@ -61,7 +61,7 @@ public: size_t retval = 0; for (auto &dls_header : this->dls_headers) { - retval += dls_header.hm_column_size; + retval += dls_header.hm_column_size + 1; } return retval; }; @@ -75,8 +75,6 @@ public: void push_header(const std::string &colstr, int type, bool graphable); - /* TODO: add support for left and right justification... numbers should */ - /* be right justified and strings should be left. */ void push_column(const char *colstr); void clear(); @@ -117,8 +115,9 @@ public: stacked_bar_chart dls_chart; std::vector dls_headers; - std::vector > dls_rows; + std::vector> dls_rows; std::vector dls_time_column; + std::vector dls_cell_width; int dls_time_column_index{-1}; static const char *NULL_STR; diff --git a/src/extension-functions.cc b/src/extension-functions.cc index 8b26f062..3386fc16 100644 --- a/src/extension-functions.cc +++ b/src/extension-functions.cc @@ -669,8 +669,8 @@ static void floorFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ ** string that constains s contatenated n times */ static void replicateFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ - unsigned char *z; /* input string */ - unsigned char *zo; /* result string */ + static const char *EMPTY = ""; + unsigned char *z; /* result string */ i64 iCount; /* times to repeat */ i64 nLen; /* length of the input string (no multibyte considerations) */ i64 nTLen; /* length of the result string (no multibyte considerations) */ @@ -683,28 +683,29 @@ static void replicateFunc(sqlite3_context *context, int argc, sqlite3_value **ar if( iCount<0 ){ sqlite3_result_error(context, "domain error", -1); - }else{ + return; + } + + if (iCount == 0) { + sqlite3_result_text(context, EMPTY, 0, SQLITE_STATIC); + return; + } nLen = sqlite3_value_bytes(argv[0]); nTLen = nLen*iCount; z= (unsigned char *) sqlite3_malloc(nTLen + 1); - zo= (unsigned char *) sqlite3_malloc(nLen + 1); - if (!z || !zo){ + if (!z){ sqlite3_result_error_nomem(context); if (z) sqlite3_free(z); - if (zo) sqlite3_free(zo); return; } - strcpy((char*)zo, (char*)sqlite3_value_text(argv[0])); + auto zo = sqlite3_value_text(argv[0]); for(i=0; i ht_enum_values; std::vector ht_tags; std::vector ht_opposites; + help_function_type_t ht_function_type{help_function_type_t::HFT_REGULAR}; + void *ht_impl{nullptr}; help_text() = default; @@ -104,6 +111,12 @@ struct help_text { return *this; }; + help_text &sql_agg_function() noexcept { + this->ht_context = help_context_t::HC_SQL_FUNCTION; + this->ht_function_type = help_function_type_t::HFT_AGGREGATE; + return *this; + }; + help_text &sql_table_valued_function() noexcept { this->ht_context = help_context_t::HC_SQL_TABLE_VALUED_FUNCTION; return *this; @@ -171,6 +184,12 @@ struct help_text { help_text &with_opposites(const std::initializer_list &opps) noexcept; + template + help_text &with_impl(F impl) { + this->ht_impl = (void *) impl; + return *this; + } + void index_tags(); static std::multimap TAGGED; diff --git a/src/help_text_formatter.cc b/src/help_text_formatter.cc index 312d16ef..aa3c8ba2 100644 --- a/src/help_text_formatter.cc +++ b/src/help_text_formatter.cc @@ -466,6 +466,9 @@ static std::string link_name(const help_text &ht) } else { scrubbed_name = ht.ht_name; } + if (ht.ht_function_type == help_function_type_t::HFT_AGGREGATE) { + scrubbed_name += "_agg"; + } for (auto ¶m : ht.ht_parameters) { if (!is_sql_infix && param.ht_name[0]) { continue; diff --git a/src/internals/cmd-ref.rst b/src/internals/cmd-ref.rst index a8a8e32a..27b5e1cd 100644 --- a/src/internals/cmd-ref.rst +++ b/src/internals/cmd-ref.rst @@ -44,7 +44,7 @@ :alt-msg Press t to switch to the text view **See Also** - :ref:`echo`, :ref:`eval`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_to` + :ref:`echo`, :ref:`eval`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_to` ---- @@ -67,7 +67,7 @@ :append-to /tmp/interesting-lines.txt **See Also** - :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_to` + :ref:`echo`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_to` ---- @@ -214,7 +214,7 @@ :create-logline-table task_durations **See Also** - :ref:`create_search_table`, :ref:`create_search_table`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to` + :ref:`create_search_table`, :ref:`create_search_table`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_table_to` ---- @@ -238,7 +238,7 @@ :create-search-table task_durations duration=(?\d+) **See Also** - :ref:`create_logline_table`, :ref:`create_logline_table`, :ref:`delete_search_table`, :ref:`delete_search_table`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to` + :ref:`create_logline_table`, :ref:`create_logline_table`, :ref:`delete_search_table`, :ref:`delete_search_table`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_table_to` ---- @@ -295,7 +295,7 @@ :delete-logline-table task_durations **See Also** - :ref:`create_logline_table`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`create_search_table`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to` + :ref:`create_logline_table`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`create_search_table`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_table_to` ---- @@ -318,7 +318,7 @@ :delete-search-table task_durations **See Also** - :ref:`create_logline_table`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`create_search_table`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to` + :ref:`create_logline_table`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`create_search_table`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_table_to` ---- @@ -387,7 +387,7 @@ :echo *msg* ^^^^^^^^^^^ - Echo the given message + Echo the given message to the screen or, if :redirect-to has been called, to output file specified in the redirect. Variable substitution is performed on the message. Use a backslash to escape any special characters, like '$' **Parameters** * **msg\*** --- The message to display @@ -400,7 +400,7 @@ :echo Hello, World! **See Also** - :ref:`alt_msg`, :ref:`eval`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_to` + :ref:`alt_msg`, :ref:`append_to`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_to`, :ref:`write_to` ---- @@ -452,12 +452,6 @@ * **command\*** --- The command or query to perform substitution on. **Examples** - To output the user's home directory: - - .. code-block:: lnav - - :eval :echo $HOME - To substitute the table name from a variable: .. code-block:: lnav @@ -465,7 +459,7 @@ :eval ;SELECT * FROM ${table} **See Also** - :ref:`alt_msg`, :ref:`echo`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_to` + :ref:`alt_msg`, :ref:`echo`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_to` ---- @@ -848,7 +842,7 @@ :pipe-line-to sed -e 's/foo/bar/g' **See Also** - :ref:`append_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_to` + :ref:`append_to`, :ref:`echo`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_to` ---- @@ -871,7 +865,7 @@ :pipe-to sed -e s/foo/bar/g **See Also** - :ref:`append_to`, :ref:`pipe_line_to`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_to` + :ref:`append_to`, :ref:`echo`, :ref:`pipe_line_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_to` ---- @@ -980,7 +974,7 @@ :redirect-to *\[path\]* ^^^^^^^^^^^^^^^^^^^^^^^ - Redirect the output of commands to the given file + Redirect the output of commands that write to stdout to the given file **Parameters** * **path** --- The path to the file to write. If not specified, the current redirect will be cleared @@ -993,7 +987,7 @@ :redirect-to /tmp/script-output.txt **See Also** - :ref:`alt_msg`, :ref:`append_to`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_to`, :ref:`write_to` + :ref:`alt_msg`, :ref:`append_to`, :ref:`echo`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_to`, :ref:`write_to` ---- @@ -1352,12 +1346,12 @@ ---- -.. _write_cols_to: +.. _write_table_to: -:write-cols-to *path* -^^^^^^^^^^^^^^^^^^^^^ +:write-table-to *path* +^^^^^^^^^^^^^^^^^^^^^^ - Write SQL results to the given file in a columnar format + Write SQL results to the given file in a tabular format **Parameters** * **path\*** --- The path to the file to write @@ -1367,10 +1361,10 @@ .. code-block:: lnav - :write-cols-to /tmp/table.txt + :write-table-to /tmp/table.txt **See Also** - :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_to`, :ref:`write_to` + :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_to`, :ref:`write_to` ---- @@ -1393,7 +1387,7 @@ :write-csv-to /tmp/table.csv **See Also** - :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_to`, :ref:`write_to` + :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_to`, :ref:`write_to` ---- @@ -1416,7 +1410,7 @@ :write-json-to /tmp/table.json **See Also** - :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_to`, :ref:`write_to` + :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_to`, :ref:`write_to` ---- @@ -1439,7 +1433,7 @@ :write-jsonlines-to /tmp/table.json **See Also** - :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_to`, :ref:`write_to` + :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_to`, :ref:`write_to` ---- @@ -1462,7 +1456,7 @@ :write-raw-to /tmp/table.txt **See Also** - :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_to`, :ref:`write_to` + :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_to`, :ref:`write_to` ---- @@ -1485,7 +1479,7 @@ :write-screen-to /tmp/table.txt **See Also** - :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_to`, :ref:`write_to` + :ref:`alt_msg`, :ref:`append_to`, :ref:`create_logline_table`, :ref:`create_search_table`, :ref:`echo`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_table_to`, :ref:`write_to`, :ref:`write_to` ---- @@ -1508,7 +1502,7 @@ :write-to /tmp/interesting-lines.txt **See Also** - :ref:`alt_msg`, :ref:`append_to`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_cols_to`, :ref:`write_cols_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to` + :ref:`alt_msg`, :ref:`append_to`, :ref:`echo`, :ref:`echo`, :ref:`eval`, :ref:`pipe_line_to`, :ref:`pipe_to`, :ref:`redirect_to`, :ref:`redirect_to`, :ref:`write_csv_to`, :ref:`write_csv_to`, :ref:`write_json_to`, :ref:`write_json_to`, :ref:`write_jsonlines_to`, :ref:`write_jsonlines_to`, :ref:`write_raw_to`, :ref:`write_raw_to`, :ref:`write_screen_to`, :ref:`write_screen_to`, :ref:`write_table_to`, :ref:`write_table_to` ---- diff --git a/src/internals/sql-ref.rst b/src/internals/sql-ref.rst index 8a910f90..d3b946dc 100644 --- a/src/internals/sql-ref.rst +++ b/src/internals/sql-ref.rst @@ -771,7 +771,7 @@ char(*X*) HI **See Also** - :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -804,7 +804,7 @@ charindex(*needle*, *haystack*, *\[start\]*) 0 **See Also** - :ref:`char`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -1068,7 +1068,7 @@ endswith(*str*, *suffix*) 0 **See Also** - :ref:`char`, :ref:`charindex`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -1123,7 +1123,7 @@ extract(*str*) {"col_0":1.0,"col_1":2.0} **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -1273,12 +1273,12 @@ group_concat(*X*, *\[sep\]*) hw,gw **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- -.. _group_spooky_hash: +.. _group_spooky_hash_agg: group_spooky_hash(*str*) ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1297,7 +1297,7 @@ group_spooky_hash(*str*) 4e7a190aead058cb123c94290f29c34a **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -1324,6 +1324,30 @@ hex(*X*) ---- +.. _humanize_file_size: + +humanize_file_size(*value*) +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Format the given file size as a human-friendly string + + **Parameters** + * **value\*** --- The file size to format + + **Examples** + To format an amount: + + .. code-block:: custsqlite + + ;SELECT humanize_file_size(10 * 1024 * 1024) + 10.0MB + + **See Also** + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + +---- + + .. _ifnull: ifnull(*X*, *Y*) @@ -1367,7 +1391,7 @@ instr(*haystack*, *needle*) 2 **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -1720,7 +1744,7 @@ leftstr(*str*, *N*) abc **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -1744,7 +1768,7 @@ length(*str*) 3 **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -1912,7 +1936,7 @@ lower(*str*) abc **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -1944,7 +1968,7 @@ ltrim(*str*, *\[chars\]*) c **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2101,7 +2125,7 @@ padc(*str*, *len*) abcdef ghi **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2133,7 +2157,7 @@ padl(*str*, *len*) abcdef **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2165,7 +2189,7 @@ padr(*str*, *len*) abcdefghi **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2263,7 +2287,7 @@ printf(*format*, *X*) value: 00011 **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2287,7 +2311,7 @@ proper(*str*) Hello, World! **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2470,7 +2494,7 @@ regexp_capture(*string*, *pattern*) 1 2 3 8 9 2 **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2509,7 +2533,7 @@ regexp_match(*re*, *str*) {"num":123,"str":"four"} **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_replace`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_replace`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2542,7 +2566,7 @@ regexp_replace(*str*, *re*, *repl*) <123> **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_match`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_match`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2575,7 +2599,7 @@ replace(*str*, *old*, *replacement*) zbc **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2600,7 +2624,7 @@ replicate(*str*, *N*) abcabcabc **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2624,7 +2648,7 @@ reverse(*str*) cba **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2656,7 +2680,7 @@ rightstr(*str*, *N*) abc **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2713,7 +2737,7 @@ row_number() .. code-block:: custsqlite ;SELECT row_number() OVER (PARTITION BY ex_procname ORDER BY log_line) AS msg_num, ex_procname, log_body FROM lnav_example_log - msg_num ex_procname log_body + msg_num ex_procname log_body 1 gw Goodbye, World! 2 gw Goodbye, World! 3 gw Goodbye, World! @@ -2752,7 +2776,7 @@ rtrim(*str*, *\[chars\]*) a **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2795,6 +2819,56 @@ sign(*num*) ---- +.. _sparkline: + +sparkline(*value*, *\[upper\]*) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Converts a numeric value on a range to a bar chart character + + **Parameters** + * **value\*** --- The numeric value to convert + * **upper** --- The upper bound of the numeric range (default: 100) + + **Examples** + To get the unicode block element for the value 32 in the range of 0-128: + + .. code-block:: custsqlite + + ;SELECT sparkline(32, 128) + ▂ + + **See Also** + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + +---- + + +.. _sparkline_agg: + +sparkline(*value*, *\[upper\]*) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + An aggregate function to convert numeric values to a sparkline bar chart + + **Parameters** + * **value\*** --- The numeric values to chart + * **upper** --- The upper bound of the numeric range. If not provided, the default is derived from all of the provided values + + **Examples** + To chart the values in a JSON array: + + .. code-block:: custsqlite + + ;SELECT sparkline(value) FROM json_each('[0, 1, 2, 3, 4, 5, 6, 7, 8]') + ▁▂▃▄▅▆▇█ + + **See Also** + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :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:`upper`, :ref:`xpath` + +---- + + .. _spooky_hash: spooky_hash(*str*) @@ -2835,7 +2909,7 @@ spooky_hash(*str*) f96b3d9c1a19f4394c97a1b79b1880df **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2949,7 +3023,7 @@ startswith(*str*, *prefix*) 0 **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -2974,7 +3048,7 @@ strfilter(*source*, *include*) bcbc **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -3061,7 +3135,7 @@ substr(*str*, *start*, *\[size\]*) b **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`trim`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -3184,11 +3258,8 @@ timeslice(*time*, *slice*) .. code-block:: custsqlite - ;SELECT timeslice(log_time, '5m') AS slice, count(*) FROM lnav_example_log GROUP BY slice - slice count(*) - 2017-02-03 04:05:00.000 2 - 2017-02-03 04:25:00.000 1 - 2017-02-03 04:55:00.000 1 + ;SELECT timeslice(log_time_msecs, '5m') AS slice, count(*) FROM lnav_example_log GROUP BY slice + 2017-01-01 05:00:00.000 **See Also** :ref:`date`, :ref:`datetime`, :ref:`julianday`, :ref:`strftime`, :ref:`time`, :ref:`timediff` @@ -3258,7 +3329,7 @@ trim(*str*, *\[chars\]*) abc **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`unicode`, :ref:`upper`, :ref:`xpath` ---- @@ -3311,7 +3382,7 @@ unicode(*X*) 97 **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`upper`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`upper`, :ref:`xpath` ---- @@ -3349,7 +3420,7 @@ upper(*str*) ABC **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`xpath` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`xpath` ---- @@ -3371,7 +3442,7 @@ xpath(*xpath*, *xmldoc*) .. code-block:: custsqlite ;SELECT * FROM xpath('/abc/def', 'HelloBye') - result node_path node_attr node_text + result node_path node_attr node_text Hello /abc/def[1] {"a":"b"} Hello Bye /abc/def[2] {} Bye @@ -3380,7 +3451,7 @@ xpath(*xpath*, *xmldoc*) .. code-block:: custsqlite ;SELECT * FROM xpath('/abc/def/@a', 'HelloBye') - result node_path node_attr node_text + result node_path node_attr node_text b /abc/def[1]/@a {"a":"b"} Hello To select the text nodes on the path '/abc/def': @@ -3388,11 +3459,11 @@ xpath(*xpath*, *xmldoc*) .. code-block:: custsqlite ;SELECT * FROM xpath('/abc/def/text()', 'Hello ★') - result node_path node_attr node_text - Hello ★ /abc/def/text() {} Hello ★ + result node_path node_attr node_text + Hello ★ /abc/def/text() {} Hello ★ **See Also** - :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper` + :ref:`char`, :ref:`charindex`, :ref:`endswith`, :ref:`extract`, :ref:`group_concat`, :ref:`group_spooky_hash_agg`, :ref:`humanize_file_size`, :ref:`instr`, :ref:`leftstr`, :ref:`length`, :ref:`lower`, :ref:`ltrim`, :ref:`padc`, :ref:`padl`, :ref:`padr`, :ref:`printf`, :ref:`proper`, :ref:`regexp_capture`, :ref:`regexp_match`, :ref:`regexp_replace`, :ref:`replace`, :ref:`replicate`, :ref:`reverse`, :ref:`rightstr`, :ref:`rtrim`, :ref:`sparkline_agg`, :ref:`sparkline`, :ref:`spooky_hash`, :ref:`startswith`, :ref:`strfilter`, :ref:`substr`, :ref:`trim`, :ref:`unicode`, :ref:`upper` ---- diff --git a/src/lnav.cc b/src/lnav.cc index c5860e94..f17213c6 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -256,6 +256,7 @@ bool setup_logline_table(exec_context &ec) { // Hidden columns don't show up in the table_info pragma. static const char *hidden_table_columns[] = { + "log_time_msecs", "log_path", "log_text", "log_body", @@ -2617,7 +2618,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%' return EXIT_FAILURE; } init_session(); - lnav_data.ld_exec_context.ec_output_stack.back() = stdout; + lnav_data.ld_exec_context.set_output("stdout", stdout); alerter::singleton().enabled(false); log_tc = &lnav_data.ld_views[LNV_LOG]; diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index 78f8396f..961da13f 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -666,12 +666,8 @@ static Result com_save_to(exec_context &ec, string cmdline, vect vector split_args; shlex lexer(fn); - scoped_resolver scopes = { - &ec.ec_local_vars.top(), - &ec.ec_global_vars, - }; - if (!lexer.split(split_args, scopes)) { + if (!lexer.split(split_args, ec.create_resolver())) { return ec.make_error("unable to parse arguments"); } if (split_args.size() > 1) { @@ -685,16 +681,15 @@ static Result com_save_to(exec_context &ec, string cmdline, vect mode = "w"; } - textview_curses * tc = *lnav_data.ld_view_stack.top(); - bookmark_vector &bv = - tc->get_bookmarks()[&textview_curses::BM_USER]; - db_label_source &dls = lnav_data.ld_db_row_source; - db_overlay_source &dos = lnav_data.ld_db_overlay; + auto *tc = *lnav_data.ld_view_stack.top(); + auto &bv = tc->get_bookmarks()[&textview_curses::BM_USER]; + auto &dls = lnav_data.ld_db_row_source; if (args[0] == "write-csv-to" || args[0] == "write-json-to" || args[0] == "write-jsonlines-to" || - args[0] == "write-cols-to") { + args[0] == "write-cols-to" || + args[0] == "write-table-to") { if (dls.dls_headers.empty()) { return ec.make_error("no query result to write, use ';' to execute a query"); } @@ -791,26 +786,84 @@ static Result com_save_to(exec_context &ec, string cmdline, vect line_count += 1; } } - else if (args[0] == "write-cols-to") { - attr_line_t header_line; + else if (args[0] == "write-cols-to" || args[0] == "write-table-to") { + bool first = true; + + fprintf(outfile, "\u250f"); + for (const auto& hdr : dls.dls_headers) { + auto cell_line = repeat("\u2501", hdr.hm_column_size); - dos.list_value_for_overlay(lnav_data.ld_views[LNV_DB], 0, 1, 0_vl, header_line); - fputs(header_line.get_string().c_str(), outfile); - fputc('\n', outfile); - for (size_t lpc = 0; lpc < dls.text_line_count(); lpc++) { - if (ec.ec_dry_run && lpc > 10) { + if (!first) { + fprintf(outfile, "\u2533"); + } + fprintf(outfile, "%s", cell_line.c_str()); + first = false; + } + fprintf(outfile, "\u2513\n"); + + for (const auto& hdr : dls.dls_headers) { + auto centered_hdr = center_str(hdr.hm_name, hdr.hm_column_size); + + fprintf(outfile, "\u2503"); + fprintf(outfile, "%s", centered_hdr.c_str()); + } + fprintf(outfile, "\u2503\n"); + + first = true; + fprintf(outfile, "\u2521"); + for (const auto& hdr : dls.dls_headers) { + auto cell_line = repeat("\u2501", hdr.hm_column_size); + + if (!first) { + fprintf(outfile, "\u2547"); + } + fprintf(outfile, "%s", cell_line.c_str()); + first = false; + } + fprintf(outfile, "\u2529\n"); + + for (size_t row = 0; row < dls.text_line_count(); row++) { + if (ec.ec_dry_run && row > 10) { break; } - string line; + for (size_t col = 0; col < dls.dls_headers.size(); col++) { + const auto& hdr = dls.dls_headers[col]; + + fprintf(outfile, "\u2502"); - dls.text_value_for_line(lnav_data.ld_views[LNV_DB], lpc, line, - text_sub_source::RF_RAW); - fputs(line.c_str(), outfile); - fputc('\n', outfile); + auto cell = dls.dls_rows[row][col]; + auto cell_byte_len = strlen(cell); + auto cell_length = utf8_string_length(cell, cell_byte_len) + .unwrapOr(cell_byte_len); + auto padding = hdr.hm_column_size - cell_length; + + if (hdr.hm_column_type != SQLITE3_TEXT) { + fprintf(outfile, "%s", std::string(padding, ' ').c_str()); + } + fprintf(outfile, "%s", cell); + if (hdr.hm_column_type == SQLITE3_TEXT) { + fprintf(outfile, "%s", std::string(padding, ' ').c_str()); + } + } + fprintf(outfile, "\u2502\n"); line_count += 1; } + + first = true; + fprintf(outfile, "\u2514"); + for (const auto& hdr : dls.dls_headers) { + auto cell_line = repeat("\u2501", hdr.hm_column_size); + + if (!first) { + fprintf(outfile, "\u2534"); + } + fprintf(outfile, "%s", cell_line.c_str()); + first = false; + } + fprintf(outfile, "\u2518\n"); + } else if (args[0] == "write-json-to") { yajlpp_gen gen; @@ -1123,7 +1176,7 @@ static Result com_redirect_to(exec_context &ec, string cmdline, return Ok(string("info: redirect will be cleared")); } - ec.ec_output_stack.back() = nonstd::nullopt; + ec.clear_output(); return Ok(string("info: cleared redirect")); } @@ -1146,13 +1199,18 @@ static Result com_redirect_to(exec_context &ec, string cmdline, return Ok("info: output will be redirected to -- " + split_args[0]); } - FILE *file = fopen(split_args[0].c_str(), "w"); + nonstd::optional file; - if (file == nullptr) { - return ec.make_error("unable to open file -- {}", split_args[0]); - } + if (split_args[0] == "-") { + ec.clear_output(); + } else { + FILE *file = fopen(split_args[0].c_str(), "w"); + if (file == nullptr) { + return ec.make_error("unable to open file -- {}", split_args[0]); + } - ec.ec_output_stack.back() = file; + ec.set_output(split_args[0], file); + } return Ok("info: redirecting output to file -- " + split_args[0]); } @@ -3396,20 +3454,24 @@ static Result com_echo(exec_context &ec, string cmdline, vector< } else if (args.size() >= 1) { bool lf = true; + string src; if (args.size() > 2 && args[1] == "-n") { string::size_type index_in_cmdline = cmdline.find(args[1]); lf = false; - retval = cmdline.substr(index_in_cmdline + args[1].length() + 1); + src = cmdline.substr(index_in_cmdline + args[1].length() + 1); } else if (args.size() >= 2) { - retval = cmdline.substr(args[0].length() + 1); + src = cmdline.substr(args[0].length() + 1); } else { - retval = ""; + src = ""; } + auto lexer = shlex(src); + lexer.eval(retval, ec.create_resolver()); + auto ec_out = ec.get_output(); if (ec.ec_dry_run) { lnav_data.ld_preview_status_source.get_description() @@ -4804,11 +4866,11 @@ readline_context::command_t STD_COMMANDS[] = { }) }, { - "write-cols-to", + "write-table-to", com_save_to, - help_text(":write-cols-to") - .with_summary("Write SQL results to the given file in a columnar format") + help_text(":write-table-to") + .with_summary("Write SQL results to the given file in a tabular format") .with_parameter(help_text("path", "The path to the file to write")) .with_tags({"io", "scripting", "sql"}) .with_example({ @@ -4873,7 +4935,8 @@ readline_context::command_t STD_COMMANDS[] = { com_redirect_to, help_text(":redirect-to") - .with_summary("Redirect the output of commands to the given file") + .with_summary("Redirect the output of commands that write to " + "stdout 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") @@ -5256,9 +5319,13 @@ readline_context::command_t STD_COMMANDS[] = { com_echo, help_text(":echo") - .with_summary("Echo the given message") + .with_summary( + "Echo the given message to the screen or, if :redirect-to has " + "been called, to output file specified in the redirect. " + "Variable substitution is performed on the message. Use a " + "backslash to escape any special characters, like '$'") .with_parameter(help_text("msg", "The message to display")) - .with_tags({"scripting"}) + .with_tags({"io", "scripting"}) .with_example({ "To output 'Hello, World!'", "Hello, World!" @@ -5289,10 +5356,6 @@ readline_context::command_t STD_COMMANDS[] = { .with_tags({"scripting"}) .with_examples( { - { - "To output the user's home directory", - ":echo $HOME" - }, { "To substitute the table name from a variable", ";SELECT * FROM ${table}" @@ -5354,6 +5417,7 @@ readline_context::command_t STD_COMMANDS[] = { static unordered_map> aliases = { { "quit", { "q", "q!" } }, + { "write-table-to", { "write-cols-to", }} }; void init_lnav_commands(readline_context::command_map_t &cmd_map) diff --git a/src/log_format.cc b/src/log_format.cc index 6fa6c085..6336bbf6 100644 --- a/src/log_format.cc +++ b/src/log_format.cc @@ -2099,7 +2099,7 @@ public: virtual bool next(log_cursor &lc, logfile_sub_source &lss) { - lc.lc_curr_line = lc.lc_curr_line + vis_line_t(1); + lc.lc_curr_line = lc.lc_curr_line + 1_vl; lc.lc_sub_index = 0; if (lc.is_eof()) { @@ -2107,7 +2107,7 @@ public: } content_line_t cl(lss.at(lc.lc_curr_line)); - shared_ptr lf = lss.find(cl); + auto lf = lss.find_file_ptr(cl); auto lf_iter = lf->begin() + cl; uint8_t mod_id = lf_iter->get_module_id(); @@ -2115,12 +2115,12 @@ public: return false; } - auto format = lf->get_format(); - this->elt_module_format.mf_mod_format = nullptr; - if (format->get_name() == this->lfvi_format.get_name()) { + if (lf->get_format_name() == this->lfvi_format.get_name()) { return true; } else if (mod_id && mod_id == this->lfvi_format.lf_mod_index) { + auto format = lf->get_format(); + return lf->read_line(lf_iter).map([this, format, cl](auto line) { std::vector values; shared_buffer_ref body_ref; diff --git a/src/log_vtab_impl.cc b/src/log_vtab_impl.cc index 154adf7c..dd2b4b7a 100644 --- a/src/log_vtab_impl.cc +++ b/src/log_vtab_impl.cc @@ -60,6 +60,7 @@ static const char *LOG_COLUMNS = R"( ( static const char *LOG_FOOTER_COLUMNS = R"( -- END Format-specific fields + log_time_msecs INTEGER HIDDEN, -- The adjusted timestamp for the log message as the number of milliseconds from the epoch log_path TEXT HIDDEN COLLATE naturalnocase, -- The path to the log file this message is from log_text TEXT HIDDEN, -- The full text of the log message log_body TEXT HIDDEN -- The body of the log message @@ -518,6 +519,10 @@ static int vt_column(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) switch (post_col_number) { case 0: { + sqlite3_result_int64(ctx, ll->get_time_in_millis()); + break; + } + case 1: { const string &fn = lf->get_filename(); sqlite3_result_text(ctx, @@ -526,7 +531,7 @@ static int vt_column(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) SQLITE_STATIC); break; } - case 1: { + case 2: { shared_buffer_ref line; lf->read_full_message(ll, line); @@ -536,7 +541,7 @@ static int vt_column(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int col) SQLITE_TRANSIENT); break; } - case 2: { + case 3: { if (vc->line_values.empty()) { lf->read_full_message(ll, vc->log_msg); vt->vi->extract(lf, line_number, vc->log_msg, vc->line_values); diff --git a/src/log_vtab_impl.hh b/src/log_vtab_impl.hh index b7c16cd1..d72c8acb 100644 --- a/src/log_vtab_impl.hh +++ b/src/log_vtab_impl.hh @@ -137,6 +137,7 @@ public: keys_inout.emplace_back("log_line"); keys_inout.emplace_back("min(log_line)"); keys_inout.emplace_back("log_mark"); + keys_inout.emplace_back("log_time_msecs"); }; virtual void extract(std::shared_ptr lf, diff --git a/src/logfile.cc b/src/logfile.cc index 0371a020..81bfbb06 100644 --- a/src/logfile.cc +++ b/src/logfile.cc @@ -651,3 +651,12 @@ logfile::read_raw_message(logfile::const_iterator ll) return this->lf_line_buffer.read_range(this->get_file_range(ll)); } + +intern_string_t logfile::get_format_name() const +{ + if (this->lf_format) { + return this->lf_format->get_name(); + } + + return {}; +} diff --git a/src/logfile.hh b/src/logfile.hh index 0cbe797a..5416bfe4 100644 --- a/src/logfile.hh +++ b/src/logfile.hh @@ -164,6 +164,8 @@ public: */ std::shared_ptr get_format() const { return this->lf_format; }; + intern_string_t get_format_name() const; + text_format_t get_text_format() const { return this->lf_text_format; } diff --git a/src/logfile_sub_source.cc b/src/logfile_sub_source.cc index 20884322..6ef8911f 100644 --- a/src/logfile_sub_source.cc +++ b/src/logfile_sub_source.cc @@ -1068,6 +1068,10 @@ bool logfile_sub_source::eval_sql_filter(sqlite3_stmt *stmt, iterator ld, logfil SQLITE_STATIC); continue; } + if (strcmp(name, ":log_time_msecs") == 0) { + sqlite3_bind_int64(stmt, lpc + 1, ll->get_time_in_millis()); + continue; + } if (strcmp(name, ":log_mark") == 0) { sqlite3_bind_int(stmt, lpc + 1, ll->is_marked()); continue; diff --git a/src/logfile_sub_source.hh b/src/logfile_sub_source.hh index 042ca20c..ea1da9bd 100644 --- a/src/logfile_sub_source.hh +++ b/src/logfile_sub_source.hh @@ -501,6 +501,14 @@ public: return retval; }; + logfile *find_file_ptr(content_line_t &line) + { + auto retval = this->lss_files[line / MAX_LINES_PER_FILE]->get_file_ptr(); + line = content_line_t(line % MAX_LINES_PER_FILE); + + return retval; + }; + logline *find_line(content_line_t line) { logline *retval = nullptr; @@ -599,7 +607,7 @@ public: { this->ld_filter_state.lfo_filter_state.clear(); }; -\ + void set_file(const std::shared_ptr &lf) { this->ld_filter_state.lfo_filter_state.tfs_logfile = lf; lf->set_logline_observer(&this->ld_filter_state); @@ -609,6 +617,10 @@ public: return this->ld_filter_state.lfo_filter_state.tfs_logfile; }; + logfile *get_file_ptr() const { + return this->ld_filter_state.lfo_filter_state.tfs_logfile.get(); + }; + bool is_visible() const { return this->ld_visible; } diff --git a/src/readline_callbacks.cc b/src/readline_callbacks.cc index 1c7c655e..e8f2061a 100644 --- a/src/readline_callbacks.cc +++ b/src/readline_callbacks.cc @@ -200,7 +200,6 @@ bool rl_sql_help(readline_curses *rc) auto vtab_module_iter = vtab_module_ddls.find(intern_ident); string ddl; - log_debug("ident %s", ident.c_str()); if (vtab != nullptr) { ddl = trim(vtab->get_table_statement()); } else if (vtab_module_iter != vtab_module_ddls.end()) { @@ -367,7 +366,6 @@ static void rl_search_internal(readline_curses *rc, ln_mode_t mode, bool complet lnav_data.ld_log_source.set_preview_sql_filter(nullptr); tc->reload_data(); - log_debug("rl_search_int"); switch (mode) { case LNM_SEARCH: case LNM_SEARCH_FILTERS: @@ -633,22 +631,23 @@ static void rl_callback_int(readline_curses *rc, bool is_alt) rc->set_value("Unable to open temporary output file: " + string(strerror(errno))); } else { - auto_fd fd(fileno(tmpout)); - auto_fd fd_copy((const auto_fd &) fd); + auto fd_copy = auto_fd::dup_of(fileno(tmpout)); char desc[256], timestamp[32]; - time_t current_time = time(NULL); + time_t current_time = time(nullptr); string path_and_args = rc->get_value(); - ec.ec_output_stack.back() = tmpout.in(); - string result = execute_file(ec, path_and_args) - .map(ok_prefix) - .orElse(err_to_ok).unwrap(); - string::size_type lf_index = result.find('\n'); - if (lf_index != string::npos) { - result = result.substr(0, lf_index); + { + exec_context::output_guard og(ec, "tmp", tmpout.release()); + + string result = execute_file(ec, path_and_args) + .map(ok_prefix) + .orElse(err_to_ok).unwrap(); + string::size_type lf_index = result.find('\n'); + if (lf_index != string::npos) { + result = result.substr(0, lf_index); + } + rc->set_value(result); } - rc->set_value(result); - ec.ec_output_stack.back() = nonstd::nullopt; struct stat st; @@ -666,7 +665,7 @@ static void rl_callback_int(readline_curses *rc, bool is_alt) .with_detect_format(false); lnav_data.ld_files_to_front.emplace_back(desc, 0); - if (lnav_data.ld_rl_view != NULL) { + if (lnav_data.ld_rl_view != nullptr) { lnav_data.ld_rl_view->set_alt_value( HELP_MSG_1(X, "to close the file")); } diff --git a/src/readline_possibilities.cc b/src/readline_possibilities.cc index 36c7284c..43c083ba 100644 --- a/src/readline_possibilities.cc +++ b/src/readline_possibilities.cc @@ -223,6 +223,7 @@ void add_filter_expr_possibilities(readline_curses *rlc, int context, const std: static const char *BUILTIN_VARS[] = { ":log_level", ":log_time", + ":log_time_msecs", ":log_mark", ":log_comment", ":log_tags", diff --git a/src/relative_time.hh b/src/relative_time.hh index e14217c2..7e24b089 100644 --- a/src/relative_time.hh +++ b/src/relative_time.hh @@ -274,7 +274,7 @@ public: } }; - int64_t to_microseconds() { + int64_t to_microseconds() const { int64_t retval; retval = this->rt_field[RTF_YEARS].value * 12; diff --git a/src/scripts/dhclient-summary.lnav b/src/scripts/dhclient-summary.lnav index bed7675b..87d0c1a4 100644 --- a/src/scripts/dhclient-summary.lnav +++ b/src/scripts/dhclient-summary.lnav @@ -20,4 +20,4 @@ (SELECT lt2.start_time AS end_time FROM lease_times AS lt2 WHERE lt1.start_time < lt2.start_time LIMIT 1) AS end_time, ip FROM lease_times AS lt1) -:write-cols-to - +:write-table-to - diff --git a/src/scripts/partition-by-boot.lnav b/src/scripts/partition-by-boot.lnav index a563000a..e052774a 100644 --- a/src/scripts/partition-by-boot.lnav +++ b/src/scripts/partition-by-boot.lnav @@ -5,6 +5,8 @@ # @description: Partition the log view based on boot messages from the Linux kernel. # -;UPDATE syslog_log SET log_part = 'Boot: ' || log_time WHERE log_text LIKE '%kernel: Linux version%'; +;UPDATE syslog_log + SET log_part = 'Boot: ' || log_time + WHERE log_text LIKE '%kernel:%Linux version%'; ;SELECT 'Created ' || changes() || ' partitions(s)'; diff --git a/src/shlex.cc b/src/shlex.cc index 2890b45a..481af4a8 100644 --- a/src/shlex.cc +++ b/src/shlex.cc @@ -193,3 +193,22 @@ void shlex::scan_variable_ref(pcre_context::capture_t &cap_out, token_out = shlex_token_t::ST_ERROR; } } + +void shlex::resolve_home_dir(std::string &result, + const pcre_context::capture_t cap) const +{ + if (cap.length() == 1) { + result.append(getenv_opt("HOME").value_or("~")); + } else { + auto username = (char *) alloca(cap.length()); + + memcpy(username, &this->s_str[cap.c_begin + 1], cap.length() - 1); + username[cap.length() - 1] = '\0'; + auto pw = getpwnam(username); + if (pw != nullptr) { + result.append(pw->pw_dir); + } else { + result.append(&this->s_str[cap.c_begin], cap.length()); + } + } +} diff --git a/src/shlex.hh b/src/shlex.hh index 4502804c..0232f5ab 100644 --- a/src/shlex.hh +++ b/src/shlex.hh @@ -141,6 +141,14 @@ public: case shlex_token_t::ST_TILDE: this->resolve_home_dir(result, cap); break; + case shlex_token_t::ST_DOUBLE_QUOTE_START: + case shlex_token_t::ST_DOUBLE_QUOTE_END: + result.append("\""); + break; + case shlex_token_t::ST_SINGLE_QUOTE_START: + case shlex_token_t::ST_SINGLE_QUOTE_END: + result.append("'"); + break; default: break; } @@ -220,22 +228,7 @@ public: void scan_variable_ref(pcre_context::capture_t &cap_out, shlex_token_t &token_out); - void resolve_home_dir(std::string& result, const pcre_context::capture_t cap) const { - if (cap.length() == 1) { - result.append(getenv_opt("HOME").value_or("~")); - } else { - auto username = (char *) alloca(cap.length()); - - memcpy(username, &this->s_str[cap.c_begin + 1], cap.length() - 1); - username[cap.length() - 1] = '\0'; - auto pw = getpwnam(username); - if (pw != nullptr) { - result.append(pw->pw_dir); - } else { - result.append(&this->s_str[cap.c_begin], cap.length()); - } - } - } + void resolve_home_dir(std::string& result, const pcre_context::capture_t cap) const; enum class state_t { STATE_NORMAL, diff --git a/src/sqlite-extension-func.cc b/src/sqlite-extension-func.cc index ddb163bd..c438fb12 100644 --- a/src/sqlite-extension-func.cc +++ b/src/sqlite-extension-func.cc @@ -98,7 +98,7 @@ int register_sqlite_funcs(sqlite3 *db, sqlite_registration_func_t *reg_funcs) agg_funcs[i].nArg, SQLITE_UTF8, (void *) &agg_funcs[i], - 0, + nullptr, agg_funcs[i].xStep, agg_funcs[i].xFinalize); diff --git a/src/string-extension-functions.cc b/src/string-extension-functions.cc index 719fe8f2..a0bcfcba 100644 --- a/src/string-extension-functions.cc +++ b/src/string-extension-functions.cc @@ -17,6 +17,7 @@ #include "pcrepp/pcrepp.hh" +#include "base/humanize.hh" #include "base/string_util.hh" #include "yajlpp/yajlpp.hh" #include "column_namer.hh" @@ -248,6 +249,60 @@ static void sql_spooky_hash_final(sqlite3_context *context) } } +struct sparkline_context { + bool sc_initialized{true}; + double sc_max_value{0.0}; + std::vector sc_values; +}; + +static void sparkline_step(sqlite3_context *context, + int argc, + sqlite3_value **argv) +{ + auto *sc = (sparkline_context *) + sqlite3_aggregate_context(context, sizeof(sparkline_context)); + + if (!sc->sc_initialized) { + new (sc) sparkline_context; + } + + if (argc == 0) { + return; + } + + sc->sc_values.push_back(sqlite3_value_double(argv[0])); + sc->sc_max_value = std::max(sc->sc_max_value, sc->sc_values.back()); + + if (argc >= 2) { + sc->sc_max_value = std::max(sc->sc_max_value, + sqlite3_value_double(argv[1])); + } +} + +static void sparkline_final(sqlite3_context *context) +{ + auto *sc = (sparkline_context *) + sqlite3_aggregate_context(context, sizeof(sparkline_context)); + + if (!sc->sc_initialized) { + sqlite3_result_text(context, "", 0, SQLITE_STATIC); + return; + } + + auto retval = (char *) sqlite3_malloc(sc->sc_values.size() * 3 + 1); + auto start = retval; + + for (const auto& value : sc->sc_values) { + auto bar = humanize::sparkline(value, sc->sc_max_value); + + strcpy(start, bar.c_str()); + start += bar.length(); + } + *start = '\0'; + + sqlite3_result_text(context, retval, -1, sqlite3_free); +} + int string_extension_functions(struct FuncDef **basic_funcs, struct FuncDefAgg **agg_funcs) { @@ -301,6 +356,34 @@ int string_extension_functions(struct FuncDef **basic_funcs, }) ), + sqlite_func_adapter::builder( + help_text("humanize_file_size", + "Format the given file size as a human-friendly string") + .sql_function() + .with_parameter({"value", "The file size to format"}) + .with_tags({"string"}) + .with_example({ + "To format an amount", + "SELECT humanize_file_size(10 * 1024 * 1024)" + }) + ), + + sqlite_func_adapter::builder( + help_text("sparkline", + "Converts a numeric value on a range to a bar chart character") + .sql_function() + .with_parameter({"value", "The numeric value to convert"}) + .with_parameter(help_text( + "upper", "The upper bound of the numeric range (default: 100)") + .optional()) + .with_tags({"string"}) + .with_example({ + "To get the unicode block element for the value 32 in the " + "range of 0-128", + "SELECT sparkline(32, 128)" + }) + ), + sqlite_func_adapter::builder( help_text("extract", "Automatically Parse and extract data from a string") @@ -388,7 +471,7 @@ int string_extension_functions(struct FuncDef **basic_funcs, sql_spooky_hash_step, sql_spooky_hash_final, help_text("group_spooky_hash", "Compute the hash value for the given arguments") - .sql_function() + .sql_agg_function() .with_parameter(help_text("str", "The string to hash") .one_or_more()) .with_tags({"string"}) @@ -398,6 +481,26 @@ int string_extension_functions(struct FuncDef **basic_funcs, }) }, + { + "sparkline", -1, 0, + sparkline_step, sparkline_final, + help_text("sparkline", + "An aggregate function to convert numeric values to a " + "sparkline bar chart") + .sql_agg_function() + .with_parameter({"value", "The numeric values to chart"}) + .with_parameter(help_text( + "upper", "The upper bound of the numeric range. " + "If not provided, the default is derived from " + "all of the provided values") + .optional()) + .with_tags({"string"}) + .with_example({ + "To chart the values in a JSON array", + "SELECT sparkline(value) FROM json_each('[0, 1, 2, 3, 4, 5, 6, 7, 8]')" + }) + }, + {nullptr} }; diff --git a/src/textview_curses.cc b/src/textview_curses.cc index cba598f4..bd3147fe 100644 --- a/src/textview_curses.cc +++ b/src/textview_curses.cc @@ -234,15 +234,22 @@ void textview_curses::grep_begin(grep_proc &gp, vis_line_t start, vi this->tc_searching += 1; this->tc_search_action(this); - bookmark_vector &search_bv = this->tc_bookmarks[&BM_SEARCH]; - - if (start != -1) { - auto pair = search_bv.equal_range(vis_line_t(start), vis_line_t(stop)); + if (start != -1_vl) { + auto& search_bv = this->tc_bookmarks[&BM_SEARCH]; + auto pair = search_bv.equal_range(start, stop); + if (pair.first != pair.second) { + this->set_needs_update(); + } for (auto mark_iter = pair.first; mark_iter != pair.second; ++mark_iter) { - this->set_user_mark(&BM_SEARCH, *mark_iter, false); + if (this->tc_sub_source) { + this->tc_sub_source->text_mark(&BM_SEARCH, *mark_iter, false); + } + } + if (pair.first != pair.second) { + search_bv.erase(pair.first, pair.second); } } @@ -652,6 +659,71 @@ textview_curses::horiz_shift(vis_line_t start, vis_line_t end, int off_start, range_out = std::make_pair(prev_hit, next_hit); } +void +textview_curses::set_user_mark(bookmark_type_t *bm, vis_line_t vl, bool marked) +{ + bookmark_vector &bv = this->tc_bookmarks[bm]; + bookmark_vector::iterator iter; + + if (marked) { + bv.insert_once(vl); + } + else { + iter = std::lower_bound(bv.begin(), bv.end(), vl); + if (iter != bv.end() && *iter == vl) { + bv.erase(iter); + } + } + if (this->tc_sub_source) { + this->tc_sub_source->text_mark(bm, vl, marked); + } + + if (marked) { + this->search_range(vl, vl + 1_vl); + this->search_new_data(); + } + this->set_needs_update(); +} + +void +textview_curses::toggle_user_mark(bookmark_type_t *bm, vis_line_t start_line, + vis_line_t end_line) +{ + if (end_line == -1) { + end_line = start_line; + } + if (start_line > end_line) { + std::swap(start_line, end_line); + } + + if (start_line >= this->get_inner_height()) { + return; + } + if (end_line >= this->get_inner_height()) { + end_line = vis_line_t(this->get_inner_height() - 1); + } + for (vis_line_t curr_line = start_line; curr_line <= end_line; + ++curr_line) { + bookmark_vector &bv = this->tc_bookmarks[bm]; + bookmark_vector::iterator iter; + bool added; + + iter = bv.insert_once(curr_line); + if (iter == bv.end()) { + added = true; + } + else { + bv.erase(iter); + added = false; + } + if (this->tc_sub_source) { + this->tc_sub_source->text_mark(bm, curr_line, added); + } + } + this->search_range(start_line, end_line + 1_vl); + this->search_new_data(); +} + void text_time_translator::scroll_invoked(textview_curses *tc) { if (tc->get_inner_height() > 0) { diff --git a/src/textview_curses.hh b/src/textview_curses.hh index 2fe38378..53ed9e88 100644 --- a/src/textview_curses.hh +++ b/src/textview_curses.hh @@ -635,64 +635,9 @@ public: void toggle_user_mark(bookmark_type_t *bm, vis_line_t start_line, - vis_line_t end_line = vis_line_t(-1)) - { - if (end_line == -1) { - end_line = start_line; - } - if (start_line > end_line) { - std::swap(start_line, end_line); - } + vis_line_t end_line = vis_line_t(-1));; - if (start_line >= this->get_inner_height()) { - return; - } - if (end_line >= this->get_inner_height()) { - end_line = vis_line_t(this->get_inner_height() - 1); - } - for (vis_line_t curr_line = start_line; curr_line <= end_line; - ++curr_line) { - bookmark_vector &bv = this->tc_bookmarks[bm]; - bookmark_vector::iterator iter; - bool added; - - iter = bv.insert_once(curr_line); - if (iter == bv.end()) { - added = true; - } - else { - bv.erase(iter); - added = false; - } - if (this->tc_sub_source) { - this->tc_sub_source->text_mark(bm, curr_line, added); - } - } - this->search_range(start_line, end_line + 1_vl); - this->search_new_data(); - }; - - void set_user_mark(bookmark_type_t *bm, vis_line_t vl, bool marked) { - bookmark_vector &bv = this->tc_bookmarks[bm]; - bookmark_vector::iterator iter; - - if (marked) { - bv.insert_once(vl); - } - else { - iter = std::lower_bound(bv.begin(), bv.end(), vl); - if (iter != bv.end() && *iter == vl) { - bv.erase(iter); - } - } - if (this->tc_sub_source) { - this->tc_sub_source->text_mark(bm, vl, marked); - } - - this->search_range(vl, vl + 1_vl); - this->search_new_data(); - this->set_needs_update(); - }; + void set_user_mark(bookmark_type_t *bm, vis_line_t vl, bool marked);; textview_curses &set_sub_source(text_sub_source *src) { this->tc_sub_source = src; diff --git a/src/time-extension-functions.cc b/src/time-extension-functions.cc index f6105f37..394de70a 100644 --- a/src/time-extension-functions.cc +++ b/src/time-extension-functions.cc @@ -36,8 +36,10 @@ #include #include +#include #include "base/date_time_scanner.hh" +#include "base/lrucache.hpp" #include "sql_util.hh" #include "relative_time.hh" @@ -45,47 +47,75 @@ using namespace std; -static string timeslice(const char *time_in, nonstd::optional slice_in_opt) +static lnav::sqlite::text_buffer timeslice(sqlite3_value *time_in, nonstd::optional slice_in_opt) { - const char *slice_in = slice_in_opt.value_or("15m"); - auto parse_res = relative_time::from_str(slice_in, strlen(slice_in)); - date_time_scanner dts; - time_t now; + thread_local date_time_scanner dts; + thread_local struct { + std::string c_slice_str; + relative_time c_rel_time; + } cache; + const auto slice_in = string_fragment(slice_in_opt.value_or("15m")); + + if (slice_in.empty()) { + throw sqlite_func_error("no time slice value given"); + } - time(&now); - dts.set_base_time(now); + if (slice_in != cache.c_slice_str.c_str()) { + auto parse_res = relative_time::from_str(slice_in.data()); + if (parse_res.isErr()) { + throw sqlite_func_error("unable to parse time slice value: {} -- {}", + slice_in, parse_res.unwrapErr().pe_msg); + } - if (parse_res.isErr()) { - throw sqlite_func_error("unable to parse time slice value: {} -- {}", - slice_in, parse_res.unwrapErr().pe_msg); - } + cache.c_rel_time = parse_res.unwrap(); + if (cache.c_rel_time.empty()) { + throw sqlite_func_error("could not determine a time slice from: {}", + slice_in); + } - auto rt = parse_res.unwrap(); - if (rt.empty()) { - throw sqlite_func_error("no time slice value given"); - } + if (cache.c_rel_time.is_absolute()) { + throw sqlite_func_error("absolute time slices are not valid"); + } - if (rt.is_absolute()) { - throw sqlite_func_error("absolute time slices are not valid"); + cache.c_slice_str = slice_in.to_string(); } - struct exttm tm; + int64_t us, remainder; struct timeval tv; - if (dts.scan(time_in, strlen(time_in), nullptr, &tm, tv) == nullptr) { - throw sqlite_func_error("unable to parse time value -- {}", time_in); + switch (sqlite3_value_type(time_in)) { + case SQLITE3_TEXT: { + const char *time_in_str = reinterpret_cast(sqlite3_value_text( + time_in)); + struct exttm tm; + + if (dts.scan(time_in_str, strlen(time_in_str), nullptr, &tm, tv, false) == nullptr) { + dts.unlock(); + if (dts.scan(time_in_str, strlen(time_in_str), nullptr, &tm, tv, false) == nullptr) { + throw sqlite_func_error("unable to parse time value -- {}", + time_in_str); + } + } + + us = tv.tv_sec * 1000000LL + tv.tv_usec; + break; + } + case SQLITE_INTEGER: { + auto msecs = sqlite3_value_int64(time_in); + + us = msecs * 1000LL; + break; + } } - int64_t us = tv.tv_sec * 1000000LL + tv.tv_usec, remainder; - - remainder = us % rt.to_microseconds(); + remainder = us % cache.c_rel_time.to_microseconds(); us -= remainder; - tv.tv_sec = us / (1000 * 1000); - tv.tv_usec = us % (1000 * 1000); + tv.tv_sec = us / (1000LL * 1000LL); + tv.tv_usec = us % (1000LL * 1000LL); - char ts[64]; - sql_strftime(ts, sizeof(ts), tv); + auto ts = lnav::sqlite::text_buffer::alloc(64); + ts.tb_length = sql_strftime(ts.tb_value, ts.tb_length, tv); return ts; } @@ -132,7 +162,7 @@ int time_extension_functions(struct FuncDef **basic_funcs, }) .with_example({ "To group log messages into five minute buckets and count them", - "SELECT timeslice(log_time, '5m') AS slice, count(*) FROM lnav_example_log GROUP BY slice" + "SELECT timeslice(log_time_msecs, '5m') AS slice, count(*) FROM lnav_example_log GROUP BY slice" }) ), diff --git a/src/vtab_module.hh b/src/vtab_module.hh index f7b5f2ba..0ce8b95b 100644 --- a/src/vtab_module.hh +++ b/src/vtab_module.hh @@ -172,6 +172,27 @@ inline void to_sqlite(sqlite3_context *ctx, const char *str) } } +namespace lnav { +namespace sqlite { +struct text_buffer { + static text_buffer alloc(size_t size) { + return text_buffer { + (char *) malloc(size), + size, + }; + } + + char *tb_value; + size_t tb_length; +}; +} +} + +inline void to_sqlite(sqlite3_context *ctx, const lnav::sqlite::text_buffer &str) +{ + sqlite3_result_text(ctx, str.tb_value, str.tb_length, free); +} + inline void to_sqlite(sqlite3_context *ctx, const std::string &str) { sqlite3_result_text(ctx, str.c_str(), str.length(), SQLITE_TRANSIENT); diff --git a/test/expected_help.txt b/test/expected_help.txt index f10ae13a..3bac24a4 100644 --- a/test/expected_help.txt +++ b/test/expected_help.txt @@ -1233,10 +1233,11 @@ Parameter X The unicode code point values See Also charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), - startswith(), strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), upper(), xpath() Example #1 To get a string with the code points 0x48 and 0x49: ;SELECT char(0x48, 0x49) @@ -1252,11 +1253,12 @@ Parameters haystack The string to search within start The one-based index within the haystack to start the search See Also - char(), endswith(), extract(), group_concat(), group_spooky_hash(), instr(), - leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + char(), endswith(), extract(), group_concat(), group_spooky_hash(), + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), upper(), xpath() Examples #1 To search for the string 'abc' within 'abcabc' and starting at position 2: ;SELECT charindex('abc', 'abcabc', 2) @@ -1402,11 +1404,12 @@ Parameters str The string to test suffix The suffix to check in the string See Also - char(), charindex(), extract(), group_concat(), group_spooky_hash(), instr(), - leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + char(), charindex(), extract(), group_concat(), group_spooky_hash(), + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), upper(), xpath() Examples #1 To test if the string 'notbad.jpg' ends with '.jpg': ;SELECT endswith('notbad.jpg', '.jpg') @@ -1436,11 +1439,12 @@ Synopsis Parameter str The string to parse See Also - char(), charindex(), endswith(), group_concat(), group_spooky_hash(), instr(), - leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + char(), charindex(), endswith(), group_concat(), group_spooky_hash(), + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), upper(), xpath() Examples #1 To extract key/value pairs from a string: ;SELECT extract('foo=1 bar=2 name="Rolo Tomassi"') @@ -1518,11 +1522,12 @@ Parameters X The value to concatenate. sep The separator to place between the values. See Also - char(), charindex(), endswith(), extract(), group_spooky_hash(), instr(), - leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + char(), charindex(), endswith(), extract(), group_spooky_hash(), + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), upper(), xpath() Examples #1 To concatenate the values of the column 'ex_procname' from the table 'lnav_example_log' : @@ -1545,11 +1550,12 @@ Synopsis Parameter str The string to hash See Also - char(), charindex(), endswith(), extract(), group_concat(), instr(), leftstr(), - length(), lower(), ltrim(), padc(), padl(), padr(), printf(), proper(), - regexp_capture(), regexp_match(), regexp_replace(), replace(), replicate(), - reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), strfilter(), - substr(), trim(), unicode(), upper(), xpath() + char(), charindex(), endswith(), extract(), group_concat(), + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), upper(), xpath() Example #1 To produce a hash of all of the values of 'column1': ;SELECT group_spooky_hash(column1) FROM (VALUES ('abc'), ('123')) @@ -1568,6 +1574,24 @@ Example +Synopsis + humanize_file_size(value) -- Format the given file size as a human-friendly + string +Parameter + value The file size to format +See Also + char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), + instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), + printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() +Example +#1 To format an amount: + ;SELECT humanize_file_size(10 * 1024 * 1024) + + + Synopsis ifnull(X, Y) -- Returns a copy of its first non-NULL argument, or NULL if both arguments are NULL @@ -1590,10 +1614,11 @@ Parameters needle The string to look for in the haystack See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), leftstr(), length(), lower(), ltrim(), padc(), padl(), + padr(), printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Example #1 To test get the position of 'b' in the string 'abc': ;SELECT instr('abc', 'b') @@ -1795,10 +1820,11 @@ Parameters N The number of characters from the left side of the string to return. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), length(), lower(), ltrim(), padc(), padl(), padr(), printf(), proper(), - regexp_capture(), regexp_match(), regexp_replace(), replace(), replicate(), - reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), strfilter(), - substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), length(), lower(), ltrim(), padc(), padl(), + padr(), printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To get the first character of the string 'abc': ;SELECT leftstr('abc', 1) @@ -1816,10 +1842,11 @@ Parameter str The string to determine the length of See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), lower(), ltrim(), padc(), padl(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), lower(), ltrim(), padc(), padl(), + padr(), printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Example #1 To get the length of the string 'abc': ;SELECT length('abc') @@ -1911,10 +1938,11 @@ Parameter str The string to convert. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), ltrim(), padc(), padl(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), ltrim(), padc(), padl(), + padr(), printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Example #1 To lowercase the string 'AbC': ;SELECT lower('AbC') @@ -1930,10 +1958,11 @@ Parameters chars The characters to trim. Defaults to spaces. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), padc(), padl(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), padc(), padl(), + padr(), printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To trim the leading whitespace from the string ' abc': ;SELECT ltrim(' abc') @@ -2028,10 +2057,11 @@ Parameters len The minimum desired length of the output string See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padl(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padl(), + padr(), printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To pad the string 'abc' to a length of six characters: ;SELECT padc('abc', 6) || 'def' @@ -2050,10 +2080,11 @@ Parameters len The minimum desired length of the output string See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padr(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padr(), printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To pad the string 'abc' to a length of six characters: ;SELECT padl('abc', 6) @@ -2072,10 +2103,11 @@ Parameters len The minimum desired length of the output string See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), printf(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To pad the string 'abc' to a length of six characters: ;SELECT padr('abc', 6) || 'def' @@ -2128,10 +2160,11 @@ Parameters X The argument to substitute at a given position in the format. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - proper(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), proper(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To substitute 'World' into the string 'Hello, %s!': ;SELECT printf('Hello, %s!', 'World') @@ -2152,10 +2185,11 @@ Parameter str The string to capitalize. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), regexp_capture(), regexp_match(), regexp_replace(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), regexp_capture(), regexp_match(), regexp_replace(), + replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Example #1 To capitalize the words in the string 'hello, world!': ;SELECT proper('hello, world!') @@ -2256,10 +2290,11 @@ Results content The captured value from the string. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_match(), regexp_replace(), replace(), replicate(), - reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), strfilter(), - substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_match(), regexp_replace(), replace(), + replicate(), reverse(), rightstr(), rtrim(), sparkline(), sparkline(), + spooky_hash(), startswith(), strfilter(), substr(), trim(), unicode(), upper(), + xpath() Example #1 To extract the key/value pairs 'a'/1 and 'b'/2 from the string 'a=1; b=2': ;SELECT * FROM regexp_capture('a=1; b=2', '(\w+)=(\d+)') @@ -2274,10 +2309,11 @@ Parameters str The string to test against the regular expression See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_replace(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), - startswith(), strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_replace(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), upper(), xpath() Examples #1 To capture the digits from the string '123': ;SELECT regexp_match('(\d+)', '123') @@ -2304,10 +2340,11 @@ Parameters backslash followed by the number of the group, starting with 1. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_match(), replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_match(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), upper(), xpath() Examples #1 To replace the word at the start of the string 'Hello, World!' with 'Goodbye': ;SELECT regexp_replace('Hello, World!', '^(\w+)', 'Goodbye') @@ -2328,10 +2365,11 @@ Parameters replacement The string to replace any occurrences of the old string with. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replicate(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To replace the string 'x' with 'z' in 'abc': ;SELECT replace('abc', 'x', 'z') @@ -2349,10 +2387,11 @@ Parameters N The number of times to replicate the string. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), reverse(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), reverse(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Example #1 To repeat the string 'abc' three times: ;SELECT replicate('abc', 3) @@ -2365,10 +2404,11 @@ Parameter str The string to reverse. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), rightstr(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), rightstr(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Example #1 To reverse the string 'abc': ;SELECT reverse('abc') @@ -2383,10 +2423,11 @@ Parameters N The number of characters from the right side of the string to return. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rtrim(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rtrim(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To get the last character of the string 'abc': ;SELECT rightstr('abc', 1) @@ -2443,10 +2484,11 @@ Parameters chars The characters to trim. Defaults to spaces. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), spooky_hash(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), sparkline(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To trim the whitespace from the end of the string 'abc ': ;SELECT rtrim('abc ') @@ -2479,16 +2521,37 @@ Examples +Synopsis + sparkline(value, [upper]) -- An aggregate function to convert numeric values + to a sparkline bar chart +Parameters + value The numeric values to chart + upper The upper bound of the numeric range. If not provided, the default + is derived from all of the provided values +See Also + char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), spooky_hash(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() +Example +#1 To chart the values in a JSON array: + ;SELECT sparkline(value) FROM json_each('[0, 1, 2, 3, 4, 5, 6, 7, 8]') + + + Synopsis spooky_hash(str, ...) -- Compute the hash value for the given arguments. Parameter str The string to hash See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), startswith(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), startswith(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To produce a hash for the string 'Hello, World!': ;SELECT spooky_hash('Hello, World!') @@ -2557,10 +2620,11 @@ Parameters prefix The prefix to check in the string See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), - strfilter(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), strfilter(), substr(), trim(), + unicode(), upper(), xpath() Examples #1 To test if the string 'foobar' starts with 'foo': ;SELECT startswith('foobar', 'foo') @@ -2579,10 +2643,11 @@ Parameters include The characters to include in the result See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), - startswith(), substr(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), substr(), trim(), + unicode(), upper(), xpath() Example #1 To get the 'b', 'c', and 'd' characters from the string 'abcabc': ;SELECT strfilter('abcabc', 'bcd') @@ -2627,10 +2692,11 @@ Parameters the characters before the start are returned. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), - startswith(), strfilter(), trim(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), trim(), + unicode(), upper(), xpath() Examples #1 To get the substring starting at the second character until the end of the string 'abc' : @@ -2719,8 +2785,8 @@ Examples #2 To group log messages into five minute buckets and count them: - ;SELECT timeslice(log_time, '5m') AS slice, count(*) FROM lnav_example_log GROUP BY - slice + ;SELECT timeslice(log_time_msecs, '5m') AS slice, count(*) FROM lnav_example_log GROUP + BY slice @@ -2753,10 +2819,11 @@ Parameters chars The characters to trim. Defaults to spaces. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), - startswith(), strfilter(), substr(), unicode(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + unicode(), upper(), xpath() Examples #1 To trim whitespace from the start and end of the string ' abc ': ;SELECT trim(' abc ') @@ -2790,10 +2857,11 @@ Parameter X The string to examine. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), - startswith(), strfilter(), substr(), trim(), upper(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), upper(), xpath() Example #1 To get the unicode code point for the first character of 'abc': ;SELECT unicode('abc') @@ -2813,10 +2881,11 @@ Parameter str The string to convert. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), - startswith(), strfilter(), substr(), trim(), unicode(), xpath() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), xpath() Example #1 To uppercase the string 'aBc': ;SELECT upper('aBc') @@ -2836,10 +2905,11 @@ Results node_text The node's text value. See Also char(), charindex(), endswith(), extract(), group_concat(), group_spooky_hash(), - instr(), leftstr(), length(), lower(), ltrim(), padc(), padl(), padr(), - printf(), proper(), regexp_capture(), regexp_match(), regexp_replace(), - replace(), replicate(), reverse(), rightstr(), rtrim(), spooky_hash(), - startswith(), strfilter(), substr(), trim(), unicode(), upper() + humanize_file_size(), instr(), leftstr(), length(), lower(), ltrim(), padc(), + padl(), padr(), printf(), proper(), regexp_capture(), regexp_match(), + regexp_replace(), replace(), replicate(), reverse(), rightstr(), rtrim(), + sparkline(), sparkline(), spooky_hash(), startswith(), strfilter(), substr(), + trim(), unicode(), upper() Examples #1 To select the XML nodes on the path '/abc/def': ;SELECT * FROM xpath('/abc/def', 'HelloBye') diff --git a/test/formats/scripts/multiline-echo.lnav b/test/formats/scripts/multiline-echo.lnav index 5a99525a..69130885 100644 --- a/test/formats/scripts/multiline-echo.lnav +++ b/test/formats/scripts/multiline-echo.lnav @@ -1,3 +1,3 @@ ;SELECT 'World' as name -:eval :echo Hello, ${name}! +:echo Hello, ${name}! Goodbye, ${name}! diff --git a/test/nested.lnav b/test/nested.lnav index 763389d0..836e030c 100644 --- a/test/nested.lnav +++ b/test/nested.lnav @@ -1,2 +1,2 @@ -:eval :echo nested here $0 $1 $2 +:echo nested here $0 $1 $2 diff --git a/test/test_bookmarks.cc b/test/test_bookmarks.cc index 431ccf3e..9ab0a12b 100644 --- a/test/test_bookmarks.cc +++ b/test/test_bookmarks.cc @@ -41,16 +41,43 @@ int main(int argc, char *argv[]) int lpc, retval = EXIT_SUCCESS; bookmark_vector bv, bv_cp; - bv.insert_once(vis_line_t(1)); - bv.insert_once(vis_line_t(1)); + bv.insert_once(vis_line_t(2)); + bv.insert_once(vis_line_t(2)); assert(bv.size() == 1); + bv.insert_once(vis_line_t(4)); bv.insert_once(vis_line_t(3)); - bv.insert_once(vis_line_t(2)); - assert(bv[0] == 1); - assert(bv[1] == 2); - assert(bv[2] == 3); - + assert(bv[0] == 2); + assert(bv[1] == 3); + assert(bv[2] == 4); + + { + auto range = bv.equal_range(0_vl, 5_vl); + + assert(range.first != range.second); + assert(*range.first == 2_vl); + ++range.first; + assert(range.first != range.second); + assert(*range.first == 3_vl); + ++range.first; + assert(range.first != range.second); + assert(*range.first == 4_vl); + ++range.first; + assert(range.first == range.second); + } + + { + auto range = bv.equal_range(0_vl, 1_vl); + + assert(range.first == range.second); + } + + { + auto range = bv.equal_range(10_vl, 10_vl); + + assert(range.first == range.second); + } + bv.clear(); assert(bv.next(vis_line_t(0)) == -1); assert(bv.prev(vis_line_t(0)) == -1); diff --git a/test/test_cmds.sh b/test/test_cmds.sh index ded6565c..38984579 100644 --- a/test/test_cmds.sh +++ b/test/test_cmds.sh @@ -60,12 +60,15 @@ EOF run_test ${lnav_test} -n -d /tmp/lnav.err \ -c ";SELECT 1 AS c1, 'Hello, World!' AS c2" \ - -c ":write-cols-to -" \ + -c ":write-table-to -" \ "${test_dir}/logfile_access_log.*" check_output "writing columns does not work?" <