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

ascii-boxes / boxes / 6518013212

14 Oct 2023 01:37PM UTC coverage: 81.211% (-0.4%) from 81.608%
6518013212

push

github

tsjensen
remove

2349 of 3210 branches covered (0.0%)

Branch coverage included in aggregate %.

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

3767 of 4321 relevant lines covered (87.18%)

7801.63 hits per line

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

84.29
/src/boxes.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
#include "config.h"
17

18
#include <ncurses.h>
19
#include <locale.h>
20
#include <stdio.h>
21
#include <stdlib.h>
22
#include <string.h>
23
#include <uniconv.h>
24
#include <unistd.h>
25

26
#include "boxes.h"
27
#include "cmdline.h"
28
#include "discovery.h"
29
#include "generate.h"
30
#include "input.h"
31
#include "list.h"
32
#include "parsing.h"
33
#include "query.h"
34
#include "remove.h"
35
#include "tools.h"
36
#include "unicode.h"
37

38

39

40
/*       _\|/_
41
         (o o)
42
 +----oOO-{_}-OOo------------------------------------------------------------+
43
 |                    G l o b a l   V a r i a b l e s                        |
44
 +--------------------------------------------------------------------------*/
45

46
design_t *designs = NULL;            /* available box designs */
47
int num_designs = 0;                 /* number of designs after parsing */
48

49
opt_t opt;                           /* command line options */
50

51
input_t input;                       /* input lines */
52

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

55

56

57
/*       _\|/_
58
         (o o)
59
 +----oOO-{_}-OOo------------------------------------------------------------+
60
 |                           F u n c t i o n s                               |
61
 +--------------------------------------------------------------------------*/
62

63

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

85
    *adesigns = (design_t *) calloc(1, sizeof(design_t));
4✔
86
    if (*adesigns == NULL) {
4!
87
        perror(PROJECT);
×
88
        return 1;
×
89
    }
90
    dp = *adesigns;                      /* for readability */
4✔
91

92
    dp->name = "<Command Line Definition>";
4✔
93
    dp->aliases = (char **) calloc(1, sizeof(char *));
4✔
94
    dp->created = bxs_from_ascii("now");
4✔
95
    dp->revision = "1.0";
4✔
96
    dp->sample = bxs_from_ascii("n/a");
4✔
97
    dp->indentmode = DEF_INDENTMODE;
4✔
98
    dp->padding[BLEF] = 1;
4✔
99
    dp->defined_in = bxs_from_ascii("(command line)");
4✔
100

101
    dp->tags = (char **) calloc(2, sizeof(char *));
4✔
102
    dp->tags[0] = "transient";
4✔
103

104
    uint32_t *cld_u32 = u32_strconv_from_arg(cld, "UTF-8");  /* CHECK wrong on Windows (UTF-16) or different IME */
4✔
105
    bxstr_t *cldW = bxs_from_unicode(cld_u32);
4✔
106
    BFREE(cld_u32);
4!
107

108
    dp->shape[W].height = 1;
4✔
109
    dp->shape[W].width = cldW->num_columns;
4✔
110
    dp->shape[W].elastic = 1;
4✔
111
    rc = genshape(dp->shape[W].width, dp->shape[W].height, &(dp->shape[W].chars), &(dp->shape[W].mbcs));
4✔
112
    if (rc) {
4!
113
        return rc;
×
114
    }
115
    bxs_free(dp->shape[W].mbcs[0]);
4✔
116
    dp->shape[W].mbcs[0] = cldW;
4✔
117
    strcpy(dp->shape[W].chars[0], cld);
4✔
118

119
    for (i = 0; i < NUM_SHAPES; ++i) {
68✔
120
        c = dp->shape + i;
64✔
121

122
        if (i == NNW || i == NNE || i == WNW || i == ENE || i == W
64✔
123
                || i == WSW || i == ESE || i == SSW || i == SSE) {
44✔
124
                    continue;
36✔
125
        }
126

127
        switch (i) {
28!
128
            case NW:
8✔
129
            case SW:
130
                c->width = dp->shape[W].width;
8✔
131
                c->height = 1;
8✔
132
                c->elastic = 0;
8✔
133
                break;
8✔
134

135
            case NE:
8✔
136
            case SE:
137
                c->width = 1;
8✔
138
                c->height = 1;
8✔
139
                c->elastic = 0;
8✔
140
                break;
8✔
141

142
            case N:
12✔
143
            case S:
144
            case E:
145
                c->width = 1;
12✔
146
                c->height = 1;
12✔
147
                c->elastic = 1;
12✔
148
                break;
12✔
149

150
            default:
×
151
                fprintf(stderr, "%s: internal error\n", PROJECT);
×
152
                return 1;                /* never happens ;-) */
×
153
        }
154

155
        rc = genshape(c->width, c->height, &(c->chars), &(c->mbcs));
28✔
156
        if (rc) {
28!
157
            return rc;
×
158
        }
159
    }
160

161
    dp->maxshapeheight = 1;
4✔
162
    dp->minwidth = dp->shape[W].width + 2;
4✔
163
    dp->minheight = 3;
4✔
164

165
    return 0;
4✔
166
}
167

168

169

170
/**
171
 * Process command line options and store the result in the global `opt` struct. May exit the program.
172
 */
173
static void handle_command_line(int argc, char *argv[])
170✔
174
{
175
    #ifdef DEBUG
176
        fprintf (stderr, "Processing Command Line ...\n");
177
    #endif
178
    opt_t *parsed_opts = process_commandline(argc, argv);
170✔
179
    if (parsed_opts == NULL) {
170✔
180
        exit(EXIT_FAILURE);
10✔
181
    }
182
    if (parsed_opts->help) {
160✔
183
        usage_long(stdout);
1✔
184
        exit(EXIT_SUCCESS);
1✔
185
    }
186
    if (parsed_opts->version_requested) {
159!
187
        printf("%s version %s\n", PROJECT, VERSION);
×
188
        exit(EXIT_SUCCESS);
×
189
    }
190
    memcpy(&opt, parsed_opts, sizeof(opt_t));
159✔
191
    BFREE(parsed_opts);
159!
192
}
159✔
193

194

195

196
/**
197
 * Parse config file(s), then reset design pointer. May exit the program.
198
 */
199
static void handle_config_parsing()
159✔
200
{
201
    bxstr_t *config_file = discover_config_file(0);
159✔
202
    if (config_file == NULL) {
159✔
203
        exit(EXIT_FAILURE);
3✔
204
    }
205
    if (opt.cld == NULL) {
156✔
206
        size_t r_num_designs = 0;
152✔
207
        designs = parse_config_files(config_file, &r_num_designs);
152✔
208
        if (designs == NULL) {
152✔
209
            exit(EXIT_FAILURE);
3✔
210
        }
211
        num_designs = (int) r_num_designs;
149✔
212
    }
213
    else {
214
        int rc = build_design(&designs, opt.cld);
4✔
215
        if (rc) {
4!
216
            exit(EXIT_FAILURE);
×
217
        }
218
        num_designs = 1;
4✔
219
    }
220
    BFREE (opt.design);
153✔
221
    opt.design = designs;
153✔
222
}
153✔
223

224

225

226
/**
227
 * Adjust box size to command line specification.
228
 * Increase box width/height by width/height of empty sides in order to match appearance of box with the user's
229
 * expectations (if -s).
230
 */
231
static void apply_expected_size()
121✔
232
{
233
    if (opt.reqheight > (long) opt.design->minheight) {
121✔
234
        opt.design->minheight = opt.reqheight;
60✔
235
    }
236
    if (opt.reqwidth > (long) opt.design->minwidth) {
121✔
237
        opt.design->minwidth = opt.reqwidth;
60✔
238
    }
239
    if (opt.reqwidth) {
121✔
240
        if (empty_side(opt.design->shape, BRIG)) {
65✔
241
            opt.design->minwidth += opt.design->shape[SE].width;
2✔
242
        }
243
        if (empty_side(opt.design->shape, BLEF)) {
65✔
244
            opt.design->minwidth += opt.design->shape[NW].width;
2✔
245
        }
246
    }
247
    if (opt.reqheight) {
121✔
248
        if (empty_side(opt.design->shape, BTOP)) {
64!
249
            opt.design->minheight += opt.design->shape[NW].height;
×
250
        }
251
        if (empty_side(opt.design->shape, BBOT)) {
64!
252
            opt.design->minheight += opt.design->shape[SE].height;
×
253
        }
254
    }
255
}
121✔
256

257

258

259
/**
260
 * Read all input lines and store the result in the global `input` structure. May exit the program.
261
 */
262
static void handle_input()
122✔
263
{
264
    #ifdef DEBUG
265
        fprintf (stderr, "Reading all input ...\n");
266
    #endif
267
    input_t *raw_input = NULL;
122✔
268
    if (opt.mend != 0) {
122✔
269
        raw_input = read_all_input();
121✔
270
        if (raw_input == NULL) {
121!
271
            exit(EXIT_FAILURE);
×
272
        }
273
    }
274
    if (analyze_input(raw_input ? raw_input : &input)) {
122!
275
        exit(EXIT_FAILURE);
×
276
    }
277
    if (raw_input) {
122✔
278
        memcpy(&input, raw_input, sizeof(input_t));
121✔
279
        BFREE(raw_input);
121!
280
    }
281

282
    #ifdef DEBUG
283
        fprintf(stderr, "Effective encoding: %s\n", encoding);
284
        print_input_lines(NULL);
285
    #endif
286
    if (input.num_lines == 0) {
122!
287
        exit(EXIT_SUCCESS);
×
288
    }
289
}
122✔
290

291

292

293
/**
294
 * Adjust box size to fit requested padding value.
295
 * Command line-specified box size takes precedence over padding.
296
 */
297
static void adjust_size_and_padding()
122✔
298
{
299
    for (int i = 0; i < NUM_SIDES; ++i) {
610✔
300
        if (opt.padding[i] > -1) {
488✔
301
            opt.design->padding[i] = opt.padding[i];
25✔
302
        }
303
    }
304

305
    size_t pad = opt.design->padding[BTOP] + opt.design->padding[BBOT];
122✔
306
    if (pad > 0) {
122✔
307
        pad += input.num_lines;
3✔
308
        pad += opt.design->shape[NW].height + opt.design->shape[SW].height;
3✔
309
        if (pad > opt.design->minheight) {
3!
310
            if (opt.reqheight) {
3✔
311
                for (int i = 0; i < (int) (pad - opt.design->minheight); ++i) {
7✔
312
                    if (opt.design->padding[i % 2 ? BBOT : BTOP]) {
6✔
313
                        opt.design->padding[i % 2 ? BBOT : BTOP] -= 1;
5✔
314
                    } else if (opt.design->padding[i % 2 ? BTOP : BBOT]) {
1!
315
                        opt.design->padding[i % 2 ? BTOP : BBOT] -= 1;
×
316
                    } else {
317
                        break;
1✔
318
                    }
319
                }
320
            }
321
            else {
322
                opt.design->minheight = pad;
1✔
323
            }
324
        }
325
    }
326

327
    pad = opt.design->padding[BLEF] + opt.design->padding[BRIG];
122✔
328
    if (pad > 0) {
122✔
329
        pad += input.maxline;
113✔
330
        pad += opt.design->shape[NW].width + opt.design->shape[NE].width;
113✔
331
        if (pad > opt.design->minwidth) {
113✔
332
            if (opt.reqwidth) {
57✔
333
                for (int i = 0; i < (int) (pad - opt.design->minwidth); ++i) {
19✔
334
                    if (opt.design->padding[i % 2 ? BRIG : BLEF]) {
16✔
335
                        opt.design->padding[i % 2 ? BRIG : BLEF] -= 1;
13✔
336
                    } else if (opt.design->padding[i % 2 ? BLEF : BRIG]) {
3✔
337
                        opt.design->padding[i % 2 ? BLEF : BRIG] -= 1;
1!
338
                    } else {
339
                        break;
2✔
340
                    }
341
                }
342
            }
343
            else {
344
                opt.design->minwidth = pad;
52✔
345
            }
346
        }
347
    }
348
}
122✔
349

350

351

352
/**
353
 * Generate box. May exit the program.
354
 */
355
static void handle_generate_box()
114✔
356
{
357
    #ifdef DEBUG
358
        fprintf (stderr, "Generating Box ...\n");
359
    #endif
360
    sentry_t *thebox = (sentry_t *) calloc(NUM_SIDES, sizeof(sentry_t));
114✔
361
    if (thebox == NULL) {
114!
362
        perror(PROJECT);
×
363
        exit(EXIT_FAILURE);
×
364
    }
365
    int rc = generate_box(thebox);
114✔
366
    if (rc) {
114!
367
        exit(EXIT_FAILURE);
×
368
    }
369
    output_box(thebox);
114✔
370
}
114✔
371

372

373

374
/**
375
 * Remove box. May exit the program.
376
 */
377
static void handle_remove_box()
8✔
378
{
379
    #ifdef DEBUG
380
        fprintf (stderr, "Removing Box ...\n");
381
    #endif
382
    if (opt.killblank == -1) {
8✔
383
        if (empty_side(opt.design->shape, BTOP) && empty_side(opt.design->shape, BBOT)) {
7!
384
            opt.killblank = 0;
×
385
        } else {
386
            opt.killblank = 1;
7✔
387
        }
388
    }
389
    int rc = remove_box();
8✔
390
    if (rc) {
8!
391
        exit(EXIT_FAILURE);
×
392
    }
393
    rc = apply_substitutions(&input, 1);
8✔
394
    if (rc) {
8!
395
        exit(EXIT_FAILURE);
×
396
    }
397
    output_input(opt.mend > 0);
8✔
398
}
8✔
399

400

401

402
/* These two functions are actually declared in term.h, but for some reason, that can't be included. */
403
extern NCURSES_EXPORT(int) setupterm (NCURSES_CONST char *, int, int *);
404
extern NCURSES_EXPORT(int) tigetnum (NCURSES_CONST char *);
405

406
static int terminal_has_colors()
158✔
407
{
408
    int result = 0;
158✔
409
    char *termtype = getenv("TERM");
158✔
410
    if (termtype != NULL && setupterm(termtype, STDOUT_FILENO, NULL) == OK && tigetnum("colors") >= 8) {
158!
411
        result = 1;
158✔
412
    }
413
    #if defined(DEBUG)
414
        int num_colors = result ? tigetnum("colors") : 0;
415
        fprintf(stderr, "Terminal \"%s\" %s colors (number of colors = %d).\n", termtype != NULL ? termtype : "(null)",
416
                result ? "has" : "does NOT have", num_colors);
417
    #endif
418
    return result;
158✔
419
}
420

421

422

423
static int check_color_support(int opt_color)
159✔
424
{
425
    int result = 0;
159✔
426
    if (opt_color == force_ansi_color) {
159✔
427
        result = 1;
1✔
428
    }
429
    else if (opt_color == color_from_terminal) {
158!
430
        result = terminal_has_colors();
158✔
431
    }
432

433
    #if defined(DEBUG)
434
        fprintf(stderr, "%s: Color support %sabled\n", PROJECT, result ? "\x1b[92mEN\x1b[0m" : "DIS");
435
    #endif
436
    return result;
159✔
437
}
438

439

440

441
/*       _\|/_
442
         (o o)
443
 +----oOO-{_}-OOo------------------------------------------------------------+
444
 |                       P r o g r a m   S t a r t                           |
445
 +--------------------------------------------------------------------------*/
446

447
int main(int argc, char *argv[])
170✔
448
{
449
    int rc;                           /* general return code */
450
    int saved_designwidth;            /* opt.design->minwith backup, used for mending */
451
    int saved_designheight;           /* opt.design->minheight backup, used for mending */
452

453
    #ifdef DEBUG
454
        fprintf (stderr, "BOXES STARTING ...\n");
455
    #endif
456

457
    /* Temporarily set the system encoding, for proper output of --help text etc. */
458
    setlocale(LC_ALL, "");    /* switch from default "C" encoding to system encoding */
170✔
459
    encoding = locale_charset();
170✔
460

461
    handle_command_line(argc, argv);
170✔
462

463
    /* Store system character encoding */
464
    encoding = check_encoding(opt.encoding, locale_charset());
159✔
465
    #ifdef DEBUG
466
        fprintf (stderr, "Character Encoding = %s\n", encoding);
467
    #endif
468

469
    color_output_enabled = check_color_support(opt.color);
159✔
470

471
    handle_config_parsing();
159✔
472

473
    /* If "-l" option was given, list designs and exit. */
474
    if (opt.l) {
153✔
475
        rc = list_designs();
24✔
476
        exit(rc);
24✔
477
    }
478

479
    /* If "-q" option was given, print results of tag query and exit. */
480
    if (opt.query != NULL && opt.query[0] != NULL && !query_is_undoc()) {
129!
481
        rc = query_by_tag();
8✔
482
        exit(rc);
8✔
483
    }
484

485
    apply_expected_size();
121✔
486
    if (opt.indentmode) {
121!
487
        opt.design->indentmode = opt.indentmode;
×
488
    }
489
    saved_designwidth = opt.design->minwidth;
121✔
490
    saved_designheight = opt.design->minheight;
121✔
491

492
    do {
493
        if (opt.mend == 1) {  /* Mending a box works in two phases: */
122✔
494
            opt.r = 0;        /* opt.mend == 2: remove box          */
1✔
495
        }
496
        --opt.mend;           /* opt.mend == 1: add it back         */
122✔
497
        opt.design->minwidth = saved_designwidth;
122✔
498
        opt.design->minheight = saved_designheight;
122✔
499

500
        handle_input();
122✔
501

502
        adjust_size_and_padding();
122✔
503

504
        if (opt.r) {
122✔
505
            handle_remove_box();
8✔
506
        }
507
        else {
508
            handle_generate_box();
114✔
509
        }
510
    } while (opt.mend > 0);
122✔
511

512
    return EXIT_SUCCESS;
121✔
513
}
514

515
/*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