|2.2||Publishing issues on the web|
|3||Gemtext markup for issues|
|4.1||Object and record constructors|
tissue is an issue tracker and project information management system built on plain text files and git. It is specifically intended for small free software projects. It features a static site generator to build a project website and a powerful search interface to search through project issues and documentation. The search interface is built on the Xapian search engine library, and is available both as a command-line program and as a web server.
tissue moves away from the discussion-oriented style of popular issue trackers such as the GitHub issue tracker. It separates the discussion of an issue from the documentation of it. You discuss somewhere else (say, on a mailing list, on IRC, or even face-to-face), and then distill the discussion into a coherent issue report that reads cleanly from top to bottom. Too often, the problem with discussion-oriented issue trackers like GitHub is that new readers of the issue have to follow the whole discussion from start to finish and put it all together in their head to understand what's going on. This is tiring, and sometimes people simply give up on reading issues that have long discussions. It's much better to have a clear succinct actionable issue report. This way, the issue tracker is a list of clear actionable items rather than a mess of unreproducible issues.
Discussion-oriented issue trackers force an append-only style where updates to the issue are only possible as newly appended messages to the discussion. tissue, on the other hand, allows and encourages rewriting of issues to keep the overall issue easily readable to a newcomer.
In this tutorial, we will learn how to create issues for an existing project, and how to publish those issues on the web.
We start with a git repository for our project.
The repository presumably has project source code committed in it. We now create a directory issues and populate it with a few issues.
Each issue is a gemtext file. Let's write our first issue issues/crash-on-invalid-query.gmi and commit it.
# Search engine crashes on invalid query * tags: bug * assigned: Arun Isaac When a syntatically invalid search query is entered into the search engine, the search engine process crashes. Further queries all return a 500 Internal Server Error.
~/my-project$ git add issues/crash-on-invalid-query.gmi ~/my-project$ git commit -m "Add first issue"
Let's add a second issue issues/add-emacs-interface.gmi and commit it.
Now that we have a couple of issues, let's tell tissue about them. We do this using a configuration file tissue.scm.
(tissue-configuration #:indexed-documents (map read-gemtext-issue (gemtext-files-in-directory "issues")))
This tells tissue to index all files in the issues
directory as issues. The
returns a list of filenames (strings) in the issues
read-gemtext-issue function reads each file
and returns an
<issue> object. Now, we may list all issues
on the command line using tissue.
~/my-project$ tissue Add Emacs interface feature-request ISSUE issues/add-emacs-interface.gmi opened 70 minutes ago by Arun Isaac Search engine crashes on invalid query (assigned: Arun Isaac) ISSUE issues/crash-on-invalid-query.gmi opened 71 minutes ago by Arun Isaac
We could also search through and shortlist. The search interface is a powerful full text search engine powered by the excellent Xapian library.
~/my-project$ tissue search assigned:arun Search engine crashes on invalid query (assigned: Arun Isaac) ISSUE issues/crash-on-invalid-query.gmi opened 76 minutes ago by Arun Isaac ~/my-project$ tissue search emacs Add Emacs interface feature-request ISSUE issues/add-emacs-interface.gmi opened 87 minutes ago by Arun Isaac Add Emacs interface * tags: feature-request Add Emacs interface to search for... ~/my-project$ tissue search tag:bug Search engine crashes on invalid query bug (assigned: Arun Isaac) ISSUE issues/crash-on-invalid-query.gmi opened 88 minutes ago by Arun Isaac
Now, let's try to get our issue tracker on the web. tissue does not treat issue files specially. You will notice that it only speaks of indexed documents and not specifically about issues. We need to explicitly tell it to create web pages for each issue file and to associate each issue to its respective web page. We do this with the following tissue.scm configuration.
(tissue-configuration #:indexed-documents (map (lambda (filename) (slot-set (read-gemtext-issue filename) 'web-uri (string-append "/" (string-remove-suffix ".gmi" filename)))) (gemtext-files-in-directory "issues")) #:web-files (map (lambda (filename) (file (replace-extension filename "html") (gemtext-exporter filename))) (gemtext-files-in-directory "issues")))
#:indexed-documents keyword argument is the
same as earlier, but in addition, we set the
<issue> object to the HTTP URI at which the issue may
In order to actually put web pages for each issue at the
aforementioned HTTP URIs, we need the
#:web-files keyword argument takes a list of
<file> objects that describe files on the website. Each file
object constitutes a file path and something we call a writer
function. A writer function is a one-argument function that accepts
a port and writes the contents of a file to it. Here, we use the
gemtext-exporter function provided by tissue to create a
writer function for each issue file.
Now, to see this in a web interface, run
Visiting http://localhost:8080/ on your browser should get you the web search interface.
Issues must be written in gemtext markup with added extra notation to specify issue metadata.
Close issues. Use either of
Assign issues to one or more people.
Create task lists with regular gemtext lists starting with
[ ]. Tasks may be marked as completed by putting an
x within the brackets, like so:
(tissue-configuration #:key (aliases '()) (indexed-documents '()) (web-search-renderer (default-theme)) (web-files '()))
Construct a <tissue-configuration> object. All arguments are evaluated lazily.
aliases is a list of aliases used to refer to authors in the repository. Each element is in turn a list of aliases an author goes by, the first of which is the canonical name of that author.
indexed-documents is a list of
<document> objects (or objects of classes inheriting from
<document>) representing documents to index.
web-search-renderer is a function that accepts two arguments---a
<search-page> object describing the search page and a
<tissue-configuration> object describing the project. It must return the rendered SXML.
web-files is a list of
<file> objects representing files to be written to the web output.
(file filename writer)
<file>object that represents an output file to be created.
These functions produce
<document> objects (or
objects of classes inheriting from
<document>) by reading
files or other data sources.
Read issue from gemtext file and return an
Read gemtext document from file and return a
Return a list of <commit> objects representing commits in current repository.
Return a writer function that copies file.
(gemtext-exporter file #:key (reader (gemtext-reader)) (engine (html-engine)))
Return a writer function that reads gemtext file using reader and exports it using engine.
(skribe-exporter file #:key (reader (make-reader 'skribe)) (engine (html-engine)))
Return a writer function that reads skribe file using reader and exports it using engine.
(gemtext-files-in-directory #:optional directory)
Return a list of all gemtext files in directory tracked in the current git repository. The returned paths are relative to the top-level directory of the current repository and do not have a leading slash.
If directory is unspecified, return the list of all gemtext files tracked in the current git repository regardless of which directory they are in.
(git-tracked-files #:optional (repository (current-git-repository)))
Return a list of all files and directories tracked in repository. The returned paths are relative to the top-level directory of repository and do not have a leading slash.
(slot-set object slot-name value)
Set slot-name in object to value. This is a purely functional setter that operates on a copy of object. It does not mutate object.