[markdown] add support for going to an anchor

pull/1031/head
Tim Stack 2 years ago
parent fd956c66d3
commit 87791ec94b

@ -45,6 +45,7 @@ jobs:
--exclude src/ww898
--exclude src/yajl
--exclude test
--exclude src/data_scanner_re.cc
--gcov-options '\-lp'
build:

12
NEWS

@ -18,6 +18,16 @@ lnav v0.11.0:
TEXT view. The breadcrumb bar at the top will also be updated
depending on the section of the document that you are in and you
can use it to jump to different parts of the doc.
* The ":goto" command will now accept anchor links (i.e. #section-id)
as an argument when the text file being viewed has sections. You
can also specify an anchor when opening a file by appending
"#<link-name>". For example, "README.md#screenshot".
* Log message comments are now treated as markdown and rendered
accordingly in the overlay. Multi-line comments are now supported
as well.
* Metadata embedded in files can now be accessed by the
"lnav_file_metadata" table. Currently, only the front-matter in
Markdown files is supported.
* Added an integration with regex101.com to make it easier to edit
log message regular expressions. Using the new "management CLI"
(activated by the -m option), a log format can be created from
@ -88,6 +98,8 @@ lnav v0.11.0:
* Added an lnav_views_echo table that is a real SQLite table that
you can create TRIGGERs on in order to perform actions when
scrolling in a view.
* Added a "yaml_to_json()" SQL function that converts a YAML
document to the equivalent JSON.
Breaking Changes:
* Formats definitions are now checked to ensure that values have a

@ -1,9 +1,11 @@
<!-- This is a comment for testing purposes -->
[![Build](https://github.com/tstack/lnav/workflows/ci-build/badge.svg)](https://github.com/tstack/lnav/actions?query=workflow%3Aci-build)
[![Docs](https://readthedocs.org/projects/lnav/badge/?version=latest&style=plastic)](https://docs.lnav.org)
[![Coverage Status](https://coveralls.io/repos/github/tstack/lnav/badge.svg?branch=master)](https://coveralls.io/github/tstack/lnav?branch=master)
[![lnav](https://snapcraft.io//lnav/badge.svg)](https://snapcraft.io/lnav)
[![lnav](https://snapcraft.io/lnav/badge.svg)](https://snapcraft.io/lnav)
[<img src="https://assets-global.website-files.com/6257adef93867e50d84d30e2/62594fddd654fc29fcc07359_cb48d2a8d4991281d7a6a95d2f58195e.svg" height="20">](https://discord.gg/erBPnKwz7R)
[<img src="https://assets-global.website-files.com/6257adef93867e50d84d30e2/62594fddd654fc29fcc07359_cb48d2a8d4991281d7a6a95d2f58195e.svg" height="20"/>](https://discord.gg/erBPnKwz7R)
_This is the source repository for **lnav**, visit [https://lnav.org](https://lnav.org) for a high level overview._

@ -0,0 +1,54 @@
import datetime
import os
import random
import shutil
import sys
MSGS = [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
]
GLOG_DATE_FMT = "%Y%m%d %H:%M:%S"
START_TIME = datetime.datetime.fromtimestamp(1490191111)
try:
shutil.rmtree("/tmp/demo")
os.makedirs("/tmp/demo")
except OSError:
pass
PIDS = [
"123",
"123",
"123",
"121",
"124",
"123",
"61456",
"61456",
"61457",
]
LOG_LOCS = [
"demo.cc:123",
"demo.cc:352",
"loader.cc:13",
"loader.cc:552",
"blaster.cc:352",
"blaster.cc:112",
"blaster.cc:6782",
]
CURR_TIME = START_TIME
for _index in range(0, int(sys.argv[1])):
CURR_TIME += datetime.timedelta(seconds=random.randrange(1, 22))
print("I%s.%06d %s %s] %s" % (
CURR_TIME.strftime(GLOG_DATE_FMT),
random.randrange(0, 100000),
random.choice(PIDS),
random.choice(LOG_LOCS),
random.choice(MSGS)))

@ -0,0 +1,18 @@
{
"$schema": "https://lnav.org/schemas/config-v1.schema.json",
"global": {
"lnav_tutorial_name": "tutorial1"
},
"ui": {
"keymap-defs": {
"default": {
"x79": {
"command": "|lnav-tutorial-key-handler next"
},
"x59": {
"command": "|lnav-tutorial-key-handler prev"
}
}
}
}
}

@ -0,0 +1,29 @@
;SELECT filepath AS tutorial_path FROM lnav_file
WHERE filepath GLOB '*/tutorial1/index.md' LIMIT 1
;SELECT CASE
WHEN $1 = 'next' AND
step < (SELECT max(step) FROM lnav_tutorial_steps WHERE name = $lnav_tutorial_name)
THEN step + 1
WHEN $1 = 'prev' AND step > 1 THEN step - 1
ELSE step
END AS new_step
FROM lnav_tutorial_step WHERE name = $lnav_tutorial_name
;SELECT CASE
WHEN $1 = 'next' AND
step = (SELECT max(step) FROM lnav_tutorial_steps WHERE name = $lnav_tutorial_name)
THEN '#conclusion'
ELSE '#step-' || $new_step
END AS new_anchor
FROM lnav_tutorial_step WHERE name = $lnav_tutorial_name
;UPDATE lnav_tutorial_step SET step = $new_step WHERE name = $lnav_tutorial_name
;UPDATE lnav_views SET top_meta = json_object(
'file', $tutorial_path,
'anchor', $new_anchor
)
WHERE name = 'text'
:switch-to-view text
;UPDATE lnav_views SET top = 0
WHERE name = 'log'
;REPLACE INTO lnav_user_notifications (id, views, message)
SELECT * FROM lnav_tutorial_log_notification;

@ -0,0 +1,141 @@
CREATE TABLE lnav_tutorial_step
(
name TEXT NOT NULL PRIMARY KEY,
step INTEGER NOT NULL
);
INSERT INTO lnav_tutorial_step
VALUES ('tutorial1', 1);
CREATE TABLE lnav_tutorial_steps
(
name TEXT NOT NULL,
step INTEGER NOT NULL,
achievements TEXT NOT NULL,
PRIMARY KEY (name, step)
);
CREATE TABLE IF NOT EXISTS lnav_tutorial_progress
(
name TEXT NOT NULL,
step INTEGER NOT NULL,
achieved TEXT NOT NULL,
PRIMARY KEY (name, step, achieved)
);
CREATE TABLE IF NOT EXISTS lnav_tutorial_lines
(
name TEXT NOT NULL,
step INTEGER NOT NULL,
view_ptr TEXT NOT NULL,
view_value TEXT NOT NULL,
achievement TEXT NOT NULL,
log_comment TEXT
);
CREATE TRIGGER IF NOT EXISTS add_tutorial_data
AFTER INSERT
ON lnav_events
WHEN jget(new.content, '/$schema') = 'https://lnav.org/event-file-format-detected-v1.schema.json' AND
jget(new.content, '/format') = 'text/markdown'
BEGIN
INSERT INTO lnav_tutorial_steps
SELECT jget(tutorial_meta, '/name'),
key + 1,
value
FROM (SELECT yaml_to_json(lnav_file_metadata.content) AS tutorial_meta
FROM lnav_file_metadata
WHERE filepath = jget(new.content, '/filename')) AS meta_content,
json_each(jget(meta_content.tutorial_meta, '/steps'));
REPLACE INTO lnav_tutorial_lines
SELECT name,
step,
jget(value, '/view_ptr'),
jget(value, '/view_value'),
key,
jget(value, '/comment')
FROM lnav_tutorial_steps,
json_each(achievements)
WHERE jget(value, '/view_ptr') IS NOT NULL;
REPLACE INTO lnav_user_notifications (id, views, message)
SELECT *
FROM lnav_tutorial_log_notification;
END;
CREATE TRIGGER IF NOT EXISTS lnav_tutorial_view_listener UPDATE OF top
ON lnav_views_echo
WHEN new.name = 'log'
BEGIN
INSERT OR IGNORE INTO lnav_tutorial_progress
SELECT lnav_tutorial_lines.name,
lnav_tutorial_lines.step,
achievement
FROM lnav_tutorial_step,
lnav_tutorial_lines
WHERE lnav_tutorial_step.step = lnav_tutorial_lines.step
AND jget(json_object('top', new.top,
'left', new.left,
'search', new.search),
view_ptr) = view_value;
UPDATE all_logs
SET log_comment = (SELECT log_comment
FROM lnav_tutorial_step,
lnav_tutorial_lines
WHERE lnav_tutorial_step.step = lnav_tutorial_lines.step
AND lnav_tutorial_lines.log_comment IS NOT NULL
AND jget(json_object('top', new.top,
'left', new.left,
'search', new.search), view_ptr) = view_value)
WHERE log_line = new.top
AND log_comment IS NULL;
END;
CREATE TABLE lnav_tutorial_message
(
msgid INTEGER PRIMARY KEY,
msg TEXT
);
CREATE VIEW lnav_tutorial_current_achievements AS
SELECT key AS achievement, value
FROM lnav_tutorial_step,
lnav_tutorial_steps, json_each(lnav_tutorial_steps.achievements)
WHERE lnav_tutorial_step.step = lnav_tutorial_steps.step;
CREATE VIEW lnav_tutorial_current_progress AS
SELECT achieved
FROM lnav_tutorial_step,
lnav_tutorial_progress
WHERE lnav_tutorial_step.step = lnav_tutorial_progress.step;
CREATE VIEW lnav_tutorial_remaining_achievements AS
SELECT *
FROM lnav_tutorial_current_achievements
WHERE achievement NOT IN (SELECT * FROM lnav_tutorial_current_progress);
CREATE VIEW lnav_tutorial_log_notification AS
SELECT *
FROM (SELECT 'org.lnav.tutorial.log' AS id, '["log"]' AS views, jget(value, '/notification') AS message
FROM lnav_tutorial_remaining_achievements
UNION ALL
SELECT 'org.lnav.tutorial.log' AS id,
'["log"]' AS views,
'Press y to go to the next step in the tutorial' AS message)
LIMIT 1;
CREATE TRIGGER IF NOT EXISTS lnav_tutorial_progress_listener
AFTER INSERT
ON lnav_tutorial_progress
BEGIN
DELETE FROM lnav_user_notifications WHERE id = 'org.lnav.tutorial.log';
REPLACE INTO lnav_user_notifications (id, views, message)
SELECT *
FROM lnav_tutorial_log_notification;
END;
REPLACE INTO lnav_user_notifications (id, views, message)
VALUES ('org.lnav.tutorial.text', '["text"]', 'Press "q" to go to the log view')

@ -0,0 +1,96 @@
---
name: tutorial1
steps:
- move-to-error:
description: "Move to an error"
view_ptr: /top
view_value: 6
notification: "Press e/Shift+E to move through the errors"
comment: |
You found the error!
[Log formats](https://docs.lnav.org/en/latest/formats.html#format-file-reference)
can define the log levels for a given message.
The [theme](https://docs.lnav.org/en/latest/config.html#theme-definitions) defines
how the levels are displayed.
move-to-warning:
description: "Move to a warning"
notification: "Press w/Shift+W to move through the warnings"
view_ptr: /top
view_value: 3
comment: |
You found the warning! The scrollbar on the right is highlighted
to show the position of
<span class="-lnav_log-level-styles_warning">warnings</span> and
<span class="-lnav_log-level-styles_error">errors</span> in this
view.
- search-for-term:
description: "Search for something"
notification: "Press / to search for '1AF9...'"
view_ptr: /search
view_value: 1AF9293A-F42D-4318-BCDF-60234B240955
move-to-next-hit:
description: "Move to the next hit"
notification: "Press n/Shift+N to move through the search hits"
view_ptr: /top
view_value: 53
comment: |
The matching text in a search is highlighted in
<span class="-lnav_styles_search">reverse-video</span>.
However, the text is not always on-screen, so the bar on the
left will also be highlighted. You can then press `>` to
move right to the next (horizontal) search hit. Pressing
`<` will move left to the previous (horizontal) hit or all
the way back to the start of the line.
move-right:
description: "Move to the right"
notification: "Press > to move horizontally to view the search hit"
view_ptr: /left
view_value: 150
---
# Tutorial 1
Welcome to the first _interactive_ **lnav** tutorial!
This tutorial will guide you through the basics of navigating log files.
## Step 1
Finding errors quickly is one of the main use-cases for **lnav**. To
make that quick and easy, **lnav** parses the log messages in log files
as they are loaded and builds indexes of the errors and warnings. You
can then use the following hotkeys to jump to them in the log view:
| Key | Action |
|-----------|----------------------------------------------------------------------------------|
| `e` | Move to the next <span class="-lnav_log-level-styles_error">error</span> |
| `Shift+E` | Move to the previous <span class="-lnav_log-level-styles_error">error</span> |
| `w` | Move to the next <span class="-lnav_log-level-styles_warning">warning</span> |
| `Shift+W` | Move to the previous <span class="-lnav_log-level-styles_warning">warning</span> |
To complete this step in the tutorial, you'll need to navigate to the
errors and warnings in the sample log file. You can check the upper-right
status bar for tips on what you need to do next. Now, press `q` to switch
to the log view and begin navigating the sample log file.
## Step 2
To search for text in files, you can press `/` to enter the search
prompt. To make it easier to search for text that is on-screen, you
can press `TAB` to complete values that are shown on screen. For
example, to search for the UUID "1AF9293A-F42D-4318-BCDF-60234B240955"
that is in one of the error messages, you can enter "1AF9" and then
press `TAB` to complete the rest of the UUID.
Press `q` to switch to the log view and try searching for the UUID.
## Conclusion
That's all for now, visit https://lnav.org/downloads to find how to
download/install a copy of lnav for your system. The full documentation
is available at https://docs.lnav.org.
## Colophon
The source for this tutorial is available here:
https://github.com/tstack/lnav/tree/master/docs/tutorial/tutorial1

@ -0,0 +1,100 @@
I20170322 06:58:47.082758 61456 blaster.cc:112] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 06:58:58.019562 121 demo.cc:123] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 06:58:59.059175 123 blaster.cc:6782] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
W20170322 06:59:16.062826 61456 demo.cc:352] Ut enim ad minim veniam, quis nostrud exercitation 1AF9293A-F42D-4318-BCDF-60234B240955 ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 06:59:28.084062 124 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 06:59:32.053551 123 loader.cc:13] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
E20170322 06:59:53.084969 123 loader.cc:552] Excepteur sint occaecat cupidatat non proident 1AF9293A-F42D-4318-BCDF-60234B240955, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:00:00.096693 123 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:00:03.049849 123 demo.cc:352] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:00:08.070575 123 blaster.cc:6782] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:00:23.019849 123 blaster.cc:352] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:00:28.022692 61457 loader.cc:552] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:00:29.058438 61456 blaster.cc:352] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:00:30.028483 123 loader.cc:13] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:00:49.070676 123 demo.cc:352] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:00:56.095214 123 loader.cc:552] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
W20170322 07:01:14.042785 123 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:01:31.083704 123 blaster.cc:112] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:01:44.013733 121 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:01:55.024085 121 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:02:02.027811 121 blaster.cc:6782] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:02:14.022939 61456 blaster.cc:112] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:02:30.035925 123 loader.cc:13] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:02:49.024985 123 loader.cc:13] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:03:09.056478 121 blaster.cc:6782] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:03:15.023777 123 demo.cc:352] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:03:32.066107 123 blaster.cc:352] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:03:48.028662 124 blaster.cc:6782] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:03:54.027078 123 demo.cc:123] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:04:09.041478 123 demo.cc:123] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:04:14.068162 121 blaster.cc:6782] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:04:28.099513 124 blaster.cc:112] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:04:40.063473 124 loader.cc:552] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:04:50.024030 123 loader.cc:552] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:04:56.081415 121 blaster.cc:352] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:05:14.096304 123 blaster.cc:352] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:05:21.086331 123 demo.cc:352] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:05:33.039503 123 loader.cc:13] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:05:43.092657 124 blaster.cc:6782] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:05:59.002644 123 demo.cc:123] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:06:01.022102 123 demo.cc:352] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:06:22.005675 123 blaster.cc:6782] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:06:37.088974 123 blaster.cc:112] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:06:44.043938 61457 demo.cc:123] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:06:47.060703 123 loader.cc:13] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:06:49.052185 61456 demo.cc:123] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:06:52.074424 61457 demo.cc:352] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:07:02.063191 123 demo.cc:123] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:07:10.030327 61457 blaster.cc:112] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:07:11.011338 123 loader.cc:13] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:07:27.078391 123 blaster.cc:352] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:07:41.061684 123 blaster.cc:112] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:07:53.076558 121 blaster.cc:112] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:08:04.055174 121 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur: 1AF9293A-F42D-4318-BCDF-60234B240955
I20170322 07:08:18.046756 123 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:08:28.004198 123 loader.cc:552] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:08:36.032193 61457 blaster.cc:352] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:08:50.028964 61456 loader.cc:13] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:08:56.074576 124 blaster.cc:112] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:08:57.090258 123 loader.cc:13] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:09:00.067690 121 blaster.cc:352] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:09:19.036483 61457 blaster.cc:112] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:09:40.048046 123 blaster.cc:352] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:09:52.051526 123 loader.cc:13] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:10:11.003845 61456 loader.cc:552] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:10:27.094133 123 blaster.cc:6782] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:10:43.027892 121 blaster.cc:352] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:10:57.078489 124 demo.cc:352] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:11:09.014685 123 demo.cc:123] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:11:18.029203 61456 blaster.cc:352] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:11:24.067068 121 demo.cc:123] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:11:38.053891 61456 loader.cc:552] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:11:59.027292 61457 blaster.cc:112] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:12:10.069054 61457 loader.cc:13] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:12:22.018053 123 loader.cc:552] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:12:39.000436 123 demo.cc:352] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:12:53.009916 123 loader.cc:13] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:13:13.051890 121 demo.cc:123] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:13:24.076724 123 demo.cc:123] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:13:34.075980 123 blaster.cc:6782] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:13:35.096130 61456 blaster.cc:6782] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:13:49.087790 121 demo.cc:123] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:14:08.033671 61457 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:14:23.091358 61456 blaster.cc:112] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:14:35.088133 61456 demo.cc:123] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:14:55.005577 123 blaster.cc:352] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:14:58.008392 61457 demo.cc:123] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:15:05.004789 123 loader.cc:552] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:15:07.070013 123 blaster.cc:6782] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:15:08.012805 123 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:15:25.042509 61456 loader.cc:552] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
E20170322 07:15:32.027688 123 blaster.cc:6782] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:15:41.020299 61456 blaster.cc:6782] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:15:42.021039 124 loader.cc:552] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:15:59.063918 123 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
I20170322 07:16:19.082250 123 loader.cc:552] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:16:20.026445 61457 loader.cc:13] Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
I20170322 07:16:41.048447 123 blaster.cc:6782] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
I20170322 07:16:52.097215 61456 demo.cc:123] Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
I20170322 07:17:01.020663 61456 blaster.cc:112] Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

@ -406,6 +406,7 @@ add_library(
sequence_matcher.cc
shlex.cc
sqlite-extension-func.cc
static_file_vtab.cc
statusview_curses.cc
string-extension-functions.cc
sysclip.cc
@ -435,6 +436,7 @@ add_library(
xml_util.cc
xpath_vtab.cc
xterm_mouse.cc
yaml-extension-functions.cc
third-party/md4c/md4c.c
third-party/sqlite/ext/series.c
third-party/sqlite/ext/dbdump.c
@ -525,6 +527,7 @@ add_library(
sqlitepp.hh
sql_help.hh
sql_util.hh
static_file_vtab.hh
strong_int.hh
sysclip.hh
sysclip.cfg.hh
@ -591,7 +594,10 @@ add_library(
set(lnav_SRCS lnav.cc)
target_include_directories(diag PUBLIC . fmtlib ${CMAKE_CURRENT_BINARY_DIR}
third-party third-party/base64/include)
third-party
third-party/base64/include
third-party/rapidyaml
)
target_link_libraries(
diag

@ -122,6 +122,7 @@ AM_CPPFLAGS = \
-I$(srcdir)/fmtlib \
-I$(srcdir)/third-party \
-I$(srcdir)/third-party/base64/include \
-I$(srcdir)/third-party/rapidyaml \
-I$(top_srcdir)/src/third-party/scnlib/include \
-Wall \
$(CODE_COVERAGE_CPPFLAGS) \
@ -284,6 +285,7 @@ noinst_HEADERS = \
sql_help.hh \
sql_util.hh \
sqlite-extension-func.hh \
static_file_vtab.hh \
styling.hh \
statusview_curses.hh \
strong_int.hh \
@ -436,6 +438,7 @@ libdiag_a_SOURCES = \
spectro_source.cc \
sqlitepp.cc \
sqlite-extension-func.cc \
static_file_vtab.cc \
statusview_curses.cc \
string-extension-functions.cc \
styling.cc \
@ -461,6 +464,7 @@ libdiag_a_SOURCES = \
xml_util.cc \
xpath_vtab.cc \
xterm_mouse.cc \
yaml-extension-functions.cc \
spookyhash/SpookyV2.cpp
PLUGIN_SRCS = \

@ -34,6 +34,7 @@
#include <exception>
#include <iterator>
#include <string>
#include <utility>
#include <assert.h>
@ -238,10 +239,15 @@ public:
const char* begin() const { return this->ab_buffer; }
void push_back(char ch)
auto_buffer& push_back(char ch)
{
if (this->ab_size == this->ab_capacity) {
this->expand_by(256);
}
this->ab_buffer[this->ab_size] = ch;
this->ab_size += 1;
return *this;
}
void pop_back() { this->ab_size -= 1; }
@ -370,6 +376,8 @@ public:
this->expand_to(this->ab_capacity + amount);
}
std::string to_string() const { return {this->ab_buffer, this->ab_size}; }
private:
auto_buffer(char* buffer, size_t capacity)
: ab_buffer(buffer), ab_capacity(capacity)

@ -266,8 +266,11 @@ struct string_fragment {
template<typename P>
string_fragment find_left_boundary(size_t start, P&& predicate) const
{
assert((int) start < this->length());
assert((int) start <= this->length());
if (start > 0 && start == this->length()) {
start -= 1;
}
while (start > 0) {
if (predicate(this->data()[start])) {
start += 1;

@ -1,6 +1,6 @@
/*
/*
Mathieu Stefani, 03 mai 2016
This header provides a Result type that can be used to replace exceptions in code
that has to handle error.
@ -774,7 +774,7 @@ struct Result {
{
if (!isOk()) {
::fprintf(stderr, "%s\n", str);
std::terminate();
abort();
}
return expect_impl(std::is_same<T, void>());
}
@ -887,7 +887,7 @@ struct Result {
}
::fprintf(stderr, "Attempting to unwrap an error Result\n");
std::terminate();
abort();
}
template<typename U = T>
@ -901,7 +901,7 @@ struct Result {
}
::fprintf(stderr, "Attempting to unwrap an error Result\n");
std::terminate();
abort();
}
template<typename U = T>
@ -913,7 +913,7 @@ struct Result {
}
::fprintf(stderr, "Attempting to unwrap an error Result\n");
std::terminate();
abort();
}
E unwrapErr() const
@ -923,7 +923,7 @@ struct Result {
}
::fprintf(stderr, "Attempting to unwrapErr an ok Result\n");
std::terminate();
abort();
}
private:

@ -57,19 +57,46 @@ scrub_to_utf8(char* buffer, size_t length)
}
}
size_t
unquote(char* dst, const char* str, size_t len)
void
quote_content(auto_buffer& buf, const string_fragment& sf, char quote_char)
{
if (str[0] == 'r' || str[0] == 'u') {
str += 1;
len -= 1;
for (char ch : sf) {
if (ch == quote_char) {
buf.push_back('\\').push_back(ch);
continue;
}
switch (ch) {
case '\\':
buf.push_back('\\').push_back('\\');
break;
case '\n':
buf.push_back('\\').push_back('n');
break;
case '\t':
buf.push_back('\\').push_back('t');
break;
case '\r':
buf.push_back('\\').push_back('r');
break;
case '\a':
buf.push_back('\\').push_back('a');
break;
case '\b':
buf.push_back('\\').push_back('b');
break;
default:
buf.push_back(ch);
break;
}
}
char quote_char = str[0];
size_t index = 0;
}
require(str[0] == '\'' || str[0] == '"');
size_t
unquote_content(char* dst, const char* str, size_t len, char quote_char)
{
size_t index = 0;
for (size_t lpc = 1; lpc < (len - 1); lpc++, index++) {
for (size_t lpc = 0; lpc < len; lpc++, index++) {
dst[index] = str[lpc];
if (str[lpc] == quote_char) {
lpc += 1;
@ -96,6 +123,20 @@ unquote(char* dst, const char* str, size_t len)
return index;
}
size_t
unquote(char* dst, const char* str, size_t len)
{
if (str[0] == 'r' || str[0] == 'u') {
str += 1;
len -= 1;
}
char quote_char = str[0];
require(str[0] == '\'' || str[0] == '"');
return unquote_content(dst, &str[1], len - 2, quote_char);
}
size_t
unquote_w3c(char* dst, const char* str, size_t len)
{

@ -35,6 +35,8 @@
#include <string.h>
#include "auto_mem.hh"
#include "intern_string.hh"
#include "ww898/cp_utf8.hpp"
void scrub_to_utf8(char* buffer, size_t length);
@ -45,6 +47,12 @@ is_line_ending(char ch)
return ch == '\r' || ch == '\n';
}
void quote_content(auto_buffer& buf,
const string_fragment& sf,
char quote_char);
size_t unquote_content(char* dst, const char* str, size_t len, char quote_char);
size_t unquote(char* dst, const char* str, size_t len);
size_t unquote_w3c(char* dst, const char* str, size_t len);

@ -63,48 +63,6 @@ SELECT count(*) AS total, min(log_line) AS log_line, log_msg_format
ORDER BY total DESC
)";
struct bind_visitor {
bind_visitor(sqlite3_stmt* stmt, int index) : bv_stmt(stmt), bv_index(index)
{
}
void operator()(const std::string& str) const
{
sqlite3_bind_text(this->bv_stmt,
this->bv_index,
str.c_str(),
str.size(),
SQLITE_TRANSIENT);
}
void operator()(const string_fragment& str) const
{
sqlite3_bind_text(this->bv_stmt,
this->bv_index,
str.data(),
str.length(),
SQLITE_TRANSIENT);
}
void operator()(null_value_t) const
{
sqlite3_bind_null(this->bv_stmt, this->bv_index);
}
void operator()(int64_t value) const
{
sqlite3_bind_int64(this->bv_stmt, this->bv_index, value);
}
void operator()(double value) const
{
sqlite3_bind_double(this->bv_stmt, this->bv_index, value);
}
sqlite3_stmt* bv_stmt;
int bv_index;
};
int
sql_progress(const struct log_cursor& lc)
{
@ -272,12 +230,12 @@ bind_sql_parameters(exec_context& ec, sqlite3_stmt* stmt)
}
if ((local_var = lvars.find(&name[1])) != lvars.end()) {
mapbox::util::apply_visitor(bind_visitor(stmt, lpc + 1),
local_var->second);
mapbox::util::apply_visitor(
sqlitepp::bind_visitor(stmt, lpc + 1), local_var->second);
retval[name] = local_var->second;
} else if ((global_var = gvars.find(&name[1])) != gvars.end()) {
mapbox::util::apply_visitor(bind_visitor(stmt, lpc + 1),
global_var->second);
mapbox::util::apply_visitor(
sqlitepp::bind_visitor(stmt, lpc + 1), global_var->second);
retval[name] = global_var->second;
} else if ((env_value = getenv(&name[1])) != nullptr) {
sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
@ -620,6 +578,9 @@ execute_file_contents(exec_context& ec,
line_number += 1;
if (trim(line.in()).empty()) {
if (multiline && cmdline) {
cmdline = cmdline.value() + "\n";
}
continue;
}
if (line[0] == '#') {
@ -920,7 +881,7 @@ execute_init_commands(
.with_fd(std::move(fd_copy))
.with_include_in_session(false)
.with_detect_format(false);
lnav_data.ld_files_to_front.emplace_back(OUTPUT_NAME, 0);
lnav_data.ld_files_to_front.emplace_back(OUTPUT_NAME, 0_vl);
if (lnav_data.ld_rl_view != nullptr) {
lnav_data.ld_rl_view->set_alt_value(
@ -1063,7 +1024,7 @@ pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd)
.with_fd(pp->get_fd())
.with_include_in_session(false)
.with_detect_format(false);
lnav_data.ld_files_to_front.emplace_back(desc, 0);
lnav_data.ld_files_to_front.emplace_back(desc, 0_vl);
if (lnav_data.ld_rl_view != nullptr) {
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_1(X, "to close the file"));
}

@ -1,4 +1,4 @@
/* Generated by re2c 3.0 on Sat Aug 20 21:19:55 2022 */
/* Generated by re2c 3.0 on Mon Aug 22 22:00:24 2022 */
#line 1 "../../lnav/src/data_scanner_re.re"
/**
* Copyright (c) 2015, Timothy Stack

@ -87,6 +87,10 @@ struct hier_node {
template<typename F>
static void depth_first(hier_node* root, F func)
{
if (root == nullptr) {
return;
}
for (auto& child : root->hn_children) {
depth_first(child.get(), func);
}

@ -35,6 +35,7 @@
#include "config.h"
#include "log_format_ext.hh"
#include "log_vtab_impl.hh"
#include "md2attr_line.hh"
#include "readline_highlighters.hh"
#include "relative_time.hh"
#include "vtab_module.hh"
@ -459,20 +460,41 @@ field_overlay_source::build_meta_line(const listview_curses& lv,
if (!line_meta.bm_comment.empty()) {
const auto* lead = line_meta.bm_tags.empty() ? " \u2514 " : " \u251c ";
md2attr_line mdal;
attr_line_t al;
al.with_string(lead).append(lnav::roles::comment(line_meta.bm_comment));
al.insert(0, filename_width, ' ');
if (tc != nullptr) {
auto hl = tc->get_highlights();
auto hl_iter = hl.find({highlight_source_t::PREVIEW, "search"});
auto parse_res = md4cpp::parse(line_meta.bm_comment, mdal);
if (parse_res.isOk()) {
al = parse_res.unwrap();
} else {
log_error("%d: cannot convert comment to markdown: %s",
(int) row,
parse_res.unwrapErr().c_str());
al = line_meta.bm_comment;
}
if (hl_iter != hl.end()) {
hl_iter->second.annotate(al, filename_width);
auto comment_lines = al.rtrim().split_lines();
for (size_t lpc = 0; lpc < comment_lines.size(); lpc++) {
auto& comment_line = comment_lines[lpc];
if (lpc == 0 && comment_line.empty()) {
continue;
}
comment_line.with_attr_for_all(VC_ROLE.value(role_t::VCR_COMMENT));
comment_line.insert(
0, lpc == comment_lines.size() - 1 ? lead : " \u2502 ");
comment_line.insert(0, filename_width, ' ');
if (tc != nullptr) {
auto hl = tc->get_highlights();
auto hl_iter = hl.find({highlight_source_t::PREVIEW, "search"});
if (hl_iter != hl.end()) {
hl_iter->second.annotate(comment_line, filename_width);
}
}
}
dst.emplace_back(al);
dst.emplace_back(comment_line);
}
}
if (!line_meta.bm_tags.empty()) {
attr_line_t al;

@ -66,6 +66,8 @@ child_poller::poll(file_collection& fc)
return child_poll_result_t::ALIVE;
},
[this, &fc](auto_pid<process_state::finished>& finished) {
require(this->cp_finalizer);
this->cp_finalizer(fc, finished);
return child_poll_result_t::FINISHED;
});

@ -88,16 +88,20 @@ public:
auto_pid<process_state::finished>&)> finalizer)
: cp_child(std::move(child)), cp_finalizer(std::move(finalizer))
{
ensure(this->cp_finalizer);
}
child_poller(child_poller&& other) noexcept
: cp_child(std::move(other.cp_child)),
cp_finalizer(std::move(other.cp_finalizer))
{
ensure(this->cp_finalizer);
}
child_poller& operator=(child_poller&& other) noexcept
{
require(other.cp_finalizer);
this->cp_child = std::move(other.cp_child);
this->cp_finalizer = std::move(other.cp_finalizer);
@ -106,6 +110,10 @@ public:
~child_poller() noexcept = default;
child_poller(const child_poller&) = delete;
child_poller& operator=(const child_poller&) = delete;
child_poll_result_t poll(file_collection& fc);
private:

@ -63,15 +63,9 @@ CREATE TABLE lnav_file (
explicit lnav_file(file_collection& fc) : lf_collection(fc) {}
iterator begin()
{
return this->lf_collection.fc_files.begin();
}
iterator begin() { return this->lf_collection.fc_files.begin(); }
iterator end()
{
return this->lf_collection.fc_files.end();
}
iterator end() { return this->lf_collection.fc_files.end(); }
int get_column(const cursor& vc, sqlite3_context* ctx, int col)
{
@ -170,14 +164,14 @@ CREATE TABLE lnav_file (
{
vt->zErrMsg = sqlite3_mprintf("Rows cannot be deleted from this table");
return SQLITE_ERROR;
};
}
int insert_row(sqlite3_vtab* tab, sqlite3_int64& rowid_out)
{
tab->zErrMsg
= sqlite3_mprintf("Rows cannot be inserted into this table");
return SQLITE_ERROR;
};
}
int update_row(sqlite3_vtab* tab,
sqlite3_int64& rowid,
@ -223,15 +217,128 @@ CREATE TABLE lnav_file (
}
return SQLITE_OK;
};
}
file_collection& lf_collection;
};
struct lnav_file_metadata {
static constexpr const char* NAME = "lnav_file_metadata";
static constexpr const char* CREATE_STMT = R"(
-- Access the metadata embedded in open files
CREATE TABLE lnav_file_metadata (
filepath text, -- The path to the file.
descriptor text, -- The descriptor that identifies the source of the metadata.
mimetype text, -- The MIME type of the metadata.
content text -- The metadata itself.
);
)";
struct cursor {
struct metadata_row {
metadata_row(std::shared_ptr<logfile> lf, std::string desc)
: mr_logfile(lf), mr_descriptor(std::move(desc))
{
}
std::shared_ptr<logfile> mr_logfile;
std::string mr_descriptor;
};
sqlite3_vtab_cursor base;
lnav_file_metadata& c_meta;
std::vector<metadata_row>::iterator c_iter;
std::vector<metadata_row> c_rows;
cursor(sqlite3_vtab* vt)
: base({vt}),
c_meta(((vtab_module<lnav_file_metadata>::vtab*) vt)->v_impl)
{
for (auto& lf : this->c_meta.lfm_collection.fc_files) {
auto& lf_meta = lf->get_embedded_metadata();
for (const auto& meta_pair : lf_meta) {
this->c_rows.emplace_back(lf, meta_pair.first);
}
}
}
~cursor() { this->c_iter = this->c_rows.end(); }
int next()
{
if (this->c_iter != this->c_rows.end()) {
++this->c_iter;
}
return SQLITE_OK;
}
int eof() { return this->c_iter == this->c_rows.end(); }
int reset()
{
this->c_iter = this->c_rows.begin();
return SQLITE_OK;
}
int get_rowid(sqlite3_int64& rowid_out)
{
rowid_out = this->c_iter - this->c_rows.begin();
return SQLITE_OK;
}
};
explicit lnav_file_metadata(file_collection& fc) : lfm_collection(fc) {}
int get_column(const cursor& vc, sqlite3_context* ctx, int col)
{
auto& mr = *vc.c_iter;
switch (col) {
case 0:
to_sqlite(ctx, mr.mr_logfile->get_filename());
break;
case 1:
to_sqlite(ctx, mr.mr_descriptor);
break;
case 2:
to_sqlite(
ctx,
fmt::to_string(
mr.mr_logfile->get_embedded_metadata()[mr.mr_descriptor]
.m_format));
break;
case 3:
to_sqlite(
ctx,
fmt::to_string(
mr.mr_logfile->get_embedded_metadata()[mr.mr_descriptor]
.m_value));
break;
default:
ensure(0);
break;
}
return SQLITE_OK;
}
file_collection& lfm_collection;
};
struct injectable_lnav_file : vtab_module<lnav_file> {
using vtab_module<lnav_file>::vtab_module;
using injectable = injectable_lnav_file(file_collection&);
};
struct injectable_lnav_file_metadata
: vtab_module<tvt_no_update<lnav_file_metadata>> {
using vtab_module<tvt_no_update<lnav_file_metadata>>::vtab_module;
using injectable = injectable_lnav_file_metadata(file_collection&);
};
static auto file_binder
= injector::bind_multiple<vtab_module_base>().add<injectable_lnav_file>();
static auto file_meta_binder = injector::bind_multiple<vtab_module_base>()
.add<injectable_lnav_file_metadata>();

@ -7,6 +7,9 @@
"regex": {
"std": {
"pattern": "^(?<level>[IWECF])(?<timestamp>\\d{4} \\d{2}:\\d{2}:\\d{2}\\.\\d{6}) +(?<thread>\\d+) (?<src_file>[^:]+):(?<src_line>\\d+)\\] (?<body>.*)"
},
"std-with-year": {
"pattern": "^(?<level>[IWECF])(?<timestamp>\\d{8} \\d{2}:\\d{2}:\\d{2}\\.\\d{6}) +(?<thread>\\d+) (?<src_file>[^:]+):(?<src_line>\\d+)\\] (?<body>.*)"
}
},
"level-field": "level",
@ -39,6 +42,10 @@
},
{
"line": "E0517 15:04:22.619632 52992 logging_unittest.cc:253] Log every 3, iteration 19"
},
{
"line": "I20200308 23:47:32.089828 400441 config.cc:27] Loading user configuration: /home/aesophor/.config/wmderland/config",
"level": "info"
}
]
}

@ -104,7 +104,7 @@ CREATE TABLE fstat (
cursor(sqlite3_vtab* vt) : base({vt})
{
memset(&this->c_stat, 0, sizeof(this->c_stat));
};
}
void load_stat()
{
@ -115,7 +115,7 @@ CREATE TABLE fstat (
{
this->c_path_index += 1;
}
};
}
int next()
{
@ -124,25 +124,19 @@ CREATE TABLE fstat (
this->load_stat();
}
return SQLITE_OK;
};
int reset()
{
return SQLITE_OK;
}
int eof()
{
return this->c_path_index >= this->c_glob->gl_pathc;
};
int reset() { return SQLITE_OK; }
int eof() { return this->c_path_index >= this->c_glob->gl_pathc; }
int get_rowid(sqlite3_int64& rowid_out)
{
rowid_out = this->c_path_index;
return SQLITE_OK;
};
}
};
int get_column(const cursor& vc, sqlite3_context* ctx, int col)

@ -107,8 +107,8 @@ down to display the new lines, much like `tail -f`.
On color displays, the lines will be highlighted as follows:
* Errors will be colored in ${ansi_red}red${ansi_norm};
* warnings will be ${ansi_yellow}yellow${ansi_norm};
* Errors will be colored in <span class="-lnav_log-level-styles_error">red</span>;
* warnings will be <span class="-lnav_log-level-styles_warning">yellow</span>;
* boundaries between days will be ${ansi_underline}underlined${ansi_norm}; and
* various color highlights will be applied to: IP addresses, SQL keywords,
XML tags, file and line numbers in Java backtraces, and quoted strings.

@ -100,12 +100,20 @@ highlighter::annotate_capture(attr_line_t& al, const line_range& lr) const
void
highlighter::annotate(attr_line_t& al, int start) const
{
if (!this->h_regex) {
return;
}
auto& vc = view_colors::singleton();
const auto& str = al.get_string();
auto& sa = al.get_attrs();
auto sf = string_fragment::from_str_range(
str, start, std::min(size_t{8192}, str.size()));
if (!sf.is_valid()) {
return;
}
pcre_context_static<60> pc;
pcre_input pi(sf);

@ -162,6 +162,8 @@ handle_keyseq(const char* keyseq)
auto& var_stack = ec.ec_local_vars;
ec.ec_global_vars = lnav_data.ld_exec_context.ec_global_vars;
ec.ec_error_callback_stack
= lnav_data.ld_exec_context.ec_error_callback_stack;
var_stack.push(std::map<std::string, scoped_value_t>());
auto& vars = var_stack.top();
vars["keyseq"] = keyseq;
@ -174,11 +176,7 @@ handle_keyseq(const char* keyseq)
} else {
auto um = result.unwrapErr();
um.um_snippets.clear();
um.um_reason.clear();
um.um_notes.clear();
um.um_help.clear();
lnav_data.ld_rl_view->set_attr_value(um.to_attr_line());
ec.ec_error_callback_stack.back()(um);
}
if (!kc.kc_alt_msg.empty()) {
@ -295,12 +293,10 @@ handle_paging_key(int ch)
break;
case '>': {
std::pair<int, int> range;
tc->horiz_shift(
tc->get_top(), tc->get_bottom(), tc->get_left(), range);
if (range.second != INT_MAX) {
tc->set_left(range.second);
auto range_opt = tc->horiz_shift(
tc->get_top(), tc->get_bottom(), tc->get_left());
if (range_opt && range_opt.value().second != INT_MAX) {
tc->set_left(range_opt.value().second);
lnav_data.ld_rl_view->set_alt_value(
HELP_MSG_1(m, "to bookmark a line"));
} else {
@ -312,12 +308,10 @@ handle_paging_key(int ch)
if (tc->get_left() == 0) {
alerter::singleton().chime("no more search hits to the left");
} else {
std::pair<int, int> range;
tc->horiz_shift(
tc->get_top(), tc->get_bottom(), tc->get_left(), range);
if (range.first != -1) {
tc->set_left(range.first);
auto range_opt = tc->horiz_shift(
tc->get_top(), tc->get_bottom(), tc->get_left());
if (range_opt && range_opt.value().first != -1) {
tc->set_left(range_opt.value().first);
} else {
tc->set_left(0);
}
@ -395,8 +389,7 @@ handle_paging_key(int ch)
tc->shift_top(1_vl);
}
if (lnav_data.ld_last_user_mark[tc] + 1
>= tc->get_inner_height())
{
>= tc->get_inner_height()) {
break;
}
lnav_data.ld_last_user_mark[tc] += 1;
@ -442,8 +435,7 @@ handle_paging_key(int ch)
case 'M':
if (lnav_data.ld_last_user_mark.find(tc)
== lnav_data.ld_last_user_mark.end())
{
== lnav_data.ld_last_user_mark.end()) {
alerter::singleton().chime("no lines have been marked");
} else {
int start_line = std::min((int) tc->get_top(),
@ -459,20 +451,20 @@ handle_paging_key(int ch)
break;
#if 0
case 'S':
{
bookmark_vector<vis_line_t>::iterator iter;
case 'S':
{
bookmark_vector<vis_line_t>::iterator iter;
for (iter = bm[&textview_curses::BM_SEARCH].begin();
iter != bm[&textview_curses::BM_SEARCH].end();
++iter) {
tc->toggle_user_mark(&textview_curses::BM_USER, *iter);
}
for (iter = bm[&textview_curses::BM_SEARCH].begin();
iter != bm[&textview_curses::BM_SEARCH].end();
++iter) {
tc->toggle_user_mark(&textview_curses::BM_USER, *iter);
}
lnav_data.ld_last_user_mark[tc] = -1;
tc->reload_data();
}
break;
lnav_data.ld_last_user_mark[tc] = -1;
tc->reload_data();
}
break;
#endif
case 's':
@ -487,8 +479,7 @@ handle_paging_key(int ch)
while (next_top < tc->get_inner_height()) {
if (!lss->find_line(lss->at(next_top))->is_message()) {
} else if (lss->get_line_accel_direction(next_top)
== log_accel::A_DECEL)
{
== log_accel::A_DECEL) {
--next_top;
tc->set_top(next_top);
break;
@ -511,8 +502,7 @@ handle_paging_key(int ch)
while (0 <= next_top && next_top < tc->get_inner_height()) {
if (!lss->find_line(lss->at(next_top))->is_message()) {
} else if (lss->get_line_accel_direction(next_top)
== log_accel::A_DECEL)
{
== log_accel::A_DECEL) {
--next_top;
tc->set_top(next_top);
break;
@ -625,8 +615,7 @@ handle_paging_key(int ch)
while (true) {
if (ch == 'o') {
if (++next_helper.lh_current_line
>= tc->get_inner_height())
{
>= tc->get_inner_height()) {
break;
}
} else {
@ -782,8 +771,7 @@ handle_paging_key(int ch)
for (row = 0; row < dls.dls_rows.size(); row++) {
if (strcmp(dls.dls_rows[row][log_line_index.value()],
linestr.data())
== 0)
{
== 0) {
vis_line_t db_line(row);
db_tc->set_top(db_line);
@ -821,8 +809,7 @@ handle_paging_key(int ch)
size_t col_len = strlen(col_value);
if (dts.scan(col_value, col_len, nullptr, &tm, tv)
!= nullptr)
{
!= nullptr) {
lnav_data.ld_log_source.find_from_time(tv) |
[tc](auto vl) {
tc->set_top(vl);

@ -161,7 +161,7 @@
:comment *text*
^^^^^^^^^^^^^^^
Attach a comment to the top log line
Attach a comment to the top log line. The comment will be displayed right below the log message it is associated with. The comment can be formatted using markdown and you can add new-lines with '\n'.
**Parameters**
* **text\*** --- The comment text
@ -571,13 +571,13 @@
.. _goto:
:goto *line#|N%|timestamp*
^^^^^^^^^^^^^^^^^^^^^^^^^^
:goto *line#|N%|timestamp|#anchor*
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Go to the given location in the top view
**Parameters**
* **line#|N%|timestamp\*** --- A line number, percent into the file, or a timestamp
* **line#|N%|timestamp|#anchor\*** --- A line number, percent into the file, timestamp, or an anchor in a text file
**Examples**
To go to line 22:
@ -598,6 +598,12 @@
:goto 2017-01-01
To go to the Screenshots section:
.. code-block:: lnav
:goto #screenshots
**See Also**
:ref:`next_location`, :ref:`next_mark`, :ref:`prev_location`, :ref:`prev_mark`, :ref:`relative_goto`

@ -1626,7 +1626,7 @@ jget(*json*, *ptr*, *\[default\]*)
Hello
**See Also**
:ref:`json_concat`, :ref:`json_contains`, :ref:`json_group_array`, :ref:`json_group_object`
:ref:`json_concat`, :ref:`json_contains`, :ref:`json_group_array`, :ref:`json_group_object`, :ref:`yaml_to_json`
----
@ -1710,7 +1710,7 @@ json_concat(*json*, *value*)
[1,2,3,4,5]
**See Also**
:ref:`jget`, :ref:`json_contains`, :ref:`json_group_array`, :ref:`json_group_object`
:ref:`jget`, :ref:`json_contains`, :ref:`json_group_array`, :ref:`json_group_object`, :ref:`yaml_to_json`
----
@ -1742,7 +1742,7 @@ json_contains(*json*, *value*)
1
**See Also**
:ref:`jget`, :ref:`json_concat`, :ref:`json_group_array`, :ref:`json_group_object`
:ref:`jget`, :ref:`json_concat`, :ref:`json_group_array`, :ref:`json_group_object`, :ref:`yaml_to_json`
----
@ -1773,7 +1773,7 @@ json_group_array(*value*)
[1,2,3]
**See Also**
:ref:`jget`, :ref:`json_concat`, :ref:`json_contains`, :ref:`json_group_object`
:ref:`jget`, :ref:`json_concat`, :ref:`json_contains`, :ref:`json_group_object`, :ref:`yaml_to_json`
----
@ -1805,7 +1805,7 @@ json_group_object(*name*, *value*)
{"a":1,"b":2}
**See Also**
:ref:`jget`, :ref:`json_concat`, :ref:`json_contains`, :ref:`json_group_array`
:ref:`jget`, :ref:`json_concat`, :ref:`json_contains`, :ref:`json_group_array`, :ref:`yaml_to_json`
----
@ -3731,6 +3731,30 @@ xpath(*xpath*, *xmldoc*)
----
.. _yaml_to_json:
yaml_to_json(*yaml*)
^^^^^^^^^^^^^^^^^^^^
Convert a YAML document to a JSON-encoded string
**Parameters**
* **yaml\*** --- The YAML value to convert to JSON.
**Examples**
To convert the document "abc: def":
.. code-block:: custsqlite
;SELECT yaml_to_json('abc: def')
{"abc": "def"}
**See Also**
:ref:`jget`, :ref:`json_concat`, :ref:`json_contains`, :ref:`json_group_array`, :ref:`json_group_object`
----
.. _zeroblob:
zeroblob(*N*)

@ -118,6 +118,7 @@
#include "readline_curses.hh"
#include "readline_highlighters.hh"
#include "regexp_vtab.hh"
#include "scn/scn.h"
#include "service_tags.hh"
#include "session_data.hh"
#include "spectro_source.hh"
@ -125,6 +126,7 @@
#include "sql_util.hh"
#include "sqlite-extension-func.hh"
#include "sqlitepp.client.hh"
#include "static_file_vtab.hh"
#include "tailer/tailer.looper.hh"
#include "term_extra.hh"
#include "termios_guard.hh"
@ -1079,6 +1081,60 @@ looper()
lnav_data.ld_rl_view->add_possibility(
ln_mode_t::COMMAND, "levelname", level_names);
auto echo_views_stmt_res = prepare_stmt(lnav_data.ld_db,
#if SQLITE_VERSION_NUMBER < 3033000
R"(
UPDATE lnav_views_echo
SET top = (SELECT top FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
left = (SELECT left FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
height = (SELECT height FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
inner_height = (SELECT inner_height FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
top_time = (SELECT top_time FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
search = (SELECT search FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name)
WHERE EXISTS (SELECT * FROM lnav_views WHERE name = lnav_views_echo.name AND
(
lnav_views.top != lnav_views_echo.top OR
lnav_views.left != lnav_views_echo.left OR
lnav_views.height != lnav_views_echo.height OR
lnav_views.inner_height != lnav_views_echo.inner_height OR
lnav_views.top_time != lnav_views_echo.top_time OR
lnav_views.search != lnav_views_echo.search
))
)"
#else
R"(
UPDATE lnav_views_echo
SET top = orig.top,
left = orig.left,
height = orig.height,
inner_height = orig.inner_height,
top_time = orig.top_time,
search = orig.search
FROM (SELECT * FROM lnav_views) AS orig
WHERE orig.name = lnav_views_echo.name AND
(
orig.top != lnav_views_echo.top OR
orig.left != lnav_views_echo.left OR
orig.height != lnav_views_echo.height OR
orig.inner_height != lnav_views_echo.inner_height OR
orig.top_time != lnav_views_echo.top_time OR
orig.search != lnav_views_echo.search
)
)"
#endif
);
if (echo_views_stmt_res.isErr()) {
lnav::console::print(
stderr,
lnav::console::user_message::error(
"unable to prepare UPDATE statement for lnav_views_echo "
"table")
.with_reason(echo_views_stmt_res.unwrapErr()));
return;
}
auto echo_views_stmt = echo_views_stmt_res.unwrap();
(void) signal(SIGINT, sigint);
(void) signal(SIGTERM, sigint);
(void) signal(SIGWINCH, sigwinch);
@ -1370,33 +1426,6 @@ looper()
auto next_status_update_time = next_rebuild_time;
auto next_rescan_time = next_rebuild_time;
auto echo_views_stmt = prepare_stmt(lnav_data.ld_db,
#if SQLITE_VERSION_NUMBER < 3033000
R"(
UPDATE lnav_views_echo
SET top = (SELECT top FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
left = (SELECT left FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
height = (SELECT height FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
inner_height = (SELECT inner_height FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
top_time = (SELECT top_time FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name),
search = (SELECT search FROM lnav_views WHERE lnav_views.name = lnav_views_echo.name)
)"
#else
R"(
UPDATE lnav_views_echo
SET top = orig.top,
left = orig.left,
height = orig.height,
inner_height = orig.inner_height,
top_time = orig.top_time,
search = orig.search
FROM (SELECT * FROM lnav_views) AS orig
WHERE orig.name = lnav_views_echo.name
)"
#endif
)
.unwrap();
while (lnav_data.ld_looping) {
auto loop_deadline
= ui_clock::now() + (session_stage == 0 ? 3s : 50ms);
@ -1723,7 +1752,6 @@ UPDATE lnav_views_echo
&& lnav_data.ld_text_source.text_line_count() > 0)
{
ensure_view(&lnav_data.ld_views[LNV_TEXT]);
lnav_data.ld_views[LNV_TEXT].set_top(0_vl);
lnav_data.ld_rl_view->set_alt_value(HELP_MSG_2(
f, F, "to switch to the next/previous file"));
}
@ -1811,8 +1839,12 @@ UPDATE lnav_views_echo
{
const auto& vs
= session_data.sd_view_states[view_index];
auto& tview = lnav_data.ld_views[view_index];
if (vs.vs_top > 0) {
if (vs.vs_top > 0 && tview.get_top() == 0_vl) {
log_info("restoring %s view top: %d",
lnav_view_strings[view_index],
vs.vs_top);
lnav_data.ld_views[view_index].set_top(
vis_line_t(vs.vs_top));
}
@ -1820,7 +1852,7 @@ UPDATE lnav_views_echo
if (lnav_data.ld_mode == ln_mode_t::FILES) {
if (lnav_data.ld_active_files.fc_name_to_errors.empty())
{
log_debug("switching to paging!");
log_info("switching to paging!");
lnav_data.ld_mode = ln_mode_t::PAGING;
lnav_data.ld_active_files.fc_files
| lnav::itertools::for_each(
@ -2017,6 +2049,7 @@ main(int argc, char* argv[])
}
register_environ_vtab(lnav_data.ld_db.in());
register_static_file_vtab(lnav_data.ld_db.in());
{
static auto vtab_modules
= injector::get<std::vector<std::shared_ptr<vtab_module_base>>>();
@ -2476,7 +2509,7 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
lnav_data.ld_pipers.push_back(pp);
lnav_data.ld_active_files.fc_file_names[desc].with_fd(
pp->get_fd());
lnav_data.ld_files_to_front.template emplace_back(desc, 0);
lnav_data.ld_files_to_front.template emplace_back(desc, 0_vl);
}))
.add_input_delegate(lnav_data.ld_log_source)
.set_tail_space(2_vl)
@ -2557,8 +2590,10 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
}
}
load_format_extra(
lnav_data.ld_db.in(), lnav_data.ld_config_paths, loader_errors);
load_format_extra(lnav_data.ld_db.in(),
ec.ec_global_vars,
lnav_data.ld_config_paths,
loader_errors);
load_format_vtabs(lnav_data.ld_vtab_manager.get(), loader_errors);
if (!loader_errors.empty()) {
@ -2622,9 +2657,36 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
}
for (auto& file_path : file_args) {
auto file_path_without_trailer = file_path;
auto file_loc = file_location_t{mapbox::util::no_init{}};
auto_mem<char> abspath;
struct stat st;
auto colon_index = file_path.rfind(':');
if (colon_index != std::string::npos) {
file_path_without_trailer = file_path.substr(0, colon_index);
auto top_range = scn::string_view{&file_path[colon_index + 1],
&(*file_path.cend())};
auto scan_res = scn::scan_value<int>(top_range);
if (scan_res) {
file_path_without_trailer = file_path.substr(0, colon_index);
file_loc = vis_line_t(scan_res.value());
} else {
log_warning(
"failed to parse line number from file path with colon: %s",
file_path.c_str());
}
}
auto hash_index = file_path.rfind('#');
if (hash_index != std::string::npos) {
file_loc = file_path.substr(hash_index);
file_path_without_trailer = file_path.substr(0, hash_index);
}
if (stat(file_path_without_trailer.c_str(), &st) == 0) {
file_path = file_path_without_trailer;
}
if (file_path == "-") {
load_stdin = true;
}
@ -2711,6 +2773,10 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
} else {
lnav_data.ld_active_files.fc_file_names.emplace(
abspath.in(), logfile_open_options());
if (file_loc.valid()) {
lnav_data.ld_files_to_front.emplace_back(abspath.in(),
file_loc);
}
}
}
@ -2933,13 +2999,13 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
log_tc->set_top(0_vl);
text_tc = &lnav_data.ld_views[LNV_TEXT];
text_tc->set_top(0_vl);
text_tc->set_height(vis_line_t(text_tc->get_inner_height()));
text_tc->set_height(vis_line_t(text_tc->get_inner_height()
- text_tc->get_top()));
setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
if (lnav_data.ld_log_source.text_line_count() == 0
&& lnav_data.ld_text_source.text_line_count() > 0)
{
toggle_view(&lnav_data.ld_views[LNV_TEXT]);
ensure_view(&lnav_data.ld_views[LNV_TEXT]);
}
log_info("Executing initial commands");

@ -158,6 +158,8 @@ struct key_repeat_history {
};
};
using file_location_t = mapbox::util::variant<vis_line_t, std::string>;
struct lnav_data_t {
std::map<std::string, std::list<session_pair_t>> ld_session_id;
time_t ld_session_time;
@ -171,7 +173,7 @@ struct lnav_data_t {
std::vector<ghc::filesystem::path> ld_config_paths;
file_collection ld_active_files;
std::list<child_poller> ld_child_pollers;
std::list<std::pair<std::string, int>> ld_files_to_front;
std::list<std::pair<std::string, file_location_t>> ld_files_to_front;
bool ld_stdout_used;
sig_atomic_t ld_looping;
sig_atomic_t ld_winched;
@ -258,8 +260,7 @@ struct lnav_data_t {
bool ld_show_help_view{false};
};
struct static_service {
};
struct static_service {};
class main_looper
: public isc::service<main_looper>

@ -150,8 +150,8 @@ public:
auto iter = session_data.sd_file_states.find(lf->get_filename());
if (iter != session_data.sd_file_states.end()) {
log_debug("found state for log file %d",
iter->second.fs_is_visible);
log_info(" found visibility state for log file: %d",
iter->second.fs_is_visible);
lnav_data.ld_log_source.find_data(lf) | [&iter](auto ld) {
ld->set_visibility(iter->second.fs_is_visible);
@ -181,7 +181,7 @@ public:
}
std::shared_ptr<logfile> front_file;
int front_top{-1};
file_location_t front_top;
bool did_promotion{false};
};
@ -219,12 +219,35 @@ rebuild_indexes(nonstd::optional<ui_clock::time_point> deadline)
old_bottoms[LNV_TEXT] = -1_vl;
}
if (cb.front_top < 0) {
cb.front_top += text_view.get_inner_height();
}
if (cb.front_top < text_view.get_inner_height()) {
text_view.set_top(vis_line_t(cb.front_top));
nonstd::optional<vis_line_t> new_top_opt;
cb.front_top.match(
[&new_top_opt](vis_line_t vl) {
log_info("file open request to jump to line: %d", (int) vl);
if (vl < 0_vl) {
vl += lnav_data.ld_views[LNV_TEXT].get_inner_height();
}
if (vl < lnav_data.ld_views[LNV_TEXT].get_inner_height()) {
new_top_opt = vl;
}
},
[&new_top_opt](const std::string& loc) {
log_info("file open request to jump to anchor: %s",
loc.c_str());
auto* ta = dynamic_cast<text_anchors*>(
lnav_data.ld_views[LNV_TEXT].get_sub_source());
if (ta != nullptr) {
new_top_opt = ta->row_for_anchor(loc);
}
});
if (new_top_opt) {
log_info(" setting requested top line: %d",
(int) new_top_opt.value());
text_view.set_top(new_top_opt.value());
log_info(" actual top is now: %d", (int) text_view.get_top());
scroll_downs[LNV_TEXT] = false;
} else {
log_warning("could not jump to requested line");
}
}
if (cb.did_promotion && deadline) {
@ -364,6 +387,7 @@ update_active_files(file_collection& new_files)
lnav_data.ld_active_files.fc_child_pollers.begin()),
std::make_move_iterator(
lnav_data.ld_active_files.fc_child_pollers.end()));
lnav_data.ld_active_files.fc_child_pollers.clear();
lnav::events::publish(
lnav_data.ld_db.in(), new_files.fc_files, [](const auto& lf) {
@ -393,12 +417,19 @@ rescan_files(bool req)
continue;
}
if (lnav_data.ld_active_files.fc_name_to_errors.count(pair.first)) {
continue;
}
if (lnav_data.ld_active_files.fc_synced_files.count(pair.first)
== 0)
{
all_synced = false;
}
}
if (!lnav_data.ld_active_files.fc_name_to_errors.empty()) {
return false;
}
if (!all_synced) {
delay = 30ms;
}

@ -335,10 +335,25 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
std::string retval;
if (args.empty()) {
args.emplace_back("move-time");
args.emplace_back("move-args");
} else if (args.size() > 1) {
std::string all_args = remaining_args(cmdline, args);
auto* tc = *lnav_data.ld_view_stack.top();
nonstd::optional<vis_line_t> dst_vl;
if (startswith(all_args, "#")) {
auto* ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
if (ta == nullptr) {
return ec.make_error("view does not support anchor links");
}
dst_vl = ta->row_for_anchor(all_args);
if (!dst_vl) {
return ec.make_error("unable to find anchor: {}", all_args);
}
}
auto* ttt = dynamic_cast<text_time_translator*>(tc->get_sub_source());
int line_number, consumed;
date_time_scanner dts;
@ -346,10 +361,12 @@ com_goto(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
struct timeval tv;
struct exttm tm;
float value;
nonstd::optional<vis_line_t> dst_vl;
auto parse_res = relative_time::from_str(all_args);
if (parse_res.isOk()) {
if (dst_vl) {
}
else if (parse_res.isOk()) {
if (ttt == nullptr) {
return ec.make_error(
"relative time values only work in a time-indexed view");
@ -2355,7 +2372,6 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
}
std::vector<std::string> word_exp;
size_t colon_index;
std::string pat;
file_collection fc;
@ -2372,17 +2388,27 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
return ec.make_error("unable to parse arguments");
}
std::vector<std::pair<std::string, int>> files_to_front;
std::vector<std::pair<std::string, file_location_t>> files_to_front;
std::vector<std::string> closed_files;
for (auto fn : split_args) {
int top = 0;
if (access(fn.c_str(), R_OK) != 0
&& (colon_index = fn.rfind(':')) != std::string::npos)
{
if (sscanf(&fn.c_str()[colon_index + 1], "%d", &top) == 1) {
fn = fn.substr(0, colon_index);
file_location_t file_loc;
if (access(fn.c_str(), R_OK) != 0) {
auto colon_index = fn.rfind(':');
auto hash_index = fn.rfind('#');
if (colon_index != std::string::npos) {
auto top_range = scn::string_view{
&fn[colon_index + 1], &(*fn.cend())};
auto scan_res = scn::scan_value<int>(top_range);
if (scan_res) {
fn = fn.substr(0, colon_index);
file_loc = vis_line_t(scan_res.value());
}
} else if (hash_index != std::string::npos) {
file_loc = fn.substr(hash_index);
fn = fn.substr(0, hash_index);
}
}
@ -2398,7 +2424,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
break;
}
files_to_front.emplace_back(fn, top);
files_to_front.emplace_back(fn, file_loc);
retval = "";
break;
}
@ -2418,7 +2444,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
ul->copy_fd());
isc::to<curl_looper&, services::curl_streamer_t>().send(
[ul](auto& clooper) { clooper.add_request(ul); });
lnav_data.ld_files_to_front.emplace_back(fn, top);
lnav_data.ld_files_to_front.emplace_back(fn, file_loc);
retval = "info: opened URL";
} else {
retval = "";
@ -2520,7 +2546,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
fn = abspath.in();
fc.fc_file_names.emplace(fn, logfile_open_options());
retval = "info: opened -- " + fn;
files_to_front.emplace_back(fn, top);
files_to_front.emplace_back(fn, file_loc);
closed_files.push_back(fn);
if (lnav_data.ld_rl_view != nullptr) {
@ -2862,7 +2888,7 @@ com_comment(exec_context& ec,
if (ec.ec_dry_run) {
return Ok(std::string());
}
textview_curses* tc = *lnav_data.ld_view_stack.top();
auto* tc = *lnav_data.ld_view_stack.top();
if (tc != &lnav_data.ld_views[LNV_LOG]) {
return ec.make_error(
@ -2871,12 +2897,15 @@ com_comment(exec_context& ec,
auto& lss = lnav_data.ld_log_source;
args[1] = trim(remaining_args(cmdline, args));
auto unquoted = auto_buffer::alloc(args[1].size() + 1);
auto unquoted_len = unquote_content(unquoted.in(), args[1].c_str(), args[1].size(), 0);
unquoted.resize(unquoted_len + 1);
tc->set_user_mark(&textview_curses::BM_META, tc->get_top(), true);
auto& line_meta = lss.get_bookmark_metadata(tc->get_top());
line_meta.bm_comment = args[1];
line_meta.bm_comment = unquoted.in();
lss.set_line_meta_changed();
lss.text_filters_changed();
tc->reload_data();
@ -2902,7 +2931,11 @@ com_comment_prompt(exec_context& ec, const std::string& cmdline)
auto line_meta_opt = lss.find_bookmark_metadata(tc->get_top());
if (line_meta_opt && !line_meta_opt.value()->bm_comment.empty()) {
return trim(cmdline) + " " + trim(line_meta_opt.value()->bm_comment);
auto trimmed_comment = trim(line_meta_opt.value()->bm_comment);
auto buf = auto_buffer::alloc(trimmed_comment.size() + 16);
quote_content(buf, trimmed_comment, 0);
return trim(cmdline) + " " + buf.to_string();
}
return "";
@ -4428,8 +4461,10 @@ com_quit(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
static void
command_prompt(std::vector<std::string>& args)
{
textview_curses* tc = *lnav_data.ld_view_stack.top();
auto* tc = *lnav_data.ld_view_stack.top();
auto* rlc = lnav_data.ld_rl_view;
rlc->clear_possibilities(ln_mode_t::COMMAND, "move-args");
if (lnav_data.ld_views[LNV_LOG].get_inner_height() > 0) {
static const char* MOVE_TIMES[]
= {"here", "now", "today", "yesterday", nullptr};
@ -4497,10 +4532,9 @@ command_prompt(std::vector<std::string>& args)
ldh.clear();
readline_curses* rlc = lnav_data.ld_rl_view;
rlc->clear_possibilities(ln_mode_t::COMMAND, "move-time");
rlc->add_possibility(ln_mode_t::COMMAND, "move-time", MOVE_TIMES);
rlc->add_possibility(ln_mode_t::COMMAND, "move-args", MOVE_TIMES);
rlc->clear_possibilities(ln_mode_t::COMMAND, "line-time");
{
struct timeval tv = lf->get_time_offset();
@ -4509,6 +4543,7 @@ command_prompt(std::vector<std::string>& args)
sql_strftime(
buffer, sizeof(buffer), ll->get_time(), ll->get_millis(), 'T');
rlc->add_possibility(ln_mode_t::COMMAND, "line-time", buffer);
rlc->add_possibility(ln_mode_t::COMMAND, "move-args", buffer);
rlc->add_possibility(ln_mode_t::COMMAND, "move-time", buffer);
sql_strftime(buffer,
sizeof(buffer),
@ -4516,6 +4551,7 @@ command_prompt(std::vector<std::string>& args)
ll->get_millis() - (tv.tv_usec / 1000),
'T');
rlc->add_possibility(ln_mode_t::COMMAND, "line-time", buffer);
rlc->add_possibility(ln_mode_t::COMMAND, "move-args", buffer);
rlc->add_possibility(ln_mode_t::COMMAND, "move-time", buffer);
}
}
@ -4537,6 +4573,11 @@ command_prompt(std::vector<std::string>& args)
add_file_possibilities();
add_recent_netlocs_possibilities();
auto *ta = dynamic_cast<text_anchors*>(tc->get_sub_source());
if (ta != nullptr) {
rlc->add_possibility(ln_mode_t::COMMAND, "move-args", ta->get_anchors());
}
if (tc == &lnav_data.ld_views[LNV_LOG]) {
add_filter_expr_possibilities(
lnav_data.ld_rl_view, ln_mode_t::COMMAND, "filter-expr-syms");
@ -4797,13 +4838,15 @@ readline_context::command_t STD_COMMANDS[] = {
help_text(":goto")
.with_summary("Go to the given location in the top view")
.with_parameter(
help_text("line#|N%|timestamp",
"A line number, percent into the file, or a timestamp"))
help_text("line#|N%|timestamp|#anchor",
"A line number, percent into the file, timestamp, "
"or an anchor in a text file"))
.with_examples(
{{"To go to line 22", "22"},
{"To go to the line 75% of the way into the view", "75%"},
{"To go to the first message on the first day of 2017",
"2017-01-01"}})
"2017-01-01"},
{"To go to the Screenshots section", "#screenshots"}})
.with_tags({"navigation"})},
{"relative-goto",
com_relative_goto,
@ -5326,7 +5369,11 @@ readline_context::command_t STD_COMMANDS[] = {
com_comment,
help_text(":comment")
.with_summary("Attach a comment to the top log line")
.with_summary(
"Attach a comment to the top log line. The comment will be "
"displayed right below the log message it is associated with. "
"The comment can be formatted using markdown and you can add "
"new-lines with '\\n'.")
.with_parameter(help_text("text", "The comment text"))
.with_example({"To add the comment 'This is where it all went "
"wrong' to the top line",

@ -620,42 +620,42 @@ static const struct json_path_container theme_styles_handlers = {
.with_description("Styling for top-level headers")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_header[0];
return &root->lt_style_header[0].pp_value;
})
.with_children(style_config_handlers),
yajlpp::property_handler("h2")
.with_description("Styling for 2nd-level headers")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_header[1];
return &root->lt_style_header[1].pp_value;
})
.with_children(style_config_handlers),
yajlpp::property_handler("h3")
.with_description("Styling for 3rd-level headers")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_header[2];
return &root->lt_style_header[2].pp_value;
})
.with_children(style_config_handlers),
yajlpp::property_handler("h4")
.with_description("Styling for 4th-level headers")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_header[3];
return &root->lt_style_header[3].pp_value;
})
.with_children(style_config_handlers),
yajlpp::property_handler("h5")
.with_description("Styling for 5th-level headers")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_header[4];
return &root->lt_style_header[4].pp_value;
})
.with_children(style_config_handlers),
yajlpp::property_handler("h6")
.with_description("Styling for 6th-level headers")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
return &root->lt_style_header[5];
return &root->lt_style_header[5].pp_value;
})
.with_children(style_config_handlers),
yajlpp::property_handler("hr")
@ -844,10 +844,14 @@ static const struct json_path_container theme_log_level_styles_handlers = {
"warning|error|critical|fatal|invalid)")
.with_obj_provider<style_config, lnav_theme>(
[](const yajlpp_provider_context& ypc, lnav_theme* root) {
style_config& sc = root->lt_level_styles[string2level(
auto& sc = root->lt_level_styles[string2level(
ypc.ypc_extractor.get_substr_i("level").get())];
return &sc;
if (ypc.ypc_parse_context != nullptr && sc.pp_path.empty()) {
sc.pp_path = ypc.ypc_parse_context->get_full_path();
}
return &sc.pp_value;
})
.with_path_provider<lnav_theme>(
[](struct lnav_theme* cfg, std::vector<std::string>& paths_out) {

@ -1304,6 +1304,7 @@ load_formats(const std::vector<ghc::filesystem::path>& extra_paths,
static void
exec_sql_in_path(sqlite3* db,
const std::map<std::string, scoped_value_t>& global_vars,
const ghc::filesystem::path& path,
std::vector<lnav::console::user_message>& errors)
{
@ -1321,7 +1322,7 @@ exec_sql_in_path(sqlite3* db,
auto content = read_res.unwrap();
sql_execute_script(
db, filename.c_str(), content.c_str(), errors);
db, global_vars, filename.c_str(), content.c_str(), errors);
} else {
errors.emplace_back(
lnav::console::user_message::error(
@ -1335,11 +1336,12 @@ exec_sql_in_path(sqlite3* db,
void
load_format_extra(sqlite3* db,
const std::map<std::string, scoped_value_t>& global_vars,
const std::vector<ghc::filesystem::path>& extra_paths,
std::vector<lnav::console::user_message>& errors)
{
for (const auto& extra_path : extra_paths) {
exec_sql_in_path(db, extra_path, errors);
exec_sql_in_path(db, global_vars, extra_path, errors);
}
}

@ -40,6 +40,7 @@
#include "base/intern_string.hh"
#include "base/lnav.console.hh"
#include "ghc/filesystem.hpp"
#include "shlex.resolver.hh"
class log_vtab_manager;
@ -54,6 +55,7 @@ void load_format_vtabs(log_vtab_manager* vtab_manager,
std::vector<lnav::console::user_message>& errors);
void load_format_extra(sqlite3* db,
const std::map<std::string, scoped_value_t>& global_vars,
const std::vector<ghc::filesystem::path>& extra_paths,
std::vector<lnav::console::user_message>& errors);

@ -100,6 +100,11 @@ public:
using iterator = std::vector<logline>::iterator;
using const_iterator = std::vector<logline>::const_iterator;
struct metadata {
text_format_t m_format;
std::string m_value;
};
/**
* Construct a logfile with the given arguments.
*
@ -360,6 +365,16 @@ public:
return this->lf_bookmark_metadata;
}
std::map<std::string, metadata>& get_embedded_metadata()
{
return this->lf_embedded_metadata;
}
const std::map<std::string, metadata>& get_embedded_metadata() const
{
return this->lf_embedded_metadata;
}
protected:
/**
* Process a line from the file.
@ -416,6 +431,7 @@ private:
robin_hood::unordered_map<uint32_t, bookmark_metadata> lf_bookmark_metadata;
std::vector<std::shared_ptr<format_tag_def>> lf_applicable_taggers;
std::map<std::string, metadata> lf_embedded_metadata;
};
class logline_observer {

@ -46,6 +46,7 @@
#include "lnav.events.hh"
#include "log_accel.hh"
#include "logfile_sub_source.cfg.hh"
#include "md2attr_line.hh"
#include "readline_highlighters.hh"
#include "relative_time.hh"
#include "sql_util.hh"
@ -1908,9 +1909,19 @@ logfile_sub_source::meta_grepper::grep_value_for_line(vis_line_t line,
if (!line_meta_opt) {
value_out.clear();
} else {
bookmark_metadata& bm = *(line_meta_opt.value());
auto& bm = *(line_meta_opt.value());
{
md2attr_line mdal;
auto parse_res = md4cpp::parse(bm.bm_comment, mdal);
if (parse_res.isOk()) {
value_out.append(parse_res.unwrap().get_string());
} else {
value_out.append(bm.bm_comment);
}
}
value_out.append(bm.bm_comment);
value_out.append("\x1c");
for (const auto& tag : bm.bm_tags) {
value_out.append(tag);

@ -33,6 +33,7 @@
#include "base/itertools.hh"
#include "base/lnav_log.hh"
#include "pcrepp/pcrepp.hh"
#include "pugixml/pugixml.hpp"
#include "readline_highlighters.hh"
#include "view_curses.hh"
@ -145,8 +146,7 @@ md2attr_line::leave_block(const md4cpp::event_handler::block& bl)
last_block.append("\n");
}
if (this->ml_list_stack.empty()
&& !endswith(last_block.get_string(), "\n\n"))
{
&& !endswith(last_block.get_string(), "\n\n")) {
last_block.append("\n");
}
}
@ -208,8 +208,7 @@ md2attr_line::leave_block(const md4cpp::event_handler::block& bl)
for (auto line : block_text.split_lines()) {
if (!cmd_block.empty()
&& endswith(cmd_block.get_string(), "\\\n"))
{
&& endswith(cmd_block.get_string(), "\\\n")) {
cmd_block.append(line).append("\n");
continue;
}
@ -361,8 +360,7 @@ md2attr_line::leave_block(const md4cpp::event_handler::block& bl)
}
}
for (size_t line_index = 0; line_index < max_cell_lines;
line_index++)
{
line_index++) {
size_t col = 0;
for (const auto& cell : cells) {
block_text.append(" ");
@ -392,6 +390,12 @@ md2attr_line::leave_block(const md4cpp::event_handler::block& bl)
} else if (bl.is<MD_BLOCK_TD_DETAIL*>()) {
this->ml_tables.back().t_rows.back().r_columns.push_back(block_text);
} else {
if (bl.is<block_html>()) {
if (startswith(block_text.get_string(), "<!--")) {
return Ok();
}
}
text_wrap_settings tws = {0, this->ml_blocks.size() == 1 ? 70 : 10000};
if (!last_block.empty()) {
@ -456,6 +460,15 @@ md2attr_line::leave_span(const md4cpp::event_handler::span& sp)
lr,
VC_STYLE.value(text_attrs{A_BOLD}),
});
} else if (sp.is<span_u>()) {
line_range lr{
static_cast<int>(this->ml_span_starts.back()),
static_cast<int>(last_block.length()),
};
last_block.with_attr({
lr,
VC_STYLE.value(text_attrs{A_UNDERLINE}),
});
} else if (sp.is<MD_SPAN_A_DETAIL*>()) {
auto* a_detail = sp.get<MD_SPAN_A_DETAIL*>();
auto href_str = std::string(a_detail->href.text, a_detail->href.size);
@ -475,6 +488,7 @@ Result<void, std::string>
md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
{
static const auto& entity_map = md4cpp::get_xml_entity_map();
static const auto& vc = view_colors::singleton();
auto& last_block = this->ml_blocks.back();
@ -497,6 +511,45 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
}
break;
}
case MD_TEXT_HTML: {
last_block.append(sf);
if (sf.startswith("<span ")) {
this->ml_html_span_starts.push_back(last_block.length()
- sf.length());
} else if (sf == "</span>" && !this->ml_html_span_starts.empty()) {
std::string html_span = last_block.get_string().substr(
this->ml_html_span_starts.back());
pugi::xml_document doc;
auto load_res = doc.load_string(html_span.c_str());
if (load_res) {
auto span = doc.child("span");
if (span) {
auto styled_span = attr_line_t(span.text().get());
auto span_class = span.attribute("class");
if (span_class) {
auto cl_iter
= vc.vc_class_to_role.find(span_class.value());
if (cl_iter == vc.vc_class_to_role.end()) {
log_error("unknown span class: %s",
span_class.value());
} else {
styled_span.with_attr_for_all(cl_iter->second);
}
}
last_block.erase(this->ml_html_span_starts.back());
last_block.append(styled_span);
}
} else {
log_error("failed to parse: %s", load_res.description());
}
this->ml_html_span_starts.pop_back();
}
break;
}
default: {
static const pcrepp REPL_RE(R"(-{2,3}|:[^:\s]*(?:::[^:\s]*)*:)");
static const auto& emojis = md4cpp::get_emoji_map();
@ -508,29 +561,36 @@ md2attr_line::text(MD_TEXTTYPE tt, const string_fragment& sf)
pcre_input pi(sf);
pcre_context_static<30> pc;
std::string span_text;
while (REPL_RE.match(pc, pi)) {
auto prev = pi.get_up_to(pc.all());
last_block.append(prev);
span_text.append(prev.data(), prev.length());
auto matched = pi.get_string_fragment(pc.all());
if (matched == "--") {
last_block.append("\u2013");
span_text.append("\u2013");
} else if (matched == "---") {
last_block.append("\u2014");
span_text.append("\u2014");
} else if (matched.startswith(":")) {
auto em_iter
= emojis.em_shortname2emoji.find(matched.to_string());
if (em_iter == emojis.em_shortname2emoji.end()) {
last_block.append(matched);
span_text.append(matched.data(), matched.length());
} else {
last_block.append(em_iter->second.get().e_value);
span_text.append(em_iter->second.get().e_value);
}
}
}
this->ml_blocks.back().append(sf.substr(pi.pi_offset));
auto last_frag = sf.substr(pi.pi_offset);
span_text.append(last_frag.data(), last_frag.length());
text_wrap_settings tws
= {0, this->ml_blocks.size() == 1 ? 70 : 10000};
last_block.append(span_text, &tws);
break;
}
}

@ -83,6 +83,7 @@ private:
std::vector<list_block_t> ml_list_stack;
std::vector<table_t> ml_tables;
std::vector<size_t> ml_span_starts;
std::vector<size_t> ml_html_span_starts;
std::vector<attr_line_t> ml_footnotes;
int32_t ml_code_depth{0};
};

@ -37,266 +37,254 @@
namespace md4cpp {
static const typed_json_path_container<xml_entity> xml_entity_handlers = {
yajlpp::property_handler("characters").for_field(&xml_entity::xe_chars),
};
static const typed_json_path_container<xml_entity_map> xml_entity_map_handlers
= {
yajlpp::pattern_property_handler("(?<var_name>\\&\\w+;?)")
.with_synopsis("<name>")
.with_path_provider<xml_entity_map>(
[](struct xml_entity_map* xem,
std::vector<std::string>& paths_out) {
for (const auto& iter : xem->xem_entities) {
paths_out.emplace_back(iter.first);
}
})
.with_obj_provider<xml_entity, xml_entity_map>(
[](const yajlpp_provider_context& ypc, xml_entity_map* xem) {
auto entity_name = ypc.get_substr(0);
return &xem->xem_entities[entity_name];
})
.with_children(xml_entity_handlers),
};
static const typed_json_path_container<emoji> emoji_handlers = {
yajlpp::property_handler("emoji").for_field(&emoji::e_value),
yajlpp::property_handler("shortname").for_field(&emoji::e_shortname),
};
static const typed_json_path_container<emoji_map> emoji_map_handlers = {
yajlpp::property_handler("emojis#")
.for_field(&emoji_map::em_emojis)
.with_children(emoji_handlers),
};
static xml_entity_map
load_xml_entity_map()
{
static const intern_string_t name
= intern_string::lookup(xml_entities_json.get_name());
auto parse_res
= xml_entity_map_handlers.parser_for(name).with_ignore_unused(true).of(
xml_entities_json.to_string_fragment());
assert(parse_res.isOk());
return parse_res.unwrap();
}
const xml_entity_map&
get_xml_entity_map()
{
static const auto retval = load_xml_entity_map();
return retval;
}
static emoji_map
load_emoji_map()
{
static const intern_string_t name
= intern_string::lookup(emojis_json.get_name());
auto parse_res
= emoji_map_handlers.parser_for(name).with_ignore_unused(true).of(
emojis_json.to_string_fragment());
assert(parse_res.isOk());
auto retval = parse_res.unwrap();
for (auto& em : retval.em_emojis) {
retval.em_shortname2emoji.emplace(em.e_shortname, em);
static const typed_json_path_container<xml_entity> xml_entity_handlers = {
yajlpp::property_handler("characters").for_field(&xml_entity::xe_chars),
};
static const typed_json_path_container<xml_entity_map> xml_entity_map_handlers
= {
yajlpp::pattern_property_handler("(?<var_name>\\&\\w+;?)")
.with_synopsis("<name>")
.with_path_provider<xml_entity_map>(
[](struct xml_entity_map *xem,
std::vector<std::string> &paths_out) {
for (const auto &iter: xem->xem_entities) {
paths_out.emplace_back(iter.first);
}
})
.with_obj_provider<xml_entity, xml_entity_map>(
[](const yajlpp_provider_context &ypc, xml_entity_map *xem) {
auto entity_name = ypc.get_substr(0);
return &xem->xem_entities[entity_name];
})
.with_children(xml_entity_handlers),
};
static const typed_json_path_container<emoji> emoji_handlers = {
yajlpp::property_handler("emoji").for_field(&emoji::e_value),
yajlpp::property_handler("shortname").for_field(&emoji::e_shortname),
};
static const typed_json_path_container<emoji_map> emoji_map_handlers = {
yajlpp::property_handler("emojis#")
.for_field(&emoji_map::em_emojis)
.with_children(emoji_handlers),
};
static xml_entity_map
load_xml_entity_map() {
static const intern_string_t name
= intern_string::lookup(xml_entities_json.get_name());
auto parse_res
= xml_entity_map_handlers.parser_for(name).with_ignore_unused(true).of(
xml_entities_json.to_string_fragment());
assert(parse_res.isOk());
return parse_res.unwrap();
}
return retval;
}
const emoji_map&
get_emoji_map()
{
static const auto retval = load_emoji_map();
return retval;
}
struct parse_userdata {
event_handler& pu_handler;
std::string pu_error_msg;
};
static event_handler::block
build_block(MD_BLOCKTYPE type, void* detail)
{
switch (type) {
case MD_BLOCK_DOC:
return event_handler::block_doc{};
case MD_BLOCK_QUOTE:
return event_handler::block_quote{};
case MD_BLOCK_UL:
return static_cast<MD_BLOCK_UL_DETAIL*>(detail);
case MD_BLOCK_OL:
return static_cast<MD_BLOCK_OL_DETAIL*>(detail);
case MD_BLOCK_LI:
return static_cast<MD_BLOCK_LI_DETAIL*>(detail);
case MD_BLOCK_HR:
return event_handler::block_hr{};
case MD_BLOCK_H:
return static_cast<MD_BLOCK_H_DETAIL*>(detail);
case MD_BLOCK_CODE:
return static_cast<MD_BLOCK_CODE_DETAIL*>(detail);
case MD_BLOCK_HTML:
return event_handler::block_html{};
case MD_BLOCK_P:
return event_handler::block_p{};
case MD_BLOCK_TABLE:
return static_cast<MD_BLOCK_TABLE_DETAIL*>(detail);
case MD_BLOCK_THEAD:
return event_handler::block_thead{};
case MD_BLOCK_TBODY:
return event_handler::block_tbody{};
case MD_BLOCK_TR:
return event_handler::block_tr{};
case MD_BLOCK_TH:
return event_handler::block_th{};
case MD_BLOCK_TD:
return static_cast<MD_BLOCK_TD_DETAIL*>(detail);
}
const xml_entity_map &
get_xml_entity_map() {
static const auto retval = load_xml_entity_map();
return {};
}
static event_handler::span
build_span(MD_SPANTYPE type, void* detail)
{
switch (type) {
case MD_SPAN_EM:
return event_handler::span_em{};
case MD_SPAN_STRONG:
return event_handler::span_strong{};
case MD_SPAN_A:
return static_cast<MD_SPAN_A_DETAIL*>(detail);
case MD_SPAN_IMG:
return static_cast<MD_SPAN_IMG_DETAIL*>(detail);
case MD_SPAN_CODE:
return event_handler::span_code{};
case MD_SPAN_DEL:
return event_handler::span_del{};
case MD_SPAN_U:
return event_handler::span_u{};
default:
break;
return retval;
}
return {};
}
static emoji_map
load_emoji_map() {
static const intern_string_t name
= intern_string::lookup(emojis_json.get_name());
auto parse_res
= emoji_map_handlers.parser_for(name).with_ignore_unused(true).of(
emojis_json.to_string_fragment());
assert(parse_res.isOk());
static int
md4cpp_enter_block(MD_BLOCKTYPE type, void* detail, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
auto retval = parse_res.unwrap();
for (auto &em: retval.em_emojis) {
retval.em_shortname2emoji.emplace(em.e_shortname, em);
}
auto enter_res = pu->pu_handler.enter_block(build_block(type, detail));
if (enter_res.isErr()) {
pu->pu_error_msg = enter_res.unwrapErr();
return 1;
return retval;
}
return 0;
}
const emoji_map &
get_emoji_map() {
static const auto retval = load_emoji_map();
static int
md4cpp_leave_block(MD_BLOCKTYPE type, void* detail, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
return retval;
}
struct parse_userdata {
event_handler &pu_handler;
std::string pu_error_msg;
};
static event_handler::block
build_block(MD_BLOCKTYPE type, void *detail) {
switch (type) {
case MD_BLOCK_DOC:
return event_handler::block_doc{};
case MD_BLOCK_QUOTE:
return event_handler::block_quote{};
case MD_BLOCK_UL:
return static_cast<MD_BLOCK_UL_DETAIL *>(detail);
case MD_BLOCK_OL:
return static_cast<MD_BLOCK_OL_DETAIL *>(detail);
case MD_BLOCK_LI:
return static_cast<MD_BLOCK_LI_DETAIL *>(detail);
case MD_BLOCK_HR:
return event_handler::block_hr{};
case MD_BLOCK_H:
return static_cast<MD_BLOCK_H_DETAIL *>(detail);
case MD_BLOCK_CODE:
return static_cast<MD_BLOCK_CODE_DETAIL *>(detail);
case MD_BLOCK_HTML:
return event_handler::block_html{};
case MD_BLOCK_P:
return event_handler::block_p{};
case MD_BLOCK_TABLE:
return static_cast<MD_BLOCK_TABLE_DETAIL *>(detail);
case MD_BLOCK_THEAD:
return event_handler::block_thead{};
case MD_BLOCK_TBODY:
return event_handler::block_tbody{};
case MD_BLOCK_TR:
return event_handler::block_tr{};
case MD_BLOCK_TH:
return event_handler::block_th{};
case MD_BLOCK_TD:
return static_cast<MD_BLOCK_TD_DETAIL *>(detail);
}
return {};
}
auto leave_res = pu->pu_handler.leave_block(build_block(type, detail));
if (leave_res.isErr()) {
pu->pu_error_msg = leave_res.unwrapErr();
return 1;
static event_handler::span
build_span(MD_SPANTYPE type, void *detail) {
switch (type) {
case MD_SPAN_EM:
return event_handler::span_em{};
case MD_SPAN_STRONG:
return event_handler::span_strong{};
case MD_SPAN_A:
return static_cast<MD_SPAN_A_DETAIL *>(detail);
case MD_SPAN_IMG:
return static_cast<MD_SPAN_IMG_DETAIL *>(detail);
case MD_SPAN_CODE:
return event_handler::span_code{};
case MD_SPAN_DEL:
return event_handler::span_del{};
case MD_SPAN_U:
return event_handler::span_u{};
default:
break;
}
return {};
}
return 0;
}
static int
md4cpp_enter_block(MD_BLOCKTYPE type, void *detail, void *userdata) {
auto *pu = static_cast<parse_userdata *>(userdata);
static int
md4cpp_enter_span(MD_SPANTYPE type, void* detail, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
auto enter_res = pu->pu_handler.enter_block(build_block(type, detail));
if (enter_res.isErr()) {
pu->pu_error_msg = enter_res.unwrapErr();
return 1;
}
auto enter_res = pu->pu_handler.enter_span(build_span(type, detail));
if (enter_res.isErr()) {
pu->pu_error_msg = enter_res.unwrapErr();
return 1;
return 0;
}
return 0;
}
static int
md4cpp_leave_block(MD_BLOCKTYPE type, void *detail, void *userdata) {
auto *pu = static_cast<parse_userdata *>(userdata);
static int
md4cpp_leave_span(MD_SPANTYPE type, void* detail, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
auto leave_res = pu->pu_handler.leave_block(build_block(type, detail));
if (leave_res.isErr()) {
pu->pu_error_msg = leave_res.unwrapErr();
return 1;
}
auto leave_res = pu->pu_handler.leave_span(build_span(type, detail));
if (leave_res.isErr()) {
pu->pu_error_msg = leave_res.unwrapErr();
return 1;
return 0;
}
return 0;
}
static int
md4cpp_text(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata)
{
auto* pu = static_cast<parse_userdata*>(userdata);
auto leave_res = pu->pu_handler.text(type, string_fragment(text, 0, size));
if (leave_res.isErr()) {
pu->pu_error_msg = leave_res.unwrapErr();
return 1;
}
static int
md4cpp_enter_span(MD_SPANTYPE type, void *detail, void *userdata) {
auto *pu = static_cast<parse_userdata *>(userdata);
auto enter_res = pu->pu_handler.enter_span(build_span(type, detail));
if (enter_res.isErr()) {
pu->pu_error_msg = enter_res.unwrapErr();
return 1;
}
return 0;
}
namespace details {
Result<void, std::string>
parse(const string_fragment& sf, event_handler& eh)
{
const char* utf8_errmsg = nullptr;
int utf8_faulty_bytes = 0;
auto utf8_erroff = is_utf8((unsigned char*) sf.data(),
sf.length(),
&utf8_errmsg,
&utf8_faulty_bytes);
if (utf8_errmsg != nullptr) {
return Err(
fmt::format(FMT_STRING("file has invalid UTF-8 at offset {}: {}"),
utf8_erroff,
utf8_errmsg));
return 0;
}
MD_PARSER parser = {0};
auto pu = parse_userdata{eh};
static int
md4cpp_leave_span(MD_SPANTYPE type, void *detail, void *userdata) {
auto *pu = static_cast<parse_userdata *>(userdata);
parser.abi_version = 0;
parser.flags = MD_DIALECT_GITHUB;
parser.enter_block = md4cpp_enter_block;
parser.leave_block = md4cpp_leave_block;
parser.enter_span = md4cpp_enter_span;
parser.leave_span = md4cpp_leave_span;
parser.text = md4cpp_text;
auto leave_res = pu->pu_handler.leave_span(build_span(type, detail));
if (leave_res.isErr()) {
pu->pu_error_msg = leave_res.unwrapErr();
return 1;
}
return 0;
}
auto rc = md_parse(sf.data(), sf.length(), &parser, &pu);
static int
md4cpp_text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *userdata) {
auto *pu = static_cast<parse_userdata *>(userdata);
auto text_res = pu->pu_handler.text(type, string_fragment(text, 0, size));
if (text_res.isErr()) {
pu->pu_error_msg = text_res.unwrapErr();
return 1;
}
if (rc == 0) {
return Ok();
return 0;
}
return Err(pu.pu_error_msg);
}
} // namespace details
namespace details {
Result<void, std::string>
parse(const string_fragment &sf, event_handler &eh) {
const char *utf8_errmsg = nullptr;
int utf8_faulty_bytes = 0;
auto utf8_erroff = is_utf8((unsigned char *) sf.data(),
sf.length(),
&utf8_errmsg,
&utf8_faulty_bytes);
if (utf8_errmsg != nullptr) {
return Err(
fmt::format(FMT_STRING("file has invalid UTF-8 at offset {}: {}"),
utf8_erroff,
utf8_errmsg));
}
MD_PARSER parser = {0};
auto pu = parse_userdata{eh};
parser.abi_version = 0;
parser.flags = MD_DIALECT_GITHUB | MD_FLAG_UNDERLINE;
parser.enter_block = md4cpp_enter_block;
parser.leave_block = md4cpp_leave_block;
parser.enter_span = md4cpp_enter_span;
parser.leave_span = md4cpp_leave_span;
parser.text = md4cpp_text;
auto rc = md_parse(sf.data(), sf.length(), &parser, &pu);
if (rc == 0) {
return Ok();
}
return Err(pu.pu_error_msg);
}
} // namespace details
} // namespace md4cpp

@ -303,3 +303,71 @@ plain_text_source::text_crumbs_for_line(int line,
: breadcrumb::crumb::expected_input_t::index_or_exact;
}
}
nonstd::optional<vis_line_t>
plain_text_source::row_for_anchor(const std::string& id)
{
nonstd::optional<vis_line_t> retval;
if (this->tds_doc_sections.m_sections_root == nullptr) {
return retval;
}
lnav::document::hier_node::depth_first(
this->tds_doc_sections.m_sections_root.get(),
[this, &id, &retval](const lnav::document::hier_node* node) {
for (const auto& child_pair : node->hn_named_children) {
auto child_anchor
= text_anchors::to_anchor_string(child_pair.first);
if (child_anchor == id) {
retval = this->line_for_offset(child_pair.second->hn_start);
}
}
});
return retval;
}
std::unordered_set<std::string>
plain_text_source::get_anchors()
{
std::unordered_set<std::string> retval;
lnav::document::hier_node::depth_first(
this->tds_doc_sections.m_sections_root.get(),
[&retval](const lnav::document::hier_node* node) {
for (const auto& child_pair : node->hn_named_children) {
retval.emplace(
text_anchors::to_anchor_string(child_pair.first));
}
});
return retval;
}
nonstd::optional<std::string>
plain_text_source::anchor_for_row(vis_line_t vl)
{
nonstd::optional<std::string> retval;
if (vl > this->tds_lines.size()
|| this->tds_doc_sections.m_sections_root == nullptr)
{
return retval;
}
const auto& tl = this->tds_lines[vl];
this->tds_doc_sections.m_sections_tree.visit_overlapping(
tl.tl_offset, [&retval](const lnav::document::section_interval_t& iv) {
retval = iv.value.match(
[](const std::string& str) {
return nonstd::make_optional(
text_anchors::to_anchor_string(str));
},
[](size_t) { return nonstd::nullopt; });
});
return retval;
}

@ -40,7 +40,8 @@
class plain_text_source
: public text_sub_source
, public vis_location_history {
, public vis_location_history
, public text_anchors {
public:
struct text_line {
text_line(file_off_t off, attr_line_t value)
@ -82,10 +83,7 @@ public:
size_t text_line_count() override { return this->tds_lines.size(); }
bool empty() const
{
return this->tds_lines.empty();
}
bool empty() const { return this->tds_lines.empty(); }
size_t text_line_width(textview_curses& curses) override;
@ -120,6 +118,10 @@ public:
void text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs) override;
nonstd::optional<vis_line_t> row_for_anchor(const std::string& id) override;
nonstd::optional<std::string> anchor_for_row(vis_line_t vl) override;
std::unordered_set<std::string> get_anchors() override;
protected:
size_t compute_longest_line();

@ -364,7 +364,8 @@ rl_change(readline_curses* rc)
}
if (cmd.c_prompt != nullptr && generation == 0
&& trim(line) == args[0]) {
&& trim(line) == args[0])
{
const auto new_prompt
= cmd.c_prompt(lnav_data.ld_exec_context, line);
@ -386,7 +387,8 @@ rl_change(readline_curses* rc)
auto iter = scripts.as_scripts.find(script_name);
if (iter == scripts.as_scripts.end()
|| iter->second[0].sm_description.empty()) {
|| iter->second[0].sm_description.empty())
{
lnav_data.ld_bottom_source.set_prompt(
"Enter a script to execute: " ABORT_MSG);
} else {
@ -779,7 +781,7 @@ rl_callback_int(readline_curses* rc, bool is_alt)
.with_fd(std::move(fd_copy))
.with_include_in_session(false)
.with_detect_format(false);
lnav_data.ld_files_to_front.emplace_back(desc, 0);
lnav_data.ld_files_to_front.emplace_back(desc, 0_vl);
if (lnav_data.ld_rl_view != nullptr) {
lnav_data.ld_rl_view->set_alt_value(

@ -36,6 +36,7 @@
#include <string>
#include <vector>
#include "base/intern_string.hh"
#include "fmt/format.h"
#include "mapbox/variant.hpp"
@ -85,10 +86,7 @@ public:
return this->end();
}
const_iterator end() const
{
return this->sr_stack.back()->end();
}
const_iterator end() const { return this->sr_stack.back()->end(); }
std::vector<const std::map<std::string, scoped_value_t>*> sr_stack;
};

@ -50,6 +50,7 @@
#include "pcrepp/pcrepp.hh"
#include "readline_context.hh"
#include "readline_highlighters.hh"
#include "shlex.resolver.hh"
#include "sql_help.hh"
#include "sqlite-extension-func.hh"
@ -727,11 +728,109 @@ annotate_sql_with_error(sqlite3* db, const char* sql, const char* tail)
return retval;
}
void
static void
sql_execute_script(sqlite3* db,
const std::map<std::string, scoped_value_t>& global_vars,
const char* src_name,
sqlite3_stmt* stmt,
std::vector<lnav::console::user_message>& errors)
{
std::map<std::string, scoped_value_t> lvars;
bool done = false;
int param_count;
sqlite3_clear_bindings(stmt);
param_count = sqlite3_bind_parameter_count(stmt);
for (int lpc = 0; lpc < param_count; lpc++) {
const char* name;
name = sqlite3_bind_parameter_name(stmt, lpc + 1);
if (name[0] == '$') {
const char* env_value;
auto iter = lvars.find(&name[1]);
if (iter != lvars.end()) {
mapbox::util::apply_visitor(
sqlitepp::bind_visitor(stmt, lpc + 1), iter->second);
} else {
auto giter = global_vars.find(&name[1]);
if (giter != global_vars.end()) {
mapbox::util::apply_visitor(
sqlitepp::bind_visitor(stmt, lpc + 1), giter->second);
} else if ((env_value = getenv(&name[1])) != nullptr) {
sqlite3_bind_text(
stmt, lpc + 1, env_value, -1, SQLITE_TRANSIENT);
} else {
sqlite3_bind_null(stmt, lpc + 1);
}
}
} else {
sqlite3_bind_null(stmt, lpc + 1);
}
}
while (!done) {
int retcode = sqlite3_step(stmt);
switch (retcode) {
case SQLITE_OK:
case SQLITE_DONE:
done = true;
break;
case SQLITE_ROW: {
int ncols = sqlite3_column_count(stmt);
for (int lpc = 0; lpc < ncols; lpc++) {
const char* name = sqlite3_column_name(stmt, lpc);
auto* raw_value = sqlite3_column_value(stmt, lpc);
auto value_type = sqlite3_value_type(raw_value);
scoped_value_t value;
switch (value_type) {
case SQLITE_INTEGER:
value = (int64_t) sqlite3_value_int64(raw_value);
break;
case SQLITE_FLOAT:
value = sqlite3_value_double(raw_value);
break;
case SQLITE_NULL:
value = null_value_t{};
break;
default:
value = string_fragment::from_bytes(
sqlite3_value_text(raw_value),
sqlite3_value_bytes(raw_value));
break;
}
lvars[name] = value;
}
break;
}
default: {
const auto* sql_str = sqlite3_sql(stmt);
auto sql_content
= annotate_sql_with_error(db, sql_str, nullptr);
errors.emplace_back(
lnav::console::user_message::error(
"failed to execute SQL statement")
.with_reason(sqlite3_errmsg_to_attr_line(db))
.with_snippet(lnav::console::snippet::from(
intern_string::lookup(src_name), sql_content)));
done = true;
break;
}
}
}
sqlite3_reset(stmt);
}
static void
sql_compile_script(sqlite3* db,
const std::map<std::string, scoped_value_t>& global_vars,
const char* src_name,
const char* script_orig,
std::vector<sqlite3_stmt*>& stmts,
std::vector<lnav::console::user_message>& errors)
{
const char* script = script_orig;
@ -766,115 +865,27 @@ sql_compile_script(sqlite3* db,
intern_string::lookup(src_name), sql_content)
.with_line(line_number)));
break;
} else if (script == tail) {
}
if (script == tail) {
break;
} else if (stmt == nullptr) {
}
if (stmt == nullptr) {
} else {
stmts.push_back(stmt.release());
sql_execute_script(db, global_vars, src_name, stmt.in(), errors);
}
script = tail;
}
}
static void
sql_execute_script(sqlite3* db,
const char* src_name,
const std::vector<sqlite3_stmt*>& stmts,
std::vector<lnav::console::user_message>& errors)
{
std::map<std::string, std::string> lvars;
for (sqlite3_stmt* stmt : stmts) {
bool done = false;
int param_count;
sqlite3_clear_bindings(stmt);
param_count = sqlite3_bind_parameter_count(stmt);
for (int lpc = 0; lpc < param_count; lpc++) {
const char* name;
name = sqlite3_bind_parameter_name(stmt, lpc + 1);
if (name[0] == '$') {
std::map<std::string, std::string>::iterator iter;
const char* env_value;
if ((iter = lvars.find(&name[1])) != lvars.end()) {
sqlite3_bind_text(stmt,
lpc + 1,
iter->second.c_str(),
-1,
SQLITE_TRANSIENT);
} else if ((env_value = getenv(&name[1])) != nullptr) {
sqlite3_bind_text(
stmt, lpc + 1, env_value, -1, SQLITE_TRANSIENT);
} else {
sqlite3_bind_null(stmt, lpc + 1);
}
} else {
sqlite3_bind_null(stmt, lpc + 1);
}
}
while (!done) {
int retcode = sqlite3_step(stmt);
switch (retcode) {
case SQLITE_OK:
case SQLITE_DONE:
done = true;
break;
case SQLITE_ROW: {
int ncols = sqlite3_column_count(stmt);
for (int lpc = 0; lpc < ncols; lpc++) {
const char* name = sqlite3_column_name(stmt, lpc);
const char* value
= (const char*) sqlite3_column_text(stmt, lpc);
lvars[name] = value;
}
break;
}
default: {
const auto* sql_str = sqlite3_sql(stmt);
auto sql_content
= annotate_sql_with_error(db, sql_str, nullptr);
errors.emplace_back(
lnav::console::user_message::error(
"failed to execute SQL statement")
.with_reason(sqlite3_errmsg_to_attr_line(db))
.with_snippet(lnav::console::snippet::from(
intern_string::lookup(src_name), sql_content)));
done = true;
break;
}
}
}
sqlite3_reset(stmt);
}
}
void
sql_execute_script(sqlite3* db,
const std::map<std::string, scoped_value_t>& global_vars,
const char* src_name,
const char* script,
std::vector<lnav::console::user_message>& errors)
{
std::vector<sqlite3_stmt*> stmts;
auto init_error_count = errors.size();
sql_compile_script(db, src_name, script, stmts, errors);
if (errors.size() == init_error_count) {
sql_execute_script(db, src_name, stmts, errors);
}
for (sqlite3_stmt* stmt : stmts) {
sqlite3_finalize(stmt);
}
sql_compile_script(db, global_vars, src_name, script, errors);
}
static struct {

@ -105,16 +105,12 @@ char* sql_quote_ident(const char* ident);
std::string sql_safe_ident(const string_fragment& ident);
void sql_compile_script(sqlite3* db,
const char* src_name,
const char* script,
std::vector<sqlite3_stmt*>& stmts,
std::vector<lnav::console::user_message>& errors);
void sql_execute_script(sqlite3* db,
const char* src_name,
const char* script,
std::vector<lnav::console::user_message>& errors);
void sql_execute_script(
sqlite3* db,
const std::map<std::string, scoped_value_t>& global_vars,
const char* src_name,
const char* script,
std::vector<lnav::console::user_message>& errors);
int guess_type_from_pcre(const std::string& pattern, std::string& collator);

@ -46,16 +46,18 @@ int sqlite3_series_init(sqlite3* db,
const sqlite3_api_routines* pApi);
}
sqlite_registration_func_t sqlite_registration_funcs[]
= {common_extension_functions,
state_extension_functions,
string_extension_functions,
network_extension_functions,
fs_extension_functions,
json_extension_functions,
time_extension_functions,
nullptr};
sqlite_registration_func_t sqlite_registration_funcs[] = {
common_extension_functions,
state_extension_functions,
string_extension_functions,
network_extension_functions,
fs_extension_functions,
json_extension_functions,
yaml_extension_functions,
time_extension_functions,
nullptr,
};
int
register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)

@ -88,11 +88,15 @@ int json_extension_functions(struct FuncDef** basic_funcs,
int time_extension_functions(struct FuncDef** basic_funcs,
struct FuncDefAgg** agg_funcs);
int yaml_extension_functions(struct FuncDef** basic_funcs,
struct FuncDefAgg** agg_funcs);
extern sqlite_registration_func_t sqlite_registration_funcs[];
int register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs);
extern "C" {
extern "C"
{
int sqlite3_db_dump(
sqlite3* db, /* The database connection */
const char* zSchema, /* Which schema to dump. Usually "main". */

@ -38,6 +38,7 @@
#include "base/auto_mem.hh"
#include "optional.hpp"
#include "shlex.resolver.hh"
/* XXX figure out how to do this with the template */
void sqlite_close_wrapper(void* mem);
@ -60,6 +61,48 @@ quote(const nonstd::optional<std::string>& str)
extern const char* ERROR_PREFIX;
struct bind_visitor {
bind_visitor(sqlite3_stmt* stmt, int index) : bv_stmt(stmt), bv_index(index)
{
}
void operator()(const std::string& str) const
{
sqlite3_bind_text(this->bv_stmt,
this->bv_index,
str.c_str(),
str.size(),
SQLITE_TRANSIENT);
}
void operator()(const string_fragment& str) const
{
sqlite3_bind_text(this->bv_stmt,
this->bv_index,
str.data(),
str.length(),
SQLITE_TRANSIENT);
}
void operator()(null_value_t) const
{
sqlite3_bind_null(this->bv_stmt, this->bv_index);
}
void operator()(int64_t value) const
{
sqlite3_bind_int64(this->bv_stmt, this->bv_index, value);
}
void operator()(double value) const
{
sqlite3_bind_double(this->bv_stmt, this->bv_index, value);
}
sqlite3_stmt* bv_stmt;
int bv_index;
};
} // namespace sqlitepp
#endif

@ -0,0 +1,333 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <map>
#include "static_file_vtab.hh"
#include <stdlib.h>
#include <string.h>
#include "base/auto_mem.hh"
#include "base/fs_util.hh"
#include "base/lnav_log.hh"
#include "base/paths.hh"
#include "config.h"
#include "ghc/filesystem.hpp"
#include "lnav.hh"
#include "vtab_module.hh"
const char* const STATIC_FILE_CREATE_STMT = R"(
-- Access static files in the lnav configuration directories
CREATE TABLE lnav_static_files (
name TEXT PRIMARY KEY,
filepath TEXT,
content BLOB HIDDEN
);
)";
struct vtab {
sqlite3_vtab base;
sqlite3* db;
};
struct static_file_info {
ghc::filesystem::path sfi_path;
};
struct vtab_cursor {
sqlite3_vtab_cursor base;
std::map<std::string, static_file_info>::iterator vc_files_iter;
std::map<std::string, static_file_info> vc_files;
};
static int sfvt_destructor(sqlite3_vtab* p_svt);
static int
sfvt_create(sqlite3* db,
void* pAux,
int argc,
const char* const* argv,
sqlite3_vtab** pp_vt,
char** pzErr)
{
vtab* p_vt;
/* Allocate the sqlite3_vtab/vtab structure itself */
p_vt = (vtab*) sqlite3_malloc(sizeof(*p_vt));
if (p_vt == nullptr) {
return SQLITE_NOMEM;
}
memset(&p_vt->base, 0, sizeof(sqlite3_vtab));
p_vt->db = db;
*pp_vt = &p_vt->base;
int rc = sqlite3_declare_vtab(db, STATIC_FILE_CREATE_STMT);
return rc;
}
static int
sfvt_destructor(sqlite3_vtab* p_svt)
{
vtab* p_vt = (vtab*) p_svt;
/* Free the SQLite structure */
sqlite3_free(p_vt);
return SQLITE_OK;
}
static int
sfvt_connect(sqlite3* db,
void* p_aux,
int argc,
const char* const* argv,
sqlite3_vtab** pp_vt,
char** pzErr)
{
return sfvt_create(db, p_aux, argc, argv, pp_vt, pzErr);
}
static int
sfvt_disconnect(sqlite3_vtab* pVtab)
{
return sfvt_destructor(pVtab);
}
static int
sfvt_destroy(sqlite3_vtab* p_vt)
{
return sfvt_destructor(p_vt);
}
static int sfvt_next(sqlite3_vtab_cursor* cur);
static void
find_static_files(vtab_cursor* p_cur, const ghc::filesystem::path& dir)
{
auto& file_map = p_cur->vc_files;
std::error_code ec;
for (const auto& format_dir_entry :
ghc::filesystem::directory_iterator(dir, ec))
{
if (!format_dir_entry.is_directory()) {
continue;
}
auto format_static_files_dir = format_dir_entry.path() / "static-files";
log_debug("format static files: %s", format_static_files_dir.c_str());
for (const auto& static_file_entry :
ghc::filesystem::recursive_directory_iterator(
format_static_files_dir, ec))
{
auto rel_path = ghc::filesystem::relative(static_file_entry.path(),
format_static_files_dir);
file_map[rel_path.string()] = {static_file_entry.path()};
}
}
}
static int
sfvt_open(sqlite3_vtab* p_svt, sqlite3_vtab_cursor** pp_cursor)
{
vtab* p_vt = (vtab*) p_svt;
p_vt->base.zErrMsg = NULL;
vtab_cursor* p_cur = (vtab_cursor*) new vtab_cursor();
if (p_cur == nullptr) {
return SQLITE_NOMEM;
}
*pp_cursor = (sqlite3_vtab_cursor*) p_cur;
p_cur->base.pVtab = p_svt;
for (const auto& config_path : lnav_data.ld_config_paths) {
auto formats_root = config_path / "formats";
log_debug("format root: %s", formats_root.c_str());
find_static_files(p_cur, formats_root);
auto configs_root = config_path / "configs";
log_debug("configs root: %s", configs_root.c_str());
find_static_files(p_cur, configs_root);
}
return SQLITE_OK;
}
static int
sfvt_close(sqlite3_vtab_cursor* cur)
{
vtab_cursor* p_cur = (vtab_cursor*) cur;
p_cur->vc_files_iter = p_cur->vc_files.end();
/* Free cursor struct. */
delete p_cur;
return SQLITE_OK;
}
static int
sfvt_eof(sqlite3_vtab_cursor* cur)
{
vtab_cursor* vc = (vtab_cursor*) cur;
return vc->vc_files_iter == vc->vc_files.end();
}
static int
sfvt_next(sqlite3_vtab_cursor* cur)
{
vtab_cursor* vc = (vtab_cursor*) cur;
if (vc->vc_files_iter != vc->vc_files.end()) {
++vc->vc_files_iter;
}
return SQLITE_OK;
}
static int
sfvt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
{
vtab_cursor* vc = (vtab_cursor*) cur;
switch (col) {
case 0:
to_sqlite(ctx, vc->vc_files_iter->first);
break;
case 1: {
sqlite3_result_text(ctx,
vc->vc_files_iter->second.sfi_path.c_str(),
-1,
SQLITE_TRANSIENT);
break;
}
case 2: {
auto read_res = lnav::filesystem::read_file(
vc->vc_files_iter->second.sfi_path);
if (read_res.isErr()) {
auto um = lnav::console::user_message::error(
"unable to read static file")
.with_reason(read_res.unwrapErr());
to_sqlite(ctx, um);
} else {
auto str = read_res.unwrap();
sqlite3_result_blob(
ctx, str.c_str(), str.size(), SQLITE_TRANSIENT);
}
break;
}
}
return SQLITE_OK;
}
static int
sfvt_rowid(sqlite3_vtab_cursor* cur, sqlite_int64* p_rowid)
{
vtab_cursor* p_cur = (vtab_cursor*) cur;
*p_rowid = std::distance(p_cur->vc_files.begin(), p_cur->vc_files_iter);
return SQLITE_OK;
}
static int
sfvt_best_index(sqlite3_vtab* tab, sqlite3_index_info* p_info)
{
return SQLITE_OK;
}
static int
sfvt_filter(sqlite3_vtab_cursor* cur,
int idxNum,
const char* idxStr,
int argc,
sqlite3_value** argv)
{
vtab_cursor* p_cur = (vtab_cursor*) cur;
p_cur->vc_files_iter = p_cur->vc_files.begin();
return SQLITE_OK;
}
static sqlite3_module static_file_vtab_module = {
0, /* iVersion */
sfvt_create, /* xCreate - create a vtable */
sfvt_connect, /* xConnect - associate a vtable with a connection */
sfvt_best_index, /* xBestIndex - best index */
sfvt_disconnect, /* xDisconnect - disassociate a vtable with a connection
*/
sfvt_destroy, /* xDestroy - destroy a vtable */
sfvt_open, /* xOpen - open a cursor */
sfvt_close, /* xClose - close a cursor */
sfvt_filter, /* xFilter - configure scan constraints */
sfvt_next, /* xNext - advance a cursor */
sfvt_eof, /* xEof - inidicate end of result set*/
sfvt_column, /* xColumn - read data */
sfvt_rowid, /* xRowid - read data */
nullptr, /* xUpdate - write data */
nullptr, /* xBegin - begin transaction */
nullptr, /* xSync - sync transaction */
nullptr, /* xCommit - commit transaction */
nullptr, /* xRollback - rollback transaction */
nullptr, /* xFindFunction - function overloading */
};
int
register_static_file_vtab(sqlite3* db)
{
auto_mem<char, sqlite3_free> errmsg;
int rc;
rc = sqlite3_create_module(
db, "lnav_static_file_vtab_impl", &static_file_vtab_module, nullptr);
ensure(rc == SQLITE_OK);
if ((rc = sqlite3_exec(db,
"CREATE VIRTUAL TABLE lnav_static_files USING "
"lnav_static_file_vtab_impl()",
nullptr,
nullptr,
errmsg.out()))
!= SQLITE_OK)
{
fprintf(stderr,
"unable to create lnav_static_file table %s\n",
errmsg.in());
}
return rc;
}

@ -0,0 +1,39 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef static_file_vtab_hh
#define static_file_vtab_hh
#include <sqlite3.h>
int register_static_file_vtab(sqlite3* db);
extern const char* const STATIC_FILE_CREATE_STMT;
#endif

@ -39,6 +39,7 @@
#include "base/result.h"
#include "log_level.hh"
#include "mapbox/variant.hpp"
#include "yajlpp/yajlpp.hh"
struct rgb_color {
static Result<rgb_color, std::string> from_str(const string_fragment& sf);
@ -121,17 +122,13 @@ struct term_color_palette {
namespace styling {
struct semantic {
};
struct semantic {};
class color_unit {
public:
static Result<color_unit, std::string> from_str(const string_fragment& sf);
static color_unit make_empty()
{
return color_unit{rgb_color{}};
}
static color_unit make_empty() { return color_unit{rgb_color{}}; }
bool empty() const
{
@ -164,65 +161,65 @@ struct highlighter_config {
struct lnav_theme {
std::map<std::string, std::string> lt_vars;
style_config lt_style_identifier;
style_config lt_style_text;
style_config lt_style_alt_text;
style_config lt_style_ok;
style_config lt_style_info;
style_config lt_style_error;
style_config lt_style_warning;
style_config lt_style_popup;
style_config lt_style_focused;
style_config lt_style_disabled_focused;
style_config lt_style_scrollbar;
style_config lt_style_hidden;
style_config lt_style_adjusted_time;
style_config lt_style_skewed_time;
style_config lt_style_offset_time;
style_config lt_style_invalid_msg;
style_config lt_style_status_title;
style_config lt_style_status_title_hotkey;
style_config lt_style_status_disabled_title;
style_config lt_style_status_subtitle;
style_config lt_style_status_info;
style_config lt_style_status_hotkey;
style_config lt_style_quoted_code;
style_config lt_style_code_border;
style_config lt_style_keyword;
style_config lt_style_string;
style_config lt_style_comment;
style_config lt_style_doc_directive;
style_config lt_style_variable;
style_config lt_style_symbol;
style_config lt_style_number;
style_config lt_style_re_special;
style_config lt_style_re_repeat;
style_config lt_style_diff_delete;
style_config lt_style_diff_add;
style_config lt_style_diff_section;
style_config lt_style_low_threshold;
style_config lt_style_med_threshold;
style_config lt_style_high_threshold;
style_config lt_style_status;
style_config lt_style_warn_status;
style_config lt_style_alert_status;
style_config lt_style_active_status;
style_config lt_style_inactive_status;
style_config lt_style_inactive_alert_status;
style_config lt_style_file;
style_config lt_style_header[6];
style_config lt_style_hr;
style_config lt_style_hyperlink;
style_config lt_style_list_glyph;
style_config lt_style_breadcrumb;
style_config lt_style_table_border;
style_config lt_style_table_header;
style_config lt_style_quote_border;
style_config lt_style_quoted_text;
style_config lt_style_footnote_border;
style_config lt_style_footnote_text;
style_config lt_style_snippet_border;
std::map<log_level_t, style_config> lt_level_styles;
positioned_property<style_config> lt_style_identifier;
positioned_property<style_config> lt_style_text;
positioned_property<style_config> lt_style_alt_text;
positioned_property<style_config> lt_style_ok;
positioned_property<style_config> lt_style_info;
positioned_property<style_config> lt_style_error;
positioned_property<style_config> lt_style_warning;
positioned_property<style_config> lt_style_popup;
positioned_property<style_config> lt_style_focused;
positioned_property<style_config> lt_style_disabled_focused;
positioned_property<style_config> lt_style_scrollbar;
positioned_property<style_config> lt_style_hidden;
positioned_property<style_config> lt_style_adjusted_time;
positioned_property<style_config> lt_style_skewed_time;
positioned_property<style_config> lt_style_offset_time;
positioned_property<style_config> lt_style_invalid_msg;
positioned_property<style_config> lt_style_status_title;
positioned_property<style_config> lt_style_status_title_hotkey;
positioned_property<style_config> lt_style_status_disabled_title;
positioned_property<style_config> lt_style_status_subtitle;
positioned_property<style_config> lt_style_status_info;
positioned_property<style_config> lt_style_status_hotkey;
positioned_property<style_config> lt_style_quoted_code;
positioned_property<style_config> lt_style_code_border;
positioned_property<style_config> lt_style_keyword;
positioned_property<style_config> lt_style_string;
positioned_property<style_config> lt_style_comment;
positioned_property<style_config> lt_style_doc_directive;
positioned_property<style_config> lt_style_variable;
positioned_property<style_config> lt_style_symbol;
positioned_property<style_config> lt_style_number;
positioned_property<style_config> lt_style_re_special;
positioned_property<style_config> lt_style_re_repeat;
positioned_property<style_config> lt_style_diff_delete;
positioned_property<style_config> lt_style_diff_add;
positioned_property<style_config> lt_style_diff_section;
positioned_property<style_config> lt_style_low_threshold;
positioned_property<style_config> lt_style_med_threshold;
positioned_property<style_config> lt_style_high_threshold;
positioned_property<style_config> lt_style_status;
positioned_property<style_config> lt_style_warn_status;
positioned_property<style_config> lt_style_alert_status;
positioned_property<style_config> lt_style_active_status;
positioned_property<style_config> lt_style_inactive_status;
positioned_property<style_config> lt_style_inactive_alert_status;
positioned_property<style_config> lt_style_file;
positioned_property<style_config> lt_style_header[6];
positioned_property<style_config> lt_style_hr;
positioned_property<style_config> lt_style_hyperlink;
positioned_property<style_config> lt_style_list_glyph;
positioned_property<style_config> lt_style_breadcrumb;
positioned_property<style_config> lt_style_table_border;
positioned_property<style_config> lt_style_table_header;
positioned_property<style_config> lt_style_quote_border;
positioned_property<style_config> lt_style_quoted_text;
positioned_property<style_config> lt_style_footnote_border;
positioned_property<style_config> lt_style_footnote_text;
positioned_property<style_config> lt_style_snippet_border;
std::map<log_level_t, positioned_property<style_config>> lt_level_styles;
std::map<std::string, highlighter_config> lt_highlights;
};

@ -349,7 +349,7 @@ tailer::looper::host_tailer::for_host(const std::string& netloc)
}
std::vector<std::string> error_queue;
log_debug("starting err reader");
log_debug("tailer(%s): starting err reader", netloc.c_str());
std::thread err_reader([netloc,
err = std::move(err_pipe.read_end()),
&error_queue]() mutable {
@ -358,25 +358,30 @@ tailer::looper::host_tailer::for_host(const std::string& netloc)
read_err_pipe(netloc, err, error_queue);
});
log_debug("writing to child");
log_debug("tailer(%s): writing to child", netloc.c_str());
auto sf = tailer_bin[0].to_string_fragment();
ssize_t total_bytes = 0;
bool write_failed = false;
while (total_bytes < sf.length()) {
log_debug("attempting to write %d", sf.length() - total_bytes);
auto rc = write(
in_pipe.write_end(), sf.data(), sf.length() - total_bytes);
log_debug("wrote %d", rc);
if (rc < 0) {
log_error(" tailer(%s): write failed -- %s",
netloc.c_str(),
strerror(errno));
write_failed = true;
break;
}
log_debug(" wrote %d", rc);
total_bytes += rc;
}
in_pipe.write_end().reset();
while (true) {
while (!write_failed) {
char buffer[1024];
auto rc = read(out_pipe.read_end(), buffer, sizeof(buffer));
@ -1142,6 +1147,7 @@ tailer::looper::remote_path_queue::send_synced_to_main(
void
tailer::looper::report_error(std::string path, std::string msg)
{
log_error("reporting error: %s -- %s", path.c_str(), msg.c_str());
isc::to<main_looper&, services::main_t>().send([=](auto& mlooper) {
file_collection fc;

@ -42,16 +42,17 @@
enum class text_format_t {
TF_UNKNOWN,
TF_C_LIKE,
TF_JAVA,
TF_JSON,
TF_LOG,
TF_MAN,
TF_MARKDOWN,
TF_PYTHON,
TF_RUST,
TF_JAVA,
TF_C_LIKE,
TF_SQL,
TF_XML,
TF_JSON,
TF_MAN,
TF_MARKDOWN,
TF_YAML,
};
namespace fmt {
@ -95,6 +96,9 @@ struct formatter<text_format_t> : formatter<string_view> {
case text_format_t::TF_MARKDOWN:
name = "text/markdown";
break;
case text_format_t::TF_YAML:
name = "application/yaml";
break;
}
return formatter<string_view>::format(name, ctx);
}

@ -31,9 +31,13 @@
#include "base/ansi_scrubber.hh"
#include "base/fs_util.hh"
#include "base/injector.hh"
#include "base/itertools.hh"
#include "bound_tags.hh"
#include "config.h"
#include "lnav.events.hh"
#include "md2attr_line.hh"
#include "sqlitepp.hh"
using namespace lnav::roles::literals;
@ -439,6 +443,10 @@ textfile_sub_source::rescan_files(
textfile_sub_source::scan_callback& callback,
nonstd::optional<ui_clock::time_point> deadline)
{
static auto& lnav_db
= injector::get<auto_mem<sqlite3, sqlite_close_wrapper>&,
sqlite_db_tag>();
file_iterator iter;
bool retval = false;
@ -502,6 +510,7 @@ textfile_sub_source::rescan_files(
if (read_res.isOk()) {
auto content = read_res.unwrap();
auto content_sf = string_fragment{content};
std::string frontmatter;
auto front_matter_terminator = content.length() > 8
? content.find("\n---\n", 4)
: std::string::npos;
@ -509,6 +518,8 @@ textfile_sub_source::rescan_files(
if (startswith(content, "---\n")
&& front_matter_terminator != std::string::npos)
{
frontmatter
= content.substr(4, front_matter_terminator - 3);
content_sf
= content_sf.substr(front_matter_terminator + 4);
}
@ -524,7 +535,21 @@ textfile_sub_source::rescan_files(
rf.rf_text_source = std::make_unique<plain_text_source>();
rf.rf_text_source->register_view(this->tss_view);
if (parse_res.isOk()) {
auto& lf_meta = lf->get_embedded_metadata();
rf.rf_text_source->replace_with(parse_res.unwrap());
if (!frontmatter.empty()) {
lf_meta["net.daringfireball.markdown.frontmatter"]
= {text_format_t::TF_YAML, frontmatter};
}
lnav::events::publish(
lnav_db,
lnav::events::file::format_detected{
lf->get_filename(),
fmt::to_string(lf->get_text_format()),
});
} else {
auto view_content
= lnav::console::user_message::error(
@ -637,3 +662,147 @@ textfile_sub_source::quiesce()
lf->quiesce();
}
}
nonstd::optional<vis_line_t>
textfile_sub_source::row_for_anchor(const std::string& id)
{
auto lf = this->current_file();
if (!lf) {
return nonstd::nullopt;
}
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter != this->tss_rendered_files.end()) {
return rend_iter->second.rf_text_source->row_for_anchor(id);
}
auto iter = this->tss_doc_metadata.find(lf->get_filename());
if (iter == this->tss_doc_metadata.end()) {
return nonstd::nullopt;
}
const auto& meta = iter->second.ms_metadata;
nonstd::optional<vis_line_t> retval;
lnav::document::hier_node::depth_first(
meta.m_sections_root.get(),
[lf, &id, &retval](const lnav::document::hier_node* node) {
for (const auto& child_pair : node->hn_named_children) {
auto child_anchor
= text_anchors::to_anchor_string(child_pair.first);
if (child_anchor == id) {
auto ll_opt
= lf->line_for_offset(child_pair.second->hn_start);
if (ll_opt != lf->end()) {
retval = vis_line_t(
std::distance(lf->cbegin(), ll_opt.value()));
}
}
}
});
return retval;
}
std::unordered_set<std::string>
textfile_sub_source::get_anchors()
{
std::unordered_set<std::string> retval;
auto lf = this->current_file();
if (!lf) {
return retval;
}
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter != this->tss_rendered_files.end()) {
return rend_iter->second.rf_text_source->get_anchors();
}
auto iter = this->tss_doc_metadata.find(lf->get_filename());
if (iter == this->tss_doc_metadata.end()) {
return retval;
}
const auto& meta = iter->second.ms_metadata;
lnav::document::hier_node::depth_first(
meta.m_sections_root.get(),
[&retval](const lnav::document::hier_node* node) {
if (retval.size() > 100) {
return;
}
for (const auto& child_pair : node->hn_named_children) {
retval.emplace(
text_anchors::to_anchor_string(child_pair.first));
}
});
return retval;
}
nonstd::optional<std::string>
textfile_sub_source::anchor_for_row(vis_line_t vl)
{
nonstd::optional<std::string> retval;
auto lf = this->current_file();
if (!lf) {
return retval;
}
auto rend_iter = this->tss_rendered_files.find(lf->get_filename());
if (rend_iter != this->tss_rendered_files.end()) {
return rend_iter->second.rf_text_source->anchor_for_row(vl);
}
auto iter = this->tss_doc_metadata.find(lf->get_filename());
if (iter == this->tss_doc_metadata.end()) {
return retval;
}
auto* lfo = dynamic_cast<line_filter_observer*>(lf->get_logline_observer());
auto ll_iter = lf->begin() + lfo->lfo_filter_state.tfs_index[vl];
auto ll_next_iter = ll_iter + 1;
auto end_offset = (ll_next_iter == lf->end())
? lf->get_index_size() - 1
: ll_next_iter->get_offset() - 1;
iter->second.ms_metadata.m_sections_tree.visit_overlapping(
ll_iter->get_offset(),
end_offset,
[&retval](const lnav::document::section_interval_t& iv) {
retval = iv.value.match(
[](const std::string& str) {
return nonstd::make_optional(
text_anchors::to_anchor_string(str));
},
[](size_t) { return nonstd::nullopt; });
});
return retval;
}
bool
textfile_sub_source::to_front(const std::string& filename)
{
auto lf_opt = this->tss_files
| lnav::itertools::find_if([&filename](const auto& elem) {
return elem->get_filename() == filename;
});
if (!lf_opt) {
lf_opt = this->tss_hidden_files
| lnav::itertools::find_if([&filename](const auto& elem) {
return elem->get_filename() == filename;
});
}
if (!lf_opt) {
return false;
}
this->to_front(*(lf_opt.value()));
return true;
}

@ -40,7 +40,8 @@
class textfile_sub_source
: public text_sub_source
, public vis_location_history {
, public vis_location_history
, public text_anchors {
public:
using file_iterator = std::deque<std::shared_ptr<logfile>>::iterator;
@ -94,6 +95,8 @@ public:
void to_front(const std::shared_ptr<logfile>& lf);
bool to_front(const std::string& filename);
void set_top_from_off(file_off_t off);
void rotate_left();
@ -133,6 +136,12 @@ public:
void text_crumbs_for_line(int line,
std::vector<breadcrumb::crumb>& crumbs) override;
nonstd::optional<vis_line_t> row_for_anchor(const std::string& id) override;
nonstd::optional<std::string> anchor_for_row(vis_line_t vl) override;
std::unordered_set<std::string> get_anchors() override;
void quiesce() override;
private:

@ -644,13 +644,16 @@ textview_curses::execute_search(const std::string& regex_orig)
}
}
void
textview_curses::horiz_shift(vis_line_t start,
vis_line_t end,
int off_start,
std::pair<int, int>& range_out)
nonstd::optional<std::pair<int, int>>
textview_curses::horiz_shift(vis_line_t start, vis_line_t end, int off_start)
{
auto& hl = this->tc_highlights[{highlight_source_t::PREVIEW, "search"}];
auto hl_iter
= this->tc_highlights.find({highlight_source_t::PREVIEW, "search"});
if (hl_iter == this->tc_highlights.end()
|| hl_iter->second.h_regex == nullptr)
{
return nonstd::nullopt;
}
int prev_hit = -1, next_hit = INT_MAX;
for (; start < end; ++start) {
@ -660,7 +663,7 @@ textview_curses::horiz_shift(vis_line_t start,
const auto& str = rows[0].get_string();
pcre_context_static<60> pc;
pcre_input pi(str);
while (hl.h_regex->match(pc, pi)) {
while (hl_iter->second.h_regex->match(pc, pi)) {
if (pc.all()->c_begin < off_start) {
prev_hit = std::max(prev_hit, pc.all()->c_begin);
} else if (pc.all()->c_begin > off_start) {
@ -669,7 +672,10 @@ textview_curses::horiz_shift(vis_line_t start,
}
}
range_out = std::make_pair(prev_hit, next_hit);
if (prev_hit == -1 && next_hit == INT_MAX) {
return nonstd::nullopt;
}
return std::make_pair(prev_hit, next_hit);
}
void
@ -1117,3 +1123,12 @@ logfile_filter_state::content_line_to_vis_line(uint32_t line)
return nonstd::make_optional(std::distance(this->tfs_index.begin(), iter));
}
std::string
text_anchors::to_anchor_string(const std::string& raw)
{
static const pcrepp ANCHOR_RE(R"([^\w]+)");
return fmt::format(FMT_STRING("#{}"),
ANCHOR_RE.replace(tolower(raw).c_str(), "-"));
}

@ -237,6 +237,20 @@ protected:
};
};
class text_anchors {
public:
virtual ~text_anchors() = default;
static std::string to_anchor_string(const std::string& raw);
virtual nonstd::optional<vis_line_t> row_for_anchor(const std::string& id)
= 0;
virtual nonstd::optional<std::string> anchor_for_row(vis_line_t vl) = 0;
virtual std::unordered_set<std::string> get_anchors() = 0;
};
class location_history {
public:
virtual ~location_history() = default;
@ -514,15 +528,14 @@ public:
return this->tc_delegate;
}
void horiz_shift(vis_line_t start,
vis_line_t end,
int off_start,
std::pair<int, int>& range_out);
nonstd::optional<std::pair<int, int>> horiz_shift(vis_line_t start,
vis_line_t end,
int off_start);
void set_search_action(action sa)
{
this->tc_search_action = std::move(sa);
};
}
void grep_end_batch(grep_proc<vis_line_t>& gp);
void grep_end(grep_proc<vis_line_t>& gp);

File diff suppressed because it is too large Load Diff

@ -49,6 +49,7 @@ TIME_FORMATS = \
"%m/%d/%Y %H:%M:%S" \
"%d/%b/%y %H:%M:%S" \
"%m%d %H:%M:%S" \
"%Y%m%d %H:%M:%S" \
"%Y%m%d.%H%M%S" \
"%H:%M:%S" \
"%M:%S" \

@ -325,7 +325,8 @@ view_curses::mvwattrline(WINDOW* window,
} else if (iter->sa_type == &VC_STYLE) {
attrs = iter->sa_value.get<text_attrs>();
} else if (iter->sa_type == &SA_LEVEL) {
attrs = vc.vc_level_attrs[iter->sa_value.get<int64_t>()].first;
attrs = vc.attrs_for_level(
(log_level_t) iter->sa_value.get<int64_t>());
} else if (iter->sa_type == &VC_ROLE) {
auto role = iter->sa_value.get<role_t>();
attrs = vc.attrs_for_role(role);
@ -581,13 +582,26 @@ attr_for_colors(nonstd::optional<short> fg, nonstd::optional<short> bg)
return retval;
}
std::pair<text_attrs, text_attrs>
view_colors::role_attrs
view_colors::to_attrs(const lnav_theme& lt,
const style_config& sc,
const style_config& fallback_sc,
const positioned_property<style_config>& pp_sc,
const positioned_property<style_config>& pp_fallback_sc,
lnav_config_listener::error_reporter& reporter)
{
const auto& sc = pp_sc.pp_value;
const auto& fallback_sc = pp_fallback_sc.pp_value;
std::string fg1, bg1, fg_color, bg_color;
intern_string_t role_class;
if (!pp_sc.pp_path.empty()) {
auto role_class_path
= ghc::filesystem::path(pp_sc.pp_path.to_string()).parent_path();
auto inner = role_class_path.filename().string();
auto outer = role_class_path.parent_path().filename().string();
role_class = intern_string::lookup(
fmt::format(FMT_STRING("-lnav_{}_{}"), outer, inner));
}
fg1 = sc.sc_color;
if (fg1.empty()) {
@ -629,7 +643,7 @@ view_colors::to_attrs(const lnav_theme& lt,
retval2.ta_attrs |= A_BOLD;
}
return {retval1, retval2};
return {retval1, retval2, role_class};
}
void
@ -695,14 +709,17 @@ view_colors::init_roles(const lnav_theme& lt,
if (lnav_config.lc_ui_dim_text) {
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_TEXT)]
.first.ta_attrs
.ra_normal.ta_attrs
|= A_DIM;
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_TEXT)]
.second.ta_attrs
.ra_reverse.ta_attrs
|= A_DIM;
}
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_SEARCH)]
= std::make_pair(text_attrs{A_REVERSE}, text_attrs{A_REVERSE});
= role_attrs{text_attrs{A_REVERSE}, text_attrs{A_REVERSE}};
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_SEARCH)]
.ra_class_name
= intern_string::lookup("-lnav_styles_search");
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_IDENTIFIER)]
= this->to_attrs(
lt, lt.lt_style_identifier, lt.lt_style_text, reporter);
@ -743,17 +760,19 @@ view_colors::init_roles(const lnav_theme& lt,
= this->to_attrs(
lt, lt.lt_style_active_status, lt.lt_style_status, reporter);
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_ACTIVE_STATUS2)]
= std::make_pair(this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_ACTIVE_STATUS)]
.first,
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_ACTIVE_STATUS)]
.second);
= role_attrs{
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_ACTIVE_STATUS)]
.ra_normal,
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_ACTIVE_STATUS)]
.ra_reverse,
};
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_ACTIVE_STATUS2)]
.first.ta_attrs
.ra_normal.ta_attrs
|= A_BOLD;
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_ACTIVE_STATUS2)]
.second.ta_attrs
.ra_reverse.ta_attrs
|= A_BOLD;
this->vc_role_attrs[lnav::enums::to_underlying(role_t::VCR_STATUS_TITLE)]
= this->to_attrs(
@ -824,61 +843,69 @@ view_colors::init_roles(const lnav_theme& lt,
lt, lt.lt_style_snippet_border, lt.lt_style_text, reporter);
{
style_config stitch_sc;
positioned_property<style_config> stitch_sc;
stitch_sc.sc_color = lt.lt_style_status_subtitle.sc_background_color;
stitch_sc.sc_background_color
= lt.lt_style_status_title.sc_background_color;
stitch_sc.pp_value.sc_color
= lt.lt_style_status_subtitle.pp_value.sc_background_color;
stitch_sc.pp_value.sc_background_color
= lt.lt_style_status_title.pp_value.sc_background_color;
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_STATUS_STITCH_TITLE_TO_SUB)]
= this->to_attrs(lt, stitch_sc, lt.lt_style_status, reporter);
}
{
style_config stitch_sc;
positioned_property<style_config> stitch_sc;
stitch_sc.sc_color = lt.lt_style_status_title.sc_background_color;
stitch_sc.sc_background_color
= lt.lt_style_status_subtitle.sc_background_color;
stitch_sc.pp_value.sc_color
= lt.lt_style_status_title.pp_value.sc_background_color;
stitch_sc.pp_value.sc_background_color
= lt.lt_style_status_subtitle.pp_value.sc_background_color;
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_STATUS_STITCH_SUB_TO_TITLE)]
= this->to_attrs(lt, stitch_sc, lt.lt_style_status, reporter);
}
{
style_config stitch_sc;
positioned_property<style_config> stitch_sc;
stitch_sc.sc_color = lt.lt_style_status.sc_background_color;
stitch_sc.sc_background_color
= lt.lt_style_status_subtitle.sc_background_color;
stitch_sc.pp_value.sc_color
= lt.lt_style_status.pp_value.sc_background_color;
stitch_sc.pp_value.sc_background_color
= lt.lt_style_status_subtitle.pp_value.sc_background_color;
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_STATUS_STITCH_SUB_TO_NORMAL)]
= this->to_attrs(lt, stitch_sc, lt.lt_style_status, reporter);
}
{
style_config stitch_sc;
positioned_property<style_config> stitch_sc;
stitch_sc.sc_color = lt.lt_style_status_subtitle.sc_background_color;
stitch_sc.sc_background_color = lt.lt_style_status.sc_background_color;
stitch_sc.pp_value.sc_color
= lt.lt_style_status_subtitle.pp_value.sc_background_color;
stitch_sc.pp_value.sc_background_color
= lt.lt_style_status.pp_value.sc_background_color;
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_STATUS_STITCH_NORMAL_TO_SUB)]
= this->to_attrs(lt, stitch_sc, lt.lt_style_status, reporter);
}
{
style_config stitch_sc;
positioned_property<style_config> stitch_sc;
stitch_sc.sc_color = lt.lt_style_status.sc_background_color;
stitch_sc.sc_background_color
= lt.lt_style_status_title.sc_background_color;
stitch_sc.pp_value.sc_color
= lt.lt_style_status.pp_value.sc_background_color;
stitch_sc.pp_value.sc_background_color
= lt.lt_style_status_title.pp_value.sc_background_color;
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_STATUS_STITCH_TITLE_TO_NORMAL)]
= this->to_attrs(lt, stitch_sc, lt.lt_style_status, reporter);
}
{
style_config stitch_sc;
positioned_property<style_config> stitch_sc;
stitch_sc.sc_color = lt.lt_style_status_title.sc_background_color;
stitch_sc.sc_background_color = lt.lt_style_status.sc_background_color;
stitch_sc.pp_value.sc_color
= lt.lt_style_status_title.pp_value.sc_background_color;
stitch_sc.pp_value.sc_background_color
= lt.lt_style_status.pp_value.sc_background_color;
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_STATUS_STITCH_NORMAL_TO_TITLE)]
= this->to_attrs(lt, stitch_sc, lt.lt_style_status, reporter);
@ -909,19 +936,21 @@ view_colors::init_roles(const lnav_theme& lt,
= this->to_attrs(
lt, lt.lt_style_scrollbar, lt.lt_style_status, reporter);
{
style_config bar_sc;
positioned_property<style_config> bar_sc;
bar_sc.sc_color = lt.lt_style_error.sc_color;
bar_sc.sc_background_color = lt.lt_style_scrollbar.sc_background_color;
bar_sc.pp_value.sc_color = lt.lt_style_error.pp_value.sc_color;
bar_sc.pp_value.sc_background_color
= lt.lt_style_scrollbar.pp_value.sc_background_color;
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_SCROLLBAR_ERROR)]
= this->to_attrs(lt, bar_sc, lt.lt_style_alert_status, reporter);
}
{
style_config bar_sc;
positioned_property<style_config> bar_sc;
bar_sc.sc_color = lt.lt_style_warning.sc_color;
bar_sc.sc_background_color = lt.lt_style_scrollbar.sc_background_color;
bar_sc.pp_value.sc_color = lt.lt_style_warning.pp_value.sc_color;
bar_sc.pp_value.sc_background_color
= lt.lt_style_scrollbar.pp_value.sc_background_color;
this->vc_role_attrs[lnav::enums::to_underlying(
role_t::VCR_SCROLLBAR_WARNING)]
= this->to_attrs(lt, bar_sc, lt.lt_style_warn_status, reporter);
@ -984,7 +1013,7 @@ view_colors::init_roles(const lnav_theme& lt,
if (level_iter == lt.lt_level_styles.end()) {
this->vc_level_attrs[level]
= std::make_pair(text_attrs{}, text_attrs{});
= role_attrs{text_attrs{}, text_attrs{}};
} else {
this->vc_level_attrs[level] = this->to_attrs(
lt, level_iter->second, lt.lt_style_text, reporter);
@ -995,6 +1024,28 @@ view_colors::init_roles(const lnav_theme& lt,
this->vc_color_pair_end = 1;
}
this->vc_dyn_pairs.clear();
for (int32_t role_index = 0;
role_index < lnav::enums::to_underlying(role_t::VCR__MAX);
role_index++)
{
const auto& ra = this->vc_role_attrs[role_index];
if (ra.ra_class_name.empty()) {
continue;
}
this->vc_class_to_role[ra.ra_class_name.to_string()]
= VC_ROLE.value(role_t(role_index));
}
for (int level_index = 0; level_index < LEVEL__MAX; level_index++) {
const auto& ra = this->vc_level_attrs[level_index];
if (ra.ra_class_name.empty()) {
continue;
}
this->vc_class_to_role[ra.ra_class_name.to_string()]
= SA_LEVEL.value(level_index);
}
}
int
@ -1198,12 +1249,6 @@ lab_color::operator!=(const lab_color& rhs) const
return !(rhs == *this);
}
string_attr_pair
view_colors::roles::file()
{
return VC_ROLE.value(role_t::VCR_FILE);
}
#include <term.h>
Result<screen_curses, std::string>

@ -56,6 +56,7 @@
#include <functional>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
#include "base/attr_line.hh"
@ -228,16 +229,8 @@ public:
require(role < role_t::VCR__MAX);
return selected
? this->vc_role_attrs[lnav::enums::to_underlying(role)].second
: this->vc_role_attrs[lnav::enums::to_underlying(role)].first;
}
text_attrs reverse_attrs_for_role(role_t role) const
{
require(role > role_t::VCR_NONE);
require(role < role_t::VCR__MAX);
return this->vc_role_reverse_colors[lnav::enums::to_underlying(role)];
? this->vc_role_attrs[lnav::enums::to_underlying(role)].ra_reverse
: this->vc_role_attrs[lnav::enums::to_underlying(role)].ra_normal;
}
nonstd::optional<short> color_for_ident(const char* str, size_t len) const;
@ -259,6 +252,11 @@ public:
return this->attrs_for_ident(str.c_str(), str.length());
}
text_attrs attrs_for_level(log_level_t level) const
{
return this->vc_level_attrs[level].ra_normal;
}
int ensure_color_pair(short fg, short bg);
int ensure_color_pair(nonstd::optional<short> fg,
@ -272,22 +270,12 @@ public:
nonstd::optional<short> match_color(const styling::color_unit& color) const;
std::pair<text_attrs, text_attrs> to_attrs(
const lnav_theme& lt,
const style_config& sc,
const style_config& fallback_sc,
lnav_config_listener::error_reporter& reporter);
std::pair<text_attrs, text_attrs> vc_level_attrs[LEVEL__MAX];
short ansi_to_theme_color(short ansi_fg) const
{
return this->vc_ansi_to_theme[ansi_fg];
}
struct roles {
static string_attr_pair file();
};
std::unordered_map<std::string, string_attr_pair> vc_class_to_role;
static bool initialized;
@ -301,12 +289,21 @@ private:
int dp_color_pair;
};
struct role_attrs {
text_attrs ra_normal;
text_attrs ra_reverse;
intern_string_t ra_class_name;
};
role_attrs to_attrs(const lnav_theme& lt,
const positioned_property<style_config>& sc,
const positioned_property<style_config>& fallback_sc,
lnav_config_listener::error_reporter& reporter);
role_attrs vc_level_attrs[LEVEL__MAX];
/** Map of role IDs to attribute values. */
std::pair<text_attrs, text_attrs>
vc_role_attrs[lnav::enums::to_underlying(role_t::VCR__MAX)];
/** Map of role IDs to reverse-video attribute values. */
text_attrs
vc_role_reverse_colors[lnav::enums::to_underlying(role_t::VCR__MAX)];
role_attrs vc_role_attrs[lnav::enums::to_underlying(role_t::VCR__MAX)];
short vc_ansi_to_theme[8];
short vc_highlight_colors[HI_COLOR_COUNT];
int vc_color_pair_end{0};

@ -45,6 +45,7 @@
#include "shlex.hh"
#include "sql_help.hh"
#include "sql_util.hh"
#include "static_file_vtab.hh"
#include "view_helpers.crumbs.hh"
#include "view_helpers.examples.hh"
#include "view_helpers.hist.hh"
@ -108,6 +109,7 @@ open_schema_view()
schema += "\n\n-- Virtual Table Definitions --\n\n";
schema += ENVIRON_CREATE_STMT;
schema += STATIC_FILE_CREATE_STMT;
schema += vtab_module_schemas;
for (const auto& vtab_iter : *lnav_data.ld_vtab_manager) {
schema += "\n" + vtab_iter.second->get_table_statement();

@ -162,12 +162,14 @@ static const typed_json_path_container<resolved_crumb> breadcrumb_crumb_handlers
struct top_line_meta {
nonstd::optional<std::string> tlm_time;
nonstd::optional<std::string> tlm_file;
nonstd::optional<std::string> tlm_anchor;
std::vector<resolved_crumb> tlm_crumbs;
};
static const typed_json_path_container<top_line_meta> top_line_meta_handlers = {
yajlpp::property_handler("time").for_field(&top_line_meta::tlm_time),
yajlpp::property_handler("file").for_field(&top_line_meta::tlm_file),
yajlpp::property_handler("anchor").for_field(&top_line_meta::tlm_anchor),
yajlpp::property_handler("breadcrumbs#")
.for_field(&top_line_meta::tlm_crumbs)
.with_children(breadcrumb_crumb_handlers),
@ -283,6 +285,7 @@ CREATE TABLE lnav_views (
if (tss != nullptr && tss->text_line_count() > 0) {
auto* time_source = dynamic_cast<text_time_translator*>(
tc.get_sub_source());
auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
std::vector<breadcrumb::crumb> crumbs;
tss->text_crumbs_for_line(tc.get_top(), crumbs);
@ -302,6 +305,9 @@ CREATE TABLE lnav_views (
tlm.tlm_time = timestamp;
}
}
if (ta != nullptr) {
tlm.tlm_anchor = ta->anchor_for_row(tc.get_top());
}
tlm.tlm_file = tc.map_top_row([](const auto& al) {
return get_string_attr(al.get_attrs(), logline::L_FILE)
| [](const auto wrapper) {
@ -385,6 +391,50 @@ CREATE TABLE lnav_views (
return SQLITE_ERROR;
}
}
if (top_meta != nullptr) {
static const intern_string_t SQL_SRC
= intern_string::lookup("top_meta");
auto parse_res = top_line_meta_handlers.parser_for(SQL_SRC).of(
string_fragment::from_c_str(top_meta));
if (parse_res.isErr()) {
auto errmsg = parse_res.unwrapErr();
tab->zErrMsg = sqlite3_mprintf(
"Invalid top_meta: %s",
errmsg[0].to_attr_line().get_string().c_str());
return SQLITE_ERROR;
}
auto tlm = parse_res.unwrap();
if (index == LNV_TEXT && tlm.tlm_file) {
if (!lnav_data.ld_text_source.to_front(tlm.tlm_file.value())) {
auto errmsg = parse_res.unwrapErr();
tab->zErrMsg = sqlite3_mprintf("unknown top_meta.file: %s",
tlm.tlm_file->c_str());
return SQLITE_ERROR;
}
}
auto* ta = dynamic_cast<text_anchors*>(tc.get_sub_source());
if (ta != nullptr && tlm.tlm_anchor
&& !tlm.tlm_anchor.value().empty())
{
auto req_anchor = tlm.tlm_anchor.value();
auto req_anchor_top = ta->row_for_anchor(req_anchor);
if (req_anchor_top) {
auto curr_anchor = ta->anchor_for_row(tc.get_top());
if (!curr_anchor || curr_anchor.value() != req_anchor) {
tc.set_top(req_anchor_top.value());
}
} else {
tab->zErrMsg = sqlite3_mprintf(
"unknown top_meta.anchor: %s", req_anchor.c_str());
return SQLITE_ERROR;
}
}
}
tc.set_left(left);
tc.set_paused(is_paused);
tc.execute_search(search);

@ -413,6 +413,8 @@ struct sqlite_func_adapter<Return (*)(Args...), f> {
Return retval = f(from_sqlite<Args>()(argc, argv, Idx)...);
to_sqlite(context, std::move(retval));
} catch (const lnav::console::user_message& um) {
to_sqlite(context, um);
} catch (from_sqlite_conversion_error& e) {
char buffer[256];
@ -935,6 +937,8 @@ struct tvt_iterator_cursor {
template<typename T>
struct tvt_no_update : public T {
using T::T;
int delete_row(sqlite3_vtab* vt, sqlite3_int64 rowid)
{
vt->zErrMsg = sqlite3_mprintf("Rows cannot be deleted from this table");

@ -683,7 +683,6 @@ yajlpp_parse_context::update_callbacks(const json_path_container* orig_handlers,
}
this->ypc_sibling_handlers = orig_handlers;
pcre_input pi(&this->ypc_path[0], 0, this->ypc_path.size() - 1);
this->ypc_callbacks = DEFAULT_CALLBACKS;
@ -731,7 +730,7 @@ yajlpp_parse_context::update_callbacks(const json_path_container* orig_handlers,
|| index != yajlpp_provider_context::nindex))
{
this->ypc_obj_stack.push(jph.jph_obj_provider(
{{this->ypc_pcre_context, pi}, index},
{{this->ypc_pcre_context, pi}, index, this},
this->ypc_obj_stack.top()));
}
}
@ -1080,7 +1079,7 @@ const intern_string_t
yajlpp_parse_context::get_full_path() const
{
if (this->ypc_path.size() <= 1) {
static intern_string_t SLASH = intern_string::lookup("/");
static const intern_string_t SLASH = intern_string::lookup("/");
return SLASH;
}

@ -94,6 +94,7 @@ class yajlpp_parse_context;
struct yajlpp_provider_context {
pcre_extractor ypc_extractor;
size_t ypc_index{0};
yajlpp_parse_context* ypc_parse_context;
static constexpr size_t nindex = static_cast<size_t>(-1);
@ -135,10 +136,7 @@ public:
this->ye_msg = reinterpret_cast<const char*>(yajl_msg.in());
}
const char* what() const noexcept override
{
return this->ye_msg.c_str();
}
const char* what() const noexcept override { return this->ye_msg.c_str(); }
private:
std::string ye_msg;
@ -517,10 +515,7 @@ public:
return yajl_gen_status_ok;
}
yajl_gen_status operator()()
{
return yajl_gen_null(this->yg_handle);
}
yajl_gen_status operator()() { return yajl_gen_null(this->yg_handle); }
private:
yajl_gen yg_handle;
@ -543,10 +538,7 @@ public:
yajl_gen_map_open(handle);
}
~yajlpp_map()
{
yajl_gen_map_close(this->ycb_handle);
}
~yajlpp_map() { yajl_gen_map_close(this->ycb_handle); }
};
class yajlpp_array : public yajlpp_container_base {
@ -556,10 +548,7 @@ public:
yajl_gen_array_open(handle);
}
~yajlpp_array()
{
yajl_gen_array_close(this->ycb_handle);
}
~yajlpp_array() { yajl_gen_array_close(this->ycb_handle); }
};
class yajlpp_gen_context {
@ -606,15 +595,9 @@ public:
this->yg_handle = yajl_gen_alloc(nullptr);
}
yajl_gen get_handle() const
{
return this->yg_handle.in();
}
yajl_gen get_handle() const { return this->yg_handle.in(); }
operator yajl_gen()
{
return this->yg_handle.in();
}
operator yajl_gen() { return this->yg_handle.in(); }
string_fragment to_string_fragment();

@ -37,7 +37,6 @@
#include "config.h"
#include "mapbox/variant.hpp"
#include "relative_time.hh"
#include "view_curses.hh"
#include "yajlpp.hh"
#define FOR_FIELD(T, FIELD) for_field<T, decltype(T ::FIELD), &T ::FIELD>()
@ -432,11 +431,13 @@ struct json_path_handler : public json_path_handler_base {
}
if ((rc = yajl_gen_string(handle, pair.first))
!= yajl_gen_status_ok) {
!= yajl_gen_status_ok)
{
return rc;
}
if ((rc = yajl_gen_string(handle, pair.second))
!= yajl_gen_status_ok) {
!= yajl_gen_status_ok)
{
return rc;
}
}
@ -669,6 +670,22 @@ struct json_path_handler : public json_path_handler_base {
return *this;
}
template<typename T, typename U>
json_path_handler& for_child(positioned_property<U>(T::*field))
{
this->jph_obj_provider
= [field](const yajlpp_provider_context& ypc, void* root) -> void* {
auto& child = json_path_handler::get_field(root, field);
if (ypc.ypc_parse_context != nullptr && child.pp_path.empty()) {
child.pp_path = ypc.ypc_parse_context->get_full_path();
}
return &child.pp_value;
};
return *this;
}
template<typename... Args>
json_path_handler& for_child(Args... args)
{
@ -685,7 +702,8 @@ struct json_path_handler : public json_path_handler_base {
template<typename... Args,
std::enable_if_t<
LastIs<std::map<std::string, std::string>, Args...>::value,
bool> = true>
bool>
= true>
json_path_handler& for_field(Args... args)
{
this->add_cb(str_field_cb2);
@ -732,7 +750,8 @@ struct json_path_handler : public json_path_handler_base {
template<typename... Args,
std::enable_if_t<
LastIs<std::map<std::string, json_any_t>, Args...>::value,
bool> = true>
bool>
= true>
json_path_handler& for_field(Args... args)
{
this->add_cb(bool_field_cb);
@ -859,7 +878,8 @@ struct json_path_handler : public json_path_handler_base {
template<
typename... Args,
std::enable_if_t<LastIs<nonstd::optional<std::string>, Args...>::value,
bool> = true>
bool>
= true>
json_path_handler& for_field(Args... args)
{
this->add_cb(str_field_cb2);
@ -920,7 +940,8 @@ struct json_path_handler : public json_path_handler_base {
template<typename... Args,
std::enable_if_t<
LastIs<positioned_property<std::string>, Args...>::value,
bool> = true>
bool>
= true>
json_path_handler& for_field(Args... args)
{
this->add_cb(str_field_cb2);
@ -975,9 +996,9 @@ struct json_path_handler : public json_path_handler_base {
return *this;
}
template<
typename... Args,
std::enable_if_t<LastIs<intern_string_t, Args...>::value, bool> = true>
template<typename... Args,
std::enable_if_t<LastIs<intern_string_t, Args...>::value, bool>
= true>
json_path_handler& for_field(Args... args)
{
this->add_cb(str_field_cb2);
@ -1031,7 +1052,8 @@ struct json_path_handler : public json_path_handler_base {
template<typename... Args,
std::enable_if_t<
LastIs<positioned_property<intern_string_t>, Args...>::value,
bool> = true>
bool>
= true>
json_path_handler& for_field(Args... args)
{
this->add_cb(str_field_cb2);
@ -1191,9 +1213,10 @@ struct json_path_handler : public json_path_handler_base {
return *this;
}
template<typename... Args,
std::enable_if_t<LastIs<std::chrono::seconds, Args...>::value,
bool> = true>
template<
typename... Args,
std::enable_if_t<LastIs<std::chrono::seconds, Args...>::value, bool>
= true>
json_path_handler& for_field(Args... args)
{
this->add_cb(str_field_cb2);

@ -0,0 +1,103 @@
/**
* Copyright (c) 2022, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @file yaml-extension-functions.cc
*/
#include <string>
#define RYML_SINGLE_HDR_DEFINE_NOW
#include "ryml_all.hpp"
#include "sqlite-extension-func.hh"
#include "vtab_module.hh"
#include "yajlpp/yajlpp.hh"
using namespace lnav::roles::literals;
static void
ryml_error_to_um(const char* msg, size_t len, ryml::Location loc, void* ud)
{
intern_string_t src = intern_string::lookup(
string_fragment::from_bytes(loc.name.data(), loc.name.size()));
auto& sf = *(static_cast<string_fragment*>(ud));
auto msg_str = string_fragment::from_bytes(msg, len).trim().to_string();
if (loc.offset == sf.length()) {
loc.line -= 1;
}
throw lnav::console::user_message::error("failed to parse YAML content")
.with_reason(msg_str)
.with_snippet(lnav::console::snippet::from(
source_location{src, (int32_t) loc.line}, ""));
}
static text_auto_buffer
yaml_to_json(string_fragment in)
{
ryml::Callbacks callbacks(&in, nullptr, nullptr, ryml_error_to_um);
ryml::set_callbacks(callbacks);
auto tree = ryml::parse_in_arena(
"input", ryml::csubstr{in.data(), (size_t) in.length()});
auto output = ryml::emit_json(
tree, tree.root_id(), ryml::substr{}, /*error_on_excess*/ false);
auto buf = auto_buffer::alloc(output.len);
buf.resize(output.len);
output = ryml::emit_json(tree,
tree.root_id(),
ryml::substr(buf.in(), buf.size()),
/*error_on_excess*/ true);
return {std::move(buf)};
}
int
yaml_extension_functions(struct FuncDef** basic_funcs,
struct FuncDefAgg** agg_funcs)
{
static struct FuncDef yaml_funcs[] = {
sqlite_func_adapter<decltype(&yaml_to_json), yaml_to_json>::builder(
help_text("yaml_to_json",
"Convert a YAML document to a JSON-encoded string")
.sql_function()
.with_parameter({"yaml", "The YAML value to convert to JSON."})
.with_tags({"json", "yaml"})
.with_example({
"To convert the document \"abc: def\"",
"SELECT yaml_to_json('abc: def')",
})),
{nullptr},
};
*basic_funcs = yaml_funcs;
return SQLITE_OK;
}

@ -208,6 +208,7 @@ dist_noinst_SCRIPTS = \
test_sql_time_func.sh \
test_sql_views_vtab.sh \
test_sql_xml_func.sh \
test_sql_yaml_func.sh \
test_text_file.sh \
test_tui.sh \
test_view_colors.sh \
@ -411,6 +412,7 @@ TESTS = \
test_sql_time_func.sh \
test_sql_views_vtab.sh \
test_sql_xml_func.sh \
test_sql_yaml_func.sh \
test_text_file.sh \
test_tui.sh \
test_data_parser.sh \

@ -118,6 +118,9 @@ SYNOPSIS
DESCRIPTION
Lorem ipsum
AbcDef
Lorem ipsum
)";
auto meta = lnav::document::discover_structure(INPUT, line_range{0, -1});

@ -302,6 +302,8 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_meta.sh_41f643bb4f720130625b042563e9591bee4ae588.out \
$(srcdir)/%reldir%/test_meta.sh_45ff39a3d0ac0ca0c95aaca14d043450cec1cedd.err \
$(srcdir)/%reldir%/test_meta.sh_45ff39a3d0ac0ca0c95aaca14d043450cec1cedd.out \
$(srcdir)/%reldir%/test_meta.sh_48e85ba0c0945a5085fb4ee255771406061a9c17.err \
$(srcdir)/%reldir%/test_meta.sh_48e85ba0c0945a5085fb4ee255771406061a9c17.out \
$(srcdir)/%reldir%/test_meta.sh_4c39b356748c67ccf8a6027a1af88da532f8252a.err \
$(srcdir)/%reldir%/test_meta.sh_4c39b356748c67ccf8a6027a1af88da532f8252a.out \
$(srcdir)/%reldir%/test_meta.sh_7b75763926d832bf9784ca234a060859770aabe7.err \
@ -912,6 +914,8 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_sql_time_func.sh_f3b1ea49779117bf45f85ad5615fdc5e89193db6.out \
$(srcdir)/%reldir%/test_sql_views_vtab.sh_28e23f4e98b1acd6478e39844fd9306b444550c3.err \
$(srcdir)/%reldir%/test_sql_views_vtab.sh_28e23f4e98b1acd6478e39844fd9306b444550c3.out \
$(srcdir)/%reldir%/test_sql_views_vtab.sh_32acc1a8bb5028636fdbf08f077f9a835ab51bec.err \
$(srcdir)/%reldir%/test_sql_views_vtab.sh_32acc1a8bb5028636fdbf08f077f9a835ab51bec.out \
$(srcdir)/%reldir%/test_sql_views_vtab.sh_485a6ac7c69bd4b5d34d3399a9c17f6a2dc89ad3.err \
$(srcdir)/%reldir%/test_sql_views_vtab.sh_485a6ac7c69bd4b5d34d3399a9c17f6a2dc89ad3.out \
$(srcdir)/%reldir%/test_sql_views_vtab.sh_62d15cb9d5a9259f198aa01ca8ed200d6da38d68.err \
@ -960,12 +964,22 @@ EXPECTED_FILES = \
$(srcdir)/%reldir%/test_sql_xml_func.sh_b036c73528a446cba46625767517cdac868aba72.out \
$(srcdir)/%reldir%/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.err \
$(srcdir)/%reldir%/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.out \
$(srcdir)/%reldir%/test_sql_yaml_func.sh_41c6abde708a69e74f5b7fde865d88fa75f91e0a.err \
$(srcdir)/%reldir%/test_sql_yaml_func.sh_41c6abde708a69e74f5b7fde865d88fa75f91e0a.out \
$(srcdir)/%reldir%/test_text_file.sh_2e69c22dcfa37b5c3e8490a6026eacb7ca953998.err \
$(srcdir)/%reldir%/test_text_file.sh_2e69c22dcfa37b5c3e8490a6026eacb7ca953998.out \
$(srcdir)/%reldir%/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.err \
$(srcdir)/%reldir%/test_text_file.sh_5b51b55dff7332c5bee2c9b797c401c5614d574a.out \
$(srcdir)/%reldir%/test_text_file.sh_6a24078983cf1b7a80b6fb65d5186cd125498136.err \
$(srcdir)/%reldir%/test_text_file.sh_6a24078983cf1b7a80b6fb65d5186cd125498136.out \
$(srcdir)/%reldir%/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.err \
$(srcdir)/%reldir%/test_text_file.sh_87943c6be50d701a03e901f16493314c839af1ab.out \
$(srcdir)/%reldir%/test_text_file.sh_ac486314c4e02e480d829ea2f077b86c49fedcec.err \
$(srcdir)/%reldir%/test_text_file.sh_ac486314c4e02e480d829ea2f077b86c49fedcec.out \
$(srcdir)/%reldir%/test_text_file.sh_ac872aadda29b9a824361a2c711d62ec1c75d40f.err \
$(srcdir)/%reldir%/test_text_file.sh_ac872aadda29b9a824361a2c711d62ec1c75d40f.out \
$(srcdir)/%reldir%/test_text_file.sh_c2a346ca1da2da4346f1d310212e166767993ce9.err \
$(srcdir)/%reldir%/test_text_file.sh_c2a346ca1da2da4346f1d310212e166767993ce9.out \
$(srcdir)/%reldir%/test_text_file.sh_e088ea61a5382458cc48a2607e2639e52b0be1da.err \
$(srcdir)/%reldir%/test_text_file.sh_e088ea61a5382458cc48a2607e2639e52b0be1da.out \
$()

@ -389,7 +389,7 @@ can always use  q  to pop the top view off of the stack.
Key(s) Action
════════════════════════════════════════════════════════════════════════
/regexp Start a search for the given regular expression.
/regexp Start a search for the given regular expression.
The search is live, so when there is a pause in
typing, the currently running search will be
canceled and a new one started. The first ten
@ -770,7 +770,9 @@ For support questions, email:
:comment text
══════════════════════════════════════════════════════════════════════
Attach a comment to the top log line
Attach a comment to the top log line. The comment will be displayed
right below the log message it is associated with. The comment can
be formatted using markdown and you can add new-lines with '\n'.
Parameter
text The comment text
See Also
@ -1045,12 +1047,13 @@ For support questions, email:
:goto line#|N%|timestamp
:goto line#|N%|timestamp|#anchor
══════════════════════════════════════════════════════════════════════
Go to the given location in the top view
Parameter
line#|N%|timestamp A line number, percent
into the file, or a timestamp
line#|N%|timestamp|#anchor A line
number, percent into the file,
timestamp, or an anchor in a text file
See Also
:next-location, :next-mark, :prev-location, :prev-mark, :relative-goto
Examples
@ -1066,6 +1069,10 @@ For support questions, email:
:goto 2017-01-01 
#4 To go to the Screenshots section:
:goto #screenshots 
:help
══════════════════════════════════════════════════════════════════════
@ -2600,7 +2607,7 @@ For support questions, email:
default The default value if the value was not found
See Also
json_concat(), json_contains(), json_group_array(),
json_group_object()
json_group_object(), yaml_to_json()
Examples
#1 To get the root of a JSON value:
;SELECT jget('1', '') 
@ -2655,7 +2662,8 @@ For support questions, email:
json The initial JSON value.
value The value(s) to add to the end of the array.
See Also
jget(), json_contains(), json_group_array(), json_group_object()
jget(), json_contains(), json_group_array(), json_group_object(),
yaml_to_json()
Examples
#1 To append the number 4 to null:
;SELECT json_concat(NULL, 4) 
@ -2677,7 +2685,8 @@ For support questions, email:
json The JSON value to query.
value The value to look for in the first argument
See Also
jget(), json_concat(), json_group_array(), json_group_object()
jget(), json_concat(), json_group_array(), json_group_object(),
yaml_to_json()
Examples
#1 To test if a JSON array contains the number 4:
;SELECT json_contains('[1, 2, 3]', 4) 
@ -2694,7 +2703,8 @@ For support questions, email:
Parameter
value The values to append to the array
See Also
jget(), json_concat(), json_contains(), json_group_object()
jget(), json_concat(), json_contains(), json_group_object(),
yaml_to_json()
Examples
#1 To create an array from arguments:
;SELECT json_group_array('one', 2, 3.4) 
@ -2712,7 +2722,8 @@ For support questions, email:
name The property name for the value
value The value to add to the object
See Also
jget(), json_concat(), json_contains(), json_group_array()
jget(), json_concat(), json_contains(), json_group_array(),
yaml_to_json()
Examples
#1 To create an object from arguments:
;SELECT json_group_object('a', 1, 'b', 2) 
@ -4122,6 +4133,20 @@ For support questions, email:
yaml_to_json(yaml)
══════════════════════════════════════════════════════════════════════
Convert a YAML document to a JSON-encoded string
Parameter
yaml The YAML value to convert to JSON.
See Also
jget(), json_concat(), json_contains(), json_group_array(),
json_group_object()
Example
#1 To convert the document "abc: def":
;SELECT yaml_to_json('abc: def') 
zeroblob(N)
══════════════════════════════════════════════════════════════════════
Returns a BLOB consisting of N bytes of 0x00.

@ -2,6 +2,6 @@
reason: expecting line number/percentage, timestamp, or relative time
 --> command-option:1
 | :goto invalid 
 = help: :goto line#|N%|timestamp
 = help: :goto line#|N%|timestamp|#anchor
══════════════════════════════════════════════════════════════════════
Go to the given location in the top view

@ -0,0 +1,6 @@
192.168.202.254 - - [20/Jul/2009:22:59:26 +0000] "GET /vmw/cgi/tramp HTTP/1.0" 200 134 "-" "gPXE/0.9.7"
Hello, World!
This is  markdown  now!
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkboot.gz HTTP/1.0" 404 46210 "-" "gPXE/0.9.7"
192.168.202.254 - - [20/Jul/2009:22:59:29 +0000] "GET /vmw/vSphere/default/vmkernel.gz HTTP/1.0" 200 78929 "-" "gPXE/0.9.7"

@ -0,0 +1,19 @@
Build
Lnav follows the usual GNU style for configuring and installing
software:
Run  ./autogen.sh  if compiling from a cloned repository.
$ ./configure 
$ make 
$ sudo make install 
See Also
Angle-grinder[1] is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
▌[1] - https://github.com/rcoh/angle-grinder

@ -0,0 +1,4 @@
✘ error: failed to parse YAML content
reason: closing ] not found
 --> command-option:1
 | ;SELECT yaml_to_json('[abc') 

@ -0,0 +1,2 @@
✘ error: unable to open file: non-existent:
reason: failed to ssh to host: ssh: Could not resolve hostname non-existent: nodename nor servname provided, or not known

@ -6,17 +6,17 @@
▌[4] - https://docs.lnav.org
▌[5] - https://coveralls.io/repos/github/tstack/lnav/badge.svg?branch=master
▌[6] - https://coveralls.io/github/tstack/lnav?branch=master
▌[7] - https://snapcraft.io//lnav/badge.svg
▌[7] - https://snapcraft.io/lnav/badge.svg
▌[8] - https://snapcraft.io/lnav
<img
src="https://assets-global.website-files.com/6257adef93867e50d84d30e2/62594fddd654fc29fcc07359_cb48d2a8d4991281d7a6a95d2f58195e.svg"
height="20">[1]
height="20"/>[1]
▌[1] - https://discord.gg/erBPnKwz7R
This is the source repository for lnav, visit https://lnav.org[1] for
a high level overview.
This is the source repository for lnav, visit https://lnav.org[1] for
a high level overview.
▌[1] - https://lnav.org

@ -0,0 +1,149 @@
Screenshot
The following screenshot shows a syslog file. Log lines are displayed
with highlights. Errors are red and warnings are yellow.
Screenshot[1][2]
▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png
▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png
Features
• Log messages from different files are collated together
into a single view
• Automatic detection of log format
• Automatic decompression of GZip and BZip2 files
• Filter log messages based on regular expressions
• Use SQL to analyze your logs
• And more...
Installation
Download a statically-linked binary for Linux/MacOS from the release
page[1]
▌[1] - https://github.com/tstack/lnav/releases/latest#release-artifacts
Usage
The only file installed is the executable,  lnav . You can execute it
with no arguments to view the default set of files:
$ lnav 
You can view all the syslog messages by running:
$ lnav /var/log/messages* 
Usage with  systemd-journald 
On systems running  systemd-journald , you can use  lnav  as the
pager:
$ journalctl | lnav 
or in follow mode:
$ journalctl -f | lnav 
Since  journalctl 's default output format omits the year, if you are
viewing logs which span multiple years you will need to change the
output format to include the year, otherwise  lnav  gets confused:
$ journalctl -o short-iso | lnav 
It is also possible to use  journalctl 's json output format and  lnav
will make use of additional fields such as PRIORITY and _SYSTEMD_UNIT:
$ journalctl -o json | lnav 
In case some MESSAGE fields contain special characters such as ANSI
color codes which are considered as unprintable by journalctl,
specifying  journalctl 's  -a  option might be preferable in order to
output those messages still in a non-binary representation:
$ journalctl -a -o json | lnav 
If using systemd v236 or newer, the output fields can be limited to
the ones actually recognized by  lnav  for increased efficiency:
$ journalctl -o json --output-fields=MESSAGE,PRIORITY,_PID,SYSLOG_IDENTIFIER,_SYSTEMD_UNIT | lnav 
If your system has been running for a long time, for increased
efficiency you may want to limit the number of log lines fed into  lnav
, e.g. via  journalctl 's  -n  or  --since=...  options.
In case of a persistent journal, you may want to limit the number of
log lines fed into  lnav  via  journalctl 's  -b  option.
Support
Please file issues on this repository or use the discussions section.
The following alternatives are also available:
• support@lnav.org[1]
• Discord[2]
• Google Groups[3]
▌[1] - mailto:support@lnav.org
▌[2] - https://discord.gg/erBPnKwz7R
▌[3] - https://groups.google.com/g/lnav
Links
• Main Site[1]
• Documentation[2] on Read the Docs
• Internal Architecture[3]
▌[1] - https://lnav.org
▌[2] - https://docs.lnav.org
▌[3] - file://{top_srcdir}/ARCHITECTURE.md
Contributing
• Become a Sponsor on GitHub[1]
▌[1] - https://github.com/sponsors/tstack
Building From Source
Prerequisites
The following software packages are required to build lnav:
• gcc/clang - A C++14-compatible compiler.
• libpcre - The Perl Compatible Regular Expression
(PCRE) library.
• sqlite - The SQLite database engine. Version 3.9.0
or higher is required.
• ncurses - The ncurses text UI library.
• readline - The readline line editing library.
• zlib - The zlib compression library.
• bz2 - The bzip2 compression library.
• libcurl - The cURL library for downloading files
from URLs. Version 7.23.0 or higher is required.
• libarchive - The libarchive library for opening archive
files, like zip/tgz.
• wireshark - The 'tshark' program is used to interpret
pcap files.
Build
Lnav follows the usual GNU style for configuring and installing
software:
Run  ./autogen.sh  if compiling from a cloned repository.
$ ./configure 
$ make 
$ sudo make install 
See Also
Angle-grinder[1] is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
▌[1] - https://github.com/rcoh/angle-grinder

@ -0,0 +1,4 @@
you might find this tool more comfortable to work with.
▌[1] - https://github.com/rcoh/angle-grinder

@ -2,6 +2,7 @@
{
"top_meta": {
"file": "{top_srcdir}/README.md",
"anchor": "#support",
"breadcrumbs": [
{
"display_value": "README.md",

@ -0,0 +1,149 @@
Screenshot
The following screenshot shows a syslog file. Log lines are displayed
with highlights. Errors are red and warnings are yellow.
Screenshot[1][2]
▌[1] - file://{top_srcdir}/docs/assets/images/lnav-syslog-thumb.png
▌[2] - file://{top_srcdir}/docs/assets/images/lnav-syslog.png
Features
• Log messages from different files are collated together
into a single view
• Automatic detection of log format
• Automatic decompression of GZip and BZip2 files
• Filter log messages based on regular expressions
• Use SQL to analyze your logs
• And more...
Installation
Download a statically-linked binary for Linux/MacOS from the release
page[1]
▌[1] - https://github.com/tstack/lnav/releases/latest#release-artifacts
Usage
The only file installed is the executable,  lnav . You can execute it
with no arguments to view the default set of files:
$ lnav 
You can view all the syslog messages by running:
$ lnav /var/log/messages* 
Usage with  systemd-journald 
On systems running  systemd-journald , you can use  lnav  as the
pager:
$ journalctl | lnav 
or in follow mode:
$ journalctl -f | lnav 
Since  journalctl 's default output format omits the year, if you are
viewing logs which span multiple years you will need to change the
output format to include the year, otherwise  lnav  gets confused:
$ journalctl -o short-iso | lnav 
It is also possible to use  journalctl 's json output format and  lnav
will make use of additional fields such as PRIORITY and _SYSTEMD_UNIT:
$ journalctl -o json | lnav 
In case some MESSAGE fields contain special characters such as ANSI
color codes which are considered as unprintable by journalctl,
specifying  journalctl 's  -a  option might be preferable in order to
output those messages still in a non-binary representation:
$ journalctl -a -o json | lnav 
If using systemd v236 or newer, the output fields can be limited to
the ones actually recognized by  lnav  for increased efficiency:
$ journalctl -o json --output-fields=MESSAGE,PRIORITY,_PID,SYSLOG_IDENTIFIER,_SYSTEMD_UNIT | lnav 
If your system has been running for a long time, for increased
efficiency you may want to limit the number of log lines fed into  lnav
, e.g. via  journalctl 's  -n  or  --since=...  options.
In case of a persistent journal, you may want to limit the number of
log lines fed into  lnav  via  journalctl 's  -b  option.
Support
Please file issues on this repository or use the discussions section.
The following alternatives are also available:
• support@lnav.org[1]
• Discord[2]
• Google Groups[3]
▌[1] - mailto:support@lnav.org
▌[2] - https://discord.gg/erBPnKwz7R
▌[3] - https://groups.google.com/g/lnav
Links
• Main Site[1]
• Documentation[2] on Read the Docs
• Internal Architecture[3]
▌[1] - https://lnav.org
▌[2] - https://docs.lnav.org
▌[3] - file://{top_srcdir}/ARCHITECTURE.md
Contributing
• Become a Sponsor on GitHub[1]
▌[1] - https://github.com/sponsors/tstack
Building From Source
Prerequisites
The following software packages are required to build lnav:
• gcc/clang - A C++14-compatible compiler.
• libpcre - The Perl Compatible Regular Expression
(PCRE) library.
• sqlite - The SQLite database engine. Version 3.9.0
or higher is required.
• ncurses - The ncurses text UI library.
• readline - The readline line editing library.
• zlib - The zlib compression library.
• bz2 - The bzip2 compression library.
• libcurl - The cURL library for downloading files
from URLs. Version 7.23.0 or higher is required.
• libarchive - The libarchive library for opening archive
files, like zip/tgz.
• wireshark - The 'tshark' program is used to interpret
pcap files.
Build
Lnav follows the usual GNU style for configuring and installing
software:
Run  ./autogen.sh  if compiling from a cloned repository.
$ ./configure 
$ make 
$ sudo make install 
See Also
Angle-grinder[1] is a tool to slice and dice log files on the
command-line. If you're familiar with the SumoLogic query language,
you might find this tool more comfortable to work with.
▌[1] - https://github.com/rcoh/angle-grinder

@ -101,3 +101,10 @@ run_cap_test ${lnav_test} -n \
run_cap_test ${lnav_test} -d /tmp/lnav.err -n \
-I ${test_dir} \
${test_dir}/logfile_xml_msg.0
run_cap_test ${lnav_test} -n -f- \
${test_dir}/logfile_access_log.0 <<'EOF'
:comment Hello, **World**!
This is `markdown` now!
EOF

@ -727,7 +727,7 @@ EOF
schema_dump() {
${lnav_test} -n -c ';.schema' ${test_dir}/logfile_access_log.0 | head -n19
${lnav_test} -n -c ';.schema' ${test_dir}/logfile_access_log.0 | head -n21
}
run_test schema_dump
@ -735,12 +735,14 @@ run_test schema_dump
check_output "schema view is not working" <<EOF
ATTACH DATABASE '' AS 'main';
CREATE VIRTUAL TABLE environ USING environ_vtab_impl();
CREATE VIRTUAL TABLE lnav_static_files USING lnav_static_file_vtab_impl();
CREATE VIRTUAL TABLE lnav_views USING lnav_views_impl();
CREATE VIRTUAL TABLE lnav_view_filter_stats USING lnav_view_filter_stats_impl();
CREATE VIRTUAL TABLE lnav_view_files USING lnav_view_files_impl();
CREATE VIRTUAL TABLE lnav_view_stack USING lnav_view_stack_impl();
CREATE VIRTUAL TABLE lnav_view_filters USING lnav_view_filters_impl();
CREATE VIRTUAL TABLE lnav_file USING lnav_file_impl();
CREATE VIRTUAL TABLE lnav_file_metadata USING lnav_file_metadata_impl();
CREATE VIEW lnav_view_filters_and_stats AS
SELECT * FROM lnav_view_filters LEFT NATURAL JOIN lnav_view_filter_stats;
CREATE VIRTUAL TABLE regexp_capture USING regexp_capture_impl();

@ -167,3 +167,7 @@ run_cap_test ${lnav_test} -n \
-c ":goto 0" \
-c ":next-mark search" \
${test_dir}/logfile_generic.0
run_cap_test ${lnav_test} -n \
-c ";UPDATE lnav_views SET top_meta = json_object('anchor', '#build') WHERE name = 'text'" \
${top_srcdir}/README.md

@ -0,0 +1,5 @@
#! /bin/bash
export YES_COLOR=1
run_cap_test ${lnav_test} -n -c ";SELECT yaml_to_json('[abc')"

@ -6,6 +6,15 @@ unset XDG_CONFIG_HOME
run_cap_test ${lnav_test} -n \
${top_srcdir}/README.md
run_cap_test ${lnav_test} -n -c ':goto #screenshot' \
${top_srcdir}/README.md
run_cap_test ${lnav_test} -n ${top_srcdir}/README.md#screenshot
run_cap_test ${lnav_test} -n ${test_dir}/non-existent:4
run_cap_test ${lnav_test} -n ${top_srcdir}/README.md:-4
run_cap_test ${lnav_test} -n \
-c ':goto 115' \
-c ";SELECT top_meta FROM lnav_views WHERE name = 'text'" \

Loading…
Cancel
Save