aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore17
-rw-r--r--Makefile33
-rw-r--r--README60
-rw-r--r--arg.h44
-rw-r--r--config.mk6
-rw-r--r--gpp.1163
-rw-r--r--gpp.c245
7 files changed, 481 insertions, 87 deletions
diff --git a/.gitignore b/.gitignore
index df7be57..4240069 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,16 +1,5 @@
-_/
-/bin/
-/obj/
-__pycache__/
-.*
-!.git*
-\#*\#
+*\#*
*~
-*.sw[op]
-*.py[co]
-*.bak
+*.o
+*.su
/gpp
-*.info
-*.gz
-*.install
-
diff --git a/Makefile b/Makefile
index f841829..dae30f0 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README b/README
index a703e75..ec06c25 100644
--- a/README
+++ b/README
@@ -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
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..79d054c
--- /dev/null
+++ b/arg.h
@@ -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
diff --git a/gpp.1 b/gpp.1
index 5e32c03..1b7bb69 100644
--- a/gpp.1
+++ b/gpp.1
@@ -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),
diff --git a/gpp.c b/gpp.c
new file mode 100644
index 0000000..569ae76
--- /dev/null
+++ b/gpp.c
@@ -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;
+}