Compare commits

...

93 Commits

Author SHA1 Message Date
Tim Stack 11545f84ca [files] add a comment about log format match 7 days ago
Tim Stack 6538675d15 [textview] execute lnav scripts 1 week ago
Tim Stack 5deadab00e [release] bump makefile version 2 weeks ago
Tim Stack 80a38fbac2 [build] missing expected files in makefile 2 weeks ago
Tim Stack edc5bf171b [coverity] (more) use move to avoid auto copies 2 weeks ago
Tim Stack eed8f3871a [coverity] use move to avoid auto copies 2 weeks ago
Tim Stack 842eabb4af [sql] do not dump the log on an empty SQL result
Related to #466
2 weeks ago
Tim Stack cccc72f3c1 [coverity] moare fixes 2 weeks ago
Tim Stack 2117a12ded [text] auto pretty-print text files 2 weeks ago
Tim Stack 4e29ee1397 [coverity] undo suggestion 2 weeks ago
Tim Stack 2610a8eac4 [coverity] some more fixes 2 weeks ago
Tim Stack 97f12cf4aa [coverity] fix some warnings 2 weeks ago
Tim Stack 213b4e927d tweak to quiet mode 3 weeks ago
Tim Stack d6eb020b1f [logfile] make notes a user_message 3 weeks ago
Tim Stack 6132b6a944 [cleanup] fix a few things 3 weeks ago
Tim Stack 223b6af82f [sql] add pretty_print() function 3 weeks ago
Tim Stack edf1eae9a0 gcc again 3 weeks ago
Tim Stack db9c9205cf more gcc 3 weeks ago
Tim Stack cff588776f [spec] gcc 12 - 13 3 weeks ago
Tim Stack 34aeaf7541 [tests] add set-text-view-mode tests 3 weeks ago
Tim Stack a5d12dc948 [text] support for changing the view mode 3 weeks ago
Tim Stack 5ace34000e [actions] remove 2nd coverity workflow 3 weeks ago
Tim Stack b084655be5 [misc] fix some warnings 3 weeks ago
Tim Stack afcdad717d [cov] suppress 3 weeks ago
Tim Stack cf8773b057 [coverity] url... 3 weeks ago
Tim Stack f6618dee1b [coverity] other action 3 weeks ago
Tim Stack 4abbec7b70 [coverity] maint 3 weeks ago
Tim Stack 9694d93ff3 [files] add a detail view to the files panel 4 weeks ago
Tim Stack e6aac98193 [piper] fix stall when logging partial match 4 weeks ago
Tim Stack 937a4588f7 [formats] add caddy log
Related to #1268
4 weeks ago
Tim Stack a28ddb7196 [piper] add some demux debug log 4 weeks ago
Tim Stack f4f207f641 [news] mention crash upload 4 weeks ago
Tim Stack d4cdbc1f49 [crashd] support uploading crash logs 4 weeks ago
Tim Stack fc79d703b1 [script] pass full path to script as $0 1 month ago
Tim Stack c3bf727483 [generic_log] tweak regex to ignore NONE
Fixes #1145
1 month ago
Tim Stack 3fdf710dfd [pager] try to fix the check for switching to headless 1 month ago
Tim Stack 4759a59b23 [timeline] rename gantt view to timeline 1 month ago
Tim Stack 5355b6854e [gantt] allow selection in the details view 1 month ago
Tim Stack 61c2d2c8aa [formats] add a format for the lnav debug log 1 month ago
Tim Stack 30ec70d760 [tests] fixup test_logfile.sh for std::filesystem change 1 month ago
Tim Stack 6ec38336d1 [fs] fix file_time_type conversion 1 month ago
Tim Stack 14d40cafe4 [tests] fix test that uses TMPDIR 1 month ago
Tim Stack 8187035b3d [url] fix some url opening issues 1 month ago
Tim Stack b14f68463c [misc] fix win build and allow reopening a url 1 month ago
Tim Stack 624a9a7761 [build] some missing includes 1 month ago
Tim Stack 4195f926d3
Merge pull request #1266 from tstack/dependabot/bundler/docs/nokogiri-1.16.5
Bump nokogiri from 1.16.3 to 1.16.5 in /docs
1 month ago
dependabot[bot] f3f5ca19b4
---

							
						
1 month ago
Tim Stack 41d76dfd88
Merge pull request #1265 from tstack/dependabot/bundler/docs/rexml-3.2.8
Bump rexml from 3.2.5 to 3.2.8 in /docs
1 month ago
dependabot[bot] bbeb685909
---

							
						
1 month ago
Tim Stack 53b883d015 [internal] remove ghc::filesystem 1 month ago
Tim Stack 9a1f383ce1 [textview] some more support for handling hyperlinks 1 month ago
Tim Stack 0697009b16 [demux] ignore demux control lines 1 month ago
Tim Stack 5ab75e553e [demux] add control-pattern config 1 month ago
Tim Stack 50e895c289 [theme] use default styles for cursor-line/selected-text if they are missing from the current theme
Some markdown-related changes as well
1 month ago
Tim Stack 4fd6b62380 [listview] set_selection() wasn't checking if the view was selectable
Fixes #1263
1 month ago
Tim Stack f9c49712c6 [wcwidth] remove override for now 1 month ago
Tim Stack 4cd2c1e344 [sections] drop open child sections at diff boundaries 1 month ago
Tim Stack 8d8467dfcb [markdown] add support for github alerts 1 month ago
Tim Stack 54b391e53f [sql] support doing an UPDATE on the lnav_top_view SQL view 1 month ago
Tim Stack 984e133292 [internal] add enumerate() helper 1 month ago
Tim Stack 77b9829224 [mouse] treat ctrl+click the same as shift+click
Some terminals capture shift+click so add ctrl+click as
an alternate way to highlight/mark lines.
1 month ago
Tim Stack fdecf28699 [opid] add opids set by the user to session exports 1 month ago
Tim Stack a90fe01eab [indexing] index more of demuxed file 1 month ago
Tim Stack 48ed0ff458 [gantt] adjust time bounds to show the full range of all ops 1 month ago
Tim Stack f81ffeeba7 [tests] fix distclean 2 months ago
Tim Stack 3b4db06f40 [build] try to use vcpkg 2 months ago
Tim Stack 2ed19ec00c [tests] revert debugging stuff 2 months ago
Tim Stack 6b0b82f856 [tests] unset XDG_CONFIG_HOME for tests 2 months ago
Tim Stack 95dde417d3 [tests] debug 2 months ago
Tim Stack 5a2ebbdd2a [tests] more timestamp debugging 2 months ago
Tim Stack b47aa11a5e [tests] wth 2 months ago
Tim Stack efb85ed570 [tests] try to address some test issues 2 months ago
Tim Stack 74a01bba4d [piper] write header to temp file first
Fixes #1262
2 months ago
Tim Stack 71a1850dd3 [demux] some more testing 2 months ago
Tim Stack 954334ca98 [breadcrumb] pressing ` should close the crumb popup 2 months ago
Tim Stack b3cb30f293 [tests] ensure logfile_glog.0 has the right time 2 months ago
Tim Stack 2b54ab2768 [top_status] reset notification statement so DB tables aren't locked 2 months ago
Tim Stack e27a7d8e9a [log_format] add support for synthetic opids 2 months ago
Tim Stack 40a93ca3d3 [misc] fixing some compiler warnings 2 months ago
Tim Stack dd47e66832 [datepp] check for TZDIR when looking up zoneinfo 2 months ago
Tim Stack 8aa72b16e4 [demux] add lnav_file_demux_metadata view 2 months ago
Tim Stack 30c8beeb2e [demux] add a demuxer for container with type 2 months ago
Tim Stack 964e84707b [demux] fixing various bugs 2 months ago
Tim Stack 8c7d7c3e82 [misc] fix a couple of memory issues 2 months ago
Tim Stack e1a1e7099b [java_log] add another java_log regex 2 months ago
Tim Stack 4ad451b27d [tests] update tests for mouse change 2 months ago
Tim Stack d8ee0fa58d [mouse] do not enable mouse unless using the alt screen 2 months ago
Tim Stack 8aa84ad143 [build] more optional 2 months ago
Tim Stack e732267cfe [build] missing functional include 2 months ago
Tim Stack bf6b64c4ea [build] missing optional includes 2 months ago
Tim Stack 11c9952e03 [build] missing include 2 months ago
Tim Stack 46b2a9f1ee [piper] start support for demux
... and other misc things:

* Bump to C++17
* Add zookeeper_log
2 months ago
Tim Stack 6dd6ec8862 [site] bump version to v0.12.2 2 months ago

@ -65,7 +65,6 @@ jobs:
autoconf
g++
libpcre2-dev
libpcre3-dev
libncurses-dev
libsqlite3-dev
libbz2-dev

@ -15,6 +15,9 @@ on:
# Allow for manual triggers from the web.
workflow_dispatch:
env:
COVERITY_SUPPRESS_ASSERT: 2564
jobs:
coverity:
runs-on: ubuntu-latest
@ -29,14 +32,17 @@ jobs:
automake
autoconf
g++
libpcre3-dev
libpcre2-dev
libncurses-dev
libsqlite3-dev
libbz2-dev
libcurl4-openssl-dev
libreadline-dev
pipx
tshark
zlib1g-dev
- name: Install python packages
run: pipx install check-jsonschema
- name: autogen
run: ./autogen.sh
- name: configure
@ -46,3 +52,10 @@ jobs:
command: make -j$(getconf _NPROCESSORS_CONF)
email: ${{ secrets.COVERITY_SCAN_EMAIL }}
token: ${{ secrets.COVERITY_SCAN_TOKEN }}
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3
with:
# Artifact name
name: lnav-cov-int.zip
# A file, directory or wildcard pattern that describes what to upload
path: cov-int/build-log.txt

1
.gitignore vendored

@ -32,6 +32,7 @@ missing
mkinstalldirs
test-driver
docs/build
release/lnav.spec
release/release-NEWS.md
release/linux-pkg/
release/osx-build-dir/

@ -2,10 +2,10 @@ cmake_minimum_required(VERSION 3.14)
include(cmake/prelude.cmake)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 17)
project(
lnav
VERSION 0.12.2
VERSION 0.12.3
DESCRIPTION "An advanced log file viewer for the terminal."
HOMEPAGE_URL "https://lnav.org/"
LANGUAGES CXX C
@ -14,24 +14,24 @@ project(
include(cmake/project-is-top-level.cmake)
include(cmake/variables.cmake)
find_package(SQLite3 REQUIRED)
set(CURSES_NEED_NCURSES TRUE)
find_package(PkgConfig REQUIRED)
find_package(unofficial-sqlite3 CONFIG REQUIRED)
find_package(BZip2 REQUIRED)
find_package(LibArchive REQUIRED)
find_package(ZLIB REQUIRED)
find_package(pcre REQUIRED)
find_package(pcre2 REQUIRED)
find_package(readline REQUIRED)
find_package(ncurses REQUIRED)
find_package(pcre2 CONFIG REQUIRED)
pkg_check_modules(readline REQUIRED IMPORTED_TARGET readline)
find_package(Curses REQUIRED)
find_package(CURL REQUIRED)
set(lnav_LIBS
CURL::libcurl
SQLite::SQLite3
unofficial::sqlite3::sqlite3
BZip2::BZip2
ncurses::libcurses
pcre::libpcre
pcre2::pcre2
readline::readline
${CURSES_LIBRARIES}
PCRE2::8BIT PCRE2::16BIT PCRE2::32BIT PCRE2::POSIX
PkgConfig::readline
LibArchive::LibArchive
ZLIB::ZLIB
)

@ -1,142 +1,13 @@
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "cmake-pedantic",
"hidden": true,
"warnings": {
"dev": true,
"deprecated": true,
"uninitialized": true,
"unusedCli": true,
"systemVars": false
},
"errors": {
"dev": true,
"deprecated": true
}
},
{
"name": "dev-mode",
"hidden": true,
"inherits": "cmake-pedantic",
"cacheVariables": {
"lnav_DEVELOPER_MODE": "ON"
}
},
{
"name": "conan",
"hidden": true,
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/conan/conan_toolchain.cmake"
}
},
{
"name": "cppcheck",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_CPPCHECK": "cppcheck;--inline-suppr"
}
},
{
"name": "clang-tidy",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=${sourceDir}/*"
}
},
{
"name": "ci-std",
"description": "This preset makes sure the project actually builds with at least the specified standard",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_EXTENSIONS": "OFF",
"CMAKE_CXX_STANDARD": "14",
"CMAKE_CXX_STANDARD_REQUIRED": "ON"
}
},
{
"name": "flags-unix",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": ""
}
},
{
"name": "flags-windows",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "/W4 /permissive- /utf-8 /volatile:iso /EHsc /Zc:__cplusplus /Zc:throwingNew"
}
},
{
"name": "ci-unix",
"generator": "Unix Makefiles",
"hidden": true,
"inherits": ["flags-unix", "ci-std"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "ci-win64",
"inherits": ["flags-windows", "ci-std"],
"generator": "Visual Studio 17 2022",
"architecture": "x64",
"hidden": true
},
{
"name": "coverage-unix",
"binaryDir": "${sourceDir}/build/coverage",
"inherits": "ci-unix",
"hidden": true,
"cacheVariables": {
"ENABLE_COVERAGE": "ON",
"CMAKE_BUILD_TYPE": "Coverage",
"CMAKE_CXX_FLAGS_COVERAGE": "-Og -g --coverage -fkeep-inline-functions -fkeep-static-functions",
"CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
"CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage",
"CMAKE_MAP_IMPORTED_CONFIG_SANITIZE": "Coverage;RelWithDebInfo;Release;Debug;"
}
},
{
"name": "ci-coverage",
"inherits": ["coverage-unix", "dev-mode", "conan"],
"cacheVariables": {
"COVERAGE_HTML_COMMAND": ""
}
},
{
"name": "ci-sanitize",
"binaryDir": "${sourceDir}/build/sanitize",
"inherits": ["ci-unix", "dev-mode", "conan"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Sanitize",
"CMAKE_CXX_FLAGS_SANITIZE": "-O2 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-common",
"CMAKE_MAP_IMPORTED_CONFIG_SANITIZE": "Sanitize;RelWithDebInfo;Release;Debug;"
}
},
{
"name": "ci-build",
"binaryDir": "${sourceDir}/build",
"hidden": true
},
{
"name": "ci-macos",
"inherits": ["ci-build", "ci-unix", "dev-mode", "conan"]
},
{
"name": "ci-ubuntu",
"inherits": ["ci-build", "ci-unix", "clang-tidy", "conan", "dev-mode"]
},
{
"name": "ci-windows",
"inherits": ["ci-build", "ci-win64", "dev-mode", "conan"]
}
]
}
"version": 2,
"configurePresets": [
{
"name": "default",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
]
}

@ -1,3 +1,69 @@
## lnav v0.12.3
Features:
* Files that contain a mixture of log messages from separate
services (e.g. docker logs) can now be automatically
de-multiplexed into separate files that lnav can digest.
* The `log_opid` column on log vtables can now be `UPDATE`d
so that you can manually set an opid on log messages that
don't have one. Setting an opid allows messages to show
up in the timeline view.
* The Files panel now has a details view on the right side
that shows extra information about the selected file.
You can look here for details of why lnav selected a
particular log format.
* Add support for GitHub Markdown Alerts.
* Added the `:xopen` command that will open the given paths
using an external opener like `open` or `xdg-open`.
* Clicking on a link in a markdown file will open the Actions
with the following options:
- opening the link target in lnav or, if it's an lnav script,
executing the script;
- opening the target with `:xopen`;
- or, copying the link to the clipboard.
* Added a `crash upload` command to the management CLI that will
upload crash logs to a server for analysis.
* Added a `:set-text-view-mode` command that controls whether
file contents, such as markdown, are rendered or shown in
their raw state.
* Text files with lines longer than 240 characters will be
automatically pretty-printed. You can revert to the raw view
using the `:set-text-view-mode` command.
* Added a `pretty_print()` SQL function that provides the same
functionality as the PRETTY view.
* Keymap definitions can now bind to a function key using an
identifier that starts with `f` followed by the number of the
function key.
Interface Changes:
* The "Gantt Chart" view has been renamed to "timeline".
* In the timeline view, pressing `ENTER` will focus on
the preview pane, so you can scroll through messages
with the selected Op ID.
* With mouse mode enabled, `CTRL` can be used as an alternate
to `SHIFT` when clicking/dragging in the main view to
highlight lines. A few terminals capture shift+clicks as a
way to select text and do not pass them to the application.
* Clicking on an internal link in a Markdown document will move
to that section.
Bug Fixes:
* Log messages in formats with custom timestamp formats were
not being converted to the local timezone.
* The timezone offset is now shown in the parser details
overlay for log messages.
* If a theme does not define `cursor-line` or `selected-text`
styles, the styles from the default theme will be used.
* The first argument to a script is now the full path of the
script and not just the script name.
Maintenance:
* You can now do an `UPDATE` on the `lnav_top_view` SQL view.
This makes it easier to write queries that manipulate the
current view.
* Upgrade to C++17
## lnav v0.12.2
Features:

@ -1,6 +1,7 @@
#! /bin/bash
unset NO_COLOR
unset XDG_CONFIG_HOME
top_srcdir="@abssrcdir@"
export top_srcdir
@ -37,9 +38,6 @@ export TSHARK_CMD
LIBARCHIVE_LIBS="@LIBARCHIVE_LIBS@"
export LIBARCHIVE_LIBS
HOME="${top_builddir}/test"
export HOME
# The full path of the test case
test_file=$1
# The base name of the test case
@ -47,6 +45,12 @@ test_file_base=`basename $1`
# The current test number for shell based tests.
test_num=0
HOME="${top_builddir}/test/cfg/${test_file_base}"
export HOME
rm -rf "${top_builddir}/test/cfg/${test_file_base}"
mkdir -p ${HOME}
test_hash=""
lnav_test="${top_builddir}/src/lnav-test"

@ -1,14 +0,0 @@
hunter_config(
libpcre
VERSION 8.41
CMAKE_ARGS
EXTRA_FLAGS=--enable-unicode-properties --enable-jit --enable-utf
)
hunter_config(
readline
VERSION 6.3
CMAKE_ARGS
EXTRA_FLAGS=CFLAGS=-Wno-implicit-function-declaration
)

@ -1,528 +0,0 @@
# Copyright (c) 2013-2019, Ruslan Baratov
# 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.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
# This is a gate file to Hunter package manager.
# Include this file using `include` command and add package you need, example:
#
# cmake_minimum_required(VERSION 3.2)
#
# include("cmake/HunterGate.cmake")
# HunterGate(
# URL "https://github.com/path/to/hunter/archive.tar.gz"
# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d"
# )
#
# project(MyProject)
#
# hunter_add_package(Foo)
# hunter_add_package(Boo COMPONENTS Bar Baz)
#
# Projects:
# * https://github.com/hunter-packages/gate/
# * https://github.com/ruslo/hunter
option(HUNTER_ENABLED "Enable Hunter package manager support" ON)
if(HUNTER_ENABLED)
if(CMAKE_VERSION VERSION_LESS "3.2")
message(
FATAL_ERROR
"At least CMake version 3.2 required for Hunter dependency management."
" Update CMake or set HUNTER_ENABLED to OFF."
)
endif()
endif()
include(CMakeParseArguments) # cmake_parse_arguments
option(HUNTER_STATUS_PRINT "Print working status" ON)
option(HUNTER_STATUS_DEBUG "Print a lot info" OFF)
option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON)
set(HUNTER_ERROR_PAGE "https://docs.hunter.sh/en/latest/reference/errors")
function(hunter_gate_status_print)
if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG)
foreach(print_message ${ARGV})
message(STATUS "[hunter] ${print_message}")
endforeach()
endif()
endfunction()
function(hunter_gate_status_debug)
if(HUNTER_STATUS_DEBUG)
foreach(print_message ${ARGV})
string(TIMESTAMP timestamp)
message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}")
endforeach()
endif()
endfunction()
function(hunter_gate_error_page error_page)
message("------------------------------ ERROR ------------------------------")
message(" ${HUNTER_ERROR_PAGE}/${error_page}.html")
message("-------------------------------------------------------------------")
message("")
message(FATAL_ERROR "")
endfunction()
function(hunter_gate_internal_error)
message("")
foreach(print_message ${ARGV})
message("[hunter ** INTERNAL **] ${print_message}")
endforeach()
message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
message("")
hunter_gate_error_page("error.internal")
endfunction()
function(hunter_gate_fatal_error)
cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}")
if("${hunter_ERROR_PAGE}" STREQUAL "")
hunter_gate_internal_error("Expected ERROR_PAGE")
endif()
message("")
foreach(x ${hunter_UNPARSED_ARGUMENTS})
message("[hunter ** FATAL ERROR **] ${x}")
endforeach()
message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
message("")
hunter_gate_error_page("${hunter_ERROR_PAGE}")
endfunction()
function(hunter_gate_user_error)
hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data")
endfunction()
function(hunter_gate_self root version sha1 result)
string(COMPARE EQUAL "${root}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("root is empty")
endif()
string(COMPARE EQUAL "${version}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("version is empty")
endif()
string(COMPARE EQUAL "${sha1}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("sha1 is empty")
endif()
string(SUBSTRING "${sha1}" 0 7 archive_id)
set(
hunter_self
"${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
)
set("${result}" "${hunter_self}" PARENT_SCOPE)
endfunction()
# Set HUNTER_GATE_ROOT cmake variable to suitable value.
function(hunter_gate_detect_root)
# Check CMake variable
string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty)
if(not_empty)
set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable")
return()
endif()
# Check environment variable
string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty)
if(not_empty)
set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT detected by environment variable")
return()
endif()
# Check HOME environment variable
string(COMPARE NOTEQUAL "$ENV{HOME}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable")
return()
endif()
# Check SYSTEMDRIVE and USERPROFILE environment variable (windows only)
if(WIN32)
string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug(
"HUNTER_ROOT set using SYSTEMDRIVE environment variable"
)
return()
endif()
string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug(
"HUNTER_ROOT set using USERPROFILE environment variable"
)
return()
endif()
endif()
hunter_gate_fatal_error(
"Can't detect HUNTER_ROOT"
ERROR_PAGE "error.detect.hunter.root"
)
endfunction()
function(hunter_gate_download dir)
string(
COMPARE
NOTEQUAL
"$ENV{HUNTER_DISABLE_AUTOINSTALL}"
""
disable_autoinstall
)
if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL)
hunter_gate_fatal_error(
"Hunter not found in '${dir}'"
"Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'"
"Settings:"
" HUNTER_ROOT: ${HUNTER_GATE_ROOT}"
" HUNTER_SHA1: ${HUNTER_GATE_SHA1}"
ERROR_PAGE "error.run.install"
)
endif()
string(COMPARE EQUAL "${dir}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("Empty 'dir' argument")
endif()
string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("HUNTER_GATE_SHA1 empty")
endif()
string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("HUNTER_GATE_URL empty")
endif()
set(done_location "${dir}/DONE")
set(sha1_location "${dir}/SHA1")
set(build_dir "${dir}/Build")
set(cmakelists "${dir}/CMakeLists.txt")
hunter_gate_status_debug("Locking directory: ${dir}")
file(LOCK "${dir}" DIRECTORY GUARD FUNCTION)
hunter_gate_status_debug("Lock done")
if(EXISTS "${done_location}")
# while waiting for lock other instance can do all the job
hunter_gate_status_debug("File '${done_location}' found, skip install")
return()
endif()
file(REMOVE_RECURSE "${build_dir}")
file(REMOVE_RECURSE "${cmakelists}")
file(MAKE_DIRECTORY "${build_dir}") # check directory permissions
# Disabling languages speeds up a little bit, reduces noise in the output
# and avoids path too long windows error
file(
WRITE
"${cmakelists}"
"cmake_minimum_required(VERSION 3.2)\n"
"project(HunterDownload LANGUAGES NONE)\n"
"include(ExternalProject)\n"
"ExternalProject_Add(\n"
" Hunter\n"
" URL\n"
" \"${HUNTER_GATE_URL}\"\n"
" URL_HASH\n"
" SHA1=${HUNTER_GATE_SHA1}\n"
" DOWNLOAD_DIR\n"
" \"${dir}\"\n"
" TLS_VERIFY\n"
" ${HUNTER_TLS_VERIFY}\n"
" SOURCE_DIR\n"
" \"${dir}/Unpacked\"\n"
" CONFIGURE_COMMAND\n"
" \"\"\n"
" BUILD_COMMAND\n"
" \"\"\n"
" INSTALL_COMMAND\n"
" \"\"\n"
")\n"
)
if(HUNTER_STATUS_DEBUG)
set(logging_params "")
else()
set(logging_params OUTPUT_QUIET)
endif()
hunter_gate_status_debug("Run generate")
# Need to add toolchain file too.
# Otherwise on Visual Studio + MDD this will fail with error:
# "Could not find an appropriate version of the Windows 10 SDK installed on this machine"
if(EXISTS "${CMAKE_TOOLCHAIN_FILE}")
get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE)
set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}")
else()
# 'toolchain_arg' can't be empty
set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=")
endif()
string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make)
if(no_make)
set(make_arg "")
else()
# Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM
set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}")
endif()
execute_process(
COMMAND
"${CMAKE_COMMAND}"
"-H${dir}"
"-B${build_dir}"
"-G${CMAKE_GENERATOR}"
"${toolchain_arg}"
${make_arg}
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE download_result
${logging_params}
)
if(NOT download_result EQUAL 0)
hunter_gate_internal_error(
"Configure project failed."
"To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}"
"In directory ${dir}"
)
endif()
hunter_gate_status_print(
"Initializing Hunter workspace (${HUNTER_GATE_SHA1})"
" ${HUNTER_GATE_URL}"
" -> ${dir}"
)
execute_process(
COMMAND "${CMAKE_COMMAND}" --build "${build_dir}"
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE download_result
${logging_params}
)
if(NOT download_result EQUAL 0)
hunter_gate_internal_error("Build project failed")
endif()
file(REMOVE_RECURSE "${build_dir}")
file(REMOVE_RECURSE "${cmakelists}")
file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}")
file(WRITE "${done_location}" "DONE")
hunter_gate_status_debug("Finished")
endfunction()
# Must be a macro so master file 'cmake/Hunter' can
# apply all variables easily just by 'include' command
# (otherwise PARENT_SCOPE magic needed)
macro(HunterGate)
if(HUNTER_GATE_DONE)
# variable HUNTER_GATE_DONE set explicitly for external project
# (see `hunter_download`)
set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
endif()
# First HunterGate command will init Hunter, others will be ignored
get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET)
if(NOT HUNTER_ENABLED)
# Empty function to avoid error "unknown function"
function(hunter_add_package)
endfunction()
set(
_hunter_gate_disabled_mode_dir
"${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode"
)
if(EXISTS "${_hunter_gate_disabled_mode_dir}")
hunter_gate_status_debug(
"Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}"
)
list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}")
endif()
elseif(_hunter_gate_done)
hunter_gate_status_debug("Secondary HunterGate (use old settings)")
hunter_gate_self(
"${HUNTER_CACHED_ROOT}"
"${HUNTER_VERSION}"
"${HUNTER_SHA1}"
_hunter_self
)
include("${_hunter_self}/cmake/Hunter")
else()
set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}")
string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name)
if(_have_project_name)
hunter_gate_fatal_error(
"Please set HunterGate *before* 'project' command. "
"Detected project: ${PROJECT_NAME}"
ERROR_PAGE "error.huntergate.before.project"
)
endif()
cmake_parse_arguments(
HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV}
)
string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1)
string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url)
string(
COMPARE
NOTEQUAL
"${HUNTER_GATE_UNPARSED_ARGUMENTS}"
""
_have_unparsed
)
string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global)
string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath)
if(_have_unparsed)
hunter_gate_user_error(
"HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}"
)
endif()
if(_empty_sha1)
hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory")
endif()
if(_empty_url)
hunter_gate_user_error("URL suboption of HunterGate is mandatory")
endif()
if(_have_global)
if(HUNTER_GATE_LOCAL)
hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)")
endif()
if(_have_filepath)
hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)")
endif()
endif()
if(HUNTER_GATE_LOCAL)
if(_have_global)
hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)")
endif()
if(_have_filepath)
hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)")
endif()
endif()
if(_have_filepath)
if(_have_global)
hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)")
endif()
if(HUNTER_GATE_LOCAL)
hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)")
endif()
endif()
hunter_gate_detect_root() # set HUNTER_GATE_ROOT
# Beautify path, fix probable problems with windows path slashes
get_filename_component(
HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE
)
hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}")
if(NOT HUNTER_ALLOW_SPACES_IN_PATH)
string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces)
if(NOT _contain_spaces EQUAL -1)
hunter_gate_fatal_error(
"HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces."
"Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error"
"(Use at your own risk!)"
ERROR_PAGE "error.spaces.in.hunter.root"
)
endif()
endif()
string(
REGEX
MATCH
"[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*"
HUNTER_GATE_VERSION
"${HUNTER_GATE_URL}"
)
string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty)
if(_is_empty)
set(HUNTER_GATE_VERSION "unknown")
endif()
hunter_gate_self(
"${HUNTER_GATE_ROOT}"
"${HUNTER_GATE_VERSION}"
"${HUNTER_GATE_SHA1}"
_hunter_self
)
set(_master_location "${_hunter_self}/cmake/Hunter")
get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE)
set(_done_location "${_archive_id_location}/DONE")
set(_sha1_location "${_archive_id_location}/SHA1")
# Check Hunter already downloaded by HunterGate
if(NOT EXISTS "${_done_location}")
hunter_gate_download("${_archive_id_location}")
endif()
if(NOT EXISTS "${_done_location}")
hunter_gate_internal_error("hunter_gate_download failed")
endif()
if(NOT EXISTS "${_sha1_location}")
hunter_gate_internal_error("${_sha1_location} not found")
endif()
file(READ "${_sha1_location}" _sha1_value)
string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal)
if(NOT _is_equal)
hunter_gate_internal_error(
"Short SHA1 collision:"
" ${_sha1_value} (from ${_sha1_location})"
" ${HUNTER_GATE_SHA1} (HunterGate)"
)
endif()
if(NOT EXISTS "${_master_location}")
hunter_gate_user_error(
"Master file not found:"
" ${_master_location}"
"try to update Hunter/HunterGate"
)
endif()
include("${_master_location}")
set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
endif()
endmacro()

@ -1,112 +0,0 @@
cmake_minimum_required(VERSION 3.14)
foreach(var IN ITEMS PROJECT_BINARY_DIR PROJECT_SOURCE_DIR)
if(NOT DEFINED "${var}")
message(FATAL_ERROR "${var} must be defined")
endif()
endforeach()
set(bin "${PROJECT_BINARY_DIR}")
set(src "${PROJECT_SOURCE_DIR}")
# ---- Dependencies ----
set(mcss_SOURCE_DIR "${bin}/docs/.ci")
if(NOT IS_DIRECTORY "${mcss_SOURCE_DIR}")
file(MAKE_DIRECTORY "${mcss_SOURCE_DIR}")
file(
DOWNLOAD
https://github.com/friendlyanon/m.css/releases/download/release-1/mcss.zip
"${mcss_SOURCE_DIR}/mcss.zip"
STATUS status
EXPECTED_MD5 00cd2757ebafb9bcba7f5d399b3bec7f
)
if(NOT status MATCHES "^0;")
message(FATAL_ERROR "Download failed with ${status}")
endif()
execute_process(
COMMAND "${CMAKE_COMMAND}" -E tar xf mcss.zip
WORKING_DIRECTORY "${mcss_SOURCE_DIR}"
RESULT_VARIABLE result
)
if(NOT result EQUAL "0")
message(FATAL_ERROR "Extraction failed with ${result}")
endif()
file(REMOVE "${mcss_SOURCE_DIR}/mcss.zip")
endif()
find_program(Python3_EXECUTABLE NAMES python3 python)
if(NOT Python3_EXECUTABLE)
message(FATAL_ERROR "Python executable was not found")
endif()
# ---- Process project() call in CMakeLists.txt ----
file(READ "${src}/CMakeLists.txt" content)
string(FIND "${content}" "project(" index)
if(index EQUAL "-1")
message(FATAL_ERROR "Could not find \"project(\"")
endif()
string(SUBSTRING "${content}" "${index}" -1 content)
string(FIND "${content}" "\n)\n" index)
if(index EQUAL "-1")
message(FATAL_ERROR "Could not find \"\\n)\\n\"")
endif()
string(SUBSTRING "${content}" 0 "${index}" content)
file(WRITE "${bin}/docs-ci.project.cmake" "docs_${content}\n)\n")
macro(list_pop_front list out)
list(GET "${list}" 0 "${out}")
list(REMOVE_AT "${list}" 0)
endmacro()
function(docs_project name)
cmake_parse_arguments(PARSE_ARGV 1 "" "" "VERSION;DESCRIPTION;HOMEPAGE_URL" LANGUAGES)
set(PROJECT_NAME "${name}" PARENT_SCOPE)
if(DEFINED _VERSION)
set(PROJECT_VERSION "${_VERSION}" PARENT_SCOPE)
string(REGEX MATCH "^[0-9]+(\\.[0-9]+)*" versions "${_VERSION}")
string(REPLACE . ";" versions "${versions}")
set(suffixes MAJOR MINOR PATCH TWEAK)
while(NOT versions STREQUAL "" AND NOT suffixes STREQUAL "")
list_pop_front(versions version)
list_pop_front(suffixes suffix)
set("PROJECT_VERSION_${suffix}" "${version}" PARENT_SCOPE)
endwhile()
endif()
if(DEFINED _DESCRIPTION)
set(PROJECT_DESCRIPTION "${_DESCRIPTION}" PARENT_SCOPE)
endif()
if(DEFINED _HOMEPAGE_URL)
set(PROJECT_HOMEPAGE_URL "${_HOMEPAGE_URL}" PARENT_SCOPE)
endif()
endfunction()
include("${bin}/docs-ci.project.cmake")
# ---- Generate docs ----
if(NOT DEFINED DOXYGEN_OUTPUT_DIRECTORY)
set(DOXYGEN_OUTPUT_DIRECTORY "${bin}/docs")
endif()
set(out "${DOXYGEN_OUTPUT_DIRECTORY}")
foreach(file IN ITEMS Doxyfile conf.py)
configure_file("${src}/docs/${file}.in" "${bin}/docs/${file}" @ONLY)
endforeach()
set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py")
set(config "${bin}/docs/conf.py")
file(REMOVE_RECURSE "${out}/html" "${out}/xml")
execute_process(
COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}"
WORKING_DIRECTORY "${bin}/docs"
RESULT_VARIABLE result
)
if(NOT result EQUAL "0")
message(FATAL_ERROR "m.css returned with ${result}")
endif()

@ -1,46 +0,0 @@
# ---- Dependencies ----
set(extract_timestamps "")
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
set(extract_timestamps DOWNLOAD_EXTRACT_TIMESTAMP YES)
endif ()
include(FetchContent)
FetchContent_Declare(
mcss URL
https://github.com/friendlyanon/m.css/releases/download/release-1/mcss.zip
URL_MD5 00cd2757ebafb9bcba7f5d399b3bec7f
SOURCE_DIR "${PROJECT_BINARY_DIR}/mcss"
UPDATE_DISCONNECTED YES
${extract_timestamps}
)
FetchContent_MakeAvailable(mcss)
find_package(Python3 3.6 REQUIRED)
# ---- Declare documentation target ----
set(
DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/docs"
CACHE PATH "Path for the generated Doxygen documentation"
)
set(working_dir "${PROJECT_BINARY_DIR}/docs")
foreach (file IN ITEMS Doxyfile conf.py)
configure_file("docs/${file}.in" "${working_dir}/${file}" @ONLY)
endforeach ()
set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py")
set(config "${working_dir}/conf.py")
add_custom_target(
docs
COMMAND "${CMAKE_COMMAND}" -E remove_directory
"${DOXYGEN_OUTPUT_DIRECTORY}/html"
"${DOXYGEN_OUTPUT_DIRECTORY}/xml"
COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}"
COMMENT "Building documentation using Doxygen and m.css"
WORKING_DIRECTORY "${working_dir}"
VERBATIM
)

@ -1,58 +0,0 @@
from conans import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps
class LnavConan(ConanFile):
name = "lnav"
version = "0.12.1"
homepage = "https://lnav.org"
url = "https://github.com/tstack/lnav.git"
license = "BSD-2-Clause"
description = (
"The Log File Navigator, lnav for short, is an advanced "
"log file viewer for the small-scale"
)
settings = "os", "compiler", "build_type", "arch"
exports_sources = "*"
no_copy_source = True
requires = (
"bzip2/1.0.8",
"libarchive/3.6.0",
"libcurl/7.85.0",
"ncurses/6.3",
"pcre2/10.40",
"readline/8.1.2",
"sqlite3/3.38.0",
"zlib/1.2.12",
)
generators = ("virtualrunenv",)
default_options = {
"libarchive:with_bzip2": True,
"libarchive:with_lz4": True,
"libarchive:with_lzo": True,
"libarchive:with_lzma": True,
"libarchive:with_zstd": True,
"pcre2:support_jit": True,
"pcre2:build_pcre2_8": True,
"sqlite3:enable_json1": True,
"sqlite3:enable_soundex": True,
"readline:with_library": "curses",
}
def generate(self):
CMakeToolchain(self).generate()
CMakeDeps(self).generate()
def build(self):
cmake = CMake(self)
cmake.configure()
if self.settings.os == "Macos" and self.settings.arch == "armv8":
cmake.definitions["CMAKE_SYSTEM_PROCESSOR"] = "arm64"
cmake.build()
def package(self):
cmake = CMake(self)
cmake.install()
def deploy(self):
self.copy("*", dst="bin", src="bin")

@ -1,4 +1,4 @@
AC_INIT([lnav],[0.12.2],[lnav@googlegroups.com],[lnav],[http://lnav.org])
AC_INIT([lnav],[0.12.3],[lnav@googlegroups.com],[lnav],[http://lnav.org])
AC_CONFIG_SRCDIR([src/lnav.cc])
AC_CONFIG_MACRO_DIR([m4])
AM_INIT_AUTOMAKE([foreign subdir-objects])
@ -17,7 +17,7 @@ CXX="$PTHREAD_CXX"
CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS"
AC_LANG(C++)
AX_CXX_COMPILE_STDCXX_14([noext], [mandatory])
AX_CXX_COMPILE_STDCXX_17([noext], [mandatory])
dnl abssrcdir is the absolute path to the source base (regardless of where
dnl you are building it)

@ -0,0 +1,19 @@
# syntax=docker/dockerfile:1
ARG PYTHON_VERSION=3.12.3
FROM python:${PYTHON_VERSION}-slim
LABEL fly_launch_runtime="flask"
WORKDIR /code
RUN apt-get update && apt-get install -y --no-install-recommends gcc build-essential
COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt
COPY . .
EXPOSE 8080
CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0", "--port=8080"]

@ -0,0 +1 @@
web: gunicorn app:app

@ -0,0 +1,148 @@
from werkzeug.middleware.proxy_fix import ProxyFix
from flask import Flask, request, send_file, after_this_request
import os
import glob
import uuid
from datetime import datetime
import zipfile
import spookyhash
app = Flask(__name__)
app.wsgi_app = ProxyFix(
app.wsgi_app, x_for=2, x_proto=2, x_host=2, x_prefix=2
)
ROOT_PAGE = """
<html>
<head>
<title>lnav crash upload site</title>
</head>
<body>
You can help improve <b>lnav</b> by uploading your crash logs by running:
<pre>
lnav -m crash upload
</pre>
</body>
</html>
"""
# Function to check free space on filesystem
def check_free_space():
statvfs = os.statvfs('/logs')
# Calculate free space in MB
free_space_mb = (statvfs.f_bsize * statvfs.f_bavail) / (1024 * 1024)
return free_space_mb
@app.route('/crash', methods=['POST'])
def crash_handler():
# Check free space on filesystem
free_space_mb = check_free_space()
if free_space_mb < 100:
return 'Insufficient free space on the filesystem!\n', 500
# Retrieve the secret value from the environment variable
lnav_secret_env = os.environ.get('LNAV_SECRET')
# Check if header 'lnav-secret' exists and has the correct value
if request.headers.get('lnav-secret') == lnav_secret_env:
# Check if content length is provided
if 'Content-Length' not in request.headers:
return 'Content length header is missing!', 400
# Get the content length
content_length = int(request.headers['Content-Length'])
# Check if content length is zero
if content_length == 0:
return 'Empty request body!', 400
# Check if content length exceeds 10MB
if content_length > 10 * 1024 * 1024: # 10MB limit
return 'Content size exceeds the limit of 10MB!', 413
# Get the content from the request body
content = request.data
nonce = request.headers.get('X-lnav-nonce', '')
crash_hash = request.headers.get('X-lnav-hash', '')
if not crash_hash.startswith("0000"):
return "Invalid proof of work hash", 401
sh = spookyhash.Hash128()
sh.update(nonce.encode('utf-8'))
sh.update(content)
verify_hash = sh.hexdigest()
if verify_hash != crash_hash:
return "Proof of work hash does not match", 401
# Generate a unique ID
unique_id = str(uuid.uuid4())
# Get the current time
current_time = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
# Construct the file name with current time and unique ID
file_name = f'crash_log_{current_time}_{unique_id}.txt'
full_path = os.path.join("/logs", file_name)
# Save the content to the file in the current directory
with open(full_path, 'wb') as f:
f.write(content)
return 'Data saved successfully!', 200
else:
return 'Unauthorized access!', 401
@app.route('/download_crashes', methods=['GET'])
def download_crashes():
# Retrieve the secret value for downloading from the environment variable
lnav_download_secret_env = os.environ.get('LNAV_DOWNLOAD_SECRET')
# Check if header 'lnav-secret' exists and has the correct value for downloading
if request.headers.get('lnav-secret') == lnav_download_secret_env:
# Get all the files in the current directory
crash_files = glob.glob("/logs/crash_log_*")
# Generate a unique ID for the zip file
zip_id = str(uuid.uuid4())
# Construct the zip file name
zip_file_name = f'crash_archive_{zip_id}.zip'
# Create a new zip file
with zipfile.ZipFile(zip_file_name, 'w') as zipf:
# Add each crash file to the zip file
for crash_file in crash_files:
zipf.write(crash_file)
# Delete the crash files
for crash_file in crash_files:
os.remove(crash_file)
@after_this_request
def remove_zip(response):
try:
os.remove(zip_file_name)
except Exception as e:
app.logger.error(f"Error removing zip file: {e}")
return response
# Send the zip file as a response
return send_file(zip_file_name, as_attachment=True, download_name=zip_file_name)
else:
return 'Unauthorized access!\n', 401
@app.route('/', methods=['GET'])
def root_page():
return ROOT_PAGE, 200
if __name__ == '__main__':
# Run the Flask app
app.run()

@ -0,0 +1,26 @@
# fly.toml app configuration file generated for crashd on 2024-05-24T15:13:43-07:00
#
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
#
app = 'crashd'
primary_region = 'sea'
[build]
[mounts]
source = "crash_logs"
destination = "/logs"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ['app']
[[vm]]
memory = '1gb'
cpu_kind = 'shared'
cpus = 1

@ -0,0 +1,10 @@
blinker==1.8.2
click==8.1.7
Flask==3.0.3
gunicorn==22.0.0
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
packaging==24.0
spookyhash==2.1.0
Werkzeug==3.0.3

@ -211,11 +211,11 @@ GEM
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.18.1)
nokogiri (1.16.3-arm64-darwin)
nokogiri (1.16.5-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.3-x86_64-darwin)
nokogiri (1.16.5-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.3-x86_64-linux)
nokogiri (1.16.5-x86_64-linux)
racc (~> 1.4)
octokit (4.25.1)
faraday (>= 1, < 3)
@ -223,11 +223,12 @@ GEM
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (4.0.7)
racc (1.7.3)
racc (1.8.0)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
rexml (3.2.5)
rexml (3.2.8)
strscan (>= 3.0.9)
rouge (3.26.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
@ -242,6 +243,7 @@ GEM
faraday (>= 0.17.3, < 3)
simpleidn (0.2.1)
unf (~> 0.1.4)
strscan (3.1.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
typhoeus (1.4.0)

@ -19,7 +19,7 @@
# in the templates via {{ site.myvariable }}.
title: The Logfile Navigator
version: 0.12.1
version: 0.12.2
email: support@lnav.org
description: >- # this means to ignore newlines until "baseurl:"
The Logfile Navigator, lnav for short, is an advanced log file viewer

@ -184,7 +184,7 @@
"properties": {
"test": {
"title": "/tuning/clipboard/impls/<clipboard_impl_name>/test",
"description": "The command that checks",
"description": "The command that checks if a clipboard command is available",
"type": "string",
"examples": [
"command -v pbcopy"
@ -209,6 +209,46 @@
},
"additionalProperties": false
},
"external-opener": {
"description": "Settings related to opening external files/URLs",
"title": "/tuning/external-opener",
"type": "object",
"properties": {
"impls": {
"description": "External opener implementations",
"title": "/tuning/external-opener/impls",
"type": "object",
"patternProperties": {
"^([\\w\\-]+)$": {
"description": "External opener implementation",
"title": "/tuning/external-opener/impls/<opener_impl_name>",
"type": "object",
"properties": {
"test": {
"title": "/tuning/external-opener/impls/<opener_impl_name>/test",
"description": "The command that checks if an external opener is available",
"type": "string",
"examples": [
"command -v open"
]
},
"command": {
"title": "/tuning/external-opener/impls/<opener_impl_name>/command",
"description": "The command used to open a file or URL",
"type": "string",
"examples": [
"open"
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"url-scheme": {
"description": "Settings related to custom URL handling",
"title": "/tuning/url-scheme",
@ -789,7 +829,7 @@
"title": "/ui/keymap-defs/<keymap_name>",
"type": "object",
"patternProperties": {
"^((?:x[0-9a-f]{2})+)$": {
"^((?:x[0-9a-f]{2}|f[0-9]{1,2})+)$": {
"description": "Map of key codes to commands to execute. The field names are the keys to be mapped using as a hexadecimal representation of the UTF-8 encoding. Each byte of the UTF-8 should start with an 'x' followed by the hexadecimal representation of the byte.",
"title": "/ui/keymap-defs/<keymap_name>/<key_seq>",
"type": "object",
@ -899,6 +939,37 @@
}
},
"additionalProperties": false
},
"demux": {
"description": "Demultiplexer definitions",
"title": "/log/demux",
"type": "object",
"patternProperties": {
"^([\\w\\-\\.]+)$": {
"description": "The definition of a demultiplexer",
"title": "/log/demux/<name>",
"type": "object",
"properties": {
"enabled": {
"title": "/log/demux/<name>/enabled",
"description": "Indicates whether this demuxer will be used at the demuxing stage",
"type": "boolean"
},
"pattern": {
"title": "/log/demux/<name>/pattern",
"description": "A regular expression to match a line in a multiplexed file",
"type": "string"
},
"control-pattern": {
"title": "/log/demux/<name>/control-pattern",
"description": "A regular expression to match a control line in a multiplexed file",
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": false

@ -194,10 +194,11 @@ bottom-right section of the UI will be updated with the help text.
Key Sequence Encoding
^^^^^^^^^^^^^^^^^^^^^
Key presses are converted into a hex-encoded string that is used to lookup an
entry in the keymap. Each byte of the keypress value is formatted as an
Key presses are converted into a string that is used to lookup an
entry in the keymap. Function keys are encoded as an :code:`f` followed by
the key number. Other keys are encoded as UTF-8 bytes and formatted as an
:code:`x` followed by the hex-encoding in lowercase. For example, the encoding
for the £ key would be :code:`xc2xa3`. To make it easier to discover the
for the :code:`£` key would be :code:`xc2xa3`. To make it easier to discover the
encoding for unassigned keys, **lnav** will print in the command prompt the
:code:`:config` command and
`JSON-Pointer <https://tools.ietf.org/html/rfc6901>`_ for assigning a command
@ -306,11 +307,43 @@ standard input. The handler should then generate the annotation
content on the standard output. The output is treated as Markdown,
so the content can be styled as desired.
Demultiplexing (v0.12.3+)
^^^^^^^^^^^^^^^^^^^^^^^^^
Files that are a mix of content from different sources, like
the output of :code:`docker compose logs`, can be automatically
demultiplexed so that *lnav* can process them correctly. Each
line of the input file must have a unique identifier that can
be used to determine which service the line belongs to. The
lines are then distributed to separate files based on the
identifier. A demultiplexer is a regular expression that
extracts the identifier, the log message, and an optional
timestamp.
Demultiplexers are defined in the main configuration under
the :code:`/log/demux` path. The pattern for the demuxer
has the following known capture names:
:mux_id: (required) Captures the unique identifier.
:body: (required) Captures the body of the log message
that should be written to the file.
:timestamp: (optional) The timestamp for the log message.
If this is available and the log message does not have
it's own timestamp, this will be used instead.
If there are additional captures, they will be included
in the file metadata that can be accessed by the
:code:`lnav_file_demux_metadata` view of the
:code:`lnav_file_metadata` table.
Reference
^^^^^^^^^
.. jsonschema:: ../schemas/config-v1.schema.json#/properties/log/properties/watch-expressions/patternProperties/^([\w\.\-]+)$
.. jsonschema:: ../schemas/config-v1.schema.json#/properties/log/properties/annotations/patternProperties/^([\w\.\-]+)$
.. jsonschema:: ../schemas/config-v1.schema.json#/properties/log/properties/demux/patternProperties/^([\w\-\.]+)$
.. _tuning:

@ -101,3 +101,12 @@ can use the following commands to generate customized output for a report:
:language: custsqlite
:caption: report-demo.lnav
:linenos:
Creating a Runbook
^^^^^^^^^^^^^^^^^^
If you have a collection of lnav scripts, you can create a markdown file
that links to the scripts and use it as a runbook. Clicking on a link to
a :file:`.lnav` file will open an "Actions" menu with an "Execute" menu
item. Clicking on "Execute" will launch the script.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

@ -104,14 +104,19 @@ To hide the panels again, press :kbd:`q`.
Screenshot of the files panel showing the loaded files.
The Files panel is open initially to display progress in loading files.
The following information can be displayed for each file:
The following information is displayed for each file:
* the "unique" portion of the path relative to the other files and
* the amount of data that has been indexed.
To the right of the file list is a panel that shows details for each
file, such as:
* the "unique" portion of the path relative to the other files;
* the amount of data that has been indexed;
* the date range of log messages contained in the file;
* the errors that were encountered while trying to index the file;
* the notes recorded for files where some automatic action was taken,
like hiding the file if it was seen as a duplicate of another file.
like hiding the file if it was seen as a duplicate of another file;
* the details of the demultiplexing and log format matching process.
.. figure:: lnav-filters-panel.png
:align: center
@ -305,19 +310,19 @@ can also press :kbd:`Shift` + :kbd:`i` to toggle the histogram view
while synchronizing the top time. While in the histogram view,
pressing :kbd:`z` / :kbd:`Shift` + :kbd:`z` will zoom in/out.
GANTT
^^^^^
TIMELINE
^^^^^^^^
.. note:: This feature is available in v0.12.0+.
.. figure:: lnav-gantt-1.png
.. figure:: lnav-timeline-1.png
:align: center
Screenshot of the Gantt chart view when viewing logs from the
Screenshot of the timeline view when viewing logs from the
VMWare Update Manager. Most rows show API requests as they
are received and processed.
The Gantt Chart view visualizes operations over time. The operations
The timeline view [#]_ visualizes operations over time. The operations
are identified by the "opid" field defined in the log format. In the
view, there is a header that shows the overall time span, the
narrowed time span around the focused line, and the column headers.
@ -346,7 +351,7 @@ The following hotkeys can be useful in this view:
:code:`opid/subid` property, this will toggle an overlay panel
that displays the sub-operation descriptions.
.. figure:: lnav-gantt-2.png
.. figure:: lnav-timeline-2.png
:align: center
Screenshot showing the same log as above after pressing
@ -354,12 +359,14 @@ The following hotkeys can be useful in this view:
sub-operations performed while processing the main operation.
* :kbd:`Shift` + :kbd:`q` -- Return to the previous view and change
its focused line to match the time that was focused in the gantt
its focused line to match the time that was focused in the timeline
view.
* :kbd:`Shift` + :kbd:`a` -- After leaving the gantt view, pressing
these keys will return to the Gantt view while keeping the focused
* :kbd:`Shift` + :kbd:`a` -- After leaving the timeline view, pressing
these keys will return to the timeline view while keeping the focused
time in sync.
.. [#] Formerly called the "Gantt Chart" view.
PRETTY
^^^^^^
@ -428,6 +435,9 @@ elements will respond to mouse inputs:
* clicking on the main view will move the cursor to the given
row and dragging will scroll the view as needed;
* :kbd:`Shift` (or :kbd:`CTRL`) clicking/dragging in the main
view will highlight lines and then toggle their bookmark
status on release;
* double-clicking in the main view will select the underlying
text and drag-selecting within a line will select the given
text;

@ -0,0 +1,70 @@
vcpkg_download_distfile(
ARCHIVE_PATH
URLS
"https://invisible-mirror.net/archives/ncurses/ncurses-${VERSION}.tar.gz"
"ftp://ftp.invisible-island.net/ncurses/ncurses-${VERSION}.tar.gz"
"https://ftp.gnu.org/gnu/ncurses/ncurses-${VERSION}.tar.gz"
FILENAME "ncurses-${VERSION}.tgz"
SHA512 1c2efff87a82a57e57b0c60023c87bae93f6718114c8f9dc010d4c21119a2f7576d0225dab5f0a227c2cfc6fb6bdbd62728e407f35fce5bf351bb50cf9e0fd34
)
vcpkg_extract_source_archive(
SOURCE_PATH
ARCHIVE "${ARCHIVE_PATH}"
)
vcpkg_list(SET OPTIONS)
if (VCPKG_LIBRARY_LINKAGE STREQUAL "dynamic")
list(APPEND OPTIONS
--with-cxx-shared
--with-shared # "lib model"
--without-normal # "lib model"
)
endif ()
if (NOT VCPKG_TARGET_IS_MINGW)
list(APPEND OPTIONS
--enable-mixed-case
)
endif ()
if (VCPKG_TARGET_IS_MINGW)
list(APPEND OPTIONS
--disable-home-terminfo
--enable-term-driver
--disable-termcap
)
endif ()
vcpkg_configure_make(
SOURCE_PATH "${SOURCE_PATH}"
DETERMINE_BUILD_TRIPLET
NO_ADDITIONAL_PATHS
OPTIONS
${OPTIONS}
--disable-db-install
--enable-pc-files
--enable-widec
--enable-sigwinch
--enable-termcap
--enable-ext-colors
--without-ada
--without-debug # "lib model"
--without-manpages
--without-progs
--without-tack
--without-tests
--with-pkg-config-libdir=libdir
)
vcpkg_install_make()
vcpkg_fixup_pkgconfig()
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/bin")
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/bin")
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share")
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/COPYING")

@ -0,0 +1,10 @@
The package ncurses is compatible with built-in CMake variables:
set(CURSES_NEED_NCURSES TRUE)
find_package(Curses REQUIRED)
target_include_directories(main PRIVATE ${CURSES_INCLUDE_DIRS})
target_compile_options(main PRIVATE ${CURSES_CFLAGS})
target_link_libraries(main PRIVATE ${CURSES_LIBRARIES})
Blah

@ -0,0 +1,9 @@
{
"name": "ncurses",
"version": "6.4",
"port-version": 2,
"description": "Free software emulation of curses in System V Release 4.0, and more",
"homepage": "https://invisible-island.net/ncurses/announce.html",
"license": "MIT",
"supports": "!windows | mingw"
}

@ -0,0 +1 @@
set(VCPKG_POLICY_EMPTY_PACKAGE enabled)

@ -0,0 +1,9 @@
{
"name": "readline-osx",
"version-date": "2020-01-04",
"description": "empty package, linking to readline-unix",
"supports": "osx",
"dependencies": [
"readline-unix"
]
}

@ -0,0 +1,34 @@
set(filename readline-${VERSION}.tar.gz)
vcpkg_download_distfile(
ARCHIVE
URLS
"https://ftpmirror.gnu.org/gnu/readline/${filename}"
"https://ftp.gnu.org/gnu/readline/${filename}"
FILENAME "${filename}"
SHA512 0a451d459146bfdeecc9cdd94bda6a6416d3e93abd80885a40b334312f16eb890f8618a27ca26868cebbddf1224983e631b1cbc002c1a4d1cd0d65fba9fea49a
)
vcpkg_extract_source_archive(SOURCE_PATH
ARCHIVE "${ARCHIVE}"
PATCHES
8.2p1.diff
)
vcpkg_configure_make(
SOURCE_PATH "${SOURCE_PATH}"
DETERMINE_BUILD_TRIPLET
OPTIONS
--with-curses=yes
--disable-install-examples
)
vcpkg_install_make()
file(REMOVE_RECURSE
"${CURRENT_PACKAGES_DIR}/debug/share"
"${CURRENT_PACKAGES_DIR}/tools"
)
vcpkg_fixup_pkgconfig()
file(INSTALL "${SOURCE_PATH}/COPYING" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)

@ -0,0 +1,12 @@
{
"name": "readline-unix",
"version": "8.2",
"port-version": 1,
"description": "The GNU Readline library provides a set of functions for use by applications that allow users to edit command lines as they are typed in.",
"homepage": "https://tiswww.case.edu/php/chet/readline/rltop.html",
"license": "GPL-3.0-or-later",
"supports": "!windows",
"dependencies": [
"ncurses"
]
}

@ -0,0 +1,6 @@
if(VCPKG_CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
message(FATAL_ERROR "No implementation of readline is currently available for UWP targets")
endif()
set(VCPKG_POLICY_EMPTY_PACKAGE enabled)
FILE(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")

@ -0,0 +1,10 @@
the package readline can be used under windows via:
find_package(unofficial-readline-win32 CONFIG REQUIRED)
target_link_libraries(main PRIVATE unofficial::readline-win32::readline)
The package readline can be imported via the CMake FindPkgConfig module:
find_package(PkgConfig REQUIRED)
pkg_check_modules(readline REQUIRED IMPORTED_TARGET readline)
target_link_libraries(main PRIVATE PkgConfig::readline)

@ -0,0 +1,17 @@
{
"name": "readline",
"version": "0",
"port-version": 5,
"description": "GNU readline and history libraries",
"supports": "!uwp",
"dependencies": [
{
"name": "readline-unix",
"platform": "!windows"
},
{
"name": "readline-win32",
"platform": "windows"
}
]
}

@ -1,5 +1,5 @@
VERSION=0.12.2
VERSION=0.12.3
VERSION_TAG=v$(VERSION)

@ -25,9 +25,9 @@ URL: https://lnav.org
# Source: https://github.com/tstack/%{name}/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz
Source: /github/home/rpmbuild/SOURCES/%{name}-%{version}.tar.gz
# Source1: lnav.desktop
BuildRequires: gcc-toolset-12
BuildRequires: gcc-toolset-12-annobin-plugin-gcc
BuildRequires: gcc-toolset-12-annobin-annocheck
BuildRequires: gcc-toolset-13
BuildRequires: gcc-toolset-13-annobin-plugin-gcc
BuildRequires: gcc-toolset-13-annobin-annocheck
BuildRequires: autoconf
BuildRequires: automake
BuildRequires: cargo
@ -64,8 +64,7 @@ quickly and efficiently focus on problems.
%autosetup -p1
%build
source /opt/rh/gcc-toolset-12/enable
(cd /opt/rh/gcc-toolset-12/root/usr/lib/gcc/x86_64-redhat-linux/12/plugin/ && ln -s annobin.so gcc-annobin.so)
source /opt/rh/gcc-toolset-13/enable
autoreconf -fiv
%configure \
--disable-silent-rules \

@ -45,16 +45,23 @@ set(TIME_FORMATS
"%Y-%m-%d %H:%M:%S:%L"
"%Y-%m-%d %H:%M:%S"
"%Y-%m-%d %H:%M"
"%Y-%m-%dT%H:%M:%S %p %Z"
"%Y-%m-%dT%H:%M:%S.%N%z"
"%y-%m-%dT%H:%M:%S.%N%z"
"%Y-%m-%dT%H:%M:%S.%f%z"
"%y-%m-%dT%H:%M:%S.%f%z"
"%Y-%m-%dT%H:%M:%S.%L%z"
"%y-%m-%dT%H:%M:%S.%L%z"
"%Y-%m-%dT%H:%M:%S%z"
"%Y-%m-%dT%H:%M:%S"
"%Y-%m-%dT%H:%M:%S%z"
"%Y-%m-%dT%H:%M:%S"
"%Y-%m-%dT%H:%M"
"%Y/%m/%d %H:%M:%S %z"
"%Y/%m/%d %H:%M:%S%z"
"%Y/%m/%d %H:%M:%S.%f%z"
"%Y/%m/%d %H:%M:%S.%f%Z"
"%Y/%m/%d %H:%M:%S.%f %z"
"%Y/%m/%d %H:%M:%S.%f %Z"
"%Y/%m/%d %H:%M:%S.%f"
"%Y/%m/%d %H:%M:%S.%L"
"%Y/%m/%d %H:%M:%S"
@ -64,6 +71,7 @@ set(TIME_FORMATS
"%Y %b %d %H:%M:%S"
"%a %b %d %H:%M:%S %Y"
"%a %b %d %H:%M:%S.%f %Y"
"%a %b %d %H:%M:%S:%f %Y"
"%a %b %d %H:%M:%S %Z %Y"
"%a %b %d %I:%M:%S %p %Z %Y"
"%a %b %d %H:%M:%S "
@ -79,6 +87,7 @@ set(TIME_FORMATS
"%d %b %Y %H:%M:%S.%L"
"%d %b %Y %H:%M:%S,%L"
"%d %b %Y %H:%M"
"%b-%d %H:%M:%S"
"%b %d %H:%M:%S"
"%b %d %k:%M:%S"
"%b %d %l:%M:%S"
@ -110,6 +119,7 @@ set(TIME_FORMATS
"%Y/%m/%d"
"%Y/%m"
"%s.%f"
)
set(GEN_SRCS "")
@ -144,8 +154,11 @@ set(FORMAT_FILES
formats/access_log.json
formats/alb_log.json
formats/block_log.json
formats/bunyan_log.json
formats/candlepin_log.json
formats/choose_repo_log.json
formats/cloudflare_log.json
formats/cloudvm_ram_log.json
formats/cups_log.json
formats/dpkg_log.json
formats/elb_log.json
@ -153,18 +166,22 @@ set(FORMAT_FILES
formats/error_log.json
formats/esx_syslog_log.json
formats/fsck_hfs_log.json
formats/github_events_log.json
formats/glog_log.json
formats/haproxy_log.json
formats/java_log.json
formats/journald_json_log.json
formats/katello_log.json
formats/lnav_debug_log.json
formats/nextcloud_log.json
formats/nextflow_log.json
formats/openam_log.json
formats/openamdb_log.json
formats/openstack_log.json
formats/page_log.json
formats/papertrail_log.json
formats/pcap_log.json
formats/procstate_log.json
formats/redis_log.json
formats/snaplogic_log.json
formats/sssd_log.json
formats/strace_log.json
@ -173,13 +190,17 @@ set(FORMAT_FILES
formats/s3_log.json
formats/tcf_log.json
formats/tcsh_history.json
formats/unifi_log.json
formats/uwsgi_log.json
formats/vdsm_log.json
formats/vmk_log.json
formats/vmw_log.json
formats/vmw_vc_svc_log.json
formats/vmw_py_log.json
formats/xmlrpc_log.json)
formats/vpostgres_log.json
formats/xmlrpc_log.json
formats/zookeeper_log.json
)
set(FORMAT_FILE_PATHS ${FORMAT_FILES})
@ -401,12 +422,14 @@ add_library(
collation-functions.cc
column_namer.cc
command_executor.cc
crashd.client.cc
curl_looper.cc
db_sub_source.cc
dump_internals.cc
elem_to_json.cc
environ_vtab.cc
extension-functions.cc
external_opener.cc
field_overlay_source.cc
file_collection.cc
file_converter_manager.cc
@ -420,7 +443,6 @@ add_library(
fs-extension-functions.cc
fstat_vtab.cc
fts_fuzzy_match.cc
gantt_source.cc
help_text.cc
help_text_formatter.cc
highlighter.cc
@ -482,10 +504,12 @@ add_library(
styling.cc
text_anonymizer.cc
text_format.cc
text_link_handler.cc
text_overlay_menu.cc
textfile_highlighters.cc
textfile_sub_source.cc
textview_curses.cc
timeline_source.cc
top_status_source.cc
time-extension-functions.cc
timer.cc
@ -519,10 +543,13 @@ add_library(
byte_array.hh
command_executor.hh
column_namer.hh
crashd.client.hh
curl_looper.hh
doc_status_source.hh
dump_internals.hh
elem_to_json.hh
external_opener.hh
external_opener.cfg.hh
field_overlay_source.hh
file_collection.hh
file_converter_manager.hh
@ -534,8 +561,6 @@ add_library(
filter_sub_source.hh
fstat_vtab.hh
fts_fuzzy_match.hh
gantt_source.hh
gantt_status_source.hh
grep_highlighter.hh
hasher.hh
help_text.hh
@ -570,7 +595,6 @@ add_library(
logfile_stats.hh
md2attr_line.hh
md4cpp.hh
optional.hpp
file_converter_manager.hh
plain_text_source.hh
pretty_printer.hh
@ -609,12 +633,15 @@ add_library(
termios_guard.hh
text_anonymizer.hh
text_format.hh
text_link_handler.hh
text_overlay_menu.hh
textfile_highlighters.hh
textfile_sub_source.hh
textview_curses.hh
textview_curses_fwd.hh
time_T.hh
timeline_source.hh
timeline_status_source.hh
timer.hh
top_status_source.hh
url_handler.cfg.hh
@ -633,12 +660,6 @@ add_library(
mapbox/variant.hpp
mapbox/variant_io.hpp
mapbox/variant_visitor.hpp
ghc/filesystem.hpp
ghc/fs_fwd.hpp
ghc/fs_impl.hpp
ghc/fs_std.hpp
ghc/fs_std_fwd.hpp
ghc/fs_std_impl.hpp
ww898/cp_utf8.hpp
log_level_re.cc
@ -690,7 +711,9 @@ target_include_directories(diag PUBLIC . fmtlib ${CMAKE_CURRENT_BINARY_DIR}
third-party/date/include
third-party/prqlc-c
third-party/rapidyaml
${CURSES_INCLUDE_DIRS}
)
target_compile_options(diag PRIVATE ${CURSES_CFLAGS})
target_link_libraries(
diag

@ -227,6 +227,7 @@ noinst_HEADERS = \
byte_array.hh \
column_namer.hh \
command_executor.hh \
crashd.client.hh \
curl_looper.hh \
data_scanner.hh \
data_scanner_re.re \
@ -237,6 +238,8 @@ noinst_HEADERS = \
dump_internals.hh \
elem_to_json.hh \
environ_vtab.hh \
external_opener.hh \
external_opener.cfg.hh \
field_overlay_source.hh \
file_collection.hh \
file_converter_manager.hh \
@ -249,8 +252,6 @@ noinst_HEADERS = \
filter_sub_source.hh \
fstat_vtab.hh \
fts_fuzzy_match.hh \
gantt_source.hh \
gantt_status_source.hh \
grep_highlighter.hh \
grep_proc.hh \
hasher.hh \
@ -302,7 +303,6 @@ noinst_HEADERS = \
mapbox/variant_visitor.hpp \
md2attr_line.hh \
md4cpp.hh \
optional.hpp \
piper.looper.hh \
piper.looper.cfg.hh \
plain_text_source.hh \
@ -349,12 +349,15 @@ noinst_HEADERS = \
term_extra.hh \
text_anonymizer.hh \
text_format.hh \
text_link_handler.hh \
text_overlay_menu.hh \
textfile_highlighters.hh \
textfile_sub_source.hh \
textview_curses.hh \
textview_curses_fwd.hh \
time_T.hh \
timeline_source.hh \
timeline_status_source.hh \
timer.hh \
top_status_source.hh \
top_status_source.cfg.hh \
@ -377,12 +380,6 @@ noinst_HEADERS = \
xpath_vtab.hh \
xterm_mouse.hh \
spookyhash/SpookyV2.h \
ghc/filesystem.hpp \
ghc/fs_fwd.hpp \
ghc/fs_impl.hpp \
ghc/fs_std.hpp \
ghc/fs_std_fwd.hpp \
ghc/fs_std_impl.hpp \
ww898/cp_utf8.hpp
nodist_libdiag_a_SOURCES = \
@ -430,6 +427,7 @@ libdiag_a_SOURCES = \
collation-functions.cc \
column_namer.cc \
command_executor.cc \
crashd.client.cc \
curl_looper.cc \
db_sub_source.cc \
document.sections.cc \
@ -437,6 +435,7 @@ libdiag_a_SOURCES = \
elem_to_json.cc \
environ_vtab.cc \
extension-functions.cc \
external_opener.cc \
field_overlay_source.cc \
file_collection.cc \
file_converter_manager.cc \
@ -449,7 +448,6 @@ libdiag_a_SOURCES = \
fstat_vtab.cc \
fs-extension-functions.cc \
fts_fuzzy_match.cc \
gantt_source.cc \
grep_proc.cc \
help_text.cc \
help_text_formatter.cc \
@ -507,6 +505,7 @@ libdiag_a_SOURCES = \
styling.cc \
text_anonymizer.cc \
text_format.cc \
text_link_handler.cc \
text_overlay_menu.cc \
textfile_sub_source.cc \
timer.cc \
@ -517,6 +516,7 @@ libdiag_a_SOURCES = \
textfile_highlighters.cc \
textview_curses.cc \
time-extension-functions.cc \
timeline_source.cc \
top_status_source.cc \
unique_path.cc \
view_curses.cc \

@ -83,7 +83,7 @@ all_logs_vtab::extract(logfile* lf,
this->vi_attrs.clear();
sub_values.lvv_sbr = line.clone();
format->annotate(line_number, this->vi_attrs, sub_values, false);
format->annotate(lf, line_number, this->vi_attrs, sub_values, false);
auto body = find_string_attr_range(this->vi_attrs, &SA_BODY);
if (body.lr_start == -1) {
@ -111,6 +111,7 @@ all_logs_vtab::extract(logfile* lf,
this->alv_values_meta,
json_string(gen).to_string_fragment().to_string());
values.lvv_opid_value = std::move(sub_values.lvv_opid_value);
values.lvv_opid_provenance = sub_values.lvv_opid_provenance;
}
bool

@ -52,7 +52,7 @@
#include "fmt/format.h"
#include "hasher.hh"
namespace fs = ghc::filesystem;
namespace fs = std::filesystem;
namespace archive_manager {
@ -124,8 +124,8 @@ describe(const fs::path& filename)
archive_entry_strmode(entry),
archive_entry_mtime(entry),
archive_entry_size_is_set(entry)
? nonstd::make_optional(archive_entry_size(entry))
: nonstd::nullopt,
? std::make_optional(archive_entry_size(entry))
: std::nullopt,
});
} while (archive_read_next_header(arc, &entry) == ARCHIVE_OK);
@ -269,7 +269,8 @@ extract(const std::string& filename, const extract_cb& cb)
}
}
if (file_count > 0) {
fs::last_write_time(done_path, std::chrono::system_clock::now());
auto now = fs::file_time_type::clock::now();
fs::last_write_time(done_path, now);
log_info("%s: archive has already been extracted!",
done_path.c_str());
return Ok();
@ -399,7 +400,7 @@ void
cleanup_cache()
{
(void) std::async(std::launch::async, []() {
auto now = std::chrono::system_clock::now();
auto now = std::filesystem::file_time_type::clock::now();
auto cache_path = archive_cache_path();
const auto& cfg = injector::get<const config&>();
std::vector<fs::path> to_remove;

@ -39,32 +39,31 @@
#include "base/file_range.hh"
#include "base/result.h"
#include "ghc/filesystem.hpp"
#include <filesystem>
#include "mapbox/variant.hpp"
#include "optional.hpp"
namespace archive_manager {
struct extract_progress {
extract_progress(ghc::filesystem::path path, ssize_t total)
extract_progress(std::filesystem::path path, ssize_t total)
: ep_path(std::move(path)), ep_total_size(total)
{
}
const ghc::filesystem::path ep_path;
const std::filesystem::path ep_path;
const ssize_t ep_total_size;
std::atomic<size_t> ep_out_size{0};
};
using extract_cb
= std::function<extract_progress*(const ghc::filesystem::path&, ssize_t)>;
= std::function<extract_progress*(const std::filesystem::path&, ssize_t)>;
struct archive_info {
struct entry {
ghc::filesystem::path e_name;
std::filesystem::path e_name;
const char* e_mode;
time_t e_mtime;
nonstd::optional<file_ssize_t> e_size;
std::optional<file_ssize_t> e_size;
};
const char* ai_format_name;
std::vector<entry> ai_entries;
@ -74,9 +73,9 @@ struct unknown_file {};
using describe_result = mapbox::util::variant<archive_info, unknown_file>;
Result<describe_result, std::string> describe(
const ghc::filesystem::path& filename);
const std::filesystem::path& filename);
ghc::filesystem::path filename_to_tmp_path(const std::string& filename);
std::filesystem::path filename_to_tmp_path(const std::string& filename);
using walk_result_t = Result<void, std::string>;
@ -91,8 +90,8 @@ using walk_result_t = Result<void, std::string>;
walk_result_t walk_archive_files(
const std::string& filename,
const extract_cb& cb,
const std::function<void(const ghc::filesystem::path&,
const ghc::filesystem::directory_entry&)>&);
const std::function<void(const std::filesystem::path&,
const std::filesystem::directory_entry&)>&);
void cleanup_cache();

@ -52,6 +52,7 @@ add_library(
is_utf8.hh
isc.hh
itertools.hh
itertools.enumerate.hh
keycodes.hh
line_range.hh
lnav.console.hh
@ -69,6 +70,7 @@ add_library(
strnatcmp.h
time_util.hh
types.hh
wcwidth9.h
../third-party/xxHash/xxhash.h
../third-party/xxHash/xxhash.c
@ -76,8 +78,16 @@ add_library(
target_include_directories(base PUBLIC . .. ../third-party
../third-party/date/include
${CURSES_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR}/..)
target_link_libraries(base cppfmt cppscnlib pcrepp ncurses::libcurses pthread lnavdt datepp)
target_link_libraries(base
cppfmt
cppscnlib
pcrepp
${CURSES_LIBRARIES}
pthread
lnavdt
datepp)
add_executable(
test_base

@ -48,6 +48,7 @@ noinst_HEADERS = \
is_utf8.hh \
isc.hh \
itertools.hh \
itertools.enumerate.hh \
keycodes.hh \
line_range.hh \
lnav_log.hh \
@ -68,7 +69,8 @@ noinst_HEADERS = \
string_util.hh \
strnatcmp.h \
time_util.hh \
types.hh
types.hh \
wcwidth9.h
libbase_a_SOURCES = \
ansi_scrubber.cc \

@ -56,7 +56,7 @@ erase_ansi_escapes(string_fragment input)
static thread_local auto md = lnav::pcre2pp::match_data::unitialized();
const auto& regex = ansi_regex();
nonstd::optional<int> move_start;
std::optional<int> move_start;
size_t fill_index = 0;
auto matcher = regex.capture_from(input).into(md);
@ -125,7 +125,7 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
static const auto semi_pred = string_fragment::tag1{';'};
const auto& regex = ansi_regex();
nonstd::optional<std::string> href;
std::optional<std::string> href;
size_t href_start = 0;
string_attrs_t tmp_sa;
size_t cp_dst = std::string::npos;
@ -267,7 +267,7 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
struct line_range lr;
text_attrs attrs;
bool has_attrs = false;
nonstd::optional<role_t> role;
std::optional<role_t> role;
if (md[3]) {
auto osc_id = scn::scan_value<int32_t>(md[3]->to_string_view());
@ -289,7 +289,7 @@ scrub_ansi_string(std::string& str, string_attrs_t* sa)
},
VC_HYPERLINK.value(href.value()));
}
href = nonstd::nullopt;
href = std::nullopt;
}
if (!uri.empty()) {
href = uri.to_string();

@ -37,7 +37,7 @@ attr_line_builder::append_as_hexdump(const string_fragment& sf)
if (byte_off == 8) {
this->append(" ");
}
nonstd::optional<role_t> ro;
std::optional<role_t> ro;
if (ch == '\0') {
ro = role_t::VCR_NULL;
} else if (isspace(ch) || iscntrl(ch)) {

@ -41,7 +41,7 @@ public:
class attr_guard {
public:
explicit attr_guard(attr_line_t& al)
: ag_line(al), ag_start(nonstd::nullopt)
: ag_line(al), ag_start(std::nullopt)
{
}
@ -59,7 +59,7 @@ public:
: ag_line(other.ag_line), ag_start(std::move(other.ag_start)),
ag_attr(std::move(other.ag_attr))
{
other.ag_start = nonstd::nullopt;
other.ag_start = std::nullopt;
}
~attr_guard()
@ -76,7 +76,7 @@ public:
private:
attr_line_t& ag_line;
nonstd::optional<int> ag_start;
std::optional<int> ag_start;
string_attr_pair ag_attr;
};

@ -356,6 +356,19 @@ attr_line_t::insert(size_t index,
return *this;
}
attr_line_t&
attr_line_t::wrap_with(text_wrap_settings* tws)
{
attr_line_t tmp;
tmp.al_string = std::move(this->al_string);
tmp.al_attrs = std::move(this->al_attrs);
this->append(tmp, tws);
return *this;
}
attr_line_t
attr_line_t::subline(size_t start, size_t len) const
{
@ -457,7 +470,7 @@ attr_line_t::apply_hide()
}
attr_line_t&
attr_line_t::rtrim(nonstd::optional<const char*> chars)
attr_line_t::rtrim(std::optional<const char*> chars)
{
auto index = this->al_string.length();
@ -510,7 +523,7 @@ attr_line_t::erase(size_t pos, size_t len)
attr_line_t&
attr_line_t::pad_to(ssize_t size)
{
const auto curr_len = this->utf8_length_or_length();
const auto curr_len = this->column_width();
if (curr_len < size) {
this->append((size - curr_len), ' ');
@ -690,9 +703,8 @@ find_string_attr(const string_attrs_t& sa,
const string_attr_type_base* type,
int start)
{
string_attrs_t::const_iterator iter;
for (iter = sa.begin(); iter != sa.end(); ++iter) {
auto iter = sa.begin();
for (; iter != sa.end(); ++iter) {
if (iter->sa_type == type && iter->sa_range.lr_start >= start) {
break;
}
@ -701,7 +713,7 @@ find_string_attr(const string_attrs_t& sa,
return iter;
}
nonstd::optional<const string_attr*>
std::optional<const string_attr*>
get_string_attr(const string_attrs_t& sa,
const string_attr_type_base* type,
int start)
@ -709,8 +721,8 @@ get_string_attr(const string_attrs_t& sa,
auto iter = find_string_attr(sa, type, start);
if (iter == sa.end()) {
return nonstd::nullopt;
return std::nullopt;
}
return nonstd::make_optional(&(*iter));
return std::make_optional(&(*iter));
}

@ -33,6 +33,7 @@
#define attr_line_hh
#include <new>
#include <optional>
#include <string>
#include <vector>
@ -97,11 +98,11 @@ using string_attrs_t = std::vector<string_attr>;
string_attrs_t::const_iterator find_string_attr(
const string_attrs_t& sa, const string_attr_type_base* type, int start = 0);
nonstd::optional<const string_attr*> get_string_attr(
std::optional<const string_attr*> get_string_attr(
const string_attrs_t& sa, const string_attr_type_base* type, int start = 0);
template<typename T>
inline nonstd::optional<string_attr_wrapper<T>>
inline std::optional<string_attr_wrapper<T>>
get_string_attr(const string_attrs_t& sa,
const string_attr_type<T>& type,
int start = 0)
@ -109,10 +110,10 @@ get_string_attr(const string_attrs_t& sa,
auto iter = find_string_attr(sa, &type, start);
if (iter == sa.end()) {
return nonstd::nullopt;
return std::nullopt;
}
return nonstd::make_optional(string_attr_wrapper<T>(&(*iter)));
return std::make_optional(string_attr_wrapper<T>(&(*iter)));
}
template<typename T>
@ -300,7 +301,7 @@ public:
size_t start_len = this->al_string.length();
this->al_string.append(std::move(value.first));
this->append(std::move(value.first));
line_range lr{(int) start_len, (int) this->al_string.length()};
@ -482,7 +483,7 @@ public:
attr_line_t& erase(size_t pos, size_t len = std::string::npos);
attr_line_t& rtrim(nonstd::optional<const char*> chars = nonstd::nullopt);
attr_line_t& rtrim(std::optional<const char*> chars = std::nullopt);
attr_line_t& erase_utf8_chars(size_t start)
{
@ -579,8 +580,13 @@ public:
size_t nearest_text(size_t x) const;
attr_line_t& wrap_with(text_wrap_settings* tws);
void apply_hide();
attr_line_t move() & { return std::move(*this); }
attr_line_t move() && { return std::move(*this); }
std::string al_string;
string_attrs_t al_attrs;
};

@ -79,12 +79,18 @@ auto_fd::openpt(int flags)
return Ok(auto_fd{rc});
}
auto_fd::auto_fd(int fd) : af_fd(fd)
auto_fd::
auto_fd(int fd)
: af_fd(fd)
{
require(fd >= -1);
}
auto_fd::auto_fd(auto_fd&& af) noexcept : af_fd(af.release()) {}
auto_fd::
auto_fd(auto_fd&& af) noexcept
: af_fd(af.release())
{
}
auto_fd
auto_fd::dup() const
@ -98,11 +104,18 @@ auto_fd::dup() const
return auto_fd{new_fd};
}
auto_fd::~auto_fd()
auto_fd::~
auto_fd()
{
this->reset();
}
void
auto_fd::copy_to(int fd) const
{
dup2(this->get(), fd);
}
void
auto_fd::reset(int fd)
{
@ -184,7 +197,8 @@ auto_pipe::for_child_fd(int child_fd)
return Ok(std::move(retval));
}
auto_pipe::auto_pipe(int child_fd, int child_flags)
auto_pipe::
auto_pipe(int child_fd, int child_flags)
: ap_child_flags(child_flags), ap_child_fd(child_fd)
{
switch (child_fd) {

@ -146,6 +146,8 @@ public:
return retval;
}
void copy_to(int fd) const;
/**
* @return The file descriptor.
*/

@ -223,7 +223,7 @@ public:
this->ab_capacity = 0;
}
auto_buffer& operator=(auto_buffer&) = delete;
auto_buffer& operator=(const auto_buffer&) = delete;
auto_buffer& operator=(auto_buffer&& other) noexcept
{

@ -195,7 +195,6 @@ date_time_scanner::scan(const char* time_dest,
{
const auto sec_diff = tm_out->et_tm.tm_sec - last_tm.tm_sec;
// log_debug("diff %d", sec_diff);
tv_out = this->dts_last_tv;
tv_out.tv_sec += sec_diff;
tm_out->et_tm.tm_wday = last_tm.tm_wday;
@ -231,20 +230,43 @@ date_time_scanner::scan(const char* time_dest,
if (convert_local
&& (this->dts_local_time
|| tm_out->et_flags & ETF_EPOCH_TIME
|| (tm_out->et_flags & ETF_ZONE_SET
|| ((tm_out->et_flags & ETF_ZONE_SET
|| this->dts_default_zone != nullptr)
&& this->dts_zoned_to_local)))
{
time_t gmt = tm_out->to_timeval().tv_sec;
if (!(tm_out->et_flags & ETF_ZONE_SET)
&& !(tm_out->et_flags & ETF_EPOCH_TIME)
&& this->dts_default_zone != nullptr)
{
date::local_seconds stime;
stime += std::chrono::seconds{gmt};
auto ztime
= date::make_zoned(this->dts_default_zone, stime);
gmt = std::chrono::duration_cast<std::chrono::seconds>(
ztime.get_sys_time().time_since_epoch())
.count();
}
this->to_localtime(gmt, *tm_out);
#ifdef HAVE_STRUCT_TM_TM_ZONE
tm_out->et_tm.tm_zone = nullptr;
#endif
tm_out->et_tm.tm_isdst = 0;
}
const auto& last_tm = this->dts_last_tm.et_tm;
if (last_tm.tm_year == tm_out->et_tm.tm_year
&& last_tm.tm_mon == tm_out->et_tm.tm_mon
&& last_tm.tm_mday == tm_out->et_tm.tm_mday
&& last_tm.tm_hour == tm_out->et_tm.tm_hour
&& last_tm.tm_min == tm_out->et_tm.tm_min)
{
const auto sec_diff = tm_out->et_tm.tm_sec - last_tm.tm_sec;
tv_out = tm_out->to_timeval();
secs2wday(tv_out, &tm_out->et_tm);
tv_out = this->dts_last_tv;
tv_out.tv_sec += sec_diff;
tm_out->et_tm.tm_wday = last_tm.tm_wday;
} else {
tv_out = tm_out->to_timeval();
secs2wday(tv_out, &tm_out->et_tm);
}
tv_out.tv_usec = tm_out->et_nsec / 1000;
this->dts_fmt_lock = curr_time_fmt;
this->dts_fmt_len = retval - time_dest;

@ -43,6 +43,18 @@ public:
struct metadata {
bool m_valid_utf{true};
bool m_has_ansi{false};
metadata& operator|=(const metadata& meta)
{
if (!meta.m_valid_utf) {
this->m_valid_utf = false;
}
if (meta.m_has_ansi) {
this->m_has_ansi = true;
}
return *this;
}
};
file_off_t fr_offset{0};

@ -27,9 +27,12 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <fstream>
#include "fs_util.hh"
#include <stdlib.h>
#include <sys/param.h>
#include "config.h"
#include "fmt/format.h"
@ -40,8 +43,38 @@
namespace lnav {
namespace filesystem {
Result<ghc::filesystem::path, std::string>
realpath(const ghc::filesystem::path& path)
std::string
escape_path(const std::filesystem::path& p)
{
auto p_str = p.string();
std::string retval;
for (const auto ch : p_str) {
switch (ch) {
case ' ':
case '$':
case '\\':
case ';':
case '&':
case '<':
case '>':
case '\'':
case '"':
case '*':
case '[':
case ']':
case '?':
retval.push_back('\\');
break;
}
retval.push_back(ch);
}
return retval;
}
Result<std::filesystem::path, std::string>
realpath(const std::filesystem::path& path)
{
char resolved[PATH_MAX];
auto rc = ::realpath(path.c_str(), resolved);
@ -50,11 +83,11 @@ realpath(const ghc::filesystem::path& path)
return Err(std::string(strerror(errno)));
}
return Ok(ghc::filesystem::path(resolved));
return Ok(std::filesystem::path(resolved));
}
Result<auto_fd, std::string>
create_file(const ghc::filesystem::path& path, int flags, mode_t mode)
create_file(const std::filesystem::path& path, int flags, mode_t mode)
{
auto fd = openp(path, flags | O_CREAT, mode);
@ -68,7 +101,7 @@ create_file(const ghc::filesystem::path& path, int flags, mode_t mode)
}
Result<auto_fd, std::string>
open_file(const ghc::filesystem::path& path, int flags)
open_file(const std::filesystem::path& path, int flags)
{
auto fd = openp(path, flags);
@ -81,8 +114,8 @@ open_file(const ghc::filesystem::path& path, int flags)
return Ok(auto_fd(fd));
}
Result<std::pair<ghc::filesystem::path, auto_fd>, std::string>
open_temp_file(const ghc::filesystem::path& pattern)
Result<std::pair<std::filesystem::path, auto_fd>, std::string>
open_temp_file(const std::filesystem::path& pattern)
{
auto pattern_str = pattern.string();
char pattern_copy[pattern_str.size() + 1];
@ -96,14 +129,14 @@ open_temp_file(const ghc::filesystem::path& pattern)
strerror(errno)));
}
return Ok(std::make_pair(ghc::filesystem::path(pattern_copy), auto_fd(fd)));
return Ok(std::make_pair(std::filesystem::path(pattern_copy), auto_fd(fd)));
}
Result<std::string, std::string>
read_file(const ghc::filesystem::path& path)
read_file(const std::filesystem::path& path)
{
try {
ghc::filesystem::ifstream file_stream(path);
std::ifstream file_stream(path);
if (!file_stream) {
return Err(std::string(strerror(errno)));
@ -119,7 +152,7 @@ read_file(const ghc::filesystem::path& path)
}
Result<write_file_result, std::string>
write_file(const ghc::filesystem::path& path,
write_file(const std::filesystem::path& path,
const string_fragment& content,
std::set<write_file_options> options)
{
@ -145,11 +178,11 @@ write_file(const ghc::filesystem::path& path,
std::error_code ec;
if (options.count(write_file_options::backup_existing)) {
if (ghc::filesystem::exists(path, ec)) {
if (std::filesystem::exists(path, ec)) {
auto backup_path = path;
backup_path += ".bak";
ghc::filesystem::rename(path, backup_path, ec);
std::filesystem::rename(path, backup_path, ec);
if (ec) {
return Err(
fmt::format(FMT_STRING("unable to backup file {}: {}"),
@ -161,7 +194,7 @@ write_file(const ghc::filesystem::path& path,
}
}
ghc::filesystem::rename(tmp_pair.first, path, ec);
std::filesystem::rename(tmp_pair.first, path, ec);
if (ec) {
return Err(
fmt::format(FMT_STRING("unable to move temporary file {}: {}"),
@ -173,7 +206,7 @@ write_file(const ghc::filesystem::path& path,
}
std::string
build_path(const std::vector<ghc::filesystem::path>& paths)
build_path(const std::vector<std::filesystem::path>& paths)
{
return paths
| lnav::itertools::map([](const auto& path) { return path.string(); })
@ -190,7 +223,7 @@ build_path(const std::vector<ghc::filesystem::path>& paths)
}
Result<struct stat, std::string>
stat_file(const ghc::filesystem::path& path)
stat_file(const std::filesystem::path& path)
{
struct stat retval;
@ -203,7 +236,8 @@ stat_file(const ghc::filesystem::path& path)
strerror(errno)));
}
file_lock::file_lock(const ghc::filesystem::path& archive_path)
file_lock::
file_lock(const std::filesystem::path& archive_path)
{
auto lock_path = archive_path;
@ -222,7 +256,7 @@ file_lock::file_lock(const ghc::filesystem::path& archive_path)
namespace fmt {
auto
formatter<ghc::filesystem::path>::format(const ghc::filesystem::path& p,
formatter<std::filesystem::path>::format(const std::filesystem::path& p,
format_context& ctx)
-> decltype(ctx.out()) const
{

@ -30,12 +30,17 @@
#ifndef lnav_fs_util_hh
#define lnav_fs_util_hh
#include <filesystem>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <sys/stat.h>
#include <unistd.h>
#include "auto_fd.hh"
#include "ghc/filesystem.hpp"
#include "intern_string.hh"
#include "result.h"
@ -50,55 +55,57 @@ is_glob(const std::string& fn)
|| fn.find('[') != std::string::npos);
}
std::string escape_path(const std::filesystem::path& p);
inline int
statp(const ghc::filesystem::path& path, struct stat* buf)
statp(const std::filesystem::path& path, struct stat* buf)
{
return stat(path.c_str(), buf);
}
inline int
openp(const ghc::filesystem::path& path, int flags)
openp(const std::filesystem::path& path, int flags)
{
return open(path.c_str(), flags);
}
inline int
openp(const ghc::filesystem::path& path, int flags, mode_t mode)
openp(const std::filesystem::path& path, int flags, mode_t mode)
{
return open(path.c_str(), flags, mode);
}
Result<ghc::filesystem::path, std::string> realpath(
const ghc::filesystem::path& path);
Result<std::filesystem::path, std::string> realpath(
const std::filesystem::path& path);
Result<auto_fd, std::string> create_file(const ghc::filesystem::path& path,
Result<auto_fd, std::string> create_file(const std::filesystem::path& path,
int flags,
mode_t mode);
Result<auto_fd, std::string> open_file(const ghc::filesystem::path& path,
Result<auto_fd, std::string> open_file(const std::filesystem::path& path,
int flags);
Result<struct stat, std::string> stat_file(const ghc::filesystem::path& path);
Result<struct stat, std::string> stat_file(const std::filesystem::path& path);
Result<std::pair<ghc::filesystem::path, auto_fd>, std::string> open_temp_file(
const ghc::filesystem::path& pattern);
Result<std::pair<std::filesystem::path, auto_fd>, std::string> open_temp_file(
const std::filesystem::path& pattern);
Result<std::string, std::string> read_file(const ghc::filesystem::path& path);
Result<std::string, std::string> read_file(const std::filesystem::path& path);
enum class write_file_options {
backup_existing,
};
struct write_file_result {
nonstd::optional<ghc::filesystem::path> wfr_backup_path;
std::optional<std::filesystem::path> wfr_backup_path;
};
Result<write_file_result, std::string> write_file(
const ghc::filesystem::path& path,
const std::filesystem::path& path,
const string_fragment& content,
std::set<write_file_options> options = {});
std::string build_path(const std::vector<ghc::filesystem::path>& paths);
std::string build_path(const std::vector<std::filesystem::path>& paths);
class file_lock {
public:
@ -133,7 +140,7 @@ public:
void unlock() const { lockf(this->lh_fd, F_ULOCK, 0); }
explicit file_lock(const ghc::filesystem::path& archive_path);
explicit file_lock(const std::filesystem::path& archive_path);
auto_fd lh_fd;
};
@ -143,8 +150,8 @@ public:
namespace fmt {
template<>
struct formatter<ghc::filesystem::path> : formatter<string_view> {
auto format(const ghc::filesystem::path& p, format_context& ctx)
struct formatter<std::filesystem::path> : formatter<string_view> {
auto format(const std::filesystem::path& p, format_context& ctx)
-> decltype(ctx.out()) const;
};
} // namespace fmt

@ -27,6 +27,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <filesystem>
#include <iostream>
#include "base/fs_util.hh"

@ -74,7 +74,7 @@ public:
explicit future_queue(
std::function<processor_result_t(std::future<T>&)> processor,
size_t max_queue_size = 8)
: fq_processor(processor), fq_max_queue_size(max_queue_size)
: fq_processor(std::move(processor)), fq_max_queue_size(max_queue_size)
{
}

@ -77,7 +77,7 @@ file_size(file_ssize_t value, alignment align)
}
const std::string&
sparkline(double value, nonstd::optional<double> upper_opt)
sparkline(double value, std::optional<double> upper_opt)
{
static const std::string ZERO = " ";
static const std::string BARS[] = {

@ -51,7 +51,7 @@ enum class alignment {
*/
std::string file_size(file_ssize_t value, alignment align);
const std::string& sparkline(double value, nonstd::optional<double> upper);
const std::string& sparkline(double value, std::optional<double> upper);
} // namespace humanize

@ -30,13 +30,14 @@
#include "humanize.network.hh"
#include "config.h"
#include "itertools.hh"
#include "pcrepp/pcre2pp.hh"
namespace humanize {
namespace network {
namespace path {
nonstd::optional<::network::path>
std::optional<::network::path>
from_str(string_fragment sf)
{
static const auto REMOTE_PATTERN = lnav::pcre2pp::code::from_const(
@ -52,11 +53,11 @@ from_str(string_fragment sf)
.ignore_error();
if (!match_res) {
return nonstd::nullopt;
return std::nullopt;
}
const auto username = REMOTE_MATCH_DATA["username"].map(
[](auto sf) { return sf.to_string(); });
const auto username = REMOTE_MATCH_DATA["username"]
| lnav::itertools::map([](auto sf) { return sf.to_string(); });
const auto ipv6 = REMOTE_MATCH_DATA["ipv6"];
const auto hostname = REMOTE_MATCH_DATA["hostname"];
const auto locality_hostname = ipv6 ? ipv6.value() : hostname.value();
@ -66,7 +67,7 @@ from_str(string_fragment sf)
path = string_fragment::from_const(".");
}
return ::network::path{
{username, locality_hostname.to_string(), nonstd::nullopt},
{username, locality_hostname.to_string(), std::nullopt},
path.to_string(),
};
}

@ -35,7 +35,6 @@
#include "fmt/format.h"
#include "intern_string.hh"
#include "network.tcp.hh"
#include "optional.hpp"
namespace fmt {
@ -100,7 +99,7 @@ namespace humanize {
namespace network {
namespace path {
nonstd::optional<::network::path> from_str(string_fragment sf);
std::optional<::network::path> from_str(string_fragment sf);
} // namespace path
} // namespace network

@ -57,8 +57,9 @@ point::as_time_ago() const
current_time.tv_sec = convert_log_time_to_local(current_time.tv_sec);
}
auto delta
= std::chrono::seconds(current_time.tv_sec - this->p_past_point.tv_sec);
auto curr_secs = std::chrono::seconds(current_time.tv_sec);
auto past_secs = std::chrono::seconds(this->p_past_point.tv_sec);
auto delta = curr_secs - past_secs;
if (delta < 0s) {
return "in the future";
}
@ -113,8 +114,8 @@ point::as_precise_time_ago() const
return fmt::format(FMT_STRING("{:2} seconds ago"), diff.tv_sec);
}
time_t seconds = diff.tv_sec % 60;
time_t minutes = diff.tv_sec / 60;
lnav::time64_t seconds = diff.tv_sec % 60;
lnav::time64_t minutes = diff.tv_sec / 60;
return fmt::format(FMT_STRING("{:2} minute{} and {:2} second{} ago"),
minutes,

@ -30,12 +30,11 @@
#ifndef lnav_humanize_time_hh
#define lnav_humanize_time_hh
#include <optional>
#include <string>
#include <sys/time.h>
#include "optional.hpp"
namespace humanize {
namespace time {
@ -66,7 +65,7 @@ private:
}
struct timeval p_past_point;
nonstd::optional<struct timeval> p_recent_point;
std::optional<struct timeval> p_recent_point;
bool p_convert_to_local{false};
};

@ -32,6 +32,7 @@
#ifndef lnav_injector_hh
#define lnav_injector_hh
#include <functional>
#include <map>
#include <memory>
#include <type_traits>

@ -36,6 +36,7 @@
#include <string.h>
#include "config.h"
#include "fmt/ostream.h"
#include "pcrepp/pcre2pp.hh"
#include "ww898/cp_utf8.hpp"
#include "xxHash/xxhash.h"
@ -179,11 +180,35 @@ string_fragment::trim() const
return this->trim(" \t\r\n");
}
nonstd::optional<string_fragment>
string_fragment
string_fragment::rtrim(const char* tokens) const
{
string_fragment retval = *this;
while (retval.sf_begin < retval.sf_end) {
bool found = false;
for (int lpc = 0; tokens[lpc] != '\0'; lpc++) {
if (retval.sf_string[retval.sf_end - 1] == tokens[lpc]) {
found = true;
break;
}
}
if (!found) {
break;
}
retval.sf_end -= 1;
}
return retval;
}
std::optional<string_fragment>
string_fragment::consume_n(int amount) const
{
if (amount > this->length()) {
return nonstd::nullopt;
return std::nullopt;
}
return string_fragment{
@ -197,7 +222,7 @@ string_fragment::split_result
string_fragment::split_n(int amount) const
{
if (amount > this->length()) {
return nonstd::nullopt;
return std::nullopt;
}
return std::make_pair(
@ -391,8 +416,8 @@ string_fragment
string_fragment::sub_cell_range(int cell_start, int cell_end) const
{
int byte_index = this->sf_begin;
nonstd::optional<int> byte_start;
nonstd::optional<int> byte_end;
std::optional<int> byte_start;
std::optional<int> byte_end;
int cell_index = 0;
while (byte_index < this->sf_end) {
@ -416,9 +441,14 @@ string_fragment::sub_cell_range(int cell_start, int cell_end) const
cell_index += 1;
} while (cell_index % 8);
break;
default:
cell_index += wcwidth(read_res.unwrap());
default: {
auto wcw_res = wcwidth(read_res.unwrap());
if (wcw_res < 0) {
wcw_res = 1;
}
cell_index += wcw_res;
break;
}
}
}
}
@ -456,9 +486,14 @@ string_fragment::column_width() const
retval += 1;
} while (retval % 8);
break;
default:
retval += wcwidth(read_res.unwrap());
default: {
auto wcw_res = wcwidth(read_res.unwrap());
if (wcw_res < 0) {
wcw_res = 1;
}
retval += wcw_res;
break;
}
}
}
}

@ -32,6 +32,7 @@
#ifndef intern_string_hh
#define intern_string_hh
#include <optional>
#include <ostream>
#include <vector>
@ -40,11 +41,12 @@
#include <sys/types.h>
#include "fmt/format.h"
#include "optional.hpp"
#include "result.h"
#include "scn/util/string_view.h"
#include "strnatcmp.h"
unsigned long hash_str(const char* str, size_t len);
struct string_fragment {
using iterator = const char*;
@ -279,6 +281,12 @@ struct string_fragment {
string_fragment sub_range(int begin, int end) const
{
if (this->sf_begin + begin > this->sf_end) {
begin = this->sf_end - this->sf_begin;
}
if (this->sf_begin + end > this->sf_end) {
end = this->sf_end - this->sf_begin;
}
return string_fragment{
this->sf_string, this->sf_begin + begin, this->sf_begin + end};
}
@ -302,7 +310,7 @@ struct string_fragment {
return retval;
}
nonstd::optional<size_t> find(char ch) const
std::optional<int> find(char ch) const
{
for (int lpc = this->sf_begin; lpc < this->sf_end; lpc++) {
if (this->sf_string[lpc] == ch) {
@ -310,7 +318,7 @@ struct string_fragment {
}
}
return nonstd::nullopt;
return std::nullopt;
}
template<typename P>
@ -374,21 +382,21 @@ struct string_fragment {
start - left.sf_begin, predicate, count);
}
nonstd::optional<std::pair<uint32_t, string_fragment>> consume_codepoint()
std::optional<std::pair<uint32_t, string_fragment>> consume_codepoint()
const
{
auto cp = this->front_codepoint();
auto index_res = this->codepoint_to_byte_index(1);
if (index_res.isErr()) {
return nonstd::nullopt;
return std::nullopt;
}
return std::make_pair(cp, this->substr(index_res.unwrap()));
}
template<typename P>
nonstd::optional<string_fragment> consume(P predicate) const
std::optional<string_fragment> consume(P predicate) const
{
int consumed = 0;
while (consumed < this->length()) {
@ -400,7 +408,7 @@ struct string_fragment {
}
if (consumed == 0) {
return nonstd::nullopt;
return std::nullopt;
}
return string_fragment{
@ -410,7 +418,7 @@ struct string_fragment {
};
}
nonstd::optional<string_fragment> consume_n(int amount) const;
std::optional<string_fragment> consume_n(int amount) const;
template<typename P>
string_fragment skip(P predicate) const
@ -428,7 +436,7 @@ struct string_fragment {
}
using split_result
= nonstd::optional<std::pair<string_fragment, string_fragment>>;
= std::optional<std::pair<string_fragment, string_fragment>>;
template<typename P>
split_result split_while(P&& predicate) const
@ -443,7 +451,7 @@ struct string_fragment {
}
if (consumed == 0) {
return nonstd::nullopt;
return std::nullopt;
}
return std::make_pair(
@ -500,7 +508,7 @@ struct string_fragment {
}
if (consumed == this->length()) {
return nonstd::nullopt;
return std::nullopt;
}
return std::make_pair(
@ -573,6 +581,7 @@ struct string_fragment {
}
string_fragment trim(const char* tokens) const;
string_fragment rtrim(const char* tokens) const;
string_fragment trim() const;
string_fragment prepend(const char* str, int amount) const
@ -637,6 +646,11 @@ struct string_fragment {
std::string to_string_with_case_style(case_style style) const;
unsigned long hash() const
{
return hash_str(this->data(), this->length());
}
const char* sf_string;
int sf_begin;
int sf_end;
@ -805,8 +819,6 @@ private:
const intern_string* ist_interned_string;
};
unsigned long hash_str(const char* str, size_t len);
namespace fmt {
template<>
struct formatter<string_fragment> : formatter<string_view> {

@ -184,3 +184,17 @@ World!)");
CHECK(all_sf2 == "Hello,\nWorld!");
}
}
TEST_CASE("string_fragment::column_width")
{
{
const auto sf = string_fragment::from_const("Key(s)\n");
CHECK(7 == sf.column_width());
}
{
const auto sf = string_fragment::from_const("\u26a0");
CHECK(1 == sf.column_width());
}
}

@ -60,7 +60,7 @@
error.
*/
utf8_scan_result
is_utf8(string_fragment str, nonstd::optional<unsigned char> terminator)
is_utf8(string_fragment str, std::optional<unsigned char> terminator)
{
const auto* ustr = str.udata();
utf8_scan_result retval;

@ -32,13 +32,12 @@
#include <sys/types.h>
#include "intern_string.hh"
#include "optional.hpp"
struct utf8_scan_result {
const char* usr_message{nullptr};
size_t usr_faulty_bytes{0};
string_fragment usr_valid_frag{string_fragment::invalid()};
nonstd::optional<string_fragment> usr_remaining;
std::optional<string_fragment> usr_remaining;
bool usr_has_ansi{false};
size_t usr_column_width_guess{0};
@ -54,7 +53,7 @@ struct utf8_scan_result {
};
utf8_scan_result is_utf8(string_fragment frag,
nonstd::optional<unsigned char> terminator
= nonstd::nullopt);
std::optional<unsigned char> terminator
= std::nullopt);
#endif /* _IS_UTF8_H */

@ -53,7 +53,20 @@ service_base::run()
mstime_t current_time = getmstime();
auto timeout = this->compute_timeout(current_time);
this->s_port.process_for(timeout);
try {
this->s_port.process_for(timeout);
} catch (const std::exception& e) {
log_error("%s: message failed with -- %s",
this->s_name.c_str(),
e.what());
this->s_looping = false;
continue;
} catch (...) {
log_error("%s: message failed with non-standard exception",
this->s_name.c_str());
this->s_looping = false;
continue;
}
this->s_children.cleanup_children();
try {

@ -166,7 +166,7 @@ protected:
template<typename T>
class service : public service_base {
public:
explicit service(std::string sub_name = "")
explicit service(const std::string& sub_name = "")
: service_base(std::string(__PRETTY_FUNCTION__) + " " + sub_name)
{
}
@ -174,9 +174,11 @@ public:
template<typename F>
void send(F msg)
{
this->s_port.send({[lifetime = this->shared_from_this(), this, msg]() {
msg(*(static_cast<T*>(this)));
}});
this->s_port.send({
[lifetime = this->shared_from_this(),
this,
msg2 = std::move(msg)]() { msg2(*(static_cast<T*>(this))); },
});
}
template<typename F, class Rep, class Period>
@ -185,12 +187,16 @@ public:
{
msg_port reply_port;
this->s_port.send(
{[lifetime = this->shared_from_this(), this, &reply_port, msg]() {
msg(*(static_cast<T*>(this)));
this->s_port.send({
[lifetime = this->shared_from_this(),
this,
&reply_port,
msg2 = std::move(msg)]() {
msg2(*(static_cast<T*>(this)));
reply_port.send(empty_msg());
}});
reply_port.template process_for(rel_time);
},
});
reply_port.process_for(rel_time);
}
};
@ -200,7 +206,7 @@ struct to {
{
auto& service = injector::get<T&, Service>();
service.send(cb);
service.send(std::move(cb));
}
template<class Rep, class Period>
@ -209,14 +215,14 @@ struct to {
{
auto& service = injector::get<T&, Service>();
service.send_and_wait(cb, rel_time);
service.send_and_wait(std::move(cb), rel_time);
}
void send_and_wait(std::function<void(T)> cb)
{
using namespace std::literals::chrono_literals;
this->send_and_wait(cb, 48h);
this->send_and_wait(std::move(cb), 48h);
}
};

@ -0,0 +1,88 @@
/**
* Copyright (c) 2024, Timothy Stack
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Timothy Stack nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef lnav_itertools_enumerate_hh
#define lnav_itertools_enumerate_hh
#include <tuple>
namespace lnav::itertools {
namespace details {
template<typename Iterator, typename CounterType = size_t>
class EnumerateIterator {
Iterator iter;
CounterType index;
public:
EnumerateIterator(Iterator iter, CounterType index)
: iter(std::move(iter)), index(index)
{
}
EnumerateIterator& operator++()
{
++iter;
++index;
return *this;
}
auto operator*() { return std::forward_as_tuple(index, *iter); }
bool operator!=(EnumerateIterator const& rhs) const
{
return iter != rhs.iter;
}
};
} // namespace details
template<typename Iterable, typename CounterType = size_t>
class enumerate {
public:
enumerate(Iterable& iterable, CounterType start = 0)
: iterable(iterable), index(start)
{
}
auto begin()
{
return details::EnumerateIterator{std::begin(iterable), index};
}
auto end() { return details::EnumerateIterator{std::end(iterable), index}; }
private:
Iterable& iterable;
CounterType index;
};
} // namespace lnav::itertools
#endif

@ -34,12 +34,12 @@
#include <deque>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <type_traits>
#include <vector>
#include "func_util.hh"
#include "optional.hpp"
namespace lnav {
namespace itertools {
@ -122,7 +122,7 @@ struct append {
};
struct nth {
nonstd::optional<size_t> a_index;
std::optional<size_t> a_index;
};
struct skip {
@ -190,7 +190,7 @@ second()
}
inline details::nth
nth(nonstd::optional<size_t> index)
nth(std::optional<size_t> index)
{
return details::nth{
index,
@ -340,7 +340,7 @@ sum()
} // namespace lnav
template<typename C, typename P>
nonstd::optional<std::conditional_t<
std::optional<std::conditional_t<
std::is_const<typename std::remove_reference_t<C>>::value,
typename std::remove_reference_t<C>::const_iterator,
typename std::remove_reference_t<C>::iterator>>
@ -348,44 +348,44 @@ operator|(C&& in, const lnav::itertools::details::find_if<P>& finder)
{
for (auto iter = in.begin(); iter != in.end(); ++iter) {
if (lnav::func::invoke(finder.fi_predicate, *iter)) {
return nonstd::make_optional(iter);
return std::make_optional(iter);
}
}
return nonstd::nullopt;
return std::nullopt;
}
template<typename C, typename T>
nonstd::optional<size_t>
std::optional<size_t>
operator|(const C& in, const lnav::itertools::details::find<T>& finder)
{
size_t retval = 0;
for (const auto& elem : in) {
if (elem == finder.f_value) {
return nonstd::make_optional(retval);
return std::make_optional(retval);
}
retval += 1;
}
return nonstd::nullopt;
return std::nullopt;
}
template<typename C>
nonstd::optional<typename C::const_iterator>
std::optional<typename C::const_iterator>
operator|(const C& in, const lnav::itertools::details::nth indexer)
{
if (!indexer.a_index.has_value()) {
return nonstd::nullopt;
return std::nullopt;
}
if (indexer.a_index.value() < in.size()) {
auto iter = in.begin();
std::advance(iter, indexer.a_index.value());
return nonstd::make_optional(iter);
return std::make_optional(iter);
}
return nonstd::nullopt;
return std::nullopt;
}
template<typename C>
@ -402,10 +402,10 @@ operator|(const C& in, const lnav::itertools::details::first indexer)
}
template<typename C>
nonstd::optional<typename C::value_type>
std::optional<typename C::value_type>
operator|(const C& in, const lnav::itertools::details::max_value maxer)
{
nonstd::optional<typename C::value_type> retval;
std::optional<typename C::value_type> retval;
for (const auto& elem : in) {
if (!retval) {
@ -572,13 +572,13 @@ template<typename T,
typename F,
std::enable_if_t<lnav::func::is_invocable<F, T>::value, int> = 0>
auto
operator|(nonstd::optional<T> in,
operator|(std::optional<T> in,
const lnav::itertools::details::flat_mapper<F>& mapper) ->
typename std::remove_const_t<typename std::remove_reference_t<
decltype(lnav::func::invoke(mapper.fm_func, in.value()))>>
{
if (!in) {
return nonstd::nullopt;
return std::nullopt;
}
return lnav::func::invoke(mapper.fm_func, in.value());
@ -588,7 +588,7 @@ template<typename T,
typename F,
std::enable_if_t<lnav::func::is_invocable<F, T>::value, int> = 0>
void
operator|(nonstd::optional<T> in,
operator|(std::optional<T> in,
const lnav::itertools::details::for_eacher<F>& eacher)
{
if (!in) {
@ -602,7 +602,7 @@ template<typename T,
typename F,
std::enable_if_t<lnav::func::is_invocable<F, T>::value, int> = 0>
void
operator|(std::vector<std::shared_ptr<T>>& in,
operator|(const std::vector<std::shared_ptr<T>>& in,
const lnav::itertools::details::for_eacher<F>& eacher)
{
for (auto& elem : in) {
@ -610,21 +610,33 @@ operator|(std::vector<std::shared_ptr<T>>& in,
}
}
template<typename T,
typename F,
std::enable_if_t<lnav::func::is_invocable<F, T>::value, int> = 0>
void
operator|(const std::vector<T>& in,
const lnav::itertools::details::for_eacher<F>& eacher)
{
for (auto& elem : in) {
lnav::func::invoke(eacher.fe_func, elem);
}
}
template<typename T,
typename F,
std::enable_if_t<lnav::func::is_invocable<F, T>::value, int> = 0>
auto
operator|(nonstd::optional<T> in,
operator|(std::optional<T> in,
const lnav::itertools::details::mapper<F>& mapper)
-> nonstd::optional<
-> std::optional<
typename std::remove_const_t<typename std::remove_reference_t<
decltype(lnav::func::invoke(mapper.m_func, in.value()))>>>
{
if (!in) {
return nonstd::nullopt;
return std::nullopt;
}
return nonstd::make_optional(lnav::func::invoke(mapper.m_func, in.value()));
return std::make_optional(lnav::func::invoke(mapper.m_func, in.value()));
}
template<typename T, typename F>
@ -813,38 +825,38 @@ template<typename T,
typename F,
std::enable_if_t<!lnav::func::is_invocable<F, T>::value, int> = 0>
auto
operator|(nonstd::optional<T> in,
operator|(std::optional<T> in,
const lnav::itertools::details::mapper<F>& mapper)
-> nonstd::optional<typename std::remove_reference_t<
-> std::optional<typename std::remove_reference_t<
typename std::remove_const_t<decltype(((in.value()).*mapper.m_func))>>>
{
if (!in) {
return nonstd::nullopt;
return std::nullopt;
}
return nonstd::make_optional((in.value()).*mapper.m_func);
return std::make_optional((in.value()).*mapper.m_func);
}
template<typename T,
typename F,
std::enable_if_t<!lnav::func::is_invocable<F, T>::value, int> = 0>
auto
operator|(nonstd::optional<T> in,
operator|(std::optional<T> in,
const lnav::itertools::details::mapper<F>& mapper)
-> nonstd::optional<
-> std::optional<
typename std::remove_const_t<typename std::remove_reference_t<
decltype(((*in.value()).*mapper.m_func))>>>
{
if (!in) {
return nonstd::nullopt;
return std::nullopt;
}
return nonstd::make_optional((*in.value()).*mapper.m_func);
return std::make_optional((*in.value()).*mapper.m_func);
}
template<typename T>
T
operator|(nonstd::optional<T> in,
operator|(std::optional<T> in,
const lnav::itertools::details::unwrap_or<T>& unwrapper)
{
return in.value_or(unwrapper.uo_value);

@ -51,13 +51,13 @@ snippet::from_content_with_offset(intern_string_t src,
size_t offset,
const std::string& errmsg)
{
auto content_sf = string_fragment::from_str(content.get_string());
auto line_with_error = content_sf.find_boundaries_around(
const auto content_sf = string_fragment::from_str(content.get_string());
const auto line_with_error = content_sf.find_boundaries_around(
offset, string_fragment::tag1{'\n'});
auto line_with_context = content_sf.find_boundaries_around(
const auto line_with_context = content_sf.find_boundaries_around(
offset, string_fragment::tag1{'\n'}, 3);
auto line_number = content_sf.sub_range(0, offset).count('\n');
auto erroff_in_line = offset - line_with_error.sf_begin;
const auto line_number = content_sf.sub_range(0, offset).count('\n');
const auto erroff_in_line = offset - line_with_error.sf_begin;
attr_line_t pointer;
@ -252,7 +252,7 @@ user_message::to_attr_line(std::set<render_flags> flags) const
return retval;
}
static nonstd::optional<fmt::terminal_color>
static std::optional<fmt::terminal_color>
curses_color_to_terminal_color(int curses_color)
{
switch (curses_color) {
@ -273,7 +273,7 @@ curses_color_to_terminal_color(int curses_color)
case COLOR_RED:
return fmt::terminal_color::red;
default:
return nonstd::nullopt;
return std::nullopt;
}
}
@ -326,7 +326,7 @@ println(FILE* file, const attr_line_t& al)
}
}
nonstd::optional<size_t> last_point;
std::optional<size_t> last_point;
for (const auto& point : points) {
if (!last_point) {
last_point = point;
@ -337,7 +337,7 @@ println(FILE* file, const attr_line_t& al)
auto line_style = fmt::text_style{};
auto fg_style = fmt::text_style{};
auto start = last_point.value();
nonstd::optional<std::string> href;
std::optional<std::string> href;
for (const auto& attr : al.get_attrs()) {
if (!attr.sa_range.contains(start)
@ -409,7 +409,9 @@ println(FILE* file, const attr_line_t& al)
default:
break;
}
} else if (attr.sa_type == &VC_ROLE) {
} else if (attr.sa_type == &VC_ROLE
|| attr.sa_type == &VC_ROLE_FG)
{
auto saw = string_attr_wrapper<role_t>(&attr);
auto role = saw.get();
@ -441,6 +443,9 @@ println(FILE* file, const attr_line_t& al)
line_style |= fmt::emphasis::bold
| fmt::fg(fmt::terminal_color::green);
break;
case role_t::VCR_FOOTNOTE_BORDER:
line_style |= fmt::fg(fmt::terminal_color::blue);
break;
case role_t::VCR_INFO:
case role_t::VCR_STATUS:
line_style |= fmt::emphasis::bold

@ -94,6 +94,13 @@ struct user_message {
static user_message ok(const attr_line_t& al);
user_message() = default;
user_message(user_message&&) = default;
user_message(const user_message&) = default;
user_message& operator=(user_message&&) = default;
user_message& operator=(const user_message&) = default;
user_message& with_reason(const attr_line_t& al)
{
this->um_reason = al;
@ -186,6 +193,9 @@ struct user_message {
attr_line_t to_attr_line(std::set<render_flags> flags
= {render_flags::prefix}) const;
user_message move() & { return std::move(*this); }
user_message move() && { return std::move(*this); }
level um_level{level::ok};
attr_line_t um_message;
std::vector<snippet> um_snippets;

@ -50,15 +50,11 @@ compress(const void* input, size_t len)
{
auto retval = auto_buffer::alloc(len + 4096);
z_stream zs;
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
z_stream zs = {};
zs.avail_in = (uInt) len;
zs.next_in = (Bytef*) input;
zs.avail_out = (uInt) retval.capacity();
zs.next_out = (Bytef*) retval.in();
zs.total_out = 0;
auto rc = deflateInit2(
&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY);
@ -83,16 +79,11 @@ Result<auto_buffer, std::string>
uncompress(const std::string& src, const void* buffer, size_t size)
{
auto uncomp = auto_buffer::alloc(size * 2);
z_stream strm;
z_stream strm = {};
int err;
strm.next_in = (Bytef*) buffer;
strm.msg = Z_NULL;
strm.avail_in = size;
strm.total_in = 0;
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if ((err = inflateInit2(&strm, (16 + MAX_WBITS))) != Z_OK) {
return Err(fmt::format(FMT_STRING("invalid gzip data: {} -- {}"),

@ -76,6 +76,8 @@
#elif defined HAVE_NCURSES_H
# include <ncurses.h>
# include <termcap.h>
#elif defined HAVE_CURSESW_H
# include <cursesw.h>
#elif defined HAVE_CURSES_H
# include <curses.h>
# include <termcap.h>
@ -83,30 +85,19 @@
# error "SysV or X/Open-compatible Curses header file required"
#endif
#include "ansi_scrubber.hh"
#include "auto_mem.hh"
#include "enum_util.hh"
#include "lnav_log.hh"
#include "opt_util.hh"
static const size_t BUFFER_SIZE = 256 * 1024;
static const size_t MAX_LOG_LINE_SIZE = 2 * 1024;
static const char* CRASH_MSG
= "\n"
"\n"
"==== GURU MEDITATION ====\n"
"Unfortunately, lnav has crashed, sorry for the inconvenience.\n"
"\n"
"You can help improve lnav by sending the following file "
"to " PACKAGE_BUGREPORT
" :\n"
" %s\n"
"=========================\n";
nonstd::optional<FILE*> lnav_log_file;
static constexpr size_t BUFFER_SIZE = 256 * 1024;
static constexpr size_t MAX_LOG_LINE_SIZE = 2 * 1024;
std::optional<FILE*> lnav_log_file;
lnav_log_level_t lnav_log_level = lnav_log_level_t::DEBUG;
const char* lnav_log_crash_dir;
nonstd::optional<const struct termios*> lnav_log_orig_termios;
std::optional<const struct termios*> lnav_log_orig_termios;
// NOTE: This mutex is leaked so that it is not destroyed during exit.
// Otherwise, any attempts to log will fail.
static std::mutex*
@ -326,20 +317,25 @@ log_msg(lnav_log_level_t level,
gettimeofday(&curr_time, nullptr);
localtime_r(&curr_time.tv_sec, &localtm);
auto line = log_alloc();
prefix_size = snprintf(line,
MAX_LOG_LINE_SIZE,
"%4d-%02d-%02dT%02d:%02d:%02d.%03d %s t%u %s:%d ",
localtm.tm_year + 1900,
localtm.tm_mon + 1,
localtm.tm_mday,
localtm.tm_hour,
localtm.tm_min,
localtm.tm_sec,
(int) (curr_time.tv_usec / 1000),
LEVEL_NAMES[lnav::enums::to_underlying(level)],
current_thid.t_id,
src_file,
line_number);
auto gmtoff = std::abs(localtm.tm_gmtoff) / 60;
prefix_size
= snprintf(line,
MAX_LOG_LINE_SIZE,
"%4d-%02d-%02dT%02d:%02d:%02d.%03d%c%02d:%02d %s t%u %s:%d ",
localtm.tm_year + 1900,
localtm.tm_mon + 1,
localtm.tm_mday,
localtm.tm_hour,
localtm.tm_min,
localtm.tm_sec,
(int) (curr_time.tv_usec / 1000),
localtm.tm_gmtoff < 0 ? '-' : '+',
(int) gmtoff / 60,
(int) gmtoff % 60,
LEVEL_NAMES[lnav::enums::to_underlying(level)],
current_thid.t_id,
src_file,
line_number);
#if 0
if (!thread_log_prefix.empty()) {
prefix_size += snprintf(
@ -539,7 +535,29 @@ sigabrt(int sig, siginfo_t* info, void* ctx)
tcsetattr(STDOUT_FILENO, TCSAFLUSH, termios);
dup2(STDOUT_FILENO, STDERR_FILENO);
};
fprintf(stderr, CRASH_MSG, crash_path);
fmt::print(R"(
{red_start}==== GURU MEDITATION ===={norm}
Unfortunately, lnav has crashed, sorry for the inconvenience.
You can help improve lnav by executing the following command
to upload the crash logs to https://crash.lnav.org:
{green_start}${norm} {bold_start}lnav -m crash upload{norm}
Or, you can send the following file to {PACKAGE_BUGREPORT}:
{crash_path}
{red_start}========================={norm}
)",
fmt::arg("red_start", ANSI_COLOR(1)),
fmt::arg("green_start", ANSI_COLOR(2)),
fmt::arg("bold_start", ANSI_BOLD_START),
fmt::arg("norm", ANSI_NORM),
fmt::arg("PACKAGE_BUGREPORT", PACKAGE_BUGREPORT),
fmt::arg("crash_path", crash_path));
#ifndef ATTACH_ON_SIGNAL
if (isatty(STDIN_FILENO)) {
@ -658,12 +676,14 @@ log_pipe_err(int fd)
reader.detach();
}
log_state_dumper::log_state_dumper()
log_state_dumper::
log_state_dumper()
{
DUMPER_LIST().push_back(this);
}
log_state_dumper::~log_state_dumper()
log_state_dumper::~
log_state_dumper()
{
auto iter = std::find(DUMPER_LIST().begin(), DUMPER_LIST().end(), this);
if (iter != DUMPER_LIST().end()) {
@ -671,12 +691,14 @@ log_state_dumper::~log_state_dumper()
}
}
log_crash_recoverer::log_crash_recoverer()
log_crash_recoverer::
log_crash_recoverer()
{
CRASH_LIST.push_back(this);
}
log_crash_recoverer::~log_crash_recoverer()
log_crash_recoverer::~
log_crash_recoverer()
{
auto iter = std::find(CRASH_LIST.begin(), CRASH_LIST.end(), this);

@ -33,6 +33,7 @@
#define lnav_log_hh
#include <cstdint>
#include <optional>
#include <string>
#include <stdio.h>
@ -43,8 +44,6 @@
# define lnav_dead2 __attribute__((noreturn))
#endif
#include "optional.hpp"
struct termios;
enum class lnav_log_level_t : uint32_t {
@ -80,7 +79,7 @@ public:
virtual ~log_state_dumper();
virtual void log_state(){
virtual void log_state() {
};
@ -97,9 +96,9 @@ public:
virtual void log_crash_recover() = 0;
};
extern nonstd::optional<FILE*> lnav_log_file;
extern std::optional<FILE*> lnav_log_file;
extern const char* lnav_log_crash_dir;
extern nonstd::optional<const struct termios*> lnav_log_orig_termios;
extern std::optional<const struct termios*> lnav_log_orig_termios;
extern enum lnav_log_level_t lnav_log_level;
#define log_msg_wrapper(level, fmt...) \
@ -146,15 +145,17 @@ extern enum lnav_log_level_t lnav_log_level;
((void) ((lhs >= rhs) \
? 0 \
: lnav_require_binary( \
#lhs " >= " #rhs, lhs, rhs, __FILE__, __LINE__)))
#lhs " >= " #rhs, lhs, rhs, __FILE__, __LINE__)))
#define require_gt(lhs, rhs) \
((void) ((lhs > rhs) ? 0 \
: lnav_require_binary( \
#lhs " > " #rhs, lhs, rhs, __FILE__, __LINE__)))
((void) ((lhs > rhs) \
? 0 \
: lnav_require_binary( \
#lhs " > " #rhs, lhs, rhs, __FILE__, __LINE__)))
#define require_lt(lhs, rhs) \
((void) ((lhs < rhs) ? 0 \
: lnav_require_binary( \
#lhs " < " #rhs, lhs, rhs, __FILE__, __LINE__)))
((void) ((lhs < rhs) \
? 0 \
: lnav_require_binary( \
#lhs " < " #rhs, lhs, rhs, __FILE__, __LINE__)))
#define lnav_require_binary(e, lhs, rhs, file, line) \
(log_msg(lnav_log_level_t::ERROR, \

@ -1,4 +1,4 @@
/*
/*
* File: lrucache.hpp
* Author: Alexander Ponomarev
*
@ -6,78 +6,75 @@
*/
#ifndef _LRUCACHE_HPP_INCLUDED_
#define _LRUCACHE_HPP_INCLUDED_
#define _LRUCACHE_HPP_INCLUDED_
#include <map>
#include <list>
#include <cstddef>
#include <list>
#include <map>
#include <optional>
#include <stdexcept>
#include "optional.hpp"
namespace cache {
template<typename key_t, typename value_t>
class lru_cache {
public:
typedef typename std::pair<key_t, value_t> key_value_pair_t;
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;
lru_cache(size_t max_size) :
_max_size(max_size) {
}
void put(const key_t& key, const value_t& value) {
auto it = _cache_items_map.find(key);
_cache_items_list.push_front(key_value_pair_t(key, value));
if (it != _cache_items_map.end()) {
_cache_items_list.erase(it->second);
_cache_items_map.erase(it);
}
_cache_items_map[key] = _cache_items_list.begin();
if (_cache_items_map.size() > _max_size) {
auto last = _cache_items_list.end();
last--;
_cache_items_map.erase(last->first);
_cache_items_list.pop_back();
}
}
nonstd::optional<value_t> get(const key_t& key) {
auto it = _cache_items_map.find(key);
if (it == _cache_items_map.end()) {
return nonstd::nullopt;
}
_cache_items_list.splice(_cache_items_list.begin(), _cache_items_list, it->second);
typedef typename std::pair<key_t, value_t> key_value_pair_t;
typedef typename std::list<key_value_pair_t>::iterator list_iterator_t;
lru_cache(size_t max_size) : _max_size(max_size) {}
void put(const key_t& key, const value_t& value)
{
auto it = _cache_items_map.find(key);
_cache_items_list.push_front(key_value_pair_t(key, value));
if (it != _cache_items_map.end()) {
_cache_items_list.erase(it->second);
_cache_items_map.erase(it);
}
_cache_items_map[key] = _cache_items_list.begin();
if (_cache_items_map.size() > _max_size) {
auto last = _cache_items_list.end();
last--;
_cache_items_map.erase(last->first);
_cache_items_list.pop_back();
}
}
std::optional<value_t> get(const key_t& key)
{
auto it = _cache_items_map.find(key);
if (it == _cache_items_map.end()) {
return std::nullopt;
}
_cache_items_list.splice(
_cache_items_list.begin(), _cache_items_list, it->second);
return it->second->second;
}
bool exists(const key_t& key) const {
return _cache_items_map.find(key) != _cache_items_map.end();
}
size_t size() const {
return _cache_items_map.size();
}
void set_max_size(size_t max_size) {
this->_max_size = max_size;
}
void clear() {
this->_cache_items_map.clear();
this->_cache_items_list.clear();
}
}
bool exists(const key_t& key) const
{
return _cache_items_map.find(key) != _cache_items_map.end();
}
size_t size() const { return _cache_items_map.size(); }
void set_max_size(size_t max_size) { this->_max_size = max_size; }
void clear()
{
this->_cache_items_map.clear();
this->_cache_items_list.clear();
}
private:
std::list<key_value_pair_t> _cache_items_list;
std::map<key_t, list_iterator_t> _cache_items_map;
size_t _max_size;
std::list<key_value_pair_t> _cache_items_list;
std::map<key_t, list_iterator_t> _cache_items_map;
size_t _max_size;
};
} // namespace cache
#endif /* _LRUCACHE_HPP_INCLUDED_ */
} // namespace cache
#endif /* _LRUCACHE_HPP_INCLUDED_ */

@ -32,16 +32,15 @@
#include <functional>
#include <map>
#include <optional>
#include <type_traits>
#include <vector>
#include "optional.hpp"
namespace lnav {
namespace map {
template<typename C>
nonstd::optional<
std::optional<
std::reference_wrapper<std::conditional_t<std::is_const<C>::value,
const typename C::mapped_type,
typename C::mapped_type>>>
@ -49,10 +48,10 @@ find(C& container, const typename C::key_type& key)
{
auto iter = container.find(key);
if (iter != container.end()) {
return nonstd::make_optional(std::ref(iter->second));
return std::make_optional(std::ref(iter->second));
}
return nonstd::nullopt;
return std::nullopt;
}
template<typename K, typename V, typename M = std::map<K, V>>

@ -65,7 +65,7 @@ connect(const char* hostname, const char* servname)
return Err(fmt::format(FMT_STRING("unable to connect to {}:{} -- {}"),
hostname,
servname,
strerror(rc)));
strerror(errno)));
}
return Ok(std::move(retval));

@ -33,23 +33,22 @@
#include <string>
#include "auto_fd.hh"
#include "optional.hpp"
#include "result.h"
namespace network {
struct locality {
locality(nonstd::optional<std::string> username,
locality(std::optional<std::string> username,
std::string hostname,
nonstd::optional<std::string> service)
std::optional<std::string> service)
: l_username(std::move(username)), l_hostname(std::move(hostname)),
l_service(std::move(service))
{
}
nonstd::optional<std::string> l_username;
std::optional<std::string> l_username;
std::string l_hostname;
nonstd::optional<std::string> l_service;
std::optional<std::string> l_service;
};
struct path {

@ -30,9 +30,9 @@
#ifndef lnav_opt_util_hh
#define lnav_opt_util_hh
#include <stdlib.h>
#include <optional>
#include "optional.hpp"
#include <stdlib.h>
namespace detail {
@ -47,25 +47,22 @@ template<class T>
typename std::enable_if<not std::is_void<T>::value, T>::type
void_or_nullopt()
{
return nonstd::nullopt;
return std::nullopt;
}
template<class T>
struct is_optional : std::false_type {
};
struct is_optional : std::false_type {};
template<class T>
struct is_optional<nonstd::optional<T>> : std::true_type {
};
struct is_optional<std::optional<T>> : std::true_type {};
} // namespace detail
template<class T,
class F,
std::enable_if_t<detail::is_optional<std::decay_t<T>>::value, int> = 0>
auto
operator|(T&& t, F f)
-> decltype(detail::void_or_nullopt<decltype(f(std::forward<T>(t).
operator*()))>())
operator|(T&& t, F f) -> decltype(detail::void_or_nullopt<decltype(f(
std::forward<T>(t).operator*()))>())
{
using return_type = decltype(f(std::forward<T>(t).operator*()));
if (t)
@ -75,28 +72,27 @@ operator|(T&& t, F f)
}
template<class T>
optional_constexpr nonstd::optional<typename std::decay<T>::type>
constexpr std::optional<typename std::decay<T>::type>
make_optional_from_nullable(T&& v)
{
if (v != nullptr) {
return nonstd::optional<typename std::decay<T>::type>(
std::forward<T>(v));
return std::optional<typename std::decay<T>::type>(std::forward<T>(v));
}
return nonstd::nullopt;
return std::nullopt;
}
template<template<typename, typename...> class C, typename T>
nonstd::optional<T>
std::optional<T>
cget(const C<T>& container, size_t index)
{
if (index < container.size()) {
return container[index];
}
return nonstd::nullopt;
return std::nullopt;
}
inline nonstd::optional<const char*>
inline std::optional<const char*>
getenv_opt(const char* name)
{
return make_optional_from_nullable(getenv(name));

@ -30,10 +30,13 @@
#include "config.h"
#ifdef __CYGWIN__
# include <algorithm>
# include <iostream>
# include <sstream>
#endif
#include <unistd.h>
#include "fmt/format.h"
#include "paths.hh"
@ -76,7 +79,7 @@ windows_to_unix_file_path(char* input)
}
#endif
ghc::filesystem::path
std::filesystem::path
dotlnav()
{
#ifdef __CYGWIN__
@ -87,26 +90,26 @@ dotlnav()
auto xdg_config_home = getenv("XDG_CONFIG_HOME");
if (home_env != nullptr) {
auto home_path = ghc::filesystem::path(home_env);
auto home_path = std::filesystem::path(home_env);
if (ghc::filesystem::is_directory(home_path)) {
if (std::filesystem::is_directory(home_path)) {
auto home_lnav = home_path / ".lnav";
if (ghc::filesystem::is_directory(home_lnav)) {
if (std::filesystem::is_directory(home_lnav)) {
return home_lnav;
}
if (xdg_config_home != nullptr) {
auto xdg_path = ghc::filesystem::path(xdg_config_home);
auto xdg_path = std::filesystem::path(xdg_config_home);
if (ghc::filesystem::is_directory(xdg_path)) {
if (std::filesystem::is_directory(xdg_path)) {
return xdg_path / "lnav";
}
}
auto home_config = home_path / ".config";
if (ghc::filesystem::is_directory(home_config)) {
if (std::filesystem::is_directory(home_config)) {
return home_config / "lnav";
}
@ -114,16 +117,16 @@ dotlnav()
}
}
return ghc::filesystem::current_path();
return std::filesystem::current_path();
}
ghc::filesystem::path
std::filesystem::path
workdir()
{
auto subdir_name = fmt::format(FMT_STRING("lnav-user-{}-work"), getuid());
auto tmp_path = ghc::filesystem::temp_directory_path();
auto tmp_path = std::filesystem::temp_directory_path();
return tmp_path / ghc::filesystem::path(subdir_name);
return tmp_path / std::filesystem::path(subdir_name);
}
} // namespace paths

@ -30,7 +30,7 @@
#ifndef lnav_paths_hh
#define lnav_paths_hh
#include "ghc/filesystem.hpp"
#include <filesystem>
namespace lnav {
namespace paths {
@ -48,9 +48,9 @@ char* windows_to_unix_file_path(char* input);
* @param sub The path to the file in the '.lnav' directory.
* @return The full path
*/
ghc::filesystem::path dotlnav();
std::filesystem::path dotlnav();
ghc::filesystem::path workdir();
std::filesystem::path workdir();
} // namespace paths
} // namespace lnav

@ -32,15 +32,21 @@
#include <arpa/inet.h>
#include <unistd.h>
#include "base/injector.hh"
#include "base/lnav_log.hh"
#include "base/paths.hh"
#include "base/snippet_highlighters.hh"
#include "piper.looper.cfg.hh"
#include "readline_highlighters.hh"
using namespace lnav::roles::literals;
namespace lnav {
namespace piper {
const char HEADER_MAGIC[4] = {'L', 0, 'N', 1};
const ghc::filesystem::path&
const std::filesystem::path&
storage_path()
{
static auto INSTANCE = lnav::paths::workdir() / "piper";
@ -48,7 +54,7 @@ storage_path()
return INSTANCE;
}
nonstd::optional<auto_buffer>
std::optional<auto_buffer>
read_header(int fd, const char* first8)
{
if (memcmp(first8, HEADER_MAGIC, sizeof(HEADER_MAGIC)) != 0) {
@ -57,7 +63,7 @@ read_header(int fd, const char* first8)
first8[1],
first8[2],
first8[3]);
return nonstd::nullopt;
return std::nullopt;
}
uint32_t meta_size = ntohl(*((uint32_t*) &first8[4]));
@ -65,17 +71,155 @@ read_header(int fd, const char* first8)
auto meta_buf = auto_buffer::alloc(meta_size);
if (meta_buf.in() == nullptr) {
log_error("failed to alloc %d bytes for header", meta_size);
return nonstd::nullopt;
return std::nullopt;
}
auto meta_prc = pread(fd, meta_buf.in(), meta_size, 8);
if (meta_prc != meta_size) {
log_error("failed to read piper header: %s", strerror(errno));
return nonstd::nullopt;
return std::nullopt;
}
meta_buf.resize(meta_size);
return meta_buf;
}
multiplex_matcher::match_result
multiplex_matcher::match(const string_fragment& line)
{
const auto& cfg = injector::get<const config&>();
auto md = lnav::pcre2pp::match_data::unitialized();
for (const auto& demux_pair : cfg.c_demux_definitions) {
const auto& df = demux_pair.second;
if (!df.dd_valid) {
continue;
}
if (!this->mm_partial_match_ids.empty()
&& this->mm_partial_match_ids.count(demux_pair.first) == 0)
{
continue;
}
log_info("attempting to demux using: %s", demux_pair.first.c_str());
{
md = df.dd_pattern.pp_value->create_match_data();
if (df.dd_pattern.pp_value->capture_from(line)
.into(md)
.matches()
.ignore_error())
{
log_info(" demuxer pattern matched");
if (!md[df.dd_muxid_capture_index].has_value()) {
log_info(" however, mux_id was not captured");
const auto match_um = lnav::console::user_message::warning(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" matched, however the ")
.append("mux_id"_symbol)
.append(" was not captured"));
this->mm_details.emplace_back(match_um);
continue;
}
if (!md[df.dd_body_capture_index].has_value()) {
log_info(" however, body was not captured");
const auto match_um = lnav::console::user_message::warning(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" matched, however the ")
.append("body"_symbol)
.append(" was not captured"));
this->mm_details.emplace_back(match_um);
continue;
}
log_info(" and required captures were found, using demuxer");
if (df.dd_enabled) {
auto match_um = lnav::console::user_message::ok(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" matched line ")
.append(lnav::roles::number(
fmt::to_string(this->mm_line_count))));
this->mm_details.emplace_back(match_um);
return found{demux_pair.first};
}
auto config_al
= attr_line_t()
.append(fmt::format(
FMT_STRING(":config /log/demux/{}/enabled "
"true"),
demux_pair.first))
.move();
readline_lnav_highlighter(config_al, -1);
auto match_um
= lnav::console::user_message::info(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" matched line ")
.append(lnav::roles::number(
fmt::to_string(this->mm_line_count)))
.append(", however, it is disabled"))
.with_help(
attr_line_t("Use ")
.append_quoted(
lnav::roles::quoted_code(config_al))
.append(" to enable this demuxer"))
.move();
this->mm_details.emplace_back(match_um);
}
auto partial_size = df.dd_pattern.pp_value->match_partial(
line.sub_range(0, 1024));
auto regex_al = attr_line_t(df.dd_pattern.pp_value->get_pattern());
lnav::snippets::regex_highlighter(
regex_al, -1, line_range{0, (int) regex_al.length()});
auto in_line = line.sub_range(0, 1024).rtrim("\n").to_string();
auto esc_res
= fmt::v10::detail::find_escape(&in_line[0], &(*in_line.end()));
if (esc_res.end != nullptr) {
in_line = fmt::format(FMT_STRING("{:?}"), in_line);
}
auto note = attr_line_t("pattern: ")
.append(regex_al)
.append("\n ")
.append(lnav::roles::quoted_code(in_line))
.append("\n")
.append(partial_size + 2, ' ')
.append("^ matched up to here")
.move();
auto match_um = lnav::console::user_message::info(
attr_line_t("demuxer ")
.append_quoted(demux_pair.first)
.append(" did not match line ")
.append(lnav::roles::number(
fmt::to_string(this->mm_line_count))))
.with_note(note)
.move();
this->mm_details.emplace_back(match_um);
}
if (df.dd_control_pattern.pp_value) {
md = df.dd_control_pattern.pp_value->create_match_data();
if (df.dd_control_pattern.pp_value->capture_from(line)
.into(md)
.matches()
.ignore_error())
{
log_info(" demuxer control pattern matched");
this->mm_partial_match_ids.emplace(demux_pair.first);
}
}
}
this->mm_line_count += 1;
if (this->mm_partial_match_ids.empty()) {
return not_found{};
}
return partial{};
}
} // namespace piper
} // namespace lnav

@ -30,24 +30,38 @@
#ifndef lnav_piper_file_hh
#define lnav_piper_file_hh
#include <filesystem>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <sys/time.h>
#include "auto_mem.hh"
#include "ghc/filesystem.hpp"
#include "optional.hpp"
#include "base/intern_string.hh"
#include "lnav.console.hh"
#include "mapbox/variant_io.hpp"
#include "time_util.hh"
namespace lnav {
namespace piper {
enum class demux_output_t {
not_applicable,
signal,
invalid,
};
struct header {
timeval h_ctime{};
std::string h_name;
std::string h_cwd;
std::map<std::string, std::string> h_env;
std::string h_timezone;
std::string h_mux_id;
demux_output_t h_demux_output{demux_output_t::not_applicable};
std::map<std::string, std::string> h_demux_meta;
bool operator<(const header& rhs) const
{
@ -63,12 +77,32 @@ struct header {
}
};
const ghc::filesystem::path& storage_path();
const std::filesystem::path& storage_path();
constexpr size_t HEADER_SIZE = 8;
extern const char HEADER_MAGIC[4];
nonstd::optional<auto_buffer> read_header(int fd, const char* first8);
std::optional<auto_buffer> read_header(int fd, const char* first8);
class multiplex_matcher {
public:
struct found {
std::string f_id;
};
struct partial {};
struct not_found {};
using match_result = mapbox::util::variant<found, partial, not_found>;
match_result match(const string_fragment& line);
std::vector<lnav::console::user_message> mm_details;
private:
std::set<std::string> mm_partial_match_ids;
size_t mm_line_count{0};
};
} // namespace piper
} // namespace lnav

@ -99,7 +99,7 @@ find_matching_bracket(
}
}
nonstd::optional<int> first_left;
std::optional<int> first_left;
depth = 0;
@ -156,7 +156,7 @@ safe_read(const std::string& str, std::string::size_type index)
}
void
regex_highlighter(attr_line_t& al, int x, line_range sub)
regex_highlighter(attr_line_t& al, std::optional<int> x, line_range sub)
{
static const char* brackets[] = {
"[]",
@ -336,7 +336,8 @@ regex_highlighter(attr_line_t& al, int x, line_range sub)
}
for (int lpc = 0; brackets[lpc]; lpc++) {
find_matching_bracket(al, x, sub, brackets[lpc][0], brackets[lpc][1]);
find_matching_bracket(
al, x.value_or(0), sub, brackets[lpc][0], brackets[lpc][1]);
}
}

@ -35,7 +35,7 @@
namespace lnav {
namespace snippets {
void regex_highlighter(attr_line_t& al, int x, line_range sub);
void regex_highlighter(attr_line_t& al, std::optional<int> x, line_range sub);
} // namespace snippets
} // namespace lnav

@ -164,8 +164,8 @@ struct text_attrs {
}
int32_t ta_attrs{0};
nonstd::optional<short> ta_fg_color;
nonstd::optional<short> ta_bg_color;
std::optional<short> ta_fg_color;
std::optional<short> ta_bg_color;
};
struct block_elem_t {

@ -333,6 +333,36 @@ scrub_ws(const char* in, ssize_t len)
return retval;
}
static constexpr const char* const SUPERSCRIPT_NUMS[] = {
"",
"¹",
"²",
"³",
"",
"",
"",
"",
"",
"",
};
std::string
to_superscript(const std::string& in)
{
std::string retval;
for (const auto ch : in) {
if (isdigit(ch)) {
auto index = ch - '0';
retval.append(SUPERSCRIPT_NUMS[index]);
} else {
retval.push_back(ch);
}
}
return retval;
}
namespace fmt {
auto
formatter<lnav::tainted_string>::format(const lnav::tainted_string& ts,
@ -375,7 +405,7 @@ is_meta(char ch)
}
}
static nonstd::optional<const char*>
static std::optional<const char*>
char_escape_seq(char ch)
{
switch (ch) {
@ -385,7 +415,7 @@ char_escape_seq(char ch)
return "\\n";
}
return nonstd::nullopt;
return std::nullopt;
}
std::string

@ -246,6 +246,15 @@ on_blank(const std::string& str, const std::string& def)
return str;
}
std::string to_superscript(const std::string& in);
template<typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
inline std::string
to_superscript(T in)
{
return to_superscript(fmt::to_string(in));
}
namespace lnav {
class tainted_string {
public:
@ -282,8 +291,8 @@ private:
namespace fmt {
template<>
struct formatter<lnav::tainted_string> : formatter<string_view> {
auto format(const lnav::tainted_string& ts,
format_context& ctx) -> decltype(ctx.out()) const;
auto format(const lnav::tainted_string& ts, format_context& ctx)
-> decltype(ctx.out()) const;
};
} // namespace fmt

@ -38,7 +38,6 @@
#include "config.h"
#include "lnav_log.hh"
#include "optional.hpp"
namespace lnav {
@ -80,18 +79,28 @@ strftime_rfc3339(
return index;
}
static nonstd::optional<Posix::time_zone>
std::string
to_rfc3339_string(time64_t tim, int millis, char sep)
{
char buf[64];
strftime_rfc3339(buf, sizeof(buf), tim, millis, sep);
return buf;
}
static std::optional<Posix::time_zone>
get_posix_zone(const char* name)
{
if (name == nullptr) {
return nonstd::nullopt;
return std::nullopt;
}
try {
return date::zoned_traits<Posix::time_zone>::locate_zone(name);
} catch (const std::runtime_error& e) {
log_error("invalid TZ value: %s -- %s", name, e.what());
return nonstd::nullopt;
return std::nullopt;
}
}
@ -121,6 +130,8 @@ to_sys_time(date::local_seconds secs)
return TZ_POSIX_ZONE.value().to_sys(secs);
}
auto inf = TZ_DATE_ZONE->get_info(secs);
return TZ_DATE_ZONE->to_sys(secs);
}
@ -138,6 +149,34 @@ to_local_time(date::sys_seconds secs)
return TZ_DATE_ZONE->to_local(secs);
}
date::sys_info
sys_time_to_info(date::sys_seconds secs)
{
static const auto* TZ = getenv("TZ");
static const auto TZ_POSIX_ZONE = get_posix_zone(TZ);
static const auto* TZ_DATE_ZONE = get_date_zone(TZ);
if (TZ_POSIX_ZONE) {
return TZ_POSIX_ZONE.value().get_info(secs);
}
return TZ_DATE_ZONE->get_info(secs);
}
date::local_info
local_time_to_info(date::local_seconds secs)
{
static const auto* TZ = getenv("TZ");
static const auto TZ_POSIX_ZONE = get_posix_zone(TZ);
static const auto* TZ_DATE_ZONE = get_date_zone(TZ);
if (TZ_POSIX_ZONE) {
return TZ_POSIX_ZONE.value().get_info(secs);
}
return TZ_DATE_ZONE->get_info(secs);
}
} // namespace lnav
static time_t BAD_DATE = -1;

@ -40,16 +40,26 @@
#include "config.h"
#include "date/date.h"
#include "date/tz.h"
namespace lnav {
using time64_t = uint64_t;
ssize_t strftime_rfc3339(char* buffer,
size_t buffer_size,
lnav::time64_t tim,
int millis,
char sep = ' ');
ssize_t strftime_rfc3339(
char* buffer, size_t buffer_size, time64_t tim, int millis, char sep = ' ');
std::string to_rfc3339_string(time64_t tim, int millis, char sep = ' ');
inline std::string
to_rfc3339_string(struct timeval tv, char sep = ' ')
{
return to_rfc3339_string(tv.tv_sec, tv.tv_usec / 1000, sep);
}
date::sys_info sys_time_to_info(date::sys_seconds secs);
date::local_info local_time_to_info(date::local_seconds secs);
date::sys_seconds to_sys_time(date::local_seconds secs);
@ -250,6 +260,16 @@ struct time_range {
struct timeval tr_begin;
struct timeval tr_end;
bool valid() const { return this->tr_end.tv_sec == 0; }
void invalidate()
{
this->tr_begin.tv_sec = INT_MAX;
this->tr_begin.tv_usec = 0;
this->tr_end.tv_sec = 0;
this->tr_end.tv_usec = 0;
}
bool operator<(const time_range& rhs) const
{
return this->tr_begin < rhs.tr_begin;

File diff suppressed because it is too large Load Diff

@ -68,26 +68,29 @@ bookmark_metadata::empty(bookmark_metadata::categories props) const
{
switch (props) {
case categories::any:
return this->bm_name.empty() && this->bm_comment.empty()
&& this->bm_tags.empty()
return this->bm_name.empty() && this->bm_opid.empty()
&& this->bm_comment.empty() && this->bm_tags.empty()
&& this->bm_annotations.la_pairs.empty();
case categories::partition:
return this->bm_name.empty();
case categories::notes:
return this->bm_comment.empty() && this->bm_tags.empty()
&& this->bm_annotations.la_pairs.empty();
case categories::opid:
return this->bm_opid.empty();
}
}
void
bookmark_metadata::clear()
{
this->bm_opid.clear();
this->bm_comment.clear();
this->bm_tags.clear();
this->bm_annotations.la_pairs.clear();
}
nonstd::optional<bookmark_type_t*>
std::optional<bookmark_type_t*>
bookmark_type_t::find_type(const std::string& name)
{
return get_all_types()

@ -51,6 +51,7 @@ struct bookmark_metadata {
any = 0,
partition = 0x01,
notes = 0x02,
opid = 0x04,
};
bool has(categories props) const
@ -71,10 +72,15 @@ struct bookmark_metadata {
return true;
}
if (props == categories::opid && !this->bm_opid.empty()) {
return true;
}
return false;
}
std::string bm_name;
std::string bm_opid;
std::string bm_comment;
logmsg_annotations bm_annotations;
std::vector<std::string> bm_tags;
@ -153,7 +159,7 @@ public:
* the next bookmark is returned. If the 'start' value is not a
* bookmark, the next highest value in the vector is returned.
*/
nonstd::optional<LineType> next(LineType start) const;
std::optional<LineType> next(LineType start) const;
/**
* @param start The value to start the search for the previous
@ -162,7 +168,7 @@ public:
* are no more prior bookmarks.
* @see next
*/
nonstd::optional<LineType> prev(LineType start) const;
std::optional<LineType> prev(LineType start) const;
};
/**
@ -177,8 +183,7 @@ public:
static type_iterator type_end() { return get_all_types().end(); }
static nonstd::optional<bookmark_type_t*> find_type(
const std::string& name);
static std::optional<bookmark_type_t*> find_type(const std::string& name);
static std::vector<bookmark_type_t*>& get_all_types();
@ -194,10 +199,10 @@ private:
};
template<typename LineType>
nonstd::optional<LineType>
std::optional<LineType>
bookmark_vector<LineType>::next(LineType start) const
{
nonstd::optional<LineType> retval;
std::optional<LineType> retval;
require(start >= -1);
@ -212,10 +217,10 @@ bookmark_vector<LineType>::next(LineType start) const
}
template<typename LineType>
nonstd::optional<LineType>
std::optional<LineType>
bookmark_vector<LineType>::prev(LineType start) const
{
nonstd::optional<LineType> retval;
std::optional<LineType> retval;
require(start >= 0);

@ -71,13 +71,13 @@ bottom_status_source::update_line_number(listview_curses* lc)
this->bss_line_error.set_value(
lc->map_top_row([](const attr_line_t& top_row)
-> nonstd::optional<std::string> {
-> std::optional<std::string> {
const auto& sa = top_row.get_attrs();
auto error_wrapper = get_string_attr(sa, SA_ERROR);
if (error_wrapper) {
return error_wrapper.value().get();
}
return nonstd::nullopt;
return std::nullopt;
}).value_or(""));
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save