mirror of
https://github.com/ascii-boxes/boxes.git
synced 2025-03-10 04:08:17 +01:00
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:
parent
6427d1efde
commit
af6f123c99
19
src/Makefile
19
src/Makefile
@ -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
|
||||
|
682
src/boxes.c
682
src/boxes.c
@ -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
|
||||
|
@ -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,13 +60,6 @@
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
#define __TJ(s) fprintf (stderr, s);
|
||||
#else
|
||||
#define __TJ(s) /**/
|
||||
#endif
|
||||
|
||||
|
||||
#define BTOP 0 /* for use with sides */
|
||||
#define BRIG 1
|
||||
#define BBOT 2
|
||||
@ -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 */
|
||||
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;
|
||||
|
824
src/cmdline.c
Normal file
824
src/cmdline.c
Normal 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
47
src/cmdline.h
Normal 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: */
|
Loading…
Reference in New Issue
Block a user