đŸ Archived View for tilde.pink âș ~ssb22 âș 4dmlman.gmi captured on 2024-02-05 at 10:27:14. Gemini links have been rewritten to link to archived content
âŹ ïž Previous capture (2023-01-29)
-=-=-=-=-=-=-
Silas S. Brown 26 October 2006
1. Introduction
2. Invocation 2.1. Specifying the input 2.1.1. -input (Read XML)
2.1.2. -spaninput (Read XML with COLSPAN/ROWSPAN)
2.1.3. -inpspec (Read XML with overlaps)
2.1.4. -spaninpspec (Read XML with colspan/rowspan and overlaps)
2.1.5. -minput (Read MML)
2.1.6. -link (Add links to input)
2.2. Specifying the model 2.2.1. -model (XML model)
2.2.2. -cmod (CML on command line)
2.2.3. -cmodel (Embedded CML)
2.2.4. -pmodel (Python module)
2.2.5. -python (Auxiliary Python module)
2.2.6. Model as part of the input
2.3. Specifying the function 2.3.1. -transform (Perform transform)
2.3.2. -gui (Run GUI)
2.3.3. -guidebug (Run GUI with call trace)
2.4. Output
2.5. Improving the speed
2.6. Internationalisation and support for many characters
3. Input in XML 3.1. Special attributes for overlapping markup
4. Input in MML (Matrix Markup Language) 4.1. MML basics
4.2. Blocks 4.2.1. Initial element name
4.2.2. have ... as
4.2.3. also
4.2.4. special
4.3. Emacs major mode
5. Model basics
6. The Compact Model Language (CML)
7. Models as Python Modules 7.1. Syntax
7.2. Filter methods
7.3. Using hash-bang to execute models
8. Model Parameters 8.1. Changing the markup (XML) 8.1.1. rename
8.1.2. from
8.1.3. allow-empty
8.1.4. no-input
8.1.5. _
8.2. Changing the markup (not XML) 8.2.1. nomarkup
8.2.2. before
8.2.3. after
8.2.4. between
8.2.5. group
8.2.6. group-size
8.2.7. begin
8.2.8. end
8.3. Adjusting the choice of elements processed 8.3.1. start-at
8.3.2. end-at
8.3.3. number
8.3.4. renumber
8.3.5. value
8.3.6. total
8.3.7. other-values
8.3.8. external
8.3.9. broaden
8.4. Changing the behaviour of child-element processing 8.4.1. children-only
8.4.2. sequential
8.4.3. flatten
8.4.4. no-strip
8.4.5. merge
8.4.6. include-rest
8.4.7. reverse
8.5. Recursion and subroutines 8.5.1. call
8.5.2. export-code
8.6. Numbering and alternative markup 8.6.1. count
8.6.2. if-changed
8.7. Error checking 8.7.1. expected
8.8. Miscellaneous 8.8.1. wildcard
8.8.2. debugdump
8.8.3. python
9. Author
The 4DML transformation utility can be used for the rapid prototyping of complex structural transformations on data.
4DML is a data structure that can represent both tree-like and matrix-like structures in any number of dimensions and supports multiple independent structures over the same data.âThe transformation utility converts data into 4DML and then reads it out in the order dictated by a âmodelâ, which shows the desired structure of the output.
This describes the invocation and command-line arguments of the 4DML transformation utility.
Read the input from an XML file.âThe next argument specifies the input filename; a filename of - represents the standard input.
Read the input from an XML file, recognising ROWSPAN and COLSPAN attributes according to the semantics of HTML tables.âPosition numbers are adjusted so that the position number of an element reflects which column it begins in, taking ROWSPAN and COLSPAN into account.âThe next argument specifies the input filename; a filename of - represents the standard input.
Read the input from an XML file, but recognise the special attributes for overlapping markup.âThe next argument specifies the input filename; a filename of - represents the standard input.
This option is a combination of -spaninput and -inpspec.
Read the input from an MML (Matrix Markup Language) file.âThe next argument specifies the input filename; a filename of - represents the standard input.
The Unix installation script also sets up a command âmml2xmlâ, to convert MML (on standard input) into XML.
Automatically adds âlinkâ elements that join together any two or more strings that happen to be the same.âThese elements are independent from the rest of the markup and can be ignored if not needed.âFor a description of why they might be useful, see the documentation for the âbroadenâ model parameter (under âModel parametersâ and âAdjusting the choice of elements processedâ).
The link elements will be named according to what is specified here, so you can say â-link autolinkâ if you want them to be called âautolinkâ, or â-link linkâ if you want them to be called âlinkâ.âChoose something that does not conflict with the other names you are using.
If the model doesnât need this parameter (and if you wrote it then you should know if it does), specifying this parameter should be harmless, but it slows the program down and takes more memory.
Read the model from an XML file.âThe next argument specifies the input filename; a filename of - represents the standard input.
The next argument specifies the model in CML (Compact Model Language) as a string.
Read the model from a text file with embedded CML (Compact Model Language).âThe next argument specifies the filename; a filename of - represents the standard input.
The Unix installation script also sets up a command âcml2xmlâ, to convert embedded CML (on standard input) into XML.
Load a Python module for introspection, and derive the model from that.âThe next argument specifies the Python module to load.âA full pathname is also acceptable.
Load a Python module, but do not introspect it; the functions in the module are accessible from the âpythonâ model parameter.âThe model itself should be loaded from another format (such as CML).
If no model is specified on the command line then it is looked for in the input, under an element called âmodelâ (the operation âmodel number=1â will be executed to extract it).âThe model is assumed to be in CML regardless of the input format.âThe element âmodelâ is not removed from the input.
Perform a model transform and output the result.âThis is the default.
Run the GUI instead of performing any transform at the command line.
Run the GUI, and perform a âCapture call treeâ operation to show the details of how the model was interpreted.
Output is always written to the standard output.âYou can use the shell to redirect it to a file or to pipe it to another command.
If the problem is that you are running out of RAM, then try setting the no_psyco environment variable before running 4DML.âThis suppresses the use of Psyco (an optimiser which can often take lots of RAM) and also suppresses the use of partial-result caches (these caches save time provided that enough RAM is available, but if RAM is in short supply, or you are transforming a very large dataset, then itâs best not to have them).
The Unix installation script installs other commands besides 4dml, including:
Additionally, it is possible to instruct the 4DML utility to send its fourspace to a faster computer (via SSH or similar) and perform processing on that.âThis has not yet been documented.
The 4DML transformation utility assumes by default that its input is encoded in UTF-8. Output is also in UTF-8. If necessary, a utility such as tcs can be used to translate between this and other encodings (such as GB2312, Big5, JIS, etc).
Each element in the XML is given a depth number and a position number.âThe top-level element is at depth 1, and position numbers begin again at 1 with each level of recursion.âFor example, in the markup
<TR><TD/><TD/></TR> <TR><TD/><TD/></TR>
position numbers are assigned as follows:
<TR p=1><TD p=1/><TD p=2/></TR> <TR p=2><TD p=1/><TD p=2/></TR>
Hence, a âfor each TDâ operation, if not enclosed by âfor each TRâ, will cut across the TR elements when grouping the TD elements together.
These are not recognised unless specifically asked for on the command line.
The attribute â_InitPosâ sets the position number of this element, overriding the position number that would have been given to it by default.
Elements that have the special attributes described below should be empty.âThe name of the element should correspond with the name of a higher-level element that is in progress at that point, and the attributes specify directives for manipulating the position number of that higher-level element, while it is in progress.âHence the higher-level element can be divided into two without interrupting the intervening markup.âThe element with special attributes does not itself appear in the 4DML.
The attribute â_IncrementPosâ causes the position number of the higher-level element to be incremented.
The attribute â_SetPosâ causes the position number of the higher-level element to be set to the given value.
The attribute â_Renameâ changes the name of the higher-level element to the one given.âThis should not normally be used.
The attribute â_Unfinishedâ signifies that we are not through with adjusting positioning information.âThis means that, if there is another element following with no intervening character data, then no empty element will be recorded to note the new position number at this point.âIf you wish to change the position numbers of several higher-level elements in one operation then it is generally a good idea to use the â_Unfinishedâ attribute so as to prevent an inconsistent state being recorded.
Matrix Markup Language (MML) is a text-based language that can represent structure in several ways, such as by using âbeginâ/âendâ pairs, âelement: valueâ lines, and matrix-like blocks.âIt is meant to be a convenient way of hand-coding data that is both hierarchical and matrix-like in structure.
âbegin eâ and âend eâ cause everything in between to be enclosed in an element named âeâ. An alternative way of representing this is âe:â followed by some text; the rest of the line is treated as the contents of element âeâ. The construct âadvance eâ will cause the position of âeâ (an element currently in progress) to be advanced, even if it is not the innermost element that is currently in progress.
Files in MML are enclosed in an implicit âdocumentâ element.
Note: When using âe:â followed by some text, the rest of the line should be non-empty, otherwise the next non-empty line will be used instead.â(This can be useful when working with very narrow displays.)
An MML matrix-like block is enclosed within â!blockâ and â!endblockâ.âEach block has two parts, a header and a body.âThe first blank line separates the header from the body.âThe header specifies the meaning of the separation symbols that are to be used in the body.âThe following constructs can appear in the header:
The first word after a â!blockâ can be the name of an element in which the whole block is to be contained, so long as this is not a reserved word (if it is a reserved word then you have to write a âbeginâ/âendâ construct around the block instead).
The construct âhave A as Bâ causes the symbol A to be used as a separator, advancing the position number of an element called B.
If there are several âhave...asâ constructs, then they are given increasing depth numbers, and an advancement of a higher-level position number (i.e. lower depth number) causes all positions at lower levels to be reset to 1.
As a special case, the keyword âparagraphâ matches one or more blank lines; the keyword ânewlineâ matches a single newline character, and the keyword âwhitespaceâ matches any amount of whitespace.âIf they are all used, paragraph, newline and whitespace should be specified in that order, because a new paragraph can be interpreted as new lines, which in turn can be interpreted as whitespace.
The keyword âcharacterâ causes the input to be split into individual characters (like setting the separator to the empty string); use this with care.
If desired, multiple âhave...asâ constructs can be amalgamated thus: âhave A1 A2 A3... as B1 B2 B3...â
The word âalsoâ can be used to separate independent groups of âhave...asâ constructs.âChanges of positions in one group will not change the positions in the others.âHence the blockâs body can represent multiple, independent hierarchies over the same data.
This can be used to specify special ways of interpreting some operators.âCurrently, it can be used as follows:
special: A maximum 5 per B
Here, it is specified that there are at most 5 elements called A; effectively, all the Aâs are grouped together into Bâs, with a maximum of 5 per group.
special: A label 4 as B
Specifies that the 4th instance of element A should really be called B (rather than A). Can be useful if you need to define an operator whose parameters are completely different things (although this should be rare).
special: A switches B
Here the operator A will switch the element B to an arbitrary position.âThe position is determined by a mnemonic that occurs after A, before the next space.
For example, if A is / and B is âprojectâ, then /myprj (followed by whitespace) will cause the following data to be under a âprojectâ element with position given by the mnemonic âmyprjâ (the textual mnemonics that you use are automatically given numbers).
This can be a useful shortcut if you are organising a large amount of unsorted data into several categories.
special: A switches.
(i.e. a dot after âswitchesâ) is the same as above, except that the B is defined as the same as the mnemonic.âIn other words, immediately after you use the operator A, you will specify the name of the element that the following text is to appear under, followed by whitespace.
A major mode is available for editing MML files with emacs and xemacs.âThis is supplied with 4dml as 4dml.el (will be compiled to 4dml.elc if emacs is available at install time).âTo install it, you need to put something like this in your emacs init file (remembering to substitute the correct installation directory if necessary):
(autoload '4dml-mml-mode "/usr/local/lib/site-python/4dml/4dml.elc" "Major mode for editing 4DML MML files" t) (setq auto-mode-alist (append '(("\\.mml\\'" . 4dml-mml-mode)) auto-mode-alist))
The mode should syntax highlight reasonably correctly when loading a file or doing font-lock-fontify-buffer, but might not be so good when actually editing, especially when editing !block constructs, since font-lock only considers one line at a time. c.f. HTML mode problems in comments etc.âIf in doubt, do M-x font-lock-fontify-buffer.
By default, each element in the model will cause the following things to occur.
1. The input is searched for all elements that match the name of the model element.âOnly such elements at the highest level at which they occur will be used.âThe search will cut across all other markup.
2. The input is divided into groups, one for each distinct element that was found, and the groups are sorted by position number.âAny other markup from the input will be included in each group.
3. Any model code that occurs within the current model element will be executed once for each group.
4. If the model element is empty (a leaf node), then the data from each group is copied to the output, discarding all remaining markup.
Thus an element X in the model is effectively saying âfor each Xâ.
Arbitrary text in the model is copied to the output whenever it is encountered.
Model elements can be given attributes (parameters) to specify their behaviour in a more flexible manner; see the section on Model Parameters.
CML is a compact markup language for models, which often have elements packed inside each other (i.e. of the form <x><y><z>...). CML makes that kind of hierarchy quicker to type.
Use X/Y/Z to put an element Z within a Y, within an X.
Any element name can be followed by attributes, with whitespace before each attribute.âAttributes take the form name [ =value ] where the text enclosed in [] is optional (value defaults to 1; this is intended for use when the attribute merely has to be present).âValues should be quoted with â...â if they contain any unusual characters.
Use a comma (,) to separate two or more elements that are at the same level.âThis has a lower precedence than / but you can use parentheses.
Arbitrary text can appear in quotes in place of an element.âIt can be enclosed within ]] and [[cml, which imply commas either side of them unless they are being used to specify the value of a parameter; in this case the text can span multiple lines and include any character such as quotes.âWhen CML is embedded in a text file, the whole file is automatically enclosed within those operators, so the effect is that the CML parts of the file are delineated by [[cml and ]].
Models in CML are enclosed in an implicit âdocumentâ element, which has implicit attributes ânomarkupâ and âno-inputâ.
Models can be specified as Python modules that are examined by introspection.âThis makes it possible to embed arbitrary Python code within 4DML models.
The model will be derived from the Python module by introspection.âA class in the Python module corresponds to an element in the model, and this class can have other classes as members (inner classes) to signify child elements.âAttributes are specified as variables within the class.âFor example:
class paragraph: between = "\\par " class word: between = " "
Any underlines (_) in attributes are changed into hyphens (-).
If necessary, an attribute ârealnameâ can specify the real name of the model element, should the class have to be named something else (such as to avoid duplication).âIf there are several classes (elements) at the same level, then their order may be specified in a list, such as
order = [ class1, class2, .. ]
after the classes have been defined.âSome kind of order specification is necessary because Python classes are dictionaries; the items are not necessarily stored in the order in which they appear in the source code.
If (and only if) the Python module specifies more than one top-level element, then it is enclosed in an implicit âdocumentâ element.
These can be used to code arbitrary low-level filters for the data, to be applied during the model transform.
If a class has a method called âfilterâ, and it is a leaf node in the model (i.e. it causes data to be copied to the output), then the class will be instantiated, and the âfilterâ method will be called for each string that is to be output (by that particular leaf node).âThe âfilterâ method is expected to take the string as an argument and to return a replacement string.
In Unix-based systems, you can use the âhash-bangâ feature of the kernel to make the Python-based models executable directly.âSome kernels have problems running 4DML with hash-bang (because 4DML is itself a script), but you can use a hack such as:
#!/bin/bash "eval" "exec 4dml -pmodel $0"
Other arguments can be added if desired.
These parameters can be added as attributes to any element of a model, to change the behaviour of the model processor with respect to that element or its children.
Specifies a new name for the element, to be used in the XML output.âThis is useful if the nomenclature is different (e.g. <BAR rename=âMEASUREâ>). Element names that are empty (rename=ââ) are accepted; when converting to XML, no markup is output around such elements (an alias for ârename=âââ is âdiscardâ).
This is an alternative to ârenameâ; it specifies the old name of the element.âFor example, <BAR rename=âMEASUREâ> is equivalent to <MEASURE from=âBARâ>. You might want to use this if you find it easier to specify the desired structure in the element names.
Note that if you use both âfromâ and ârenameâ, the name of the element itself becomes meaningless.
Allows the element to be empty in the output.âNormally the element is only included if there is some data in it.âIf âallow-emptyâ is set then the element will appear in the output whether or not there is data available (although it will not appear if there is a model within it and that model does not cause any output).
If present, specifies that this element is to be copied directly to the output, regardless of the input.âThe name of the element itself can be used to specify the name of the output element.âThis can be used to specify XML markup that is to appear in the output unconditionally, perhaps enclosing the parts of the output that depend on the input.
Any parameter beginning with an underline (_) will be copied directly to the output as an attribute, with the underline removed.âThis is a way of hard-coding output attributes into the model.
These parameters were designed for producing non-XML markup, but they are also allowed when producing XML markup.
Equivalent to setting rename=ââ for this and all child elements; causes no XML markup to be output.âCan be used for the top-level element when the output is not XML (can also be used for non-XML fragments within an XML document).
If any element under a ânomarkupâ element does have a ârenameâ or âfromâ attribute, then the ânomarkupâ does not apply to that particular element, although it does apply to its children.âThis does not affect whether or not the whole output is treated as XML (if the top-level element is ânomarkupâ then it is treated as plain text, and any elements underneath that have ârenameâ or âfromâ attributes are written into the text in normal XML syntax).
Specifies any text that is to be included in the output before the first one of these elements.
Specifies any text that is to be included in the output after the last one of these elements.
Specifies any text that is to be included in the output between each of these elements (but not before the first or after the last).
Specifies any text that is to be included in the output between each of these elements, as an alternative to the âbetweenâ text.âThe âgroupâ text is used instead of the âbetweenâ text every 10th element (the value of 10 can be changed by specifying group-size).âThis can be used as a rudimentary way of dividing the elements into groups for readability.
Specifies the number of elements to count before substituting âgroupâ for âbetweenâ for one element.
Specifies any text that is to be included in the output inside each one of these elements, at its beginning.
Specifies any text that is to be included in the output inside each one of these elements, at its end.
Does not process any elements before the specified position number; the first position number is 1. Note that this is absolute; if the currently-available position numbers start at 3, for example, then start-at=3 will start at the first one.âUse the ârenumberâ attribute as well if this is not desirable.
Does not process any elements after the specified position number; the first position number is 1. Note that this is absolute; if the currently-available position numbers start at 3, for example, then end-at=3 will end at the first one.âUse the ârenumberâ attribute as well if this is not desirable.
Sets both start-at and end-at to the given value.âOnly elements with the given position will be processed.âSee also ârenumberâ.
Changes the behaviour of âstart-atâ, âend-atâ and ânumberâ so that the position numbers are effectively renumbered.âThis does not affect any other processing of position numbers.
Restricts which elements are processed by including only those that have the given value as their contents.âThis can be used to substitute one value for another.âSee also âother-valuesâ.
Restricts which elements are processed by including only those that occur exactly the given number of times in the data that is currently under consideration.âThis can be used for different processing depending on how many there are.âSee also âother-valuesâ.
Specified immediately after any âvalueâ or âtotalâ restrictions; matches only the elements that have not already been matched by the above restrictions.
Specifies whether âforeachâ should search the display stack (see below).âA value of âneverâ avoids searching the display stack; âalwaysâ searches only the display stack.âThe default action is to search the display stack only if no result is found in the current data.
Consider the effect of the model
<SCORE> <PART> <TITLE/> . . .
on the input:
<SCORE> <TITLE> . . </TITLE> <PART> . . </PART> . . .
The intention here is that each PART bears the TITLE of the SCORE.âHowever, TITLEâs data is outside the scope of the PART element and will therefore not be present in the corresponding subset of the input when PART is processed.âIt can be reached by searching a display stack of fourspaces that contain all points not present in any of the subspaces generated by the model.âHence, if no TITLEs are found in the PART, then the SCORE will be searched (this search excluding all of the PARTs); if no TITLEs are found there then the next level up will be searched and so on.
Setting external=clear will cause a new display stack to be started for the part of the model that is enclosed by this element.âThe effect is to isolate this part of the model from the rest as far as the display stack is concerned.âIt is rarely needed.
Specifies that, after âforeachâ selects an element, it should then select the full extent of that element from the original fourspace (not just the subset of it that it was looking at).âHence the element is âbroadenedâ to its full value.
âbroadenâ may take a value, corresponding to another element name; if an element matching this latter name exists, and is above the element that âforeachâ is trying to broaden, then the former element is only broadened within the scope of the latter one.âThis should be used to disambiguate cases where âbroadenâ incorrectly âmergesâ elements that should have been separate.
One application of the âbroadenâ parameter is in following links, as in:
pointer/link broaden/id/whole-entry broaden
where an element called âwhole-entryâ contains an element called âidâ, which contains the same string as referenced elsewhere by one or more elements called âpointerâ (one of which is being inspected at this point in the model).
All links in this manner can be treated as bi-directional many-to-many mappings and read as needed.
Causes only elements that are (direct or indirect) children of the given element to be considered; any markup above that element is not included in the subset.
Causes each input elementâs immediate children to be processed sequentially, rather than being grouped by element name as would normally happen.âThis will often be used when processing documents that contain a mixture of different types of object in any order (as is the case with XHTML).âSetting sequential implies children-only, and also that only an elementâs immediate children are considered by the next level of the model.âAs a special case, sequential=âcdataâ additionally causes any cdata elements (at that level) to be copied from input to output.
Destroy all depth information in the generated subsets.âUsed when you later want to divide the data in a manner that is depth-independent.âThis usually occurs when there are two or more discrete types of data (e.g. words and music) that have some value to synchronise on (e.g. time) but it is not at the same level.âIt would be possible to perform a âflat searchâ individually for each element, but usually there are a lot of them so it is easier to âflattenâ everything.âNote that this can have unexpected effects on data that is hierarchical, such as mathematics.
The markup that this element identifies is not removed before processing child elements.âFor example,
<X no-strip="1" after="y"> <X number="1" after="z" /> </X>
is equivalent to:
<X number="1" after="zy" /> <X start-at="2" after="y" />
This can be useful for calling library modules that handle special cases (such as the first element) differently.
Causes all elements (subsets) to be merged and processed as one.âCan be useful on occasions.âCan be combined with no-strip to provide a form of conditional processing depending on the number of any given element present, e.g.
X no-strip number=3 merge / (...)
will execute (...) if and only if there are exactly 3 Xâs, but will not iterate for each X (you might want to process them in a different order, for example).
The ârestâ of the data (i.e. any data that was not covered by an instance of the given element) is normally discarded; setting âinclude-restâ will cause it to be included in all of the subsets.âThis can be useful if it is needed later, for example, if there is a song with several verses and one copy of the music (or one chorus), and it is desired for each verse to be printed along with the music (or chorus).
Reverses the order of the generated subsets before processing them.âNote that this reversal occurs after any selection with âstart-atâ, âend-atâ, ânumberâ or ârenumberâ.
This is a means of adding recursion to the model.âIt behaves as though the model under the previous element named by this parameter is also under the current element.âBoth recursive and non-recursive calling is supported.
Causes the child elements of this model element to be available for âcallâ even after this element has finished.âNormally they would go out of scope. âexport-codeâ can be used in conjunction with a nonsense element name to write a âlibraryâ of model fragments.
Behaves as though there were an element in the input of the same name as this model element, whose value is the position count of the innermost such element that is currently in progress.âFor example, the following CML fragment will begin each âverseâ with a number:
verse / ( verse count after=".", ...
Only process this element if its position has changed since last time round the loop.âThis is used to read off an alternate set of markup that is independent from the main set of markup.âFor example, the CML
page if-changed / ...
will do something whenever âpageâ changes (which might indicate a physical page break in the original source material that is independent of the logical structure).
Indicates that the element named in the model here is expected, that is, at least one of them should be found in the input at this point, and an error should be raised if not.â(Normally 4DML ignores model elements that are not found, since the model can specify what to do with certain elements in the event that they exist.)
Using âexpectedâ in appropriate places can catch most errors like running the wrong model on the wrong sort of data (e.g. trying to transform mathematics as though it were music).âIt can also catch many mismatches between row lengths etc (if you have inputted two or more rows of data and you read them by column, and you say that each column is âexpectedâ to have something from each of the row types you inputted, then there should be an error if one row is longer than the other).
Specifies an element name that is to be treated as a âwildcardâ that will match any element name (and will not remove that element from the subset generated).
Adds the name of this top-level element to its âbeginâ attribute.âThis can be used for debugging wildcards when using ânomarkupâ (you can specify âdebugdumpâ to see what the wildcard matches).
Calls a given Python function on each contents string (not markup string) that is written to the output.âThe contents is passed to the named function as a string, and the functionâs return value (which should also be a string) is used in its place.âThis can be used to code arbitrary low-level filters for the data, to be applied during the model transform.
The function named here should be defined in a Python module that is loaded with the â-pythonâ command-line option.
Copyright 2002-2006 Silas S. Brown (Computer Lab, University of Cambridge, 15 JJ Thompson Avenue, Cambridge UK).
No warranty.
All material © Silas S. Brown unless otherwise stated. Debian is a trademark owned by Software in the Public Interest, Inc. Python is a trademark of the Python Software Foundation. Unix is a trademark of The Open Group. Any other trademarks I mentioned without realising are trademarks of their respective holders.