feat: implemented getopt_long

This commit is contained in:
Renge 2022-03-04 21:29:50 -05:00
parent c1c2feb549
commit 7d21448432
2 changed files with 386 additions and 137 deletions

View File

@ -7,7 +7,6 @@
/* This is ANSI C code. */ /* This is ANSI C code. */
#include "errmsg.h" #include "errmsg.h"
#include "buffer.h" /* Also includes <stddef.h>. */ #include "buffer.h" /* Also includes <stddef.h>. */
#include "reformat.h" #include "reformat.h"
@ -16,38 +15,35 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <getopt.h>
#undef NULL #undef NULL
#define NULL ((void *)0) #define NULL ((void *)0)
const char *const progname = "par"; const char *const progname = "par";
const char *const version = "3.20"; const char *const version = "3.20";
static int digtoint(char c) static int digtoint(char c)
/* Returns the value represented by the digit c, */ /* Returns the value represented by the digit c, */
/* or -1 if c is not a digit. Does not use errmsg. */ /* or -1 if c is not a digit. Does not use errmsg. */
{ {
return c == '0' ? 0 : return c == '0' ? 0 : c == '1' ? 1
c == '1' ? 1 : : c == '2' ? 2
c == '2' ? 2 : : c == '3' ? 3
c == '3' ? 3 : : c == '4' ? 4
c == '4' ? 4 : : c == '5' ? 5
c == '5' ? 5 : : c == '6' ? 6
c == '6' ? 6 : : c == '7' ? 7
c == '7' ? 7 : : c == '8' ? 8
c == '8' ? 8 : : c == '9' ? 9
c == '9' ? 9 : : -1;
-1;
/* We can't simply return c - '0' because this is ANSI */ /* We can't simply return c - '0' because this is ANSI */
/* C code, so it has to work for any character set, not */ /* C code, so it has to work for any character set, not */
/* just ones which put the digits together in order. */ /* just ones which put the digits together in order. */
} }
static int strtoudec(const char *s, int *pn) static int strtoudec(const char *s, int *pn)
/* Puts the decimal value of the string s into *pn, returning */ /* Puts the decimal value of the string s into *pn, returning */
@ -57,10 +53,13 @@ static int strtoudec(const char *s, int *pn)
{ {
int n = 0; int n = 0;
if (!*s) return 0; if (!*s)
return 0;
do { do
if (n >= 1000 || !isdigit(*s)) return 0; {
if (n >= 1000 || !isdigit(*s))
return 0;
n = 10 * n + digtoint(*s); n = 10 * n + digtoint(*s);
} while (*++s); } while (*++s);
@ -69,11 +68,9 @@ static int strtoudec(const char *s, int *pn)
return 1; return 1;
} }
static void parseopt( static void parseopt(
const char *opt, int *pwidth, int *pprefix, const char *opt, int *pwidth, int *pprefix,
int *psuffix, int *phang, int *plast, int *pmin int *psuffix, int *phang, int *plast, int *pmin)
)
/* Parses the single option in opt, setting *pwidth, *pprefix, */ /* Parses the single option in opt, setting *pwidth, *pprefix, */
/* *psuffix, *phang, *plast, or *pmin as appropriate. Uses errmsg. */ /* *psuffix, *phang, *plast, or *pmin as appropriate. Uses errmsg. */
{ {
@ -84,9 +81,11 @@ static void parseopt(
char *buf; char *buf;
size_t len; size_t len;
if (*opt == '-') ++opt; if (*opt == '-')
++opt;
if (!strcmp(opt, "version")) { if (!strcmp(opt, "version"))
{
stream = open_memstream(&buf, &len); stream = open_memstream(&buf, &len);
fprintf(stream, "%s %s\n", progname, version); fprintf(stream, "%s %s\n", progname, version);
fflush(stream); fflush(stream);
@ -98,29 +97,46 @@ static void parseopt(
oc = *opt; oc = *opt;
if (isdigit(oc)) { if (isdigit(oc))
if (!strtoudec(opt, &n)) goto badopt; {
if (n <= 8) *pprefix = n; if (!strtoudec(opt, &n))
else *pwidth = n; goto badopt;
if (n <= 8)
*pprefix = n;
else
*pwidth = n;
} }
else { else
if (!oc) goto badopt; {
if (!oc)
goto badopt;
n = 1; n = 1;
r = strtoudec(opt + 1, &n); r = strtoudec(opt + 1, &n);
if (opt[1] && !r) goto badopt; if (opt[1] && !r)
goto badopt;
if (oc == 'w' || oc == 'p' || oc == 's') { if (oc == 'w' || oc == 'p' || oc == 's')
if (!r) goto badopt; {
if (oc == 'w') *pwidth = n; if (!r)
else if (oc == 'p') *pprefix = n; goto badopt;
else *psuffix = n; if (oc == 'w')
*pwidth = n;
else if (oc == 'p')
*pprefix = n;
else
*psuffix = n;
} }
else if (oc == 'h') *phang = n; else if (oc == 'h')
else if (n <= 1) { *phang = n;
if (oc == 'l') *plast = n; else if (n <= 1)
else if (oc == 'm') *pmin = n; {
if (oc == 'l')
*plast = n;
else if (oc == 'm')
*pmin = n;
} }
else goto badopt; else
goto badopt;
} }
clear_error(); clear_error();
@ -135,7 +151,6 @@ badopt:
free(buf); free(buf);
} }
static char **readlines(void) static char **readlines(void)
/* Reads lines from stdin until EOF, or until a blank line is encountered, */ /* Reads lines from stdin until EOF, or until a blank line is encountered, */
@ -148,56 +163,78 @@ static char **readlines(void)
char ch, *ln, *nullline = NULL, nullchar = '\0', **lines = NULL; char ch, *ln, *nullline = NULL, nullchar = '\0', **lines = NULL;
cbuf = newbuffer(sizeof(char)); cbuf = newbuffer(sizeof(char));
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
pbuf = newbuffer(sizeof(char *)); pbuf = newbuffer(sizeof(char *));
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
for (blank = 1; ; ) { for (blank = 1;;)
{
c = getchar(); c = getchar();
if (c == EOF) break; if (c == EOF)
if (c == '\n') { break;
if (blank) { if (c == '\n')
{
if (blank)
{
ungetc(c, stdin); ungetc(c, stdin);
break; break;
} }
additem(cbuf, &nullchar); additem(cbuf, &nullchar);
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
ln = copyitems(cbuf); ln = copyitems(cbuf);
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
additem(pbuf, &ln); additem(pbuf, &ln);
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
clearbuffer(cbuf); clearbuffer(cbuf);
blank = 1; blank = 1;
} }
else { else
if (!isspace(c)) blank = 0; {
if (!isspace(c))
blank = 0;
ch = c; ch = c;
additem(cbuf, &ch); additem(cbuf, &ch);
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
} }
} }
if (!blank) { if (!blank)
{
additem(cbuf, &nullchar); additem(cbuf, &nullchar);
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
ln = copyitems(cbuf); ln = copyitems(cbuf);
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
additem(pbuf, &ln); additem(pbuf, &ln);
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
} }
additem(pbuf, &nullline); additem(pbuf, &nullline);
if (is_error()) goto rlcleanup; if (is_error())
goto rlcleanup;
lines = copyitems(pbuf); lines = copyitems(pbuf);
rlcleanup: rlcleanup:
if (cbuf) freebuffer(cbuf); if (cbuf)
if (pbuf) { freebuffer(cbuf);
if (!lines) { if (pbuf)
for (;;) { {
if (!lines)
{
for (;;)
{
lines = nextitem(pbuf); lines = nextitem(pbuf);
if (!lines) break; if (!lines)
break;
free(*lines); free(*lines);
} }
} }
@ -207,11 +244,9 @@ rlcleanup:
return lines; return lines;
} }
static void setdefaults( static void setdefaults(
const char *const *inlines, int *pwidth, int *pprefix, const char *const *inlines, int *pwidth, int *pprefix,
int *psuffix, int *phang, int *plast, int *pmin int *psuffix, int *phang, int *plast, int *pmin)
)
/* If any of *pwidth, *pprefix, *psuffix, *phang, *plast, *pmin are */ /* If any of *pwidth, *pprefix, *psuffix, *phang, *plast, *pmin are */
/* less than 0, sets them to default values based on inlines, according */ /* less than 0, sets them to default values based on inlines, according */
/* to "par.doc". Does not use errmsg because it always succeeds. */ /* to "par.doc". Does not use errmsg because it always succeeds. */
@ -219,23 +254,32 @@ static void setdefaults(
int numlines; int numlines;
const char *start, *end, *const *line, *p1, *p2; const char *start, *end, *const *line, *p1, *p2;
if (*pwidth < 0) *pwidth = 72; if (*pwidth < 0)
if (*phang < 0) *phang = 0; *pwidth = 72;
if (*plast < 0) *plast = 0; if (*phang < 0)
if (*pmin < 0) *pmin = *plast; *phang = 0;
if (*plast < 0)
*plast = 0;
if (*pmin < 0)
*pmin = *plast;
for (line = inlines; *line; ++line); for (line = inlines; *line; ++line)
;
numlines = line - inlines; numlines = line - inlines;
if (*pprefix < 0) if (*pprefix < 0)
{ {
if (numlines <= *phang + 1) if (numlines <= *phang + 1)
*pprefix = 0; *pprefix = 0;
else { else
{
start = inlines[*phang]; start = inlines[*phang];
for (end = start; *end; ++end); for (end = start; *end; ++end)
for (line = inlines + *phang + 1; *line; ++line) { ;
for (p1 = start, p2 = *line; p1 < end && *p1 == *p2; ++p1, ++p2); for (line = inlines + *phang + 1; *line; ++line)
{
for (p1 = start, p2 = *line; p1 < end && *p1 == *p2; ++p1, ++p2)
;
end = p1; end = p1;
} }
*pprefix = end - start; *pprefix = end - start;
@ -246,23 +290,28 @@ static void setdefaults(
{ {
if (numlines <= 1) if (numlines <= 1)
*psuffix = 0; *psuffix = 0;
else { else
{
start = *inlines; start = *inlines;
for (end = start; *end; ++end); for (end = start; *end; ++end)
for (line = inlines + 1; *line; ++line) { ;
for (p2 = *line; *p2; ++p2); for (line = inlines + 1; *line; ++line)
{
for (p2 = *line; *p2; ++p2)
;
for (p1 = end; for (p1 = end;
p1 > start && p2 > *line && p1[-1] == p2[-1]; p1 > start && p2 > *line && p1[-1] == p2[-1];
--p1, --p2); --p1, --p2)
;
start = p1; start = p1;
} }
while (end - start >= 2 && isspace(*start) && isspace(start[1])) ++start; while (end - start >= 2 && isspace(*start) && isspace(start[1]))
++start;
*psuffix = end - start; *psuffix = end - start;
} }
} }
} }
static void freelines(char **lines) static void freelines(char **lines)
/* Frees the strings pointed to in the NULL-terminated array lines, then */ /* Frees the strings pointed to in the NULL-terminated array lines, then */
/* frees the array. Does not use errmsg because it always succeeds. */ /* frees the array. Does not use errmsg because it always succeeds. */
@ -275,65 +324,261 @@ static void freelines(char **lines)
free(lines); free(lines);
} }
static int setValue(int *val, char *arg, char *name)
{
FILE *stream;
char *buf;
size_t len;
if (!arg)
{
stream = open_memstream(&buf, &len);
fprintf(stream, "Require value for argument %s", name);
fflush(stream);
set_error(buf);
fclose(stream);
free(buf);
}
// if (*val != -1)
// {
// stream = open_memstream(&buf, &len);
// fprintf(stream, "Multiple input for argument %s", name);
// fflush(stream);
// set_error(buf);
// fclose(stream);
// free(buf);
// return 0;
// }
if (!strtoudec(arg, val))
{
stream = open_memstream(&buf, &len);
fprintf(stream, "Invalid Input %s\n", arg);
fflush(stream);
set_error(buf);
fclose(stream);
free(buf);
return 0;
}
return 1;
}
static int setOptions(int argc, char **argv, int *widthbak, int *prefixbak, int *suffixbak, int * hangbak, int *lastbak, int *minbak)
{
FILE *stream;
char *buf;
size_t len;
int option_index = 0;
static struct option long_options[] = {
{"version", no_argument, 0, 'v'},
{"width", required_argument, 0, 'w'},
{"prefix", required_argument, 0, 'p'},
{"suffix", required_argument, 0, 's'},
{"hang", optional_argument, 0, 'h'},
{"last", no_argument, 0, 'L'},
{"no-last", no_argument, 0, 'n'},
{"min", no_argument, 0, 'M'},
{"no-min", no_argument, 0, 'N'},
{0, 0, 0, 0}};
for (char ch = getopt_long(argc, argv, "w:p:s:h::l:m:", long_options, &option_index); ch != -1; ch = getopt_long(argc, argv, "w:p:s:h::l:m:", long_options, &option_index))
{
switch (ch)
{
case 'v':
stream = open_memstream(&buf, &len);
fprintf(stream, "%s %s\n", progname, version);
fflush(stream);
set_error(buf);
fclose(stream);
free(buf);
return 0;
break;
case 'W':
if (!setValue(widthbak, optarg, "width"))
return 0;
break;
case 'w':
if (!setValue(widthbak, optarg, "width"))
return 0;
break;
case 'p':
if (!setValue(prefixbak, optarg, "prefix"))
return 0;
break;
case 's':
if (!setValue(suffixbak, optarg, "suffix"))
return 0;
break;
case 'h':
if (!optarg)
{
optarg = "1";
}
if (!setValue(hangbak, optarg, "hang"))
return 0;
break;
case 'l':
if (!setValue(lastbak, optarg, "last"))
return 0;
if (*lastbak != 0 && *lastbak != 1)
{
stream = open_memstream(&buf, &len);
fprintf(stream, "Value for -l must be 0 or 1");
fflush(stream);
set_error(buf);
fclose(stream);
free(buf);
return 0;
}
break;
case 'm':
if (!setValue(minbak, optarg, "last"))
return 0;
if (*minbak != 0 && *minbak != 1)
{
stream = open_memstream(&buf, &len);
fprintf(stream, "Value for -m must be 0 or 1");
fflush(stream);
set_error(buf);
fclose(stream);
free(buf);
return 0;
}
break;
case 'L':
if (!setValue(lastbak, "1", "last"))
return 0;
break;
case 'n':
if (!setValue(lastbak, "0", "last"))
return 0;
break;
case 'M':
if (!setValue(minbak, "1", "min"))
return 0;
break;
case 'N':
if (!setValue(minbak, "0", "last"))
return 0;
break;
default:
break;
}
}
return 1;
}
int original_main(int argc, const char *const *argv) int original_main(int argc, const char *const *argv)
{ {
int width, widthbak = -1, prefix, prefixbak = -1, suffix, suffixbak = -1, int width, widthbak = -1, prefix, prefixbak = -1, suffix, suffixbak = -1,
hang, hangbak = -1, last, lastbak = -1, min, minbak = -1, c; hang, hangbak = -1, last, lastbak = -1, min, minbak = -1, c, argc_env;
char *parinit, *picopy = NULL, *opt, **inlines = NULL, **outlines = NULL, char *parinit, *picopy = NULL, *opt, **inlines = NULL, **outlines = NULL,
**line; **line, **argv_env, *tmp;
const char *const whitechars = " \f\n\r\t\v"; const char *const whitechars = " \f\n\r\t\v";
// parinit = getenv("PARINIT");
// if (parinit) {
// picopy = malloc((strlen(parinit) + 1) * sizeof (char));
// if (!picopy) {
// set_error(outofmem);
// goto parcleanup;
// }
// strcpy(picopy,parinit);
// opt = strtok(picopy,whitechars);
// while (opt) {
// parseopt(opt, &widthbak, &prefixbak,
// &suffixbak, &hangbak, &lastbak, &minbak);
// if (is_error()) goto parcleanup;
// opt = strtok(NULL,whitechars);
// }
// free(picopy);
// picopy = NULL;
// }
// while (*++argv) {
// parseopt(*argv, &widthbak, &prefixbak,
// &suffixbak, &hangbak, &lastbak, &minbak);
// if (is_error()) goto parcleanup;
// }
parinit = getenv("PARINIT"); parinit = getenv("PARINIT");
if (parinit) { if (parinit)
{
picopy = malloc((strlen(parinit) + 1) * sizeof (char)); picopy = malloc((strlen(parinit) + 1) * sizeof (char));
if (!picopy) { if (!picopy) {
set_error(outofmem); set_error(outofmem);
goto parcleanup; goto parcleanup;
} }
argc_env = 1;
argv_env = malloc((argc_env) * sizeof (char*));
argv_env[0] = malloc((strlen(argv[0])+1) * sizeof(char));
tmp = argv_env[0];
strcpy(tmp, argv[0]);
strcpy(picopy,parinit); strcpy(picopy,parinit);
opt = strtok(picopy,whitechars); opt = strtok(picopy,whitechars);
while (opt) { while (opt) {
parseopt(opt, &widthbak, &prefixbak, argc_env ++;
&suffixbak, &hangbak, &lastbak, &minbak); argv_env = realloc(argv_env, (argc_env + 1)*sizeof(char*));
if (is_error()) goto parcleanup; argv_env[argc_env-1] = malloc((strlen(opt)+1)*sizeof(char));
tmp = argv_env[argc_env-1];
strcpy(tmp, opt);
opt = strtok(NULL,whitechars); opt = strtok(NULL,whitechars);
} }
free(picopy); argv_env = realloc(argv_env, (argc_env + 1)*sizeof(char*));
picopy = NULL; argv_env[argc_env] = NULL;
for (size_t i = 0; i < argc_env; i++)
{
printf("%s\n", argv_env[i]);
} }
while (*++argv) {
parseopt(*argv, &widthbak, &prefixbak,
&suffixbak, &hangbak, &lastbak, &minbak);
if (is_error()) goto parcleanup;
}
for (;;) { if (!setOptions(argc_env, (char **)argv_env, &widthbak, &prefixbak, &suffixbak, &hangbak, &lastbak, &minbak))
for (;;) { goto parcleanup;
freelines(argv_env);
}
if (!setOptions(argc, (char **)argv, &widthbak, &prefixbak, &suffixbak, &hangbak, &lastbak, &minbak))
goto parcleanup;
printf("width: %d, prefix: %d, suffix: %d, hang: %d, last: %d, min: %d", widthbak, prefixbak, suffixbak, hangbak, lastbak, minbak);
for (;;)
{
for (;;)
{
c = getchar(); c = getchar();
if (c == EOF) goto parcleanup; if (c == EOF)
if (c != '\n') break; goto parcleanup;
if (c != '\n')
break;
putchar(c); putchar(c);
} }
ungetc(c, stdin); ungetc(c, stdin);
inlines = readlines(); inlines = readlines();
if (is_error()) goto parcleanup; if (is_error())
if (!*inlines) { goto parcleanup;
if (!*inlines)
{
free(inlines); free(inlines);
inlines = NULL; inlines = NULL;
continue; continue;
} }
width = widthbak; prefix = prefixbak; suffix = suffixbak; width = widthbak;
hang = hangbak; last = lastbak; min = minbak; prefix = prefixbak;
suffix = suffixbak;
hang = hangbak;
last = lastbak;
min = minbak;
setdefaults((const char *const *)inlines, setdefaults((const char *const *)inlines,
&width, &prefix, &suffix, &hang, &last, &min); &width, &prefix, &suffix, &hang, &last, &min);
outlines = reformat((const char *const *)inlines, outlines = reformat((const char *const *)inlines,
width, prefix, suffix, hang, last, min); width, prefix, suffix, hang, last, min);
if (is_error()) goto parcleanup; if (is_error())
goto parcleanup;
freelines(inlines); freelines(inlines);
inlines = NULL; inlines = NULL;
@ -347,11 +592,15 @@ int original_main(int argc, const char * const *argv)
parcleanup: parcleanup:
if (picopy) free(picopy); if (picopy)
if (inlines) freelines(inlines); free(picopy);
if (outlines) freelines(outlines); if (inlines)
freelines(inlines);
if (outlines)
freelines(outlines);
if (is_error()) { if (is_error())
{
report_error(stderr); report_error(stderr);
clear_error(); clear_error();
return (EXIT_FAILURE); return (EXIT_FAILURE);

View File

@ -37,7 +37,7 @@ Test(base_suite, basic_test) {
*/ */
Test(base_suite, prefix_suffix_test) { Test(base_suite, prefix_suffix_test) {
char *name = "prefix_suffix"; char *name = "prefix_suffix";
sprintf(program_options, "%s", "w80"); sprintf(program_options, "%s", "-w 80");
int err = run_using_system(name, "", "", STANDARD_LIMITS); int err = run_using_system(name, "", "", STANDARD_LIMITS);
assert_expected_status(EXIT_SUCCESS, err); assert_expected_status(EXIT_SUCCESS, err);
assert_outfile_matches(name, NULL); assert_outfile_matches(name, NULL);
@ -62,7 +62,7 @@ Test(base_suite, valgrind_leak_test) {
*/ */
Test(base_suite, valgrind_uninitialized_test) { Test(base_suite, valgrind_uninitialized_test) {
char *name = "valgrind_uninitialized"; char *name = "valgrind_uninitialized";
sprintf(program_options, "%s", "p10 s10"); sprintf(program_options, "%s", "-p 10 -s 10");
int err = run_using_system(name, "", "valgrind --leak-check=no --undef-value-errors=yes --error-exitcode=37", STANDARD_LIMITS); int err = run_using_system(name, "", "valgrind --leak-check=no --undef-value-errors=yes --error-exitcode=37", STANDARD_LIMITS);
assert_no_valgrind_errors(err); assert_no_valgrind_errors(err);
assert_expected_status(0x1, err); assert_expected_status(0x1, err);