docs: Generate grammar and option summaries from manpage.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 22 Feb 2006 02:07:00 +0000 (02:07 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 9 Mar 2006 20:13:57 +0000 (20:13 +0000)
I've been dreaming about this for ages and finally obtained a tuit from
somewhere.  The (rather grungy) script make-manpage extracts the grammar
and option information from the input manpage and spits it out again.

It's slightly surprising how bad a deroffer it can get away with...

.gitignore
Makefile.am
debian/rules
fw.1.in [moved from fw.1 with 89% similarity]
fw.c
grammar [deleted file]
make-manpage [new file with mode: 0755]
mantext.h [new file with mode: 0644]

index 51c0f7e..5108762 100644 (file)
@@ -13,3 +13,4 @@ mkinstalldirs
 depcomp
 autom4te.cache
 
 depcomp
 autom4te.cache
 
+GRAMMAR
index 40dd33d..2df8226 100644 (file)
@@ -31,12 +31,27 @@ bin_PROGRAMS = fw
 man_MANS = fw.1
 
 EXTRA_DIST = \
 man_MANS = fw.1
 
 EXTRA_DIST = \
-       $(man_MANS) \
+       fw.1.in GRAMMAR \
+       make-manpage \
        debian/changelog debian/copyright debian/control debian/rules
 
        debian/changelog debian/copyright debian/control debian/rules
 
+BUILT_SOURCES = mantext.c
+
+$(srcdir)/GRAMMAR: fw.1.in make-manpage
+       perl $(srcdir)/make-manpage text <$(srcdir)/fw.1.in >$@.new
+       mv $@.new $@
+
+fw.1: fw.1.in make-manpage
+       perl $(srcdir)/make-manpage man <$(srcdir)/fw.1.in >$@.new
+       mv $@.new $@
+
+mantext.c: fw.1.in make-manpage
+       perl $(srcdir)/make-manpage c <$(srcdir)/fw.1.in >$@.new
+       mv $@.new $@    
+
 fw_SOURCES = \
 fw_SOURCES = \
-       chan.c conf.c endpt.c fw.c reffd.c scan.c \
-       chan.h conf.h endpt.h fw.h reffd.h scan.h \
+       chan.c conf.c endpt.c fw.c mantext.c reffd.c scan.c \
+       chan.h conf.h endpt.h fw.h mantext.h reffd.h scan.h \
        \
        source.c source.h target.h \
          exec.c file.c socket.c \
        \
        source.c source.h target.h \
          exec.c file.c socket.c \
@@ -49,4 +64,7 @@ fw_SOURCES = \
        acl.c fattr.c identify.c privconn.c \
        acl.h fattr.h identify.h privconn.h rlimits.h
 
        acl.c fattr.c identify.c privconn.c \
        acl.h fattr.h identify.h privconn.h rlimits.h
 
+CLEANFILES = $(BUILT_SOURCES) fw.1
+MAINTAINERCLEANFILES = $(srcdir)/GRAMMAR
+
 ##----- That's all, folks ---------------------------------------------------
 ##----- That's all, folks ---------------------------------------------------
index 3970631..21b6cee 100755 (executable)
@@ -23,7 +23,7 @@ binary-arch: install
        dh_testdir -a
        dh_testroot -a
        dh_compress -a
        dh_testdir -a
        dh_testroot -a
        dh_compress -a
-       dh_installdocs -a grammar
+       dh_installdocs -a GRAMMAR
        dh_strip -a
        dh_shlibdeps -a
        dh_gencontrol -a
        dh_strip -a
        dh_shlibdeps -a
        dh_gencontrol -a
diff --git a/fw.1 b/fw.1.in
similarity index 89%
rename from fw.1
rename to fw.1.in
index 51dc460..542e48b 100644 (file)
--- a/fw.1
+++ b/fw.1.in
@@ -59,6 +59,8 @@
 .de GE
 .PP
 ..
 .de GE
 .PP
 ..
+.de GL
+..
 .
 .de OS
 .PP
 .
 .de OS
 .PP
@@ -69,6 +71,8 @@
 .de OE
 .RE
 ..
 .de OE
 .RE
 ..
+.de OL
+..
 .
 .\" --- Other bits of styling ---
 .
 .
 .\" --- Other bits of styling ---
 .
@@ -477,7 +481,7 @@ is good enough for setting global options, and the implicit context
 disambiguates local options.
 .PP
 The following file attribute options are supported:
 disambiguates local options.
 .PP
 The following file attribute options are supported:
-.OS "File attribute options (`fattr')"
+.OS "File attributes (`fattr')"
 .IB prefix .fattr.mode
 .RB [ = ]
 .I mode
 .IB prefix .fattr.mode
 .RB [ = ]
 .I mode
@@ -495,7 +499,7 @@ and
 .RB ` , '
 do not have to be quoted within the mode string.
 .OE
 .RB ` , '
 do not have to be quoted within the mode string.
 .OE
-.OS "File attribute options (`fattr')"
+.OS "File attributes (`fattr')"
 .IB prefix .fattr.owner
 .RB [ = ]
 .I user
 .IB prefix .fattr.owner
 .RB [ = ]
 .I user
@@ -511,7 +515,7 @@ and
 are accepted in place of
 .BR owner .
 .OE
 are accepted in place of
 .BR owner .
 .OE
-.OS "File attribute options (`fattr')"
+.OS "File attributes (`fattr')"
 .IB prefix .fattr.group
 .RB [ = ]
 .I group
 .IB prefix .fattr.group
 .RB [ = ]
 .I group
@@ -542,7 +546,7 @@ is used as a source, it is set up immediately.
 The syntax of
 .B file
 sources and targets is like this:
 The syntax of
 .B file
 sources and targets is like this:
-.GS "File sources and targets"
+.GS "File source and target"
 .I source
 ::=
 .I file
 .I source
 ::=
 .I file
@@ -680,7 +684,7 @@ an error is reported.  If
 the new data is appended to the file.
 .OE
 .OS "File options"
 the new data is appended to the file.
 .OE
 .OS "File options"
-.BR file.fattr.*
+.BR file.fattr. *
 .OD
 The
 .B file
 .OD
 The
 .B file
@@ -706,7 +710,7 @@ which is as follows:
 .br
 .I target
 ::=
 .br
 .I target
 ::=
-exec
+.I exec
 .br
 .I exec
 ::=
 .br
 .I exec
 ::=
@@ -875,7 +879,7 @@ The soft limit cannot be set above the hard limit.
 .OD
 Clears the program's environment.
 .OE
 .OD
 Clears the program's environment.
 .OE
-.PP
+.OS "Exec options"
 .B exec.env.unset
 .I var
 .OD
 .B exec.env.unset
 .I var
 .OD
@@ -997,6 +1001,8 @@ log messages are not generated.
 Address types also provide their own options.
 .
 .SS "The `inet' socket address type"
 Address types also provide their own options.
 .
 .SS "The `inet' socket address type"
+.GL "Socket source and target"
+.OL "Socket options"
 The
 .B inet
 address type provides access to TCP ports.  The
 The
 .B inet
 address type provides access to TCP ports.  The
@@ -1041,7 +1047,7 @@ source address accepts the following options:
 .OS "Socket options"
 .B socket.inet.source.addr
 .RB [ = ]
 .OS "Socket options"
 .B socket.inet.source.addr
 .RB [ = ]
-.RR any | \c
+.BR any | \c
 .I addr
 .OD
 Specify the IP address on which to listen for incoming connections.  The
 .I addr
 .OD
 Specify the IP address on which to listen for incoming connections.  The
@@ -1078,7 +1084,7 @@ the range 0--1023.
 .OS "Socket options"
 .B socket.inet.dest.addr
 .RB [ = ]
 .OS "Socket options"
 .B socket.inet.dest.addr
 .RB [ = ]
-.RR any | \c
+.BR any | \c
 .I addr
 .OD
 Specify the IP address to bind the local socket to when making an
 .I addr
 .OD
 Specify the IP address to bind the local socket to when making an
@@ -1128,6 +1134,8 @@ of the last entry tried.  If there are no entries defined, the default
 is to allow all clients.
 .
 .SS "The `unix' socket address type"
 is to allow all clients.
 .
 .SS "The `unix' socket address type"
+.GL "Socket source and target"
+.OL "Socket options"
 The
 .B unix
 address type allows access to Unix-domain sockets.  The syntax for
 The
 .B unix
 address type allows access to Unix-domain sockets.  The syntax for
@@ -1227,330 +1235,11 @@ just logs a message about the signal and continues.
 .\"--------------------------------------------------------------------------
 .SH "GRAMMAR SUMMARY"
 .
 .\"--------------------------------------------------------------------------
 .SH "GRAMMAR SUMMARY"
 .
-.SS "Basic syntax"
-.I file
-::=
-.I empty
-|
-.I file
-.I stmt
-.RB [ ; ]
-.br
-.I stmt
-::=
-.I option-stmt
-|
-.I fw-stmt
-.br
-.I fw-stmt
-::=
-.B fw
-.I source
-.I options
-.RB [ to | \-> ]
-.I target
-.I options
-.br
-.I options
-::=
-.B {
-.I option-seq
-.B }
-.br
-.I option-seq
-::=
-.I empty
-|
-.I option-stmt
-.RB [ ; ]
-.I option-seq
-.
-.SS "Option syntax"
-.I option-stmt
-::=
-.I q-option
-.br
-.I q-option
-::=
-.I option
-.br
-       |
-.I prefix
-.B .\&
-.I q-option
-.br
-       |
-.I prefix
-.B {
-.I option-seq
-.B }
-.br
-.I prefix
-::=
-.I word
-.
-.SS "File source and target"
-.I source
-::=
-.I file
-.br
-.I target
-::=
-.I file
-.br
-.I file
-::=
-.B file
-.RB  [ .\& ]
-.I fspec
-.RB [ ,
-.IR fspec ]
-.br
-.I fspec
-::=
-.I fd-spec
-|
-.I name-spec
-|
-.I null-spec
-.br
-.I fd-spec
-::=
-.RB [[ : ] fd [ : ]]
-.IR number \c
-.RB | stdin | stdout
-.br
-.I name-spec
-::=
-.RB [[ : ] name [ : ]]
-.I file-name
-.br
-.I file-name
-::=
-.I path-seq
-|
-.B [
-.I path-seq
-.B ]
-.br
-.I path-seq
-::=
-.I path-elt
-|
-.I path-seq
-.I path-elt
-.br
-.I path-elt
-::=
-.B /
-|
-.I word
-.br
-.I null-spec
-::=
-.RB [ : ] null [ : ]
-.
-.SS "Exec source and target"
-.I source
-::=
-.I exec
-.br
-.I target
-::=
-exec
-.br
-.I exec
-::=
-.BR exec
-.RB [ .\& ]
-.I cmd-spec
-.br
-.I cmd-spec
-::=
-.I shell-cmd
-|
-.RI [ prog-name ]
-.B [
-.I argv0
-.I arg-seq
-.B ]
-.br
-.I arg-seq
-::=
-.I word
-|
-.I arg-seq
-.I word
-.br
-.I shell-cmd
-::=
-.I word
-.br
-.I argv0
-::=
-.I word
-.
-.SS "Socket source and target"
-.ll +8i
-.I source
-::=
-.I socket-source
-.br
-.I target
-::=
-.I socket-target
-.br
-.I socket-source
-::=
-.RB [ socket [ .\& ]]
-.RB [[ : ] \c
-.IR addr-type \c
-.RB [ : ]]
-.I source-addr
-.br
-.I socket-target
-::=
-.RB [ socket [ .\& ]]
-.RB [[ : ] \c
-.IR addr-type \c
-.RB [ : ]]
-.I target-addr
-.ll -8i
-.PP
-.I inet-source-addr
-::=
-.RB [ port ]
-.I port
-.br
-.I inet-target-addr
-::=
-.I address
-.RB [ : ]
-.I port
-.br
-.I address
-::=
-.I addr-elt
-|
-.I address
-.I addr-elt
-.br
-.I addr-elt
-::=
-.B .\&
-|
-.I word
-.PP
-.I unix-source-addr
-::=
-.I file-name
-.br
-.I unix-target-addr
-::=
-.I file-name
-.
+@@@ grammar
 .\"--------------------------------------------------------------------------
 .SH "OPTION SUMMARY"
 .
 .\"--------------------------------------------------------------------------
 .SH "OPTION SUMMARY"
 .
-.SS "File attributes (`fattr')"
-.IB prefix .fattr.mode
-.RB [ = ]
-.I mode
-.br
-.IB prefix .fattr.owner
-.RB [ = ]
-.I user
-.br
-.IB prefix .fattr.group
-.RB [ = ]
-.I group
-.
-.SS "File options"
-.B file.create
-.RB [ = ]
-.BR yes | no
-.br
-.B file.open
-.RB [ = ]
-.BR no | truncate | append
-.br
-.BR file.fattr. *
-.
-.SS "Exec options"
-.B exec.logging
-.RB [ = ]
-.BR yes | no
-.br
-.B exec.dir
-.RB [ = ]
-.I file-name
-.br
-.B exec.root
-.RB [ = ]
-.I file-name
-.br
-.B exec.user
-.RB [ = ]
-.I user
-.br
-.B exec.group
-.RB [ = ]
-.I group
-.br
-.BI exec.rlimit. limit \c
-.RB [ .hard | .soft ]
-.RB [ = ]
-.I value
-.br
-.B exec.env.clear
-.br
-.B exec.env.unset
-.I var
-.br
-.BR exec.env. [ set ]
-.I var
-.RB [ = ]
-.I value
-.
-.SS "Socket options"
-.B socket.conn
-.RB [ = ]
-.IR number | \c
-.BR unlimited | one-shot
-.br
-.B socket.listen
-.RB [ = ]
-.I number
-.br
-.B socket.logging
-.RB [ = ]
-.BR yes | no
-.PP
-.BR socket.inet.source. [ allow | deny ]
-.RB [ host ]
-.I addr
-.RB [ /
-.IR addr ]
-.br
-.BR socket.inet.source. [ allow | deny ]
-.B priv-port
-.br
-.B socket.inet.source.addr
-.RB [ = ]
-.BR any | \c
-.I addr
-.br
-.B socket.inet.dest.addr
-.RB [ = ]
-.BR any | \c
-.I addr
-.br
-.B socket.inet.dest.priv-port
-.RB [=]
-.BR yes | no
-.PP
-.BR socket.unix.fattr. *
-.
+@@@ option
 .\"--------------------------------------------------------------------------
 .SH "BUGS"
 .
 .\"--------------------------------------------------------------------------
 .SH "BUGS"
 .
diff --git a/fw.c b/fw.c
index 6f886e5..f626c6e 100644 (file)
--- a/fw.c
+++ b/fw.c
@@ -61,6 +61,7 @@
 #include "fattr.h"
 #include "file.h"
 #include "fw.h"
 #include "fattr.h"
 #include "file.h"
 #include "fw.h"
+#include "mantext.h"
 #include "privconn.h"
 #include "scan.h"
 #include "socket.h"
 #include "privconn.h"
 #include "scan.h"
 #include "socket.h"
@@ -468,100 +469,15 @@ Other options:\n\
 static void grammar(FILE *fp)
 {
   version(fp);
 static void grammar(FILE *fp)
 {
   version(fp);
-  pquis(fp, "\n\
-Grammar summary\n\
-\n\
-Basic syntax\n\
-       FILE ::= EMPTY | FILE STMT [`;']\n\
-       STMT ::= OPTION-STMT | FW-STMT\n\
-       FW-STMT ::= `fw' SOURCE OPTIONS [`to'|`->'] TARGET OPTIONS\n\
-       OPTIONS ::= `{' OPTION-SEQ `}'\n\
-       OPTION-SEQ ::= EMPTY | OPTION-STMT [`;'] OPTION-SEQ\n\
-\n\
-Option syntax\n\
-       OPTION-STMT ::= Q-OPTION\n\
-       Q-OPTION ::= OPTION\n\
-            | PREFIX `.' Q-OPTION\n\
-            | PREFIX `{' OPTION-SEQ `}'\n\
-       PREFIX ::= WORD\n\
-\n\
-File source and target\n\
-       SOURCE ::= FILE\n\
-       TARGET ::= FILE\n\
-       FILE ::= `file' [`.'] FSPEC [`,' FSPEC]\n\
-       FSPEC ::= FD-SPEC | NAME-SPEC | NULL-SPEC\n\
-       FD-SPEC ::= [[`:']`fd'[`:']] NUMBER|`stdin'|`stdout'\n\
-       NAME-SPEC ::= [[`:']`name'[`:']] FILE-NAME\n\
-       FILE-NAME ::= PATH-SEQ | [ PATH-SEQ ]\n\
-       PATH-SEQ ::= PATH-ELT | PATH-SEQ PATH-ELT\n\
-       PATH-ELT ::= `/' | WORD\n\
-       NULL-SPEC ::= [`:']`null'[`:']\n\
-\n\
-Exec source and target\n\
-       SOURCE ::= EXEC\n\
-       TARGET ::= EXEC\n\
-       EXEC ::= `exec' [`.'] CMD-SPEC\n\
-       CMD-SPEC ::= SHELL-CMD | [PROG-NAME] `[' ARGV0 ARG-SEQ `]'\n\
-       ARG-SEQ ::= WORD | ARG-SEQ WORD\n\
-       SHELL-CMD ::= WORD\n\
-       ARGV0 ::= WORD\n\
-\n\
-Socket source and target\n\
-       SOURCE ::= SOCKET-SOURCE\n\
-       TARGET ::= SOCKET-TARGET\n\
-       SOCKET-SOURCE ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] SOURCE-ADDR\n\
-       SOCKET-TARGET ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] TARGET-ADDR\n\
-\n\
-       INET-SOURCE-ADDR ::= [`port'] PORT\n\
-       INET-TARGET-ADDR ::= ADDRESS [`:'] PORT\n\
-       ADDRESS ::= ADDR-ELT | ADDRESS ADDR-ELT\n\
-       ADDR-ELT ::= `.' | WORD\n\
-\n\
-       UNIX-SOURCE-ADDR ::= FILE-NAME\n\
-       UNIX-TARGET-ADDR ::= FILE-NAME\n\
-");
+  fputs("\nGrammar summary\n\n", fp);
+  fputs(grammar_text, fp);
 }
 
 static void options(FILE *fp)
 {
   version(fp);
 }
 
 static void options(FILE *fp)
 {
   version(fp);
-  pquis(fp, "\n\
-Options summary\n\
-\n\
-File attributes (`fattr')\n\
-       prefix.FATTR.MODE [=] MODE\n\
-       prefix.FATTR.OWNER [=] USER\n\
-       prefix.FATTR.GROUP [=] GROUP\n\
-\n\
-File options\n\
-       file.create [=] yes|no\n\
-       file.open [=] no|truncate|append\n\
-       file.fattr.*\n\
-\n\
-Exec options\n\
-       exec.logging [=] yes|no\n\
-       exec.dir [=] FILE-NAME\n\
-       exec.root [=] FILE-NAME\n\
-       exec.user [=] USER\n\
-       exec.group [=] GROUP\n\
-       exec.rlimit.LIMIT[.hard|.soft] [=] VALUE\n\
-       exec.env.clear\n\
-       exec.env.unset VAR\n\
-       exec.env.[set] VAR [=] VALUE\n\
-\n\
-Socket options\n\
-       socket.conn [=] NUMBER|unlimited|one-shot\n\
-       socket.listen [=] NUMBER\n\
-       socket.logging [=] yes|no\n\
-\n\
-       socket.inet.source.[allow|deny] [host] ADDR [/ ADDR]\n\
-       socket.inet.source.[allow|deny] priv-port\n\
-       socket.inet.source.addr [=] any|ADDR\n\
-       socket.inet.dest.addr [=] any|ADDR\n\
-       socket.inet.dest.priv-port [=] yes|no\n\
-\n\
-       socket.unix.fattr.*\n\
-");
+  fputs("\nOption summary\n\n", fp);
+  fputs(option_text, fp);
 }
 
 /* --- @main@ --- *
 }
 
 /* --- @main@ --- *
diff --git a/grammar b/grammar
deleted file mode 100644 (file)
index 02b5b16..0000000
--- a/grammar
+++ /dev/null
@@ -1,81 +0,0 @@
-Basic syntax
-       FILE ::= EMPTY | FILE STMT [`;']
-       STMT ::= OPTION-STMT | FW-STMT
-       FW-STMT ::= `fw' SOURCE OPTIONS [`to'|`->'] TARGET OPTIONS
-       OPTIONS ::= `{' OPTION-SEQ `}'
-       OPTION-SEQ ::= EMPTY | OPTION-STMT [`;'] OPTION-SEQ
-
-Option syntax
-       OPTION-STMT ::= Q-OPTION
-       Q-OPTION ::= OPTION
-            | PREFIX `.' Q-OPTION
-            | PREFIX `{' OPTION-SEQ `}'
-       PREFIX ::= WORD
-
-File source and target
-       SOURCE ::= FILE
-       TARGET ::= FILE
-       FILE ::= `file' [`.'] FSPEC [`,' FSPEC]
-       FSPEC ::= FD-SPEC | NAME-SPEC | NULL-SPEC
-       FD-SPEC ::= [[`:']`fd'[`:']] NUMBER|`stdin'|`stdout'
-       NAME-SPEC ::= [[`:']`file'[`:']] FILE-NAME
-       FILE-NAME ::= PATH-SEQ | [ PATH-SEQ ]
-       PATH-SEQ ::= PATH-ELT | PATH-SEQ PATH-ELT
-       PATH-ELT ::= `/' | WORD
-       NULL-SPEC ::= [`:']`null'[`:']
-
-Exec source and target
-       SOURCE ::= EXEC
-       TARGET ::= EXEC
-       EXEC ::= `exec' [`.'] CMD-SPEC
-       CMD-SPEC ::= SHELL-CMD | [PROG-NAME] `[' ARGV0 ARG-SEQ `]'
-       ARG-SEQ ::= WORD | ARG-SEQ WORD
-       SHELL-CMD ::= WORD
-       ARGV0 ::= WORD
-
-Socket source and target
-       SOURCE ::= SOCKET-SOURCE
-       TARGET ::= SOCKET-TARGET
-       SOCKET-SOURCE ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] SOURCE-ADDR
-       SOCKET-TARGET ::= [`socket'[`.']] [[`:']ADDR-TYPE[`:']] TARGET-ADDR
-
-       INET-SOURCE-ADDR ::= [`port'] PORT
-       INET-TARGET-ADDR ::= ADDRESS [`:'] PORT
-       ADDRESS ::= ADDR-ELT | ADDRESS ADDR-ELT
-       ADDR-ELT ::= `.' | WORD
-
-       UNIX-SOURCE-ADDR ::= FILE-NAME
-       UNIX-TARGET-ADDR ::= FILE-NAME
-
-File attributes (`fattr')
-       PREFIX.fattr.mode [=] MODE
-       PREFIX.fattr.owner [=] USER
-       PREFIX.fattr.group [=] GROUP
-
-File options
-       file.create [=] yes|no
-       file.open [=] no|truncate|append
-       file.fattr.*
-
-Exec options
-       exec.logging [=] yes|no
-       exec.dir [=] FILE-NAME
-       exec.root [=] FILE-NAME
-       exec.user [=] USER
-       exec.group [=] GROUP
-       exec.rlimit.LIMIT[.hard|.soft] [=] VALUE
-       exec.env.clear
-       exec.env.unset VAR
-       exec.env.[set] VAR [=] VALUE
-
-Socket options
-       socket.conn [=] NUMBER|unlimited|one-shot
-       socket.logging [=] yes|no
-
-       socket.inet.source.[allow|deny] priv-port
-       socket.inet.source.[allow|deny] [host] ADDR [/ ADDR]
-       socket.inet.source.addr [=] any|ADDR
-       socket.inet.dest.addr [=] any|ADDR
-       oscket.inet.dest.priv-port [=] yes|no
-
-       socket.unix.source.fattr.*
diff --git a/make-manpage b/make-manpage
new file mode 100755 (executable)
index 0000000..b82f4d2
--- /dev/null
@@ -0,0 +1,152 @@
+#! /usr/bin/perl
+
+%ch = ();
+%ord = ();
+$chunk = undef;
+$mode = $ARGV[0] || die "mode?";
+shift;
+
+sub setup {
+  my $r = \%{$ch{$_[0]}};
+  if (exists $r->{$1}) {
+    $r->{$1} .= ".br\n";
+  } else {
+    push @{$ord{$_[0]}}, $1;
+  }
+  $chunk = \$r->{$1};
+}
+while (<>) {
+  if (/^\.GS (.*)$/) {
+    setup('grammar');
+  } elsif (/^\.OS (.*)$/) {
+    setup('option');
+  } elsif (/^\.GE/ || /^\.OD/) {
+    $chunk = undef;
+  } elsif (/^\.GL (.*)$/) {
+    $ch{'grammar'}{$1} .= ".PP\n";
+    next;
+  } elsif (/^\.OL (.*)$/) {
+    $ch{'option'}{$1} .= ".PP\n";
+    next;
+  } elsif (/^@@@ (\w+)$/) {
+    if ($mode eq 'man') {
+      foreach $head (@{$ord{$1}}) {
+       print ".SS $head\n";
+       print $ch{$1}{$head};
+      }
+    }
+    next;
+  } else {
+    $$chunk .= $_ if $chunk;
+  }
+  print if $mode eq 'man';
+}
+
+sub ital { uc($_[0]); }
+sub bold { $head eq 'option' ? $_[0] : "`$_[0]'"; }
+sub rom { $_[0]; }
+
+sub deroff {
+  $head = $_[0];
+  my $chsep = "";
+  my $out = "";
+  for my $chunk (@{$ord{$head}}) {
+    (my $chh = $chunk) =~ s/^"(.*)"$/$1/;
+    $out .= "$chsep$chh\n";
+    $chsep = "\n";
+    $sep = "\t";
+    for (split /\n/, $ch{$head}{$chunk}) {
+      s/\s$//; s/\\\&//g; s/\\-/-/g;
+      if (/^\.I (.*)$/) {
+       $out .= $sep . ital($1);
+       $sep = " ";
+      } elsif (/^\.B (.*)$/) {
+       $out .= $sep . bold($1);
+       $sep = " ";
+      } elsif (/^\.RB (.*)/) {
+       my $i = 1; $out .= $sep; $sep = " ";
+       for my $w (split ' ', $1) {
+         if ($w eq "\\c") {
+           $sep = ""; last;
+         }
+         $out .= ($i++%2 ? rom($w) : bold($w));
+       }
+      } elsif (/^\.BR (.*)/) {
+       my $i = 1; $out .= $sep; $sep = " ";
+       for my $w (split ' ', $1) {
+         if ($w eq "\\c") {
+           $sep = ""; last;
+         }
+         $out .= ($i++%2 ? bold($w) : rom($w));
+       }
+      } elsif (/^\.IR (.*)/) {
+       my $i = 1; $out .= $sep; $sep = " ";
+       for my $w (split ' ', $1) {
+         if ($w eq "\\c") {
+           $sep = ""; last;
+         }
+         $out .= ($i++%2 ? ital($w) : rom($w));
+       }
+      } elsif (/^\.RI (.*)/) {
+       my $i = 1; $out .= $sep; $sep = " ";
+       for my $w (split ' ', $1) {
+         if ($w eq "\\c") {
+           $sep = ""; last;
+         }
+         $out .= ($i++%2 ? rom($w) : ital($w));
+       }
+      } elsif (/^\.BI (.*)/) {
+       my $i = 1; $out .= $sep; $sep = " ";
+       for my $w (split ' ', $1) {
+         if ($w eq "\\c") {
+           $sep = ""; last;
+         }
+         $out .= ($i++%2 ? bold($w) : ital($w));
+       }
+      } elsif (/^\.IB (.*)/) {
+       my $i = 1; $out .= $sep; $sep = " ";
+       for my $w (split ' ', $1) {
+         if ($w eq "\\c") {
+           $sep = ""; last;
+         }
+         $out .= ($i++%2 ? ital($w) : bold($w));
+       }
+      } elsif (/^\.$/) {
+       # foo
+      } elsif (/^[^.]/) {
+       $out .= $sep . $_;
+       $sep = " "; 
+      } elsif (/^\.br$/ || /^\.PP$/) {
+       $out .= "\n";
+       $sep = "\t";
+      }
+    }
+    $out .= "\n";
+  }
+  return $out;
+}
+
+if ($mode eq 'c') {
+  print <<EOF;
+/* -*-c-*-
+ *
+ * Generated grammar and options summary
+ */
+
+#include "mantext.h"
+
+EOF
+  for $head (keys %ch) {
+    print "const char ${head}_text[] = \"\\\n";
+    $text = deroff($head);
+    $text =~ s/\n/\\n\\\n/g;
+    print $text;
+    print "\";\n\n";
+  }
+} elsif ($mode eq 'text') {
+  my $sep = "";
+  for $head (keys %ch) {
+    print $sep, deroff($head);
+    $sep = "\n";
+  }
+}
diff --git a/mantext.h b/mantext.h
new file mode 100644 (file)
index 0000000..0d83dd9
--- /dev/null
+++ b/mantext.h
@@ -0,0 +1,47 @@
+/* -*-c-*-
+ *
+ * $Id$
+ *
+ * Grammar declarations
+ *
+ * (c) 2006 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of the `fw' port forwarder.
+ *
+ * `fw' is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * `fw' is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with `fw'; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef MANTEXT_H
+#define MANTEXT_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Data provided -----------------------------------------------------*/
+
+extern const char grammar_text[];
+extern const char option_text[];
+    
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif