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

ascii-boxes / boxes / 25991660202

17 May 2026 01:04PM UTC coverage: 82.822%. Remained the same
25991660202

push

github

tsjensen
Fix a Heisenbug in u32_insert_space_at() in unicode.c

2776 of 3695 branches covered (75.13%)

Branch coverage included in aggregate %.

5 of 7 new or added lines in 1 file covered. (71.43%)

297 existing lines in 19 files now uncovered.

4345 of 4903 relevant lines covered (88.62%)

98937.49 hits per line

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

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

17
#include "config.h"
18

19
#ifndef __MINGW32__
20
#include <ncurses.h>
21
#endif
22
#include <locale.h>
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <string.h>
26
#include <uniconv.h>
27
#include <unistd.h>
28
#ifdef _WIN32
29
#include <windows.h>
30
#endif
31

32
#include "boxes.h"
33
#include "bxstring.h"
34
#include "cmdline.h"
35
#include "discovery.h"
36
#include "generate.h"
37
#include "input.h"
38
#include "list.h"
39
#include "logging.h"
40
#include "parsing.h"
41
#include "query.h"
42
#include "remove.h"
43
#include "shape.h"
44
#include "tools.h"
45
#include "unicode.h"
46

47

48

49
/*       _\|/_
50
         (o o)
51
 +----oOO-{_}-OOo------------------------------------------------------------+
52
 |                    G l o b a l   V a r i a b l e s                        |
53
 +--------------------------------------------------------------------------*/
54

55
design_t *designs = NULL;            /* available box designs */
56
int num_designs = 0;                 /* number of designs after parsing */
57

58
opt_t opt;                           /* command line options */
59

60
input_t input;                       /* input lines */
61

62
int color_output_enabled;            /* Flag indicating if ANSI color codes should be printed (1) or not (0) */
63

64

65

66
/*       _\|/_
67
         (o o)
68
 +----oOO-{_}-OOo------------------------------------------------------------+
69
 |                           F u n c t i o n s                               |
70
 +--------------------------------------------------------------------------*/
71

72

73
static int build_design(design_t **adesigns, const char *cld)
4✔
74
/*
75
 *  Build a box design.
76
 *
77
 *      adesigns    Pointer to global designs list
78
 *      cld         the W shape as specified on the command line
79
 *
80
 *  Builds the global design list containing only one design which was
81
 *  built from the -c command line definition.
82
 *
83
 *  RETURNS:  != 0   on error (out of memory)
84
 *            == 0   on success
85
 *
86
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
87
 */
88
{
89
    design_t *dp;                        /* pointer to design to be created */
90
    sentry_t *c;                         /* pointer to current shape */
91
    int i;
92
    int rc;
93

94
    *adesigns = (design_t *) calloc(1, sizeof(design_t));
4✔
95
    if (*adesigns == NULL) {
4!
96
        perror(PROJECT);
×
UNCOV
97
        return 1;
×
98
    }
99
    dp = *adesigns;                      /* for readability */
4✔
100

101
    dp->name = "<Command Line Definition>";
4✔
102
    dp->aliases = (char **) calloc(1, sizeof(char *));
4✔
103
    dp->sample = bxs_from_ascii("n/a");
4✔
104
    dp->indentmode = DEF_INDENTMODE;
4✔
105
    dp->padding[BLEF] = 1;
4✔
106
    dp->defined_in = bxs_from_ascii("(command line)");
4✔
107

108
    dp->tags = (char **) calloc(2, sizeof(char *));
4✔
109
    dp->tags[0] = "transient";
4✔
110

111
    /* We always use UTF-8, which is correct for Linux and MacOS, and for modern Windows configured for UTF-8. */
112
    uint32_t *cld_u32 = u32_strconv_from_arg(cld, "UTF-8");
4✔
113
    bxstr_t *cldW = bxs_from_unicode(cld_u32);
4✔
114
    BFREE(cld_u32);
4!
115

116
    dp->shape[W].name = W;
4✔
117
    dp->shape[W].height = 1;
4✔
118
    dp->shape[W].width = cldW->num_columns;
4✔
119
    dp->shape[W].elastic = 1;
4✔
120
    rc = genshape(dp->shape[W].width, dp->shape[W].height, &(dp->shape[W].chars), &(dp->shape[W].mbcs));
4✔
121
    if (rc) {
4!
UNCOV
122
        return rc;
×
123
    }
124
    bxs_free(dp->shape[W].mbcs[0]);
4✔
125
    dp->shape[W].mbcs[0] = cldW;
4✔
126
    strcpy(dp->shape[W].chars[0], cld);
4✔
127

128
    for (i = 0; i < NUM_SHAPES; ++i) {
68✔
129
        c = dp->shape + i;
64✔
130

131
        if (i == NNW || i == NNE || i == WNW || i == ENE || i == W
64✔
132
                || i == WSW || i == ESE || i == SSW || i == SSE) {
44✔
133
                    continue;
36✔
134
        }
135

136
        switch (i) {
28!
137
            case NW:
8✔
138
            case SW:
139
                c->width = dp->shape[W].width;
8✔
140
                c->height = 1;
8✔
141
                c->elastic = 0;
8✔
142
                break;
8✔
143

144
            case NE:
8✔
145
            case SE:
146
                c->width = 1;
8✔
147
                c->height = 1;
8✔
148
                c->elastic = 0;
8✔
149
                break;
8✔
150

151
            case N:
12✔
152
            case S:
153
            case E:
154
                c->width = 1;
12✔
155
                c->height = 1;
12✔
156
                c->elastic = 1;
12✔
157
                break;
12✔
158

159
            default:
×
160
                fprintf(stderr, "%s: internal error\n", PROJECT);
×
UNCOV
161
                return 1;
×
162
        }
163
        c->name = i;
28✔
164

165
        rc = genshape(c->width, c->height, &(c->chars), &(c->mbcs));
28✔
166
        if (rc) {
28!
UNCOV
167
            return rc;
×
168
        }
169
    }
170

171
    dp->maxshapeheight = 1;
4✔
172
    dp->minwidth = dp->shape[W].width + 2;
4✔
173
    dp->minheight = 3;
4✔
174

175
    return 0;
4✔
176
}
177

178

179

180
/**
181
 * Process command line options and store the result in the global `opt` struct. May exit the program.
182
 */
183
static void handle_command_line(int argc, char *argv[])
401✔
184
{
185
    log_debug(__FILE__, MAIN, "Processing Command Line ...\n");
401✔
186

187
    opt_t *parsed_opts = process_commandline(argc, argv);
401✔
188
    if (parsed_opts == NULL) {
401✔
189
        exit(EXIT_FAILURE);
10✔
190
    }
191
    if (parsed_opts->help) {
391✔
192
        usage_long(stdout);
1✔
193
        exit(EXIT_SUCCESS);
1✔
194
    }
195
    if (parsed_opts->version_requested) {
390!
196
        printf("%s version %s\n", PROJECT, VERSION);
×
UNCOV
197
        exit(EXIT_SUCCESS);
×
198
    }
199
    memcpy(&opt, parsed_opts, sizeof(opt_t));
390✔
200
    BFREE(parsed_opts);
390!
201
}
390✔
202

203

204

205
/**
206
 * Parse config file(s), then reset design pointer. May exit the program.
207
 */
208
static void handle_config_parsing()
390✔
209
{
210
    bxstr_t *config_file = discover_config_file(0);
390✔
211
    if (config_file == NULL) {
390✔
212
        exit(EXIT_FAILURE);
3✔
213
    }
214
    if (opt.cld == NULL) {
387✔
215
        size_t r_num_designs = 0;
383✔
216
        designs = parse_config_files(config_file, &r_num_designs);
383✔
217
        if (designs == NULL) {
383✔
218
            exit(EXIT_FAILURE);
3✔
219
        }
220
        num_designs = (int) r_num_designs;
380✔
221
    }
222
    else {
223
        int rc = build_design(&designs, opt.cld);
4✔
224
        if (rc) {
4!
UNCOV
225
            exit(EXIT_FAILURE);
×
226
        }
227
        num_designs = 1;
4✔
228
    }
229
    BFREE (opt.design);
384✔
230
    opt.design = designs;
384✔
231
}
384✔
232

233

234

235
/**
236
 * Adjust box size to command line specification.
237
 * Increase box width/height by width/height of empty sides in order to match appearance of box with the user's
238
 * expectations (if -s).
239
 */
240
static void apply_expected_size()
351✔
241
{
242
    if (opt.reqheight > (long) opt.design->minheight) {
351✔
243
        opt.design->minheight = opt.reqheight;
60✔
244
    }
245
    if (opt.reqwidth > (long) opt.design->minwidth) {
351✔
246
        opt.design->minwidth = opt.reqwidth;
60✔
247
    }
248
    if (opt.reqwidth) {
351✔
249
        if (empty_side(opt.design->shape, BRIG)) {
65✔
250
            opt.design->minwidth += opt.design->shape[SE].width;
2✔
251
        }
252
        if (empty_side(opt.design->shape, BLEF)) {
65✔
253
            opt.design->minwidth += opt.design->shape[NW].width;
2✔
254
        }
255
    }
256
    if (opt.reqheight) {
351✔
257
        if (empty_side(opt.design->shape, BTOP)) {
65✔
258
            opt.design->minheight += opt.design->shape[NW].height;
1✔
259
        }
260
        if (empty_side(opt.design->shape, BBOT)) {
65✔
261
            opt.design->minheight += opt.design->shape[SE].height;
1✔
262
        }
263
    }
264
}
351✔
265

266

267

268
/**
269
 * Read all input lines and store the result in the global `input` structure. May exit the program.
270
 */
271
static void handle_input()
424✔
272
{
273
    log_debug(__FILE__, MAIN, "Reading all input ...\n");
424✔
274

275
    input_t *raw_input = NULL;
424✔
276
    if (opt.mend != 0) {
424✔
277
        raw_input = read_all_input();
351✔
278
        if (raw_input == NULL) {
351!
UNCOV
279
            exit(EXIT_FAILURE);
×
280
        }
281
    }
282
    if (analyze_input(raw_input ? raw_input : &input)) {
424!
UNCOV
283
        exit(EXIT_FAILURE);
×
284
    }
285
    if (raw_input) {
424✔
286
        memcpy(&input, raw_input, sizeof(input_t));
351✔
287
        BFREE(raw_input);
351!
288
    }
289

290
    if (is_debug_logging(MAIN)) {
424✔
291
        log_debug(__FILE__, MAIN, "Effective encoding: %s\n", encoding);
3✔
292
        print_input_lines(NULL);
3✔
293
    }
294
    if (input.num_lines == 0) {
424!
UNCOV
295
        exit(EXIT_SUCCESS);
×
296
    }
297
}
424✔
298

299

300

301
/**
302
 * Adjust box size to fit requested padding value.
303
 * Command line-specified box size takes precedence over padding.
304
 */
305
static void adjust_size_and_padding()
424✔
306
{
307
    for (int i = 0; i < NUM_SIDES; ++i) {
2,120✔
308
        if (opt.padding[i] > -1) {
1,696✔
309
            opt.design->padding[i] = opt.padding[i];
27✔
310
        }
311
    }
312

313
    size_t pad = opt.design->padding[BTOP] + opt.design->padding[BBOT];
424✔
314
    if (pad > 0) {
424✔
315
        pad += input.num_lines;
88✔
316
        pad += opt.design->shape[NW].height + opt.design->shape[SW].height;
88✔
317
        if (pad > opt.design->minheight) {
88✔
318
            if (opt.reqheight) {
84✔
319
                for (int i = 0; i < (int) (pad - opt.design->minheight); ++i) {
7✔
320
                    if (opt.design->padding[i % 2 ? BBOT : BTOP]) {
6✔
321
                        opt.design->padding[i % 2 ? BBOT : BTOP] -= 1;
5✔
322
                    } else if (opt.design->padding[i % 2 ? BTOP : BBOT]) {
1!
UNCOV
323
                        opt.design->padding[i % 2 ? BTOP : BBOT] -= 1;
×
324
                    } else {
325
                        break;
1✔
326
                    }
327
                }
328
            }
329
            else {
330
                opt.design->minheight = pad;
82✔
331
            }
332
        }
333
    }
334

335
    pad = opt.design->padding[BLEF] + opt.design->padding[BRIG];
424✔
336
    if (pad > 0) {
424✔
337
        pad += input.maxline;
367✔
338
        pad += opt.design->shape[NW].width + opt.design->shape[NE].width;
367✔
339
        if (pad > opt.design->minwidth) {
367✔
340
            if (opt.reqwidth) {
310✔
341
                for (int i = 0; i < (int) (pad - opt.design->minwidth); ++i) {
19✔
342
                    if (opt.design->padding[i % 2 ? BRIG : BLEF]) {
16✔
343
                        opt.design->padding[i % 2 ? BRIG : BLEF] -= 1;
13✔
344
                    } else if (opt.design->padding[i % 2 ? BLEF : BRIG]) {
3✔
345
                        opt.design->padding[i % 2 ? BLEF : BRIG] -= 1;
1!
346
                    } else {
347
                        break;
2✔
348
                    }
349
                }
350
            }
351
            else {
352
                opt.design->minwidth = pad;
305✔
353
            }
354
        }
355
    }
356
}
424✔
357

358

359

360
/**
361
 * Generate box. May exit the program.
362
 */
363
static void handle_generate_box()
263✔
364
{
365
    log_debug(__FILE__, MAIN, "Generating Box ...\n");
263✔
366

367
    sentry_t *thebox = (sentry_t *) calloc(NUM_SIDES, sizeof(sentry_t));
263✔
368
    if (thebox == NULL) {
263!
369
        perror(PROJECT);
×
UNCOV
370
        exit(EXIT_FAILURE);
×
371
    }
372
    int rc = generate_box(thebox);
263✔
373
    if (rc) {
263!
UNCOV
374
        exit(EXIT_FAILURE);
×
375
    }
376
    output_box(thebox);
263✔
377
}
263✔
378

379

380

381
/**
382
 * Remove box. May exit the program.
383
 */
384
static void handle_remove_box()
161✔
385
{
386
    log_debug(__FILE__, MAIN, "Removing Box ...\n");
161✔
387

388
    if (opt.killblank == -1) {
161✔
389
        if (empty_side(opt.design->shape, BTOP) && empty_side(opt.design->shape, BBOT)) {
87✔
390
            opt.killblank = 0;
11✔
391
        } else {
392
            opt.killblank = 1;
76✔
393
        }
394
    }
395
    int rc = remove_box();
161✔
396
    if (rc) {
160!
UNCOV
397
        exit(EXIT_FAILURE);
×
398
    }
399
    rc = apply_substitutions(&input, 1);
160✔
400
    if (rc) {
160!
UNCOV
401
        exit(EXIT_FAILURE);
×
402
    }
403
    output_input(opt.mend > 0);
160✔
404
}
160✔
405

406

407

408
#ifndef __MINGW32__
409
    /* These two functions are actually declared in term.h, but for some reason, that can't be included. */
410
    extern NCURSES_EXPORT(int) setupterm(NCURSES_CONST char *, int, int *);
411
    extern NCURSES_EXPORT(int) tigetnum(NCURSES_CONST char *);
412
#endif
413

414
static int terminal_has_colors()
389✔
415
{
416
    int result = 0;
389✔
417
    char *termtype = getenv("TERM");
389✔
418
    #ifdef __MINGW32__
419
        result = 1; /* On Windows, we always assume color capability. */
420
        UNUSED(termtype);
421
    #else
422
        if (termtype != NULL && setupterm(termtype, STDOUT_FILENO, NULL) == OK && tigetnum("colors") >= 8) {
389!
423
            result = 1;
389✔
424
        }
425
    #endif
426

427
    if (is_debug_logging(MAIN)) {
389✔
428
        #ifdef __MINGW32__
429
            int num_colors = 1;
430
        #else
431
            int num_colors = result ? tigetnum("colors") : 0;
3!
432
        #endif
433
        log_debug(__FILE__, MAIN, "Terminal \"%s\" %s colors (number of colors = %d).\n",
3!
434
                termtype != NULL ? termtype : "(null)", result ? "has" : "does NOT have", num_colors);
435
    }
436
    return result;
389✔
437
}
438

439

440

441
static int check_color_support(int opt_color)
390✔
442
{
443
    int result = 0;
390✔
444
    if (opt_color == force_ansi_color) {
390✔
445
        result = 1;
1✔
446
    }
447
    else if (opt_color == color_from_terminal) {
389!
448
        result = terminal_has_colors();
389✔
449
    }
450

451
    log_debug(__FILE__, MAIN, "Color support %sabled\n", result ? "\x1b[92mEN\x1b[0m" : "DIS");
390!
452
    return result;
390✔
453
}
454

455

456

457
/**
458
 * Switch from default "C" encoding to system encoding.
459
 */
460
static void activateSystemEncoding()
401✔
461
{
462
    #ifdef _WIN32
463
        SetConsoleOutputCP(CP_ACP);
464
        SetConsoleCP(CP_ACP);  
465
        /* If it should one day turn out that this doesn't have the desired effect, try setlocale(LC_ALL, ".UTF8"). */
466
    #else
467
        setlocale(LC_ALL, "");
401✔
468
    #endif
469
}
401✔
470

471

472

473
/**
474
 * On some (presumably older) Windows (like Windows 10), we must enable ANSI code support in the terminal.
475
 */
476
void enable_ansi_mode() {
390✔
477
    #if defined(_WIN32) || defined(__MINGW32__)
478
        HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
479
        if (hOut == INVALID_HANDLE_VALUE) {
480
            return;
481
        }
482

483
        DWORD dwMode = 0;
484
        if (!GetConsoleMode(hOut, &dwMode)) {
485
            return;
486
        }
487

488
        #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
489
            #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
490
        #endif
491
        dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT;
492
        SetConsoleMode(hOut, dwMode);
493
    #endif
494
}
390✔
495

496

497

498
/*       _\|/_
499
         (o o)
500
 +----oOO-{_}-OOo------------------------------------------------------------+
501
 |                       P r o g r a m   S t a r t                           |
502
 +--------------------------------------------------------------------------*/
503

504
int main(int argc, char *argv[])
401✔
505
{
506
    int rc;                           /* general return code */
507
    int saved_designwidth;            /* opt.design->minwith backup, used for mending */
508
    int saved_designheight;           /* opt.design->minheight backup, used for mending */
509

510
    /* Temporarily set the system encoding, for proper output of --help text etc. */
511
    activateSystemEncoding();
401✔
512
    encoding = locale_charset();
401✔
513

514
    handle_command_line(argc, argv);
401✔
515

516
    /* Store system character encoding */
517
    encoding = check_encoding(opt.encoding, locale_charset());
390✔
518
    log_debug(__FILE__, MAIN, "Character Encoding = %s\n", encoding);
390✔
519

520
    color_output_enabled = check_color_support(opt.color);
390✔
521
    enable_ansi_mode();
390✔
522

523
    handle_config_parsing();
390✔
524

525
    /* If "-l" option was given, list designs and exit. */
526
    if (opt.l) {
384✔
527
        rc = list_designs();
24✔
528
        exit(rc);
24✔
529
    }
530

531
    /* If "-q" option was given, print results of tag query and exit. */
532
    if (opt.query != NULL && opt.query[0] != NULL) {
360!
533
        rc = query_by_tag();
9✔
534
        exit(rc);
9✔
535
    }
536

537
    apply_expected_size();
351✔
538
    if (opt.indentmode) {
351✔
539
        opt.design->indentmode = opt.indentmode;
2✔
540
    }
541
    saved_designwidth = opt.design->minwidth;
351✔
542
    saved_designheight = opt.design->minheight;
351✔
543

544
    do {
545
        if (opt.mend == 1) {  /* Mending a box works in two phases: */
424✔
546
            opt.r = 0;        /* opt.mend == 2: remove box          */
73✔
547
        }
548
        --opt.mend;           /* opt.mend == 1: add it back         */
424✔
549
        opt.design->minwidth = saved_designwidth;
424✔
550
        opt.design->minheight = saved_designheight;
424✔
551

552
        handle_input();
424✔
553

554
        adjust_size_and_padding();
424✔
555

556
        if (opt.r) {
424✔
557
            handle_remove_box();
161✔
558
        }
559
        else {
560
            handle_generate_box();
263✔
561
        }
562
    } while (opt.mend > 0);
423✔
563

564
    return EXIT_SUCCESS;
350✔
565
}
566

567
/* vim: set 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

© 2026 Coveralls, Inc