tissue
1Introduction
1.1Why tissue?
2Tutorial
2.1Creating issues
2.2Publishing issues on the web
2.3Production deployment
3Gemtext markup for issues
4Reference
4.1Object and record constructors
4.2Reader functions
4.3Writer functions
4.4Utility functions

1 Introduction

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.

1.1 Why tissue?

1.1.1 tissue is not discussion-oriented

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.

1.1.2 tissue allows and encourages rewriting of 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.

2 Tutorial

In this tutorial, we will learn how to create issues for an existing project, and how to publish those issues on the web.

2.1 Creating issues

We start with a git repository for our project.

~/my-project$

The repository presumably has project source code committed in it. We now create a directory issues and populate it with a few issues.

~/my-project$ mkdir 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.

# Add Emacs interface

* tags: feature-request

Add Emacs interface to search for issues.
~/my-project$ git add issues/add-emacs-interface.gmi
~/my-project$ git commit -m "Add second issue"

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")))
~/my-project$ git add tissue.scm
~/my-project$ git commit -m "Add tissue configuration"

This tells tissue to index all files in the issues directory as issues. The gemtext-files-in-directory function returns a list of filenames (strings) in the issues directory. The 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

2.2 Publishing issues on the web

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")))
~/my-project$ git add tissue.scm
~/my-project$ git commit -m "Add web configuration"

The #:indexed-documents keyword argument is the same as earlier, but in addition, we set the 'web-uri slot of the <issue> object to the HTTP URI at which the issue may be found.

In order to actually put web pages for each issue at the aforementioned HTTP URIs, we need the #:web-files keyword argument. 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

~/my-project$ tissue web-dev
Tissue development web server listening at http://localhost:8080

Visiting http://localhost:8080/ on your browser should get you the web search interface.

2.3 Production deployment

TODO

3 Gemtext markup for issues

Issues must be written in gemtext markup with added extra notation to specify issue metadata.

Tag issues.

* tags: enhancement, good first issue

Close issues. Use either of

* closed
* status: closed

Assign issues to one or more people.

* assigned: mekalai
* assigned: muthu, mekalai

Create task lists with regular gemtext lists starting with [ ]. Tasks may be marked as completed by putting an x within the brackets, like so: [x]

* [x] Do this.
* [ ] Then, do this.
* [ ] Finally, do this.

4 Reference

4.1 Object and record constructors

(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)
Construct a <file> object that represents an output file to be created.
filename
the name of the file to create as a string
writer
a one-argument function that takes a port as an argument and writes data destined for filename into that port

4.2 Reader functions

These functions produce <document> objects (or objects of classes inheriting from <document>) by reading files or other data sources.

(read-gemtext-issue file)

Read issue from gemtext file and return an <issue> object.

(read-gemtext-document file)

Read gemtext document from file and return a <file-document> object.

(commits-in-current-repository)

Return a list of <commit> objects representing commits in current repository.

4.3 Writer functions

These functions write output files and are meant to be specified in a <file> object.
(copier file)

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.

4.4 Utility functions

These miscellaneous functions are useful when writing tissue configuration files.
(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.

(made with skribilo)