diff options
Diffstat (limited to 'tcllib/modules/doctools/doctools.tcl')
-rw-r--r-- | tcllib/modules/doctools/doctools.tcl | 1361 |
1 files changed, 1361 insertions, 0 deletions
diff --git a/tcllib/modules/doctools/doctools.tcl b/tcllib/modules/doctools/doctools.tcl new file mode 100644 index 0000000..8365633 --- /dev/null +++ b/tcllib/modules/doctools/doctools.tcl @@ -0,0 +1,1361 @@ +# doctools.tcl -- +# +# Implementation of doctools objects for Tcl. +# +# Copyright (c) 2003-2014 Andreas Kupries <andreas_kupries@sourceforge.net> +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +package require Tcl 8.2 +package require textutil::expander + +# @mdgen OWNER: api.tcl +# @mdgen OWNER: checker.tcl +# @mdgen OWNER: mpformats/*.tcl +# @mdgen OWNER: mpformats/*.msg +# @mdgen OWNER: mpformats/fmt.* +# @mdgen OWNER: mpformats/man.macros + +namespace eval ::doctools { + # Data storage in the doctools module + # ------------------------------- + # + # One namespace per object, containing + # 1) A list of additional search paths for format definition files. + # This list extends the list of standard paths known to the module. + # The paths in the list are searched before the standard paths. + # 2) Configuration information + # a) string: The format to use when converting the input. + # b) boolean: A flag telling us whether to warn when visual markup + # is used in the input, or not. + # c) File information associated with the input, if any. + # d) Module information associated with the input, if any. + # e) Copyright information, if any + # 4) Name of the interpreter used to perform the syntax check of the + # input (= allowed order of formatting commands). + # 5) Name of the interpreter containing the code coming from the format + # definition file. + # 6) Name of the expander object used to interpret the input to convert. + + # commands is the list of subcommands recognized by the doctools objects + variable commands [list \ + "cget" \ + "configure" \ + "destroy" \ + "format" \ + "map" \ + "search" \ + "warnings" \ + "parameters" \ + "setparam" \ + ] + + # Only export the toplevel commands + namespace export new search help + + # Global data + + # 1) List of standard paths to look at when searching for a format + # definition. Extensible. + # 2) Location of this file in the filesystem + + variable paths [list] + variable here [file dirname [info script]] +} + +# ::doctools::search -- +# +# Extend the list of paths used when searching for format definition files. +# +# Arguments: +# path Path to add to the list. The path has to exist, has to be a +# directory, and has to be readable. +# +# Results: +# None. +# +# Sideeffects: +# The specified path is added to the front of the list of search +# paths. This means that the new path is search before the +# standard paths set at module initialization time. + +proc ::doctools::search {path} { + variable paths + + if {![file exists $path]} {return -code error "doctools::search: path does not exist"} + if {![file isdirectory $path]} {return -code error "doctools::search: path is not a directory"} + if {![file readable $path]} {return -code error "doctools::search: path cannot be read"} + + set paths [linsert $paths 0 $path] + return +} + +# ::doctools::help -- +# +# Return a string containing short help +# regarding the existing formatting commands. +# +# Arguments: +# None. +# +# Results: +# A string. + +proc ::doctools::help {} { + return "formatting commands\n\ + * manpage_begin - begin of manpage\n\ + * moddesc - module description\n\ + * titledesc - manpage title\n\ + * copyright - copyright assignment\n\ + * manpage_end - end of manpage\n\ + * require - package requirement\n\ + * description - begin of manpage body\n\ + * section - begin new section of body\n\ + * subsection - begin new sub-section of body\n\ + * para - begin new paragraph\n\ + * list_begin - begin a list\n\ + * list_end - end of a list\n\ + * lst_item - begin item of definition list\n\ + * call - command definition, adds to synopsis\n\ + * usage - see above, without adding to synopsis\n\ + * bullet - begin item in bulleted list\n\ + * enum - begin item in enumerated list\n\ + * arg_def - begin item in argument list\n\ + * cmd_def - begin item in command list\n\ + * opt_def - begin item in option list\n\ + * tkoption_def - begin item in tkoption list\n\ + * example - example block\n\ + * example_begin - begin example\n\ + * example_end - end of example\n\ + * category - category declaration\n\ + * see_also - cross reference declaration\n\ + * keywords - keyword declaration\n\ + * nl - paragraph break in list items\n\ + * arg - semantic markup - argument\n\ + * cmd - semantic markup - command\n\ + * opt - semantic markup - optional data\n\ + * comment - semantic markup - comment\n\ + * sectref - semantic markup - section reference\n\ + * syscmd - semantic markup - system command\n\ + * method - semantic markup - object method\n\ + * namespace - semantic markup - namespace name\n\ + * option - semantic markup - option\n\ + * widget - semantic markup - widget\n\ + * fun - semantic markup - function\n\ + * type - semantic markup - data type\n\ + * package - semantic markup - package\n\ + * class - semantic markup - class\n\ + * var - semantic markup - variable\n\ + * file - semantic markup - file \n\ + * uri - semantic markup - uri (optional label)\n\ + * term - semantic markup - unspecific terminology\n\ + * const - semantic markup - constant value\n\ + * emph - emphasis\n\ + * strong - emphasis, deprecated, usage is discouraged\n\ + " +} + +# ::doctools::new -- +# +# Create a new doctools object with a given name. May configure the object. +# +# Arguments: +# name Name of the doctools object. +# args Options configuring the new object. +# +# Results: +# name Name of the doctools created + +proc ::doctools::new {name args} { + + if { [llength [info commands ::$name]] } { + return -code error "command \"$name\" already exists, unable to create doctools object" + } + if {[llength $args] % 2 == 1} { + return -code error "wrong # args: doctools::new name ?opt val...??" + } + + # The arguments seem to be ok, setup the namespace for the object + + namespace eval ::doctools::doctools$name { + variable paths [list] + variable format "" + variable formatfile "" + variable deprecated 0 + variable file "" + variable mainfile "" + variable ibase "" + variable module "" + variable copyright "" + variable format_ip "" + variable chk_ip "" + variable expander "[namespace current]::ex" + variable ex_ok 0 + variable msg [list] + variable param [list] + variable map ; array set map {} + } + + # Create the command to manipulate the object + # $name -> ::doctools::DoctoolsProc $name + interp alias {} ::$name {} ::doctools::DoctoolsProc $name + + # If the name was followed by arguments use them to configure the + # object before returning its handle to the caller. + + if {[llength $args] > 1} { + # Use linsert trick to make the command a pure list. + eval [linsert $args 0 _configure $name] + } + return $name +} + +########################## +# Private functions follow + +# ::doctools::DoctoolsProc -- +# +# Command that processes all doctools object commands. +# Dispatches any object command to the appropriate internal +# command implementing its functionality. +# +# Arguments: +# name Name of the doctools object to manipulate. +# cmd Subcommand to invoke. +# args Arguments for subcommand. +# +# Results: +# Varies based on command to perform + +proc ::doctools::DoctoolsProc {name {cmd ""} args} { + # Do minimal args checks here + if { [llength [info level 0]] == 2 } { + error "wrong # args: should be \"$name option ?arg arg ...?\"" + } + + # Split the args into command and args components + + if { [llength [info commands ::doctools::_$cmd]] == 0 } { + variable commands + set optlist [join $commands ", "] + set optlist [linsert $optlist "end-1" "or"] + return -code error "bad option \"$cmd\": must be $optlist" + } + return [eval [list ::doctools::_$cmd $name] $args] +} + +########################## +# Method implementations follow (these are also private commands) + +# ::doctools::_cget -- +# +# Retrieve the current value of a particular option +# +# Arguments: +# name Name of the doctools object to query +# option Name of the option whose value we are asking for. +# +# Results: +# The value of the option + +proc ::doctools::_cget {name option} { + _configure $name $option +} + +# ::doctools::_configure -- +# +# Configure a doctools object, or query its configuration. +# +# Arguments: +# name Name of the doctools object to configure +# args Options and their values. +# +# Results: +# None if configuring the object. +# A list of all options and their values if called without arguments. +# The value of one particular option if called with a single argument. + +proc ::doctools::_configure {name args} { + upvar #0 ::doctools::doctools${name}::format_ip format_ip + upvar #0 ::doctools::doctools${name}::chk_ip chk_ip + upvar #0 ::doctools::doctools${name}::expander expander + upvar #0 ::doctools::doctools${name}::passes passes + + if {[llength $args] == 0} { + # Retrieve the current configuration. + + upvar #0 ::doctools::doctools${name}::file file + upvar #0 ::doctools::doctools${name}::ibase ibase + upvar #0 ::doctools::doctools${name}::module module + upvar #0 ::doctools::doctools${name}::format format + upvar #0 ::doctools::doctools${name}::copyright copyright + upvar #0 ::doctools::doctools${name}::deprecated deprecated + + set res [list] + lappend res -file $file + lappend res -ibase $ibase + lappend res -module $module + lappend res -format $format + lappend res -copyright $copyright + lappend res -deprecated $deprecated + return $res + + } elseif {[llength $args] == 1} { + # Query the value of one particular option. + + switch -exact -- [lindex $args 0] { + -file { + upvar #0 ::doctools::doctools${name}::file file + return $file + } + -ibase { + upvar #0 ::doctools::doctools${name}::ibase ibase + return $ibase + } + -module { + upvar #0 ::doctools::doctools${name}::module module + return $module + } + -copyright { + upvar #0 ::doctools::doctools${name}::copyright copyright + return $copyright + } + -format { + upvar #0 ::doctools::doctools${name}::format format + return $format + } + -deprecated { + upvar #0 ::doctools::doctools${name}::deprecated deprecated + return $deprecated + } + default { + return -code error \ + "doctools::_configure: Unknown option \"[lindex $args 0]\", expected\ + -copyright, -file, -ibase, -module, -format, or -deprecated" + } + } + } else { + # Reconfigure the object. + + if {[llength $args] % 2 == 1} { + return -code error "wrong # args: doctools::_configure name ?opt val...??" + } + + foreach {option value} $args { + switch -exact -- $option { + -file { + upvar #0 ::doctools::doctools${name}::file file + upvar #0 ::doctools::doctools${name}::mainfile mfile + set file $value + set mfile $value + } + -ibase { + upvar #0 ::doctools::doctools${name}::ibase ibase + set ibase $value + } + -module { + upvar #0 ::doctools::doctools${name}::module module + set module $value + } + -copyright { + upvar #0 ::doctools::doctools${name}::copyright copyright + set copyright $value + } + -format { + if {[catch { + set fmtfile [LookupFormat $name $value] + SetupFormatter $name $fmtfile + upvar #0 ::doctools::doctools${name}::format format + set format $value + } msg]} { + return -code error \ + -errorinfo $::errorInfo \ + "doctools::_configure: -format: $msg" + } + } + -deprecated { + if {![string is boolean $value]} { + return -code error \ + "doctools::_configure: -deprecated expected a boolean, got \"$value\"" + } + upvar #0 ::doctools::doctools${name}::deprecated deprecated + set deprecated $value + } + default { + return -code error \ + "doctools::_configure: Unknown option \"$option\", expected\ + -copyright, -file, -ibase, -module, -format, or -deprecated" + } + } + } + } + return "" +} + +# ::doctools::_destroy -- +# +# Destroy a doctools object, including its associated command and data storage. +# +# Arguments: +# name Name of the doctools object to destroy. +# +# Results: +# None. + +proc ::doctools::_destroy {name} { + # Check the object for sub objects which have to destroyed before + # the namespace is torn down. + namespace eval ::doctools::doctools$name { + if {$format_ip != ""} {interp delete $format_ip} + if {$chk_ip != ""} {interp delete $chk_ip} + + # Expander objects have no delete/destroy method. This would + # be a leak if not for the fact that an expander object is a + # namespace, and we have arranged to make it a sub namespace of + # the doctools object. Therefore tearing down our object namespace + # also cleans up the expander object. + # if {$expander != ""} {$expander destroy} + + } + namespace delete ::doctools::doctools$name + interp alias {} ::$name {} + return +} + +# ::doctools::_map -- +# +# Add a mapping from symbolic to actual filename to the object. +# +# Arguments: +# name Name of the doctools object to use +# sfname Symbolic filename to map +# afname Actual filename +# +# Results: +# None. + +proc ::doctools::_map {name sfname afname} { + upvar #0 ::doctools::doctools${name}::map map + set map($sfname) $afname + return +} + +# ::doctools::_img -- +# + +# Add a mapping from symbolic to the actual image filenames to +# the object. Two actual paths! The path the image is found at +# in the input, and the path for where image is to be placed in +# the output. +# +# Arguments: +# name Name of the doctools object to use +# sfname Symbolic filename to map +# afnameo Actual filename, origin +# afnamed Actual filename, destination +# +# Results: +# None. + +proc ::doctools::_img {name sfname afnameo afnamed} { + upvar #0 ::doctools::doctools${name}::imap imap + set imap($sfname) [list $afnameo $afnamed] + return +} + +# ::doctools::_format -- +# +# Convert some text in doctools format +# according to the configuration in the object. +# +# Arguments: +# name Name of the doctools object to use +# text Text to convert. +# +# Results: +# The conversion result. + +proc ::doctools::_format {name text} { + upvar #0 ::doctools::doctools${name}::format format + if {$format == ""} { + return -code error "$name: No format was specified" + } + + upvar #0 ::doctools::doctools${name}::format_ip format_ip + upvar #0 ::doctools::doctools${name}::chk_ip chk_ip + upvar #0 ::doctools::doctools${name}::ex_ok ex_ok + upvar #0 ::doctools::doctools${name}::expander expander + upvar #0 ::doctools::doctools${name}::passes passes + upvar #0 ::doctools::doctools${name}::msg warnings + + if {!$ex_ok} {SetupExpander $name} + if {$chk_ip == ""} {SetupChecker $name} + # assert (format_ip != "") + + set warnings [list] + if {[catch {$format_ip eval fmt_initialize}]} { + return -code error -errorcode {DOCTOOLS ENGINE} \ + "Could not initialize engine" + } + set result "" + + for { + set p $passes ; set n 1 + } { + $p > 0 + } { + incr p -1 ; incr n + } { + if {[catch {$format_ip eval [list fmt_setup $n]}]} { + catch {$format_ip eval fmt_shutdown} + return -code error -errorcode {DOCTOOLS ENGINE} \ + "Could not initialize pass $n of engine" + } + $chk_ip eval ck_initialize $n + + if {[catch {set result [$expander expand $text]} msg]} { + catch {$format_ip eval fmt_shutdown} + # Filter for checker errors and reduce them to the essential message. + + if {![regexp {^Error in} $msg]} { + return -code error -errorcode {DOCTOOLS INPUT} $msg + } + #set msg [join [lrange [split $msg \n] 2 end]] + + if {![regexp {^--> \(FmtError\) } $msg]} { + return -code error -errorcode {DOCTOOLS INPUT} "Doctools $msg" + } + set msg [lindex [split $msg \n] 0] + regsub {^--> \(FmtError\) } $msg {} msg + + return -code error -errorcode {DOCTOOLS INPUT} $msg + } + + $chk_ip eval ck_complete + } + + if {[catch {set result [$format_ip eval [list fmt_postprocess $result]]}]} { + return -code error -errorcode {DOCTOOLS ENGINE} \ + "Unable to post process final result" + } + if {[catch {$format_ip eval fmt_shutdown}]} { + return -code error -errorcode {DOCTOOLS ENGINE} \ + "Could not shut engine down" + } + return $result + +} + +# ::doctools::_search -- +# +# Add a search path to the object. +# +# Arguments: +# name Name of the doctools object to extend +# path Search path to add. +# +# Results: +# None. + +proc ::doctools::_search {name path} { + if {![file exists $path]} {return -code error "$name search: path does not exist"} + if {![file isdirectory $path]} {return -code error "$name search: path is not a directory"} + if {![file readable $path]} {return -code error "$name search: path cannot be read"} + + upvar #0 ::doctools::doctools${name}::paths paths + set paths [linsert $paths 0 $path] + return +} + +# ::doctools::_warnings -- +# +# Return the warning accumulated during the last invocation of 'format'. +# +# Arguments: +# name Name of the doctools object to query +# +# Results: +# A list of warnings. + +proc ::doctools::_warnings {name} { + upvar #0 ::doctools::doctools${name}::msg msg + return $msg +} + +# ::doctools::_parameters -- +# +# Returns a list containing the parameters provided +# by the selected formatting engine. +# +# Arguments: +# name Name of the doctools object to query +# +# Results: +# A list of parameter names + +proc ::doctools::_parameters {name} { + upvar #0 ::doctools::doctools${name}::param param + return $param +} + +# ::doctools::_setparam -- +# +# Set a named engine parameter to a value. +# +# Arguments: +# name Name of the doctools object to query +# param Name of the parameter to set. +# value Value to set the parameter to. +# +# Results: +# None. + +proc ::doctools::_setparam {name param value} { + upvar #0 ::doctools::doctools${name}::format_ip format_ip + + if {$format_ip == {}} { + return -code error \ + "Unable to set parameters without a valid format" + } + + $format_ip eval [list fmt_varset $param $value] + return +} + +########################## +# Support commands + +# ::doctools::LookupFormat -- +# +# Search a format definition file based upon its name +# +# Arguments: +# name Name of the doctools object to use +# format Name of the format to look for. +# +# Results: +# The file containing the format definition + +proc ::doctools::LookupFormat {name format} { + # Order of searching + # 1) Is the name of the format an existing file ? + # If yes, take this file. + # 2) Look for the file in the directories given to the object itself.. + # 3) Look for the file in the standard directories of this package. + + if {[file exists $format] && [file isfile $format] } { + return $format + } + + upvar #0 ::doctools::doctools${name}::paths opaths + foreach path $opaths { + set f [file join $path fmt.$format] + if {[file exists $f] && [file isfile $f]} { + return $f + } + } + + variable paths + foreach path $paths { + set f [file join $path fmt.$format] + if {[file exists $f] && [file isfile $f]} { + return $f + } + } + + return -code error "Unknown format \"$format\"" +} + +# ::doctools::SetupFormatter -- +# +# Create and initializes an interpreter containing a +# formatting engine +# +# Arguments: +# name Name of the doctools object to manipulate +# format Name of file containing the code of the engine +# +# Results: +# None. + +proc ::doctools::SetupFormatter {name format} { + + # Create and initialize the interpreter first. + # Use a transient variable. Interrogate the + # engine and check its response. Bail out in + # case of errors. Only if we pass the checks + # we tear down the old engine and make the new + # one official. + + variable here + set mpip [interp create -safe] ; # interpreter for the formatting engine + $mpip eval [list set auto_path $::auto_path] + #set mpip [interp create] ; # interpreter for the formatting engine + + $mpip invokehidden source [file join $here api.tcl] + #$mpip eval [list source [file join $here api.tcl]] + interp alias $mpip dt_source {} ::doctools::Source $mpip [file dirname $format] + interp alias $mpip dt_read {} ::doctools::Read $mpip [file dirname $format] + interp alias $mpip dt_package {} ::doctools::Package $mpip + interp alias $mpip file {} ::doctools::FileOp $mpip + interp alias $mpip puts_stderr {} ::puts stderr + interp alias $mpip puts_stdout {} ::puts stdout + $mpip invokehidden source $format + #$mpip eval [list source $format] + + # Check the engine for useability in doctools. + + foreach api { + fmt_numpasses + fmt_initialize + fmt_setup + fmt_postprocess + fmt_shutdown + fmt_listvariables + fmt_varset + } { + if {[$mpip eval [list info commands $api]] == {}} { + interp delete $mpip + error "$format error: API incomplete, cannot use this engine" + } + } + if {[catch { + set passes [$mpip eval fmt_numpasses] + }]} { + interp delete $mpip + error "$format error: Unable to query for number of passes" + } + if {![string is integer $passes] || ($passes < 1)} { + interp delete $mpip + error "$format error: illegal number of passes \"$passes\"" + } + if {[catch { + set parameters [$mpip eval fmt_listvariables] + }]} { + interp delete $mpip + error "$format error: Unable to query for list of parameters" + } + + # Passed the tests. Tear down existing engine, + # and checker. The latter is destroyed because + # of its aliases into the formatter, which are + # now invalid. It will be recreated during the + # next call of 'format'. + + upvar #0 ::doctools::doctools${name}::formatfile formatfile + upvar #0 ::doctools::doctools${name}::format_ip format_ip + upvar #0 ::doctools::doctools${name}::chk_ip chk_ip + upvar #0 ::doctools::doctools${name}::expander expander + upvar #0 ::doctools::doctools${name}::passes xpasses + upvar #0 ::doctools::doctools${name}::param xparam + + if {$chk_ip != {}} {interp delete $chk_ip} + if {$format_ip != {}} {interp delete $format_ip} + + set chk_ip "" + set format_ip "" + + # Now link engine API into it. + + interp alias $mpip dt_file {} ::doctools::GetFile $name + interp alias $mpip dt_mainfile {} ::doctools::GetMainFile $name + interp alias $mpip dt_fileid {} ::doctools::GetFileId $name + interp alias $mpip dt_ibase {} ::doctools::GetIBase $name + interp alias $mpip dt_module {} ::doctools::GetModule $name + interp alias $mpip dt_copyright {} ::doctools::GetCopyright $name + interp alias $mpip dt_format {} ::doctools::GetFormat $name + interp alias $mpip dt_user {} ::doctools::GetUser $name + interp alias $mpip dt_lnesting {} ::doctools::ListLevel $name + interp alias $mpip dt_fmap {} ::doctools::MapFile $name + interp alias $mpip dt_imgsrc {} ::doctools::ImgSrc $name + interp alias $mpip dt_imgdst {} ::doctools::ImgDst $name + interp alias $mpip dt_imgdata {} ::doctools::ImgData $name + interp alias $mpip file {} ::doctools::FileCmd + + foreach cmd {cappend cget cis cname cpop cpush ctopandclear cset lb rb} { + interp alias $mpip ex_$cmd {} $expander $cmd + } + + set format_ip $mpip + set formatfile $format + set xpasses $passes + set xparam $parameters + return +} + +# ::doctools::SetupChecker -- +# +# Create and initializes an interpreter for checking the usage of +# doctools formatting commands +# +# Arguments: +# name Name of the doctools object to manipulate +# +# Results: +# None. + +proc ::doctools::SetupChecker {name} { + # Create an interpreter for checking the usage of doctools formatting commands + # and initialize it: Link it to the interpreter doing the formatting, the + # expander object and the configuration information. All of which + # is accessible through the token/handle (name of state/object array). + + variable here + + upvar #0 ::doctools::doctools${name}::chk_ip chk_ip + if {$chk_ip != ""} {return} + + upvar #0 ::doctools::doctools${name}::expander expander + upvar #0 ::doctools::doctools${name}::format_ip format_ip + + set chk_ip [interp create] ; # interpreter hosting the formal format checker + + # Make configuration available through command, then load the code base. + + foreach {cmd ckcmd} { + dt_search SearchPaths + dt_deprecated Deprecated + dt_error FmtError + dt_warning FmtWarning + dt_where Where + dt_file GetFile + } { + interp alias $chk_ip $cmd {} ::doctools::$ckcmd $name + } + $chk_ip eval [list source [file join $here checker.tcl]] + + # Simple expander commands are directly routed back into it, no + # checking required. + + foreach cmd {cappend cget cis cname cpop cpush ctopandclear cset lb rb} { + interp alias $chk_ip $cmd {} $expander $cmd + } + + # Link the formatter commands into the checker. We use the prefix + # 'fmt_' to distinguish them from the checking commands. + + foreach cmd { + manpage_begin moddesc titledesc copyright manpage_end require + description section para list_begin list_end lst_item call + bullet enum example example_begin example_end see_also + keywords nl arg cmd opt comment sectref syscmd method option + widget fun type package class var file uri usage term const + arg_def cmd_def opt_def tkoption_def emph strong plain_text + namespace subsection category image + } { + interp alias $chk_ip fmt_$cmd $format_ip fmt_$cmd + } + return +} + +# ::doctools::SetupExpander -- +# +# Create and initializes the expander for input +# +# Arguments: +# name Name of the doctools object to manipulate +# +# Results: +# None. + +proc ::doctools::SetupExpander {name} { + upvar #0 ::doctools::doctools${name}::ex_ok ex_ok + if {$ex_ok} {return} + + upvar #0 ::doctools::doctools${name}::expander expander + ::textutil::expander $expander + $expander evalcmd [list ::doctools::Eval $name] + $expander textcmd plain_text + set ex_ok 1 + return +} + +# ::doctools::SearchPaths -- +# +# API for checker. Returns list of search paths for format +# definitions. Used to look for message catalogs as well. +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# None. + +proc ::doctools::SearchPaths {name} { + upvar #0 ::doctools::doctools${name}::paths opaths + variable paths + + set p $opaths + foreach s $paths {lappend p $s} + return $p +} + +# ::doctools::Deprecated -- +# +# API for checker. Returns flag determining +# whether visual markup is warned against, or not. +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# None. + +proc ::doctools::Deprecated {name} { + upvar #0 ::doctools::doctools${name}::deprecated deprecated + return $deprecated +} + +# ::doctools::FmtError -- +# +# API for checker. Called when an error occurred. +# +# Arguments: +# name Name of the doctools object to query. +# text Error message +# +# Results: +# None. + +proc ::doctools::FmtError {name text} { + return -code error "(FmtError) $text" +} + +# ::doctools::FmtWarning -- +# +# API for checker. Called when a warning was generated +# +# Arguments: +# name Name of the doctools object +# text Warning message +# +# Results: +# None. + +proc ::doctools::FmtWarning {name text} { + upvar #0 ::doctools::doctools${name}::msg msg + lappend msg $text + return +} + +# ::doctools::Where -- +# +# API for checker. Called when the current location is needed +# +# Arguments: +# name Name of the doctools object +# +# Results: +# List containing offset, line, column + +proc ::doctools::Where {name} { + upvar #0 ::doctools::doctools${name}::expander expander + return [$expander where] +} + +# ::doctools::Eval -- +# +# API for expander. Routes the macro invocations +# into the checker interpreter +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# None. + +proc ::doctools::Eval {name macro} { + upvar #0 ::doctools::doctools${name}::chk_ip chk_ip + + #puts stderr "\t\t$name [lindex [split $macro] 0]" + + # Handle the [include] command directly + if {[string match include* $macro]} { + set macro [$chk_ip eval [list subst $macro]] + foreach {cmd filename} $macro break + return [ExpandInclude $name $filename] + } + + # Rewrite the [namespace] command before passing it on. + # "namespace" is a special command. The interpreter the validator + # resides in uses the package "msgcat", which in turn uses the + # builtin namespace. So the builtin cannot be simply + # overwritten. We use a different name. + + if {[string match namespace* $macro]} { + set macro _$macro + } + return [$chk_ip eval $macro] +} + +# ::doctools::ExpandInclude -- +# +# Handle inclusion of files. +# +# Arguments: +# name Name of the doctools object to query. +# path Name of file to include and expand. +# +# Results: +# None. + +proc ::doctools::ExpandInclude {name path} { + upvar #0 ::doctools::doctools${name}::file file + upvar #0 ::doctools::doctools${name}::ibase ibase + + set savedi $ibase + set savedf $file + + set base $ibase + if {$base eq {}} { set base $file } + + set ipath [file normalize [file join [file dirname $base] $path]] + if {![file exists $ipath]} { + set ipath $path + if {![file exists $ipath]} { + return -code error "Unable to find include file \"$path\"" + } + } + + set chan [open $ipath r] + set text [read $chan] + close $chan + + upvar #0 ::doctools::doctools${name}::expander expander + + set ibase $ipath + set res [$expander expand $text] + + set ibase $savedi + set file $savedf + + return $res +} + +# ::doctools::GetUser -- +# +# API for formatter. Returns name of current user +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# String, name of current user. + +proc ::doctools::GetUser {name} { + global tcl_platform + return $tcl_platform(user) +} + +# ::doctools::GetFile -- +# +# API for formatter. Returns file information +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# File information + +proc ::doctools::GetFile {name} { + + #puts stderr "GetFile $name" + + upvar #0 ::doctools::doctools${name}::file file + + #puts stderr "ok $file" + return $file +} + +proc ::doctools::GetMainFile {name} { + + #puts stderr "GetMainFile $name" + + upvar #0 ::doctools::doctools${name}::mainfile mfile + + #puts stderr "ok $mfile" + return $mfile +} + +# ::doctools::GetFileId -- +# +# API for formatter. Returns file information (truncated to stem of filename) +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# File information + +proc ::doctools::GetFileId {name} { + return [file rootname [file tail [GetFile $name]]] +} + +proc ::doctools::GetIBase {name} { + upvar #0 ::doctools::doctools${name}::file file + upvar #0 ::doctools::doctools${name}::ibase ibase + + set base $ibase + if {$base eq {}} { set base $file } + return $base +} + +# ::doctools::FileCmd -- +# +# API for formatter. Restricted implementation of file. +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# Module information + +proc ::doctools::FileCmd {cmd args} { + switch -exact -- $cmd { + split {return [eval file split $args]} + join {return [eval file join $args]} + tail {return [eval file tail $args]} + rootname {return [eval file rootname $args]} + } + return -code error "Illegal subcommand: $cmd $args" +} + +# ::doctools::GetModule -- +# +# API for formatter. Returns module information +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# Module information + +proc ::doctools::GetModule {name} { + upvar #0 ::doctools::doctools${name}::module module + return $module +} + +# ::doctools::GetCopyright -- +# +# API for formatter. Returns copyright information +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# Copyright information + +proc ::doctools::GetCopyright {name} { + upvar #0 ::doctools::doctools${name}::copyright copyright + return $copyright +} + +# ::doctools::GetFormat -- +# +# API for formatter. Returns format information +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# Format information + +proc ::doctools::GetFormat {name} { + upvar #0 ::doctools::doctools${name}::format format + return $format +} + +# ::doctools::ListLevel -- +# +# API for formatter. Returns number of open lists +# +# Arguments: +# name Name of the doctools object to query. +# +# Results: +# Boolean flag. + +proc ::doctools::ListLevel {name} { + upvar #0 ::doctools::doctools${name}::chk_ip chk_ip + return [$chk_ip eval LNest] +} + +# ::doctools::MapFile -- +# +# API for formatter. Maps symbolic to actual filename in a doctools +# item. If no mapping is found it is assumed that the symbolic name +# is also the actual name. +# +# Arguments: +# name Name of the doctools object to query. +# fname Symbolic name of the file. +# +# Results: +# Actual name of the file. + +proc ::doctools::MapFile {name fname} { + upvar #0 ::doctools::doctools${name}::map map + + #parray map + + if {[info exists map($fname)]} { + return $map($fname) + } + return $fname +} + +# ::doctools::Img{Src,Dst} -- +# +# API for formatter. Maps symbolic to actual image in a doctools +# item. Returns nothing if no mapping is found. +# +# Arguments: +# name Name of the doctools object to query. +# iname Symbolic name of the image file. +# extensions List of acceptable file extensions. +# +# Results: +# Actual name of the file. + +proc ::doctools::ImgData {name iname extensions} { + + # The system searches for the image relative to the current input + # file, and the current main file + + upvar #0 ::doctools::doctools${name}::imap imap + + #parray imap + + foreach e $extensions { + if {[info exists imap($iname.$e)]} { + foreach {origin dest} $imap($iname.$e) break + + set f [open $origin r] + set img [read $f] + close $f + + return $img + } + } + return {} +} + +proc ::doctools::ImgSrc {name iname extensions} { + + # The system searches for the image relative to the current input + # file, and the current main file + + upvar #0 ::doctools::doctools${name}::imap imap + + #parray imap + + foreach e $extensions { + if {[info exists imap($iname.$e)]} { + foreach {origin dest} $imap($iname.$e) break + return $origin + } + } + return {} +} + +proc ::doctools::ImgDst {name iname extensions} { + # The system searches for the image relative to the current input + # file, and the current main file + + upvar #0 ::doctools::doctools${name}::imap imap + + #parray imap + + foreach e $extensions { + if {[info exists imap($iname.$e)]} { + foreach {origin dest} $imap($iname.$e) break + file mkdir [file dirname $dest] + file copy -force $origin $dest + return $dest + } + } + return {} +} + +# ::doctools::Source -- +# +# API for formatter. Used by engine to ask for +# additional script files support it. +# +# Arguments: +# name Name of the doctools object to change. +# +# Results: +# Boolean flag. + +proc ::doctools::Source {ip path file} { + #puts stderr "$ip (source $path $file)" + + $ip invokehidden source [file join $path [file tail $file]] + #$ip eval [list source [file join $path [file tail $file]]] + return +} + +proc ::doctools::Read {ip path file} { + #puts stderr "$ip (read $path $file)" + + return [read [set f [open [file join $path [file tail $file]]]]][close $f] +} + +proc ::doctools::Locate {p} { + # @mdgen NODEP: doctools::__undefined__ + catch {package require doctools::__undefined__} + + #puts stderr "auto_path = [join $::auto_path \n]" + + # Check if requested package is in the list of loadable packages. + # Then get the highest possible version, and then the index script + + if {[lsearch -exact [package names] $p] < 0} { + return -code error "Unknown package $p" + } + + set v [lindex [lsort -increasing [package versions $p]] end] + + #puts stderr "Package $p = $v" + + return [package ifneeded $p $v] +} + +proc ::doctools::FileOp {ip args} { + #puts stderr "$ip (file $args)" + # -- FUTURE -- disallow unsafe operations -- + + return [eval [linsert $args 0 file]] +} + +proc ::doctools::Package {ip pkg} { + #puts stderr "$ip package require $pkg" + + set indexScript [Locate $pkg] + + $ip expose source + $ip expose load + $ip eval $indexScript + $ip hide source + $ip hide load + #$ip eval [list source [file join $path [file tail $file]]] + return +} + +#------------------------------------ +# Module initialization + +namespace eval ::doctools { + # Reverse order of searching. First to search is specified last. + + # FOO/doctools.tcl + # => FOO/mpformats + + #catch {search [file join $here lib doctools mpformats]} + #catch {search [file join [file dirname $here] lib doctools mpformats]} + catch {search [file join $here mpformats]} +} + +package provide doctools 1.4.19 |