• 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

74.27
/src/base/string_util.cc
1
/**
2
 * Copyright (c) 2019, 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 <iterator>
32
#include <regex>
33
#include <sstream>
34
#include <string_view>
35

36
#include "string_util.hh"
37

38
#include "config.h"
39
#include "is_utf8.hh"
40
#include "lnav_log.hh"
41
#include "scn/scan.h"
42

43
using namespace std::string_view_literals;
44

45
void
46
scrub_to_utf8(char* buffer, size_t length)
7✔
47
{
48
    size_t index = 0;
7✔
49
    while (index < length) {
103✔
50
        auto start_index = index;
96✔
UNCOV
51
        auto ch_res = ww898::utf::utf8::read([buffer, &index, length]() {
×
52
            if (index < length) {
98✔
53
                return buffer[index++];
98✔
54
            }
UNCOV
55
            return '\x00';
×
56
        });
96✔
57
        if (ch_res.isErr()) {
96✔
58
            buffer[start_index] = '?';
6✔
59
        }
60
    }
96✔
61
}
7✔
62

63
void
UNCOV
64
quote_content(auto_buffer& buf, const string_fragment& sf, char quote_char)
×
65
{
66
    for (char ch : sf) {
×
67
        if (ch == quote_char) {
×
68
            buf.push_back('\\').push_back(ch);
×
69
            continue;
×
70
        }
71
        switch (ch) {
×
72
            case '\\':
×
73
                buf.push_back('\\').push_back('\\');
×
74
                break;
×
75
            case '\n':
×
76
                buf.push_back('\\').push_back('n');
×
77
                break;
×
78
            case '\t':
×
79
                buf.push_back('\\').push_back('t');
×
80
                break;
×
81
            case '\r':
×
82
                buf.push_back('\\').push_back('r');
×
83
                break;
×
84
            case '\a':
×
85
                buf.push_back('\\').push_back('a');
×
86
                break;
×
UNCOV
87
            case '\b':
×
UNCOV
88
                buf.push_back('\\').push_back('b');
×
UNCOV
89
                break;
×
UNCOV
90
            default:
×
UNCOV
91
                buf.push_back(ch);
×
UNCOV
92
                break;
×
93
        }
94
    }
95
}
96

97
size_t
98
unquote_content(char* dst, const char* str, size_t len, char quote_char)
174✔
99
{
100
    size_t index = 0;
174✔
101

102
    for (size_t lpc = 0; lpc < len; lpc++, index++) {
7,395✔
103
        dst[index] = str[lpc];
7,221✔
104
        if (str[lpc] == quote_char) {
7,221✔
105
            lpc += 1;
62✔
106
        } else if (str[lpc] == '\\' && (lpc + 1) < len) {
7,159✔
107
            switch (str[lpc + 1]) {
111✔
108
                case 'n':
6✔
109
                    dst[index] = '\n';
6✔
110
                    break;
6✔
UNCOV
111
                case 'r':
×
UNCOV
112
                    dst[index] = '\r';
×
UNCOV
113
                    break;
×
UNCOV
114
                case 't':
×
UNCOV
115
                    dst[index] = '\t';
×
UNCOV
116
                    break;
×
117
                default:
105✔
118
                    dst[index] = str[lpc + 1];
105✔
119
                    break;
105✔
120
            }
121
            lpc += 1;
111✔
122
        }
123
    }
124
    dst[index] = '\0';
174✔
125

126
    return index;
174✔
127
}
128

129
size_t
130
unquote(char* dst, const char* str, size_t len)
171✔
131
{
132
    if (str[0] == 'f' || str[0] == 'r' || str[0] == 'u' || str[0] == 'R'
171✔
133
        || str[0] == 'x' || str[0] == 'X')
170✔
134
    {
135
        str += 1;
1✔
136
        len -= 1;
1✔
137
    }
138
    char quote_char = str[0];
171✔
139

140
    require(str[0] == '\'' || str[0] == '"');
171✔
141

142
    return unquote_content(dst, &str[1], len - 2, quote_char);
171✔
143
}
144

145
size_t
146
unquote_w3c(char* dst, const char* str, size_t len)
7✔
147
{
148
    size_t index = 0;
7✔
149

150
    require(str[0] == '\'' || str[0] == '"');
7✔
151

152
    for (size_t lpc = 1; lpc < (len - 1); lpc++, index++) {
514✔
153
        dst[index] = str[lpc];
507✔
154
        if (str[lpc] == '"') {
507✔
UNCOV
155
            lpc += 1;
×
156
        }
157
    }
158
    dst[index] = '\0';
7✔
159

160
    return index;
7✔
161
}
162

163
void
164
truncate_to(std::string& str, size_t max_char_len)
1,263✔
165
{
166
    static const std::string ELLIPSIS = "\u22ef";
1,475✔
167

168
    if (str.length() < max_char_len) {
1,263✔
169
        return;
1,248✔
170
    }
171

172
    auto str_char_len_res = utf8_string_length(str);
29✔
173

174
    if (str_char_len_res.isErr()) {
29✔
175
        // XXX
UNCOV
176
        return;
×
177
    }
178

179
    auto str_char_len = str_char_len_res.unwrap();
29✔
180
    if (str_char_len <= max_char_len) {
29✔
181
        return;
12✔
182
    }
183

184
    if (max_char_len < 3) {
17✔
185
        str = ELLIPSIS;
2✔
186
        return;
2✔
187
    }
188

189
    auto chars_to_remove = (str_char_len - max_char_len) + 1;
15✔
190
    auto midpoint = str_char_len / 2;
15✔
191
    auto chars_to_keep_at_front = midpoint - (chars_to_remove / 2);
15✔
192
    auto bytes_to_keep_at_front
193
        = utf8_char_to_byte_index(str, chars_to_keep_at_front);
15✔
194
    auto remove_up_to_bytes = utf8_char_to_byte_index(
30✔
195
        str, chars_to_keep_at_front + chars_to_remove);
15✔
196
    auto bytes_to_remove = remove_up_to_bytes - bytes_to_keep_at_front;
15✔
197
    str.erase(bytes_to_keep_at_front, bytes_to_remove);
15✔
198
    str.insert(bytes_to_keep_at_front, ELLIPSIS);
15✔
199
}
29✔
200

201
ssize_t
202
utf8_char_to_byte_index(const std::string& str, ssize_t ch_index)
270✔
203
{
204
    ssize_t retval = 0;
270✔
205

206
    while (ch_index > 0) {
12,081✔
207
        auto ch_len
208
            = ww898::utf::utf8::char_size([&str, retval]() {
11,811✔
209
                  return std::make_pair(str[retval], str.length() - retval - 1);
11,811✔
210
              }).unwrapOr(1);
11,811✔
211

212
        retval += ch_len;
11,811✔
213
        ch_index -= 1;
11,811✔
214
    }
215

216
    return retval;
270✔
217
}
218

219
size_t
220
last_word_str(char* str, size_t len, size_t max_len)
2✔
221
{
222
    if (len < max_len) {
2✔
UNCOV
223
        return len;
×
224
    }
225

226
    size_t last_start = 0;
2✔
227

228
    for (size_t index = 0; index < len; index++) {
27✔
229
        switch (str[index]) {
25✔
230
            case '.':
2✔
231
            case '-':
232
            case '/':
233
            case ':':
234
                last_start = index + 1;
2✔
235
                break;
2✔
236
        }
237
    }
238

239
    if (last_start == 0) {
2✔
240
        return len;
1✔
241
    }
242

243
    memmove(&str[0], &str[last_start], len - last_start);
1✔
244
    return len - last_start;
1✔
245
}
246

247
size_t
248
abbreviate_str(char* str, size_t len, size_t max_len)
141✔
249
{
250
    size_t last_start = 1;
141✔
251

252
    if (len < max_len) {
141✔
253
        return len;
1✔
254
    }
255

256
    for (size_t index = 0; index < len; index++) {
1,632✔
257
        switch (str[index]) {
1,603✔
258
            case '.':
279✔
259
            case '-':
260
            case '/':
261
            case ':':
262
                memmove(&str[last_start], &str[index], len - index);
279✔
263
                len -= (index - last_start);
279✔
264
                index = last_start + 1;
279✔
265
                last_start = index + 1;
279✔
266

267
                if (len < max_len) {
279✔
268
                    return len;
111✔
269
                }
270
                break;
168✔
271
        }
272
    }
273

274
    return len;
29✔
275
}
276

277
void
278
split_ws(const std::string& str, std::vector<std::string>& toks_out)
677✔
279
{
280
    auto str_sf = string_fragment::from_str(str);
677✔
281

282
    while (true) {
283
        auto split_pair = str_sf.split_when(isspace);
2,674✔
284
        if (split_pair.first.empty()) {
2,674✔
285
            if (split_pair.second.empty()) {
856✔
286
                break;
677✔
287
            }
288
            str_sf = split_pair.second;
179✔
289
            continue;
179✔
290
        }
291

292
        toks_out.emplace_back(split_pair.first.to_string());
1,818✔
293
        str_sf = split_pair.second;
1,818✔
294
    }
1,997✔
295
}
677✔
296

297
std::string
298
repeat(const std::string& input, size_t num)
1,118✔
299
{
300
    std::ostringstream os;
1,118✔
301
    std::fill_n(std::ostream_iterator<std::string>(os), num, input);
1,118✔
302
    return os.str();
2,236✔
303
}
1,118✔
304

305
std::string
306
center_str(const std::string& subject, size_t width)
36✔
307
{
308
    std::string retval = subject;
36✔
309

310
    truncate_to(retval, width);
36✔
311

312
    auto retval_length = utf8_string_length(retval).unwrapOr(retval.length());
36✔
313
    auto total_fill = width - retval_length;
36✔
314
    auto before = total_fill / 2;
36✔
315
    auto after = total_fill - before;
36✔
316

317
    retval.insert(0, before, ' ');
36✔
318
    retval.append(after, ' ');
36✔
319

320
    return retval;
36✔
UNCOV
321
}
×
322

323
bool
324
is_blank(const std::string& str)
3,539✔
325
{
326
    return std::all_of(
3,539✔
327
        str.begin(), str.end(), [](const auto ch) { return isspace(ch); });
7,241✔
328
}
329

330
std::string
331
scrub_ws(const char* in, ssize_t len)
24✔
332
{
333
    static constexpr auto TAB_SYMBOL = "\u21e5"sv;
334
    static constexpr auto LF_SYMBOL = "\u240a"sv;
335
    static constexpr auto CR_SYMBOL = "\u240d"sv;
336

337
    std::string retval;
24✔
338

339
    if (len > 0) {
24✔
340
        retval.reserve(len);
8✔
341
    }
342

343
    for (ssize_t lpc = 0; (len == -1 && in[lpc]) || (len >= 0 && lpc < len);
623✔
344
         lpc++)
345
    {
346
        auto ch = in[lpc];
599✔
347

348
        switch (ch) {
599✔
UNCOV
349
            case '\t':
×
UNCOV
350
                retval.append(TAB_SYMBOL);
×
UNCOV
351
                break;
×
352
            case '\n':
14✔
353
                retval.append(LF_SYMBOL);
14✔
354
                break;
14✔
UNCOV
355
            case '\r':
×
UNCOV
356
                retval.append(CR_SYMBOL);
×
UNCOV
357
                break;
×
358
            default:
585✔
359
                retval.push_back(ch);
585✔
360
                break;
585✔
361
        }
362
    }
363

364
    return retval;
24✔
UNCOV
365
}
×
366

367
static constexpr const char* const SUPERSCRIPT_NUMS[] = {
368
    "⁰",
369
    "¹",
370
    "²",
371
    "³",
372
    "⁴",
373
    "⁵",
374
    "⁶",
375
    "⁷",
376
    "⁸",
377
    "⁹",
378
};
379

380
std::string
381
to_superscript(const std::string& in)
283✔
382
{
383
    std::string retval;
283✔
384
    for (const auto ch : in) {
573✔
385
        if (isdigit(ch)) {
290✔
386
            auto index = ch - '0';
290✔
387

388
            retval.append(SUPERSCRIPT_NUMS[index]);
290✔
389
        } else {
UNCOV
390
            retval.push_back(ch);
×
391
        }
392
    }
393

394
    return retval;
283✔
UNCOV
395
}
×
396

397
namespace fmt {
398
auto
UNCOV
399
formatter<lnav::tainted_string>::format(const lnav::tainted_string& ts,
×
400
                                        format_context& ctx)
401
    -> decltype(ctx.out()) const
402
{
UNCOV
403
    auto esc_res = fmt::v10::detail::find_escape(&(*ts.ts_str.begin()),
×
UNCOV
404
                                                 &(*ts.ts_str.end()));
×
UNCOV
405
    if (esc_res.end == nullptr) {
×
UNCOV
406
        return formatter<string_view>::format(ts.ts_str, ctx);
×
407
    }
408

UNCOV
409
    return format_to(ctx.out(), FMT_STRING("{:?}"), ts.ts_str);
×
410
}
411
}  // namespace fmt
412

413
namespace lnav::pcre2pp {
414

415
static bool
416
is_meta(char ch)
3,256,069✔
417
{
418
    switch (ch) {
3,256,069✔
419
        case '\\':
6,546✔
420
        case '^':
421
        case '$':
422
        case '.':
423
        case '[':
424
        case ']':
425
        case '(':
426
        case ')':
427
        case '*':
428
        case '+':
429
        case '?':
430
        case '{':
431
        case '}':
432
            return true;
6,546✔
433
        default:
3,249,523✔
434
            return false;
3,249,523✔
435
    }
436
}
437

438
static std::optional<const char*>
439
char_escape_seq(char ch)
3,249,523✔
440
{
441
    switch (ch) {
3,249,523✔
442
        case '\t':
×
443
            return "\\t";
×
UNCOV
444
        case '\n':
×
UNCOV
445
            return "\\n";
×
446
        default:
3,249,523✔
447
            return std::nullopt;
3,249,523✔
448
    }
449
}
450

451
std::string
452
quote(string_fragment str)
386,568✔
453
{
454
    std::string retval;
386,568✔
455

456
    while (true) {
457
        auto cp_pair_opt = str.consume_codepoint();
3,642,637✔
458
        if (!cp_pair_opt) {
3,642,637✔
459
            break;
386,568✔
460
        }
461

462
        auto cp_pair = cp_pair_opt.value();
3,256,069✔
463
        if ((cp_pair.first & ~0xff) == 0) {
3,256,069✔
464
            if (is_meta(cp_pair.first)) {
3,256,069✔
465
                retval.push_back('\\');
6,546✔
466
            } else {
467
                auto esc_seq = char_escape_seq(cp_pair.first);
3,249,523✔
468
                if (esc_seq) {
3,249,523✔
UNCOV
469
                    retval.append(esc_seq.value());
×
UNCOV
470
                    str = cp_pair_opt->second;
×
UNCOV
471
                    continue;
×
472
                }
473
            }
474
        }
475
        ww898::utf::utf8::write(cp_pair.first,
3,256,069✔
476
                                [&retval](char ch) { retval.push_back(ch); });
6,512,138✔
477
        str = cp_pair_opt->second;
3,256,069✔
478
    }
3,256,069✔
479

480
    return retval;
386,568✔
UNCOV
481
}
×
482

483
}  // namespace lnav::pcre2pp
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