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

tstack / lnav / 11872214087-1756

16 Nov 2024 06:12PM UTC coverage: 70.243% (+0.5%) from 69.712%
11872214087-1756

push

github

tstack
[build] disable regex101

46266 of 65866 relevant lines covered (70.24%)

467515.63 hits per line

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

91.96
/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
92✔
47
{
48
    return make_optional_from_nullable(key.match(
184✔
49
        [this](const std::string& str) -> hier_node* {
156✔
50
            auto iter = this->hn_named_children.find(str);
78✔
51
            if (iter != this->hn_named_children.end()) {
78✔
52
                return iter->second;
74✔
53
            }
54

55
            return nullptr;
4✔
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
        }));
184✔
63
}
64

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

70
    for (const auto& child : this->hn_children) {
11✔
71
        if (child.get() == hn) {
11✔
72
            return retval;
11✔
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,
11✔
82
                           file_off_t offset) const
83
{
84
    auto index_opt = this->child_index(hn);
11✔
85
    if (!index_opt) {
11✔
86
        return std::nullopt;
×
87
    }
88

89
    hier_node::child_neighbors_result retval;
11✔
90

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

96
            if (parent_neighbors_opt) {
6✔
97
                retval.cnr_previous = parent_neighbors_opt->cnr_previous;
6✔
98
            }
99
        } else {
100
            retval.cnr_previous = hn;
5✔
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) {
11✔
120
        if (this->hn_parent != nullptr) {
5✔
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()) {
5✔
128
            for (const auto& child : hn->hn_children) {
9✔
129
                if (child->hn_start > offset) {
9✔
130
                    retval.cnr_next = child.get();
5✔
131
                    break;
5✔
132
                }
133
            }
134
        }
135
    } else {
136
        const auto* next_hn = this->hn_children[index_opt.value() + 1].get();
6✔
137

138
        if (next_hn->hn_start > offset
6✔
139
            && (hn->hn_line_number == 0
6✔
140
                || (next_hn->hn_line_number - hn->hn_line_number) > 1))
6✔
141
        {
142
            retval.cnr_next = next_hn;
4✔
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;
11✔
154
}
155

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

163
    hier_node::child_neighbors_result retval;
1✔
164

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

173
    return retval;
1✔
174
}
175

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

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

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

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

194
    return retval;
57✔
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);
18✔
205
        });
18✔
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() &&
241✔
217
    {
218
        return {
219
            std::move(this->mb_intervals),
241✔
220
            std::move(this->mb_root_node),
241✔
221
            std::move(this->mb_type_intervals),
241✔
222
            std::move(this->mb_indents),
241✔
223
            this->mb_text_format,
241✔
224
        };
241✔
225
    }
226
};
227

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

238
                       auto role = attr.sa_value.get<role_t>();
44,230✔
239
                       switch (role) {
44,230✔
240
                           case role_t::VCR_H1:
4,547✔
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,547✔
247
                           default:
39,683✔
248
                               return false;
39,683✔
249
                       }
250
                   })
251
        | lnav::itertools::sort_by(&string_attr::sa_range);
482✔
252

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

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

264
    struct open_interval_t {
265
        open_interval_t(uint32_t level, file_off_t start, section_key_t id)
4,533✔
266
            : oi_level(level), oi_start(start), oi_id(std::move(id))
4,533✔
267
        {
268
        }
4,533✔
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;
241✔
276
    auto root_node = std::make_unique<hier_node>();
241✔
277

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

284
        for (auto& oi : open_intervals) {
20,714✔
285
            if (oi.oi_level >= role_num) {
16,167✔
286
                // close out this section
287
                intervals.emplace_back(
4,447✔
288
                    oi.oi_start, hdr_attr.sa_range.lr_start - 1, oi.oi_id);
4,447✔
289
                auto* node_ptr = oi.oi_node.get();
4,447✔
290
                auto* parent_node = oi.oi_node->hn_parent;
4,447✔
291
                if (parent_node != nullptr) {
4,447✔
292
                    parent_node->hn_children.emplace_back(
4,447✔
293
                        std::move(oi.oi_node));
4,447✔
294
                    parent_node->hn_named_children.insert({
4,447✔
295
                        oi.oi_id.get<std::string>(),
296
                        node_ptr,
297
                    });
298
                }
299
            } else {
300
                new_open_intervals.emplace_back(std::move(oi));
11,720✔
301
            }
302
        }
303
        if (!hdr_attr.sa_range.empty()) {
4,547✔
304
            auto* parent_node = new_open_intervals.empty()
4,533✔
305
                ? root_node.get()
4,533✔
306
                : new_open_intervals.back().oi_node.get();
4,463✔
307
            new_open_intervals.emplace_back(
4,533✔
308
                role_num,
309
                hdr_attr.sa_range.lr_start,
4,533✔
310
                al.get_substring(hdr_attr.sa_range));
9,066✔
311
            new_open_intervals.back().oi_node->hn_parent = parent_node;
4,533✔
312
            new_open_intervals.back().oi_node->hn_start
4,533✔
313
                = hdr_attr.sa_range.lr_start;
9,066✔
314
        }
315
        open_intervals = std::move(new_open_intervals);
4,547✔
316
    }
4,547✔
317

318
    for (auto& oi : open_intervals) {
327✔
319
        // close out this section
320
        intervals.emplace_back(oi.oi_start, al.length(), oi.oi_id);
86✔
321
        auto* node_ptr = oi.oi_node.get();
86✔
322
        auto* parent_node = oi.oi_node->hn_parent;
86✔
323
        if (parent_node == nullptr) {
86✔
324
            root_node = std::move(oi.oi_node);
×
325
        } else {
326
            parent_node->hn_children.emplace_back(std::move(oi.oi_node));
86✔
327
            parent_node->hn_named_children.insert({
86✔
328
                oi.oi_id.get<std::string>(),
329
                node_ptr,
330
            });
331
        }
332
    }
333

334
    for (auto& interval : intervals) {
5,516✔
335
        auto start_off_iter = find_string_attr_containing(
5,275✔
336
            orig_attrs, &SA_ORIGIN_OFFSET, interval.start);
337
        if (start_off_iter != orig_attrs.end()) {
5,275✔
338
            interval.start += start_off_iter->sa_value.get<int64_t>();
20✔
339
        }
340
        auto stop_off_iter = find_string_attr_containing(
5,275✔
341
            orig_attrs, &SA_ORIGIN_OFFSET, interval.stop - 1);
5,275✔
342
        if (stop_off_iter != orig_attrs.end()) {
5,275✔
343
            interval.stop += stop_off_iter->sa_value.get<int64_t>();
20✔
344
        }
345
    }
346
    for (auto& interval : mb.mb_type_intervals) {
264✔
347
        auto start_off_iter = find_string_attr_containing(
23✔
348
            orig_attrs, &SA_ORIGIN_OFFSET, interval.start);
349
        if (start_off_iter != orig_attrs.end()) {
23✔
350
            interval.start += start_off_iter->sa_value.get<int64_t>();
×
351
        }
352
        auto stop_off_iter = find_string_attr_containing(
23✔
353
            orig_attrs, &SA_ORIGIN_OFFSET, interval.stop - 1);
23✔
354
        if (stop_off_iter != orig_attrs.end()) {
23✔
355
            interval.stop += stop_off_iter->sa_value.get<int64_t>();
×
356
        }
357
    }
358

359
    hier_node::depth_first(root_node.get(), [&orig_attrs](hier_node* node) {
241✔
360
        auto off_opt
361
            = get_string_attr(orig_attrs, &SA_ORIGIN_OFFSET, node->hn_start);
4,774✔
362

363
        if (off_opt) {
4,774✔
364
            node->hn_start += off_opt.value()->sa_value.get<int64_t>();
407✔
365
        }
366
    });
4,774✔
367

368
    hier_node::depth_first(
241✔
369
        mb.mb_root_node.get(), [&orig_attrs](hier_node* node) {
811✔
370
            auto off_opt = get_string_attr(
811✔
371
                orig_attrs, &SA_ORIGIN_OFFSET, node->hn_start);
811✔
372

373
            if (off_opt) {
811✔
374
                node->hn_start += off_opt.value()->sa_value.get<int64_t>();
20✔
375
            }
376
        });
811✔
377

378
    if (!root_node->hn_children.empty()
241✔
379
        || !root_node->hn_named_children.empty())
241✔
380
    {
381
        mb.mb_root_node = std::move(root_node);
47✔
382
    }
383
}
241✔
384

385
metadata
386
discover_metadata(const attr_line_t& al)
138✔
387
{
388
    metadata_builder mb;
138✔
389

390
    discover_metadata_int(al, mb);
138✔
391

392
    return std::move(mb).to_metadata();
276✔
393
}
138✔
394

395
class structure_walker {
396
public:
397
    explicit structure_walker(attr_line_t& al, line_range lr, text_format_t tf)
103✔
398
        : sw_line(al), sw_range(lr), sw_text_format(tf),
103✔
399
          sw_scanner(string_fragment::from_str_range(
103✔
400
              al.get_string(), lr.lr_start, lr.lr_end))
206✔
401
    {
402
        this->sw_interval_state.resize(1);
103✔
403
        this->sw_hier_nodes.push_back(std::make_unique<hier_node>());
103✔
404
    }
103✔
405

406
    bool is_structured_text() const
2,283✔
407
    {
408
        switch (this->sw_text_format) {
2,283✔
409
            case text_format_t::TF_JSON:
544✔
410
            case text_format_t::TF_YAML:
411
            case text_format_t::TF_TOML:
412
            case text_format_t::TF_LOG:
413
            case text_format_t::TF_UNKNOWN:
414
                return true;
544✔
415
            default:
1,739✔
416
                return false;
1,739✔
417
        }
418
    }
419

420
    metadata walk()
103✔
421
    {
422
        metadata_builder mb;
103✔
423

424
        mb.mb_text_format = this->sw_text_format;
103✔
425
        while (true) {
426
            auto tokenize_res
427
                = this->sw_scanner.tokenize2(this->sw_text_format);
27,685✔
428
            if (!tokenize_res) {
27,685✔
429
                break;
103✔
430
            }
431

432
            auto dt = tokenize_res->tr_token;
27,582✔
433

434
            element el(dt, tokenize_res->tr_capture);
27,582✔
435
            const auto& inner_cap = tokenize_res->tr_inner_capture;
27,582✔
436

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

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

627
                                    auto sf
628
                                        = this->sw_scanner.to_string_fragment(
164✔
629
                                            obj_cap);
630
                                    if (!sf.find('\n')) {
164✔
631
                                        this->sw_hier_stage->hn_named_children
101✔
632
                                            .clear();
101✔
633
                                        this->sw_hier_stage->hn_children
101✔
634
                                            .clear();
101✔
635
                                        while (
101✔
636
                                            !this->sw_intervals.empty()
183✔
637
                                            && this->sw_intervals.back().start
310✔
638
                                                > obj_cap.c_begin)
127✔
639
                                        {
640
                                            this->sw_intervals.pop_back();
82✔
641
                                        }
642
                                    }
643
                                }
644
                                this->sw_container_tokens.pop_back();
164✔
645
                            } while (!found);
164✔
646
                        }
647
                    }
648
                    this->sw_values.emplace_back(el);
853✔
649
                    break;
853✔
650
                case DT_COMMA:
579✔
651
                    if (this->is_structured_text()) {
579✔
652
                        if (this->sw_depth > 0) {
226✔
653
                            auto term = this->flush_values();
181✔
654
                            this->append_child_node(term);
181✔
655
                        }
656
                    } else {
657
                        this->sw_values.emplace_back(el);
353✔
658
                    }
659
                    break;
579✔
660
                case DT_LINE:
2,706✔
661
                    this->sw_line_number += 1;
2,706✔
662
                    this->sw_at_start = true;
2,706✔
663
                    break;
2,706✔
664
                case DT_WHITE:
8,905✔
665
                    if (this->sw_at_start) {
8,905✔
666
                        size_t indent_size = 0;
952✔
667

668
                        for (auto ch : tokenize_res->to_string_fragment()) {
7,528✔
669
                            if (ch == '\t') {
6,576✔
670
                                do {
671
                                    indent_size += 1;
29✔
672
                                } while (indent_size % 8);
29✔
673
                            } else {
674
                                indent_size += 1;
6,572✔
675
                            }
676
                        }
677
                        this->sw_indents.insert(indent_size);
952✔
678
                        this->sw_at_start = false;
952✔
679
                    }
680
                    break;
8,905✔
681
                case DT_ZERO_WIDTH_SPACE:
×
682
                    break;
×
683
                default:
12,970✔
684
                    if (dt == DT_QUOTED_STRING) {
12,970✔
685
                        auto quoted_sf = tokenize_res->to_string_fragment();
387✔
686

687
                        if (quoted_sf.find('\n')) {
387✔
688
                            this->sw_type_intervals.emplace_back(
5✔
689
                                el.e_capture.c_begin,
690
                                el.e_capture.c_end,
691
                                section_types_t::multiline_string);
5✔
692
                            this->sw_line.get_attrs().emplace_back(
10✔
693
                                line_range{
×
694
                                    this->sw_range.lr_start
5✔
695
                                        + el.e_capture.c_begin,
5✔
696
                                    this->sw_range.lr_start
5✔
697
                                        + el.e_capture.c_end,
5✔
698
                                },
699
                                VC_ROLE.value(role_t::VCR_STRING));
10✔
700
                        }
701
                    }
702
                    this->sw_values.emplace_back(el);
12,970✔
703
                    break;
12,970✔
704
            }
705
        }
27,582✔
706
        this->flush_values();
103✔
707

708
        if (this->sw_hier_stage != nullptr) {
103✔
709
            this->sw_hier_stage->hn_parent = this->sw_hier_nodes.back().get();
29✔
710
            this->sw_hier_nodes.back()->hn_children.push_back(
58✔
711
                std::move(this->sw_hier_stage));
29✔
712
        }
713
        this->sw_hier_stage = std::move(this->sw_hier_nodes.back());
103✔
714
        this->sw_hier_nodes.pop_back();
103✔
715
        if (this->sw_hier_stage->hn_children.size() == 1
103✔
716
            && this->sw_hier_stage->hn_named_children.empty())
103✔
717
        {
718
            this->sw_hier_stage
719
                = std::move(this->sw_hier_stage->hn_children.front());
28✔
720
            this->sw_hier_stage->hn_parent = nullptr;
28✔
721
        }
722

723
        if (!this->sw_indents.empty()) {
103✔
724
            auto low_indent_iter = this->sw_indents.begin();
51✔
725

726
            if (*low_indent_iter == 1) {
51✔
727
                // adding guides for small indents is noisy, drop for now
728
                this->sw_indents.clear();
13✔
729
            } else {
730
                auto lcm = *low_indent_iter;
38✔
731

732
                for (auto indent_iter = this->sw_indents.begin();
38✔
733
                     indent_iter != this->sw_indents.end();)
117✔
734
                {
735
                    if ((*indent_iter % lcm) == 0) {
79✔
736
                        ++indent_iter;
73✔
737
                    } else {
738
                        indent_iter = this->sw_indents.erase(indent_iter);
6✔
739
                    }
740
                }
741
            }
742
        }
743

744
        mb.mb_root_node = std::move(this->sw_hier_stage);
103✔
745
        mb.mb_intervals = std::move(this->sw_intervals);
103✔
746
        mb.mb_type_intervals = std::move(this->sw_type_intervals);
103✔
747
        mb.mb_indents = std::move(this->sw_indents);
103✔
748

749
        discover_metadata_int(this->sw_line, mb);
103✔
750

751
        return std::move(mb).to_metadata();
206✔
752
    }
103✔
753

754
private:
755
    struct element {
756
        element(data_token_t token, data_scanner::capture_t& cap)
27,582✔
757
            : e_token(token), e_capture(cap)
27,582✔
758
        {
759
        }
27,582✔
760

761
        data_token_t e_token;
762
        data_scanner::capture_t e_capture;
763
    };
764

765
    struct interval_state {
766
        std::optional<file_off_t> is_start;
767
        size_t is_line_number{0};
768
        std::string is_name;
769
    };
770

771
    void drop_open_children()
8✔
772
    {
773
        while (this->sw_depth > 0) {
8✔
774
            this->sw_depth -= 1;
×
775
            this->sw_interval_state.pop_back();
×
776
            this->sw_hier_nodes.pop_back();
×
777
            this->sw_container_tokens.pop_back();
×
778
        }
779
    }
8✔
780

781
    std::optional<data_scanner::capture_t> flush_values()
1,558✔
782
    {
783
        std::optional<data_scanner::capture_t> last_key;
1,558✔
784
        std::optional<data_scanner::capture_t> retval;
1,558✔
785

786
        if (!this->sw_values.empty()) {
1,558✔
787
            if (!this->sw_interval_state.back().is_start) {
814✔
788
                this->sw_interval_state.back().is_start
656✔
789
                    = this->sw_values.front().e_capture.c_begin;
656✔
790
                this->sw_interval_state.back().is_line_number
656✔
791
                    = this->sw_line_number;
656✔
792
            }
793
            retval = this->sw_values.back().e_capture;
814✔
794
        }
795
        for (const auto& el : this->sw_values) {
16,449✔
796
            switch (el.e_token) {
14,891✔
797
                case DT_SYMBOL:
8,612✔
798
                case DT_CONSTANT:
799
                case DT_WORD:
800
                case DT_QUOTED_STRING:
801
                    last_key = el.e_capture;
8,612✔
802
                    break;
8,612✔
803
                case DT_COLON:
485✔
804
                case DT_EQUALS:
805
                    if (last_key) {
485✔
806
                        this->sw_interval_state.back().is_name
451✔
807
                            = this->sw_scanner
808
                                  .to_string_fragment(last_key.value())
451✔
809
                                  .to_unquoted_string();
902✔
810
                        if (!this->sw_interval_state.back().is_name.empty()) {
451✔
811
                            this->sw_interval_state.back().is_start
451✔
812
                                = static_cast<ssize_t>(
902✔
813
                                    last_key.value().c_begin);
451✔
814
                            this->sw_interval_state.back().is_line_number
451✔
815
                                = this->sw_line_number;
451✔
816
                        }
817
                        last_key = std::nullopt;
451✔
818
                    }
819
                    break;
485✔
820
                default:
5,794✔
821
                    break;
5,794✔
822
            }
823
        }
824

825
        this->sw_values.clear();
1,558✔
826

827
        return retval;
1,558✔
828
    }
829

830
    void append_child_node(std::optional<data_scanner::capture_t> terminator)
920✔
831
    {
832
        auto& ivstate = this->sw_interval_state.back();
920✔
833
        if (!ivstate.is_start || !terminator || this->sw_depth == 0) {
920✔
834
            ivstate.is_start = std::nullopt;
10✔
835
            ivstate.is_line_number = 0;
10✔
836
            ivstate.is_name.clear();
10✔
837
            return;
10✔
838
        }
839

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

870
    attr_line_t& sw_line;
871
    line_range sw_range;
872
    text_format_t sw_text_format;
873
    data_scanner sw_scanner;
874
    int sw_depth{0};
875
    size_t sw_line_number{0};
876
    bool sw_at_start{true};
877
    std::set<size_t> sw_indents;
878
    std::vector<element> sw_values{};
879
    std::vector<data_token_t> sw_container_tokens;
880
    std::vector<interval_state> sw_interval_state;
881
    std::vector<lnav::document::section_interval_t> sw_intervals;
882
    std::vector<lnav::document::section_type_interval_t> sw_type_intervals;
883
    std::vector<std::unique_ptr<lnav::document::hier_node>> sw_hier_nodes;
884
    std::unique_ptr<lnav::document::hier_node> sw_hier_stage;
885
};
886

887
metadata
888
discover_structure(attr_line_t& al, struct line_range lr, text_format_t tf)
103✔
889
{
890
    return structure_walker(al, lr, tf).walk();
206✔
891
}
892

893
std::vector<breadcrumb::possibility>
894
metadata::possibility_provider(const std::vector<section_key_t>& path)
15✔
895
{
896
    std::vector<breadcrumb::possibility> retval;
15✔
897
    auto curr_node = lnav::document::hier_node::lookup_path(
15✔
898
        this->m_sections_root.get(), path);
15✔
899
    if (curr_node) {
15✔
900
        auto* parent_node = curr_node.value()->hn_parent;
15✔
901

902
        if (parent_node != nullptr) {
15✔
903
            for (const auto& sibling : parent_node->hn_named_children) {
67✔
904
                retval.emplace_back(sibling.first);
52✔
905
            }
906
        }
907
    }
908
    return retval;
30✔
909
}
×
910

911
}  // namespace document
912
}  // namespace lnav
913

914
namespace fmt {
915
auto
916
formatter<lnav::document::section_key_t>::format(
6✔
917
    const lnav::document::section_key_t& key, fmt::format_context& ctx)
918
    -> decltype(ctx.out()) const
919
{
920
    return key.match(
12✔
921
        [this, &ctx](const std::string& str) {
4✔
922
            return formatter<string_view>::format(str, ctx);
8✔
923
        },
924
        [&ctx](size_t index) {
4✔
925
            return format_to(ctx.out(), FMT_STRING("{}"), index);
10✔
926
        });
2✔
927
}
2✔
928

12✔
929
auto
930
formatter<std::vector<lnav::document::section_key_t>>::format(
931
    const std::vector<lnav::document::section_key_t>& path,
932
    fmt::format_context& ctx) -> decltype(ctx.out()) const
2✔
933
{
934
    for (const auto& part : path) {
935
        format_to(ctx.out(), FMT_STRING("\uff1a"));
936
        format_to(ctx.out(), FMT_STRING("{}"), part);
8✔
937
    }
24✔
938
    return ctx.out();
6✔
939
}
6✔
940
}  // 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