#+Title: Directory and feed generator #+Date: 2021-08-25 This file is used to generate a number of files in the absence of server-side scripting. I intend for this file to also be usable to others, as long as you have prerequisites. Granted, the prerequisites are fairly steep, as they require org-mode, which in turn needs Emacs, as well as ~zsh~ and a number of other utilities that are /normally/ installed with most Linux computers but not always. This file and all source code within it is licensed under GPLv3 or later. * Quick reference #+CALL: generate-feed() #+RESULTS: [[file:atom.xml]] #+CALL: make-directory-file() #+RESULTS: [[file:dirs.gmi]] * Directory listing This lists all the gemtext files inside the capsule. First, we define the global UUID. The ID is for use in the Atom feed, but can also be considered to be the ID of the capsule itself. #+NAME: main-uuid : 43730995-ccfc-4202-afef-f4ba2b2a42c2 Then, we get all the gemtext files in and below the directory this file is in, and ask for its path and the creation date. A version 3 UUID is also generated for the Atom feed. #+NAME: file-list #+begin_src shell :var mainuuid=main-uuid filedir=(format "%s" default-directory) :hlines yes # <<>> for file in "$filedir"/**/*.gmi; do echo -n "${file#$filedir/} " date -r "$file" +"%Y-%m-%dT%H:%M:%S%z" | tr '\n' ' ' uuidgen -m -n "$mainuuid" -N "${file#$filedir}" done #+end_src #+RESULTS: file-list | 1-29-493.gmi | 2022-11-19T18:28:16+0800 | 0e1d6677-c051-3186-8780-df8e5e27029b | | accessibility.gmi | 2021-12-13T07:34:10+0800 | 118150e6-0978-3e2f-b4bc-777488959f4a | | aesthetic.gmi | 2021-11-05T17:15:19+0800 | 555d9cea-8ef7-3816-8090-ffdbad45d185 | | article-conventions.gmi | 2021-09-04T22:50:28+0800 | 3e3e28f1-d4d8-3b7a-a6fc-58e98a90a216 | | cars.gmi | 2022-02-22T16:44:01+0800 | 1fe1ce39-3851-3cd8-9c02-82d4da9a5fcc | | detritus.gmi | 2023-12-26T04:05:59+0800 | a5ee1f9e-e989-3d43-8788-4e38010b5fad | | digits-10-and-11.gmi | 2021-08-28T20:30:50+0800 | 604fba54-299d-3853-ac3f-c5fa5150488f | | dirs.gmi | 2023-12-26T04:05:23+0800 | 4ffc8e5c-e98d-3ac4-9682-182df8f66c03 | | ed1.5.gmi | 2022-11-01T01:42:52+0800 | 9db4da3c-d8cd-3697-8308-88bc0e8560d6 | | energy-competition.gmi | 2021-10-23T15:57:38+0800 | 3d2215b0-f25f-3424-8df2-7ee2956cd68c | | fillet.gmi | 2023-08-19T23:16:35+0800 | ab03db0f-040b-36b5-b3f2-c5c30e8f685a | | franchises.gmi | 2023-12-26T04:04:42+0800 | 192c1d2b-1c5e-370b-911c-28a8c82f97ba | | gemlog-2021-09b.gmi | 2021-10-31T00:44:57+0800 | 3c0b1833-41a8-39e2-b65c-ca4481325367 | | gemlog-2021-10.gmi | 2021-10-31T00:46:22+0800 | 13acd661-06d7-3b31-9ee3-c959e6735972 | | gem-org-odt.gmi | 2021-11-22T11:04:42+0800 | 602080ab-b3ec-382c-86cc-8b71fb758d42 | | headspace-claim.gmi | 2022-07-11T21:19:17+0800 | d7926488-4c44-3a5c-b02f-08ddd2c5778b | | history-line.gmi | 2022-06-26T20:25:43+0800 | fb8db6ae-8837-3883-abf2-46c123a2a88e | | index.gmi | 2021-09-20T14:45:35+0800 | 618744bb-4dff-3bbe-bbe5-70c36d516100 | | just-so-story.gmi | 2023-11-17T02:27:47+0800 | 32d041e8-f8b3-35e5-afdd-9da69323a699 | | jyutcitzi.gmi | 2021-12-06T22:50:26+0800 | b72b9f46-e518-3dfc-b62d-a4f2bd1ad159 | | kaohsiung.gmi | 2022-05-02T20:43:03+0800 | 02d56cae-4183-3f4b-91e8-cd4ef416b082 | | language-list.gmi | 2021-12-31T23:15:39+0800 | a5a5cf51-08b5-3601-b3a1-90169f88e9e5 | | lispy-chess-piece-notation.gmi | 2022-10-14T21:34:04+0800 | d340fa93-6b20-32d2-a4e2-c9c40f4434ce | | masquerades.gmi | 2022-05-06T14:26:25+0800 | db47f8aa-749c-3cdb-ada8-b5f859d4406a | | misc-ideas.gmi | 2022-12-25T21:17:18+0800 | 41ce4b08-8e91-3d29-ac6b-13a80e1b1d7f | | mjr.gmi | 2022-03-16T19:24:52+0800 | 4b757ae7-c58f-3f93-a2e8-04b62734a2b4 | | monetising-conlangs.gmi | 2021-09-17T23:38:55+0800 | 058e3984-2473-3c0c-9213-2b915a37445f | | mouse-pointer.gmi | 2022-04-04T21:35:09+0800 | c8e67642-be83-3e18-9c51-3f152c142cbf | | names.gmi | 2022-09-19T17:32:22+0800 | f97cd747-70de-35c8-888b-06120e6743a7 | | noncharacters.gmi | 2022-06-06T14:15:25+0800 | 2a418a17-5a12-345f-9ba5-11feb56713c6 | | not-a-merc.gmi | 2023-04-25T13:45:43+0800 | ec95970d-ad19-397d-a666-c7a589cc5c28 | | notation.gmi | 2021-10-03T22:09:18+0800 | afa2bc53-f0a6-3de3-b2fa-a5e054f22e88 | | parabishops.gmi | 2022-12-04T18:42:52+0800 | 6a842d7f-d534-3270-b6ba-83ba29cb9d73 | | pi-mj.gmi | 2022-02-23T00:03:14+0800 | d56b580a-e507-3c61-a660-f6bd3e0d39f0 | | programming.gmi | 2022-03-16T19:14:51+0800 | 99a8c7e9-fc8e-3445-a63c-2d4c989d07e4 | | rejected-pmon-titles.gmi | 2022-06-02T23:39:27+0800 | ccd1d106-7ae3-3d0a-bb79-0da85b7dd183 | | re/power-of-git.gmi | 2021-09-21T13:50:15+0800 | dc7bc220-0b7e-38fc-8591-b3ff34f7c00b | | roads-and-life.gmi | 2022-04-29T11:30:27+0800 | 411952ce-1970-353e-9744-1b91c9944b16 | | scheduler.gmi | 2021-11-25T22:33:05+0800 | f4bea301-3619-3939-8771-7f3aa3458bdd | | some-writing-ideas.gmi | 2022-10-18T12:40:05+0800 | bdd75087-5fa7-3f67-878b-1480fa6a02d2 | | station-songs.gmi | 2022-02-14T18:32:29+0800 | 592e1b4e-028c-399c-bd05-8cad68a1c5de | | topic-symbols.gmi | 2021-09-26T22:17:19+0800 | b4027394-01f5-37da-9009-76354306fb76 | | transport-mentality.gmi | 2022-05-28T20:34:07+0800 | a03a4203-c91c-34c7-b74d-c62d75c2873c | | twitch-stats.gmi | 2021-10-09T22:42:17+0800 | 7210075b-8dd9-31d5-b846-d5f45a75020c | | ujmj.gmi | 2022-02-14T23:15:30+0800 | 736106f0-4bcc-3823-9255-674e25565d7b | | wonder.gmi | 2022-06-21T20:38:39+0800 | bc871501-bb0b-3cd8-85a9-924e9e5b69ba | | worldbuilding.gmi | 2023-08-19T23:18:04+0800 | 18850017-bbc2-3999-9df8-2448003d90ba | The UUID is version 3 so it can be reliably recovered using the global UUID and the path (which works as the namespace and the name respectively); and since we do not need to concern ourselves with security here, the MD5 hash is used. Based on the table above we can make the directory file. We add a title, a summary and some explanatory text, as well as a link to this page as the source code. #+NAME: make-directory-file #+begin_src shell :var file_list=file-list :results file :file dirs.gmi gentime=$(date +"%Y-%m-%d %H:%M:%S %Z") cat < dirs.org Source code for file generation EOF echo $file_list | while read file date _; do echo "=> $file ${file%.gmi}: $(grep "^# " $file | head -n 1 | cut -c3-)" done | sort #+end_src #+RESULTS: make-directory-file [[file:dirs.gmi]] * Atom feed for creative files In this section, we generate an Atom feed for the following files: #+NAME: selected-files - language-list.gmi - ujmj.gmi - digits-10-and-11.gmi - article-conventions.gmi - monetising-conlangs.gmi - re/power-of-git.gmi - notation.gmi - twitch-stats.gmi - energy-competition.gmi - aesthetic.gmi - gem-org-odt.gmi - scheduler.gmi - jyutcitzi.gmi - station-songs.gmi - cars.gmi - mjr.gmi - pi-mj.gmi - mouse-pointer.gmi - roads-and-life.gmi - masquerades.gmi - transport-mentality.gmi - wonder.gmi - noncharacters.gmi - history-line.gmi - headspace-claim.gmi - rejected-pmon-titles.gmi - names.gmi - lispy-chess-piece-notation.gmi - 1-29-493.gmi - parabishops.gmi - not-a-merc.gmi - fillet.gmi - just-so-story.gmi - franchises.gmi - four-random-questions.gmi (For use later) - topic-symbols.gmi The files here are selected as, for lack of a better word, "content files": they are why the capsule is put up here in the first place. This is handled using Emacs Lisp's ~xmlgen~ and some fairly involved text-manipulating commands that Emacs Lisp has (hence the change in language). The text-manipulation commands are actually just there to the resulting output human-readable and also visually appealing, though some actions are done in shell. The feed uses my name as the author for all files, and [[ref:file-list]] for the remaining data. (It's actually why we pre-computed the UUIDs there, to save on another shell-out). The summary is the first paragraph (non-blank line that isn't a heading, quote or link), and the content is left empty. #+Name: generate-feed #+begin_src emacs-lisp :var uuid=main-uuid files=file-list selected=selected-files[,0] :results file :file atom.xml (with-temp-buffer ;; Write XML (let ((gemini-host "gemini://isoraqathedh.pollux.casa/") (https-host "https://isoraqathedh.pollux.casa/") (title-program "grep \"^#\" \"%s\" | sed '1s/^# //' | head -n 1 | tr -d '\n'") (summary-program "grep -Pv \"^($|>|#|=>)\" \"%s\" | sed '1s/^# //' | head -n 1 | fold -s -w 70 | sed 's/ $//'")) (flet ((file->entry (file) `(entry (link :href ,(concat gemini-host (first file))) (link :rel "alternate" :href ,(concat https-host (first file))) (updated ,(second file)) (id "urn:uuid:" ,(third file)) (title ,(shell-command-to-string (format title-program (first file)))) (summary "\n" ,(shell-command-to-string (format summary-program (first file)))) (author (name "isoraqathedh"))))) (insert "" (xmlgen `(feed :xmlns "http://www.w3.org/2005/Atom" (link :href ,gemini-host) (link :rel "self" :type "application/atom+xml" :href ,(concat gemini-host "atom.xml")) (id "urn:uuid:" ,(string-trim uuid)) (updated ,(format-time-string "%Y-%m-%dT%H:%M:%S%z")) (generator "dirs.org") (title "kapsyƫl") ,@(mapcar #'file->entry (sort (cl-remove-if-not #'(lambda (x) (cl-find (first x) selected :test #'string=)) files) #'(lambda (x y) (string> (second x) (second y)))))))) ;; Format nicely (nxml-mode) (goto-char (1+ (point-min))) (while (re-search-forward "<\\([^/]\\)" nil t nil) (replace-match "\n<\\1")) (goto-char (point-min)) (while (re-search-forward ">\n