home *** CD-ROM | disk | FTP | other *** search
/ PC World 1998 October / PCWorld_1998-10_cd.bin / software / prehled / komix / DATA.Z / rwmethods.tcl < prev    next >
Text File  |  1997-08-21  |  15KB  |  588 lines

  1. #---------------------------------------------------------------------------
  2. #
  3. #    (c) Cadre Technologies Inc. 1995
  4. #
  5. #    File:        @(#)rwmethods.tcl    /main/titanic/4
  6. #    Author:        Harm Leijendeckers
  7. #    Description:    Implementation of methods of class Report
  8. #
  9. #---------------------------------------------------------------------------
  10. # SccsId = @(#)rwmethods.tcl    /main/titanic/4    21 Aug 1997    Copyright 1995 Cadre Technologies Inc.
  11.  
  12.  
  13. ###########################################################################
  14. #
  15. #    Public methods.
  16. #
  17. ###########################################################################
  18.  
  19.  
  20. constructor Report {class this} {
  21.     set this [GCObject::constructor $class $this]
  22.     $this init
  23.     return $this
  24. }
  25.  
  26.  
  27. # Initialize attributes. It's also called in the constructor.
  28. method Report::init {this args} {
  29.     set errormsg "wrong # args: should be \"Report::init ?-linelen num?\
  30.         ?-pagelen num?\""
  31.  
  32.     # check arguments
  33.     set ret [scan $args "%s%d%s%d" what1 val1 what2 val2]
  34.     if { $ret != -1 && $ret != 2 && $ret != 4 } { error $errormsg }
  35.  
  36.     set oldLinelen [$this linelen]
  37.     set oldPagelen [$this pagelen]
  38.     $this linelen 0
  39.     $this pagelen 0
  40.  
  41.     for { set i 0 } { $i < [expr $ret/2] } { incr i } {
  42.     if { [Report::lchkfirst args -linelen] } {
  43.         $this linelen [lvarpop args]
  44.     }
  45.     if { [Report::lchkfirst args -pagelen] } {
  46.         $this pagelen [lvarpop args]
  47.     }
  48.     }
  49.  
  50.     if ![lempty $args] {
  51.     $this linelen oldLinelen
  52.     $this pagelen oldPagelen
  53.     error $errormsg
  54.     }
  55.  
  56.     # set linelen to parameter if used else use the m4 variable
  57.     # and if that is not set use the default value
  58.     if { [$this linelen] <= 0 } { $this linelen [m4_var get M4_a_printer_llen] }
  59.     if { [$this linelen] <= 0 } { $this linelen 130 }
  60.  
  61.     # set pagelen to parameter if used else use the m4 variable
  62.     # and if that is not set use the default value
  63.     if { [$this pagelen] <= 0 } {
  64.     $this pagelen [m4_var get M4_a_printer_plen]
  65.     }
  66.     if { [$this pagelen] <= 0 } { $this pagelen 61 }
  67.  
  68.     $this toPrint ""
  69.     $this lineno 0
  70.     $this pageno 0
  71.     $this date [clock format [clock seconds] -format "%d %h %Y %X"]
  72.     $this headerList ""
  73.     $this footerList ""
  74.  
  75.     return
  76. }
  77.  
  78.  
  79. # Main print procedure.
  80. method Report::print {this str args} {
  81.  
  82.     if ![Report::lchkfirst args verbatim] {
  83.     # substitute all tabs in str for one space
  84.     regsub -all (\t) $str " " str
  85.  
  86.     # substitute all newlines in str for one space
  87.     if {! [Report::lchkfirst args keepnl]} {
  88.         regsub -all (\r?\n) $str " " str
  89.     }
  90.     }
  91.  
  92.     set strLen [string first "\n" $str]
  93.     if {$strLen == -1} {
  94.     set strLen [string length $str]
  95.     set doSplit 0
  96.     } else {
  97.     set doSplit 1
  98.     }
  99.  
  100.     # number
  101.     set len [lindex $args 0]
  102.     expr { [Report::isInt $len] ? [lvarpop args]
  103.                 : [set len $strLen] }
  104.  
  105.     # 'left', 'center', 'right' or 'fill'
  106.     switch -exact [lindex $args 0] {
  107.     left    { set align left; lvarpop args }
  108.     center  { set align center; lvarpop args }
  109.     right   { set align right; lvarpop args }
  110.     fill    { set align fill; lvarpop args }
  111.     default { set align left }
  112.     }
  113.  
  114.     # 'break'
  115.     set break [Report::lchkfirst args break]
  116.  
  117.     # format str according to len, align and break and add str to toPrint
  118.     if { ((! $doSplit) && ($strLen <= $len)) || $len == 0 } {
  119.     # strlen(str) < len
  120.     lappend new_s [Report::align $str $len $align]
  121.     } else {
  122.     # strlen(str) > len
  123.     if { $break } {
  124.         # truncate str
  125.         regsub -all (\r?\n) $str " " str
  126.         lappend new_s [list [string range $str 0 [expr $len-1]]]
  127.     } else {
  128.         # break == off so divide str over more lines
  129.         foreach line [split $str "\n"] {
  130.         while { [string length $line] > $len } {
  131.             set sub [string range $line 0 [expr $len-1]]
  132.             set line [string range $line $len [string length $line]]
  133.             lappend new_s [list $sub]
  134.         }
  135.         # strlen(str) <= len so add aligned str
  136.         lappend new_s [Report::align $line $len $align]
  137.         }
  138.     }
  139.     }
  140.  
  141.     # add new_s to global toPrint
  142.     set tmp [$this toPrint]
  143.     lappend tmp [join $new_s]
  144.     $this toPrint $tmp
  145.  
  146.     # look if the next argument is 'line'
  147.     if { [Report::lchkfirst args line] } { $this line }
  148.  
  149.     # look if the next argument is 'page'
  150.     if { [Report::lchkfirst args page] } { $this page }
  151.  
  152.     if ![lempty $args] {
  153.     error [format "print command: unknown argument(s): %s" $args]
  154.     }
  155.  
  156.     return
  157. }
  158.  
  159.  
  160. # Print all outstanding output and a newline.
  161. method Report::line {this} {
  162.     set tp [$this toPrint]
  163.     $this toPrint ""
  164.  
  165.     # look if we need to print a header first
  166.     $this doHeader
  167.     $this doFooter
  168.  
  169.     if [lempty $tp] {
  170.     puts ""
  171.     $this lineno [expr [$this lineno]+1]
  172.     return
  173.     }
  174.  
  175.     for { set i 0 } { $i < [Report::maxelem $tp] } { incr i } {
  176.     set s ""
  177.     foreach j $tp {
  178.         if { [Report::isSeparator $j] } {
  179.             set elem [lindex $j 1]
  180.         } else {
  181.         if [lempty [lindex $j $i]] {
  182.             set elem [join [Report::align " " \
  183.             [string length [lindex $j 0]] fill]]
  184.         } else { set elem [lindex $j $i] }
  185.         }
  186.         append s $elem
  187.     }
  188.  
  189.     # delete trailing spaces
  190.     set s [string trimright $s]
  191.  
  192.     $this doHeader
  193.  
  194.     # if s is longer than linelen, we only have to print
  195.     # s again with length linelen
  196.     if { [string length $s] > [$this linelen] } {
  197.         $this print "$s" [$this linelen] line
  198.     } else {
  199.         puts $s
  200.         $this lineno [expr [$this lineno]+1]
  201.         $this doFooter
  202.     }
  203.     }
  204.  
  205.     return
  206. }
  207.  
  208.  
  209. # Print all outstanding lines and fill the rest of the page with empty lines.
  210. method Report::page {this} {
  211.     # print outstanding lines
  212.     if ![lempty [$this toPrint]] { $this line }
  213.  
  214.     # and fill the rest of the page with empty lines
  215.     while { ![$this doFooter] } { $this line }
  216.  
  217.     return
  218. }
  219.  
  220.  
  221. # Print a separator. This can be any kind of string.
  222. method Report::separator {this s args} {
  223.     set tmp [$this toPrint]
  224.     lappend tmp [list {} $s]
  225.     $this toPrint $tmp
  226.  
  227.     # look if the next argument is 'line'
  228.     if { [Report::lchkfirst args line] } { $this line }
  229.  
  230.     # look if the next argument is 'page'
  231.     if { [Report::lchkfirst args page] } { $this page }
  232.  
  233.     if ![lempty $args] {
  234.        error [format "separator command: unknown argument(s): %s" $args]
  235.     }
  236.  
  237.     return
  238. }
  239.  
  240.  
  241. # Print a number of spaces, default is one.
  242. method Report::space {this args} {
  243.     if [lempty $args] { set args 1 }
  244.     if { [llength $args] > 1 ||
  245.     ( [llength $args] == 1 && ![Report::isInt $args] ) } {
  246.     error [format "space command: unknown argument(s): %s" $args]
  247.     return
  248.     }
  249.  
  250.     $this print " " $args
  251. }
  252.  
  253.  
  254. method Report::file {this fileName {indent 0}} {
  255.     set fp [open $fileName]
  256.     set fstatus [gets $fp s]
  257.  
  258.     while { $fstatus != -1 } {
  259.     $this print " " $indent fill
  260.     set s [Report::expandTabs $s $indent 8]
  261.     $this print $s [expr [$this linelen]-$indent] line
  262.     set fstatus [gets $fp s]
  263.     }
  264.     close $fp
  265.  
  266.     return
  267.  
  268.  
  269. # Add a header to the headerlist. A header is a set of Tcl commands.
  270. method Report::header {this cmdstr} {
  271.     lappend hl [expr [info level]-1]
  272.     lappend hl $cmdstr
  273.  
  274.     # append to the end of headerList
  275.     set tmp [$this headerList]
  276.     lappend tmp $hl
  277.     $this headerList $tmp
  278.  
  279.     return
  280. }
  281.  
  282.  
  283. # Add a footer to the footerlist. A footer is a set of Tcl commands.
  284. method Report::footer {this cmdstr {len -1}} {
  285.     if { $len < 1 } { set len [Report::countLines $cmdstr] }
  286.  
  287.     lappend fl [expr [info level]-1]
  288.     lappend fl $len
  289.     lappend fl $cmdstr
  290.  
  291.     # insert at the beginning of footerList
  292.     $this footerList [linsert [$this footerList] 0 $fl]
  293.  
  294.     return
  295. }
  296.  
  297.  
  298. # Remove a header, footer or both on this level or on all levels.
  299. method Report::remove {this what args} {
  300.     set argerror "wrong # args: should be \"remove header|footer|hf <all>\""
  301.     set all [lvarpop args]
  302.  
  303.     if { ![lempty $args] || ($all != "" && $all != "all") } {
  304.     error $argerror
  305.     }
  306.  
  307.     if { $what == "hf" } {
  308.     $this remove header $all
  309.     $this remove footer $all
  310.     return
  311.     }
  312.  
  313.     if { $all != "all" } {
  314.     if { $what == "footer" } {
  315.         $this footerList [lrange [$this footerList] 1 end]
  316.         return
  317.     }
  318.     if { $what == "header" } {
  319.         $this headerList [lrange [$this headerList] \
  320.             0 [expr [llength [$this headerList]]-2]]
  321.         return
  322.     }
  323.     error $argerror
  324.     }
  325.  
  326.     # do a smart deletion of all the headers or footers on this
  327.     # level
  328.     if { $what == "header" || $what == "footer" } {
  329.     # walk through list and delete headers on
  330.     # current level and headers which should have
  331.     # been deleted by the user after leaving a
  332.     # level.
  333.  
  334.     if { $what == "header" } {
  335.         set origList [$this headerList]
  336.     } else {
  337.         set origList [Report::lreverse [$this footerList]]
  338.     }
  339.  
  340.     set callLevel [expr [info level]-2]
  341.     set prevLevel -1
  342.     set newL {}
  343.     foreach i $origList {
  344.         if { [lindex $i 0] >= $callLevel || [lindex $i 0] < $prevLevel } {
  345.             break
  346.         }
  347.         lappend newL $i
  348.         set prevLevel [lindex $i 0]
  349.     }
  350.  
  351.     if { $what == "header" } {
  352.         $this headerList $newL
  353.     } else {
  354.         $this footerList [Report::lreverse $newL]
  355.     }
  356.  
  357.     return
  358.     }
  359.  
  360.     error $argerror
  361. }
  362.  
  363.  
  364. # Return 1 if there is output to be printed, otherwise 0
  365. method Report::queued {this} {
  366.     if [lempty [$this toPrint]] {
  367.     return 0
  368.     }
  369.  
  370.     return 1
  371. }
  372.  
  373.  
  374. ###########################################################################
  375. #
  376. #    Private methods. Don't call them outside a method.
  377. #
  378. ###########################################################################
  379.  
  380.  
  381. # Execute headerList if lineno == 0.
  382. method Report::doHeader {this} {
  383.     if { ([$this lineno] != 0 && [$this lineno] <= [$this pagelen])
  384.         || [$this pagelen] == -1 } {
  385.     return
  386.     }
  387.  
  388.     $this pageno [expr [$this pageno]+1]
  389.  
  390.     # set the pagelen to unlimited so the header is always printed
  391.     # as if it fits on one page
  392.     set oldpagelen [$this pagelen]
  393.     $this pagelen -1
  394.     $this lineno 1
  395.  
  396.     foreach i [$this headerList] {
  397.     if { [info level] < [lindex $i 0] } {
  398.         # skip headers which were defined on a lower level
  399.         # than the current level
  400.         continue
  401.     }
  402.     # execute the header on the level it was defined
  403.     uplevel #[lindex $i 0] [lindex $i 1]
  404.     }
  405.  
  406.     $this pagelen $oldpagelen
  407.  
  408.     # if the header needed to be printed on more than one page
  409.     # the current lineno is bigger then pagelen, so adjust it
  410.     $this lineno [expr [$this lineno]%[$this pagelen]]
  411.     if { ![$this lineno] } { $this lineno [$this pagelen] }
  412.  
  413.     return
  414. }
  415.  
  416.  
  417. # Execute footerList if lineno+footerlength == pagelen.
  418. method Report::doFooter {this} {
  419.     if { [$this pagelen] == -1 } { return 0 }
  420.  
  421.     # calculate current footerlength
  422.     set len 0
  423.     foreach i [$this footerList] { incr len [lindex $i 1] }
  424.  
  425.     # if we have got to print the footer
  426.     if { [expr $len+[$this lineno]] > [$this pagelen] } {
  427.     set oldpagelen [$this pagelen]
  428.     $this pagelen -1
  429.  
  430.     foreach i [$this footerList] {
  431.         # execute the footer on the level it was defined
  432.         uplevel #[lindex $i 0] [lindex $i 2]
  433.     }
  434.  
  435.     # if the user forgot a "print line" we do it
  436.     if ![lempty [$this toPrint]] { $this line }
  437.  
  438.     $this pagelen $oldpagelen
  439.  
  440.     # if lineno < pagelen the footer was shorter than expected
  441.     # so print some empty lines to cover it
  442.     for { } \
  443.         { [$this lineno] <= [$this pagelen] } \
  444.         { $this lineno [expr [$this pageno]+1] } \
  445.         { puts "" }
  446.  
  447.     $this lineno 0
  448.  
  449.     return 1
  450.     }
  451.  
  452.     return 0
  453. }
  454.  
  455.  
  456. ###########################################################################
  457. #
  458. #    Public methods, implemented as procedures.
  459. #
  460. ###########################################################################
  461.  
  462.  
  463. # Correct version of lrange, used as long as the bug in lrange is still there.
  464. proc Report::lrange {list start end} {
  465.     set result ""
  466.  
  467.     if { $end == "end" } { set end [expr [llength $list]-1] }
  468.  
  469.     for { set i $start } { $i <= $end } { incr i } {
  470.     lappend result [lindex $list $i]
  471.     }
  472.  
  473.     return $result
  474. }
  475.  
  476.  
  477. # Align string 's' according to 'len' and 'align' (== left|right|center|fill).
  478. proc Report::align {s len align} {
  479.     set retval ""
  480.  
  481.     if { $len <= [string length $s] } {
  482.     set retval [string range $s 0 [expr $len-1] ]
  483.     } else {
  484.     switch -exact $align {
  485.     right   { set retval [format [format "%%%ds" $len] $s] }
  486.     center  { set tmp [format [format "%%%ds" [expr ($len+\
  487.             [string length $s])/2] ] $s]
  488.           set retval [format [format "%%-%ds" $len] $tmp] }
  489.     fill    { set tmp $s
  490.           while { [string length $tmp] < $len } {
  491.             append tmp $s
  492.           }
  493.           set retval [string range $tmp 0 [expr $len-1] ] }
  494.     default { set retval [format [format "%%-%ds" $len] $s] }
  495.     }
  496.     }
  497.  
  498.     return [list $retval]
  499. }
  500.  
  501.  
  502. # Return 1 if parameter is an integer, else 0.
  503. proc Report::isInt {int} {
  504.     return [expr { [scan $int "%d%s" int rest] == 1 }]
  505. }
  506.  
  507.  
  508. # Return 1 if the first element of 'list' == 'first' and delete that first
  509. # element, else return 0.
  510. proc Report::lchkfirst {list first} {
  511.     upvar 1 $list l
  512.  
  513.     if { [lindex $l 0] == $first } {
  514.     lvarpop l
  515.     return 1
  516.     }
  517.  
  518.     return 0
  519. }
  520.  
  521.  
  522. # Return 1 if parameter is a separater, else 0.
  523. proc Report::isSeparator {args} {
  524.     set args [join $args]
  525.     return [expr {[llength $args] == 2 &&
  526.           [string length [lindex $args 0]] == 0 &&
  527.           [string length [lindex $args 1]] != 0}]
  528. }
  529.  
  530.  
  531. # Return the number of elements of the biggest subtree in 'args'.
  532. proc Report::maxelem {args} {
  533.     set args [join $args]
  534.     set retval 0
  535.     foreach i $args {
  536.     if { [Report::isSeparator $i] } { continue }
  537.     if { [llength $i] > $retval } { set retval [llength $i] }
  538.     }
  539.     return $retval
  540. }
  541.  
  542.  
  543. # Count the number of times that word "line" occurs in string 's'.
  544. proc Report::countLines {s} {
  545.     set num 0
  546.     while { [regsub "line" $s "" s] } { incr num }
  547.     return $num
  548. }
  549.  
  550.  
  551. # Expand tabs to spaces in string 's', tablength is variable, default = 8.
  552. proc Report::expandTabs {s indent {tablen 8}} {
  553.     if { $tablen < 0 } { set tablen 0 }
  554.  
  555.     # add indent spaces
  556.     set s [format [format "%%%ds%%s" $indent] "" $s]
  557.  
  558.     set pos [string first \t $s]
  559.     while { $pos != -1 } {
  560.     if { $tablen != 0 } {
  561.         set numspaces [expr $tablen-$pos%$tablen]
  562.     } else {
  563.         set numspaces 0
  564.     }
  565.  
  566.     set spaces [Report::align " " $numspaces fill]
  567.  
  568.     # join is needed otherwise spaces are enclosed in braces
  569.     regsub \t $s [join $spaces] s
  570.  
  571.     set pos [string first \t $s]
  572.     }
  573.  
  574.     # remove the indented spaces
  575.     set s [string range $s $indent end]
  576.  
  577.     return $s
  578. }
  579.  
  580.  
  581. # Return the reverse of list 'l'.
  582. proc Report::lreverse {l} {
  583.     if [lempty $l] { return {} }
  584.     set elem [lvarpop l]
  585.     return [concat [Report::lreverse $l] [list $elem]]
  586. }
  587.