Publishing Tcl Procedures
by
Jon Salz
and
Michael Yoon
on 6 July 2000
ACS Documentation :
ACS Core Architecture Guide :
API Publication :
Publishing Tcl Procedures
The Big Picture
Using the Tcl language's built-in
proc
command to define
procedures poses two main problems:
- There is no built-in facility for documenting procedures defined
with
proc
.
- In order to accept switches (e.g., the
regexp
command's -nocase
switch), a procedure defined with
proc
must include logic to parse its own argument list.
Therefore, Tcl procedure APIs in the ACS are published by using
ad_proc
instead of
proc
command to define
procedures, which provides automatic switch parsing (based
on an extended syntax for the argument list) and optionally accepts an
argument containing documentation for the procedure.
New in ACS 3.4
ACS 3.4 includes an enhanced version of
ad_proc
that
provides several new features:
- true boolean switches that do not require the caller to specify a
value (like the switches for built-in Tcl commands)
- mandatory switches and optional switches without default values
(previously each switch had to have a default value)
- structured documentation (argument descriptions, potential errors,
etc. à la Javadoc)
- defining procedures with no switches (previously, each proc had to
have at least one switch)
- explicit distinction between public and private procedures (by
definition, APIs consist only of the former)
- deprecation of obsolete procedures
(The predecessor of
ad_proc
,
proc_doc
, only
solved the first of the above problems and is deprecated as of ACS
3.4.)
ACS 3.4 also introduces the ad_library
proc that
supersedes file header comments as the method of providing
documentation for the set of Tcl procedures in one library file.
How to Use ad_proc
The syntax for calling
ad_proc
is:
ad_proc [ -public | -private ] [ -deprecated [ -warn ] ] proc_name arg-list \
[ doc-string ] code-block
Here are descriptions of each switch that
ad_proc
itself
accepts:
-public
- proc_name is part of the enclosing package's public
interface (a.k.a. API)
-private
- proc_name is not part of the enclosing package's API
-deprecated
- proc_name is obsolete and should not be called
-warn
- the first time proc_name is called, it will automatically
log a warning, so that the site maintainer will be notified and can
remove the call to the deprecated procedure (requires that
-deprecated
also be specified)
The
arg-list argument of
ad_proc
defines the
syntax for the procedure
proc_name, and consists of:
- Zero or more switch declarations: the set of switches that
can be provided when calling proc_name
- Zero or more positional argument declarations: the set of
arguments that proc_name accepts
Switch Declarations
A
switch declaration must take one of the following
forms:
{ -switch_name default }
- An optional switch that takes a default value of
default
if no value is provided. The switch's
value will be placed in $switch_name
-switch_name
- An optional switch that takes an argument with no default
value. If the switch is provided, the argument will be placed in
$switch_name
; if not, then
$switch_name
will not be initialized (i.e.,
info exists switch_name
will return 0).
-switch_name:required
- A required switch that takes an argument. The switch's value will
be placed in
$switch_name
.
-switch_name:boolean
- An optional boolean switch (see below). If the switch is provided,
$switch_name_p
will be set to 1; if not,
$switch_name_p
will be set to 0.
When invoking a procedure defined with
ad_proc
, boolean
switches can be specified in one of two ways, either:
- with no value, i.e., as a hyphen followed by the switch name,
e.g., "
-debug
" in
ad_register_filter -debug preauth GET /* my_filter
- with a value, i.e., as a hyphen followed by the switch name,
followed by an equal sign, followed by an integer value, e.g.,
"
-debug=n
" in
ad_register_filter -debug=1 preauth GET /* my_filter
This is useful when program logic determines the value for the switch
at runtime, e.g.:
ad_register_filter -debug=$debug_p preauth GET /* ad_my_filter
Without this feature, one would need to write
if { $debug_p } {
ad_register_filter -debug preauth GET /* ad_my_filter
} else {
ad_register_filter preauth GET /* ad_my_filter
}
or build the command in a list and use eval
.
Positional Argument Declarations
Declaring a
positional argument with
ad_proc
is
identical to declaring a positional argument with
proc
:
either just a name (for required arguments) or a two-element list
consisting of a name and default value (for optional arguments). As
with
proc
, the last positional argument can be
args
, in which case the
$args
variable is
set to a list of any extra arguments supplied in the procedure call.
Examples: Argument List Declarations
Consider
ad_register_filter
, the ACS analog of
AOLserver's
ns_register_filter
:
ad_proc -public ad_register_filter {
-debug:boolean
-critical:boolean
{ -priority 10000 }
-description
kind method path proc args
} {
... documentation ...
} {
... code ...
}
The argument list is everything inside the braces immediately
following the procedure name
ad_register_filter
. The
switch declarations are the first four items
(
-debug:boolean
through
-description
), and
the positional arguments are the remaining items.
In the scope of the code block, $priority
is always set
(to the supplied value of the -priority
switch, or 10000
if omitted), whereas $description
is only set if the
-description
switch is provided. This is an important
distinction, since the semantics of omitting a switch are often
different from those of setting the value of a switch to be the empty
string.
The db_string
proc (part of Database Access API) illustrates this
distinction clearly: If its optional -default
switch is
provided, then it is OK for the query to return zero rows; the
supplied default value will be returned. However, if the switch is
omitted, then the query must return one row, or an error will be
raised.
Of course, the implementation of db_string
(and of any
procedure that accepts optional switches) must handle both these
cases:
if { [info exists default] } {
# A default value was provided; zero rows are OK.
... code ...
} else {
# No default value; throw an error if the query
# returns zero rows.
... code ...
}
Javadoc
serves as our model for structured documentation, not only for
procedures but also for other type of API.
A documentation string consists of HTML-formatted text that
informs the reader what the procedure does and how to use it
correctly. The first sentence of the documentation string should be a
standalone description of the procedure, as it will be presented in
the summary view of the API Documentation Browser. Since the text of
the documentation string will be interpreted as HTML, you must
properly escape HTML characters that you don't want interpreted, such
as <, > and &.
The main text of the documentation string is followed by a series of
blocks that look like:
@tag Comment for the tag
A documentation string can contain:
- multiple
@author
tags, each followed
by the name (and e-mail address, in parentheses) of an author of the
procedure
- multiple
@param
tags, each followed
by the name of a parameter and then a description
- one
@return
tag, followed
by a description of the return value
- one
@error
tag, followed by a
description of the conditions under which an error will be thrown
- multiple
@see
tags, followed by the
name of a related Tcl procedure.
- one
@arguments
tag, followed by an
HTML-formatted description of the command line syntax, which, for
ad_proc
, would look like:
[ -public | -private ] [ -deprecated [ -warn ] ] >proc-name</em> <em>arg-list</em> \<br>
[ <em>doc-string</em> ] <em>code-block</em>
(In general, the @arguments
tag is not necessary,
since the API Documentation Browser can generate these strings
automatically, but it may be useful to specify procedures with complex
syntax more precisely, e.g., mutually exclusive flags or flags that
require other flags.)
For example:
ad_proc ad_get_cookie {
-include_set_cookies:boolean
-default
name
} {
Returns the value of a cookie.
@author Jon Salz (jsalz@mit.edu)
@param include_set_cookies if provided, also examines
<code>Set-Cookie</code> headers in
<code>[ns_conn outputheaders]</code> for a cookie about
to be set.
@param default the default value for the cookie (in case the cookie
is not set).
@param name the name of the cookie.
@return the cookie's value.
@error if the cookie is not set, and no default value is provided.
@see ad_set_cookie
} {
# The code for the routine.
}
The corresponding, automatically-generated documentation will look
like this:
ad_get_cookie
ad_get_cookie [ -include_set_cookies ] [ -default default ] name
Returns the value of a cookie.
- Switches:
- -include_set_cookies (Boolean) - if provided, also examines
Set-Cookie headers in
[ns_conn outputheaders] for a cookie about
to be set.
-default (optional) - the default value for the cookie (in case the cookie
is not set).
- Parameters:
- name - the name of the cookie.
- Returns:
- the cookie's value.
- Error:
- if the cookie is not set, and no default value is provided.
- See Also:
- ad_set_cookie
|
If a procedure is deprecated, this fact is so noted before the procedure's
description:
set_the_usual_form_variables
set_the_usual_form_variables [ error_if_not_found_p ]
Deprecated. Invoking this procedure generates a warning.
For each parameter
specified in an HTTP GET or POST query, ...
|
Documenting Library Files
The syntax for calling
ad_library
is:
ad_library doc-string
ad_library
replaces the file header comment found at the
top of Tcl library files, i.e., files in the
/tcl/
directory under the server root and
*-procs.tcl
files
under the
/packages/
directory.
Like ad_proc
, the documentation string format for
ad_library
is based on Javadoc, i.e., a general
description of the library's function, followed optionally by a series
of named attributes tagged by @
signs:
- zero or more
@author
tags, one for
each author; specify the author's name, followed by his or her email
address in parentheses
- one
@creation-date
tag, indicating
when the page was first created
- one
@cvs-id
tag containing the
page's CVS identification string; just use $Id$
when creating the file, and CVS will substitute an appropriate string
when you check the file in.
Instead of:
#
# path from server root
#
# description
#
# author-contact-info, creation-date
#
# $Id$
#
write:
# /packages/acs-core/00-proc-procs.tcl
ad_library {
description
@creation-date creation-date
@author author-contact-info
@cvs-id $Id$
}
Here's a real example from the ACS Core package:
# /packages/acs-core/00-proc-procs.tcl
ad_library {
Routines for defining procedures and libraries of procedures (<code>-procs.tcl</code>
files).
@creation-date 7 Jun 2000
@author Jon Salz (jsalz@mit.edu)
@cvs-id $Id$
}
Future Improvements
We plan to introduce a method for documenting API internals,
specifically, NSV arrays and script-global variables, by defining a
procedure tentatively named
ad_doc
:
ad_doc [ -public | -private ] [ -deprecated ] type name documentation
where
type
is the kind of structure being
documented;
name
is its name, and
documentation
is the same sort of documentation
described above. Allowable syntaxes will be:
ad_doc nsv name ...
ad_doc nsv name(one_key) ...
ad_doc nsv name(\$key) ...
|
Documents the usage of an NSV array.
|
ad_doc global name ...
ad_doc global name(one_key) ...
ad_doc global name(\$key) ...
|
Documents the usage of a script-global variable.
|
Note that we define three syntaxes for describing NSV arrays and
global variables. The first describes an array as a whole; the second
describes one entry in an array, where the key is a pre-defined
literal; the third describes what the value of the array entry will be
for different keys, e.g.:
ad_doc nsv rp_registered_procs(\$method) {
A list of registered procs to be considered for HTTP requests
with method <em>method</em>.
@see ad_register_filter
@see ad_register_proc
}
(The backslash before the dollar sign is necessary to prevent the Tcl
interpreter from attempting to perform variable interpolation at the
time that
ad_doc
is invoked.)
jsalz@mit.edu
michael@arsdigita.com