• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tstack / lnav / 11918362462-1766

19 Nov 2024 05:22PM UTC coverage: 70.222% (-0.01%) from 70.232%
11918362462-1766

push

github

tstack
[tests] update expected output

46302 of 65937 relevant lines covered (70.22%)

467211.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.74
/src/document.sections.cc
1
/**
2
 * Copyright (c) 2022, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29

30
#include <algorithm>
31
#include <utility>
32
#include <vector>
33

34
#include "document.sections.hh"
35

36
#include "base/enum_util.hh"
37
#include "base/itertools.hh"
38
#include "base/lnav_log.hh"
39
#include "base/opt_util.hh"
40
#include "data_scanner.hh"
41

42
namespace lnav {
43
namespace document {
44

45
std::optional<hier_node*>
46
hier_node::lookup_child(section_key_t key) const
102✔
47
{
48
    return make_optional_from_nullable(key.match(
204✔
49
        [this](const std::string& str) -> hier_node* {
176✔
50
            auto iter = this->hn_named_children.find(str);
88✔
51
            if (iter != this->hn_named_children.end()) {
88✔
52
                return iter->second;
83✔
53
            }
54

55
            return nullptr;
5✔
56
        },
57
        [this](size_t index) -> hier_node* {
28✔
58
            if (index < this->hn_children.size()) {
14✔
59
                return this->hn_children[index].get();
14✔
60
            }
61
            return nullptr;
×
62
        }));
204✔
63
}
64

65
std::optional<size_t>
66
hier_node::child_index(const hier_node* hn) const
13✔
67
{
68
    size_t retval = 0;
13✔
69

70
    for (const auto& child : this->hn_children) {
13✔
71
        if (child.get() == hn) {
13✔
72
            return retval;
13✔
73
        }
74
        retval += 1;
×
75
    }
76

77
    return std::nullopt;
×
78
}
79

80
std::optional<hier_node::child_neighbors_result>
81
hier_node::child_neighbors(const lnav::document::hier_node* hn,
13✔
82
                           file_off_t offset) const
83
{
84
    auto index_opt = this->child_index(hn);
13✔
85
    if (!index_opt) {
13✔
86
        return std::nullopt;
×
87
    }
88

89
    hier_node::child_neighbors_result retval;
13✔
90

91
    if (index_opt.value() == 0) {
13✔
92
        if (this->hn_parent != nullptr) {
13✔
93
            auto parent_neighbors_opt
94
                = this->hn_parent->child_neighbors(this, offset);
7✔
95

96
            if (parent_neighbors_opt) {
7✔
97
                retval.cnr_previous = parent_neighbors_opt->cnr_previous;
7✔
98
            }
99
        } else {
100
            retval.cnr_previous = hn;
6✔
101
        }
102
    } else {
103
        const auto* prev_hn = this->hn_children[index_opt.value() - 1].get();
×
104

105
        if (hn->hn_line_number == 0
×
106
            || (hn->hn_line_number - prev_hn->hn_line_number) > 1)
×
107
        {
108
            retval.cnr_previous = prev_hn;
×
109
        } else if (this->hn_parent != nullptr) {
×
110
            auto parent_neighbors_opt
111
                = this->hn_parent->child_neighbors(this, offset);
×
112

113
            if (parent_neighbors_opt) {
×
114
                retval.cnr_previous = parent_neighbors_opt->cnr_previous;
×
115
            }
116
        }
117
    }
118

119
    if (index_opt.value() == this->hn_children.size() - 1) {
13✔
120
        if (this->hn_parent != nullptr) {
6✔
121
            auto parent_neighbors_opt
122
                = this->hn_parent->child_neighbors(this, offset);
×
123

124
            if (parent_neighbors_opt) {
×
125
                retval.cnr_next = parent_neighbors_opt->cnr_next;
×
126
            }
127
        } else if (!hn->hn_children.empty()) {
6✔
128
            for (const auto& child : hn->hn_children) {
11✔
129
                if (child->hn_start > offset) {
11✔
130
                    retval.cnr_next = child.get();
6✔
131
                    break;
6✔
132
                }
133
            }
134
        }
135
    } else {
136
        const auto* next_hn = this->hn_children[index_opt.value() + 1].get();
7✔
137

138
        if (next_hn->hn_start > offset
7✔
139
            && (hn->hn_line_number == 0
7✔
140
                || (next_hn->hn_line_number - hn->hn_line_number) > 1))
6✔
141
        {
142
            retval.cnr_next = next_hn;
5✔
143
        } else if (this->hn_parent != nullptr) {
2✔
144
            auto parent_neighbors_opt
145
                = this->hn_parent->child_neighbors(this, offset);
2✔
146

147
            if (parent_neighbors_opt) {
2✔
148
                retval.cnr_next = parent_neighbors_opt->cnr_next;
2✔
149
            }
150
        }
151
    }
152

153
    return retval;
13✔
154
}
155

156
std::optional<hier_node::child_neighbors_result>
157
hier_node::line_neighbors(size_t ln) const
×
158
{
159
    if (this->hn_children.empty()) {
×
160
        return std::nullopt;
×
161
    }
162

163
    hier_node::child_neighbors_result retval;
×
164

165
    for (const auto& child : this->hn_children) {
×
166
        if (child->hn_line_number > ln) {
×
167
            retval.cnr_next = child.get();
×
168
            break;
×
169
        }
170
        retval.cnr_previous = child.get();
×
171
    }
172

173
    return retval;
×
174
}
175

176
std::optional<const hier_node*>
177
hier_node::lookup_path(const hier_node* root,
60✔
178
                       const std::vector<section_key_t>& path)
179
{
180
    auto retval = make_optional_from_nullable(root);
60✔
181

182
    for (const auto& comp : path) {
150✔
183
        if (!retval) {
90✔
184
            break;
×
185
        }
186

187
        retval = retval.value()->lookup_child(comp);
90✔
188
    }
189

190
    if (!retval) {
60✔
191
        return std::nullopt;
×
192
    }
193

194
    return retval;
60✔
195
}
196

197
std::vector<section_key_t>
198
metadata::path_for_range(size_t start, size_t stop)
15✔
199
{
200
    std::vector<section_key_t> retval;
15✔
201

202
    this->m_sections_tree.visit_overlapping(
15✔
203
        start, stop, [&retval](const lnav::document::section_interval_t& iv) {
15✔
204
            retval.emplace_back(iv.value);
20✔
205
        });
20✔
206
    return retval;
15✔
207
}
×
208

209
struct metadata_builder {
210
    std::vector<section_interval_t> mb_intervals;
211
    std::vector<section_type_interval_t> mb_type_intervals;
212
    std::unique_ptr<hier_node> mb_root_node;
213
    std::set<size_t> mb_indents;
214
    text_format_t mb_text_format{text_format_t::TF_UNKNOWN};
215

216
    metadata to_metadata() &&
242✔
217
    {
218
        return {
219
            std::move(this->mb_intervals),
242✔
220
            std::move(this->mb_root_node),
242✔
221
            std::move(this->mb_type_intervals),
242✔
222
            std::move(this->mb_indents),
242✔
223
            this->mb_text_format,
242✔
224
        };
242✔
225
    }
226
};
227

228
static void
229
discover_metadata_int(const attr_line_t& al, metadata_builder& mb)
242✔
230
{
231
    const auto& orig_attrs = al.get_attrs();
242✔
232
    auto headers = orig_attrs
233
        | lnav::itertools::filter_in([](const string_attr& attr) {
242✔
234
                       if (attr.sa_type != &VC_ROLE) {
63,016✔
235
                           return false;
18,580✔
236
                       }
237

238
                       const auto role = attr.sa_value.get<role_t>();
44,436✔
239
                       switch (role) {
44,436✔
240
                           case role_t::VCR_H1:
4,634✔
241
                           case role_t::VCR_H2:
242
                           case role_t::VCR_H3:
243
                           case role_t::VCR_H4:
244
                           case role_t::VCR_H5:
245
                           case role_t::VCR_H6:
246
                               return true;
4,634✔
247
                           default:
39,802✔
248
                               return false;
39,802✔
249
                       }
250
                   })
251
        | lnav::itertools::sort_by(&string_attr::sa_range);
484✔
252

253
    // Remove headers from quoted text
254
    for (const auto& orig_attr : orig_attrs) {
63,258✔
255
        if (orig_attr.sa_type == &VC_ROLE
126,032✔
256
            && orig_attr.sa_value.get<role_t>() == role_t::VCR_QUOTED_TEXT)
63,016✔
257
        {
258
            remove_string_attr(headers, orig_attr.sa_range);
59✔
259
        }
260
    }
261

262
    auto& intervals = mb.mb_intervals;
242✔
263

264
    struct open_interval_t {
265
        open_interval_t(uint32_t level, file_off_t start, section_key_t id)
4,620✔
266
            : oi_level(level), oi_start(start), oi_id(std::move(id))
4,620✔
267
        {
268
        }
4,620✔
269

270
        int32_t oi_level;
271
        file_off_t oi_start;
272
        section_key_t oi_id;
273
        std::unique_ptr<hier_node> oi_node{std::make_unique<hier_node>()};
274
    };
275
    std::vector<open_interval_t> open_intervals;
242✔
276
    auto root_node = std::make_unique<hier_node>();
242✔
277

278
    for (const auto& hdr_attr : headers) {
4,876✔
279
        const auto role = hdr_attr.sa_value.get<role_t>();
4,634✔
280
        auto role_num = lnav::enums::to_underlying(role)
4,634✔
281
            - lnav::enums::to_underlying(role_t::VCR_H1);
4,634✔
282
        std::vector<open_interval_t> new_open_intervals;
4,634✔
283

284
        for (auto& oi : open_intervals) {
20,887✔
285
            if (oi.oi_level >= role_num) {
16,253✔
286
                // close out this section
287
                auto sf = string_fragment::from_str(al.get_string());
4,533✔
288
                auto left_sf = sf.find_left_boundary(
4,533✔
289
                    hdr_attr.sa_range.lr_start, string_fragment::tag1{'\n'});
4,533✔
290
                if (left_sf.sf_begin > 0) {
4,533✔
291
                    left_sf.sf_begin -= 1;
4,533✔
292
                }
293
                intervals.emplace_back(oi.oi_start, left_sf.sf_begin, oi.oi_id);
4,533✔
294
                auto* node_ptr = oi.oi_node.get();
4,533✔
295
                auto* parent_node = oi.oi_node->hn_parent;
4,533✔
296
                if (parent_node != nullptr) {
4,533✔
297
                    parent_node->hn_children.emplace_back(
4,533✔
298
                        std::move(oi.oi_node));
4,533✔
299
                    parent_node->hn_named_children.insert({
4,533✔
300
                        oi.oi_id.get<std::string>(),
301
                        node_ptr,
302
                    });
303
                }
304
            } else {
305
                new_open_intervals.emplace_back(std::move(oi));
11,720✔
306
            }
307
        }
308
        if (!hdr_attr.sa_range.empty()) {
4,634✔
309
            auto* parent_node = new_open_intervals.empty()
4,620✔
310
                ? root_node.get()
4,620✔
311
                : new_open_intervals.back().oi_node.get();
4,463✔
312
            auto sf = string_fragment::from_str(al.get_string());
4,620✔
313
            auto left_sf = sf.find_left_boundary(hdr_attr.sa_range.lr_start,
4,620✔
314
                                                 string_fragment::tag1{'\n'});
4,620✔
315
            new_open_intervals.emplace_back(
4,620✔
316
                role_num,
317
                left_sf.sf_begin,
318
                al.get_substring(hdr_attr.sa_range));
9,240✔
319
            new_open_intervals.back().oi_node->hn_parent = parent_node;
4,620✔
320
            new_open_intervals.back().oi_node->hn_start = left_sf.sf_begin;
4,620✔
321
        }
322
        open_intervals = std::move(new_open_intervals);
4,634✔
323
    }
4,634✔
324

325
    for (auto& oi : open_intervals) {
329✔
326
        // close out this section
327
        intervals.emplace_back(oi.oi_start, al.length(), oi.oi_id);
87✔
328
        auto* node_ptr = oi.oi_node.get();
87✔
329
        auto* parent_node = oi.oi_node->hn_parent;
87✔
330
        if (parent_node == nullptr) {
87✔
331
            root_node = std::move(oi.oi_node);
×
332
        } else {
333
            parent_node->hn_children.emplace_back(std::move(oi.oi_node));
87✔
334
            parent_node->hn_named_children.insert({
87✔
335
                oi.oi_id.get<std::string>(),
336
                node_ptr,
337
            });
338
        }
339
    }
340

341
    for (auto& interval : intervals) {
5,604✔
342
        auto start_off_iter = find_string_attr_containing(
5,362✔
343
            orig_attrs, &SA_ORIGIN_OFFSET, interval.start);
344
        if (start_off_iter != orig_attrs.end()) {
5,362✔
345
            interval.start += start_off_iter->sa_value.get<int64_t>();
20✔
346
        }
347
        auto stop_off_iter = find_string_attr_containing(
5,362✔
348
            orig_attrs, &SA_ORIGIN_OFFSET, interval.stop - 1);
5,362✔
349
        if (stop_off_iter != orig_attrs.end()) {
5,362✔
350
            interval.stop += stop_off_iter->sa_value.get<int64_t>();
20✔
351
        }
352
    }
353
    for (auto& interval : mb.mb_type_intervals) {
265✔
354
        auto start_off_iter = find_string_attr_containing(
23✔
355
            orig_attrs, &SA_ORIGIN_OFFSET, interval.start);
356
        if (start_off_iter != orig_attrs.end()) {
23✔
357
            interval.start += start_off_iter->sa_value.get<int64_t>();
×
358
        }
359
        auto stop_off_iter = find_string_attr_containing(
23✔
360
            orig_attrs, &SA_ORIGIN_OFFSET, interval.stop - 1);
23✔
361
        if (stop_off_iter != orig_attrs.end()) {
23✔
362
            interval.stop += stop_off_iter->sa_value.get<int64_t>();
×
363
        }
364
    }
365

366
    hier_node::depth_first(root_node.get(), [&orig_attrs](hier_node* node) {
242✔
367
        auto off_opt
368
            = get_string_attr(orig_attrs, &SA_ORIGIN_OFFSET, node->hn_start);
4,862✔
369

370
        if (off_opt) {
4,862✔
371
            node->hn_start += off_opt.value()->sa_value.get<int64_t>();
407✔
372
        }
373
    });
4,862✔
374

375
    hier_node::depth_first(
242✔
376
        mb.mb_root_node.get(), [&orig_attrs](hier_node* node) {
812✔
377
            auto off_opt = get_string_attr(
812✔
378
                orig_attrs, &SA_ORIGIN_OFFSET, node->hn_start);
812✔
379

380
            if (off_opt) {
812✔
381
                node->hn_start += off_opt.value()->sa_value.get<int64_t>();
20✔
382
            }
383
        });
812✔
384

385
    if (!root_node->hn_children.empty()
242✔
386
        || !root_node->hn_named_children.empty())
242✔
387
    {
388
        mb.mb_root_node = std::move(root_node);
48✔
389
    }
390
}
242✔
391

392
metadata
393
discover_metadata(const attr_line_t& al)
138✔
394
{
395
    metadata_builder mb;
138✔
396

397
    discover_metadata_int(al, mb);
138✔
398

399
    return std::move(mb).to_metadata();
276✔
400
}
138✔
401

402
class structure_walker {
403
public:
404
    explicit structure_walker(attr_line_t& al, line_range lr, text_format_t tf)
104✔
405
        : sw_line(al), sw_range(lr), sw_text_format(tf),
104✔
406
          sw_scanner(string_fragment::from_str_range(
104✔
407
              al.get_string(), lr.lr_start, lr.lr_end))
208✔
408
    {
409
        this->sw_interval_state.resize(1);
104✔
410
        this->sw_hier_nodes.push_back(std::make_unique<hier_node>());
104✔
411
    }
104✔
412

413
    bool is_structured_text() const
4,996✔
414
    {
415
        switch (this->sw_text_format) {
4,996✔
416
            case text_format_t::TF_JSON:
544✔
417
            case text_format_t::TF_YAML:
418
            case text_format_t::TF_TOML:
419
            case text_format_t::TF_LOG:
420
            case text_format_t::TF_UNKNOWN:
421
                return true;
544✔
422
            default:
4,452✔
423
                return false;
4,452✔
424
        }
425
    }
426

427
    metadata walk()
104✔
428
    {
429
        metadata_builder mb;
104✔
430

431
        mb.mb_text_format = this->sw_text_format;
104✔
432
        while (true) {
433
            auto tokenize_res
434
                = this->sw_scanner.tokenize2(this->sw_text_format);
81,644✔
435
            if (!tokenize_res) {
81,644✔
436
                break;
104✔
437
            }
438

439
            auto dt = tokenize_res->tr_token;
81,540✔
440

441
            element el(dt, tokenize_res->tr_capture);
81,540✔
442
            const auto& inner_cap = tokenize_res->tr_inner_capture;
81,540✔
443

444
#if 0
445
            printf("tok %s %s\n",
446
                   data_scanner::token2name(dt),
447
                   tokenize_res->to_string().c_str());
448
#endif
449
            if (dt != DT_WHITE) {
81,540✔
450
                this->sw_at_start = false;
48,435✔
451
            }
452
            switch (dt) {
81,540✔
453
                case DT_XML_DECL_TAG:
22✔
454
                case DT_XML_EMPTY_TAG:
455
                    this->sw_values.emplace_back(el);
22✔
456
                    break;
22✔
457
                case DT_COMMENT:
18✔
458
                    this->sw_type_intervals.emplace_back(
18✔
459
                        el.e_capture.c_begin,
460
                        el.e_capture.c_end,
461
                        section_types_t::comment);
18✔
462
                    this->sw_line.get_attrs().emplace_back(
36✔
463
                        line_range{
×
464
                            this->sw_range.lr_start + el.e_capture.c_begin,
18✔
465
                            this->sw_range.lr_start + el.e_capture.c_end,
18✔
466
                        },
467
                        VC_ROLE.value(role_t::VCR_COMMENT));
36✔
468
                    break;
18✔
469
                case DT_XML_OPEN_TAG:
324✔
470
                    this->flush_values();
324✔
471
                    this->sw_interval_state.back().is_start
324✔
472
                        = el.e_capture.c_begin;
324✔
473
                    this->sw_interval_state.back().is_line_number
324✔
474
                        = this->sw_line_number;
324✔
475
                    this->sw_interval_state.back().is_name
324✔
476
                        = tokenize_res->to_string_fragment()
324✔
477
                              .to_unquoted_string();
648✔
478
                    this->sw_depth += 1;
324✔
479
                    this->sw_interval_state.resize(this->sw_depth + 1);
324✔
480
                    this->sw_hier_nodes.push_back(
324✔
481
                        std::make_unique<hier_node>());
648✔
482
                    this->sw_container_tokens.push_back(to_closer(dt));
324✔
483
                    break;
324✔
484
                case DT_XML_CLOSE_TAG: {
317✔
485
                    auto term = this->flush_values();
317✔
486
                    if (this->sw_depth > 0
634✔
487
                        && !this->sw_container_tokens.empty())
317✔
488
                    {
489
                        auto found = false;
317✔
490
                        do {
491
                            if (this->sw_container_tokens.back() == dt) {
317✔
492
                                found = true;
317✔
493
                            }
494
                            if (term) {
317✔
495
                                this->append_child_node(term);
258✔
496
                                term = std::nullopt;
258✔
497
                            }
498
                            this->sw_interval_state.pop_back();
317✔
499
                            this->sw_hier_stage
500
                                = std::move(this->sw_hier_nodes.back());
317✔
501
                            this->sw_hier_nodes.pop_back();
317✔
502
                            this->sw_container_tokens.pop_back();
317✔
503
                        } while (!found && !this->sw_container_tokens.empty());
317✔
504
                    }
505
                    this->append_child_node(el.e_capture);
317✔
506
                    if (this->sw_depth > 0) {
317✔
507
                        this->sw_depth -= 1;
317✔
508
                    }
509
                    this->flush_values();
317✔
510
                    break;
317✔
511
                }
512
                case DT_H1: {
116✔
513
                    this->sw_line.get_attrs().emplace_back(
232✔
514
                        line_range{
×
515
                            this->sw_range.lr_start + inner_cap.c_begin,
116✔
516
                            this->sw_range.lr_start + inner_cap.c_end,
116✔
517
                        },
518
                        VC_ROLE.value(role_t::VCR_H1));
232✔
519
                    this->sw_line_number += 1;
116✔
520
                    break;
116✔
521
                }
522
                case DT_DIFF_FILE_HEADER: {
5✔
523
                    this->drop_open_children();
5✔
524

525
                    auto sf = this->sw_scanner.to_string_fragment(inner_cap);
5✔
526
                    auto split_res = sf.split_pair(string_fragment::tag1{'\n'});
5✔
527
                    auto file1 = split_res->first.consume_n(4).value();
5✔
528
                    auto file2 = split_res->second.consume_n(4).value();
5✔
529
                    if ((file1 == "/dev/null" || file1.startswith("a/"))
9✔
530
                        && file2.startswith("b/"))
9✔
531
                    {
532
                        if (file1 != "/dev/null") {
5✔
533
                            file1 = file1.consume_n(2).value();
4✔
534
                        }
535
                        file2 = file2.consume_n(2).value();
5✔
536
                    }
537
                    this->sw_line.get_attrs().emplace_back(
10✔
538
                        line_range{
×
539
                            this->sw_range.lr_start
5✔
540
                                + tokenize_res->tr_capture.c_begin,
5✔
541
                            this->sw_range.lr_start
5✔
542
                                + tokenize_res->tr_capture.c_begin,
5✔
543
                        },
544
                        VC_ROLE.value(role_t::VCR_H1));
10✔
545
                    if (file1 == "/dev/null" || file1 == file2) {
5✔
546
                        this->sw_line.get_attrs().emplace_back(
10✔
547
                            line_range{
×
548
                                this->sw_range.lr_start + file2.sf_begin,
5✔
549
                                this->sw_range.lr_start + file2.sf_end,
5✔
550
                            },
551
                            VC_ROLE.value(role_t::VCR_H1));
10✔
552
                    } else {
553
                        this->sw_line.get_attrs().emplace_back(
×
554
                            line_range{
×
555
                                this->sw_range.lr_start + inner_cap.c_begin,
×
556
                                this->sw_range.lr_start + inner_cap.c_end,
×
557
                            },
558
                            VC_ROLE.value(role_t::VCR_H1));
×
559
                    }
560
                    this->sw_line_number += 2;
5✔
561
                    break;
5✔
562
                }
563
                case DT_DIFF_HUNK_HEADING: {
3✔
564
                    this->drop_open_children();
3✔
565
                    this->sw_line.get_attrs().emplace_back(
6✔
566
                        line_range{
×
567
                            this->sw_range.lr_start
3✔
568
                                + tokenize_res->tr_capture.c_begin,
3✔
569
                            this->sw_range.lr_start
3✔
570
                                + tokenize_res->tr_capture.c_begin,
3✔
571
                        },
572
                        VC_ROLE.value(role_t::VCR_H2));
6✔
573
                    this->sw_line.get_attrs().emplace_back(
6✔
574
                        line_range{
×
575
                            this->sw_range.lr_start + inner_cap.c_begin,
3✔
576
                            this->sw_range.lr_start + inner_cap.c_end,
3✔
577
                        },
578
                        VC_ROLE.value(role_t::VCR_H2));
6✔
579
                    this->sw_line_number += 1;
3✔
580
                    break;
3✔
581
                }
582
                case DT_LCURLY:
1,029✔
583
                case DT_LSQUARE:
584
                case DT_LPAREN: {
585
                    if (this->is_structured_text()) {
1,029✔
586
                        this->flush_values();
158✔
587
                        // this->append_child_node(term);
588
                        this->sw_depth += 1;
158✔
589
                        this->sw_interval_state.back().is_start
158✔
590
                            = el.e_capture.c_begin;
158✔
591
                        this->sw_interval_state.back().is_line_number
158✔
592
                            = this->sw_line_number;
158✔
593
                        this->sw_interval_state.resize(this->sw_depth + 1);
158✔
594
                        this->sw_hier_nodes.push_back(
158✔
595
                            std::make_unique<hier_node>());
316✔
596
                        this->sw_container_tokens.push_back(to_closer(dt));
158✔
597
                    } else {
598
                        this->sw_values.emplace_back(el);
871✔
599
                    }
600
                    break;
1,029✔
601
                }
602
                case DT_RCURLY:
1,115✔
603
                case DT_RSQUARE:
604
                case DT_RPAREN:
605
                    if (this->is_structured_text()
1,115✔
606
                        && !this->sw_container_tokens.empty()
160✔
607
                        && std::find(this->sw_container_tokens.begin(),
1,435✔
608
                                     this->sw_container_tokens.end(),
609
                                     dt)
610
                            != this->sw_container_tokens.end())
1,435✔
611
                    {
612
                        auto term = this->flush_values();
158✔
613
                        if (this->sw_depth > 0) {
158✔
614
                            auto found = false;
158✔
615
                            do {
616
                                if (this->sw_container_tokens.back() == dt) {
164✔
617
                                    found = true;
158✔
618
                                }
619
                                this->append_child_node(term);
164✔
620
                                term = std::nullopt;
164✔
621
                                this->sw_depth -= 1;
164✔
622
                                this->sw_interval_state.pop_back();
164✔
623
                                this->sw_hier_stage
624
                                    = std::move(this->sw_hier_nodes.back());
164✔
625
                                this->sw_hier_nodes.pop_back();
164✔
626
                                if (this->sw_interval_state.back().is_start) {
164✔
627
                                    data_scanner::capture_t obj_cap = {
628
                                        static_cast<int>(
629
                                            this->sw_interval_state.back()
164✔
630
                                                .is_start.value()),
164✔
631
                                        el.e_capture.c_end,
632
                                    };
164✔
633

634
                                    auto sf
635
                                        = this->sw_scanner.to_string_fragment(
164✔
636
                                            obj_cap);
637
                                    if (!sf.find('\n')) {
164✔
638
                                        this->sw_hier_stage->hn_named_children
101✔
639
                                            .clear();
101✔
640
                                        this->sw_hier_stage->hn_children
101✔
641
                                            .clear();
101✔
642
                                        while (
101✔
643
                                            !this->sw_intervals.empty()
183✔
644
                                            && this->sw_intervals.back().start
310✔
645
                                                > obj_cap.c_begin)
127✔
646
                                        {
647
                                            this->sw_intervals.pop_back();
82✔
648
                                        }
649
                                    }
650
                                }
651
                                this->sw_container_tokens.pop_back();
164✔
652
                            } while (!found);
164✔
653
                        }
654
                    }
655
                    this->sw_values.emplace_back(el);
1,115✔
656
                    break;
1,115✔
657
                case DT_COMMA:
2,852✔
658
                    if (this->is_structured_text()) {
2,852✔
659
                        if (this->sw_depth > 0) {
226✔
660
                            auto term = this->flush_values();
181✔
661
                            this->append_child_node(term);
181✔
662
                        }
663
                    } else {
664
                        this->sw_values.emplace_back(el);
2,626✔
665
                    }
666
                    break;
2,852✔
667
                case DT_LINE:
5,562✔
668
                    this->sw_line_number += 1;
5,562✔
669
                    this->sw_at_start = true;
5,562✔
670
                    break;
5,562✔
671
                case DT_WHITE:
33,105✔
672
                    if (this->sw_at_start) {
33,105✔
673
                        size_t indent_size = 0;
3,289✔
674

675
                        for (auto ch : tokenize_res->to_string_fragment()) {
14,766✔
676
                            if (ch == '\t') {
11,477✔
677
                                do {
678
                                    indent_size += 1;
29✔
679
                                } while (indent_size % 8);
29✔
680
                            } else {
681
                                indent_size += 1;
11,473✔
682
                            }
683
                        }
684
                        this->sw_indents.insert(indent_size);
3,289✔
685
                        this->sw_at_start = false;
3,289✔
686
                    }
687
                    break;
33,105✔
688
                case DT_ZERO_WIDTH_SPACE:
×
689
                    break;
×
690
                default:
37,072✔
691
                    if (dt == DT_QUOTED_STRING) {
37,072✔
692
                        auto quoted_sf = tokenize_res->to_string_fragment();
1,056✔
693

694
                        if (quoted_sf.find('\n')) {
1,056✔
695
                            this->sw_type_intervals.emplace_back(
5✔
696
                                el.e_capture.c_begin,
697
                                el.e_capture.c_end,
698
                                section_types_t::multiline_string);
5✔
699
                            this->sw_line.get_attrs().emplace_back(
10✔
700
                                line_range{
×
701
                                    this->sw_range.lr_start
5✔
702
                                        + el.e_capture.c_begin,
5✔
703
                                    this->sw_range.lr_start
5✔
704
                                        + el.e_capture.c_end,
5✔
705
                                },
706
                                VC_ROLE.value(role_t::VCR_STRING));
10✔
707
                        }
708
                    }
709
                    this->sw_values.emplace_back(el);
37,072✔
710
                    break;
37,072✔
711
            }
712
        }
81,540✔
713
        this->flush_values();
104✔
714

715
        if (this->sw_hier_stage != nullptr) {
104✔
716
            this->sw_hier_stage->hn_parent = this->sw_hier_nodes.back().get();
29✔
717
            this->sw_hier_nodes.back()->hn_children.push_back(
58✔
718
                std::move(this->sw_hier_stage));
29✔
719
        }
720
        this->sw_hier_stage = std::move(this->sw_hier_nodes.back());
104✔
721
        this->sw_hier_nodes.pop_back();
104✔
722
        if (this->sw_hier_stage->hn_children.size() == 1
104✔
723
            && this->sw_hier_stage->hn_named_children.empty())
104✔
724
        {
725
            this->sw_hier_stage
726
                = std::move(this->sw_hier_stage->hn_children.front());
28✔
727
            this->sw_hier_stage->hn_parent = nullptr;
28✔
728
        }
729

730
        if (!this->sw_indents.empty()) {
104✔
731
            auto low_indent_iter = this->sw_indents.begin();
52✔
732

733
            if (*low_indent_iter == 1) {
52✔
734
                // adding guides for small indents is noisy, drop for now
735
                this->sw_indents.clear();
13✔
736
            } else {
737
                auto lcm = *low_indent_iter;
39✔
738

739
                for (auto indent_iter = this->sw_indents.begin();
39✔
740
                     indent_iter != this->sw_indents.end();)
121✔
741
                {
742
                    if ((*indent_iter % lcm) == 0) {
82✔
743
                        ++indent_iter;
75✔
744
                    } else {
745
                        indent_iter = this->sw_indents.erase(indent_iter);
7✔
746
                    }
747
                }
748
            }
749
        }
750

751
        mb.mb_root_node = std::move(this->sw_hier_stage);
104✔
752
        mb.mb_intervals = std::move(this->sw_intervals);
104✔
753
        mb.mb_type_intervals = std::move(this->sw_type_intervals);
104✔
754
        mb.mb_indents = std::move(this->sw_indents);
104✔
755

756
        discover_metadata_int(this->sw_line, mb);
104✔
757

758
        return std::move(mb).to_metadata();
208✔
759
    }
104✔
760

761
private:
762
    struct element {
763
        element(data_token_t token, data_scanner::capture_t& cap)
81,540✔
764
            : e_token(token), e_capture(cap)
81,540✔
765
        {
766
        }
81,540✔
767

768
        data_token_t e_token;
769
        data_scanner::capture_t e_capture;
770
    };
771

772
    struct interval_state {
773
        std::optional<file_off_t> is_start;
774
        size_t is_line_number{0};
775
        std::string is_name;
776
    };
777

778
    void drop_open_children()
8✔
779
    {
780
        while (this->sw_depth > 0) {
8✔
781
            this->sw_depth -= 1;
×
782
            this->sw_interval_state.pop_back();
×
783
            this->sw_hier_nodes.pop_back();
×
784
            this->sw_container_tokens.pop_back();
×
785
        }
786
    }
8✔
787

788
    std::optional<data_scanner::capture_t> flush_values()
1,559✔
789
    {
790
        std::optional<data_scanner::capture_t> last_key;
1,559✔
791
        std::optional<data_scanner::capture_t> retval;
1,559✔
792

793
        if (!this->sw_values.empty()) {
1,559✔
794
            if (!this->sw_interval_state.back().is_start) {
815✔
795
                this->sw_interval_state.back().is_start
657✔
796
                    = this->sw_values.front().e_capture.c_begin;
657✔
797
                this->sw_interval_state.back().is_line_number
657✔
798
                    = this->sw_line_number;
657✔
799
            }
800
            retval = this->sw_values.back().e_capture;
815✔
801
        }
802
        for (const auto& el : this->sw_values) {
43,265✔
803
            switch (el.e_token) {
41,706✔
804
                case DT_SYMBOL:
30,427✔
805
                case DT_CONSTANT:
806
                case DT_WORD:
807
                case DT_QUOTED_STRING:
808
                    last_key = el.e_capture;
30,427✔
809
                    break;
30,427✔
810
                case DT_COLON:
557✔
811
                case DT_EQUALS:
812
                    if (last_key) {
557✔
813
                        this->sw_interval_state.back().is_name
523✔
814
                            = this->sw_scanner
815
                                  .to_string_fragment(last_key.value())
523✔
816
                                  .to_unquoted_string();
1,046✔
817
                        if (!this->sw_interval_state.back().is_name.empty()) {
523✔
818
                            this->sw_interval_state.back().is_start
523✔
819
                                = static_cast<ssize_t>(
1,046✔
820
                                    last_key.value().c_begin);
523✔
821
                            this->sw_interval_state.back().is_line_number
523✔
822
                                = this->sw_line_number;
523✔
823
                        }
824
                        last_key = std::nullopt;
523✔
825
                    }
826
                    break;
557✔
827
                default:
10,722✔
828
                    break;
10,722✔
829
            }
830
        }
831

832
        this->sw_values.clear();
1,559✔
833

834
        return retval;
1,559✔
835
    }
836

837
    void append_child_node(std::optional<data_scanner::capture_t> terminator)
920✔
838
    {
839
        auto& ivstate = this->sw_interval_state.back();
920✔
840
        if (!ivstate.is_start || !terminator || this->sw_depth == 0) {
920✔
841
            ivstate.is_start = std::nullopt;
10✔
842
            ivstate.is_line_number = 0;
10✔
843
            ivstate.is_name.clear();
10✔
844
            return;
10✔
845
        }
846

847
        auto new_node = this->sw_hier_stage != nullptr
910✔
848
            ? std::move(this->sw_hier_stage)
445✔
849
            : std::make_unique<lnav::document::hier_node>();
1,355✔
850
        auto iv_start = ivstate.is_start.value();
910✔
851
        auto iv_stop = static_cast<ssize_t>(terminator.value().c_end);
910✔
852
        auto* top_node = this->sw_hier_nodes.back().get();
910✔
853
        auto new_key = ivstate.is_name.empty()
910✔
854
            ? lnav::document::section_key_t{top_node->hn_children.size()}
401✔
855
            : lnav::document::section_key_t{ivstate.is_name};
1,311✔
856
        auto* retval = new_node.get();
910✔
857
        new_node->hn_parent = top_node;
910✔
858
        new_node->hn_start = iv_start;
910✔
859
        new_node->hn_line_number = ivstate.is_line_number;
910✔
860
        if (this->sw_depth == 1
1,820✔
861
            || new_node->hn_line_number != top_node->hn_line_number)
910✔
862
        {
863
            this->sw_intervals.emplace_back(iv_start, iv_stop, new_key);
824✔
864
            if (!ivstate.is_name.empty()) {
824✔
865
                top_node->hn_named_children.insert({
442✔
866
                    ivstate.is_name,
442✔
867
                    retval,
868
                });
869
            }
870
            top_node->hn_children.emplace_back(std::move(new_node));
824✔
871
        }
872
        ivstate.is_start = std::nullopt;
910✔
873
        ivstate.is_line_number = 0;
910✔
874
        ivstate.is_name.clear();
910✔
875
    }
910✔
876

877
    attr_line_t& sw_line;
878
    line_range sw_range;
879
    text_format_t sw_text_format;
880
    data_scanner sw_scanner;
881
    int sw_depth{0};
882
    size_t sw_line_number{0};
883
    bool sw_at_start{true};
884
    std::set<size_t> sw_indents;
885
    std::vector<element> sw_values{};
886
    std::vector<data_token_t> sw_container_tokens;
887
    std::vector<interval_state> sw_interval_state;
888
    std::vector<lnav::document::section_interval_t> sw_intervals;
889
    std::vector<lnav::document::section_type_interval_t> sw_type_intervals;
890
    std::vector<std::unique_ptr<lnav::document::hier_node>> sw_hier_nodes;
891
    std::unique_ptr<lnav::document::hier_node> sw_hier_stage;
892
};
893

894
metadata
895
discover_structure(attr_line_t& al, struct line_range lr, text_format_t tf)
104✔
896
{
897
    return structure_walker(al, lr, tf).walk();
208✔
898
}
899

900
std::vector<breadcrumb::possibility>
901
metadata::possibility_provider(const std::vector<section_key_t>& path)
16✔
902
{
903
    std::vector<breadcrumb::possibility> retval;
16✔
904
    auto curr_node = lnav::document::hier_node::lookup_path(
16✔
905
        this->m_sections_root.get(), path);
16✔
906
    if (curr_node) {
16✔
907
        auto* parent_node = curr_node.value()->hn_parent;
16✔
908

909
        if (parent_node != nullptr) {
16✔
910
            for (const auto& sibling : parent_node->hn_named_children) {
69✔
911
                retval.emplace_back(sibling.first);
53✔
912
            }
913
        }
914
    }
915
    return retval;
32✔
916
}
×
917

918
}  // namespace document
919
}  // namespace lnav
920

921
namespace fmt {
922
auto
923
formatter<lnav::document::section_key_t>::format(
6✔
924
    const lnav::document::section_key_t& key, fmt::format_context& ctx)
925
    -> decltype(ctx.out()) const
926
{
927
    return key.match(
12✔
928
        [this, &ctx](const std::string& str) {
4✔
929
            return formatter<string_view>::format(str, ctx);
8✔
930
        },
931
        [&ctx](size_t index) {
4✔
932
            return format_to(ctx.out(), FMT_STRING("{}"), index);
10✔
933
        });
2✔
934
}
2✔
935

12✔
936
auto
937
formatter<std::vector<lnav::document::section_key_t>>::format(
938
    const std::vector<lnav::document::section_key_t>& path,
939
    fmt::format_context& ctx) -> decltype(ctx.out()) const
2✔
940
{
941
    for (const auto& part : path) {
942
        format_to(ctx.out(), FMT_STRING("\uff1a"));
943
        format_to(ctx.out(), FMT_STRING("{}"), part);
8✔
944
    }
24✔
945
    return ctx.out();
6✔
946
}
6✔
947
}  // namespace fmt
30✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc