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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

99.25
/src/pretty_printer.cc
1
/**
2
 * Copyright (c) 2015, 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 "pretty_printer.hh"
31

32
#include <sys/types.h>
33

34
#include "base/auto_mem.hh"
35
#include "base/intern_string.hh"
36
#include "base/string_util.hh"
37
#include "config.h"
38

39
void
40
pretty_printer::append_to(attr_line_t& al)
232✔
41
{
42
    if (this->pp_scanner->get_init_offset() > 0) {
232✔
43
        data_scanner::capture_t leading_cap = {
44
            0,
45
            this->pp_scanner->get_init_offset(),
48✔
46
        };
48✔
47

48
        // this->pp_stream << pi.get_substr(&leading_cap);
49
        this->pp_values.emplace_back(DT_WORD, leading_cap);
48✔
50
    }
51

52
    this->pp_scanner->reset();
232✔
53
    while (true) {
54
        auto tok_res = this->pp_scanner->tokenize2();
2,802✔
55
        if (!tok_res) {
2,802✔
56
            break;
232✔
57
        }
58

59
        element el(tok_res->tr_token, tok_res->tr_capture);
2,570✔
60

61
        switch (el.e_token) {
2,570✔
62
            case DT_XML_DECL_TAG:
4✔
63
            case DT_XML_EMPTY_TAG:
64
                if (this->pp_is_xml && this->pp_line_length > 0) {
4✔
65
                    this->start_new_line();
1✔
66
                }
67
                this->pp_values.emplace_back(el);
4✔
68
                if (this->pp_is_xml) {
4✔
69
                    this->start_new_line();
4✔
70
                }
71
                continue;
542✔
72
            case DT_XML_OPEN_TAG:
63✔
73
                if (this->pp_is_xml) {
63✔
74
                    this->start_new_line();
38✔
75
                    this->write_element(el);
38✔
76
                    this->pp_interval_state.back().is_start
38✔
77
                        = this->pp_stream.tellp();
76✔
78
                    this->pp_interval_state.back().is_name
38✔
79
                        = tok_res->to_string();
76✔
80
                    this->descend(DT_XML_CLOSE_TAG);
38✔
81
                } else {
82
                    this->pp_values.emplace_back(el);
25✔
83
                }
84
                continue;
63✔
85
            case DT_XML_CLOSE_TAG:
36✔
86
                this->flush_values();
36✔
87
                this->ascend(el.e_token);
36✔
88
                this->append_child_node();
36✔
89
                this->write_element(el);
36✔
90
                this->start_new_line();
36✔
91
                continue;
36✔
92
            case DT_LCURLY:
166✔
93
            case DT_LSQUARE:
94
            case DT_LPAREN:
95
                this->flush_values(true);
166✔
96
                this->pp_values.emplace_back(el);
166✔
97
                this->descend(to_closer(el.e_token));
166✔
98
                this->pp_interval_state.back().is_start
166✔
99
                    = this->pp_stream.tellp();
332✔
100
                continue;
166✔
101
            case DT_RCURLY:
165✔
102
            case DT_RSQUARE:
103
            case DT_RPAREN:
104
                this->flush_values();
165✔
105
                if (this->pp_body_lines.top()) {
165✔
106
                    this->start_new_line();
54✔
107
                }
108
                this->ascend(el.e_token);
165✔
109
                this->write_element(el);
165✔
110
                continue;
165✔
111
            case DT_COMMA:
130✔
112
                if (this->pp_depth > 0) {
130✔
113
                    this->flush_values(true);
107✔
114
                    if (!this->pp_is_xml) {
107✔
115
                        this->append_child_node();
107✔
116
                    }
117
                    this->write_element(el);
107✔
118
                    this->start_new_line();
107✔
119
                    this->pp_interval_state.back().is_start
107✔
120
                        = this->pp_stream.tellp();
214✔
121
                    continue;
107✔
122
                }
123
                break;
23✔
124
            case DT_WHITE:
573✔
125
                if (this->pp_values.empty() && this->pp_depth == 0
652✔
126
                    && this->pp_line_length == 0)
652✔
127
                {
128
                    this->pp_leading_indent = el.e_capture.length();
1✔
129
                    auto shift_cover
130
                        = line_range::empty_at(this->pp_stream.tellp());
1✔
131
                    shift_string_attrs(
1✔
132
                        this->pp_attrs, shift_cover, -el.e_capture.length());
1✔
133
                    continue;
1✔
134
                }
1✔
135
                break;
572✔
136
            default:
1,433✔
137
                break;
1,433✔
138
        }
434✔
139
        this->pp_values.emplace_back(el);
2,028✔
140
    }
2,570✔
141
    while (this->pp_depth > 0) {
238✔
142
        this->ascend(this->pp_container_tokens.back());
6✔
143
    }
144
    this->flush_values();
232✔
145

146
    attr_line_t combined;
464✔
147
    combined.get_string() = this->pp_stream.str();
232✔
148
    combined.get_attrs() = this->pp_attrs;
232✔
149

150
    if (!al.empty()) {
232✔
151
        al.append("\n");
×
152
    }
153
    al.append(combined);
232✔
154

155
    if (this->pp_hier_stage != nullptr) {
232✔
156
        this->pp_hier_stage->hn_parent = this->pp_hier_nodes.back().get();
32✔
157
        this->pp_hier_nodes.back()->hn_children.push_back(
64✔
158
            std::move(this->pp_hier_stage));
32✔
159
    }
160
    this->pp_hier_stage = std::move(this->pp_hier_nodes.back());
232✔
161
    this->pp_hier_nodes.pop_back();
232✔
162
    if (this->pp_hier_stage->hn_children.size() == 1
232✔
163
        && this->pp_hier_stage->hn_named_children.empty())
232✔
164
    {
165
        this->pp_hier_stage
166
            = std::move(this->pp_hier_stage->hn_children.front());
32✔
167
        this->pp_hier_stage->hn_parent = nullptr;
32✔
168
    }
169
}
232✔
170

171
void
172
pretty_printer::write_element(const element& el)
2,617✔
173
{
174
    ssize_t start_size = this->pp_stream.tellp();
2,617✔
175
    if (this->pp_leading_indent == 0 && this->pp_line_length == 0
2,617✔
176
        && el.e_token == DT_WHITE)
445✔
177
    {
178
        if (this->pp_depth == 0) {
93✔
179
            this->pp_soft_indent += el.e_capture.length();
7✔
180
        } else {
181
            auto shift_cover = line_range{
182
                (int) start_size,
183
                (int) start_size + el.e_capture.length(),
86✔
184
            };
86✔
185
            shift_string_attrs(
86✔
186
                this->pp_attrs, shift_cover, -el.e_capture.length());
86✔
187
        }
188
        return;
130✔
189
    }
190
    if (((this->pp_leading_indent == 0)
2,524✔
191
         || (this->pp_line_length <= this->pp_leading_indent))
1,013✔
192
        && el.e_token == DT_LINE)
1,869✔
193
    {
194
        this->pp_soft_indent = 0;
37✔
195
        if (this->pp_line_length > 0) {
37✔
196
            this->pp_line_length = 0;
24✔
197
            this->pp_stream << std::endl;
24✔
198
            this->pp_body_lines.top() += 1;
24✔
199
        } else {
200
            auto shift_cover = line_range::empty_at(start_size);
13✔
201
            shift_string_attrs(this->pp_attrs, shift_cover, -1);
13✔
202
        }
203
        return;
37✔
204
    }
205
    int indent_size = 0;
2,487✔
206
    if (this->pp_line_length == 0) {
2,487✔
207
        indent_size = this->append_indent();
520✔
208
    }
209
    if (el.e_token == DT_QUOTED_STRING) {
2,487✔
210
        auto unquoted_str = auto_mem<char>::malloc(el.e_capture.length() + 1);
164✔
211
        const char* start
212
            = this->pp_scanner->to_string_fragment(el.e_capture).data();
164✔
213
        auto unq_len = unquote(unquoted_str.in(), start, el.e_capture.length());
164✔
214
        data_scanner ds(
215
            string_fragment::from_bytes(unquoted_str.in(), unq_len));
164✔
216
        string_attrs_t sa;
164✔
217
        pretty_printer str_pp(
218
            &ds, sa, this->pp_leading_indent + this->pp_depth * 4);
164✔
219
        attr_line_t result;
164✔
220
        str_pp.append_to(result);
164✔
221
        if (result.get_string().find('\n') != std::string::npos) {
164✔
222
            switch (start[0]) {
5✔
223
                case 'r':
1✔
224
                case 'u':
225
                    this->pp_stream << start[0];
1✔
226
                    this->pp_stream << start[1] << start[1];
1✔
227
                    break;
1✔
228
                default:
4✔
229
                    this->pp_stream << start[0] << start[0];
4✔
230
                    break;
4✔
231
            }
232
            this->pp_stream << std::endl << result.get_string();
5✔
233
            if (result.empty() || result.get_string().back() != '\n') {
5✔
234
                this->pp_stream << std::endl;
5✔
235
            }
236
            this->pp_stream << start[el.e_capture.length() - 1]
10✔
237
                            << start[el.e_capture.length() - 1];
5✔
238
        } else {
239
            this->pp_stream
240
                << this->pp_scanner->to_string_fragment(el.e_capture);
159✔
241
        }
242
    } else {
164✔
243
        this->pp_stream << this->pp_scanner->to_string_fragment(el.e_capture);
2,323✔
244
    }
245
    auto shift_cover = line_range::empty_at(start_size);
2,487✔
246
    shift_string_attrs(this->pp_attrs, shift_cover, indent_size);
2,487✔
247
    this->pp_line_length += el.e_capture.length();
2,487✔
248
    if (el.e_token == DT_LINE) {
2,487✔
249
        this->pp_line_length = 0;
3✔
250
        this->pp_body_lines.top() += 1;
3✔
251
    }
252
}
253

254
int
255
pretty_printer::append_indent()
520✔
256
{
257
    auto start_size = this->pp_stream.tellp();
520✔
258
    auto prefix_size = this->pp_leading_indent + this->pp_soft_indent;
520✔
259
    this->pp_stream << std::string(prefix_size, ' ');
1,040✔
260
    this->pp_soft_indent = 0;
520✔
261
    if (this->pp_stream.tellp() != this->pp_leading_indent) {
520✔
262
        for (int lpc = 0; lpc < this->pp_depth; lpc++) {
765✔
263
            this->pp_stream << "    ";
477✔
264
        }
265
        if (this->pp_depth > 0) {
288✔
266
            this->pp_indents.insert(this->pp_leading_indent
238✔
267
                                    + 4 * this->pp_depth);
238✔
268
        }
269
    }
270
    return (this->pp_stream.tellp() - start_size);
520✔
271
}
272

273
bool
274
pretty_printer::flush_values(bool start_on_depth)
946✔
275
{
276
    std::optional<data_scanner::capture_t> last_key;
946✔
277
    bool retval = false;
946✔
278

279
    while (!this->pp_values.empty()) {
3,217✔
280
        {
281
            auto& el = this->pp_values.front();
2,271✔
282
            this->write_element(this->pp_values.front());
2,271✔
283
            switch (el.e_token) {
2,271✔
284
                case DT_SYMBOL:
793✔
285
                case DT_CONSTANT:
286
                case DT_WORD:
287
                case DT_QUOTED_STRING:
288
                    last_key = el.e_capture;
793✔
289
                    break;
793✔
290
                case DT_COLON:
202✔
291
                case DT_EQUALS:
292
                    if (last_key) {
202✔
293
                        this->pp_interval_state.back().is_name
159✔
294
                            = this->pp_scanner
159✔
295
                                  ->to_string_fragment(last_key.value())
159✔
296
                                  .to_string();
318✔
297
                        if (!this->pp_interval_state.back().is_name.empty()) {
159✔
298
                            this->pp_interval_state.back().is_start
159✔
299
                                = static_cast<ssize_t>(this->pp_stream.tellp());
318✔
300
                        }
301
                        last_key = std::nullopt;
159✔
302
                    }
303
                    break;
202✔
304
                default:
1,276✔
305
                    break;
1,276✔
306
            }
307
            if (start_on_depth
2,271✔
308
                && (el.e_token == DT_LSQUARE || el.e_token == DT_LCURLY))
769✔
309
            {
310
                if (this->pp_line_length > 0) {
51✔
311
                    ssize_t start_size = this->pp_stream.tellp();
51✔
312
                    this->pp_stream << std::endl;
51✔
313

314
                    auto shift_cover = line_range::empty_at(start_size);
51✔
315
                    shift_string_attrs(this->pp_attrs, shift_cover, 1);
51✔
316
                }
317
                this->pp_line_length = 0;
51✔
318
            }
319
        }
320
        this->pp_values.pop_front();
2,271✔
321
        retval = true;
2,271✔
322
    }
323
    return retval;
946✔
324
}
325

326
void
327
pretty_printer::start_new_line()
240✔
328
{
329
    bool has_output;
330

331
    ssize_t start_size = this->pp_stream.tellp();
240✔
332
    if (this->pp_line_length > 0) {
240✔
333
        this->pp_stream << std::endl;
207✔
334
        auto shift_cover = line_range::empty_at(start_size);
207✔
335
        shift_string_attrs(this->pp_attrs, shift_cover, 1);
207✔
336
        this->pp_line_length = 0;
207✔
337
    }
338
    has_output = this->flush_values();
240✔
339
    if (has_output && this->pp_line_length > 0) {
240✔
340
        start_size = this->pp_stream.tellp();
6✔
341
        this->pp_stream << std::endl;
6✔
342
        auto shift_cover = line_range::empty_at(start_size);
6✔
343
        shift_string_attrs(this->pp_attrs, shift_cover, 1);
6✔
344
    }
345
    this->pp_line_length = 0;
240✔
346
    this->pp_body_lines.top() += 1;
240✔
347
}
240✔
348

349
void
350
pretty_printer::ascend(data_token_t dt)
207✔
351
{
352
    if (this->pp_depth > 0) {
207✔
353
        if (this->pp_container_tokens.back() != dt
203✔
354
            && std::find(this->pp_container_tokens.begin(),
209✔
355
                         this->pp_container_tokens.end(),
356
                         dt)
357
                == this->pp_container_tokens.end())
209✔
358
        {
359
            return;
1✔
360
        }
361

362
        auto found = false;
202✔
363
        do {
364
            if (this->pp_container_tokens.back() == dt) {
204✔
365
                found = true;
202✔
366
            }
367
            int lines = this->pp_body_lines.top();
204✔
368
            this->pp_depth -= 1;
204✔
369
            this->pp_body_lines.pop();
204✔
370
            this->pp_body_lines.top() += lines;
204✔
371

372
            if (!this->pp_is_xml) {
204✔
373
                this->append_child_node();
165✔
374
            }
375
            this->pp_interval_state.pop_back();
204✔
376
            this->pp_hier_stage = std::move(this->pp_hier_nodes.back());
204✔
377
            this->pp_hier_nodes.pop_back();
204✔
378
            this->pp_container_tokens.pop_back();
204✔
379
        } while (!found);
204✔
380
    } else {
381
        this->pp_body_lines.top() = 0;
4✔
382
    }
383
}
384

385
void
386
pretty_printer::descend(data_token_t dt)
204✔
387
{
388
    this->pp_depth += 1;
204✔
389
    this->pp_body_lines.push(0);
204✔
390
    this->pp_container_tokens.push_back(dt);
204✔
391
    this->pp_interval_state.resize(this->pp_depth + 1);
204✔
392
    this->pp_hier_nodes.push_back(
204✔
393
        std::make_unique<lnav::document::hier_node>());
408✔
394
}
204✔
395

396
void
397
pretty_printer::append_child_node()
308✔
398
{
399
    auto& ivstate = this->pp_interval_state.back();
308✔
400
    if (!ivstate.is_start) {
308✔
UNCOV
401
        return;
×
402
    }
403

404
    auto* top_node = this->pp_hier_nodes.back().get();
308✔
405
    auto new_key = ivstate.is_name.empty()
308✔
406
        ? lnav::document::section_key_t{top_node->hn_children.size()}
308✔
407
        : lnav::document::section_key_t{ivstate.is_name};
308✔
408
    this->pp_intervals.emplace_back(
308✔
409
        ivstate.is_start.value(),
410
        static_cast<ssize_t>(this->pp_stream.tellp()),
308✔
411
        new_key);
412
    auto new_node = this->pp_hier_stage != nullptr
308✔
413
        ? std::move(this->pp_hier_stage)
308✔
414
        : std::make_unique<lnav::document::hier_node>();
308✔
415
    auto* retval = new_node.get();
308✔
416
    new_node->hn_parent = top_node;
308✔
417
    new_node->hn_start = this->pp_intervals.back().start;
308✔
418
    if (!ivstate.is_name.empty()) {
308✔
419
        top_node->hn_named_children.insert({
152✔
420
            ivstate.is_name,
152✔
421
            retval,
422
        });
423
    }
424
    top_node->hn_children.emplace_back(std::move(new_node));
308✔
425
    ivstate.is_start = std::nullopt;
308✔
426
    ivstate.is_name.clear();
308✔
427
}
308✔
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