;;; tissue --- Text based issue tracker ;;; Copyright © 2022, 2023 Arun Isaac ;;; ;;; This file is part of tissue. ;;; ;;; tissue is free software: you can redistribute it and/or modify it ;;; under the terms of the GNU General Public License as published by ;;; the Free Software Foundation, either version 3 of the License, or ;;; (at your option) any later version. ;;; ;;; tissue is distributed in the hope that it will be useful, but ;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;; General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with tissue. If not, see . (use-modules (doc skribilo)) (document :title [tissue] (toc) (chapter :title [Introduction] :ident "chapter-introduction" (p [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 ,(ref :url "https://xapian.org/" :text "Xapian search engine library"), and is available both as a command-line program and as a web server.]) (section :title "Why tissue?" (subsection :title "tissue is not discussion-oriented" (p [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.])) (subsection :title "tissue allows and encourages rewriting of issues" (p [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.])))) (chapter :title [Tutorial] :ident "chapter-tutorial" (p [In this tutorial, we will learn how to create issues for an existing project, and how to publish those issues on the web.]) (section :title [Creating issues] (p [We start with a git repository for our project.]) (prog :line #f [~/my-project$]) (p [The repository presumably has project source code committed in it. We now create a directory ,(file "issues") and populate it with a few issues.]) (prog :line #f [~/my-project$ mkdir issues]) (p [Each issue is a ,(ref :url "https://gemini.circumlunar.space/docs/cheatsheet.gmi" :text "gemtext") file. Let's write our first issue ,(file "issues/crash-on-invalid-query.gmi") and commit it.]) (prog :line #f [# 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.]) (prog :line #f [~/my-project$ git add issues/crash-on-invalid-query.gmi ~/my-project$ git commit -m "Add first issue"]) (p [Let's add a second issue ,(file "issues/add-emacs-interface.gmi") and commit it.]) (prog :line #f [# Add Emacs interface * tags: feature-request Add Emacs interface to search for issues.]) (prog :line #f [~/my-project$ git add issues/add-emacs-interface.gmi ~/my-project$ git commit -m "Add second issue"]) (p [Now that we have a couple of issues, let's tell tissue about them. We do this using a configuration file ,(file "tissue.scm").]) (prog :line #f [(tissue-configuration #:indexed-documents (map read-gemtext-issue (gemtext-files-in-directory "issues")))]) (prog :line #f [~/my-project$ git add tissue.scm ~/my-project$ git commit -m "Add tissue configuration"]) (p [This tells tissue to index all files in the ,(file "issues") directory as issues. The ,(code "gemtext-files-in-directory") function returns a list of filenames (strings) in the ,(file "issues") directory. The ,(code "read-gemtext-issue") function reads each file and returns an ,(code "") object. Now, we may list all issues on the command line using tissue.]) (prog :line #f [~/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]) (p [We could also search through and shortlist. The search interface is a powerful full text search engine powered by the excellent ,(ref :url "https://xapian.org/" :text "Xapian") library.]) (prog :line #f [~/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])) (section :title [Publishing issues on the web] (p [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 ,(emph "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 ,(file "tissue.scm") configuration.]) (prog :line #f [(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")))]) (prog :line #f [~/my-project$ git add tissue.scm ~/my-project$ git commit -m "Add web configuration"]) (p [The ,(code "#:indexed-documents") keyword argument is the same as earlier, but in addition, we set the ,(code "'web-uri") slot of the ,(code "") object to the HTTP URI at which the issue may be found.]) (p [In order to actually put web pages for each issue at the aforementioned HTTP URIs, we need the ,(code "#:web-files") keyword argument. The ,(code "#:web-files") keyword argument takes a list of ,(code "") objects that describe files on the website. Each file object constitutes a file path and something we call a ,(emph "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 ,(code "gemtext-exporter") function provided by tissue to create a writer function for each issue file.]) (p [Now, to see this in a web interface, run]) (prog :line #f [~/my-project$ tissue web-dev Tissue development web server listening at http://localhost:8080]) (p [Visiting ,(ref :url "http://localhost:8080/") on your browser should get you the web search interface.])) ;; TODO (section :title [Production deployment] (p [TODO]))) (chapter :title [Gemtext markup for issues] :ident "chapter-gemtext-markup" (p [Issues must be written in ,(ref :url "https://gemini.circumlunar.space/docs/gemtext.gmi" :text "gemtext markup") with added extra notation to specify issue metadata.]) (p [Tag issues.] (prog :line #f [* tags: enhancement, good first issue])) (p [Close issues. Use either of] (prog :line #f [* closed]) (prog :line #f [* status: closed])) (p [Assign issues to one or more people.] (prog :line #f [* assigned: mekalai]) (prog :line #f [* assigned: muthu, mekalai])) (p [Create task lists with regular gemtext lists starting with ,(code "[ ]"). Tasks may be marked as completed by putting an ,(code "x") within the brackets, like so: ,(code "[x]")] (prog :line #f [* \[x\] Do this. * \[ \] Then, do this. * \[ \] Finally, do this.]))) (chapter :title [Reference] :ident "chapter-reference" (section :title [Object and record constructors] :ident "section-constructors" (description (docstring-function-documentation "tissue/tissue.scm" 'tissue-configuration) (function-documentation 'file [filename writer] [Construct a ,(code []) object that represents an output file to be created.] (description (item :key (var [filename]) [the name of the file to create as a string]) (item :key (var [writer]) [a one-argument function that takes a port as an argument and writes data destined for ,(var [filename]) into that port]))))) (section :title [Reader functions] :ident "section-reader-functions" (p [These functions produce ,(code []) objects (or objects of classes inheriting from ,(code [])) by reading files or other data sources.]) (description (docstring-function-documentation "tissue/issue.scm" 'read-gemtext-issue) (docstring-function-documentation "tissue/file-document.scm" 'read-gemtext-document) (docstring-function-documentation "tissue/commit.scm" 'commits-in-current-repository))) (section :title [Writer functions] :ident "section-writer-functions" [These functions write output files and are meant to be specified in a ,(code []) object.] (description (docstring-function-documentation "tissue/web/static.scm" 'copier) (docstring-function-documentation "tissue/web/static.scm" 'gemtext-exporter) (docstring-function-documentation "tissue/web/static.scm" 'skribe-exporter))) (section :title [Utility functions] :ident "section-utility-functions" [These miscellaneous functions are useful when writing tissue configuration files.] (description (docstring-function-documentation "tissue/tissue.scm" 'gemtext-files-in-directory) (docstring-function-documentation "tissue/git.scm" 'git-tracked-files) (docstring-function-documentation "tissue/document.scm" 'slot-set)))))