diff options
-rw-r--r-- | .gitignore | 17 | ||||
-rw-r--r-- | Makefile | 33 | ||||
-rw-r--r-- | README | 60 | ||||
-rw-r--r-- | arg.h | 44 | ||||
-rw-r--r-- | config.mk | 6 | ||||
-rw-r--r-- | gpp.1 | 163 | ||||
-rw-r--r-- | gpp.c | 245 |
7 files changed, 481 insertions, 87 deletions
@@ -1,16 +1,5 @@ -_/ -/bin/ -/obj/ -__pycache__/ -.* -!.git* -\#*\# +*\#* *~ -*.sw[op] -*.py[co] -*.bak +*.o +*.su /gpp -*.info -*.gz -*.install - @@ -1,31 +1,32 @@ -VERSION = 1.5 +.POSIX: -PREFIX = /usr -MANPREFIX = $(PREFIX)/share/man - -PKGNAME = gpp -COMMAND = gpp - -PY = python -SHEBANG = /usr/bin/env $(PY) +CONFIGFILE = config.mk +include $(CONFIGFILE) all: gpp +gpp.o: gpp.c arg.h -gpp: gpp.py - env SHEBANG='$(SHEBANG)' $(PY) gpp.py < gpp.py > $@ +gpp: gpp.o + $(CC) -o $@ gpp.o $(LDFLAGS) + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) install: gpp install -dm755 -- "$(DESTDIR)$(PREFIX)/bin" - install -m755 -- gpp "$(DESTDIR)$(PREFIX)/bin/$(COMMAND)" + install -m755 -- gpp "$(DESTDIR)$(PREFIX)/bin/gpp" install -dm755 -- "$(DESTDIR)$(MANPREFIX)/man" - install -m644 -- gpp.1 "$(DESTDIR)$(MANPREFIX)/man/$(COMMAND).1" + install -m644 -- gpp.1 "$(DESTDIR)$(MANPREFIX)/man/gpp.1" uninstall: - -rm -- "$(DESTDIR)$(PREFIX)/bin/$(COMMAND)" - -rm -- "$(DESTDIR)$(MANPREFIX)/man/$(COMMAND).1" + -rm -- "$(DESTDIR)$(PREFIX)/bin/gpp" + -rm -- "$(DESTDIR)$(MANPREFIX)/man/gpp.1" clean: - -rm -rf -- gpp bin obj + -rm -rf -- gpp *.o *.su + +.SUFFIXES: +.SUFFIXES: .o .c .PHONY: all install uninstall clean @@ -2,15 +2,18 @@ NAME gpp - Bash-based preprocessor for anything SYNOPSIS - gpp [OPTION]... + gpp [-D name[=value]] [-f file | [-i input-file] + [-o output-file]] [-n count] [-s symbol] [-u [-u]] + [shell [argument] ...] ETYMOLOGY gpp stands for General Preprocessor. DESCRIPTION gpp lets a developer embed directives written in GNU - Bash into any text document. These directives are used - to automate the writting of parts of the document. + Bash (this can be changed) into any text document. + These directives are used to automate the writting of + parts of the document. The preprocessing directives start with a symbol (or text string) specified by the developer. By default @@ -41,40 +44,43 @@ DESCRIPTION echo verbatim. OPTIONS - -s, --symbol SYMBOL - Set the prefix symbol for preprocessor directives. - Defaults to @. + -D name=value + Set the environment variable name to hold + the value value. - -e, --encoding ENCODING - Specifies the encoding of the file. - - -n, --iterations N - Process the file recursively N times. Defaults to 1 time. + -D name + Set the environment variable name to hold + the value 1. - -u, --unshebang - Clear the shebang line, remove it if this flag - is used twice. If used twice, an empty line - will be inserted after the new first line. + -f file + Equivalent to -i FILE -o FILE. - -i, --input FILE + -i input-file Select file to process. Defaults to /dev/stdin. - -o, --output FILE + -n count + Process the file recursively count times. + Defaults to 1 time. + + -o output-file Select output file. Defaults to /dev/stdout. - -f, --file FILE - Equivalent to -i FILE -o FILE. + -s symbol + Set the prefix symbol for preprocessor directives. + Defaults to @. - -D, --export NAME=VALUE - Set the environment variable NAME to hold - the value VALUE. + -u + Clear the shebang line, remove it if this flag + is used twice. If used twice, an empty line + will be inserted after the new first line. - -D, --export NAME - Set the environment variable NAME to hold - the value 1. +OPERANDS + shell + The shell to run instead of bash. The shell + must be compatible with POSIX shell. - Short options must be joined. The value of a flag must - be in a separate argument from the flag itself. + argument ... + Command line arguments for the shell. EXAMPLES Conditional hello world @@ -0,0 +1,44 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][0] == '-') {\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +#define ARGEND }\ + } else {\ + break;\ + }\ + } + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..2e90419 --- /dev/null +++ b/config.mk @@ -0,0 +1,6 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 +CFLAGS = -std=c99 -Wall -O2 +LDFLAGS = -s @@ -4,17 +4,31 @@ gpp - Bash-based preprocessor for anything .SH SYNOPSIS .R gpp -.RI [ OPTION ]... +[-D +.IR name [= value ]] +[-f +.I file +| [-i +.IR input-file ] +[-o +.IR output-file ]] +[-n +.IR count ] +[-s +.IR symbol ] +[-u [-u]] +.RI [ shell +.RI [ argument ]\ ...] .SH ETYMOLOGY gpp stands for General Preprocessor. .SH DESCRIPTION .B gpp -lets a developer embed directives written in -.B GNU Bash -into any text document. These directives are used -to automate the writting of parts of the document. +lets a developer embed directives written in GNU Bash +(this can be changed) into any text document. These +directives are used to automate the writting of parts +of the document. .PP The preprocessing directives start with a symbol (or text string) specified by the developer. By default @@ -23,7 +37,8 @@ this symbol is (at). .PP Any line starting with -.B @< (where +.B @< +(where .B @ is the selected symbol for preprocessing directives) or .BR @> , @@ -73,41 +88,120 @@ Everything that is not a preprocessing directive is echo verbatim. .SH OPTIONS +The +.B gpp +utility conforms to the Base Definitions volume of POSIX.1-2017, +.IR "Section 12.2" , +.IR "Utility Syntax Guidelines" . +.PP +The following option is supported: .TP -.BR \-s ,\ \-\-symbol \ \fISYMBOL\fP -Set the prefix symbol for preprocessor directives. -Defaults to @ (at). +.BR \-D\ \fIname\fP\fB=\fP\fIvalue\fP +Set the environment variable \fIname\fP to hold +the value \fIvalue\fP. +.TP +.BR \-D\ \fIname\fP +Set the environment variable \fIname\fP to hold +the value 1. +.TP +.BI \-f\ file +Equivalent to \-i +.I file +\-o +.IR file . +.TP +.BI \-i\ input-file +Select file to process. If +.B - +is specified, /dev/stdin will be used. +Default value is /dev/stdin. .TP -.BR \-e ,\ \-\-encoding \ \fIENCODING\fP -Specifies the encoding of the file. +.BI \-n\ n +Process the file recursively +.I n +times. Default value is 1. .TP -.BR \-n ,\ \-\-iterations \ \fIN\fP -Process the file recursively \fIN\fP times. Defaults to 1 time. +.BI \-o\ output-file +Select output file. If +.B - +is specified, /dev/stdout will be used. +Default value is /dev/stdout. .TP -.BR \-u ,\ \-\-unshebang +.BI \-s\ symbol +Set the prefix symbol for preprocessor directives. +Default value is +.BR @ . +.TP +.B \-u Clear the shebang line, remove it if this flag is used twice. If used twice, an empty line will be inserted after the new first line. + +.SH OPERANDS +The following operands are supported: .TP -.BR \-i ,\ \-\-input \ \fIFILE\fP -Select file to process. Defaults to /dev/stdin. +.I shell +The shell to run instead of +.BR bash . +The +.I shell +must be compatible with POSIX shell. .TP -.BR \-o ,\ \-\-output \ \fIFILE\fP -Select output file. Defaults to /dev/stdout. +.IR argument \ ... +Command line arguments for the shell. + +.SH STDIN +The +.B gpp +utility does not use the standard input. + +.SH INPUT FILES +The input file may be of any type, except it may not be a directory. + +.SH ENVIRONMENT VARIABLES +The following environment variables affects the execution of +.BR gpp : .TP -.BR \-f ,\ \-\-file \ \fIFILE\fP -Equivalent to \-i \fIFILE\fP \-o \fIFILE\fP. +.B PATH +Default. See to the Base Definitions volume of POSIX.1-2017, Section 8.3, Other Environment Variables. +.PP +.B gpp +will set the environment variable +.B _GPP +to the zeroth argument +.B gpp +was execute with (the name of the command or path to the command). + +.SH ASYNCHRONOUS EVENTS +Default. + +.SH STDOUT +The +.B gpp +utility does not use the standard output. + +.SH STDERR +The standard error is only used for diagnostic messages. + +.SH OUTPUT FILES +The output file may be of any type, except it may not be a directory. + +.SH EXTENDED DESCRIPTION +None. + +.SH EXIT STATUS .TP -.BR \-D ,\ \-\-export \ \fINAME\fP=\fIVALUE\fP -Set the environment variable \fINAME\fP to hold -the value \fIVALUE\fP. +0 +Successful completion. .TP -.BR \-D ,\ \-\-export \ \fINAME\fP -Set the environment variable \fINAME\fP to hold -the value 1. -.PP -Short options must be joined. The value of a flag must -be in a separate argument from the flag itself. +1 +An error occurred. + +.SH CONSEQUENCES OF ERRORS +Default. + +.SH APPLICATION USAGE +None. .SH EXAMPLES .SS Conditional hello world @@ -236,7 +330,16 @@ C is one of the few languages that includes a preprocessor, some times it is not enough; and all languages need preprocessors. -.SH "SEE ALSO" +.SH NOTES +None. + +.SH BUS +None. + +.SH FUTURE DIRECTIONS +None. + +.SH SEE ALSO .BR bash (1), .BR jpp (1), .BR cpp (1), @@ -0,0 +1,245 @@ +/* See LICENSE file for copyright and license details. */ +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "arg.h" + + +char *argv0; + + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-D name[=value]] [-f file | [-i input-file] [-o output-file]] " + "[-n count] [-s symbol] [-u [-u]] [shell [argument] ...]\n", argv0); + exit(1); +} + + +static int +get_fd(const char *path) +{ + const char *p; + char *s; + long int tmp; + + if (!strcmp(path, "/dev/stdin")) + return STDIN_FILENO; + if (!strcmp(path, "/dev/stdout")) + return STDOUT_FILENO; + if (!strcmp(path, "/dev/stderr")) + return STDERR_FILENO; + + if (!strncmp(path, "/dev/fd/", sizeof("/dev/fd/") - 1)) + p = &path[sizeof("/dev/fd/") - 1]; + else if (!strncmp(path, "/proc/self/fd/", sizeof("/proc/self/fd/") - 1)) + p = &path[sizeof("/proc/self/fd/") - 1]; + else + return -1; + + if (!isdigit(*p)) + return -1; + errno = 0; + tmp = strtol(p, &s, 10); + if (errno || tmp > INT_MAX || *s) + return -1; + return (int)tmp; +} + + +static int +xopen(const char *path, int *do_close) +{ + int fd = get_fd(path); + if (fd >= 0) { + *do_close = 0; + return fd; + } + fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "%s: open %s O_RDONLY: %s\n", argv0, path, strerror(errno)); + exit(1); + } + *do_close = 1; + return fd; +} + + +static int +xcreate(const char *path) +{ + int fd = get_fd(path); + if (fd >= 0) + return fd; + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + fprintf(stderr, "%s: open %s O_WRONLY|O_CREAT|O_TRUNC 0666: %s\n", argv0, path, strerror(errno)); + exit(1); + } + return fd; +} + + +int +main(int argc, char *argv[]) +{ + const char *shell_[] = {"bash", NULL}, **shell = shell_; + const char *input_file = NULL; + const char *output_file = NULL; + const char *symbol = NULL; + int iterations = -1; + int unshebang = 0; + long int tmp; + char *arg, *p, *q; + char *in_data = NULL, *out_data = NULL; + size_t in_size = 0, in_len = 0, in_off = 0; + size_t out_size = 0, out_len = 0, out_off = 0; + int in_fd, out_fd, do_close; + ssize_t r; + + ARGBEGIN { + case 'D': + arg = EARGF(usage()); + if (!*arg || *arg == '=') + usage(); + p = strchr(arg, '='); + if (p) + *p++ = '\0'; + if (setenv(arg, p ? p : "1", 1)) { + fprintf(stderr, "%s: setenv %s %s 1: %s\n", argv0, arg, p ? p : "1", strerror(errno)); + return 1; + } + break; + case 'f': + if (input_file || output_file) + usage(); + input_file = output_file = EARGF(usage()); + if (!*input_file) + usage(); + break; + case 'i': + if (input_file) + usage(); + input_file = EARGF(usage()); + if (!*input_file) + usage(); + break; + case 'n': + if (iterations >= 0) + usage(); + arg = EARGF(usage()); + if (!isdigit(*arg)) + usage(); + errno = 0; + tmp = strtol(arg, &arg, 10); + if (errno || tmp > INT_MAX || *arg) + usage(); + iterations = (int)tmp; + break; + case 'o': + if (output_file) + usage(); + output_file = EARGF(usage()); + if (!*output_file) + usage(); + break; + case 's': + if (symbol) + usage(); + symbol = EARGF(usage()); + break; + case 'u': + if (unshebang == 2) + usage(); + unshebang += 1; + break; + default: + usage(); + } ARGEND; + + if (argc) + shell = (void *)argv; + + if (setenv("_GPP", argv0, 1)) + fprintf(stderr, "%s: setenv _GPP %s 1: %s\n", argv0, argv0, strerror(errno)); + + if (iterations < 0) + iterations = 1; + if (!symbol) + symbol = "@"; + if (!input_file || (input_file[0] == '-' && !input_file[1])) + input_file = "/dev/stdin"; + if (!output_file || (output_file[0] == '-' && !output_file[1])) + output_file = "/dev/stdout"; + + in_fd = xopen(input_file, &do_close); + for (;;) { + if (in_len == in_size) { + in_data = realloc(in_data, in_size += 8096); + if (!in_data) { + fprintf(stderr, "%s: realloc: %s\n", argv0, strerror(errno)); + return 1; + } + } + r = read(in_fd, &in_data[in_len], in_size - in_len); + if (r <= 0) { + if (!r) + break; + fprintf(stderr, "%s: read %s: %s\n", argv0, input_file, strerror(errno)); + } + in_len += (size_t)r; + } + if (do_close) + close(in_fd); + + if (unshebang && in_len >= 2 && in_data[0] == '#' && in_data[1] == '!') { + p = memchr(in_data, '\n', in_len); + if (!p) + goto after_unshebang; + in_off = (size_t)(++p - in_data); + if (in_off == in_len) + goto after_unshebang; + if (unshebang >= 2) { + q = memchr(p--, '\n', in_len - in_off); + if (!q) + goto after_unshebang; + memmove(p, &p[1], (size_t)(q - in_data) - in_off--); + *--q = '\n'; + } + } +after_unshebang: + + while (iterations--) { + /* TODO parse: in -> out */ + + in_len = 0; + in_off = 0; + + /* TODO shell: out -> in */ + + out_len = 0; + out_off = 0; + } + + free(out_data); + out_fd = xcreate(output_file); + while (in_off < in_len) { + r = write(out_fd, &in_data[in_off], in_len - in_off); + if (r <= 0) { + fprintf(stderr, "%s: write %s: %s\n", argv0, output_file, strerror(errno)); + return 1; + } + in_off += (size_t)r; + } + if (close(out_fd)) + fprintf(stderr, "%s: write %s: %s\n", argv0, output_file, strerror(errno)); + free(in_data); + return 0; +} |