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

ascii-boxes / boxes / 4974279124

14 May 2023 08:20PM UTC coverage: 81.546% (-0.3%) from 81.882%
4974279124

push

github

Thomas Jensen
foo

2166 of 2925 branches covered (74.05%)

Branch coverage included in aggregate %.

45 of 45 new or added lines in 3 files covered. (100.0%)

3362 of 3854 relevant lines covered (87.23%)

6215.88 hits per line

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

89.63
/src/bxstring.c
1
/*
2
 * boxes - Command line filter to draw/remove ASCII boxes around text
3
 * Copyright (c) 1999-2023 Thomas Jensen and the boxes contributors
4
 *
5
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
6
 * License, version 3, as published by the Free Software Foundation.
7
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
8
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
9
 * details.
10
 * You should have received a copy of the GNU General Public License along with this program.
11
 * If not, see <https://www.gnu.org/licenses/>.
12
 *
13
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
14
 */
15

16
/*
17
 * The boxes-internal representation of strings.
18
 */
19

20
#include "config.h"
21

22
#include <stdarg.h>
23
#include <string.h>
24
#include <unictype.h>
25
#include <unistdio.h>
26
#include <unistr.h>
27
#include <uniwidth.h>
28

29
#include "bxstring.h"
30
#include "tools.h"
31
#include "unicode.h"
32

33

34

35
bxstr_t *bxs_from_ascii(char *pAscii)
958✔
36
{
37
    if (pAscii == NULL) {
958✔
38
        bx_fprintf(stderr, "%s: internal error: from_ascii() called with NULL\n", PROJECT);
1✔
39
        return NULL;
1✔
40
    }
41

42
    bxstr_t *result = (bxstr_t *) calloc(1, sizeof(bxstr_t));
957✔
43
    result->memory = u32_strconv_from_arg(pAscii, "ASCII");
957✔
44
    if (result->memory == NULL) {
957!
45
        BFREE(result);
×
46
        return NULL;
×
47
    }
48
    result->ascii = strdup(pAscii);
957✔
49

50
    size_t error_pos = 0;
957✔
51
    if (!bxs_valid_anywhere(result, &error_pos)) {
957✔
52
        ucs4_t c = result->memory[error_pos];
1✔
53
        bx_fprintf(stderr, "%s: illegal character '%lc' (%#010x) encountered in string\n", PROJECT, c, (int) c);
1✔
54
        bxs_free(result);
1✔
55
        return NULL;
1✔
56
    }
57

58
    size_t num_esc = 0;
956✔
59
    char *ascii_copy;
60
    size_t *map;
61
    result->num_chars_invisible = count_invisible_chars(result->memory, &num_esc, &ascii_copy, &(map));
956✔
62
    BFREE(ascii_copy);
956!
63

64
    result->num_chars = strlen(pAscii);
956✔
65
    result->num_columns = result->num_chars;
956✔
66
    result->num_chars_visible = result->num_chars - result->num_chars_invisible;
956✔
67

68
    result->indent = strspn(pAscii, " ");
956✔
69
    result->trailing = my_strrspn(pAscii, " ");
956✔
70

71
    result->first_char = calloc(result->num_chars_visible + 1, sizeof(size_t));
956✔
72
    result->visible_char = calloc(result->num_chars_visible + 1, sizeof(size_t));
956✔
73
    for (size_t i = 0; i <= result->num_chars_visible; i++) {
4,706✔
74
        result->first_char[i] = i;
3,750✔
75
        result->visible_char[i] = i;
3,750✔
76
    }
77

78
    return result;
956✔
79
}
80

81

82

83
bxstr_t *bxs_from_unicode(uint32_t *pInput)
13,828✔
84
{
85
    if (pInput == NULL) {
13,828✔
86
        bx_fprintf(stderr, "%s: internal error: bxs_from_unicode() called with NULL\n", PROJECT);
1✔
87
        return NULL;
1✔
88
    }
89

90
    bxstr_t *result = (bxstr_t *) calloc(1, sizeof(bxstr_t));
13,827✔
91
    result->memory = u32_strdup(pInput);
13,827✔
92
    result->num_chars = u32_strlen(pInput);
13,827✔
93
    size_t ascii_len = ((size_t) u32_strwidth(pInput, encoding)) + 1;
13,827✔
94
    result->ascii = (char *) calloc(ascii_len, sizeof(char));
13,827✔
95
    size_t map_size = 5;
13,827✔
96
    result->first_char = (size_t *) calloc(map_size, sizeof(size_t));
13,827✔
97
    result->visible_char = (size_t *) calloc(map_size, sizeof(size_t));
13,827✔
98
    char *ascii_ptr = result->ascii;
13,827✔
99

100
    const uint32_t *rest = pInput;
13,827✔
101
    size_t step_invis = 0;
13,827✔
102
    int indent_active = 1;
13,827✔
103
    size_t blank_streak = 0;
13,827✔
104
    int first_candidate = -1;
13,827✔
105
    int non_blank_encountered = 0;
13,827✔
106
    size_t idx = 0;
13,827✔
107

108
    for (ucs4_t c = pInput[0]; c != char_nul; c = rest[0]) {
399,535✔
109
        if (result->num_chars_visible >= map_size - 2) {
385,709✔
110
            map_size = map_size * 2 + 1;
16,984✔
111
            result->first_char = (size_t *) realloc(result->first_char, map_size * sizeof(size_t));
16,984✔
112
            result->visible_char = (size_t *) realloc(result->visible_char, map_size * sizeof(size_t));
16,984✔
113
        }
114

115
        if (!is_allowed_anywhere(c)) { /* currently used for config only, reconsider when using on input data */
385,709✔
116
            bx_fprintf(stderr, "%s: illegal character '%lc' (%#010x) encountered in string\n", PROJECT, c, (int) c);
1✔
117
            bxs_free(result);
1✔
118
            return NULL;
1✔
119
        }
120
        else if (c == char_esc) {
385,708✔
121
            if (is_csi_reset(rest)) {
184✔
122
                first_candidate = -1;
91✔
123
            }
124
            else {
125
                first_candidate = idx;
93✔
126
            }
127
        }
128
        else {
129
            int cols = 1;
385,524✔
130
            if (is_ascii_printable(c)) {
385,524✔
131
                *ascii_ptr = c & 0xff;
377,911✔
132
                ++ascii_ptr;
377,911✔
133
            }
134
            else if (c == char_tab) {
7,613✔
135
                *ascii_ptr = ' ';
32✔
136
                ++ascii_ptr;
32✔
137
            }
138
            else {
139
                cols = BMAX(0, uc_width(c, encoding));
7,581✔
140
                if (cols > 0) {
7,581✔
141
                    memset(ascii_ptr, (int) (uc_is_blank(c) ? ' ' : 'x'), cols);
255✔
142
                    ascii_ptr += cols;
255✔
143
                }
144
            }
145
            if (is_blank(c)) {
385,524✔
146
                if (indent_active) {
178,920✔
147
                    result->indent += cols;
18,309✔
148
                }
149
                blank_streak++;
178,920✔
150
            }
151
            result->num_columns += BMAX(0, cols);
385,524✔
152
            result->visible_char[result->num_chars_visible] = idx;
385,524✔
153
            result->first_char[result->num_chars_visible] = first_candidate < 0 ? idx : (size_t) first_candidate;
385,524✔
154
            first_candidate = -1;
385,524✔
155
        }
156

157
        if (!is_blank(c) && c != char_esc) {
385,708✔
158
            indent_active = 0;
206,604✔
159
            blank_streak = 0;
206,604✔
160
            non_blank_encountered = 1;
206,604✔
161
        }
162

163
        rest = advance_next32(rest, &step_invis);
385,708✔
164

165
        if (step_invis == 0) {
385,708✔
166
            result->num_chars_visible++;
385,524✔
167
            idx++;
385,524✔
168
        }
169
        else {
170
            result->num_chars_invisible += step_invis;
184✔
171
            idx += step_invis;
184✔
172
        }
173
    }
174

175
    *ascii_ptr = '\0';
13,826✔
176
    result->visible_char[result->num_chars_visible] = idx;  // both point to the terminator
13,826✔
177
    result->first_char[result->num_chars_visible] = idx;
13,826✔
178
    result->trailing = non_blank_encountered ? blank_streak : 0;
13,826✔
179
    return result;
13,826✔
180
}
181

182

183

184
bxstr_t *bxs_strdup(bxstr_t *pString)
4,722✔
185
{
186
    if (pString == NULL) {
4,722✔
187
        return NULL;
1✔
188
    }
189
    bxstr_t *result = (bxstr_t *) calloc(1, sizeof(bxstr_t));
4,721✔
190
    if (result != NULL) {
4,721!
191
        result->memory = u32_strdup(pString->memory);
4,721✔
192
        result->ascii = strdup(pString->ascii);
4,721✔
193
        result->indent = pString->indent;
4,721✔
194
        result->num_columns = pString->num_columns;
4,721✔
195
        result->num_chars = pString->num_chars;
4,721✔
196
        result->num_chars_visible = pString->num_chars_visible;
4,721✔
197
        result->num_chars_invisible = pString->num_chars_invisible;
4,721✔
198
        result->trailing = pString->trailing;
4,721✔
199
        result->first_char = malloc((pString->num_chars_visible + 1) * sizeof(size_t));
4,721✔
200
        memcpy(result->first_char, pString->first_char, (pString->num_chars_visible + 1) * sizeof(size_t));
4,721✔
201
        result->visible_char = malloc((pString->num_chars_visible + 1) * sizeof(size_t));
4,721✔
202
        memcpy(result->visible_char, pString->visible_char, (pString->num_chars_visible + 1) * sizeof(size_t));
4,721✔
203
    }
204
    return result;
4,721✔
205
}
206

207

208

209
bxstr_t *bxs_trimdup(bxstr_t *pString, size_t start_idx, size_t end_idx)
579✔
210
{
211
    if (pString == NULL) {
579✔
212
        return NULL;
1✔
213
    }
214
    if (start_idx > pString->num_chars_visible) {
578✔
215
        /* a start_idx on the terminating NUL is a valid input */
216
        bx_fprintf(stderr, "%s: internal error: start_idx out of bounds in bxs_trimdup()\n", PROJECT);
1✔
217
        return NULL;
1✔
218
    }
219
    if (end_idx > pString->num_chars_visible) {
577✔
220
        bx_fprintf(stderr, "%s: internal error: end_idx out of bounds in bxs_trimdup()\n", PROJECT);
1✔
221
        return NULL;
1✔
222
    }
223
    if (end_idx < start_idx) {
576✔
224
        bx_fprintf(stderr, "%s: internal error: end_idx before start_idx in bxs_trimdup()\n", PROJECT);
1✔
225
        return NULL;
1✔
226
    }
227

228
    while (start_idx < end_idx && uc_is_blank(pString->memory[pString->visible_char[start_idx]])) {
1,051✔
229
        start_idx++;
476✔
230
    }
231
    while (start_idx < end_idx && uc_is_blank(pString->memory[pString->visible_char[end_idx - 1]])) {
734✔
232
        end_idx--;
159✔
233
    }
234

235
    ucs4_t save = char_nul;
575✔
236
    if (end_idx < pString->num_chars_visible) {
575✔
237
        save = pString->memory[pString->first_char[end_idx]];
345✔
238
        set_char_at(pString->memory, pString->first_char[end_idx], char_nul);
345✔
239
    }
240

241
    bxstr_t *result = bxs_from_unicode(pString->memory + pString->first_char[start_idx]);
575✔
242
    if (end_idx < pString->num_chars_visible) {
575✔
243
        set_char_at(pString->memory, pString->first_char[end_idx], save);
345✔
244
    }
245
    return result;
575✔
246
}
247

248

249

250
bxstr_t *bxs_strcat(bxstr_t *pString, uint32_t *pToAppend)
534✔
251
{
252
    if (pToAppend == NULL) {
534✔
253
        return bxs_strdup(pString);
1✔
254
    }
255
    size_t appened_num_chars = u32_strlen(pToAppend);
533✔
256
    if (appened_num_chars == 0) {
533✔
257
        return bxs_strdup(pString);
1✔
258
    }
259
    if (pString == NULL || pString->num_chars == 0) {
532✔
260
        return bxs_from_unicode(pToAppend);
2✔
261
    }
262

263
    size_t combined_num_chars = pString->num_chars + appened_num_chars;
530✔
264
    uint32_t *s = (uint32_t *) malloc((combined_num_chars + 1) * sizeof(uint32_t));
530✔
265
    memcpy(s, pString->memory, pString->num_chars * sizeof(uint32_t));
530✔
266
    memcpy(s + pString->num_chars, pToAppend, appened_num_chars * sizeof(uint32_t));
530✔
267
    set_char_at(s, combined_num_chars, char_nul);
530✔
268

269
    bxstr_t *result = bxs_from_unicode(s);
530✔
270
    BFREE(s);
530!
271
    return result;
530✔
272
}
273

274

275

276
bxstr_t *bxs_concat(size_t count, ...)
×
277
{
278
    if (count < 1) {
×
279
        return bxs_from_ascii("");
×
280
    }
281

282
    size_t total_len = 0;
×
283
    bxstr_t *src;
284
    va_list va;
285

286
    va_start(va, count);
×
287
    for (size_t i = 0; i < count; i++) {
×
288
        src = va_arg(va, bxstr_t *);
×
289
        if (src != NULL) {
×
290
            total_len += src->num_chars;
×
291
        }
292
    }
293
    va_end(va);
×
294

295
    uint32_t *utf32 = (uint32_t *) calloc(total_len + 1, sizeof(uint32_t));
×
296
    char *format = repeat("%s", count);
×
297

298
    va_start(va, count);
×
299
    u32_snprintf(utf32, total_len + 1, format, va);   // FIXME va is bxstr_t*, but expected to be uint32_t*
×
300
    va_end(va);
×
301

302
    bxstr_t *result = bxs_from_unicode(utf32);
×
303
    BFREE(format);
×
304
    BFREE(utf32);
×
305
    return result;
×
306
}
307

308

309

310
uint32_t *bxs_strchr(bxstr_t *pString, ucs4_t c, int *cursor)
749✔
311
{
312
    uint32_t *result = NULL;
749✔
313
    if (pString != NULL && pString->num_chars_visible > 0) {
749✔
314
        size_t start_idx = cursor != NULL ? *cursor + 1 : 0;
747✔
315
        for (size_t i = start_idx; i < pString->num_chars_visible; i++) {
5,984✔
316
            if (pString->memory[pString->visible_char[i]] == c) {
5,726✔
317
                result = pString->memory + pString->visible_char[i];
489✔
318
                if (cursor != NULL) {
489✔
319
                    *cursor = (int) i;
317✔
320
                }
321
                break;
489✔
322
            }
323
        }
324
    }
325
    return result;
749✔
326
}
327

328

329

330
bxstr_t *bxs_trim(bxstr_t *pString)
17✔
331
{
332
    if (pString == NULL) {
17✔
333
        return NULL;
1✔
334
    }
335
    if (pString->indent == 0 && pString->trailing == 0) {
16!
336
        return bxs_strdup(pString);
1✔
337
    }
338
    if (pString->indent + pString->trailing == pString->num_chars_visible) {
15✔
339
        return bxs_from_ascii("");
1✔
340
    }
341

342
    uint32_t *e = u32_strdup(pString->memory);
14✔
343
    set_char_at(e, pString->first_char[pString->num_chars_visible - pString->trailing], char_nul);
14✔
344
    uint32_t *s = e + pString->first_char[pString->indent];
14✔
345
    bxstr_t *result = bxs_from_unicode(s);
14✔
346
    BFREE(e);
14!
347
    return result;
14✔
348
}
349

350

351

352
bxstr_t *bxs_rtrim(bxstr_t *pString)
532✔
353
{
354
    if (pString == NULL) {
532✔
355
        return NULL;
1✔
356
    }
357
    if (pString->trailing == 0) {
531✔
358
        return bxs_strdup(pString);
530✔
359
    }
360

361
    uint32_t *s = u32_strdup(pString->memory);
1✔
362
    set_char_at(s, pString->first_char[pString->num_chars_visible - pString->trailing], char_nul);
1✔
363
    bxstr_t *result = bxs_from_unicode(s);
1✔
364
    BFREE(s);
1!
365
    return result;
1✔
366
}
367

368

369

370
char *bxs_to_output(bxstr_t *pString)
215✔
371
{
372
    if (pString == NULL) {
215✔
373
        return strdup("NULL");
1✔
374
    }
375

376
    if (color_output_enabled) {
214✔
377
        return u32_strconv_to_output(pString->memory);
26✔
378
    }
379

380
    uint32_t *vis = bxs_filter_visible(pString);
188✔
381
    char *result = u32_strconv_to_output(vis);
188✔
382
    BFREE(vis);
188!
383
    return result;
188✔
384
}
385

386

387

388
int bxs_is_empty(bxstr_t *pString)
278✔
389
{
390
    if (pString == NULL) {
278✔
391
        return 1;
1✔
392
    }
393
    return pString->num_chars > 0 ? 0 : 1;
277✔
394
}
395

396

397

398
int bxs_is_visible_char(bxstr_t *pString, size_t idx)
10✔
399
{
400
    int result = 0;
10✔
401
    if (pString != NULL) {
10✔
402
        for (size_t i = 0; i <= idx && i < pString->num_chars_visible; i++) {
24!
403
            if (pString->visible_char[i] == idx) {
20✔
404
                result = 1;
3✔
405
                break;
3✔
406
            }
407
            else if (pString->visible_char[i] > idx) {
17✔
408
                break;
2✔
409
            }
410
        }
411
    }
412
    return result;
10✔
413
}
414

415

416

417
uint32_t *bxs_filter_visible(bxstr_t *pString)
191✔
418
{
419
    uint32_t *result = NULL;
191✔
420
    if (pString != NULL) {
191✔
421
        if (pString->num_chars_invisible == 0) {
190✔
422
            result = u32_strdup(pString->memory);
189✔
423
        }
424
        else {
425
            result = (uint32_t *) calloc(pString->num_chars_visible + 1, sizeof(uint32_t));
1✔
426
            for (size_t i = 0; i < pString->num_chars_visible; i++) {
4✔
427
                set_char_at(result, i, pString->memory[pString->visible_char[i]]);
3✔
428
            }
429
            set_char_at(result, pString->num_chars_visible, char_nul);
1✔
430
        }
431
    }
432
    return result;
191✔
433
}
434

435

436

437
int bxs_strcmp(bxstr_t *s1, bxstr_t *s2)
19✔
438
{
439
    if (s1 == NULL) {
19✔
440
        if (s2 == NULL) {
2✔
441
            return 0;
1✔
442
        }
443
        else {
444
            return 1;
1✔
445
        }
446
    }
447
    if (s2 == NULL) {
17✔
448
        return -1;
1✔
449
    }
450
    return u32_strcmp(s1->memory, s2->memory);
16✔
451
}
452

453

454

455
static int bxs_valid_in_context(bxstr_t *pString, size_t *error_pos, int (*predicate)(const ucs4_t))
5,107✔
456
{
457
    if (pString == NULL) {
5,107✔
458
        if (error_pos != NULL) {
2✔
459
            *error_pos = 0;
1✔
460
        }
461
        return 0;   /* invalid */
2✔
462
    }
463

464
    for (size_t i = 0; pString->memory[i] != char_nul; i++) {
80,188✔
465
        if ((*predicate)(pString->memory[i]) == 0) {
75,085✔
466
            if (error_pos != NULL) {
2✔
467
                *error_pos = i;
1✔
468
            }
469
            return 0;   /* invalid */
2✔
470
        }
471
    }
472

473
    return 1;   /* valid */
5,103✔
474
}
475

476

477

478
int bxs_valid_anywhere(bxstr_t *pString, size_t *error_pos)
960✔
479
{
480
    return bxs_valid_in_context(pString, error_pos, &is_allowed_anywhere);
960✔
481
}
482

483

484

485
int bxs_valid_in_shape(bxstr_t *pString, size_t *error_pos)
3,023✔
486
{
487
    return pString->num_chars_visible > 0 && bxs_valid_in_context(pString, error_pos, &is_allowed_in_shape);
3,023!
488
}
489

490

491

492
int bxs_valid_in_sample(bxstr_t *pString, size_t *error_pos)
264✔
493
{
494
    return pString->num_chars_visible > 0 && bxs_valid_in_context(pString, error_pos, &is_allowed_in_sample);
264!
495
}
496

497

498

499
int bxs_valid_in_filename(bxstr_t *pString, size_t *error_pos)
15✔
500
{
501
    return pString->num_chars_visible > 0 && pString->num_chars_invisible == 0
14✔
502
            && bxs_valid_in_context(pString, error_pos, &is_allowed_in_filename);
29!
503
}
504

505

506

507
int bxs_valid_in_kv_string(bxstr_t *pString, size_t *error_pos)
847✔
508
{
509
    return bxs_valid_in_context(pString, error_pos, &is_allowed_in_kv_string);
847✔
510
}
511

512

513

514
void bxs_free(bxstr_t *pString)
1,725✔
515
{
516
    if (pString != NULL) {
1,725✔
517
        BFREE(pString->memory);
1,724✔
518
        BFREE(pString->ascii);
1,724✔
519
        BFREE(pString->first_char);
1,724✔
520
        BFREE(pString->visible_char);
1,724✔
521
        BFREE(pString);
1,724!
522
    }
523
}
1,725✔
524

525

526
/* vim: set cindent sw=4: */
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

© 2025 Coveralls, Inc