diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8f192a6b..b09952c4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -473,6 +473,7 @@ add_library( fstat_vtab.hh fts_fuzzy_match.hh grep_highlighter.hh + hasher.hh help_text.hh help_text_formatter.hh highlighter.hh diff --git a/src/Makefile.am b/src/Makefile.am index 783f1a6f..632fff8d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -212,6 +212,7 @@ noinst_HEADERS = \ fts_fuzzy_match.hh \ grep_highlighter.hh \ grep_proc.hh \ + hasher.hh \ help.md \ help.txt \ help_text.hh \ diff --git a/src/archive_manager.cc b/src/archive_manager.cc index c0b1998c..3aeccbe2 100644 --- a/src/archive_manager.cc +++ b/src/archive_manager.cc @@ -29,6 +29,8 @@ * @file archive_manager.cc */ +#include + #include #include "config.h" @@ -48,7 +50,7 @@ #include "base/lnav_log.hh" #include "base/paths.hh" #include "fmt/format.h" -#include "lnav_util.hh" +#include "hasher.hh" namespace fs = ghc::filesystem; diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 14ef5c09..72c28c29 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -47,6 +47,7 @@ add_library( is_utf8.hh isc.hh itertools.hh + line_range.hh lnav.console.hh lnav.console.into.hh log_level_enum.hh diff --git a/src/base/Makefile.am b/src/base/Makefile.am index 5695e34e..87d38bb5 100644 --- a/src/base/Makefile.am +++ b/src/base/Makefile.am @@ -44,6 +44,7 @@ noinst_HEADERS = \ is_utf8.hh \ isc.hh \ itertools.hh \ + line_range.hh \ lnav_log.hh \ lnav.console.hh \ lnav.console.into.hh \ diff --git a/src/base/attr_line.cc b/src/base/attr_line.cc index b65f4bac..9cb0d43c 100644 --- a/src/base/attr_line.cc +++ b/src/base/attr_line.cc @@ -535,3 +535,112 @@ line_range::shift(int32_t start, int32_t amount) return *this; } + +string_attrs_t::const_iterator +find_string_attr(const string_attrs_t& sa, size_t near) +{ + auto nearest = sa.end(); + ssize_t last_diff = INT_MAX; + + for (auto iter = sa.begin(); iter != sa.end(); ++iter) { + const auto& lr = iter->sa_range; + + if (!lr.is_valid() || !lr.contains(near)) { + continue; + } + + ssize_t diff = near - lr.lr_start; + if (diff < last_diff) { + last_diff = diff; + nearest = iter; + } + } + + return nearest; +} + +void +shift_string_attrs(string_attrs_t& sa, int32_t start, int32_t amount) +{ + for (auto& iter : sa) { + iter.sa_range.shift(start, amount); + } +} + +struct line_range +find_string_attr_range(const string_attrs_t& sa, string_attr_type_base* type) +{ + auto iter = find_string_attr(sa, type); + + if (iter != sa.end()) { + return iter->sa_range; + } + + return line_range(); +} + +void +remove_string_attr(string_attrs_t& sa, const line_range& lr) +{ + string_attrs_t::iterator iter; + + while ((iter = find_string_attr(sa, lr)) != sa.end()) { + sa.erase(iter); + } +} + +void +remove_string_attr(string_attrs_t& sa, string_attr_type_base* type) +{ + for (auto iter = sa.begin(); iter != sa.end();) { + if (iter->sa_type == type) { + iter = sa.erase(iter); + } else { + ++iter; + } + } +} + +string_attrs_t::iterator +find_string_attr(string_attrs_t& sa, const line_range& lr) +{ + string_attrs_t::iterator iter; + + for (iter = sa.begin(); iter != sa.end(); ++iter) { + if (lr.contains(iter->sa_range)) { + break; + } + } + + return iter; +} + +string_attrs_t::const_iterator +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) { + if (iter->sa_type == type && iter->sa_range.lr_start >= start) { + break; + } + } + + return iter; +} + +nonstd::optional +get_string_attr(const string_attrs_t& sa, + const string_attr_type_base* type, + int start) +{ + auto iter = find_string_attr(sa, type, start); + + if (iter == sa.end()) { + return nonstd::nullopt; + } + + return nonstd::make_optional(&(*iter)); +} diff --git a/src/base/attr_line.hh b/src/base/attr_line.hh index 57324fd1..8a62fcd4 100644 --- a/src/base/attr_line.hh +++ b/src/base/attr_line.hh @@ -40,136 +40,10 @@ #include "fmt/format.h" #include "intern_string.hh" +#include "line_range.hh" #include "string_attr_type.hh" #include "string_util.hh" -/** - * Encapsulates a range in a string. - */ -struct line_range { - enum class unit { - bytes, - codepoint, - }; - - int lr_start; - int lr_end; - unit lr_unit; - - explicit line_range(int start = -1, int end = -1, unit u = unit::bytes) - : lr_start(start), lr_end(end), lr_unit(u) - { - } - - bool is_valid() const { return this->lr_start != -1; } - - int length() const - { - return this->lr_end == -1 ? INT_MAX : this->lr_end - this->lr_start; - } - - bool empty() const { return this->length() == 0; } - - void clear() - { - this->lr_start = -1; - this->lr_end = -1; - } - - int end_for_string(const std::string& str) const - { - return this->lr_end == -1 ? str.length() : this->lr_end; - } - - bool contains(int pos) const - { - return this->lr_start <= pos - && (this->lr_end == -1 || pos < this->lr_end); - } - - bool contains(const struct line_range& other) const - { - return this->contains(other.lr_start) - && (this->lr_end == -1 || other.lr_end <= this->lr_end); - } - - bool intersects(const struct line_range& other) const - { - if (this->contains(other.lr_start)) { - return true; - } - if (other.lr_end > 0 && this->contains(other.lr_end - 1)) { - return true; - } - if (other.contains(this->lr_start)) { - return true; - } - - return false; - } - - line_range intersection(const struct line_range& other) const; - - line_range& shift(int32_t start, int32_t amount); - - void ltrim(const char* str) - { - while (this->lr_start < this->lr_end && isspace(str[this->lr_start])) { - this->lr_start += 1; - } - } - - bool operator<(const struct line_range& rhs) const - { - if (this->lr_start < rhs.lr_start) { - return true; - } - if (this->lr_start > rhs.lr_start) { - return false; - } - - // this->lr_start == rhs.lr_start - if (this->lr_end == rhs.lr_end) { - return false; - } - - // When the start is the same, the longer range has a lower priority - // than the shorter range. - if (rhs.lr_end == -1) { - return false; - } - - if ((this->lr_end == -1) || (this->lr_end > rhs.lr_end)) { - return true; - } - return false; - } - - bool operator==(const struct line_range& rhs) const - { - return (this->lr_start == rhs.lr_start && this->lr_end == rhs.lr_end); - } - - const char* substr(const std::string& str) const - { - if (this->lr_start == -1) { - return str.c_str(); - } - return &(str.c_str()[this->lr_start]); - } - - size_t sublen(const std::string& str) const - { - if (this->lr_start == -1) { - return str.length(); - } - if (this->lr_end == -1) { - return str.length() - this->lr_start; - } - return this->length(); - } -}; - inline line_range to_line_range(const string_fragment& frag) { @@ -220,35 +94,11 @@ struct string_attr_wrapper { /** A map of line ranges to attributes for that range. */ using string_attrs_t = std::vector; -inline string_attrs_t::const_iterator -find_string_attr(const string_attrs_t& sa, - const string_attr_type_base* type, - int start = 0) -{ - string_attrs_t::const_iterator iter; - - for (iter = sa.begin(); iter != sa.end(); ++iter) { - if (iter->sa_type == type && iter->sa_range.lr_start >= start) { - break; - } - } - - return iter; -} - -inline nonstd::optional -get_string_attr(const string_attrs_t& sa, - const string_attr_type_base* type, - int start = 0) -{ - auto iter = find_string_attr(sa, type, start); - - if (iter == sa.end()) { - return nonstd::nullopt; - } +string_attrs_t::const_iterator find_string_attr( + const string_attrs_t& sa, const string_attr_type_base* type, int start = 0); - return nonstd::make_optional(&(*iter)); -} +nonstd::optional get_string_attr( + const string_attrs_t& sa, const string_attr_type_base* type, int start = 0); template inline nonstd::optional> @@ -282,42 +132,11 @@ find_string_attr_containing(const string_attrs_t& sa, return iter; } -inline string_attrs_t::iterator -find_string_attr(string_attrs_t& sa, const struct line_range& lr) -{ - string_attrs_t::iterator iter; +string_attrs_t::iterator find_string_attr(string_attrs_t& sa, + const struct line_range& lr); - for (iter = sa.begin(); iter != sa.end(); ++iter) { - if (lr.contains(iter->sa_range)) { - break; - } - } - - return iter; -} - -inline string_attrs_t::const_iterator -find_string_attr(const string_attrs_t& sa, size_t near) -{ - auto nearest = sa.end(); - ssize_t last_diff = INT_MAX; - - for (auto iter = sa.begin(); iter != sa.end(); ++iter) { - const auto& lr = iter->sa_range; - - if (!lr.is_valid() || !lr.contains(near)) { - continue; - } - - ssize_t diff = near - lr.lr_start; - if (diff < last_diff) { - last_diff = diff; - nearest = iter; - } - } - - return nearest; -} +string_attrs_t::const_iterator find_string_attr(const string_attrs_t& sa, + size_t near); template inline string_attrs_t::const_iterator @@ -347,47 +166,14 @@ rfind_string_attr_if(const string_attrs_t& sa, ssize_t near, T predicate) return nearest; } -inline struct line_range -find_string_attr_range(const string_attrs_t& sa, string_attr_type_base* type) -{ - auto iter = find_string_attr(sa, type); - - if (iter != sa.end()) { - return iter->sa_range; - } - - return line_range(); -} - -inline void -remove_string_attr(string_attrs_t& sa, const struct line_range& lr) -{ - string_attrs_t::iterator iter; +struct line_range find_string_attr_range(const string_attrs_t& sa, + string_attr_type_base* type); - while ((iter = find_string_attr(sa, lr)) != sa.end()) { - sa.erase(iter); - } -} +void remove_string_attr(string_attrs_t& sa, const struct line_range& lr); -inline void -remove_string_attr(string_attrs_t& sa, string_attr_type_base* type) -{ - for (auto iter = sa.begin(); iter != sa.end();) { - if (iter->sa_type == type) { - iter = sa.erase(iter); - } else { - ++iter; - } - } -} +void remove_string_attr(string_attrs_t& sa, string_attr_type_base* type); -inline void -shift_string_attrs(string_attrs_t& sa, int32_t start, int32_t amount) -{ - for (auto& iter : sa) { - iter.sa_range.shift(start, amount); - } -} +void shift_string_attrs(string_attrs_t& sa, int32_t start, int32_t amount); struct text_wrap_settings { text_wrap_settings& with_indent(int indent) diff --git a/src/base/intern_string.cc b/src/base/intern_string.cc index 676d2bc8..5d6838a8 100644 --- a/src/base/intern_string.cc +++ b/src/base/intern_string.cc @@ -37,6 +37,7 @@ #include "config.h" #include "pcrepp/pcre2pp.hh" +#include "ww898/cp_utf8.hpp" #include "xxHash/xxhash.h" const static int TABLE_SIZE = 4095; @@ -301,3 +302,36 @@ string_fragment::to_string_with_case_style(case_style style) const return retval; } + +uint32_t +string_fragment::front_codepoint() const +{ + size_t index = 0; + try { + return ww898::utf::utf8::read( + [this, &index]() { return this->data()[index++]; }); + } catch (const std::runtime_error& e) { + return this->data()[0]; + } +} + +Result +string_fragment::codepoint_to_byte_index(ssize_t cp_index) const +{ + ssize_t retval = 0; + + while (cp_index > 0) { + if (retval >= this->length()) { + return Err("index is beyond the end of the string"); + } + auto ch_len = TRY(ww898::utf::utf8::char_size([this, retval]() { + return std::make_pair(this->data()[retval], + this->length() - retval - 1); + })); + + retval += ch_len; + cp_index -= 1; + } + + return Ok(retval); +} diff --git a/src/base/intern_string.hh b/src/base/intern_string.hh index 4ae40daa..1f25d660 100644 --- a/src/base/intern_string.hh +++ b/src/base/intern_string.hh @@ -42,9 +42,9 @@ #include "fmt/format.h" #include "optional.hpp" +#include "result.h" #include "scn/util/string_view.h" #include "strnatcmp.h" -#include "ww898/cp_utf8.hpp" struct string_fragment { using iterator = const char*; @@ -157,16 +157,7 @@ struct string_fragment { char front() const { return this->sf_string[this->sf_begin]; } - uint32_t front_codepoint() const - { - size_t index = 0; - try { - return ww898::utf::utf8::read( - [this, &index]() { return this->data()[index++]; }); - } catch (const std::runtime_error& e) { - return this->data()[0]; - } - } + uint32_t front_codepoint() const; char back() const { return this->sf_string[this->sf_end - 1]; } @@ -183,25 +174,8 @@ struct string_fragment { bool empty() const { return !this->is_valid() || length() == 0; } - Result codepoint_to_byte_index(ssize_t cp_index) const - { - ssize_t retval = 0; - - while (cp_index > 0) { - if (retval >= this->length()) { - return Err("index is beyond the end of the string"); - } - auto ch_len = TRY(ww898::utf::utf8::char_size([this, retval]() { - return std::make_pair(this->data()[retval], - this->length() - retval - 1); - })); - - retval += ch_len; - cp_index -= 1; - } - - return Ok(retval); - } + Result codepoint_to_byte_index( + ssize_t cp_index) const; const char& operator[](int index) const { @@ -315,7 +289,9 @@ struct string_fragment { } template - string_fragment find_left_boundary(size_t start, P&& predicate) const + string_fragment find_left_boundary(size_t start, + P&& predicate, + size_t count = 1) const { assert((int) start <= this->length()); @@ -324,25 +300,33 @@ struct string_fragment { } while (start > 0) { if (predicate(this->data()[start])) { - start += 1; - break; + count -= 1; + if (count == 0) { + start += 1; + break; + } } start -= 1; } return string_fragment{ this->sf_string, - (int) start, + this->sf_begin + (int) start, this->sf_end, }; } template - string_fragment find_right_boundary(size_t start, P&& predicate) const + string_fragment find_right_boundary(size_t start, + P&& predicate, + size_t count = 1) const { while ((int) start < this->length()) { if (predicate(this->data()[start])) { - break; + count -= 1; + if (count == 0) { + break; + } } start += 1; } @@ -355,10 +339,14 @@ struct string_fragment { } template - string_fragment find_boundaries_around(size_t start, P&& predicate) const + string_fragment find_boundaries_around(size_t start, + P&& predicate, + size_t count = 1) const { - return this->template find_left_boundary(start, predicate) - .find_right_boundary(0, predicate); + auto left = this->template find_left_boundary(start, predicate, count); + + return left.find_right_boundary( + start - left.sf_begin, predicate, count); } nonstd::optional> consume_codepoint() diff --git a/src/base/intern_string.tests.cc b/src/base/intern_string.tests.cc index 8816803e..3b8485e1 100644 --- a/src/base/intern_string.tests.cc +++ b/src/base/intern_string.tests.cc @@ -129,6 +129,12 @@ TEST_CASE("find_left_boundary") auto world_sf = sf.find_left_boundary( in1.length() - 3, [](auto ch) { return ch == '\n'; }); CHECK(world_sf.to_string() == "World!\n"); + auto world_sf2 = sf.find_left_boundary( + in1.length() - 3, [](auto ch) { return ch == '\n'; }, 2); + CHECK(world_sf2.to_string() == "Hello,\nWorld!\n"); + auto world_sf3 = sf.find_left_boundary( + in1.length() - 3, [](auto ch) { return ch == '\n'; }, 3); + CHECK(world_sf3.to_string() == "Hello,\nWorld!\n"); auto full_sf = sf.find_left_boundary(3, [](auto ch) { return ch == '\n'; }); CHECK(full_sf.to_string() == in1); @@ -137,16 +143,35 @@ TEST_CASE("find_left_boundary") TEST_CASE("find_right_boundary") { - std::string in1 = "Hello,\nWorld!\n"; - { - auto sf = string_fragment{in1}; + const auto sf = string_fragment::from_const("Hello,\nWorld!\n"); - auto world_sf = sf.find_right_boundary( - in1.length() - 3, [](auto ch) { return ch == '\n'; }); + auto world_sf = sf.find_right_boundary(sf.length() - 3, + string_fragment::tag1{'\n'}); CHECK(world_sf.to_string() == "Hello,\nWorld!"); auto hello_sf = sf.find_right_boundary(3, [](auto ch) { return ch == '\n'; }); CHECK(hello_sf.to_string() == "Hello,"); + auto hello_sf2 + = sf.find_right_boundary(3, string_fragment::tag1{'\n'}, 2); + CHECK(hello_sf2.to_string() == "Hello,\nWorld!"); + } +} + +TEST_CASE("find_boundaries_around") +{ + { + const auto sf = string_fragment::from_const( + R"(Hello, +World! +Goodbye, +World!)"); + + auto all_sf1 + = sf.find_boundaries_around(3, string_fragment::tag1{'\n'}); + CHECK(all_sf1 == "Hello,"); + auto all_sf2 + = sf.find_boundaries_around(3, string_fragment::tag1{'\n'}, 2); + CHECK(all_sf2 == "Hello,\nWorld!"); } } diff --git a/src/base/line_range.hh b/src/base/line_range.hh new file mode 100644 index 00000000..8ae0275a --- /dev/null +++ b/src/base/line_range.hh @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2023, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef lnav_base_line_range_hh +#define lnav_base_line_range_hh + +#include + +/** + * Encapsulates a range in a string. + */ +struct line_range { + enum class unit { + bytes, + codepoint, + }; + + int lr_start; + int lr_end; + unit lr_unit; + + explicit line_range(int start = -1, int end = -1, unit u = unit::bytes) + : lr_start(start), lr_end(end), lr_unit(u) + { + } + + bool is_valid() const { return this->lr_start != -1; } + + int length() const + { + return this->lr_end == -1 ? INT_MAX : this->lr_end - this->lr_start; + } + + bool empty() const { return this->length() == 0; } + + void clear() + { + this->lr_start = -1; + this->lr_end = -1; + } + + int end_for_string(const std::string& str) const + { + return this->lr_end == -1 ? str.length() : this->lr_end; + } + + bool contains(int pos) const + { + return this->lr_start <= pos + && (this->lr_end == -1 || pos < this->lr_end); + } + + bool contains(const struct line_range& other) const + { + return this->contains(other.lr_start) + && (this->lr_end == -1 || other.lr_end <= this->lr_end); + } + + bool intersects(const struct line_range& other) const + { + if (this->contains(other.lr_start)) { + return true; + } + if (other.lr_end > 0 && this->contains(other.lr_end - 1)) { + return true; + } + if (other.contains(this->lr_start)) { + return true; + } + + return false; + } + + line_range intersection(const struct line_range& other) const; + + line_range& shift(int32_t start, int32_t amount); + + void ltrim(const char* str) + { + while (this->lr_start < this->lr_end && isspace(str[this->lr_start])) { + this->lr_start += 1; + } + } + + bool operator<(const struct line_range& rhs) const + { + if (this->lr_start < rhs.lr_start) { + return true; + } + if (this->lr_start > rhs.lr_start) { + return false; + } + + // this->lr_start == rhs.lr_start + if (this->lr_end == rhs.lr_end) { + return false; + } + + // When the start is the same, the longer range has a lower priority + // than the shorter range. + if (rhs.lr_end == -1) { + return false; + } + + if ((this->lr_end == -1) || (this->lr_end > rhs.lr_end)) { + return true; + } + return false; + } + + bool operator==(const struct line_range& rhs) const + { + return (this->lr_start == rhs.lr_start && this->lr_end == rhs.lr_end); + } + + const char* substr(const std::string& str) const + { + if (this->lr_start == -1) { + return str.c_str(); + } + return &(str.c_str()[this->lr_start]); + } + + size_t sublen(const std::string& str) const + { + if (this->lr_start == -1) { + return str.length(); + } + if (this->lr_end == -1) { + return str.length() - this->lr_start; + } + return this->length(); + } +}; + +#endif diff --git a/src/base/lnav.console.cc b/src/base/lnav.console.cc index 94251d46..d3a43069 100644 --- a/src/base/lnav.console.cc +++ b/src/base/lnav.console.cc @@ -45,6 +45,45 @@ using namespace lnav::roles::literals; namespace lnav { namespace console { +snippet +snippet::from_content_with_offset(intern_string_t src, + const attr_line_t& content, + 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( + offset, string_fragment::tag1{'\n'}); + 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; + + attr_line_t pointer; + + pointer.append(erroff_in_line, ' ') + .append("^ "_snippet_border) + .append(lnav::roles::error(errmsg)) + .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE)); + + snippet retval; + retval.s_content + = content.subline(line_with_context.sf_begin, + line_with_error.sf_end - line_with_context.sf_begin); + if (line_with_error.sf_end >= retval.s_content.get_string().size()) { + retval.s_content.append("\n"); + } + retval.s_content.append(pointer).append( + content.subline(line_with_error.sf_end, + line_with_context.sf_end - line_with_error.sf_end)); + retval.s_location = source_location{ + src, + static_cast(1 + line_number), + }; + + return retval; +} + user_message user_message::raw(const attr_line_t& al) { diff --git a/src/base/lnav.console.hh b/src/base/lnav.console.hh index ac4c2b09..591093e2 100644 --- a/src/base/lnav.console.hh +++ b/src/base/lnav.console.hh @@ -42,6 +42,11 @@ namespace console { void println(FILE* file, const attr_line_t& al); struct snippet { + static snippet from_content_with_offset(intern_string_t src, + const attr_line_t& content, + size_t offset, + const std::string& errmsg); + static snippet from(intern_string_t src, const attr_line_t& content) { snippet retval; @@ -113,6 +118,26 @@ struct user_message { return *this; } + template + user_message& with_context_snippets(C snippets) + { + this->um_snippets.insert(this->um_snippets.begin(), + std::make_move_iterator(std::begin(snippets)), + std::make_move_iterator(std::end(snippets))); + if (this->um_snippets.size() > 1) { + for (auto iter = this->um_snippets.begin(); + iter != this->um_snippets.end();) + { + if (iter->s_content.empty()) { + iter = this->um_snippets.erase(iter); + } else { + ++iter; + } + } + } + return *this; + } + template user_message& with_snippets(C snippets) { @@ -121,7 +146,8 @@ struct user_message { std::make_move_iterator(std::end(snippets))); if (this->um_snippets.size() > 1) { for (auto iter = this->um_snippets.begin(); - iter != this->um_snippets.end();) { + iter != this->um_snippets.end();) + { if (iter->s_content.empty()) { iter = this->um_snippets.erase(iter); } else { diff --git a/src/command_executor.cc b/src/command_executor.cc index ba2e377e..02726508 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -52,7 +52,6 @@ #include "shlex.hh" #include "sql_util.hh" #include "vtab_module.hh" -#include "yajlpp/json_ptr.hh" using namespace lnav::roles::literals; @@ -409,7 +408,7 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg) log_error("sqlite3_step error code: %d", retcode); auto um = sqlite3_error_to_user_message(lnav_data.ld_db) - .with_snippets(ec.ec_source) + .with_context_snippets(ec.ec_source) .with_note(bound_note); return Err(um); diff --git a/src/file_collection.hh b/src/file_collection.hh index 43d22bee..ce058e28 100644 --- a/src/file_collection.hh +++ b/src/file_collection.hh @@ -40,11 +40,11 @@ #include #include "archive_manager.hh" +#include "base/auto_pid.hh" #include "base/future_util.hh" #include "file_format.hh" #include "logfile_fwd.hh" #include "safe/safe.h" -#include "tailer/tailer.looper.hh" struct tailer_progress { std::string tp_message; diff --git a/src/file_format.cc b/src/file_format.cc index 12582629..caf6381f 100644 --- a/src/file_format.cc +++ b/src/file_format.cc @@ -35,6 +35,7 @@ #include "base/auto_fd.hh" #include "base/fs_util.hh" #include "base/intern_string.hh" +#include "base/lnav_log.hh" #include "config.h" file_format_t @@ -45,13 +46,21 @@ detect_file_format(const ghc::filesystem::path& filename) } file_format_t retval = file_format_t::UNKNOWN; - auto_fd fd; - - if ((fd = lnav::filesystem::openp(filename, O_RDONLY)) != -1) { + auto open_res = lnav::filesystem::open_file(filename, O_RDONLY); + if (open_res.isErr()) { + log_error("unable to open file for format detection: %s -- %s", + filename.c_str(), + open_res.unwrapErr().c_str()); + } else { + auto fd = open_res.unwrap(); uint8_t buffer[32]; - ssize_t rc; + auto rc = read(fd, buffer, sizeof(buffer)); - if ((rc = read(fd, buffer, sizeof(buffer))) > 0) { + if (rc < 0) { + log_error("unable to read file for format detection: %s -- %s", + filename.c_str(), + strerror(errno)); + } else { static auto SQLITE3_HEADER = "SQLite format 3"; auto header_frag = string_fragment::from_bytes(buffer, rc); diff --git a/src/hasher.hh b/src/hasher.hh new file mode 100644 index 00000000..075f7266 --- /dev/null +++ b/src/hasher.hh @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2023, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef lnav_hasher_hh +#define lnav_hasher_hh + +#include + +#include "base/auto_mem.hh" +#include "base/intern_string.hh" +#include "byte_array.hh" +#include "spookyhash/SpookyV2.h" + +class hasher { +public: + using array_t = byte_array<2, uint64_t>; + static constexpr size_t STRING_SIZE = array_t::STRING_SIZE; + + hasher() { this->h_context.Init(0, 0); } + + hasher& update(const std::string& str) + { + this->h_context.Update(str.data(), str.length()); + + return *this; + } + + hasher& update(const string_fragment& str) + { + this->h_context.Update(str.data(), str.length()); + + return *this; + } + + hasher& update(const char* bits, size_t len) + { + this->h_context.Update(bits, len); + + return *this; + } + + hasher& update(int64_t value) + { + value = SPOOKYHASH_LITTLE_ENDIAN_64(value); + this->h_context.Update(&value, sizeof(value)); + + return *this; + } + + array_t to_array() + { + uint64_t h1; + uint64_t h2; + array_t retval; + + this->h_context.Final(&h1, &h2); + *retval.out(0) = SPOOKYHASH_LITTLE_ENDIAN_64(h1); + *retval.out(1) = SPOOKYHASH_LITTLE_ENDIAN_64(h2); + return retval; + } + + void to_string(auto_buffer& buf) + { + array_t bits = this->to_array(); + + bits.to_string(std::back_inserter(buf)); + } + + std::string to_string() + { + array_t bits = this->to_array(); + return bits.to_string(); + } + + std::string to_uuid_string() + { + array_t bits = this->to_array(); + return bits.to_uuid_string(); + } + +private: + SpookyHash h_context; +}; + +#endif diff --git a/src/line_buffer.cc b/src/line_buffer.cc index cdc3609a..b989bc6e 100644 --- a/src/line_buffer.cc +++ b/src/line_buffer.cc @@ -57,8 +57,8 @@ #include "base/math_util.hh" #include "base/paths.hh" #include "fmtlib/fmt/format.h" +#include "hasher.hh" #include "line_buffer.hh" -#include "lnav_util.hh" #include "scn/scn.h" using namespace std::chrono_literals; diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index e1a728bc..aa23ff89 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -57,6 +57,7 @@ #include "db_sub_source.hh" #include "field_overlay_source.hh" #include "fmt/printf.h" +#include "hasher.hh" #include "lnav.indexing.hh" #include "lnav_commands.hh" #include "lnav_config.hh" diff --git a/src/lnav_util.hh b/src/lnav_util.hh index 5edb5372..d3c3eed7 100644 --- a/src/lnav_util.hh +++ b/src/lnav_util.hh @@ -52,12 +52,10 @@ #include "base/intern_string.hh" #include "base/lnav.console.hh" #include "base/result.h" -#include "byte_array.hh" #include "config.h" #include "fmt/format.h" #include "optional.hpp" #include "ptimec.hh" -#include "spookyhash/SpookyV2.h" #if SIZEOF_OFF_T == 8 # define FORMAT_OFF_T "%lld" @@ -67,77 +65,6 @@ # error "off_t has unhandled size..." #endif -class hasher { -public: - using array_t = byte_array<2, uint64_t>; - static constexpr size_t STRING_SIZE = array_t::STRING_SIZE; - - hasher() { this->h_context.Init(0, 0); } - - hasher& update(const std::string& str) - { - this->h_context.Update(str.data(), str.length()); - - return *this; - } - - hasher& update(const string_fragment& str) - { - this->h_context.Update(str.data(), str.length()); - - return *this; - } - - hasher& update(const char* bits, size_t len) - { - this->h_context.Update(bits, len); - - return *this; - } - - hasher& update(int64_t value) - { - value = SPOOKYHASH_LITTLE_ENDIAN_64(value); - this->h_context.Update(&value, sizeof(value)); - - return *this; - } - - array_t to_array() - { - uint64_t h1; - uint64_t h2; - array_t retval; - - this->h_context.Final(&h1, &h2); - *retval.out(0) = SPOOKYHASH_LITTLE_ENDIAN_64(h1); - *retval.out(1) = SPOOKYHASH_LITTLE_ENDIAN_64(h2); - return retval; - } - - void to_string(auto_buffer& buf) - { - array_t bits = this->to_array(); - - bits.to_string(std::back_inserter(buf)); - } - - std::string to_string() - { - array_t bits = this->to_array(); - return bits.to_string(); - } - - std::string to_uuid_string() - { - array_t bits = this->to_array(); - return bits.to_uuid_string(); - } - -private: - SpookyHash h_context; -}; - bool change_to_parent_dir(); bool next_format(const char* const fmt[], int& index, int& locked_index); diff --git a/src/log_vtab_impl.cc b/src/log_vtab_impl.cc index 7325675f..08f37ba4 100644 --- a/src/log_vtab_impl.cc +++ b/src/log_vtab_impl.cc @@ -34,6 +34,7 @@ #include "base/lnav_log.hh" #include "base/string_util.hh" #include "config.h" +#include "hasher.hh" #include "lnav_util.hh" #include "logfile_sub_source.hh" #include "sql_util.hh" diff --git a/src/logfile.cc b/src/logfile.cc index c6b53420..a21d8dbd 100644 --- a/src/logfile.cc +++ b/src/logfile.cc @@ -35,7 +35,6 @@ #include #include -#include #include #include #include @@ -47,6 +46,7 @@ #include "base/injector.hh" #include "base/string_util.hh" #include "config.h" +#include "hasher.hh" #include "lnav_util.hh" #include "log.watch.hh" #include "log_format.hh" diff --git a/src/piper.looper.cc b/src/piper.looper.cc index a9e1e649..22f8b849 100644 --- a/src/piper.looper.cc +++ b/src/piper.looper.cc @@ -39,8 +39,8 @@ #include "base/paths.hh" #include "base/time_util.hh" #include "config.h" +#include "hasher.hh" #include "line_buffer.hh" -#include "lnav_util.hh" #include "piper.looper.cfg.hh" using namespace std::chrono_literals; diff --git a/src/session_data.cc b/src/session_data.cc index 73cb7f86..547264b5 100644 --- a/src/session_data.cc +++ b/src/session_data.cc @@ -46,6 +46,7 @@ #include "base/paths.hh" #include "command_executor.hh" #include "config.h" +#include "hasher.hh" #include "lnav.events.hh" #include "lnav.hh" #include "lnav_util.hh" diff --git a/src/shared_buffer.hh b/src/shared_buffer.hh index 71be2a16..2ee36d58 100644 --- a/src/shared_buffer.hh +++ b/src/shared_buffer.hh @@ -39,10 +39,10 @@ #include #include -#include "base/attr_line.hh" #include "base/auto_mem.hh" #include "base/file_range.hh" #include "base/intern_string.hh" +#include "base/line_range.hh" #include "base/lnav_log.hh" #include "scn/util/string_view.h" diff --git a/src/sql_util.cc b/src/sql_util.cc index 76b3f734..8f80ffc6 100644 --- a/src/sql_util.cc +++ b/src/sql_util.cc @@ -675,7 +675,7 @@ annotate_sql_with_error(sqlite3* db, const char* sql, const char* tail) if (erroff != -1) { auto line_with_error - = string_fragment(retval.get_string()) + = string_fragment::from_str(retval.get_string()) .find_boundaries_around(erroff, string_fragment::tag1{'\n'}); auto erroff_in_line = erroff - line_with_error.sf_begin; diff --git a/src/text_anonymizer.cc b/src/text_anonymizer.cc index 39403bfb..dac40ba0 100644 --- a/src/text_anonymizer.cc +++ b/src/text_anonymizer.cc @@ -38,7 +38,7 @@ #include "data_scanner.hh" #include "diseases-json.h" #include "ghc/filesystem.hpp" -#include "lnav_util.hh" +#include "hasher.hh" #include "pcrepp/pcre2pp.hh" #include "words-json.h" #include "yajlpp/yajlpp_def.hh" diff --git a/src/themes/default-theme.json b/src/themes/default-theme.json index 09e09c3f..3127f291 100644 --- a/src/themes/default-theme.json +++ b/src/themes/default-theme.json @@ -16,8 +16,7 @@ "color": "semantic()" }, "alt-text": { - "color": "Silver", - "bold": true + "background-color": "#262626" }, "ok": { "color": "Green", diff --git a/src/themes/eldar.json b/src/themes/eldar.json index f46b6cf8..b4805332 100644 --- a/src/themes/eldar.json +++ b/src/themes/eldar.json @@ -24,8 +24,6 @@ "background-color": "" }, "alt-text": { - "color": "$white", - "background-color": "", "bold": true }, "ok": { diff --git a/src/themes/grayscale.json b/src/themes/grayscale.json index 4dcbe15b..48adc5b3 100644 --- a/src/themes/grayscale.json +++ b/src/themes/grayscale.json @@ -25,8 +25,6 @@ "background-color": "" }, "alt-text": { - "color": "", - "background-color": "", "bold": true }, "ok": { diff --git a/src/themes/monocai.json b/src/themes/monocai.json index 9f3c50c9..ef9319a7 100644 --- a/src/themes/monocai.json +++ b/src/themes/monocai.json @@ -23,7 +23,6 @@ "background-color": "$black" }, "alt-text": { - "color": "#f6f6f6", "background-color": "#1c1c1c" }, "ok": { diff --git a/src/themes/night-owl.json b/src/themes/night-owl.json index a4210c91..dbddc26a 100644 --- a/src/themes/night-owl.json +++ b/src/themes/night-owl.json @@ -24,9 +24,7 @@ "background-color": "#011627" }, "alt-text": { - "color": "#d6deeb", - "background-color": "#011627", - "bold": true + "background-color": "#1c1c1c" }, "ok": { "color": "$green", diff --git a/src/themes/solarized-dark.json b/src/themes/solarized-dark.json index 1590de04..bad17037 100644 --- a/src/themes/solarized-dark.json +++ b/src/themes/solarized-dark.json @@ -33,9 +33,7 @@ "background-color": "$base03" }, "alt-text": { - "color": "$base0", - "background-color": "$base03", - "bold": true + "background-color": "$base02" }, "ok": { "color": "$green", diff --git a/src/themes/solarized-light.json b/src/themes/solarized-light.json index 3e7c5750..3e686022 100644 --- a/src/themes/solarized-light.json +++ b/src/themes/solarized-light.json @@ -33,9 +33,7 @@ "background-color": "$base3" }, "alt-text": { - "color": "$base00", - "background-color": "$base3", - "bold": true + "background-color": "$base2" }, "ok": { "color": "$green", diff --git a/src/unique_path.cc b/src/unique_path.cc index c0cbc058..8d568cc7 100644 --- a/src/unique_path.cc +++ b/src/unique_path.cc @@ -35,7 +35,7 @@ void unique_path_generator::add_source( const std::shared_ptr& path_source) { - ghc::filesystem::path path = path_source->get_path(); + const auto path = path_source->get_path(); path_source->set_unique_path(path.filename()); path_source->set_path_prefix(path.parent_path()); @@ -53,7 +53,7 @@ unique_path_generator::generate() for (const auto& pair : this->upg_unique_paths) { if (pair.second.size() == 1) { if (loop_count > 0) { - std::shared_ptr src = pair.second[0]; + const auto src = pair.second[0]; src->set_unique_path("[" + src->get_unique_path()); } @@ -67,7 +67,7 @@ unique_path_generator::generate() do { std::string common; - for (auto& src : pair.second) { + for (const auto& src : pair.second) { auto& path = src->get_path_prefix(); if (common.empty()) { @@ -81,7 +81,7 @@ unique_path_generator::generate() } if (all_common) { - for (auto& src : pair.second) { + for (const auto& src : pair.second) { auto& path = src->get_path_prefix(); auto par = path.parent_path(); @@ -103,7 +103,7 @@ unique_path_generator::generate() for (auto& src : collisions) { const auto unique_path = src->get_unique_path(); - auto& prefix = src->get_path_prefix(); + const auto& prefix = src->get_path_prefix(); if (loop_count == 0) { src->set_unique_path(prefix.filename().string() + "]/" @@ -113,7 +113,7 @@ unique_path_generator::generate() + unique_path); } - ghc::filesystem::path parent = prefix.parent_path(); + const auto parent = prefix.parent_path(); src->set_path_prefix(parent); diff --git a/src/vtab_module.cc b/src/vtab_module.cc index 043401a8..31597c76 100644 --- a/src/vtab_module.cc +++ b/src/vtab_module.cc @@ -46,6 +46,13 @@ to_sqlite(sqlite3_context* ctx, const lnav::console::user_message& um) sqlite3_result_error(ctx, errmsg.c_str(), errmsg.size()); } +void +set_vtable_errmsg(sqlite3_vtab* vtab, const lnav::console::user_message& um) +{ + vtab->zErrMsg = sqlite3_mprintf( + "%s%s", sqlitepp::ERROR_PREFIX, lnav::to_json(um).c_str()); +} + lnav::console::user_message sqlite3_error_to_user_message(sqlite3* db) { diff --git a/src/vtab_module.hh b/src/vtab_module.hh index 7ae1ed59..2710856c 100644 --- a/src/vtab_module.hh +++ b/src/vtab_module.hh @@ -218,6 +218,9 @@ struct from_sqlite> { void to_sqlite(sqlite3_context* ctx, const lnav::console::user_message& um); +void set_vtable_errmsg(sqlite3_vtab* vtab, + const lnav::console::user_message& um); + inline void to_sqlite(sqlite3_context* ctx, null_value_t) { diff --git a/src/xpath_vtab.cc b/src/xpath_vtab.cc index f44bde4c..88c824b3 100644 --- a/src/xpath_vtab.cc +++ b/src/xpath_vtab.cc @@ -34,7 +34,6 @@ #include "config.h" #include "pugixml/pugixml.hpp" #include "sql_help.hh" -#include "sql_util.hh" #include "vtab_module.hh" #include "xml_util.hh" #include "yajlpp/yajlpp.hh" @@ -314,21 +313,38 @@ rcFilter(sqlite3_vtab_cursor* pVtabCursor, pCur->c_value.assign(blob, byte_count); auto parse_res = pCur->c_doc.load_string(pCur->c_value.c_str()); if (!parse_res) { - pVtabCursor->pVtab->zErrMsg - = sqlite3_mprintf("Invalid XML document at offset %d: %s", + static const intern_string_t ARG1 = intern_string::lookup("xmldoc"); + + auto attr_xmldoc + = attr_line_t(pCur->c_value) + .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE)); + auto um = lnav::console::user_message::error("Invalid XML document") + .with_reason(parse_res.description()) + .with_snippet( + lnav::console::snippet::from_content_with_offset( + ARG1, + attr_xmldoc, parse_res.offset, - parse_res.description()); + parse_res.description())); + set_vtable_errmsg(pVtabCursor->pVtab, um); return SQLITE_ERROR; } pCur->c_xpath = (const char*) sqlite3_value_text(argv[0]); pCur->c_query = checkout_query(pCur->c_xpath); if (!pCur->c_query) { - auto& res = pCur->c_query.result(); - pVtabCursor->pVtab->zErrMsg - = sqlite3_mprintf("Invalid XPATH expression at offset %d: %s", - res.offset, - res.description()); + static const intern_string_t ARG0 = intern_string::lookup("xpath"); + + const auto& res = pCur->c_query.result(); + auto attr_xpath + = attr_line_t(pCur->c_xpath) + .with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE)); + auto um = lnav::console::user_message::error("Invalid XPath expression") + .with_reason(res.description()) + .with_snippet( + lnav::console::snippet::from_content_with_offset( + ARG0, attr_xpath, res.offset, res.description())); + set_vtable_errmsg(pVtabCursor->pVtab, um); return SQLITE_ERROR; } diff --git a/test/Makefile.am b/test/Makefile.am index 06ec3085..d178afb2 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -272,6 +272,7 @@ dist_noinst_DATA = \ dhcp.pcapng \ dhcp-trunc.pcapng \ expected_help.txt \ + invalid-books.xml \ listview_output.0 \ listview_output.1 \ listview_output.2 \ diff --git a/test/expected/expected.am b/test/expected/expected.am index 24548116..e3c50e4d 100644 --- a/test/expected/expected.am +++ b/test/expected/expected.am @@ -1014,10 +1014,14 @@ EXPECTED_FILES = \ $(srcdir)/%reldir%/test_sql_xml_func.sh_46dfa23e2effabf3fa150c4b871fd8d22b1c834d.out \ $(srcdir)/%reldir%/test_sql_xml_func.sh_4effabf11b59580e5f0727199eb74fba049c0cda.err \ $(srcdir)/%reldir%/test_sql_xml_func.sh_4effabf11b59580e5f0727199eb74fba049c0cda.out \ + $(srcdir)/%reldir%/test_sql_xml_func.sh_81ad7678f080870956db37174bcf1054586cfbad.err \ + $(srcdir)/%reldir%/test_sql_xml_func.sh_81ad7678f080870956db37174bcf1054586cfbad.out \ $(srcdir)/%reldir%/test_sql_xml_func.sh_8912b59d5b515ab1373a3d9bc635ebabacd01dfd.err \ $(srcdir)/%reldir%/test_sql_xml_func.sh_8912b59d5b515ab1373a3d9bc635ebabacd01dfd.out \ $(srcdir)/%reldir%/test_sql_xml_func.sh_b036c73528a446cba46625767517cdac868aba72.err \ $(srcdir)/%reldir%/test_sql_xml_func.sh_b036c73528a446cba46625767517cdac868aba72.out \ + $(srcdir)/%reldir%/test_sql_xml_func.sh_bcbd691bb24c4f7bcb9fe0e035b290815f1c8874.err \ + $(srcdir)/%reldir%/test_sql_xml_func.sh_bcbd691bb24c4f7bcb9fe0e035b290815f1c8874.out \ $(srcdir)/%reldir%/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.err \ $(srcdir)/%reldir%/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.out \ $(srcdir)/%reldir%/test_sql_yaml_func.sh_dc189d02e8979b7ed245d5d750f68b9965984699.err \ diff --git a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out index 0abc6a21..9b29971b 100644 --- a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out +++ b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out @@ -844,11 +844,11 @@ For support questions, email: Example #1 To create a table named 'task_durations' that matches log messages with the pattern 'duration=(?\d+)': - :create-search-table task_durations duration=(?<duration>\d+) + :create-search-table task_durations duration=(?<duration>\d+) -:current-time +:current-time ══════════════════════════════════════════════════════════════════════ Print the current time in human-readable form and seconds since the epoch diff --git a/test/expected/test_sql_xml_func.sh_81ad7678f080870956db37174bcf1054586cfbad.err b/test/expected/test_sql_xml_func.sh_81ad7678f080870956db37174bcf1054586cfbad.err new file mode 100644 index 00000000..3c5a94bd --- /dev/null +++ b/test/expected/test_sql_xml_func.sh_81ad7678f080870956db37174bcf1054586cfbad.err @@ -0,0 +1,11 @@ +✘ error: Invalid XML document + reason: Error parsing element attribute + --> command-option:1 + | ;SELECT * FROM xpath('/catalog', (SELECT content FROM lnav_file LIMIT 1)) + --> xmldoc:35 + |   + |   + |   + |  ^ Error parsing element attribute + |  Corets, Eva  + |  Oberon's Legacy  diff --git a/test/expected/test_sql_xml_func.sh_81ad7678f080870956db37174bcf1054586cfbad.out b/test/expected/test_sql_xml_func.sh_81ad7678f080870956db37174bcf1054586cfbad.out new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_sql_xml_func.sh_b036c73528a446cba46625767517cdac868aba72.err b/test/expected/test_sql_xml_func.sh_b036c73528a446cba46625767517cdac868aba72.err index 6ac0f80c..5ff58ac6 100644 --- a/test/expected/test_sql_xml_func.sh_b036c73528a446cba46625767517cdac868aba72.err +++ b/test/expected/test_sql_xml_func.sh_b036c73528a446cba46625767517cdac868aba72.err @@ -1 +1 @@ -error: sqlite3_exec failed -- Invalid XPATH expression at offset 5: Unrecognized node test +error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"Invalid XPath expression","attrs":[]},"reason":{"str":"Unrecognized node test","attrs":[]},"snippets":[{"source":"xpath","line":1,"content":{"str":"/abc[\n ^ Unrecognized node test","attrs":[{"start":0,"end":5,"type":"role","value":40},{"start":11,"end":13,"type":"role","value":74},{"start":13,"end":35,"type":"role","value":5},{"start":6,"end":35,"type":"role","value":40},{"start":35,"end":35,"type":"role","value":40}]}}],"help":{"str":"","attrs":[]}} diff --git a/test/expected/test_sql_xml_func.sh_bcbd691bb24c4f7bcb9fe0e035b290815f1c8874.err b/test/expected/test_sql_xml_func.sh_bcbd691bb24c4f7bcb9fe0e035b290815f1c8874.err new file mode 100644 index 00000000..d1ff56a7 --- /dev/null +++ b/test/expected/test_sql_xml_func.sh_bcbd691bb24c4f7bcb9fe0e035b290815f1c8874.err @@ -0,0 +1,7 @@ +✘ error: Invalid XPath expression + reason: Expected ']' to match an opening '[' + --> command-option:1 + | ;SELECT * FROM xpath('/cat[alog', (SELECT content FROM lnav_file LIMIT 1)) + --> xpath:1 + | /cat[alog  + |  ^ Expected ']' to match an opening '[' diff --git a/test/expected/test_sql_xml_func.sh_bcbd691bb24c4f7bcb9fe0e035b290815f1c8874.out b/test/expected/test_sql_xml_func.sh_bcbd691bb24c4f7bcb9fe0e035b290815f1c8874.out new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.err b/test/expected/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.err index a97d7cd3..297b56a6 100644 --- a/test/expected/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.err +++ b/test/expected/test_sql_xml_func.sh_fefeb387ae14d4171225ea06cbbff3ec43990cf0.err @@ -1 +1 @@ -error: sqlite3_exec failed -- Invalid XML document at offset 3: Error parsing start element tag +error: sqlite3_exec failed -- lnav-error:{"level":"error","message":{"str":"Invalid XML document","attrs":[]},"reason":{"str":"Error parsing start element tag","attrs":[]},"snippets":[{"source":"xmldoc","line":1,"content":{"str":" + + + Gambardella, Matthew + XML Developer's Guide + Computer + 44.95 + 2000-10-01 + An in-depth look at creating applications + with XML. + + + + Ralls, Kim + Midnight Rain + Fantasy + 5.95 + 2000-12-16 + A former architect battles corporate zombies, + an evil sorceress, and her own childhood to become queen + of the world. + + + + Corets, Eva + Oberon's Legacy + Fantasy + 5.95 + 2001-03-10 + In post-apocalypse England, the mysterious + agent known only as Oberon helps to create a new life + for the inhabitants of London. Sequel to Maeve + Ascendant. + + + + Corets, Eva + The Sundered Grail + Fantasy + 5.95 + 2001-09-10 + The two daughters of Maeve, half-sisters, + battle one another for control of England. Sequel to + Oberon's Legacy. + + + + Randall, Cynthia + Lover Birds + Romance + 4.95 + 2000-09-02 + When Carla meets Paul at an ornithology + conference, tempers fly as feathers get ruffled. + + + + Thurman, Paula + Splish Splash + Romance + 4.95 + 2000-11-02 + A deep sea diver finds true love twenty + thousand leagues beneath the sea. + + + + Knorr, Stefan + Creepy Crawlies + Horror + 4.95 + 2000-12-06 + An anthology of horror stories about roaches, + centipedes, scorpions and other insects. + + + + Kress, Peter + Paradox Lost + Science Fiction + 6.95 + 2000-11-02 + After an inadvertant trip through a Heisenberg + Uncertainty Device, James Salway discovers the problems + of being quantum. + + + + O'Brien, Tim + Microsoft .NET: The Programming Bible + Computer + 36.95 + 2000-12-09 + Microsoft's .NET initiative is explored in + detail in this deep programmer's reference. + + + + O'Brien, Tim + MSXML3: A Comprehensive Guide + Computer + 36.95 + 2000-12-01 + The Microsoft MSXML3 parser is covered in + detail, with attention to XML DOM interfaces, XSLT processing, + SAX and more. + + + + Galos, Mike + Visual Studio 7: A Comprehensive Guide + Computer + 49.95 + 2001-04-16 + Microsoft Visual Studio 7 is explored in depth, + looking at how Visual Basic, Visual C++, C#, and ASP+ are + integrated into a comprehensive development + environment. + + + diff --git a/test/test_sql_xml_func.sh b/test/test_sql_xml_func.sh index 72f1b0cc..51357ebc 100644 --- a/test/test_sql_xml_func.sh +++ b/test/test_sql_xml_func.sh @@ -1,5 +1,7 @@ #! /bin/bash +export YES_COLOR=1 + run_cap_test ./drive_sql "SELECT * FROM xpath('/abc[', '')" run_cap_test ./drive_sql "SELECT * FROM xpath('/abc', '')" run_cap_test ./drive_sql "SELECT * FROM xpath('/abc/def[@a=\"b\"]', 'ghi')" run_cap_test ./drive_sql "SELECT * FROM xpath('/abc/def', 'Hello >')" + +run_cap_test ${lnav_test} -n \ + -c ";SELECT * FROM xpath('/catalog', (SELECT content FROM lnav_file LIMIT 1))" \ + ${test_dir}/invalid-books.xml + +run_cap_test ${lnav_test} -n \ + -c ";SELECT * FROM xpath('/cat[alog', (SELECT content FROM lnav_file LIMIT 1))" \ + ${test_dir}/books.xml