1# 2# buildhelp.tcl -- 3# 4# Program to extract help files from TCL manual pages or TCL script files. 5# The help directories are built as a hierarchical tree of subjects and help 6# files. 7# 8#------------------------------------------------------------------------------ 9# Copyright 1992-1999 Karl Lehenbauer and Mark Diekhans. 10# 11# Permission to use, copy, modify, and distribute this software and its 12# documentation for any purpose and without fee is hereby granted, provided 13# that the above copyright notice appear in all copies. Karl Lehenbauer and 14# Mark Diekhans make no representations about the suitability of this 15# software for any purpose. It is provided "as is" without express or 16# implied warranty. 17#------------------------------------------------------------------------------ 18# $Id: buildhelp.tcl,v 1.3 2005/03/25 19:32:48 hobbs Exp $ 19#------------------------------------------------------------------------------ 20# 21# For nroff man pages, the areas of text to extract are delimited with: 22# 23# '\"@help: subjectdir/helpfile 24# '\"@endhelp 25# 26# start in column one. The text between these markers is extracted and stored 27# in help/subjectdir/help. The file must not exists, this is done to enforced 28# cleaning out the directories before help file generation is started, thus 29# removing any stale files. The extracted text is run through: 30# 31# nroff -man|col -xb {col -b on BSD derived systems} 32# 33# If there is other text to include in the helpfile, but not in the manual 34# page, the text, along with nroff formatting commands, may be included using: 35# 36# '\"@:Other text to include in the help page. 37# 38# A entry in the brief file, used by apropos my be included by: 39# 40# '\"@brief: Short, one line description 41# 42# These brief request must occur with in the bounds of a help section. 43# 44# If some header text, such as nroff macros, need to be preappended to the 45# text streem before it is run through nroff, then that text can be bracketed 46# with: 47# 48# '\"@header 49# '\"@endheader 50# 51# If multiple header blocks are encountered, they will all be preappended. 52# 53# For TCL script files, which are indentified because they end in ".tcl", 54# the text to be extracted is delimited by: 55# 56# #@help: subjectdir/helpfile 57# #@endhelp 58# 59# And brief lines are in the form: 60# 61# #@brief: Short, one line description 62# 63# The only processing done on text extracted from .tcl files it to replace 64# the # in column one with a space. 65# 66# 67#----------------------------------------------------------------------------- 68# 69# To generate help: 70# 71# buildhelp helpDir brief.brf filelist 72# 73# o helpDir is the help tree root directory. helpDir should exists, but any 74# subdirectories that don't exists will be created. helpDir should be 75# cleaned up before the start of manual page generation, as this program 76# will not overwrite existing files. 77# o brief.brf is the name of the brief file to create form the @brief entries. 78# It must have an extension of ".brf". It will be created in helpDir. 79# o filelist are the nroff manual pages, or .tcl, .tlib files to extract 80# the help files from. If the suffix is not .tcl or .tlib, a nroff manual 81# page is assumed. 82# 83#----------------------------------------------------------------------------- 84 85#@package: TclX-buildhelp buildhelp 86 87#----------------------------------------------------------------------------- 88# Truncate a file name of a help file if the system does not support long 89# file names. If the name starts with `Tcl_', then this prefix is removed. 90# If the name is then over 14 characters, it is truncated to 14 charactes 91# 92proc TruncFileName {pathName} { 93 global truncFileNames 94 95 if {!$truncFileNames} { 96 return $pathName} 97 set fileName [file tail $pathName] 98 if {"[crange $fileName 0 3]" == "Tcl_"} { 99 set fileName [crange $fileName 4 end]} 100 set fileName [crange $fileName 0 13] 101 return "[file dirname $pathName]/$fileName" 102} 103 104#----------------------------------------------------------------------------- 105# Proc to ensure that all directories for the specified file path exists, 106# and if they don't create them. Don't use -path so we can set the 107# permissions. 108 109proc EnsureDirs {filePath} { 110 set dirPath [file dirname $filePath] 111 if [file exists $dirPath] return 112 foreach dir [split $dirPath /] { 113 lappend dirList $dir 114 set partPath [join $dirList /] 115 if [file exists $partPath] continue 116 117 mkdir $partPath 118 chmod u=rwx,go=rx $partPath 119 } 120} 121 122#----------------------------------------------------------------------------- 123# Proc to set up scan context for use by FilterNroffManPage. 124# This keeps the a two line cache of the previous two lines encountered 125# and the blank lines that followed them. 126# 127 128proc CreateFilterNroffManPageContext {} { 129 global filterNroffManPageContext 130 131 set filterNroffManPageContext [scancontext create] 132 133 # On finding a page header, drop the previous line (which is 134 # the page footer). Also deleting the blank lines followin 135 # the last line on the previous page. 136 137 scanmatch $filterNroffManPageContext {@@@BUILDHELP@@@} { 138 catch {unset prev2Blanks} 139 catch {unset prev1Line} 140 catch {unset prev1Blanks} 141 set nukeBlanks {} 142 } 143 144 # Save blank lines 145 146 scanmatch $filterNroffManPageContext {$^} { 147 if ![info exists nukeBlanks] { 148 append prev1Blanks \n 149 } 150 } 151 152 # Non-blank line, save it. Output the 2nd previous line if necessary. 153 154 scanmatch $filterNroffManPageContext { 155 catch {unset nukeBlanks} 156 if [info exists prev2Line] { 157 puts $outFH $prev2Line 158 unset prev2Line 159 } 160 if [info exists prev2Blanks] { 161 puts $outFH $prev2Blanks nonewline 162 unset prev2Blanks 163 } 164 if [info exists prev1Line] { 165 set prev2Line $prev1Line 166 } 167 set prev1Line $matchInfo(line) 168 if [info exists prev1Blanks] { 169 set prev2Blanks $prev1Blanks 170 unset prev1Blanks 171 } 172 } 173} 174 175#----------------------------------------------------------------------------- 176# Proc to filter a formatted manual page, removing the page headers and 177# footers. This relies on each manual page having a .TH macro in the form: 178# .TH @@@BUILDHELP@@@ n 179 180proc FilterNroffManPage {inFH outFH} { 181 global filterNroffManPageContext 182 183 if ![info exists filterNroffManPageContext] { 184 CreateFilterNroffManPageContext 185 } 186 187 scanfile $filterNroffManPageContext $inFH 188 189 if [info exists prev2Line] { 190 puts $outFH $prev2Line 191 } 192} 193 194#----------------------------------------------------------------------------- 195# Proc to set up scan context for use by ExtractNroffHeader 196# 197 198proc CreateExtractNroffHeaderContext {} { 199 global extractNroffHeaderContext 200 201 set extractNroffHeaderContext [scancontext create] 202 203 scanmatch $extractNroffHeaderContext {'\\"@endheader[ ]*$} { 204 break 205 } 206 scanmatch $extractNroffHeaderContext {'\\"@:} { 207 append nroffHeader "[crange $matchInfo(line) 5 end]\n" 208 } 209 scanmatch $extractNroffHeaderContext { 210 append nroffHeader "$matchInfo(line)\n" 211 } 212} 213 214#----------------------------------------------------------------------------- 215# Proc to extract nroff text to use as a header to all pass to nroff when 216# processing a help file. 217# manPageFH - The file handle of the manual page. 218# 219 220proc ExtractNroffHeader {manPageFH} { 221 global extractNroffHeaderContext nroffHeader 222 223 if ![info exists extractNroffHeaderContext] { 224 CreateExtractNroffHeaderContext 225 } 226 scanfile $extractNroffHeaderContext $manPageFH 227} 228 229 230#----------------------------------------------------------------------------- 231# Proc to set up scan context for use by ExtractNroffHelp 232# 233 234proc CreateExtractNroffHelpContext {} { 235 global extractNroffHelpContext 236 237 set extractNroffHelpContext [scancontext create] 238 239 scanmatch $extractNroffHelpContext {^'\\"@endhelp[ ]*$} { 240 break 241 } 242 243 scanmatch $extractNroffHelpContext {^'\\"@brief:} { 244 if $foundBrief { 245 error {Duplicate "@brief:" entry} 246 } 247 set foundBrief 1 248 puts $briefHelpFH "$helpName\t[csubstr $matchInfo(line) 11 end]" 249 continue 250 } 251 252 scanmatch $extractNroffHelpContext {^'\\"@:} { 253 puts $nroffFH [csubstr $matchInfo(line) 5 end] 254 continue 255 } 256 scanmatch $extractNroffHelpContext {^'\\"@help:} { 257 error {"@help" found within another help section"} 258 } 259 scanmatch $extractNroffHelpContext { 260 puts $nroffFH $matchInfo(line) 261 } 262} 263 264#----------------------------------------------------------------------------- 265# Proc to extract a nroff help file when it is located in the text. 266# manPageFH - The file handle of the manual page. 267# manLine - The '\"@help: line starting the data to extract. 268# 269 270proc ExtractNroffHelp {manPageFH manLine} { 271 global helpDir nroffHeader briefHelpFH colArgs 272 global extractNroffHelpContext 273 274 if ![info exists extractNroffHelpContext] { 275 CreateExtractNroffHelpContext 276 } 277 278 set helpName [string trim [csubstr $manLine 9 end]] 279 set helpFile [TruncFileName "$helpDir/$helpName"] 280 if [file exists $helpFile] { 281 error "Help file already exists: $helpFile" 282 } 283 EnsureDirs $helpFile 284 285 set tmpFile "[file dirname $helpFile]/tmp.[id process]" 286 287 echo " creating help file $helpName" 288 289 set nroffFH [open "| nroff -man | col $colArgs > $tmpFile" w] 290 291 puts $nroffFH {.TH @@@BUILDHELP@@@ 1} 292 293 set foundBrief 0 294 scanfile $extractNroffHelpContext $manPageFH 295 296 # Close returns an error on if anything comes back on stderr, even if 297 # its a warning. Output errors and continue. 298 299 set stat [catch { 300 close $nroffFH 301 } msg] 302 if $stat { 303 puts stderr "nroff: $msg" 304 } 305 306 set tmpFH [open $tmpFile r] 307 set helpFH [open $helpFile w] 308 309 FilterNroffManPage $tmpFH $helpFH 310 311 close $tmpFH 312 close $helpFH 313 314 unlink $tmpFile 315 chmod a-w,a+r $helpFile 316} 317 318#----------------------------------------------------------------------------- 319# Proc to set up scan context for use by ExtractScriptHelp 320# 321 322proc CreateExtractScriptHelpContext {} { 323 global extractScriptHelpContext 324 325 set extractScriptHelpContext [scancontext create] 326 327 scanmatch $extractScriptHelpContext {^#@endhelp[ ]*$} { 328 break 329 } 330 331 scanmatch $extractScriptHelpContext {^#@brief:} { 332 if $foundBrief { 333 error {Duplicate "@brief" entry} 334 } 335 set foundBrief 1 336 puts $briefHelpFH "$helpName\t[csubstr $matchInfo(line) 9 end]" 337 continue 338 } 339 340 scanmatch $extractScriptHelpContext {^#@help:} { 341 error {"@help" found within another help section"} 342 } 343 344 scanmatch $extractScriptHelpContext {^#$} { 345 puts $helpFH "" 346 } 347 348 scanmatch $extractScriptHelpContext { 349 if {[clength $matchInfo(line)] > 1} { 350 puts $helpFH " [csubstr $matchInfo(line) 1 end]" 351 } else { 352 puts $helpFH $matchInfo(line) 353 } 354 } 355} 356 357#----------------------------------------------------------------------------- 358# Proc to extract a tcl script help file when it is located in the text. 359# ScriptPageFH - The file handle of the .tcl file. 360# ScriptLine - The #@help: line starting the data to extract. 361# 362 363proc ExtractScriptHelp {scriptPageFH scriptLine} { 364 global helpDir briefHelpFH 365 global extractScriptHelpContext 366 367 if ![info exists extractScriptHelpContext] { 368 CreateExtractScriptHelpContext 369 } 370 371 set helpName [string trim [csubstr $scriptLine 7 end]] 372 set helpFile "$helpDir/$helpName" 373 if {[file exists $helpFile]} { 374 error "Help file already exists: $helpFile" 375 } 376 EnsureDirs $helpFile 377 378 echo " creating help file $helpName" 379 380 set helpFH [open $helpFile w] 381 382 set foundBrief 0 383 scanfile $extractScriptHelpContext $scriptPageFH 384 385 close $helpFH 386 chmod a-w,a+r $helpFile 387} 388 389#----------------------------------------------------------------------------- 390# Proc to scan a nroff manual file looking for the start of a help text 391# sections and extracting those sections. 392# pathName - Full path name of file to extract documentation from. 393# 394 395proc ProcessNroffFile {pathName} { 396 global nroffScanCT scriptScanCT nroffHeader 397 398 set fileName [file tail $pathName] 399 400 set nroffHeader {} 401 set manPageFH [open $pathName r] 402 set matchInfo(fileName) [file tail $pathName] 403 404 echo " scanning $pathName" 405 406 scanfile $nroffScanCT $manPageFH 407 408 close $manPageFH 409} 410 411#----------------------------------------------------------------------------- 412# Proc to scan a Tcl script file looking for the start of a 413# help text sections and extracting those sections. 414# pathName - Full path name of file to extract documentation from. 415# 416 417proc ProcessTclScript {pathName} { 418 global scriptScanCT nroffHeader 419 420 set scriptFH [open "$pathName" r] 421 set matchInfo(fileName) [file tail $pathName] 422 423 echo " scanning $pathName" 424 scanfile $scriptScanCT $scriptFH 425 426 close $scriptFH 427} 428 429#----------------------------------------------------------------------------- 430# build: main procedure. Generates help from specified files. 431# helpDirPath - Directory were the help files go. 432# briefFile - The name of the brief file to create. 433# sourceFiles - List of files to extract help files from. 434 435proc buildhelp {helpDirPath briefFile sourceFiles} { 436 global helpDir truncFileNames nroffScanCT 437 global scriptScanCT briefHelpFH colArgs 438 439 echo "" 440 echo "Begin building help tree" 441 442 # Determine version of col command to use (no -x on BSD) 443 if {[catch {exec col -bx </dev/null >/dev/null 2>/dev/null}]} { 444 set colArgs {-b} 445 } else { 446 set colArgs {-bx} 447 } 448 set helpDir $helpDirPath 449 if {![file exists $helpDir]} { 450 mkdir $helpDir 451 } 452 453 if {![file isdirectory $helpDir]} { 454 error "$helpDir is not a directory or does not exist.\n \ 455 This should be the help root directory" 456 } 457 458 set status [catch {set tmpFH [open $helpDir/AVeryVeryBigFileName w]}] 459 if {$status != 0} { 460 set truncFileNames 1 461 } else { 462 close $tmpFH 463 unlink $helpDir/AVeryVeryBigFileName 464 set truncFileNames 0 465 } 466 467 set nroffScanCT [scancontext create] 468 469 scanmatch $nroffScanCT {'\\"@help:} { 470 ExtractNroffHelp $matchInfo(handle) $matchInfo(line) 471 continue 472 } 473 474 scanmatch $nroffScanCT {^'\\"@header} { 475 ExtractNroffHeader $matchInfo(handle) 476 continue 477 } 478 scanmatch $nroffScanCT {^'\\"@endhelp} { 479 error [concat {@endhelp" without corresponding "@help:"} \ 480 ", offset = $matchInfo(offset)"] 481 } 482 scanmatch $nroffScanCT {^'\\"@brief} { 483 error [concat {"@brief" without corresponding "@help:"} \ 484 ", offset = $matchInfo(offset)"] 485 } 486 487 set scriptScanCT [scancontext create] 488 scanmatch $scriptScanCT {^#@help:} { 489 ExtractScriptHelp $matchInfo(handle) $matchInfo(line) 490 } 491 492 if {[file extension $briefFile] != ".brf"} { 493 error "Brief file \"$briefFile\" must have an extension \".brf\"" 494 } 495 if [file exists $helpDir/$briefFile] { 496 error "Brief file \"$helpDir/$briefFile\" already exists" 497 } 498 set briefHelpFH [open "|sort > $helpDir/$briefFile" w] 499 500 foreach manFile [glob $sourceFiles] { 501 set ext [file extension $manFile] 502 if {$ext == ".tcl" || $ext == ".tlib"} { 503 set status [catch {ProcessTclScript $manFile} msg] 504 } else { 505 set status [catch {ProcessNroffFile $manFile} msg] 506 } 507 if {$status != 0} { 508 global errorInfo errorCode 509 error "Error extracting help from: $manFile" $errorInfo $errorCode 510 } 511 } 512 513 close $briefHelpFH 514 chmod a-w,a+r $helpDir/$briefFile 515 echo "Completed extraction of help files" 516} 517 518 519 520