.nr N 4 .nr O 1i .nr P 2 .nr W 6.5i .S 18 18 'ce Converting noweb markup to troff markup .sp 0.5i .S 12 15 .PH "'Converting noweb markup to troff markup''%'" .P Toroff is a noweb backend for formatting text with troff markup. Toroff was written by Phil Bewig ([[pbewig@netcom.com]]) during April, 1996, and contributed to Norman Ramsey's noweb literate programming system. Liam Quin ([[lee@sq.com]]) provided technical assistance with troff. Norman Ramsey made the code easier to port and made it conform with his idea of the noweb philosophy. Arnold Robbins ([[arnold@skeeve.com]]) modified it for use with GNU troff (hopefully improving portability even more), improved the flexibility of the noroff script, and added extra explanation about the gory troff details. .P The high-level view of toroff is that it comes in two parts. The first part is an ordinary back end, which produces troff augmented with special comments, using the [[.tm]] macro. The second part, [[noroff]], runs troff, with all of its pre-processors and post-processors. [[noroff]] also processes the comments in to a [[tags]] file. Moreover, [[noroff]] gathers from the tags file the cross-referencing information gathered on the previous formatting run. The trick that makes the whole thing work is that the troff markup added by toroff causes cross-referencing information to be written to standard error during the formatting run; the [[noroff]] script cleverly gathers this material into the [[tags]] file, which can be used as input to the next formatting run. As with LaTeX, the cross-referencing information is always one formatter run behind, and to get a consistent document you must keep running [[noroff]] until you reach a fixed point. ([[noroff]] ought to provide some help for this, to show when the tags file has changed, for example.) .HU "The shell wrapper" Toroff is implemented as an awk program in a shell wrapper, and uses a macro package to control the final formatting. In the following discussion, a sample macro package, suitable for use with either [[-mm]] or [[-ms]], is presented alongside the awk code which calls the macros. Users may wish to modify the sample macros to suit local preferences. Many do-nothing macros are provided as user exits so knowledgeable users can modify document formatting without changing the awk program. <>= #!/bin/sh LIB=|LIBDIR| AWK=nawk <> <> @ .P Processing arguments is simple. The only effect of the [[-delay]] flag is to control placement of the ``begin document'' document macro; the noroff script supplies the command to source the noroff macros as the very first line. This avoids groff warnings about undefined strings in the generated markup for the indexing and cross-references. <>= delay=0 noindex=0 for i do case $i in -delay) delay=1 ;; -noindex) noindex=1 ;; *) echo "This can't happen -- $i passed to toroff" 1>&2; exit 1;; esac done @ %def delay noindex .P Invoking the awk program is hard, because the program uses both single quotes and double quotes, so neither can be used to protect the other, and quoting each quote is ugly. The pragmatic solution is to copy the awk program into a temporary file, using a shell here-document. <>= awkfile=$($LIB/nwmktemp) || { echo "$0: Cannot create temporary file" >&2; exit 1; } trap 'rm -f $awkfile' 0 1 2 10 14 15 cat > $awkfile << 'EOF' <> EOF $AWK -f $awkfile -v delay=$delay noindex=$noindex @ %def awkfile tagsfile .HU "The main program" The overall structure of the awk program is simple. The [[tags]] file is processed in the [[BEGIN]] action, and the noweb source is processed by awk's pattern-action loop. <>= <> <> @ .HU "Beginning and ending the document" The [[-delay]] option allows the initial document chunk to be processed before invoking the ``begin document'' macro, so that necessary initialization may be performed. The only commands which are useful in such an initial chunk are bare troff commands. The delay is handled by special patterns for the initial document chunk. Because several noweb files can be processed in the same formatting run, there can be several document chunks numbered zero. The later ones are not treated specially, by the simple expedient of turning off [[delay]] after the first one. This code also handles the [[trailer]] at the end of the document. <>= /^@begin docs 0$/ { if (delay) next } /^@end docs 0$/ { if (delay) { <>; delay = 0; next } } /^@header m/ { if (!delay) { <> } } /^@trailer/ { print ".ENDOFDOCUMENT" } <>= .de ENDOFDOCUMENT .. <>= printf ".BEGINNINGOFDOCUMENT\n" <>= .de BEGINNINGOFDOCUMENT .. @ .HU "Labels, references, and section numbers" Here is the code to number code chunks in the page-number-and-letter style used by noweb. The current page number is always stored in troff's [[%]] number register, and we will arrange an auto-incrementing number register [[SECTIONLETTER]] which is reset at each top-of-page and is formatted as lower-case alphabetic characters using troff's [[.af]] request. The top-of-page trap is sprung one unit below the top of the page in order not to interfere with the top-of-page macros of [[-mm]] or [[-ms]]. <>= /^@xref label/ { lastxreflabel = $3 } /^@xref ref/ { lastxrefref = tag(substr($0, 11)) } @ %def lastxreflabel lastxrefref <>= .de RESETLETTER \" reset letter at top of page .nr SECTIONLETTER 0 1 \" count code sections on same page .af SECTIONLETTER a \" ... formatted as lower-case alpha .. .wh 1u RESETLETTER \" trap just below top of page @ .P Note that the link information on the previous chunk shows that this chunk uses the [[code]] variable, when in fact [[code]] only appears as text within a comment. This error is an artifact of the language independence of noweb. @ .HU "Beginning and ending chunks" Except for the initial document chunk, which was processed above, all beginnings and endings of documentation and code chunks are processed here. Variable [[code]] is [[0]] in text chunks, [[1]] in code chunks and [[2]] in quoted code. The argument to the [[.ENDCODECHUNK]] macro is the label which was in effect when the code chunk was started, which is stored in the variable [[lastdefnlabel]]. <>= /^@begin docs/ { printf ".BEGINDOCCHUNK\n" } /^@end docs/ { printf ".ENDDOCCHUNK\n" } /^@begin code/ { code = 1; printf ".BEGINCODECHUNK\n" } /^@end code/ { code = 0; printf ".ENDCODECHUNK %s\n", lastdefnlabel } @ %def code .P The macros related to document chunks are simple: [[.BEGINDOCCHUNK]] does nothing, and [[.ENDDOCCHUNK]] merely flushes any unprinted output. <>= .de BEGINDOCCHUNK .. .de ENDDOCCHUNK .br .. @ .P A code chunk uses a font, type size, leading, fill and adjust modes, indent and tab stops which are distinct from those in the documentation. These environment variables must be saved on entering a code chunk, appropriate values for a code chunk must be established, and the original values restored at the end of a code chunk. <>= .de BEGINCODECHUNK .sp 0.4v <> <> .di CODECHUNK .. .de ENDCODECHUNK <> <> .. @ .P The code below replaces the built-in [[.ta]] tab-setting command with a custom tab-setting command that allows tab stops to be reset and later recalled. (A.R.: I don't see a use for this; furthermore this saves the new settings in [[ta_saved]], it doesn't save the old ones.) <>= .rn ta real_ta .de ta .ds ta_saved \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 .real_ta \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 .. @ .P The font, type size and leading require two saved values, so that both the current value and the previous value can be restored. For those not intimately familiar with troff, [[.f]] is the current font number, [[.s]] is the current point size, [[.v]] is the current vertical spacing, [[.u]] is 1 or 0 for fill/no-fill, [[.j]] is the current adjust mode, and [[.i]] is the current indent mode. <>= .nr OLDft \\n(.f .ft P .nr PREVft \\n(.f .nr OLDps \\n(.s .ps .nr PREVps \\n(.s .nr OLDvs \\n(.v .vs .nr PREVvs \\n(.v .nr OLDfi \\n(.u .nr OLDad \\n(.j .nr OLDin \\n(.in @ .P Code is printed in a constant-width font at 80% the size of document text. Code is collected in fill mode with explicit breaks after each line; although it might seem more natural to collect lines of code in no-fill mode, that is not possible here because uses of code chunks must be able to appear on the same line as actual code. <>= .ft CW .ps \\n(.s*4/5 .vs \\n(.vu*4u/5u .in +0.5i .real_ta 0.5i 1i 1.5i 2i 2.5i 3i 3.5i 4i .fi @ .P In order to prevent page breaks within code chunks, each code chunk is read into a diversion and a page break is issued if the code chunk is too big to fit on the current page. After the diversion is read back in, when it is known what page the code is printed on, the location of the code chunk is written using a [[.tm]] command. (The [[dn]] number register is the height of the most recent diversion, and [[.t]] is the distance to the next trap.) <>= .br \" flush last line of code .di \" end diversion .if \\n(dn>\\n(.t .bp \" force form feed if too big .nf \" no fill mode -- already formatted .in -0.5i \" don't re-indent when re-reading text .CODECHUNK \" output body of diversion .tm ###TAG### \\$1 \\n[%]\\n+[SECTIONLETTER] \" write tag info .rm CODECHUNK \" reset diversion for next code chunk @ .P Finally, here is the code to restore the environment that existed before the beginning of the code chunk. <>= .ft \\n[PREVft] .ft \\n[OLDft] .ps \\n[PREVps] .ps \\n[OLDps] .vs \\n[PREVvs]u .vs \\n[OLDvs]u .if \\n[OLDfi] .fi .ad \\n[OLDad] .in \\n[OLDin]u .real_ta @ .HU "Text, code, and quoted code" In text chunks, text is copied from input to output basically unchanged. However, when quoted code is included in a line of text, the quoted code is processed by separate macros and it is possible that the continuation of the line of text after the quoted code begins with a character which is special to troff at the beginning of a line: a space, period, or single quote. To prevent problems in this case, variable [[text]] is set to [[0]] at the beginning of each line and incremented whenever text is written, and each text after the first is guarded by a non-printing null character. <>= /^@text/ && code == 0 { s = substr($0, 7) if (text++) printf "\\&" printf "%s", substr($0, 7) } /^@nl/ && code != 1 { text = 0; printf "\n" } @ %def text .P In code chunks, backslashes are replaced with troff's [[\e]] escape sequence and a non-printing null character is added at the beginning of each text to guard leading periods. If your macro package issues a [[.ec]] command, this code will have to change; fortunately, both [[-mm]] and [[-ms]] are nicely-behaved with respect to escape characters. <>= /^@text/ && code != 0 { s = substr($0, 7) gsub(/\\/, "\\e", s) printf "\\&%s\\c\n", s } @ .P Dealing with newlines in code chunks is surprisingly hard. We would like to print lines of code in [[nofill]] mode, but since noweb's markup and filter programs may split lines of code, we are forced to use [[fill]] mode and deal with newlines ourselves. Further, if a line is too long to print on a single line, it must be split, and the continuation line right-justified instead of left-justified. All of this processing is accomplished by calling a macro at each newline; the macro plants a page-position trap at the next line so that any continuation line will be right-justified. (The [[.dt]] command defines a trap for use within the current diversion, whereas the more common [[.wh]] is for a particular page position.) <>= /^@nl/ && code == 1 { printf ".NEWLINE\n" } <>= .de LINETRAP .dt \\n[TRAPplace]u \"cancel current trap 'ad r \" right-adjust continuation lines .. .de NEWLINE .dt \\n[TRAPplace]u \" cancel current trap \& \" end continued word .br \" flush output <> .. @ .P The newline trap is planted after each definition line, for the first line of code in the code chunk, and again after each newline in the code chunk. (The [[.d]] register indicates the current vertical position within the diversion.) <>= .nr TRAPplace \\n(.du+1u \" location of next trap .dt \\n[TRAPplace]u LINETRAP \" plant trap at next line .ad l \" left-adjust first line @ .P Quoted code in a documentation chunk is printed within its own macros, which must not cause a break. <>= /^@quote/ { code = 2; printf "\\c\n.BEGINQUOTEDCODE\n" } /^@endquote/ { code = 0; text++; printf ".ENDQUOTEDCODE\n" } <>= .de BEGINQUOTEDCODE .nr SAVEft \\n(.f .ft P .nr SAVEftP \\n(.f .ft CW .. .de ENDQUOTEDCODE .ft \\n[SAVEftP] .ft \\n[SAVEft] .. @ .HU "Definitions and uses of code chunks" Definitions and uses of code chunks are handled below. Variable [[defn[name]]] is set to a plus sign after a definition is printed, so that continuations of the definition are properly identified. Variable [[lastxrefref]] is the tag associated with the most-recently-seen cross-reference label, and refers to the section number of the original definition of the code chunk. <>= /^@defn/ { name = convquote(substr($0, 7)) lastdefnlabel = lastxreflabel if (! (name in defn)) defn[name] = "\\(==" printf ".DEFINITION %s \"%s\" %s %s\n", tag(lastdefnlabel), name, lastxrefref, defn[name] defn[name] = "\\(pl\\(==" } /^@use/ { name = convquote(substr($0, 6)) printf "\\c\n" printf ".USE \"%s\" %s\n", name, lastxrefref } @ %def name defn lastdefnlabel @ The original version of toroff used [[\(oG]] and [[\(cG]] for the double angle bracket characters. These appear to be non-standard, thus we define our own strings to take their place. (A.R.) <>= .ds o< <\h'-0.1m'< .ds c> >\h'-0.1m'> .de DEFINITION .ti -0.5i \\fR\\(sc\\$1 \\*(o<\\$2 \\$3\\*(c>\\$4\\fP <> .. .de USE \\fR\\*(o<\\$1 \\$2\\*(c>\\fP\c \" section name and original number .. @ .P Processing of quoted code within definitions and uses is performed by the [[convquote]] function. This must be processed differently than quoted code within text because the noweb markup program doesn't emit [[@quote]] .\|.\|. [[@endquote]] markers within definitions and uses. And, because the subject string is used as an argument to a macro, it would not possible to call the [[.BEGINQUOTEDCODE]] and [[.ENDQUOTEDCODE]] macros, even if noweb did emit the markers within definitions and uses. .P The original version just did [[gsub(/\]\]/, "\\*[ENDCONVQUOTE]", s)]], but this does not catch the case of something like \f(CW[\&[a[i\&]\&]\&]\fP. A more complicated loop using [[match()]] is in order. It loops through the string, picking it apart, and replacing each quoted code section individually. <>= function convquote(s, out, front, mid, tail) { gsub(/\[\[/, "\\*[BEGINCONVQUOTE]", s) # gsub(/\]\]/, "\\*[ENDCONVQUOTE]", s) out = "" mid = "\\*[ENDCONVQUOTE]" while (match(s, /\]\]+/) != 0) { # RLENGTH is length of match, want to remove last two chars # RSTART is where sequence of ]s begins tail = substr(s, RSTART + RLENGTH) if (RLENGTH == 2) # easy front = substr(s, 1, RSTART - 1) else front = substr(s, 1, RSTART - 1 + RLENGTH - 2) out = out front mid s = tail } out = out s return out } # my test program for the revised function - ADR # BEGIN { str = "abc[[foo[i]]]]]]]]junk" # print str # print convquote(str) # str2 = "nothing here" # print str2 # print convquote(str2) # str3 = "abc[[foo[i]]]]]]]]junk[[bar[i]]more stuff[[baz]]" # print str3 # print convquote(str3) # } @ %def convquote <>= .ds BEGINCONVQUOTE \f[CW] .ds ENDCONVQUOTE \fP @ .HU "Cross-referencing and indexing" The code for producing the ``hypertext links'' at the end of each code section is given below: for each type, the beginning of the message is printed, items are accumulated in a list, and the list is printed after the number of items in the list is known. The first time any of the cross-reference or identifier-index messages appears, it is necessary to reset the point size and leading to the small font used for this material, which is 80% of the size of code and 64% of the size of text. All of these lines are part of the diversion which collects a code chunk. First is the code to report definitions and uses of code. <>= /^@xref begindefs/ { <>; printf ".XREFDEFS\n" } /^@xref beginuses/ { <>; printf ".XREFUSES\n" } /^@xref notused/ { <>; printf ".XREFNOTUSED\n" } /^@xref (def|use)item/ { printf ".ADDLIST %s\n", tag($3) } /^@xref end(def|use)s/ { printf ".PRINTLIST\n"; code = 1 } <>= .de XREFDEFS This definition continued in .. .de XREFUSES This code used in .. .de XREFNOTUSED This code is not used in this document. .br .. @ .P The code to report the definitions of identifiers appears below. The [[if]] in [[@index isused]] prevents index definitions from pointing to themselves. <>= /^@index begindefs/ && !noindex { <>; printf ".INDEXDEF\n" } /^@index isused/ && !noindex { if (tag($3) != lastxrefref) printf ".ADDLIST %s\n", tag($3) } /^@index defitem/ && !noindex { printf ".DEFITEM %s\n.PRINTLIST\n", $3 } <>= .de INDEXDEF Defines: .br .. .de DEFITEM .ti +1m \\*[BEGINCONVQUOTE]\\$1\\*[ENDCONVQUOTE], .if \\n[NLIST]>0 used in .. @ .P Finally, here is the code to report uses of identifiers. <>= /^@index beginuses/ && !noindex { <>; printf ".INDEXUSE\n" } /^@index isdefined/ && !noindex { lastuse = tag($3) } /^@index useitem/ && !noindex { printf ".ADDLIST \"\\*[BEGINCONVQUOTE]%s\\*[ENDCONVQUOTE] %s\"\n", $3, lastuse } /^@index enduses/ && !noindex { printf ".PRINTLIST\n" } @ %def lastuse <>= .de INDEXUSE Uses .. @ .P The macros [[.ADDLIST s]], which adds string [[s]] to a queued list waiting to be printed, and [[.PRINTLIST]], which prints the list, appropriately formatted with commas, are described below. These macros make use of some interesting troff idioms. [[LIST]] is an array of strings; the [[n]]-th string in [[LIST]] can be set to [[s]] by [[.ds LISTn s]]. The expression [[.nr n \\$1]] converts the string passed as the first argument to a macro to the number [[n]]. Loops are implemented in troff as recursive macros, as in [[.PRINTITEM]]. <>= .nr NLIST 0 1 \" initialize list index to 0 with auto-increment 1 .de ADDLIST .ds LIST\\n+[NLIST] \\$1 .. .de PRINTITEM .nr PLIST \\$1 .nr PLISTPLUS \\n[PLIST]+1 .if \\n[NLIST]-\\n[PLIST]<0 not used in this document. .if \\n[NLIST]-\\n[PLIST]=0 \\*[LIST\\n[PLIST]]. .if \\n[NLIST]-\\n[PLIST]=1 \ \\*[LIST\\n[PLIST]] and \\*[LIST\\n[PLISTPLUS]]. .if \\n[NLIST]-\\n[PLIST]>1 \{ \\*[LIST\\n[PLIST]], . PRINTITEM 1+\\n[PLIST] \} .. .de PRINTLIST .PRINTITEM 1 .br .nr NLIST 0 1 \" re-initialize for next list .. @ .P This code and macro reduces the font size in which the cross-reference and identifier index information is printed at the end of a code chunk. The [[code]] variable is set to [[0]] to ensure that [[.STARTXREF]] is performed only once at the end of each code chunk. This code also resets the adjustment mode, which was changed to left-adjustment or right-adjustment by the [[LINETRAP]] macro, and cancels the [[NEWLINE]] trap at [[TRAPplace]]. <>= if (code) { code = 0; printf ".STARTXREF\n" } <>= .de STARTXREF .ps \\n(.s*4/5 .vs \\n(.vu*4u/5u .ft \\n[OLDft] .ad \\n[OLDad] .dt \\n[TRAPplace]u .sp 0.4v .. @ .HU "Collecting the lists of chunks and identifiers" Collecting the lists of chunks and identifiers takes two passes over the file, because the section numbers which tags refer to aren't known on the first pass. Therefore, the strategy on the first pass is to write the chunk and identifier index entries to the [[tags]] file on standard error, and actually prepare the lists when reading the [[tags]] file on the second pass. Thus, the first pass code shown here merely copies the chunk and identifier index entries from the noweb intermediate file to the [[tags]] file using troff's [[.tm]] command. The first section below handles chunks, the second section handles identifiers. <>= /^@xref beginchunks/ { printf ".tm ###BEGINCHUNKS###\n" } /^@xref chunkbegin/ { printf ".tm ###CHUNKBEGIN### %s\n", substr($0, length($3) + 19) } /^@xref chunkuse/ { printf ".tm ###CHUNKUSE### %s\n", $3 } /^@xref chunkdefn/ { printf ".tm ###CHUNKDEFN### %s\n", $3 } /^@xref chunkend/ { printf ".tm ###CHUNKEND###\n" } /^@xref endchunks/ { printf ".tm ###ENDCHUNKS###\n" } <>= /^@index beginindex/ { printf ".tm ###BEGININDEX###\n" } /^@index entrybegin/ { printf ".tm ###ENTRYBEGIN### %s\n", substr($0, length($3) + 20) } /^@index entryuse/ { printf ".tm ###ENTRYUSE### %s\n", $3 } /^@index entrydefn/ { printf ".tm ###ENTRYDEFN### %s\n", $3 } /^@index entryend/ { printf ".tm ###ENTRYEND###\n" } /^@index endindex/ { printf ".tm ###ENDINDEX###\n" } @ .HU "The \f[CW]tags\fP file" The [[tags]] file is re-created at each formatter run by the troff idiom (some people would call it a trick) of capturing the output of troff's [[.tm]] command, which writes to the standard error, in a file via command-line redirection. The code below uses an awk idiom; the [[sub]] simultaneously tests for a match and deletes the matched text. <>= { if (sub(/^###TAG### / , "")) tags[$1] = $2 else if (sub(/^###BEGINCHUNKS###/, "")) printf ".de CLIST\n.CLISTBEGIN\n" else if (sub(/^###CHUNKBEGIN### /, "")) { name = convquote($0) chunkuse = chunkdefn = "" } else if (sub(/^###CHUNKUSE### / , "")) chunkuse = chunkuse " " tag($0) else if (sub(/^###CHUNKDEFN### / , "")) chunkdefn = chunkdefn " " tag($0) else if (sub(/^###CHUNKEND###/ , "")) printf ".CITEM \"%s\" \"%s\" \"%s\"\n", name, chunkdefn, chunkuse else if (sub(/^###ENDCHUNKS###/ , "")) printf ".CLISTEND\n..\n" else if (sub(/^###BEGININDEX###/ , "")) printf ".de ILIST\n.ILISTBEGIN\n" else if (sub(/^###ENTRYBEGIN### /, "")) { name = convquote($0) entryuse = entrydefn = "" } else if (sub(/^###ENTRYUSE### / , "")) entryuse = entryuse " " tag($0) else if (sub(/^###ENTRYDEFN### / , "")) entrydefn = entrydefn " " tag($0) else if (sub(/^###ENTRYEND###/ , "")) { for (i = 1; i <= split(entrydefn, entryarray); i++) sub(entryarray[i], "\\*[BEGINDEFN]&\\*[ENDDEFN]", entryuse) printf ".IITEM \"%s\" \"%s\"\n", name, entryuse } else if (sub(/^###ENDINDEX###/ , "")) printf ".ILISTEND\n..\n" } @ %def tags chunkuse chunkdefn entryuse entrydefn entryarray .P The [[sub]] within the [[ENTRYEND]] causes definitions of identifiers to be italicized, according to the following defined strings. <>= .ds BEGINDEFN \fI .ds ENDDEFN \fP @ .P Lookup of the section number corresponding to a particular tag is performed by the [[tag]] function. <>= function tag(s) { if (s in tags) return tags[s]; else return "???" } @ %def tag @ .P To use the tags, we have to read the tags file from the back end. The tags file name is the basename of the file, with the suffix replaced with [[".nwt"]] (see the toroff script, below). <>= /^@file / { if (tagsfile == "") { tagsfile = substr($0, 7) sub(".*/", "", tagsfile) sub(/\.[^.]*$/, "", tagsfile) tagsfile = tagsfile ".nwt" while (getline 0) <> } } @ .HU "Using \\f[CW]toroff\fP" Toroff is one element of the normal noweave pipeline. Using toroff in its full generality is hard. The sample program shown below allows the user to specify the macro package (although it defaults to [[-mgm]]), and allows the user to specify troff pre-processors, post-processors and options. (Command line options to [[groff]] can be used to invoke tbl, pic, etc.) See the section below on how to generate the final formatted document. .P To avoid a race condition reading the tags file (troff is writing to it while the awk program is reading it), we first copy it for use by the awk program, and then remove the copy. The location of [[macrodir]] is likely to require customization. <>= #!/bin/sh # # noroff -- run troff using tags file trick ROFF="groff" AWK=nawk macrodir=|LIBDIR| LIB=|LIBDIR| opts= if [ $# -eq 0 ]; then echo "Usage: noroff [groff-arguments] files" 1>&2 exit 1 fi while [ $# -gt 0 ] do case $1 in -*) opts="$opts $1" shift ;; *) # end of options break; ;; esac done if [ "$opts" = "" ] then # no options, default to -mm # groff already defaults to -Tps opts="-mm" fi # otherwise assume user passed in all the arguments they want base="`basename $1 | sed '/\./s/\.[^.]*$//'`" tagsfile="$base.nwt" (echo ".so $macrodir/tmac.w" if [ -r "$tagsfile" ]; then tagstemp=$($LIB/nwmktemp) || { echo "$0: Cannot create temporary file" >&2; exit 1; } cp $tagsfile $tagstemp $AWK '<> <>' $tagstemp rm -f $tagstemp fi cat "$@") | ($ROFF $opts 2>$tagsfile) sed '/^###[A-Z][A-Z]*###/d' $tagsfile >&2 @ %def macrodir @ .HU "Macros for indexing chunks and identifiers" Toroff creates macros [[.CLIST]] and [[.ILIST]] which insert the lists of chunks and identifiers, respectively, in the output. These macros, in turn, call other macros which format the lists. The macros below cause each item to be written on a separate line, with continuation lines indented one-quarter inch. <>= .de CLISTBEGIN .in +0.25i .. .de CITEM .ti -0.25i .ie '\\$3'' \\*(o<\\$1 \\$2\\*(c> Not used in this document. .el \\*(o<\\$1 \\$2\\*(c> \\$3 .br .. .de CLISTEND .in -0.25i .. <>= .de ILISTBEGIN .in +0.25i .. .de IITEM .ti -0.25i \\f[CW]\\$1\\fP: \\$2 .br .. .de ILISTEND .in -0.25i .. @ .HU "Using \f[CW]noweave -troff\fP and \f[CW]noroff\fP" To produce the final document, the tags file is needed for both the [[noweave]] and the [[noroff]] steps. This creates a bit of a boot-strapping problem, since it is troff that first creates the tags file. The way to do it is to run [[noweave]] and [[noroff]] in turn, twice. For example, to make a printable version of this document, you would run the programs like this. <>= noweave -index -troff roff.nw > roff.tr noroff roff.tr > /dev/null # ignore warnings about CLIST and ILIST noweave -index -troff roff.nw > roff.tr noroff roff.tr > roff.ps # print this one @ .HU "Index of chunks and identifiers" The automatically-generated index of chunks and identifiers for toroff is shown below. .S 10 12 .sp .CLIST .sp .2C .ILIST