Terminal package - help file
Name:Terminal
Version:1.5.2
Last update:2019-11-27 14:12:46





Abstract

The terminal package is a collection of Tcl routines and data structures for providing a terminal and log file (similar to those of TeX) that can be used both with the embedded interpreter of the Alpha text editor and with a standard tclsh shell. The terminal package does not provide any mechanisms by which the user can provide direct input, so it can only emulate TeX's \nonstopmode and \batchmode interaction modes.

Introduction

Most of the code in AlphaTcl is of the type which makes little or no sense outside the context of a text editor with Tcl as built-in command language, but some code is more generally useful and would make sense also if run by e.g. tclsh (the Tcl shell); such code very often performs the function of converting some data in one format to another format. Most programs performing such functions do however need to occasionally convey some information to the user -- usually some error or warning message although other kinds of information can also be useful, even if not quite as necessary -- but this is not easy when the code has to run on both Alpha and tclsh, due to the lack of a suitable communication channel. In tclsh, the obvious choice is the stdout (or possibly stderr) output stream, but for Alpha the logical channel would instead be text that is inserted into a "shell" document window, but that makes no sense in tclsh. In short, these are two quite different worlds.
The solution to the dilemma is of course to introduce an API which lets the calling program handle both cases uniformly and this is precisely what terminal is about. terminal provides procedures for printing text, and whether that means writing to an output stream or inserting the text into an editor window is something that the calling program should not worry about. In addition to this abstraction of the communication with the user, terminal also provides for recording what is printed on a “log” file, and some higher level procedures which provide more convenient operations than the mere printing of a string, even though that is of course available as well. Right now the communication is limited to textual messages from the program to the user as that is the most immediate need, but additions could be made if the need arises (and a suitable implementation is found). In particular, some way of asking the user to choose a file could be a valuable addition.
As a Tcl package, terminal consists of the file terminal.tcl. This is installed as any normal Tcl package and should work on both Tcl 7 and 8 interpreters. From Tcl 8, the package is preferably loaded using the command
package require terminal
whereas on Tcl 7 it is up to the user to make sure that the file is sourced. There is also a file terminalPrefsAlpha.tcl which is only useful with Alpha. It allows the user to set preferences for the position and size of the terminal window when terminal is run under Alpha, but it does not define any of the terminal package commands.

Usage

The terminal package deals with two targets for the output it conveys: the terminal and the log file. The terminal is either an Alpha window or an output stream (by default stdout). The log file is simply something opened for writing using open; whether that there is a file on disk or something else is up to the file system to sort out. By default, output goes identically to both targets, but that can be overridden both in specific cases and in general. This is mainly used to let some output (such as verbose traces) go only to the log file, but it can be done the other way round too. Furthermore a target must first be opened before any output is actually printed on it; this is primarily to accommodate for programs where keeping a log file would seem overly elaborated -- if you do not want it then you should not be forced to open it either.

Commands for printing

Proc [terminal::print_word]
The main command one uses to print some text is the print_word procedure, which has the syntax
terminal::print_word {before} {string} {after} {target}? {flush}?
The {string} argument is the string that will be printed. There is no restriction as to which characters the {string} may contain; the `word' part of the command name refers instead to the fact that there is generally some amount of whitespace (that is managed by the command) around the {string}. The {before} and {after} arguments are used to request that the {string} is separated from surrounding text by at least some grade of whitespace: the possible values are none, space, newline, and emptyline. If the entire {string} does not fit on what remains of the line, then print_word will see to that it is printed on the beginning of a line. The {flush} argument is a boolean for whether the target should be flushed after the {string} has been printed. It defaults to 1 (flush).
The {target} argument can be used to specify to what the string should be printed. This argument can be `term' (the terminal), `log' (the log file), `both', or `none' (which essentially turns the print_word command into a no-op). The most common value is probably log, for information considered too verbose to print on the terminal. If the {target} argument is omitted then the value of the terminal::selector variable is used. This is initialized to both by default, but changing it to log would make a decent emulation of TeX's \batchmode.
Proc [terminal::print]
Besides print_word, there is also a lower-level printing procedure which is part of the interface to terminal, namely terminal::print. It is similar to print_word, but it lacks the {before} and {after} arguments, and it never inserts any whitespace before or after the string it prints. I suspect that it is usually more efficient to do one print_word on a concatenated string than to do several prints on the parts, so it is not unlikely that you will never have to use print directly.
Since some programs do not like very long lines, the terminal package generally puts a bound on how long lines can be and inserts a line break if they become too long. The bound is kept in the terminal::max_print_line variable and can be changed at any time. The default value is 79, which limits the line width to 79 characters. The print and print_word procedures do however react slightly differently to this limit. If the entire {string} to print does not fit on what remains of the line, then print_word will see to that it is printed on the beginning of the following line, but |print| makes no such adjustment. If either procedure prints the $max_print_line'th character on a line then they break the line after that character and put the next character in the {string} first on the following line.
Proc [terminal::print_block]
Another command which is occasionally useful is terminal::print_block. It has the syntax
terminal::print_block {before} {indent} {line-list} {after} {target}? {flush}?
The {line-list} argument is a list of lines that will be printed and the {indent} is a string (which of course can be empty) that will be inserted first in each line. The main difference to print_word is that these lines are not broken no matter how long they are -- this is mainly useful if one is given a block of preformatted text and has to print it as given no matter how long the lines are. If anything but emptyline is given as {before} or {after} then it is treated as newline.

Opening and closing

Proc [terminal::term_open]
As was mentioned above, a target must be opened before anything can be printed on it, as output to closed targets is silently ignored. The terminal is opened using the procedure terminal::term_open, which has the syntax
terminal::term_open {clear} {title}? {file}?
The {clear} argument is a flag for whether the terminal should be cleared after opening; 1 means clear, 0 means leave as it was. Whether clearing actually does anything depends on whether there is some way of defining and doing that with whatever serves as the terminal; it currently has no effect when the terminal is an output channel such as stdout. The {title} argument can be used to specify a title for the terminal (again provided that there is some way of defining and setting that for whatever serves as the terminal). The {title} defaults to `*terminal*'. The {file} argument is an identifier for an open channel to use for terminal output instead of the default stdout. (An obvious alternative is stderr.) In Alpha, where the terminal isn't a channel anyway, this argument is ignored.
Proc [terminal::term_autoopen]
A convenient variation on this procedure is terminal::term_autoopen, which has the syntax
terminal::term_autoopen {banner} {clear} {title}? {file}?
When run in tclsh, this is equivalent to
terminal::term_open $clear $title $file
terminal::print_block newline {} $banner newline term
but when run in Alpha it tries to be more sophisticated. If the terminal window isn't already open, then nothing will be shown until something else is written to the terminal, but at that time the above code will indeed be executed. This is intended mainly for programs which only write something when an error occurs, since opening a terminal window to which nothing interesting will be written can look rather odd.
Proc [terminal::log_open]
The log file is similarly opened using the procedure terminal::log_open, which has the syntax
terminal::log_open {name}
where {name} simply is the file system's name of the file to which the log shall be written. The file is opened in access mode w, so any previous contents in the file are deleted.
Proc [terminal::term_close], [terminal::log_close], [terminal::cleanup]
These opening procedures naturally have closing counterparts: the terminal::term_close and terminal::log_close procedures. Neither procedure takes any arguments. The log_close procedure actually effectuates a file close, but what term_close does is more of a disconnection (you usually don't want the text written to a terminal to disappear just because the program that wrote it doesn't have to write anything more). In either case though both terminal and log file should be closed if they are opened. To simplify this in case there has been any errors there is a procedure terminal::cleanup, which effectuates whatever closing operations are necessary.

Printing error messages

Proc [terminal::print_err]
Since an important class of things written to the terminal will be error messages, there is some point in combining all the related printing actions in a single procedure call. This is what the terminal::print_err procedure is for. The syntax is
terminal::print_err {message} {location} {help}?
where {message}, {location}, and {help} are all lists of strings, each of which correspond to one line of output. The {message} is the ``official'' message that identifies the error and the {location} is something which specifies where the error was detected. The purpose of this argument is to give some clue to where something should be changed (usually a position in some input file) to avoid the error. The {help}, finally, can be used to give some clue to what one should probably do to fix the error. This argument is optional, but can be quite helpful if there is a good value for it. The {help} lines are only written to the log file. The print_err procedure adds a "! " to the beginning of each {message} line (BTW, one such line is usually sufficient) and it also adds a period to the end of the last line of the {message}. The print_err procedure ends by flushing the output.
Each time print_err is called, it increments the terminal::error_count variable by one. When the value becomes greater than that of the terminal::max_error_count variable, print_err will generate the Tcl error `That makes <error_count> errors, please try again.'. The purpose of this is to stop programs that run amok generating an endless stream of error messages. If your program normally generates quite a lot of error messages, you might want to increase max_error_count; it is 100 by default, but it will not be overwritten if the value was already set when the terminal package is loaded. You can also clear the error_count whenever you want, even though it is probably a good idea to do so only in places where you know you're making progress. error_count is automatically cleared when you close the terminal or log file.

Printing progress messages

Another class of things written to the terminal are messages whose primary purpose is to show the user that some progress actually is being made on some lengthy job. In a communication channel where the contents can be updated this is rather straightforward, but in e.g. stdout where text is just being added one has to find a balance between printing something frequently enough to show that progress is being made and keeping the total amount to text written at a reasonably low level. The progress procedures have a number of features which facilitate maintaining this balance.
The idea is to only print one character most of the time, but occasionally (by default every tenth time) print something more which gives some numeric information about how much work has been done. Thus a progress message might look something like
Processing. ---------- [40%] ---------- [80%] ----- Done.
where `Processing.' and `Done.' are printed to mark the beginning and end of the message, whereas the hyphens are printed one at a time as the processing progresses. The bracketed expressions interrupting the sequence of hyphens are called markers. Proc [terminal::make_progress] The most commonly used command here is terminal::make_progress, which has the syntax
terminal::make_progress {amount}? {goal}?
It is called to inform the user that progress has been made; the {amount} and {goal} numbers are integers telling how much has been done and how much should be done in total. The percentages in the example above are the quotients of {amount} to {goal} at that particular point.
Procs [terminal::begin_progress], [terminal::end_progress]
Besides make_progress, there are also the terminal::begin_progress and terminal::end_progress procedures; these print the `Processing.' and `Done.' (both these strings may be changed by giving proper arguments to the procedures) in the above example.
A typical example of how these procedures are used is
proc do_file {fname} {
   terminal::begin_progress -title "Reading `$fname'."\
	 -percent [file size $fname] -mintime 300
   set fid [open $fname r]
   while {[gets $fid line]>=0} {
	  ##
	  # Do some processing of $line
	  ##
	  terminal::make_progress [tell $fid]
   }
   close $fid
   terminal::end_progress
}
Here there is no {goal} argument to make_progress since the goal never changes; instead that number is given after the -percent option to begin_progress. This options is one of the three begin_progress options that select the marker format:
-absolute
Selects the absolute marker format `[<amount>]', where the numerical values printed are simply the {amount} argument of make_progress. In this format, the {goal} amount is never used.
-percent {goal}
Selects the percentage marker format (as shown above), with {goal} as the goal amount.
-fraction
Selects the fraction marker format `[<amount>/<goal>]'.
If none of these options are used then make_progress prints hyphens, but never any markers. This is mainly useful if there is no quantity which can serve as {amount}. The fraction marker format is mainly intended for cases where the goal amount can change -- this occurs for example when the amount is a some number of files to process and files can include other files with the effect that these too will have to be processed -- and the goal amount is then updated using the {goal} argument of make_progress (if there is no such argument, then the most recently specified goal amount is used). It is always possible to initialise the goal amount in the call begin_progress, by using the -goal option:
-goal {goal}
Sets the goal amount to {goal}.
Another lone option is:
-title {beginning}
Sets the string printed at the beginning of a progress message. Defaults to `Processing.' if not given.
A companion of this option is however that end_progress takes as an optional argument a string to print at the end of the processing message. The default for this argument is `Done.'.
The following three options are used to control how often make_progress will print a marker.
-callsep {call separation}
This requests that make_progress must print at least {call separation} hyphens between each marker printed. The default is 10. Setting this to 0 removes this restriction.
-relsep {relative separation}
This requests that the quotient of {amount} to {goal} must have increased by at least {relative separation} between two successive markers. The default value is 0, which means nothing is requested. A {relative separation} of 0.01 means that it has to increase by at least one percentage point. This condition is ignored if no {goal} value has been given.
-abssep {absolute separation}
This requests that {amount} must have increased by at least {absolute separation} between two successive markers. The default value is 0, which means nothing is requested.
Finally, there are three options which can reduce the amount of time spent reporting progress (and thus in particular the number of hyphens printed by make_progress). The mechanism behind these is that make_progress begins with a test related to these options, and if that test fails then make_progress returns without printing a hyphen or even interpreting its arguments; the make_progress call is (so to say) effective only if the test succeeds. Exactly which tests are performed depends on the Tcl version used, and therefore a calling program might be best off specifying two options to cover all cases.
-mintime {milliseconds}
This option controls a test used in Tcl 8. The {milliseconds} is a bound on the wall clock time that should elapse between two effective make_progress calls. (Internally the test is implemented using the after and update commands.)
-mincmds {command count}
This option controls a test based on [info cmdcount] (the number of Tcl commands that have been evaluated in the interpreter); this test is used in all Tcl versions. The {command count} value is the minimal number of commands that should be evaluated between two effective make_progress calls.
-period {period}
This option used to control a test based on the number of times make_progress has been called since the last effective call to it, but now it controls the same test as the -mincmds option. To make the effect somewhat comparable however, the {period} number is multiplied by 100 before it is used as a {command count}.
All of the above has described what is written to the log file and stdout (when that serves as terminal), but when the terminal is an Alpha window, only the beginning and end strings are written to it. The user is instead given numerical information about the progress through a message shown on the status bar. The exact format of this message depends on which marker format is selected, but it is generally more verbose than the markers (as the information doesn't accumulate, there is no need to keep it short).
Printing other things between the corresponding calls to begin_progress and end_progress is not a problem, but it is probably a good idea to ensure that this material is separated from what is before and after it by newlines. This is automatically the case for material printed using print_err.

The AlphaTcl package and its preferences

There are in fact two AlphaTcl packages associated with the terminal Tcl package--the terminal library and the terminalPuts feature. This division stems from their respective inpacts on the Alpha user environment; terminal provides some code and preferences which do nothing unless explicitly called upon, whereas terminalPuts redefines the puts command to use the *terminal* window for the stdout and stderr channels. Since terminalPuts is secondary, the deinstallation information is tied to the terminal library.
There are three AlphaTcl preferences associated with the terminal package. These become available for editing in the Alpha ↣ Preferences ↣ Package Preferences ↣ Terminal Prefs dialog when the terminalPuts package is activated. They are also available as the mode preferences when the terminal window is the current.
Window Geometry
This preference can be used for setting a default size and position for the terminal window.
Max Print Line
This is a default value for the maxPrintLine variable, that places a limit on how long the lines written using print_word and friends may be. This is useful if you set the terminal window to be narrow.
Take Over Puts
This flag is only declared if terminalPuts is active. It controls a hack that redefines [puts stdout] and [puts stderr] so that text written to these files will instead show up in the terminal window. When the flag is on, terminal redefines the puts command. Turning the flag off (or deactivating the terminalPuts feature) restores the default definition.
All windows opened by terminal::term_open and terminal::term_autoopen have "Term" as mode (although there is no declaration of that mode as an Alpha package; it turns out that isn't needed). This can be useful if you want to add for example syntax colouring to terminal windows.

Notes

In their current state, the mechanisms provided by terminal (with the exception of the progress message stuff) are modelled after how TeX behaves when it is in the \nonstopmode interaction mode. If ways for the user to interact with the program are added then it might be possible to simulate the \scrollmode and \errorstopmode as well.
The progress stuff has no immediate counterpart in TeX, though -- if it looks terrible then I am to blame.
The terminal commands do not give special treatment to linefeed or carriage return characters in the string printed, meaning these are passed straight through as any other character, but there usually are underlying mechanisms which use either of these characters to signify a new line. Thus if the terminal commands are asked to print strings containing linefeeds or carriage returns then new lines may start at times where you did not expect it. The general cure to this is to [split] any string that may contain newlines into a list of lines and then print this list using [terminal::print_block].

An example

An example of what the terminal package can do can be found in the file Terminal-Example.tcl. This file contains a script which finds the undocumented Tcl commands that are built into Alpha, using the terminal package to report its finds.

Known problems

Please report any problem or bug you encounter to Alpha's Bug Tracker.

License and Disclaimer

Original Author: Lars Hellström.
Copyright (c) 2001-2019, Lars Hellström.
All rights reserved.
The Terminal package is free software and distributed under the terms of the new BSD license:
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LARS HELLSTRÖM BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
History:
modifiedbyrevreason
2001-07-21LH1.0original
2002-05-30LH1.1updated for terminal v1.3
2002-12-29LH1.2updated for terminal v1.4
2003-12-18LHupdated for terminal v1.5
2004-05-07LHupdated for terminal v1.5.1