---
---
project
  projecthouse
version
  1.0.0
unit
  struct
info
  ...
comment
  # TODOs

  ## branching scheme for storages

  At the moment, a `STORAGE` is synonymous with a directory.
  And currently, it has what I call the **branching scheme** of `[2,2]`.
  But that should be changed to be part of the `STORAGE` definition, which makes it a model of

      { root: DIRECTORY,
        branchingScheme: list (INT) }

  There should also be a function that redesigns the branching scheme for storages that grow very large.

  # Remarks

  The title for the whole thing itself if now "Caretaker in a Project House".
  The title for an equivalent "GitHub" would then be "The Project House Market".
  
  # Requirements
  
  * `Sha1Store.file2hex` uses [**sha1-file**](https://www.npmjs.com/package/sha1-file)
  * `Sha1Store.string2hex` uses [**sha1**](https://www.npmjs.com/package/sha1)

---
---










---
---
// Sha1Store
---
---
unit
  doc
header
  **SHA1 Storage**
info
  `Sha1Store` provides the functionality for the creation and management of a storage system, that is able to store and retrieve 
  
  * strings
  * JSON values
  * files
  * directories (with all contained subdirectories and files)
  
  The storage system is _irredundant_ in the sense that all items that occur multiple times, are only stored once. 
  This is achieve by applying the [sha1sum](https://en.wikipedia.org/wiki/Sha1sum) algorithm, which computes quasi-unique numerals for strings and files, a technique that is also used in source code mangement systems like [`Git`](https://en.wikipedia.org/wiki/Git).
  
  
  # `HEX40` values and the SHA1
  
  A `HEX40` value is a __numeral__ , i.e. a string representation of a number, made of 40 hexadecimal digits (`0`, `1`, ..., `9`, `a`, ..., `f`). For example, `'e8a94988d4c4041c60443d929c4a0dd5b6e38af5'` is a `HEX40` value.
  
  [**SHA-1** (the **Secure Hash Algorithm 1**)](https://en.wikipedia.org/wiki/SHA-1) that computes `HEX40` values for strings and files. 
  In our implementation here, these are the two functions
  
  * `Sha1Store.string2hex : STRING --> HEX40` 
  * `Sha1Store.file2hex: FILE --> HEX40`
  
  Actually, these functions themselves make use of the [`sha1`](https://www.npmjs.com/package/sha1) and [`sha1-file`](https://www.npmjs.com/package/sha1-file) packages that are available from the [**NPM**](https://www.npmjs.com/) online repository. (Thank's!)
  In most UNIX/Linux systems the [`sha1sum`](https://en.wikipedia.org/wiki/Sha1sum) is available by default, which does the same.
  
  Note, that `file2hex(file)` only considers the content of the given `file`, it does not care about its name. 
  In other words, if say `foo.txt` and `bar.txt` are two files with the same content, then `file2hex('foo.txt')` and `file2hex('bar.txt')` are the same `HEX40` values.
  
  `string2hex` (as well as `file2hex`) has the properties of a [cryptographic has function](https://en.wikipedia.org/wiki/Cryptographic_hash_function):
  
  1. `string2hex(str)` is quickly computed, for every string `str`.
  2. If `str1` and `str2` are different strings, then `string2hex(str1)` and `string2hex(str2)` are different, too. ([*One-to-one* or *injectivity*](https://en.wikipedia.org/wiki/Injective_function))
  3. It is infeasible to reconstruct the `str` from the `HEX40` value. (Except by trying for all strings.)
  4. The resulting `HEX40` value reveals no information of the input `str`. (In other words, even if `str1` and `str2` are only slightly different, `string2hex(str1)` and `string2hex(str2)` are still very different.)
  
  For our purposes, only property 1 and 2 are important. Properties 3 and 4 are important in [cryptography](https://en.wikipedia.org/wiki/Cryptography).
  Actually, it turned out that for cryptographic purposes, [SHA-1](https://en.wikipedia.org/wiki/SHA-1) is not considered safe anymore and is therefore replaced by improved versions.
  But as a [hash function](https://en.wikipedia.org/wiki/Hash_function), i.e. for properties 1 and 2, SHA-1 is still a very useful choice and it is therefore used as such in systems like [Git](https://en.wikipedia.org/wiki/Git), that need an effective and safe hash function for relatively big data.
  
  `string2hex` thus produces a 40-digit numeral for every string of any size. 
  Of course, in a precise mathematical sense, any function that maps an infinite type (here: strings) into a finite type (here: `HEX40`), cannot really be injective: there must be different strings `str1` and `str2` that return the same `HEX40` value.
  But hoever being finite, the number of all `HEX40` values is "practically" so big and the SHA-1 function is that random, that "in fact", i.e. "in real daily use", it is as good as impossible that a clash will every occur.
  
  So let us rephrase the properties 1 and 2, so that it holds for SHA-1 and our functions:
  
  1. `string2hex(str)` is quickly computed, for every string `str`
  2. If `str1` and `str2` are different strings, it is safe to assume that `string2hex(str1)` and `str2hex(str2)` are different, too. (*quasi-injectivity*).
  
  # The storage functions
  
  The two properties of our function `string2hex` (as well as `file2hex`) make it very suitable for our implementation of an *irredundant storage system*.
  
  When `str` is a string and `hex` the `HEX40` value `string2hex(str)` of `str`, then `hex` serves as a short address under which we can **store** `str`.
  And when we want to **retrieve** the string, again, we feed the `hex` to the store and get our result `str`.
  The quasi-injectivity ensures that there are never two different strings stored under the same `HEX40` value.
  
  In our implementation, the two according functions are the following, where `store` is a `STORAGE` value as we will define below.
  
  * `Sha1Store.storeString (store) (str)` puts the string `str` into the `store` and returns the `hex` address for this string.
  * `Sha1Store.retrieveString (store) (hex)` searches for the entry under `hex` in the `store` and returns the string that was stored there.
  
  Of course, if `str1` and `str2` are equal strings, their `HEX40` value is equal, too. And that makes the storage **irredundant** in the sense that storing both strings requires only one storage entry.
  
  As for strings, we have similar functions for files:
  
  * `Sha1Store.storeFile (store) (file)` stores the `file` content (ignoring the file name) in the `store` and returns the `hex` value
  * `Sha1Store.retrieveFile (store) (hex) (filepath)` searches in the `store` for the entry under the `hex`, and writes the content under the `filepath`.
  
  Note, that the file operations not only work for text files with string content, but also for files with binary content.
  
  # The storage
  
  The **storage** is essentially a directory of files, where the names are `HEX40` numbers and the file itself is the stored content.
  
  We thus define a `STORAGE` as a directory, such as say `/path/to/my/storage/`.
  
  * If we call 
  
          storeFile ('/path/to/my/storage/') ('some/example.txt')
        
      the `HEX40` value of `some/example.txt` is computed, say it is `'e1e1125740a8615eda6a2cacfe701ecca7ad4ec3'` and then a copy of `some/example.txt` is made and named `/path/to/my/strorage/e1e1125740a8615eda6a2cacfe701ecca7ad4ec3`.
  
  * If we then call 
  
          retrieveFile ('/path/to/my/storage/') ('e1e1125740a8615eda6a2cacfe701ecca7ad4ec3') ('new/place.txt')
        
      a copy of the file `/path/to/my/storage/e1e1125740a8615eda6a2cacfe701ecca7ad4ec3` is made and called `new/place.txt`.
  
  ## The branching scheme for storages

  We just defined a `STORAGE` as a directory with files, named by `HEX40` values.
  But in most serious uses of such a storage, the directory will fill up very soon and has to hold thousands or even millions of entries. 
  And a directory with that many files is hard to maintain.
  
  We therefore introduce the **branching scheme** for these shorages, which is a list of small integers.
  Suppose, the branching scheme is `[2,3]`. That means that each 40-digits `HEX40` value is split into three strings: one of length 2, one of length 3, and the remaining string of 35 characters.
  The example `'e1e1125740a8615eda6a2cacfe701ecca7ad4ec3'` is thus split into `['e1', 'e11', '25740a8615eda6a2cacfe701ecca7ad4ec3']`.
  And instead of storing the entry in the file named 
  
      /path/to/my/storage/e1e1125740a8615eda6a2cacfe701ecca7ad4ec3
      
  we rather store it in
   
      /path/to/my/storage/e1/e11/25740a8615eda6a2cacfe701ecca7ad4ec3
 
  i.e. we split the file into a directory named `e1`, containing another directory named `e11`, which then contains the file `25740a8615eda6a2cacfe701ecca7ad4ec3`.

  Maintaining this branching scheme, the storage now does not contain a huge amount of files, but directories named with two hexadecimal digits. There can be 256 different ones, at most. And each such dictionary contains one or more dictionaries with three digits (4096 at most). And each of these directories in turn holds one or more files with names made of 35 digits.
  
  ## The `STORAGE` creator
  
  The branching scheme is part of the definition of a `STORAGE` and we attach it to the storage name, so that the `store` and `retrieve` functions have that available so that they understand how to find an entry.
  
  The syntax of the `STORAGE` creator is this
  
      Sha1Store.createStorage (dir) (ident) (braSch) 
  
  where 
  
  * `dir` is the parent directory of the new storage, say `/path/to/my/`
  * `ident` is an identifier for the new storage name, say `magazine`
  * `braSch` is the branching scheme, say `[2, 3]`.
  
  The actual directory for the `STORAGE` (which is also the return value of the function call) is this:
  
      "/path/to/my/magazine.2-3"
      
  i.e. the branching scheme is attached to the storage identifier.
  
  If you don't want to be bothered with the definition details, you can also use
  
      Sha1Store.createDefaultStorage (dir)
      
  which only asks for the parent directory and uses the default identifier `Storage` and default branching scheme `[2,2]`.
  In case of our previous example `dir`, the returned result is the `STORAGE` value
  
      "/path/to/my/Storage.2-2"
  
  ## Storage update
  
  There is a trade-off in the choice of the branching scheme. A scheme like `[]` where there are no subdirectory branches, is effective for a small amout of entries. But when that number grows, directories are may explode is size and more branching would be more appropriate.
  We therefore have a function that modifies the branching scheme and *updates* a storage in this sense:
  
      Sha1Store.updateStorage (store) (newBraSch)
  
  creates a clone of the `store` with the new branching scheme. For example, if `store` is `/path/to/my/Storage.2-2`, then `updateStorage (store) ([2,2,2])` creates a clone of `store` with a higher branching, namely `/path/to/my/Storage.2-2-2`. You can then delete `/path/to/my/Storage.2-2` and continue with the new storage. It has the same content.
  
  # Storing and retrieving JSON values
  
  We also have two functions for the storing and retrieving of [JSON values](http://json.org/).
  
  * `Sha1Store.storeJsonValue (store) (json)` that puts the `json` value into the `store` and returns the `HEX40` address.
  * `Sha1Store.retrieveJsonValue (store) (hex)` which recovers the original `json` value, again.
  
  For example, in a Node session with an existing `store` we get
  
      > var hex = Sha1Store.storeJsonValue (store) ({one: [0, false, null], two: "Hello!", three: [12, 23, 34]})
      undefined
      
      > hex
      '4e59f42694de316117b2cc31d743e4b55fc54f99'
      
      > Sha1Store.retrieveJsonValue (store) (hex)
      { one: [ 0, false, null ], two: 'Hello!', three: [ 12, 23, 34 ] }

  Internally, `storeJsonValue` is implemented as a composition of two steps: the JSON values are converted to strings, and this string is stored.
  The retrieving function works the other way round.
  
  # Storing and retrieving entire directories
  
  Similar to storing and retrieving strings, files and JSON values in and from the storage, we also have two functions that do that with entire directories:
  
  `Sha1Store.storeDirectory (STORAGE) (DIRECTORY) : HEX40`
  
  :    stores all content of the given `DIRECTORY` into the `STORAGE` and returns the address `HEX40` under which it is stored.
  
  `Sha1Store.retrieveDirectory (STORAGE) (HEX40) (DIRECTORY) : NULL`
  
  :    restores all content that was stored under the `HEX40` in the `STORAGE` under the given `DIRECTORY`.
  
  For example, suppose we have a directory hierarchy ('+' denotes a directory and `*` a file):
  
      + AAA
        + BBB
          + CCC
            * One.html
            * Two.txt
            + pictures
              * Tree.jpg
              * Four.jpg
          + DDD
            * Five.html
        + EEE
          * Six.html
  
  When we now first store the content of `AAA/BBB/CCC`, take the resulting `hex` value and retrieve that content under `AAA/EEE`, again, i.e. if we do
  
      var hex = Sha1Store.storeDirectory (STORAGE) ('AAA/BBB/CCC');
      Sha1Store.retrieveDirectory (STORAGE) (hex) ('AAA/EEE');
  
  our hierachy then looks like this
  
      + AAA
        + BBB
          + CCC
            * One.html
            * Two.txt
            + pictures
              * Tree.jpg
              * Four.jpg
          + DDD
            * Five.html
        + EEE
          * Six.html
          * One.html
          * Two.txt
          + pictures
            * Tree.jpg
            * Four.jpg
  
  If we would have changed the target `AAA/EEE` to say `AAA/FFF`, i.e. a non-existing directory, this directory would have been created and the result would be
  
      + AAA
        + BBB
          + CCC
            * One.html
            * Two.txt
            + pictures
              * Tree.jpg
              * Four.jpg
          + DDD
            * Five.html
        + EEE
          * Six.html
        + FFF
          * One.html
          * Two.txt
          + pictures
            * Tree.jpg
            * Four.jpg
   
   
  But other than storing and retrieving strings, files and JSON values, the storage of directories involves not just one, but multiple places in the store, and it is essentially done in two steps, which will be explained, next.
  
  ### The representation of directory content by a `HEX40` tree record
  
  Suppose we have an directory named `MyHomework` with the following content ('+' denotes a directory, `*` denotes a file):
  
      + MyHomework
        + Biology
        + Englisch
          * CharlesDickens-GreatExpectations.html
          * JulianBarnes-SenseOfAnEnding.html
        + History
          * FrenchRevolution.txt
          * WorldWar2.txt
          + pictures
            * Guillotine.jpg
            * Robespierre.jpg
        + Maths
          + Algebra
            * GroupTheory.tex
            * RingTheory.tex
          * Arithmetic.doc

  Each such directory can be represented by a record of trees of `HEX40` values:
  
  * each file with name `f` and `HEX40` value `h` is given by a key-value pair `f:h`.
  * each directory with name `d` and content `f_1`, ..., `f_n` (each `f` being a file or directory), is recursively given by a record `{f_1: t_1, ..., f_n: t_n}`, where the `t_1`, ..., `t_n` are the according `HEX40` tree record.
  
  This representation can be computed by the function
  
      Sha1Store.directory2hexTreeRecord (DIRECTORY) : record(tree(HEX40))
  
  For example, calling
  
      Sha1Store.directory2hexTreeRecord ('MyHomework')
      
  computes the `HEX40` tree record

      { Biology: {},
        Englisch: 
        { 'CharlesDickens-GreatExpectations.html': '85beca341f971884732121a9083b1253f9ce33e4',
          'JulianBarnes-SenseOfAnEnding.html': '85beca341f971884732121a9083b1253f9ce33e4' },
        History: 
        { 'FrenchRevolution.txt': '53f075c07d0ecd6e02ebeb8be170616c5de070fb',
          'WorldWar2.txt': 'c22b5f9178342609428d6f51b2c5af4c0bde6a42',
          pictures: 
            { 'Guillotine.jpg': '36b5f1d0b696e6a93ba9612458a7e342750fe564',
              'Robespierre.jpg': '4235abd0aa05772dea002c7a63b9dd7d603520ff' } },
        Maths: 
        { Algebra: 
            { 'GroupTheory.tex': 'dd122581c8cd44d0227f9c305581ffcb4b6f1b46',
              'RingTheory.tex': 'dd122581c8cd44d0227f9c305581ffcb4b6f1b46' },
          'Arithmetic.doc': 'dd122581c8cd44d0227f9c305581ffcb4b6f1b46' } }

  Every `HEX40` tree record itself is a JSON value, so we can compute the `HEX40` value:
  
      > Sha1Store.json2hex (htr)
      '682dd0689e2a20327cdf24178f49779edf096cfe'
      
  ## Storing an entire directory
      
  So, storing an entire directory like `MyHomework` can be done in three steps
  
  1. Create the `HEX40` tree record `htr` from `MyHomework` by calling
  
          var htr = Sha1Store.directory2hexTreeRecord ('MyHomework')
  
  2. Store the content of all the files in `htr` under their according `HEX40` values.
  
  3. Store `htr` itself
  
          var hex = Sha1Store.storeJsonValue (STORAGE) (htr)
        
    and return `hex` as the address for the whole directory.
    
  And all this is done in one step by
  
        var hex = Sha1Store.storeDirectory (STORAGE) ('MyHomework')
      
  ## Retrieving the directory, again
    
  Retrieving the same directory under the new directory `dir` is achieve by returning the previouse steps:
  
  1. Take previous `hex` and retrieve the `HEX40` tree record via
  
          var htr = Sha1Store.retrieveJsonValue (STORAGE) (hex)
      
  2. Reconstruct the directory hierarchy under `dir` and retrieve all files from `htr` into their according place with the names given by `htr`. 
     This is done by
  
          Sha1Store.retrieveFromHexTreeRecord (STORAGE) (htr) (dir)    // returns `null` on success
        
  Afterwards, the original directory is entirely copied under the new `dir`.
  And these two steps are also combined into one, namely
  
      Sha1Store.retrieveDirectory (STORAGE) (HEX40) (dir)
  
---
---
unit
  struct
name
  Sha1Store
info
  ...
---
---
unit
  doc
name
  Sha1Store
header
  JSON values and code
---
---
unit
  struct
name
  Sha1Store.Json
---
---
unit
  type
name
  Sha1Store.Json.VALUE
value
  ANYTHING
info
  ...
comment
  IMPROVE THE IMPLEMENTATION!!!!
---
unit
  type
name
  Sha1Store.Json.CODE
value
  STRING
info
  ...
comment
  IMPROVE THE IMPLEMENTATION!!!
---
unit
  function
name
  Sha1Store.Json.value2code
type
  Type.fun ([VALUE, CODE])
value 
  function (x) {
    try {
      return JSON.stringify (x);
    } catch (e) {
      throw Error ("Unable to convert the JSON value into JSON code: " + e);
    }
  }
info
  ...
comment
  Replacing the line
  
      return JSON.stringify (x);
      
  by the line
  
      return JSON.stringify (x, null, 2);
      
  would result in better readable strings.
  But that would in the sequel lead to totally different `HEX40` values of JSON values.
---
unit
  function
name
  Sha1Store.Json.code2value
type
  Type.fun ([CODE, VALUE])
value
  function (code) {
    try {
      return JSON.parse (code);
    } catch (e) {
      throw Error ("Unable to convert the JSON code into a JSON value: " + e);
    }
  }
info
  ...
---
---
unit
  doc
name
  Sha1Store
header
  The `HEX40` values and the SHA1 (Secure Hash Algorithm 1)
info
  See the Wikipedia entry on [SHA-1](https://en.wikipedia.org/wiki/SHA-1) for an introduction of the subject.

  Note, that the SHA1 code of a file does not depend on its name, but entirely on its content.
  In other words, the SHA1 code of a text file is the same as the SHA1 of the string content of that file.

  For example,

      > var str = "Hello world!"
      undefined

      > var file = 'sample.txt'
      undefined

      > System.File.write (file) (str)
      null

  Now, the file `sample.txt` exists and contains the string `Hello world!`.
  Note, that the SHA1 value of this file is the same as the string:

      > Sha1Store.string2hex (str)
      'd3486ae9136e7856bc42212385ea797094475802'

      > Sha1Store.file2hex (file)
      'd3486ae9136e7856bc42212385ea797094475802'

---
---
unit
  type
name
  Sha1Store.HEXADECIMAL
value
  Type.regularString ('^[0-9a-f]+$')
info
  `Sha1Store.HEXADECIMAL` is the type of all __hexadecimal numerals__, i.e. all non-empty strings entirely made of hexadecimal digits ('0', ..., '9', 'a', ..., 'f').
  For example,

  * `"3f4f"` (which is `16197` in decimal notation)
  * `"a1b2c3d4e5f"`(which is `11111822610015` in decimal notation)
  
  Note, that ECMAScript itself also accepts hexadecimal notation, namely by preceding such a hexadecimal numerals with `0x`, e.g.
  
      > 0xa1b2c
      662316
      
  And different to our definition of the `HEXADECIMAL` here, ECMAScript numerals are case insensitive, i.e. `A`, `B`, `C`, `D`, `E` can be used for `a`, `b`, `c`, `d`, `e`, `f`, respectively. For example,
  
      > 0xA1B2C
      662316

      > 0x9abc === 0x9aBc
      true

  But we define `HEXADECIMAL` numerals here as a superset of `HEX40` (see next), and these occur as output of the [`fsum`](https://en.wikipedia.org/wiki/Sha1sum) program. And there, only the small letter versions occur.
---
unit
  type
name
  Sha1Store.HEX40
value
  Type.subType (HEXADECIMAL) (function (h) {
    if (h.length === 40) { return ''; }
    else { return 'The given hexadecimal has length `' + h.length + '`.'; }
  })
info
  A `HEX40` value is a __40-ary hexadecimal numerals__, given as a string of length 40, and entirely made of hexadecimal characters (`0`, ..., `9`, `A`/`a`, ..., `F`/`f`).

  For example,

      > Type.complain (Sha1Store.HEX40) ('1234567890123456789012345678901234567890')
      ''

      > Type.complain (Sha1Store.HEX40) ('abcdefabcdefabcdefabcdefabcdefabcdefabcd')
      ''

      > Type.complain (Sha1Store.HEX40) ('123abcdef')
      'Complain of the `Sha1Store.HEX40` type:\nThe given string does not have 40, but 9 characters.'

      > Type.complain (Sha1Store.HEX40) ('xyz')
      'Complain of the `Sha1Store.HEX40` type:\nThe given string does not have 40, but 3 characters.'

      > Type.complain (Sha1Store.HEX40) ('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
      'Complain of the `Sha1Store.HEX40` type:\n`xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx` contains characters other than `0`, ..., `9`, `A`, ..., `F`, `a`, ..., `f`.'

---
unit
  function
name
  Sha1Store.string2hex
type
  Type.lambda ([STRING, HEX40])
value
  function (str) {
    return require ('sha1') (str);
  }
info
  `Sha1Store.string2hex (str)` returns the `HEX40` code for the given string.
  For example,

      > Sha1Store.string2hex ('Hello world!')
      'd3486ae9136e7856bc42212385ea797094475802'

comment
  The implementation uses the [sha1](https://www.npmjs.com/package/sha1) package from the [NPM](https://npmjs.com) package repository.
---
unit
  function
name
  Sha1Store.file2hex
type
  Type.lambda ([System.FILE, HEX40])
value
  function (file) {
    var cmd = 'sha1sum ' + file;
    try {
      var hex = require ('sha1-file') (file);
      return hex;
    } catch (e) {
      throw Error ("Could not determine the SHA1 from `" + file + "`: " + e);
    }
  }
info
  `Sha1Store.file2hex (file)` returns the SHA1 code for the given `file`.
  For example,

      > Sha1Store.file2hex ('x.js')
      'ab24c06d9d329c3e4d87dc18fa4ac53e44b4471a'

  In Linux, the same is achieved with the [`sha1sum`](http://linux.die.net/man/1/sha1sum).
---
unit
  function
name
  Sha1Store.json2hex
type
  Type.lambda ([Json.VALUE, HEX40])
value
  function (x) {
    try {
      var code = Json.value2code (x);
    } catch (e) {
      throw Error ("Something is wrong with the given JSON value, it could not be converted to a string: " + e);
    }
    return string2hex (code);
  }
info
  `Sha1Store.json2hex (x)` converts the JSON value `x` into a string (by means of `Json.value2code (x)`) and then returns the SHA1 value of that code string (by means of `Sha1Store.string2hex`).

  For example,

      > Sha1Store.json2hex (null)
      '2be88ca4242c76e8253ac62474851065032d6833'

      > Sha1Store.json2hex ({one: 123, two: 234})
      'ccc594e58bafeca7864fb0b64dc72b34df7882f0'

      > Sha1Store.json2hex ([null, false, 123, "hi there", [1,2,3], {}])
      'aa142f41d66292df9f32f199e5e2f846097b25ec'

---
---
unit
  doc
name
  Sha1Store
header
  The `STORAGE` type and creation
---
---
unit
  type
name
  Sha1Store.BRANCHING_SCHEME
value
  Type.subType (Type.list (INT)) (function (iL) {
    if (List.every (function (i) { return i > 0; }) (iL)) {
      if (Int.sum (iL) <= 40) {
        return '';
      } else {
        return ("The sum " + Int.sum (iL) + " of the given integer list is too big.");
      }
    } else {
      return ("The given integer list is not a branching scheme, because not all list elements are positive.");
    }
  })
info
  A __branching scheme__ is a list `[n_1, ..., n_k]` of positive integers, so that `n_1 + ... + n_k <= 40`.
---
unit
  type
name
  Sha1Store.STORAGE
value
  Type.subType (System.DIRECTORY) (function (dir) {
    try {
      var name = System.Path.basename (dir);
      var id = storageIdentifier (dir);
      var bs = branchingScheme (dir);
      if (name === createStorageName (id) (bs)) {
        return '';
      } else {
        return ('The storage base name `' + name + '` is not a proper creation of the identifier `' + id + '` and the branching scheme `[' + bs + ']`.')
      }
    } catch (e) {
      return ("The storage name is not well-formed: " + e);
    }
  })
info
  A __STORAGE__ is an existing directory (say `/path/to/my/my_magazine.2-3-4`), where the basename (i.e. `my_magazine.2-3-4`) is a dot-separated join of an identifier (i.e. `my_magazine`) and branching scheme (i.e. `[2, 3, 4]`) converted into a hyphen-separated join (i.e. `"2-3-4"`).
---
unit
  function
name
  Sha1Store.createStorageName
type
  Type.fun ([System.Path.IDENTIFIER, BRANCHING_SCHEME, System.Path.IDENTIFIER])
value
  function (id) {
    return function (braSch) {
      return ( id + '.' + braSch.join ('-') );
    }
  }
info
  `Sha1Store.createStorageName ('identifier') ([i_1, ..., i_n])` returns `'identifier.i_1-...-i_n'`.
  
  For example,
  
      > Sha1Store.createStorageName ('my.big_storage') ([2,4,3])
      'my.big_storage.2-4-3'

      > Sha1Store.createStorageName ('my.big_storage') ([])
      'my.big_storage.'

---
unit
  function
name
  Sha1Store.storageIdentifier
type
  Type.fun ([STORAGE, System.Path.IDENTIFIER])
value
  function (store) {
    var strL = System.Path.basename (store) . split ('.');
    return strL . slice (0, strL.length - 1) . join ('.');
  }
info
  `Sha1Store.storageIdentifier (store)` reconstructs the identifier with which the `store` was created (namely via `createStorage(DIRECTORY)(IDENTIFIER)(BRANCHING_SCHEME)`). So, if `STORAGE` is say `/home/peter/path/to/magazine.3-3-3`, then `Sha1Store.storageIdentifier(store)` is `magazine`.
  
  For example,
  
      > Sha1Store.storageIdentifier ('/home/peter/path/to/storage/my.big_storage.2-4-3')
      'my.big_storage'
      
      > Sha1Store.storageIdentifier ('/home/peter/path/to/storage/my.big_storage.')
      'my.big_storage'

---
unit
  function
name
  Sha1Store.branchingScheme
type
  Type.fun ([STORAGE, BRANCHING_SCHEME])
value
  function (store) {
    var s;       // for strings
    var strL;    // for string lists
    s = System.Path.basename (store);
    strL = s.split ('.');
    if (strL.length <= 1) {
      throw Error ("The basename `" + s + "` of the storage directory `" + store + "` does not have a branching schema extension.");
    } else {
      s = strL [strL.length-1];
      strL = s.split ('-');
      if (strL.length <= 1 && strL[0] === '') {
        return [];
      } else {
        try {
          return List.map (Int.Dec.fromCode) (strL);
        } catch (e) {
          throw Error ("The path `" + store + "` of the given storage does not have a well-defined branching scheme as name extension.")
        }
      }
    }
  }
info
  `Sha1Store.branchingScheme (store)` reconstructs the branching scheme of the given storage.
  For example,

      > Sha1Store.branchingScheme ('/home/peter/path/to/storage.3-3-3')
      [3,3,3]
      
      > Sha1Store.branchingScheme ('my.big_storage.2-4-3')
      [ 2, 4, 3 ]
      
      > Sha1Store.branchingScheme ('my.big_storage.')
      []

---
unit
  function
name
  Sha1Store.createStorage
type
  Type.fun ([System.DIRECTORY, System.Path.IDENTIFIER, BRANCHING_SCHEME, STORAGE])
value
  function (parentDir) {
    return function (id) {
      return function (braSch) {
        if (! System.Path.isDirectory (parentDir)) {
          throw Error ("`" + parentDir + "` is not a directory.");
        } else { // i.e. `parentDir` is an existing directory
          var name = createStorageName (id) (braSch);
          var root = System.Path.joinList ([parentDir, name]);
          if (System.Path.exists (root)) {
            throw Error ("The path `" + root + "` for the new storage directory already exists.");
          } else {
            try {
              System.Dir.make (root);
            } catch (e) {
              throw Error ("Could not create the storage directory `" + root + "`: " + e);
            }
            return root;
          }
        }
      }
    }
  }
info
  Let `parentDir` be an existing directory (e.g. `/home/mustafa/media/important/`), `name` a suitable identifier for a directory (e.g. `main-depot`) and `braSch` a branching scheme (e.g. `[2,2]`, which is also the default branching scheme).
  Calling `Sha1Store.create (parentDir) (name) (braSch)` then created a new directory, where the path is a combination of `parentDir`, `name` and `braSch` (for the example, that is `/home/mustafa/media/important/main-depot.2-2`).
comment
  Make sure, `System.Path.IDENTIFIER` from the type definition is implemented!
---
unit
  function
name
  Sha1Store.createDefaultStorage
type
  Type.fun ([System.DIRECTORY, STORAGE])
value
  function (parentDir) {
    var defaultStorageId       = 'Storage';
    var defaultBranchingScheme = [2, 2];
    return createStorage (parentDir) (defaultStorageId) (defaultBranchingScheme);
  }
info
  If `path/to/some/Directory/` is an existing directory, `Sha1Store.createDefaultStorage` creates the new subdirectory `path/to/some/Directory/Storage.2-2` and returns that path as a `STORAGE` value.
---
unit
  function
name
  Sha1Store.hex2filepath
type
  Type.fun ([STORAGE, HEX40, System.FILEPATH])
value
  function (store) {
    return function (hex) {
      var intL = branchingScheme (store);
      var strL = [];
      var n = 0;
      for (var i = 0; i < intL.length; i++) {
        strL.push (hex.substr (n, intL [i]));
        n += intL [i];
      }
      strL.push (hex.substr (n));
      strL.unshift (store);
      var path = System.Path.joinList (strL);
      return path;
    }
  }
info
  `Sha1Store.hex2filepath (store) (hex)` determines the actual file path to the file in the `store` addressed by `hex`.
  For example,

      > Sha1Store.hex2filepath ('/home/to/my/storage.2-3-3/') ('2be88ca4242c76e8253ac62474851065032d6833');
      '/home/to/my/storage.2-3-3/2b/e88/ca4/242c76e8253ac62474851065032d6833'

---
---
unit
  doc
name
  Sha1Store
header
  Storing and retrieving files
---
---
unit
  function
name
  Sha1Store.storeFile
type
  Type.fun ([STORAGE, System.FILE, HEX40])
value
  function (store) {
    return function (file) {
      // determine the `HEX40` value from the given `file`
      try {
        var hex = file2hex (file);
      } catch (e) {
        throw Error ("Could not determine the SHA1 sum (the `HEX40` value) of `" + file + "`: " + e);
      }
      // make sure, the path to the new address does exist in the `store`; create new subdirectories, if necessary
      var path = store;
      var intL = branchingScheme (store);
      var n = 0;
      for (var i = 0; i < intL.length; i++) {
        var dir = hex.substr (n, intL [i]);
        path = System.Path.joinList ([path, dir]);
        if (! System.Path.exists (path)) {
          try {
            System.Dir.make (path);
          } catch (e) {
            throw Error ("Unable to create the new directory `" + path + "`: " + e);
          }
        }
        n += intL [i];
      }
      // copy `file` to the new `path` in the `store`, but only, if the `path` is not defined, yet
      try {
        path = System.Path.joinList ([path, hex.substr (n)]);
        if (! System.Path.exists (path)) {
          System.File.copy (file) (path);
        }
      } catch (e) {
        throw Error ("Unable to copy `" + file + "` to `" + path + "`: " + e);
      }
      // finally, return the `HEX40` value of the `file`
      return hex;
    }
  }
info
  ....
---
unit
  function
name
  Sha1Store.retrieveFile
type
  Type.fun ([STORAGE, HEX40, System.FILEPATH, NULL])
value
  function (store) {
    return function (hex) {
      return function (path) {
        var storagePath = hex2filepath (store) (hex);
        if (System.Path.isFile (storagePath)) {
          try {
            System.File.copy (storagePath) (path);
            return null;
          } catch (e) {
            throw Error ("Unable to copy the content of `" + hex + "` (in `" + store + "`) to `" + path + "`: " + e);
          }
        } else {
          throw Error ("`" + hex + "` is an undefined address in `" + store + "`, i.e. `" + storagePath + "` does not exist.");
        }
      }
    }
  }
info
  `Sha1Store.retrieveFile (store) (hex) (path)`

  ...........
---
---
unit
  doc
name
  Sha1Store
header
  Storing and retrieving strings
---
---
unit
  function
name
  Sha1Store.storeString
type
  Type.fun ([STORAGE, STRING, HEX40])
value
  function (store) {
    return function (str) {
      // determine the `HEX40` value from the given `str`
      try {
        var hex = string2hex (str);
      } catch (e) {
        throw Error ("Could not determine the SHA1 sum (the `HEX40` value) of the given string: " + e);
      }
      // make sure, the path to the new address does exist in the `store`; create new subdirectories, if necessary
      var path = store;
      var intL = branchingScheme (store);
      var n = 0;
      for (var i = 0; i < intL.length; i++) {
        var dir = hex.substr (n, intL [i]);
        path = System.Path.joinList ([path, dir]);
        if (! System.Path.exists (path)) {
          try {
            System.Dir.make (path);
          } catch (e) {
            throw Error ("Unable to create the new directory `" + path + "`: " + e);
          }
        }
        n += intL [i];
      }
      // write the `str` to the new `path` in the `store`, but only, if the `path` is not defined, yet
      try {
        path = System.Path.joinList ([path, hex.substr (n)]);
        if (! System.Path.exists (path)) {
          System.File.write (path) (str);
        }
      } catch (e) {
        throw Error ("Unable to write the given string to `" + path + "`: " + e);
      }
      // finally, return the `HEX40` value of the `str`
      return hex;
    }
  }
info
  `Sha1Store.storeString (store) (str)` determines the `HEX40` value of the `str` and stores the string under this value in the given `store`. If that is done successfully, the `HEX40` value is returned.

  For example, suppose that `dummyStore` is an existing `STORAGE`. Then

      > Sha1Store.storeString (dummyStore) ('Hello!')
      '69342c5c39e5ae5f0077aecc32c0f81811fb8193'

  writes `'Hello!'` into the storage, and

      > Sha1Store.retrieveString (dummyStore) ('69342c5c39e5ae5f0077aecc32c0f81811fb8193')
      'Hello!'

  restores its value, again.
---
unit
  function
name
  Sha1Store.retrieveString
type
  Type.fun ([STORAGE, HEX40, STRING])
value
  function (store) {
    return function (hex) {
      var storagePath = hex2filepath (store) (hex);
      if (System.Path.isFile (storagePath)) {
        try {
          return System.File.read (storagePath);
        } catch (e) {
          throw Error ("Unable to read the string content of `" + storagePath + "`: " + e);
        }
      } else {
        throw Error ("`" + hex + "` is an undefined address in `" + store + "`, i.e. `" + storagePath + "` does not exist.");
      }
    }
  }
info
  ...
---
---
unit
  doc
name
  Sha1Store
header
  Storing and retrieving JSON values
---
---
unit
  function
name
  Sha1Store.storeJsonValue
type
  Type.fun ([STORAGE, Json.VALUE, HEX40])
value
  function (store) {
    return function (x) {
      try {
        var code = Json.value2code (x);
      } catch (e) {
        throw Error ("Unable to convert the given value into JSON code: " + e);
      }
      try {
        var hex = storeString (store) (code);
      } catch (e) {
        throw Error ("Unable to write the code of the given JSON value to `" + store + "`: " + e);
      }
      return hex;
    }
  }
info
  ...
---
unit
  function
name
  Sha1Store.retrieveJsonValue
type
  Type.fun ([STORAGE, HEX40, Json.VALUE])
value
  function (store) {
    return function (hex) {
      var storagePath = hex2filepath (store) (hex);
      if (System.Path.isFile (storagePath)) {
        try {
          var str = System.File.read (storagePath);
        } catch (e) {
          throw Error ("Unable to read the JSON value of `" + storagePath + "`: " + e);
        }
        try {
          var x = Json.code2value (str);
        } catch (e) {
          throw Error ("Unable to convert the string content of `" + hex + "` in `" + store + "` to a JSON value: " + e);
        }
        return x;
      } else {
        throw Error ("`" + hex + "` is an undefined address in `" + store + "`, i.e. `" + storagePath + "` does not exist.");
      }
    }
  }
info
  ....
---
---
unit
  doc
name
  Sha1Store
header
  Directories as Hex Tree Records
info
  A __hex tree__ is a `tree (HEX40)` value, i.e. the recursively defined type of all the values which are either
  
  * a `HEX40` numeral, or
  * a record `{k_1: t_1, ..., k_n: t_n}` of hex trees `t_1`, ..., `t_n`.
  
  An example hex tree is given by
  
      .....
  
  Note, that the type of all records `{k_1: t_1, ..., k_n: t_n}` with `t_1`, ..., `t_n` being hex trees is given by `record (tree (HEX40))` in our type notation. For this type we introduce an own name `HEX_TREE_RECORD`.
  
  
  ...............................................
  
  
---
---
unit
  type
name
  Sha1Store.HEX_TREE_RECORD
value
  Type.record (Type.tree (Type.nullify (HEX40)))
info
  ...
---
unit
  function
name
  Sha1Store.directory2hexTreeRecord
type
  Type.fun ([System.DIRECTORY, HEX_TREE_RECORD])
value
  function (root) {

    function hexTree (subDir) {
      var dir = System.Path.joinList ([root, subDir]);
      var idL = System.Dir.list (dir);
      var rec = {};
      for (var i = 0; i < idL.length; i++) {
        var path = System.Path.joinList ([dir, idL [i]]);
        if (System.Path.isFile (path)) {
          rec [idL [i]] = Sha1Store.file2hex (path);
        } else if (System.Path.isDirectory (path)) {
          rec [idL [i]] = hexTree (System.Path.joinList ([subDir, idL [i]]));
        } else {
          rec [idL [i]] = null;
        }
      }
      return Rec.create (rec);
    }

    if (System.Path.isDirectory (root)) {
      return hexTree ('.');
    } else {
      throw Error ("The path `" + root + "` either does not exist or is not a directory.");
    }

  }
info
  ...

  A _hex tree_ (more precisely, a `Type.tree (HEX40)` value) is recursively defined to be either

  * a `HEX40` value (a hexadecimal number string of length 40), or
  * a record `{k_1: t_1, ..., k_n: t_n}`, where each of the `t_1`, ..., `t_n` is a hex tree.

  For example,

      ....

  For example, suppose we have a directory `/home/thomas/MyHomwork` with the following subdirectories (`+`) and files (`*`):
  
      + MyHomework
          + Englisch 
              * CharlesDickens-GreatExpectations.html
              * JulianBarnes-SenseOfAnEnding.html
          + History
              * FrenchRevolution.txt
              * WorldWar2.txt
          + Maths
              * Algebra
                  + GroupTheory.tex
                  + RingTheory.tex
              * Arithmetic.doc
  
  then calling
  
      Sha1Store.directory2hexTreeRecord ('/home/thomas/MyHomework')
      
  returns
  
      { 
        Englisch: 
          { 
            'CharlesDickens-GreatExpectations.html': '85beca341f971884732121a9083b1253f9ce33e4',
            'JulianBarnes-SenseOfAnEnding.html'    : '85beca341f971884732121a9083b1253f9ce33e4' 
          },
        History: 
          { 
            'FrenchRevolution.txt': '53f075c07d0ecd6e02ebeb8be170616c5de070fb',
            'WorldWar2.txt'       : 'c22b5f9178342609428d6f51b2c5af4c0bde6a42'
          },
        Maths: 
          {
            'Algebra': 
              {
                'GroupTheory.tex': 'dd122581c8cd44d0227f9c305581ffcb4b6f1b46',
                'RingTheory.tex' : 'dd122581c8cd44d0227f9c305581ffcb4b6f1b46'
              },
            'Arithmetic.doc': 'dd122581c8cd44d0227f9c305581ffcb4b6f1b46' 
          } 
      }

---
unit
  function
name
  Sha1Store.hexTreeComparison
type
  Type.lambda ([Type.tree (HEX40), Type.tree (HEX40), Type.tree (Type.finite ([-1,0,1,2]))])
value
  function (hexTree1) {
    return function (hexTree2) {
      return Json.comparisonValue (hexTree1) (hexTree2);
    }
  }
info
  ...
---
unit
  function
name
  Sha1Store.directory2hex
type
  Type.fun ([System.DIRECTORY, HEX40])
value
  function (dir) {
    return json2hex (directory2hexTreeRecord (dir));
  }
info
  ...
---
---
unit
  doc
name
  Sha1Store
header
  Storing and retrieving entire directories
---
---
unit
  function
name
  Sha1Store.retrieveFromHexTreeRecord
type
  Type.fun ([STORAGE, HEX_TREE_RECORD, System.DIRECTORY, NULL])
value
  function (storage) {
    return function (hexTreeRec) {
      return function (root) {

        // // // Main part // // //
        // Verify that `root` is an existing empty directory. If `root` does not exist, the directory is created.
        if (System.Path.exists (root)) {
          if (System.Path.isDirectory (root)) {
            if (! System.Dir.isEmpty (root)) {
              throw Error ("The given directory `" + root + "` is not empty.");
            }
          } else {
            throw Error ("The path `" + root + "` exists, but is not a directory.");
          }
        } else {
          try {
            System.Dir.make (root);  // returns `null` on success
          } catch (e) {
            throw Error ("The path `" + root + "` did not exist and could not be created: " + e);
          }
        }
        // call `build`, as defined below
        build (hexTreeRec, root);  // returns `null` on success

        // // // Auxiliary function // // // 
        // build : (record (tree (HEX40)), FILEPATH) --> NULL
        function build (hexTreeRec, root) {
          for (var f in hexTreeRec) {
            var path = System.Path.joinList ([root, f]);
            var hexTree = hexTreeRec [f];
            if (Type.chi (HEX40) (hexTree)) {
              try {
                retrieveFile (storage) (hexTree) (path); // returns `null` on success
              } catch (e) {
                throw Error ('Something went wrong retrieving `' + hexTree + '` as `' + path + '`: ' + e);
              }
            } else if (Type.chi (RECORD) (hexTree)) {
              try {
                System.Dir.make (path);  // returns `null` on success
              } catch (e) {
                throw Error ('Something went wrong creating a new directory `' + path + '`: ' + e);
              }
              build (hexTree, path); // returns `null` on success
            } else if (Type.chi (NULL) (hexTree)) {
              throw Error ("`" + path + "` is neither a file nor a directory (its value in the `HEX_TREE_RECORD` is `null`).");
            } else {
              throw Error ('Ill-defined hex tree value of urtype `' + Urtype.of (hexTree) + '` for `' + path + '`.');
            }
          }
          return null;
        }

      }
    }
  }
info
  `Sha1Store.retrieveFromHexTreeRecord (storage) (hexTreeRec) (root)` attempts to write the `hexTreeRec` into the `root` directory, with the file contents retrieved from the `storage`.
---
unit
  function
name
  Sha1Store.storeDirectory
type
  Type.fun ([STORAGE, System.DIRECTORY, HEX40])
value
  function (storage) {
    return function (root) {

      try {
        hexTree = directory2hexTreeRecord (root);
      } catch (e) {
        throw Error ("Could not generate the `HEX40` tree from `" + root + "`: " + e);
      }
      try {
        store (root);
      } catch (e) {
        throw Error ("Could not store the directory `" + root + "`.");
      }
      return storeJsonValue (storage) (hexTree);

      // store : FILEPATH --> NULL
      function store (path) {
        if (System.Path.isFile (path)) {
          return storeFile (storage) (path);
        } else if (System.Path.isDirectory (path)) {
          var kL = System.Dir.list (path);
          for (var i = 0; i < kL.length; i++) {
            store (System.Path.joinList ([path, kL [i]]));
          }
          return null;
        }
      }

    }
  }
info
  `Sha1Store.storeDirectory (storage) (root)` takes the `root` directory, generates a content table of the entire file system below `root` (which is actually exactly `directory2hexTreeRecord (root)`), stores that content table (as a JSON value) in the `storage`, too, and returns the `HEX40` value of this item.

  For example, if we first store a directory

      > var hex = Sha1Store.storeDirectory (store) ('/home/buc/tmp/MyHomework')
      undefined
      
      > hex
      '796368f105969f34b0eb149c6dea8629db303163'
  
  we can then use this `hex` to retrieve this directory and all its subdirectories and files
  
      > Sha1Store.retrieveDirectory (store) (hex) ('/home/buc/tmp/MyHomework2')
      null

  Afterwards, there is a second directory `MyHomework2` with the same content as the original `MyHomework`.
---
unit
  function
name
  Sha1Store.retrieveDirectory
type
  Type.fun ([STORAGE, HEX40, System.DIRECTORY, NULL])
value
  function (storage) {
    return function (hex) {
      return function (root) {
        try {
          var hexTree = retrieveJsonValue (storage) (hex);
        } catch (e) {
          throw Error ("Something went wrong when retrieving the JSON value at `" + hex + "` from the storage `" + storage + "`: " + e);
        } 
        try {
          retrieveFromHexTreeRecord (storage) (hexTree) (root);  // returns `null` on success
        } catch (e) {
          throw Error ("Something went wrong when retrieving a directory from a `HEX40` tree: " + e);
        }
        return null;
      }
    }
  }
info
  `Sha1Store.retrieveDirectory (store) (hex) (dir)` restores the directory that was stored under `hex` in the given `store` and reconstructs its subdirectories and files under the (new) name `dir`.
  
  For example, if we first store a directory

      > var hex = Sha1Store.storeDirectory (store) ('/home/buc/tmp/MyHomework')
      undefined
      
      > hex
      '796368f105969f34b0eb149c6dea8629db303163'
  
  we can then use this `hex` to retrieve this directory and all its subdirectories and files
  
      > Sha1Store.retrieveDirectory (store) (hex) ('/home/buc/tmp/MyHomework2')
      null

  Afterwards, there is a second directory `MyHomework2` with the same content as the original `MyHomework`.
---
---
unit
  doc
name
  Sha1Store
header
  Miscellaneous other functions
---
---
unit
  function
name
  Sha1Store.hexTreeRecord2hexList
type
  Type.fun ([HEX_TREE_RECORD, Type.list (HEX40)])
value
  function hexTreeRecord2hexList (hexTreeRec) {
    var hexL = [];
    for (var k in hexTreeRec) {
      var hexTree = hexTreeRec [k];
      if (Type.chi (HEX40) (hexTree)) {
        hexL.push (hexTree);
      } else if (Type.chi (RECORD) (hexTree)) {
        hexL = hexL . concat (hexTreeRecord2hexList (hexTree));
      } else if (Type.chi (NULL) (hexTree)) {
        ;
      } else {
        throw Error ("The `HEX_TREE_RECORD` is not well-defined.");
      }
    }
    return hexL;
  }
info
  `Sha1Store.hexTreeRecord2hexList (hexTreeRec)` returns the list of all the `HEX40` values that occur as values in the given `HEX_TREE_RECORD`.
  
  For example, if `hexTreeRec` is given by
  
      { Biology: {},
        Englisch: 
        { 'CharlesDickens-GreatExpectations.html': '85beca341f971884732121a9083b1253f9ce33e4',
          'JulianBarnes-SenseOfAnEnding.html': '85beca341f971884732121a9083b1253f9ce33e4' },
        History: 
        { 'FrenchRevolution.txt': '53f075c07d0ecd6e02ebeb8be170616c5de070fb',
          'WorldWar2.txt': 'c22b5f9178342609428d6f51b2c5af4c0bde6a42',
          pictures: 
            { 'Guillotine.jpg': '36b5f1d0b696e6a93ba9612458a7e342750fe564',
              'Robespierre.jpg': '4235abd0aa05772dea002c7a63b9dd7d603520ff' } },
        Maths: 
        { Algebra: 
            { 'GroupTheory.tex': 'dd122581c8cd44d0227f9c305581ffcb4b6f1b46',
              'RingTheory.tex': 'dd122581c8cd44d0227f9c305581ffcb4b6f1b46' },
          'Arithmetic.doc': 'dd122581c8cd44d0227f9c305581ffcb4b6f1b46' } }
     
  then
  
      > Sha1Store.hexTreeRecord2hexList (hexTreeRec);
      [ '85beca341f971884732121a9083b1253f9ce33e4',
        '85beca341f971884732121a9083b1253f9ce33e4',
        '53f075c07d0ecd6e02ebeb8be170616c5de070fb',
        'c22b5f9178342609428d6f51b2c5af4c0bde6a42',
        '36b5f1d0b696e6a93ba9612458a7e342750fe564',
        '4235abd0aa05772dea002c7a63b9dd7d603520ff',
        'dd122581c8cd44d0227f9c305581ffcb4b6f1b46',
        'dd122581c8cd44d0227f9c305581ffcb4b6f1b46',
        'dd122581c8cd44d0227f9c305581ffcb4b6f1b46' ]

---
unit
  function
name
  Sha1Store.copyStorageEntry
type
  Type.fun ([STORAGE, HEX40, STORAGE, NULL])
value
  function (sourceStore) {
    return function (hex) {
      return function (targetStore) {
        var sourcePath = Sha1Store.hex2filepath (sourceStore) (hex);
        var targetPath = Sha1Store.hex2filepath (targetStore) (hex);
        try {
          System.File.creativeCopy (sourcePath) (targetPath); // returns `null` on success
        } catch (e) {
          throw Error ("Trying to copy the storage entry `" + hex + "` from `" + sourceStore + "` to `" + targetStore + "`: " + e);
        }
      }
    }
  }
info
  ...
---
unit
  function
name
  Sha1Store.numberOfEntries
type
  Type.fun ([STORAGE, INT])
value
  function (store) {
  
    // main part
    return count ([]);
    
    // count (list(STRING)) : INT
    function count (idL) {
      var path = System.Path.joinList ([store].concat (idL));
      if (System.Path.isDirectory (path)) {
        var fL = System.Dir.list (path);
        var n = 0;
        for (var i = 0; i < fL.length; i++) {
          n += count (idL.concat (fL [i]));
        }
        return n;
      } else {
        return 1;
      }
    }
    
  }
info
  `Sha1Store.numberOfEntries (store)` returns the number of addresses (`HEX40` values) in the given `store`.
---
unit
  function
name
  Sha1Store.fullHexList
type
  Type.fun ([STORAGE, Type.list (HEX40)])
value
  function (store) {
  
    // main part
    return hexList ([]);
    
    // hexList (list(STRING)) : list(HEX40)
    function hexList (idL) {
      var path = System.Path.joinList ([store].concat (idL));
      if (System.Path.isDirectory (path)) {
        var fL = System.Dir.list (path);
        var hL = [];
        for (var i = 0; i < fL.length; i++) {
          hL = hL.concat (hexList (idL.concat (fL [i])));
        }
        return hL;
      } else { // i.e. `path` is a file
        return idL.join('');
      }
    }
    
  }
info
  `Sha1Store.fullHexList (store)` returns a list of all the `HEX40` values, i.e. all addresses of the given `store`.
---
unit
  function
name
  Sha1Store.updateStorage
type
  Type.fun ([STORAGE, BRANCHING_SCHEME, STORAGE])
value
  function (store) {
    return function (braSch) {
      var oldBraSch = branchingScheme (store);
      if (Any.equal (braSch) (oldBraSch)) {
        throw Error ("Updating the storage does not make sense, the specified branching scheme [" +
          braSch + "] is the same as that of the given storage `" + store + "`.");
      } else {
        var parentDir = System.Path.dirname (store);
        var ident = storageIdentifier (store);
        var newStore = createStorage (parentDir) (ident) (braSch);
        var hexL = fullHexList (store);
        for (var i = 0; i < hexL.length; i++) {
          copyStorageEntry (store) (hexL [i]) (newStore);
        }
        return newStore;
      }
    }
  }
info
  ...
---
---
// end Sha1Store
---
---






























---
---
// ProjectHouse
---
---
unit
  doc
header
  **Project House management**
info
  # Structure of a Project House
  
  <div style="color:red; font-size: x-large">................................CONTINUEHERE.....................................................</div>

  # Data Types

      PID =                // Project Identifier
        SHA1Store.HEXADECIMAL

      PROJECT = model ({
        // project descriptor
        pid         : PID,
        title       : STRING,
        creator     : Data.Email.ADDRESS,
        dateOfBirth : Data.DATETIME,
        info        : Data.MARKDOWN,
        tags        : list (STRING),   // categorical order for the index
        location    : list (STRING),   // hierarchical order for the archive
        // project backups
        backups     : list (model ({
                              timestamp: DATETIME,
                              subtitle : STRING,
                              hex      : HEX40
                      }))
      })

      ARCHIVE =
        tree (STRING)

      ROOT = System.Directory

  # File Structure of the ProjectHouse Implementation

      + ProjectHouse
        + d14f...
        + 59a3...
        + d799...
        + 1029...
        + .basement
          + cache
          * [cache.html]
          + projects
          + .storage.2-2

---
---
unit
  struct
name
  ProjectHouse
info
  ...
---
---
unit
  doc
name
  ProjectHouse
header
  Types and the Project House file tree
---
---
unit
  type
name
  ProjectHouse.PID
value
  Sha1Store.HEXADECIMAL
---
unit
  type
name
  ProjectHouse.PROJECT
value
  Type.model ({
    // the project description
    pid         : Sha1Store.HEX40,
    title       : STRING,
    creator     : Data.Email.ADDRESS,
    dateOfBirth : Data.DATETIME,
    info        : Data.MARKDOWN,
    tags        : Type.list (STRING),
    location    : Type.list (STRING),
    // the backup instances
    backups     : Type.list (Type.model ({
                              timestamp : Data.DATETIME,
                              subtitle  : STRING,
                              hex       : Sha1Store.HEX40
                            }))
  })
info
  ...
---
unit
  type
name
  ProjectHouse.ARCHIVE
value
  Type.tree (STRING)
---
unit
  type
name
  ProjectHouse.ROOT
value
  Type.subType (System.DIRECTORY) (function (dir) {
    if (Str.endsWith (dir) ('ProjectHouse')) { 
      return '';
    } else {
      return ("The last name in `" + dir + "` is not `ProjectHouse`.");
    }
  })
info
  ...
comment
  Actually, `ROOT` is not only an arbitrary directory, but should end on `ProjectHouse`, along with the other requirements, such as the structure of the `.basement` etc.
---
---
unit
  doc
name
  ProjectHouse
header
  ProjectHouse administration
info
  ...
---
---
unit
  function
name
  ProjectHouse.initProjectHouse
type
  Type.fun ([System.DIRECTORY, ROOT])
value
  function (dir) {
    // check if `dir` is actually a directory
    if (! System.Path.isDirectory (dir)) {
      throw Error ("Cannot create and initialize a new `ProjectHouse` under `" + dir + "`, because that is not a directory.");
    }
    // define the `root` and verify that it does not exist, already
    var root = System.Path.joinList ([dir, 'ProjectHouse']);
    if (System.Path.exists (root)) {
      throw Error ("Cannot initialize a new `ProjectHouse` under `" + dir + "`, because `" + root + "` already exists.");
    }
    // create the `root`
    try { System.Dir.make (root); }
    catch (e) { throw Error ("Cannot create the ProjectHouse root directory: " + e); }
    // create the `.basement`
    var basement = System.Path.joinList ([root, '.basement']);
    try { System.Dir.make (basement); }
    catch (e) { throw Error ("Cannot create the basement `" + basement + "`: " + e); }
    // create the storage
    try { Sha1Store.createDefaultStorage (basement); }
    catch (e) { throw Error ("Cannot create the storage: " + e); }
    // create the `.basement/projects/` directory
    try { System.Dir.make (System.Path.joinList ([basement, 'projects'])); }
    catch (e) { throw Error ("Cannot create the `projects` directory: " + e); }
    // create the `.basement/projects.trash/` directory
    try { System.Dir.make (System.Path.joinList ([basement, 'projects.trash'])); }
    catch (e) { throw Error ("Cannot create the `projects` directory: " + e); }
    // crate the `cache`
    try { System.Dir.make (System.Path.joinList ([basement, 'cache'])); }
    catch (e) { throw Error ("Cannot create the `cache` directory: " + e); }
    // return the root
    return root;
  }
info
  `ProjectHouse.initProjectHouse (dir)` creates a new Project House file structure under the given directory `dir`.
  More precisely, a directory `dir/ProjectHouse` is created, along with the hidden `dir/ProjectHouse/.basement` and the storage etc. in it.
  The return value is the newly created Project House root `dir/ProjectHouse`.
  
  An error is thrown:
  
  * if `dir` is not an existing directory, or
  * if `dir/ProjectHouse` already exists, or
  * if `dir/ProjectHouse` or any of its contents cannot be created.
---
unit
  function
name
  ProjectHouse.allProjectHexValues
type
  Type.fun ([ROOT, PROJECT, Type.list (Sha1Store.HEX40)])
value
  function (root) {
    return function (project) {
      var store = theStorage (root);
      var hexL = [];
      if (project.backups && project.backups.length > 0) {
        for (var i = 0; i < project.backups.length; i++) {
          var hex = project.backups[i].hex;
          hexL.push (hex);
          try {
            var hexTreeRec = Sha1Store.retrieveJsonValue (store) (hex);  // of type `HEX_TREE_RECORD`
          } catch (e) {
            throw Error ("Failed attempt to retrieve the `HEX_TREE_RECORD` stored under `" + hex + "`: " + e);
          }
          hexL = hexL.concat (Sha1Store.hexTreeRecord2hexList (hexTreeRec));
        }
      }
      return hexL;
    }
  }
info
  `ProjectHouse.allProjectHexValues (root) (project)` returns all direct and indirect `HEX40` values that are associated with the given `project`.
  In other words, for each backup in `project.backups`,
  
  1. the `hex` value of the backup is added
  2. the `HEX_TREE_RECORD` that is stored under `hex` is reconstructed
  3. all the `HEX40` values of that `HEX_TREE_RECORD` are added, too.
  
---
unit
  function
name
  ProjectHouse.cleanProjectHouseCopy
type
  Type.fun ([ROOT, System.DIRECTORY, NULL])
value
  function (root) {
    return function (dir) {
      // Verify, that `dir` does not contain a `ProjectHouse`, already. Otherwise, create it.
      var fL = System.Dir.list (dir);
      if (List.member (fL) ('ProjectHouse')) {
        throw Error ("The specified target directory `" + dir + "` already contains a `ProjectHouse`.");
      }
      var newRoot = System.Path.joinList ([dir, 'ProjectHouse']);
      try {
        System.Dir.make (newRoot);
      } catch (e) {
        throw Error ("Problems creating the `" + newRoot + "` directory: " + e);
      }
      // Create the `.basement` with subdirectories for `projects`, `projects.trash`, `cache` and an empty `cache.html` file
      var newBasement = System.Path.joinList ([newRoot, '.basement']);
      try {
        System.Dir.make (newBasement);                                                 // create `ProjectHouse/.basement/`
        System.Dir.make (System.Path.joinList ([newBasement, 'projects']));            // create `ProjectHouse/.basement/projects/`
        System.Dir.make (System.Path.joinList ([newBasement, 'projects.trash']));      // create `ProjectHouse/.basement/projects.trash/`
        System.Dir.make (System.Path.joinList ([newBasement, 'cache']));               // create `ProjectHouse/.basement/cache/`
        System.File.write (System.Path.joinList ([newBasement, 'cache.html'])) ('');   // create `ProjectHouse/.basement/cache.html`
      } catch (e) {
        throw Error ("Something went wront building the new Project House structure: " + e);
      }
      // Create a storage with the same branching scheme as the original Project House
      try {
        var oldStore = theStorage (root);
        var newStore = Sha1Store.createStorage (newBasement) (Sha1Store.storageIdentifier (oldStore)) (Sha1Store.branchingScheme (oldStore));
      } catch (e) {
        throw Error ("Something went wrong creating the new storage: " + e);
      }
      // Copy all active home directories
      var fL = System.Dir.list (root);
      for (var i = 0; i < fL.length; i++) {
        if (Type.chi (Sha1Store.HEX40) (fL [i])) {
          var source = System.Path.joinList ([root, fL [i]]);
          var target = System.Path.joinList ([newRoot, fL [i]]);
          try {
            System.Dir.copy (source) (target);
          } catch (e) {
            throw Error ("Something went wrong copying the content of the active home directory `" + source + "` into `" + target + "`: " + e);
          }
        }
      }
      // Copy all projects and store all their contents
      var source = System.Path.joinList ([root, '.basement', 'projects']);
      var target = System.Path.joinList ([newBasement, 'projects']);
      var hexL = System.Dir.list (source); // each entry is a PID for a project
      // 1. Copy the project files 
      for (var i = 0; i < hexL.length; i++) {
        try {
          System.File.copy (System.Path.joinList ([source, hexL [i]])) (System.Path.joinList ([target, hexL [i]]));
        } catch (e) {
          throw Error ("Something went wrong copying the project file: " + e);
        }
      }
      // 2. Copy the content of all hex values that occur in the projects
      for (var i = 0; i < hexL.length; i++) {
        var project = readProject (root) (hexL [i]);
        var hL = allProjectHexValues (root) (project);
        for (var j = 0; j < hL.length; j++) {
          Sha1Store.copyStorageEntry (oldStore) (hL [j]) (newStore); // returns `null` on success
        }
      }
      // Finish
      return null;
    }
  }
info
  `ProjectHouse.cleanProjectHouseCopy (root) (dir)` copies the entire project house `root` into the target directory `dir`. 
  The new version contains all projects of `root`, excep the projects in the trash.
---
unit
  function
name
  ProjectHouse.emptyTheTrash
type
  Type.fun ([ROOT, INT])
value
  function (root) {
  
    // Reconstruct ProjectHouse components
    var basement        = System.Path.joinList ([root, '.basement']);
    var projectsDir     = System.Path.joinList ([root, '.basement', 'projects']);
    var trashDir        = System.Path.joinList ([root, '.basement', 'projects.trash']);
    var oldStore        = theStorage (root);
    var oldIdentifier   = Sha1Store.storageIdentifier (oldStore);
    var branchingScheme = Sha1Store.branchingScheme (oldStore);
    
    // Check if the trash is empty. And if so, exit the function and return `0`.
    var pidL = System.Dir.list (trashDir);   // the `PID`s of the trashed projects
    if (pidL.length === 0) { return 0; }
    
    // Create a clone of the storage
    var newIdentifier = 'CLONE' + oldIdentifier;
    try {
      var newStore = Sha1Store.createStorage (basement) (newIdentifier) (branchingScheme);
    } catch (e) {
      throw Error ("Could not empty the trash. Something went wrong trying to clone the storage: " + e);
    }
    
    // For each project, read all the `HEX40` values it holds and copy these entries from the old to the new storage
    var pidL = System.Dir.list (projectsDir);  // each list element is a PID, and the according file contains a project
    for (var i = 0; i < pidL.length; i++) {
      var project = readProject (root) (pidL [i]);
      var hexL = allProjectHexValues (root) (project);
      for (var j = 0; j < hexL.length; j++) {
        Sha1Store.copyStorageEntry (oldStore) (hexL [j]) (newStore);
      }
    }
    
    // Delete the old and rename the new store by the old name
    try {
      System.Dir.removeAll (oldStore);
      System.Dir.rename (newStore) (oldStore);
    } catch (e) {
      throw Error ("Something went wrong replacing the old storage by the new clean one: " + e);
    }
    
    // Empty `.basement/projects.trash` and return the number of deleted projects.
    var pidL = System.Dir.list (trashDir);
    for (var i = 0; i < pidL.length; i++) {
      try {
        System.File.remove (System.Path.joinList ([trashDir, pidL [i]]));
      } catch (e) {
        throw Error ("Trouble emptying the trash. " + e);
      }
    }
    return pidL.length;

  }
info
  `ProjectHouse.emptyTheTrash (root)` ultimately deletes all the projects that are in the trash (directory).
  If everything went well, the number of deleted projects is returned. If problems occurred, an error is thrown.
---
unit
  function
name
  ProjectHouse.importProjectHouse
type
  Type.fun ([ROOT, ROOT, INT])
value
  function (root1) {
    return function (root2) {
    
      // Establish a couple of Project House components
      var store1 = theStorage (root1);
      var store2 = theStorage (root2);
      var pidList1 = System.Dir.list (System.Path.joinList ([root1, '.basement', 'projects'])); // list of all PID's in the archive of root1
      var pidList2 = System.Dir.list (System.Path.joinList ([root2, '.basement', 'projects'])); // list of all PID's in the archive of root2
      
      // Verify, that there are no common projects with different content
      for (var i = 0; i < pidList1.length; i++) {
        for (var j = 0; j < pidList2.length; j++) {
          if (pidList1 [i] === pidList2 [j]) {
            var projectFile1 = System.Path.joinList ([root1, '.basement', 'projects', pidList1[i]]);
            var projectFile2 = System.Path.joinList ([root2, '.basement', 'projects', pidList2[j]]);
            var hex1 = Sha1Store.file2hex (projectFile1);
            var hex2 = Sha1Store.file2hex (projectFile2);
            // check if the project files are different
            if (hex1 !== hex2) {
              throw Error ("Cannot import `" + root2 + "` into `" + root1 + "`, because there is a common project `" + pidList1[i] + 
                "` in both Project Houses with different content.");
            }
            // check if active home directories exist an if they are different
            var home1 = System.Path.join (root1) (pidList1 [i]);
            var home2 = System.Path.join (root2) (pidList2 [j]);
            var isActive1 = System.Path.isDirectory (home1);
            var isActive2 = System.Path.isDirectory (home2);
            if (isActive1 || isActive2) {
              if (isActive1 && isActive2) {
                var hex1 = Sha1Store.directory2hex (home1);
                var hex2 = Sha1Store.directory2hex (home2);
                if (hex1 !== hex2) {
                  throw Error ("Cannot import `" + root2 + "` into `" + root1 + "`, because there is a common active project `" + pidList1[i] + 
                    "` with a different active home directory in both Project Houses.");
                }
              } else {
                throw Error ("Cannot import `" + root2 + "` into `" + root1 + "`, because there is a common project `" + pidList1[i] +
                  "`, which is active in `" + (isActive1 ? root1 : root2) + "`, but not in `" + (isActive1 ? root2 : root1) + "`.");
              }
            }
          }
        }
      }
      
      // Copy all projects from `root2` to `root1`, and also their `HEX40` numbers and the according storage content
      // 1. copy the projects
      for (var j = 0; j < pidList2.length; j++) {
        var source = System.Path.joinList ([root2, '.basement', 'projects', pidList2 [j]]);
        var target = System.Path.joinList ([root1, '.basement', 'projects', pidList2 [j]]);
        try {
          System.File.copy (source) (target);
        } catch (e) {
          throw Error ("Something went wrong copying the project file `" + source + "` to `" + target + "`: " + e);
        }
      }
      // 2. copy the content of all their hex values
      for (var j = 0; j < pidList2.length; j++) {
        var project = readProject (root2) (pidList2 [j]);
        var hexL = allProjectHexValues (root2) (project);
        for (var h = 0; h < hexL.length; h++) {
          Sha1Store.copyStorageEntry (store2) (hexL [h]) (store1);
        }
      }
      
      // Copy all active home directories from `root2` to `root1`
      var fL = System.Dir.list (root2);
      for (var i = 0; i < fL.length; i++) {
        if (Type.chi (Sha1Store.HEX40) (fL [i])) {
          var source = System.Path.joinList ([root2, fL [i]]);
          var target = System.Path.joinList ([root1, fL [i]]);
          try {
            System.Dir.copy (source) (target);
          } catch (e) {
            throw Error ("Something went wrong copying the content of the active home directory `" + source + "` to `" + target + "`: " + e);
          }
        }
      }
      
      // Return the number of imported projects
      return pidList2.length;
      
    }
  }
info
  `ProjectHouse.importProjectHouse (root) (import)` copies all (non-trashed) projects from the `import` into the `root` project house and returns the number of all copied projects. All open home directories are copied, too.
  But an error is thrown (and nothing else is done) when there are projects in `root` and `import` with common PIDs.
---
---
unit
  doc
name
  ProjectHouse
header
  Components of a project house
info
  ...
---
---
unit
  function
name
  ProjectHouse.theStorage
type
  Type.fun ([ROOT, Sha1Store.STORAGE])
value
  function (root) {
    var basement = System.Path.joinList ([root, '.basement']);
    var fL = System.Dir.list (basement);
    for (var i = 0; i < fL.length; i++) {
      switch (fL [i]) {
        case 'project':
        case 'cache':
        case 'projects.trash':
          break;
        default:
          var path = System.Path.joinList ([basement, fL [i]]);
          if (Type.chi (Sha1Store.STORAGE) (path)) {
            return path;
          }
      }
    }
    throw Error ("Unable to find the storage of `" + root + "`!");
  }
info
  ...
---
unit
  function
name
  ProjectHouse.theCache
type
  Type.fun ([ROOT, System.DIRECTORY])
value
  function (root) {
    return System.Path.joinList ([root, '.basement', 'cache']);
  }
info
  ...
---
unit
  function
name
  ProjectHouse.theCacheHtmlFile
type
  Type.fun ([ROOT, System.FILEPATH])
value
  function (root) {
    return System.Path.joinList ([root, '.basement', 'cache.html']);
  }
info
  ...
---
unit
  function
name
  ProjectHouse.projectHomeDirectory
type
  Type.Fun ([ROOT, System.DIRECTORY])
value
  function (root) {
    return System.Path.joinList ([root, '.basement', 'projects']);
  }
info
  ...
---
unit
  function
name
  ProjectHouse.trashDirectory
type
  Type.Fun ([ROOT, System.DIRECTORY])
value
  function (root) {
    return System.Path.joinList ([root, '.basement', 'projects.trash']);
  }
info
  ...
---
---
unit
  doc
name
  ProjectHouse
header
  Project getting and setting
info
  ...
---
---
unit
  function
name
  ProjectHouse.projectToCode
type
  Type.lambda ([PROJECT, Sha1Store.Json.CODE])
value
  function (p) {
    return Sha1Store.Json.value2code (p);
  }
info
  For example, suppose `project` is given by

      { pid: 'a5b9b9147637028ef6968c908de9444a3347d0fc',
        creator: 'anonymous@example.com',
        dateOfBirth: Sat Sep 10 2016 03:01:09 GMT+0200 (CEST),
        title: 'A Simple Example Project',
        info: 'This project is just a simple example.',
        tags: [ 'demo' ],
        location: [ 'tmp', 'examples' ] }

  Then

      > ProjectHouse.projectToCode (project)
      '{"pid":"a5b9b9147637028ef6968c908de9444a3347d0fc","creator":"anonymous@example.com","dateOfBirth":"2016-09-10T01:01:09.522Z","title":"A Simple Example Project","info":"This project is just a simple example.","tags":["demo"],"location":["tmp","examples"]}'

      > ProjectHouse.projectFromCode (_)
      { pid: 'a5b9b9147637028ef6968c908de9444a3347d0fc',
        creator: 'anonymous@example.com',
        dateOfBirth: Sat Sep 10 2016 03:01:09 GMT+0200 (CEST),
        title: 'A Simple Example Project',
        info: 'This project is just a simple example.',
        tags: [ 'demo' ],
        location: [ 'tmp', 'examples' ] }

---
unit
  function
name
  ProjectHouse.projectFromCode
type
  Type.lambda ([Sha1Store.Json.CODE, PROJECT])
value
  function (code) {
    var r = Sha1Store.Json.code2value (code);
    r.dateOfBirth = Data.DateTime.fromString (r.dateOfBirth);
    if (r.backups) {
      for (var i = 0; i < r.backups.length; i++) {
        r.backups[i].timestamp = Data.DateTime.fromString (r.backups[i].timestamp);
      }
    }
    return r;
  }
info
  ...
---
unit
  function
name
  ProjectHouse.readProject
type
  Type.fun ([ROOT, PID, PROJECT])
value
  function (root) {
    return function (pid) {
      try {
        var hex40 = fullPid (root) (pid);
      } catch (e) {
        throw Error ("Unable to read the project: " + e);
      }
      var path = System.Path.joinList ([root, '.basement', 'projects', hex40]);
      try {
        var code = System.File.read (path);
      } catch (e) {
        throw Error ("Unable to read `path`: " + e);
      }
      try {
        var value = projectFromCode (code);
      } catch (e) {
        throw Error ("Unable to convert the file content into a `PROJECT`: " + e);
      }
      return value;
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.writeProject
type
  Type.fun ([ROOT, PROJECT, PID])
value
  function (root) {
    return function (project) {
      if (project.pid) {
        var path = System.Path.joinList ([root, '.basement', 'projects', project.pid]);
        try {
          var code = projectToCode (project);
        } catch (e) {
          throw Error ("Unable to convert the given `PROJECT` value into a code string: " + e);
        }
        try {
          System.File.write (path) (code);
        } catch (e) {
          throw Error ("Unable to write the `PROJECT` code to `" + path + "`: " + e);
        }
        return project.pid;
      } else {
        throw Error ("There is no project identifier (`pid` value) defined in the given record.");
      }
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.theProjectList
type
  Type.fun ([ROOT, Type.list (PROJECT)])
value
  function (root) {
    var dir = projectHomeDirectory (root);
    var hexL = System.Dir.list (dir);
    var proL = [];
    for (var i = 0; i < hexL.length; i++) {
      try {
        var path = System.Path.joinList ([dir, hexL [i]]);
        var code = System.File.read (path);
        var proj = projectFromCode (code);
      } catch (e) {
        throw Error ("Cannot read the project from `" + path + "`: " + e);
      }
      proL.push (proj);
    }
    return proL;
  }
info
  ...
---
---
unit
  doc
name
  ProjectHouse
header
  Project management
---
---
unit
  function
name
  ProjectHouse.createPid
type
  Type.fun ([Data.Email.ADDRESS, Data.DATETIME, PID])
value
  function (creator) {
    return function (dateOfBirth) {
      var o = {creator: creator, dateOfBirth: dateOfBirth};
      return Sha1Store.json2hex (o);
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.fullPid
type
  Type.fun ([ROOT, PID, PID])
value
  function (root) {
    return function (hex) {
      if (hex.length === 40) {
        return hex;
      } else {
        var dir = System.Path.joinList ([root, '.basement', 'projects']);
        var hL = System.Dir.list (dir);
        hL = List.filter (function (h) { return Str.startsWith (h) (hex); }) (hL);
        switch (hL.length) {
          case 0:
            throw Error ("No project has a PID that starts with `" + hex + "`.");
          case 1:
            return hL[0];
          default:
            throw Error ("There are " + hL.length + " projects with a PID that starts with `" + hex + "`, namely: `" + hL.join('`, `') + "`.");
        }
      }
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.createProject
type
  Type.fun ([ROOT, Type.modelPlusPlus ({creator: Data.Email.ADDRESS, title: STRING}) ({info: Data.MARKDOWN, tags: STRING, location: STRING}), PID])
value
  function (root) {
    return function (o) {
      // verify that `o` has values for `creator` and `title`
      if (! o.creator) {
        throw Error ("There is no `creator` value defined in the given record.");
      }
      if (! o.title) {
        throw Error ("There is no `title` value defined in the given record.");
      }
      // create the `dateOfBirth` and `pid` value
      var now = Data.DateTime.now (null);
      var pid = createPid (o.creator) (now);
      // convert the `location` and `tags` string into a list
      var locL = locString2locList (o.location || '');
      var tagL = tagString2tagList (o.tags || '');
      // create the `project`
      var project = Rec.create ({
        pid         : pid,            // of type `PID`
        title       : o.title,        // of type `STRING`
        creator     : o.creator,      // of type `Data.Email.ADDRESS`
        dateOfBirth : now,            // of type `Data.DATETIME`
        location    : locL,           // of type `list (STRING)`
        tags        : tagL,           // of type `list (STRING)`
        info        : o.info || '',   // of type `Data.MARKDOWN`
        backups     : []              // of type `list (model ({timestamp: DATETIME, subtitle: STRING, hex: HEX40}))`
      })
      // convert the `project` to code and write it to its project file
      var projectCode = projectToCode (project);
      var path = System.Path.joinList ([root, '.basement', 'projects', pid]);
      try {
        System.File.write (path) (projectCode);
      } catch (e) {
        throw Error ("Unable to write to `" + path + "`: " + e);
      }
      // return the (full) PID of the new project
      return project.pid;
    }
  }
info
  `ProjectHouse.createProject (root) (o)` creates a new project and stores that into its project file of the ProjectHouse, given by `root`.

  * The return value is the (quasi-unique) Project Identifier (`pid`) of the new project, which is automatically generated.
  * The `dateOfBirth` value is also generated automatically.
  * `o` is a string record and must at least contain the `creator` and `title` properties.
  * Optional are the `info` (Markdown), `tags` (e.g. `'uno, dos, tres'`) and `location` (e.g. `'une / deux / trois'`).
  * The `tags` and `location` strings are converted into lists (e.g. `['uno', 'dos', 'tres']` and `['une', 'deux', 'trois']`).
  * A `backups` property with an empty list `[]` value is also added.

  For example,

      > var o = { creator: "somebody@example.com", title: "Yet another simple example project" }
      undefined

      > ProjectHouse.createProject (root) (o)
      '4ec560264f0abfcf447ef785a04b392f7cefc43e'

---
unit
  function
name
  ProjectHouse.updateProject
type
  Type.fun ([ROOT, PID, Type.model ({title: STRING, info: Data.MARKDOWN, tags: STRING, location: STRING}), NULL])
value
  function (root) {
    return function (pid) {
      return function (o) {
      
        // First, recover the `project` from the `pid`
        try {
          var project = readProject (root) (pid);
        } catch (e) {
          throw Error ('Unable to read project `' + pid + '`: ' + e);
        }
        
        // Convert the `tags` string and `location` string into lists, respectively
        var tagL = tagString2tagList (o.tags || '');
        var locL = locString2locList (o.location || '');
        
        // Update the `project`
        project.title     = o.title;
        project.info      = o.info || '';
        project.tags      = tagL;
        project.location  = locL;
        
        // Write the updated `project` back to its file
        try {
          writeProject (root) (project);  // returns `pid`
        } catch (e) {
          throw Error ('Something went wrong writing the updated project `' + pid + '` back to its file: ' + e);
        }
        
        // Return `null`
        return null;
        
      }
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.cloneBackup
type
  Type.fun ([ROOT, PID, Sha1Store.HEX40, PID])
value
  function (root) {
    return function (pid) {
      return function (hex) {
      
        // First recover the `project` belonging to the `pid`
        var project;  // of type `PROJECT`
        try {
          var project = readProject (root) (pid);
        } catch (e) {
          throw Error ("Cannot clone the backup of project `" + pid + "`: " + e);
        }
 
        // Find the backup data in the `project`
        var timestamp; // of type `DATETIME`
        var subtitle;  // of type `STRING`
        if (project.backups && project.backups.length > 0) {
          for (var i = 0; i < project.backups.length; i++) {
            if (project.backups[i].hex === hex) {
              timestamp = project.backups[i].timestamp;
              subtitle  = project.backups[i].subtitle;
            }
          }
          if (! timestamp) {
            throw Error ("Project `" + pid + "` has `" + project.backups.length + "` backups, but none is identified by `" + hex + "`.");
          }
        } else { 
          throw Error ("Cannot clone project `" + pid + "`, at all, it does not have any backups.");
        }
        
        // Create a new `PROJECT`
        var newDateOfBirth =  Data.DateTime.now (null); // of type `Data.DATETIME`
        var newPid         =  createPid (project.creator) (newDateOfBirth);
        var newTitle       =  '[CLONE] ' + project.title;
        var dateString     =  Doc.dateTime2string (timestamp);
        var newSubtitle    =  'This first backup is a clone of the backup of ' + dateString + ' from project ' + pid + '.';
        var newProject     =  Rec.create ({
                                pid         : newPid,
                                title       : newTitle,
                                creator     : project.creator,
                                dateOfBirth : newDateOfBirth,
                                info        : project.info,
                                tags        : project.tags,
                                location    : project.location,
                                backups     : [Rec.create ({
                                                timestamp : newDateOfBirth,
                                                subtitle  : newSubtitle,
                                                hex       : hex
                                              })]
                              }); 
        
        // Write the `newProject` to its file
        try {
          writeProject (root) (newProject); // returns `newPid`, again
        } catch (e) {
          throw Error ("Something went wrong saving the newly cloned project to its project file: " + e);
        }
         
        // Return the newPid
        return newPid;

      }
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.isActiveProject
type
  Type.fun ([ROOT, PID, BOOLEAN])
value
  function (root) {
    return function (pid) {
      var fL = System.Dir.list (root);
      for (var i = 0; i < fL.length; i++) {
        if (Str.startsWith (fL [i]) (pid)) { return true; }
      }
      return false;
    }
  }
info
  ....
---
unit
  function
name
  ProjectHouse.activateProject
type
  Type.fun ([ROOT, PID, PID])
value
  function (root) {
    return function (hex) {
      // first, recover the full `pid`
      try {
        var pid = fullPid (root) (hex);
      } catch (e) {
        throw Error ("Unable to find the full project identifier for `" + hex + "`: " + e);
      }
      // check, if `pid` is not active, already
      if (isActiveProject (root) (pid)) {
        throw Error ("`" + pid + "` is already an active project.");
      }
      // recover the project for `pid`
      try {
        var project = readProject (root) (pid);
      } catch (e) {
        throw Error ("Unable to activate `" + pid + "`: " + e);
      }
      // get the `HEX40` value of the most recent backup, and open the empty directory named `pid` if that does not exist
      var path = System.Path.joinList ([root, pid]);
      if (project.backups && project.backups.length > 0) {
        var hex40 = project.backups[0].hex;
        try {
          Sha1Store.retrieveDirectory (theStorage (root)) (hex40) (path);
        } catch (e) {
          throw Error ("Unable to re-activate the most recent backup of `" + pid + "`: " + e);
        }
      } else { // i.e. there is no backup available;
        try {
          System.Dir.make (path);
        } catch (e) {
          throw Error ("Unable to activate the project and create the `" + path + "` directory.");
        }
      }
      // return the `pid`
      return pid;
    }
  }
info
  ....
---
unit
  function
name
  ProjectHouse.activeProjects
type
  Type.fun ([ROOT, Type.list (PROJECT)])
value
  function (root) {
    var fL = System.Dir.list (root);
    var hexL = [];
    for (var i = 0; i < fL.length; i++) {
      if (Type.chi (Sha1Store.HEX40) (fL[i])) {
        hexL.push (fL [i]);
      } else if (fL[i] !== '.basement') {
        throw Error ("There should only be active projects (i.e. directories named after `HEX40` values) and the `.basement` under `" +
          root + "`. But there is also another file or directory named `" + fL[i] + "`.");
      }
    }
    var projectL = [];
    for (var i = 0; i < hexL.length; i++) {
      try {
        projectL . push (readProject (root) (hexL [i]));
      } catch (e) {
        throw Error ("Something is wrong with the ProjectHouse! `" +
          hexL[i] + "` is a directory for an active project, but the according project file could not be found!");
      }
    }
    return projectL;
  }
info
  ...
---
unit
  function
name
  ProjectHouse.backupProject
type
  Type.fun ([ROOT, PID, STRING, PROJECT])
value
  function (root) {
    return function (pid) {
      return function (subtitle) {

        // first, find the `project` belonging to `pid`
        try {
          var project = readProject (root) (pid);
        } catch (e) {
          throw Error ("Cannot backup `" + pid + "`: " + e);
        }

        // verify, that the project is activated
        var path = System.Path.joinList ([root, project.pid]); // active project path
        if (! System.Path.isDirectory (path)) {
          throw Error ("The project identified by `" + pid + "` is not activated.");
        }
        
        // verify, that the project is modified
        if (! projectIsModified (root) (pid)) {
          throw Error ("The active project `" + pid + "` does not differ from the last backup at " + project.backups[0].timestamp + ".");
        }
        
        // make sure, the active homre directory has only proper items (files and directories, no broken links)
        var improperItems = System.Dir.improperItemList (path);
        if (improperItems.length > 0) {
          var items = improperItems . map (function (i) { return '* `' + i + '`'; }) . join ('\n');
          throw Error ("Not all the items under the active home directory `" + path + "` are proper files or directories, namely:\n\n" + items);
        }

        // make a backup and return the project
        var now = Data.DateTime.now (null);
        var hex = Sha1Store.storeDirectory (theStorage (root)) (path);
        project.backups.unshift (Rec.create ({
          timestamp: now,
          subtitle : subtitle,
          hex      : hex
        }));
        try {
          writeProject (root) (project);
        } catch (e) {
          throw Error ("Unable to write the updated project back into its file: " + e);
        }
        return project;

      }
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.projectIsModified
type
  Type.fun ([ROOT, PID, BOOLEAN])
value
  function (root) {
    return function (pid) {
      // recover the `project`
      try {
        var project = readProject (root) (pid);
      } catch (e) {
        throw Error ("Cannot read the project `" + pid + "`: " + e);
      }
      // verify that the active project `path` is indeed an existing directory
      var path = System.Path.joinList ([root, project.pid]);   // the active home directory
      if (! System.Path.isDirectory (path)) {
        throw Error ("`" + pid + "` is not an active project (`" + path + "` does not exist).");
      }
      // find out, if there is a backup with another `hex` value than the active `path`
      if (project.backups.length === 0) {
        if (System.Dir.list (path) . length > 0) { return true; }   // i.e. the active home directory is not empty
        else                                     { return false; }  // i.e. the active home directory is empty
      } else {
        var currentHex = Sha1Store.directory2hex (path);
        var previousHex = project.backups[0].hex;
        return (currentHex !== previousHex);
      }
    }
  }
info
  An active project is said to be __modified__ in one of the following cases:

  1. There is no backup and the active home directory is not empty.
  2. There are backups and the active home directory differs from the state of the last backup.

  `ProjectHouse.projectIsModified (root) (pid)` does the following:

  1. The project for `pid` is recovered. An error is thrown if that is impossible.
  2. It is verified that the project is active (i.e. an according directory must exist).
  3. If the project does not have a backup, at all, the returned answer is `true`.
     If the project does have a backup, the `HEX40` value of this backup is compared to the `HEX40` value of the active directory. If both are different, the anwer is `true`, otherwise `false`.

---
unit
  function
name
  ProjectHouse.closeProject
type
  Type.fun ([ROOT, PID, PROJECT])
value
  function (root) {
    return function (pid) {
      // recover the `project`
      try {
        var project = readProject (root) (pid);
      } catch (e) {
        throw Error ("Cannot close the project `" + pid + "`: " + e);
      }
      // verify that the active project `path` is indeed an existing directory
      var path = System.Path.joinList ([root, project.pid]);
      if (! System.Path.isDirectory (path)) {
        throw Error ("`" + pid + "` is not an active project (`" + path + "` does not exist).");
      }
      // store and delete the active directory and obtain the `hex` value
      try {
        var hex = Sha1Store.storeDirectory (theStorage (root)) (path);
      } catch (e) {
        throw Error ("Unable to copy the active home directory `" + path + "` into the storage: " + e);
      }
      try {
        System.Dir.removeAll (path);
      } catch (e) {
        throw Error ("Something went wrong removing the active home directory `" + path + "` of the given project: " + e);
      }
      // if the `hex` is other than the last backup, back it up, itself
      if (project.backups.length === 0 || project.backups[0].hex !== hex) {
        project.backups.unshift (Rec.create ({
          timestamp : Data.DateTime.now (null),
          subtitle  : "Automatic backup caused by closing the active modified project.",
          hex       : hex
        }));
      }
      // write the `project` to its file and return its value
      try {
        writeProject (root) (project);
      } catch (e) {
        throw Error ("Unable to write the (updated) project `" + pid + "` back to its file.");
      }
      return project;
    }
  }
info
  `ProjectHouse.closeProject (root) (pid)` stores the active project `pid` and removes its directory. If the active project was modified, an automatic backup is added to the `PROJECT` that belongs to the `pid`, and this updated project is saved in its project file and returned as a value.
---
unit
  function
name
  ProjectHouse.theTagIndex
type
  Type.fun ([ROOT, Type.record (Type.list (PID))])
value
  function (root) {
    var projL = theProjectList (root);
    var tagIndex = {};
    for (var i = 0; i < projL.length; i++) {
      var pid  = projL[i].pid;
      var tagL = projL[i].tags;
      for (var j = 0; j < tagL.length; j++) {
        var tag = tagL[j];
        if (tag in tagIndex) {
          tagIndex [tag] . push (pid);
        } else {
          tagIndex [tag] = [pid];
        }
      }
    }
    return Rec.create (tagIndex);
  }
info
  `ProjectHouse.theTagIndex (root)` returns the tag index of all the projects in the given Project House. This tag index is a record of the form `{tag_1: pidL_1, ..., tag_n: pidL_n}`.
---
unit
  function
name
  ProjectHouse.theCreatorIndex
type
  Type.fun ([ROOT, Type.record ([Data.Email.ADDRESS, Type.list (PID)])])
value
  function (root) {
    var projL = theProjectList (root);
    var creatorIndex = {};
    for (var i = 0; i < projL.length; i++) {
      var pid = projL[i].pid;
      var cre = projL[i].creator;
      if (cre in creatorIndex) {
        creatorIndex [cre] . push (pid);
      } else {
        creatorIndex [cre] = [pid];
      }
    }
    return Rec.create (creatorIndex);
  }
info
  ...
---
unit
  function
name
  ProjectHouse.archiveAddProject
type
  Type.fun ([ARCHIVE, PROJECT, ARCHIVE])
value
  function (archive) {
    return function (project) {

      // local function `rec`, that returns the modified `archive`
      function rec (archive, location, title, pid) {
        if (location.length === 0) {
          archive [pid] = title;
        } else {
          var f = location.shift ();
          if (f in archive) {
            archive [f] = rec (archive [f], location, title, pid);
          } else {
            archive [f] = rec ({}, location, title, pid);
          }
        }
        return archive;
      }

      // main part
      return rec (archive, project.location, project.title, project.pid);

    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.theArchive
type
  Type.fun ([ROOT, ARCHIVE])
value
  function (root) {
    var projL = theProjectList (root);
    var archive = {};
    for (var i = 0; i < projL.length; i++) {
      archive = archiveAddProject (archive) (projL [i]);
    }
    return archive;
  }
info
  ...
---
---
unit
  doc
name
  ProjectHouse
header
  Backup restoration and cache management
---
---
unit
  function
name
  ProjectHouse.selectBackupHex
type
  Type.fun ([ROOT, PID, Data.DATETIME, Sha1Store.HEX40])
value
  function (root) {
    return function (pid) {
      return function (timestamp) {
        // first, try to obtain the `project` that belongs to the `pid`
        try {
          var project = readProject (root) (pid);
        } catch (e) {
          throw Error ("Unable to find the backup for `" + pid + "`: " + e);
        }
        // find the backup that was present at the time of the `timestamp`
        if (project.backups.length === 0) {
          throw Error ("There are no backups of project `" + pid + "`.");
        } else {
          for (var i = 0; i < project.backups.length; i++) {
            var backup = project.backups [i];
            if (backup.timestamp <= timestamp) {
              return backup.hex;
            }
          }
          throw Error ("The earliest backup was made at `" + backup.timestamp +
            "`, so there is no project version available for `" + timestamp + "`.");
        }
      }
    }
  }
info
  `ProjectHouse.selectBackupHex (root) (pid) (timestamp)` looks for the backup of the project `pid` which was the ongoing backup version at the time of `timestamp`. The `hex` value of this backup is the return value.
comment
  This function is probably superfluous!
---
unit
  function
name
  ProjectHouse.clearCache
type
  Type.fun ([ROOT, NULL])
value
  function (root) {
    // Establish the `cache` directory and the cache HTML file `cfile`
    var cache = theCache (root);
    var cfile = theCacheHtmlFile (root);
    // Remove the cache directory content
    try {
      System.Dir.removeAll (cache);  // deletes the whole `cache` and all its content and returns `null`
      System.Dir.make (cache);       // re-creates the empty `cache` directory, again
    } catch (e) {
      throw Error ("Unable to remove the content of the cache directory: " + e);
    }
    // Remove the cache file
    if (System.Path.isFile (cfile)) {
      try {
        System.File.remove (cfile);  // returns `null`
      } catch (e) {
        throw Error ("Unable to delete the cache HTML file: " + e);
      }
    }
    // Everything went fine, so return `null`
    return null;
  }
info
  ...
---
unit
  function
name
  ProjectHouse.restoreBackup
type
  Type.fun ([ROOT, PID, Sha1Store.HEX40, NULL])
value
  function (root) {
    return function (pid) {
      return function (hex) {
        // First of all, clear the cache
        try {
          clearCache (root) // returns `null` on success
        } catch (e) {
          throw Error ("Unable to restore a project backup, because the initial attempt to clear the cache went into trouble: " + e);
        }
        // Recover the `project` from its `pid`
        try {
          var project = readProject (root) (pid);
        } catch (e) {
          throw Error ("Unable to restore a backup for `" + pid + "`: " + e);
        }
        // Write the project description to `html`, which also verifies that `hex` defines a backup in `project`
        try {
          var html = Doc.backup2html (project) (hex);   // throws an error if `hex` is not a backup in `project`
        } catch (e) {
          throw Error ("Unable to restore a backup: " + e);
        }
        // Add a `clone` button to the `html`
        html += Doc.menuButton ({do: "cloneBackup", pid: project.pid, hex: hex}) 
                 ('clone') ('Recover this project backup as a new project, i.e. with a new PID.');
        // Recover the directory from the storage and copy that into the cache
        try {
          Sha1Store.retrieveDirectory (theStorage (root)) (hex) (theCache (root));  // returns `null`
        } catch (e) {
          throw Error ("Could not restore the backup: " + e);
        }
        // Write the `html` to `theCackeHtmlFile`
        var cacheHtmlFile = theCacheHtmlFile (root);
        try {
          System.File.write (cacheHtmlFile) (html);
        } catch (e) {
          throw Error ("Unable to write the information about the restored backup to `" + cacheHtmlFile + "`: " + e);
        }
        // Everything went well and `null` is returned
        return null;
      }
    }
  }
info
  `ProjectHouse.restoreBackup (root) (pid) (hex)` tries to recover the backup `hex` from the project `pid`. If successful,

  * The _Cache HTML File_ is created/overwritten with some HTML info for over the backup project. It also has a `clone` button for the backup restoration.
  * The _cache_ contains the recovered directory and all its contents.
  * `null` is the returned value.

---
unit
  function
name
  ProjectHouse.pasteCacheItem
type
  Type.fun ([ROOT, System.FILEPATH, System.DIRECTORY, NULL])
value
  function (root) {
    return function (source) {
      return function (target) {
        // verify, that `target` is a directory
        if (! System.Path.isDirectory (target)) {
          throw Error ("Unable to paste the cache item to `" + target +"`, because that is not an existing directory.");
        }
        // combine the `root` and `source` to a full `path` and copy the content, depending on whether it is a file or directory
        var path = System.Path.joinList ([root, source]);
        if (System.Path.isFile (path)) {
          try {
            System.File.copy (path) (target); // copies the file `path` into `target`
          } catch (e) {
            throw Error ("Unable to paste the file `" + path + "` into `" + target + "`: " + e);
          }
        } else if (System.Path.isDirectory (path)) {
          try {
            System.Dir.copy (path) (target);  // copies the content of `path` into `target`
          } catch (e) {
            throw Error ("Unable to paste the directory `" + path + "` into `" + target + "`: " + e);
          }
        } else {
          throw Error ("Unable to paste the cache item, `" + path + "` is neither a file nor a directory: " + e);
        }
        return null;
      }
    }
  }
info
  `ProjectHouse.pasteCacheItem (root) (source) (target)` takes the file or directory `source` from the cache and copies it under the `target` directory. If `source` is the empty or root path `""`, the whole cache content is copied.
---
---
unit
  doc
name
  ProjectHouse
header
  Trash, discard and recycle
---
---
unit
  function
name
  ProjectHouse.trashedProjectList
type
  Type.fun ([ROOT, Type.list (PROJECT)])
value
  function (root) {
    var dir = trashDirectory (root);
    var hexL = System.Dir.list (dir);
    var proL = [];
    for (var i = 0; i < hexL.length; i++) {
      try {
        var path = System.Path.joinList ([dir, hexL [i]]);
        var code = System.File.read (path);
        var proj = projectFromCode (code);
      } catch (e) {
        throw Error ("Cannot read the project from `" + path + "`: " + e);
      }
      proL.push (proj);
    }
    return proL;
  }
info
  ...
---
unit
  function
name
  ProjectHouse.trashedProject
type
  Type.fun ([ROOT, PID, PROJECT])
value
  function (root) {
    return function (pid) {
      var dir = trashDirectory (root);
      var hexL = System.Dir.list (dir);
      for (var i = 0; i < hexL.length; i++) {
        if (Str.startsWith (hexL [i]) (pid)) {
          var fullPid = hexL [i];
          for (var j = i + 1; j < hexL.length; j++) {
            if (Str.startsWith (hexL [j]) (pid)) {
              throw Error ("There are several projects in the trash where the PID starts with `" + pid +
                "`, in particular `" + hexL [i] + "` and `" + hexL [j] + "`.");
            }
          }
          try {
            var path = System.Path.joinList ([dir, fullPid]);  // full path to the project file
            var code = System.File.read (path);
            var project = projectFromCode (code);
          } catch (e) {
            throw Error ("Something went wrong when project `" + pid + "` was selected from the trash: " + e);
          }
          return project;
        }
      }
      throw Error ("Unable to project `" + pid + "` in the trash.");
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.discardProject
type
  Type.fun ([ROOT, PID, NULL])
value
  function (root) {
    return function (pid) {
    
      // recover `hex`, which is the full `pid`
      try {
        var hex = fullPid (root) (pid);
      } catch (e) {
        throw Error ('Unable to discard the project `' + pid + '`: ' + e);
      }
      
      // establish three paths
      var active = System.Path.joinList ([root, hex]);                        // path of the active home directory, which should not exist!
      var source = System.Path.joinList ([projectHomeDirectory (root), hex]); // path of the project in the project directory
      var target = System.Path.joinList ([trashDirectory (root), hex]);       // path of the project in the trash
      
      // verify that the project exists and is not active
      if (! System.Path.isFile (source)) {
        throw Error ("Cannot discard the project, `" + hex + "` is not among the existing projects.");
      }
      
      // move the project to the trash
       try {
        System.File.move (source) (target);  // returns `null` on success
      } catch (e) {
        throw Error ("Error moving `" + source + "` to `" + target + "`: " + e);
      }
      
      // empty the cache (otherwise it may contain a backup of a discarded project
      try {
        clearCache (root); // returns `null` on success
      } catch (e) {
        throw Error ("Something went wrong when the cache was cleared: " + e);
      }
      
      // return `null`
      return null;
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.recycleProject
type
  Type.fun ([ROOT, PID, NULL])
value
  function (root) {
    return function (pid) {
      try {
        var project = trashedProject (root) (pid);
      } catch (e) {
        throw Error ('Unable to find `' + pid + '` in the trash: ' + e);
      }
      var source = System.Path.joinList ([trashDirectory (root), project.pid]);
      var target = System.Path.joinList ([projectHomeDirectory (root), project.pid]);
      try {
        System.File.move (source) (target);  // returns `null` on success
      } catch (e) {
        throw Error ("Error moving `" + source + "` to `" + target + "`: " + e);
      }
      return null;
    }
  }
info
  ....
---
---
unit
  doc
name
  ProjectHouse
header
  Exporting projects
---
---
unit
  function
name
  ProjectHouse.exportProject
type
  Type.fun ([ROOT, PID, System.DIRECTORY, NULL])
value
  function (root) {
    return function (project) {
      return function (dir) {
        
        // 1. Verify the existence of `dir`
        if (! System.Path.isDirectory (dir)) {
          throw Error ("The export path `" + dir + "` is not a directory.");
        }
        
        // 2. Determine the `activeHomeDir` and verify that it has no improper items
        var activeHomeDir = System.Path.join (root) (project.pid);
        var projectIsActive = System.Path.isDirectory (activeHomeDir); // of type BOOLEAN
        if (projectIsActive) {
          var improperItems = System.Dir.improperItemList (activeHomeDir);
          if (improperItems.length > 0) {
            var itemListString = improperItems . map (function (path) { return '* ' + path; }) . join ('\n');
            throw Error ("The project cannot be exported, because its active home directory contains improper items:\n" + itemListString); 
          }
        }
        
        // 3. Create `projectDir`, which is `dir` + `pid`
        var projectDir = System.Path.join (dir) (project.pid);
        try {
          System.Dir.make (projectDir);
        } catch (e) {
          throw Error ("Trouble creating the directory `" + projectDir + "` for the project export: " + e);
        }
        
        // 4. Write `project.json` under the given `projectDir`
        try {
          var path = System.Path.join (projectDir) ('project.json');
          System.File.write (path) (projectToCode (project));
        } catch (e) {
          throw Error ("Could not write to `" + path + "`: " + e);
        }
        
        // 5. If the project is active, copy the active home directory under `projectDir` + `home` and create `activeHomeHtml`
        var activeHomeHtml = '<p>The project was not active</p>\n';
        if (projectIsActive) {
          var target = System.Path.join (projectDir) ('home');
          try {
            System.Dir.copy (activeHomeDir) (target);
          } catch (e) {
            throw Error ("Something went wrong copying the active home directory `" + activeHomeDir + "` to `" + target + "`: " + e);
          }
          activeHomeHtml = '<p><a class=menuButton href="home">home</a></p>\n';
        }
        
        // 6. Create `project.html` and write it under `projectDir`
        var backupsHtml = '<p> There are no backups for this project. </p>';
        if (project.backups && project.backups.length > 0) {
          var n = project.backups.length;
          backupsHtml = '<ul>\n';
          for (var i = 0; i < n; i++) {
            var backup = project.backups[i];
            backupsHtml += '<li>'
                        +  (n - i) + '. <a class="menuButton" href="' + backup.hex + '">' + Doc.dateTime2string (backup.timestamp) + '</a> '
                        +  backup.subtitle
                        +  '</li>\n';
          }
          backupsHtml += '</ul>\n';
        }
        var htmlDoc =
          '<html>\n' +
            '<head>\n' +
              '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' +
              '<title>' + project.title + '</title>\n' +
              '<style type="text/css">\n' + Doc.css + '</style>\n' +
            '</head>\n' +
            '<body>\n' +
              '<h1>' + Doc.projectTitleHeader (project) + '</h1>\n' +
              '<h2> The Project Description </h2>\n' +
              '<table class="projectTable">\n' +
                '<tr><th> PID </th>'           + '<td>' + project.pid + '</td></tr>' +
                '<tr><th> title </th>'         + '<td>' + Doc.projectTitle2html (project.title) + '</td></tr>' +
                '<tr><th> creator </th>'       + '<td>' + project.creator + '</td></tr>' +
                '<tr><th> date of birth </th>' + '<td>' + Doc.dateTime2string (project.dateOfBirth) + '</td></tr>\n' +
                '<tr><th> tags </th>'          + '<td>' + tagList2tagString (project.tags) + '</td></tr>' +
                '<tr><th> location </th>'      + '<td>' + locList2locString (project.location) + '</td></tr>' +
                '<tr><th> info </th>'          + '<td>\n' + Data.Markdown.toHtml (project.info) + '\n</td></tr>\n' +
              '</table>\n' +
              '<h2> Active Home Directory </h2>\n' +
              activeHomeHtml +
              '<h2> The Backups </h2>\n' +
              backupsHtml +
            '<body>\n' +
          '</html>' ;
        try {
          var path = System.Path.join (projectDir) ('project.html');
          System.File.write (path) (htmlDoc);
        } catch (e) {
          throw Error ("Could not write to `" + path + "`: " + e);
        }
        
        // 7. Copy the backups into separate directories `projectDir` + `hex`, where `hex` is the backup `HEX40` numeral
        if (project.backups && project.backups.length > 0) {
          for (var i = 0; i < project.backups.length; i++) {
            var hex = project.backups[i].hex;
            var path = System.Path.join (projectDir) (hex);
            try {
              System.Dir.make (path);
              Sha1Store.retrieveDirectory (theStorage (root)) (hex) (path);
            } catch (e) {
              throw Error ("Something went wrong restoring the backup `" + hex + "` under `" + path + "`: " + e);
            }
          }
        }
        
        // 8. Return `null`
        return null;
      }
    }
  }
info
  `ProjectHouse.exportProject (root) (project) (dir)` takes the `project` and writes the project description, the active home directory (if present) and all the backups under the specified `dir`.
  
  The layout of this export is this (`+` denotes a directory and `*` a file):
  
      + dir/                /* the specified target directory `dir` */
        + pid               /* the full PID of the project */
          * project.json    /* the project description as a JSON object */
          * project.html    /* the project description as a HTML file */
          + home            /* the active home directory, in case the project is active */
          + hex3            /* the full `HEX40` numeral of the according backup, containing the backup file tree */
          + hex2            /* the full `HEX40` numeral of the according backup, containing the backup file tree */
          + hex1            /* the full `HEX40` numeral of the according backup, containing the backup file tree */
  
---
unit
  function
name
  ProjectHouse.exportProjectByPid
type
  Type.fun ([ROOT, PID, System.DIRECTORY, NULL])
value
  function (root) {
    return function (pid) {
      return function (dir) {
        try {
          var project = readProject (root) (pid);
        } catch (e) {
          throw Error ("Problem reading the project with PID `" + pid + "`: " + e);
        }
        try {
          exportProject (root) (project) (dir);
        } catch (e) {
          throw Error ("Problem exporting the project: " + e);
        }
        return null;
      }
    }
  }
info
  `ProjectHouse.exportProjectByPid (root) (pid) (dir)` takes the project identified by `pid` and writes the project description, the active home directory (if present) and all the backups under the specified `dir`.
---
unit
  function
name
  ProjectHouse.exportProjectList
type
  Type.fun ([ROOT, Type.list (PROJECT), Data.HTML, System.DIRECTORY, NULL])
value
  function (root) {
    return function (projectL) {
      return function (title) {
        return function (dir) {
      
          // // // makeArchiveTree (ARCHIVE) : HTML // // //
          function makeArchiveTree (archive) {
            var hL = [];
            for (var k in archive) {
              switch (typeof archive[k]) {
                case 'string':  // i.e. `k` is a PID and `archive[k]` is a title
                  hL.push (
                    '<a style="text-decoration:none" href="' + System.Path.join (k) ('project.html') + '">' + 
                      Doc.projectPid2html (k) + ' ' + 
                      Doc.projectTitle2html (archive [k]) + 
                    '</a>'
                  );
                  break;
                case 'object':  // i.e. `k` is a location step and `archive[k]` is a record
                  hL.push (
                    '<span class="projectLocation">' + k + '</span>\n' +
                    makeArchiveTree (archive [k])
                  );
                  break;
                default:
                  throw Error ("The archive contains an illegal item: `" + archive[k] + "`.");
              }
            }
            return ('<ul>\n' + hL . map (function(h) { return '<li>\n' + h + '</li>\n'; }) . join('\n') + '</ul>\n');
          }
          
          // // // main part // // //
          
          // verify that the `projectL` is not an empty list
          if (projectL.length === 0) {
            throw Error ("It does not make sense to export the given project list, because the list is empty.");
          }
          
          // verify that `dir` exists
          if (! System.Path.isDirectory (dir)) {
            throw Error ("The export path `" + dir + "` is not a directory.");
          }
          
          // verify for all active project, that they do not contain improper items
          var improperItems = Doc.improperWorkshopItems (root); // of type `nullify(MARKDOWN)`
          if (improperItems !== null) {
            throw Error ("Unable to export the projects, there are improper items in some of the active home directories:\n\n" + improperItems);
          }
          
          // create the `ProjectHouseExport` directory after verifying that it does not exist already
          var exportDir = System.Path.join (dir) ('ProjectHouseExport');
          if (System.Path.exists (exportDir)) {
            throw Error ("The export directory `" + exportDir + "` already exists.");
          } else {
            try {
              System.Dir.make (exportDir);
            } catch (e) {
              throw Error ("Unable to create the export directory `" + exportDir + "`: " + e);
            }
          }
          
          // create the `index.html` file and write it under `exportDir`
          var htmlDoc = 
            '<html>\n' +
              '<head>\n' +
                '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' +
                '<title> Project House Export </title>\n' +
                '<style type="text/css">\n' + Doc.css + '</style>\n' +
              '</head>\n' +
              '<body>\n' +
                '<h1> Project House </h1>\n' +
                '<p> Exported on ' + Doc.dateTime2string (Data.DateTime.now (null)) + '</p>\n' +
                makeArchiveTree (theArchive (root)) +  // `theArchive(root)` is of type `ARCHIVE`, i.e. `tree(STRING)`
              '<body>\n' +
            '</html>' ;
          try {
            System.File.write (System.Path.join (exportDir) ('index.html')) (htmlDoc);
          } catch (e) {
            throw Error ("Attempt to write `index.html` under `" + exportDir + "`: " + e);
          }
          
          // export each project from the `projectL`
          for (var i = 0; i < projectL.length; i++) {
            var project = projectL [i];
            try {
              exportProject (root) (project) (exportDir);
            } catch (e) {
              throw Error ("Trouble exporting the " + i + "-th project: " + e);
            }
          }
          
          // return `null`
          return null;
        
        }
      }
    }
  }
info
  `ProjectHouse.exportProjectList (root) (projectList) (dir)` writes the `projectList` from the given Project House `root` under the specified directory `dir`.
  
  The layout of this export is this (`+` denotes a directory and `*` a file):
  
      + dir/
        + ProjectHouseExport
          * index.html            /* HTML file with a table of all the projects */
          + pid_1                 /* the full PID of the first project */
            * project.json          /* the project description of the first project as a JSON object */
            * project.html          /* the project description of the first project as a HTML file */
            + home                  /* the active home directory, in case the first project is active */
            + hex1                  /* the full `HEX40` numeral of the according backup, containing the backup file tree */
            + ....                  /* more backups ... */
          + pid_2                 /* the full PID of the second project */
            * project.json          /* the project description of the second project as a JSON object */
            * project.html          /* the project description of the second project as a HTML file */
            + home                  /* the active home directory, in case the second project is active */
            + hex1                  /* the full `HEX40` numeral of the according backup, containing the backup file tree */
            + ....                  /* more backups ... */
          + ...                   / * more PID directories containing projects ... */

---
unit
  function
name
  ProjectHouse.exportProjectsByTag
type
  Type.fun ([ROOT, STRING, System.DIRECTORY, NULL])
value
  function (root) {
    return function (tag) {
      return function (dir) {
        var title = 'Projects with tag <em>' + tag + '</em> on ' + Doc.dateTime2string (Data.DateTime.now (null)) + '\n';
        try {
          var projectL = theProjectList (root);
          projectL = List.filter (function (project) { return List.member (project.tags) (tag); }) (projectL);
          exportProjectList (root) (projectL) (title) (dir);
        } catch (e) {
          throw Error ("Trouble exporting all projects with tag `" + tag + "`: " + e);
        }
      }
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.exportProjectsByCreator
type
  Type.fun ([ROOT, Data.Email.ADDRESS, System.DIRECTORY, NULL])
value
  function (root) {
    return function (email) {
      return function (dir) {
        var title = 'Projects with tag <em>' + tag + '</em> on ' + Doc.dateTime2string (Data.DateTime.now (null)) + '\n';
        try {
          var projectL = theProjectList (root);
          projectL = List.filter (function (project) { return (project.creator === email); }) (projectL);
          exportProjectList (root) (projectL) (title) (dir);
        } catch (e) {
          throw Error ("Trouble exporting all projects created by `" + email + "`: " + e);
        }
      }
    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.exportAllProjects
type
  Type.fun ([ROOT, System.DIRECTORY, NULL])
value
  function (root) {
    return function (dir) {
      var title = 'Export of all projects on ' + Doc.dateTime2string (Data.DateTime.now (null)) + '\n';
      var improperItems = Doc.improperWorkshopItems (root); // of type `nullify (MARKDOWN)`
      if (improperItems !== null) {
        throw Error ("Cannot export all projects, because there are improper items in the workshop:\n\n" + improperItems);
      }
      try {
        var projectL = theProjectList (root);
        exportProjectList (root) (projectL) (title) (dir);
        return null;
      } catch (e) {
        throw Error ("Trouble exporting all projects: " + e);
      }
    }
  }
info
  ...
---
---
unit
  doc
name
  ProjectHouse
header
  Auxiliary functions for converting all values in project descriptors into strings, and vice versa
---
---
unit
  function
name
  ProjectHouse.locList2locString
type
  Type.lambda ([Type.list (STRING), STRING])
value
  function (strL) {
    if (strL.length > 0) { return strL . map (Str.trim) . join ('/'); }
    else                 { return ''; }
  }
info
  For example,

      > ProjectHouse.locList2locString (['  one ', ' two', 'three  '])
      'one/two/three'

      > ProjectHouse.locList2locString (['  one '])
      'one'

      > ProjectHouse.locList2locString ([])
      ''

---
unit
  function
name
  ProjectHouse.locString2locList
type
  Type.lambda ([STRING, Type.list (STRING)])
value
  function (locStr) {
    return List.remove (locStr . replace ('\n', ' ') . split ('/') . map (Str.trim)) ('');
  }
info
  For example,

        > ProjectHouse.locString2locList ("  one / two   / three ")
        [ 'one', 'two', 'three' ]

        > ProjectHouse.locString2locList ("  one and a half / two   / three ")
        [ 'one and a half', 'two', 'three' ]

        > ProjectHouse.locString2locList ("  one and a half /\n two   / \n three ")
        [ 'one and a half', 'two', 'three' ]

        > ProjectHouse.locString2locList ('')
        []

---
unit
  function
name
  ProjectHouse.tagList2tagString
type
  Type.fun ([Type.list (STRING), STRING])
value
  function (strL) {
    if (strL.length > 0) { return strL . map (Str.trim) . join (', '); }
    else                 { return ''; }
  }
info
  For example,

      > ProjectHouse.tagList2tagString ([ 'IMPORTANT', 'Well made', 'easy to use' ])
      'IMPORTANT, Well made, easy to use'

      > ProjectHouse.tagList2tagString ([])
      ''

---
unit
  function
name
  ProjectHouse.tagString2tagList
type
  Type.fun ([STRING, Type.list (STRING)])
value
  function (tagStr) {
    return List.remove (tagStr . replace ('\n', ' ') . split (',') . map (Str.trim)) ('');
  }
info
  For example,

      > ProjectHouse.tagString2tagList ('expensive, useless')
      [ 'expensive', 'useless' ]

      > ProjectHouse.tagString2tagList (" IMPORTANT, Well made, easy to use, ")
      [ 'IMPORTANT', 'Well made', 'easy to use' ]

      > ProjectHouse.tagString2tagList (' English,\nNovember,\n ')
      [ 'English', 'November' ]

      > ProjectHouse.tagString2tagList ('  ')
      []

---
---
unit
  doc
name
  ProjectHouse
header
  HTML document generation
---
---
unit
  struct
name
  ProjectHouse.Doc
---
---
unit
  doc
name
  ProjectHouse.Doc
header
  Auxiliary functions
---
---
unit
  function
name
  ProjectHouse.Doc.linkButton
type
  Type.fun ([RECORD, STRING, STRING, Data.HTML])
value
  function (paramRec) {
    return function (text) {
      return function (toolTipText) {
        var href = "?" + Data.Uri.record2queryString (paramRec);
        return ( '<a href="' + href + '" class="linkButton" title="' + toolTipText + '">' + text + '</a>' );
      }
    }
  }
info
  `ProjectHouse.Doc.linkButton (paramRec) (text) (toolTipText)` returns a `<a>` element, where the `paramRec` is the query part of the `href` value, `text` is the part inside the element, i.e. `<a>text</a>`, and `toolTipText` is the value of the `title` attribute.
  For example,

      ......

---
unit
  function
name
  ProjectHouse.Doc.classifiedButton
type
  Type.fun ([STRING, RECORD, STRING, STRING, Data.HTML])
value
  function (className) {
    return function (paramRec) {
      return function (text) {
        return function (toolTipText) {
          return (
            '<a href="?' + Data.Uri.record2queryString (paramRec) +
            '" class="' + className +
            '" title="' + toolTipText + '">' +
            text +
            '</a>'
          );
        }
      }
    }
  }
info
  `ProjectHouse.Doc. (className) ({k_1: s_1, ..., k_n: s_n}) (text) (tootTipText)`
---
unit
  function
name
  ProjectHouse.Doc.menuButton
type
  Type.fun ([RECORD, STRING, STRING, Data.HTML])
value
  function (paramRec) {
    return function (text) {
      return function (toolTipText) {
        return classifiedButton ('menuButton') (paramRec) (text) (toolTipText);
      }
    }
  }
info
  ....
---
---
unit
  doc
name
  ProjectHouse.Doc
header
  Comparison of two directories
---
---
unit
  function
name
  ProjectHouse.Doc.dirComparison2html
type
  Type.fun ([ROOT, System.DIRECTORY, Sha1Store.HEX_TREE_RECORD, Data.HTML])
value
  function (root) {
    return function (dir) {
      return function (hexTreeRec) {
      
        // // // Auxiliary functions // // //
        
        // arrowSymbol : HTML
        var arrowSymbol = ' &#10159 ';  // Notched Lower Right-Shadowed White Rightwards Arrow
        
        // toHtml : (FILEPATH, HEX_TREE_RECORD, HEX_TREE_RECORD) --> HTML      
        function toHtml (path, htr1, htr2) {
          var fL = List.uniqueConc (Rec.keys (htr1)) (Rec.keys (htr2));
          var hL = [];
          for (var i = 0; i < fL.length; i++) {
            var f = fL[i];                            // file or directory
            var p = System.Path.joinList ([path, f]); // path
            if (! (f in htr1)) {       // i.e. `f` is only in `htr2`
              if (typeof htr2 [f] === 'string') { // i.e. `f` is a file in `htr2`
                hL.push ( '<span class="extinctFile">' + f + '</span>' );
              } else if (htr2 [f] === null) {     // i.e. `f` is a broken link in `htr2`
                hL.push ( '<span class="extinctFile">' + f + ' ' + arrowSymbol + '[BROKEN!]</span>' );
              } else {                            // i.e. `f` is a subdirectory in `htr2`
                hL.push ( '<span class="extinctDirectory">' + f + '</span>\n' + htr2only (htr2 [f]) );
              } 
            } else if (! (f in htr2)) { // i.e. `f` is only in `htr1`
              if (typeof htr1 [f] === 'string') { // i.e. `f` is a file in `htr1`
                hL.push ( '<span class="novelFile">' + f + '</span>' + symLinkInfo (p) );
              } else if (htr1 [f] === null) {     // i.e. `f` is a broken link in `htr1`
                hL.push ( '<span class="novelFile">' + f + ' ' + arrowSymbol + '[BROKEN!]</span>' );
              } else {                            // i.e. `f` is a subdirectory in `htr1`
                hL.push ( '<span class="novelDirectory">' + f + '</span>' + symLinkInfo (p) + '\n' + htr1only (p, htr1 [f]) );
              }
            } else {                  // i.e. `f` is a key in both `htr1` and `htr2`
              if (htr1[f] === null) {  // i.e. `f` is a broken link in `htr1`
                if (htr2[f] === null) { // i.e. `f` is a broken link in `htr1` and `htr2`
                  hL.push ( '<span class="fileName">' + f + ' ' + arrowSymbol + '[BROKEN!]</span>' );
                } else if (typeof htr2[f] === 'string') { // i.e. `f` is broken in `htr1` and a file in `htr2`
                  hL.push ( '<span class="alteredFile">' + f + ' ' + arrowSymbol + '[BROKEN!]</span>' );
                } else {                // i.e. `f` is a broken link in `htr1`, but not in `htr2`
                  hL.push ( '<span class="alteredFile">' + f + ' ' + arrowSymbol + '[BROKEN!]</span>' );
                  hL.push ( htr2only (htr2[f]) );
                }
              } else if (htr2[f] === null) {  // i.e. `f` is a broken link in `htr2`, and a file or directory in `htr1`
                if (typeof htr1[f] === 'string') { // `f` is a file in `htr1`
                  hL.push ( '<span class="alteredFile">' + f + '</span>' );
                } else {                           // `f` is a directory in `htr1`
                  hL.push ( '<span class="alteredDirectory">' + f + '</span>\n' + htr1only (p, htr1 [f]) );
                }
              } else if (typeof htr1[f] === typeof htr2[f]) { // i.e. `f` is either a file or a directory in both `htr1` and `htr2`
                if (typeof htr1 [f] === 'string') {    // i.e. `f` is a file in both `htr1` and `htr2`
                  if (htr1[f] === htr2[f]) {    // i.e. `f` is the same file in `htr1` and `htr2`
                    hL.push ( '<span class="fileName">' + f + '</span>' + symLinkInfo (p) );
                  } else {                      // i.e. `f` is a different file in `htr1` and `htr2`
                    hL.push ( '<span class="alteredFile">' + f + '</span>' + symLinkInfo (p) );
                  }
                } else {                        // i.e. `f` is a directory in both `htr1` and `htr2`
                  var subdir = toHtml (p, htr1[f], htr2[f]);
                  hL.push ( '<span class="directoryName">' + f + '</span>' + symLinkInfo (p) + '\n' + subdir );
                }
              } else {
                if (typeof htr1 [f] === 'string') {    // i.e. `f` is a file in `htr1` and a directory in `htr2`
                  hL.push ( '<span class="novelFile">' + f + '</span>' + symLinkInfo (p) );
                  hL.push ( '<span class="extinctDir">' + f + '</span>\n' + htr2only (htr2 [f]) );
                } else {                        // i.e. `f` is a directory in `htr1` and a file in `htr2`
                  hL.push ( '<span class="novelDir">' + f + '</span>' + symLinkInfo (p) + '\n' + htr1only (p, htr1 [f]) );
                  hL.push ( '<span class="extinctFile">' + f + '</span>' );
                }
              }
            }
          }
          return ( '<ul>\n' + hL . map (function (h) { return '<li>' + h + '</li>\n'; }) . join ('') + '</ul>\n' ) ;
        }
        
        // htr1only : (FILEPATH, HEX_TREE_RECORD) --> HTML
        function htr1only (path, htr1) {
          var fL = Rec.keys (htr1);
          var hL = [];
          for (var i = 0; i < fL.length; i++) {
            var f = fL[i];
            var p = System.Path.joinList ([path, f]);
            if (typeof htr1[f] === 'string') {  // i.e. `f` is file
              hL.push ( '<span class="novelFile">' + f + '</span>' + symLinkInfo (p) );
            } else if (htr1[f] === null) {      // i.e. `f` is a broken link
              hL.push ( '<span class="novelFile">' + f + ' ' + arrowSymbol + '[BROKEN!]</span>' );
            } else {                            // i.e. `f` is directory
              hL.push ( '<span class="novelDirectory">' + f + '</span>' +  symLinkInfo (p) + '\n' + htr1only (p, htr1 [f]) );
            }
          }
          return ( '<ul>\n' + hL . map (function (h) { return '<li>' + h + '</li>\n'; }) . join ('') + '</ul>\n' ) ;
        }
        
        // htr2only : HEX_TREE_RECORD --> HTML
        function htr2only (htr2) {
          var fL = Rec.keys (htr2);
          var hL = [];
          for (var i = 0; i < fL.length; i++) {
            var f = fL[i];
            if (typeof htr2[f] === 'string') {  // i.e. `f` is file
              hL.push ( '<span class="extinctFile">' + f + '</span>' );
            } else if (htr2[f] === null) {      // i.e. `f` is `null`, a broken link
              hL.push ( '<span class="extinctFile">' + f + ' ' + arrowSymbol + '[BROKEN!]</span> ' );
            } else {                            // i.e. `f` is directory
              var subdir = htr2only (htr2 [f]);
              hL.push ( '<span class="extinctDirectory">' + f + '</span>\n' + htr2only (htr2 [f]) );
            }
          }
          return ( '<ul>\n' + hL . map (function (h) { return '<li>' + h + '</li>\n'; }) . join ('') + '</ul>\n' ) ;
        }
        
        // symLinkInfo : FILEPATH --> HTML
        function symLinkInfo (path) {
          if (System.Path.isSymbolicLink (path)) {
            var h = arrowSymbol;
            try {
              var target = System.SymLink.absoluteTarget (path);
              h += target;
            } catch (e) {
              try {
                target = System.SymLink.target (path);
                h += target;
                h += ' <span style="color:red">[BROKEN!]</span>';
              } catch (e) {
                h += 'ERROR: ' + e;
              }
            }
            return h;
          } else {
            return '';
          }
        }
        
        // // // main part // // //
        var path = System.Path.resolve ([root, dir]);         // is an absolute path
        var htr1 = Sha1Store.directory2hexTreeRecord (path);  // is of type `record(tree(HEX40))`, because `path` is a directory and not a file
        var html = toHtml (path, htr1, hexTreeRec);           // returns a `<ul>...</ul>` element
        return (
          '<div class="directoryDiv">\n' +
            '<span class="directoryName">' + dir + '/' + '</span>\n' +
            html +
          '</div>\n'
        );
        
      }
    }
  }
info
  ...
---
---
unit
  doc
name
  ProjectHouse.Doc
header
  The generation of the HTML document
---
---
unit
  function
name
  ProjectHouse.Doc.css
type
  Data.CSS
value
  [ '* { font-family: sans-serif; }'
  
  , 'body { background-color: #CCCCCC; }'

  , 'menu { border: ridge 6pt; position: fixed; right: 10px; top: 0px; padding: 10px; }'

  , '.menuButton, .redButton, .blueButton, .yellowButton, .greyButton, .blackButton, .homeButton '
  , '  { border: solid 2pt black; padding: 5px; margin: 5px; font-size: small; text-decoration: none; color: black; }'
  , '.menuButton { background-color: yellow; }'
  , '.linkButton { border: solid 1pt; background-color: yellow; font-size: xx-small; padding: 2px; margin: 2px; text-decoration: none; }'
  , '.homeButton { border: ridge 10pt; }'
  
  , '#headerProposition { margin-left: 15px; margin-right: 15px; font-size: medium; color: #AAAAAA; }'
  , '#headerRoot { font-family: monospace; color: #DDDDDD; }'

  , '.redSection    , .redButton    { background-color: rgba(255,0,0,0.3); }'
  , '.blueSection   , .blueButton   { background-color: rgba(0,0,255,0.3); }'
  , '.yellowSection , .yellowButton { background-color: rgba(255,255,0,0.3); }'
  , '.greySection   , .greyButton   { background-color: rgba(128,128,128,0.3); }'
  , '.blackSection  , .blackButton  { background-color: black; color: white; }'

  , 'section { padding: 15px; border: ridge 10pt; }'

  , '.homeSection { text-align: center; background-color: rgba(128,128,128,0.3); overflow: auto;  }'
  
  , '.contentCard        { height:600px; width:600px; display:table; table-layout:fixed; float:left; resize:both; margin:25px; border:ridge 10pt; }' // is a <div> element
  , '.contentCardHeader  { height: 25px; width:596px; display:table-row; background-color:#DDDDDD; color:#666666; border:none; }'
  , '.contentCardHeader a { text-decoration:underline; font-size:12px; font-weight: bold; color:#666666; }'
  , '.contentCardContent { height:576px; width:596px; display:table-row; background-color: white; border:none; }'

  , 'h1, h2, h3 { }'
  , '#globalH1 { line-height: 150%; }' // the top `<h1>` element for every page

  , '.projectTable { margin: 10px; }'
  , '.projectTable table { border: solid 1pt; padding: 5px; }'
  , '.projectTable th { padding: 5px; font-weight: normal; font-size: x-small; text-align: right; }'
  , '.projectTable td { border: solid 1pt; padding: 10px; }'

  , '.backupTable { margin: 10px; }'
  , '.backupTable th { text-align: center; font-size: x-small; font-weight: normal; }'
  , '.backupTable td { border: solid 1pt; padding: 7px; }'

  , 'ul { list-style-type: square; padding-left: 30px; }'

  , '.directoryDiv { margin: 10px; padding: 10px; border: ridge 6pt; overflow: auto; resize: both; background-color: #CCCCCC; }'
  , '.directoryTree { }' // is an `<ul>` element
  , '.fileName, .novelFile, .extinctFile, .alteredFile { font-style: monospace; font-weight: bold; }'
  , '.directoryName, .novelDirectory, .extinctDirectory { font-style: italic; font-weight: bold;  }'
  , '.novelFile, .novelDirectory { color: green; }'
  , '.extinctFile, .extinctDirectory { text-decoration: line-through; color: yellow; }'
  , '.alteredFile { color: red; }'

  , '.projectTreeDiv { margin: 10px; border: ridge 6pt; overflow: auto; resize: both; }'
  , '.projectTree { list-style-type: square; }'
  , '.projectLocation { font-weight: bold; color: blue; }'
  , '.projectTitle { font-weight: bold; color: green; }'
  , '.projectSubtitle { }'

  , '.error { color: red; font-size: larger; }'

  , 'p { padding: 10px; }'
  
  , '.buttonBag { line-height: 250%; }' // for the two `<p>` elements generated by `archive2html` that hold the tag buttons and the creator buttons

  , 'code { font-family: monospace; }'
  , '.pid { color: red; font-weight: bold; font-size: larger; }'   // is a `<code>` element

  , 'textarea, input { padding: 5px; font-size: larger; }'
  , 'fieldset { border: solid; margin: 10px; }'
  , 'legend { font-weight: bold; }'
  

  ] . join ('\n')
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.theTofScripts
type
  Type.fun ([System.FILEPATH, Data.HTML])
value
  function (dir) {
    return (
      [
        'newurtype', 'value', 'newtype', 'global', 'estools', 'ecmascript', 'toffee', 'json', 'tofscript', 'data',
        'system', 'program', 'flatcode', 'session', 'unit', 'httptools', 'tofgui', 'tof', 'projecthouse'
      ] . map (
        function (s) {
          return ('<script src="' + System.Path.joinList ([dir, s]) + '" />');
        }
      ) . join ("\n")
    );
  }
info
  ....
comment
  UPDATE THE IMPLEMENTATION!!!
---
unit
  function
name
  ProjectHouse.Doc.theMenu
type
  Type.fun ([ROOT, Data.HTML])
value
  function (root) {
    return (
      '<menu>\n' +
        classifiedButton ('redButton')    ({do: 'workshop'})     ('workshop') ('List all active projects.') +
        classifiedButton ('blueButton')   ({do: 'archive'})      ('archive')  ('Show the archive of all projects and their backups.') +
        classifiedButton ('yellowButton') ({do: 'cache'})        ('cache')    ('Display the cache, where restored backups are buffered.') +
        classifiedButton ('greyButton')   ({do: 'newProject'})   ('new')      ('Define and create a new project.') +
        classifiedButton ('blackButton')  ({do: 'displayTrash'}) ('trash')    ('Display the trash content, i.e. the list of all discarded projects.') +
      '</menu>\n'
    );
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.theDocument
type
  Type.fun ([ROOT, Data.HTML, Data.Html.HTML_DOC])
value
  function (root) {
    return function (body) {
      var title =
        "Caretaker in " + root;
      var header =
        '<h1 id="globalH1">' +
          classifiedButton ('homeButton') ({do: 'home'}) ('Caretaker') ('Go home') +
          '<span id="headerProposition"> in </span>' +
          '<span id="headerRoot">' +
            root +
          '</span>' +
        '</h1>\n';
      return (
        '<!DOCTYPE html>\n' +
        '<html>\n' +
          '<head>\n' +
            '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n' +
            '<title>' +
              title + 
            '</title>\n' +
            '<style type="text/css">\n' + 
              css +
            '</style>\n' +
          '</head>\n' +
          '<body>\n' +
            theMenu (root) +
            header +
            body +
          '</body>\n' +
        '</html>'
      );
    }
  }
info
  `ProjectHouse.Doc.theDocument (root) (body)`

  ....
---
---
unit
  doc
name
  ProjectHouse.Doc
header
  Production of HTML sections, the main parts for the HTML body content
---
---
unit
  function
name
  ProjectHouse.Doc.homePageContent
type
  Type.fun ([NULL, Data.HTML])
value
  function () {
    // the URLs
    var indexURL    = 'http://tofjs.org/program/caretaker-in-a-project-house/index.html';
    var handbookURL = 'http://tofjs.org/program/caretaker-in-a-project-house/Handbook/CaretakerHandbook.html';
    var forumURL    = 'https://groups.google.com/forum/#!forum/caretaker-in-a-project-house';
    // the values of the `title` attributes
    var forumButtonTitle =
      'Go to the caretaker-in-a-project-house forum page, hosted by Google Groups for the latest nieuws, comments and bug reports.';
    var handbookButtonTitle =
      'This handbook is the primary source for users of the caretaker program and GUI.';
    var indexButtonTitle =
      'Go to http://bucephalus.org/Caretaker/index.html for an overview of all resources related to the program.';
    // create and return the content of the home page
    return (
      '<section class="homeSection">\n' +
        '<a class="menuButton" href="' + forumURL    + '" title="' + forumButtonTitle + '">forum</a> ' +
        '<a class="menuButton" href="' + handbookURL + '" title="' + handbookButtonTitle + '">handbook</a> ' +
        '<a class="menuButton" href="' + indexURL    + '" title="' + indexButtonTitle    + '">index</a>' +
      '</section>\n'
     );
  }
info
  `ProjectHouse.Doc.homePageContent (null)` returns the content in the body of the home page.
comment
  In version 1.0.0 there were different links:
  
       var indexURL    = 'http://bucephalus.org/Caretaker/index.html';
       var handbookURL = 'http://bucephalus.org/Caretaker/Handbook/CaretakerHandbook.html';
       var forumURL    = 'https://groups.google.com/forum/#!forum/caretaker-in-a-project-house';
       
---
unit
  function
name
  ProjectHouse.Doc.trash2html
type
  Type.fun ([ROOT, Data.HTML])
value
  function (root) {
    var proL = trashedProjectList (root);
    var html = '<h1> Trash </h1>'
    if (proL.length > 0) {
      html += '<p> Currently, the trash comprises ' + proL.length + ' discarded projects:</p>\n';
      var htmlL = [];
      for (var i = 0; i < proL.length; i++) {
        htmlL . push (
          projectTitleHeader (proL [i]) +
          ' (' + proL[i].creator + ') ' +
          linkButton ({do: "recycleProject", pid: proL[i].pid}) ('recycle') ('Recycle the project from the trash into the archive.')
        );
      }
      html += '<ol>\n'
           +    htmlL . map (function (h) { return ("<li>" + h + "</li>\n"); }) . join ('');
           +  '</ol>\n';
    } else { // i.e. `proL` is empty
      html += '<p> The trash is empty. </p>\n';
    }
    return (
      '<section class="blackSection">\n' +
        html +
      '</section>\n'
    );
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.workshop2html
type
  Type.fun ([ROOT, Data.HTML])
value
  function (root) {
  
    // First, recover the list of active projects
    var proL = activeProjects (root);  // of type `list(PROJECT)`
    
    // Generate the list `hL`, each entry being the HTML for one active project
    var hL = [];
    for (var i = 0; i < proL.length; i++) {
      var project = proL [i];
      var h = '';
      h += projectPid2html (project.pid);
      h += projectTitle2html (project.title);
      if (projectIsModified (root) (project.pid)) {
        h += ' <span style="color: red; font-size: x-small">(modified)</span> ';
      } else {
        h += ' <span style="color: blue; font-size: x-small">(not modified)</span> '
        h += menuButton ({do: 'closeProject', pid: project.pid}) 
              ('close') ('The project is not modified, yet, and can be closed without the need for a backup.');
      }
      h += menuButton ({do: 'displayProject', pid: project.pid}) ('show') ('Display the state of this active project.');
      hL.push (
        '<h2>' + 
          h +
        '</h2>'
      );
    }
    
    // Create the `html`, wrapped in a red section
    var html = '';
    html += '<h1> Workshop </h1>\n';
    html += menuButton ({do: 'workshop'}) ('reload') ('Reload this page to update the latest changes.');
    switch (proL.length) {
      case 0: 
        html += '<p> Currently, there are no active projects. </p>\n';
        break;
      case 1:
        html += '<p> Currently, there is one active project: </p>\n';
        html += hL [0];
        break;
      default:
        html += '<p> Currently, there are ' + proL.length + ' active projects: </p>\n';
        html += hL.join ('');
    }
    html = '<section class="redSection">\n' + html + '</section>\n';
    
    // Add the list of improper workshop items, if they exist, wrapped in another red section
    var improperItems = Doc.improperWorkshopItems (root); // of type `nullify (MARKDOWN)`
    if (improperItems !== null) {
      html += '<section class="redSection">\n' 
           +    Data.Markdown.toHtml (improperItems)
           +  '</section>\n';
    }
    
    // Return the result
    return html;
    
  }
info
  ....
---
unit
  function
name
  ProjectHouse.Doc.archive2html
type
  Type.fun ([ROOT, Data.HTML])
value
  function (root) {
    var body = '';
    body += '<h1> The archive of all projects </h1>\n';

    body += '<h2> The project tree </h2>\n';
    body += projectTree2html (root);

    body += '<h2> The tag index </h2>\n';
    body += '<p class="buttonBag">'
         +    tagIndex2html (root)
         +  '</p>\n';

    body += '<h2> The creator index </h2>\n';
    body += '<p class="buttonBag">'
         +    creatorIndex2html (root)
         +  '</p>\n';

    return (
      '<section class="blueSection">\n' + 
        body + 
      '</section>\n'
    );
    
  }
info
  ....
---
unit
  function
name
  ProjectHouse.Doc.project2html
type
  Type.fun ([ROOT, PID, Data.HTML])
value
  function (root) {
    return function (pid) {

      // First, recover the `project`
      try {
        var project = readProject (root) (pid);
      } catch (e) {
        return ('<p class="error"> Unable to display the project <code>' + pid + '</code>: ' + e);
      }

      // Two boolean values
      var isActive = isActiveProject (root) (project.pid);

      // construct the active home `dir` an `url`
      var dir = System.Path.joinList ([root, project.pid]);  // e.g. `/home/somebody/ProjectHouse/234ac8..`

      // Start to initialize the `html` with the main header
      var html = '<h1> Project ' + projectTitleHeader (project) + '</h1>\n';

      // Add an `reload` button if the project is active, and an `activate` and `discard` button if it is not
      if (isActive) {
        html += '<p>'
             +    menuButton ({do: 'displayProject', pid: project.pid}) ('reload') ('Update the status information.')
             +  '</p>\n';
      } else {
        html += '<p>' 
             +    menuButton ({do: 'activate', pid: project.pid}) ('activate') ('Restore the project in its home directory `' + dir  + '`')
             +    menuButton ({do: 'discardProject', pid: project.pid}) ('discard') ('Discard this project, throw it into the trash.')
             +  '</p>\n';
      }

      // Add the status information if the project is active
      if (isActive) {

        // establish if the active project is modified
        var isModified = projectIsModified (root) (project.pid);

        // add to `html`
        if (isModified) {
          html += '<h2>'
               +  ' The project is active and has been modified  '
               +  menuButton ({do: 'newBackup', pid: project.pid}) ('backup') ('Backup the status of the project')
               +  '</h2>\n';
        } else {
          html += '<h2>'
               +  ' The project is active, but has not been modified, yet '
               +  menuButton ({do: 'closeProject', pid: project.pid}) ('close') ('Close the project and delete its home directory')
               +  '</h2>\n';
        }

        // retrieve the hex tree record for the backup directory; or set to `{}` if there is no backup
        var backupHexTreeRec;
        if (project.backups && project.backups.length > 0) {
          try {
            var hex = project.backups[0].hex;
            backupHexTreeRec = Sha1Store.retrieveJsonValue (theStorage (root)) (hex);
          } catch (e) {
            throw Error ("Unable to retrieve the HEX40 tree for the last backup `" + hex + "`: " + e);
          }
        } else { // i.e. there are no backups
          backupHexTreeRec = {};
        }

        // generate the comparison result for the comparison of the active tree against the backup tree
        var statusBox = dirComparison2html (root) (dir) (backupHexTreeRec);

        // Add the status box
        html += statusBox;

      }

      // Add project description
      html += '<h2>'
           +    ' The Project Description '
           +    ( menuButton ({do: "editProject", pid: project.pid}) ('edit') ('Update the project description.') )
           +  '</h2>\n';
      html += projectDescriptor2htmlTable (project);

      // Add the backups
      if (project.backups && project.backups.length > 0) {
        html += '<h2> The Backups </h2>\n';
        html += projectBackups2htmlTable (project);
      } else {
        html += '<h2> There are no backups for this project. </h2>\n';
      }

      // Wrap everything in a grey section and return the result
      if (isActive) {
        return (
          '<section class="redSection">\n' +
          html +
          '</section>\n'
        );
      } else {
        return (
          '<section class="blueSection">\n' +
          html +
          '</section>\n'
        );
      }

    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.cache2html
type
  Type.fun ([ROOT, Data.HTML])
value
  function (root) {

    // Recover the paths for the cache and cache HTML file
    var cache = theCache (root);
    var cfile = theCacheHtmlFile (root);

    // Recover the `html` content of the `cfile` (which is `""` if the file does not exist)
    var html = '';  // the content of the `cfile`
    if (System.Path.isFile (cfile)) {
      html = System.File.read (cfile);
    }

    // Two boolean values
    var cacheIsEmpty = System.Dir.isEmpty (cache);  // of type `BOOLEAN`
    var cfileIsEmpty = (html === '');               // of type `BOOLEAN`
    
    // Optional `clear` button
    var clearButton = '';
    if (!cacheIsEmpty || !cfileIsEmpty) {
      clearButton = '<p>' 
                  + menuButton ({ do: 'clearCache'}) ('clear') ('Delete the content form the HTML cache file and the cache directory.')
                  + '</p>';
    }

    // Produce and return the HTML for the `cache2html` result
    return (
      '<section class="yellowSection">\n' +
        '<h1> The Cache </h1>\n' +
        clearButton +
        '<h2> Description of the Cache Content </h2>\n' +
        // add the `cfile` content
        ( html
        ? html
        : '<p>... no description available ...</p>\n' ) +
        '<h2> Content of the Cache directory </h2>\n' +
        // add the cache directory if the cache is not empty
        ( cacheIsEmpty
        ? '<p>... the cache directory is empty ...</p>\n'
        : cacheDirectory2html (root) ) +
        clearButton +
      '</section>\n'
    );

  }
info
  ....
---
unit
  function
name
  ProjectHouse.Doc.projectList2htmlList
type
  Type.lambda ([ROOT, STRING, Type.list (PROJECT), Date.HTML])
value
  function (root) {
    return function (headline) {
      return function (projectL) {
        var hL = [];
        for (var i = 0; i < projectL.length; i++) {
          var project = projectL [i];
          var h = projectPid2html (project.pid);
          h += projectTitle2html (project.title);
          if (isActiveProject (root) (project.pid)) {
            h += ' <span style="color:red; font-size: smaller">(active!)</span> ';
          }
          h += linkButton ({do: 'displayProject', pid: project.pid}) ('show') ('Display the declaration and status of this project.');
          hL.push (
            '<li>' + h + '</li>\n'
          );
        }
        return (
          '<section class="blueSection">\n' +
            '<h1>' + headline + '</h1>\n' +
            '<ol>\n' +
              hL . join ('') +
            '</ol>\n' +
          '</section>\n'
        );
      }
    }
  }
info
  ...
---
---
unit
  doc
name
  ProjectHouse.Doc
header
  Construction of special HTML components
---
---
unit
  function
name
  ProjectHouse.Doc.dateTime2string
type
  Type.fun ([Data.DATETIME, STRING])
value
  function (dt) {
    return Data.DateTime.toLocaleDateString (dt) + ' at ' + Data.DateTime.toLocaleTimeString (dt);
  }
info
  `ProjectHouse.Doc.dateTime2string (dt)` returns a localized string version of the given `DATETIME` value.
  For example,
  
      > var dt = Data.DateTime.now (null)
      undefined
      
      > dt
      Fri Oct 28 2016 21:06:16 GMT+0200 (CEST)
      
      > ProjectHouse.Doc.dateTime2string (dt)
      '10/28/2016 at 9:06:16 PM'
 
---
unit
  function
name
  ProjectHouse.Doc.tags2htmlLinks
type
  Type.fun ([Type.list (STRING), Data.HTML])
value
  function (tagL) {
    var htmlL = [];
    for (var i = 0; i < tagL.length; i++) {
      htmlL . push ('<a href="?do=selectProjects&tag=' + tagL[i] + '">' + tagL[i] + '</a>');
    }
    return htmlL . join (', ');
  }
info
  ....
---
unit
  function
name
  ProjectHouse.Doc.location2htmlLinks
type
  Type.fun ([Type.list (STRING), Data.HTML])
value
  function (locL) {
    var strL = [];
    var htmlL = [];
    for (var i = 0; i < locL.length; i++) {
      var str = locL [i];
      strL.push (str);
      htmlL . push ('<a href="?do=selectProjects&location=' + locList2locString (strL) + '">' + str + '</a>');
    }
    return htmlL . join ("/");
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.projectPid2html
type
  Type.lambda ([PID, Date.HTML])
value
  function (pid) { return ' <code class="pid" title="' + pid + '">' + pid.substr(0,4) + '..</code> '; }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.projectTitle2html
type
  Type.lambda ([STRING, Date.HTML])
value
  function (str) { return ' <span class="projectTitle">' + str + '</span> '; }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.projectTitleHeader
type
  Type.lambda ([PROJECT, Date.HTML])
value
  function (project) {
    return projectPid2html (project.pid) + ' ' + projectTitle2html (project.title);
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.projectDescriptor2htmlTable
type
  Type.lambda ([PROJECT, Data.HTML])
value
  function (project) {

    // generate the list `rowL` where each element is the content for a `<tr>...</tr>` tag
    var rowL = [];
    rowL.push (
      '<th> PID </th>' +
      '<td><code class="pid">' + project.pid + '</code></td>'
    );
    rowL.push (
      '<th> title </th>' +
      '<td>' + projectTitle2html (project.title) + '</td>'
    );
    rowL.push (
      '<th> creator </th>' +
      '<td><a href="?do=selectProjects&creator=' + project.creator + '">' + project.creator + '</a></td>'
    );
    rowL.push (
      '<th> date of birth </th>' +
      '<td>' + dateTime2string (project.dateOfBirth) + '</td>'
    );
    rowL.push (
      '<th> tags </th>' +
      '<td>' + tags2htmlLinks (project.tags) + '</td>'
    );
    rowL.push (
      '<th> location </th>' +
      '<td>' + location2htmlLinks (project.location) + '</td>'
    );
    rowL.push (
      '<th> info </th>' +
      '<td>' +
        Data.Markdown.toHtml (project.info) +
      '</td>'
    );

    // wrap the `rowL` elements into `<tr>...</tr>` tags and everything into `<table>...</table>` and return the result
    return (
      '<table class="projectTable">\n' +
        rowL . map (function (row) { return '<tr>' + row + '</tr>'; }) . join ('\n') +
      '</table>\n'
    );

  }
---
unit
  function
name
  ProjectHouse.Doc.projectBackups2htmlTable
type
  Type.lambda ([PROJECT, Data.HTML])
value
  function (project) {
    // check if there are backups, at all
    if ( (! Array.isArray (project.backups)) || project.backups.length === 0 ) {
      return "<p> There are no backups, yet. </p>\n";
    }
    // generate the list `rowL` of `<tr>` elements
    var n = project.backups.length;
    rowL = [];
    for (var i = 0; i < n; i++) {
      var backup = project.backups [i];
      rowL.push (
        '<tr>' +
          '<th> ' + (n - i) + '. </th>' +
          '<td>' + dateTime2string (backup.timestamp) + '</td>' +
          '<td><span class="projectSubtitle">' + backup.subtitle + '</span></td>' +
          '<th>' +
             menuButton ({do: 'restoreProject', pid: project.pid, hex: backup.hex })
               ('restore') ('Recover the full project into the cache.') +
          '</th>' +
       '</tr>\n'
      )
    }
    // wrap the rows into the <table> and return the result
    return (
      '<table class="backupTable">\n' +
        '<tr>' +
          '<th></th>' +
          '<th> Backup date and time </th>' +
          '<th> Subtitle </th>' +
        '<tr>\n' +
        rowL . join ('') +
      '</table>\n'
    );
  }
info
  ....
---
unit
  function
name
  ProjectHouse.Doc.backup2html
type
  Type.fun ([PROJECT, Sha1Store.HEX40, Data.HTML])
value
  function (project) {
    return function (hex) {

      // verify that the `project` has a non-empty `backups` list
      if ( (! Array.isArray (project.backups)) || project.backups.length === 0) {
        throw Error ("The given project does not have any backups, at all.");
      }

      // find the backup record `backup`, a model of `{ timestamp: DATETIME, subtitle: STRING, hex: HEX40 }`
      var i = 0;
      var backup = null;
      while (i < project.backups.length && backup === null) {
        if (project.backups[i].hex === hex) {
          backup = project.backups[i];
        }
        i++;
      }

      // verify, that the `backup` is found
      if (backup === null) {
        throw Error ("Unable to find a backup identified by `" + hex + "` in the given project.");
      }

      // generate the list `rowL` where each element is the content for a `<tr>...</tr>` tag
      var rowL = [];
      rowL.push (
        '<th> Project Identifier (PID) </th>' +
        '<td><code class="pid">' + project.pid + '</code></td>'
      );
      rowL.push (
        '<th> Project Title </th>' +
        '<td>' + projectTitle2html (project.title) + '</td>'
      );
      rowL.push (
        '<th> Project Creator </th>' +
        '<td>' + project.creator + '</td>'
      );
      rowL.push (
        '<th> Project Date of Birth </th>' +
        '<td>' + dateTime2string (project.dateOfBirth) + '</td>'
      );
      rowL.push (
        '<th> Backup Timestamp </th>' +
        '<td>' + dateTime2string (backup.timestamp) + '</td>'
      );
      rowL.push (
        '<th> Backup Subtitle </th>' +
        '<td><span class="projectSubtitle">' + backup.subtitle + '</span></td>'
      );

      // wrap the `rowL` elements into `<tr>...</tr>` tags and everything into `<table>...</table>` and return the result
      return (
        '<table class="projectTable">\n' +
          rowL . map (function (row) { return '<tr>' + row + '</tr>'; }) . join ('\n') +
        '</table>\n'
      );

    }
  }
info
  ....
---
unit
  function
name
  ProjectHouse.Doc.directory2html
type
  Type.fun ([System.DIRECTORY, Data.HTML])
value
  function (dir) {

    // the main function
    try {
      var html = toc (dir);
      html = '<div class="directoryDiv">\n' + html + '</div>\n';
      return html;
    } catch (e) {
      throw Error ("Unable to display the directory in HTML: " + e);
    }
    
    // Right arrow symbol for pointing to the target of symbolic links
    var rightArrow = ' &#10159; '  // Notched Lower Right-Shadowed White Rightwards Arrow
    

    // toc : DIRECTORY --> HTML
    function toc (dir) {
      var fL = System.Dir.list (dir);
      var html = '';
      for (var i = 0; i < fL.length; i++) {
        var path = System.Path.joinList ([dir, fL [i]]);
        if (System.Path.isFile (path)) {
          html += '<li>'
               +  '<span class="fileName">'
               +  fL [i]
               +  '</span>'
               +  '</li>\n';
        } else if (System.Path.isDirectory (path)) {
          html += '<li>'
               +  '<span class="directoryName">'
               +  fL [i]
               +  '</span>'
               +  '<br>\n'
               +  toc (path)
               +  '</li>\n';
        } else if (System.Path.isSymbolicLink (path)) {
          html += '<li>';
          var target = System.SymLink.target (path);
          if (! System.Path.exists (target)) {
            html += fL [i]
                 +  rightArrow
                 +  ' The link is broken, `' + target + '` does not exist!';
          } else if (System.Path.isFile (target)) {
            html += '<span class="fileName">'
                 +  fL [i]
                 +  '</span>'
                 +  rightArrow
                 +  '<span class="fileName">'
                 +  target
                 +  '</span>';
          } else if (System.Path.isDirectory (target)) {
            html += '<span class="directoryName">'
                 +  fL [i]
                 +  '</span>'
                 +  rightArrow
                 +  '<span class="directoryName">'
                 +  target
                 +  '</span>';
          } else {
            throw Error ("The target `" + target + "` of the symbolic link `" + path + "` is neither a file nor a directory.");
          }
          html += '</li>\n';
        } else {
          throw Error ("`" + path + "` is neither a file nor a directory.");
        }
      }
      html = '<ul class="directoryTree">\n'
           +   html
           + '</ul>\n';
      return html;
    }

  }
info
  ...
comment
  I think, the function is obsolete because it is not used anywhere.
---
unit
  function
name
  ProjectHouse.Doc.projectTree2html
type
  Type.fun ([ROOT, Data.HTML])
value
  function (root) {

    // main part
    var archive = theArchive (root);
    return (
      '<div class="projectTreeDiv">\n' +
        makeTree (archive, []) +
      '</div>\n'
    );

    // local recursive function `makeTree : (tree(STRING), list(STRING)) --> HTML`
    function makeTree (o, locL) {
      var hL = []; // list of HTML elements
      // first, add all locations (like directories)
      for (var k in o) {
        if (typeof o[k] === 'string') {  // i.e. `k` is a `PID` and `o[k]` is a `title` string
          if (isActiveProject (root) (k)) {
            hL.push (
              projectPid2html (k) +
              projectTitle2html (o [k]) +
              '<span title="This project is active." style="color:red; font-size: x-small;"> (active!) </span>' +
              linkButton ({do: "displayProject", pid: k}) ('show') ('Display the state of this active project.')  +
              linkButton ({do: "editProject", pid: k}) ('edit') ('Edit the project description.')
            );
          } else { // the project is not active
            hL.push (
              projectPid2html (k) +
              projectTitle2html (o [k]) +
              linkButton ({do: "displayProject", pid: k}) ('show') ('Display the state of this inactive project.')  +
              linkButton ({do: "activate", pid: k}) ('activate') ('Activate this project and recover its home directory.') +
              linkButton ({do: "discardProject", pid: k}) ('discard') ('Throw this project into the trash.') +
              linkButton ({do: "editProject", pid: k}) ('edit') ('Edit the project description.')
            )
          }
        }
      }
      // second, add all projects (like files)
      for (var k in o) {
        if (typeof o[k] === 'object') {  // i.e. `k` is an ordinary `STRING` (location step) and `o[k]` is a record
          hL.push (
            '<span class="projectLocation">' + locList2locString (locL.concat (k)) + '</span> <br>' +
            makeTree (o [k], locL.concat (k))
          );
        }
      }
      // wrap the `hL` list of `<li>` elements into a `<ul>` element
      return (
        '<ul class="projectTree">\n' +
          hL . map (function (h) { return ('<li>' + h + '</li>\n'); }) . join ('') +
        '</ul>\n'
      );
    }

  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.tagIndex2html
type
  Type.fun ([ROOT, Data.HTML])
value
  function (root) {
    var tagIndex = theTagIndex (root);  // of type `record (list (PID))`
    var body = '';
    for (var k in tagIndex) {
      var text  = '<strong>' + k + '</strong> (' + tagIndex[k].length + ')';
      var title = 'List all ' + tagIndex[k].length + ' projects tagged ' + k;
      body += menuButton ({do: "selectProjects", tag: k}) (text) (title);
    }
    return body;
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.creatorIndex2html
type
  Type.fun ([ROOT, Data.HTML])
value
  function (root) {
    var creatorIndex = theCreatorIndex (root); // of type `record ([Email.ADDRESS, list (PID)])`
    var html = '';
    for (var c in creatorIndex) {
      var text  = '<strong>' + c + '</strong> (' + creatorIndex[c].length + ')';
      var title = 'List all ' + creatorIndex[c].length + ' projects created by ' + c;
      html += menuButton ({do: "selectProjects", creator: c}) (text) (title);
    }
    return html;
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.cacheDirectory2html
type
  Type.fun ([ROOT, Data.HTML])
value
  function (root) {

    // the main function
    var cache = theCache (root);
    try {
      var html = toc (cache);
      html = '<div class="directoryDiv">\n' + html + '</div>\n';
      return html;
    } catch (e) {
      throw Error ("Unable to display the cache directory: " + e);
    }

    // toc : DIRECTORY --> HTML
    function toc (dir) {
      var fL = System.Dir.list (dir);
      var html = '';
      for (var i = 0; i < fL.length; i++) {
        var path = System.Path.joinList ([dir, fL [i]]);
        if (System.Path.isFile (path)) {
          html += '<li>'
               +  '<span class="fileName">'
               +  fL [i]
               +  '</span>'
               +  '</li>\n';
        } else if (System.Path.isDirectory (path)) {
          html += '<li>'
               +  '<span class="directoryName">'
               +  fL [i]
               +  '</span>'
               +  '<br>\n'
               +  toc (path)
               +  '</li>\n';
        } else {
          throw Error ("`" + path + "` is neither a file nor a directory.");
        }
      }
      html = '<ul class="directoryTree">\n'
           +   html
           + '</ul>\n';
      return html;
    }

  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.improperWorkshopItems
type
  Type.fun ([ROOT, Type.nullify (Data.MARKDOWN)])
value
  function (root) {
    var proL = activeProjects (root);
    var md = ''; // of type `MARKDOWN`
    for (var i = 0; i < proL.length; i++) {
      var project = proL[i];
      var homeDir = System.Path.join (root) (project.pid);
      var pathL = System.Dir.improperItemList (homeDir);
      if (pathL.length > 0) {
        md += '## `' + project.pid.substr(0,4) + '..` ' + project.title + '\n\n';
        md += pathL . map (function (path) { return '* `' + path + '`'; }) . join ('\n');
        md += '\n\n';
      }
    }
    if (md === '') {
      return null; 
    } else { 
      return (
        '# Improper items\n\n' + 
        md
      );
    }
  }
info
  `ProjectHouse.doc.improperWorkshopItems (root)` returns a (Markdown) 
  list for all improper items in any of the active home directories.
  An __improper item__ is any item under the active home directory that is neither a file nor a directory, e.g. a broken link.
  If there are not improper items, the return value is `null`.
---
---
unit
  doc
name
  ProjectHouse.Doc
header
  HTML forms
---
---
unit
  function
name
  ProjectHouse.Doc.newBackupForm
type
  Type.fun ([ROOT, PID, Data.HTML])
value
  function (root) {
    return function (pid) {

      try {
        var project = readProject (root) (pid);
      } catch (e) {
        throw Error ("Unable to create the new backup: " + e);
      }

      var table = (
        '<table class="projectTable">\n' +
          '<tr>' +
            '<th>pid</th>' +
            '<td><code class="pid">' + project.pid + '</code></td>' +
          '</tr>' +
          '<tr>' +
            '<th>title</th>' +
            '<td><strong>' + project.title + '</strong></td>' +
          '</tr>' +
        '</table>\n'
      );

      var action = "?do=backupProject&pid=" + project.pid;
      var form = (
        '<form method="POST" action="' + action + '">\n' +
          '<p>\n' +
            'Subtitle (add a message or some info just for this backup): <br>\n' +
            '<textarea name="subtitle" rows=2 cols="90"></textarea> <br>\n' +
            '<input type="submit" value="create backup">\n' +
          '</p>\n' +
        '</form>\n'
      );

      return (
        '<section class="redSection">\n' +
          '<h1> Create a Project Backup </h1>\n' +
          '<h2> Given Project </h2>\n' +
          table +
          '<h2> New Backup </h2>\n' +
          form +
        '</section>\n'
      );

    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.project2htmlForm
type
  Type.fun ([Type.finite (['createProject', 'updateProject']), PROJECT, Data.HTML])
value
  function (doValue) {
    return function (o) {

      // Reconstruct the project values
      var pid         = o.pid         || '';      // eventually of type `HEX40`
      var title       = o.title       || '';      // of type `STRING`
      var creator     = o.creator     || '';      // of type `Data.Email.ADDRESS`
      var dateOfBirth = o.dateOfBirth      ;      // of type `Data.DATETIME`
      var info        = o.info        || '';      // of type `Data.MARKDOWN`
      var tags        = o.tags        || [];      // of type `list (STRING)`
      var location    = o.location    || [];      // of type `list (STRING)`

      // Define the HTML element for each of the project values
      var pidElem, titleElem, creatorElem, dobElem, tagsElem, locElem, infoElem;
      // PID
      if (pid) {
        pidElem  = '<input type="hidden" name="pid" value="' + pid + '"> ';
        pidElem += '<code class="pid">' + pid + '</code>'; 
      } else {
        pidElem = '... will be generated automatically ...'; 
      }
      // Title
      var titleElem = '<input type="text" name="title" size="80" value="' + title + '">';
      // Creator
      if (creator) {
        creatorElem  = '<input type="hidden" name="creator" value="' + creator + '"> ';
        creatorElem += creator;
      } else {
        creatorElem =  '<input type="text" name="creator" size="80">';
      }
      // Date of Birth
      if (dateOfBirth) {
        dobElem = dateTime2string (dateOfBirth);
        dobElem += ' <input type="hidden" name="dateOfBirth" value="' + dateOfBirth + '">';
      } else {
        dobElem = '... will be generated automatically ...';
      }
      // Tags
      tagsElem = '<textarea name="tags" rows="2" cols="80">' + tagList2tagString(tags) + '</textarea>';
      // Location
      locElem = '<textarea name="location" rows="2" cols="80">' + locList2locString(location) + '</textarea>';
      // Info
      infoElem = '<textarea name="info" rows="8" cols="100">' + info + '</textarea>';

      // Construct and return the `<form>`
      return ([
        '<form method="POST" action="?do=' + doValue + '">',
          '<fieldset>',
            '<legend> Project Identifier (PID): </legend>',
            pidElem,
          '</fieldset>',
          '<fieldset>',
            '<legend> Title: </legend>',
            titleElem,
          '</fieldset>',
          '<fieldset>',
            '<legend> Creator: </legend>',
            'Email address: <br>',
            creatorElem,
          '</fieldset>',
          '<fieldset>',
            '<legend> Date of Birth: </legend>',
            dobElem,
          '</fieldset>',
          '<fieldset>',
            '<legend> Location: </legend>',
            'Separate the location steps by slashes `/` <br>',
            locElem,
          '</fieldset>',
          '<fieldset>',
            '<legend> Tags: </legend>',
            'Separate the tags by commata `,` <br>',
            tagsElem,
          '</fieldset>',
          '<fieldset>',
            '<legend> Info: </legend>',
            'Use <a href="https://en.wikipedia.org/wiki/Plain_text">plain text</a> ',
            'or <a href="https://en.wikipedia.org/wiki/Markdown">Markdown</a> <br>',
            infoElem,
          '</fieldset>',
          '<br>',
          '<input type="submit" value="submit">',
        '</form>\n'
      ].join ('\n'));

    }
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.newProjectForm
type
  Type.fun ([NULL, Data.HTML])
value
  function () {
    var form = project2htmlForm ('createProject') ({});
    return (
      '<section class="greySection">\n' +
        '<h1> Create a new project </h1>\n' +
        '<p> The <strong>Title</strong> and <strong>Creator</strong> entries are mandatory. All other form fields are optional. </p>\n' +
        form +
      '</section>\n'
    );
  }
info
  ...
---
unit
  function
name
  ProjectHouse.Doc.projectEditorForm
type
  Type.fun ([ROOT, PID, Data.HTML])
value
  function (root) {
    return function (pid) {
      // Recover the `project` for the given `pid`
      try {
        var project = readProject (root) (pid);
      } catch (e) {
        throw Error ("Unable to read the project for `" + pid + "`: " + e);
      }
      // Produce the HTML `form` for this `project`
      var form = project2htmlForm ('updateProject') (project);
      // return the result
      return (
        '<section class="blueSection">\n' +
          '<h1> Edit project </h1>\n' +
          form +
        '</section>\n'
      );
    }
  }
info
  ....
---
---
unit
  doc
name
  ProjectHouse
header
  The main function
---
---
unit
  function
name
  ProjectHouse.main
type
  Type.fun ([ROOT, Type.record (STRING), Data.HTML])
value
  function (root) {
    return function (o) {

      // Two functions for easy document generation; both of tyep `HTML --> HTML_DOC`
      htmlDoc  = Doc.theDocument (root);   // of type `HTML --> HTML_DOC`
      errorDoc = function (html) { return htmlDoc ('<div class="error">' + Data.Markdown.toHtml (html) + '</div>\n'); };

      // First cover the case that `o.do` is undefined
      if (! o.do) {     // i.e. there is no `do` property in `o`; the result is the same as for `{do: home}`
        return htmlDoc (Doc.homePageContent (null));
      }

      // generate the content depending on the `o.do` value
      switch (o.do) {

        // { do: "home"}
        case 'home':
          return htmlDoc (Doc.homePageContent (null));

        // { do: "displayProject", pid: PID }
        case 'displayProject':
          if (o.pid) {
            return htmlDoc (Doc.project2html (root) (o.pid));
          } else {
            return htmlDoc ('<p class="error"> Unable to display a project, there is no `pid` value defined. </p>');
          }

        // { do: "newProject"}
        case 'newProject':
          return htmlDoc (Doc.newProjectForm (null));

        // { do: "createProject", creator: Email.ADDRESS, title: STRING, info: MARKDOWN, tags: list(STRING), location: list(STRING) }
        case 'createProject':
          try {
            var pid = createProject (root) (o);
            return htmlDoc (
              '<p> A new project with project identifier (pid) ' + Doc.projectPid2html (pid) + ' is created. ' +
                Doc.menuButton ({do: "displayProject", pid: pid}) ('show') ('Display the project') +
                Doc.menuButton ({do: "activate", pid: pid}) ('activate') ('Activate the project and open its home directory.') +
              '</p>'
            );
          } catch (e) {
            return errorDoc ('Unable to create the new project: ' + e);
          }

        // { do: "editProject", pid: PID }
        case 'editProject':
          if (o.pid) {
            return htmlDoc (Doc.projectEditorForm (root) (o.pid));
          } else {
            return errorDoc ('There is no `pid` value defined.');
          }

        // { do: "updateProject", pid: PID, title: STRING, info: MARKDOWN, tags: STRING, location: STRING }
        case 'updateProject':
          if (o.pid) {
            try {
              updateProject (root) (o.pid) (o);  // returns `null`
              var project = readProject (root) (o.pid);
              return htmlDoc (Doc.project2html (root) (o.pid));
            } catch (e) {
              return errorDoc ('Something went wrong updating the project `' + o.pid + '`: ' + e);
            }
          } else {
            return errorDoc ('There is no `pid` value defined.');
          }

        // { do: "workshop" }
        case 'workshop':
          return htmlDoc (Doc.workshop2html (root));

        // { do: "newBackup", pid: PID}
        case 'newBackup':
          if (o.pid) { return htmlDoc (Doc.newBackupForm (root) (o.pid)); }
          else       { return errorDoc ('There is no `pid` value defined in the URL.'); }

        // { do: "backupProject", pid: PID, subtitle: STRING }
        case 'backupProject':
          if      (! o.pid)      { return errorDoc ('There is no `pid` value defined in the URL.'); }
          else if (! o.subtitle) { return errorDoc ('There is no `subtitle` defined for the backup.'); }
          else {
            try {
              var project = backupProject (root) (o.pid) (o.subtitle);
              return htmlDoc (
                '<p> A new backup for ' + Doc.projectTitleHeader (project) + ' was created. ' +
                  Doc.menuButton ({do: "displayProject", pid: project.pid}) ('show') ('Display the project') +
                '</p>'
              );
            } catch (e) {
              return errorDoc ('Something went wrong creating the backup: ' + e);
            }
          }

        // { do: "closeProject", pid: PID }
        case 'closeProject':
          if (o.pid) {
            try {
              var project = closeProject (root) (o.pid);
            } catch (e) {
              return errorDoc ('Something went wrong when closing the project `' + o.pid + '`: ' + e);
            }
            return htmlDoc (
              '<p>' + Doc.projectTitleHeader (project) + ' was closed, its active home directory is removed. ' +
                Doc.menuButton ({do: "displayProject", pid: project.pid}) ('show') ('Display the project') +
              '</p>\n'
            );
          } else {
            return errorDoc ('There is no `pid` value defined.');
          }

        // { do: "activate", pid: PID }
        case 'activate':
          if (o.pid) {
            try {
              var pid = activateProject (root) (o.pid);
            } catch (e) {
              return errorDoc ( 'Unable to activate the project: ' + e );
            }
            return htmlDoc (
              '<p> Project ' + Doc.projectPid2html (pid) + ' is activated. ' + 
                Doc.menuButton ({do: "displayProject", pid: pid}) ('show') ('Display the active project.') +
              '</p>\n'
            );
          } else {
            return errorDoc ( 'The <code>do</code> value is <code>&quot;activate&quot;</code> and the <code>pid</code> value is missing.' );
          }

        // { do: "selectProjects", creator: Data.Email.ADDRESS }
        // { do: "selectProjects", tag: STRING }
        case 'selectProjects':
          var projectL = theProjectList (root);
          if (o.creator) {
            projectL = List.filter (function (project) { return project.creator === o.creator; }) (projectL);
            var headline = 'All projects where creator is ' + o.creator + ':';
            return htmlDoc (Doc.projectList2htmlList (root) (headline) (projectL));
          } else if (o.tag) {
            projectL = List.filter (function (project) { return List.member (project.tags) (o.tag); }) (projectL);
            var headline = 'All projects tagged &quot;' + o.tag + '&quot;:';
            return htmlDoc (Doc.projectList2htmlList (root) (headline) (projectL));
          } else {
            return errorDoc (
              'The `do` value is `selectProjects`, but there is neither a `creator` nor a `tag` property, and other options are not available.'
            );
          }

        // { do: "restoreProject", pid: PID, hex: HEX40 }
        case 'restoreProject':
          if (! o.pid) {
            return errorDoc ('There is no `pid` value defined.');
          } else if (! o.hex) {
            return errorDoc ('There is no `hex` value for the selected backup defined.');
          } else {
            try {
              restoreBackup (root) (o.pid) (o.hex);  // returns `null` on success
            } catch (e) {
              return errorDoc ('Unable to restore the backup: ' + e);
            }
            return htmlDoc (Doc.cache2html (root));
          }

        // { do: "cloneBackup", pid: PID, hex: HEX40 }
        case 'cloneBackup':
          try {
            var newPid = cloneBackup (root) (o.pid) (o.hex);
          } catch (e) {
            return errorDoc (
              "Cloning the backup `" + Doc.projectPid2html (o.hex) + "` of project `" + Doc.projectPid2html (o.pid) + "` threw an error: " + e
            );
          }
          return htmlDoc (
            '<p>' +
              ' This project ' + Doc.projectPid2html (newPid) + 
              ' is created as a clone of a backup from project ' + Doc.projectPid2html (o.pid) + ' ' +
              Doc.menuButton ({do: "displayProject", pid: newPid}) ('show') ('Display the new project') +
            '</p>\n'
          );

        // { do: "archive" }
        case 'archive':
          return htmlDoc (Doc.archive2html (root));

        // { do: "tagIndex" }
        case 'tagIndex':
          return htmlDoc (Doc.tagIndex2html (root));

        // { do: "cache" }
        case 'cache':
          return htmlDoc (Doc.cache2html (root));

        // { do: "clearCache" }
        case 'clearCache':
          try {
            clearCache (root);
            return htmlDoc (Doc.cache2html (root));
          } catch (e) {
            return errorDoc ('Something went wrong when the cache was cleared: ' + e);
          }
          
        // { do: "displayTrash" }
        case 'displayTrash':
          return htmlDoc (Doc.trash2html (root));
        
        // { do: "discardProject", pid: PID }
        case 'discardProject':
          if (o.pid) {
            try {
              var project = readProject (root) (o.pid);
            } catch (e) {
              return errorDoc ('Problems reading the `' + o.pid + '` project: ' + e);
            }
            return htmlDoc (
              '<h1> Discard ' + Doc.projectTitleHeader (project) + '</h1>\n' +
              '<p>' +
                'Are you sure, you want to discard and trash this project: ' +
                Doc.menuButton ({do: "reallyDiscardProject", pid: o.pid}) ('discard') ('Really discard the project.') +
              '</p>\n'
            );
          } else {
            return errorDoc ('The `pid` value is missing.');
          }
        
        // { do: "reallyDiscardProject", pid: PID }
        case 'reallyDiscardProject':
          if (o.pid) {
            try {
              discardProject (root) (o.pid); // returns `null` on success
            } catch (e) {
              return errorDoc ('Something went wrong when `' + pid + '` was discarded: ' + e);
            }
            return htmlDoc ( '<p>' + Doc.projectPid2html (o.pid) + ' has been discarded and moved into the trash.</p>\n' );
          } else {
            return errorDoc ('The `pid` value is missing.');
          }
        
        // { do: "recycleProject", pid: PID }
        case 'recycleProject':
          if (o.pid) {
            try {
              recycleProject (root) (o.pid); // returns `null` on success
            } catch (e) {
              return errorDoc ('Something went wrong when `' + pid + '` was recycled: ' + e);
            }
            return htmlDoc (
              '<p> Project ' + Doc.projectPid2html (o.pid) + ' was successfully recycled. ' +
                Doc.menuButton ({do: "displayProject", pid: o.pid}) ('show') ('Display the project') +
              '</p>'
            );
          } else {
            return errorDoc ('The `pid` value is missing.');
          }

        // { do: ... }  with the string `...` other than the previous values
        default:
          return errorDoc ("Unknown `do` value `" + o.do + "`.");

      }
    }
  }
info
  ...
---
---
unit
  doc
name
  ProjectHouse
header
  The Server
---
---
unit
  function
name
  ProjectHouse.server
type
  Type.fun ([ROOT, HttpServer.HTTP_PORT_NUMBER, UNDEFINED])
value
  function (root) {
    return function (port) {
      var docProducer = main (root); // of type `RECORD --> Data.HTML`
      var httpServer = HttpServer.fileAndDocProducerServer (port) (docProducer);
      httpServer.start ();
      System.Console.print ('The Cartaker GUI Server for the Project House at `' + root + '` is running now.');
      System.Console.print ('Direct your browser to `localhost:' + port + '/?do=home` to get started.');
      System.Console.print ('Use `Ctrl + C` to stop the server from running.');
    }
  }
info
  ...
---
---



---
---
unit
  doc
name
  ProjectHouse
header
  The API
info
  ...
---
---
unit
  struct
name
  ProjectHouse.Api
---
---
unit
  function
name
  ProjectHouse.Api.main
type
  Type.fun ([STRING, RECORD, STRING])
value
  function (cmd) {
    return function (opts) {
      switch (cmd) {
        case 'help':
          return helpMessage;
        case 'start-server':
          var root = opts.root || System.Dir.current (null);
          var port = opts.port || defaultPortNumber;
          try {
            server (root) (port);
            return 'Done';
          } catch (e) {
            return ("Trouble starting the server for project house `" + root + "` at port `" + port + "`: " + e);
          }
        case 'empty-trash':
          var root = opts.root || System.Dir.current (null);
          var n = emptyTheTrash (root);
          return ("The " + n + " projects in the trash of Project House `" + root + "` have been disposed for good.");
        case 'init-house':
          var dir = opts.dir || System.Dir.current (null);
          try {
            var root = initProjectHouse (dir);
            return ("A Project House with root `" + root + "` has been created.");
          } catch (e) {
            return ("Trouble initializing a Project House under `" + dir + "`: " + e);
          }
        case 'import-house':
          var root = opts.root || System.Dir.current (null);
          var importRoot = opts['import'];
          try {
            var n = importProjectHouse (root) (importRoot);
            return ("Project House `" + importRoot + "` has been imported into Project House `" + root + "`, " + n + " projects have been added.");
          } catch (e) {
            return ("Project House `" + root + "` could not properly import Project House `" + importRoot + "`: " + e);
          }
        case 'export':
          var root = opts.root || System.Dir.current (null);
          var dir = opts.dir;
          if (! dir) {
            return ("No export directory (--dir=DIR) was specified.\n" + helpMessage);
          }
          var pid = opts.pid;
          if (pid) {
            try {
              exportProjectByPid (root) (pid) (dir);
              return ("The project with PID `" + pid + "` was exported under `" + dir + "`.");
            } catch (e) {
              return ("Trouble exporting project `" + pid + "` under `" + dir + "`: " + e);
            }
          } 
          var tag = opts.tag;
          if (tag) {
            try {
              exportProjectsByTag (root) (tag) (dir);
              return ("All projects with tag `" + tag + "` were written to " + dir + ".");
            } catch (e) {
              return ("Trouble exporting all projects with tag `" + tag + "` under `" + dir + "`: " + e);
            }
          }
          var creator = opts.creator;
          if (creator) {
            try {
              exportProjectsByCreator (root) (creator) (dir);
              return ("All projects created by `" + creator + "` were written to " + dir + ".");
            } catch (e) {
              return ("Trouble exporting all projects created by `" + creator + "` under `" + dir + "`: " + e);
            }
          }
          return ("For the `export` command, either a PID (--pid=PID), a tag (--tag=STRING) or a creator (--creator=EMAIL) value has to be specified.\n" + helpMessage);
        case 'version':
          return "Sorry, unable to retrieve the version number."; // this should be covered by the executable!!!
        case 'export-all':
          var root = opts.root || System.Dir.current (null);
          var dir = opts.dir;
          if (dir) {
            try {
              exportAllProjects (root) (dir);
              return ("All projects have been exported to `" + dir + "`.");
            } catch (e) {
              return ("Trouble exporting all projects of the Project House: " + e);
            }
          } else {
            return ("No export directory (--dir=DIRECTORY) was specified.\n" + helpMessage);
          }
        default:
          return ("Undefined command `" + cmd + "`. Use the following syntax instead.\n" + helpMessage);
      }
    }
  }
info
  ...
---
unit
  value
name
  ProjectHouse.Api.baseCommand
type
  STRING
value
  'caretaker'
---
unit
  value
name
  ProjectHouse.Api.defaultPortNumber
type
  HttpServer.HTTP_PORT_NUMBER
value
  55555
info
  ...
---
unit
  value
name
  ProjectHouse.Api.homePageUrl
type
  Data.URL
value
  'http://tofjs.org/package/caretaker-in-a-project-house/'
info
  ...
---
unit
  value
name
  ProjectHouse.Api.helpMessage
type
  STRING
value
  [ 'SYNTAX:' 
  , baseCommand + ' help'
  , '  returns this help message.'
  , baseCommand + ' version'
  , '  returns the version number.'
  , baseCommand + ' init-house --dir=DIR'
  , '  creates a Project House with root DIR/ProjectHouse under the given directory DIR'
  , baseCommand + ' start-server --root=ROOT --port=PORT'
  , '  starts the GUI server for the project house ROOT (e.g. --root=/path/to/my/ProjectHouse).'
  , '  The PORT (default is ' + defaultPortNumber + ') is the port number for the URL of the GUI server, i.e.'
  , '  the URL for the home page is then `http://localhost:' + defaultPortNumber + '/?do=home`.'
  , '  The `root` option is optional and defaults to `./ProjectHouse` when it is omitted.'
  , '  The `port` option is optional, too, and defaults to `55555` (i.e. 5 times 5).'
  , baseCommand + ' empty-trash --root=ROOT'
  , '  empties the trash in the Project House ROOT.'
  , baseCommand + ' import-house --root=ROOT --import=ROOT'
  , '  inserts the content of the import ROOT into the given Project House ROOT.'
  , '  The `root` option is optional and defaults to `./ProjectHouse` when it is omitted.'
  , baseCommand + ' export --root=ROOT --pid=PID --dir=DIR'
  , '  writes the project with the according PID to the specified directory.'
  , baseCommand + ' export --root=ROOT --tag=STRING --dir=DIR'
  , '  writes all projects with the specified tag to the specified directory.'
  , baseCommand + ' export --root=ROOT --creator=EMAIL --dir=DIR'
  , '  writes all projects created by the specified creator to the specified directory.'
  , baseCommand + ' export-all --root=ROOT --dir=DIR'
  , '  writes all projects to the specified directory.'
  , 'See ' + homePageUrl + ' for more information.'
  ].join('\n')
info
  The help message is this string
  
      SYNTAX:
      .............................................................................................................

---
unit
  function
name
  ProjectHouse.Api.stringList2record
type
  Type.fun ([Type.list (STRING), Type.record (STRING)])
value
  function (argL) {
    var o = {};
    for (var i = 0; i < argL.length; i++) {
      if (Str.startsWith (argL[i]) ('--')) {
        var pair = argL[i].substr (2);
        var a = pair . split ('=');
        if (a.length === 2) {
          o [a [0]] = a [1];
        } else {
          throw Error ("Argument " + i + " is `" + argL[i] + "` and does not have the form `--key=value`.")
        }
      } else {
        throw Error ("Argument " + i + " does not start with `--`.");
      }
    }
    return Rec.create (o);
  }
info
  For example,
  
      > ProjectHouse.Api.stringList2record (["--one=ONE", "--two=TWO", "--three=TREE"])
      { one: 'ONE', two: 'TWO', three: 'TREE' }

---
unit
  function
name
  ProjectHouse.Api.command
type
  Type.fun ([Type.list (STRING), STRING])
value
  function (argL) {
    switch (argL.length) {
      case 0:
        return main ('help') ({});
      case 1:
        return main (argL [0]) ({});
      default: // i.e. `argL.length > 1`
        try {
          var paramRec = stringList2record (argL.slice (1));
        } catch (e) {
          return (
            "Invalid input! Use the following syntax:\n\n" +
            helpMessage
          );
        }
        return main (argL [0]) (paramRec);
    }
  }
info
  ...
---
---





---
---
// end ProjectHouse
---
---

