code:
.\" (C) C.D.F. Miller, Heriot-Watt University, March 1984
.\"
.\" This file should be formatted by
.\"	lbl lbl | nroff -ms
.L= delimiter #
.ND
.TL
LBL - Symbolic Labels in Text Documents
.NH 1
Introduction
.L= sec 1 intro
.PP
.I lbl
is a pre-processor for
.I troff
and
.I nroff
which allows Sections, Figures, Tables, etc., to be referred to by
symbolic names rather than by absolute number.
It will handle forward references as well as backward ones.
.PP
Uses of a label in the text are indicated by a delimiter character (default '@',
but resettable), followed by a
.I type
name (e.g. "TABLE", "EQN", etc.), followed by the label itself
(e.g. "Profits-1983").
Each such occurrence is replaced by a reference number; the default
style of numbering is a sequence of period-separated arabic numbers
(e.g. 2.4.1) but this is resettable.
Finally, another delimiter is required to close the reference
(cf. the use of delimiters in
.I eqn
for in-line equations).
.LP
Examples:
.DS
For full details refer to Table @TABLE Profits-1983@.
As we saw in Chapter @chap intro@, ...
Figure @fig wing-profile@ shows the airflow patterns ...
.DE
.PP
Labels may be defined at any point in the text.
The definition looks like a
.I troff
macro, and the definition line is retained in the original text.
By default, the macro used is ".L=", but this can be reset.
The macro takes 3 arguments: a type-name, a level-number, and
a label-name.
.PP
The type-name corresponds to that used to signal a label occurrence in
the text.
There is no restriction on the choice of name; any sequence of
non-blank characters may be used.
Upper- and lower-case letters
.I are
distinguished.
.PP
The level-number corresponds to the header-level numbers used
by the .NH macro in
.I ms ,
the .sh macro in
.I me ,
etc.
The index at the given level is incremented by 1, and all higher indexes
set to 0.
Initially, all indexes are set to 0 by default, but other starting values
may be chosen.
There is an implementation-defined restriction on the number
of levels of index (currently 20);
it is not anticipated that this will lead to problems.
.PP
The label may be any sequence of non-blank characters;
the same label may be used in more than one type.
Furthermore, the special label-name ``*'' can be used to increment the
appropriate level-counter without defining a label at all;
this is useful, for example, when all tables, figures, etc., in a section
take their initial index from the section number: two ways of doing this
would be:
.DS L
a.
    .L= section 1 this-section
    .L= last table 0
    .L= last figure 0
    ...
    ... refer to table @section this-section@.@table profits@ ...
    ... see figure @section this-section@.@figure structure@ ...
    ...
    .L= table 1 profits
    ...
    .L= figure 1 structure
    ...

(see section #sec directives# for the use of
``.L=\ last\ ...'')

b.
    .L= section 1 this-section
    .L= table 1 *
    .L= figure 1 *
    ...
    ... refer to table @table profits@ ...
    ... see figure @figure structure@ ...
    ...
    .L= table 2 profits
    ...
    .L= figure 2 structure
    ...
.DE
It is largely a matter of taste which technique is used;
the former is more long-winded, but avoids the need to remember to
keep the tables and figures in step every time the section is
updated.
Yet a third possibility (similar to the first) would be:
.DS L
c.
    .L= section 1 this-section
    .ds Sc "@section this-section@
    .L= last table 0
    ...
    ... refer to table \*(Sc.@table profits@
    etc.
.DE
making use of the string-definition facility within
.I nroff .
.PP
It is important to be aware that all processing on labels is done
before
.I troff
processes the text; attempts to build label-references by
using macros or
.I troff
strings registers will almost certainly not work as expected.
.NH 1
Command Line Options
.L= sec 1 options
.PP
Options to
.I lbl
are set in the command line, which has the form
.DS
lbl [ -d\fIdelim\fR ] [ -m\fImacro\fR ] [ -l ] [ -s ]
.DE
.IP -d
.I \fIdelim\fR
is the character used to delimit in-line occurrences of label
references (default ``@'');
.IP -m
.I macro
is the 2-character name of
a
.I troff
macro which introduces label definitions, etc. (default ``L='');
.IP -l
causes a listing of label-definitions to be written to the standard
error stream.
Each label-type is listed, together with its print format, followed by
a line for each label of that type, showing label-name, file and line
where it is defined, and value;
.IP -s
causes the generation of the
.I troff
input file to be suppressed;
setting
.I -s
automatically also sets
.I -l .
.NH 1
Control directives
.L= sec 1 directives
.PP
In addition to defining labels, the \fB.L=\fR macro (or its substitute)
can be used for several other purposes.
These are all indicated by commands of the form
.DS
\&.L= \fIcommand\fR \fIargument\fR ...
.DE
where the \fIcommand\fRs are reserved words which may not be used as
type-names.
These commands allow control over the initialisation of label values,
the print format of labels, and thelabel reference delimiter.
.IP "\&.L= delimiter off"
.br
turns off interpretation of the delimiter character altogether;
subsequent text is copied literally until another \fIdelimiter\fR
command is encountered;
.IP "\&.L= delimiter \fIcharacter\fR"
.br
resets the delimiter in subsequent text to the given character;
.IP "\&.L= format \fItypename\fR \fIstring\fR"
.br
sets the print format for labels of the given type - see section
#sec formats#;
.IP "\&.L= last \fItypename\fR \fIcount1\fR \fIcount2\fR ..."
.br
resets the counters for \fItypename\fR as though the last label
generated had been \fIcount1.count2...\fR (with all omitted counts
taken as 0).
.NH 2
Print formats
.L= sec 2 formats
.PP
The default print format for a label is as a sequence of period-separated
arabic numerals.
However, it is also possible to specify alphabetic or roman labels,
or a mixture, and to have separators other than a period.
This is governed by the format given in a ``.LE\ format'' command.
.PP
Such a format contains escape sequences (flagged by a ``%'' character)
and literal text.
The text is copied until an escape sequence is encountered; such a
sequence is replaced by the index for the next level of the label
value, printed in one of the following formats:
.IP %1
Arabic numerals (without non-significant leading zeros);
.IP %0
As %1, but offset by 1 so that the first value printed will be 0 rather
than 1;
.IP %a
Lower-case alphabetics, starting at ``a''; ``z'' is followed by ``aa'',
etc.;
.IP %A
Upper-case alphabetics;
.IP %i
Lower-case roman numerals (with some odd choices for large numbers,
consistent with the roman numerals printed by
.I troff ).
.IP %I
Upper-case roman numerals.
.LP
A ``%'' followed by any other character (in particular another ``%'') prints
as that character.
.NH 1
Limits
.L= sec 1 limits
.PP
.I Lbl
imposes no limit on the size of text to be processed, but (like
.I refer )
needs to act on a text as a whole, rather than processing individual
files.
The limit on the number of levels of header is unlikely to prove a problem.
The number of labels which may be used is limited only by the amount
of memory available to a process; even on a small machine it is
possible to handle a few hundred label definitions.
.NH 1
Further Examples
.L= sec 1 examples
.PP
The copying of the definition macros makes it possible to use them
as
.I troff
macros, as in the following example:
.DS
\&.de L=
\&.ie '\e\e$1'sec' .NH \e\e$2
\&.el .ie '\e\e$1'table' .if !'\e\e$3'*' \e{
\&.DS C
\&Table '\e\e$3' about here
\&.DE
\&\e}
\&.el .if '\e\e$1'fig' .if !'\e\e$3'*' \e{
\&.DS C
\&Figure '\e\e$3' about here
\&.DE
\&\e}
\&..
\&.nr LL 5i
\&.ND
\&.TL
\&Example of LBL
\&.L= sec 1 intro
\&Introduction
\&.L= table 1 *
\&.PP
\&We begin with a small table (Table @table opening@).
\&.L= table 2 opening
\&and consider things in more detail in Section
\&@sec cont@.
\&.L= sec 1 cont
\&Continuation
\&.L= table 1 *
\&.PP
\&In this continuation we refer back to Section
\&@sec intro@, which contained
\&Table @table opening@, and present more detailed
\&information in Table
\&@table detail@.
\&.L= table 2 detail
\&.L= sec 2 subcont
\&Sub-continuation
\&.PP
\&In which we further refine things, as shown in
\&Table @table mega-detail@ below.
\&.L= table 2 mega-detail
.DE
In the above, the ``.L=\ sec'' macros automatically generate the
numbered headings by expanding to the \fIms\fR ``.NH'' macros,
while the ``table'' definitions cause the insertion of figures
such as
.DS C
Table 'detail' about here
.DE
The above example thus produces the following:
.DS
                  Example of LBL

\fI1.  Introduction\fR

     We begin with a small table (Table 1.1).

            Table 'opening' about here

 and consider things in more detail in Section 2.

\fI2.  Continuation\fR

     In this continuation we refer back to Section
1,  which  contained  Table  1.1, and present more
detailed information in Table 2.1.

            Table 'detail' about here

\fI2.1.  Sub-continuation\fR

     In which we further refine things,  as  shown
in Table 2.2 below.

          Table 'mega-detail' about here
.DE
.PP
The alphabetic formats may be useful for such things as appendices,
e.g.
.DS
\&.L= format appendix %A
\&.L= appendix 1 trade-marks
\&.SH
\&Appendix @appendix trade-marks@:
\&List of Registered Trade Marks
\&.LP
\&(For the addresses of the proprietors see
\&@appendix props@).
\&  ...
\&.L= appendix 1 props
.DE
which will generate
.DS
\fIAppendix A: List of Registered Trade Marks\fR

(For the addresses of the proprietors see appendix B).
.DE


code:
/** @file
 * @brief Routines to build new labels and label-types.
 */

#ifndef _BUILD_H_
#define _BUILD_H_

/**
 * @brief Copy a string to a new block of memory.
 *
 * If we run out of memory, the process exits.
 * @param str the string to copy
 * @return a pointer to the copy
 */
char *copy(char *str);

/**
 * @brief Allocate a new type struct and add it to the global list of types.
 * @param name the type name
 * @return a pointer to the new struct
 */
Type *addtype(char *name);

/**
 * @brief Define a new label.
 * @param lbltype type of label to define. Will be created if it doesn't already exist.
 * @param lbllevel nesting level of label.
 * @param lblname name by which label will be referred to in cross-references.
 */
void addlbl(char *lbltype, char *lbllevel, char *lblname);

#endif

repo_name: dbane/lbl

license: unknown


code:
/** @file
 * @brief Global variables.
 */
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#ifndef _EXTERNS_H_
#define _EXTERNS_H_

#include <list>

/** @brief Delimiter used to insert label references.
 * Defaults to '@'. */
extern char delimiter;

/** @brief 2-char macro used to define a label.
 * Defaults to "L=". */
extern char macroname[];

/** @brief Name of temporary file used across passes. */
extern char tempname[];

/** @brief Printing format for a label reference.
 * Can be changed by "L= format". */
extern char *def_format;

/** @brief argv[0] */
extern char *progname;

/** @brief Name of input file */
extern char *filename;

/** @brief Was "-l" given as a command-line option? */
extern int lflag;

/** @brief Was "-s" given as a command-line option? */
extern int sflag;

/** @brief Handle for temporary file used between passes */
extern FILE *tempfile;

/** @brief Current line in input file, so that we can print line where defined for "-l" */
extern long fileline;

/** @brief List of label types */
extern	std::list<Type> typehead;

#endif


code:
/** @file
 * @brief Routines to find a type entry and a label entry.
 */

#ifndef _FIND_H_
#define _FIND_H_

/**
 * Find a label by name.
 * @param tp the type of label to find
 * @param name the label name
 * @return a pointer to the label struct; or NULL if it can't be found
 */
Label *typeFindlabel(Type *tp, char *name);

/**
 * Find a label type, optionally creating it if it doesn't exist.
 * @param name the type name
 * @param create whether or not to create the type if it doesn't exist
 * @return a pointer to the type struct
 */
Type *findtype(char *name, int create);

#endif


code:
/** @file
 * @brief Routines to look up command keywords.
 */

#ifndef _KEYWORD_H_
#define _KEYWORD_H_

/**
 * @brief Look up a command keyword by linear search.
 */
Keyword *findkeyword(char *word);

#endif


code:
/**
 * @mainpage Lbl
 * @tableofcontents
 *
 * @section abstract Abstract
 * Tool to resolve cross-references in *roff documents.
 *
 * @section help Help
 * See the man page.
 *
 * @section globals Globals
 * See externs.h
 *
 * @section files Files
 *

 *
File	Description

 *
tempfile	Output of the 1st pass, input to the 2nd (see tempfile below

 *

 *
 * @subsection tempfile tempfile
 * This contains the output of the 1st pass, the *roff document with
 * placeholders for the eventual cross-references.
 */


code:
/** @file
 * @brief Config settings & misc. utilities.
 *
 * These are bundled in with the main program for now.
 */
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#ifndef _LBL_H_
#define _LBL_H_

#include	<stdio.h>
#include	<types.h>

/** Standard format for labels */
#define	STDFORM	"%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1.%1"

/** First character to indicate a new directive in the temporary file */
#define	MAGIC1	'\01'

/** Second character to indicate a new directive in the temporary file */
#define MAGIC2	'\02'

/** In the temporary file, set the current input filename */
#define M_FILE	'F'

/** In the temporary file, set the delimiter or switch it off altogether */
#define M_DELIM	'D'

/**
 * @brief Get the current location in the input file.
 *
 * If we're not processing an input file, the result is an empty string.
 * @return a string describing the current location
 */
char *maybe_loc(void);

#endif


code:
/** @file
 * @brief Routines to list definitions in verbose mode.
 */

#ifndef _LIST_H_
#define _LIST_H_

/**
 * @brief List details of all labels, of all types, to stderr.
 */
void	listdefs(void);

#endif


code:
/** @file
 * @brief Routines to print a label according to a format.
 */

#ifndef _PRINTL_H_
#define _PRINTL_H_

/**
 * @brief Print a label.
 * @param lab label to print.
 * @param out file handle to output on.
 */
void	labelPrint(Label *lab, FILE *out);

#endif


code:
/** @file
 * @brief Routines to read temporary file, substituting for defined labels.
 */

#ifndef _RESCAN_H_
#define _RESCAN_H_

/**
 * @brief Second pass to parse the temporary file, follow any directives,
 *        and produce output.
 */
void rescan(void);

#endif


code:
/** @file
 * @brief Scan an input file, recording label-definitions, etc.
 *
 * All special actions are introduced by a line starting
 * @verbatim
.<macro>
@endverbatim
 * where @a macro is "L=" unless redefined in the command-line.
 * Actions are
 *

 *
.L= type level label

 *

 * define @a label by incrementing the @a levelth
 * index of label-type @a type, e.g.
 * @verbatim
.L= table 1 profits_1983
@endverbatim
 *

 *
.L= delimiter @a char

 *

 * redefines the delimiter for the subsequent text.
 *

 *
.L= format type string

 *

 * defines a format for displaying the given label @a type;
 * the latest definition in the input will be used for
 * ALL occurrences, including preceding ones.
 * e.g.
 * @verbatim
.L= format appendix %A-%1
@endverbatim
 *

 *
.L= next type value value ...

 *

 * set the value of the next label generated for @a type;
 * the @a values give successive levels, with omitted
 * trailing values defaulting to 0.
 *

 *

 */

#ifndef _SCAN_H_
#define _SCAN_H_

/**
 * @brief First pass to parse the input file and output a temporary file
 *        with directives.
 * @param fname the name of the input file to process
 * @param f a file handle opened to read fname
 */
void scan(char *fname, FILE *f);

#endif


code:
/** @file
 * @brief Routines to provide clean-up after trapping external signals.
 */

#ifndef _SIGNALS_H_
#define _SIGNALS_H_

/**
 * @brief Set signal handlers for a program run.
 */
void setsignals(void);

#endif


code:
/** @file
 * @brief Concrete data types.
 *
 * On the one hand, having these all in the same file isn't
 * good modularity. On the other hand, I don't want to write
 * loads of setters and getters.
 */
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#ifndef _TYPES_H_
#define _TYPES_H_

#include <list>

/** @todo Remove this */
#define	us		unsigned

/** Max no. of header levels */
#define	NLEVELS	20

/** Type for header levels member of label struct */
typedef int levels[NLEVELS];

/** Header format */
typedef char *format;

typedef struct Type Type;		/**< Type type */
typedef struct Label Label;		/**< Label type */
typedef struct Keyword Keyword;		/**< Keyword type */

/** Label type */
struct Type {
	char   *t_name;			/**< name */
	levels	t_levels;		/**< current header levels */
	format	t_format;		/**< format */
	std::list<Label> labelhead;	/**< list of labels of this type */
};

/** Label definition */
struct Label {
	char   *l_name;			/**< Name */
	Type   *l_type;			/**< Back-reference to label type */
	char   *l_file;			/**< File where defined */
	long	l_line;			/**< line where defined */
	levels	l_levels;		/**< Counter for label no. at different levels */
	int	l_bottom;		/**< Last significant level */
	unsigned char padding[4];
};

/** Action routine for keyword processing */
typedef int (*func) ();

/** Command keyword, to come after ".L=" at the start of an input line */
struct Keyword {
	char   *k_name;			/**< How is the keyword spelt */
	func	k_action;		/**< Function pointer for action to take */
	us int	k_minargs;		/**< Min no. of args for k_action */
	us int	k_maxargs;		/**< Max no. of args for k_action */
};

#endif


code:
#!/bin/sh
# Copyright 2013,2017 Darren Bane. All rights reserved.
# Use of this source code is governed by the
# license that can be found in the NOTICE file.

# git indent pre-commit hook
#
# To use, symlink to .git/hooks/pre-commit inside your repository and make sure
# it has execute permissions.
#
# This script does not handle file names that contain spaces.

cppfiles=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(cpp|h)$')
[ -z "$cppfiles" ] && exit 0

unformatted=""
for fn in $cppfiles; do
	astyle < $fn > /tmp/indent.$$
	if ! diff $fn /tmp/indent.$$ > /dev/null; then
		unformatted="$unformatted $fn";
	fi
done
rm /tmp/indent.$$
[ -z "$unformatted" ] && exit 0

# Some files are not indented. Print message and fail.

echo >&2 "C++ files must be formatted with astyle. Please run:"
for fn in $unformatted; do
	echo >&2 "  astyle $PWD/$fn"
done

exit 1

path: hooks/pre-commit


code:
.POSIX:

BINOWN= dbane
BINGRP= staff
SHAREOWN= dbane
SHAREGRP= staff
.include <bsd.own.mk>

PREFIX?= /Users/dbane
BINDIR= $(PREFIX)/bin
MANDIR= $(PREFIX)/man/man
PROG= lbl

# Need _DARWIN_C_SOURCE for strlcpy/strlcat
CXXFLAGS+= -I../hdr -Wall -Wextra -Weffc++ -std=c++14 -D_XOPEN_SOURCE=600 -D_DARWIN_C_SOURCE
#CXXFLAGS+= -I../hdr -Wall -Wextra -Weffc++ -std=c++14 -D_XOPEN_SOURCE=600
#CFLAGS+= -I../hdr -Wall -Wextra -std=c11 -D_XOPEN_SOURCE=600
#LDADD+= -lbsd

MAN= lbl.1
SRCS= lbl.cpp actions.cpp build.cpp externs.cpp find.cpp keyword.cpp list.cpp \
      printl.cpp rescan.cpp scan.cpp signals.cpp
LIBC= /lib/x86_64-linux-gnu/libc.so.6

.include <bsd.prog.mk>

path: src/Makefile


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

/* actions.c:
 *	keyword action routines
 */

#include	<lbl.h>
#include	<ctype.h>
#include        <string.h>
#include        <stdlib.h>
#include        <err.h>
#include        "build.h"
#include        "find.h"

extern char *def_format;
extern int sflag;
extern FILE *tempfile;

void a_delimiter(int nargs, char *argvec[]);
/*ARGSUSED*/
void
a_delimiter(int nargs, char *argvec[])
{
	(void)nargs;
	if (strcmp(argvec[1], "off") == 0) {
		if (!sflag)
			fprintf(tempfile, "%c%c%c\n", MAGIC1, MAGIC2, M_DELIM);
		return;
	}
	if (argvec[1][1] != '\0') {
		warnx("%sdelimiter more than 1 character", maybe_loc());
		return;
	}
	if (argvec[1][0] == '\0') {
		warnx("%snull delimiter character", maybe_loc());
		return;
	}
	if (!sflag)
		fprintf(tempfile, "%c%c%c%c\n", MAGIC1, MAGIC2,
		        M_DELIM, argvec[1][0]);
}

void a_format(int nargs, char *argvec[]);
/*ARGSUSED*/
void
a_format(int nargs, char *argvec[])
{
	Type *tp = findtype(argvec[1], 1);

	(void)nargs;
	if (tp->t_format != def_format)
		warnx("%s[warning] format for %s redefined", maybe_loc(), tp->t_name);
	tp->t_format = copy(argvec[2]);
}

void a_last(int nargs, char *argvec[]);
/*ARGSUSED*/
void
a_last(int nargs, char *argvec[])
{
	Type *tp = findtype(argvec[1], 1);
	int indx;

	nargs -= 2;
	argvec += 2;
	for (indx = 0; indx < nargs; indx++) {
		if (!isdigit(argvec[indx][0])) {
			warnx("%snon-numeric label index", maybe_loc());
			break;
		}
		tp->t_levels[indx] = atoi(argvec[indx]);
	}
	while (indx < NLEVELS)
		tp->t_levels[indx++] = 0;
}


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#include	<lbl.h>
#include	<ctype.h>
#include        <stdlib.h>
#include        <bsd/string.h>
#include        <err.h>
#include        <sysexits.h>
#include        "build.h"
#include        "find.h"

extern char *def_format;
extern char *filename;
extern long fileline;
extern std::list<Type> typehead;

static void *alloc(unsigned long nbytes);
static void *
alloc(unsigned long nbytes)
{
	void *ptr = malloc(nbytes);

	if (ptr == NULL)
		errx(EX_OSERR, "%sran out of memory space", maybe_loc());
	return ptr;
}

char   *
copy(char *str)
{
	char   *s = (char *)alloc(strlen(str) + 1);

	strlcpy(s, str, strlen(str) + 1);
	return s;
}

void
addlbl(char *lbltype, char *lbllevel, char *lblname)
{
	Type *tp = findtype(lbltype, 1);
	Label *last_next;
	int bottom;
	int indx;

	if (!isdigit(lbllevel[0])) {
		warnx("%snon-numeric index level", maybe_loc());
		return;
	}
	if ((bottom = atoi(lbllevel)) <= 0 || bottom > NLEVELS) {
		warnx("%sindex level must be in range 1-%u", maybe_loc(),
		      NLEVELS);
		return;
	}
	bottom--;

	++(tp->t_levels[bottom]);
	for (indx = bottom + 1; indx < NLEVELS; indx++)
		tp->t_levels[indx] = 0;

	if (strcmp(lblname, "*") != 0) {
		Label *lp;

		lp = typeFindlabel(tp, lblname);

		if (lp != NULL) {
			warnx("%sredefinition of %s ignored", maybe_loc(),
			      lblname);
			return;
		}
		lp = (Label *) alloc(sizeof(Label));

		lp->l_name = copy(lblname);
		lp->l_type = tp;
		for (indx = 0; indx < bottom; indx++)
			lp->l_levels[indx] = tp->t_levels[indx];
		lp->l_levels[bottom] = tp->t_levels[bottom];
		lp->l_bottom = bottom;
		lp->l_file = filename;
		lp->l_line = fileline;
		/*
		 * Add to end of list, so that verbose listing comes out in
		 * order
		 */
		if (LIST_EMPTY(&(tp->labelhead)))
			LIST_INSERT_HEAD(&(tp->labelhead), lp, link);
		else {
			Label *last;

			last = LIST_FIRST(&(tp->labelhead));
			last_next = LIST_NEXT(last, link);
			while (last_next != NULL) {
				last = last_next;
				last_next = LIST_NEXT(last, link);
			}
			LIST_INSERT_AFTER(last, lp, link);
		}
	}
}

Type   *
addtype(char *name)
{
	Type *tp = (Type *) alloc(sizeof(Type));
	us int indx;

	tp->t_name = copy(name);
	for (indx = 0; indx < NLEVELS; indx++)
		tp->t_levels[indx] = 0;
	tp->t_format = def_format;
	LIST_INIT(&(tp->labelhead));
	LIST_INSERT_HEAD(&typehead, tp, link);
	return tp;
}


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#include	<lbl.h>
#include	"externs.h"

char	delimiter = '@';
char	macroname[] = "L=";
char	tempname[] = "/tmp/lblXXXXXX";
char   *def_format = STDFORM;
char   *progname = "lbl";
char   *filename = NULL;
int	lflag = 0;
int	sflag = 0;
FILE   *tempfile = NULL;
long	fileline;
struct typehead_t typehead = LIST_HEAD_INITIALIZER(typehead);


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#include        <string.h>
#include	<lbl.h>
#include        "build.h"
#include        "find.h"

extern LIST_HEAD(, Type) typehead;

Type   *
findtype(char *name, int create)
{
	rg Type *tp;

	LIST_FOREACH(tp, &typehead, link)
	if (strcmp(name, tp->t_name) == 0)
		return tp;
	if (create)
		return addtype(name);
	return NULL;
}

Label  *
typeFindlabel(Type *tp, char *name)
{
	rg Label *lp;

	if (tp == NULL)
		return NULL;
	LIST_FOREACH(lp, &(tp->labelhead), link)
	if (strcmp(name, lp->l_name) == 0)
		return lp;
	return NULL;
}


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

/* keyword.c:
 *	look up a command keyword (by sequential search).
 */

#include        <string.h>
#include	<lbl.h>
#include	"keyword.h"

extern int a_delimiter();
extern int a_format();
extern int a_last();

static Keyword	keytable[] = {
	{"delimiter", a_delimiter, 2, 2},
	{"format", a_format, 3, 3},
	{"last", a_last, 3, 22}
};

#define NKEYS	(sizeof(keytable) / sizeof(Keyword))

Keyword *
findkeyword(char *word)
{
	unsigned long indx;

	for (indx = 0; indx < NKEYS; indx++)
		if (strcmp(word, keytable[indx].k_name) == 0)
			return keytable + indx;
	return NULL;
}


code:
.\" (C) C.D.F. Miller, Heriot-Watt University, March 1984
.\"
.Dd $Mdocdate$
.Dt LBL 1
.Os
.Sh NAME
.Nm lbl
.Nd preprocess symbolic labels for troff(1) text
.Sh SYNOPSIS
.Nm lbl
.Op Fl d Ar delim
.Op Fl m Ar macro
.Op Fl l
.Op Fl s
.Op Ar
.Sh DESCRIPTION
.Nm
is a preprocessor for
.Xr troff 1
which constructs serial numbers automatically for tables, equations,
sections, etc., and allows them to be referred to by symbolic names.
It avoids the need for a user to keep track of such numberings, and
to alter all cross-references whenever a labelled
object is introduced or deleted.
.Pp
As many independent sequences of labels as required can be maintained;
each sequence is given a symbolic name, known as the
.Em label-type .
Within each label-type, objects are referred to by symbolic names known
as
.Em labels ;
the same label may be used in several different label-types without problems.
Names for labels and label-types may be arbitrary sequences of alphanumeric
characters.
In addition, the special label-name ``*'' may be used as an anonymous label,
used to increment a level counter without generating a label (e.g. to keep
table numbers in step with the corresponding section numbers).
.Pp
.Nm
reads as input the concatenation of all specified
.Ar file
arguments (interpreting ``-'' to mean the standard input);
if no
.Ar file
argument is given,
.Nm
reads the standard input.
The input consists of arbitrary text (normally input for
.Xr troff 1 ,
.Xr eqn 1 ,
and the rest of that family) interspersed with
.Em label-definitions
and
.Em label-references ,
together with a few other sorts of definition described below.
.Pp
.Ss Label Definitions
A label-definition
defines the value of a symbolic label.
All such definitions are introduced by a line beginning with
a special
.Em troff
macro; the default macro is
.Ic .L=
but this can be overridden in the command line.
The label-definition macro is followed by a label-type, a numeric level,
and a label-name;
a value is given to the label by incrementing the current count for
the label-type at the given level (similar to the generation of
numbered headings by such macros as
.Ic .NH
in the
.Xr groff_ms 7
package,
.Ic .H
in
.Xr groff_mm 7
and
.Ic .sh
in
.Xr groff_me 7 ).
Unless otherwise specified, the first count generated at a given level will
be 1; when a count is incremented, all deeper counts are reset to 0.
.Pp
There are several other uses of the definition-macro:
.Bl -tag -width Ds
.It .L= delimiter off
inhibits all subsequent translation of label-references;
.It .L= delimiter \fIchar\fR
resets the delimiter introducing label-references to
.Em char
(see below);
.It .L= format \fIlabel-type pattern\fR
resets the printing format for a label reference (see below);
.It .L= last \fIlabel-type count ...\fR
resets the counts for
.Em label-type
as though the last label generated
had had the specified counts for its successive levels (missing counts
all being taken as 0).
All counts must be arabic numerals, regardless of the print format being
used for the label-type.
.El
.Pp
All label-definition, etc., lines are copied unchanged to the output.
.Ss Label References
A label-reference is introduced and terminated by a
.Em delimiter
character (default ``@'');
the opening delimiter is followed by a label-type and a label-name;
the entire reference is replaced by the value of the specified label,
printed according to the format specified for the label-type (see below).
.Ss Print Formats
A print format consists of a literal string containing escape-sequences
for which successive values of the level counts of a label are substituted.
The format is copied only as far as the escape sequence for the last level
of the label being printed, and it is an error for there to be too few
escape sequences.
The sequences are
.Bl -tag -width Ds
.It %1
print in arabic numerals, no non-significant leading zeros;
.It %0
print as for ``%1'', but subtracting 1 so that counts start from 0 rather than
1.
.It %i
print in lower-case roman numerals;
.It \&%I
print in upper-case roman;
.It %a
print as a lower-case letter (starting at ``a'');
.It \&%A
print as an upper-case letter.
``%'' followed by any other character prints as that character.
.El
.Pp
The default format is to print all levels in arabic, separated by periods
(i.e. "%1.%1.%1.%1\ ...").
.Ss Command Line Options
.Bl -tag -width Ds
.It -d\fIdelim\fR
.Em delim
is a single character to be used in place of ``@''
as the label-reference delimiter.
.It -m\fImacro\fR
.Em macro
is a 2-character macro-name to be used in place of ``.L='' as the
label-definition macro.
.It -l
causes a listing of all label-definitions to be written to the standard
error stream.
.It -s
suppresses all output except error messages and the listing generated by
the \fI-l\fR option, which is automatically turned on.
.El
.Sh EXAMPLE
Input
.Bd -literal -offset indent -compact
\&.L= sec 1 intro
\&.L= format sec %A.%1.%1.%1.%1
\&.L= last table 3 1
This is Section @sec intro@; it is followed by Section
@sec next@ which contains Section @sec subsection@.
\&.L= table 2 only.
The only table used is Table @table only@.
\&.L= sec 1 next
\&.L= sec 2 subsection
.Ed
Output
.Bd -literal -offset indent -compact
\&.L= sec 1 intro
\&.L= format sec %A.%1.%1.%1.%1
\&.L= last table 3 1
This is Section A; it is followed by Section
B which contains Section B.1.
\&.L= table 2 only.
The only table used is Table 3.2.
\&.L= sec 1 next
\&.L= sec 2 subsection
.Ed
.Sh BUGS
With the
.Fl s
option, errors such as reference to an undefined label, which can only
be detected on a second scan of the input, are not reported.
.Sh FILES
.Pa /tmp/lbl*
- temporary storage
.Sh SEE ALSO
.Xr troff 1 ,
.Xr nroff 1 ,
.Xr eqn 1 ,
.Xr refer 1 ,
.Xr tbl 1


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

/* lbl.c:
 *	main program - decode switches and arguments
 *	and process each input file.
 * usage:
 *	lbl [ -d<char> ] [ -m<macro> ] [ -l ] [ -s ]
 *		-d	<char> delimits in-line occurrences of a tag
 *		-m	<macro> defines a tag value
 *		-l	list label definitions
 *		-s	suppress nroff output (implies -l)
 * defaults:
 *	-d@ -mL=
 */

#include        <bsd/string.h>
#include        <unistd.h>
#include        <stdlib.h>
#include        <err.h>
#include        <sysexits.h>
#include        <limits.h>
#include        <assert.h>
#include        "lbl.h"
#include        "signals.h"
#include        "scan.h"
#include        "rescan.h"
#include	"list.h"
#include	"externs.h"

int	decode(int argc, char *argv[]);

/** Size of macroname[] */
static const int MACRONAME_MAX = 3;

int
main(int argc, char *argv[])
{
	int nflags;
	int anydone = 0;
	static char standard[] = "standard input";

	if (argc != 0) {
		argc--;
		progname = *(argv++);
	}
	nflags = decode(argc, argv);
	argc -= nflags;
	argv += nflags;

	/*
	 * Remaining arguments must be filenames; treat "-" specially as
	 * standard input. If no arguments, use atandard input.
	 */

	/*
	 * Pass 1: copy to temp file, building table of tags
	 */
	if (!sflag) {
		mktemp(tempname);
		if ((tempfile = fopen(tempname, "w")) == NULL)
			errx(EX_CANTCREAT, "%scannot make temporary file",
			     maybe_loc());
		setsignals();
	}
	if (argc == 0) {
		scan(standard, stdin);
		anydone = 1;
	}
	else {
		while (argc != 0) {
			if (strcmp(argv[0], "-") == 0) {
				anydone = 1;
				scan(standard, stdin);
			}
			else {
				FILE *input;

				if ((input = fopen(argv[0], "r")) == NULL)
					warnx("%scannot open %s", maybe_loc(),
					      argv[0]);
				else {
					anydone = 1;
					scan(argv[0], input);
					fclose(input);
				}
			}
			argc--;
			argv++;
		}
	}
	if (!sflag)
		fclose(tempfile);
	if (lflag)
		listdefs();

	/*
	 * Pass 2: translate tags
	 */

	if (!anydone) {
		unlink(tempname);
		exit(3);
	}
	if (!sflag) {
		if ((tempfile = fopen(tempname, "r")) == NULL)
			errx(EX_NOINPUT,
			     "%stemporary file vanished before rescan!",
			     maybe_loc());
		rescan();
		unlink(tempname);
	}
	exit(0);
}

/* Decode argument list */
int
decode(int argc, char *argv[])
{
	int ch;
	int nflags = 0;

	while ((ch = getopt(argc, argv, ":d:m:ls")) != -1) {
		switch (ch) {
		case 'd':
			if (strlen(optarg) != 1)
				errx(EX_USAGE,
				     "%sdelimiter must be 1 character",
				     maybe_loc());
			delimiter = optarg[0];
			break;
		case 'm':
			if (strlen(optarg) != 2)
				errx(EX_USAGE,
				     "%smacro name must be 2 characters",
				     maybe_loc());
			strlcpy(macroname, optarg, MACRONAME_MAX);
			break;
		case 'l':
			lflag = 1;
			break;
		case 's':
			lflag = sflag = 1;
			break;
		case ':':
			assert(optopt == 'd' || optopt == 'm');
			switch (optopt) {
			case 'd':
				errx(EX_USAGE, "%smissing delimiter after -d",
				     maybe_loc());
			/*
			 * NOTREACHED
			 */
			case 'm':
				errx(EX_USAGE,
				     "%smacro name must be 2 characters",
				     maybe_loc());
				/*
				 * NOTREACHED
				 */
			}
			break;
		case '?':
		default:
			errx(EX_USAGE, "%sunknown flag '%c'", maybe_loc(),
			     optopt);
			/*
			 * NOTREACHED
			 */
		}
		nflags++;
	}
	return nflags;
}

char   *
maybe_loc(void)
{
	static char result[LINE_MAX];

	if (filename != NULL)
		snprintf(result, LINE_MAX, "%s, line %ld - ", filename,
		         fileline);
	else
		result[0] = '\x00';
	return result;
}


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#include	<lbl.h>
#include        "list.h"
#include	"printl.h"

void	listtype(Type *);

extern char *def_format;
extern LIST_HEAD(, Type) typehead;

void
listdefs()
{
	Type *tp;

	if (LIST_EMPTY(&typehead)) {
		fprintf(stderr, "No labels defined\n");
		return;
	}
	LIST_FOREACH(tp, &typehead, link)
	listtype(tp);
}

void
listtype(Type *tp)
{
	Label *lp;

	fprintf(stderr, "*** Type %s: format %s\n", tp->t_name,
	        tp->t_format == def_format ? "default" : tp->t_format);
	if (LIST_EMPTY(&(tp->labelhead))) {
		fprintf(stderr, "(No labels defined\n)");
		return;
	}
	LIST_FOREACH(lp, &(tp->labelhead), link) {
		fprintf(stderr, "%-16.16s %-14.14s %-6ld ",
		        lp->l_name,
		        lp->l_file,
		        lp->l_line);
		labelPrint(lp, stderr);
		putc('\n', stderr);
	}
	putc('\n', stderr);
}


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#include        <err.h>
#include	<lbl.h>
#include        "printl.h"

void	printr(char, int, FILE *);
static void printd(int, FILE *);
static void printa(char, int, FILE *);
static void auxprinta(char, int, FILE *);

void
labelPrint(Label *lab, FILE *out)
{
	rg format f = lab->l_type->t_format;
	rg int i;

	for (i = 0; i <= lab->l_bottom; i++) {
		while (*f) {
			if (*f != '%')
				putc(*f, out);
			else
				switch (*++f) {
				default:
					putc(*f, out);
					break;
				case '\0':
					putc('%', out);
					break;
				case '0':
					printd(lab->l_levels[i] - 1, out);
					f++;
					goto loopend;
				case '1':
					printd(lab->l_levels[i], out);
					f++;
					goto loopend;
				case 'i':
				case 'I':
					printr(*f, lab->l_levels[i],
					       out);
					f++;
					goto loopend;
				case 'a':
				case 'A':
					printa(*f, lab->l_levels[i],
					       out);
					f++;
					goto loopend;
				}
			f++;
		}
		warnx("%sformat too short to print label %s", maybe_loc(), lab->l_name);
		break;
loopend:
		continue;
	}
}

/* Print Decimal */
void
printd(int n, FILE *out)
{
	fprintf(out, "%u", n);
}

/* Print Alphabetic
 *	actually base 26 (digits a-z), displaced by 1!
 *	a is either "a" or "A" for upper or lower case.
 */
void
printa(char a, int n, FILE *out)
{
	if (n == 0)
		putc('0', out);
	else
		auxprinta(a, n - 1, out);
}

void
auxprinta(char a, int n, FILE *out)
{
	if (n > 25)
		auxprinta(a, n / 26 - 1, out);
	putc((int)a + n % 26, out);
}

/* Print Roman
 *	a is either "a" or "A" for upper or lower case.
 */
void
printr(char a, int n, FILE *out)
{
	if (n == 0) {
		putc('0', out);
		return;
	}
	if (n >= 50000) {
		putc('!', out);
		return;
	}
	while (n >= 10000)
		putc(a + ('Z' - 'I'), out), n -= 10000;
	if (n >= 9000)
		putc(a + ('M' - 'I'), out), putc(a + ('Z' - 'I'), out), n -= 9000;
	if (n >= 5000)
		putc(a + ('W' - 'I'), out), n -= 5000;
	if (n >= 4000)
		putc(a + ('M' - 'I'), out), putc(a + ('W' - 'I'), out), n -= 4000;
	while (n >= 1000)
		putc(a + ('M' - 'I'), out), n -= 1000;
	if (n >= 900)
		putc(a + ('C' - 'I'), out), putc(a + ('M' - 'I'), out), n -= 900;
	if (n >= 500)
		putc(a + ('D' - 'I'), out), n -= 500;
	if (n >= 400)
		putc(a + ('C' - 'I'), out), putc(a + ('D' - 'I'), out), n -= 400;
	while (n >= 100)
		putc(a + ('C' - 'I'), out), n -= 100;
	if (n >= 90)
		putc(a + ('X' - 'I'), out), putc(a + ('C' - 'I'), out), n -= 90;
	if (n >= 50)
		putc(a + ('L' - 'I'), out), n -= 50;
	if (n >= 40)
		putc(a + ('X' - 'I'), out), putc(a + ('L' - 'I'), out), n -= 40;
	while (n >= 10)
		putc(a + ('X' - 'I'), out), n -= 10;
	if (n >= 9)
		putc(a + ('I' - 'I'), out), putc(a + ('X' - 'I'), out), n -= 9;
	if (n >= 5)
		putc(a + ('V' - 'I'), out), n -= 5;
	if (n >= 4)
		putc(a + ('I' - 'I'), out), putc(a + ('V' - 'I'), out), n -= 4;
	while (n >= 1)
		putc(a + ('I' - 'I'), out), n -= 1;
}


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#include	<lbl.h>
#include	<ctype.h>
#include        <string.h>
#include        <err.h>
#include        "find.h"
#include	"printl.h"
#include        "rescan.h"
#include	"externs.h"

void    doreplace(void);
static void domagic(void);
static void getword(rg char *buffer);

static int translate = 1;

void
rescan()
{
	rg int  c;

	/*
	 * Keep tempfile, delimiter and translate flag in registers to speed
	 * up this routine; the last two need to be reloaded after and only
	 * after calls to domagic()
	 */
	rg FILE *tf = tempfile;
	rg int  dlm = delimiter;
	rg int  tr = translate;

	while ((c = getc(tf)) != EOF) {
		if (c == MAGIC1) {
			if ((c = getc(tf)) == MAGIC2) {
				domagic();
				dlm = delimiter;
				tr = translate;
				continue;
			}
			else
				ungetc(c, tf);
		}
		if (c == dlm && tr) {
			doreplace();
			continue;
		}
		if (c == '\n')
			fileline++;
		putchar(c);
	}
}

void
domagic()
{
	int     c;
	static char buffer[BUFSIZ];

	switch (getc(tempfile)) {
	case M_DELIM:
		c = getc(tempfile);
		if (c == '\n')
			translate = 0;
		else {
			translate = 1;
			delimiter = (char) c;
			getc(tempfile);
		}
		break;
	case M_FILE:
		if (fgets(buffer, BUFSIZ, tempfile) == NULL)
			buffer[0] = '\0';
		else
			buffer[strlen(buffer) - 1] = '\0';
		filename = buffer;
		fileline = 1;
		break;
	}
}

void
doreplace()
{
	rg int  c;
	char    typename[BUFSIZ];
	char    labelname[BUFSIZ];
	rg Type *tp;
	rg Label *lp;

	/*
	 * <delimiter><delimiter> gives <delimiter> as output
	 */
	if ((c = getc(tempfile)) == delimiter) {
		putchar(delimiter);
		return;
	}
	ungetc(c, tempfile);
	getword(typename);
	getword(labelname);
	if ((c = getc(tempfile)) != delimiter) {
		warnx("%smalformed label reference", maybe_loc());
		while (c != delimiter) {
			if (c == EOF || c == '\n')
				break;
			c = getc(tempfile);
		}
		return;
	}
	if ((tp = findtype(typename, 0)) == NULL) {
		warnx("%sundefined type '%s'", maybe_loc(), typename);
		printf("<<%s %s>>", typename, labelname);
		return;
	}
	if ((lp = typeFindlabel(tp, labelname)) == NULL) {
		warnx("%sundefined label '%s'", maybe_loc(), labelname);
		printf("<<%s %s>>", typename, labelname);
		return;
	}
	labelPrint(lp, stdout);
}

void
getword(char *buffer)
{
	rg int  c;
	rg int  dlm = delimiter;
	rg FILE *tf = tempfile;

	c = getc(tf);
	while (isspace(c))
		c = getc(tf);
	while (c != dlm && !isspace(c) && c != '\n' && c != EOF) {
		*buffer++ = (char) c;
		c = getc(tf);
	}
	*buffer = '\0';
	if (!isspace(c) && c != EOF)
		ungetc(c, tf);
}


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#include	<lbl.h>
#include	<ctype.h>
#include        <bsd/string.h>
#include        <err.h>
#include        "build.h"
#include        "scan.h"

void	process(char *);

extern char macroname[];
extern char *filename;
extern int sflag;
extern FILE *tempfile;
extern long fileline;

void
scan(char *fname, FILE *f)
{
	char line[BUFSIZ];
	rg int line_complete = 1;

	/*
	 * Mark start of file in temp file
	 */
	if (!sflag)
		fprintf(tempfile, "%c%c%c%s\n", MAGIC1, MAGIC2, M_FILE, fname);
	filename = fname;
	fileline = 0L;

	while (fgets(line, BUFSIZ, f) != NULL) {
		if (!sflag)
			fputs(line, tempfile);
		if (line_complete) {
			fileline++;
			line_complete = line[strlen(line) - 1] == '\n';
			if (line[0] == '.' && line[1] == macroname[0] &&
			          line[2] == macroname[1]) {
				if (line_complete)
					process(line + 3);
				else
					warnx("%s.%s line too long",
					      maybe_loc(), macroname);
			}
		}
		else
			line_complete = line[strlen(line) - 1] == '\n';
	}
}

void
process(char *line)
{
	us int split();
	Keyword *findkeyword();
	us int nargs;
	char *argvec[NLEVELS + 2];
	Keyword *key;

	if ((nargs = split(line, argvec)) == 0) {
		warnx("%sempty .%s line", maybe_loc(), macroname);
		return;
	}
	/*
	 * Check for and act upon reserved words
	 */
	if ((key = findkeyword(argvec[0])) != NULL) {
		if (nargs < key->k_minargs) {
			warnx("%s%s - too few arguments", maybe_loc(),
			      key->k_name);
			return;
		}
		if (nargs > key->k_maxargs) {
			warnx("%s%s - too many arguments", maybe_loc(),
			      key->k_name);
			return;
		}
		(*(key->k_action)) (nargs, argvec);
		return;
	}
	/*
	 * Process a label definition
	 */
	if (nargs != 3) {
		warnx("%stoo %s arguments in label definition",
		      maybe_loc(), nargs < 3 ? "few" : "many");
		return;
	}
	addlbl(argvec[0], argvec[1], argvec[2]);
}

/* split - split a line into arguments
 *	N.B. successive calls of this invalidate earlier calls;
 *	argvec is set to point to strings within the
 *	static local buffer "copybuf" which is overwritten on
 *	each call.
 */
us int	split(char *line, char *argvec[]);
us int
split(char *line, char *argvec[])
{
	us int nargs = 0;
	static char copybuf[BUFSIZ];
	char *ln = copybuf;

	strlcpy(ln, line, BUFSIZ);
	while (*ln) {
		while (isspace(*ln))
			ln++;
		if (*ln) {
			if (nargs > 2 + NLEVELS) {
				warnx("%ssurplus arguments to .%s ignored",
				      maybe_loc(), macroname);
				break;
			}
			argvec[nargs++] = ln;
			while (*ln && !isspace(*ln))
				ln++;
			if (*ln)
				*ln++ = '\0';
		}
	}
	if (nargs > 2 + NLEVELS)
		nargs = 2 + NLEVELS;
	return nargs;
}


code:
#+SEQ_TODO: TODO(t) INPROGRESS(i) WAITING(w@) | DONE(d) CANCELED(c@)
#+TAGS: fix(f) verify(v) noexport(n) template(t) usetemplate(u)
#+EXPORT_EXCLUDE_TAGS: noexport

#+LANGUAGE: en
#+OPTIONS: toc:4 h:4 html-postamble:auto html-preamble:t tex:t

#+HTML_CONTAINER: div
#+HTML_DOCTYPE: <!DOCTYPE html5>
#+HTML_HEAD: <link href="http://fonts.googleapis.com/css?family=Droid+Sans:400,700|Inconsolata" rel="stylesheet" type="text/css" />

#+AUTHOR: Darren Bane
#+EMAIL: dbane@sdf-eu.org


code:
/* (C) C.D.F. Miller, Heriot-Watt University, March 1984
 *
 *	Permission is hereby given to reproduce or modify this
 *	software freely, provided that this notice be retained,
 *	and that no use be made of the software for commercial
 *	purposes without the express written permission of the
 *	author.
 */

#include	<lbl.h>
#include	<signal.h>
#include        <unistd.h>
#include        "signals.h"

typedef void (*sig_t) (int);

extern char tempname[];

static void onsignal(int unused) __attribute__((noreturn));
static void
onsignal(int unused)
{
	(void)unused;
	unlink(tempname);
	_exit(2);
}

void
setsignals()
{
	if (signal(SIGINT, onsignal) == SIG_IGN)
		signal(SIGINT, SIG_IGN);
	if (signal(SIGQUIT, onsignal) == SIG_IGN)
		signal(SIGQUIT, SIG_IGN);
	if (signal(SIGHUP, onsignal) == SIG_IGN)
		signal(SIGHUP, SIG_IGN);
	if (signal(SIGTERM, onsignal) == SIG_IGN)
		signal(SIGTERM, SIG_IGN);
}


code:
FROM alpine:3.10
RUN apk add sudo sassc inotify-tools psmisc build-base ruby ruby-dev libxml2 libxml2-dev git libgit2 libgit2-dev sassc inotify-tools psmisc build-base bash the_silver_searcher sed moreutils ruby-rdoc rsync ruby-etc ruby-rouge ruby-webrick ruby-nokogiri
RUN gem install redcarpet
WORKDIR /site
COPY . /site

path: Dockerfile

license: wtfpl


code:
.PHONY: build serve
BUILD_COMMAND=./build
GITBUILD_COMMAND=./gitbuild
WEBSERVER_COMMAND=mkdir -p dist && cd dist && ruby -run -ehttpd . -p8000
WEBSERVER_KILL_COMMAND=killall ruby

USER_AT_SERVER=$(shell cat ~/.user_at_server)

default:
	$(MAKE) -j2 build serve

build:
	$(BUILD_COMMAND) || true
	$(WEBSERVER_KILL_COMMAND) || true
	@inotifywait -e modify -r src || true
	$(MAKE) build

oneshot:
	echo "Oneshot building"
	$(BUILD_COMMAND)

serve:
	$(WEBSERVER_COMMAND) || true
	$(MAKE) serve

deploy:
	@rsync --exclude trigger -e "ssh -o StrictHostKeyChecking=no" -avh --delete dist/ $(USER_AT_SERVER):/home/public/milesalan.com

deps:
	gem install redcarpet rouge nokogiri rouge webrick

docker_image:
	docker build -t ma .

docker_dev: docker_image
	docker run -v $(shell pwd)/dist:/site/dist -v $(shell pwd)/src:/site/src -v $(shell pwd)/gitsrc:/site/gitsrc --net host -it --rm --name ma ma make default

docker_oneshot: docker_image
	docker run -v $(shell pwd)/dist:/site/dist -v $(shell pwd)/src:/site/src -v $(shell pwd)/gitsrc:/site/gitsrc --net host -it --rm --name ma ma make oneshot

docker_shell:
	docker run -v $(shell pwd)/dist:/site/dist -v $(shell pwd)/src:/site/src -v $(shell pwd)/gitsrc:/site/gitsrc --net host -it --rm --name ma ma sh

path: Makefile


code:
#!/usr/bin/env ruby
require 'fileutils';
require 'nokogiri'
require 'redcarpet'
require 'rouge'
require 'rouge/plugins/redcarpet'
require 'ostruct'
require 'erb'
require 'date'
require 'yaml'
class HTML < Redcarpet::Render::HTML; include Rouge::Plugins::Redcarpet; end
class String; def color(code); "\e[#{code}m#{self}\e[0m"; end; end;
class ErbalT < OpenStruct; def render(template); ERB.new(template).result(binding); end; end

class H#elpers
  def self.datefmt(d);      d.strftime("%Y / %b %e"); end
  def self.monthfmt_let(d); d.strftime("%Y / %b"); end
  def self.monthfmt_num(d); d.strftime("%Y-%m"); end
  def self.nav_active(l_sect, c_sect); l_sect == c_sect ? "active": ""; end

  def self.run_template(template, params)
    #ERB.new(part).result_with_hash(parms)
    et = ErbalT.new(params)
    et.render(template)
  end

  def self.get_partials()
    return $partials if $partials
    $partials = Dir["./src/tmpl/*.erb.html"]
      .select { |p| p.match(/partial_.+/) }
      .map { |p|
        partial_name = p.split("/")[-1].split(".")[0].gsub("partial_", "")
        tmpl = File.read(p)
        [partial_name.to_sym, tmpl]
      }.to_h
  end
  def self.partial(partial, context = {})
    part, parms = H.get_partials()[partial], U.symbolify_keys(context)
    self.run_template(part, parms)
  end
end

class U#tilities
  def self.mkdirp_for_file(file); d = File.dirname(file); unless File.directory?(d); FileUtils.mkdir_p(d); end; end
  def self.symbolify_keys(hash); hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}; end
  def self.read_file_yaml_and_md(file)
    sep_c, fem_content, md_content = 0, [], []
    File.readlines(file).each do |l|
      if l.strip == "---" then; sep_c += 1; next; end
      (sep_c > 1 ? md_content : fem_content).push(l)
    end
    fem_content, md_content = fem_content.join("\n").strip, md_content.join("").strip
    markdown = Redcarpet::Markdown.new(HTML.new, :fenced_code_blocks => true, :tables => true )
    y, m = YAML.load(
      fem_content,
      permitted_classes: [Date, Time]
    ), markdown.render(md_content)
    begin
      m = Nokogiri::HTML(m)
      m.search("//a").each do |a|
        a['target'] = '_blank' if (a.attribute("href").content =~ /^http(s?)/)
      end
    rescue; puts "Error Nokogiri processing #{file}"; end;
    { :md => m, :yaml => y, :file => file }
  end
  def self.compile_template(parms)
    if parms[:source_content_file]
      yaml, md = U.read_file_yaml_and_md(parms[:source_content_file]).values_at(:yaml, :md)
      parms[:content] = md; parms.merge!(yaml)
    end
    parms = U.symbolify_keys(parms)
    U.mkdirp_for_file(parms[:dest_file])
    File.write(
      parms[:dest_file], H.run_template(File.read(parms[:template_file]), parms)
    )
  end
  def self.get_listing_items_for_section(section)
    listing_items = Dir["./src/#{section}/*.md"].map { |f|
      U.read_file_yaml_and_md(f)
    }.map { |f|
      if f[:yaml] && f[:yaml]['url'] != nil
        f[:url] = f[:yaml]['url']
      else
        f[:target] = "_blank"
        f[:url] = U.post_raw_filename_to_target_filename(f[:file], f[:title], true); f
      end
      f
    }.sort { |a,b|
      if b[:yaml]['sort_index'] ||  b[:yaml]['sort_index']
        a[:yaml]['sort_index'] <=> b[:yaml]['sort_index']
      else
        b[:yaml]['date'] <=> a[:yaml]['date']
      end
    }
  end
  def self.post_raw_filename_to_target_filename(f, section, as_url_link)
    "#{section}/" + f.downcase
      .gsub(/\d{4}-\d{2}-\d{2}-/, '')
      .gsub("./src/#{section}", '')
      .gsub(/\.md$/, as_url_link ? '' : '/index.html')
  end
end

class Task
  SECTIONS = %w[notes projects]

  def self.build_about_page
    U.compile_template(
      section: "about",
      dest_file: 'dist/' + "index.html",
      template_file: "./src/tmpl/layout_about.erb.html",
      source_content_file: "./src/about.md"
    )
  end
  def self.build_post_pages
    SECTIONS.each do |section|
      Dir["./src/#{section}/*.md"].each do |f|
        U.compile_template(
          section: section,
          dest_file: 'dist/' + U.post_raw_filename_to_target_filename(f, section, false),
          template_file: "./src/tmpl/layout_post.erb.html",
          source_content_file: f
        )
      end
    end
  end
  def self.build_listing_pages
    notes_section = "notes"
    notes_listing_items = U.get_listing_items_for_section(notes_section)
    dest_fold = "#{notes_section}/"
    U.compile_template(
      section: notes_section,
      title: notes_section.capitalize,
      dest_file: "./dist/#{notes_section}/index.html",
      template_file: "./src/tmpl/layout_listing_page.erb.html",
      listing_items: notes_listing_items
    )

    project_listing_items = U.get_listing_items_for_section("projects").reject { |item| item[:yaml]["archived"] }
    projects_section = "projects"
    dest_fold = "#{projects_section}/"
    U.compile_template(
      section: projects_section,
      title: projects_section.capitalize,
      dest_file: "./dist/#{projects_section}/index.html",
      template_file: "./src/tmpl/layout_listing_page.erb.html",
      listing_items: project_listing_items
    )

    archived_section = "archived"
    archived_listing_items = U.get_listing_items_for_section("projects").select { |item| item[:yaml]["archived"] }
    dest_fold = "#{archived_section}/"
    U.compile_template(
      section: archived_section,
      title: archived_section.capitalize,
      dest_file: "./dist/#{archived_section}/index.html",
      template_file: "./src/tmpl/layout_listing_page.erb.html",
      listing_items: archived_listing_items
    )
  end
  def self.clean; %x[rm -rf ./dist]; end
  def self.build_sass_to_css; %x[mkdir -p dist && sassc src/tmpl/scss/all.scss > dist/all.css]; end
  def self.copy_assets; FileUtils.cp_r "./src/assets/.", './dist/assets'; end
  def self.copy_root; FileUtils.cp_r "./src/assets/_root/.", './dist/'; end

  ############################################################################
  def self.main
    puts "======"
    %w[
      clean build_sass_to_css copy_assets copy_root build_post_pages
      build_listing_pages build_about_page
    ].each {|fn|
      print fn.color(34);
      t1 = Time.now; Task.send(fn); t2 = Time.now; delta = t2 - t1;
      print " executed in #{(delta.to_s + "s").color(36)}\n";
    }
    puts "======"
  end
end

Task.main

path: build


code:
---
title: About
---

Hi my name is Miles, I go by mla or mil in some channels; this is my
personal website. You can refer to [projects](/projects) to see some
of my recent projects and [notes](/notes) hosts an infrequently updated, but
long-running, blog. See [srht](http://git.sr.ht/~mil/) for git repos &
releases.

What am I working on now?:

- [Mepo](http://sr.ht/~mil/mepo): is a minimalist OSM map application for mobile and
  desktop Linux. It's available on pmOS, Alpine, Arch, NixOS, and as a Flatpak.
  It was funded through a NLnet grant between 2021-2022, and I am continuing
  to work on it self-funded in 2023.
- [Tlml](http://sr.ht/~mil/tlml): is a set of utilities designed to make
  standard desktop Linux tiling applications accessible on mobile Linux.
  It's what I use on my Pinephone and pmOS devices. The eventual plan is to
  have this available as a [pmOS](http://postmarketos.org) UI.

If you'd like to write to me an email about anything, then please do!
My email is [m at milesalan dot com](mailto:m at milesalan dot com)


code:
http://userbound.com/* http://milesalan.com/:splat 301!
http://www.userbound.com/* http://milesalan.com/:splat 301!
https://userbound.com/* https://milesalan.com/:splat 301!
https://www.userbound.com/* https://milesalan.com/:splat 301!

/home       /
/blog       /
/notes      /
/interfaces /projects

/blog/Host-Specific-i3-Config-Variables               /notes/host-specific-i3-config-variables
/blog/Trackman-Marble-with-xset                       /notes/trackman-marble-with-xset
/blog/Monome-via-Serial-and-Go                        /notes/monome-via-serial-and-go
/blog/Garden-with-Clojurescript                       /notes/garden-with-clojurescript
/blog/A-Map-Function-for-Sass                         /notes/a-map-function-for-sass
/blog/Pango-Markup-in-i3bar                           /notes/pango-markup-in-i3bar
/blog/Gulp-for-Sitebuilds                             /notes/gulp-for-sitebuilds
/blog/Graphical-REPLs                                 /notes/graphical-repls
/blog/Restore-Rockbox-Root-on-Clip+                   /notes/restore-rockbox-root-on-clip+
/blog/A-Simpler-TODO-List                             /notes/a-simpler-todo-list
/blog/Autojump                                        /notes/autojump
/blog/My-New-Alarm-Clock                              /notes/my-new-alarm-clock
/blog/Hackerschool                                    /notes/hackerschool
/blog/Mplayers-FIFO                                   /notes/mplayers-fifo
/blog/Extending-Io-Objects                            /notes/extending-io-objects
/blog/Linux-on-Macbooks                               /notes/linux-on-macbooks
/blog/Bar-Aint-Recursive                              /notes/bar-aint-recursive
/blog/Incremental-Search-In-Bash-And-Zsh              /notes/incremental-search-in-bash-and-zsh
/blog/ii-a-better-way-to-irc                          /notes/ii-a-better-way-to-irc
/blog/Window-Resizing-In-Xlib                         /notes/window-resizing-in-xlib
/blog/Naming-Workspaces-in-i3                         /notes/naming-workspaces-in-i3
/blog/X-Cursors-And-Gumby                             /notes/x-cursors-and-gumby
/blog/Markview-Ruby-Socket-Plus-Redcarpet             /notes/markview-ruby-socket-plus-redcarpet
/blog/Secure-Hashed-Passwords-Using-Dwb-and-Pwdhash   /notes/secure-hashed-passwords-using-dwb-and-pwdhash
/blog/*                                               /notes

/works/*                     /projects/untitled_prints
/interfaces/Cream-Minitouch  /projects/cream-minitouch
/interfaces/Asciiw           /projects/asciiw
/interfaces/Foo-Wm           /projects/foo-wm
/interfaces/Keyboard-Tiler   /projects/keyboard-tiler
/interfaces/Markdown-Tree    /projects/markdown-tree
/interfaces/Mil-Edit         /projects/mil-edit
/interfaces/Mmvp.js          /projects/mmvp.js
/interfaces/Mustache.io      /projects/mustache.io
/interfaces/Cube             /projects/cube
/interfaces/Piply            /projects/piply
/interfaces/Blank-Thinkpad   /projects/blank-thinkpad
/interfaces/*                /projects/

/*  /


code:
==================================================================
https://keybase.io/mils
--------------------------------------------------------------------

I hereby claim:

  * I am an admin of https://milesalan.com
  * I am mils (https://keybase.io/mils) on keybase.
  * I have a public key with fingerprint 18FA E463 01B5 0948 C2B6  9C0F AAB6 DDF0 0081 40A2

To do so, I am signing this object:

{
  "body": {
    "key": {
      "eldest_kid": "01017c590d955f04a4a71807de1b3bc47d455adea2ebf3a51b5454942dc787c866440a",
      "fingerprint": "18fae46301b50948c2b69c0faab6ddf0008140a2",
      "host": "keybase.io",
      "key_id": "aab6ddf0008140a2",
      "kid": "01017c590d955f04a4a71807de1b3bc47d455adea2ebf3a51b5454942dc787c866440a",
      "uid": "a88dcac2e79563c36e4f537b75235a19",
      "username": "mils"
    },
    "service": {
      "hostname": "milesalan.com",
      "protocol": "https:"
    },
    "type": "web_service_binding",
    "version": 1
  },
  "ctime": 1581383346,
  "expire_in": 157680000,
  "prev": "ea80817d8ecbd38ef9e9e73bba10d6a4e91aface47be61db341e9ff1cf6cfa26",
  "seqno": 10,
  "tag": "signature"
}

which yields the signature:

-----BEGIN PGP MESSAGE-----

owGtUn9QFGUYPn7FRZpoSCkcysYJMki7t78vDJw0Giu5MU/MGT2+3f32buG4O273
0Bs4Rn45ClMJ4jAiTSMVlCgwhkwUTdmYl8RMzNEYDg06Iz/MgSYhh4EM27Wc/unP
/vrme9/ned7nfeY9sTJCpw+ToqVay3rL1bDvf+F0B7ct95chnFvwI+YypAg+eqBT
gLJiK5IExIygGIrRPMmiAkuSIkoAAtAYg9ICxDic4wlaIEgSCBCYICfigMQ4kiAJ
ljAJPM3QPENRBIECJAMRJZcdej1eyaWoshgjAkhQOKriUZZgeBNHsTwqAsBRgiCi
KMpgKs+kEh1uWWOo5jggw0zJrdbUj+2Rvf/A/8++fX/PYRiBB7wJ0ixJ4TxOQUIk
cZqjSRNOAozVgDL0ukAxVNHFklNGAhmIWimVeKiFqm3xbxfKwAlcmby7WCV6vG7F
zbudasuhKB7ZrFEVv0fDHoKc7R8VGye5BDVElVEKvbLkdiFmTEXyiqTJYiSD4QyO
E1QGAg97JC+0SRqCpClGjQfV5sBSVRICRg2LFhi4l+cEnIEiC1lI4xwHMFSgAAFZ
DIiAhwTNQQoTOJzAICuKGC9SvAhMFKItVuJyq+KqqgLsqqgs2V1A8XkhEog5Ho5H
6sL0OkN8cqSc3lKz5DempL/786rHlxcVrp2dLubJ2MeVo5ZVS+GHR3cvrTg90VT7
fm79y6M/OIvbqEFj0pm81t9mekZHKhcM+fjoBx3xjn2vrr/zrL6+44XE2fivlITo
F8PWGdh56dyOmNSyoeBAX2JKb7sUqPsyJ7uqJvDWnr4jI7lIo/AJt1B0s7XzylD2
F0nD0WcuT568VQYHnz52wdTvgcbJ6duyr1bIz7eWXdzUHWWEMWnXZ8YaO6PSiPo/
ckKm0413htbJ09d6D3xU6Uprafup+r7uuSZ7Xl1FFrK2+u1e+TuDo7ygPPdolHjf
GzFR4i8J9o9bJomlz0Md73WNPWh4BgyHlvGEnbemmw1x88/XWbjXigrcXeP62LUV
XaHCe409LbWy9amCsyUfJ1mj32yyHnp9TfCNfqVnpmqhPEKc2llhvHCxpp1v/NQX
W7/meEvyzfLFvECkbyph9RO7LZvHzv14oHprztRn3DtKXnB/qcOZuD9jz58NH+7d
NHQiFGf55u7sSmP12ZEVWzoyf72sv6Fsm9sQc+1KoL35RjA59O3iltXzs6m7Fpty
Hbg/c47pPs9O3d03F/IqcHLjgO/rTH29PTV9uNIsBPgQn/5K9exLKROFpzKUk81K
XFdi63le7FweaA88uC5e0rWY+wsHj9gePtwVXx4xc6qhrffS1qA9aN2eZXD2LflD
t++B3w9u7j5Wk4074/zW8R1c1dWsmsK/AA==
=Uuws
-----END PGP MESSAGE-----

And finally, I am proving ownership of this host by posting or
appending to this document.

View my publicly-auditable identity here: https://keybase.io/mils

==================================================================


code:
html, body, .root-contain {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.root-pager {
  position: absolute;
  bottom: 20px;
  right: 20px;
}
.root-pager a {
  display: inline-block;
  background: black;
  color: white;
  width: 20px;
  height: 20px;
  text-align: center;
  line-height: 20px;
  font-weight: bold;
  padding: 5px;
  text-decoration: none;
}
.root-pager a:hover {
  background: white;
  color: black;
}