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

ascii-boxes / boxes / 9333132136

01 Jun 2024 05:26PM UTC coverage: 87.204% (-0.4%) from 87.63%
9333132136

push

github

tsjensen
logg

3259 of 4089 branches covered (79.7%)

Branch coverage included in aggregate %.

12 of 13 new or added lines in 2 files covered. (92.31%)

3 existing lines in 2 files now uncovered.

4994 of 5375 relevant lines covered (92.91%)

177804.94 hits per line

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

89.7
/src/boxes.c
1
/*
2
 * boxes - Command line filter to draw/remove ASCII boxes around text
3
 * Copyright (c) 1999-2024 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
#include "config.h"
17

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

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

45

46

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

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

56
opt_t opt;                           /* command line options */
57

58
input_t input;                       /* input lines */
59

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

62

63

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

70

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

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

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

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

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

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

4✔
126
    for (i = 0; i < NUM_SHAPES; ++i) {
68✔
127
        c = dp->shape + i;
132✔
128

64✔
129
        if (i == NNW || i == NNE || i == WNW || i == ENE || i == W
96✔
130
                || i == WSW || i == ESE || i == SSW || i == SSE) {
112✔
131
                    continue;
80✔
132
        }
36✔
133

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

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

8✔
149
            case N:
150
            case S:
12✔
151
            case E:
152
                c->width = 1;
12✔
153
                c->height = 1;
24✔
154
                c->elastic = 1;
24✔
155
                break;
24✔
156

12✔
157
            default:
UNCOV
158
                fprintf(stderr, "%s: internal error\n", PROJECT);
×
159
                return 1;                /* never happens ;-) */
×
160
        }
161
        c->name = i;
28✔
162

28✔
163
        rc = genshape(c->width, c->height, &(c->chars), &(c->mbcs));
28✔
164
        if (rc) {
56!
165
            return rc;
28!
166
        }
167
    }
28✔
168

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

4✔
173
    return 0;
4✔
174
}
8✔
175

176

177

178
/**
179
 * Process command line options and store the result in the global `opt` struct. May exit the program.
180
 */
181
static void handle_command_line(int argc, char *argv[])
398✔
182
{
398✔
183
    #ifdef DEBUG
184
        fprintf (stderr, "Processing Command Line ...\n");
398✔
185
    #endif
186
    opt_t *parsed_opts = process_commandline(argc, argv);
796✔
187
    if (parsed_opts == NULL) {
796✔
188
        exit(EXIT_FAILURE);
20✔
189
    }
190
    if (parsed_opts->help) {
776✔
191
        usage_long(stdout);
2✔
192
        exit(EXIT_SUCCESS);
2✔
193
    }
194
    if (parsed_opts->version_requested) {
774!
195
        printf("%s version %s\n", PROJECT, VERSION);
×
196
        exit(EXIT_SUCCESS);
×
197
    }
198
    memcpy(&opt, parsed_opts, sizeof(opt_t));
774✔
199
    BFREE(parsed_opts);
774✔
200
}
774✔
201

202

203

204
/**
205
 * Parse config file(s), then reset design pointer. May exit the program.
206
 */
207
static void handle_config_parsing()
774✔
208
{
209
    bxstr_t *config_file = discover_config_file(0);
774✔
210
    if (config_file == NULL) {
774✔
211
        exit(EXIT_FAILURE);
6✔
212
    }
213
    if (opt.cld == NULL) {
768✔
214
        size_t r_num_designs = 0;
760✔
215
        designs = parse_config_files(config_file, &r_num_designs);
760✔
216
        if (designs == NULL) {
760✔
217
            exit(EXIT_FAILURE);
6✔
218
        }
219
        num_designs = (int) r_num_designs;
754✔
220
    }
377✔
221
    else {
222
        int rc = build_design(&designs, opt.cld);
8✔
223
        if (rc) {
8✔
224
            exit(EXIT_FAILURE);
×
225
        }
226
        num_designs = 1;
8✔
227
    }
228
    BFREE (opt.design);
762✔
229
    opt.design = designs;
762✔
230
}
762✔
231

232

233

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

265

266

267
/**
268
 * Read all input lines and store the result in the global `input` structure. May exit the program.
269
 */
270
static void handle_input()
842✔
271
{
272
    #ifdef DEBUG
421✔
273
        fprintf (stderr, "Reading all input ...\n");
274
    #endif
421✔
275
    input_t *raw_input = NULL;
842✔
276
    if (opt.mend != 0) {
769✔
277
        raw_input = read_all_input();
696!
278
        if (raw_input == NULL) {
348!
279
            exit(EXIT_FAILURE);
280
        }
281
    }
769!
282
    if (analyze_input(raw_input ? raw_input : &input)) {
421!
283
        exit(EXIT_FAILURE);
284
    }
421✔
285
    if (raw_input) {
769✔
286
        memcpy(&input, raw_input, sizeof(input_t));
696!
287
        BFREE(raw_input);
348!
288
    }
348✔
289

421!
290
    #ifdef DEBUG
291
        fprintf(stderr, "Effective encoding: %s\n", encoding);
292
        print_input_lines(NULL);
293
    #endif
421!
294
    if (input.num_lines == 0) {
421!
295
        exit(EXIT_SUCCESS);
296
    }
421✔
297
}
421✔
298

299

300

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

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

421✔
335
    pad = opt.design->padding[BLEF] + opt.design->padding[BRIG];
842✔
336
    if (pad > 0) {
786✔
337
        pad += input.maxline;
730✔
338
        pad += opt.design->shape[NW].width + opt.design->shape[NE].width;
730✔
339
        if (pad > opt.design->minwidth) {
673✔
340
            if (opt.reqwidth) {
327✔
341
                for (int i = 0; i < (int) (pad - opt.design->minwidth); ++i) {
35✔
342
                    if (opt.design->padding[i % 2 ? BRIG : BLEF]) {
29✔
343
                        opt.design->padding[i % 2 ? BRIG : BLEF] -= 1;
16✔
344
                    } else if (opt.design->padding[i % 2 ? BLEF : BRIG]) {
17!
345
                        opt.design->padding[i % 2 ? BLEF : BRIG] -= 1;
1✔
346
                    } else {
3✔
347
                        break;
2✔
348
                    }
349
                }
14✔
350
            }
5✔
351
            else {
303✔
352
                opt.design->minwidth = pad;
303✔
353
            }
354
        }
308✔
355
    }
786✔
356
}
421✔
357

358

359

360
/**
361
 * Generate box. May exit the program.
362
 */
260✔
363
static void handle_generate_box()
260✔
364
{
260✔
365
    #ifdef DEBUG
366
        fprintf (stderr, "Generating Box ...\n");
260✔
367
    #endif
260!
368
    sentry_t *thebox = (sentry_t *) calloc(NUM_SIDES, sizeof(sentry_t));
260✔
369
    if (thebox == NULL) {
260!
370
        perror(PROJECT);
371
        exit(EXIT_FAILURE);
260✔
372
    }
260!
373
    int rc = generate_box(thebox);
260✔
374
    if (rc) {
260!
375
        exit(EXIT_FAILURE);
260✔
376
    }
260✔
377
    output_box(thebox);
260✔
378
}
260✔
379

380

381

382
/**
383
 * Remove box. May exit the program.
161✔
384
 */
385
static void handle_remove_box()
321✔
386
{
387
    #ifdef DEBUG
161✔
388
        fprintf (stderr, "Removing Box ...\n");
87✔
389
    #endif
11✔
390
    if (opt.killblank == -1) {
160✔
391
        if (empty_side(opt.design->shape, BTOP) && empty_side(opt.design->shape, BBOT)) {
163✔
392
            opt.killblank = 0;
11✔
393
        } else {
11✔
394
            opt.killblank = 1;
237✔
395
        }
160!
396
    }
87✔
397
    int rc = remove_box();
160✔
398
    if (rc) {
320!
399
        exit(EXIT_FAILURE);
160!
400
    }
401
    rc = apply_substitutions(&input, 1);
160✔
402
    if (rc) {
320!
403
        exit(EXIT_FAILURE);
160✔
404
    }
405
    output_input(opt.mend > 0);
160✔
406
}
160✔
407

408

409

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

386✔
416
static int terminal_has_colors()
772✔
417
{
418
    int result = 0;
386✔
419
    char *termtype = getenv("TERM");
386✔
420
    #ifdef __MINGW32__
421
        result = 1; /* On Windows, we always assume color capability. */
386!
422
        UNUSED(termtype);
386✔
423
    #else
424
        if (termtype != NULL && setupterm(termtype, STDOUT_FILENO, NULL) == OK && tigetnum("colors") >= 8) {
386!
425
            result = 1;
386✔
426
        }
772!
427
    #endif
428
    #if defined(DEBUG)
429
        #ifdef __MINGW32__
430
            int num_colors = 1;
×
431
        #else
432
            int num_colors = result ? tigetnum("colors") : 0;
×
433
        #endif
434
        fprintf(stderr, "Terminal \"%s\" %s colors (number of colors = %d).\n", termtype != NULL ? termtype : "(null)",
435
                result ? "has" : "does NOT have", num_colors);
386✔
436
    #endif
437
    return result;
386✔
438
}
439

440

387✔
441

442
static int check_color_support(int opt_color)
774✔
443
{
387✔
444
    int result = 0;
388✔
445
    if (opt_color == force_ansi_color) {
387✔
446
        result = 1;
387!
447
    }
387✔
448
    else if (opt_color == color_from_terminal) {
386!
449
        result = terminal_has_colors();
386✔
450
    }
773!
451

387✔
452
    #if defined(DEBUG)
453
        fprintf(stderr, "%s: Color support %sabled\n", PROJECT, result ? "\x1b[92mEN\x1b[0m" : "DIS");
454
    #endif
455
    return result;
387✔
456
}
457

458

459

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

474

475

476
/*       _\|/_
477
         (o o)
478
 +----oOO-{_}-OOo------------------------------------------------------------+
398✔
479
 |                       P r o g r a m   S t a r t                           |
480
 +--------------------------------------------------------------------------*/
481

482
int main(int argc, char *argv[])
381✔
483
{
484
    int rc;                           /* general return code */
398✔
485
    int saved_designwidth;            /* opt.design->minwith backup, used for mending */
486
    int saved_designheight;           /* opt.design->minheight backup, used for mending */
487

398✔
488
    #ifdef DEBUG
398✔
489
        fprintf (stderr, "BOXES STARTING ...\n");
490
    #endif
398✔
491

492
    /* Temporarily set the system encoding, for proper output of --help text etc. */
493
    activateSystemEncoding();
768✔
494
    encoding = locale_charset();
768✔
495

496
    handle_command_line(argc, argv);
768✔
497

498
    /* Store system character encoding */
387✔
499
    encoding = check_encoding(opt.encoding, locale_charset());
381✔
500
    #ifdef DEBUG
501
        fprintf (stderr, "Character Encoding = %s\n", encoding);
381✔
502
    #endif
24✔
503

24✔
504
    color_output_enabled = check_color_support(opt.color);
381✔
505

506
    handle_config_parsing();
381✔
507

357!
508
    /* If "-l" option was given, list designs and exit. */
9✔
509
    if (opt.l) {
390✔
510
        rc = list_designs();
24✔
511
        exit(rc);
24✔
512
    }
348✔
513

348✔
514
    /* If "-q" option was given, print results of tag query and exit. */
2✔
515
    if (opt.query != NULL && opt.query[0] != NULL && !query_is_undoc()) {
357!
516
        rc = query_by_tag();
357✔
517
        exit(rc);
357✔
518
    }
519

520
    apply_expected_size();
769✔
521
    if (opt.indentmode) {
421✔
522
        opt.design->indentmode = opt.indentmode;
2✔
523
    }
423✔
524
    saved_designwidth = opt.design->minwidth;
769✔
525
    saved_designheight = opt.design->minheight;
769✔
526

527
    do {
769✔
528
        if (opt.mend == 1) {  /* Mending a box works in two phases: */
421✔
529
            opt.r = 0;        /* opt.mend == 2: remove box          */
494✔
530
        }
73✔
531
        --opt.mend;           /* opt.mend == 1: add it back         */
842✔
532
        opt.design->minwidth = saved_designwidth;
582✔
533
        opt.design->minheight = saved_designheight;
421✔
534

535
        handle_input();
681✔
536

537
        adjust_size_and_padding();
841✔
538

539
        if (opt.r) {
768✔
540
            handle_remove_box();
161✔
541
        }
161✔
542
        else {
543
            handle_generate_box();
260✔
544
        }
545
    } while (opt.mend > 0);
421✔
546

547
    return EXIT_SUCCESS;
348✔
548
}
549

550
/*EOF*/                                                  /* 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