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:
  1. There is no built-in facility for documenting procedures defined with proc.
  2. 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: (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:
  1. Zero or more switch declarations: the set of switches that can be provided when calling proc_name

  2. 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:

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 ...
}

Procedure Documentation Strings

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: 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:

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