commit 986714435dc0117b9aabdc0c5be3375f8dcbc980
Author: Ed van Bruggen <edvb@uw.edu>
Date: Mon, 24 Dec 2018 17:46:45 -0800
Initial commit
Diffstat:
.gitignore | | | 21 | +++++++++++++++++++++ |
LICENSE | | | 19 | +++++++++++++++++++ |
Makefile | | | 55 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
arg.h | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
config.def.h | | | 11 | +++++++++++ |
config.mk | | | 18 | ++++++++++++++++++ |
markman.c | | | 394 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
util.c | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
util.h | | | 14 | ++++++++++++++ |
9 files changed, 670 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,21 @@
+# swap files
+*~
+*.swp
+*.swo
+\#*\#
+
+# compiled files
+markman
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.o
+*.obj
+*.so
+*.so.*
+
+# misc
+tags
+config.h
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,19 @@
+zlib License
+
+Copyright (c) 2017-2018 Ed van Bruggen
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,55 @@
+# markman
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+EXE = markman
+SRC = $(wildcard *.c)
+OBJ = $(SRC:.c=.o)
+
+all: options $(EXE)
+
+options:
+ @echo $(EXE) build options:
+ @echo "CFLAGS = $(CFLAGS)"
+ @echo "LDFLAGS = $(LDFLAGS)"
+
+.o:
+ @echo $(LD) $@
+ @$(LD) -o $@ $< $(LDFLAGS)
+
+.c.o:
+ @echo $(CC) $<
+ @$(CC) -c -o $@ $< $(CFLAGS)
+
+$(OBJ): config.h config.mk
+
+config.h:
+ @echo creating $@ from config.def.h
+ @cp config.def.h $@
+
+$(EXE): $(OBJ)
+ @echo $(CC) -o $@
+ @$(CC) -o $@ $(OBJ) $(LDFLAGS)
+
+clean:
+ @echo cleaning
+ @rm -f $(OBJ) $(EXE)
+
+install: all
+ @echo installing $(EXE) to $(DESTDIR)$(PREFIX)/bin
+ @mkdir -p $(DESTDIR)$(PREFIX)/bin
+ @cp -f $(EXE) $(DESTDIR)$(PREFIX)/bin
+ @chmod 755 $(DESTDIR)$(PREFIX)/bin/$(EXE)
+ @echo installing manual page to $(DESTDIR)$(MANPREFIX)/man1
+ @mkdir -p $(DESTDIR)$(MANPREFIX)/man1
+ @sed "s/VERSION/$(VERSION)/g" < $(EXE).1 > $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1
+ @chmod 644 $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1
+
+uninstall:
+ @echo removing $(EXE) from $(DESTDIR)$(PREFIX)/bin
+ @rm -f $(DESTDIR)$(PREFIX)/bin/$(EXE)
+ @echo removing manual page from $(DESTDIR)$(MANPREFIX)/man1
+ @rm -f $(DESTDIR)$(MANPREFIX)/man1/$(EXE).1
+
+.PHONY: all options clean install uninstall
diff --git a/arg.h b/arg.h
@@ -0,0 +1,63 @@
+/*
+ * 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 = basename(*argv), argv++, argc--;\
+ argv[0] && argv[0][0] == '-'\
+ && argv[0][1];\
+ argc--, argv++) {\
+ char argc_;\
+ char **argv_;\
+ int brk_;\
+ 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_)
+
+/* Handles -NUM syntax */
+#define ARGNUM case '0':\
+ case '1':\
+ case '2':\
+ case '3':\
+ case '4':\
+ case '5':\
+ case '6':\
+ case '7':\
+ case '8':\
+ case '9'
+
+#define ARGEND }\
+ }
+
+#define ARGC() argc_
+
+#define ARGNUMF() (brk_ = 1, atoi(argv[0]))
+
+#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])))
+
+#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
+ (char *)0 :\
+ (brk_ = 1, (argv[0][1] != '\0')?\
+ (&argv[0][1]) :\
+ (argc--, argv++, argv[0])))
+
+#endif
diff --git a/config.def.h b/config.def.h
@@ -0,0 +1,11 @@
+int cap = 1; /* if section headers should be capitalized */
+
+size_t sec = 1; /* default section number */
+char *title = NULL; /* default title, NULL if set to filename */
+char *date = NULL; /* default date man page generated, NULL if set to current YYYY-MM-DD */
+char *ver = NULL; /* default optional version of programming being documented, NULL if none */
+char *mid = NULL; /* default optional text to be displayed in the middle */
+
+int namesec = 0;
+char *synsec = NULL; /* optional text for synopsis section to be inserted */
+int descsec = 0; /* insert section heading for description */
diff --git a/config.mk b/config.mk
@@ -0,0 +1,18 @@
+# markman version number
+VERSION = 0.0.0
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = $(PREFIX)/share/man
+
+# includes and libraries
+INCS = -Iinclude
+LIBS =
+
+# flags
+CPPFLAGS = -DVERSION=\"$(VERSION)\" -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=600
+CFLAGS = -g -std=c99 -pedantic -Wall $(INCS) $(CPPFLAGS)
+LDFLAGS = -g $(LIBS)
+
+# compiler and linker
+CC = cc
diff --git a/markman.c b/markman.c
@@ -0,0 +1,394 @@
+/* See LICENSE file for copyright and license details. */
+#include <libgen.h>
+#include <time.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+
+#include "arg.h"
+#include "util.h"
+
+#include "config.h"
+
+#define WS " \t\n"
+
+char *argv0;
+
+struct Block;
+typedef struct Block *Block;
+struct Line;
+typedef struct Line *Line;
+
+typedef enum {
+ STR,
+ BOLD,
+ ITALIC,
+ LINK,
+ LCODE,
+} LineType;
+
+typedef enum {
+ BR,
+ HEADER,
+ PARA,
+ QUOTE,
+ ULIST,
+ OLIST,
+ BCODE,
+} BlockType;
+
+struct Line {
+ LineType t;
+ union {
+ char *s; /* STR, BOLD, ITALIC, LCODE */
+ struct { /* LINK */
+ Line txt;
+ char *url;
+ } u;
+ } v;
+ Line next;
+};
+
+struct Block {
+ BlockType t; /* BR */
+ union {
+ char *s; /* BCODE */
+ Line l; /* PARA, QUOTE, */
+ struct { /* HEADER */
+ int lvl;
+ Line l;
+ } h;
+ } v;
+ Block next;
+};
+
+static char *
+str_cap(char *s)
+{
+ char *p;
+ if (cap)
+ for (p = s; *p; p++)
+ *p = toupper(*p);
+ return s;
+}
+
+static Line
+mk_str(char *s, LineType t)
+{
+ Line l = emalloc(sizeof(struct Line));
+ l->t = t;
+ l->v.s = estrdup(s);
+ l->next = NULL;
+ return l;
+}
+
+static Block
+mk_header(int lvl, Line l)
+{
+ Block b = emalloc(sizeof(struct Block));
+ b->t = HEADER;
+ b->v.h.lvl = lvl;
+ b->v.h.l = l;
+ b->next = NULL;
+ return b;
+}
+
+static Block
+mk_para(Line l)
+{
+ Block b = emalloc(sizeof(struct Block));
+ b->t = PARA;
+ b->v.l = l;
+ b->next = NULL;
+ return b;
+}
+
+static Block
+mk_bcode(char *s)
+{
+ Block b = emalloc(sizeof(struct Block));
+ b->t = BCODE;
+ b->v.s = estrdup(s);
+ b->next = NULL;
+ return b;
+}
+
+static Line
+line_parse(char *src, LineType t)
+{
+ Line ret;
+ char *s;
+ for (s = src; *src; src++) /* TODO rewrite this mess */
+ if ((*src == '*' && src[1] == '*') ||
+ (*src == '_' && src[1] == '_')) {
+ *src = '\0';
+ ret = mk_str(s, t);
+ src += 2;
+ ret->next = line_parse(src, t == BOLD ? STR : BOLD);
+ return ret;
+ } else if (*src == '*' || *src == '_') {
+ *src = '\0';
+ ret = mk_str(s, t);
+ src += 1;
+ ret->next = line_parse(src, t == ITALIC ? STR : ITALIC);
+ return ret;
+ } else if (*src == '`') {
+ *src = '\0';
+ ret = mk_str(s, t);
+ src += 1;
+ ret->next = line_parse(src, t == LCODE ? STR : LCODE);
+ return ret;
+ } else if (*src == '[') {
+ *src = '\0';
+ ret = mk_str(s, t);
+ src += 1;
+ ret->next = line_parse(src, LINK);
+ return ret;
+ } else if (*src == ']' && t == LINK) {
+ *src = '\0';
+ ret = mk_str(s, t);
+ src += 1;
+ src += strcspn(src, WS);
+ ret->next = line_parse(src, STR);
+ return ret;
+ } else if (*src == '!' && src[1] == '[') {
+ *src = '\0';
+ ret = mk_str(s, t);
+ src += 1;
+ src += strcspn(src, "]") + 1;
+ src += strcspn(src, "])") + 1;
+ ret->next = line_parse(++src, STR);
+ return ret;
+ }
+ return mk_str(s, STR);
+}
+
+Block
+markman_parse(char *src)
+{
+ Block ret;
+ int lvl = 1, i;
+ char *s;
+ switch (*src) {
+ case '#':
+ for (; *src && *(++src) == '#'; lvl++);
+ src += strspn(src, WS);
+ s = ecalloc(strcspn(src, "\n") + 1, sizeof(char));
+ for (i = 0; *src && *src != '\n'; src++, i++)
+ s[i] = *src;
+ ret = mk_header(lvl, line_parse((s[0] != '-') ? str_cap(s) : s, STR));
+ if (lvl >= 4)
+ ret->v.h.l->t = BOLD;
+ free(s);
+ ret->next = markman_parse(++src);
+ return ret;
+ case '\n':
+ return markman_parse(++src);
+ case '\0':
+ return NULL;
+ /* case '\t': */
+ /* s = ++src; */
+ /* for (; *src; src++) */
+ /* if (*src == '\n' && src[1] == '\t') */
+ /* src[1] */
+ /* ret = mk_bcode(s); */
+ /* ret->next = markman_parse(src); */
+ /* return ret; */
+ case '`':
+ if (src[1] == '`' && src[2] == '`')
+ src += strcspn(src, "\n");
+ for (s = src; *src; src++)
+ if (!strncmp(src, "\n```\n", 5)) {
+ /* printf("'%s'\n", src); */
+ src[1] = '\0';
+ src += 5;
+ /* src += strcspn(src, "\n"); */
+ break;
+ }
+ /* printf("'%s'\n", src); */
+ ret = mk_bcode(s);
+ ret->next = markman_parse(src);
+ return ret;
+ default:
+ src += strspn(src, WS);
+ s = ecalloc(strlen(src), sizeof(char));
+ for (i = 0; *src; src++, i++)
+ if (*src == '\n' && src[1] == '\n') {
+ s[i+1] = '\0';
+ break;
+ } else if (*src == '\n')
+ s[i] = ' ';
+ else
+ s[i] = *src;
+ src += strspn(src, WS);
+ ret = mk_para(line_parse(s, STR));
+ ret->next = markman_parse(src);
+ return ret;
+ }
+}
+
+void disp_line(Line l)
+{
+ switch (l->t) {
+ case LINK:
+ case STR:
+ printf(l->v.s);
+ break;
+ case BOLD:
+ printf("\\fB%s\\fP", l->v.s);
+ break;
+ case ITALIC:
+ printf("\\fI%s\\fP", l->v.s);
+ break;
+ case LCODE:
+ printf("'%s'", l->v.s);
+ break;
+ }
+ if (l->next)
+ disp_line(l->next);
+}
+
+void
+disp_block(Block b, Block prev)
+{
+ switch (b->t) {
+ case BR:
+ break;
+ case HEADER:
+ if (b->v.h.lvl == 1 && namesec) break;
+ switch (b->v.h.lvl) {
+ case 1:
+ printf(".TH ");
+ break;
+ case 2:
+ printf(".SH ");
+ break;
+ case 3:
+ printf(".SS ");
+ break;
+ case 4:
+ printf(".TP\n");
+ break;
+ }
+ disp_line(b->v.h.l);
+ putchar('\n');
+ break;
+ case PARA:
+ if (prev && prev->t == HEADER && prev->v.h.lvl < 4)
+ puts(".PP");
+ disp_line(b->v.l);
+ puts("\n.PP");
+ break;
+ case BCODE:
+ printf(".RS 4\n.EX\n%s\n.EE\n.RE\n", b->v.s);
+ break;
+ case QUOTE:
+ case ULIST:
+ case OLIST:
+ break;
+ }
+ if (b->next)
+ disp_block(b->next, b);
+}
+
+void
+markman_disp(Block b, char *name)
+{
+ if (!b) return;
+ if (b->t != HEADER || b->v.h.lvl != 1 || namesec) {
+ printf(".TH %s %ld ", str_cap(name), sec);
+ if (date) {
+ printf("\"%s\" ", date);
+ } else {
+ /* TODO option to configure date format */
+ time_t tt = time(NULL);
+ struct tm *t = localtime(&tt);
+ printf("%d-%02d-%02d ", t->tm_year+1900, t->tm_mon+1, t->tm_mday);
+ }
+ if (ver)
+ printf("\"%s\" ", ver);
+ if (mid)
+ printf("\"%s\"", mid);
+ putchar('\n');
+ }
+ if (namesec && b->t == HEADER) {
+ puts(".PP\n.SH NAME");
+ disp_line(b->v.h.l);
+ putchar('\n');
+ }
+ if (synsec)
+ printf(".PP\n.SH SYNOPSIS\n%s\n", synsec);
+ if (descsec)
+ puts(".PP\n.SH DESCRIPTION");
+ disp_block(b, NULL);
+}
+
+static void
+usage(const int eval)
+{
+ die(eval, "usage: %s [-cChv] [-t TITLE] [-d DATE] [-SECNUM] [FILENAME]", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ ARGBEGIN {
+ case 'c':
+ cap = 0;
+ break;
+ case 'C':
+ cap = 1;
+ break;
+ ARGNUM:
+ if ((sec = ARGNUMF()) > 8 || sec == 0)
+ die(1, "%s: invalid section number, expect 1-8, received [%d]", argv0, sec);
+ break;
+ case 't':
+ title = EARGF(usage(1));
+ break;
+ case 'd':
+ date = EARGF(usage(1));
+ break;
+ case 'V':
+ ver = EARGF(usage(1));
+ break;
+ case 'm':
+ mid = EARGF(usage(1));
+ break;
+ case 'n':
+ namesec = 1;
+ break;
+ case 's':
+ synsec = EARGF(usage(1));
+ break;
+ case 'D':
+ descsec = 1;
+ break;
+ case 'h':
+ usage(0);
+ case 'v':
+ die(0, "%s v%s (c) 2017-2018 Ed van Bruggen", argv0, VERSION);
+ default:
+ usage(1);
+ } ARGEND;
+
+ char buf[BUFSIZ];
+ FILE *fp;
+ if (*argv) {
+ if (!(fp = fopen(*argv, "r")))
+ die(1, "%s: %s:", argv[0], *argv);
+ while ((fread(buf, 1, sizeof(buf), fp)) > 0) ;
+ if (!title) title = *argv;
+ markman_disp(markman_parse(buf), title);
+ } else {
+ while ((fread(buf, 1, sizeof(buf), stdin)) > 0) ;
+ markman_disp(markman_parse(buf), "stdin");
+ }
+ putchar('\n');
+
+ return EXIT_SUCCESS;
+}
diff --git a/util.c b/util.c
@@ -0,0 +1,75 @@
+/* See LICENSE file for copyright and license details. */
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+
+ if (!(p = calloc(nmemb, size)))
+ die(1, "calloc:");
+
+ return p;
+}
+
+void *
+emalloc(size_t size)
+{
+ void *p;
+
+ if (!(p = malloc(size)))
+ die(1, "malloc:");
+
+ return p;
+}
+
+void *
+erealloc(void *p, size_t size)
+{
+ if (!(p = realloc(p, size)))
+ die(1, "realloc:");
+
+ return p;
+}
+
+char *
+estrdup(char *s)
+{
+ if (!(s = strdup(s)))
+ die(1, "strdup:");
+
+ return s;
+}
+
+void
+efree(void *p)
+{
+ if (p)
+ free(p);
+}
+
+void
+die(int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+
+ if (eval > -1)
+ exit(eval);
+}
diff --git a/util.h b/util.h
@@ -0,0 +1,14 @@
+/* See LICENSE file for copyright and license details. */
+
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
+#define LEN(X) (sizeof(X) / sizeof((X)[0]))
+
+void *ecalloc(size_t nmemb, size_t size);
+void *emalloc(size_t size);
+void *erealloc(void *p, size_t size);
+char *estrdup(char *s);
+void efree(void *p);
+
+void die(int eval, const char *fmt, ...);