4 # Tcl help command. (see TclX manual)
6 #------------------------------------------------------------------------------
7 # Copyright 1992-1999 Karl Lehenbauer and Mark Diekhans.
9 # Permission to use, copy, modify, and distribute this software and its
10 # documentation for any purpose and without fee is hereby granted, provided
11 # that the above copyright notice appear in all copies. Karl Lehenbauer and
12 # Mark Diekhans make no representations about the suitability of this
13 # software for any purpose. It is provided "as is" without express or
15 #------------------------------------------------------------------------------
16 # The help facility is based on a hierarchical tree of subjects (directories)
17 # and help pages (files). There is a virtual root to this tree. The root
18 # being the merger of all "help" directories found along the $auto_path
20 #------------------------------------------------------------------------------
21 # $Id: help.tcl,v 1.2 2007/11/23 10:59:34 anthony Exp $
22 #------------------------------------------------------------------------------
25 #@package: TclX-help help helpcd helppwd apropos
27 namespace eval ::help {
28 variable curSubject
"/"
31 #------------------------------------------------------------------------------
34 proc help
{{what
{}}} {
35 variable ::help::lineCnt 0
37 # Special case "help help", so we can get it at any level.
39 if {($what == "help") ||
($what == "?")} {
44 set pathList
[help
::ConvertPath $what]
45 if {[file isfile
[lindex $pathList 0]]} {
46 help
::DisplayPage [lindex $pathList 0]
50 help
::ListSubject $what $pathList subjects pages
51 set relativeDir
[help
::RelativePath [lindex $pathList 0]]
53 if {[llength $subjects] != 0} {
54 help
::Display "\nSubjects available in $relativeDir:"
55 help
::DisplayColumns $subjects
57 if {[llength $pages] != 0} {
58 help
::Display "\nHelp pages available in $relativeDir:"
59 help
::DisplayColumns $pages
64 #------------------------------------------------------------------------------
65 # helpcd command. The name of the new current directory is assembled from the
66 # current directory and the argument.
68 proc helpcd
{{dir
/}} {
69 variable ::help::curSubject
71 set pathName
[lindex [help
::ConvertPath $dir] 0]
73 if {![file isdirectory
$pathName]} {
74 error "\"$dir\" is not a subject" [list TCLXHELP NOTSUBJECT
$dir]
77 set ::help::curSubject [help
::RelativePath $pathName]
81 #------------------------------------------------------------------------------
85 variable ::help::curSubject
86 puts "Current help subject: $::help::curSubject"
89 #------------------------------------------------------------------------------
90 # apropos command. This search the
92 proc apropos
{regexp} {
93 variable ::help::lineCnt 0
94 variable ::help::curSubject
97 foreach dir
[help
::RootDirs] {
98 foreach brief
[glob -nocomplain $dir/*.brf
] {
99 set briefFH
[open $brief]
100 while {[gets $briefFH line
] != -1} {
101 if {[regexp -nocase $regexp $line] == 1} {
102 set path
[lindex $line 0]
103 set desc
[lrange $line 1 end
]
104 if {![help
::Display [format "%s - %s" $path $desc]]} {
117 ## Private Helper Routines
120 #----------------------------------------------------------------------
121 # Return a list of help root directories.
123 proc ::help::RootDirs {} {
126 foreach dir
$auto_path {
127 if {[file isdirectory
$dir/help
]} {
128 lappend roots
$dir/help
134 #--------------------------------------------------------------------------
135 # Take a path name which might have "." and ".." elements and flatten them
136 # out. Also removes trailing and adjacent "/", unless its the only
139 proc ::help::FlattenPath pathName
{
141 foreach element
[split $pathName /] {
142 if {"$element" == "." ||
![string compare
"" $element]} continue
144 if {"$element" == ".."} {
145 if {[llength [join $newPath /]] == 0} {
146 error "Help: name goes above subject directory root" {} \
147 [list TCLXHELP NAMEABOVEROOT
$pathName]
149 set index
[expr [llength $newPath]-1]
150 set newPath
[lreplace $newPath $index $index]
153 lappend newPath
$element
155 set newPath
[join $newPath /]
157 # Take care of the case where we started with something line "/" or "/."
159 if {("$newPath" == "") && [string match
"/*" $pathName]} {
166 #--------------------------------------------------------------------------
167 # Given a pathName relative to the virtual help root, convert it to a list
168 # of real file paths. A list is returned because the path could be "/",
169 # returning a list of all roots. The list is returned in the same order of
170 # the auto_path variable. If path does not start with a "/", it is take as
171 # relative to the current help subject. Note: The root directory part of
172 # the name is not flattened. This lets other commands pick out the part
173 # relative to the one of the root directories.
175 proc ::help::ConvertPath pathName
{
178 if {![string match
"/*" $pathName]} {
179 if {![string compare
$curSubject "/"]} {
180 set pathName
"/$pathName"
182 set pathName
"$curSubject/$pathName"
185 set pathName
[FlattenPath
$pathName]
187 # If the virtual root is specified, return a list of directories.
189 if {$pathName == "/"} {
193 # Not the virtual root find the first match.
195 foreach dir
[RootDirs
] {
196 if {[file readable
$dir/$pathName]} {
197 return [list $dir/$pathName]
201 # Not found, try to find a file matching only the file tail,
202 # for example if --> <helpDir>/tcl/control/if.
204 set fileTail
[file tail
$pathName]
205 foreach dir
[RootDirs
] {
206 set fileName
[exec find
$dir -name $fileTail | head
-1]
207 if {$fileName != {}} {
208 return [list $fileName]
212 error "\"$pathName\" does not exist" {} \
213 [list TCLXHELP NOEXIST
$pathName]
216 #--------------------------------------------------------------------------
217 # Return the virtual root relative name of the file given its absolute
218 # path. The root part of the path should not have been flattened, as we
219 # would not be able to match it.
221 proc ::help::RelativePath pathName
{
222 foreach dir
[RootDirs
] {
223 if {[string range
$pathName 0 [expr [string length
$dir] - 1]] == $dir} {
224 set name
[string range
$pathName [string length
$dir] end
]
225 if {$name == ""} {set name
/}
229 if {![info exists found
]} {
230 error "problem translating \"$pathName\"" {} [list TCLXHELP INTERROR
]
234 #--------------------------------------------------------------------------
235 # Given a list of path names to subjects generated by ConvertPath, return
236 # the contents of the subjects. Two lists are returned, subjects under
237 # that subject and a list of pages under the subject. Both lists are
238 # returned sorted. This merges all the roots into a virtual root.
239 # pathName is the string that was passed to ConvertPath and is used for
240 # error reporting. *.brk files are not returned.
242 proc ::help::ListSubject {pathName pathList subjectsVar pagesVar
} {
243 upvar $subjectsVar subjects
$pagesVar pages
248 foreach dir
$pathList {
249 if {![file isdirectory
$dir] ||
![string compare
[file tail
$dir] CVS
]} continue
251 foreach file [glob -nocomplain $dir/*] {
252 if {[lsearch {.brf .orig .diff .rej
} [file extension
$file]] \
254 if [file isdirectory
$file] {
255 lappend subjects
[file tail
$file]/
257 lappend pages
[file tail
$file]
262 if {![string compare
$pathName /]} {
264 error "no \"help\" directories found on auto_path ($auto_path)" {} \
265 [list TCLXHELP NOHELPDIRS
]
267 error "\"$pathName\" is not a subject" {} \
268 [list TCLXHELP NOTSUBJECT
$pathName]
271 set subjects
[lsort $subjects]
272 set pages
[lsort $pages]
276 #--------------------------------------------------------------------------
277 # Display a line of output, pausing waiting for input before displaying if
278 # the screen size has been reached. Return 1 if output is to continue,
279 # return 0 if no more should be outputed, indicated by input other than
283 proc ::help::Display line
{
285 if {$lineCnt >= 23} {
287 puts -nonewline stdout
":"
290 if {[string compare
"" $response]} {
297 #--------------------------------------------------------------------------
298 # Display a help page (file).
300 proc ::help::DisplayPage filePath
{
302 set inFH
[open $filePath r
]
303 while {[gets $inFH fileBuf
] >= 0} {
304 if {![Display
$fileBuf]} {
311 #--------------------------------------------------------------------------
312 # Display a list of file names in a column format. This use columns of 14
313 # characters 3 blanks.
315 proc ::help::DisplayColumns {nameList
} {
318 foreach name
$nameList {
323 if {[incr count
] < 4} {
324 set padLen
[expr 17-[string length
$name]]
328 for {set i
0} {$i < $padLen} {incr i
1} {
333 if {![Display
$outLine]} {
340 Display
[string trimright
$outLine]}
345 #--------------------------------------------------------------------------
346 # Display help on help, the first occurance of a help page called "help" in
349 proc ::help::HelpOnHelp {} {
350 set helpPage
[lindex [ConvertPath
/help
] 0]
351 if {![string compare
"" $helpPage]} {
352 error "No help page on help found" {} \
353 [list TCLXHELP NOHELPPAGE
]
355 DisplayPage
$helpPage