markman

Unnamed repository; edit this file 'description' to name the repository.
git clone git://edryd.org/markman
Log | Files | Refs | LICENSE

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, ...);