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
|
;;; Copyright © 2019 - 2023 Jakob L. Kreuze <zerodaysfordays@sdf.org>
;;;
;;; This program 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.
;;;
;;; This program 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 this program. If not, see
;;; <http://www.gnu.org/licenses/>.
(define-module (jakob builder atom)
#:use-module (haunt html)
#:use-module (haunt page)
#:use-module (haunt post)
#:use-module (haunt site)
#:use-module (haunt utils)
#:use-module (ice-9 match)
#:use-module (jakob builder blog)
#:use-module (jakob utils)
#:use-module (jakob utils sxml)
#:use-module (srfi srfi-19)
#:use-module (srfi srfi-26)
#:use-module (web uri)
#:export (atom-feed))
;; Slight hack to use relative URLs in the Atom feed for Tor and i2p mirrors.
(define %disable-compliance (make-parameter (getenv "DISABLE_ATOM_COMPLIANCE")))
(define (format-date date)
"Format DATE into a date-time production as defined in RFC 3339"
(let* ((formatted (date->string date "~4"))
(up-to-tz-minute (string-drop-right formatted 2))
(tz-minute (string-take-right formatted 2)))
(string-concatenate `(,up-to-tz-minute ":" ,tz-minute))))
(define (format-relative-path site path)
"Return an absolute URI for PATH"
(if (%disable-compliance)
path
(let ((path (if (not (string-prefix? "/" path))
(format #f "/~a" path)
path)))
(uri->string
(build-uri 'https ;; (site-scheme site)
#:host (site-domain site)
#:path path)))))
(define* (post->atom-entry site post #:key (blog-prefix ""))
"Convert POST into an Atom <entry> XML node."
(let ((uri (or (post-ref post 'crosspost)
(post-uri post))))
`(entry
(title ,(post-ref post 'title))
(id ,(format-relative-path site uri))
(author
(name ,(post-ref post 'author))
,(let ((email (post-ref post 'email)))
(if email `(email ,email) '())))
(updated ,(format-date (post-date post)))
(link (@ (href ,uri) (rel "alternate")))
(summary (@ (type "html"))
,(sxml->html-string
(append (first-paragraph post)
(if (post-ref post 'crosspost)
`((p "...")
(p "This is a crosspost. Click "
,(hyperlink (post-ref post 'crosspost) "here")
" to read the rest of the article."))
'()))))
,@(map (lambda (enclosure)
`(link (@ (rel "enclosure")
(title ,(enclosure-title enclosure))
(href ,(enclosure-url enclosure))
(type ,(enclosure-mime-type enclosure))
,@(map (match-lambda
((key . value)
(list key value)))
(enclosure-extra enclosure)))))
(post-ref-all post 'enclosure)))))
(define* (atom-feed #:key
(file-name "feed.xml")
(subtitle "Recent Posts")
(filter posts/reverse-chronological)
(max-entries 20)
(blog-prefix ""))
"Minor modification to the 'atom-feed' builder in '(haunt builder atom)' to
add support for cross-posts. See the docstring in that manual for details on the
use of this function."
(lambda (site posts)
(make-page file-name
`(feed (@ (xmlns "http://www.w3.org/2005/Atom"))
(title ,(site-title site))
(id ,(format-relative-path site file-name))
(subtitle ,subtitle)
(updated ,(format-date (current-date)))
(link (@ (href ,(format-relative-path site file-name))
(rel "self")))
(link (@ (href ,(format-relative-path site ""))))
,@(map (cut post->atom-entry site <>
#:blog-prefix blog-prefix)
(take-up-to max-entries (filter posts))))
(@@ (haunt builder atom) sxml->xml*))))
|