#
# help.tcl --
#
# Tcl help command. (see TclX manual)
#
#------------------------------------------------------------------------------
# Copyright 1992-1999 Karl Lehenbauer and Mark Diekhans.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted, provided
# that the above copyright notice appear in all copies. Karl Lehenbauer and
# Mark Diekhans make no representations about the suitability of this
# software for any purpose. It is provided "as is" without express or
# implied warranty.
#------------------------------------------------------------------------------
# The help facility is based on a hierarchical tree of subjects (directories)
# and help pages (files). There is a virtual root to this tree. The root
# being the merger of all "help" directories found along the $auto_path
# variable.
#------------------------------------------------------------------------------
# $Id: help.tcl,v 1.2 2004/11/23 05:54:15 hobbs Exp $
#------------------------------------------------------------------------------
#
#@package: TclX-help help helpcd helppwd apropos
namespace eval ::tclx {
namespace export help helpcd helppwd apropos
}
namespace eval ::tclx::help {
variable curSubject "/"
}
#------------------------------------------------------------------------------
# Help command.
proc ::tclx::help {{what {}}} {
variable ::tclx::help::lineCnt 0
# Special case "help help", so we can get it at any level.
if {($what == "help") || ($what == "?")} {
tclx::help::HelpOnHelp
return
}
set pathList [tclx::help::ConvertPath $what]
if {[file isfile [lindex $pathList 0]]} {
tclx::help::DisplayPage [lindex $pathList 0]
return
}
tclx::help::ListSubject $what $pathList subjects pages
set relativeDir [tclx::help::RelativePath [lindex $pathList 0]]
if {[llength $subjects] != 0} {
tclx::help::Display "\nSubjects available in $relativeDir:"
tclx::help::DisplayColumns $subjects
}
if {[llength $pages] != 0} {
tclx::help::Display "\nHelp pages available in $relativeDir:"
tclx::help::DisplayColumns $pages
}
}
#------------------------------------------------------------------------------
# helpcd command. The name of the new current directory is assembled from the
# current directory and the argument.
proc ::tclx::helpcd {{dir /}} {
variable ::tclx::help::curSubject
set pathName [lindex [tclx::help::ConvertPath $dir] 0]
if {![file isdirectory $pathName]} {
error "\"$dir\" is not a subject" [list TCLXHELP NOTSUBJECT $dir]
}
set ::tclx::help::curSubject [tclx::help::RelativePath $pathName]
return
}
#------------------------------------------------------------------------------
# Helpcd main.
proc ::tclx::helppwd {} {
variable ::tclx::help::curSubject
echo "Current help subject: $::tclx::help::curSubject"
}
#------------------------------------------------------------------------------
# apropos command. This search the
proc ::tclx::apropos {regexp} {
variable ::tclx::help::lineCnt 0
variable ::tclx::help::curSubject
set ch [scancontext create]
scanmatch -nocase $ch $regexp {
set path [lindex $matchInfo(line) 0]
set desc [lrange $matchInfo(line) 1 end]
if {![tclx::help::Display [format "%s - %s" $path $desc]]} {
set stop 1
return
}
}
set stop 0
foreach dir [tclx::help::RootDirs] {
foreach brief [glob -nocomplain $dir/*.brf] {
set briefFH [open $brief]
try_eval {
scanfile $ch $briefFH
} {} {
close $briefFH
}
if {$stop} break
}
if {$stop} break
}
scancontext delete $ch
}
##
## Private Helper Routines
##
#----------------------------------------------------------------------
# Return a list of help root directories.
proc ::tclx::help::RootDirs {} {
global auto_path
set roots {}
foreach dir $auto_path {
if {[file isdirectory $dir/help]} {
lappend roots $dir/help
}
}
return $roots
}
#--------------------------------------------------------------------------
# Take a path name which might have "." and ".." elements and flatten them
# out. Also removes trailing and adjacent "/", unless its the only
# character.
proc ::tclx::help::FlattenPath pathName {
set newPath {}
foreach element [split $pathName /] {
if {"$element" == "." || [lempty $element]} continue
if {"$element" == ".."} {
if {[llength [join $newPath /]] == 0} {
error "Help: name goes above subject directory root" {} \
[list TCLXHELP NAMEABOVEROOT $pathName]
}
lvarpop newPath [expr [llength $newPath]-1]
continue
}
lappend newPath $element
}
set newPath [join $newPath /]
# Take care of the case where we started with something line "/" or "/."
if {("$newPath" == "") && [string match "/*" $pathName]} {
set newPath "/"
}
return $newPath
}
#--------------------------------------------------------------------------
# Given a pathName relative to the virtual help root, convert it to a list
# of real file paths. A list is returned because the path could be "/",
# returning a list of all roots. The list is returned in the same order of
# the auto_path variable. If path does not start with a "/", it is take as
# relative to the current help subject. Note: The root directory part of
# the name is not flattened. This lets other commands pick out the part
# relative to the one of the root directories.
proc ::tclx::help::ConvertPath pathName {
variable curSubject
if {![string match "/*" $pathName]} {
if {[cequal $curSubject "/"]} {
set pathName "/$pathName"
} else {
set pathName "$curSubject/$pathName"
}
}
set pathName [FlattenPath $pathName]
# If the virtual root is specified, return a list of directories.
if {$pathName == "/"} {
return [RootDirs]
}
# Not the virtual root find the first match.
foreach dir [RootDirs] {
if {[file readable $dir/$pathName]} {
return [list $dir/$pathName]
}
}
# Not found, try to find a file matching only the file tail,
# for example if --> <helpDir>/tcl/control/if.
set fileTail [file tail $pathName]
foreach dir [RootDirs] {
set fileName [exec find $dir -name $fileTail | head -1]
if {$fileName != {}} {
return [list $fileName]
}
}
error "\"$pathName\" does not exist" {} \
[list TCLXHELP NOEXIST $pathName]
}
#--------------------------------------------------------------------------
# Return the virtual root relative name of the file given its absolute
# path. The root part of the path should not have been flattened, as we
# would not be able to match it.
proc ::tclx::help::RelativePath pathName {
foreach dir [RootDirs] {
if {[csubstr $pathName 0 [clength $dir]] == $dir} {
set name [csubstr $pathName [clength $dir] end]
if {$name == ""} {set name /}
return $name
}
}
if {![info exists found]} {
error "problem translating \"$pathName\"" {} [list TCLXHELP INTERROR]
}
}
#--------------------------------------------------------------------------
# Given a list of path names to subjects generated by ConvertPath, return
# the contents of the subjects. Two lists are returned, subjects under
# that subject and a list of pages under the subject. Both lists are
# returned sorted. This merges all the roots into a virtual root.
# pathName is the string that was passed to ConvertPath and is used for
# error reporting. *.brk files are not returned.
proc ::tclx::help::ListSubject {pathName pathList subjectsVar pagesVar} {
upvar $subjectsVar subjects $pagesVar pages
set subjects {}
set pages {}
set foundDir 0
foreach dir $pathList {
if {![file isdirectory $dir] || [cequal [file tail $dir] CVS]} continue
set foundDir 1
foreach file [glob -nocomplain $dir/*] {
if {[lsearch {.brf .orig .diff .rej} [file extension $file]] \
>= 0} continue
if [file isdirectory $file] {
lappend subjects [file tail $file]/
} else {
lappend pages [file tail $file]
}
}
}
if {!$foundDir} {
if {[cequal $pathName /]} {
global auto_path
error "no \"help\" directories found on auto_path ($auto_path)" {} \
[list TCLXHELP NOHELPDIRS]
} else {
error "\"$pathName\" is not a subject" {} \
[list TCLXHELP NOTSUBJECT $pathName]
}
}
set subjects [lsort $subjects]
set pages [lsort $pages]
return {}
}
#--------------------------------------------------------------------------
# Display a line of output, pausing waiting for input before displaying if
# the screen size has been reached. Return 1 if output is to continue,
# return 0 if no more should be outputed, indicated by input other than
# return.
#
proc ::tclx::help::Display line {
variable lineCnt
if {$lineCnt >= 23} {
set lineCnt 0
puts -nonewline stdout ":"
flush stdout
gets stdin response
if {![lempty $response]} {
return 0}
}
puts stdout $line
incr lineCnt
}
#--------------------------------------------------------------------------
# Display a help page (file).
proc ::tclx::help::DisplayPage filePath {
set inFH [open $filePath r]
try_eval {
while {[gets $inFH fileBuf] >= 0} {
if {![Display $fileBuf]} {
break
}
}
} {} {
close $inFH
}
}
#--------------------------------------------------------------------------
# Display a list of file names in a column format. This use columns of 14
# characters 3 blanks.
proc ::tclx::help::DisplayColumns {nameList} {
set count 0
set outLine ""
foreach name $nameList {
if {$count == 0} {
append outLine " "
}
append outLine $name
if {[incr count] < 4} {
set padLen [expr 17-[clength $name]]
if {$padLen < 3} {
set padLen 3}
append outLine [replicate " " $padLen]
} else {
if {![Display $outLine]} {
return}
set outLine ""
set count 0
}
}
if {$count != 0} {
Display [string trimright $outLine]}
return
}
#--------------------------------------------------------------------------
# Display help on help, the first occurance of a help page called "help" in
# the help root.
proc ::tclx::help::HelpOnHelp {} {
set helpPage [lindex [ConvertPath /help] 0]
if {[lempty $helpPage]} {
error "No help page on help found" {} \
[list TCLXHELP NOHELPPAGE]
}
DisplayPage $helpPage
}
Copyright 2K16 - 2K18 Indonesian Hacker Rulez