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

tstack / lnav / 18577582398-2579

16 Oct 2025 11:24PM UTC coverage: 69.212% (-0.9%) from 70.063%
18577582398-2579

push

github

tstack
[build] fix type

1 of 1 new or added line in 1 file covered. (100.0%)

2573 existing lines in 65 files now uncovered.

50113 of 72405 relevant lines covered (69.21%)

415651.95 hits per line

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

81.38
/src/md4cpp.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 "md4cpp.hh"
31

32
#include "base/is_utf8.hh"
33
#include "base/lnav_log.hh"
34
#include "emojis-json.h"
35
#include "pcrepp/pcre2pp.hh"
36
#include "xml-entities-json.h"
37
#include "yajlpp/yajlpp_def.hh"
38

39
namespace md4cpp {
40

41
static const typed_json_path_container<xml_entity_map>&
42
get_xml_entity_map_handlers()
35✔
43
{
44
    static const typed_json_path_container<xml_entity> xml_entity_handlers = {
45
        yajlpp::property_handler("characters").for_field(&xml_entity::xe_chars),
70✔
46
    };
140✔
47

48
    static const typed_json_path_container<xml_entity_map> retval = {
49
        yajlpp::pattern_property_handler("(?<var_name>\\&\\w+;?)")
×
50
            .with_synopsis("<name>")
35✔
51
            .with_path_provider<xml_entity_map>(
35✔
52
                [](struct xml_entity_map* xem,
×
53
                   std::vector<std::string>& paths_out) {
54
                    for (const auto& iter : xem->xem_entities) {
×
55
                        paths_out.emplace_back(iter.first);
×
56
                    }
57
                })
×
58
            .with_obj_provider<xml_entity, xml_entity_map>(
35✔
59
                [](const yajlpp_provider_context& ypc, xml_entity_map* xem) {
35✔
60
                    auto entity_name = ypc.get_substr(0);
468,510✔
61
                    return &xem->xem_entities[entity_name];
937,020✔
62
                })
468,510✔
63
            .with_children(xml_entity_handlers),
35✔
64
    };
140✔
65

66
    return retval;
35✔
67
}
140✔
68

69
static const typed_json_path_container<emoji_map>&
70
get_emoji_map_handlers()
37✔
71
{
72
    static const typed_json_path_container<emoji> emoji_handlers = {
73
        yajlpp::property_handler("emoji").for_field(&emoji::e_value),
74✔
74
        yajlpp::property_handler("shortname").for_field(&emoji::e_shortname),
74✔
75
    };
185✔
76

77
    static const typed_json_path_container<emoji_map> retval = {
78
        yajlpp::property_handler("emojis#")
74✔
79
            .for_field(&emoji_map::em_emojis)
37✔
80
            .with_children(emoji_handlers),
37✔
81
    };
148✔
82

83
    return retval;
37✔
84
}
185✔
85

86
static xml_entity_map
87
load_xml_entity_map()
35✔
88
{
89
    static const intern_string_t name
90
        = intern_string::lookup(xml_entities_json.get_name());
35✔
91
    auto sfp = xml_entities_json.to_string_fragment_producer();
35✔
92
    auto parse_res = get_xml_entity_map_handlers()
35✔
93
                         .parser_for(name)
70✔
94
                         .with_ignore_unused(true)
35✔
95
                         .of(*sfp);
35✔
96

97
    assert(parse_res.isOk());
98

99
    return parse_res.unwrap();
70✔
100
}
35✔
101

102
const xml_entity_map&
103
get_xml_entity_map()
35✔
104
{
105
    static const auto retval = load_xml_entity_map();
35✔
106

107
    return retval;
35✔
108
}
109

110
static emoji_map
111
load_emoji_map()
37✔
112
{
113
    static const intern_string_t name
114
        = intern_string::lookup(emojis_json.get_name());
37✔
115
    auto sfp = emojis_json.to_string_fragment_producer();
37✔
116
    auto parse_res
117
        = get_emoji_map_handlers().parser_for(name).with_ignore_unused(true).of(
74✔
118
            *sfp);
37✔
119

120
    assert(parse_res.isOk());
121

122
    auto retval = parse_res.unwrap();
37✔
123
    for (auto& em : retval.em_emojis) {
149,221✔
124
        retval.em_shortname2emoji.emplace(em.e_shortname, em);
149,184✔
125
    }
126

127
    return retval;
74✔
128
}
37✔
129

130
const emoji_map&
131
get_emoji_map()
127✔
132
{
133
    static const auto retval = load_emoji_map();
127✔
134

135
    return retval;
127✔
136
}
137

138
text_auto_buffer
139
escape_html(string_fragment content)
1✔
140
{
141
    auto retval = auto_buffer::alloc(content.length());
1✔
142

143
    for (const auto ch : content) {
20✔
144
        switch (ch) {
19✔
145
            case '"':
×
146
                retval.append("&quot;");
×
147
                break;
×
148
            case '\'':
×
149
                retval.append("&apos;");
×
150
                break;
×
151
            case '<':
×
152
                retval.append("&lt;");
×
153
                break;
×
154
            case '>':
×
155
                retval.append("&gt;");
×
156
                break;
×
157
            case '&':
1✔
158
                retval.append("&amp;");
1✔
159
                break;
1✔
160
            default:
18✔
161
                retval.push_back(ch);
18✔
162
                break;
18✔
163
        }
164
    }
165

166
    return text_auto_buffer{std::move(retval)};
2✔
167
}
1✔
168

169
file
170
parse_file(const std::filesystem::path& src, const string_fragment& sf)
12✔
171
{
172
    static const auto FRONTMATTER_RE = lnav::pcre2pp::code::from_const(
173
        R"((?:^---\n(.*?)\n---\n|^\+\+\+\n(.*)\n\+\+\+\n))",
174
        PCRE2_MULTILINE | PCRE2_DOTALL);
12✔
175
    thread_local auto md = FRONTMATTER_RE.create_match_data();
12✔
176

177
    auto frontmatter_sf = string_fragment{};
12✔
178
    auto frontmatter_format = text_format_t::TF_UNKNOWN;
12✔
179
    auto content_sf = sf;
12✔
180

181
    auto cap_res = FRONTMATTER_RE.capture_from(content_sf)
12✔
182
                       .into(md)
12✔
183
                       .matches()
24✔
184
                       .ignore_error();
12✔
185
    if (cap_res) {
12✔
UNCOV
186
        if (md[1]) {
×
187
            frontmatter_format = text_format_t::TF_YAML;
×
188
            frontmatter_sf = md[1].value();
×
189
        } else if (md[2]) {
×
190
            frontmatter_format = text_format_t::TF_TOML;
×
191
            frontmatter_sf = md[2].value();
×
192
        }
UNCOV
193
        content_sf = cap_res->f_remaining;
×
194
    } else if (content_sf.startswith("{")) {
12✔
195
        yajlpp_parse_context ypc(intern_string::lookup(src));
4✔
196
        auto handle = yajlpp::alloc_handle(&ypc.ypc_callbacks, &ypc);
4✔
197

198
        yajl_config(handle.in(), yajl_allow_trailing_garbage, 1);
4✔
199
        ypc.with_ignore_unused(true)
4✔
200
            .with_handle(handle.in())
4✔
201
            .with_error_reporter([&src](const auto& ypc, const auto& um) {
4✔
UNCOV
202
                log_error(
×
203
                    "%s: failed to parse JSON front matter "
204
                    "-- %s",
205
                    src.c_str(),
206
                    um.um_reason.al_string.c_str());
UNCOV
207
            });
×
208
        if (ypc.parse_doc(content_sf)) {
4✔
209
            ssize_t consumed = ypc.ypc_total_consumed;
4✔
210
            if (consumed < content_sf.length() && content_sf[consumed] == '\n')
4✔
211
            {
212
                frontmatter_format = text_format_t::TF_JSON;
4✔
213
                frontmatter_sf = sf.sub_range(0, consumed);
4✔
214
                content_sf = content_sf.substr(consumed);
4✔
215
            }
216
        }
217
    }
4✔
218

219
    return {
220
        frontmatter_sf,
221
        frontmatter_format,
222
        content_sf,
223
    };
12✔
224
}
225

226
struct parse_userdata {
227
    event_handler& pu_handler;
228
    std::string pu_error_msg;
229
};
230

231
void
232
event_handler::set_line_number_from(const char* text)
276✔
233
{
234
    if (this->eh_fragment.begin() <= text && text < this->eh_fragment.end()) {
276✔
235
        auto off = text - this->eh_fragment.begin();
168✔
236

237
        this->eh_tree->visit_overlapping(
168✔
238
            off, off + 1, [this](const auto& cintv) {
168✔
239
                this->eh_line_number = cintv.value;
184✔
240
            });
184✔
241
    }
242
}
276✔
243

244
event_handler::block
245
event_handler::build_block(MD_BLOCKTYPE type, void* detail)
5,930✔
246
{
247
    switch (type) {
5,930✔
248
        case MD_BLOCK_DOC:
216✔
249
            return block_doc{};
216✔
250
        case MD_BLOCK_QUOTE:
70✔
251
            return block_quote{};
70✔
252
        case MD_BLOCK_UL:
182✔
253
            return static_cast<MD_BLOCK_UL_DETAIL*>(detail);
182✔
254
        case MD_BLOCK_OL:
6✔
255
            return static_cast<MD_BLOCK_OL_DETAIL*>(detail);
6✔
256
        case MD_BLOCK_LI:
716✔
257
            return static_cast<MD_BLOCK_LI_DETAIL*>(detail);
716✔
UNCOV
258
        case MD_BLOCK_HR:
×
259
            return block_hr{};
×
260
        case MD_BLOCK_H:
424✔
261
            return static_cast<MD_BLOCK_H_DETAIL*>(detail);
424✔
262
        case MD_BLOCK_CODE: {
276✔
263
            auto retval = static_cast<MD_BLOCK_CODE_DETAIL*>(detail);
276✔
264

265
            this->set_line_number_from(retval->lang.text);
276✔
266
            return retval;
276✔
267
        }
268
        case MD_BLOCK_HTML:
64✔
269
            return block_html{};
64✔
270
        case MD_BLOCK_P:
1,226✔
271
            return block_p{};
1,226✔
272
        case MD_BLOCK_TABLE:
100✔
273
            return static_cast<MD_BLOCK_TABLE_DETAIL*>(detail);
100✔
274
        case MD_BLOCK_THEAD:
100✔
275
            return block_thead{};
100✔
276
        case MD_BLOCK_TBODY:
100✔
277
            return block_tbody{};
100✔
278
        case MD_BLOCK_TR:
798✔
279
            return block_tr{};
798✔
280
        case MD_BLOCK_TH:
208✔
281
            return block_th{};
208✔
282
        case MD_BLOCK_TD:
1,444✔
283
            return static_cast<MD_BLOCK_TD_DETAIL*>(detail);
1,444✔
284
    }
285

UNCOV
286
    return {};
×
287
}
288

289
event_handler::span
290
event_handler::build_span(MD_SPANTYPE type, void* detail)
1,758✔
291
{
292
    switch (type) {
1,758✔
293
        case MD_SPAN_EM:
8✔
294
            return span_em{};
8✔
295
        case MD_SPAN_STRONG:
180✔
296
            return span_strong{};
180✔
297
        case MD_SPAN_A:
546✔
298
            return static_cast<MD_SPAN_A_DETAIL*>(detail);
546✔
299
        case MD_SPAN_IMG:
96✔
300
            return static_cast<MD_SPAN_IMG_DETAIL*>(detail);
96✔
301
        case MD_SPAN_CODE:
884✔
302
            return span_code{};
884✔
303
        case MD_SPAN_DEL:
8✔
304
            return span_del{};
8✔
305
        case MD_SPAN_U:
36✔
306
            return span_u{};
36✔
UNCOV
307
        default:
×
308
            break;
×
309
    }
310

UNCOV
311
    return {};
×
312
}
313

314
static int
315
md4cpp_enter_block(MD_BLOCKTYPE type, void* detail, void* userdata)
2,965✔
316
{
317
    auto* pu = static_cast<parse_userdata*>(userdata);
2,965✔
318

319
    auto enter_res
320
        = pu->pu_handler.enter_block(pu->pu_handler.build_block(type, detail));
2,965✔
321
    if (enter_res.isErr()) {
2,965✔
UNCOV
322
        pu->pu_error_msg = enter_res.unwrapErr();
×
323
        return 1;
×
324
    }
325

326
    return 0;
2,965✔
327
}
2,965✔
328

329
static int
330
md4cpp_leave_block(MD_BLOCKTYPE type, void* detail, void* userdata)
2,965✔
331
{
332
    auto* pu = static_cast<parse_userdata*>(userdata);
2,965✔
333
    auto leave_res
334
        = pu->pu_handler.leave_block(pu->pu_handler.build_block(type, detail));
2,965✔
335
    if (leave_res.isErr()) {
2,965✔
UNCOV
336
        pu->pu_error_msg = leave_res.unwrapErr();
×
337
        return 1;
×
338
    }
339

340
    return 0;
2,965✔
341
}
2,965✔
342

343
static int
344
md4cpp_enter_span(MD_SPANTYPE type, void* detail, void* userdata)
879✔
345
{
346
    auto* pu = static_cast<parse_userdata*>(userdata);
879✔
347

348
    auto enter_res
349
        = pu->pu_handler.enter_span(pu->pu_handler.build_span(type, detail));
879✔
350
    if (enter_res.isErr()) {
879✔
UNCOV
351
        pu->pu_error_msg = enter_res.unwrapErr();
×
352
        return 1;
×
353
    }
354

355
    return 0;
879✔
356
}
879✔
357

358
static int
359
md4cpp_leave_span(MD_SPANTYPE type, void* detail, void* userdata)
879✔
360
{
361
    auto* pu = static_cast<parse_userdata*>(userdata);
879✔
362

363
    auto leave_res
364
        = pu->pu_handler.leave_span(pu->pu_handler.build_span(type, detail));
879✔
365
    if (leave_res.isErr()) {
879✔
UNCOV
366
        pu->pu_error_msg = leave_res.unwrapErr();
×
367
        return 1;
×
368
    }
369

370
    return 0;
879✔
371
}
879✔
372

373
static int
374
md4cpp_text(MD_TEXTTYPE type, const MD_CHAR* text, MD_SIZE size, void* userdata)
5,882✔
375
{
376
    auto* pu = static_cast<parse_userdata*>(userdata);
5,882✔
377

378
    auto text_res = pu->pu_handler.text(type, string_fragment(text, 0, size));
5,882✔
379
    if (text_res.isErr()) {
5,882✔
UNCOV
380
        pu->pu_error_msg = text_res.unwrapErr();
×
381
        return 1;
×
382
    }
383

384
    return 0;
5,882✔
385
}
5,882✔
386

387
namespace details {
388
Result<void, std::string>
389
parse(const string_fragment& sf, event_handler& eh)
108✔
390
{
391
    auto scan_res = is_utf8(sf);
108✔
392
    if (!scan_res.is_valid()) {
108✔
UNCOV
393
        return Err(
×
394
            fmt::format(FMT_STRING("file has invalid UTF-8 at offset {}: {}"),
×
395
                        scan_res.usr_valid_frag.sf_end,
UNCOV
396
                        scan_res.usr_message));
×
397
    }
398

399
    auto eols = std::vector<event_handler::line_type_t>{};
108✔
400
    int lineno = 1;
108✔
401

402
    auto rest_sf = sf;
108✔
403
    while (!rest_sf.empty()) {
4,240✔
404
        auto split_pair = rest_sf.split_when(string_fragment::tag1{'\n'});
4,132✔
405
        auto line_sf = split_pair.first;
4,132✔
406

407
        eols.emplace_back(line_sf.sf_begin, line_sf.sf_end, lineno++);
4,132✔
408
        rest_sf = split_pair.second;
4,132✔
409
    }
410

411
    MD_PARSER parser = {0};
108✔
412
    eh.eh_fragment = sf;
108✔
413
    eh.eh_tree = std::make_unique<event_handler::lines_tree_t>(std::move(eols));
108✔
414
    auto pu = parse_userdata{eh};
108✔
415

416
    parser.abi_version = 0;
108✔
417
    parser.flags
418
        = (MD_DIALECT_GITHUB | MD_FLAG_UNDERLINE | MD_FLAG_STRIKETHROUGH)
108✔
419
        & ~(MD_FLAG_PERMISSIVEAUTOLINKS);
420
    parser.enter_block = md4cpp_enter_block;
108✔
421
    parser.leave_block = md4cpp_leave_block;
108✔
422
    parser.enter_span = md4cpp_enter_span;
108✔
423
    parser.leave_span = md4cpp_leave_span;
108✔
424
    parser.text = md4cpp_text;
108✔
425

426
    auto rc = md_parse(sf.data(), sf.length(), &parser, &pu);
108✔
427

428
    if (rc == 0) {
108✔
429
        return Ok();
108✔
430
    }
431

UNCOV
432
    return Err(pu.pu_error_msg);
×
433
}
108✔
434
}  // namespace details
435

436
}  // namespace md4cpp
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc