| 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 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 2.1 1992/10/25 17:07:40 markd 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 run this program: |
| 70 | # |
| 71 | # tcl buildhelp.tcl [-m mergeTree] -b brief.brf helpDir file-1 file-2 ... |
| 72 | # |
| 73 | # o -m mergeTree is a tree of help code, plus a brief file to merge with the |
| 74 | # help files that are to be extracted. This will become part of the new |
| 75 | # help tree. Used to merge in the documentation from UCB Tcl. |
| 76 | # o -b specified the name of the brief file to create form the @brief entries. |
| 77 | # It must have an extension of ".brf". |
| 78 | # o helpDir is the help tree root directory. helpDir should exists, but any |
| 79 | # subdirectories that don't exists will be created. helpDir should be |
| 80 | # cleaned up before the start of manual page generation, as this program |
| 81 | # will not overwrite existing files. |
| 82 | # o file-n are the nroff manual pages (.man) or .tcl or .tlib files to extract |
| 83 | # the help files from. |
| 84 | # |
| 85 | #----------------------------------------------------------------------------- |
| 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 | # |
| 92 | proc TruncFileName {pathName} { |
| 93 | global G_truncFileNames |
| 94 | |
| 95 | if {!$G_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 | |
| 109 | proc 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 | #----------------------------------------------------------------------------- |
| 124 | # |
| 125 | # Proc to extract nroff text to use as a header to all pass to nroff when |
| 126 | # processing a help file. |
| 127 | # manPageFH - The file handle of the manual page. |
| 128 | # |
| 129 | |
| 130 | proc ExtractNroffHeader {manPageFH} { |
| 131 | global nroffHeader |
| 132 | while {[gets $manPageFH manLine] >= 0} { |
| 133 | if {[string first "'@endheader" $manLine] == 0} { |
| 134 | break; |
| 135 | } |
| 136 | if {[string first "'@:" $manLine] == 0} { |
| 137 | set manLine [csubstr manLine 3 end] |
| 138 | } |
| 139 | append nroffHeader "$manLine\n" |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | #----------------------------------------------------------------------------- |
| 144 | # |
| 145 | # Proc to extract a nroff help file when it is located in the text. |
| 146 | # manPageFH - The file handle of the manual page. |
| 147 | # manLine - The '@help: line starting the data to extract. |
| 148 | # |
| 149 | |
| 150 | proc ExtractNroffHelp {manPageFH manLine} { |
| 151 | global G_helpDir nroffHeader G_briefHelpFH G_colArgs |
| 152 | |
| 153 | set helpName [string trim [csubstr $manLine 7 end]] |
| 154 | set helpFile [TruncFileName "$G_helpDir/$helpName"] |
| 155 | if {[file exists $helpFile]} { |
| 156 | error "Help file already exists: $helpFile"} |
| 157 | EnsureDirs $helpFile |
| 158 | set helpFH [open "| nroff -man | col $G_colArgs > $helpFile" w] |
| 159 | echo " creating help file $helpName" |
| 160 | |
| 161 | # Nroff commands from .TH macro to get the formatting right. The `\n' |
| 162 | # are newline separators to output, the `\\n' become `\n' in the text. |
| 163 | |
| 164 | puts $helpFH ".ad b\n.PD\n.nrIN \\n()Mu\n.nr)R 0\n.nr)I \\n()Mu" |
| 165 | puts $helpFH ".nr)R 0\n.\}E\n.DT\n.na\n.nh" |
| 166 | puts $helpFH $nroffHeader |
| 167 | set foundBrief 0 |
| 168 | while {[gets $manPageFH manLine] >= 0} { |
| 169 | if {[string first "'@endhelp" $manLine] == 0} { |
| 170 | break; |
| 171 | } |
| 172 | if {[string first "'@brief:" $manLine] == 0} { |
| 173 | if $foundBrief { |
| 174 | error {Duplicate "'@brief" entry"} |
| 175 | } |
| 176 | set foundBrief 1 |
| 177 | puts $G_briefHelpFH "$helpName\t[csubstr $manLine 8 end]" |
| 178 | continue; |
| 179 | } |
| 180 | if {[string first "'@:" $manLine] == 0} { |
| 181 | set manLine [csubstr $manLine 3 end] |
| 182 | } |
| 183 | if {[string first "'@help" $manLine] == 0} { |
| 184 | error {"'@help" found within another help section"} |
| 185 | } |
| 186 | puts $helpFH $manLine |
| 187 | } |
| 188 | close $helpFH |
| 189 | chmod a-w,a+r $helpFile |
| 190 | } |
| 191 | |
| 192 | #----------------------------------------------------------------------------- |
| 193 | # |
| 194 | # Proc to extract a tcl script help file when it is located in the text. |
| 195 | # ScriptPageFH - The file handle of the .tcl file. |
| 196 | # ScriptLine - The #@help: line starting the data to extract. |
| 197 | # |
| 198 | |
| 199 | proc ExtractScriptHelp {ScriptPageFH ScriptLine} { |
| 200 | global G_helpDir G_briefHelpFH |
| 201 | set helpName [string trim [csubstr $ScriptLine 7 end]] |
| 202 | set helpFile "$G_helpDir/$helpName" |
| 203 | if {[file exists $helpFile]} { |
| 204 | error "Help file already exists: $helpFile"} |
| 205 | EnsureDirs $helpFile |
| 206 | set helpFH [open $helpFile w] |
| 207 | echo " creating help file $helpName" |
| 208 | set foundBrief 0 |
| 209 | while {[gets $ScriptPageFH ScriptLine] >= 0} { |
| 210 | if {[string first "#@endhelp" $ScriptLine] == 0} { |
| 211 | break; |
| 212 | } |
| 213 | if {[string first "#@brief:" $ScriptLine] == 0} { |
| 214 | if $foundBrief { |
| 215 | error {Duplicate "#@brief" entry"} |
| 216 | } |
| 217 | set foundBrief 1 |
| 218 | puts $G_briefHelpFH "$helpName\t[csubstr $ScriptLine 8 end]" |
| 219 | continue; |
| 220 | } |
| 221 | if {[string first "#@help" $ScriptLine] == 0} { |
| 222 | error {"#@help" found within another help section"} |
| 223 | } |
| 224 | if {[clength $ScriptLine] > 1} { |
| 225 | set ScriptLine " [csubstr $ScriptLine 1 end]" |
| 226 | } else { |
| 227 | set ScriptLine "" |
| 228 | } |
| 229 | puts $helpFH $ScriptLine |
| 230 | } |
| 231 | close $helpFH |
| 232 | chmod a-w,a+r $helpFile |
| 233 | } |
| 234 | |
| 235 | #----------------------------------------------------------------------------- |
| 236 | # |
| 237 | # Proc to scan a nroff manual file looking for the start of a help text |
| 238 | # sections and extracting those sections. |
| 239 | # pathName - Full path name of file to extract documentation from. |
| 240 | # |
| 241 | |
| 242 | proc ProcessNroffFile {pathName} { |
| 243 | global G_nroffScanCT G_scriptScanCT nroffHeader |
| 244 | |
| 245 | set fileName [file tail $pathName] |
| 246 | |
| 247 | set nroffHeader {} |
| 248 | set manPageFH [open $pathName r] |
| 249 | echo " scanning $pathName" |
| 250 | set matchInfo(fileName) [file tail $pathName] |
| 251 | scanfile $G_nroffScanCT $manPageFH |
| 252 | close $manPageFH |
| 253 | } |
| 254 | |
| 255 | #----------------------------------------------------------------------------- |
| 256 | # |
| 257 | # Proc to scan a Tcl script file looking for the start of a |
| 258 | # help text sections and extracting those sections. |
| 259 | # pathName - Full path name of file to extract documentation from. |
| 260 | # |
| 261 | |
| 262 | proc ProcessTclScript {pathName} { |
| 263 | global G_scriptScanCT nroffHeader |
| 264 | |
| 265 | set scriptFH [open "$pathName" r] |
| 266 | |
| 267 | echo " scanning $pathName" |
| 268 | set matchInfo(fileName) [file tail $pathName] |
| 269 | scanfile $G_scriptScanCT $scriptFH |
| 270 | close $scriptFH |
| 271 | } |
| 272 | |
| 273 | #----------------------------------------------------------------------------- |
| 274 | # Proc to copy the help merge tree, excluding the brief file and RCS files |
| 275 | # |
| 276 | |
| 277 | proc CopyMergeTree {helpDirPath mergeTree} { |
| 278 | if {"[cindex $helpDirPath 0]" != "/"} { |
| 279 | set helpDirPath "[pwd]/$helpDirPath" |
| 280 | } |
| 281 | set oldDir [pwd] |
| 282 | cd $mergeTree |
| 283 | |
| 284 | set curHelpDir "." |
| 285 | |
| 286 | for_recursive_glob mergeFile {.} { |
| 287 | if [string match "*/RCS/*" $mergeFile] continue |
| 288 | |
| 289 | set helpFile "$helpDirPath/$mergeFile" |
| 290 | if [file isdirectory $mergeFile] continue |
| 291 | |
| 292 | if {[file exists $helpFile]} { |
| 293 | error "Help file already exists: $helpFile"} |
| 294 | EnsureDirs $helpFile |
| 295 | set inFH [open $mergeFile r] |
| 296 | set outFH [open $helpFile w] |
| 297 | copyfile $inFH $outFH |
| 298 | close $outFH |
| 299 | close $inFH |
| 300 | chmod a-w,a+r $helpFile |
| 301 | } |
| 302 | cd $oldDir |
| 303 | } |
| 304 | |
| 305 | #----------------------------------------------------------------------------- |
| 306 | # GenerateHelp: main procedure. Generates help from specified files. |
| 307 | # helpDirPath - Directory were the help files go. |
| 308 | # mergeTree - Help file tree to merge with the extracted help files. |
| 309 | # briefFile - The name of the brief file to create. |
| 310 | # sourceFiles - List of files to extract help files from. |
| 311 | |
| 312 | proc GenerateHelp {helpDirPath briefFile mergeTree sourceFiles} { |
| 313 | global G_helpDir G_truncFileNames G_nroffScanCT |
| 314 | global G_scriptScanCT G_briefHelpFH G_colArgs |
| 315 | |
| 316 | echo "" |
| 317 | echo "Begin building help tree" |
| 318 | |
| 319 | # Determine version of col command to use (no -x on BSD) |
| 320 | if {[system {col -bx </dev/null >/dev/null 2>&1}] != 0} { |
| 321 | set G_colArgs {-b} |
| 322 | } else { |
| 323 | set G_colArgs {-bx} |
| 324 | } |
| 325 | set G_helpDir [glob $helpDirPath] |
| 326 | |
| 327 | if {![file isdirectory $G_helpDir]} { |
| 328 | error [concat "$G_helpDir is not a directory or does not exist. " |
| 329 | "This should be the help root directory"] |
| 330 | } |
| 331 | |
| 332 | set status [catch {set tmpFH [open $G_helpDir/AVeryVeryBigFileName w]}] |
| 333 | if {$status != 0} { |
| 334 | set G_truncFileNames 1 |
| 335 | } else { |
| 336 | close $tmpFH |
| 337 | unlink $G_helpDir/AVeryVeryBigFileName |
| 338 | set G_truncFileNames 0 |
| 339 | } |
| 340 | |
| 341 | set G_nroffScanCT [scancontext create] |
| 342 | |
| 343 | scanmatch $G_nroffScanCT "^'@help:" { |
| 344 | ExtractNroffHelp $matchInfo(handle) $matchInfo(line) |
| 345 | continue |
| 346 | } |
| 347 | |
| 348 | scanmatch $G_nroffScanCT "^'@header" { |
| 349 | ExtractNroffHeader $matchInfo(handle) |
| 350 | continue |
| 351 | } |
| 352 | scanmatch $G_nroffScanCT "^'@endhelp" { |
| 353 | error [concat {"'@endhelp" without corresponding "'@help:"} \ |
| 354 | ", offset = $matchInfo(offset)"] |
| 355 | } |
| 356 | scanmatch $G_nroffScanCT "^'@brief" { |
| 357 | error [concat {"'@brief" without corresponding "'@help:"} |
| 358 | ", offset = $matchInfo(offset)"] |
| 359 | } |
| 360 | |
| 361 | set G_scriptScanCT [scancontext create] |
| 362 | scanmatch $G_scriptScanCT "^#@help:" { |
| 363 | ExtractScriptHelp $matchInfo(handle) $matchInfo(line) |
| 364 | } |
| 365 | |
| 366 | if ![lempty $mergeTree] { |
| 367 | echo " Merging tree: $mergeTree" |
| 368 | CopyMergeTree $helpDirPath $mergeTree |
| 369 | } |
| 370 | |
| 371 | |
| 372 | if {[file extension $briefFile] != ".brf"} { |
| 373 | puts stderr "Brief file \"$briefFile\" must have an extension \".brf\"" |
| 374 | exit 1 |
| 375 | } |
| 376 | if [file exists $G_helpDir/$briefFile] { |
| 377 | puts stderr "Brief file \"$G_helpDir/$briefFile\" already exists" |
| 378 | exit 1 |
| 379 | } |
| 380 | set G_briefHelpFH [open "|sort > $G_helpDir/$briefFile" w] |
| 381 | |
| 382 | foreach manFile $sourceFiles { |
| 383 | set manFile [glob $manFile] |
| 384 | set ext [file extension $manFile] |
| 385 | if {"$ext" == ".man"} { |
| 386 | set status [catch {ProcessNroffFile $manFile} msg] |
| 387 | } else { |
| 388 | set status [catch {ProcessTclScript $manFile} msg] |
| 389 | } |
| 390 | if {$status != 0} { |
| 391 | echo "Error extracting help from: $manFile" |
| 392 | echo $msg |
| 393 | global errorInfo interactiveSession |
| 394 | if {!$interactiveSession} { |
| 395 | echo $errorInfo |
| 396 | exit 1 |
| 397 | } |
| 398 | } |
| 399 | } |
| 400 | |
| 401 | close $G_briefHelpFH |
| 402 | chmod a-w,a+r $G_helpDir/$briefFile |
| 403 | echo "*** completed extraction of all help files" |
| 404 | } |
| 405 | |
| 406 | #----------------------------------------------------------------------------- |
| 407 | # Print a usage message and exit the program |
| 408 | proc Usage {} { |
| 409 | puts stderr {Wrong args: [-m mergetree] -b briefFile helpdir manfile1 [manfile2..]} |
| 410 | exit 1 |
| 411 | } |
| 412 | |
| 413 | #----------------------------------------------------------------------------- |
| 414 | # Main program body, decides if help is interactive or batch. |
| 415 | |
| 416 | if {$interactiveSession} { |
| 417 | echo "To extract help, use the command:" |
| 418 | echo {GenerateHelp helpdir -m mergetree file-1 file-2 ...} |
| 419 | } else { |
| 420 | set mergeTree {} |
| 421 | set briefFile {} |
| 422 | while {[string match "-*" [lindex $argv 0]]} { |
| 423 | set flag [lvarpop argv 0] |
| 424 | case $flag in { |
| 425 | "-m" {set mergeTree [lvarpop argv]} |
| 426 | "-b" {set briefFile [lvarpop argv]} |
| 427 | default Usage |
| 428 | } |
| 429 | } |
| 430 | if {[llength $argv] < 2} { |
| 431 | Usage |
| 432 | } |
| 433 | if [lempty $briefFile] { |
| 434 | puts stderr {must specify -b argument} |
| 435 | Usage |
| 436 | } |
| 437 | GenerateHelp [lindex $argv 0] $briefFile $mergeTree [lrange $argv 1 end] |
| 438 | |
| 439 | } |