Add option to override line terminators #60

This commit is contained in:
Thomas Jensen 2021-04-10 15:32:52 +02:00
parent 8a6d0d3315
commit 139994f97f
No known key found for this signature in database
GPG Key ID: A4ACEE270D0FB7DB
10 changed files with 210 additions and 102 deletions

View File

@ -6,8 +6,8 @@
boxes \- text mode box and comment drawing filter
.SH SYNOPSIS
.B boxes
[\-hlmrv] [\-a\ format] [\-d\ design] [\-f\ file] [\-i\ indent] [\-k\ bool]
[\-n\ encoding] [\-p\ pad] [\-q query] [\-s\ size] [\-t\ tabopts]
[\-hlmrv] [\-a\ format] [\-d\ design] [\-e\ eol] [\-f\ file] [\-i\ indent]
[\-k\ bool] [\-n\ encoding] [\-p\ pad] [\-q query] [\-s\ size] [\-t\ tabopts]
[infile [outfile]]
.SH DESCRIPTION
.I Boxes
@ -132,6 +132,25 @@ Design selection. The one argument of this option is the name of the design to
use.
.\" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
.TP 0.6i
.B \-e \fIeol\fP
Override line terminator.
.I eol
can be
.I CR\fP,
.I LF\fP, or
.I CRLF\fP.
The default is to use the system-specific line terminator, which means
.I CRLF
on Windows, and
.I LF
otherwise. This option should only be used in an emergency, because normally
the system-specific line terminator will be just fine. This option is
considered experimental, and may go away in a future version of
.I boxes\fP.
Let us know in <URL:https://github.com/ascii-boxes/boxes/issues/60> if you
think we should keep it.
.\" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
.TP 0.6i
.B \-f \fIstring\fP
Use alternate config file. The one argument of this option is the name of a
valid

View File

@ -98,7 +98,7 @@ 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 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
package: $(BOXES_EXECUTABLE_NAME)
if [ -z "$(PKG_NAME)" ] ; then exit 1 ; fi

View File

@ -34,6 +34,11 @@
#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 "tools.h"
@ -69,19 +74,18 @@ opt_t opt; /* command line options */
input_t input = INPUT_INITIALIZER; /* input lines */
/* _\|/_
(o o)
+----oOO-{_}-OOo------------------------------------------------------------+
| F u n c t i o n s |
+--------------------------------------------------------------------------*/
static void usage(FILE *st)
/*
* Print usage information on stream st.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/**
* 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);
@ -89,6 +93,8 @@ static void usage(FILE *st)
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");
@ -108,12 +114,11 @@ static void usage(FILE *st)
static void usage_short(FILE *st)
/*
* Print abbreviated usage information on stream st.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/**
* 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);
@ -121,12 +126,11 @@ static void usage_short(FILE *st)
static void usage_long(FILE *st)
/*
* Print usage information on stream st, including a header text.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/**
* 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");
@ -135,6 +139,26 @@ static void usage_long(FILE *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) {
@ -236,6 +260,7 @@ static int process_commandline(int argc, char *argv[])
*/
memset(&opt, 0, sizeof(opt_t));
opt.tabstop = DEF_TABSTOP;
opt.eol = EOL_DEFAULT;
opt.f = NULL;
opt.tabexp = 'e';
opt.killblank = -1;
@ -257,7 +282,7 @@ static int process_commandline(int argc, char *argv[])
* Parse Command Line
*/
do {
oc = getopt(argc, argv, "a:c:d:f:hi:k:lmn:p:q:rs:t:v");
oc = getopt(argc, argv, "a:c:d:e:f:hi:k:lmn:p:q:rs:t:v");
switch (oc) {
@ -365,6 +390,22 @@ static int process_commandline(int argc, char *argv[])
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
@ -620,7 +661,7 @@ static int process_commandline(int argc, char *argv[])
*/
if (argv[optind] == NULL) { /* neither infile nor outfile given */
opt.infile = stdin;
opt.outfile = stdout;
opt.outfile = get_stdout_configured();
}
else if (argv[optind + 1] && argv[optind + 2]) { /* illegal third file */
@ -642,13 +683,13 @@ static int process_commandline(int argc, char *argv[])
}
if (argv[optind + 1] == NULL) {
opt.outfile = stdout; /* no outfile given */
opt.outfile = get_stdout_configured(); /* no outfile given */
}
else if (strcmp(argv[optind + 1], "-") == 0) {
opt.outfile = stdout; /* use stdout for output */
opt.outfile = get_stdout_configured(); /* use stdout for output */
}
else {
opt.outfile = fopen(argv[optind + 1], "w");
opt.outfile = fopen(argv[optind + 1], "wb");
if (opt.outfile == NULL) {
perror(PROJECT);
if (opt.infile != stdin) {
@ -683,6 +724,8 @@ static int process_commandline(int argc, char *argv[])
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;
@ -942,7 +985,7 @@ static void print_tags(tagstats_t *tagstats, size_t num_tags)
}
fprintf(opt.outfile, "%s (%d)", tagstats[tidx].tag, (int) tagstats[tidx].count);
}
fprintf(opt.outfile, "\n");
fprintf(opt.outfile, "%s", opt.eol);
}
}
@ -990,13 +1033,12 @@ static int list_styles()
memset(&space, ' ', LINE_MAX_BYTES);
space[LINE_MAX_BYTES] = '\0';
fprintf(opt.outfile, "Complete Design Information for \"%s\":\n",
d->name);
fprintf(opt.outfile, "Complete Design Information for \"%s\":%s", d->name, opt.eol);
fprintf(opt.outfile, "-----------------------------------");
for (i = strlen(d->name); i > 0; --i) {
fprintf(opt.outfile, "-");
}
fprintf(opt.outfile, "\n");
fprintf(opt.outfile, "%s", opt.eol);
fprintf(opt.outfile, "Alias Names: ");
size_t aidx = 0;
@ -1007,66 +1049,66 @@ static int list_styles()
if (aidx == 0) {
fprintf(opt.outfile, "none");
}
fprintf(opt.outfile, "\n");
fprintf(opt.outfile, "%s", opt.eol);
fprintf(opt.outfile, "Author: %s\n",
d->author ? d->author : "(unknown author)");
fprintf(opt.outfile, "Original Designer: %s\n",
d->designer ? d->designer : "(unknown artist)");
fprintf(opt.outfile, "Creation Date: %s\n",
d->created ? d->created : "(unknown)");
fprintf(opt.outfile, "Author: %s%s",
d->author ? d->author : "(unknown author)", opt.eol);
fprintf(opt.outfile, "Original Designer: %s%s",
d->designer ? d->designer : "(unknown artist)", opt.eol);
fprintf(opt.outfile, "Creation Date: %s%s",
d->created ? d->created : "(unknown)", opt.eol);
fprintf(opt.outfile, "Current Revision: %s%s%s\n",
fprintf(opt.outfile, "Current Revision: %s%s%s%s",
d->revision ? d->revision : "",
d->revision && d->revdate ? " as of " : "",
d->revdate ? d->revdate : (d->revision ? "" : "(unknown)"));
d->revdate ? d->revdate : (d->revision ? "" : "(unknown)"), opt.eol);
fprintf(opt.outfile, "Configuration File: %s\n", d->defined_in);
fprintf(opt.outfile, "Configuration File: %s%s", d->defined_in, opt.eol);
fprintf(opt.outfile, "Indentation Mode: ");
switch (d->indentmode) {
case 'b':
fprintf(opt.outfile, "box (indent box)\n");
fprintf(opt.outfile, "box (indent box)%s", opt.eol);
break;
case 't':
fprintf(opt.outfile, "text (retain indentation inside of box)\n");
fprintf(opt.outfile, "text (retain indentation inside of box)%s", opt.eol);
break;
default:
fprintf(opt.outfile, "none (discard indentation)\n");
fprintf(opt.outfile, "none (discard indentation)%s", opt.eol);
break;
}
fprintf(opt.outfile, "Replacement Rules: ");
if (d->anz_reprules > 0) {
for (i = 0; i < (int) d->anz_reprules; ++i) {
fprintf(opt.outfile, "%d. (%s) \"%s\" WITH \"%s\"\n", i + 1,
fprintf(opt.outfile, "%d. (%s) \"%s\" WITH \"%s\"%s", i + 1,
d->reprules[i].mode == 'g' ? "glob" : "once",
d->reprules[i].search, d->reprules[i].repstr);
d->reprules[i].search, d->reprules[i].repstr, opt.eol);
if (i < (int) d->anz_reprules - 1) {
fprintf(opt.outfile, " ");
}
}
}
else {
fprintf(opt.outfile, "none\n");
fprintf(opt.outfile, "none%s", opt.eol);
}
fprintf(opt.outfile, "Reversion Rules: ");
if (d->anz_revrules > 0) {
for (i = 0; i < (int) d->anz_revrules; ++i) {
fprintf(opt.outfile, "%d. (%s) \"%s\" TO \"%s\"\n", i + 1,
fprintf(opt.outfile, "%d. (%s) \"%s\" TO \"%s\"%s", i + 1,
d->revrules[i].mode == 'g' ? "glob" : "once",
d->revrules[i].search, d->revrules[i].repstr);
d->revrules[i].search, d->revrules[i].repstr, opt.eol);
if (i < (int) d->anz_revrules - 1) {
fprintf(opt.outfile, " ");
}
}
}
else {
fprintf(opt.outfile, "none\n");
fprintf(opt.outfile, "none%s", opt.eol);
}
fprintf(opt.outfile, "Minimum Box Dimensions: %d x %d (width x height)\n",
(int) d->minwidth, (int) d->minheight);
fprintf(opt.outfile, "Minimum Box Dimensions: %d x %d (width x height)%s",
(int) d->minwidth, (int) d->minheight, opt.eol);
fprintf(opt.outfile, "Default Padding: ");
if (d->padding[BTOP] || d->padding[BRIG]
@ -1092,15 +1134,14 @@ static int list_styles()
if (d->padding[BBOT]) {
fprintf(opt.outfile, "bottom %d", d->padding[BBOT]);
}
fprintf(opt.outfile, "\n");
fprintf(opt.outfile, "%s", opt.eol);
}
else {
fprintf(opt.outfile, "none\n");
fprintf(opt.outfile, "none%s", opt.eol);
}
fprintf(opt.outfile, "Default Killblank: %s\n",
empty_side(opt.design->shape, BTOP) &&
empty_side(opt.design->shape, BBOT) ? "no" : "yes");
fprintf(opt.outfile, "Default Killblank: %s%s",
empty_side(opt.design->shape, BTOP) && empty_side(opt.design->shape, BBOT) ? "no" : "yes", opt.eol);
fprintf(opt.outfile, "Tags: ");
size_t tidx = 0;
@ -1111,7 +1152,7 @@ static int list_styles()
if (tidx == 0) {
fprintf(opt.outfile, "none");
}
fprintf(opt.outfile, "\n");
fprintf(opt.outfile, "%s", opt.eol);
fprintf(opt.outfile, "Elastic Shapes: ");
sstart = 0;
@ -1124,13 +1165,13 @@ static int list_styles()
sstart = 1;
}
}
fprintf(opt.outfile, "\n");
fprintf(opt.outfile, "%s", opt.eol);
/*
* Display all shapes
*/
if (query_is_undoc()) {
fprintf(opt.outfile, "Sample:\n%s\n", d->sample);
fprintf(opt.outfile, "Sample:%s%s%s", opt.eol, d->sample, opt.eol);
}
else {
int first_shape = 1;
@ -1140,11 +1181,12 @@ static int list_styles()
}
for (w = 0; w < d->shape[i].height; ++w) {
char *escaped_line = escape(d->shape[i].chars[w], d->shape[i].width);
fprintf(opt.outfile, "%-24s%3s%c \"%s\"%c\n",
fprintf(opt.outfile, "%-24s%3s%c \"%s\"%c%s",
(first_shape == 1 && w == 0 ? "Defined Shapes:" : ""),
(w == 0 ? shape_name[i] : ""), (w == 0 ? ':' : ' '),
escaped_line,
(w < d->shape[i].height - 1 ? ',' : ' ')
(w < d->shape[i].height - 1 ? ',' : ' '),
opt.eol
);
BFREE (escaped_line);
}
@ -1166,18 +1208,23 @@ static int list_styles()
for (i = 0; i < anz_designs; ++i) {
char *all_names = names(list[i]);
if (list[i]->author && list[i]->designer && strcmp(list[i]->author, list[i]->designer) != 0) {
fprintf(opt.outfile, "%s\n%s, coded by %s:\n\n%s\n\n", all_names,
list[i]->designer, list[i]->author, list[i]->sample);
fprintf(opt.outfile, "%s%s%s, coded by %s:%s%s%s%s%s", all_names, opt.eol,
list[i]->designer, list[i]->author, opt.eol, opt.eol,
list[i]->sample, opt.eol, opt.eol);
}
else if (list[i]->designer) {
fprintf(opt.outfile, "%s\n%s:\n\n%s\n\n", all_names, list[i]->designer, list[i]->sample);
fprintf(opt.outfile, "%s%s%s:%s%s%s%s%s", all_names, opt.eol,
list[i]->designer, opt.eol, opt.eol,
list[i]->sample, opt.eol, opt.eol);
}
else if (list[i]->author) {
fprintf(opt.outfile, "%s\nunknown artist, coded by %s:\n\n%s\n\n", all_names,
list[i]->author, list[i]->sample);
fprintf(opt.outfile, "%s%sunknown artist, coded by %s:%s%s%s%s%s", all_names, opt.eol,
list[i]->author, opt.eol, opt.eol,
list[i]->sample, opt.eol, opt.eol);
}
else {
fprintf(opt.outfile, "%s:\n\n%s\n\n", all_names, list[i]->sample);
fprintf(opt.outfile, "%s:%s%s%s%s%s", all_names, opt.eol, opt.eol,
list[i]->sample, opt.eol, opt.eol);
}
BFREE(all_names);
@ -1204,9 +1251,9 @@ static int query_by_tag()
}
for (int i = 0; i < anz_designs; ++i) {
if (filter_by_tag(list[i]->tags)) {
fprintf(opt.outfile, "%s\n", list[i]->name);
fprintf(opt.outfile, "%s%s", list[i]->name, opt.eol);
for (size_t aidx = 0; list[i]->aliases[aidx] != NULL; ++aidx) {
fprintf(opt.outfile, "%s (alias)\n", list[i]->aliases[aidx]);
fprintf(opt.outfile, "%s (alias)%s", list[i]->aliases[aidx], opt.eol);
}
}
}

View File

@ -119,11 +119,20 @@ 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) */

View File

@ -1012,7 +1012,7 @@ int output_box(const sentry_t *thebox)
BFREE (restored_indent);
}
fprintf(opt.outfile, "%s%s", obuf, (input.final_newline || j < nol - skip_end - 1 ? "\n" : ""));
fprintf(opt.outfile, "%s%s", obuf, (input.final_newline || j < nol - skip_end - 1 ? opt.eol : ""));
}
BFREE (indentspc);

View File

@ -30,6 +30,8 @@
#include "discovery.h"
#include "tools.h"
#include "regulex.h"
#include "unicode.h"
#include "parsecode.h"
#include "parsing.h"
#include "parser.h"
@ -42,6 +44,8 @@
*/
#define LEX_SDELIM "\"~'`!@%&*=:;<>?/|.\\"
static pcre2_code *eol_pattern = NULL;
static void chg_strdelims (pass_to_bison *bison_args, const char asesc, const char asdel)
@ -924,4 +928,47 @@ int action_add_alias(pass_to_bison *bison_args, char *alias_name)
}
static char *adjust_eols(char *sample)
{
if (eol_pattern == NULL) {
eol_pattern = compile_pattern("(?(?=\r)(\r\n?)|(\n))");
}
uint32_t *u32_sample = u32_strconv_from_input(sample);
uint32_t *replaced = regex_replace(eol_pattern, opt.eol, u32_sample, strlen(sample), 1);
char *result = u32_strconv_to_output(replaced);
BFREE(replaced);
BFREE(u32_sample);
return result;
}
int action_sample_block(pass_to_bison *bison_args, char *sample)
{
#ifdef PARSER_DEBUG
fprintf(stderr, "SAMPLE block rule satisfied\n");
#endif
if (curdes.sample) {
yyerror(bison_args, "duplicate SAMPLE block");
return RC_ERROR;
}
char *p = sample;
while ((*p == '\r' || *p == '\n') && *p != '\0') {
p++;
}
char *line = adjust_eols(p);
if (line == NULL) {
perror(PROJECT);
return RC_ABORT;
}
curdes.sample = line;
++(bison_args->num_mandatory);
return RC_SUCCESS;
}
/*EOF*/ /* vim: set cindent sw=4: */

View File

@ -148,6 +148,17 @@ void recover(pass_to_bison *bison_args);
int tag_record(pass_to_bison *bison_args, char *tag);
/**
* Add the sample block to the current design.
* @param bison_args the parser state
* @param sample the sample block content (non-empty when this is invoked)
* @return 0: success;
* 1: YYERROR must be invoked
* 2: YYABORT must be invoked
*/
int action_sample_block(pass_to_bison *bison_args, char *sample);
#endif
/*EOF*/ /* vim: set cindent sw=4: */

View File

@ -277,32 +277,7 @@ entry: KEYWORD STRING
block: YSAMPLE STRING YENDSAMPLE
{
/*
* SAMPLE block (STRING is non-empty if we get here)
*/
char *line;
#ifdef PARSER_DEBUG
fprintf (stderr, "SAMPLE block rule satisfied\n");
#endif
if (curdes.sample) {
yyerror(bison_args, "duplicate SAMPLE block");
YYERROR;
}
char *p = $2;
while ((*p == '\r' || *p == '\n') && *p != '\0') {
p++;
}
line = (char *) strdup (p);
if (line == NULL) {
perror (PROJECT);
YYABORT;
}
curdes.sample = line;
++(bison_args->num_mandatory);
invoke_action(action_sample_block(bison_args, $2));
}
| YSHAPES '{' slist '}'

View File

@ -80,26 +80,26 @@ void print_design_list_header()
fprintf(opt.outfile, "%d Available Style%s", anz_designs, anz_designs == 1 ? "" : "s");
if (num_parent_configs > 0) {
fprintf(opt.outfile, ":\n");
fprintf(opt.outfile, ":%s", opt.eol);
fprintf(opt.outfile, "-----------------%s", anz_designs == 1 ? "" : "-");
for (int i = strlen(buf); i > 0; --i) {
fprintf(opt.outfile, "-");
}
fprintf(opt.outfile, "\n\n");
fprintf(opt.outfile, "Configuration Files:\n");
fprintf(opt.outfile, " - %s\n", first_config_file);
fprintf(opt.outfile, "%s%s", opt.eol, opt.eol);
fprintf(opt.outfile, "Configuration Files:%s", opt.eol);
fprintf(opt.outfile, " - %s%s", first_config_file, opt.eol);
for (size_t i = 0; i < num_parent_configs; i++) {
fprintf(opt.outfile, " - %s (parent)\n", parent_configs[i]);
fprintf(opt.outfile, " - %s (parent)%s", parent_configs[i], opt.eol);
}
}
else {
fprintf(opt.outfile, " in \"%s\":\n", first_config_file);
fprintf(opt.outfile, " in \"%s\":%s", first_config_file, opt.eol);
fprintf(opt.outfile, "-----------------------%s", anz_designs == 1 ? "" : "-");
for (int i = strlen(first_config_file) + strlen(buf); i > 0; --i) {
fprintf(opt.outfile, "-");
}
}
fprintf(opt.outfile, "\n\n");
fprintf(opt.outfile, "%s%s", opt.eol, opt.eol);
}

View File

@ -1103,7 +1103,7 @@ void output_input(const int trim_only)
}
fprintf(opt.outfile, "%s%s%s", indentspc, u32_strconv_to_output(advance32(input.lines[j].mbtext, indent)),
(input.final_newline || j < input.anz_lines - 1 ? "\n" : ""));
(input.final_newline || j < input.anz_lines - 1 ? opt.eol : ""));
BFREE (indentspc);
}
}