Code

4a523bd9236d7f8f1d6a66fcd94cf6f47a6bc9f5
[roundup.git] / doc / design.html
1 <?xml version="1.0" encoding="utf-8"?>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" SYSTEM "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3 <html lang="en">
4 <head>
5 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6 <meta name="generator" content="Docutils: http://docutils.sourceforge.net/">
7 <link rel="stylesheet" href="default.css" type="text/css" />
8 <title>Roundup - An Issue-Tracking System for Knowledge Workers</title>
9 <meta name="author" content="Ka-Ping Yee (original)" />
10 <meta name="author" content="Richard Jones (implementation)" />
11 </head>
12 <body>
13 <div class="document" id="roundup-an-issue-tracking-system-for-knowledge-workers" name="roundup-an-issue-tracking-system-for-knowledge-workers">
14 <h1 class="title">Roundup - An Issue-Tracking System for Knowledge Workers</h1>
15 <table class="docinfo" frame="void" rules="none">
16 <col class="docinfo-name" />
17 <col class="docinfo-content" />
18 <tbody valign="top">
19 <tr><td class="docinfo-name">Author:&nbsp;</td><td>
20 Ka-Ping Yee (original)</td></tr>
21 <tr><td class="docinfo-name">Author:&nbsp;</td><td>
22 Richard Jones (implementation)</td></tr>
23 </tbody>
24 </table>
25 <div class="contents topic" id="contents" name="contents">
26 <p class="topic-title">Contents</p>
27 <ul class="simple">
28 <li id="id2" name="id2"><a class="reference" href="#introduction">Introduction</a></li>
29 <li id="id3" name="id3"><a class="reference" href="#the-layer-cake">The Layer Cake</a></li>
30 <li id="id4" name="id4"><a class="reference" href="#hyperdatabase">Hyperdatabase</a><ul>
31 <li id="id5" name="id5"><a class="reference" href="#dates-and-date-arithmetic">Dates and Date Arithmetic</a></li>
32 <li id="id6" name="id6"><a class="reference" href="#nodes-and-classes">Nodes and Classes</a></li>
33 <li id="id7" name="id7"><a class="reference" href="#identifiers-and-designators">Identifiers and Designators</a></li>
34 <li id="id8" name="id8"><a class="reference" href="#property-names-and-types">Property Names and Types</a></li>
35 <li id="id9" name="id9"><a class="reference" href="#hyperdb-interface-specification">Hyperdb Interface Specification</a></li>
36 <li id="id10" name="id10"><a class="reference" href="#hyperdatabase-implementations">Hyperdatabase Implementations</a></li>
37 <li id="id11" name="id11"><a class="reference" href="#application-example">Application Example</a></li>
38 </ul>
39 </li>
40 <li id="id12" name="id12"><a class="reference" href="#roundup-database">Roundup Database</a><ul>
41 <li id="id13" name="id13"><a class="reference" href="#reserved-classes">Reserved Classes</a><ul>
42 <li id="id14" name="id14"><a class="reference" href="#users">Users</a></li>
43 <li id="id15" name="id15"><a class="reference" href="#messages">Messages</a></li>
44 <li id="id16" name="id16"><a class="reference" href="#files">Files</a></li>
45 </ul>
46 </li>
47 <li id="id17" name="id17"><a class="reference" href="#issue-classes">Issue Classes</a></li>
48 <li id="id18" name="id18"><a class="reference" href="#roundupdb-interface-specification">Roundupdb Interface Specification</a></li>
49 <li id="id19" name="id19"><a class="reference" href="#default-schema">Default Schema</a></li>
50 </ul>
51 </li>
52 <li id="id20" name="id20"><a class="reference" href="#detector-interface">Detector Interface</a><ul>
53 <li id="id21" name="id21"><a class="reference" href="#detector-interface-specification">Detector Interface Specification</a></li>
54 <li id="id22" name="id22"><a class="reference" href="#detector-example">Detector Example</a></li>
55 </ul>
56 </li>
57 <li id="id23" name="id23"><a class="reference" href="#command-interface">Command Interface</a><ul>
58 <li id="id24" name="id24"><a class="reference" href="#command-interface-specification">Command Interface Specification</a></li>
59 <li id="id25" name="id25"><a class="reference" href="#usage-example">Usage Example</a></li>
60 </ul>
61 </li>
62 <li id="id26" name="id26"><a class="reference" href="#e-mail-user-interface">E-mail User Interface</a><ul>
63 <li id="id27" name="id27"><a class="reference" href="#message-processing">Message Processing</a></li>
64 <li id="id28" name="id28"><a class="reference" href="#nosy-lists">Nosy Lists</a></li>
65 <li id="id29" name="id29"><a class="reference" href="#setting-properties">Setting Properties</a></li>
66 </ul>
67 </li>
68 <li id="id30" name="id30"><a class="reference" href="#web-user-interface">Web User Interface</a><ul>
69 <li id="id31" name="id31"><a class="reference" href="#views-and-view-specifiers">Views and View Specifiers</a></li>
70 <li id="id32" name="id32"><a class="reference" href="#displaying-properties">Displaying Properties</a></li>
71 <li id="id33" name="id33"><a class="reference" href="#index-views">Index Views</a><ul>
72 <li id="id34" name="id34"><a class="reference" href="#index-view-specifiers">Index View Specifiers</a></li>
73 <li id="id35" name="id35"><a class="reference" href="#filter-section">Filter Section</a></li>
74 <li id="id36" name="id36"><a class="reference" href="#index-section">Index Section</a></li>
75 <li id="id37" name="id37"><a class="reference" href="#sorting">Sorting</a></li>
76 </ul>
77 </li>
78 <li id="id38" name="id38"><a class="reference" href="#issue-views">Issue Views</a><ul>
79 <li id="id39" name="id39"><a class="reference" href="#issue-view-specifiers">Issue View Specifiers</a></li>
80 <li id="id40" name="id40"><a class="reference" href="#editor-section">Editor Section</a></li>
81 <li id="id41" name="id41"><a class="reference" href="#spool-section">Spool Section</a></li>
82 </ul>
83 </li>
84 </ul>
85 </li>
86 <li id="id42" name="id42"><a class="reference" href="#deployment-scenarios">Deployment Scenarios</a></li>
87 <li id="id43" name="id43"><a class="reference" href="#acknowledgements">Acknowledgements</a></li>
88 <li id="id44" name="id44"><a class="reference" href="#changes-to-this-document">Changes to this document</a></li>
89 </ul>
90 </div>
91 <div class="section" id="introduction" name="introduction">
92 <h1><a class="toc-backref" href="#id2">Introduction</a></h1>
93 <p>This document presents a description of the components
94 of the Roundup system and specifies their interfaces and
95 behaviour in sufficient detail to guide an implementation.
96 For the philosophy and rationale behind the Roundup design,
97 see the first-round Software Carpentry submission for Roundup.
98 This document fleshes out that design as well as specifying
99 interfaces so that the components can be developed separately.</p>
100 </div>
101 <div class="section" id="the-layer-cake" name="the-layer-cake">
102 <h1><a class="toc-backref" href="#id3">The Layer Cake</a></h1>
103 <p>Lots of software design documents come with a picture of
104 a cake.  Everybody seems to like them.  I also like cakes
105 (i think they are tasty).  So i, too, shall include
106 a picture of a cake here:</p>
107 <pre class="literal-block"> _________________________________________________________________________
108 |  E-mail Client   |   Web Browser   |   Detector Scripts   |    Shell    |
109 |------------------+-----------------+----------------------+-------------|
110 |   E-mail User    |    Web User     |      Detector        |   Command   | 
111 |-------------------------------------------------------------------------|
112 |                         Roundup Database Layer                          |
113 |-------------------------------------------------------------------------|
114 |                          Hyperdatabase Layer                            |
115 |-------------------------------------------------------------------------|
116 |                             Storage Layer                               |
117  -------------------------------------------------------------------------</pre>
118 <p>The colourful parts of the cake are part of our system;
119 the faint grey parts of the cake are external components.</p>
120 <p>I will now proceed to forgo all table manners and
121 eat from the bottom of the cake to the top.  You may want
122 to stand back a bit so you don't get covered in crumbs.</p>
123 </div>
124 <div class="section" id="hyperdatabase" name="hyperdatabase">
125 <h1><a class="toc-backref" href="#id4">Hyperdatabase</a></h1>
126 <p>The lowest-level component to be implemented is the hyperdatabase.
127 The hyperdatabase is intended to be
128 a flexible data store that can hold configurable data in
129 records which we call nodes.</p>
130 <p>The hyperdatabase is implemented on top of the storage layer,
131 an external module for storing its data.  The storage layer could
132 be a third-party RDBMS; for a &quot;batteries-included&quot; distribution,
133 implementing the hyperdatabase on the standard bsddb
134 module is suggested.</p>
135 <div class="section" id="dates-and-date-arithmetic" name="dates-and-date-arithmetic">
136 <h2><a class="toc-backref" href="#id5">Dates and Date Arithmetic</a></h2>
137 <p>Before we get into the hyperdatabase itself, we need a
138 way of handling dates.  The hyperdatabase module provides
139 Timestamp objects for
140 representing date-and-time stamps and Interval objects for
141 representing date-and-time intervals.</p>
142 <p>As strings, date-and-time stamps are specified with
143 the date in international standard format
144 (<tt class="literal">yyyy-mm-dd</tt>)
145 joined to the time (<tt class="literal">hh:mm:ss</tt>)
146 by a period &quot;<tt class="literal">.</tt>&quot;.  Dates in
147 this form can be easily compared and are fairly readable
148 when printed.  An example of a valid stamp is
149 &quot;<tt class="literal">2000-06-24.13:03:59</tt>&quot;.
150 We'll call this the &quot;full date format&quot;.  When Timestamp objects are
151 printed as strings, they appear in the full date format with
152 the time always given in GMT.  The full date format is always
153 exactly 19 characters long.</p>
154 <p>For user input, some partial forms are also permitted:
155 the whole time or just the seconds may be omitted; and the whole date
156 may be omitted or just the year may be omitted.  If the time is given,
157 the time is interpreted in the user's local time zone.
158 The Date constructor takes care of these conversions.
159 In the following examples, suppose that <tt class="literal">yyyy</tt> is the current year,
160 <tt class="literal">mm</tt> is the current month, and <tt class="literal">dd</tt> is the current
161 day of the month; and suppose that the user is on Eastern Standard Time.</p>
162 <ul class="simple">
163 <li>&quot;2000-04-17&quot; means &lt;Date 2000-04-17.00:00:00&gt;</li>
164 <li>&quot;01-25&quot; means &lt;Date yyyy-01-25.00:00:00&gt;</li>
165 <li>&quot;2000-04-17.03:45&quot; means &lt;Date 2000-04-17.08:45:00&gt;</li>
166 <li>&quot;08-13.22:13&quot; means &lt;Date yyyy-08-14.03:13:00&gt;</li>
167 <li>&quot;11-07.09:32:43&quot; means &lt;Date yyyy-11-07.14:32:43&gt;</li>
168 <li>&quot;14:25&quot; means</li>
169 <li>&lt;Date yyyy-mm-dd.19:25:00&gt;</li>
170 <li>&quot;8:47:11&quot; means</li>
171 <li>&lt;Date yyyy-mm-dd.13:47:11&gt;</li>
172 <li>the special date &quot;.&quot; means &quot;right now&quot;</li>
173 </ul>
174 <p>Date intervals are specified using the suffixes
175 &quot;y&quot;, &quot;m&quot;, and &quot;d&quot;.  The suffix &quot;w&quot; (for &quot;week&quot;) means 7 days.
176 Time intervals are specified in hh:mm:ss format (the seconds
177 may be omitted, but the hours and minutes may not).</p>
178 <ul class="simple">
179 <li>&quot;3y&quot; means three years</li>
180 <li>&quot;2y 1m&quot; means two years and one month</li>
181 <li>&quot;1m 25d&quot; means one month and 25 days</li>
182 <li>&quot;2w 3d&quot; means two weeks and three days</li>
183 <li>&quot;1d 2:50&quot; means one day, two hours, and 50 minutes</li>
184 <li>&quot;14:00&quot; means 14 hours</li>
185 <li>&quot;0:04:33&quot; means four minutes and 33 seconds</li>
186 </ul>
187 <p>The Date class should understand simple date expressions of the form 
188 <em>stamp</em> <tt class="literal">+</tt> <em>interval</em> and <em>stamp</em> <tt class="literal">-</tt> <em>interval</em>.
189 When adding or subtracting intervals involving months or years, the
190 components are handled separately.  For example, when evaluating
191 &quot;<tt class="literal">2000-06-25 + 1m 10d</tt>&quot;, we first add one month to
192 get 2000-07-25, then add 10 days to get
193 2000-08-04 (rather than trying to decide whether
194 1m 10d means 38 or 40 or 41 days).</p>
195 <p>Here is an outline of the Date and Interval classes:</p>
196 <pre class="literal-block">class Date:
197     def __init__(self, spec, offset):
198         &quot;&quot;&quot;Construct a date given a specification and a time zone offset.
200         'spec' is a full date or a partial form, with an optional
201         added or subtracted interval.  'offset' is the local time
202         zone offset from GMT in hours.
203         &quot;&quot;&quot;
205     def __add__(self, interval):
206         &quot;&quot;&quot;Add an interval to this date to produce another date.&quot;&quot;&quot;
208     def __sub__(self, interval):
209         &quot;&quot;&quot;Subtract an interval from this date to produce another date.&quot;&quot;&quot;
211     def __cmp__(self, other):
212         &quot;&quot;&quot;Compare this date to another date.&quot;&quot;&quot;
214     def __str__(self):
215         &quot;&quot;&quot;Return this date as a string in the yyyy-mm-dd.hh:mm:ss format.&quot;&quot;&quot;
217     def local(self, offset):
218         &quot;&quot;&quot;Return this date as yyyy-mm-dd.hh:mm:ss in a local time zone.&quot;&quot;&quot;
220 class Interval:
221     def __init__(self, spec):
222         &quot;&quot;&quot;Construct an interval given a specification.&quot;&quot;&quot;
224     def __cmp__(self, other):
225         &quot;&quot;&quot;Compare this interval to another interval.&quot;&quot;&quot;
226         
227     def __str__(self):
228         &quot;&quot;&quot;Return this interval as a string.&quot;&quot;&quot;</pre>
229 <p>Here are some examples of how these classes would behave in practice.
230 For the following examples, assume that we are on Eastern Standard
231 Time and the current local time is 19:34:02 on 25 June 2000:</p>
232 <pre class="literal-block">&gt;&gt;&gt; Date(&quot;.&quot;)
233 &lt;Date 2000-06-26.00:34:02&gt;
234 &gt;&gt;&gt; _.local(-5)
235 &quot;2000-06-25.19:34:02&quot;
236 &gt;&gt;&gt; Date(&quot;. + 2d&quot;)
237 &lt;Date 2000-06-28.00:34:02&gt;
238 &gt;&gt;&gt; Date(&quot;1997-04-17&quot;, -5)
239 &lt;Date 1997-04-17.00:00:00&gt;
240 &gt;&gt;&gt; Date(&quot;01-25&quot;, -5)
241 &lt;Date 2000-01-25.00:00:00&gt;
242 &gt;&gt;&gt; Date(&quot;08-13.22:13&quot;, -5)
243 &lt;Date 2000-08-14.03:13:00&gt;
244 &gt;&gt;&gt; Date(&quot;14:25&quot;, -5)
245 &lt;Date 2000-06-25.19:25:00&gt;
246 &gt;&gt;&gt; Interval(&quot;  3w  1  d  2:00&quot;)
247 &lt;Interval 22d 2:00&gt;
248 &gt;&gt;&gt; Date(&quot;. + 2d&quot;) - Interval(&quot;3w&quot;)
249 &lt;Date 2000-06-07.00:34:02&gt;</pre>
250 </div>
251 <div class="section" id="nodes-and-classes" name="nodes-and-classes">
252 <h2><a class="toc-backref" href="#id6">Nodes and Classes</a></h2>
253 <p>Nodes contain data in properties.  To Python, these
254 properties are presented as the key-value pairs of a dictionary.
255 Each node belongs to a class which defines the names
256 and types of its properties.  The database permits the creation
257 and modification of classes as well as nodes.</p>
258 </div>
259 <div class="section" id="identifiers-and-designators" name="identifiers-and-designators">
260 <h2><a class="toc-backref" href="#id7">Identifiers and Designators</a></h2>
261 <p>Each node has a numeric identifier which is unique among
262 nodes in its class.  The nodes are numbered sequentially
263 within each class in order of creation, starting from 1.
264 The designator
265 for a node is a way to identify a node in the database, and
266 consists of the name of the node's class concatenated with
267 the node's numeric identifier.</p>
268 <p>For example, if &quot;spam&quot; and &quot;eggs&quot; are classes, the first
269 node created in class &quot;spam&quot; has id 1 and designator &quot;spam1&quot;.
270 The first node created in class &quot;eggs&quot; also has id 1 but has
271 the distinct designator &quot;eggs1&quot;.  Node designators are
272 conventionally enclosed in square brackets when mentioned
273 in plain text.  This permits a casual mention of, say,
274 &quot;[patch37]&quot; in an e-mail message to be turned into an active
275 hyperlink.</p>
276 </div>
277 <div class="section" id="property-names-and-types" name="property-names-and-types">
278 <h2><a class="toc-backref" href="#id8">Property Names and Types</a></h2>
279 <p>Property names must begin with a letter.</p>
280 <p>A property may be one of five basic types:</p>
281 <ul class="simple">
282 <li>String properties are for storing arbitrary-length strings.</li>
283 <li>Boolean properties are for storing true/false, or yes/no values.</li>
284 <li>Number properties are for storing numeric values.</li>
285 <li>Date properties store date-and-time stamps.
286 Their values are Timestamp objects.</li>
287 <li>A Link property refers to a single other node
288 selected from a specified class.  The class is part of the property;
289 the value is an integer, the id of the chosen node.</li>
290 <li>A Multilink property refers to possibly many nodes
291 in a specified class.  The value is a list of integers.</li>
292 </ul>
293 <p><em>None</em> is also a permitted value for any of these property
294 types.  An attempt to store None into a Multilink property stores an empty list.</p>
295 <p>A property that is not specified will return as None from a <em>get</em>
296 operation.</p>
297 </div>
298 <div class="section" id="hyperdb-interface-specification" name="hyperdb-interface-specification">
299 <h2><a class="toc-backref" href="#id9">Hyperdb Interface Specification</a></h2>
300 <p>The hyperdb module provides property objects to designate
301 the different kinds of properties.  These objects are used when
302 specifying what properties belong in classes:</p>
303 <pre class="literal-block">class String:
304     def __init__(self, indexme='no'):
305         &quot;&quot;&quot;An object designating a String property.&quot;&quot;&quot;
307 class Boolean:
308     def __init__(self):
309         &quot;&quot;&quot;An object designating a Boolean property.&quot;&quot;&quot;
311 class Number:
312     def __init__(self):
313         &quot;&quot;&quot;An object designating a Number property.&quot;&quot;&quot;
315 class Date:
316     def __init__(self):
317         &quot;&quot;&quot;An object designating a Date property.&quot;&quot;&quot;
319 class Link:
320     def __init__(self, classname, do_journal='yes'):
321         &quot;&quot;&quot;An object designating a Link property that links to
322         nodes in a specified class.
324         If the do_journal argument is not 'yes' then changes to
325         the property are not journalled in the linked node.
326         &quot;&quot;&quot;
328 class Multilink:
329     def __init__(self, classname, do_journal='yes'):
330         &quot;&quot;&quot;An object designating a Multilink property that links
331         to nodes in a specified class.
333         If the do_journal argument is not 'yes' then changes to
334         the property are not journalled in the linked node(s).
335         &quot;&quot;&quot;</pre>
336 <p>Here is the interface provided by the hyperdatabase:</p>
337 <pre class="literal-block">class Database:
338     &quot;&quot;&quot;A database for storing records containing flexible data types.&quot;&quot;&quot;
340     def __init__(self, storagelocator, journaltag):
341         &quot;&quot;&quot;Open a hyperdatabase given a specifier to some storage.
343         The meaning of 'storagelocator' depends on the particular
344         implementation of the hyperdatabase.  It could be a file name,
345         a directory path, a socket descriptor for a connection to a
346         database over the network, etc.
348         The 'journaltag' is a token that will be attached to the journal
349         entries for any edits done on the database.  If 'journaltag' is
350         None, the database is opened in read-only mode: the Class.create(),
351         Class.set(), and Class.retire() methods are disabled.
352         &quot;&quot;&quot;
354     def __getattr__(self, classname):
355         &quot;&quot;&quot;A convenient way of calling self.getclass(classname).&quot;&quot;&quot;
357     def getclasses(self):
358         &quot;&quot;&quot;Return a list of the names of all existing classes.&quot;&quot;&quot;
360     def getclass(self, classname):
361         &quot;&quot;&quot;Get the Class object representing a particular class.
363         If 'classname' is not a valid class name, a KeyError is raised.
364         &quot;&quot;&quot;
366 class Class:
367     &quot;&quot;&quot;The handle to a particular class of nodes in a hyperdatabase.&quot;&quot;&quot;
369     def __init__(self, db, classname, **properties):
370         &quot;&quot;&quot;Create a new class with a given name and property specification.
372         'classname' must not collide with the name of an existing class,
373         or a ValueError is raised.  The keyword arguments in 'properties'
374         must map names to property objects, or a TypeError is raised.
375         &quot;&quot;&quot;
377     # Editing nodes:
379     def create(self, **propvalues):
380         &quot;&quot;&quot;Create a new node of this class and return its id.
382         The keyword arguments in 'propvalues' map property names to values.
383         The values of arguments must be acceptable for the types of their
384         corresponding properties or a TypeError is raised.  If this class
385         has a key property, it must be present and its value must not
386         collide with other key strings or a ValueError is raised.  Any other
387         properties on this class that are missing from the 'propvalues'
388         dictionary are set to None.  If an id in a link or multilink
389         property does not refer to a valid node, an IndexError is raised.
390         &quot;&quot;&quot;
392     def get(self, nodeid, propname):
393         &quot;&quot;&quot;Get the value of a property on an existing node of this class.
395         'nodeid' must be the id of an existing node of this class or an
396         IndexError is raised.  'propname' must be the name of a property
397         of this class or a KeyError is raised.
398         &quot;&quot;&quot;
400     def set(self, nodeid, **propvalues):
401         &quot;&quot;&quot;Modify a property on an existing node of this class.
402         
403         'nodeid' must be the id of an existing node of this class or an
404         IndexError is raised.  Each key in 'propvalues' must be the name
405         of a property of this class or a KeyError is raised.  All values
406         in 'propvalues' must be acceptable types for their corresponding
407         properties or a TypeError is raised.  If the value of the key
408         property is set, it must not collide with other key strings or a
409         ValueError is raised.  If the value of a Link or Multilink
410         property contains an invalid node id, a ValueError is raised.
411         &quot;&quot;&quot;
413     def retire(self, nodeid):
414         &quot;&quot;&quot;Retire a node.
415         
416         The properties on the node remain available from the get() method,
417         and the node's id is never reused.  Retired nodes are not returned
418         by the find(), list(), or lookup() methods, and other nodes may
419         reuse the values of their key properties.
420         &quot;&quot;&quot;
422     def history(self, nodeid):
423         &quot;&quot;&quot;Retrieve the journal of edits on a particular node.
425         'nodeid' must be the id of an existing node of this class or an
426         IndexError is raised.
428         The returned list contains tuples of the form
430             (date, tag, action, params)
432         'date' is a Timestamp object specifying the time of the change and
433         'tag' is the journaltag specified when the database was opened.
434         'action' may be:
436             'create' or 'set' -- 'params' is a dictionary of property values
437             'link' or 'unlink' -- 'params' is (classname, nodeid, propname)
438             'retire' -- 'params' is None
439         &quot;&quot;&quot;
441     # Locating nodes:
443     def setkey(self, propname):
444         &quot;&quot;&quot;Select a String property of this class to be the key property.
446         'propname' must be the name of a String property of this class or
447         None, or a TypeError is raised.  The values of the key property on
448         all existing nodes must be unique or a ValueError is raised.
449         &quot;&quot;&quot;
451     def getkey(self):
452         &quot;&quot;&quot;Return the name of the key property for this class or None.&quot;&quot;&quot;
454     def lookup(self, keyvalue):
455         &quot;&quot;&quot;Locate a particular node by its key property and return its id.
457         If this class has no key property, a TypeError is raised.  If the
458         'keyvalue' matches one of the values for the key property among
459         the nodes in this class, the matching node's id is returned;
460         otherwise a KeyError is raised.
461         &quot;&quot;&quot;
463     def find(self, propname, nodeid):
464         &quot;&quot;&quot;Get the ids of nodes in this class which link to a given node.
465         
466         'propname' must be the name of a property in this class, or a
467         KeyError is raised.  That property must be a Link or Multilink
468         property, or a TypeError is raised.  'nodeid' must be the id of
469         an existing node in the class linked to by the given property,
470         or an IndexError is raised.
471         &quot;&quot;&quot;
473     def list(self):
474         &quot;&quot;&quot;Return a list of the ids of the active nodes in this class.&quot;&quot;&quot;
476     def count(self):
477         &quot;&quot;&quot;Get the number of nodes in this class.
479         If the returned integer is 'numnodes', the ids of all the nodes
480         in this class run from 1 to numnodes, and numnodes+1 will be the
481         id of the next node to be created in this class.
482         &quot;&quot;&quot;
484     # Manipulating properties:
486     def getprops(self):
487         &quot;&quot;&quot;Return a dictionary mapping property names to property objects.&quot;&quot;&quot;
489     def addprop(self, **properties):
490         &quot;&quot;&quot;Add properties to this class.
492         The keyword arguments in 'properties' must map names to property
493         objects, or a TypeError is raised.  None of the keys in 'properties'
494         may collide with the names of existing properties, or a ValueError
495         is raised before any properties have been added.
496         &quot;&quot;&quot;</pre>
497 <p>TODO: additional methods</p>
498 </div>
499 <div class="section" id="hyperdatabase-implementations" name="hyperdatabase-implementations">
500 <h2><a class="toc-backref" href="#id10">Hyperdatabase Implementations</a></h2>
501 <p>Hyperdatabase implementations exist to create the interface described in the
502 <a class="reference" href="#hyperdb-interface-specification">hyperdb interface specification</a>
503 over an existing storage mechanism. Examples are relational databases,
504 *dbm key-value databases, and so on.</p>
505 <p>TODO: finish</p>
506 </div>
507 <div class="section" id="application-example" name="application-example">
508 <h2><a class="toc-backref" href="#id11">Application Example</a></h2>
509 <p>Here is an example of how the hyperdatabase module would work in practice:</p>
510 <pre class="literal-block">&gt;&gt;&gt; import hyperdb
511 &gt;&gt;&gt; db = hyperdb.Database(&quot;foo.db&quot;, &quot;ping&quot;)
512 &gt;&gt;&gt; db
513 &lt;hyperdb.Database &quot;foo.db&quot; opened by &quot;ping&quot;&gt;
514 &gt;&gt;&gt; hyperdb.Class(db, &quot;status&quot;, name=hyperdb.String())
515 &lt;hyperdb.Class &quot;status&quot;&gt;
516 &gt;&gt;&gt; _.setkey(&quot;name&quot;)
517 &gt;&gt;&gt; db.status.create(name=&quot;unread&quot;)
519 &gt;&gt;&gt; db.status.create(name=&quot;in-progress&quot;)
521 &gt;&gt;&gt; db.status.create(name=&quot;testing&quot;)
523 &gt;&gt;&gt; db.status.create(name=&quot;resolved&quot;)
525 &gt;&gt;&gt; db.status.count()
527 &gt;&gt;&gt; db.status.list()
528 [1, 2, 3, 4]
529 &gt;&gt;&gt; db.status.lookup(&quot;in-progress&quot;)
531 &gt;&gt;&gt; db.status.retire(3)
532 &gt;&gt;&gt; db.status.list()
533 [1, 2, 4]
534 &gt;&gt;&gt; hyperdb.Class(db, &quot;issue&quot;, title=hyperdb.String(), status=hyperdb.Link(&quot;status&quot;))
535 &lt;hyperdb.Class &quot;issue&quot;&gt;
536 &gt;&gt;&gt; db.issue.create(title=&quot;spam&quot;, status=1)
538 &gt;&gt;&gt; db.issue.create(title=&quot;eggs&quot;, status=2)
540 &gt;&gt;&gt; db.issue.create(title=&quot;ham&quot;, status=4)
542 &gt;&gt;&gt; db.issue.create(title=&quot;arguments&quot;, status=2)
544 &gt;&gt;&gt; db.issue.create(title=&quot;abuse&quot;, status=1)
546 &gt;&gt;&gt; hyperdb.Class(db, &quot;user&quot;, username=hyperdb.Key(), password=hyperdb.String())
547 &lt;hyperdb.Class &quot;user&quot;&gt;
548 &gt;&gt;&gt; db.issue.addprop(fixer=hyperdb.Link(&quot;user&quot;))
549 &gt;&gt;&gt; db.issue.getprops()
550 {&quot;title&quot;: &lt;hyperdb.String&gt;, &quot;status&quot;: &lt;hyperdb.Link to &quot;status&quot;&gt;,
551  &quot;user&quot;: &lt;hyperdb.Link to &quot;user&quot;&gt;}
552 &gt;&gt;&gt; db.issue.set(5, status=2)
553 &gt;&gt;&gt; db.issue.get(5, &quot;status&quot;)
555 &gt;&gt;&gt; db.status.get(2, &quot;name&quot;)
556 &quot;in-progress&quot;
557 &gt;&gt;&gt; db.issue.get(5, &quot;title&quot;)
558 &quot;abuse&quot;
559 &gt;&gt;&gt; db.issue.find(&quot;status&quot;, db.status.lookup(&quot;in-progress&quot;))
560 [2, 4, 5]
561 &gt;&gt;&gt; db.issue.history(5)
562 [(&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}),
563  (&lt;Date 2000-06-28.19:11:04&gt;, &quot;ping&quot;, &quot;set&quot;, {&quot;status&quot;: 2})]
564 &gt;&gt;&gt; db.status.history(1)
565 [(&lt;Date 2000-06-28.19:09:43&gt;, &quot;ping&quot;, &quot;link&quot;, (&quot;issue&quot;, 5, &quot;status&quot;)),
566  (&lt;Date 2000-06-28.19:11:04&gt;, &quot;ping&quot;, &quot;unlink&quot;, (&quot;issue&quot;, 5, &quot;status&quot;))]
567 &gt;&gt;&gt; db.status.history(2)
568 [(&lt;Date 2000-06-28.19:11:04&gt;, &quot;ping&quot;, &quot;link&quot;, (&quot;issue&quot;, 5, &quot;status&quot;))]</pre>
569 <p>For the purposes of journalling, when a Multilink property is
570 set to a new list of nodes, the hyperdatabase compares the old
571 list to the new list.
572 The journal records &quot;unlink&quot; events for all the nodes that appear
573 in the old list but not the new list,
574 and &quot;link&quot; events for
575 all the nodes that appear in the new list but not in the old list.</p>
576 </div>
577 </div>
578 <div class="section" id="roundup-database" name="roundup-database">
579 <h1><a class="toc-backref" href="#id12">Roundup Database</a></h1>
580 <p>The Roundup database layer is implemented on top of the
581 hyperdatabase and mediates calls to the database.
582 Some of the classes in the Roundup database are considered
583 issue classes.
584 The Roundup database layer adds detectors and user nodes,
585 and on issues it provides mail spools, nosy lists, and superseders.</p>
586 <p>TODO: where functionality is implemented.</p>
587 <div class="section" id="reserved-classes" name="reserved-classes">
588 <h2><a class="toc-backref" href="#id13">Reserved Classes</a></h2>
589 <p>Internal to this layer we reserve three special classes
590 of nodes that are not issues.</p>
591 <div class="section" id="users" name="users">
592 <h3><a class="toc-backref" href="#id14">Users</a></h3>
593 <p>Users are stored in the hyperdatabase as nodes of
594 class &quot;user&quot;.  The &quot;user&quot; class has the definition:</p>
595 <pre class="literal-block">hyperdb.Class(db, &quot;user&quot;, username=hyperdb.String(),
596                           password=hyperdb.String(),
597                           address=hyperdb.String())
598 db.user.setkey(&quot;username&quot;)</pre>
599 </div>
600 <div class="section" id="messages" name="messages">
601 <h3><a class="toc-backref" href="#id15">Messages</a></h3>
602 <p>E-mail messages are represented by hyperdatabase nodes of class &quot;msg&quot;.
603 The actual text content of the messages is stored in separate files.
604 (There's no advantage to be gained by stuffing them into the
605 hyperdatabase, and if messages are stored in ordinary text files,
606 they can be grepped from the command line.)  The text of a message is
607 saved in a file named after the message node designator (e.g. &quot;msg23&quot;)
608 for the sake of the command interface (see below).  Attachments are
609 stored separately and associated with &quot;file&quot; nodes.
610 The &quot;msg&quot; class has the definition:</p>
611 <pre class="literal-block">hyperdb.Class(db, &quot;msg&quot;, author=hyperdb.Link(&quot;user&quot;),
612                          recipients=hyperdb.Multilink(&quot;user&quot;),
613                          date=hyperdb.Date(),
614                          summary=hyperdb.String(),
615                          files=hyperdb.Multilink(&quot;file&quot;))</pre>
616 <p>The &quot;author&quot; property indicates the author of the message
617 (a &quot;user&quot; node must exist in the hyperdatabase for any messages
618 that are stored in the system).
619 The &quot;summary&quot; property contains a summary of the message for display
620 in a message index.</p>
621 </div>
622 <div class="section" id="files" name="files">
623 <h3><a class="toc-backref" href="#id16">Files</a></h3>
624 <p>Submitted files are represented by hyperdatabase
625 nodes of class &quot;file&quot;.  Like e-mail messages, the file content
626 is stored in files outside the database,
627 named after the file node designator (e.g. &quot;file17&quot;).
628 The &quot;file&quot; class has the definition:</p>
629 <pre class="literal-block">hyperdb.Class(db, &quot;file&quot;, user=hyperdb.Link(&quot;user&quot;),
630                           name=hyperdb.String(),
631                           type=hyperdb.String())</pre>
632 <p>The &quot;user&quot; property indicates the user who submitted the
633 file, the &quot;name&quot; property holds the original name of the file,
634 and the &quot;type&quot; property holds the MIME type of the file as received.</p>
635 </div>
636 </div>
637 <div class="section" id="issue-classes" name="issue-classes">
638 <h2><a class="toc-backref" href="#id17">Issue Classes</a></h2>
639 <p>All issues have the following standard properties:</p>
640 <table frame="border" rules="all">
641 <colgroup>
642 <col colwidth="30%" />
643 <col colwidth="70%" />
644 </colgroup>
645 <thead valign="bottom">
646 <tr><th>Property</th>
647 <th>Definition</th>
648 </tr>
649 </thead>
650 <tbody valign="top">
651 <tr><td>title</td>
652 <td>hyperdb.String()</td>
653 </tr>
654 <tr><td>messages</td>
655 <td>hyperdb.Multilink(&quot;msg&quot;)</td>
656 </tr>
657 <tr><td>files</td>
658 <td>hyperdb.Multilink(&quot;file&quot;)</td>
659 </tr>
660 <tr><td>nosy</td>
661 <td>hyperdb.Multilink(&quot;user&quot;)</td>
662 </tr>
663 <tr><td>superseder</td>
664 <td>hyperdb.Multilink(&quot;issue&quot;)</td>
665 </tr>
666 </tbody>
667 </table>
668 <p>Also, two Date properties named &quot;creation&quot; and &quot;activity&quot; are
669 fabricated by the Roundup database layer.  By &quot;fabricated&quot; we
670 mean that no such properties are actually stored in the
671 hyperdatabase, but when properties on issues are requested, the
672 &quot;creation&quot; and &quot;activity&quot; properties are made available.
673 The value of the &quot;creation&quot; property is the date when an issue was
674 created, and the value of the &quot;activity&quot; property is the
675 date when any property on the issue was last edited (equivalently,
676 these are the dates on the first and last records in the issue's journal).</p>
677 </div>
678 <div class="section" id="roundupdb-interface-specification" name="roundupdb-interface-specification">
679 <h2><a class="toc-backref" href="#id18">Roundupdb Interface Specification</a></h2>
680 <p>The interface to a Roundup database delegates most method
681 calls to the hyperdatabase, except for the following
682 changes and additional methods:</p>
683 <pre class="literal-block">class Database:
684     def getuid(self):
685         &quot;&quot;&quot;Return the id of the &quot;user&quot; node associated with the user
686         that owns this connection to the hyperdatabase.&quot;&quot;&quot;
688 class Class:
689     # Overridden methods:
691     def create(self, **propvalues):
692     def set(self, **propvalues):
693     def retire(self, nodeid):
694         &quot;&quot;&quot;These operations trigger detectors and can be vetoed.  Attempts
695         to modify the &quot;creation&quot; or &quot;activity&quot; properties cause a KeyError.
696         &quot;&quot;&quot;
698     # New methods:
700     def audit(self, event, detector):
701     def react(self, event, detector):
702         &quot;&quot;&quot;Register a detector (see below for more details).&quot;&quot;&quot;
704 class IssueClass(Class):
705     # Overridden methods:
707     def __init__(self, db, classname, **properties):
708         &quot;&quot;&quot;The newly-created class automatically includes the &quot;messages&quot;,
709         &quot;files&quot;, &quot;nosy&quot;, and &quot;superseder&quot; properties.  If the 'properties'
710         dictionary attempts to specify any of these properties or a
711         &quot;creation&quot; or &quot;activity&quot; property, a ValueError is raised.&quot;&quot;&quot;
713     def get(self, nodeid, propname):
714     def getprops(self):
715         &quot;&quot;&quot;In addition to the actual properties on the node, these
716         methods provide the &quot;creation&quot; and &quot;activity&quot; properties.&quot;&quot;&quot;
718     # New methods:
720     def addmessage(self, nodeid, summary, text):
721         &quot;&quot;&quot;Add a message to an issue's mail spool.
723         A new &quot;msg&quot; node is constructed using the current date, the
724         user that owns the database connection as the author, and
725         the specified summary text.  The &quot;files&quot; and &quot;recipients&quot;
726         fields are left empty.  The given text is saved as the body
727         of the message and the node is appended to the &quot;messages&quot;
728         field of the specified issue.
729         &quot;&quot;&quot;
731     def sendmessage(self, nodeid, msgid):
732         &quot;&quot;&quot;Send a message to the members of an issue's nosy list.
734         The message is sent only to users on the nosy list who are not
735         already on the &quot;recipients&quot; list for the message.  These users
736         are then added to the message's &quot;recipients&quot; list.
737         &quot;&quot;&quot;</pre>
738 </div>
739 <div class="section" id="default-schema" name="default-schema">
740 <h2><a class="toc-backref" href="#id19">Default Schema</a></h2>
741 <p>The default schema included with Roundup turns it into a
742 typical software bug tracker.  The database is set up like this:</p>
743 <pre class="literal-block">pri = Class(db, &quot;priority&quot;, name=hyperdb.String(), order=hyperdb.String())
744 pri.setkey(&quot;name&quot;)
745 pri.create(name=&quot;critical&quot;, order=&quot;1&quot;)
746 pri.create(name=&quot;urgent&quot;, order=&quot;2&quot;)
747 pri.create(name=&quot;bug&quot;, order=&quot;3&quot;)
748 pri.create(name=&quot;feature&quot;, order=&quot;4&quot;)
749 pri.create(name=&quot;wish&quot;, order=&quot;5&quot;)
751 stat = Class(db, &quot;status&quot;, name=hyperdb.String(), order=hyperdb.String())
752 stat.setkey(&quot;name&quot;)
753 stat.create(name=&quot;unread&quot;, order=&quot;1&quot;)
754 stat.create(name=&quot;deferred&quot;, order=&quot;2&quot;)
755 stat.create(name=&quot;chatting&quot;, order=&quot;3&quot;)
756 stat.create(name=&quot;need-eg&quot;, order=&quot;4&quot;)
757 stat.create(name=&quot;in-progress&quot;, order=&quot;5&quot;)
758 stat.create(name=&quot;testing&quot;, order=&quot;6&quot;)
759 stat.create(name=&quot;done-cbb&quot;, order=&quot;7&quot;)
760 stat.create(name=&quot;resolved&quot;, order=&quot;8&quot;)
762 Class(db, &quot;keyword&quot;, name=hyperdb.String())
764 Class(db, &quot;issue&quot;, fixer=hyperdb.Multilink(&quot;user&quot;),
765                    topic=hyperdb.Multilink(&quot;keyword&quot;),
766                    priority=hyperdb.Link(&quot;priority&quot;),
767                    status=hyperdb.Link(&quot;status&quot;))</pre>
768 <p>(The &quot;order&quot; property hasn't been explained yet.  It
769 gets used by the Web user interface for sorting.)</p>
770 <p>The above isn't as pretty-looking as the schema specification
771 in the first-stage submission, but it could be made just as easy
772 with the addition of a convenience function like Choice
773 for setting up the &quot;priority&quot; and &quot;status&quot; classes:</p>
774 <pre class="literal-block">def Choice(name, *options):
775     cl = Class(db, name, name=hyperdb.String(), order=hyperdb.String())
776     for i in range(len(options)):
777         cl.create(name=option[i], order=i)
778     return hyperdb.Link(name)</pre>
779 </div>
780 </div>
781 <div class="section" id="detector-interface" name="detector-interface">
782 <h1><a class="toc-backref" href="#id20">Detector Interface</a></h1>
783 <p>Detectors are Python functions that are triggered on certain
784 kinds of events.  The definitions of the
785 functions live in Python modules placed in a directory set aside
786 for this purpose.  Importing the Roundup database module also
787 imports all the modules in this directory, and the <tt class="literal">init()</tt>
788 function of each module is called when a database is opened to
789 provide it a chance to register its detectors.</p>
790 <p>There are two kinds of detectors:</p>
791 <ol class="arabic simple">
792 <li>an auditor is triggered just before modifying an node</li>
793 <li>a reactor is triggered just after an node has been modified</li>
794 </ol>
795 <p>When the Roundup database is about to perform a
796 <tt class="literal">create()</tt>, <tt class="literal">set()</tt>, or <tt class="literal">retire()</tt>
797 operation, it first calls any <em>auditors</em> that
798 have been registered for that operation on that class.
799 Any auditor may raise a <em>Reject</em> exception
800 to abort the operation.</p>
801 <p>If none of the auditors raises an exception, the database
802 proceeds to carry out the operation.  After it's done, it
803 then calls all of the <em>reactors</em> that have been registered
804 for the operation.</p>
805 <div class="section" id="detector-interface-specification" name="detector-interface-specification">
806 <h2><a class="toc-backref" href="#id21">Detector Interface Specification</a></h2>
807 <p>The <tt class="literal">audit()</tt> and <tt class="literal">react()</tt> methods
808 register detectors on a given class of nodes:</p>
809 <pre class="literal-block">class Class:
810     def audit(self, event, detector):
811         &quot;&quot;&quot;Register an auditor on this class.
813         'event' should be one of &quot;create&quot;, &quot;set&quot;, or &quot;retire&quot;.
814         'detector' should be a function accepting four arguments.
815         &quot;&quot;&quot;
817     def react(self, event, detector):
818         &quot;&quot;&quot;Register a reactor on this class.
820         'event' should be one of &quot;create&quot;, &quot;set&quot;, or &quot;retire&quot;.
821         'detector' should be a function accepting four arguments.
822         &quot;&quot;&quot;</pre>
823 <p>Auditors are called with the arguments:</p>
824 <pre class="literal-block">audit(db, cl, nodeid, newdata)</pre>
825 <p>where <tt class="literal">db</tt> is the database, <tt class="literal">cl</tt> is an
826 instance of Class or IssueClass within the database, and <tt class="literal">newdata</tt>
827 is a dictionary mapping property names to values.</p>
828 <p>For a <tt class="literal">create()</tt>
829 operation, the <tt class="literal">nodeid</tt> argument is None and newdata
830 contains all of the initial property values with which the node
831 is about to be created.</p>
832 <p>For a <tt class="literal">set()</tt> operation, newdata
833 contains only the names and values of properties that are about
834 to be changed.</p>
835 <p>For a <tt class="literal">retire()</tt> operation, newdata is None.</p>
836 <p>Reactors are called with the arguments:</p>
837 <pre class="literal-block">react(db, cl, nodeid, olddata)</pre>
838 <p>where <tt class="literal">db</tt> is the database, <tt class="literal">cl</tt> is an
839 instance of Class or IssueClass within the database, and <tt class="literal">olddata</tt>
840 is a dictionary mapping property names to values.</p>
841 <p>For a <tt class="literal">create()</tt>
842 operation, the <tt class="literal">nodeid</tt> argument is the id of the
843 newly-created node and <tt class="literal">olddata</tt> is None.</p>
844 <p>For a <tt class="literal">set()</tt> operation, <tt class="literal">olddata</tt>
845 contains the names and previous values of properties that were changed.</p>
846 <p>For a <tt class="literal">retire()</tt> operation, <tt class="literal">nodeid</tt> is the
847 id of the retired node and <tt class="literal">olddata</tt> is None.</p>
848 </div>
849 <div class="section" id="detector-example" name="detector-example">
850 <h2><a class="toc-backref" href="#id22">Detector Example</a></h2>
851 <p>Here is an example of detectors written for a hypothetical
852 project-management application, where users can signal approval
853 of a project by adding themselves to an &quot;approvals&quot; list, and
854 a project proceeds when it has three approvals:</p>
855 <pre class="literal-block"># Permit users only to add themselves to the &quot;approvals&quot; list.
857 def check_approvals(db, cl, id, newdata):
858     if newdata.has_key(&quot;approvals&quot;):
859         if cl.get(id, &quot;status&quot;) == db.status.lookup(&quot;approved&quot;):
860             raise Reject, &quot;You can't modify the approvals list &quot; \
861                 &quot;for a project that has already been approved.&quot;
862         old = cl.get(id, &quot;approvals&quot;)
863         new = newdata[&quot;approvals&quot;]
864         for uid in old:
865             if uid not in new and uid != db.getuid():
866                 raise Reject, &quot;You can't remove other users from the &quot;
867                     &quot;approvals list; you can only remove yourself.&quot;
868         for uid in new:
869             if uid not in old and uid != db.getuid():
870                 raise Reject, &quot;You can't add other users to the approvals &quot;
871                     &quot;list; you can only add yourself.&quot;
873 # When three people have approved a project, change its
874 # status from &quot;pending&quot; to &quot;approved&quot;.
876 def approve_project(db, cl, id, olddata):
877     if olddata.has_key(&quot;approvals&quot;) and len(cl.get(id, &quot;approvals&quot;)) == 3:
878         if cl.get(id, &quot;status&quot;) == db.status.lookup(&quot;pending&quot;):
879             cl.set(id, status=db.status.lookup(&quot;approved&quot;))
881 def init(db):
882     db.project.audit(&quot;set&quot;, check_approval)
883     db.project.react(&quot;set&quot;, approve_project)</pre>
884 <p>Here is another example of a detector that can allow or prevent
885 the creation of new nodes.  In this scenario, patches for a software
886 project are submitted by sending in e-mail with an attached file,
887 and we want to ensure that there are text/plain attachments on
888 the message.  The maintainer of the package can then apply the
889 patch by setting its status to &quot;applied&quot;:</p>
890 <pre class="literal-block"># Only accept attempts to create new patches that come with patch files.
892 def check_new_patch(db, cl, id, newdata):
893     if not newdata[&quot;files&quot;]:
894         raise Reject, &quot;You can't submit a new patch without &quot; \
895                       &quot;attaching a patch file.&quot;
896     for fileid in newdata[&quot;files&quot;]:
897         if db.file.get(fileid, &quot;type&quot;) != &quot;text/plain&quot;:
898             raise Reject, &quot;Submitted patch files must be text/plain.&quot;
900 # When the status is changed from &quot;approved&quot; to &quot;applied&quot;, apply the patch.
902 def apply_patch(db, cl, id, olddata):
903     if cl.get(id, &quot;status&quot;) == db.status.lookup(&quot;applied&quot;) and \
904         olddata[&quot;status&quot;] == db.status.lookup(&quot;approved&quot;):
905         # ...apply the patch...
907 def init(db):
908     db.patch.audit(&quot;create&quot;, check_new_patch)
909     db.patch.react(&quot;set&quot;, apply_patch)</pre>
910 </div>
911 </div>
912 <div class="section" id="command-interface" name="command-interface">
913 <h1><a class="toc-backref" href="#id23">Command Interface</a></h1>
914 <p>The command interface is a very simple and minimal interface,
915 intended only for quick searches and checks from the shell prompt.
916 (Anything more interesting can simply be written in Python using
917 the Roundup database module.)</p>
918 <div class="section" id="command-interface-specification" name="command-interface-specification">
919 <h2><a class="toc-backref" href="#id24">Command Interface Specification</a></h2>
920 <p>A single command, roundup, provides basic access to
921 the hyperdatabase from the command line:</p>
922 <pre class="literal-block">roundup get [-list] designator[, designator,...] propname
923 roundup set designator[, designator,...] propname=value ...
924 roundup find [-list] classname propname=value ...</pre>
925 <p>TODO: more stuff here</p>
926 <p>Property values are represented as strings in command arguments
927 and in the printed results:</p>
928 <ul class="simple">
929 <li>Strings are, well, strings.</li>
930 <li>Numbers are displayed the same as strings.</li>
931 <li>Booleans are displayed as 'Yes' or 'No'.</li>
932 <li>Date values are printed in the full date format in the local
933 time zone, and accepted in the full format or any of the partial
934 formats explained above.</li>
935 <li>Link values are printed as node designators.  When given as
936 an argument, node designators and key strings are both accepted.</li>
937 <li>Multilink values are printed as lists of node designators
938 joined by commas.  When given as an argument, node designators
939 and key strings are both accepted; an empty string, a single node,
940 or a list of nodes joined by commas is accepted.</li>
941 </ul>
942 <p>When multiple nodes are specified to the
943 roundup get or roundup set
944 commands, the specified properties are retrieved or set
945 on all the listed nodes.</p>
946 <p>When multiple results are returned by the roundup get
947 or roundup find commands, they are printed one per
948 line (default) or joined by commas (with the -list) option.</p>
949 </div>
950 <div class="section" id="usage-example" name="usage-example">
951 <h2><a class="toc-backref" href="#id25">Usage Example</a></h2>
952 <p>To find all messages regarding in-progress issues that
953 contain the word &quot;spam&quot;, for example, you could execute the
954 following command from the directory where the database
955 dumps its files:</p>
956 <pre class="literal-block">shell% for issue in `roundup find issue status=in-progress`; do
957 &gt; grep -l spam `roundup get $issue messages`
958 &gt; done
959 msg23
960 msg49
961 msg50
962 msg61
963 shell%</pre>
964 <p>Or, using the -list option, this can be written as a single command:</p>
965 <pre class="literal-block">shell% grep -l spam `roundup get \
966     \`roundup find -list issue status=in-progress\` messages`
967 msg23
968 msg49
969 msg50
970 msg61
971 shell%</pre>
972 </div>
973 </div>
974 <div class="section" id="e-mail-user-interface" name="e-mail-user-interface">
975 <h1><a class="toc-backref" href="#id26">E-mail User Interface</a></h1>
976 <p>The Roundup system must be assigned an e-mail address
977 at which to receive mail.  Messages should be piped to
978 the Roundup mail-handling script by the mail delivery
979 system (e.g. using an alias beginning with &quot;|&quot; for sendmail).</p>
980 <div class="section" id="message-processing" name="message-processing">
981 <h2><a class="toc-backref" href="#id27">Message Processing</a></h2>
982 <p>Incoming messages are examined for multiple parts.
983 In a multipart/mixed message or part, each subpart is
984 extracted and examined.  In a multipart/alternative
985 message or part, we look for a text/plain subpart and
986 ignore the other parts.  The text/plain subparts are
987 assembled to form the textual body of the message, to
988 be stored in the file associated with a &quot;msg&quot; class node.
989 Any parts of other types are each stored in separate
990 files and given &quot;file&quot; class nodes that are linked to
991 the &quot;msg&quot; node.</p>
992 <p>The &quot;summary&quot; property on message nodes is taken from
993 the first non-quoting section in the message body.
994 The message body is divided into sections by blank lines.
995 Sections where the second and all subsequent lines begin
996 with a &quot;&gt;&quot; or &quot;|&quot; character are considered &quot;quoting
997 sections&quot;.  The first line of the first non-quoting 
998 section becomes the summary of the message.</p>
999 <p>All of the addresses in the To: and Cc: headers of the
1000 incoming message are looked up among the user nodes, and
1001 the corresponding users are placed in the &quot;recipients&quot;
1002 property on the new &quot;msg&quot; node.  The address in the From:
1003 header similarly determines the &quot;author&quot; property of the
1004 new &quot;msg&quot; node.
1005 The default handling for
1006 addresses that don't have corresponding users is to create
1007 new users with no passwords and a username equal to the
1008 address.  (The web interface does not permit logins for
1009 users with no passwords.)  If we prefer to reject mail from
1010 outside sources, we can simply register an auditor on the
1011 &quot;user&quot; class that prevents the creation of user nodes with
1012 no passwords.</p>
1013 <p>The subject line of the incoming message is examined to
1014 determine whether the message is an attempt to create a new
1015 issue or to discuss an existing issue.  A designator enclosed
1016 in square brackets is sought as the first thing on the
1017 subject line (after skipping any &quot;Fwd:&quot; or &quot;Re:&quot; prefixes).</p>
1018 <p>If an issue designator (class name and id number) is found
1019 there, the newly created &quot;msg&quot; node is added to the &quot;messages&quot;
1020 property for that issue, and any new &quot;file&quot; nodes are added to
1021 the &quot;files&quot; property for the issue.</p>
1022 <p>If just an issue class name is found there, we attempt to
1023 create a new issue of that class with its &quot;messages&quot; property
1024 initialized to contain the new &quot;msg&quot; node and its &quot;files&quot;
1025 property initialized to contain any new &quot;file&quot; nodes.</p>
1026 <p>Both cases may trigger detectors (in the first case we
1027 are calling the set() method to add the message to the
1028 issue's spool; in the second case we are calling the
1029 create() method to create a new node).  If an auditor
1030 raises an exception, the original message is bounced back to
1031 the sender with the explanatory message given in the exception.</p>
1032 </div>
1033 <div class="section" id="nosy-lists" name="nosy-lists">
1034 <h2><a class="toc-backref" href="#id28">Nosy Lists</a></h2>
1035 <p>A standard detector is provided that watches for additions
1036 to the &quot;messages&quot; property.  When a new message is added, the
1037 detector sends it to all the users on the &quot;nosy&quot; list for the
1038 issue that are not already on the &quot;recipients&quot; list of the
1039 message.  Those users are then appended to the &quot;recipients&quot;
1040 property on the message, so multiple copies of a message
1041 are never sent to the same user.  The journal recorded by
1042 the hyperdatabase on the &quot;recipients&quot; property then provides
1043 a log of when the message was sent to whom.</p>
1044 </div>
1045 <div class="section" id="setting-properties" name="setting-properties">
1046 <h2><a class="toc-backref" href="#id29">Setting Properties</a></h2>
1047 <p>The e-mail interface also provides a simple way to set
1048 properties on issues.  At the end of the subject line,
1049 <tt class="literal">propname=value</tt> pairs can be
1050 specified in square brackets, using the same conventions
1051 as for the roundup <tt class="literal">set</tt> shell command.</p>
1052 </div>
1053 </div>
1054 <div class="section" id="web-user-interface" name="web-user-interface">
1055 <h1><a class="toc-backref" href="#id30">Web User Interface</a></h1>
1056 <p>The web interface is provided by a CGI script that can be
1057 run under any web server.  A simple web server can easily be
1058 built on the standard CGIHTTPServer module, and
1059 should also be included in the distribution for quick
1060 out-of-the-box deployment.</p>
1061 <p>The user interface is constructed from a number of template
1062 files containing mostly HTML.  Among the HTML tags in templates
1063 are interspersed some nonstandard tags, which we use as
1064 placeholders to be replaced by properties and their values.</p>
1065 <div class="section" id="views-and-view-specifiers" name="views-and-view-specifiers">
1066 <h2><a class="toc-backref" href="#id31">Views and View Specifiers</a></h2>
1067 <p>There are two main kinds of views: <em>index</em> views and <em>issue</em> views.
1068 An index view displays a list of issues of a particular class,
1069 optionally sorted and filtered as requested.  An issue view
1070 presents the properties of a particular issue for editing
1071 and displays the message spool for the issue.</p>
1072 <p>A view specifier is a string that specifies
1073 all the options needed to construct a particular view.
1074 It goes after the URL to the Roundup CGI script or the
1075 web server to form the complete URL to a view.  When the
1076 result of selecting a link or submitting a form takes
1077 the user to a new view, the Web browser should be redirected
1078 to a canonical location containing a complete view specifier
1079 so that the view can be bookmarked.</p>
1080 </div>
1081 <div class="section" id="displaying-properties" name="displaying-properties">
1082 <h2><a class="toc-backref" href="#id32">Displaying Properties</a></h2>
1083 <p>Properties appear in the user interface in three contexts:
1084 in indices, in editors, and as filters.  For each type of
1085 property, there are several display possibilities.  For example,
1086 in an index view, a string property may just be printed as
1087 a plain string, but in an editor view, that property should
1088 be displayed in an editable field.</p>
1089 <p>The display of a property is handled by functions in
1090 a displayers module.  Each function accepts at
1091 least three standard arguments -- the database, class name,
1092 and node id -- and returns a chunk of HTML.</p>
1093 <p>Displayer functions are triggered by &lt;display&gt;
1094 tags in templates.  The call attribute of the tag
1095 provides a Python expression for calling the displayer
1096 function.  The three standard arguments are inserted in
1097 front of the arguments given.  For example, the occurrence of:</p>
1098 <pre class="literal-block">&lt;display call=&quot;plain('status', max=30)&quot;&gt;</pre>
1099 <p>in a template triggers a call to:</p>
1100 <pre class="literal-block">plain(db, &quot;issue&quot;, 13, &quot;status&quot;, max=30)</pre>
1101 <p>when displaying issue 13 in the &quot;issue&quot; class.  The displayer
1102 functions can accept extra arguments to further specify
1103 details about the widgets that should be generated.  By defining new
1104 displayer functions, the user interface can be highly customized.</p>
1105 <p>Some of the standard displayer functions include:</p>
1106 <table frame="border" rules="all">
1107 <colgroup>
1108 <col colwidth="12%" />
1109 <col colwidth="88%" />
1110 </colgroup>
1111 <thead valign="bottom">
1112 <tr><th>Function</th>
1113 <th>Description</th>
1114 </tr>
1115 </thead>
1116 <tbody valign="top">
1117 <tr><td>plain</td>
1118 <td>display a String property directly;
1119 display a Date property in a specified time zone with an option
1120 to omit the time from the date stamp; for a Link or Multilink
1121 property, display the key strings of the linked nodes (or the
1122 ids if the linked class has no key property)</td>
1123 </tr>
1124 <tr><td>field</td>
1125 <td>display a property like the
1126 plain displayer above, but in a text field
1127 to be edited</td>
1128 </tr>
1129 <tr><td>menu</td>
1130 <td>for a Link property, display
1131 a menu of the available choices</td>
1132 </tr>
1133 <tr><td>link</td>
1134 <td>for a Link or Multilink property,
1135 display the names of the linked nodes, hyperlinked to the
1136 issue views on those nodes</td>
1137 </tr>
1138 <tr><td>count</td>
1139 <td>for a Multilink property, display
1140 a count of the number of links in the list</td>
1141 </tr>
1142 <tr><td>reldate</td>
1143 <td>display a Date property in terms
1144 of an interval relative to the current date (e.g. &quot;+ 3w&quot;, &quot;- 2d&quot;).</td>
1145 </tr>
1146 <tr><td>download</td>
1147 <td>show a Link(&quot;file&quot;) or Multilink(&quot;file&quot;)
1148 property using links that allow you to download files</td>
1149 </tr>
1150 <tr><td>checklist</td>
1151 <td>for a Link or Multilink property,
1152 display checkboxes for the available choices to permit filtering</td>
1153 </tr>
1154 </tbody>
1155 </table>
1156 </div>
1157 <div class="section" id="index-views" name="index-views">
1158 <h2><a class="toc-backref" href="#id33">Index Views</a></h2>
1159 <p>An index view contains two sections: a filter section
1160 and an index section.
1161 The filter section provides some widgets for selecting
1162 which issues appear in the index.  The index section is
1163 a table of issues.</p>
1164 <div class="section" id="index-view-specifiers" name="index-view-specifiers">
1165 <h3><a class="toc-backref" href="#id34">Index View Specifiers</a></h3>
1166 <p>An index view specifier looks like this (whitespace
1167 has been added for clarity):</p>
1168 <pre class="literal-block">/issue?status=unread,in-progress,resolved&amp;amp;
1169     topic=security,ui&amp;amp;
1170     :group=+priority&amp;amp;
1171     :sort=-activity&amp;amp;
1172     :filters=status,topic&amp;amp;
1173     :columns=title,status,fixer</pre>
1174 <p>The index view is determined by two parts of the
1175 specifier: the layout part and the filter part.
1176 The layout part consists of the query parameters that
1177 begin with colons, and it determines the way that the
1178 properties of selected nodes are displayed.
1179 The filter part consists of all the other query parameters,
1180 and it determines the criteria by which nodes 
1181 are selected for display.</p>
1182 <p>The filter part is interactively manipulated with
1183 the form widgets displayed in the filter section.  The
1184 layout part is interactively manipulated by clicking
1185 on the column headings in the table.</p>
1186 <p>The filter part selects the union of the
1187 sets of issues with values matching any specified Link
1188 properties and the intersection of the sets
1189 of issues with values matching any specified Multilink
1190 properties.</p>
1191 <p>The example specifies an index of &quot;issue&quot; nodes.
1192 Only issues with a &quot;status&quot; of either
1193 &quot;unread&quot; or &quot;in-progres&quot; or &quot;resolved&quot; are displayed,
1194 and only issues with &quot;topic&quot; values including both
1195 &quot;security&quot; and &quot;ui&quot; are displayed.  The issues
1196 are grouped by priority, arranged in ascending order;
1197 and within groups, sorted by activity, arranged in
1198 descending order.  The filter section shows filters
1199 for the &quot;status&quot; and &quot;topic&quot; properties, and the
1200 table includes columns for the &quot;title&quot;, &quot;status&quot;, and
1201 &quot;fixer&quot; properties.</p>
1202 <p>Associated with each issue class is a default
1203 layout specifier.  The layout specifier in the above
1204 example is the default layout to be provided with
1205 the default bug-tracker schema described above in
1206 section 4.4.</p>
1207 </div>
1208 <div class="section" id="filter-section" name="filter-section">
1209 <h3><a class="toc-backref" href="#id35">Filter Section</a></h3>
1210 <p>The template for a filter section provides the
1211 filtering widgets at the top of the index view.
1212 Fragments enclosed in <tt class="literal">&lt;property&gt;...&lt;/property&gt;</tt>
1213 tags are included or omitted depending on whether the
1214 view specifier requests a filter for a particular property.</p>
1215 <p>Here's a simple example of a filter template:</p>
1216 <pre class="literal-block">&lt;property name=status&gt;
1217     &lt;display call=&quot;checklist('status')&quot;&gt;
1218 &lt;/property&gt;
1219 &lt;br&gt;
1220 &lt;property name=priority&gt;
1221     &lt;display call=&quot;checklist('priority')&quot;&gt;
1222 &lt;/property&gt;
1223 &lt;br&gt;
1224 &lt;property name=fixer&gt;
1225     &lt;display call=&quot;menu('fixer')&quot;&gt;
1226 &lt;/property&gt;</pre>
1227 </div>
1228 <div class="section" id="index-section" name="index-section">
1229 <h3><a class="toc-backref" href="#id36">Index Section</a></h3>
1230 <p>The template for an index section describes one row of
1231 the index table.
1232 Fragments enclosed in <tt class="literal">&lt;property&gt;...&lt;/property&gt;</tt>
1233 tags are included or omitted depending on whether the
1234 view specifier requests a column for a particular property.
1235 The table cells should contain &lt;display&gt; tags
1236 to display the values of the issue's properties.</p>
1237 <p>Here's a simple example of an index template:</p>
1238 <pre class="literal-block">&lt;tr&gt;
1239     &lt;property name=title&gt;
1240         &lt;td&gt;&lt;display call=&quot;plain('title', max=50)&quot;&gt;&lt;/td&gt;
1241     &lt;/property&gt;
1242     &lt;property name=status&gt;
1243         &lt;td&gt;&lt;display call=&quot;plain('status')&quot;&gt;&lt;/td&gt;
1244     &lt;/property&gt;
1245     &lt;property name=fixer&gt;
1246         &lt;td&gt;&lt;display call=&quot;plain('fixer')&quot;&gt;&lt;/td&gt;
1247     &lt;/property&gt;
1248 &lt;/tr&gt;</pre>
1249 </div>
1250 <div class="section" id="sorting" name="sorting">
1251 <h3><a class="toc-backref" href="#id37">Sorting</a></h3>
1252 <p>String and Date values are sorted in the natural way.
1253 Link properties are sorted according to the value of the
1254 &quot;order&quot; property on the linked nodes if it is present; or
1255 otherwise on the key string of the linked nodes; or
1256 finally on the node ids.  Multilink properties are
1257 sorted according to how many links are present.</p>
1258 </div>
1259 </div>
1260 <div class="section" id="issue-views" name="issue-views">
1261 <h2><a class="toc-backref" href="#id38">Issue Views</a></h2>
1262 <p>An issue view contains an editor section and a spool section.
1263 At the top of an issue view, links to superseding and superseded
1264 issues are always displayed.</p>
1265 <div class="section" id="issue-view-specifiers" name="issue-view-specifiers">
1266 <h3><a class="toc-backref" href="#id39">Issue View Specifiers</a></h3>
1267 <p>An issue view specifier is simply the issue's designator:</p>
1268 <pre class="literal-block">/patch23</pre>
1269 </div>
1270 <div class="section" id="editor-section" name="editor-section">
1271 <h3><a class="toc-backref" href="#id40">Editor Section</a></h3>
1272 <p>The editor section is generated from a template
1273 containing &lt;display&gt; tags to insert
1274 the appropriate widgets for editing properties.</p>
1275 <p>Here's an example of a basic editor template:</p>
1276 <pre class="literal-block">&lt;table&gt;
1277 &lt;tr&gt;
1278     &lt;td colspan=2&gt;
1279         &lt;display call=&quot;field('title', size=60)&quot;&gt;
1280     &lt;/td&gt;
1281 &lt;/tr&gt;
1282 &lt;tr&gt;
1283     &lt;td&gt;
1284         &lt;display call=&quot;field('fixer', size=30)&quot;&gt;
1285     &lt;/td&gt;
1286     &lt;td&gt;
1287         &lt;display call=&quot;menu('status')&gt;
1288     &lt;/td&gt;
1289 &lt;/tr&gt;
1290 &lt;tr&gt;
1291     &lt;td&gt;
1292         &lt;display call=&quot;field('nosy', size=30)&quot;&gt;
1293     &lt;/td&gt;
1294     &lt;td&gt;
1295         &lt;display call=&quot;menu('priority')&gt;
1296     &lt;/td&gt;
1297 &lt;/tr&gt;
1298 &lt;tr&gt;
1299     &lt;td colspan=2&gt;
1300         &lt;display call=&quot;note()&quot;&gt;
1301     &lt;/td&gt;
1302 &lt;/tr&gt;
1303 &lt;/table&gt;</pre>
1304 <p>As shown in the example, the editor template can also
1305 request the display of a &quot;note&quot; field, which is a
1306 text area for entering a note to go along with a change.</p>
1307 <p>When a change is submitted, the system automatically
1308 generates a message describing the changed properties.
1309 The message displays all of the property values on the
1310 issue and indicates which ones have changed.
1311 An example of such a message might be this:</p>
1312 <pre class="literal-block">title: Polly Parrot is dead
1313 priority: critical
1314 status: unread -&gt; in-progress
1315 fixer: (none)
1316 keywords: parrot,plumage,perch,nailed,dead</pre>
1317 <p>If a note is given in the &quot;note&quot; field, the note is
1318 appended to the description.  The message is then added
1319 to the issue's message spool (thus triggering the standard
1320 detector to react by sending out this message to the nosy list).</p>
1321 </div>
1322 <div class="section" id="spool-section" name="spool-section">
1323 <h3><a class="toc-backref" href="#id41">Spool Section</a></h3>
1324 <p>The spool section lists messages in the issue's &quot;messages&quot;
1325 property.  The index of messages displays the &quot;date&quot;, &quot;author&quot;,
1326 and &quot;summary&quot; properties on the message nodes, and selecting a
1327 message takes you to its content.</p>
1328 </div>
1329 </div>
1330 </div>
1331 <div class="section" id="deployment-scenarios" name="deployment-scenarios">
1332 <h1><a class="toc-backref" href="#id42">Deployment Scenarios</a></h1>
1333 <p>The design described above should be general enough
1334 to permit the use of Roundup for bug tracking, managing
1335 projects, managing patches, or holding discussions.  By
1336 using nodes of multiple types, one could deploy a system
1337 that maintains requirement specifications, catalogs bugs,
1338 and manages submitted patches, where patches could be
1339 linked to the bugs and requirements they address.</p>
1340 </div>
1341 <div class="section" id="acknowledgements" name="acknowledgements">
1342 <h1><a class="toc-backref" href="#id43">Acknowledgements</a></h1>
1343 <p>My thanks are due to Christy Heyl for 
1344 reviewing and contributing suggestions to this paper
1345 and motivating me to get it done, and to
1346 Jesse Vincent, Mark Miller, Christopher Simons,
1347 Jeff Dunmall, Wayne Gramlich, and Dean Tribble for
1348 their assistance with the first-round submission.</p>
1349 </div>
1350 <div class="section" id="changes-to-this-document" name="changes-to-this-document">
1351 <h1><a class="toc-backref" href="#id44">Changes to this document</a></h1>
1352 <ul class="simple">
1353 <li>Added Boolean and Number types</li>
1354 <li>Added section Hyperdatabase Implementations</li>
1355 <li>&quot;Item&quot; has been renamed to &quot;Issue&quot; to account for the more specific nature
1356 of the Class.</li>
1357 </ul>
1358 </div>
1359 </div>
1360 <hr class="footer"/>
1361 <div class="footer">
1362 Generated on: 2002-07-29. </div>
1363 </body>
1364 </html>