Code

aargh
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 30 Jul 2002 01:46:25 +0000 (01:46 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 30 Jul 2002 01:46:25 +0000 (01:46 +0000)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@928 57a73879-2fb5-44c3-a270-3262357dd7e2

doc/.cvsignore
doc/design.html [deleted file]

index 2361bebcf4fed3eb3b07276772d4eae4d1e4990c..39a51b507c0a3621473d6a18593aa13b6356bd3b 100644 (file)
@@ -11,3 +11,4 @@ security.html
 features.html
 upgrading.html
 glossary.html
+design.html
diff --git a/doc/design.html b/doc/design.html
deleted file mode 100644 (file)
index 4a523bd..0000000
+++ /dev/null
@@ -1,1364 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<html lang="en">
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-<meta name="generator" content="Docutils: http://docutils.sourceforge.net/">
-<link rel="stylesheet" href="default.css" type="text/css" />
-<title>Roundup - An Issue-Tracking System for Knowledge Workers</title>
-<meta name="author" content="Ka-Ping Yee (original)" />
-<meta name="author" content="Richard Jones (implementation)" />
-</head>
-<body>
-<div class="document" id="roundup-an-issue-tracking-system-for-knowledge-workers" name="roundup-an-issue-tracking-system-for-knowledge-workers">
-<h1 class="title">Roundup - An Issue-Tracking System for Knowledge Workers</h1>
-<table class="docinfo" frame="void" rules="none">
-<col class="docinfo-name" />
-<col class="docinfo-content" />
-<tbody valign="top">
-<tr><td class="docinfo-name">Author:&nbsp;</td><td>
-Ka-Ping Yee (original)</td></tr>
-<tr><td class="docinfo-name">Author:&nbsp;</td><td>
-Richard Jones (implementation)</td></tr>
-</tbody>
-</table>
-<div class="contents topic" id="contents" name="contents">
-<p class="topic-title">Contents</p>
-<ul class="simple">
-<li id="id2" name="id2"><a class="reference" href="#introduction">Introduction</a></li>
-<li id="id3" name="id3"><a class="reference" href="#the-layer-cake">The Layer Cake</a></li>
-<li id="id4" name="id4"><a class="reference" href="#hyperdatabase">Hyperdatabase</a><ul>
-<li id="id5" name="id5"><a class="reference" href="#dates-and-date-arithmetic">Dates and Date Arithmetic</a></li>
-<li id="id6" name="id6"><a class="reference" href="#nodes-and-classes">Nodes and Classes</a></li>
-<li id="id7" name="id7"><a class="reference" href="#identifiers-and-designators">Identifiers and Designators</a></li>
-<li id="id8" name="id8"><a class="reference" href="#property-names-and-types">Property Names and Types</a></li>
-<li id="id9" name="id9"><a class="reference" href="#hyperdb-interface-specification">Hyperdb Interface Specification</a></li>
-<li id="id10" name="id10"><a class="reference" href="#hyperdatabase-implementations">Hyperdatabase Implementations</a></li>
-<li id="id11" name="id11"><a class="reference" href="#application-example">Application Example</a></li>
-</ul>
-</li>
-<li id="id12" name="id12"><a class="reference" href="#roundup-database">Roundup Database</a><ul>
-<li id="id13" name="id13"><a class="reference" href="#reserved-classes">Reserved Classes</a><ul>
-<li id="id14" name="id14"><a class="reference" href="#users">Users</a></li>
-<li id="id15" name="id15"><a class="reference" href="#messages">Messages</a></li>
-<li id="id16" name="id16"><a class="reference" href="#files">Files</a></li>
-</ul>
-</li>
-<li id="id17" name="id17"><a class="reference" href="#issue-classes">Issue Classes</a></li>
-<li id="id18" name="id18"><a class="reference" href="#roundupdb-interface-specification">Roundupdb Interface Specification</a></li>
-<li id="id19" name="id19"><a class="reference" href="#default-schema">Default Schema</a></li>
-</ul>
-</li>
-<li id="id20" name="id20"><a class="reference" href="#detector-interface">Detector Interface</a><ul>
-<li id="id21" name="id21"><a class="reference" href="#detector-interface-specification">Detector Interface Specification</a></li>
-<li id="id22" name="id22"><a class="reference" href="#detector-example">Detector Example</a></li>
-</ul>
-</li>
-<li id="id23" name="id23"><a class="reference" href="#command-interface">Command Interface</a><ul>
-<li id="id24" name="id24"><a class="reference" href="#command-interface-specification">Command Interface Specification</a></li>
-<li id="id25" name="id25"><a class="reference" href="#usage-example">Usage Example</a></li>
-</ul>
-</li>
-<li id="id26" name="id26"><a class="reference" href="#e-mail-user-interface">E-mail User Interface</a><ul>
-<li id="id27" name="id27"><a class="reference" href="#message-processing">Message Processing</a></li>
-<li id="id28" name="id28"><a class="reference" href="#nosy-lists">Nosy Lists</a></li>
-<li id="id29" name="id29"><a class="reference" href="#setting-properties">Setting Properties</a></li>
-</ul>
-</li>
-<li id="id30" name="id30"><a class="reference" href="#web-user-interface">Web User Interface</a><ul>
-<li id="id31" name="id31"><a class="reference" href="#views-and-view-specifiers">Views and View Specifiers</a></li>
-<li id="id32" name="id32"><a class="reference" href="#displaying-properties">Displaying Properties</a></li>
-<li id="id33" name="id33"><a class="reference" href="#index-views">Index Views</a><ul>
-<li id="id34" name="id34"><a class="reference" href="#index-view-specifiers">Index View Specifiers</a></li>
-<li id="id35" name="id35"><a class="reference" href="#filter-section">Filter Section</a></li>
-<li id="id36" name="id36"><a class="reference" href="#index-section">Index Section</a></li>
-<li id="id37" name="id37"><a class="reference" href="#sorting">Sorting</a></li>
-</ul>
-</li>
-<li id="id38" name="id38"><a class="reference" href="#issue-views">Issue Views</a><ul>
-<li id="id39" name="id39"><a class="reference" href="#issue-view-specifiers">Issue View Specifiers</a></li>
-<li id="id40" name="id40"><a class="reference" href="#editor-section">Editor Section</a></li>
-<li id="id41" name="id41"><a class="reference" href="#spool-section">Spool Section</a></li>
-</ul>
-</li>
-</ul>
-</li>
-<li id="id42" name="id42"><a class="reference" href="#deployment-scenarios">Deployment Scenarios</a></li>
-<li id="id43" name="id43"><a class="reference" href="#acknowledgements">Acknowledgements</a></li>
-<li id="id44" name="id44"><a class="reference" href="#changes-to-this-document">Changes to this document</a></li>
-</ul>
-</div>
-<div class="section" id="introduction" name="introduction">
-<h1><a class="toc-backref" href="#id2">Introduction</a></h1>
-<p>This document presents a description of the components
-of the Roundup system and specifies their interfaces and
-behaviour in sufficient detail to guide an implementation.
-For the philosophy and rationale behind the Roundup design,
-see the first-round Software Carpentry submission for Roundup.
-This document fleshes out that design as well as specifying
-interfaces so that the components can be developed separately.</p>
-</div>
-<div class="section" id="the-layer-cake" name="the-layer-cake">
-<h1><a class="toc-backref" href="#id3">The Layer Cake</a></h1>
-<p>Lots of software design documents come with a picture of
-a cake.  Everybody seems to like them.  I also like cakes
-(i think they are tasty).  So i, too, shall include
-a picture of a cake here:</p>
-<pre class="literal-block"> _________________________________________________________________________
-|  E-mail Client   |   Web Browser   |   Detector Scripts   |    Shell    |
-|------------------+-----------------+----------------------+-------------|
-|   E-mail User    |    Web User     |      Detector        |   Command   | 
-|-------------------------------------------------------------------------|
-|                         Roundup Database Layer                          |
-|-------------------------------------------------------------------------|
-|                          Hyperdatabase Layer                            |
-|-------------------------------------------------------------------------|
-|                             Storage Layer                               |
- -------------------------------------------------------------------------</pre>
-<p>The colourful parts of the cake are part of our system;
-the faint grey parts of the cake are external components.</p>
-<p>I will now proceed to forgo all table manners and
-eat from the bottom of the cake to the top.  You may want
-to stand back a bit so you don't get covered in crumbs.</p>
-</div>
-<div class="section" id="hyperdatabase" name="hyperdatabase">
-<h1><a class="toc-backref" href="#id4">Hyperdatabase</a></h1>
-<p>The lowest-level component to be implemented is the hyperdatabase.
-The hyperdatabase is intended to be
-a flexible data store that can hold configurable data in
-records which we call nodes.</p>
-<p>The hyperdatabase is implemented on top of the storage layer,
-an external module for storing its data.  The storage layer could
-be a third-party RDBMS; for a &quot;batteries-included&quot; distribution,
-implementing the hyperdatabase on the standard bsddb
-module is suggested.</p>
-<div class="section" id="dates-and-date-arithmetic" name="dates-and-date-arithmetic">
-<h2><a class="toc-backref" href="#id5">Dates and Date Arithmetic</a></h2>
-<p>Before we get into the hyperdatabase itself, we need a
-way of handling dates.  The hyperdatabase module provides
-Timestamp objects for
-representing date-and-time stamps and Interval objects for
-representing date-and-time intervals.</p>
-<p>As strings, date-and-time stamps are specified with
-the date in international standard format
-(<tt class="literal">yyyy-mm-dd</tt>)
-joined to the time (<tt class="literal">hh:mm:ss</tt>)
-by a period &quot;<tt class="literal">.</tt>&quot;.  Dates in
-this form can be easily compared and are fairly readable
-when printed.  An example of a valid stamp is
-&quot;<tt class="literal">2000-06-24.13:03:59</tt>&quot;.
-We'll call this the &quot;full date format&quot;.  When Timestamp objects are
-printed as strings, they appear in the full date format with
-the time always given in GMT.  The full date format is always
-exactly 19 characters long.</p>
-<p>For user input, some partial forms are also permitted:
-the whole time or just the seconds may be omitted; and the whole date
-may be omitted or just the year may be omitted.  If the time is given,
-the time is interpreted in the user's local time zone.
-The Date constructor takes care of these conversions.
-In the following examples, suppose that <tt class="literal">yyyy</tt> is the current year,
-<tt class="literal">mm</tt> is the current month, and <tt class="literal">dd</tt> is the current
-day of the month; and suppose that the user is on Eastern Standard Time.</p>
-<ul class="simple">
-<li>&quot;2000-04-17&quot; means &lt;Date 2000-04-17.00:00:00&gt;</li>
-<li>&quot;01-25&quot; means &lt;Date yyyy-01-25.00:00:00&gt;</li>
-<li>&quot;2000-04-17.03:45&quot; means &lt;Date 2000-04-17.08:45:00&gt;</li>
-<li>&quot;08-13.22:13&quot; means &lt;Date yyyy-08-14.03:13:00&gt;</li>
-<li>&quot;11-07.09:32:43&quot; means &lt;Date yyyy-11-07.14:32:43&gt;</li>
-<li>&quot;14:25&quot; means</li>
-<li>&lt;Date yyyy-mm-dd.19:25:00&gt;</li>
-<li>&quot;8:47:11&quot; means</li>
-<li>&lt;Date yyyy-mm-dd.13:47:11&gt;</li>
-<li>the special date &quot;.&quot; means &quot;right now&quot;</li>
-</ul>
-<p>Date intervals are specified using the suffixes
-&quot;y&quot;, &quot;m&quot;, and &quot;d&quot;.  The suffix &quot;w&quot; (for &quot;week&quot;) means 7 days.
-Time intervals are specified in hh:mm:ss format (the seconds
-may be omitted, but the hours and minutes may not).</p>
-<ul class="simple">
-<li>&quot;3y&quot; means three years</li>
-<li>&quot;2y 1m&quot; means two years and one month</li>
-<li>&quot;1m 25d&quot; means one month and 25 days</li>
-<li>&quot;2w 3d&quot; means two weeks and three days</li>
-<li>&quot;1d 2:50&quot; means one day, two hours, and 50 minutes</li>
-<li>&quot;14:00&quot; means 14 hours</li>
-<li>&quot;0:04:33&quot; means four minutes and 33 seconds</li>
-</ul>
-<p>The Date class should understand simple date expressions of the form 
-<em>stamp</em> <tt class="literal">+</tt> <em>interval</em> and <em>stamp</em> <tt class="literal">-</tt> <em>interval</em>.
-When adding or subtracting intervals involving months or years, the
-components are handled separately.  For example, when evaluating
-&quot;<tt class="literal">2000-06-25 + 1m 10d</tt>&quot;, we first add one month to
-get 2000-07-25, then add 10 days to get
-2000-08-04 (rather than trying to decide whether
-1m 10d means 38 or 40 or 41 days).</p>
-<p>Here is an outline of the Date and Interval classes:</p>
-<pre class="literal-block">class Date:
-    def __init__(self, spec, offset):
-        &quot;&quot;&quot;Construct a date given a specification and a time zone offset.
-
-        'spec' is a full date or a partial form, with an optional
-        added or subtracted interval.  'offset' is the local time
-        zone offset from GMT in hours.
-        &quot;&quot;&quot;
-
-    def __add__(self, interval):
-        &quot;&quot;&quot;Add an interval to this date to produce another date.&quot;&quot;&quot;
-
-    def __sub__(self, interval):
-        &quot;&quot;&quot;Subtract an interval from this date to produce another date.&quot;&quot;&quot;
-
-    def __cmp__(self, other):
-        &quot;&quot;&quot;Compare this date to another date.&quot;&quot;&quot;
-
-    def __str__(self):
-        &quot;&quot;&quot;Return this date as a string in the yyyy-mm-dd.hh:mm:ss format.&quot;&quot;&quot;
-
-    def local(self, offset):
-        &quot;&quot;&quot;Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone.&quot;&quot;&quot;
-
-class Interval:
-    def __init__(self, spec):
-        &quot;&quot;&quot;Construct an interval given a specification.&quot;&quot;&quot;
-
-    def __cmp__(self, other):
-        &quot;&quot;&quot;Compare this interval to another interval.&quot;&quot;&quot;
-        
-    def __str__(self):
-        &quot;&quot;&quot;Return this interval as a string.&quot;&quot;&quot;</pre>
-<p>Here are some examples of how these classes would behave in practice.
-For the following examples, assume that we are on Eastern Standard
-Time and the current local time is 19:34:02 on 25 June 2000:</p>
-<pre class="literal-block">&gt;&gt;&gt; Date(&quot;.&quot;)
-&lt;Date 2000-06-26.00:34:02&gt;
-&gt;&gt;&gt; _.local(-5)
-&quot;2000-06-25.19:34:02&quot;
-&gt;&gt;&gt; Date(&quot;. + 2d&quot;)
-&lt;Date 2000-06-28.00:34:02&gt;
-&gt;&gt;&gt; Date(&quot;1997-04-17&quot;, -5)
-&lt;Date 1997-04-17.00:00:00&gt;
-&gt;&gt;&gt; Date(&quot;01-25&quot;, -5)
-&lt;Date 2000-01-25.00:00:00&gt;
-&gt;&gt;&gt; Date(&quot;08-13.22:13&quot;, -5)
-&lt;Date 2000-08-14.03:13:00&gt;
-&gt;&gt;&gt; Date(&quot;14:25&quot;, -5)
-&lt;Date 2000-06-25.19:25:00&gt;
-&gt;&gt;&gt; Interval(&quot;  3w  1  d  2:00&quot;)
-&lt;Interval 22d 2:00&gt;
-&gt;&gt;&gt; Date(&quot;. + 2d&quot;) - Interval(&quot;3w&quot;)
-&lt;Date 2000-06-07.00:34:02&gt;</pre>
-</div>
-<div class="section" id="nodes-and-classes" name="nodes-and-classes">
-<h2><a class="toc-backref" href="#id6">Nodes and Classes</a></h2>
-<p>Nodes contain data in properties.  To Python, these
-properties are presented as the key-value pairs of a dictionary.
-Each node belongs to a class which defines the names
-and types of its properties.  The database permits the creation
-and modification of classes as well as nodes.</p>
-</div>
-<div class="section" id="identifiers-and-designators" name="identifiers-and-designators">
-<h2><a class="toc-backref" href="#id7">Identifiers and Designators</a></h2>
-<p>Each node has a numeric identifier which is unique among
-nodes in its class.  The nodes are numbered sequentially
-within each class in order of creation, starting from 1.
-The designator
-for a node is a way to identify a node in the database, and
-consists of the name of the node's class concatenated with
-the node's numeric identifier.</p>
-<p>For example, if &quot;spam&quot; and &quot;eggs&quot; are classes, the first
-node created in class &quot;spam&quot; has id 1 and designator &quot;spam1&quot;.
-The first node created in class &quot;eggs&quot; also has id 1 but has
-the distinct designator &quot;eggs1&quot;.  Node designators are
-conventionally enclosed in square brackets when mentioned
-in plain text.  This permits a casual mention of, say,
-&quot;[patch37]&quot; in an e-mail message to be turned into an active
-hyperlink.</p>
-</div>
-<div class="section" id="property-names-and-types" name="property-names-and-types">
-<h2><a class="toc-backref" href="#id8">Property Names and Types</a></h2>
-<p>Property names must begin with a letter.</p>
-<p>A property may be one of five basic types:</p>
-<ul class="simple">
-<li>String properties are for storing arbitrary-length strings.</li>
-<li>Boolean properties are for storing true/false, or yes/no values.</li>
-<li>Number properties are for storing numeric values.</li>
-<li>Date properties store date-and-time stamps.
-Their values are Timestamp objects.</li>
-<li>A Link property refers to a single other node
-selected from a specified class.  The class is part of the property;
-the value is an integer, the id of the chosen node.</li>
-<li>A Multilink property refers to possibly many nodes
-in a specified class.  The value is a list of integers.</li>
-</ul>
-<p><em>None</em> is also a permitted value for any of these property
-types.  An attempt to store None into a Multilink property stores an empty list.</p>
-<p>A property that is not specified will return as None from a <em>get</em>
-operation.</p>
-</div>
-<div class="section" id="hyperdb-interface-specification" name="hyperdb-interface-specification">
-<h2><a class="toc-backref" href="#id9">Hyperdb Interface Specification</a></h2>
-<p>The hyperdb module provides property objects to designate
-the different kinds of properties.  These objects are used when
-specifying what properties belong in classes:</p>
-<pre class="literal-block">class String:
-    def __init__(self, indexme='no'):
-        &quot;&quot;&quot;An object designating a String property.&quot;&quot;&quot;
-
-class Boolean:
-    def __init__(self):
-        &quot;&quot;&quot;An object designating a Boolean property.&quot;&quot;&quot;
-
-class Number:
-    def __init__(self):
-        &quot;&quot;&quot;An object designating a Number property.&quot;&quot;&quot;
-
-class Date:
-    def __init__(self):
-        &quot;&quot;&quot;An object designating a Date property.&quot;&quot;&quot;
-
-class Link:
-    def __init__(self, classname, do_journal='yes'):
-        &quot;&quot;&quot;An object designating a Link property that links to
-        nodes in a specified class.
-
-        If the do_journal argument is not 'yes' then changes to
-        the property are not journalled in the linked node.
-        &quot;&quot;&quot;
-
-class Multilink:
-    def __init__(self, classname, do_journal='yes'):
-        &quot;&quot;&quot;An object designating a Multilink property that links
-        to nodes in a specified class.
-
-        If the do_journal argument is not 'yes' then changes to
-        the property are not journalled in the linked node(s).
-        &quot;&quot;&quot;</pre>
-<p>Here is the interface provided by the hyperdatabase:</p>
-<pre class="literal-block">class Database:
-    &quot;&quot;&quot;A database for storing records containing flexible data types.&quot;&quot;&quot;
-
-    def __init__(self, storagelocator, journaltag):
-        &quot;&quot;&quot;Open a hyperdatabase given a specifier to some storage.
-
-        The meaning of 'storagelocator' depends on the particular
-        implementation of the hyperdatabase.  It could be a file name,
-        a directory path, a socket descriptor for a connection to a
-        database over the network, etc.
-
-        The 'journaltag' is a token that will be attached to the journal
-        entries for any edits done on the database.  If 'journaltag' is
-        None, the database is opened in read-only mode: the Class.create(),
-        Class.set(), and Class.retire() methods are disabled.
-        &quot;&quot;&quot;
-
-    def __getattr__(self, classname):
-        &quot;&quot;&quot;A convenient way of calling self.getclass(classname).&quot;&quot;&quot;
-
-    def getclasses(self):
-        &quot;&quot;&quot;Return a list of the names of all existing classes.&quot;&quot;&quot;
-
-    def getclass(self, classname):
-        &quot;&quot;&quot;Get the Class object representing a particular class.
-
-        If 'classname' is not a valid class name, a KeyError is raised.
-        &quot;&quot;&quot;
-
-class Class:
-    &quot;&quot;&quot;The handle to a particular class of nodes in a hyperdatabase.&quot;&quot;&quot;
-
-    def __init__(self, db, classname, **properties):
-        &quot;&quot;&quot;Create a new class with a given name and property specification.
-
-        'classname' must not collide with the name of an existing class,
-        or a ValueError is raised.  The keyword arguments in 'properties'
-        must map names to property objects, or a TypeError is raised.
-        &quot;&quot;&quot;
-
-    # Editing nodes:
-
-    def create(self, **propvalues):
-        &quot;&quot;&quot;Create a new node of this class and return its id.
-
-        The keyword arguments in 'propvalues' map property names to values.
-        The values of arguments must be acceptable for the types of their
-        corresponding properties or a TypeError is raised.  If this class
-        has a key property, it must be present and its value must not
-        collide with other key strings or a ValueError is raised.  Any other
-        properties on this class that are missing from the 'propvalues'
-        dictionary are set to None.  If an id in a link or multilink
-        property does not refer to a valid node, an IndexError is raised.
-        &quot;&quot;&quot;
-
-    def get(self, nodeid, propname):
-        &quot;&quot;&quot;Get the value of a property on an existing node of this class.
-
-        'nodeid' must be the id of an existing node of this class or an
-        IndexError is raised.  'propname' must be the name of a property
-        of this class or a KeyError is raised.
-        &quot;&quot;&quot;
-
-    def set(self, nodeid, **propvalues):
-        &quot;&quot;&quot;Modify a property on an existing node of this class.
-        
-        'nodeid' must be the id of an existing node of this class or an
-        IndexError is raised.  Each key in 'propvalues' must be the name
-        of a property of this class or a KeyError is raised.  All values
-        in 'propvalues' must be acceptable types for their corresponding
-        properties or a TypeError is raised.  If the value of the key
-        property is set, it must not collide with other key strings or a
-        ValueError is raised.  If the value of a Link or Multilink
-        property contains an invalid node id, a ValueError is raised.
-        &quot;&quot;&quot;
-
-    def retire(self, nodeid):
-        &quot;&quot;&quot;Retire a node.
-        
-        The properties on the node remain available from the get() method,
-        and the node's id is never reused.  Retired nodes are not returned
-        by the find(), list(), or lookup() methods, and other nodes may
-        reuse the values of their key properties.
-        &quot;&quot;&quot;
-
-    def history(self, nodeid):
-        &quot;&quot;&quot;Retrieve the journal of edits on a particular node.
-
-        'nodeid' must be the id of an existing node of this class or an
-        IndexError is raised.
-
-        The returned list contains tuples of the form
-
-            (date, tag, action, params)
-
-        'date' is a Timestamp object specifying the time of the change and
-        'tag' is the journaltag specified when the database was opened.
-        'action' may be:
-
-            'create' or 'set' -- 'params' is a dictionary of property values
-            'link' or 'unlink' -- 'params' is (classname, nodeid, propname)
-            'retire' -- 'params' is None
-        &quot;&quot;&quot;
-
-    # Locating nodes:
-
-    def setkey(self, propname):
-        &quot;&quot;&quot;Select a String property of this class to be the key property.
-
-        'propname' must be the name of a String property of this class or
-        None, or a TypeError is raised.  The values of the key property on
-        all existing nodes must be unique or a ValueError is raised.
-        &quot;&quot;&quot;
-
-    def getkey(self):
-        &quot;&quot;&quot;Return the name of the key property for this class or None.&quot;&quot;&quot;
-
-    def lookup(self, keyvalue):
-        &quot;&quot;&quot;Locate a particular node by its key property and return its id.
-
-        If this class has no key property, a TypeError is raised.  If the
-        'keyvalue' matches one of the values for the key property among
-        the nodes in this class, the matching node's id is returned;
-        otherwise a KeyError is raised.
-        &quot;&quot;&quot;
-
-    def find(self, propname, nodeid):
-        &quot;&quot;&quot;Get the ids of nodes in this class which link to a given node.
-        
-        'propname' must be the name of a property in this class, or a
-        KeyError is raised.  That property must be a Link or Multilink
-        property, or a TypeError is raised.  'nodeid' must be the id of
-        an existing node in the class linked to by the given property,
-        or an IndexError is raised.
-        &quot;&quot;&quot;
-
-    def list(self):
-        &quot;&quot;&quot;Return a list of the ids of the active nodes in this class.&quot;&quot;&quot;
-
-    def count(self):
-        &quot;&quot;&quot;Get the number of nodes in this class.
-
-        If the returned integer is 'numnodes', the ids of all the nodes
-        in this class run from 1 to numnodes, and numnodes+1 will be the
-        id of the next node to be created in this class.
-        &quot;&quot;&quot;
-
-    # Manipulating properties:
-
-    def getprops(self):
-        &quot;&quot;&quot;Return a dictionary mapping property names to property objects.&quot;&quot;&quot;
-
-    def addprop(self, **properties):
-        &quot;&quot;&quot;Add properties to this class.
-
-        The keyword arguments in 'properties' must map names to property
-        objects, or a TypeError is raised.  None of the keys in 'properties'
-        may collide with the names of existing properties, or a ValueError
-        is raised before any properties have been added.
-        &quot;&quot;&quot;</pre>
-<p>TODO: additional methods</p>
-</div>
-<div class="section" id="hyperdatabase-implementations" name="hyperdatabase-implementations">
-<h2><a class="toc-backref" href="#id10">Hyperdatabase Implementations</a></h2>
-<p>Hyperdatabase implementations exist to create the interface described in the
-<a class="reference" href="#hyperdb-interface-specification">hyperdb interface specification</a>
-over an existing storage mechanism. Examples are relational databases,
-*dbm key-value databases, and so on.</p>
-<p>TODO: finish</p>
-</div>
-<div class="section" id="application-example" name="application-example">
-<h2><a class="toc-backref" href="#id11">Application Example</a></h2>
-<p>Here is an example of how the hyperdatabase module would work in practice:</p>
-<pre class="literal-block">&gt;&gt;&gt; import hyperdb
-&gt;&gt;&gt; db = hyperdb.Database(&quot;foo.db&quot;, &quot;ping&quot;)
-&gt;&gt;&gt; db
-&lt;hyperdb.Database &quot;foo.db&quot; opened by &quot;ping&quot;&gt;
-&gt;&gt;&gt; hyperdb.Class(db, &quot;status&quot;, name=hyperdb.String())
-&lt;hyperdb.Class &quot;status&quot;&gt;
-&gt;&gt;&gt; _.setkey(&quot;name&quot;)
-&gt;&gt;&gt; db.status.create(name=&quot;unread&quot;)
-1
-&gt;&gt;&gt; db.status.create(name=&quot;in-progress&quot;)
-2
-&gt;&gt;&gt; db.status.create(name=&quot;testing&quot;)
-3
-&gt;&gt;&gt; db.status.create(name=&quot;resolved&quot;)
-4
-&gt;&gt;&gt; db.status.count()
-4
-&gt;&gt;&gt; db.status.list()
-[1, 2, 3, 4]
-&gt;&gt;&gt; db.status.lookup(&quot;in-progress&quot;)
-2
-&gt;&gt;&gt; db.status.retire(3)
-&gt;&gt;&gt; db.status.list()
-[1, 2, 4]
-&gt;&gt;&gt; hyperdb.Class(db, &quot;issue&quot;, title=hyperdb.String(), status=hyperdb.Link(&quot;status&quot;))
-&lt;hyperdb.Class &quot;issue&quot;&gt;
-&gt;&gt;&gt; db.issue.create(title=&quot;spam&quot;, status=1)
-1
-&gt;&gt;&gt; db.issue.create(title=&quot;eggs&quot;, status=2)
-2
-&gt;&gt;&gt; db.issue.create(title=&quot;ham&quot;, status=4)
-3
-&gt;&gt;&gt; db.issue.create(title=&quot;arguments&quot;, status=2)
-4
-&gt;&gt;&gt; db.issue.create(title=&quot;abuse&quot;, status=1)
-5
-&gt;&gt;&gt; hyperdb.Class(db, &quot;user&quot;, username=hyperdb.Key(), password=hyperdb.String())
-&lt;hyperdb.Class &quot;user&quot;&gt;
-&gt;&gt;&gt; db.issue.addprop(fixer=hyperdb.Link(&quot;user&quot;))
-&gt;&gt;&gt; db.issue.getprops()
-{&quot;title&quot;: &lt;hyperdb.String&gt;, &quot;status&quot;: &lt;hyperdb.Link to &quot;status&quot;&gt;,
- &quot;user&quot;: &lt;hyperdb.Link to &quot;user&quot;&gt;}
-&gt;&gt;&gt; db.issue.set(5, status=2)
-&gt;&gt;&gt; db.issue.get(5, &quot;status&quot;)
-2
-&gt;&gt;&gt; db.status.get(2, &quot;name&quot;)
-&quot;in-progress&quot;
-&gt;&gt;&gt; db.issue.get(5, &quot;title&quot;)
-&quot;abuse&quot;
-&gt;&gt;&gt; db.issue.find(&quot;status&quot;, db.status.lookup(&quot;in-progress&quot;))
-[2, 4, 5]
-&gt;&gt;&gt; db.issue.history(5)
-[(&lt;Date 2000-06-28.19:09:43&gt;, &quot;ping&quot;, &quot;create&quot;, {&quot;title&quot;: &quot;abuse&quot;, &quot;status&quot;: 1}),
- (&lt;Date 2000-06-28.19:11:04&gt;, &quot;ping&quot;, &quot;set&quot;, {&quot;status&quot;: 2})]
-&gt;&gt;&gt; db.status.history(1)
-[(&lt;Date 2000-06-28.19:09:43&gt;, &quot;ping&quot;, &quot;link&quot;, (&quot;issue&quot;, 5, &quot;status&quot;)),
- (&lt;Date 2000-06-28.19:11:04&gt;, &quot;ping&quot;, &quot;unlink&quot;, (&quot;issue&quot;, 5, &quot;status&quot;))]
-&gt;&gt;&gt; db.status.history(2)
-[(&lt;Date 2000-06-28.19:11:04&gt;, &quot;ping&quot;, &quot;link&quot;, (&quot;issue&quot;, 5, &quot;status&quot;))]</pre>
-<p>For the purposes of journalling, when a Multilink property is
-set to a new list of nodes, the hyperdatabase compares the old
-list to the new list.
-The journal records &quot;unlink&quot; events for all the nodes that appear
-in the old list but not the new list,
-and &quot;link&quot; events for
-all the nodes that appear in the new list but not in the old list.</p>
-</div>
-</div>
-<div class="section" id="roundup-database" name="roundup-database">
-<h1><a class="toc-backref" href="#id12">Roundup Database</a></h1>
-<p>The Roundup database layer is implemented on top of the
-hyperdatabase and mediates calls to the database.
-Some of the classes in the Roundup database are considered
-issue classes.
-The Roundup database layer adds detectors and user nodes,
-and on issues it provides mail spools, nosy lists, and superseders.</p>
-<p>TODO: where functionality is implemented.</p>
-<div class="section" id="reserved-classes" name="reserved-classes">
-<h2><a class="toc-backref" href="#id13">Reserved Classes</a></h2>
-<p>Internal to this layer we reserve three special classes
-of nodes that are not issues.</p>
-<div class="section" id="users" name="users">
-<h3><a class="toc-backref" href="#id14">Users</a></h3>
-<p>Users are stored in the hyperdatabase as nodes of
-class &quot;user&quot;.  The &quot;user&quot; class has the definition:</p>
-<pre class="literal-block">hyperdb.Class(db, &quot;user&quot;, username=hyperdb.String(),
-                          password=hyperdb.String(),
-                          address=hyperdb.String())
-db.user.setkey(&quot;username&quot;)</pre>
-</div>
-<div class="section" id="messages" name="messages">
-<h3><a class="toc-backref" href="#id15">Messages</a></h3>
-<p>E-mail messages are represented by hyperdatabase nodes of class &quot;msg&quot;.
-The actual text content of the messages is stored in separate files.
-(There's no advantage to be gained by stuffing them into the
-hyperdatabase, and if messages are stored in ordinary text files,
-they can be grepped from the command line.)  The text of a message is
-saved in a file named after the message node designator (e.g. &quot;msg23&quot;)
-for the sake of the command interface (see below).  Attachments are
-stored separately and associated with &quot;file&quot; nodes.
-The &quot;msg&quot; class has the definition:</p>
-<pre class="literal-block">hyperdb.Class(db, &quot;msg&quot;, author=hyperdb.Link(&quot;user&quot;),
-                         recipients=hyperdb.Multilink(&quot;user&quot;),
-                         date=hyperdb.Date(),
-                         summary=hyperdb.String(),
-                         files=hyperdb.Multilink(&quot;file&quot;))</pre>
-<p>The &quot;author&quot; property indicates the author of the message
-(a &quot;user&quot; node must exist in the hyperdatabase for any messages
-that are stored in the system).
-The &quot;summary&quot; property contains a summary of the message for display
-in a message index.</p>
-</div>
-<div class="section" id="files" name="files">
-<h3><a class="toc-backref" href="#id16">Files</a></h3>
-<p>Submitted files are represented by hyperdatabase
-nodes of class &quot;file&quot;.  Like e-mail messages, the file content
-is stored in files outside the database,
-named after the file node designator (e.g. &quot;file17&quot;).
-The &quot;file&quot; class has the definition:</p>
-<pre class="literal-block">hyperdb.Class(db, &quot;file&quot;, user=hyperdb.Link(&quot;user&quot;),
-                          name=hyperdb.String(),
-                          type=hyperdb.String())</pre>
-<p>The &quot;user&quot; property indicates the user who submitted the
-file, the &quot;name&quot; property holds the original name of the file,
-and the &quot;type&quot; property holds the MIME type of the file as received.</p>
-</div>
-</div>
-<div class="section" id="issue-classes" name="issue-classes">
-<h2><a class="toc-backref" href="#id17">Issue Classes</a></h2>
-<p>All issues have the following standard properties:</p>
-<table frame="border" rules="all">
-<colgroup>
-<col colwidth="30%" />
-<col colwidth="70%" />
-</colgroup>
-<thead valign="bottom">
-<tr><th>Property</th>
-<th>Definition</th>
-</tr>
-</thead>
-<tbody valign="top">
-<tr><td>title</td>
-<td>hyperdb.String()</td>
-</tr>
-<tr><td>messages</td>
-<td>hyperdb.Multilink(&quot;msg&quot;)</td>
-</tr>
-<tr><td>files</td>
-<td>hyperdb.Multilink(&quot;file&quot;)</td>
-</tr>
-<tr><td>nosy</td>
-<td>hyperdb.Multilink(&quot;user&quot;)</td>
-</tr>
-<tr><td>superseder</td>
-<td>hyperdb.Multilink(&quot;issue&quot;)</td>
-</tr>
-</tbody>
-</table>
-<p>Also, two Date properties named &quot;creation&quot; and &quot;activity&quot; are
-fabricated by the Roundup database layer.  By &quot;fabricated&quot; we
-mean that no such properties are actually stored in the
-hyperdatabase, but when properties on issues are requested, the
-&quot;creation&quot; and &quot;activity&quot; properties are made available.
-The value of the &quot;creation&quot; property is the date when an issue was
-created, and the value of the &quot;activity&quot; property is the
-date when any property on the issue was last edited (equivalently,
-these are the dates on the first and last records in the issue's journal).</p>
-</div>
-<div class="section" id="roundupdb-interface-specification" name="roundupdb-interface-specification">
-<h2><a class="toc-backref" href="#id18">Roundupdb Interface Specification</a></h2>
-<p>The interface to a Roundup database delegates most method
-calls to the hyperdatabase, except for the following
-changes and additional methods:</p>
-<pre class="literal-block">class Database:
-    def getuid(self):
-        &quot;&quot;&quot;Return the id of the &quot;user&quot; node associated with the user
-        that owns this connection to the hyperdatabase.&quot;&quot;&quot;
-
-class Class:
-    # Overridden methods:
-
-    def create(self, **propvalues):
-    def set(self, **propvalues):
-    def retire(self, nodeid):
-        &quot;&quot;&quot;These operations trigger detectors and can be vetoed.  Attempts
-        to modify the &quot;creation&quot; or &quot;activity&quot; properties cause a KeyError.
-        &quot;&quot;&quot;
-
-    # New methods:
-
-    def audit(self, event, detector):
-    def react(self, event, detector):
-        &quot;&quot;&quot;Register a detector (see below for more details).&quot;&quot;&quot;
-
-class IssueClass(Class):
-    # Overridden methods:
-
-    def __init__(self, db, classname, **properties):
-        &quot;&quot;&quot;The newly-created class automatically includes the &quot;messages&quot;,
-        &quot;files&quot;, &quot;nosy&quot;, and &quot;superseder&quot; properties.  If the 'properties'
-        dictionary attempts to specify any of these properties or a
-        &quot;creation&quot; or &quot;activity&quot; property, a ValueError is raised.&quot;&quot;&quot;
-
-    def get(self, nodeid, propname):
-    def getprops(self):
-        &quot;&quot;&quot;In addition to the actual properties on the node, these
-        methods provide the &quot;creation&quot; and &quot;activity&quot; properties.&quot;&quot;&quot;
-
-    # New methods:
-
-    def addmessage(self, nodeid, summary, text):
-        &quot;&quot;&quot;Add a message to an issue's mail spool.
-
-        A new &quot;msg&quot; node is constructed using the current date, the
-        user that owns the database connection as the author, and
-        the specified summary text.  The &quot;files&quot; and &quot;recipients&quot;
-        fields are left empty.  The given text is saved as the body
-        of the message and the node is appended to the &quot;messages&quot;
-        field of the specified issue.
-        &quot;&quot;&quot;
-
-    def sendmessage(self, nodeid, msgid):
-        &quot;&quot;&quot;Send a message to the members of an issue's nosy list.
-
-        The message is sent only to users on the nosy list who are not
-        already on the &quot;recipients&quot; list for the message.  These users
-        are then added to the message's &quot;recipients&quot; list.
-        &quot;&quot;&quot;</pre>
-</div>
-<div class="section" id="default-schema" name="default-schema">
-<h2><a class="toc-backref" href="#id19">Default Schema</a></h2>
-<p>The default schema included with Roundup turns it into a
-typical software bug tracker.  The database is set up like this:</p>
-<pre class="literal-block">pri = Class(db, &quot;priority&quot;, name=hyperdb.String(), order=hyperdb.String())
-pri.setkey(&quot;name&quot;)
-pri.create(name=&quot;critical&quot;, order=&quot;1&quot;)
-pri.create(name=&quot;urgent&quot;, order=&quot;2&quot;)
-pri.create(name=&quot;bug&quot;, order=&quot;3&quot;)
-pri.create(name=&quot;feature&quot;, order=&quot;4&quot;)
-pri.create(name=&quot;wish&quot;, order=&quot;5&quot;)
-
-stat = Class(db, &quot;status&quot;, name=hyperdb.String(), order=hyperdb.String())
-stat.setkey(&quot;name&quot;)
-stat.create(name=&quot;unread&quot;, order=&quot;1&quot;)
-stat.create(name=&quot;deferred&quot;, order=&quot;2&quot;)
-stat.create(name=&quot;chatting&quot;, order=&quot;3&quot;)
-stat.create(name=&quot;need-eg&quot;, order=&quot;4&quot;)
-stat.create(name=&quot;in-progress&quot;, order=&quot;5&quot;)
-stat.create(name=&quot;testing&quot;, order=&quot;6&quot;)
-stat.create(name=&quot;done-cbb&quot;, order=&quot;7&quot;)
-stat.create(name=&quot;resolved&quot;, order=&quot;8&quot;)
-
-Class(db, &quot;keyword&quot;, name=hyperdb.String())
-
-Class(db, &quot;issue&quot;, fixer=hyperdb.Multilink(&quot;user&quot;),
-                   topic=hyperdb.Multilink(&quot;keyword&quot;),
-                   priority=hyperdb.Link(&quot;priority&quot;),
-                   status=hyperdb.Link(&quot;status&quot;))</pre>
-<p>(The &quot;order&quot; property hasn't been explained yet.  It
-gets used by the Web user interface for sorting.)</p>
-<p>The above isn't as pretty-looking as the schema specification
-in the first-stage submission, but it could be made just as easy
-with the addition of a convenience function like Choice
-for setting up the &quot;priority&quot; and &quot;status&quot; classes:</p>
-<pre class="literal-block">def Choice(name, *options):
-    cl = Class(db, name, name=hyperdb.String(), order=hyperdb.String())
-    for i in range(len(options)):
-        cl.create(name=option[i], order=i)
-    return hyperdb.Link(name)</pre>
-</div>
-</div>
-<div class="section" id="detector-interface" name="detector-interface">
-<h1><a class="toc-backref" href="#id20">Detector Interface</a></h1>
-<p>Detectors are Python functions that are triggered on certain
-kinds of events.  The definitions of the
-functions live in Python modules placed in a directory set aside
-for this purpose.  Importing the Roundup database module also
-imports all the modules in this directory, and the <tt class="literal">init()</tt>
-function of each module is called when a database is opened to
-provide it a chance to register its detectors.</p>
-<p>There are two kinds of detectors:</p>
-<ol class="arabic simple">
-<li>an auditor is triggered just before modifying an node</li>
-<li>a reactor is triggered just after an node has been modified</li>
-</ol>
-<p>When the Roundup database is about to perform a
-<tt class="literal">create()</tt>, <tt class="literal">set()</tt>, or <tt class="literal">retire()</tt>
-operation, it first calls any <em>auditors</em> that
-have been registered for that operation on that class.
-Any auditor may raise a <em>Reject</em> exception
-to abort the operation.</p>
-<p>If none of the auditors raises an exception, the database
-proceeds to carry out the operation.  After it's done, it
-then calls all of the <em>reactors</em> that have been registered
-for the operation.</p>
-<div class="section" id="detector-interface-specification" name="detector-interface-specification">
-<h2><a class="toc-backref" href="#id21">Detector Interface Specification</a></h2>
-<p>The <tt class="literal">audit()</tt> and <tt class="literal">react()</tt> methods
-register detectors on a given class of nodes:</p>
-<pre class="literal-block">class Class:
-    def audit(self, event, detector):
-        &quot;&quot;&quot;Register an auditor on this class.
-
-        'event' should be one of &quot;create&quot;, &quot;set&quot;, or &quot;retire&quot;.
-        'detector' should be a function accepting four arguments.
-        &quot;&quot;&quot;
-
-    def react(self, event, detector):
-        &quot;&quot;&quot;Register a reactor on this class.
-
-        'event' should be one of &quot;create&quot;, &quot;set&quot;, or &quot;retire&quot;.
-        'detector' should be a function accepting four arguments.
-        &quot;&quot;&quot;</pre>
-<p>Auditors are called with the arguments:</p>
-<pre class="literal-block">audit(db, cl, nodeid, newdata)</pre>
-<p>where <tt class="literal">db</tt> is the database, <tt class="literal">cl</tt> is an
-instance of Class or IssueClass within the database, and <tt class="literal">newdata</tt>
-is a dictionary mapping property names to values.</p>
-<p>For a <tt class="literal">create()</tt>
-operation, the <tt class="literal">nodeid</tt> argument is None and newdata
-contains all of the initial property values with which the node
-is about to be created.</p>
-<p>For a <tt class="literal">set()</tt> operation, newdata
-contains only the names and values of properties that are about
-to be changed.</p>
-<p>For a <tt class="literal">retire()</tt> operation, newdata is None.</p>
-<p>Reactors are called with the arguments:</p>
-<pre class="literal-block">react(db, cl, nodeid, olddata)</pre>
-<p>where <tt class="literal">db</tt> is the database, <tt class="literal">cl</tt> is an
-instance of Class or IssueClass within the database, and <tt class="literal">olddata</tt>
-is a dictionary mapping property names to values.</p>
-<p>For a <tt class="literal">create()</tt>
-operation, the <tt class="literal">nodeid</tt> argument is the id of the
-newly-created node and <tt class="literal">olddata</tt> is None.</p>
-<p>For a <tt class="literal">set()</tt> operation, <tt class="literal">olddata</tt>
-contains the names and previous values of properties that were changed.</p>
-<p>For a <tt class="literal">retire()</tt> operation, <tt class="literal">nodeid</tt> is the
-id of the retired node and <tt class="literal">olddata</tt> is None.</p>
-</div>
-<div class="section" id="detector-example" name="detector-example">
-<h2><a class="toc-backref" href="#id22">Detector Example</a></h2>
-<p>Here is an example of detectors written for a hypothetical
-project-management application, where users can signal approval
-of a project by adding themselves to an &quot;approvals&quot; list, and
-a project proceeds when it has three approvals:</p>
-<pre class="literal-block"># Permit users only to add themselves to the &quot;approvals&quot; list.
-
-def check_approvals(db, cl, id, newdata):
-    if newdata.has_key(&quot;approvals&quot;):
-        if cl.get(id, &quot;status&quot;) == db.status.lookup(&quot;approved&quot;):
-            raise Reject, &quot;You can't modify the approvals list &quot; \
-                &quot;for a project that has already been approved.&quot;
-        old = cl.get(id, &quot;approvals&quot;)
-        new = newdata[&quot;approvals&quot;]
-        for uid in old:
-            if uid not in new and uid != db.getuid():
-                raise Reject, &quot;You can't remove other users from the &quot;
-                    &quot;approvals list; you can only remove yourself.&quot;
-        for uid in new:
-            if uid not in old and uid != db.getuid():
-                raise Reject, &quot;You can't add other users to the approvals &quot;
-                    &quot;list; you can only add yourself.&quot;
-
-# When three people have approved a project, change its
-# status from &quot;pending&quot; to &quot;approved&quot;.
-
-def approve_project(db, cl, id, olddata):
-    if olddata.has_key(&quot;approvals&quot;) and len(cl.get(id, &quot;approvals&quot;)) == 3:
-        if cl.get(id, &quot;status&quot;) == db.status.lookup(&quot;pending&quot;):
-            cl.set(id, status=db.status.lookup(&quot;approved&quot;))
-
-def init(db):
-    db.project.audit(&quot;set&quot;, check_approval)
-    db.project.react(&quot;set&quot;, approve_project)</pre>
-<p>Here is another example of a detector that can allow or prevent
-the creation of new nodes.  In this scenario, patches for a software
-project are submitted by sending in e-mail with an attached file,
-and we want to ensure that there are text/plain attachments on
-the message.  The maintainer of the package can then apply the
-patch by setting its status to &quot;applied&quot;:</p>
-<pre class="literal-block"># Only accept attempts to create new patches that come with patch files.
-
-def check_new_patch(db, cl, id, newdata):
-    if not newdata[&quot;files&quot;]:
-        raise Reject, &quot;You can't submit a new patch without &quot; \
-                      &quot;attaching a patch file.&quot;
-    for fileid in newdata[&quot;files&quot;]:
-        if db.file.get(fileid, &quot;type&quot;) != &quot;text/plain&quot;:
-            raise Reject, &quot;Submitted patch files must be text/plain.&quot;
-
-# When the status is changed from &quot;approved&quot; to &quot;applied&quot;, apply the patch.
-
-def apply_patch(db, cl, id, olddata):
-    if cl.get(id, &quot;status&quot;) == db.status.lookup(&quot;applied&quot;) and \
-        olddata[&quot;status&quot;] == db.status.lookup(&quot;approved&quot;):
-        # ...apply the patch...
-
-def init(db):
-    db.patch.audit(&quot;create&quot;, check_new_patch)
-    db.patch.react(&quot;set&quot;, apply_patch)</pre>
-</div>
-</div>
-<div class="section" id="command-interface" name="command-interface">
-<h1><a class="toc-backref" href="#id23">Command Interface</a></h1>
-<p>The command interface is a very simple and minimal interface,
-intended only for quick searches and checks from the shell prompt.
-(Anything more interesting can simply be written in Python using
-the Roundup database module.)</p>
-<div class="section" id="command-interface-specification" name="command-interface-specification">
-<h2><a class="toc-backref" href="#id24">Command Interface Specification</a></h2>
-<p>A single command, roundup, provides basic access to
-the hyperdatabase from the command line:</p>
-<pre class="literal-block">roundup get [-list] designator[, designator,...] propname
-roundup set designator[, designator,...] propname=value ...
-roundup find [-list] classname propname=value ...</pre>
-<p>TODO: more stuff here</p>
-<p>Property values are represented as strings in command arguments
-and in the printed results:</p>
-<ul class="simple">
-<li>Strings are, well, strings.</li>
-<li>Numbers are displayed the same as strings.</li>
-<li>Booleans are displayed as 'Yes' or 'No'.</li>
-<li>Date values are printed in the full date format in the local
-time zone, and accepted in the full format or any of the partial
-formats explained above.</li>
-<li>Link values are printed as node designators.  When given as
-an argument, node designators and key strings are both accepted.</li>
-<li>Multilink values are printed as lists of node designators
-joined by commas.  When given as an argument, node designators
-and key strings are both accepted; an empty string, a single node,
-or a list of nodes joined by commas is accepted.</li>
-</ul>
-<p>When multiple nodes are specified to the
-roundup get or roundup set
-commands, the specified properties are retrieved or set
-on all the listed nodes.</p>
-<p>When multiple results are returned by the roundup get
-or roundup find commands, they are printed one per
-line (default) or joined by commas (with the -list) option.</p>
-</div>
-<div class="section" id="usage-example" name="usage-example">
-<h2><a class="toc-backref" href="#id25">Usage Example</a></h2>
-<p>To find all messages regarding in-progress issues that
-contain the word &quot;spam&quot;, for example, you could execute the
-following command from the directory where the database
-dumps its files:</p>
-<pre class="literal-block">shell% for issue in `roundup find issue status=in-progress`; do
-&gt; grep -l spam `roundup get $issue messages`
-&gt; done
-msg23
-msg49
-msg50
-msg61
-shell%</pre>
-<p>Or, using the -list option, this can be written as a single command:</p>
-<pre class="literal-block">shell% grep -l spam `roundup get \
-    \`roundup find -list issue status=in-progress\` messages`
-msg23
-msg49
-msg50
-msg61
-shell%</pre>
-</div>
-</div>
-<div class="section" id="e-mail-user-interface" name="e-mail-user-interface">
-<h1><a class="toc-backref" href="#id26">E-mail User Interface</a></h1>
-<p>The Roundup system must be assigned an e-mail address
-at which to receive mail.  Messages should be piped to
-the Roundup mail-handling script by the mail delivery
-system (e.g. using an alias beginning with &quot;|&quot; for sendmail).</p>
-<div class="section" id="message-processing" name="message-processing">
-<h2><a class="toc-backref" href="#id27">Message Processing</a></h2>
-<p>Incoming messages are examined for multiple parts.
-In a multipart/mixed message or part, each subpart is
-extracted and examined.  In a multipart/alternative
-message or part, we look for a text/plain subpart and
-ignore the other parts.  The text/plain subparts are
-assembled to form the textual body of the message, to
-be stored in the file associated with a &quot;msg&quot; class node.
-Any parts of other types are each stored in separate
-files and given &quot;file&quot; class nodes that are linked to
-the &quot;msg&quot; node.</p>
-<p>The &quot;summary&quot; property on message nodes is taken from
-the first non-quoting section in the message body.
-The message body is divided into sections by blank lines.
-Sections where the second and all subsequent lines begin
-with a &quot;&gt;&quot; or &quot;|&quot; character are considered &quot;quoting
-sections&quot;.  The first line of the first non-quoting 
-section becomes the summary of the message.</p>
-<p>All of the addresses in the To: and Cc: headers of the
-incoming message are looked up among the user nodes, and
-the corresponding users are placed in the &quot;recipients&quot;
-property on the new &quot;msg&quot; node.  The address in the From:
-header similarly determines the &quot;author&quot; property of the
-new &quot;msg&quot; node.
-The default handling for
-addresses that don't have corresponding users is to create
-new users with no passwords and a username equal to the
-address.  (The web interface does not permit logins for
-users with no passwords.)  If we prefer to reject mail from
-outside sources, we can simply register an auditor on the
-&quot;user&quot; class that prevents the creation of user nodes with
-no passwords.</p>
-<p>The subject line of the incoming message is examined to
-determine whether the message is an attempt to create a new
-issue or to discuss an existing issue.  A designator enclosed
-in square brackets is sought as the first thing on the
-subject line (after skipping any &quot;Fwd:&quot; or &quot;Re:&quot; prefixes).</p>
-<p>If an issue designator (class name and id number) is found
-there, the newly created &quot;msg&quot; node is added to the &quot;messages&quot;
-property for that issue, and any new &quot;file&quot; nodes are added to
-the &quot;files&quot; property for the issue.</p>
-<p>If just an issue class name is found there, we attempt to
-create a new issue of that class with its &quot;messages&quot; property
-initialized to contain the new &quot;msg&quot; node and its &quot;files&quot;
-property initialized to contain any new &quot;file&quot; nodes.</p>
-<p>Both cases may trigger detectors (in the first case we
-are calling the set() method to add the message to the
-issue's spool; in the second case we are calling the
-create() method to create a new node).  If an auditor
-raises an exception, the original message is bounced back to
-the sender with the explanatory message given in the exception.</p>
-</div>
-<div class="section" id="nosy-lists" name="nosy-lists">
-<h2><a class="toc-backref" href="#id28">Nosy Lists</a></h2>
-<p>A standard detector is provided that watches for additions
-to the &quot;messages&quot; property.  When a new message is added, the
-detector sends it to all the users on the &quot;nosy&quot; list for the
-issue that are not already on the &quot;recipients&quot; list of the
-message.  Those users are then appended to the &quot;recipients&quot;
-property on the message, so multiple copies of a message
-are never sent to the same user.  The journal recorded by
-the hyperdatabase on the &quot;recipients&quot; property then provides
-a log of when the message was sent to whom.</p>
-</div>
-<div class="section" id="setting-properties" name="setting-properties">
-<h2><a class="toc-backref" href="#id29">Setting Properties</a></h2>
-<p>The e-mail interface also provides a simple way to set
-properties on issues.  At the end of the subject line,
-<tt class="literal">propname=value</tt> pairs can be
-specified in square brackets, using the same conventions
-as for the roundup <tt class="literal">set</tt> shell command.</p>
-</div>
-</div>
-<div class="section" id="web-user-interface" name="web-user-interface">
-<h1><a class="toc-backref" href="#id30">Web User Interface</a></h1>
-<p>The web interface is provided by a CGI script that can be
-run under any web server.  A simple web server can easily be
-built on the standard CGIHTTPServer module, and
-should also be included in the distribution for quick
-out-of-the-box deployment.</p>
-<p>The user interface is constructed from a number of template
-files containing mostly HTML.  Among the HTML tags in templates
-are interspersed some nonstandard tags, which we use as
-placeholders to be replaced by properties and their values.</p>
-<div class="section" id="views-and-view-specifiers" name="views-and-view-specifiers">
-<h2><a class="toc-backref" href="#id31">Views and View Specifiers</a></h2>
-<p>There are two main kinds of views: <em>index</em> views and <em>issue</em> views.
-An index view displays a list of issues of a particular class,
-optionally sorted and filtered as requested.  An issue view
-presents the properties of a particular issue for editing
-and displays the message spool for the issue.</p>
-<p>A view specifier is a string that specifies
-all the options needed to construct a particular view.
-It goes after the URL to the Roundup CGI script or the
-web server to form the complete URL to a view.  When the
-result of selecting a link or submitting a form takes
-the user to a new view, the Web browser should be redirected
-to a canonical location containing a complete view specifier
-so that the view can be bookmarked.</p>
-</div>
-<div class="section" id="displaying-properties" name="displaying-properties">
-<h2><a class="toc-backref" href="#id32">Displaying Properties</a></h2>
-<p>Properties appear in the user interface in three contexts:
-in indices, in editors, and as filters.  For each type of
-property, there are several display possibilities.  For example,
-in an index view, a string property may just be printed as
-a plain string, but in an editor view, that property should
-be displayed in an editable field.</p>
-<p>The display of a property is handled by functions in
-a displayers module.  Each function accepts at
-least three standard arguments -- the database, class name,
-and node id -- and returns a chunk of HTML.</p>
-<p>Displayer functions are triggered by &lt;display&gt;
-tags in templates.  The call attribute of the tag
-provides a Python expression for calling the displayer
-function.  The three standard arguments are inserted in
-front of the arguments given.  For example, the occurrence of:</p>
-<pre class="literal-block">&lt;display call=&quot;plain('status', max=30)&quot;&gt;</pre>
-<p>in a template triggers a call to:</p>
-<pre class="literal-block">plain(db, &quot;issue&quot;, 13, &quot;status&quot;, max=30)</pre>
-<p>when displaying issue 13 in the &quot;issue&quot; class.  The displayer
-functions can accept extra arguments to further specify
-details about the widgets that should be generated.  By defining new
-displayer functions, the user interface can be highly customized.</p>
-<p>Some of the standard displayer functions include:</p>
-<table frame="border" rules="all">
-<colgroup>
-<col colwidth="12%" />
-<col colwidth="88%" />
-</colgroup>
-<thead valign="bottom">
-<tr><th>Function</th>
-<th>Description</th>
-</tr>
-</thead>
-<tbody valign="top">
-<tr><td>plain</td>
-<td>display a String property directly;
-display a Date property in a specified time zone with an option
-to omit the time from the date stamp; for a Link or Multilink
-property, display the key strings of the linked nodes (or the
-ids if the linked class has no key property)</td>
-</tr>
-<tr><td>field</td>
-<td>display a property like the
-plain displayer above, but in a text field
-to be edited</td>
-</tr>
-<tr><td>menu</td>
-<td>for a Link property, display
-a menu of the available choices</td>
-</tr>
-<tr><td>link</td>
-<td>for a Link or Multilink property,
-display the names of the linked nodes, hyperlinked to the
-issue views on those nodes</td>
-</tr>
-<tr><td>count</td>
-<td>for a Multilink property, display
-a count of the number of links in the list</td>
-</tr>
-<tr><td>reldate</td>
-<td>display a Date property in terms
-of an interval relative to the current date (e.g. &quot;+ 3w&quot;, &quot;- 2d&quot;).</td>
-</tr>
-<tr><td>download</td>
-<td>show a Link(&quot;file&quot;) or Multilink(&quot;file&quot;)
-property using links that allow you to download files</td>
-</tr>
-<tr><td>checklist</td>
-<td>for a Link or Multilink property,
-display checkboxes for the available choices to permit filtering</td>
-</tr>
-</tbody>
-</table>
-</div>
-<div class="section" id="index-views" name="index-views">
-<h2><a class="toc-backref" href="#id33">Index Views</a></h2>
-<p>An index view contains two sections: a filter section
-and an index section.
-The filter section provides some widgets for selecting
-which issues appear in the index.  The index section is
-a table of issues.</p>
-<div class="section" id="index-view-specifiers" name="index-view-specifiers">
-<h3><a class="toc-backref" href="#id34">Index View Specifiers</a></h3>
-<p>An index view specifier looks like this (whitespace
-has been added for clarity):</p>
-<pre class="literal-block">/issue?status=unread,in-progress,resolved&amp;amp;
-    topic=security,ui&amp;amp;
-    :group=+priority&amp;amp;
-    :sort=-activity&amp;amp;
-    :filters=status,topic&amp;amp;
-    :columns=title,status,fixer</pre>
-<p>The index view is determined by two parts of the
-specifier: the layout part and the filter part.
-The layout part consists of the query parameters that
-begin with colons, and it determines the way that the
-properties of selected nodes are displayed.
-The filter part consists of all the other query parameters,
-and it determines the criteria by which nodes 
-are selected for display.</p>
-<p>The filter part is interactively manipulated with
-the form widgets displayed in the filter section.  The
-layout part is interactively manipulated by clicking
-on the column headings in the table.</p>
-<p>The filter part selects the union of the
-sets of issues with values matching any specified Link
-properties and the intersection of the sets
-of issues with values matching any specified Multilink
-properties.</p>
-<p>The example specifies an index of &quot;issue&quot; nodes.
-Only issues with a &quot;status&quot; of either
-&quot;unread&quot; or &quot;in-progres&quot; or &quot;resolved&quot; are displayed,
-and only issues with &quot;topic&quot; values including both
-&quot;security&quot; and &quot;ui&quot; are displayed.  The issues
-are grouped by priority, arranged in ascending order;
-and within groups, sorted by activity, arranged in
-descending order.  The filter section shows filters
-for the &quot;status&quot; and &quot;topic&quot; properties, and the
-table includes columns for the &quot;title&quot;, &quot;status&quot;, and
-&quot;fixer&quot; properties.</p>
-<p>Associated with each issue class is a default
-layout specifier.  The layout specifier in the above
-example is the default layout to be provided with
-the default bug-tracker schema described above in
-section 4.4.</p>
-</div>
-<div class="section" id="filter-section" name="filter-section">
-<h3><a class="toc-backref" href="#id35">Filter Section</a></h3>
-<p>The template for a filter section provides the
-filtering widgets at the top of the index view.
-Fragments enclosed in <tt class="literal">&lt;property&gt;...&lt;/property&gt;</tt>
-tags are included or omitted depending on whether the
-view specifier requests a filter for a particular property.</p>
-<p>Here's a simple example of a filter template:</p>
-<pre class="literal-block">&lt;property name=status&gt;
-    &lt;display call=&quot;checklist('status')&quot;&gt;
-&lt;/property&gt;
-&lt;br&gt;
-&lt;property name=priority&gt;
-    &lt;display call=&quot;checklist('priority')&quot;&gt;
-&lt;/property&gt;
-&lt;br&gt;
-&lt;property name=fixer&gt;
-    &lt;display call=&quot;menu('fixer')&quot;&gt;
-&lt;/property&gt;</pre>
-</div>
-<div class="section" id="index-section" name="index-section">
-<h3><a class="toc-backref" href="#id36">Index Section</a></h3>
-<p>The template for an index section describes one row of
-the index table.
-Fragments enclosed in <tt class="literal">&lt;property&gt;...&lt;/property&gt;</tt>
-tags are included or omitted depending on whether the
-view specifier requests a column for a particular property.
-The table cells should contain &lt;display&gt; tags
-to display the values of the issue's properties.</p>
-<p>Here's a simple example of an index template:</p>
-<pre class="literal-block">&lt;tr&gt;
-    &lt;property name=title&gt;
-        &lt;td&gt;&lt;display call=&quot;plain('title', max=50)&quot;&gt;&lt;/td&gt;
-    &lt;/property&gt;
-    &lt;property name=status&gt;
-        &lt;td&gt;&lt;display call=&quot;plain('status')&quot;&gt;&lt;/td&gt;
-    &lt;/property&gt;
-    &lt;property name=fixer&gt;
-        &lt;td&gt;&lt;display call=&quot;plain('fixer')&quot;&gt;&lt;/td&gt;
-    &lt;/property&gt;
-&lt;/tr&gt;</pre>
-</div>
-<div class="section" id="sorting" name="sorting">
-<h3><a class="toc-backref" href="#id37">Sorting</a></h3>
-<p>String and Date values are sorted in the natural way.
-Link properties are sorted according to the value of the
-&quot;order&quot; property on the linked nodes if it is present; or
-otherwise on the key string of the linked nodes; or
-finally on the node ids.  Multilink properties are
-sorted according to how many links are present.</p>
-</div>
-</div>
-<div class="section" id="issue-views" name="issue-views">
-<h2><a class="toc-backref" href="#id38">Issue Views</a></h2>
-<p>An issue view contains an editor section and a spool section.
-At the top of an issue view, links to superseding and superseded
-issues are always displayed.</p>
-<div class="section" id="issue-view-specifiers" name="issue-view-specifiers">
-<h3><a class="toc-backref" href="#id39">Issue View Specifiers</a></h3>
-<p>An issue view specifier is simply the issue's designator:</p>
-<pre class="literal-block">/patch23</pre>
-</div>
-<div class="section" id="editor-section" name="editor-section">
-<h3><a class="toc-backref" href="#id40">Editor Section</a></h3>
-<p>The editor section is generated from a template
-containing &lt;display&gt; tags to insert
-the appropriate widgets for editing properties.</p>
-<p>Here's an example of a basic editor template:</p>
-<pre class="literal-block">&lt;table&gt;
-&lt;tr&gt;
-    &lt;td colspan=2&gt;
-        &lt;display call=&quot;field('title', size=60)&quot;&gt;
-    &lt;/td&gt;
-&lt;/tr&gt;
-&lt;tr&gt;
-    &lt;td&gt;
-        &lt;display call=&quot;field('fixer', size=30)&quot;&gt;
-    &lt;/td&gt;
-    &lt;td&gt;
-        &lt;display call=&quot;menu('status')&gt;
-    &lt;/td&gt;
-&lt;/tr&gt;
-&lt;tr&gt;
-    &lt;td&gt;
-        &lt;display call=&quot;field('nosy', size=30)&quot;&gt;
-    &lt;/td&gt;
-    &lt;td&gt;
-        &lt;display call=&quot;menu('priority')&gt;
-    &lt;/td&gt;
-&lt;/tr&gt;
-&lt;tr&gt;
-    &lt;td colspan=2&gt;
-        &lt;display call=&quot;note()&quot;&gt;
-    &lt;/td&gt;
-&lt;/tr&gt;
-&lt;/table&gt;</pre>
-<p>As shown in the example, the editor template can also
-request the display of a &quot;note&quot; field, which is a
-text area for entering a note to go along with a change.</p>
-<p>When a change is submitted, the system automatically
-generates a message describing the changed properties.
-The message displays all of the property values on the
-issue and indicates which ones have changed.
-An example of such a message might be this:</p>
-<pre class="literal-block">title: Polly Parrot is dead
-priority: critical
-status: unread -&gt; in-progress
-fixer: (none)
-keywords: parrot,plumage,perch,nailed,dead</pre>
-<p>If a note is given in the &quot;note&quot; field, the note is
-appended to the description.  The message is then added
-to the issue's message spool (thus triggering the standard
-detector to react by sending out this message to the nosy list).</p>
-</div>
-<div class="section" id="spool-section" name="spool-section">
-<h3><a class="toc-backref" href="#id41">Spool Section</a></h3>
-<p>The spool section lists messages in the issue's &quot;messages&quot;
-property.  The index of messages displays the &quot;date&quot;, &quot;author&quot;,
-and &quot;summary&quot; properties on the message nodes, and selecting a
-message takes you to its content.</p>
-</div>
-</div>
-</div>
-<div class="section" id="deployment-scenarios" name="deployment-scenarios">
-<h1><a class="toc-backref" href="#id42">Deployment Scenarios</a></h1>
-<p>The design described above should be general enough
-to permit the use of Roundup for bug tracking, managing
-projects, managing patches, or holding discussions.  By
-using nodes of multiple types, one could deploy a system
-that maintains requirement specifications, catalogs bugs,
-and manages submitted patches, where patches could be
-linked to the bugs and requirements they address.</p>
-</div>
-<div class="section" id="acknowledgements" name="acknowledgements">
-<h1><a class="toc-backref" href="#id43">Acknowledgements</a></h1>
-<p>My thanks are due to Christy Heyl for 
-reviewing and contributing suggestions to this paper
-and motivating me to get it done, and to
-Jesse Vincent, Mark Miller, Christopher Simons,
-Jeff Dunmall, Wayne Gramlich, and Dean Tribble for
-their assistance with the first-round submission.</p>
-</div>
-<div class="section" id="changes-to-this-document" name="changes-to-this-document">
-<h1><a class="toc-backref" href="#id44">Changes to this document</a></h1>
-<ul class="simple">
-<li>Added Boolean and Number types</li>
-<li>Added section Hyperdatabase Implementations</li>
-<li>&quot;Item&quot; has been renamed to &quot;Issue&quot; to account for the more specific nature
-of the Class.</li>
-</ul>
-</div>
-</div>
-<hr class="footer"/>
-<div class="footer">
-Generated on: 2002-07-29. </div>
-</body>
-</html>