1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
|
;;; tissue --- Text based issue tracker
;;; Copyright © 2022, 2023 Arun Isaac <arunisaac@systemreboot.net>
;;;
;;; 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 <https://www.gnu.org/licenses/>.
(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 "<issue>") 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 "<issue>") 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 "<file>") 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 [<file>]) 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 [<document>]) objects (or
objects of classes inheriting from ,(code [<document>])) 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 [<file>]) 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)))))
|