pax_global_header00006660000000000000000000000064145641006430014515gustar00rootroot0000000000000052 comment=0528bcb993cac6c412acd3ae2e09539e994c0a59 scdoc-1.11.3/000077500000000000000000000000001456410064300126735ustar00rootroot00000000000000scdoc-1.11.3/.build.yml000066400000000000000000000002171456410064300145730ustar00rootroot00000000000000image: alpine/edge sources: - https://git.sr.ht/~sircmpwn/scdoc tasks: - build: | cd scdoc make - check: | cd scdoc make check scdoc-1.11.3/.editorconfig000066400000000000000000000001401456410064300153430ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf [*.{c,h}] indent_style = tab indent_size = 4 scdoc-1.11.3/.gitignore000066400000000000000000000000461456410064300146630ustar00rootroot00000000000000.build scdoc scdoc.1 scdoc.5 scdoc.pc scdoc-1.11.3/COPYING000066400000000000000000000020471456410064300137310ustar00rootroot00000000000000Copyright © 2017 Drew DeVault Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. scdoc-1.11.3/Makefile000066400000000000000000000031071456410064300143340ustar00rootroot00000000000000VERSION=1.11.3 CFLAGS?=-g MAINFLAGS:=-DVERSION='"$(VERSION)"' -Wall -Wextra -Werror -Wno-unused-parameter LDFLAGS+=-static INCLUDE+=-Iinclude PREFIX?=/usr/local BINDIR?=$(PREFIX)/bin MANDIR?=$(PREFIX)/share/man PCDIR?=$(PREFIX)/share/pkgconfig OUTDIR=.build HOST_SCDOC=./scdoc .DEFAULT_GOAL=all OBJECTS=\ $(OUTDIR)/main.o \ $(OUTDIR)/string.o \ $(OUTDIR)/utf8_chsize.o \ $(OUTDIR)/utf8_decode.o \ $(OUTDIR)/utf8_encode.o \ $(OUTDIR)/utf8_fgetch.o \ $(OUTDIR)/utf8_fputch.o \ $(OUTDIR)/utf8_size.o \ $(OUTDIR)/util.o $(OUTDIR)/%.o: src/%.c @mkdir -p $(OUTDIR) $(CC) -std=c99 -pedantic -c -o $@ $(CFLAGS) $(MAINFLAGS) $(INCLUDE) $< scdoc: $(OBJECTS) $(CC) $(LDFLAGS) -o $@ $^ scdoc.1: scdoc.1.scd $(HOST_SCDOC) $(HOST_SCDOC) < $< > $@ scdoc.5: scdoc.5.scd $(HOST_SCDOC) $(HOST_SCDOC) < $< > $@ scdoc.pc: scdoc.pc.in sed -e 's:@prefix@:$(PREFIX):g' -e 's:@version@:$(VERSION):g' < $< > $@ all: scdoc scdoc.1 scdoc.5 scdoc.pc clean: rm -rf $(OUTDIR) scdoc scdoc.1 scdoc.5 scdoc.pc install: all mkdir -p $(DESTDIR)/$(BINDIR) $(DESTDIR)/$(MANDIR)/man1 $(DESTDIR)/$(MANDIR)/man5 $(DESTDIR)/$(PCDIR) install -m755 scdoc $(DESTDIR)/$(BINDIR)/scdoc install -m644 scdoc.1 $(DESTDIR)/$(MANDIR)/man1/scdoc.1 install -m644 scdoc.5 $(DESTDIR)/$(MANDIR)/man5/scdoc.5 install -m644 scdoc.pc $(DESTDIR)/$(PCDIR)/scdoc.pc uninstall: rm -f $(DESTDIR)/$(BINDIR)/scdoc rm -f $(DESTDIR)/$(MANDIR)/man1/scdoc.1 rm -f $(DESTDIR)/$(MANDIR)/man5/scdoc.5 rm -f $(DESTDIR)/$(PCDIR)/scdoc.pc check: scdoc scdoc.1 scdoc.5 @find test -perm -111 -exec '{}' \; .PHONY: all clean install uninstall check scdoc-1.11.3/README.md000066400000000000000000000011611456410064300141510ustar00rootroot00000000000000# scdoc scdoc is a simple man page generator for POSIX systems written in C99. ## Installation scdoc is likely available from your local system distribution -- your package manager is the preferred means of installation. To install from source: make sudo make install You can pass PREFIX or DESTDIR to make if you'd like: make PREFIX=/usr sudo make PREFIX=/usr install Uninstallation is similar: sudo make uninstall ## Usage See scdoc(1) ## Contributing Send patches/bug reports to [~sircmpwn/public-inbox@lists.sr.ht][mailing-list] [mailing-list]: mailto:~sircmpwn/public-inbox@lists.sr.ht scdoc-1.11.3/contrib/000077500000000000000000000000001456410064300143335ustar00rootroot00000000000000scdoc-1.11.3/contrib/_incr_version000077500000000000000000000001721456410064300171200ustar00rootroot00000000000000#!/bin/sh -eux sed -i Makefile -e "s/^VERSION=${1}/VERSION=${2}/" git add Makefile git commit -m "Update version to ${2}" scdoc-1.11.3/include/000077500000000000000000000000001456410064300143165ustar00rootroot00000000000000scdoc-1.11.3/include/str.h000066400000000000000000000004151456410064300152770ustar00rootroot00000000000000#ifndef _SCDOC_STRING_H #define _SCDOC_STRING_H #include struct str { char *str; size_t len, size; }; struct str *str_create(void); void str_free(struct str *str); void str_reset(struct str *str); int str_append_ch(struct str *str, uint32_t ch); #endif scdoc-1.11.3/include/unicode.h000066400000000000000000000016341456410064300161210ustar00rootroot00000000000000#ifndef _SCDOC_UNICODE_H #define _SCDOC_UNICODE_H #include #include #include // Technically UTF-8 supports up to 6 byte codepoints, but Unicode itself // doesn't really bother with more than 4. #define UTF8_MAX_SIZE 4 #define UTF8_INVALID 0x80 /** * Grabs the next UTF-8 character and advances the string pointer */ uint32_t utf8_decode(const char **str); /** * Encodes a character as UTF-8 and returns the length of that character. */ size_t utf8_encode(char *str, uint32_t ch); /** * Returns the size of the next UTF-8 character */ int utf8_size(const char *str); /** * Returns the size of a UTF-8 character */ size_t utf8_chsize(uint32_t ch); /** * Reads and returns the next character from the file. */ uint32_t utf8_fgetch(FILE *f); /** * Writes this character to the file and returns the number of bytes written. */ size_t utf8_fputch(FILE *f, uint32_t ch); #endif scdoc-1.11.3/include/util.h000066400000000000000000000012361456410064300154460ustar00rootroot00000000000000#ifndef _SCDOC_PARSER_H #define _SCDOC_PARSER_H #include #include struct parser { FILE *input, *output; int line, col; int qhead; uint32_t queue[32]; uint32_t flags; const char *str; int fmt_line, fmt_col; }; enum formatting { FORMAT_BOLD = 1, FORMAT_UNDERLINE = 2, FORMAT_LAST = 4, }; void parser_fatal(struct parser *parser, const char *err); uint32_t parser_getch(struct parser *parser); void parser_pushch(struct parser *parser, uint32_t ch); void parser_pushstr(struct parser *parser, const char *str); int roff_macro(struct parser *p, char *cmd, ...); void *xcalloc(size_t n, size_t s); void *xrealloc(void *p, size_t s); #endif scdoc-1.11.3/scdoc.1.scd000066400000000000000000000013151456410064300146200ustar00rootroot00000000000000SCDOC(1) # NAME scdoc - generate _man_(7) manual pages # SYNOPSIS *scdoc* < _input_ # DESCRIPTION The scdoc utility reads _scdoc_(5) syntax from the standard input and writes _man_(7) style roff to the standard output. # ENVIRONMENT The scdoc utility supports the standard _SOURCE_DATE_EPOCH_ environment variable for reproducible builds. This variables specifies the date in number of seconds since the Unix epoch, see: https://reproducible-builds.org/specs/source-date-epoch/ # SEE ALSO _scdoc_(5) # AUTHORS Maintained by Drew DeVault . Up-to-date sources can be found at https://git.sr.ht/~sircmpwn/scdoc and bugs/patches can be submitted by email to ~sircmpwn/public-inbox@lists.sr.ht. scdoc-1.11.3/scdoc.5.scd000066400000000000000000000111551456410064300146270ustar00rootroot00000000000000SCDOC(5) # NAME scdoc - document format for writing manual pages # SYNTAX Input files must use the UTF-8 encoding. ## Preamble Each scdoc file must begin with the following preamble: _NAME_(_section_) ["left\_footer" ["center\_header"]] _NAME_ is the name of the man page you are writing, and _section_ is the section you're writing for (see _man_(1) for information on manual sections). _left\_footer_ and _center\_header_ are optional arguments which set the text positioned at those locations in the generated man page, and *must* be surrounded with double quotes. ## Section headers Each section of your man page should begin with something similar to the following: # HEADER NAME Subsection headers are also understood - use two hashes. Each header must have an empty line on either side. ## Paragraphs Begin a new paragraph with an empty line. ## Line breaks Insert a line break by ending a line with \+\+. The result looks++ like this. ## Formatting Text can be made *bold* or _underlined_ with asterisks and underscores: \*bold\* or \_underlined\_. Underscores in the_middle_of_words will be disregarded. ## Indentation You may indent lines with tab characters (*\\t*) to indent them by 4 spaces in the output. Indented lines may not contain headers. The result looks something like this. You may use multiple lines and most _formatting_. Deindent to return to normal, or indent again to increase your indentation depth. ## Lists You may start bulleted lists with dashes (-), like so: ``` - Item 1 - Item 2 - Subitem 1 - Subitem 2 - Item 3 ``` The result looks like this: - Item 1 - Item 2 - Subitem 1 - Subitem 2 - Item 3 You may also extend long entries onto another line by giving it the same indent level, plus two spaces. They will be rendered as a single list entry. ``` - Item 1 is pretty long so let's break it up onto two lines - Item 2 is shorter - But its children can go on for a while ``` - Item 1 is pretty long so let's break it up onto two lines - Item 2 is shorter - But its children can go on for a while ## Numbered lists Numbered lists are similar to normal lists, but begin with periods (.) instead of dashes (-), like so: ``` . Item 1 . Item 2 . Item 3, with multiple lines ``` . Item 1 . Item 2 . Item 3, with multiple lines ## Tables To begin a table, add an empty line followed by any number of rows. Each line of a table should start with | or : to start a new row or column respectively (or space to continue the previous cell on multiple lines), followed by [ or - or ] to align the contents to the left, center, or right, followed by a space and the contents of that cell. You may use a space instead of an alignment specifier to inherit the alignment of the same column in the previous row. Each row must have the same number of columns; empty columns are permitted. The first character of the first row is not limited to | and has special meaning. [ will produce a table with borders around each cell. | will produce a table with no borders. ] will produce a table with one border around the whole table. To conclude your table, add an empty line after the last row. ``` [[ *Foo* :- _Bar_ :- | *Row 1* : Hello :] world! | *Row 2* : こんにちは : 世界 ! ``` [[ *Foo* :- _Bar_ :- | *Row 1* : Hello :] world! | *Row 2* : こんにちは : 世界 ! You may also cause columns to expand to fill the available space with < (left align), = (center align), and > (right align), like so: ``` [[ *Normal column* :< Expanded column | *Foo* : Bar ``` [[ *Normal column* :< Expanded column | *Foo* : Bar ## Literal text You may turn off scdoc formatting and output literal text with escape codes and literal blocks. Inserting a \\ into your source will cause the subsequent symbol to be treated as a literal and copied directly to the output. You may also make blocks of literal syntax like so: ``` \``` _This formatting_ will *not* be interpreted by scdoc. \``` ``` These blocks will be indented one level. Note that literal text is shown literally in the man viewer - that is, it's not a means for inserting your own roff macros into the output. Note that \\ is still interpreted within literal blocks, which for example can be useful to output \``` inside of a literal block. ## Comments Lines beginning with ; and a space are ignored. ``` ; This is a comment ``` # CONVENTIONS By convention, all scdoc documents should be hard wrapped at 80 columns. # SEE ALSO _scdoc_(1) # AUTHORS Maintained by Drew DeVault . Up-to-date sources can be found at https://git.sr.ht/~sircmpwn/scdoc and bugs/patches can be submitted by email to ~sircmpwn/public-inbox@lists.sr.ht. scdoc-1.11.3/scdoc.pc.in000066400000000000000000000002051456410064300147140ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=${prefix} scdoc=${exec_prefix}/bin/scdoc Name: scdoc Description: Man page generator Version: @version@ scdoc-1.11.3/src/000077500000000000000000000000001456410064300134625ustar00rootroot00000000000000scdoc-1.11.3/src/main.c000066400000000000000000000420231456410064300145530ustar00rootroot00000000000000#define _XOPEN_SOURCE 600 #include #include #include #include #include #include #include #include #include #include #include "str.h" #include "unicode.h" #include "util.h" static struct str *parse_section(struct parser *p) { struct str *section = str_create(); uint32_t ch; char *subsection; while ((ch = parser_getch(p)) != UTF8_INVALID) { if (ch < 0x80 && isalnum((unsigned char)ch)) { int ret = str_append_ch(section, ch); assert(ret != -1); } else if (ch == ')') { if (section->len == 0) { break; } int sec = strtol(section->str, &subsection, 10); if (section->str == subsection) { parser_fatal(p, "Expected section digit"); break; } if (sec < 0 || sec > 9) { parser_fatal(p, "Expected section between 0 and 9"); break; } return section; } else { parser_fatal(p, "Expected alphanumerical character or )"); break; } }; parser_fatal(p, "Expected manual section"); return NULL; } static struct str *parse_extra(struct parser *p) { struct str *extra = str_create(); int ret = str_append_ch(extra, '"'); assert(ret != -1); uint32_t ch; while ((ch = parser_getch(p)) != UTF8_INVALID) { if (ch == '"') { ret = str_append_ch(extra, ch); assert(ret != -1); return extra; } else if (ch == '\n') { parser_fatal(p, "Unclosed extra preamble field"); break; } else { ret = str_append_ch(extra, ch); assert(ret != -1); } } str_free(extra); return NULL; } static void parse_preamble(struct parser *p) { struct str *name = str_create(); int ex = 0; struct str *extras[2] = { NULL }; struct str *section = NULL; uint32_t ch; time_t date_time; char date[256]; char *source_date_epoch = getenv("SOURCE_DATE_EPOCH"); if (source_date_epoch != NULL) { unsigned long long epoch; char *endptr; errno = 0; epoch = strtoull(source_date_epoch, &endptr, 10); if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0)) || (errno != 0 && epoch == 0)) { fprintf(stderr, "$SOURCE_DATE_EPOCH: strtoull: %s\n", strerror(errno)); exit(EXIT_FAILURE); } if (endptr == source_date_epoch) { fprintf(stderr, "$SOURCE_DATE_EPOCH: No digits were found: %s\n", endptr); exit(EXIT_FAILURE); } if (*endptr != '\0') { fprintf(stderr, "$SOURCE_DATE_EPOCH: Trailing garbage: %s\n", endptr); exit(EXIT_FAILURE); } if (epoch > ULONG_MAX) { fprintf(stderr, "$SOURCE_DATE_EPOCH: value must be smaller than or " "equal to %lu but was found to be: %llu \n", ULONG_MAX, epoch); exit(EXIT_FAILURE); } date_time = epoch; } else { date_time = time(NULL); } struct tm *date_tm = gmtime(&date_time); strftime(date, sizeof(date), "%F", date_tm); while ((ch = parser_getch(p)) != UTF8_INVALID) { if ((ch < 0x80 && isalnum((unsigned char)ch)) || ch == '_' || ch == '-' || ch == '.') { int ret = str_append_ch(name, ch); assert(ret != -1); } else if (ch == '(') { section = parse_section(p); } else if (ch == '"') { if (ex == 2) { parser_fatal(p, "Too many extra preamble fields"); } extras[ex++] = parse_extra(p); } else if (ch == '\n') { if (name->len == 0) { parser_fatal(p, "Expected preamble"); } if (section == NULL) { parser_fatal(p, "Expected manual section"); } char *ex2 = extras[0] != NULL ? extras[0]->str : NULL; char *ex3 = extras[1] != NULL ? extras[1]->str : NULL; fprintf(p->output, ".TH \"%s\" \"%s\" \"%s\"", name->str, section->str, date); /* ex2 and ex3 are already double-quoted */ if (ex2) { fprintf(p->output, " %s", ex2); } if (ex3) { fprintf(p->output, " %s", ex3); } fprintf(p->output, "\n"); break; } else if (section == NULL) { parser_fatal(p, "Name characters must be A-Z, a-z, 0-9, `-`, `_`, or `.`"); } } str_free(name); for (int i = 0; i < 2; ++i) { if (extras[i] != NULL) { str_free(extras[i]); } } } static void parse_format(struct parser *p, enum formatting fmt) { char formats[FORMAT_LAST] = { [FORMAT_BOLD] = 'B', [FORMAT_UNDERLINE] = 'I', }; char error[512]; if (p->flags) { if ((p->flags & ~fmt)) { snprintf(error, sizeof(error), "Cannot nest inline formatting " "(began with %c at %d:%d)", p->flags == FORMAT_BOLD ? '*' : '_', p->fmt_line, p->fmt_col); parser_fatal(p, error); } fprintf(p->output, "\\fR"); } else { fprintf(p->output, "\\f%c", formats[fmt]); p->fmt_line = p->line; p->fmt_col = p->col; } p->flags ^= fmt; } static bool parse_linebreak(struct parser *p) { uint32_t plus = parser_getch(p); if (plus != '+') { fprintf(p->output, "+"); parser_pushch(p, plus); return false; } uint32_t lf = parser_getch(p); if (lf != '\n') { fprintf(p->output, "+"); parser_pushch(p, lf); parser_pushch(p, plus); return false; } uint32_t ch = parser_getch(p); if (ch == '\n') { parser_fatal( p, "Explicit line breaks cannot be followed by a blank line"); } parser_pushch(p, ch); fprintf(p->output, "\n.br\n"); return true; } static void parse_text(struct parser *p) { uint32_t ch, next, last = ' '; int i = 0; while ((ch = parser_getch(p)) != UTF8_INVALID) { switch (ch) { case '\\': ch = parser_getch(p); if (ch == UTF8_INVALID) { parser_fatal(p, "Unexpected EOF"); } else if (ch == '\\') { fprintf(p->output, "\\e"); } else if (ch == '`') { fprintf(p->output, "\\`"); } else { utf8_fputch(p->output, ch); } break; case '*': parse_format(p, FORMAT_BOLD); break; case '_': next = parser_getch(p); if (!isalnum((unsigned char)last) || ( (p->flags & FORMAT_UNDERLINE) && !isalnum((unsigned char)next))) { parse_format(p, FORMAT_UNDERLINE); } else { utf8_fputch(p->output, ch); } if (next == UTF8_INVALID) { return; } parser_pushch(p, next); break; case '+': if (parse_linebreak(p)) { last = '\n'; } break; case '\n': utf8_fputch(p->output, ch); return; case '.': if (!i) { // Escape . if it's the first character fprintf(p->output, "\\&.\\&"); break; } /* fallthrough */ case '\'': if (!i) { // Escape ' if it's the first character fprintf(p->output, "\\&'\\&"); break; } /* fallthrough */ case '!': case '?': last = ch; utf8_fputch(p->output, ch); // Suppress sentence spacing fprintf(p->output, "\\&"); break; default: last = ch; utf8_fputch(p->output, ch); break; } ++i; } } static void parse_heading(struct parser *p) { uint32_t ch; int level = 1; while ((ch = parser_getch(p)) != UTF8_INVALID) { if (ch == '#') { ++level; } else if (ch == ' ') { break; } else { parser_fatal(p, "Invalid start of heading (probably needs a space)"); } } switch (level) { case 1: fprintf(p->output, ".SH "); break; case 2: fprintf(p->output, ".SS "); break; default: parser_fatal(p, "Only headings up to two levels deep are permitted"); break; } while ((ch = parser_getch(p)) != UTF8_INVALID) { utf8_fputch(p->output, ch); if (ch == '\n') { break; } } } static int parse_indent(struct parser *p, int *indent, bool write) { int i = 0; uint32_t ch; while ((ch = parser_getch(p)) == '\t') { ++i; } parser_pushch(p, ch); if ((ch == '\n' || ch == UTF8_INVALID) && *indent != 0) { // Don't change indent when we encounter empty lines or EOF return *indent; } if (write) { if ((i - *indent) > 1) { parser_fatal(p, "Indented by an amount greater than 1"); } else if (i < *indent) { for (int j = *indent; i < j; --j) { roff_macro(p, "RE", NULL); } } else if (i == *indent + 1) { fprintf(p->output, ".RS 4\n"); } } *indent = i; return i; } static void list_header(struct parser *p, int *num) { if (*num == -1) { fprintf(p->output, ".IP %s 4\n", "\\(bu"); } else { fprintf(p->output, ".IP %d. 4\n", *num); *num = *num + 1; } } static void parse_list(struct parser *p, int *indent, int num) { uint32_t ch; if ((ch = parser_getch(p)) != ' ') { parser_fatal(p, "Expected space before start of list entry"); } fprintf(p->output, ".PD 0\n"); list_header(p, &num); parse_text(p); do { parse_indent(p, indent, true); if ((ch = parser_getch(p)) == UTF8_INVALID) { break; } switch (ch) { case ' ': if ((ch = parser_getch(p)) != ' ') { parser_fatal(p, "Expected two spaces for list entry continuation"); } parse_text(p); break; case '-': case '.': if ((ch = parser_getch(p)) != ' ') { parser_fatal(p, "Expected space before start of list entry"); } list_header(p, &num); parse_text(p); break; default: roff_macro(p, "PD", NULL); parser_pushch(p, ch); return; } } while (ch != UTF8_INVALID); } static void parse_literal(struct parser *p, int *indent) { uint32_t ch; if ((ch = parser_getch(p)) != '`' || (ch = parser_getch(p)) != '`' || (ch = parser_getch(p)) != '\n') { parser_fatal(p, "Expected ``` and a newline to begin literal block"); } int stops = 0; roff_macro(p, "nf", NULL); fprintf(p->output, ".RS 4\n"); bool check_indent = true; do { if (check_indent) { int _indent = *indent; parse_indent(p, &_indent, false); if (_indent < *indent) { parser_fatal(p, "Cannot deindent in literal block"); } while (_indent > *indent) { --_indent; fprintf(p->output, "\t"); } check_indent = false; } if ((ch = parser_getch(p)) == UTF8_INVALID) { break; } if (ch == '`') { if (++stops == 3) { if ((ch = parser_getch(p)) != '\n') { parser_fatal(p, "Expected literal block to end with newline"); } roff_macro(p, "fi", NULL); roff_macro(p, "RE", NULL); return; } } else { while (stops != 0) { fputc('`', p->output); --stops; } switch (ch) { case '.': fprintf(p->output, "\\&."); break; case '\'': fprintf(p->output, "\\&'"); break; case '\\': ch = parser_getch(p); if (ch == UTF8_INVALID) { parser_fatal(p, "Unexpected EOF"); } else if (ch == '\\') { fprintf(p->output, "\\\\"); } else { utf8_fputch(p->output, ch); } break; case '\n': check_indent = true; /* fallthrough */ default: utf8_fputch(p->output, ch); break; } } } while (ch != UTF8_INVALID); } enum table_align { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_LEFT_EXPAND, ALIGN_CENTER_EXPAND, ALIGN_RIGHT_EXPAND, }; struct table_row { struct table_cell *cell; struct table_row *next; }; struct table_cell { enum table_align align; struct str *contents; struct table_cell *next; }; static void parse_table(struct parser *p, uint32_t style) { struct table_row *table = NULL; struct table_row *currow = NULL, *prevrow = NULL; struct table_cell *curcell = NULL; int column = 0; int numcolumns = -1; uint32_t ch; parser_pushch(p, '|'); do { if ((ch = parser_getch(p)) == UTF8_INVALID) { break; } switch (ch) { case '\n': goto commit_table; case '|': prevrow = currow; currow = xcalloc(1, sizeof(struct table_row)); if (prevrow) { if (column != numcolumns && numcolumns != -1) { parser_fatal(p, "Each row must have the " "same number of columns"); } numcolumns = column; column = 0; prevrow->next = currow; } curcell = xcalloc(1, sizeof(struct table_cell)); currow->cell = curcell; if (!table) { table = currow; } break; case ':': if (!currow) { parser_fatal(p, "Cannot start a column without " "starting a row first"); } else { struct table_cell *prev = curcell; curcell = xcalloc(1, sizeof(struct table_cell)); if (prev) { prev->next = curcell; } ++column; } break; case ' ': goto continue_cell; default: parser_fatal(p, "Expected either '|' or ':'"); break; } if ((ch = parser_getch(p)) == UTF8_INVALID) { break; } switch (ch) { case '[': curcell->align = ALIGN_LEFT; break; case '-': curcell->align = ALIGN_CENTER; break; case ']': curcell->align = ALIGN_RIGHT; break; case '<': curcell->align = ALIGN_LEFT_EXPAND; break; case '=': curcell->align = ALIGN_CENTER_EXPAND; break; case '>': curcell->align = ALIGN_RIGHT_EXPAND; break; case ' ': if (prevrow) { struct table_cell *pcell = prevrow->cell; for (int i = 0; i <= column && pcell; ++i, pcell = pcell->next) { if (i == column) { curcell->align = pcell->align; break; } } } else { parser_fatal(p, "No previous row to infer alignment from"); } break; default: parser_fatal(p, "Expected one of '[', '-', ']', or ' '"); break; } curcell->contents = str_create(); continue_cell: switch (ch = parser_getch(p)) { case ' ': // Read out remainder of the text while ((ch = parser_getch(p)) != UTF8_INVALID) { switch (ch) { case '\n': goto commit_cell; default:; int ret = str_append_ch(curcell->contents, ch); assert(ret != -1); break; } } break; case '\n': goto commit_cell; default: parser_fatal(p, "Expected ' ' or a newline"); break; } commit_cell: if (strstr(curcell->contents->str, "T{") || strstr(curcell->contents->str, "T}")) { parser_fatal(p, "Cells cannot contain T{ or T} " "due to roff limitations"); } } while (ch != UTF8_INVALID); commit_table: if (ch == UTF8_INVALID) { return; } roff_macro(p, "TS", NULL); switch (style) { case '[': fprintf(p->output, "allbox;"); break; case ']': fprintf(p->output, "box;"); break; } // Print alignments first currow = table; while (currow) { curcell = currow->cell; while (curcell) { char *align = ""; switch (curcell->align) { case ALIGN_LEFT: align = "l"; break; case ALIGN_CENTER: align = "c"; break; case ALIGN_RIGHT: align = "r"; break; case ALIGN_LEFT_EXPAND: align = "lx"; break; case ALIGN_CENTER_EXPAND: align = "cx"; break; case ALIGN_RIGHT_EXPAND: align = "rx"; break; } fprintf(p->output, "%s%s", align, curcell->next ? " " : ""); curcell = curcell->next; } fprintf(p->output, "%s\n", currow->next ? "" : "."); currow = currow->next; } // Then contents currow = table; while (currow) { curcell = currow->cell; fprintf(p->output, "T{\n"); while (curcell) { parser_pushstr(p, curcell->contents->str); parse_text(p); if (curcell->next) { fprintf(p->output, "\nT}\tT{\n"); } else { fprintf(p->output, "\nT}"); } struct table_cell *prev = curcell; curcell = curcell->next; str_free(prev->contents); free(prev); } fprintf(p->output, "\n"); struct table_row *prev = currow; currow = currow->next; free(prev); } roff_macro(p, "TE", NULL); fprintf(p->output, ".sp 1\n"); } static void parse_document(struct parser *p) { uint32_t ch; int indent = 0; do { parse_indent(p, &indent, true); if ((ch = parser_getch(p)) == UTF8_INVALID) { break; } switch (ch) { case ';': if ((ch = parser_getch(p)) != ' ') { parser_fatal(p, "Expected space after ; to begin comment"); } do { ch = parser_getch(p); } while (ch != UTF8_INVALID && ch != '\n'); break; case '#': if (indent != 0) { parser_pushch(p, ch); parse_text(p); break; } parse_heading(p); break; case '-': parse_list(p, &indent, -1); break; case '.': if ((ch = parser_getch(p)) == ' ') { parser_pushch(p, ch); parse_list(p, &indent, 1); } else { parser_pushch(p, ch); parse_text(p); } break; case '`': parse_literal(p, &indent); break; case '[': case '|': case ']': if (indent != 0) { parser_fatal(p, "Tables cannot be indented"); } parse_table(p, ch); break; case ' ': parser_fatal(p, "Tabs are required for indentation"); break; case '\n': if (p->flags) { char error[512]; snprintf(error, sizeof(error), "Expected %c before starting " "new paragraph (began with %c at %d:%d)", p->flags == FORMAT_BOLD ? '*' : '_', p->flags == FORMAT_BOLD ? '*' : '_', p->fmt_line, p->fmt_col); parser_fatal(p, error); } roff_macro(p, "PP", NULL); break; default: parser_pushch(p, ch); parse_text(p); break; } } while (ch != UTF8_INVALID); } static void output_scdoc_preamble(struct parser *p) { fprintf(p->output, ".\\\" Generated by scdoc " VERSION "\n"); fprintf(p->output, ".\\\" Complete documentation for this program is not " "available as a GNU info page\n"); // Fix weird quotation marks // http://bugs.debian.org/507673 // http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html fprintf(p->output, ".ie \\n(.g .ds Aq \\(aq\n"); fprintf(p->output, ".el .ds Aq '\n"); // Disable hyphenation: roff_macro(p, "nh", NULL); // Disable justification: roff_macro(p, "ad l", NULL); fprintf(p->output, ".\\\" Begin generated content:\n"); } int main(int argc, char **argv) { if (argc == 2 && strcmp(argv[1], "-v") == 0) { printf("scdoc " VERSION "\n"); return 0; } else if (argc > 1) { fprintf(stderr, "Usage: scdoc < input.scd > output.roff\n"); return 1; } struct parser p = { .input = stdin, .output = stdout, .line = 1, .col = 1 }; output_scdoc_preamble(&p); parse_preamble(&p); parse_document(&p); return 0; } scdoc-1.11.3/src/string.c000066400000000000000000000014321456410064300151340ustar00rootroot00000000000000#include #include #include "str.h" #include "unicode.h" #include "util.h" static void ensure_capacity(struct str *str, size_t len) { if (len + 1 >= str->size) { char *new = xrealloc(str->str, str->size * 2); str->str = new; str->size *= 2; } } struct str *str_create(void) { struct str *str = xcalloc(1, sizeof(struct str)); str->str = xcalloc(16, 1); str->size = 16; str->len = 0; str->str[0] = '\0'; return str; } void str_free(struct str *str) { if (!str) return; free(str->str); free(str); } int str_append_ch(struct str *str, uint32_t ch) { int size = utf8_chsize(ch); if (size <= 0) { return -1; } ensure_capacity(str, str->len + size); utf8_encode(&str->str[str->len], ch); str->len += size; str->str[str->len] = '\0'; return size; } scdoc-1.11.3/src/utf8_chsize.c000066400000000000000000000003341456410064300160610ustar00rootroot00000000000000#include #include #include "unicode.h" size_t utf8_chsize(uint32_t ch) { if (ch < 0x80) { return 1; } else if (ch < 0x800) { return 2; } else if (ch < 0x10000) { return 3; } return 4; } scdoc-1.11.3/src/utf8_decode.c000066400000000000000000000007621456410064300160240ustar00rootroot00000000000000#include #include "unicode.h" uint8_t masks[] = { 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; uint32_t utf8_decode(const char **char_str) { uint8_t **s = (uint8_t **)char_str; uint32_t cp = 0; if (**s < 128) { // shortcut cp = **s; ++*s; return cp; } int size = utf8_size((char *)*s); if (size == -1) { ++*s; return UTF8_INVALID; } uint8_t mask = masks[size - 1]; cp = **s & mask; ++*s; while (--size) { cp <<= 6; cp |= **s & 0x3f; ++*s; } return cp; } scdoc-1.11.3/src/utf8_encode.c000066400000000000000000000007101456410064300160270ustar00rootroot00000000000000#include #include #include "unicode.h" size_t utf8_encode(char *str, uint32_t ch) { size_t len = 0; uint8_t first; if (ch < 0x80) { first = 0; len = 1; } else if (ch < 0x800) { first = 0xc0; len = 2; } else if (ch < 0x10000) { first = 0xe0; len = 3; } else { first = 0xf0; len = 4; } for (size_t i = len - 1; i > 0; --i) { str[i] = (ch & 0x3f) | 0x80; ch >>= 6; } str[0] = ch | first; return len; } scdoc-1.11.3/src/utf8_fgetch.c000066400000000000000000000007661456410064300160450ustar00rootroot00000000000000#include #include #include "unicode.h" uint32_t utf8_fgetch(FILE *f) { char buffer[UTF8_MAX_SIZE]; int c = fgetc(f); if (c == EOF) { return UTF8_INVALID; } buffer[0] = (char)c; int size = utf8_size(buffer); if (size > UTF8_MAX_SIZE) { fseek(f, size - 1, SEEK_CUR); return UTF8_INVALID; } if (size > 1) { int amt = fread(&buffer[1], 1, size - 1, f); if (amt != size - 1) { return UTF8_INVALID; } } const char *ptr = buffer; return utf8_decode(&ptr); } scdoc-1.11.3/src/utf8_fputch.c000066400000000000000000000003461456410064300160700ustar00rootroot00000000000000#include #include #include "unicode.h" size_t utf8_fputch(FILE *f, uint32_t ch) { char buffer[UTF8_MAX_SIZE]; char *ptr = buffer; size_t size = utf8_encode(ptr, ch); return fwrite(&buffer, 1, size, f); } scdoc-1.11.3/src/utf8_size.c000066400000000000000000000007361456410064300155540ustar00rootroot00000000000000#include #include #include "unicode.h" struct { uint8_t mask; uint8_t result; int octets; } sizes[] = { { 0x80, 0x00, 1 }, { 0xE0, 0xC0, 2 }, { 0xF0, 0xE0, 3 }, { 0xF8, 0xF0, 4 }, { 0xFC, 0xF8, 5 }, { 0xFE, 0xF8, 6 }, { 0x80, 0x80, -1 }, }; int utf8_size(const char *s) { uint8_t c = (uint8_t)*s; for (size_t i = 0; i < sizeof(sizes) / 2; ++i) { if ((c & sizes[i].mask) == sizes[i].result) { return sizes[i].octets; } } return -1; } scdoc-1.11.3/src/util.c000066400000000000000000000032241456410064300146040ustar00rootroot00000000000000#include #include #include #include #include "unicode.h" #include "util.h" void parser_fatal(struct parser *parser, const char *err) { fprintf(stderr, "Error at %d:%d: %s\n", parser->line, parser->col, err); fclose(parser->input); fclose(parser->output); exit(1); } uint32_t parser_getch(struct parser *parser) { if (parser->qhead) { return parser->queue[--parser->qhead]; } if (parser->str) { uint32_t ch = utf8_decode(&parser->str); if (!ch || ch == UTF8_INVALID) { parser->str = NULL; return UTF8_INVALID; } return ch; } uint32_t ch = utf8_fgetch(parser->input); if (ch == '\n') { parser->col = 0; ++parser->line; } else { ++parser->col; } return ch; } void parser_pushch(struct parser *parser, uint32_t ch) { if (ch != UTF8_INVALID) { parser->queue[parser->qhead++] = ch; } } void parser_pushstr(struct parser *parser, const char *str) { parser->str = str; } int roff_macro(struct parser *p, char *cmd, ...) { FILE *f = p->output; int l = fprintf(f, ".%s", cmd); va_list ap; va_start(ap, cmd); const char *arg; while ((arg = va_arg(ap, const char *))) { fputc(' ', f); fputc('"', f); while (*arg) { uint32_t ch = utf8_decode(&arg); if (ch == '"') { fputc('\\', f); ++l; } l += utf8_fputch(f, ch); } fputc('"', f); l += 3; } va_end(ap); fputc('\n', f); return l + 1; } void *xcalloc(size_t n, size_t s) { void *p = calloc(n, s); if (!p) { fputs("Out of memory\n", stderr); abort(); } return p; } void *xrealloc(void *p, size_t s) { void *ret = realloc(p, s); if (!ret) { fputs("Out of memory\n", stderr); abort(); } return ret; } scdoc-1.11.3/test/000077500000000000000000000000001456410064300136525ustar00rootroot00000000000000scdoc-1.11.3/test/comments000077500000000000000000000004161456410064300154260ustar00rootroot00000000000000#!/bin/sh . test/lib.sh begin "Ignore comments" scdoc </dev/null test(8) ; this is a comment Hello world! EOF end 1 begin "Fail on invalid comments" scdoc </dev/null test(8) ;this is an invalid comment Hello world! EOF end 1 scdoc-1.11.3/test/heading000077500000000000000000000006471456410064300152060ustar00rootroot00000000000000#!/bin/sh . test/lib.sh begin "Fail on ###" scdoc </dev/null test(8) ### this is not a valid heading EOF end 1 begin "Expects a space after #" scdoc </dev/null test(8) #needs a space there EOF end 1 begin "Emits a new section" scdoc </dev/null test(8) # HEADER EOF end 0 begin "Emits a new subsection" scdoc </dev/null test(8) ## HEADER EOF end 0 scdoc-1.11.3/test/indent000077500000000000000000000020421456410064300150570ustar00rootroot00000000000000#!/bin/sh . test/lib.sh begin "Indents indented text" scdoc </dev/null test(8) Not indented Indented one level EOF end 0 begin "Deindents following indented text" scdoc </dev/null test(8) Not indented Indented one level Not indented EOF end 0 begin "Disallows multi-step indents" scdoc </dev/null test(8) Not indented Indented one level Indented three levels Not indented EOF end 1 begin "Allows indentation changes > 1 in literal blocks" scdoc </dev/null test(8) This is some code: \`\`\` foobar: # asdf \`\`\` EOF end 0 begin "Allows multi-step dedents" scdoc </dev/null test(8) Not indented Indented one level Indented two levels Not indented EOF end 0 begin "Allows indented literal blocks" scdoc </dev/null test(8) \`\`\` This block is indented. \`\`\` EOF end 0 begin "Disallows dedenting in literal blocks" scdoc </dev/null test(8) \`\`\` This block is indented. This line is dedented past the start of the block. \`\`\` EOF end 1 scdoc-1.11.3/test/inline-formatting000077500000000000000000000015051456410064300172270ustar00rootroot00000000000000#!/bin/sh . test/lib.sh begin "Disallows nested formatting" scdoc </dev/null test(8) _hello *world*_ EOF end 1 begin "Ignores underscores in words" scdoc </dev/null test(8) hello_world EOF end 0 begin "Ignores underscores in underlined words" scdoc </dev/null test(8) _hello_world_ EOF end 0 begin "Ignores underscores in bolded words" scdoc </dev/null test(8) *hello_world* EOF end 0 begin "Emits bold text" scdoc </dev/null test(8) hello *world* EOF end 0 begin "Emits underlined text" scdoc </dev/null test(8) hello _world_ EOF end 0 begin "Handles escaped characters" scdoc </dev/null test(8) hello \_world\_ EOF end 0 scdoc-1.11.3/test/lib.sh000066400000000000000000000003011456410064300147460ustar00rootroot00000000000000printf '== %s\n' "$0" trap "printf '\n'" EXIT begin() { printf '%-50s' "$1" } scdoc() { ./scdoc "$@" 2>&1 } end() { if [ $? -ne "$1" ] then printf 'FAIL\n' else printf 'OK\n' fi } scdoc-1.11.3/test/line-breaks000077500000000000000000000012701456410064300157740ustar00rootroot00000000000000#!/bin/sh . test/lib.sh begin "Handles line break" scdoc </dev/null test(8) hello++ world EOF end 0 begin "Disallows empty line after line break" scdoc </dev/null test(8) hello++ world EOF end 1 begin "Leave single +" scdoc </dev/null test(8) hello+world EOF end 0 begin "Leave double + without newline" scdoc </dev/null test(8) hello++world EOF end 0 begin "Handles underlined text following line break" scdoc </dev/null test(8) hello++ _world_ EOF end 0 begin "Suppresses sentence spacing" scdoc </dev/null test(8) hel!lo. world. EOF end 0 scdoc-1.11.3/test/preamble000077500000000000000000000035371456410064300153770ustar00rootroot00000000000000#!/bin/sh . test/lib.sh begin "Expects a name" scdoc </dev/null (8) EOF end 1 begin "Expects a section" scdoc </dev/null test EOF end 1 begin "Expects a section within the parentheses" scdoc </dev/null test() EOF end 1 begin "Expects name to alphanumeric" scdoc </dev/null !!!!(8) EOF end 1 begin "Expects section to start with a number" scdoc </dev/null test(hello) EOF end 1 begin "Expects section to be legit" scdoc </dev/null test(100) EOF end 1 begin "Expects section to be legit with subsection" scdoc </dev/null test(100hello) EOF end 1 begin "Expects section not to contain a space" scdoc </dev/null test(8 hello) EOF end 1 begin "Accepts a valid preamble" scdoc </dev/null test(8) EOF end 0 begin "Accepts a valid preamble with subsection" scdoc </dev/null test(8hello) EOF end 0 # Make sure SOURCE_DATE_EPOCH is not set for the next tests unset SOURCE_DATE_EPOCH begin "Writes the appropriate header" scdoc </dev/null test(8) EOF end 0 begin "Preserves dashes" scdoc </dev/null test-manual(8) EOF end 0 begin "Handles extra footer field" scdoc </dev/null test-manual(8) "Footer" EOF end 0 begin "Handles both extra fields" scdoc </dev/null test-manual(8) "Footer" "Header" EOF end 0 begin "Emits empty footer correctly" scdoc </dev/null test-manual(8) "" "Header" EOF end 0 export SOURCE_DATE_EPOCH=1512861537 begin "Supports \$SOURCE_DATE_EPOCH" scdoc </dev/null reproducible-manual(8) EOF end 0 scdoc-1.11.3/test/tables000077500000000000000000000003561456410064300150560ustar00rootroot00000000000000#!/bin/sh . test/lib.sh begin "Handles empty table cells" scdoc </dev/null bug-example(1) [[ *Foo* :- :- EOF end 0 begin "Disallows differing row lengths" scdoc </dev/null bug-example(1) [- :- :- |- :- |- :- :- EOF end 1