Extract command line parsing functionality into its own, new 'cmdline' module #78

Also encapsulate this feature better, eliminate side effects,
and refactor into smaller functions.
This commit is contained in:
Thomas Jensen 2021-04-11 22:07:40 +02:00
parent 6427d1efde
commit af6f123c99
No known key found for this signature in database
GPG Key ID: A4ACEE270D0FB7DB
5 changed files with 950 additions and 752 deletions

View File

@ -28,9 +28,9 @@ GEN_HDR = parser.h boxes.h lex.yy.h
GEN_SRC = parser.c lex.yy.c
GEN_FILES = $(GEN_SRC) $(GEN_HDR)
ORIG_HDRCL = boxes.in.h config.h
ORIG_HDR = $(ORIG_HDRCL) discovery.h generate.h lexer.h parsecode.h parsing.h regulex.h remove.h shape.h tools.h unicode.h
ORIG_HDR = $(ORIG_HDRCL) cmdline.h discovery.h generate.h lexer.h parsecode.h parsing.h regulex.h remove.h shape.h tools.h unicode.h
ORIG_GEN = lexer.l parser.y
ORIG_NORM = boxes.c discovery.c generate.c parsecode.c parsing.c regulex.c remove.c shape.c tools.c unicode.c
ORIG_NORM = boxes.c cmdline.c discovery.c generate.c parsecode.c parsing.c regulex.c remove.c shape.c tools.c unicode.c
ORIG_SRC = $(ORIG_GEN) $(ORIG_NORM)
ORIG_FILES = $(ORIG_SRC) $(ORIG_HDR)
@ -87,18 +87,19 @@ lex.yy.c lex.yy.h: lexer.l | check_dir
$(LEX) --header-file=lex.yy.h $<
boxes.o: boxes.c boxes.h discovery.h regulex.h shape.h tools.h unicode.h generate.h remove.h config.h | check_dir
cmdline.o: cmdline.c cmdline.h boxes.h tools.h config.h | check_dir
discovery.o: discovery.c discovery.h boxes.h tools.h config.h | check_dir
tools.o: tools.c tools.h boxes.h shape.h unicode.h config.h | check_dir
unicode.o: unicode.c unicode.h boxes.h tools.h config.h | check_dir
shape.o: shape.c shape.h boxes.h tools.h config.h | check_dir
generate.o: generate.c generate.h boxes.h shape.h tools.h unicode.h config.h | check_dir
parsing.o: parsing.c parsing.h parser.h lex.yy.h boxes.h tools.h config.h | check_dir
remove.o: remove.c remove.h boxes.h shape.h tools.h unicode.h config.h | check_dir
regulex.o: regulex.c regulex.h boxes.h tools.h unicode.h config.h | check_dir
getopt.o: misc/getopt.c misc/getopt.h | check_dir
parser.o: parser.c boxes.h lex.yy.h parser.h parsing.h tools.h shape.h discovery.h config.h | check_dir
lex.yy.o: lex.yy.c parser.h boxes.h parsing.h tools.h shape.h config.h | check_dir
parsecode.o: parsecode.c parser.h boxes.h tools.h lex.yy.h regulex.h unicode.h config.h | check_dir
parser.o: parser.c boxes.h lex.yy.h parser.h parsing.h tools.h shape.h discovery.h config.h | check_dir
parsing.o: parsing.c parsing.h parser.h lex.yy.h boxes.h tools.h config.h | check_dir
regulex.o: regulex.c regulex.h boxes.h tools.h unicode.h config.h | check_dir
remove.o: remove.c remove.h boxes.h shape.h tools.h unicode.h config.h | check_dir
shape.o: shape.c shape.h boxes.h tools.h config.h | check_dir
tools.o: tools.c tools.h boxes.h shape.h unicode.h config.h | check_dir
unicode.o: unicode.c unicode.h boxes.h tools.h config.h | check_dir
package: $(BOXES_EXECUTABLE_NAME)
if [ -z "$(PKG_NAME)" ] ; then exit 1 ; fi

View File

@ -20,13 +20,11 @@
#include "config.h"
#include <errno.h>
#include <limits.h>
#include <locale.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <unictype.h>
#include <unistdio.h>
@ -34,13 +32,9 @@
#include <unitypes.h>
#include <uniwidth.h>
#ifdef __MINGW32__
#include <fcntl.h> /* _O_BINARY */
#include <io.h> /* _setmode() */
#endif
#include "shape.h"
#include "boxes.h"
#include "cmdline.h"
#include "shape.h"
#include "tools.h"
#include "discovery.h"
#include "generate.h"
@ -63,9 +57,6 @@ typedef struct {
| G l o b a l V a r i a b l e s |
+--------------------------------------------------------------------------*/
extern char *optarg; /* for getopt() */
extern int optind, opterr, optopt; /* for getopt() */
design_t *designs = NULL; /* available box designs */
int anz_designs = 0; /* no of designs after parsing */
@ -81,658 +72,6 @@ input_t input = INPUT_INITIALIZER; /* input lines */
+--------------------------------------------------------------------------*/
/**
* Print usage information on stream `st`.
* @param st the stream to print to
*/
static void usage(FILE *st)
{
char *config_file = discover_config_file(0);
fprintf(st, "Usage: %s [options] [infile [outfile]]\n", PROJECT);
fprintf(st, " -a fmt alignment/positioning of text inside box [default: hlvt]\n");
fprintf(st, " -c str use single shape box design where str is the W shape\n");
fprintf(st, " -d name box design [default: first one in file]\n");
fprintf(st, " -e eol Override line break type (experimental) [default: %s]\n",
strcmp(EOL_DEFAULT, "\r\n") == 0 ? "CRLF" : "LF");
fprintf(st, " -f file configuration file [default: %s]\n", config_file != NULL ? config_file : "none");
fprintf(st, " -h print usage information\n");
fprintf(st, " -i mode indentation mode [default: box]\n");
fprintf(st, " -k bool leading/trailing blank line retention on removal\n");
fprintf(st, " -l list available box designs w/ samples\n");
fprintf(st, " -m mend box, i.e. remove it and redraw it afterwards\n");
fprintf(st, " -n enc Character encoding of input and output [default: %s]\n", locale_charset());
fprintf(st, " -p fmt padding [default: none]\n");
fprintf(st, " -q qry query the list of designs by tag\n"); /* with "(undoc)" as query, trigger undocumented webui stuff instead */
fprintf(st, " -r remove box\n");
fprintf(st, " -s wxh box size (width w and/or height h)\n");
fprintf(st, " -t str tab stop distance and expansion [default: %de]\n", DEF_TABSTOP);
fprintf(st, " -v print version information\n");
BFREE(config_file);
}
/**
* Print abbreviated usage information on stream `st`.
* @param st the stream to print to
*/
static void usage_short(FILE *st)
{
fprintf(st, "Usage: %s [options] [infile [outfile]]\n", PROJECT);
fprintf(st, "Try `%s -h' for more information.\n", PROJECT);
}
/**
* Print usage information on stream `st`, including a header text.
* @param st the stream to print to
*/
static void usage_long(FILE *st)
{
fprintf(st, "%s - draws any kind of box around your text (or removes it)\n", PROJECT);
fprintf(st, " Website: https://boxes.thomasjensen.com/\n");
usage(st);
}
/**
* Set *stdout* to binary mode, so that we can control the line terminator. This function only ever does anything on
* Windows, because on Linux, we already do have control over line terminators.
* @return the *stdout* stream, reconfigured to binary if necessary
*/
static FILE *get_stdout_configured()
{
#ifdef __MINGW32__
if (opt.eol != NULL) {
int rc = _setmode(fileno(stdout), _O_BINARY);
if (rc == -1) {
perror(PROJECT);
}
}
#endif
return stdout;
}
static int validate_tag(char *tag)
{
if (strcmp(tag, QUERY_ALL) == 0 || strcmp(tag, QUERY_UNDOC) == 0) {
return 1;
}
return tag_is_valid(tag);
}
static char **parse_tag_query(char *optarg)
{
char **result = NULL;
char *dup = strdup(optarg); /* required because strtok() modifies its input */
int contains_positive_element = 0;
size_t num_expr = 0;
for (char *q = strtok(dup, ","); q != NULL; q = strtok(NULL, ","))
{
char *trimmed = trimdup(q, q + strlen(q) - 1);
if (strlen(trimmed) == 0) {
BFREE(trimmed);
continue;
}
if (trimmed[0] != '-') {
contains_positive_element = 1;
}
char *raw_tag = (trimmed[0] == '+' || trimmed[0] == '-') ? (trimmed + 1) : trimmed;
if (!validate_tag(raw_tag)) {
fprintf(stderr, "%s: not a tag -- %s\n", PROJECT, raw_tag);
return NULL;
}
if (result != NULL) {
for (size_t i = 0; result[i] != NULL; ++i) {
char *restag = (result[i][0] == '+' || result[i][0] == '-') ? (result[i] + 1) : result[i];
if (strcasecmp(restag, raw_tag) == 0) {
fprintf(stderr, "%s: duplicate query expression -- %s\n", PROJECT, trimmed);
return NULL;
}
}
}
++num_expr;
result = (char **) realloc(result, (num_expr + 1) * sizeof(char *));
if (result == NULL) {
perror(PROJECT);
break;
}
result[num_expr - 1] = trimmed;
result[num_expr] = NULL;
}
BFREE(dup);
if (num_expr == 0) {
fprintf(stderr, "%s: empty tag query -- %s\n", PROJECT, optarg);
return NULL;
}
if (!contains_positive_element) {
++num_expr;
result = (char **) realloc(result, (num_expr + 1) * sizeof(char *));
if (result == NULL) {
perror(PROJECT);
}
else {
result[num_expr - 1] = QUERY_ALL;
result[num_expr] = NULL;
}
}
return result;
}
static int process_commandline(int argc, char *argv[])
/*
* Process command line options.
*
* argc, argv command line as passed to main()
*
* RETURNS: == 0 success, continue
* == 42 success, but terminate anyway (e.g. help/version)
* != 0/42 error
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
{
int oc; /* option character */
int idummy;
char *pdummy;
char c;
int errfl = 0; /* true on error */
size_t optlen;
/*
* Set default values
*/
memset(&opt, 0, sizeof(opt_t));
opt.tabstop = DEF_TABSTOP;
opt.eol = EOL_DEFAULT;
opt.f = NULL;
opt.tabexp = 'e';
opt.killblank = -1;
opt.encoding = NULL;
opt.query = NULL;
for (idummy = 0; idummy < ANZ_SIDES; ++idummy) {
opt.padding[idummy] = -1;
}
/*
* Intercept '--help' and '-?' cases first, as they are not supported by getopt()
*/
if (argc >= 2 && argv[1] != NULL && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)) {
usage_long(stdout);
return 42;
}
/*
* Parse Command Line
*/
do {
oc = getopt(argc, argv, "a:c:d:e:f:hi:k:lmn:p:q:rs:t:v");
switch (oc) {
case 'a':
/*
* Alignment/positioning of text inside box
*/
errfl = 0;
pdummy = optarg;
while (*pdummy) {
if (pdummy[1] == '\0' && !strchr("lLcCrR", *pdummy)) {
errfl = 1;
break;
}
switch (*pdummy) {
case 'h': case 'H':
switch (pdummy[1]) {
case 'c': case 'C': opt.halign = 'c'; break;
case 'l': case 'L': opt.halign = 'l'; break;
case 'r': case 'R': opt.halign = 'r'; break;
default: errfl = 1; break;
}
++pdummy;
break;
case 'v': case 'V':
switch (pdummy[1]) {
case 'c': case 'C': opt.valign = 'c'; break;
case 't': case 'T': opt.valign = 't'; break;
case 'b': case 'B': opt.valign = 'b'; break;
default: errfl = 1; break;
}
++pdummy;
break;
case 'j': case 'J':
switch (pdummy[1]) {
case 'l': case 'L': opt.justify = 'l'; break;
case 'c': case 'C': opt.justify = 'c'; break;
case 'r': case 'R': opt.justify = 'r'; break;
default: errfl = 1; break;
}
++pdummy;
break;
case 'l': case 'L':
opt.justify = 'l';
opt.halign = 'l';
opt.valign = 'c';
break;
case 'r': case 'R':
opt.justify = 'r';
opt.halign = 'r';
opt.valign = 'c';
break;
case 'c': case 'C':
opt.justify = 'c';
opt.halign = 'c';
opt.valign = 'c';
break;
default:
errfl = 1;
break;
}
if (errfl) {
break;
} else {
++pdummy;
}
}
if (errfl) {
fprintf(stderr, "%s: Illegal text format -- %s\n", PROJECT, optarg);
return 1;
}
break;
case 'c':
/*
* Command line design definition
*/
opt.cld = (char *) strdup(optarg);
if (opt.cld == NULL) {
perror(PROJECT);
return 1;
}
else {
line_t templine = {0};
templine.len = strlen(opt.cld);
templine.text = opt.cld;
if (empty_line(&templine)) {
fprintf(stderr, "%s: boxes may not consist entirely of whitespace\n", PROJECT);
return 1;
}
}
opt.design_choice_by_user = 1;
break;
case 'd':
/*
* Box design selection
*/
BFREE (opt.design);
opt.design = (design_t *) ((char *) strdup(optarg));
if (opt.design == NULL) {
perror(PROJECT);
return 1;
}
opt.design_choice_by_user = 1;
break;
case 'e':
/*
* EOL Override
*/
if (strcasecmp(optarg, "CRLF") == 0) {
opt.eol = "\r\n";
} else if (strcasecmp(optarg, "LF") == 0) {
opt.eol = "\n";
} else if (strcasecmp(optarg, "CR") == 0) {
opt.eol = "\r";
} else {
fprintf(stderr, "%s: invalid eol spec -- %s\n", PROJECT, optarg);
return 1;
}
break;
case 'f':
/*
* Input File
*/
opt.f = optarg;
break;
case 'h':
/*
* Display usage information and terminate
*/
usage_long(stdout);
return 42;
case 'i':
/*
* Indentation mode
*/
optlen = strlen(optarg);
if (optlen <= 3 && !strncasecmp("box", optarg, optlen)) {
opt.indentmode = 'b';
} else if (optlen <= 4 && !strncasecmp("text", optarg, optlen)) {
opt.indentmode = 't';
} else if (optlen <= 4 && !strncasecmp("none", optarg, optlen)) {
opt.indentmode = 'n';
} else {
fprintf(stderr, "%s: invalid indentation mode\n", PROJECT);
return 1;
}
break;
/*
* Kill blank lines or not [default: design-dependent]
*/
case 'k':
if (opt.killblank == -1) {
if (strisyes(optarg)) {
opt.killblank = 1;
} else if (strisno(optarg)) {
opt.killblank = 0;
} else {
fprintf(stderr, "%s: -k: invalid parameter\n", PROJECT);
return 1;
}
}
break;
case 'l':
/*
* List available box styles
*/
opt.l = 1;
break;
case 'm':
/*
* Mend box: remove, then redraw
*/
opt.mend = 2;
opt.r = 1;
opt.killblank = 0;
break;
case 'n':
/*
* Character encoding
*/
opt.encoding = (char *) strdup(optarg);
if (opt.encoding == NULL) {
perror(PROJECT);
return 1;
}
break;
case 'p':
/*
* Padding. format is ([ahvtrbl]n)+
*/
errfl = 0;
pdummy = optarg;
while (*pdummy) {
if (pdummy[1] == '\0') {
errfl = 1;
break;
}
c = *pdummy;
errno = 0;
idummy = (int) strtol(pdummy + 1, &pdummy, 10);
if (errno || idummy < 0) {
errfl = 1;
break;
}
switch (c) {
case 'a': case 'A':
opt.padding[BTOP] = idummy;
opt.padding[BBOT] = idummy;
opt.padding[BLEF] = idummy;
opt.padding[BRIG] = idummy;
break;
case 'h': case 'H':
opt.padding[BLEF] = idummy;
opt.padding[BRIG] = idummy;
break;
case 'v': case 'V':
opt.padding[BTOP] = idummy;
opt.padding[BBOT] = idummy;
break;
case 't': case 'T':
opt.padding[BTOP] = idummy;
break;
case 'l': case 'L':
opt.padding[BLEF] = idummy;
break;
case 'b': case 'B':
opt.padding[BBOT] = idummy;
break;
case 'r': case 'R':
opt.padding[BRIG] = idummy;
break;
default:
errfl = 1;
break;
}
if (errfl) {
break;
}
}
if (errfl) {
fprintf(stderr, "%s: invalid padding specification - %s\n", PROJECT, optarg);
return 1;
}
break;
case 'q':
/*
* Record the argument of the '-q' option. Combined with '-l', this is a tag query,
* else it activates undocumented special behavior used by the web UI.
*/
opt.query = parse_tag_query(optarg);
if (opt.query == NULL) {
return 1;
}
break;
case 'r':
/*
* Remove box from input
*/
opt.r = 1;
break;
case 's':
/*
* Specify desired box target size
*/
pdummy = strchr(optarg, 'x');
if (!pdummy) {
pdummy = strchr(optarg, 'X');
}
if (pdummy) {
*pdummy = '\0';
}
errno = 0;
if (optarg != pdummy) {
opt.reqwidth = strtol(optarg, NULL, 10);
}
if (pdummy) {
opt.reqheight = strtol(pdummy + 1, NULL, 10);
*pdummy = 'x';
}
if (errno || (opt.reqwidth == 0 && opt.reqheight == 0)
|| opt.reqwidth < 0 || opt.reqheight < 0) {
fprintf(stderr, "%s: invalid box size specification -- %s\n",
PROJECT, optarg);
return 1;
}
break;
case 't':
/*
* Tab handling. Format is n[eku]
*/
idummy = (int) strtol(optarg, &pdummy, 10);
if (idummy < 1 || idummy > MAX_TABSTOP) {
fprintf(stderr, "%s: invalid tab stop distance -- %d\n",
PROJECT, idummy);
return 1;
}
opt.tabstop = idummy;
errfl = 0;
if (*pdummy != '\0') {
if (pdummy[1] != '\0') {
errfl = 1;
}
else {
switch (*pdummy) {
case 'e': case 'E':
opt.tabexp = 'e';
break;
case 'k': case 'K':
opt.tabexp = 'k';
break;
case 'u': case 'U':
opt.tabexp = 'u';
break;
default:
errfl = 1;
break;
}
}
}
if (errfl) {
fprintf(stderr, "%s: invalid tab handling specification - "
"%s\n", PROJECT, optarg);
return 1;
}
break;
case 'v':
/*
* Print version number
*/
printf("%s version %s\n", PROJECT, VERSION);
return 42;
case ':':
case '?':
/*
* Missing argument or illegal option - do nothing else
*/
usage_short(stderr);
return 1;
case EOF:
/*
* End of list, do nothing more
*/
break;
default: /* This case must never be */
fprintf(stderr, "%s: internal error\n", PROJECT);
return 1;
}
} while (oc != EOF);
/*
* Input and Output Files
*
* After any command line options, an input file and an output file may
* be specified (in that order). "-" may be substituted for standard
* input or output. A third file name would be invalid.
*/
if (argv[optind] == NULL) { /* neither infile nor outfile given */
opt.infile = stdin;
opt.outfile = get_stdout_configured();
}
else if (argv[optind + 1] && argv[optind + 2]) { /* illegal third file */
fprintf(stderr, "%s: illegal parameter -- %s\n", PROJECT, argv[optind + 2]);
usage_short(stderr);
return 1;
}
else {
if (strcmp(argv[optind], "-") == 0) {
opt.infile = stdin; /* use stdin for input */
}
else {
opt.infile = fopen(argv[optind], "r");
if (opt.infile == NULL) {
fprintf(stderr, "%s: Can\'t open input file -- %s\n", PROJECT, argv[optind]);
return 9; /* can't read infile */
}
}
if (argv[optind + 1] == NULL) {
opt.outfile = get_stdout_configured(); /* no outfile given */
}
else if (strcmp(argv[optind + 1], "-") == 0) {
opt.outfile = get_stdout_configured(); /* use stdout for output */
}
else {
opt.outfile = fopen(argv[optind + 1], "wb");
if (opt.outfile == NULL) {
perror(PROJECT);
if (opt.infile != stdin) {
fclose(opt.infile);
}
return 10;
}
}
}
#if defined(DEBUG) || 0
fprintf (stderr, "Command line option settings (excerpt):\n");
fprintf (stderr, "- Explicit config file: %s\n", opt.f ? opt.f : "no");
fprintf (stderr, "- Padding: l:%d t:%d r:%d b:%d\n", opt.padding[BLEF],
opt.padding[BTOP], opt.padding[BRIG], opt.padding[BBOT]);
fprintf (stderr, "- Requested box size: %ldx%ld\n", opt.reqwidth, opt.reqheight);
fprintf (stderr, "- Tabstop distance: %d\n", opt.tabstop);
fprintf (stderr, "- Tab handling: \'%c\'\n", opt.tabexp);
fprintf (stderr, "- Alignment: horiz %c, vert %c\n", opt.halign?opt.halign:'?', opt.valign?opt.valign:'?');
fprintf (stderr, "- Indentmode: \'%c\'\n", opt.indentmode? opt.indentmode: '?');
fprintf (stderr, "- Line justification: \'%c\'\n", opt.justify? opt.justify: '?');
fprintf (stderr, "- Kill blank lines: %d\n", opt.killblank);
fprintf (stderr, "- Remove box: %d\n", opt.r);
fprintf (stderr, "- Tag Query / Special handling for Web UI: ");
if (opt.query != NULL) {
for (size_t qidx = 0; opt.query[qidx] != NULL; ++qidx) {
fprintf(stderr, "%s%s", qidx > 0 ? ", " : "", opt.query[qidx]);
}
} else {
fprintf (stderr, "(none)");
}
fprintf (stderr, "\n");
fprintf (stderr, "- Mend box: %d\n", opt.mend);
fprintf (stderr, "- Design Definition W shape: %s\n", opt.cld? opt.cld: "n/a");
fprintf (stderr, "- Line terminator used: %s\n",
strcmp(opt.eol, "\r\n") == 0 ? "CRLF" : (strcmp(opt.eol, "\r") == 0 ? "CR" : "LF"));
#endif
return 0;
}
static int build_design(design_t **adesigns, const char *cld)
/*
* Build a box design.
@ -1639,13 +978,20 @@ int main(int argc, char *argv[])
#ifdef DEBUG
fprintf (stderr, "Processing Command Line ...\n");
#endif
rc = process_commandline(argc, argv);
if (rc == 42) {
exit(EXIT_SUCCESS);
}
if (rc) {
opt_t *parsed_opts = process_commandline(argc, argv);
if (parsed_opts == NULL) {
exit(EXIT_FAILURE);
}
if (parsed_opts->help) {
usage_long(stdout);
exit(EXIT_SUCCESS);
}
if (parsed_opts->version_requested) {
printf("%s version %s\n", PROJECT, VERSION);
exit(EXIT_SUCCESS);
}
memcpy(&opt, parsed_opts, sizeof(opt_t));
BFREE(parsed_opts);
/*
* Store system character encoding

View File

@ -42,16 +42,8 @@
#define GLOBALCONF "--GLOBALCONF--" /* name of system-wide config file */
/*
* default settings of all kinds (THIS PARAGRAPH MAY BE EDITED)
*/
#define DEF_TABSTOP 8 /* default tab stop distance (part of -t) */
#define DEF_INDENTMODE 'b' /* indent box, not text by default */
#define DEF_INDENTMODE 'b' /* default indent mode of a design (indent box, not text) */
/*
* max. allowed tab stop distance
*/
#define MAX_TABSTOP 16
/*
* max. supported line length in bytes
@ -68,14 +60,7 @@
#endif
#ifdef DEBUG
#define __TJ(s) fprintf (stderr, s);
#else
#define __TJ(s) /**/
#endif
#define BTOP 0 /* for use with sides */
#define BTOP 0 /* for use with sides */
#define BRIG 1
#define BBOT 2
#define BLEF 3
@ -84,34 +69,34 @@
typedef struct {
char *search;
char *repstr;
pcre2_code *prog; /* compiled search pattern */
int line; /* line of definition in config file */
char mode; /* 'g' or 'o' */
pcre2_code *prog; /* compiled search pattern */
int line; /* line of definition in config file */
char mode; /* 'g' or 'o' */
} reprule_t;
typedef struct {
char *name;
char **aliases; /* zero-terminated array of alias names of the design */
char *author; /* creator of the configuration file entry */
char *designer; /* creator of the original ASCII artwork */
char *created; /* date created, free format */
char *revision; /* revision number of design */
char *revdate; /* date of current revision */
char **aliases; /* zero-terminated array of alias names of the design */
char *author; /* creator of the configuration file entry */
char *designer; /* creator of the original ASCII artwork */
char *created; /* date created, free format */
char *revision; /* revision number of design */
char *revdate; /* date of current revision */
char *sample;
char indentmode; /* 'b', 't', or 'n' */
char indentmode; /* 'b', 't', or 'n' */
sentry_t shape[ANZ_SHAPES];
size_t maxshapeheight; /* height of highest shape in design */
size_t maxshapeheight; /* height of highest shape in design */
size_t minwidth;
size_t minheight;
int padding[ANZ_SIDES];
char **tags;
char *defined_in; /* path to config file where this was defined */
char *defined_in; /* path to config file where this was defined */
reprule_t *current_rule;
reprule_t *reprules; /* applied when drawing a box */
reprule_t *reprules; /* applied when drawing a box */
size_t anz_reprules;
reprule_t *revrules; /* applied upon removal of a box */
reprule_t *revrules; /* applied upon removal of a box */
size_t anz_revrules;
} design_t;
@ -119,37 +104,32 @@ extern design_t *designs;
extern int anz_designs;
/* system default line terminator */
#ifdef __MINGW32__
#define EOL_DEFAULT "\r\n"
#else
#define EOL_DEFAULT "\n"
#endif
typedef struct { /* Command line options: */
int l; /** list available designs */
char *f; /** the string specified as argument to -f ; config file path */
int mend; /** 1 if -m is given, 2 in 2nd loop */
char **query; /** parsed tag query expression passed in via -q; also, special handling of web UI needs */
char *eol; /** line break to use. Never NULL, default to EOL_DEFAULT. */
int r; /** remove box from input */
int tabstop; /** tab stop distance */
char tabexp; /** tab expansion mode (for leading tabs) */
int padding[ANZ_SIDES]; /** in spaces or lines resp. */
design_t *design; /** currently used box design */
int design_choice_by_user; /** true if design was chosen by user */
char *cld; /** commandline design definition, -c */
long reqwidth; /** requested box width (-s) */
long reqheight; /** requested box height (-s) */
char valign; /** text position inside box */
char halign; /** ( h[lcr]v[tcb] ) */
char indentmode; /** 'b', 't', 'n', or '\0' */
char justify; /** 'l', 'c', 'r', or '\0' */
int killblank; /** -1 if not set */
char *encoding; /** character encoding override for input and output text */
FILE *infile; /** where we get our input */
FILE *outfile; /** where we put our output */
typedef struct { /* Command line options: */
char valign; /** `-a`: text position inside box */
char halign; /** `-a`: ( h[lcr]v[tcb] ) */
char justify; /** `-a`: 'l', 'c', 'r', or '\0' */
char *cld; /** `-c`: commandline design definition */
design_t *design; /** `-d`: currently used box design */
int design_choice_by_user; /** `-d`, `-c`: true if design was chosen by user */
char *eol; /** `-e`: line break to use. Never NULL, default to "\n". */
int eol_overridden; /** `-e`: 0: value in `eol` is the default; 1: value in `eol` specified via `-e` */
char *f; /** `-f`: config file path */
int help; /** `-h`: flags if help argument was specified */
char indentmode; /** `-i`: 'b', 't', 'n', or '\0' */
int killblank; /** `-k`: kill blank lines, -1 if not set */
int l; /** `-l`: list available designs */
int mend; /** `-m`: 1 if -m is given, 2 in 2nd loop */
char *encoding; /** `-n`: character encoding override for input and output text */
int padding[ANZ_SIDES]; /** `-p`: in spaces or lines resp. */
char **query; /** `-q`: parsed tag query expression passed in via -q; also, special handling of web UI needs */
int r; /** `-r`: remove box from input */
long reqwidth; /** `-s`: requested box width */
long reqheight; /** `-s`: requested box height */
int tabstop; /** `-t`: tab stop distance */
char tabexp; /** `-t`: tab expansion mode (for leading tabs) */
int version_requested; /** `-v`: request to show version number */
FILE *infile;
FILE *outfile;
} opt_t;
extern opt_t opt;
@ -163,27 +143,27 @@ int query_is_undoc();
typedef struct {
size_t len; /* length of visible text in columns (visible character positions in a text terminal), which is the same as the length of the 'text' field */
char *text; /* ASCII line content, tabs expanded, ansi escapes removed, multi-byte chars replaced with one or more 'x' */
size_t invis; /* number of invisble columns/characters (part of an ansi sequence) */
size_t len; /* length of visible text in columns (visible character positions in a text terminal), which is the same as the length of the 'text' field */
char *text; /* ASCII line content, tabs expanded, ansi escapes removed, multi-byte chars replaced with one or more 'x' */
size_t invis; /* number of invisble columns/characters (part of an ansi sequence) */
uint32_t *mbtext; /* multi-byte (original) line content, tabs expanded. We use UTF-32 in order to enable pointer arithmetic. */
size_t num_chars; /* total number of characters in mbtext, visible + invisible */
uint32_t *mbtext_org; /* mbtext as originally allocated, so that we can free it again */
uint32_t *mbtext; /* multi-byte (original) line content, tabs expanded. We use UTF-32 in order to enable pointer arithmetic. */
size_t num_chars; /* total number of characters in mbtext, visible + invisible */
uint32_t *mbtext_org; /* mbtext as originally allocated, so that we can free it again */
size_t *tabpos; /* tab positions in expanded work strings, or NULL if not needed */
size_t tabpos_len; /* number of tabs in a line */
size_t *posmap; /* for each character in `text`, position of corresponding char in `mbtext`. Needed for box removal. */
size_t *tabpos; /* tab positions in expanded work strings, or NULL if not needed */
size_t tabpos_len; /* number of tabs in a line */
size_t *posmap; /* for each character in `text`, position of corresponding char in `mbtext`. Needed for box removal. */
} line_t;
#ifndef FILE_LEXER_L
typedef struct {
line_t *lines;
size_t anz_lines; /* number of entries in input */
size_t maxline; /* length of longest input line */
size_t indent; /* number of leading spaces found */
int final_newline; /* true if the last line of input ends with newline */
size_t anz_lines; /* number of entries in input */
size_t maxline; /* length of longest input line */
size_t indent; /* number of leading spaces found */
int final_newline; /* true if the last line of input ends with newline */
} input_t;
#define INPUT_INITIALIZER {NULL, 0, 0, LINE_MAX_BYTES, 0}

824
src/cmdline.c Normal file
View File

@ -0,0 +1,824 @@
/*
* boxes - Command line filter to draw/remove ASCII boxes around text
* Copyright (c) 1999-2021 Thomas Jensen and the boxes contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 2, as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
/*
* Processing of command line options.
*/
#include "config.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <uniconv.h>
#include <unistd.h>
#ifdef __MINGW32__
#include <fcntl.h> /* _O_BINARY */
#include <io.h> /* _setmode() */
#endif
#include "boxes.h"
#include "discovery.h"
#include "tools.h"
#include "cmdline.h"
extern char *optarg; /* for getopt() */
extern int optind; /* for getopt() */
/* default tab stop distance (part of -t) */
#define DEF_TABSTOP 8
/* max. allowed tab stop distance */
#define MAX_TABSTOP 16
/* System default line terminator.
* Used only for display in usage info. The real default is always "\n", with stdout in text mode. */
#ifdef __MINGW32__
#define EOL_DEFAULT "\r\n"
#else
#define EOL_DEFAULT "\n"
#endif
/**
* Print abbreviated usage information on stream `st`.
* @param st the stream to print to
*/
static void usage_short(FILE *st)
{
fprintf(st, "Usage: %s [options] [infile [outfile]]\n", PROJECT);
fprintf(st, "Try `%s -h' for more information.\n", PROJECT);
}
/**
* Print usage information on stream `st`, including a header text.
* @param st the stream to print to
*/
void usage_long(FILE *st)
{
char *config_file = discover_config_file(0);
fprintf(st, "%s - draws any kind of box around your text (or removes it)\n", PROJECT);
fprintf(st, " Website: https://boxes.thomasjensen.com/\n");
fprintf(st, "Usage: %s [options] [infile [outfile]]\n", PROJECT);
fprintf(st, " -a fmt alignment/positioning of text inside box [default: hlvt]\n");
fprintf(st, " -c str use single shape box design where str is the W shape\n");
fprintf(st, " -d name box design [default: first one in file]\n");
fprintf(st, " -e eol Override line break type (experimental) [default: %s]\n",
strcmp(EOL_DEFAULT, "\r\n") == 0 ? "CRLF" : "LF");
fprintf(st, " -f file configuration file [default: %s]\n", config_file != NULL ? config_file : "none");
fprintf(st, " -h print usage information\n");
fprintf(st, " -i mode indentation mode [default: box]\n");
fprintf(st, " -k bool leading/trailing blank line retention on removal\n");
fprintf(st, " -l list available box designs w/ samples\n");
fprintf(st, " -m mend box, i.e. remove it and redraw it afterwards\n");
fprintf(st, " -n enc Character encoding of input and output [default: %s]\n", locale_charset());
fprintf(st, " -p fmt padding [default: none]\n");
fprintf(st, " -q qry query the list of designs by tag\n"); /* with "(undoc)" as query, trigger undocumented webui stuff instead */
fprintf(st, " -r remove box\n");
fprintf(st, " -s wxh box size (width w and/or height h)\n");
fprintf(st, " -t str tab stop distance and expansion [default: %de]\n", DEF_TABSTOP);
fprintf(st, " -v print version information\n");
BFREE(config_file);
}
static int validate_tag(char *tag)
{
if (strcmp(tag, QUERY_ALL) == 0 || strcmp(tag, QUERY_UNDOC) == 0) {
return 1;
}
return tag_is_valid(tag);
}
static opt_t *create_new_opt()
{
opt_t *result = (opt_t *) calloc(1, sizeof(opt_t));
if (result != NULL) {
/* all valued initialized with 0 or NULL */
result->tabstop = DEF_TABSTOP;
result->eol = "\n"; /* we must default to "\n" instead of EOL_DEFAULT as long as stdout is in text mode */
result->tabexp = 'e';
result->killblank = -1;
for (int i = 0; i < ANZ_SIDES; ++i) {
result->padding[i] = -1;
}
}
else {
perror(PROJECT);
}
return result;
}
/**
* Alignment/positioning of text inside box.
* @param result the options struct we are building
* @param optarg the argument to `-a` on the command line
* @returns 0 on success, anything else on error
*/
static int alignment(opt_t *result, char *optarg)
{
int errfl = 0;
char *p = optarg;
while (*p) {
if (p[1] == '\0' && !strchr("lLcCrR", *p)) {
errfl = 1;
break;
}
switch (*p) {
case 'h': case 'H':
switch (p[1]) {
case 'c': case 'C': result->halign = 'c'; break;
case 'l': case 'L': result->halign = 'l'; break;
case 'r': case 'R': result->halign = 'r'; break;
default: errfl = 1; break;
}
++p;
break;
case 'v': case 'V':
switch (p[1]) {
case 'c': case 'C': result->valign = 'c'; break;
case 't': case 'T': result->valign = 't'; break;
case 'b': case 'B': result->valign = 'b'; break;
default: errfl = 1; break;
}
++p;
break;
case 'j': case 'J':
switch (p[1]) {
case 'l': case 'L': result->justify = 'l'; break;
case 'c': case 'C': result->justify = 'c'; break;
case 'r': case 'R': result->justify = 'r'; break;
default: errfl = 1; break;
}
++p;
break;
case 'l': case 'L':
result->justify = 'l';
result->halign = 'l';
result->valign = 'c';
break;
case 'r': case 'R':
result->justify = 'r';
result->halign = 'r';
result->valign = 'c';
break;
case 'c': case 'C':
result->justify = 'c';
result->halign = 'c';
result->valign = 'c';
break;
default:
errfl = 1;
break;
}
if (errfl) {
break;
} else {
++p;
}
}
if (errfl) {
fprintf(stderr, "%s: Illegal text format -- %s\n", PROJECT, optarg);
return 1;
}
return 0;
}
/**
* Command line design definition.
* @param result the options struct we are building
* @param optarg the argument to `-c` on the command line
* @returns 0 on success, anything else on error
*/
static int command_line_design(opt_t *result, char *optarg)
{
result->cld = (char *) strdup(optarg);
if (result->cld == NULL) {
perror(PROJECT);
return 1;
}
else {
line_t templine = {0};
templine.len = strlen(result->cld);
templine.text = result->cld;
if (empty_line(&templine)) {
fprintf(stderr, "%s: boxes may not consist entirely of whitespace\n", PROJECT);
return 1;
}
}
result->design_choice_by_user = 1;
return 0;
}
/**
* Box design selection.
* @param result the options struct we are building
* @param optarg the argument to `-d` on the command line
* @returns 0 on success, anything else on error
*/
static int design_choice(opt_t *result, char *optarg)
{
BFREE (result->design);
result->design = (design_t *) ((char *) strdup(optarg));
if (result->design == NULL) {
perror(PROJECT);
return 1;
}
result->design_choice_by_user = 1;
return 0;
}
/**
* EOL Override.
* @param result the options struct we are building
* @param optarg the argument to `-e` on the command line
* @returns 0 on success, anything else on error
*/
static int eol_override(opt_t *result, char *optarg)
{
result->eol_overridden = 1;
if (strcasecmp(optarg, "CRLF") == 0) {
result->eol = "\r\n";
}
else if (strcasecmp(optarg, "LF") == 0) {
result->eol = "\n";
}
else if (strcasecmp(optarg, "CR") == 0) {
result->eol = "\r";
}
else {
fprintf(stderr, "%s: invalid eol spec -- %s\n", PROJECT, optarg);
return 1;
}
return 0;
}
/**
* Indentation mode.
* @param result the options struct we are building
* @param optarg the argument to `-i` on the command line
* @returns 0 on success, anything else on error
*/
static int indentation_mode(opt_t *result, char *optarg)
{
size_t optlen = strlen(optarg);
if (optlen <= 3 && !strncasecmp("box", optarg, optlen)) {
result->indentmode = 'b';
}
else if (optlen <= 4 && !strncasecmp("text", optarg, optlen)) {
result->indentmode = 't';
}
else if (optlen <= 4 && !strncasecmp("none", optarg, optlen)) {
result->indentmode = 'n';
}
else {
fprintf(stderr, "%s: invalid indentation mode\n", PROJECT);
return 1;
}
return 0;
}
/**
* Kill blank lines or not [default: design-dependent].
* @param result the options struct we are building
* @param optarg the argument to `-k` on the command line
* @returns 0 on success, anything else on error
*/
static int killblank(opt_t *result, char *optarg)
{
if (result->killblank == -1) {
if (strisyes(optarg)) {
result->killblank = 1;
}
else if (strisno(optarg)) {
result->killblank = 0;
}
else {
fprintf(stderr, "%s: -k: invalid parameter\n", PROJECT);
return 1;
}
}
return 0;
}
/**
* Padding. Format is `([ahvtrbl]n)+`.
* @param result the options struct we are building
* @param optarg the argument to `-p` on the command line
* @returns 0 on success, anything else on error
*/
static int padding(opt_t *result, char *optarg)
{
int errfl = 0;
char *p = optarg;
while (*p) {
if (p[1] == '\0') {
errfl = 1;
break;
}
char c = *p;
errno = 0;
int size = (int) strtol(p + 1, &p, 10);
if (errno || size < 0) {
errfl = 1;
break;
}
switch (c) {
case 'a': case 'A':
result->padding[BTOP] = size;
result->padding[BBOT] = size;
result->padding[BLEF] = size;
result->padding[BRIG] = size;
break;
case 'h': case 'H':
result->padding[BLEF] = size;
result->padding[BRIG] = size;
break;
case 'v': case 'V':
result->padding[BTOP] = size;
result->padding[BBOT] = size;
break;
case 't': case 'T':
result->padding[BTOP] = size;
break;
case 'l': case 'L':
result->padding[BLEF] = size;
break;
case 'b': case 'B':
result->padding[BBOT] = size;
break;
case 'r': case 'R':
result->padding[BRIG] = size;
break;
default:
errfl = 1;
break;
}
if (errfl) {
break;
}
}
if (errfl) {
fprintf(stderr, "%s: invalid padding specification - %s\n", PROJECT, optarg);
return 1;
}
return 0;
}
/**
* Parse the tag query specified with `-q`.
* @param result the options struct we are building
* @param optarg the argument to `-q` on the command line
* @returns 0 on success, anything else on error
*/
static int query_by_tag(opt_t *result, char *optarg)
{
char **query = NULL;
char *dup = strdup(optarg); /* required because strtok() modifies its input */
int contains_positive_element = 0;
size_t num_expr = 0;
for (char *q = strtok(dup, ","); q != NULL; q = strtok(NULL, ","))
{
char *trimmed = trimdup(q, q + strlen(q) - 1);
if (strlen(trimmed) == 0) {
BFREE(trimmed);
continue;
}
if (trimmed[0] != '-') {
contains_positive_element = 1;
}
char *raw_tag = (trimmed[0] == '+' || trimmed[0] == '-') ? (trimmed + 1) : trimmed;
if (!validate_tag(raw_tag)) {
fprintf(stderr, "%s: not a tag -- %s\n", PROJECT, raw_tag);
return 1;
}
if (query != NULL) {
for (size_t i = 0; query[i] != NULL; ++i) {
char *restag = (query[i][0] == '+' || query[i][0] == '-') ? (query[i] + 1) : query[i];
if (strcasecmp(restag, raw_tag) == 0) {
fprintf(stderr, "%s: duplicate query expression -- %s\n", PROJECT, trimmed);
return 1;
}
}
}
++num_expr;
query = (char **) realloc(query, (num_expr + 1) * sizeof(char *));
if (query == NULL) {
perror(PROJECT);
break;
}
query[num_expr - 1] = trimmed;
query[num_expr] = NULL;
}
BFREE(dup);
if (num_expr == 0) {
fprintf(stderr, "%s: empty tag query -- %s\n", PROJECT, optarg);
return 1;
}
if (!contains_positive_element) {
++num_expr;
query = (char **) realloc(query, (num_expr + 1) * sizeof(char *));
if (query == NULL) {
perror(PROJECT);
}
else {
query[num_expr - 1] = QUERY_ALL;
query[num_expr] = NULL;
}
}
result->query = query;
return 0;
}
/**
* Specify desired box target size.
* @param result the options struct we are building
* @param optarg the argument to `-s` on the command line
* @returns 0 on success, anything else on error
*/
static int size_of_box(opt_t *result, char *optarg)
{
char *p = strchr(optarg, 'x');
if (!p) {
p = strchr(optarg, 'X');
}
if (p) {
*p = '\0';
}
errno = 0;
if (optarg != p) {
result->reqwidth = strtol(optarg, NULL, 10);
}
if (p) {
result->reqheight = strtol(p + 1, NULL, 10);
*p = 'x';
}
if (errno || (result->reqwidth == 0 && result->reqheight == 0) || result->reqwidth < 0 || result->reqheight < 0) {
fprintf(stderr, "%s: invalid box size specification -- %s\n", PROJECT, optarg);
return 1;
}
return 0;
}
/**
* Tab handling. Format is `n[eku]`.
* @param result the options struct we are building
* @param optarg the argument to `-t` on the command line
* @returns 0 on success, anything else on error
*/
static int tab_handling(opt_t *result, char *optarg)
{
char *p;
int width = (int) strtol(optarg, &p, 10);
if (width < 1 || width > MAX_TABSTOP) {
fprintf(stderr, "%s: invalid tab stop distance -- %d\n", PROJECT, width);
return 1;
}
result->tabstop = width;
int errfl = 0;
if (*p != '\0') {
if (p[1] != '\0') {
errfl = 1;
}
else {
switch (*p) {
case 'e': case 'E':
result->tabexp = 'e';
break;
case 'k': case 'K':
result->tabexp = 'k';
break;
case 'u': case 'U':
result->tabexp = 'u';
break;
default:
errfl = 1;
break;
}
}
}
if (errfl) {
fprintf(stderr, "%s: invalid tab handling specification - %s\n", PROJECT, optarg);
return 1;
}
return 0;
}
/**
* Set *stdout* to binary mode, so that we can control the line terminator. This function only ever does anything on
* Windows, because on Linux, we already do have control over line terminators.
* @param result the options struct we are building
* @return the *stdout* stream, reconfigured to binary if necessary
*/
static FILE *get_stdout_configured(opt_t *result)
{
if (result->eol_overridden) {
#ifdef __MINGW32__
int rc = _setmode(fileno(stdout), _O_BINARY);
if (rc == -1) {
perror(PROJECT);
}
#endif
}
return stdout;
}
/**
* Input and Output Files. After any command line options, an input file and an output file may be specified (in that
* order). "-" may be substituted for standard input or output. A third file name would be invalid.
* @param result the options struct we are building
* @param argv the original command line options as specified
* @param optind the index of the next element to be processed in `argv`
* @returns 0 on success, anything else on error
*/
static int input_output_files(opt_t *result, char *argv[], int optind)
{
if (argv[optind] == NULL) { /* neither infile nor outfile given */
result->infile = stdin;
result->outfile = get_stdout_configured(result);
}
else if (argv[optind + 1] && argv[optind + 2]) { /* illegal third file */
fprintf(stderr, "%s: illegal parameter -- %s\n", PROJECT, argv[optind + 2]);
usage_short(stderr);
return 1;
}
else {
if (strcmp(argv[optind], "-") == 0) {
result->infile = stdin; /* use stdin for input */
}
else {
result->infile = fopen(argv[optind], "r");
if (result->infile == NULL) {
fprintf(stderr, "%s: Can\'t open input file -- %s\n", PROJECT, argv[optind]);
return 9; /* can't read infile */
}
}
if (argv[optind + 1] == NULL) {
result->outfile = get_stdout_configured(result); /* no outfile given */
}
else if (strcmp(argv[optind + 1], "-") == 0) {
result->outfile = get_stdout_configured(result); /* use stdout for output */
}
else {
result->outfile = fopen(argv[optind + 1], "wb");
if (result->outfile == NULL) {
perror(PROJECT);
if (result->infile != stdin) {
fclose(result->infile);
}
return 10;
}
}
}
return 0;
}
static void print_debug_info(opt_t *result)
{
if (result != NULL) {
#if defined(DEBUG)
fprintf (stderr, "Command line option settings (excerpt):\n");
fprintf (stderr, "- Alignment (-a): horiz %c, vert %c\n",
result->halign ? result->halign : '?', result->valign ? result->valign : '?');
fprintf (stderr, "- Line justification (-a): \'%c\'\n", result->justify ? result->justify : '?');
fprintf (stderr, "- Design Definition W shape (-c): %s\n", result->cld ? result->cld : "n/a");
fprintf (stderr, "- Line terminator used (-e): %s\n",
strcmp(result->eol, "\r\n") == 0 ? "CRLF" : (strcmp(result->eol, "\r") == 0 ? "CR" : "LF"));
fprintf (stderr, "- Explicit config file (-f): %s\n", result->f ? result->f : "no");
fprintf (stderr, "- Indentmode (-i): \'%c\'\n", result->indentmode ? result->indentmode : '?');
fprintf (stderr, "- Kill blank lines (-k): %d\n", result->killblank);
fprintf (stderr, "- Mend box (-m): %d\n", result->mend);
fprintf (stderr, "- Padding (-p): l:%d t:%d r:%d b:%d\n",
result->padding[BLEF], result->padding[BTOP], result->padding[BRIG], result->padding[BBOT]);
fprintf (stderr, "- Tag Query / Special handling for Web UI (-q): ");
if (result->query != NULL) {
for (size_t qidx = 0; result->query[qidx] != NULL; ++qidx) {
fprintf(stderr, "%s%s", qidx > 0 ? ", " : "", result->query[qidx]);
}
} else {
fprintf (stderr, "(none)");
}
fprintf (stderr, "\n");
fprintf (stderr, "- Remove box (-r): %d\n", result->r);
fprintf (stderr, "- Requested box size (-s): %ldx%ld\n", result->reqwidth, result->reqheight);
fprintf (stderr, "- Tabstop distance (-t): %d\n", result->tabstop);
fprintf (stderr, "- Tab handling (-t): \'%c\'\n", result->tabexp);
#endif
}
}
opt_t *process_commandline(int argc, char *argv[])
{
opt_t *result = create_new_opt();
/* Intercept '--help' and '-?' cases first, as they are not supported by getopt() */
if (argc >= 2 && argv[1] != NULL && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)) {
result->help = 1;
return result;
}
int oc; /* option character */
do {
oc = getopt(argc, argv, "a:c:d:e:f:hi:k:lmn:p:q:rs:t:v");
switch (oc) {
case 'a':
if (alignment(result, optarg) != 0) {
return NULL;
}
break;
case 'c':
if (command_line_design(result, optarg) != 0) {
return NULL;
}
break;
case 'd':
if (design_choice(result, optarg) != 0) {
return NULL;
}
break;
case 'e':
if (eol_override(result, optarg) != 0) {
return NULL;
}
break;
case 'f':
result->f = strdup(optarg); /* input file */
break;
case 'h':
result->help = 1;
return result;
case 'i':
if (indentation_mode(result, optarg) != 0) {
return NULL;
}
break;
case 'k':
if (killblank(result, optarg) != 0) {
return NULL;
}
break;
case 'l':
result->l = 1; /* list available box styles */
break;
case 'm':
result->mend = 2; /* Mend box: remove, then redraw */
result->r = 1;
result->killblank = 0;
break;
case 'n':
result->encoding = strdup(optarg); /* character encoding */
if (result->encoding == NULL) {
perror(PROJECT);
return NULL;
}
break;
case 'p':
if (padding(result, optarg) != 0) {
return NULL;
}
break;
case 'q':
if (query_by_tag(result, optarg) != 0) {
return NULL;
}
break;
case 'r':
result->r = 1; /* remove box */
break;
case 's':
if (size_of_box(result, optarg) != 0) {
return NULL;
}
break;
case 't':
if (tab_handling(result, optarg) != 0) {
return NULL;
}
break;
case 'v':
result->version_requested = 1; /* print version number */
return result;
case ':':
case '?':
/* Missing argument or illegal option - do nothing else */
usage_short(stderr);
return NULL;
case EOF:
/* End of list, do nothing more */
break;
default:
fprintf(stderr, "%s: internal error\n", PROJECT);
return NULL;
}
} while (oc != EOF);
if (input_output_files(result, argv, optind) != 0) {
return NULL;
}
print_debug_info(result);
return result;
}
/*EOF*/ /* vim: set sw=4: */

47
src/cmdline.h Normal file
View File

@ -0,0 +1,47 @@
/*
* boxes - Command line filter to draw/remove ASCII boxes around text
* Copyright (c) 1999-2021 Thomas Jensen and the boxes contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License, version 2, as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
/*
* Processing of command line options.
*/
#ifndef CMDLINE_H
#define CMDLINE_H
/**
* Print usage information on stream `st`, including a header text.
* @param st the stream to print to
*/
void usage_long(FILE *st);
/**
* Process command line options. Does not set the global `opt`. This function simply returns a value.
* @param argc number of command line options as passed to `main()`
* @param argv contents of command line options as passed to `main()`
* @returns a pointer to a filled-in option struct, for which memory was allocated; `NULL` on error
*/
opt_t *process_commandline(int argc, char *argv[]);
#endif
/*EOF*/ /* vim: set cindent sw=4: */