One might also look at Expand or the textutil::expander module of TclLib -- WHD
ulis: One day I was tired to write the hand-made documentation of my OOlib. So I wondered about a tool which will help me. The result is a tiny language for generating documents. I named it "feathers" and for now it generates HTML documents. But it is easy to modify it to generate nroff or rtf documents. This is great for making and displaying dynamic documents without Tk.I'm still wondering: what magic could be done with feathers and the Tcl plug-in?(feathers is free and you can do what you want with it)feathers syntax:
<cmd>:
' multiline string
<key> * ?<opts>?... ?--? ?script?
<key> ' ?<opts>?... ?--? ?multiline string?
<text cmd>
<text cmd>:
' monoline string
"" <text>
<key> ?<opts>?... ?--? ?<text>?
<text>: ??monoline string?...?[<text cmd>]?...?...
<key>:
key -> generate <KEY OPTs><text></KEY>
!key -> generate <KEY OPTs><text>
<opts>:
-opt val -> generate OPT="val"feathers example: # the helper
feathers eval \
{
proc page {page title text} \
{
uplevel 1 [format \
{
html * \
{
head * { title {%s}; style ' {H2 {text-align: center; padding: 1em}} }
body * -bgcolor #f0f0ff { h2 {%s}; !br; !br {%s} }
}
} $page $title $text]
}
}
# the work
puts [feathers eval {page feathers {this is an example} {of the use of [b feathers]}}]The result <HTML>
<HEAD>
<TITLE>feathers</TITLE>
<STYLE>H2 {text-align: center; padding: 1em}</STYLE>
</HEAD>
<BODY BGCOLOR="#f0f0ff">
<H2>this is an example</H2>
<BR>
<BR>of the use of <B>feathers</B>
</BODY>
</HTML>The package.
package provide feathers
namespace eval ::feathers \
{
# this package exports nothing but the feathers interpreter
interp create feathers
feathers eval \
{
# ------------------------
#
# HTML mechanism
#
# ------------------------
set ::text 0
set ::uniq 0
# ------------
# learn from keys
# ------------
proc unknown {key args} \
{
set KEY [string toupper $key]
eval [format {interp alias {} %s {} _gener %s} $key $KEY]
uplevel 1 _gener $KEY $args
}
# ------------
# basic generator (<KEY OPTs><text></KEY>)
# ------------
interp alias {} "" {} _gener ""
interp alias {} ' {} _gener "" '
proc _gener {KEY args} \
{
if {[string index $KEY 0] == "!"} \
{
incr ::uniq
set KEY [string range $KEY 1 end]
}
foreach {opts s eval str uniq} [eval _tokenz $args] break
while { [string index $s 0] == "\{"
&& [string index $s end] == "\}"} \
{ set s [string range $s 1 end-1] }
set level [info level]
if {$::text} \
{
# ------------
# inside text
# result is returned
if {$str} { set res [_str $uniq $KEY $opts $s] } \
else { set res [_txt $uniq $KEY $opts $s] }
} \
else \
{
# ------------
# command
# intermediate result goes to ::eval([info level])
# final result is returned
set pfx [_pfx]
if {$eval} \
{
set res $pfx<${KEY}$opts>\n
set lvl2 [expr {$level + 1}]
set ::eval($lvl2) ""
eval $s
append res $::eval($lvl2)
unset ::eval($lvl2)
append res $pfx</$KEY>\n
} \
elseif {$str} { set res [_str $uniq $KEY $opts $s] } \
else { set res [_txt $uniq $KEY $opts $s] }
append ::eval($level) $res
set ::eval($level)
}
}
# ------------
# tokenizer (?*? ?-opt val?... ?--? ?text?)
# ------------
proc _tokenz {args} \
{
foreach v {str eval uniq skip n} { set $v 0 }
set opts ""
foreach item $args \
{
if {$skip} { set skip 0; continue } \
elseif {$item == "--"} \
{ # end of options
incr n
break
} \
elseif {$item == "*"} \
{ # eval flag
set eval 1
incr n
} \
elseif {$item == "'"} \
{ # string flag
set str 1
incr n
} \
elseif {[string index $item 0] == "-"} \
{ # option
set KEY [string toupper [string range $item 1 end]]
incr n
set value [lindex $args $n]
incr n
append opts " $KEY=\"$value\""
set skip 1
} \
else { break }
}
set max [llength $args]
if {[incr n] < $max} \
{ error "bad expression: $args\n" }
if {$n == $max } { set s [lindex $args end] } \
else { set s "" }
if {$::uniq} {set uniq 1; incr ::uniq -1 }
return [list $opts $s $eval $str $uniq]
}
# ------------
# level prefix (HTML beautifier)
# ------------
proc _pfx {{i 0}} \
{
if {$::text} { return "" }
set n [info level]
incr n $i
incr n -2
string repeat " " $n
}
# ------------
# text line (with embedded commands)
# ------------
proc _txt {uniq KEY opts txt} \
{
set beg ""; set end ""
if {$KEY != ""} \
{
set beg <$KEY$opts>
if {!$uniq} { set end </$KEY> }
}
set res [_pfx -1]$beg
incr ::text
set level [info level]
set ::eval([expr {$level + 2}]) ""
set txt [eval concat "$txt"]
append res $txt
incr ::text -1
if {$end != ""} { append res $end }
if {$::text < 1} { append res \n }
set res
}
# ------------
# multiline string
# ------------
proc _str {uniq KEY opts text} \
{
set beg ""; set end ""
if {$KEY != ""} \
{
set beg <$KEY$opts>
if {!$uniq} { set end </$KEY> }
}
set lines {}
set p 0
while {[set n [string first \n $text $p]] > -1} \
{
lappend lines [string range $text $p [incr n -1]]
set p [incr n 2]
}
if {[incr p] < [string length $text]} \
{ lappend lines [string range $text [incr p -1] end] }
if {[lindex $lines 0] == ""} { set lines [lreplace $lines 0 0] }
if {[lindex $lines end] == ""} { set lines [lreplace $lines end end] }
set max [llength $lines]
set res ""
if {$beg != ""} { append res [_pfx -1]$beg }
append res [lindex $lines 0]
for {set i 1} {$i < $max} {incr i} \
{
append res \n[lindex $lines $i]
}
if {$end != ""} { append res $end }
if {$::text < 1} { append res \n }
set res
}
# ------------
# some helpers
# ------------
interp alias {} br {} !br
interp alias {} BR {} !br
interp alias {} !BR {} !br
proc !br {args} \
{
if {$args == ""} { uplevel 1 _gener !BR ' {[sp]} } \
else { uplevel 1 _gener !BR $args }
}
proc sp {} { _gener "" ' { } } ;# non breakable space
proc obrace {} { _gener "" ' {{} } ;# open brace -> {
proc pipe {} { _gener "" ' {|} } ;# pipe -> |
proc cbrace {} { _gener "" ' {}} } ;# close brace -> }
proc obracket {} { _gener "" ' {[} } ;# open bracket -> [
proc bslash {} { _gener "" ' {\} } ;# back slash -> \
proc cbracket {} { _gener "" ' {]} } ;# close bracket -> ]
proc squote {} { _gener "" ' {'} } ;# single quote
proc dquote {} { _gener "" ' {"} } ;# double quote
proc dollar {} { _gener "" ' {$} } ;# dollar
}
# end of feathers namespace eval
}Now a more elaborate example: one page of the OOlib man.First, the helpers.
# ==============================================
#
# usage
#
# ==============================================
# ------------------------
# OOlib man extension
# ------------------------
feathers eval \
{
# the page disposition
proc page {title body} \
{
set cmd [format \
{
copyright {ulis (C) 2002}
html * \
{
header {%s}
body * \
{
h2 {%s}
%s
}
}
} $title $title $body]
uplevel 1 $cmd
}
# a copyright notice
proc copyright {text} \
{
uplevel 1 [format {' {<!-- %s -->}} $text]
}
# the header component
proc header {args} \
{
set cmd [format \
{
head * \
{
title {%s}
style ' \
{
BODY { background: #FFFAFC }
H2 { color: gold; background-color: #F0F0F0; text-align: center; padding: 1em }
H3 { color: brown }
A { color: green; text-decoration: none }
UL { display: inline; margin-left: +10mm }
DIV { display: block; margin-left: 10mm }
PRE, CODE { color: blue }
}
}
} $args]
uplevel 1 $cmd
}
# a division component
proc division {name args} \
{
if {[llength $args] > 1} \
{
set pre [lindex $args 0]
set body [lrange $args 1 end]
} \
else \
{
set pre ""
set body $args
}
set cmd [format \
{
h3 {%s}
if {{%s} != ""} { pre ' -style margin-left:1cm {%s} }
div * {%s}
} $name $pre $pre $body]
uplevel 1 $cmd
}
# a list division
proc divli {title args} \
{
if {[llength $args] > 1} \
{
set type [lindex $args 0]
set body [lrange $args 1 end]
} \
else \
{
set type ""
set body $args
}
set cmd [format \
{
!br {[!li {%s}]}
div %s {%s}
} $title $type $body]
uplevel 1 $cmd
}
# a reference to the glossary
proc v {pattern {text ""}} \
{
if {$text == ""} { set text $pattern }
eval [format {a -href "Vocabulaire.html#$pattern" {%s}} $text]
}
# an index pointer
proc left {target text} \
{
set cmd [format \
{
a -href %s {[sp][!img -src left.gif -border 0][sp] %s}
} $target $text]
uplevel 1 $cmd
}
}
----
Second, making the page.
# ------------------------
# OOlib man example : the "aliases directive" page
# ------------------------
set res [ \
feathers eval \
{
page {aliases directive} \
{
division NOM \
{
"" {aliases - déclare des alias pour une méthode}
}
division SYNOPSIS \
{
"" {[b aliases] [i method] [b ?][i alias][b ?...]}
}
division DESCRIPTION \
{
"" {Cette directive permet de déclarer des alias (synonymes) pour les méthodes.}
!br {Un nom d'alias est redéfinissable (par une méthode ou un alias).}
!br
divli {Au moment de la déclaration} * \
{ "" {[i alias] doit être un nom [v redéfinissable] (par une méthode ou un alias).}
!br {Il ne peut pas commencer par le caractère _ (souligné).}
}
divli {Au moment de l'exécution} * \
{ "" {[i method] doit être [v définition définie].}}
}
division EXAMPLE \
{
methods config
aliases config co conf configure
} \
{
"" {La méthode [b config] pourra aussi être appelée par [b co], [b conf] ou [b configure].}
}
division {VOIR AUSSI} \
{
"" {[left rename.htm {renames directive}],}
"" {[left Directives.htm {Directives}]}
}
}
} ]Third, showing the result.
set fn /tmp/feathers_result.html set h [open $fn w] puts $h $res close $h eval exec [auto_execok start] "file:$fn" &

