gemtext in tissue issue tracker

2024-06-01 @rrobin

The tissue issue tracker stores issues as gemtext files in a git repository, it serves them as a web page and provides a CLI tool to manage them.

Gemtext is not a very powerfull format, it only supports text, bullet points, titles, links and blocks.

Tissue overcomes its limitations with some conventions. Metadata is encoded in bullet points, these have special meaning:

* tags: important
* assigned: bruce
* status: closed

The other convention encodes tasks, their description and state:

* [ ] buy groceries
* [x] fix the car

There are also some shorthand forms e.g. this is also valid to close a ticket

* closed

Issues

Issues are read by the read-gemtext-issue function:

(define (read-gemtext-issue file)
  "Read issue from gemtext @var{file} and return an @code{}
object."
  (let* ((file-document (read-gemtext-document file))

I am not familiar with guile's define-class, but these seem to be the components of an issue:

(define-class  ()
  (assigned #:accessor issue-assigned #:init-keyword #:assigned)
  (keywords #:accessor issue-keywords #:init-keyword #:keywords)
  (open? #:accessor issue-open? #:init-keyword #:open?)
  (tasks #:accessor issue-tasks #:init-keyword #:tasks)
  (completed-tasks #:accessor issue-completed-tasks #:init-keyword #:completed-tasks))

that is

I find this easier to read in the issue read function

    (make 
      #:path file
      ;; Fallback to filename if title has no alphabetic characters.
      #:title (let ((title (hashtable-ref file-details 'title "")))
                (if (string-any char-set:letter title) title file))
      #:assigned (hashtable-ref file-details 'assigned '())
      ;; "closed" is a special keyword to indicate the open/closed
      ;; status of an issue.
      #:keywords (delete "closed" all-keywords)
      #:open? (not (member "closed" all-keywords))
      #:tasks (hashtable-ref file-details 'tasks 0)
      #:completed-tasks (hashtable-ref file-details 'completed-tasks 0)
      #:commits (file-document-commits file-document)
      #:snippet-source-text (document-snippet-source-text file-document))))

That is the following information

The function that extracts this metatadata from the file is file-details

file-details

As the documentation states this function returns an hash-table, generated from the file contents:

(define (file-details port)
  "Return a hashtable of details extracted from input PORT reading a
gemtext file."
  (let ((result (make-eq-hashtable))

I am not familiar with transducers in guile/scheme but port-transduce seems to be processing one line at a time (get-line-dos-or-unix) from the input and using the provided lambda on each line:

    (port-transduce (tmap (lambda (line)
                            (cond
                             ;; Toggle preformatted state.
                             ((string=? "```" line)
                              (set! in-preformatted (not in-preformatted)))
                             ;; Ignore preformatted blocks.
                             (in-preformatted #t)
                             ;; Checkbox lists are tasks. If the
                             ;; checkbox has any character other
                             ;; than space in it, the task is
                             ;; completed.

this function goes along mutating the result hashtable

There are some special rules in place as well

                                   ;; Insert values based on
                                   ;; their keys.
                                   (for-each (match-lambda
                                               (((or 'assign 'assigned) . values)
                                                (hashtable-prepend! result 'assigned
                                                                    (map (cut resolve-alias <> (%aliases))
                                                                         values)))
                                               (((or 'keyword 'keywords 'severity 'status
                                                     'priority 'tag 'tags 'type)
                                                 . values)
                                                (hashtable-prepend! result 'keywords values))

Finally there is a search for keywords in lists of keywords separated by commas:

						 (string-contains element keyword))
					       (list "request" "bug" "critical"
                                                     "enhancement" "progress"
                                                     "testing" "later" "documentation"
                                                     "help" "closed")))

file-document-commits

There also a reference to file-document-commits that is used to get a list of commits that relate to the gemtext file. This is first seen in the file-document class definition

(define-class  ()
  (path #:accessor file-document-path #:init-keyword #:path)
  ;; List of  objects, oldest first.
  (commits #:accessor file-document-commits #:init-keyword #:commits))

and it is initialized during document creation based on commits-affecting-file

(define (commits-affecting-file file)
  "Return a list of commits affecting @var{file} in current repository."

I did not look deeper into this but it seems to be derived from a map of files to related commits created by file-modification-table:

(define (file-modification-table repository)
  "Return a hashtable mapping files to the list of commits in REPOSITORY
that modified them."

References

=> https://git.systemreboot.net/tissue/ | https://tissue.systemreboot.net/ | https://tissue.systemreboot.net/manual/dev/en/ | https://www.gnu.org/software/guile/manual/html_node/SRFI_002d171.html

Metadata

Proxy Information
Original URL
gemini://tilde.pink/~rrobin/2024-06-01-gemtext-in-tissue-issue-tracker.gmi
Status Code
Success (20)
Meta
text/gemini;
Capsule Response Time
21.491671 milliseconds
Gemini-to-HTML Time
1.225141 milliseconds

This content has been proxied by September (ba2dc).