summaryrefslogtreecommitdiffstats
path: root/tcllib/apps
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
commitea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch)
tree6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/apps
parentb5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff)
parent7c621da28f07e449ad90c387344f07a453927569 (diff)
downloadblt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/apps')
-rwxr-xr-xtcllib/apps/dtplite28
-rw-r--r--tcllib/apps/dtplite.man447
-rwxr-xr-xtcllib/apps/nns291
-rw-r--r--tcllib/apps/nns.man143
-rwxr-xr-xtcllib/apps/nnsd153
-rw-r--r--tcllib/apps/nnsd.man91
-rwxr-xr-xtcllib/apps/nnslog182
-rw-r--r--tcllib/apps/nnslog.man93
-rwxr-xr-xtcllib/apps/page820
-rw-r--r--tcllib/apps/page.man467
-rwxr-xr-xtcllib/apps/pt156
-rw-r--r--tcllib/apps/pt.man242
-rwxr-xr-xtcllib/apps/tcldocstrip537
-rw-r--r--tcllib/apps/tcldocstrip.man197
14 files changed, 3847 insertions, 0 deletions
diff --git a/tcllib/apps/dtplite b/tcllib/apps/dtplite
new file mode 100755
index 0000000..6f447bf
--- /dev/null
+++ b/tcllib/apps/dtplite
@@ -0,0 +1,28 @@
+#! /usr/bin/env tclsh
+# -*- tcl -*-
+
+# @@ Meta Begin
+# Application dtplite 1.0.5
+# Meta platform tcl
+# Meta summary Lightweight DocTools Processor
+# Meta description This application is a simple processor
+# Meta description for documents written in the doctools
+# Meta description markup language. It covers the most
+# Meta description common use cases, but is not as
+# Meta description configurable as its big brother dtp.
+# Meta category Processing doctools documents
+# Meta subject doctools doctoc docidx
+# Meta require {dtplite 1.0.5}
+# Meta author Andreas Kupries
+# Meta license BSD
+# @@ Meta End
+
+package require dtplite 1.0.5
+
+# dtp lite - Lightweight DocTools Processor
+# ======== = ==============================
+
+exit [dtplite::do $argv]
+
+# ### ### ### ######### ######### #########
+exit
diff --git a/tcllib/apps/dtplite.man b/tcllib/apps/dtplite.man
new file mode 100644
index 0000000..8f506e1
--- /dev/null
+++ b/tcllib/apps/dtplite.man
@@ -0,0 +1,447 @@
+[comment {-*- tcl -*- doctools manpage}]
+[manpage_begin dtplite n 1.0.5]
+[see_also {docidx introduction}]
+[see_also {doctoc introduction}]
+[see_also {doctools introduction}]
+[keywords conversion]
+[keywords docidx]
+[keywords doctoc]
+[keywords doctools]
+[keywords HTML]
+[keywords manpage]
+[keywords markup]
+[keywords nroff]
+[keywords TMML]
+[copyright {2004-2013 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[titledesc {Lightweight DocTools Markup Processor}]
+[moddesc {Documentation toolbox}]
+[category {Documentation tools}]
+[description]
+[para]
+
+The application described by this document, [syscmd dtplite], is the
+successor to the extremely simple [syscmd mpexpand]. Influenced in its
+functionality by the [syscmd dtp] doctools processor it is much more
+powerful than [syscmd mpexpand], yet still as easy to use; definitely
+easier than [syscmd dtp] with its myriad of subcommands and options.
+
+[para]
+
+[syscmd dtplite] is based upon the package [package doctools], like
+the other two processors.
+
+[subsection {USE CASES}]
+
+[syscmd dtplite] was written with the following three use cases in
+mind.
+
+[para]
+[list_begin enumerated]
+[enum]
+Validation of a single document, i.e. checking that it was written in
+valid doctools format. This mode can also be used to get a preliminary
+version of the formatted output for a single document, for display in
+a browser, nroff, etc., allowing proofreading of the formatting.
+
+[enum]
+Generation of the formatted documentation for a single package,
+i.e. all the manpages, plus a table of contents and an index of
+keywords.
+
+[enum]
+An extension of the previous mode of operation, a method for the easy
+generation of one documentation tree for several packages, and
+especially of a unified table of contents and keyword index.
+
+[list_end]
+
+[para]
+
+Beyond the above we also want to make use of the customization
+features provided by the HTML formatter. It is not the only format the
+application should be able to generate, but we anticipiate it to be
+the most commonly used, and it is one of the few which do provide
+customization hooks.
+
+[para]
+
+We allow the caller to specify a header string, footer string, a
+stylesheet, and data for a bar of navigation links at the top of the
+generated document.
+
+While all can be set as long as the formatting engine provides an
+appropriate engine parameter (See section [sectref OPTIONS]) the last
+two have internal processing which make them specific to HTML.
+
+[subsection {COMMAND LINE}]
+
+[list_begin definitions]
+
+[call [cmd dtplite] [option -o] [arg output] [opt options] [arg format] [arg inputfile]]
+
+This is the form for use case [lb]1[rb]. The [arg options] will be
+explained later, in section [sectref OPTIONS].
+
+[list_begin arguments]
+
+[arg_def path output in]
+
+This argument specifies where to write the generated document. It can
+be the path to a file or directory, or [const -].
+
+The last value causes the application to write the generated
+documented to [const stdout].
+
+[para]
+
+If the [arg output] does not exist then [lb]file dirname $output[rb]
+has to exist and must be a writable directory.
+
+The generated document will be written to a file in that directory,
+and the name of that file will be derived from the [arg inputfile],
+the [arg format], and the value given to option [option -ext] (if
+present).
+
+[arg_def (path|handle) format in]
+
+This argument specifies the formatting engine to use when processing
+the input, and thus the format of the generated document. See section
+[sectref FORMATS] for the possibilities recognized by the application.
+
+[arg_def path inputfile in]
+
+This argument specifies the path to the file to process. It has to
+exist, must be readable, and written in [term doctools] format.
+
+[list_end]
+[para]
+
+[call [cmd dtplite] [const validate] [arg inputfile]]
+
+This is a simpler form for use case [lb]1[rb]. The "validate" format
+generates no output at all, only syntax checks are performed. As such
+the specification of an output file or other options is not necessary
+and left out.
+
+[call [cmd dtplite] [option -o] [arg output] [opt options] [arg format] [arg inputdirectory]]
+
+This is the form for use case [lb]2[rb]. It differs from the form for
+use case [lb]1[rb] by having the input documents specified through a
+directory instead of a file. The other arguments are identical, except
+for [arg output], which now has to be the path to an existing and
+writable directory.
+
+[para]
+
+The input documents are all files in [arg inputdirectory] or any of
+its subdirectories which were recognized by [cmd fileutil::fileType]
+as containing text in [term doctools] format.
+
+[call [cmd dtplite] [option -merge] [option -o] [arg output] [opt options] [arg format] [arg inputdirectory]]
+
+This is the form for use case [lb]3[rb]. The only difference to the
+form for use case [lb]2[rb] is the additional option [option -merge].
+
+[para]
+
+Each such call will merge the generated documents coming from
+processing the input documents under [arg inputdirectory] or any of
+its subdirectories to the files under [arg output]. In this manner it
+is possible to incrementally build the unified documentation for any
+number of packages. Note that it is necessary to run through all the
+packages twice to get fully correct cross-references (for formats
+supporting them).
+
+[list_end]
+
+[subsection OPTIONS]
+
+This section describes all the options available to the user of the
+application, with
+
+the exception of the options [option -o] and [option -merge]. These
+two were described already, in section [sectref {COMMAND LINE}].
+
+[para]
+[list_begin options]
+[opt_def -exclude string]
+
+This option specifies an exclude (glob) pattern. Any files identified
+as manpages to process which match the exclude pattern are
+ignored. The option can be provided multiple times, each usage adding
+an additional pattern to the list of exclusions.
+
+[opt_def -ext string]
+
+If the name of an output file has to be derived from the name of an
+input file it will use the name of the [arg format] as the extension
+by default. This option here will override this however, forcing it to
+use [arg string] as the file extension. This option is ignored if the
+name of the output file is fully specified through option [option -o].
+
+[para]
+
+When used multiple times only the last definition is relevant.
+
+[opt_def -header file]
+
+This option can be used if and only if the selected [arg format]
+provides an engine parameter named "header". It takes the contents of
+the specified file and assign them to that parameter, for whatever use
+by the engine. The HTML engine will insert the text just after the tag
+[const <body>].
+
+If navigation buttons are present (see option [option -nav] below),
+then the HTML generated for them is appended to the header data
+originating here before the final assignment to the parameter.
+
+[para]
+
+When used multiple times only the last definition is relevant.
+
+[opt_def -footer file]
+
+Like [option -header], except that: Any navigation buttons are ignored,
+the corresponding required engine parameter is named "footer", and the
+data is inserted just before the tag [const </body>].
+
+[para]
+
+When used multiple times only the last definition is relevant.
+
+[opt_def -style file]
+
+This option can be used if and only if the selected [arg format]
+provides an engine parameter named "meta". When specified it will
+generate a piece of HTML code declaring the [arg file] as the
+stylesheet for the generated document and assign that to the
+parameter. The HTML engine will insert this inot the document, just
+after the tag [const <head>].
+
+[para]
+
+When processing an input directory the stylesheet file is copied into
+the output directory and the generated HTML will refer to the copy, to
+make the result more self-contained. When processing an input file we
+have no location to copy the stylesheet to and so just reference it as
+specified.
+
+[para]
+
+When used multiple times only the last definition is relevant.
+
+[opt_def -toc path]
+
+This option specifies a doctoc file to use for the table of contents
+instead of generating our own.
+
+[para]
+
+When used multiple times only the last definition is relevant.
+
+[opt_def -pre+toc "label path|text"]
+[opt_def -post+toc "label path|text"]
+
+This option specifies additional doctoc files (or texts) to use in
+the navigation bar.
+
+[para] Positioning and handling of multiple uses is like for options
+[option -prenav] and [option -postnav], see below.
+
+[opt_def -nav "label url"]
+[opt_def -prenav "label url"]
+
+Use this option to specify a navigation button with [arg label] to
+display and the [arg url] to link to. This option can be used if and
+only if the selected [arg format] provides an engine parameter named
+"header". The HTML generated for this is appended to whatever data we
+got from option [option -header] before it is inserted into the
+generated documents.
+
+[para]
+
+When used multiple times all definitions are collected and a
+navigation bar is created, with the first definition shown at the left
+edge and the last definition to the right.
+
+[para] The url can be relative. In that case it is assumed to be relative
+to the main files (TOC and Keyword index), and will be transformed for
+all others to still link properly.
+
+[opt_def -postnav "label url"]
+
+Use this option to specify a navigation button with [arg label] to
+display and the [arg url] to link to. This option can be used if and
+only if the selected [arg format] provides an engine parameter named
+"header". The HTML generated for this is appended to whatever data we
+got from option [option -header] before it is inserted into the
+generated documents.
+
+[para]
+
+When used multiple times all definitions are collected and a
+navigation bar is created, with the last definition shown at the right
+edge and the first definition to the left.
+
+[para] The url can be relative. In that case it is assumed to be relative
+to the main files (TOC and Keyword index), and will be transformed for
+all others to still link properly.
+
+[list_end]
+
+[subsection FORMATS]
+
+At first the [arg format] argument will be treated as a path to a tcl
+file containing the code for the requested formatting engine. The
+argument will be treated as the name of one of the predefined formats
+listed below if and only if the path does not exist.
+
+[para]
+
+[emph {Note a limitation}]: If treating the format as path to the tcl
+script implementing the engine was sucessful, then this script has to
+implement not only the engine API for doctools, i.e.
+
+[term doctools_api], but for [term doctoc_api] and [term docidx_api]
+as well. Otherwise the generation of a table of contents and of a
+keyword index will fail.
+
+[para]
+
+List of predefined formats, i.e. as provided by the
+package [package doctools]:
+
+[para]
+[list_begin definitions]
+
+[def [const nroff]]
+
+The processor generates *roff output, the standard format for unix
+manpages.
+
+[def [const html]]
+
+The processor generates HTML output, for usage in and display by web
+browsers. This engine is currently the only one providing the various
+engine parameters required for the additional customaization of the
+output.
+
+[def [const tmml]]
+
+The processor generates TMML output, the Tcl Manpage Markup Language,
+a derivative of XML.
+
+[def [const latex]]
+
+The processor generates LaTeX output.
+
+[def [const wiki]]
+
+The processor generates Wiki markup as understood by [syscmd wikit].
+
+[def [const list]]
+
+The processor extracts the information provided by [cmd manpage_begin].
+[see_also {docidx introduction}]
+[see_also {doctoc introduction}]
+[see_also {doctools introduction}]
+[keywords conversion]
+[keywords docidx]
+[keywords doctoc]
+[keywords doctools]
+[keywords HTML]
+[keywords manpage]
+[keywords markup]
+[keywords nroff]
+[keywords TMML]
+
+This format is used internally to extract the meta data from which
+both table of contents and keyword index are derived from.
+
+[def [const null]]
+
+The processor does not generate any output. This is equivalent to
+[const validate].
+
+[list_end]
+
+[subsection {DIRECTORY STRUCTURES}]
+
+In this section we describe the directory structures generated by the
+application under [arg output] when processing all documents in an
+[arg inputdirectory]. In other words, this is only relevant to the use
+cases [lb]2[rb] and [lb]3[rb].
+
+[list_begin definitions]
+
+[def "[lb]2[rb]"]
+
+The following directory structure is created when processing a single
+set of input documents. The file extension used is for output in
+HTML, but that is not relevant to the structure and was just used to
+have proper file names.
+
+[example {
+ output/
+ toc.html
+ index.html
+ files/
+ path/to/FOO.html
+}]
+
+The last line in the example shows the document
+generated for a file FOO located at
+
+[example {
+ inputdirectory/path/to/FOO
+}]
+
+[def "[lb]3[rb]"]
+
+When merging many packages into a unified set of documents the
+generated directory structure is a bit deeper:
+
+[example {
+ output
+ .toc
+ .idx
+ .tocdoc
+ .idxdoc
+ .xrf
+ toc.html
+ index.html
+ FOO1/
+ ...
+ FOO2/
+ toc.html
+ files/
+ path/to/BAR.html
+}]
+
+Each of the directories FOO1, ... contains the documents generated for
+the package FOO1, ... and follows the structure shown for use case
+[lb]2[rb]. The only exception is that there is no per-package index.
+
+[para]
+
+The files [file .toc], [file .idx], and [file .xrf] contain the
+internal status of the whole output and will be read and updated by
+the next invokation. Their contents will not be documented. Remove
+these files when all packages wanted for the output have been
+processed, i.e. when the output is complete.
+
+[para]
+
+The files [file .tocdoc], and [file .idxdoc], are intermediate files
+in doctoc and docidx markup, respectively, containing the main table
+of contents and keyword index for the set of documents before their
+conversion to the chosen output format.
+
+They are left in place, i.e. not deleted, to serve as demonstrations
+of doctoc and docidx markup.
+
+[list_end]
+
+[vset CATEGORY doctools]
+[include ../modules/doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/apps/nns b/tcllib/apps/nns
new file mode 100755
index 0000000..ccf58aa
--- /dev/null
+++ b/tcllib/apps/nns
@@ -0,0 +1,291 @@
+#! /usr/bin/env tclsh
+# -*- tcl -*-
+
+# @@ Meta Begin
+# Application nns 1.2
+# Meta platform tcl
+# Meta summary Nano Name Service Client
+# Meta description This application connects to a name service demon
+# Meta description and either registers a name with associated data
+# Meta description (until exit) or searches for entries matching a
+# Meta description glob pattern. Operations to identify client and
+# Meta description server are made available as well. It will survive
+# Meta description the loss of the nameserver and automatically reconnect
+# Meta description and continue when it comes back (bind and search).
+# Meta description
+# Meta subject {name service} client
+# Meta require {Tcl 8.4}
+# Meta require logger
+# Meta require nameserv::auto
+# Meta require struct::matrix
+# Meta author Andreas Kupries
+# Meta license BSD
+# @@ Meta End
+
+package provide nns 1.2
+
+# nns - Nano Name Service Client
+# === = ========================
+#
+# Use cases
+# ---------
+#
+# (1) Register something at a nano name service
+# (2) Query protocol and feature information.
+# (3) Provide application version, and protocol information
+# (4) Search service for entries matching a glob-pattern
+#
+# Command syntax
+# --------------
+#
+# (Ad 1) nns bind ?-host NAME|IP? ?-port PORT? name data
+# (Ad 2) nns ident ?-host NAME|IP? ?-port PORT?
+# (Ad 3) nns who
+# (Ad 4) nns search ?-host NAME|IP? ?-port PORT? ?-continuous? ?pattern?
+#
+# Register a name with data. If no port is specified the default
+# port 38573 is used to connect to it. If no host is specified
+# the default (localhost) is used to connect to it.
+
+# ### ### ### ######### ######### #########
+## Requirements
+
+lappend auto_path [file join [file dirname [file dirname \
+ [file normalize [info script]]]] modules]
+
+package require nameserv::auto 0.3 ;# Need auto-restoring search.
+package require struct::matrix
+
+logger::initNamespace ::nns
+namespace eval ::nns { log::setlevel info }
+
+# ### ### ### ######### ######### #########
+## Process application command line
+
+proc ::nns::ProcessCommandLine {} {
+ global argv
+ variable xcmd
+ variable xname
+ variable xdata
+ variable xpat *
+ variable xwatch 0
+
+ # Process the options, perform basic validation.
+
+ if {[llength $argv] < 1} Usage
+
+ set cmd [lindex $argv 0]
+ set argv [lrange $argv 1 end]
+
+ switch -exact -- $cmd {
+ bind - ident - who - search {set xcmd $cmd}
+ default Usage
+ }
+
+ while {[llength $argv]} {
+ set opt [lindex $argv 0]
+ if {![string match "-*" $opt]} break
+
+ switch -exact -- $opt {
+ -host {
+ if {$xcmd == "who"} Usage
+ if {[llength $argv] < 2} Usage
+
+ set host [lindex $argv 1]
+ set argv [lrange $argv 2 end]
+
+ nameserv::auto::configure -host $host
+ }
+ -port {
+ if {$xcmd == "who"} Usage
+ if {[llength $argv] < 2} Usage
+
+ # Todo: Check non-zero unsigned short integer
+ set port [lindex $argv 1]
+ set argv [lrange $argv 2 end]
+
+ nameserv::auto::configure -port $port
+ }
+ -continuous {
+ set xwatch 1
+ set argv [lrange $argv 1 end]
+ }
+ -debug {
+ # Undocumented. Activate the logger services provided
+ # by various packages.
+ logger::setlevel debug
+ set argv [lrange $argv 1 end]
+ }
+ default Usage
+ }
+ }
+
+ # Additional validation, and extraction of the non-option
+ # arguments. Of which this application has none.
+
+ switch -exact -- $xcmd {
+ bind {
+ if {[llength $argv] != 2} Usage
+ foreach {xname xdata} $argv break
+ }
+ search {
+ if {[llength $argv] > 1} Usage
+ if {[llength $argv] == 1} {
+ set xpat [lindex $argv 0]
+ }
+ }
+ who - ident {
+ if {[llength $argv] != 0} Usage
+ }
+ }
+ return
+}
+
+proc ::nns::Usage {{sfx {}}} {
+ global argv0 ; append argv0 $sfx
+ set blank [blank $argv0]
+ puts stderr "$argv0 wrong#args, expected: bind ?-host NAME|IP? ?-port PORT? NAME DATA"
+ puts stderr "$blank ident ?-host NAME|IP? ?-port PORT?"
+ puts stderr "$blank search ?-host NAME|IP? ?-port PORT? ?-continuous? ?PATTERN?"
+ puts stderr "$blank who"
+ exit 1
+}
+
+proc ::nns::ArgError {text} {
+ global argv0
+ puts stderr "$argv0: $text"
+ #puts $::errorInfo
+ exit 1
+}
+
+proc ::nns::blank {s} {
+ regsub -all -- {[^ ]} $s { } s
+ return $s
+}
+
+# ### ### ### ######### ######### #########
+
+proc ::nns::My {} {
+ # Quick access to format the identity of the name service the
+ # client talks to.
+ return "[nameserv::auto::cget -host] @[nameserv::auto::cget -port]"
+}
+
+proc ::nns::Connection {message args} {
+ # args = tag event details, ignored
+ log::info $message
+ return
+}
+
+proc ::nns::MonitorConnection {} {
+ uevent::bind nameserv lost-connection [list ::nns::Connection "Disconnected name service at [My]"]
+ uevent::bind nameserv re-connection [list ::nns::Connection "Reconnected2 name service at [My]"]
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Main
+
+proc ::nns::Do.bind {} {
+ global argv0
+ variable xname
+ variable xdata
+
+ MonitorConnection
+ log::info "Binding with name service at [My]: $xname = $xdata"
+ nameserv::auto::bind $xname $xdata
+
+ vwait ::forever
+ # Not reached.
+ return
+}
+
+proc ::nns::Do.ident {} {
+ set sp [nameserv::auto::server_protocol]
+ set sf [join [nameserv::auto::server_features] {, }]
+
+ if {[llength $sf] > 1} {
+ set sf [linsert $sf end-1 and]
+ }
+
+ puts "Server [My]"
+ puts " Protocol: $sp"
+ puts " Features: $sf"
+ return
+}
+
+proc ::nns::Do.search {} {
+ variable xpat
+ variable xwatch
+
+ struct::matrix M
+ M add columns 2
+
+ if {$xwatch} {
+ MonitorConnection
+ set contents [nameserv::auto::search -continuous $xpat]
+ $contents configure -command [list ::nns::Do.search.change $contents]
+
+ vwait ::forever
+ # Not reached.
+ } else {
+ Do.search.print [nameserv::auto::search $xpat]
+ }
+ return
+}
+
+proc ::nns::Do.search.change {res type response} {
+ # Ignoring the arguments, we simply print the full results every
+ # time.
+
+ if {$type eq "stop"} {
+ # Cannot happen for nameserv::auto client, we are free to panic.
+ $res destroy
+ log::critical {Bad event 'stop' <=> Lost connection, search closed}
+ return
+ }
+
+ # Clear screen ...
+ puts -nonewline stdout "\033\[H\033\[J"; # Home + Erase Down
+ flush stdout
+
+ ::nns::Do.search.print [$res getall]
+ return
+}
+
+proc ::nns::Do.search.print {contents} {
+ log::info "Searching at name service at [My]"
+
+ if {![llength $contents]} {
+ log info "Nothing found..."
+ return
+ }
+
+ catch {M delete rows [M rows]}
+ foreach {name data} $contents {
+ M add row [list $name $data]
+ }
+
+ foreach line [split [M format 2string] \n] { log::info $line }
+ return
+}
+
+proc ::nns::Do.who {} {
+ # FUTURE: access and print the metadata contained in ourselves.
+ global argv0
+ puts "$argv0 [package require nns] (Client Protocol [nameserv::auto::protocol])"
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Invoking the functionality.
+
+::nns::ProcessCommandLine
+if {[catch {
+ ::nns::Do.$::nns::xcmd
+} msg]} {
+ ::nns::ArgError $msg
+}
+
+# ### ### ### ######### ######### #########
+exit
diff --git a/tcllib/apps/nns.man b/tcllib/apps/nns.man
new file mode 100644
index 0000000..22363f1
--- /dev/null
+++ b/tcllib/apps/nns.man
@@ -0,0 +1,143 @@
+[comment {-*- tcl -*- doctools manpage}]
+[manpage_begin nns n 1.1]
+[see_also nameserv(n)]
+[see_also nameserv::common(n)]
+[keywords application]
+[keywords client]
+[keywords {name service}]
+[copyright {2007-2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Name service facility}]
+[titledesc {Name service facility, Commandline Client Application}]
+[category Networking]
+[description]
+[para]
+
+Please read [term {Name service facility, introduction}] first.
+
+[para]
+
+The application described by this document, [syscmd nns], is a simple
+command line client for the nano name service facility provided by the
+Tcllib packages [package nameserv], and [package nameserv::server].
+
+Beyond that the application's sources also serve as an example of how
+to use the client package [package nameserv]. All abilities of a
+client are covered, from configuration to registration of names to
+searching.
+
+[para]
+
+This name service facility has nothing to do with the Internet's
+[term {Domain Name System}], otherwise known as [term DNS]. If the
+reader is looking for a package dealing with that please see either of
+the packages [package dns] and [package resolv], both found in Tcllib
+too.
+
+[subsection {USE CASES}]
+
+[syscmd nns] was written with the following two main use cases in
+mind.
+
+[para]
+[list_begin enumerated]
+[enum]
+Registration of a name/data pair in the name service.
+
+[enum]
+Searching the name service for entries matching a glob pattern.
+
+[list_end]
+
+[para]
+
+Beyond the above we also want to be able to identify the client, and
+get information about the name service.
+
+[subsection {COMMAND LINE}]
+
+[list_begin definitions]
+
+[call [cmd nns] [method bind] \
+ [opt "[option -host] [arg host]"] \
+ [opt "[option -port] [arg port]"] \
+ [arg name] [arg data]]
+
+This form registers the [arg name]/[arg data] pair in the specified
+name service. In this form the command will [emph not] exit to keep
+the registration alive. The user has to kill it explicitly, either by
+sending a signal, or through the job-control facilities of the shell
+in use. It will especially survive the loss of the connection to the
+name service and reestablish the [arg name]/[arg data] pair when the
+connection is restored.
+
+[para]
+The options to specify the name service will be explained later, in
+section [sectref OPTIONS].
+
+[call [cmd nns] [method search] \
+ [opt "[option -host] [arg host]"] \
+ [opt "[option -port] [arg port]"] \
+ [opt [option -continuous]] \
+ [opt [arg pattern]]]
+
+This form searches the specified name service for entries matching the
+glob-[arg pattern] and prints them to stdout, with each entry on its
+own line. If no pattern is specified it defaults to [const *],
+matching everything.
+
+[para]
+The options to specify the name service will be explained later, in
+section [sectref OPTIONS].
+
+[para]
+
+If the option [option -continuous] is specified the client will not
+exit after performing the search, but start to continuously monitor
+the service for changes to the set of matching entries, appropriately
+updating the display as changes arrive. In that form it will
+especially also survive the loss of the connection to the name service
+and reestablish the search when the connection is restored.
+
+[call [cmd nns] [method ident] \
+ [opt "[option -host] [arg host]"] \
+ [opt "[option -port] [arg port]"]]
+
+This form asks the specified name service for the version and features
+of the name service protocol it supports and prints the results to
+stdout.
+
+[para]
+The options to specify the name service will be explained later, in
+section [sectref OPTIONS].
+
+[call [cmd nns] [method who]]
+
+This form prints name, version, and protocol version of the
+application to stdout.
+
+[list_end]
+
+[subsection OPTIONS]
+
+This section describes all the options available to the user of the
+application
+
+[para]
+[list_begin options]
+[opt_def -host name|ipaddress]
+
+If this option is not specified it defaults to [const localhost]. It
+specifies the name or ip-address of the host the name service to talk
+to is running on.
+
+[opt_def -port number]
+
+If this option is not specified it defaults to [const 38573]. It
+specifies the TCP port the name service to talk to is listening on for
+requests.
+
+[list_end]
+
+[vset CATEGORY nameserv]
+[include ../modules/doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/apps/nnsd b/tcllib/apps/nnsd
new file mode 100755
index 0000000..dd11233
--- /dev/null
+++ b/tcllib/apps/nnsd
@@ -0,0 +1,153 @@
+#! /usr/bin/env tclsh
+# -*- tcl -*-
+
+# @@ Meta Begin
+# Application nnsd 1.0.1
+# Meta platform tcl
+# Meta summary Nano Name Service Demon
+# Meta description This application is a simple demon on top
+# Meta description of the nano name service facilities
+# Meta subject {name service} server demon
+# Meta require {Tcl 8.4}
+# Meta require comm
+# Meta require logger
+# Meta require interp
+# Meta require nameserv::common
+# Meta require nameserv::server
+# Meta author Andreas Kupries
+# Meta license BSD
+# @@ Meta End
+
+package provide nnsd 1.0.1
+
+# nnsd - Nano Name Service Demon
+# ==== = =======================
+#
+# Use cases
+# ---------
+#
+# (1) Run a simple trusted name service on some host.
+#
+# Command syntax
+# --------------
+#
+# Ad 1) nnsd ?-localonly BOOL? ?-port PORT?
+#
+# Run the server. If no port is specified the default port 38573
+# is used to listen for client. The option -localonly determines
+# what connections are acceptable, local only (default), or
+# remote connections as well. Local connections are whose
+# originating from the same host which is running the server.
+# Remote connections come from other hosts.
+
+lappend auto_path [file join [file dirname [file dirname [file normalize [info script]]]] modules]
+
+package require nameserv::server
+
+namespace eval ::nnsd {}
+
+proc ::nnsd::ProcessCommandLine {} {
+ global argv
+
+ # Process the options, perform basic validation.
+
+ while {[llength $argv]} {
+ set opt [lindex $argv 0]
+ if {![string match "-*" $opt]} break
+
+ switch -exact -- $opt {
+ -localonly {
+ if {[llength $argv] % 2 == 1} Usage
+
+ # Todo: Check boolean
+ set local [lindex $argv 1]
+ set argv [lrange $argv 2 end]
+
+ nameserv::server::configure -localonly $local
+ }
+ -port {
+ if {[llength $argv] % 2 == 1} Usage
+
+ # Todo: Check non-zero unsigned short integer
+ set port [lindex $argv 1]
+ set argv [lrange $argv 2 end]
+
+ nameserv::server::configure -port $port
+ }
+ -debug {
+ # Undocumented. Activate the logger services provided
+ # by various packages.
+ logger::setlevel debug
+ set argv [lrange $argv 1 end]
+ }
+ default {
+ Usage
+ }
+ }
+ }
+
+ # Additional validation, and extraction of the non-option
+ # arguments. Of which this application has none.
+
+ if {[llength $argv]} Usage
+
+ return
+}
+
+proc ::nnsd::Usage {} {
+ global argv0
+ puts stderr "$argv0 wrong#args, expected:\
+ ?-localonly BOOL? ?-port PORT?"
+ exit 1
+}
+
+proc ::nnsd::ArgError {text} {
+ global argv0
+ puts stderr "$argv0: $text"
+ exit 1
+}
+
+proc bgerror {args} {
+ puts stderr $args
+ puts stderr $::errorInfo
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Main
+
+proc ::nnsd::Headline {} {
+ global argv0
+ set p [nameserv::server::cget -port]
+ set l [expr {[nameserv::server::cget -localonly]
+ ? "local only"
+ : "local & remote"}]
+
+ puts "$argv0 [package require nnsd], listening on $p ($l)"
+ return
+}
+
+proc ::nnsd::Do {} {
+ global argv0
+
+ ProcessCommandLine
+
+ nameserv::server::start
+ Headline
+
+ vwait forever
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Invoking the functionality.
+
+if {[catch {
+ ::nnsd::Do
+} msg]} {
+ puts $::errorInfo
+ #::nnsd::ArgError $msg
+}
+
+# ### ### ### ######### ######### #########
+exit
diff --git a/tcllib/apps/nnsd.man b/tcllib/apps/nnsd.man
new file mode 100644
index 0000000..28b139a
--- /dev/null
+++ b/tcllib/apps/nnsd.man
@@ -0,0 +1,91 @@
+[comment {-*- tcl -*- doctools manpage}]
+[manpage_begin nnsd n 1.0.1]
+[see_also nameserv::common(n)]
+[see_also nameserv::server(n)]
+[keywords application]
+[keywords {name service}]
+[keywords server]
+[copyright {2007-2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Name service facility}]
+[titledesc {Name service facility, Commandline Server Application}]
+[category Networking]
+[description]
+[para]
+
+Please read [term {Name service facility, introduction}] first.
+
+[para]
+
+The application described by this document, [syscmd nns], is a simple
+command line server for the nano name service facility provided by the
+Tcllib packages [package nameserv], and [package nameserv::server].
+
+Beyond that the application's sources also serve as an example of how
+to use the server package [package nameserv::server].
+
+[para]
+
+This name service facility has nothing to do with the Internet's
+[term {Domain Name System}], otherwise known as [term DNS]. If the
+reader is looking for a package dealing with that please see either of
+the packages [package dns] and [package resolv], both found in Tcllib
+too.
+
+[subsection {USE CASES}]
+
+[syscmd nnsd] was written with the following main use case in
+mind.
+
+[para]
+[list_begin enumerated]
+[enum]
+Run a nano name service on some host.
+
+[list_end]
+
+[para]
+
+[subsection {COMMAND LINE}]
+
+[list_begin definitions]
+
+[call [cmd nnsd] \
+ [opt "[option -localonly] [arg flag]"] \
+ [opt "[option -port] [arg port]"]]
+
+The command configures a server per the specified options and starts
+it. The command will not exit on its own, as it keeps the name service
+database wholly in memory. The user has to kill it explicitly, either
+by sending a a signal, or through the job-control facilities of the
+shell in use.
+
+[para]
+The options to configure the name service are explained in section
+[sectref OPTIONS].
+
+[list_end]
+
+[subsection OPTIONS]
+
+This section describes all the options available to the user of the
+application
+
+[para]
+[list_begin options]
+[opt_def -localonly bool]
+
+If this option is not specified it defaults to [const true], i.e.
+acceptance of only local connections. The server will accept remote
+connections, i.e. connections from other hosts, if and only if this
+option is configured to [const false].
+
+[opt_def -port number]
+
+If this option is not specified it defaults to [const 38573]. It
+specifies the TCP port the server has to listen on for requests.
+
+[list_end]
+
+[vset CATEGORY nameserv]
+[include ../modules/doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/apps/nnslog b/tcllib/apps/nnslog
new file mode 100755
index 0000000..87989eb
--- /dev/null
+++ b/tcllib/apps/nnslog
@@ -0,0 +1,182 @@
+#! /usr/bin/env tclsh
+# -*- tcl -*-
+
+# @@ Meta Begin
+# Application nnslog 1.1
+# Meta platform tcl
+# Meta summary Nano Name Service Logger
+# Meta description This application connects to a name service demon
+# Meta description and then continuously logs all changes (new/removed
+# Meta description definitions) to the standard output. It will survive
+# Meta description the loss of the nameserver and automatically reconnect
+# Meta description and continue when it comes back.
+# Meta subject {name service} client log
+# Meta require {Tcl 8.4}
+# Meta require logger
+# Meta require nameserv::auto
+# Meta author Andreas Kupries
+# Meta license BSD
+# @@ Meta End
+
+package provide nnslog 1.0
+
+# nns - Nano Name Service Logger
+# === = ========================
+#
+# Use cases
+# ---------
+#
+# (1) Continuously monitor a nameservice for changes.
+#
+# Command syntax
+# --------------
+#
+# (Ad 1) nnslog ?-host NAME|IP? ?-port PORT? ?-color BOOL?
+#
+# Monitor a name server. If no port is specified the default
+# port 38573 is used to connect to it. If no host is specified
+# the default (localhost) is used to connect to it.
+
+# ### ### ### ######### ######### #########
+## Requirements
+
+lappend auto_path [file join [file dirname [file dirname \
+ [file normalize [info script]]]] modules]
+
+package require nameserv::auto 0.3 ;# Need auto-restoring search.
+
+logger::initNamespace ::nnslog
+namespace eval ::nnslog { log::setlevel info }
+
+# ### ### ### ######### ######### #########
+## Process application command line
+
+proc ::nnslog::ProcessCommandLine {} {
+ global argv
+
+ # Process the options, perform basic validation.
+ set xcolor 0
+
+ if {[llength $argv] < 1} return
+
+ while {[llength $argv]} {
+ set opt [lindex $argv 0]
+ if {![string match "-*" $opt]} break
+
+ switch -exact -- $opt {
+ -host {
+ if {[llength $argv] < 2} Usage
+
+ set host [lindex $argv 1]
+ set argv [lrange $argv 2 end]
+
+ nameserv::configure -host $host
+ }
+ -port {
+ if {[llength $argv] < 2} Usage
+
+ # Todo: Check non-zero unsigned short integer
+ set port [lindex $argv 1]
+ set argv [lrange $argv 2 end]
+
+ nameserv::configure -port $port
+ }
+ -debug {
+ # Undocumented. Activate the logger services provided
+ # by various packages.
+ logger::setlevel debug
+ set argv [lrange $argv 1 end]
+ }
+ default Usage
+ }
+ }
+
+ # Additional validation. no arguments should be left over.
+ if {[llength $argv] > 1} Usage
+ return
+}
+
+proc ::nnslog::Usage {{sfx {}}} {
+ global argv0 ; append argv0 $sfx
+ puts stderr "$argv0 wrong#args, expected: ?-host NAME|IP? ?-port PORT?"
+ exit 1
+}
+
+proc ::nnslog::ArgError {text} {
+ global argv0
+ puts stderr "$argv0: $text"
+ #puts $::errorInfo
+ exit 1
+}
+
+# ### ### ### ######### ######### #########
+## Setup a text|graphical report
+
+proc ::nnslog::My {} {
+ # Quick access to format the identity of the name service the
+ # client talks to.
+ return "[nameserv::auto::cget -host] @[nameserv::auto::cget -port]"
+}
+
+proc ::nnslog::Connection {message args} {
+ # args = tag event details, ignored
+ log::info $message
+ return
+}
+
+proc ::nnslog::MonitorConnection {} {
+ uevent::bind nameserv lost-connection [list ::nnslog::Connection "Disconnected name service at [My]"]
+ uevent::bind nameserv re-connection [list ::nnslog::Connection "Reconnected2 name service at [My]"]
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Main
+
+proc ::nnslog::Do.search {} {
+ MonitorConnection
+ set contents [nameserv::auto::search -continuous *]
+ $contents configure -command [list ::nnslog::Do.search.change $contents]
+
+ log::info "Logging name service at [My]"
+ vwait ::forever
+ # Not reached.
+ return
+}
+
+namespace eval ::nnslog {
+ variable map
+ array set map {
+ add +++
+ remove ---
+ }
+}
+
+proc ::nnslog::Do.search.change {res type response} {
+ variable map
+
+ if {$type eq "stop"} {
+ # Cannot happen for nameserv::auto client, we are free to panic.
+ $res destroy
+ log::critical {Bad event 'stop' <=> Lost connection, search closed}
+ return
+ }
+ # Print events ...
+ foreach {name value} $response {
+ log::info "$map($type) : [list $name = $value]"
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Invoking the functionality.
+
+::nnslog::ProcessCommandLine
+if {[catch {
+ ::nnslog::Do.search
+} msg]} {
+ ::nnslog::ArgError $msg
+}
+
+# ### ### ### ######### ######### #########
+exit
diff --git a/tcllib/apps/nnslog.man b/tcllib/apps/nnslog.man
new file mode 100644
index 0000000..91d93d1
--- /dev/null
+++ b/tcllib/apps/nnslog.man
@@ -0,0 +1,93 @@
+[comment {-*- tcl -*- doctools manpage}]
+[manpage_begin nnslog n 1.0]
+[see_also nameserv(n)]
+[see_also nameserv::common(n)]
+[keywords application]
+[keywords client]
+[keywords {name service}]
+[copyright {2008 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {Name service facility}]
+[titledesc {Name service facility, Commandline Logging Client Application}]
+[category Networking]
+[description]
+[para]
+
+Please read [term {Name service facility, introduction}] first.
+
+[para]
+
+The application described by this document, [syscmd nnslog], is a
+simple command line client for the nano name service facility provided
+by the Tcllib packages [package nameserv], and [package nameserv::server].
+
+[para]
+
+It essentially implements "[syscmd nns] search -continuous *", but
+uses a different output formatting. Instead of continuously showing
+the current contents of the server in the terminal it simply logs all
+received add/remove events to [const stdout].
+
+[para]
+
+This name service facility has nothing to do with the Internet's
+[term {Domain Name System}], otherwise known as [term DNS]. If the
+reader is looking for a package dealing with that please see either of
+the packages [package dns] and [package resolv], both found in Tcllib
+too.
+
+[subsection {USE CASES}]
+
+[syscmd nnslog] was written with the following main use case in mind.
+
+[para]
+[list_begin enumerated]
+[enum]
+Monitoring the name service for all changes and logging them in a text
+terminal.
+[list_end]
+
+[para]
+
+[subsection {COMMAND LINE}]
+
+[list_begin definitions]
+[call [cmd nnslog] \
+ [opt "[option -host] [arg host]"] \
+ [opt "[option -port] [arg port]"]]
+
+The command connects to the specified name service, sets up a search
+for all changes and then prints all received events to stdout, with
+each events on its own line. The command will not exit until it is
+explicitly terminated by the user. It will especially survive the loss
+of the connection to the name service and reestablish the search and
+log when the connection is restored.
+
+[para]
+The options to specify the name service will be explained later, in
+section [sectref OPTIONS].
+
+[list_end]
+
+[subsection OPTIONS]
+
+This section describes all the options available to the user of the
+application
+
+[list_begin options]
+[opt_def -host name|ipaddress]
+
+If this option is not specified it defaults to [const localhost]. It
+specifies the name or ip-address of the host the name service to talk
+to is running on.
+
+[opt_def -port number]
+
+If this option is not specified it defaults to [const 38573]. It
+specifies the TCP port the name service to talk to is listening on for
+requests.
+
+[list_end]
+
+[vset CATEGORY nameserv]
+[include ../modules/doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/apps/page b/tcllib/apps/page
new file mode 100755
index 0000000..e8985fa
--- /dev/null
+++ b/tcllib/apps/page
@@ -0,0 +1,820 @@
+#! /usr/bin/env tclsh
+# -*- tcl -*-
+
+# @@ Meta Begin
+# Application page 1.0
+# Meta platform tcl
+# Meta summary Tool for general text transformation
+# Meta description While the name is an allusion to parser
+# Meta description generation, the modular plugin-based
+# Meta description nature of this application allows for
+# Meta description any type of text transformation which
+# Meta description can be put into a plugin. Still, the
+# Meta description plugins coming with Tcllib all deal
+# Meta description with parser generation.
+# Meta category Processing text files
+# Meta subject {parser generation} {text transformation}
+# Meta require page::pluginmgr
+# Meta require logger
+# Meta require struct::matrix
+# Meta author Andreas Kupries
+# Meta license BSD
+# @@ Meta End
+
+package provide page 1.0
+
+lappend auto_path [file join [lindex $tcl_pkgPath end] page]
+lappend auto_path [file join [file dirname [file dirname [file normalize [info script]]]] modules]
+
+#lappend auto_path [file join [file dirname [info script]] .. modules]
+#source [file join [file dirname [info script]] .. modules struct tree.tcl]
+
+# /=
+# $Id: page,v 1.3 2011/11/10 21:16:02 andreas_kupries Exp $
+# \=
+#
+# PAGE - PArser GEnerator | GTT - General Text Transformation
+# ==== = ================ + === = ===========================
+#
+# Use cases
+# ---------
+#
+# (1) Read a grammar specification and write out code implementing a
+# parser for that grammar.
+#
+# (2) As (1), and additionally allow the user to select between a
+# number of different backends for writing the results.
+# Different forms for the same parser, pretty printing the
+# grammar, different parser types (LL vs LR vs ...). Etc.
+#
+# (3) As (1) and/or (2), and additionally allow the user to select
+# the frontend, i.e. the part reading the grammar. This allows
+# the use of different input grammars for the specification of
+# grammars, i.e. PEG, Yacc, Tyacc, Coco, etc.
+#
+# Note: For grammars it may be possible to write a unifying
+# frontend whose reader grammar is able to recognize many
+# different grammar formats without requiring the user to
+# specify which format the supplied input is in.
+#
+# (4) As (1) and/or (2), and/or (3), and additionally allow the user
+# to select the transformations to execute on the data provided
+# by the frontend before it is given to the backend. At this
+# point the parser generator has transformed into a general tool
+# for the reading, transformation, and writing of any type of
+# structured information.
+#
+# Note: For the use cases from (1) to (3) the representations returned
+# by the frontend, and taken by the backend have to be fully
+# specified to ensure that all the parts are working together.
+# For the use case (4) it becomes the responsibility of the user
+# of the tool to specify frontend, backed, and transformations
+# which work properly together.
+
+# Command syntax
+# --------------
+#
+# Ad 1) page ?-rd peg|hb|ser? ?-gen tpcp|hb|ser|tree|peg|me|null? ?-min no|reach|use|all? [input|"-" [output|"-"]]
+#
+# The tool reads the grammar from the specified inputfile,
+# transforms it as needed and then writes the resulting parser
+# to the outputfile. Usage of "-" for the input signals that the
+# grammar should be read from stdin. Analoguously usage of "-"
+# for the output signals that the results should be written to
+# stdout.
+#
+# Unspecified parts of the command line default to "-".
+#
+# Ad 2) Not specified yet.
+# Ad 3) S.a.
+# Ad 4) S.a.
+
+# ### ### ### ######### ######### #########
+## Requisites
+
+package require page::pluginmgr ; # Management of the PAGE plugins.
+package require logger ; # Logging subsystem for debugging.
+package require struct::matrix ; # Matrices. For statistics report
+
+# ### ### ### ######### ######### #########
+## Internal data and status
+
+namespace eval ::page {
+ # Path to where the output goes to. The name of a file, or "-" for
+ # stdout.
+
+ variable output ""
+
+ # Path to where the input comes from. The name of a file, or "-"
+ # for stdin.
+
+ variable input ""
+
+ # Boolean flag. Input processing is timed.
+
+ variable timed 0
+
+ # Boolean flag. Input processing has progressbar.
+
+ variable progress 0
+
+ # Reader plugin and options.
+
+ variable rd {}
+
+ # List of transforms and their options.
+
+ variable tr {}
+
+ # Writer plugin an options.
+
+ variable wr {}
+
+ # ### ### ### ######### ######### #########
+
+ # Statistics.
+ # The number of characters read from the input.
+
+ variable nread 0
+
+ # Progress
+ # Counter for when to print progress notification.
+
+ variable ncount 0
+ variable ndelta 100
+
+ # Collected statistical output. A matrix object, for proper
+ # columnar formatting when generating the report. And the last
+ # non-empty string in the first column, to prevent repetition.
+
+ variable statistics {}
+ variable slast {}
+
+ # ### ### ### ######### ######### #########
+}
+
+# ### ### ### ######### ######### #########
+## External data and status
+
+# This tool does not use external files to save and load status
+# information. It has no history. If history is required, or data
+# beyond the regular input see use cases (2-4). These may allow the
+# specification of options specific to the selected frontend, backend,
+# and transformations.
+
+# ### ### ### ######### ######### #########
+## Option processing.
+## Validate command line.
+## Full command line syntax.
+##
+# page [input|"-" [output|"-"]]
+##
+
+proc ::page::ProcessCmdline {} {
+ global argv
+
+ variable output
+ variable input
+
+ set logging 0
+ set n [ProcessArguments]
+
+ # No options at all => Default -c peg.
+
+ if {!$n} {
+ set argv [linsert $argv 0 -c peg]
+ ProcessArguments
+ }
+
+ # Additional validation, and extraction of the non-option
+ # arguments.
+
+ if {[llength $argv] > 2} Usage
+
+ set input [lindex $argv 0]
+ set output [lindex $argv 1]
+
+ # Final validation across the whole configuration.
+
+ if {$input eq ""} {
+ set input -
+ } elseif {$input ne "-"} {
+ CheckInputFile $input {Input file}
+ }
+
+ if {$output eq ""} {
+ set output -
+ } elseif {$output ne "-"} {
+ CheckTheOutput
+ }
+
+ CheckReader
+ CheckWriter
+ CheckTransforms
+
+ if {$logging} {
+ pluginmgr::log [::logger::init page]
+ } else {
+ pluginmgr::log {}
+ }
+ return
+}
+
+proc ::page::ProcessArguments {} {
+ global argv
+ upvar 1 logging logging
+
+ variable rd {}
+ variable tr {}
+ variable wr {}
+ variable timed 0
+ variable progress 0
+
+ # Process the options, perform basic validation.
+
+ set type {}
+ set name {}
+ set options {}
+ set mode {}
+ set nextmode {}
+
+ set noptions 0
+
+ while {[llength $argv]} {
+ #puts ([join $argv ") ("])
+
+ set opt [lindex $argv 0]
+ if {![string match "-*" $opt]} {
+ # End of options reached.
+ break
+ }
+ incr noptions
+ Shift
+ switch -exact -- $opt {
+ --help - -h - -? {Usage}
+ --version - -V {Version}
+
+ -v - --verbose - --log {set logging 1}
+ -q - --quiet - --nolog {set logging 0}
+
+ -P {set progress 1}
+ -T {set timed 1}
+
+ -D {
+ # Activate logging in the safe base for better debugging.
+ ::safe::setLogCmd {puts stderr}
+ }
+
+ -r - -rd - --reader {
+ Complete
+ set type rd
+ set name [Shift]
+ set options {}
+ }
+ -w - -wr - --writer {
+ Complete
+ set type wr
+ set name [Shift]
+ set options {}
+ }
+ -t - -tr - --transform {
+ Complete
+ set type tr
+ set name [Shift]
+ if {$mode eq ""} {set mode tail}
+ set options {}
+ }
+ -c - --config {
+ set configfile [Shift]
+ if {($configfile eq "") || [catch {
+ set newargv [pluginmgr::configuration \
+ $configfile]
+ } msg]} {
+ set msg [string map {
+ {Unable to locate}
+ {Unable to locate configuration}} $msg]
+
+ ArgError "Bad argument \"$configfile\".\n\t$msg"
+ }
+
+ if {[llength $newargv]} {
+ if {![llength $argv]} {
+ set argv $newargv
+ } else {
+ # linsert argv 0 {expanded}newargv
+ # --------------
+ # linsert options 0 (linsert argv 0)
+
+ set argv [eval [linsert $newargv 0 linsert $argv 0]]
+ #set argv [linsert $argv 0 {expand}$options]
+ }
+ }
+ }
+ -p - --prepend {set nextmode head}
+ -a - --append {set nextmode tail}
+
+ --reset {Complete ; set tr {}}
+
+ default {
+ # All unknown options go into the
+ # configuration of the last plugin
+ # defined (-r, -w, -t)
+ lappend options $opt [Shift]
+ }
+ }
+ }
+
+ Complete
+ return $noptions
+}
+
+proc ::page::Shift {} {
+ upvar 1 argv argv
+ if {![llength $argv]} {return {}}
+ set first [lindex $argv 0]
+ set argv [lrange $argv 1 end]
+ return $first
+}
+
+proc ::page::Complete {} {
+ upvar 1 type type name name options options mode mode \
+ nextmode nextmode rd rd wr wr tr tr
+
+ #puts "$type $name ($options) \[$mode/$nextmode\]"
+
+ set currentmode $mode
+ if {$nextmode ne $mode} {
+ set mode $nextmode
+ }
+
+ if {$type eq ""} return
+
+ switch -exact -- $type {
+ rd {set rd [list $name $options]}
+ wr {set wr [list $name $options]}
+ tr {
+ if {$currentmode eq "tail"} {
+ lappend tr [list $name $options]
+ } else {
+ set tr [linsert $tr 0 [list $name $options]]
+ }
+ }
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Option processing.
+## Helpers: Generation of error messages.
+## I. General usage/help message.
+## II. Specific messages.
+#
+# Both write their messages to stderr and then
+# exit the application with status 1.
+##
+
+proc ::page::Usage {} {
+ global argv0
+ puts stderr "Expected $argv0 ?options? ?inputpath|- ?outputpath|-??"
+
+ puts stderr " --help, -h, -? This help"
+ puts stderr " --version, -V, Version information"
+ puts stderr " -v, --verbose, --log Activate logging in all loaded plugins"
+ puts stderr " -q, --quiet, --nolog Disable logging in all loaded plugins"
+ puts stderr " -P Activate progress feedback"
+ puts stderr " -T Activate collection of timings"
+ puts stderr " -r reader Specify input plugin"
+ puts stderr " -rd, --reader See above"
+ puts stderr " -w writer Specify output plugin"
+ puts stderr " -wr, --writer See above"
+ puts stderr " -t transform Specify processing plugin"
+ puts stderr " -tr, --transform See above"
+ puts stderr " -p, --prepend Place processing at front"
+ puts stderr " -a, --append Place processing at end"
+ puts stderr " --reset Clear list of transforms"
+ puts stderr " -c file Read configuration file"
+ puts stderr " --configuration See above."
+ puts stderr " "
+
+ # --log, --nolog, -v, --verbose, -q, --quiet
+
+ exit 1
+}
+
+proc ::page::Version {} {
+ puts stderr {$Id: page,v 1.3 2011/11/10 21:16:02 andreas_kupries Exp $}
+ exit 1
+}
+
+proc ::page::ArgError {text} {
+ global argv0
+ puts stderr "$argv0: $text"
+ exit 1
+}
+
+proc in {list item} {
+ expr {([lsearch -exact $list $item] >= 0)}
+}
+
+# ### ### ### ######### ######### #########
+## Check existence and permissions of an input/output file
+
+proc ::page::CheckReader {} {
+ variable rd
+
+ if {![llength $rd]} {
+ ArgError "Input processing module is missing"
+ }
+
+ foreach {name options} $rd break
+
+ if {[catch {
+ set po [pluginmgr::reader $name]
+ } msg]} {
+ set msg [string map {
+ {Unable to locate}
+ {Unable to locate reader}} $msg]
+
+ ArgError "Bad argument \"$name\".\n\t$msg"
+ }
+
+ set opt {}
+ foreach {k v} $options {
+ if {![in $po $k]} {
+ ArgError "Input plugin $name: Bad option $k"
+ }
+ lappend opt $k $v
+ }
+
+ pluginmgr::rconfigure $opt
+ return
+}
+
+proc ::page::CheckWriter {} {
+ variable wr
+
+ if {![llength $wr]} {
+ ArgError "Output module is missing"
+ }
+
+ foreach {name options} $wr break
+
+ if {[catch {
+ set po [pluginmgr::writer $name]
+ } msg]} {
+ set msg [string map {
+ {Unable to locate}
+ {Unable to locate writer}} $msg]
+
+ ArgError "Bad argument \"$name\".\n\t$msg"
+ }
+
+ set opt {}
+ foreach {k v} $options {
+ if {![in $po $k]} {
+ ArgError "Output plugin $name: Bad option $k"
+ }
+ lappend opt $k $v
+ }
+
+ pluginmgr::wconfigure $opt
+ return
+}
+
+proc ::page::CheckTransforms {} {
+ variable tr
+
+ set idlist {}
+ foreach t $tr {
+ foreach {name options} $t break
+
+ if {[catch {
+ foreach {id po} \
+ [pluginmgr::transform $name] \
+ break
+ } msg]} {
+ set msg [string map {
+ {Unable to locate}
+ {Unable to locate transformation}} $msg]
+
+ ArgError "Bad argument \"$name\".\n\t$msg"
+ }
+
+ set opt {}
+ foreach {k v} $options {
+ if {![in $po $k]} {
+ ArgError "Processing plugin $name: Bad option $k"
+ }
+ lappend opt $k $v
+ }
+
+ pluginmgr::tconfigure $id $opt
+ lappend idlist $id
+ }
+
+ set tr $idlist
+ return
+}
+
+proc ::page::CheckInputFile {f label} {
+ if {![file exists $f]} {
+ ArgError "Unable to find $label \"$f\""
+ } elseif {![file isfile $f]} {
+ ArgError "$label \"$f\" is not a file"
+ } elseif {![file readable $f]} {
+ ArgError "$label \"$f\" not readable (permission denied)"
+ }
+ return
+}
+
+proc ::page::CheckTheOutput {} {
+ variable output
+
+ set base [file dirname $output]
+ if {$base eq ""} {set base [pwd]}
+
+ if {![file exists $output]} {
+ if {![file exists $base]} {
+ ArgError "Output base path \"$base\" not found"
+ }
+ if {![file writable $base]} {
+ ArgError "Output base path \"$base\" not writable (permission denied)"
+ }
+ } elseif {![file writable $output]} {
+ ArgError "Output path \"$output\" not writable (permission denied)"
+ } elseif {![file isfile $output]} {
+ ArgError "Output path \"$output\" is not a file"
+ }
+
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Commands implementing the main functionality.
+
+proc ::page::Read {} {
+ variable input
+ variable progress
+ variable timed
+ variable nread
+
+ set label \[[pluginmgr::rlabel]\]
+ set msg ""
+ append msg $label " "
+
+ if {$input eq "-"} {
+ append msg {Reading grammar from stdin}
+ set chan stdin
+ } else {
+ append msg {Reading grammar from file "} $input {"}
+ set chan [open $input r]
+ }
+
+ pluginmgr::report info $msg
+
+ if {!$timed && !$progress} {
+ # Regular run
+ set data [pluginmgr::read \
+ [list read $chan] [list eof $chan]]
+
+ } elseif {$timed && $progress} {
+ # Timed, with feedback
+ if {[pluginmgr::rtimeable]} {
+ pluginmgr::rtime
+ set data [pluginmgr::read \
+ [list ::page::ReadPT $chan] [list eof $chan] \
+ ::page::ReadComplete]
+ set usec [pluginmgr::rgettime]
+ } else {
+ set usec [lindex [time {
+ set data [pluginmgr::read \
+ [list ::page::ReadPT $chan] [list eof $chan] \
+ ::page::ReadComplete]
+ }] 0] ; # {}
+ }
+ } elseif {$timed} {
+ # Timed only
+ if {[pluginmgr::rtimeable]} {
+ pluginmgr::rtime
+ set data [pluginmgr::read \
+ [list ::page::ReadT $chan] [list eof $chan]]
+ set usec [pluginmgr::rgettime]
+ } else {
+ set usec [lindex [time {
+ set data [pluginmgr::read \
+ [list ::page::ReadT $chan] [list eof $chan]]
+ }] 0] ; # {}
+ }
+ } else {
+ # Feedback only ...
+ set data [pluginmgr::read \
+ [list ::page::ReadPT $chan] [list eof $chan] \
+ ::page::ReadComplete]
+ }
+
+ if {$input ne "-"} {
+ close $chan
+ }
+
+ if {$timed} {
+ Statistics $label "Characters:" $nread
+ Statistics $label "Seconds:" [expr {double($usec)/1000000}]
+ Statistics $label "Char/Seconds:" [expr {1000000*double($nread)/$usec}]
+ Statistics $label "Microseconds:" $usec
+ Statistics $label "Microsec/Char:" [expr {$usec/double($nread)}]
+ } elseif {$progress} {
+ pluginmgr::report info " Read $nread [expr {$nread == 1 ? "character" : "characters"}]"
+ }
+ return $data
+}
+
+proc ::page::Transform {data} {
+ variable timed
+ variable tr
+
+ if {$data eq ""} {return $data}
+
+ if 0 {
+ pluginmgr::report info ----------------------------
+ foreach tid $tr {
+ set label "\[[pluginmgr::tlabel $tid]\]"
+ pluginmgr::report info $label
+ }
+ pluginmgr::report info ----------------------------
+ }
+
+ #puts /($data)/
+
+ foreach tid $tr {
+ set label "\[[pluginmgr::tlabel $tid]\]"
+
+ pluginmgr::report info $label
+
+ if {!$timed} {
+ set data [pluginmgr::transform_do $tid $data]
+ } else {
+ if {[pluginmgr::ttimeable $tid]} {
+ pluginmgr::ttime $tid
+ set data [pluginmgr::transform_do $tid $data]
+ set usec [pluginmgr::tgettime $tid]
+ } else {
+ set usec [lindex [time {
+ set data [pluginmgr::transform_do $tid $data]
+ }] 0]; #{}
+ }
+ Statistics $label Seconds: [expr {double($usec)/1000000}]
+ }
+ }
+ return $data
+}
+
+proc ::page::Write {data} {
+ variable timed
+ variable output
+
+ if {$data eq ""} {return $data}
+
+ set label \[[pluginmgr::wlabel]\]
+ set msg ""
+ append msg $label " "
+
+ if {$output eq "-"} {
+ append msg {Writing to stdout}
+ set chan stdout
+ } else {
+ append msg {Writing to file "} $output {"}
+ set chan [open $output w]
+ }
+
+ pluginmgr::report info $msg
+
+ if {!$timed} {
+ pluginmgr::write $chan $data
+ } else {
+ if {[pluginmgr::wtimeable]} {
+ pluginmgr::wtime
+ pluginmgr::write $chan $data
+ set usec [pluginmgr::wgettime]
+ } else {
+ set usec [lindex [time {
+ pluginmgr::write $chan $data
+ }] 0]; #{}
+ }
+ Statistics $label Seconds: [expr {double($usec)/1000000}]
+ }
+
+ if {$output ne "-"} {
+ close $chan
+ }
+ return
+}
+
+proc ::page::StatisticsBegin {} {
+ variable timed
+ variable statistics
+ if {!$timed} return
+
+ set statistics [struct::matrix ::page::STAT]
+
+ Statistics _Statistics_________
+ return
+}
+
+proc ::page::Statistics {module args} {
+ variable statistics
+ variable slast
+
+ set n [expr {1+[llength $args]}]
+
+ if {[$statistics columns] < $n} {
+ $statistics add columns [expr {
+ $n - [$statistics columns]
+ }] ; # {}
+ }
+
+ if {$module eq $slast} {
+ set prefix ""
+ } else {
+ set prefix $module
+ set slast $module
+ }
+
+ $statistics add row [linsert $args 0 $prefix]
+ return
+}
+
+proc ::page::StatisticsComplete {} {
+ variable timed
+ variable statistics
+ if {!$timed} return
+
+ pluginmgr::report info ""
+ foreach line [split [$statistics \
+ format 2string] \n] {
+ pluginmgr::report info $line
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Helper commands.
+
+proc ::page::ReadPT {chan {n {}}} {
+ variable nread
+ variable ncount
+ variable ndelta
+
+ if {$n eq ""} {
+ set data [read $chan]
+ } else {
+ set data [read $chan $n]
+ }
+
+ set n [string length $data]
+ incr nread $n
+
+ while {$ncount < $nread} {
+ puts -nonewline stderr .
+ flush stderr
+ incr ncount $ndelta
+ }
+
+ return $data
+}
+
+proc ::page::ReadComplete {} {
+ puts stderr ""
+ flush stderr
+ return
+}
+
+proc ::page::ReadT {chan {n {}}} {
+ variable nread
+
+ if {$n eq ""} {
+ set data [read $chan]
+ } else {
+ set data [read $chan $n]
+ }
+
+ set n [string length $data]
+ incr nread $n
+
+ return $data
+}
+
+# ### ### ### ######### ######### #########
+## Invoking the functionality.
+
+if {[catch {
+ ::page::ProcessCmdline
+ ::page::StatisticsBegin
+ ::page::Write [::page::Transform [::page::Read]]
+ ::page::StatisticsComplete
+} msg]} {
+ puts $::errorInfo
+ #::page::ArgError $msg
+}
+
+# ### ### ### ######### ######### #########
+exit
diff --git a/tcllib/apps/page.man b/tcllib/apps/page.man
new file mode 100644
index 0000000..bae424d
--- /dev/null
+++ b/tcllib/apps/page.man
@@ -0,0 +1,467 @@
+[comment {-*- tcl -*- doctools manpage}]
+[manpage_begin page n 1.0]
+[see_also page::pluginmgr]
+[keywords {parser generator}]
+[keywords {text processing}]
+[copyright {2005 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[titledesc {Parser Generator}]
+[moddesc {Development Tools}]
+[category {Page Parser Generator}]
+[description]
+[para]
+
+The application described by this document, [syscmd page], is actually
+not just a parser generator, as the name implies, but a generic tool
+for the execution of arbitrary transformations on texts.
+
+[para]
+
+Its genericity comes through the use of [term plugins] for reading,
+transforming, and writing data, and the predefined set of plugins
+provided by Tcllib is for the generation of memoizing recursive
+descent parsers (aka [term {packrat parsers}]) from grammar
+specifications ([term {Parsing Expression Grammars}]).
+
+[para]
+
+[syscmd page] is written on top of the package
+
+[package page::pluginmgr], wrapping its functionality into a command
+line based application. All the other [package page::*] packages are
+plugin and/or supporting packages for the generation of parsers. The
+parsers themselves are based on the packages [package grammar::peg],
+[package grammar::peg::interp], and [package grammar::mengine].
+
+[subsection {COMMAND LINE}]
+
+[list_begin definitions]
+
+[call [cmd page] [opt [arg options]...] [opt "[arg input] [opt [arg output]]"]]
+
+This is general form for calling [syscmd page]. The application will
+read the contents of the file [arg input], process them under the
+control of the specified [arg options], and then write the result to
+the file [arg output].
+
+[para]
+
+If [arg input] is the string [const -] the data to process will be
+read from [const stdin] instead of a file. Analogously the result will
+be written to [const stdout] instead of a file if [arg output] is the
+string [const -]. A missing output or input specification causes the
+application to assume [const -].
+
+[para]
+
+The detailed specifications of the recognized [arg options] are
+provided in section [sectref OPTIONS].
+
+[list_begin arguments]
+[arg_def path input in]
+
+This argument specifies the path to the file to be processed by the
+application, or [const -]. The last value causes the application to
+read the text from [const stdin]. Otherwise it has to exist, and be
+readable. If the argument is missing [const -] is assumed.
+
+[arg_def path output in]
+
+This argument specifies where to write the generated text. It can be
+the path to a file, or [const -]. The last value causes the
+application to write the generated documented to [const stdout].
+
+[para]
+
+If the file [arg output] does not exist then
+[lb]file dirname $output[rb] has to exist and must be a writable
+directory, as the application will create the fileto write to.
+
+[para]
+
+If the argument is missing [const -] is assumed.
+
+[list_end]
+[list_end]
+
+[subsection OPERATION]
+
+... reading ... transforming ... writing - plugins - pipeline ...
+
+[subsection OPTIONS]
+
+This section describes all the options available to the user of the
+application. Options are always processed in order. I.e. of both
+[option --help] and [option --version] are specified the option
+encountered first has precedence.
+
+[para]
+
+Unknown options specified before any of the options [option -rd],
+[option -wr], or [option -tr] will cause processing to abort with an
+error. Unknown options coming in between these options, or after the
+last of them are assumed to always take a single argument and are
+associated with the last plugin option coming before them. They will
+be checked after all the relevant plugins, and thus the options they
+understand, are known. I.e. such unknown options cause error if and
+only if the plugin option they are associated with does not understand
+them, and was not superceded by a plugin option coming after.
+
+[para]
+
+Default options are used if and only if the command line did not
+contain any options at all. They will set the application up as a
+PEG-based parser generator. The exact list of options is
+
+[para]
+[example {-c peg}]
+[para]
+
+And now the recognized options and their arguments, if they have any:
+
+[para]
+[list_begin options]
+
+[opt_def --help]
+[opt_def -h]
+[opt_def -?]
+
+When one of these options is found on the command line all arguments
+coming before or after are ignored. The application will print a short
+description of the recognized options and exit.
+
+[opt_def --version]
+[opt_def -V]
+
+When one of these options is found on the command line all arguments
+coming before or after are ignored. The application will print its
+own revision and exit.
+
+[opt_def -P]
+
+This option signals the application to activate visual feedback while
+reading the input.
+
+[opt_def -T]
+
+This option signals the application to collect statistics while
+reading the input and to print them after reading has completed,
+before processing started.
+
+[opt_def -D]
+
+This option signals the application to activate logging in the Safe
+base, for the debugging of problems with plugins.
+
+[opt_def -r parser]
+[opt_def -rd parser]
+[opt_def --reader parser]
+
+These options specify the plugin the application has to use for
+reading the [arg input]. If the options are used multiple times the
+last one will be used.
+
+[opt_def -w generator]
+[opt_def -wr generator]
+[opt_def --writer generator]
+
+These options specify the plugin the application has to use for
+generating and writing the final [arg output]. If the options are used
+multiple times the last one will be used.
+
+[opt_def -t process]
+[opt_def -tr process]
+[opt_def --transform process]
+
+These options specify a plugin to run on the input. In contrast to
+readers and writers each use will [emph not] supersede previous
+uses, but add each chosen plugin to a list of transformations, either
+at the front, or the end, per the last seen use of either option
+[option -p] or [option -a]. The initial default is to append the new
+transformations.
+
+[opt_def -a]
+[opt_def --append]
+
+These options signal the application that all following
+transformations should be added at the end of the list of
+transformations.
+
+[opt_def -p]
+[opt_def --prepend]
+
+These options signal the application that all following
+transformations should be added at the beginning of the list of
+transformations.
+
+[opt_def --reset]
+
+This option signals the application to clear the list of
+transformations. This is necessary to wipe out the default
+transformations used.
+
+[opt_def -c file]
+[opt_def --configuration file]
+
+This option causes the application to load a configuration file and/or
+plugin. This is a plugin which in essence provides a pre-defined set
+of commandline options. They are processed exactly as if they have
+been specified in place of the option and its arguments. This means
+that unknown options found at the beginning of the configuration file
+are associated with the last plugin, even if that plugin was specified
+before the configuration file itself. Conversely, unknown options
+coming after the configuration file can be associated with a plugin
+specified in the file.
+
+[para]
+
+If the argument is a file which cannot be loaded as a plugin the
+application will assume that its contents are a list of options and
+their arguments, separated by space, tabs, and newlines. Options and
+argumentes containing spaces can be quoted via double-quotes (") and
+quotes ('). The quote character can be specified within in a quoted
+string by doubling it. Newlines in a quoted string are accepted as is.
+
+[comment {"}]
+[list_end]
+
+[subsection PLUGINS]
+
+[syscmd page] makes use of four different types of plugins, namely:
+readers, writers, transformations, and configurations. Here we provide
+only a basic introduction on how to use them from [syscmd page]. The
+exact APIs provided to and expected from the plugins can be found in
+the documentation for [package page::pluginmgr], for those who wish to
+write their own plugins.
+
+[para]
+
+Plugins are specified as arguments to the options [option -r],
+[option -w], [option -t], [option -c], and their equivalent longer
+forms. See the section [sectref OPTIONS] for reference.
+
+[para]
+
+Each such argument will be first treated as the name of a file and
+this file is loaded as the plugin. If however there is no file with
+that name, then it will be translated into the name of a package, and
+this package is then loaded. For each type of plugins the package
+management searches not only the regular paths, but a set application-
+and type-specific paths as well. Please see the section
+[sectref {PLUGIN LOCATIONS}] for a listing of all paths and their
+sources.
+
+[para]
+
+[list_begin definitions]
+[def "[option -c] [arg name]"]
+
+Configurations. The name of the package for the plugin [arg name] is
+"page::config::[arg name]".
+
+[para]
+We have one predefined plugin:
+
+[list_begin definitions]
+[def [emph peg]]
+
+It sets the application up as a parser generator accepting parsing
+expression grammars and writing a packrat parser in Tcl. The actual
+arguments it specifies are:
+
+[para]
+[example {
+ --reset
+ --append
+ --reader peg
+ --transform reach
+ --transform use
+ --writer me
+}]
+[para]
+
+[list_end]
+
+[def "[option -r] [arg name]"]
+
+Readers. The name of the package for the plugin [arg name] is
+"page::reader::[arg name]".
+
+[para]
+We have five predefined plugins:
+
+[list_begin definitions]
+[def [emph peg]]
+
+Interprets the input as a parsing expression grammar ([term PEG]) and
+generates a tree representation for it. Both the syntax of PEGs and
+the structure of the tree representation are explained in their own
+manpages.
+
+[def [emph hb]]
+
+Interprets the input as Tcl code as generated by the writer plugin
+[emph hb] and generates its tree representation.
+
+[def [emph ser]]
+
+Interprets the input as the serialization of a PEG, as generated by
+the writer plugin [emph ser], using the package
+[package grammar::peg].
+
+[def [emph lemon]]
+
+Interprets the input as a grammar specification as understood by
+Richard Hipp's [term LEMON] parser generator and generates a tree
+representation for it. Both the input syntax and the structure of the
+tree representation are explained in their own manpages.
+
+[def [emph treeser]]
+
+Interprets the input as the serialization of a
+[package struct::tree]. It is validated as such,
+but nothing else. It is [emph not] assumed to
+be the tree representation of a grammar.
+[list_end]
+
+[def "[option -w] [arg name]"]
+
+Writers. The name of the package for the plugin [arg name] is
+"page::writer::[arg name]".
+
+[para]
+We have eight predefined plugins:
+
+[list_begin definitions]
+
+[def [emph identity]]
+
+Simply writes the incoming data as it is, without making any
+changes. This is good for inspecting the raw result of a reader or
+transformation.
+
+[def [emph null]]
+
+Generates nothing, and ignores the incoming data structure.
+
+[def [emph tree]]
+
+Assumes that the incoming data structure is a [package struct::tree]
+and generates an indented textual representation of all nodes, their
+parental relationships, and their attribute information.
+
+[def [emph peg]]
+
+Assumes that the incoming data structure is a tree representation of a
+[term PEG] or other other grammar and writes it out as a PEG. The
+result is nicely formatted and partially simplified (strings as
+sequences of characters). A pretty printer in essence, but can also be
+used to obtain a canonical representation of the input grammar.
+
+[def [emph tpc]]
+
+Assumes that the incoming data structure is a tree representation of a
+[term PEG] or other other grammar and writes out Tcl code defining a
+package which defines a [package grammar::peg] object containing the
+grammar when it is loaded into an interpreter.
+
+[def [emph hb]]
+
+This is like the writer plugin [emph tpc], but it writes only the
+statements which define stat expression and grammar rules. The code
+making the result a package is left out.
+
+[def [emph ser]]
+
+Assumes that the incoming data structure is a tree representation of a
+[term PEG] or other other grammar, transforms it internally into a
+[package grammar::peg] object and writes out its serialization.
+
+[def [emph me]]
+
+Assumes that the incoming data structure is a tree representation of a
+[term PEG] or other other grammar and writes out Tcl code defining a
+package which implements a memoizing recursive descent parser based on
+the match engine (ME) provided by the package [package grammar::mengine].
+
+[list_end]
+
+[def "[option -t] [arg name]"]
+
+Transformers. The name of the package for the plugin [arg name] is
+"page::transform::[arg name]".
+
+[para]
+We have two predefined plugins:
+
+[list_begin definitions]
+[def [emph reach]]
+
+Assumes that the incoming data structure is a tree representation of a
+[term PEG] or other other grammar. It determines which nonterminal
+symbols and rules are reachable from start-symbol/expression. All
+nonterminal symbols which were not reached are removed.
+
+[def [emph use]]
+
+Assumes that the incoming data structure is a tree representation of a
+[term PEG] or other other grammar. It determines which nonterminal
+symbols and rules are able to generate a [emph finite] sequences of
+terminal symbols (in the sense for a Context Free Grammar). All
+nonterminal symbols which were not deemed useful in this sense are
+removed.
+
+[list_end]
+[list_end]
+
+[subsection {PLUGIN LOCATIONS}]
+
+The application-specific paths searched by [syscmd page] either are,
+or come from:
+
+[para]
+
+[list_begin enumerated]
+[enum] The directory [file ~/.page/plugin]
+[enum] The environment variable [term PAGE_PLUGINS]
+[enum] The registry entry [term "HKEY_LOCAL_MACHINE\\SOFTWARE\\PAGE\\PLUGINS"]
+[enum] The registry entry [term "HKEY_CURRENT_USER\\SOFTWARE\\PAGE\\PLUGINS"]
+[list_end]
+
+[para]
+
+The type-specific paths searched by [syscmd page] either are, or come
+from:
+
+[para]
+[list_begin enumerated]
+[enum] The directory [file ~/.page/plugin/<TYPE>]
+[enum] The environment variable [term PAGE_<TYPE>_PLUGINS]
+[enum] The registry entry [term "HKEY_LOCAL_MACHINE\\SOFTWARE\\PAGE\\<TYPE>\\PLUGINS"]
+[enum] The registry entry [term "HKEY_CURRENT_USER\\SOFTWARE\\PAGE\\<TYPE>\\PLUGINS"]
+[list_end]
+
+[para]
+
+Where the placeholder [term <TYPE>] is always one of the values below,
+properly capitalized.
+
+[list_begin enumerated]
+[enum] reader
+[enum] writer
+[enum] transform
+[enum] config
+[list_end]
+[para]
+
+The registry entries are specific to the Windows(tm) platform, all
+other platforms will ignore them.
+
+[para]
+
+The contents of both environment variables and registry entries are
+interpreted as a list of paths, with the elements separated by either
+colon (Unix), or semicolon (Windows).
+
+[vset CATEGORY page]
+[include ../modules/doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/apps/pt b/tcllib/apps/pt
new file mode 100755
index 0000000..7389604
--- /dev/null
+++ b/tcllib/apps/pt
@@ -0,0 +1,156 @@
+#!/usr/bin/env tclsh
+# -*- tcl -*-
+
+package require Tcl 8.5
+# activate commands below for execution from within the pt directory
+set self [file normalize [info script]]
+set selfdir [file dirname $self]
+lappend auto_path $selfdir [file dirname $selfdir]
+# When debugging package loading trouble, show the search paths
+#puts [join $auto_path \n]
+
+# # ## ### ##### ######## ############# #####################
+
+package require pt::pgen 1.0.3
+package require pt::util
+package require fileutil
+package require try
+
+namespace eval ::pt::app {
+ namespace export generate help
+ namespace ensemble create
+}
+
+# # ## ### ##### ######## ############# #####################
+
+proc main {} {
+ global argv argv0 errorInfo
+ if {![llength $argv]} { lappend argv help }
+ if {[catch {
+ set status [::pt::app {*}$argv]
+ } msg]} {
+ set elines [split $errorInfo \n]
+ if {[llength $elines] == 3} {
+ if {[string match *unknown* $msg]} {
+ #puts stderr "$argv0 $msg"
+ ::pt::app help
+ exit 1
+ } elseif {[string match {*wrong # args*} $msg]} {
+ #puts $msg
+ # Extracting the command name from the error message,
+ # because there a prefix will have been expanded to
+ # the actual command. <lindex argv 0> OTOH would be a
+ # possible prefix, without a properly matching topic.
+ puts stderr Usage:
+ ::pt::app help [lindex $msg 5 1]
+ exit 1
+ }
+ }
+ set prefix {INTERNAL ERROR :: }
+ puts ${prefix}[join $elines \n$prefix]
+ exit 1
+ }
+ exit $status
+}
+
+# # ## ### ##### ######## ############# #####################
+
+proc ::pt::app::helpHelp {} {
+ return {
+ @ help ?TOPIC?
+
+ Provides general help, or specific to the given topic.
+ }
+}
+proc ::pt::app::help {{topic {}}} {
+ global argv0
+ if {[llength [info level 0]] == 1} {
+ puts stderr "Usage: $argv0 command ...\n\nKnown commands:\n"
+ foreach topic [Topics] {
+ ::pt::app help $topic
+ }
+ } elseif {$topic ni [Topics]} {
+ puts stderr "$argv0: Unknown help topic '$topic'"
+ puts stderr "\tUse one of [linsert [join [Topics] {, }] end-1 or]"
+ puts stderr ""
+ } else {
+ puts stderr \t[join [split [string map [list @ $argv0] [string trim [::pt::app::${topic}Help]]] \n] \n\t]
+ puts stderr ""
+ }
+ return 0
+}
+
+proc ::pt::app::Topics {} {
+ namespace eval ::TEMP { namespace import ::pt::app::* }
+ set commands [info commands ::TEMP::*]
+ namespace delete ::TEMP
+
+ set res {}
+ foreach c $commands {
+ lappend res [regsub ^::TEMP:: $c {}]
+ }
+ proc ::pt::app::Topics {} [list return $res]
+ return $res
+}
+
+# # ## ### ##### ######## ############# #####################
+
+proc ::pt::app::generateHelp {} {
+ return {
+ @ generate PFORMAT ?-option value...? PFILE INFORMAT GFILE
+
+ Generate data in format PFORMAT and write it to PFILE. Read
+ the grammar to be processed from GFILE (assuming the format
+ GFORMAT). Use any options to configure the generator. The are
+ dependent on PFORMAT.
+ }
+}
+proc ::pt::app::generate {args} {
+ # args = parserformat ?...? parserfile grammarformat grammarfile
+
+ if {[llength $args] < 4} {
+ # Just enough that the help code can extract the method name
+ return -code error "wrong # args, should be \"@ generate ...\""
+ }
+
+ set args [lassign $args parserformat]
+ lassign [lrange $args end-2 end] \
+ parserfile grammarformat grammarfile
+ set args [Template [lrange $args 0 end-3]]
+ lappend args -file $grammarfile
+
+ puts "Reading $grammarformat $grammarfile ..."
+ set grammar [fileutil::cat $grammarfile]
+
+ puts "Generating a $parserformat parser ..."
+ try {
+ set parser [::pt::pgen $grammarformat $grammar $parserformat {*}$args]
+ } trap {PT RDE SYNTAX} {e o} {
+ puts [pt::util error2readable $e $grammar]
+ return 1
+ }
+
+ puts "Saving to $parserfile ..."
+ fileutil::writeFile $parserfile $parser
+
+ puts OK
+ return 0
+}
+
+# Lift template specifications from file paths to the file's contents.
+
+proc ::pt::app::Template {optiondict} {
+ set res {}
+ foreach {option value} $optiondict {
+ if {$option eq "-template"} {
+ set value [fileutil::cat $value]
+ }
+ lappend res $option $value
+ }
+ return $res
+}
+
+# # ## ### ##### ######## ############# #####################
+
+main
+exit
diff --git a/tcllib/apps/pt.man b/tcllib/apps/pt.man
new file mode 100644
index 0000000..577b625
--- /dev/null
+++ b/tcllib/apps/pt.man
@@ -0,0 +1,242 @@
+[comment {-*- text -*- doctools manpage}]
+[manpage_begin pt n 1]
+[include ../modules/pt/include/module.inc]
+[titledesc {Parser Tools Application}]
+[description]
+[include ../modules/pt/include/ref_intro.inc]
+
+This document describes [cmd pt], the main application of the module,
+a [term {parser generator}]. Its intended audience are people who wish
+to create a parser for some language of theirs. Should you wish to
+modify the application instead, please see the section about the
+application's [sectref {Internals}] for the basic references.
+
+[para]
+
+It resides in the User Application Layer of Parser Tools.
+[para][image arch_user_app][para]
+
+[section {Command Line}]
+
+[list_begin definitions]
+
+[call [cmd pt] [method generate] \
+ [arg resultformat] [opt [arg options...]] [arg resultfile] \
+ [arg inputformat] [arg inputfile]]
+
+This sub-command of the application reads the parsing expression
+grammar stored in the [arg inputfile] in the format [arg inputformat],
+converts it to the [arg resultformat] under the direction of the
+(format-specific) set of options specified by the user and stores the
+result in the [arg resultfile].
+
+[para]
+
+The [arg inputfile] has to exist, while the [arg resultfile] may be
+created, overwriting any pre-existing content of the file. Any missing
+directory in the path to the [arg resultfile] will be created as well.
+
+[para]
+
+The exact form of the result for, and the set of options supported by
+the known result-formats, are explained in the upcoming sections of
+this document, with the list below providing an index mapping between
+format name and its associated section. In alphabetical order:
+
+[para]
+[list_begin definitions]
+[def [const c]] A [term resultformat]. See section [sectref {C Parser}].
+[def [const container]] A [term resultformat]. See section [sectref {Grammar Container}].
+[def [const critcl]] A [term resultformat]. See section [sectref {C Parser Embedded In Tcl}].
+[def [const json]] A [term input]- and [term resultformat]. See section [sectref {JSON Grammar Exchange}].
+[def [const oo]] A [term resultformat]. See section [sectref {TclOO Parser}].
+[def [const peg]] A [term input]- and [term resultformat]. See section [sectref {PEG Specification Language}].
+[def [const snit]] A [term resultformat]. See section [sectref {Snit Parser}].
+[list_end]
+[list_end]
+
+Of the seven possible results four are parsers outright ([const c],
+[const critcl], [const oo], and [const snit]), one ([const container])
+provides code which can be used in conjunction with a generic parser
+(also known as a grammar interpreter), and the last two ([const json]
+and [const peg]) are doing double-duty as input formats, allowing the
+transformation of grammars for exchange, reformatting, and the like.
+
+[para]
+
+The created parsers fall into three categories:
+[include ../modules/pt/include/gen_options.inc]
+
+[list_begin definitions]
+
+[def [const {Specialized parsers implemented in C}]]
+
+The fastest parsers are created when using the result formats
+[const c] and [const critcl]. The first returns the raw C code for the
+parser, while the latter wraps it into a Tcl package using
+[term CriTcl].
+
+[para]
+
+This makes the latter much easier to use than the former. On the other
+hand, the former can be adapted to the users' requirements through a
+multitude of options, allowing for things like usage of the parser
+outside of a Tcl environment, something the [const critcl] format
+doesn't support. As such the [const c] format is meant for more
+advanced users, or users with special needs.
+
+[para]
+
+A disadvantage of all the parsers in this section is the need to run
+them through a C compiler to make them actually executable. This is
+not something everyone has the necessary tools for. The parsers in the
+next section are for people under such restrictions.
+
+[def [const {Specialized parsers implemented in Tcl}]]
+
+As the parsers in this section are implemented in Tcl they are quite a
+bit slower than anything from the previous section. On the other hand
+this allows them to be used in pure-Tcl environments, or in
+environments which allow only a limited set of binary packages. In the
+latter case it will be advantageous to lobby for the inclusion of the
+C-based runtime support (notes below) into the environment to reduce
+the impact of Tcl's on the speed of these parsers.
+
+[para]
+
+The relevant formats are [const snit] and [const oo]. Both place their
+result into a Tcl package containing a [cmd snit::type], or TclOO
+[cmd class] respectively.
+
+[para]
+
+Of the supporting runtime, which is the package [package pt::rde], the
+user has to know nothing but that it does exist and that the parsers
+are dependent on it. Knowledge of the API exported by the runtime for
+the parsers' consumption is [emph not] required by the parsers' users.
+
+[def [const {Interpreted parsing implemented in Tcl}]]
+
+The last category, grammar interpretation. This means that an
+interpreter for parsing expression grammars takes the description of
+the grammar to parse input for, and uses it guide the parsing process.
+
+This is the slowest of the available options, as the interpreter has
+to continually run through the configured grammar, whereas the
+specialized parsers of the previous sections have the relevant
+knowledge about the grammar baked into them.
+
+[para]
+
+The only places where using interpretation make sense is where the
+grammar for some input may be changed interactively by the user, as
+the interpretation allows for quick turnaround after each change,
+whereas the previous methods require the generation of a whole new
+parser, which is not as fast.
+
+On the other hand, wherever the grammar to use is fixed, the previous
+methods are much more advantageous as the time to generate the parser
+is minuscule compared to the time the parser code is in use.
+
+[para]
+
+The relevant result format is [const container].
+
+It (quickly) generates grammar descriptions (instead of a full parser)
+which match the API expected by ParserTools' grammar interpreter.
+
+The latter is provided by the package [package pt::peg::interp].
+
+[list_end]
+
+All the parsers generated by [const critcl], [const snit], and
+[const oo], and the grammar interpreter share a common API for access
+to the actual parsing functionality, making them all
+plug-compatible.
+
+It is described in the [manpage {Parser API}] specification document.
+
+[section {PEG Specification Language}]
+[include ../modules/pt/include/format/whatis_peg.inc]
+[para]
+
+For either an introduction to or the formal specification of the
+language, please go and read the [manpage {PEG Language Tutorial}].
+
+[para]
+
+When used as a result-format this format supports the following
+options:
+
+[include ../modules/pt/include/format/options_peg.inc]
+
+[section {JSON Grammar Exchange}]
+[include ../modules/pt/include/format/whatis_json.inc]
+[para]
+
+For the formal specification of the JSON grammar exchange format,
+please go and read [manpage {The JSON Grammar Exchange Format}].
+
+[para]
+
+When used as a result-format this format supports the following
+options:
+
+[include ../modules/pt/include/format/options_json.inc]
+
+[section {C Parser Embedded In Tcl}]
+[include ../modules/pt/include/format/whatis_cparam_critcl.inc]
+[para]
+
+This result-format supports the following options:
+
+[include ../modules/pt/include/format/options_cparam_critcl.inc]
+
+[section {C Parser}]
+[include ../modules/pt/include/format/whatis_cparam_rawc.inc]
+[para]
+
+This result-format supports the following options:
+
+[include ../modules/pt/include/format/options_cparam_rawc.inc]
+
+[section {Snit Parser}]
+[include ../modules/pt/include/format/whatis_tclparam_snit.inc]
+[para]
+
+This result-format supports the following options:
+
+[include ../modules/pt/include/format/options_tclparam_snit.inc]
+
+[section {TclOO Parser}]
+[include ../modules/pt/include/format/whatis_tclparam_oo.inc]
+[para]
+
+This result-format supports the following options:
+
+[include ../modules/pt/include/format/options_tclparam_oo.inc]
+
+[section {Grammar Container}]
+[include ../modules/pt/include/format/whatis_container.inc]
+[para]
+
+This result-format supports the following options:
+
+[include ../modules/pt/include/format/options_container.inc]
+
+[section Example]
+[vset MODE app][include ../modules/pt/include/example/full.inc]
+
+[section Internals]
+
+This section is intended for users of the application which wish to
+modify or extend it. Users only interested in the generation of
+parsers can ignore it.
+
+[para]
+
+The main functionality of the application is encapsulated in the
+package [package pt::pgen]. Please read it for more information.
+
+[include ../modules/pt/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/apps/tcldocstrip b/tcllib/apps/tcldocstrip
new file mode 100755
index 0000000..6d73425
--- /dev/null
+++ b/tcllib/apps/tcldocstrip
@@ -0,0 +1,537 @@
+#! /usr/bin/env tclsh
+# -*- tcl -*-
+
+# @@ Meta Begin
+# Application tcldocstrip 1.0.1
+# Meta platform tcl
+# Meta summary TeX's docstrip written in Tcl
+# Meta description This application is an implementation
+# Meta description of TeX's docstrip application in Tcl.
+# Meta description It provides commands to convert a docstrip
+# Meta description weave according to a set of guards, to
+# Meta description assemble an output based on several sets
+# Meta description guards and input files, i.e. of a document
+# Meta description spread over several inputs and/or guards,
+# Meta description and to extract and list all unique guard
+# Meta description expressions found in a document.
+# Meta category Processing docstrip documents
+# Meta subject docstrip TeX LaTeX
+# Meta require docstrip
+# Meta author Andreas Kupries
+# Meta license BSD
+# @@ Meta End
+
+package provide tcldocstrip 1.0.1
+
+# TODO __________________________
+# Add handling of pre- and postambles.
+
+# tcldocstrip - Docstrip written in Tcl
+# =========== = =======================
+#
+# Use cases
+# ---------
+#
+# (-) Providing access to the functionality of the tcllib/docstrip
+# package from within shell and other scripts which are not Tcl.
+#
+# (1) Conversion of a single input file according to the listed
+# guards into the stripped output.
+#
+# This handles the most simple case of a set of guards
+# specifying a single document found in a single input file.
+#
+# (2) Stitching, or the assembly of an output from several sets of
+# guards, in a specific order, and possibly from different
+# files. This is the second common case. One document spread
+# over several inputs, and/or spread over different guard sets.
+#
+# (3) Extraction and listing of all the unique guard expressions and
+# guards used within a document to help a person which did not
+# author the document in question in familiarizing itself with
+# it.
+#
+# Command syntax
+# --------------
+#
+# Ad 1) tcldocstrip output|"-" ?options? input ?guards?
+#
+# Converts the input file according to the specified guards and
+# options. The result is written to the named output. Usage of
+# the string "-" as output signals that the result should be
+# written to stdout. The guards are document-specific and have
+# to be known to the caller. The options are the same as
+# accepted by docstrip::extract.
+#
+# -metaprefix string
+# -onerror mode {ignore,puts,throw}
+# -trimlines bool
+#
+# Additional options understood are
+#
+# -premamble text
+# -postamble text
+# -nopremamble
+# -nopostamble
+#
+# These are processed by the application itself. The -no*amble
+# options deactivate pre- and postambles altogether, whereas the
+# -*amble specify the _user_ part of pre- and postambles. This
+# part can be empty, in that case only the standard parts are
+# shown. This is the default.
+#
+# Ad 2) tcldocstrip ?options? output|"-" (?options? input|"." guards)...
+#
+# Extracts data from the various input files, according to the
+# specified options and guards, and writes the result to the
+# given output, in the order of their specification on the
+# command line. Options specified before the output are global
+# settings, whereas the options specified before each input are
+# valid only just for this input file. Unspecified values are
+# taken from the global settings. As in (1) "-" as output causes
+# the application to write to stdout. Using "." for an input
+# file signals that the last input file should be used
+# again. This enables the assembly of the output from one input
+# file using multiple and different sets of guards.
+#
+# Ad 3) tcldocstrip -guards input
+#
+# Determines the guards, and unique guard expressions used
+# within the input document. The found strings are written to
+# stdout, one string per line.
+#
+
+lappend auto_path [file join [file dirname [file dirname [info script]]] modules]
+package require docstrip
+
+# ### ### ### ######### ######### #########
+## Internal data and status
+
+namespace eval ::tcldocstrip {
+
+ # List of global options and their arguments found in the command
+ # line. No checking was done on them, they are simply passed to
+ # the extraction command.
+
+ variable options {}
+
+ # List of input specifications. Each element is a list specifying
+ # the extraction options, input file, and guard set, in this
+ # order.
+
+ variable stitch {}
+
+ # Name of the file to write to. "-" signals that output has to be
+ # written to stdout.
+
+ variable output {}
+
+ # Mode of operation: Conversion, or guard retrieval
+
+ variable mode Extract
+
+ # The input file for guard retrieval mode.
+
+ variable input {}
+
+ # Standard preamble to preambles
+
+ variable preamble {}
+ append preamble \n
+ append preamble "This is file `@output@'," \n
+ append preamble "generated with the tcldocstrip utility." \n
+ append preamble \n
+ append preamble "The original source files were:" \n
+ append preamble \n
+ append preamble "@input@ (with options: `@guards@')" \n
+ append preamble \n
+
+ # Standard postamble to postambles
+
+ variable postamble {}
+ append postamble \n
+ append postamble \n
+ append postamble "End of file `@output@'."
+
+ # Default values for the options which are relevant to the
+ # application itself and thus have to be defined always.
+ # They are processed as global options, as part of argv.
+
+ variable defaults {-metaprefix {%} -preamble {} -postamble {}}
+}
+
+# ### ### ### ######### ######### #########
+## External data and status
+#
+## This tool does not depend on external data and/or status.
+
+# ### ### ### ######### ######### #########
+## Option processing.
+## Validate command line.
+## Full command line syntax.
+##
+# tcldocstrip ?-option value...? input ?guard...?
+##
+
+proc ::tcldocstrip::processCmdline {} {
+ global argv
+
+ variable defaults
+ variable preamble
+ variable postamble
+ variable options
+ variable stitch
+ variable output
+ variable input
+ variable mode
+
+ # Process the options, perform basic validation.
+
+ set optbuf {}
+ set stitchbuf {}
+ set get output
+
+ if {![llength $argv]} {
+ set argv $defaults
+ } else {
+ set argv [eval [linsert $argv 0 linsert $defaults end]]
+ }
+
+ while {[llength $argv]} {
+ set opt [lindex $argv 0]
+ if {($opt eq "-") || ![string match "-*" $opt]} {
+ # Non option state machine. Output first. Then input and
+ # guards alternating.
+
+ set argv [lrange $argv 1 end]
+ switch -exact -- $get {
+ output {
+ set output $opt
+ set get input
+ }
+ input {
+ lappend stitchbuf $optbuf $opt
+ set optbuf {}
+ set get guards
+ }
+ guards {
+ lappend stitchbuf $opt
+ set get input
+ lappend stitch $stitchbuf
+ set stitchbuf {}
+ }
+ }
+ continue
+ }
+
+ switch -exact -- $opt {
+ -guards {
+ if {
+ ($get ne "output") ||
+ ([llength $argv] != 2)
+ } Usage
+
+ set mode Guards
+ set input [lindex $argv 1]
+ break
+ }
+ -nopreamble -
+ -nopostamble {
+ set o -[string range $opt 3 end]
+ if {$get eq "output"} {
+ lappend options $o ""
+ } else {
+ lappend optbuf $o ""
+ }
+ }
+ -preamble {
+ set val $preamble[lindex $argv 1]
+ if {$get eq "output"} {
+ lappend options $opt $val
+ } else {
+ lappend optbuf $opt $val
+ }
+ set argv [lrange $argv 2 end]
+ }
+ -postamble {
+ set val [lindex $argv 1]$postamble
+ if {$get eq "output"} {
+ lappend options $opt $val
+ } else {
+ lappend optbuf $opt $val
+ }
+ set argv [lrange $argv 2 end]
+ }
+ default {
+ set val [lindex $argv 1]
+ if {$get eq "output"} {
+ lappend options $opt $val
+ } else {
+ lappend optbuf $opt $val
+ }
+
+ set argv [lrange $argv 2 end]
+ }
+ }
+ }
+
+ if {$get eq "guards"} {
+ # Complete last input spec, may have no guards.
+ lappend stitchbuf {}
+ lappend stitch $stitchbuf
+ set stitchbuf {}
+ }
+
+ # Additional validation.
+
+ if {$mode eq "Guards"} {
+ CheckInput $input {Input path}
+ return
+ }
+
+ if {![llength $stitch]} {
+ Usage
+ }
+
+ set first 1
+ foreach in $stitch {
+ foreach {o i g} $in break
+ if {$first || ($i ne ".")} {
+ # First input file must not be ".".
+ CheckInput $i {Input path}
+ }
+ set first 0
+ }
+
+ CheckTheOutput
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Option processing.
+## Helpers: Generation of error messages.
+## I. General usage/help message.
+## II. Specific messages.
+#
+# Both write their messages to stderr and then
+# exit the application with status 1.
+##
+
+proc ::tcldocstrip::Usage {} {
+ global argv0
+ puts stderr "$argv0: ?options? output (?options? input guards)..."
+ puts stderr "$argv0: -guards input"
+ exit 1
+}
+
+proc ::tcldocstrip::ArgError {text} {
+ global argv0
+ puts stderr "$argv0: $text"
+ exit 1
+}
+
+proc in {list item} {
+ expr {([lsearch -exact $list $item] >= 0)}
+}
+
+# ### ### ### ######### ######### #########
+## Check existence and permissions of an input/output file or
+## directory.
+
+proc ::tcldocstrip::CheckInput {f label} {
+ if {![file exists $f]} {
+ ArgError "Unable to find $label \"$f\""
+ } elseif {![file readable $f]} {
+ ArgError "$label \"$f\" not readable (permission denied)"
+ } elseif {![file isfile $f]} {
+ ArgError "$label \"$f\" is not a file"
+ }
+ return
+}
+
+proc ::tcldocstrip::CheckTheOutput {} {
+ variable output
+
+ if {$output eq ""} {
+ ArgError "No output path specified"
+ } elseif {$output eq "-"} {
+ # Stdout. This is ok.
+ return
+ }
+
+ set base [file dirname $output]
+ if {[string equal $base ""]} {set base [pwd]}
+
+ if {![file exists $output]} {
+ if {![file exists $base]} {
+ ArgError "Output base path \"$base\" not found"
+ }
+ if {![file writable $base]} {
+ ArgError "Output base path \"$base\" not writable (permission denied)"
+ }
+ } elseif {![file writable $output]} {
+ ArgError "Output path \"$output\" not writable (permission denied)"
+ } elseif {![file isfile $output]} {
+ ArgError "Output path \"$output\" is not a file"
+ }
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Helper commands. File reading and writing.
+
+proc ::tcldocstrip::Get {f} {
+ variable data
+ if {[info exists data($f)]} {return $data($f)}
+ return [set data($f) [read [set in [open $f r]]][close $in]]
+}
+
+proc ::tcldocstrip::Write {f data} {
+ puts -nonewline [set out [open $f w]] $data
+ close $out
+ return
+}
+
+proc ::tcldocstrip::WriteStdout {data} {
+ puts -nonewline stdout $data
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Helper commands. Guard extraction.
+
+proc ::tcldocstrip::Guards {text} {
+ array set g {}
+ set verbatim 0
+ set verbtag {}
+ foreach line [split $text \n] {
+ if {$verbatim} {
+ # End of verbatim mode
+ if {$line eq $verbtag} {set verbatim 0}
+ continue
+ }
+ switch -glob -- $line {
+ %<<* {
+ # Start of verbatim mode.
+ set verbatim 1
+ set verbtag %[string range $line 3 end]
+ continue
+ }
+ %<* {
+ if {![regexp -- {^%<([*/+-]?)([^>]*)>(.*)$} \
+ $line --> modifier expression line]} {
+ # Malformed guard. FUTURE Handle via -onerror. For now: ignore.
+ continue
+ }
+ # Remember the guard. Hashtable ensures that
+ # duplicates are removed automatically.
+ set g($expression) .
+ }
+ default {continue}
+ }
+ }
+ return [array names g]
+}
+
+
+# ### ### ### ######### ######### #########
+## Configuation phase, validate command line.
+
+::tcldocstrip::processCmdline
+
+# ### ### ### ######### ######### #########
+## Commands implementing the main functionality.
+
+proc ::tcldocstrip::Do.Extract {} {
+ variable stitch
+ variable output
+ variable options
+
+ set text ""
+
+ foreach in $stitch {
+ foreach {opt input guards} $in break
+
+ # Merge defaults, global and local options, then filch the
+ # options handled in the application.
+
+ unset -nocomplain o
+ array set o $options
+ array set o $opt
+
+ set pre ""
+ if {[info exists o(-preamble)]} {
+ set pre $o(-preamble)
+ unset o(-preamble)
+ }
+ set post ""
+ if {[info exists o(-postamble)]} {
+ set post $o(-postamble)
+ unset o(-postamble)
+ }
+
+ set opt [array get o]
+ set c $o(-metaprefix)
+
+ set pmap [list \
+ @output@ $output \
+ @input@ $input \
+ @guards@ $guards \
+ ]
+
+ if {$pre ne ""} {
+ append text $c $c " " [join [split [string map $pmap $pre] \n] "\n$c$c "]
+ }
+
+ append text [eval [linsert $opt 0 docstrip::extract [Get $input] $guards]]
+
+ if {$post ne ""} {
+ append text $c $c " " [join [split [string map $pmap $post] \n] "\n$c$c "]
+ }
+ }
+
+ if {$output eq "-"} {
+ WriteStdout $text
+ } else {
+ Write $output $text
+ }
+ return
+}
+
+proc ::tcldocstrip::Do.Guards {} {
+ variable input
+
+ WriteStdout [join [lsort [Guards [Get $input]]] \n]
+ return
+}
+
+# ### ### ### ######### ######### #########
+## Invoking the functionality.
+
+if {[catch {
+ set mode $::tcldocstrip::mode
+ ::tcldocstrip::Do.$mode
+} msg]} {
+ ## puts $::errorInfo
+ ::tcldocstrip::ArgError $msg
+}
+
+# ### ### ### ######### ######### #########
+exit
+
+# Generic internal command for error handling. Factored out of the
+# implementation of extract into its own command.
+
+proc HandleError {text attr lineno} {
+ variable O
+
+ switch -- [string tolower $O(-onerror)] "puts" {
+ puts stderr "docstrip: $text on line $lineno."
+ } "ignore" {} default {
+ return \
+ -code error \
+ -errorinfo "" \
+ -errorcode [linsert $attr end $lineno] \
+ $text
+ }
+}
diff --git a/tcllib/apps/tcldocstrip.man b/tcllib/apps/tcldocstrip.man
new file mode 100644
index 0000000..aa35b7d
--- /dev/null
+++ b/tcllib/apps/tcldocstrip.man
@@ -0,0 +1,197 @@
+[comment {-*- tcl -*- doctools manpage}]
+[manpage_begin tcldocstrip n 1.0]
+[see_also docstrip]
+[keywords .dtx]
+[keywords conversion]
+[keywords docstrip]
+[keywords documentation]
+[keywords LaTeX]
+[keywords {literate programming}]
+[keywords markup]
+[keywords source]
+[copyright {2005 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[titledesc {Tcl-based Docstrip Processor}]
+[moddesc {Textprocessing toolbox}]
+[category {Documentation tools}]
+[description]
+[para]
+
+The application described by this document, [syscmd tcldocstrip], is a
+relative of [syscmd docstrip], a simple literate programming tool for
+LaTeX.
+
+[para]
+
+[syscmd tcldocstrip] is based upon the package [package docstrip].
+
+[subsection {USE CASES}]
+
+[syscmd tcldocstrip] was written with the following three use cases in
+mind.
+
+[para]
+[list_begin enumerated]
+[enum]
+Conversion of a single input file according to the listed guards into
+the stripped output. This handles the most simple case of a set of
+guards specifying a single document found in a single input file.
+
+[enum]
+Stitching, or the assembly of an output from several sets of guards,
+in a specific order, and possibly from different files. This is the
+second common case. One document spread over several inputs, and/or
+spread over different guard sets.
+
+[enum]
+Extraction and listing of all the unique guard expressions and guards
+used within a document to help a person which did not author the
+document in question in familiarizing itself with it.
+
+[list_end]
+
+[subsection {COMMAND LINE}]
+
+[list_begin definitions]
+
+[call [cmd tcldocstrip] [arg output] [opt options] [arg input] [opt [arg guards]]]
+
+This is the form for use case [lb]1[rb]. It converts the [arg input]
+file according to the specified [arg guards] and options. The result
+is written to the named [arg output] file.
+
+Usage of the string [const "-"] as the name of the output signals that
+the result should be written to [const stdout]. The guards are
+document-specific and have to be known to the caller. The
+[arg options] will be explained later, in section [sectref OPTIONS].
+
+[list_begin arguments]
+
+[arg_def path output in]
+
+This argument specifies where to write the generated document. It can
+be the path to a file or directory, or [const -].
+
+The last value causes the application to write the generated
+documented to [const stdout].
+
+[para]
+
+If the [arg output] does not exist then [lb]file dirname $output[rb]
+has to exist and must be a writable directory.
+
+[arg_def path inputfile in]
+
+This argument specifies the path to the file to process. It has to
+exist, must be readable, and written in [term docstrip] format.
+
+[list_end]
+[para]
+
+[call [cmd tcldocstrip] [opt options] [arg output] ([opt options] [arg input] [arg guards])...]
+
+This is the form for use case [lb]2[rb]. It differs from the form for
+use case [lb]1[rb] by the possibility of having options before the
+output file, which apply in general, and specifying more than one
+inputfile, each with its own set of input specific options and guards.
+
+[para]
+
+It extracts data from the various [arg input] files, according to the
+specified [arg options] and [arg guards], and writes the result to the
+given [arg output], in the order of their specification on the command
+line. Options specified before the output are global settings, whereas
+the options specified before each input are valid only just for this
+input file. Unspecified values are taken from the global settings, or
+defaults. As for form [lb]1[rb] using the string [const "-"] as output
+causes the application to write to stdout.
+
+Using the string [const "."] for an input file signals that the last
+input file should be used again. This enables the assembly of the
+output from one input file using multiple and different sets of
+guards, without having to specify the full name of the file every
+time.
+
+[call [cmd tcldocstrip] [option -guards] [arg input]]
+
+This is the form for use case [lb]3[rb].
+
+It determines the guards, and unique guard expressions used within the
+provided [arg input] document. The found strings are written to
+stdout, one string per line.
+
+[list_end]
+
+[subsection OPTIONS]
+
+This section describes all the options available to the user of the
+application, with the exception of the option [option -guards]. This
+option was described already, in section [sectref {COMMAND LINE}].
+
+[para]
+[list_begin options]
+[opt_def -metaprefix string]
+
+This option is inherited from the command [cmd docstrip::extract]
+provided by the package [package docstrip].
+
+[para]
+
+It specifies the string by which the '%%' prefix of a metacomment line
+will be replaced. Defaults to '%%'. For Tcl code this would typically
+be '#'.
+
+[opt_def -onerror mode]
+
+This option is inherited from the command [cmd docstrip::extract]
+provided by the package [package docstrip].
+
+[para]
+
+It controls what will be done when a format error in the [arg text]
+being processed is detected. The settings are:
+
+[list_begin definitions]
+[def [const ignore]]
+Just ignore the error; continue as if nothing happened.
+
+[def [const puts]]
+Write an error message to [const stderr], then continue processing.
+
+[def [const throw]]
+Throw an error. [var ::errorCode] is set to a list whose first element
+is [const DOCSTRIP], second element is the type of error, and third
+element is the line number where the error is detected. This is the
+default.
+[list_end]
+
+[opt_def -trimlines bool]
+
+This option is inherited from the command [cmd docstrip::extract]
+provided by the package [package docstrip].
+
+[para]
+
+Controls whether [emph spaces] at the end of a line should be trimmed
+away before the line is processed. Defaults to [const true].
+
+[opt_def -preamble text]
+[opt_def -postamble text]
+[opt_def -nopreamble]
+[opt_def -nopostamble]
+
+The -no*amble options deactivate file pre- and postambles altogether,
+whereas the -*amble options specify the [emph user] part of the file
+pre- and postambles. This part can be empty, in that case only the
+standard parts are shown. This is the default.
+
+[para]
+
+Preambles, when active, are written before the actual content of a
+generated file. In the same manner postambles are, when active,
+written after the actual content of a generated file.
+
+[list_end]
+
+[vset CATEGORY docstrip]
+[include ../modules/doctools2base/include/feedback.inc]
+[manpage_end]