Compare commits
21 Commits
Author | SHA1 | Date |
---|---|---|
|
0130760f3f | |
![]() |
65adbb052f | |
|
d66515e8b3 | |
|
e4d7e9b605 | |
![]() |
d92411f7c1 | |
![]() |
794125e408 | |
![]() |
efb1739a69 | |
![]() |
142006f884 | |
![]() |
e25ed2d569 | |
![]() |
1bfb388123 | |
![]() |
286edaa5de | |
![]() |
a7dac982c2 | |
![]() |
928f0e2921 | |
![]() |
98471930c9 | |
![]() |
8891da0e3a | |
![]() |
15c4605e1d | |
![]() |
51e2f10645 | |
![]() |
ea14e56f0c | |
![]() |
b37ef63b8a | |
![]() |
f49a5cb3d4 | |
![]() |
efa145cc4b |
|
@ -13,6 +13,7 @@ Makefile.in
|
||||||
/example/site/
|
/example/site/
|
||||||
/scripts/haunt
|
/scripts/haunt
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
*.tar.gz.asc
|
||||||
/website/site
|
/website/site
|
||||||
/test-env
|
/test-env
|
||||||
*.log
|
*.log
|
||||||
|
|
|
@ -54,6 +54,7 @@ SOURCES = \
|
||||||
haunt/builder/assets.scm \
|
haunt/builder/assets.scm \
|
||||||
haunt/builder/atom.scm \
|
haunt/builder/atom.scm \
|
||||||
haunt/builder/blog.scm \
|
haunt/builder/blog.scm \
|
||||||
|
haunt/builder/rss.scm \
|
||||||
haunt/reader.scm \
|
haunt/reader.scm \
|
||||||
haunt/reader/texinfo.scm \
|
haunt/reader/texinfo.scm \
|
||||||
haunt/ui.scm \
|
haunt/ui.scm \
|
||||||
|
|
2
README
2
README
|
@ -5,7 +5,7 @@ simple, functional, and extensible.
|
||||||
|
|
||||||
* Features
|
* Features
|
||||||
|
|
||||||
- Easy blog and Atom feed generation
|
- Easy blog and Atom/RSS feed generation
|
||||||
- Supports any markup language that can be parsed to SXML
|
- Supports any markup language that can be parsed to SXML
|
||||||
- Simple development server
|
- Simple development server
|
||||||
- Purely functional build process
|
- Purely functional build process
|
||||||
|
|
2
THANKS
2
THANKS
|
@ -1,3 +1,5 @@
|
||||||
Ben Sturmfels <ben@sturm.com.au>
|
Ben Sturmfels <ben@sturm.com.au>
|
||||||
Vladimir Zhbanov <vzhbanov@gmail.com>
|
Vladimir Zhbanov <vzhbanov@gmail.com>
|
||||||
Urbain Vaes <urbain@vaes.uk>
|
Urbain Vaes <urbain@vaes.uk>
|
||||||
|
Alex Kost <alezost@gmail.com>
|
||||||
|
Jorge Maldonado Ventura <jorgesumle@freakspot.net>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
dnl -*- Autoconf -*-
|
dnl -*- Autoconf -*-
|
||||||
|
|
||||||
AC_INIT(Haunt, 0.2.2)
|
AC_INIT(Haunt, 0.2.4)
|
||||||
AC_CONFIG_SRCDIR(haunt)
|
AC_CONFIG_SRCDIR(haunt)
|
||||||
AC_CONFIG_AUX_DIR([build-aux])
|
AC_CONFIG_AUX_DIR([build-aux])
|
||||||
AM_INIT_AUTOMAKE([color-tests -Wall -Wno-portability foreign])
|
AM_INIT_AUTOMAKE([color-tests -Wall -Wno-portability foreign])
|
||||||
|
@ -11,7 +11,7 @@ AC_CONFIG_FILES([pre-inst-env], [chmod +x pre-inst-env])
|
||||||
AC_CONFIG_FILES([test-env], [chmod +x test-env])
|
AC_CONFIG_FILES([test-env], [chmod +x test-env])
|
||||||
AC_CONFIG_FILES([scripts/haunt], [chmod +x scripts/haunt])
|
AC_CONFIG_FILES([scripts/haunt], [chmod +x scripts/haunt])
|
||||||
|
|
||||||
GUILE_PKG([2.2 2.0])
|
GUILE_PKG([3.0 2.2 2.0])
|
||||||
GUILE_PROGS
|
GUILE_PROGS
|
||||||
|
|
||||||
dnl Guile-reader is needed for Skribe support
|
dnl Guile-reader is needed for Skribe support
|
||||||
|
|
|
@ -144,9 +144,9 @@ Happy haunting!
|
||||||
@node Downloading
|
@node Downloading
|
||||||
@section Downloading
|
@section Downloading
|
||||||
|
|
||||||
Official Haunt source code release tarballs can be found on the
|
Official Haunt source code release tarballs can be found under Releases
|
||||||
@url{http://haunt.dthompson.us/downloads.html, downloads page} of
|
in @url{https://dthompson.us/projects/haunt.html, Haunt's website},
|
||||||
Haunt's website, along with their associated checksums.
|
along with their associated checksums.
|
||||||
|
|
||||||
@node Requirements
|
@node Requirements
|
||||||
@section Requirements
|
@section Requirements
|
||||||
|
@ -803,7 +803,7 @@ files in @var{directory} that match @var{keep?}, recursively.
|
||||||
|
|
||||||
Builders are procedures that return one or more page objects
|
Builders are procedures that return one or more page objects
|
||||||
(@pxref{Pages}) when applied. A builder accepts two arguments: A site
|
(@pxref{Pages}) when applied. A builder accepts two arguments: A site
|
||||||
(@pxref{Sites} and a list of posts (@pxref{Posts}).
|
(@pxref{Sites}) and a list of posts (@pxref{Posts}).
|
||||||
|
|
||||||
Haunt comes with a few convenient builders to help users who want to
|
Haunt comes with a few convenient builders to help users who want to
|
||||||
create a simple blog with an Atom feed.
|
create a simple blog with an Atom feed.
|
||||||
|
|
1
guix.scm
1
guix.scm
|
@ -35,6 +35,7 @@
|
||||||
(gnu packages)
|
(gnu packages)
|
||||||
(gnu packages autotools)
|
(gnu packages autotools)
|
||||||
(gnu packages guile)
|
(gnu packages guile)
|
||||||
|
(gnu packages guile-xyz)
|
||||||
(gnu packages pkg-config)
|
(gnu packages pkg-config)
|
||||||
(gnu packages texinfo))
|
(gnu packages texinfo))
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(define-module (haunt builder atom)
|
(define-module (haunt builder atom)
|
||||||
|
#:use-module (srfi srfi-9)
|
||||||
#:use-module (srfi srfi-19)
|
#:use-module (srfi srfi-19)
|
||||||
#:use-module (srfi srfi-26)
|
#:use-module (srfi srfi-26)
|
||||||
#:use-module (ice-9 match)
|
#:use-module (ice-9 match)
|
||||||
|
@ -33,39 +34,138 @@
|
||||||
#:use-module (haunt page)
|
#:use-module (haunt page)
|
||||||
#:use-module (haunt utils)
|
#:use-module (haunt utils)
|
||||||
#:use-module (haunt html)
|
#:use-module (haunt html)
|
||||||
#:export (atom-feed
|
#:use-module (haunt serve mime-types)
|
||||||
|
#:use-module (web uri)
|
||||||
|
#:export (make-enclosure
|
||||||
|
enclosure?
|
||||||
|
enclosure-title
|
||||||
|
enclosure-url
|
||||||
|
enclosure-extra
|
||||||
|
enclosure-mime-type
|
||||||
|
|
||||||
|
atom-feed
|
||||||
atom-feeds-by-tag))
|
atom-feeds-by-tag))
|
||||||
|
|
||||||
|
(define-record-type <enclosure>
|
||||||
|
(make-enclosure title url extra)
|
||||||
|
enclosure?
|
||||||
|
(title enclosure-title)
|
||||||
|
(url enclosure-url)
|
||||||
|
(extra enclosure-extra))
|
||||||
|
|
||||||
|
(define (enclosure-mime-type enclosure)
|
||||||
|
(mime-type (enclosure-url enclosure)))
|
||||||
|
|
||||||
|
(define char-set:enclosure-key
|
||||||
|
(char-set-union char-set:letter+digit
|
||||||
|
(char-set-delete char-set:punctuation #\: #\")
|
||||||
|
(char-set-delete char-set:symbol #\=)))
|
||||||
|
|
||||||
|
(define (parse-enclosure s)
|
||||||
|
(call-with-input-string s
|
||||||
|
(lambda (port)
|
||||||
|
(define (assert-char char)
|
||||||
|
(let ((c (read-char port)))
|
||||||
|
(unless (eqv? c char)
|
||||||
|
(error "enclosure: parse: expected" char "got" c))))
|
||||||
|
(define (whitespace? char)
|
||||||
|
(char-set-contains? char-set:whitespace char))
|
||||||
|
(define (consume-whitespace)
|
||||||
|
(match (peek-char port)
|
||||||
|
((? eof-object?) *unspecified*)
|
||||||
|
((? whitespace?)
|
||||||
|
(read-char port)
|
||||||
|
(consume-whitespace))
|
||||||
|
(_ *unspecified*)))
|
||||||
|
(define (read-escape-character)
|
||||||
|
(match (read-char port)
|
||||||
|
(#\" #\")
|
||||||
|
(#\\ #\\)
|
||||||
|
(char (error "enclosure: parse: invalid escape character:" char))))
|
||||||
|
(define (read-unquoted-string)
|
||||||
|
(list->string
|
||||||
|
(let loop ()
|
||||||
|
(let ((c (peek-char port)))
|
||||||
|
(cond
|
||||||
|
((eof-object? c)
|
||||||
|
'())
|
||||||
|
((char-set-contains? char-set:enclosure-key c)
|
||||||
|
(read-char port)
|
||||||
|
(cons c (loop)))
|
||||||
|
(else
|
||||||
|
'()))))))
|
||||||
|
(define (read-string)
|
||||||
|
(if (eqv? (peek-char port) #\")
|
||||||
|
(begin
|
||||||
|
(assert-char #\")
|
||||||
|
(list->string
|
||||||
|
(let loop ()
|
||||||
|
(match (read-char port)
|
||||||
|
((? eof-object?)
|
||||||
|
(error "enclosure: parse: EOF while reading string"))
|
||||||
|
(#\" '())
|
||||||
|
(#\\ (cons (read-escape-character) (loop)))
|
||||||
|
(char (cons char (loop)))))))
|
||||||
|
(read-unquoted-string)))
|
||||||
|
(define (read-key)
|
||||||
|
(string->symbol (read-unquoted-string)))
|
||||||
|
(let loop ((attrs '()))
|
||||||
|
(consume-whitespace)
|
||||||
|
(if (eof-object? (peek-char port))
|
||||||
|
(make-enclosure (assq-ref attrs 'title)
|
||||||
|
(assq-ref attrs 'url)
|
||||||
|
(let loop ((attrs attrs))
|
||||||
|
(match attrs
|
||||||
|
(() '())
|
||||||
|
((((or 'title 'url) . _) . rest)
|
||||||
|
(loop rest))
|
||||||
|
((attr . rest)
|
||||||
|
(cons attr (loop rest))))))
|
||||||
|
(let ((key (read-key)))
|
||||||
|
(assert-char #\:)
|
||||||
|
(loop (cons (cons key (read-string)) attrs))))))))
|
||||||
|
|
||||||
|
(register-metadata-parser! 'enclosure parse-enclosure)
|
||||||
|
|
||||||
(define (sxml->xml* sxml port)
|
(define (sxml->xml* sxml port)
|
||||||
"Write SXML to PORT, preceded by an <?xml> tag."
|
"Write SXML to PORT, preceded by an <?xml> tag."
|
||||||
(display "<?xml version=\"1.0\" encoding=\"utf-8\"?>" port)
|
(display "<?xml version=\"1.0\" encoding=\"utf-8\"?>" port)
|
||||||
(sxml->xml sxml port))
|
(sxml->xml sxml port))
|
||||||
|
|
||||||
(define (date->string* date)
|
(define (date->string* date)
|
||||||
"Convert date to ISO-8601 formatted string."
|
"Convert date to RFC-3339 formatted string."
|
||||||
(date->string date "~4"))
|
(date->string date "~Y-~m-~dT~H:~M:~SZ"))
|
||||||
|
|
||||||
(define* (post->atom-entry site post #:key (blog-prefix ""))
|
(define* (post->atom-entry site post)
|
||||||
"Convert POST into an Atom <entry> XML node."
|
"Convert POST into an Atom <entry> XML node."
|
||||||
`(entry
|
(let ((uri (uri->string (site-post-url site post))))
|
||||||
(title ,(post-ref post 'title))
|
`(entry
|
||||||
(author
|
(title ,(post-ref post 'title))
|
||||||
(name ,(post-ref post 'author))
|
(id ,uri)
|
||||||
,(let ((email (post-ref post 'email)))
|
(author
|
||||||
(if email `(email ,email) '())))
|
(name ,(post-ref post 'author))
|
||||||
(updated ,(date->string* (post-date post)))
|
,(let ((email (post-ref post 'email)))
|
||||||
(link (@ (href ,(string-append blog-prefix "/"
|
(if email `(email ,email) '())))
|
||||||
(site-post-slug site post) ".html"))
|
(updated ,(date->string* (post-date post)))
|
||||||
(rel "alternate")))
|
(link (@ (href ,uri) (rel "alternate")))
|
||||||
(summary (@ (type "html"))
|
(summary (@ (type "html"))
|
||||||
,(sxml->html-string (post-sxml post)))))
|
,(sxml->html-string (post-sxml post)))
|
||||||
|
,@(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
|
(define* (atom-feed #:key
|
||||||
(file-name "feed.xml")
|
(file-name "feed.xml")
|
||||||
(subtitle "Recent Posts")
|
(subtitle "Recent Posts")
|
||||||
(filter posts/reverse-chronological)
|
(filter posts/reverse-chronological)
|
||||||
(max-entries 20)
|
(max-entries 20))
|
||||||
(blog-prefix ""))
|
|
||||||
"Return a builder procedure that renders a list of posts as an Atom
|
"Return a builder procedure that renders a list of posts as an Atom
|
||||||
feed. All arguments are optional:
|
feed. All arguments are optional:
|
||||||
|
|
||||||
|
@ -74,25 +174,28 @@ SUBTITLE: The feed subtitle
|
||||||
FILTER: The procedure called to manipulate the posts list before rendering
|
FILTER: The procedure called to manipulate the posts list before rendering
|
||||||
MAX-ENTRIES: The maximum number of posts to render in the feed"
|
MAX-ENTRIES: The maximum number of posts to render in the feed"
|
||||||
(lambda (site posts)
|
(lambda (site posts)
|
||||||
(make-page file-name
|
(let ((uri (uri->string
|
||||||
`(feed (@ (xmlns "http://www.w3.org/2005/Atom"))
|
(build-uri (site-scheme site)
|
||||||
(title ,(site-title site))
|
#:host (site-domain site)
|
||||||
(subtitle ,subtitle)
|
#:path (string-append "/" file-name)))))
|
||||||
(updated ,(date->string* (current-date)))
|
(make-page file-name
|
||||||
(link (@ (href ,(string-append (site-domain site)
|
`(feed (@ (xmlns "http://www.w3.org/2005/Atom"))
|
||||||
"/" file-name))
|
(title ,(site-title site))
|
||||||
(rel "self")))
|
(id ,uri)
|
||||||
(link (@ (href ,(site-domain site))))
|
(subtitle ,subtitle)
|
||||||
,@(map (cut post->atom-entry site <>
|
(updated ,(date->string* (current-date)))
|
||||||
#:blog-prefix blog-prefix)
|
(link (@ (href ,(string-append (site-domain site)
|
||||||
(take-up-to max-entries (filter posts))))
|
"/" file-name))
|
||||||
sxml->xml*)))
|
(rel "self")))
|
||||||
|
(link (@ (href ,(site-domain site))))
|
||||||
|
,@(map (cut post->atom-entry site <>)
|
||||||
|
(take-up-to max-entries (filter posts))))
|
||||||
|
sxml->xml*))))
|
||||||
|
|
||||||
(define* (atom-feeds-by-tag #:key
|
(define* (atom-feeds-by-tag #:key
|
||||||
(prefix "feeds/tags")
|
(prefix "feeds/tags")
|
||||||
(filter posts/reverse-chronological)
|
(filter posts/reverse-chronological)
|
||||||
(max-entries 20)
|
(max-entries 20))
|
||||||
(blog-prefix ""))
|
|
||||||
"Return a builder procedure that renders an atom feed for every tag
|
"Return a builder procedure that renders an atom feed for every tag
|
||||||
used in a post. All arguments are optional:
|
used in a post. All arguments are optional:
|
||||||
|
|
||||||
|
@ -106,7 +209,6 @@ MAX-ENTRIES: The maximum number of posts to render in each feed"
|
||||||
((atom-feed #:file-name (string-append prefix "/" tag ".xml")
|
((atom-feed #:file-name (string-append prefix "/" tag ".xml")
|
||||||
#:subtitle (string-append "Tag: " tag)
|
#:subtitle (string-append "Tag: " tag)
|
||||||
#:filter filter
|
#:filter filter
|
||||||
#:max-entries max-entries
|
#:max-entries max-entries)
|
||||||
#:blog-prefix blog-prefix)
|
|
||||||
site posts)))
|
site posts)))
|
||||||
tag-groups))))
|
tag-groups))))
|
||||||
|
|
|
@ -69,16 +69,12 @@
|
||||||
" — " ,(date->string* (post-date post)))
|
" — " ,(date->string* (post-date post)))
|
||||||
(div ,(post-sxml post))))
|
(div ,(post-sxml post))))
|
||||||
|
|
||||||
(define (ugly-default-collection-template site title posts prefix)
|
(define (ugly-default-collection-template site title posts)
|
||||||
(define (post-uri post)
|
|
||||||
(string-append (or prefix "") "/"
|
|
||||||
(site-post-slug site post) ".html"))
|
|
||||||
|
|
||||||
`((h3 ,title)
|
`((h3 ,title)
|
||||||
(ul
|
(ul
|
||||||
,@(map (lambda (post)
|
,@(map (lambda (post)
|
||||||
`(li
|
`(li
|
||||||
(a (@ (href ,(post-uri post)))
|
(a (@ (href ,(site-post-path site post)))
|
||||||
,(post-ref post 'title)
|
,(post-ref post 'title)
|
||||||
" — "
|
" — "
|
||||||
,(date->string* (post-date post)))))
|
,(date->string* (post-date post)))))
|
||||||
|
@ -99,8 +95,8 @@
|
||||||
(body ((theme-post-template theme) post)))
|
(body ((theme-post-template theme) post)))
|
||||||
(with-layout theme site title body)))
|
(with-layout theme site title body)))
|
||||||
|
|
||||||
(define (render-collection theme site title posts prefix)
|
(define (render-collection theme site title posts)
|
||||||
(let ((body ((theme-collection-template theme) site title posts prefix)))
|
(let ((body ((theme-collection-template theme) site title posts)))
|
||||||
(with-layout theme site title body)))
|
(with-layout theme site title body)))
|
||||||
|
|
||||||
(define (date->string* date)
|
(define (date->string* date)
|
||||||
|
@ -117,25 +113,24 @@
|
||||||
(collections
|
(collections
|
||||||
`(("Recent Posts" "index.html" ,posts/reverse-chronological))))
|
`(("Recent Posts" "index.html" ,posts/reverse-chronological))))
|
||||||
"Return a procedure that transforms a list of posts into pages
|
"Return a procedure that transforms a list of posts into pages
|
||||||
decorated by THEME, whose URLs start with PREFIX."
|
decorated by THEME. The collection listing URL starts with PREFIX, and
|
||||||
(define (make-file-name base-name)
|
the individual posts URLs start with POST-PREFIX."
|
||||||
|
(define (make-file-name prefix base-name)
|
||||||
(if prefix
|
(if prefix
|
||||||
(string-append prefix "/" base-name)
|
(string-append prefix "/" base-name)
|
||||||
base-name))
|
base-name))
|
||||||
|
|
||||||
(lambda (site posts)
|
(lambda (site posts)
|
||||||
(define (post->page post)
|
(define (post->page post)
|
||||||
(let ((base-name (string-append (site-post-slug site post)
|
(make-page (site-post-relative-path site post)
|
||||||
".html")))
|
(render-post theme site post)
|
||||||
(make-page (make-file-name base-name)
|
sxml->html))
|
||||||
(render-post theme site post)
|
|
||||||
sxml->html)))
|
|
||||||
|
|
||||||
(define collection->page
|
(define collection->page
|
||||||
(match-lambda
|
(match-lambda
|
||||||
((title file-name filter)
|
((title file-name filter)
|
||||||
(make-page (make-file-name file-name)
|
(make-page (make-file-name prefix file-name)
|
||||||
(render-collection theme site title (filter posts) prefix)
|
(render-collection theme site title (filter posts))
|
||||||
sxml->html))))
|
sxml->html))))
|
||||||
|
|
||||||
(append (map post->page posts)
|
(append (map post->page posts)
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
;;; Haunt --- Static site generator for GNU Guile
|
||||||
|
;;; Copyright © 2018 Christopher Lemmer Webber <cwebber@dustycloud.org>
|
||||||
|
;;;
|
||||||
|
;;; This file is part of Haunt.
|
||||||
|
;;;
|
||||||
|
;;; Haunt 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.
|
||||||
|
;;;
|
||||||
|
;;; Haunt 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 Haunt. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
;;
|
||||||
|
;; RSS feed builder.
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-module (haunt builder rss)
|
||||||
|
#:use-module (srfi srfi-19)
|
||||||
|
#:use-module (srfi srfi-26)
|
||||||
|
#:use-module (ice-9 match)
|
||||||
|
#:use-module (sxml simple)
|
||||||
|
#:use-module (haunt site)
|
||||||
|
#:use-module (haunt post)
|
||||||
|
#:use-module (haunt page)
|
||||||
|
#:use-module (haunt utils)
|
||||||
|
#:use-module (haunt html)
|
||||||
|
#:use-module (haunt serve mime-types)
|
||||||
|
#:use-module (haunt builder atom)
|
||||||
|
#:export (rss-feed))
|
||||||
|
|
||||||
|
;; Reader beware: this isn't as nice as atom.scm, because rss isn't
|
||||||
|
;; as nice as atom. Worse beats better on the play field again...
|
||||||
|
|
||||||
|
;; RFC 822 dates are inferior to ISO 8601, but it's
|
||||||
|
;; what RSS wants, so...
|
||||||
|
(define (date->rfc822-str date)
|
||||||
|
(date->string date "~a, ~d ~b ~Y ~T ~z"))
|
||||||
|
|
||||||
|
(define (sxml->xml* sxml port)
|
||||||
|
"Write SXML to PORT, preceded by an <?xml> tag."
|
||||||
|
(display "<?xml version=\"1.0\" encoding=\"utf-8\"?>" port)
|
||||||
|
(sxml->xml sxml port))
|
||||||
|
|
||||||
|
(define* (post->rss-item site post)
|
||||||
|
"Convert POST into an RSS <item> node."
|
||||||
|
`(item
|
||||||
|
(title ,(post-ref post 'title))
|
||||||
|
;; Looks like: <author>lawyer@boyer.net (Lawyer Boyer)</author>
|
||||||
|
(author
|
||||||
|
,(let ((email (post-ref post 'email))
|
||||||
|
(author (post-ref post 'author)))
|
||||||
|
(string-append (if email
|
||||||
|
(string-append email " ")
|
||||||
|
"")
|
||||||
|
(if author
|
||||||
|
(string-append "(" author ")")
|
||||||
|
""))))
|
||||||
|
(pubDate ,(date->rfc822-str (post-date post)))
|
||||||
|
(link (@ (href ,(site-post-url site post))
|
||||||
|
(rel "alternate")))
|
||||||
|
(description ,(sxml->html-string (post-sxml post)))
|
||||||
|
,@(map (lambda (enclosure)
|
||||||
|
`(enclosure (@ (title ,(enclosure-title enclosure))
|
||||||
|
(url ,(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* (rss-feed #:key
|
||||||
|
(file-name "rss-feed.xml")
|
||||||
|
(subtitle "Recent Posts")
|
||||||
|
(filter posts/reverse-chronological)
|
||||||
|
(max-entries 20))
|
||||||
|
"Return a builder procedure that renders a list of posts as an RSS
|
||||||
|
feed. All arguments are optional:
|
||||||
|
|
||||||
|
FILE-NAME: The page file name
|
||||||
|
SUBTITLE: The feed subtitle
|
||||||
|
FILTER: The procedure called to manipulate the posts list before rendering
|
||||||
|
MAX-ENTRIES: The maximum number of posts to render in the feed"
|
||||||
|
(lambda (site posts)
|
||||||
|
(make-page file-name
|
||||||
|
`(rss (@ (version "2.0"))
|
||||||
|
(channel
|
||||||
|
(title ,(site-title site))
|
||||||
|
;; It looks like RSS's description and atom's subtitle
|
||||||
|
;; are equivalent?
|
||||||
|
(description ,subtitle)
|
||||||
|
(pubDate ,(date->rfc822-str (current-date)))
|
||||||
|
(link (@ (href ,(site-domain site))))
|
||||||
|
,@(map (cut post->rss-item site <>)
|
||||||
|
(take-up-to max-entries (filter posts)))))
|
||||||
|
sxml->xml*)))
|
|
@ -36,13 +36,14 @@
|
||||||
post-sxml
|
post-sxml
|
||||||
post-metadata
|
post-metadata
|
||||||
post-ref
|
post-ref
|
||||||
|
post-ref-all
|
||||||
post-slug
|
post-slug
|
||||||
%default-date
|
%default-date
|
||||||
post-date
|
post-date
|
||||||
posts/reverse-chronological
|
posts/reverse-chronological
|
||||||
posts/group-by-tag
|
posts/group-by-tag
|
||||||
|
|
||||||
register-metdata-parser!
|
register-metadata-parser!
|
||||||
parse-metadata
|
parse-metadata
|
||||||
read-metadata-headers))
|
read-metadata-headers))
|
||||||
|
|
||||||
|
@ -57,6 +58,13 @@
|
||||||
"Return the metadata corresponding to KEY within POST."
|
"Return the metadata corresponding to KEY within POST."
|
||||||
(assq-ref (post-metadata post) key))
|
(assq-ref (post-metadata post) key))
|
||||||
|
|
||||||
|
(define (post-ref-all post key)
|
||||||
|
"Return a list of all metadata values for KEY within POST."
|
||||||
|
(filter-map (match-lambda
|
||||||
|
((k . v)
|
||||||
|
(and (eq? key k) v)))
|
||||||
|
(post-metadata post)))
|
||||||
|
|
||||||
(define char-set:slug
|
(define char-set:slug
|
||||||
(char-set-union char-set:letter+digit (char-set #\-)))
|
(char-set-union char-set:letter+digit (char-set #\-)))
|
||||||
|
|
||||||
|
|
|
@ -105,8 +105,17 @@ post."
|
||||||
|
|
||||||
(define (read-html-post port)
|
(define (read-html-post port)
|
||||||
(values (read-metadata-headers port)
|
(values (read-metadata-headers port)
|
||||||
(match (xml->sxml port)
|
(let loop ()
|
||||||
(('*TOP* sxml) sxml))))
|
(let ((next-char (peek-char port)))
|
||||||
|
(cond
|
||||||
|
((eof-object? next-char)
|
||||||
|
'())
|
||||||
|
((char-set-contains? char-set:whitespace next-char)
|
||||||
|
(read-char port)
|
||||||
|
(loop))
|
||||||
|
(else
|
||||||
|
(match (xml->sxml port)
|
||||||
|
(('*TOP* sxml) (cons sxml (loop))))))))))
|
||||||
|
|
||||||
(define html-reader
|
(define html-reader
|
||||||
(make-reader (make-file-extension-matcher "html")
|
(make-reader (make-file-extension-matcher "html")
|
||||||
|
|
|
@ -33,30 +33,37 @@
|
||||||
#:use-module (haunt page)
|
#:use-module (haunt page)
|
||||||
#:use-module (haunt post)
|
#:use-module (haunt post)
|
||||||
#:use-module (haunt asset)
|
#:use-module (haunt asset)
|
||||||
|
#:use-module (web uri)
|
||||||
#:export (site
|
#:export (site
|
||||||
site?
|
site?
|
||||||
site-title
|
site-title
|
||||||
site-domain
|
site-domain
|
||||||
|
site-scheme
|
||||||
site-posts-directory
|
site-posts-directory
|
||||||
|
site-posts-output-directory
|
||||||
site-file-filter
|
site-file-filter
|
||||||
site-build-directory
|
site-build-directory
|
||||||
site-default-metadata
|
site-default-metadata
|
||||||
site-make-slug
|
site-make-slug
|
||||||
site-readers
|
site-readers
|
||||||
site-builders
|
site-builders
|
||||||
site-post-slug
|
site-post-path
|
||||||
|
site-post-relative-path
|
||||||
|
site-post-url
|
||||||
build-site
|
build-site
|
||||||
|
|
||||||
make-file-filter
|
make-file-filter
|
||||||
default-file-filter))
|
default-file-filter))
|
||||||
|
|
||||||
(define-record-type <site>
|
(define-record-type <site>
|
||||||
(make-site title domain posts-directory file-filter build-directory
|
(make-site title domain scheme posts-directory posts-output-directory file-filter
|
||||||
default-metadata make-slug readers builders)
|
build-directory default-metadata make-slug readers builders)
|
||||||
site?
|
site?
|
||||||
(title site-title)
|
(title site-title)
|
||||||
(domain site-domain)
|
(domain site-domain)
|
||||||
|
(scheme site-scheme) ; https or http
|
||||||
(posts-directory site-posts-directory)
|
(posts-directory site-posts-directory)
|
||||||
|
(posts-output-directory site-posts-output-directory)
|
||||||
(file-filter site-file-filter)
|
(file-filter site-file-filter)
|
||||||
(build-directory site-build-directory)
|
(build-directory site-build-directory)
|
||||||
(default-metadata site-default-metadata)
|
(default-metadata site-default-metadata)
|
||||||
|
@ -67,7 +74,9 @@
|
||||||
(define* (site #:key
|
(define* (site #:key
|
||||||
(title "This Place is Haunted")
|
(title "This Place is Haunted")
|
||||||
(domain "example.com")
|
(domain "example.com")
|
||||||
|
(scheme 'https)
|
||||||
(posts-directory "posts")
|
(posts-directory "posts")
|
||||||
|
(posts-output-directory "")
|
||||||
(file-filter default-file-filter)
|
(file-filter default-file-filter)
|
||||||
(build-directory "site")
|
(build-directory "site")
|
||||||
(default-metadata '())
|
(default-metadata '())
|
||||||
|
@ -77,7 +86,10 @@
|
||||||
"Create a new site object. All arguments are optional:
|
"Create a new site object. All arguments are optional:
|
||||||
|
|
||||||
TITLE: The name of the site
|
TITLE: The name of the site
|
||||||
|
DOMAIN: The domain that will host the site
|
||||||
|
SCHEME: Either 'https' or 'http' ('https' by default)
|
||||||
POSTS-DIRECTORY: The directory where posts are found
|
POSTS-DIRECTORY: The directory where posts are found
|
||||||
|
POSTS-OUTPUT-DIRECTORY: The directory to store the built posts in
|
||||||
FILE-FILTER: A predicate procedure that returns #f when a post file
|
FILE-FILTER: A predicate procedure that returns #f when a post file
|
||||||
should be ignored, and #f otherwise. Emacs temp files are ignored by
|
should be ignored, and #f otherwise. Emacs temp files are ignored by
|
||||||
default.
|
default.
|
||||||
|
@ -87,13 +99,33 @@ whose keys are symbols
|
||||||
MAKE-SLUG: A procedure generating a file name slug from a post
|
MAKE-SLUG: A procedure generating a file name slug from a post
|
||||||
READERS: A list of reader objects for processing posts
|
READERS: A list of reader objects for processing posts
|
||||||
BUILDERS: A list of procedures for building pages from posts"
|
BUILDERS: A list of procedures for building pages from posts"
|
||||||
(make-site title domain posts-directory file-filter build-directory
|
(make-site title domain scheme posts-directory posts-output-directory file-filter
|
||||||
default-metadata make-slug readers builders))
|
build-directory default-metadata make-slug readers builders))
|
||||||
|
|
||||||
(define (site-post-slug site post)
|
(define (site-post-slug site post)
|
||||||
"Return a slug string for POST using the slug generator for SITE."
|
"Return a slug string for POST using the slug generator for SITE."
|
||||||
((site-make-slug site) post))
|
((site-make-slug site) post))
|
||||||
|
|
||||||
|
(define (site-post-relative-path site post)
|
||||||
|
"Return a relative path to a POST, without the preceding slash `/'."
|
||||||
|
(let ((base-path (string-trim-right (site-posts-output-directory site)
|
||||||
|
(lambda (c) (equal? c #\/)))))
|
||||||
|
(string-append (if (equal? base-path "")
|
||||||
|
""
|
||||||
|
(string-append base-path "/"))
|
||||||
|
(site-post-slug site post)
|
||||||
|
".html")))
|
||||||
|
|
||||||
|
(define (site-post-path site post)
|
||||||
|
"Return a path to a POST, with the preceding slash `/'."
|
||||||
|
(string-append "/" (site-post-relative-path site post)))
|
||||||
|
|
||||||
|
(define (site-post-url site post)
|
||||||
|
"Return a full URL to a POST."
|
||||||
|
(build-uri (site-scheme site)
|
||||||
|
#:host (site-domain site)
|
||||||
|
#:path (site-post-path site post)))
|
||||||
|
|
||||||
(define (build-site site)
|
(define (build-site site)
|
||||||
"Build SITE in the appropriate build directory."
|
"Build SITE in the appropriate build directory."
|
||||||
(let ((posts (if (file-exists? (site-posts-directory site))
|
(let ((posts (if (file-exists? (site-posts-directory site))
|
||||||
|
|
|
@ -29,7 +29,9 @@
|
||||||
#:export (post
|
#:export (post
|
||||||
|
|
||||||
p blockquote em
|
p blockquote em
|
||||||
h1 h2 h3 h4
|
h1 h2 h3 h4 h5 h6
|
||||||
|
section
|
||||||
|
nav aside
|
||||||
code pre strong
|
code pre strong
|
||||||
ul ol li dl dt dd
|
ul ol li dl dt dd
|
||||||
anchor
|
anchor
|
||||||
|
@ -60,7 +62,9 @@ contents from METADATA+SXML."
|
||||||
em strong
|
em strong
|
||||||
code samp pre kbd var
|
code samp pre kbd var
|
||||||
cite dfn abbr
|
cite dfn abbr
|
||||||
h1 h2 h3 h4
|
h1 h2 h3 h4 h5 h6
|
||||||
|
section
|
||||||
|
nav aside
|
||||||
ul ol li dl dt dd)
|
ul ol li dl dt dd)
|
||||||
|
|
||||||
(define (anchor text uri)
|
(define (anchor text uri)
|
||||||
|
|
|
@ -117,11 +117,7 @@ culture works available under the " ,%cc-by-sa-link " license.")))))))
|
||||||
" — " ,(date->string* (post-date post)))
|
" — " ,(date->string* (post-date post)))
|
||||||
(div ,(post-sxml post))))
|
(div ,(post-sxml post))))
|
||||||
#:collection-template
|
#:collection-template
|
||||||
(lambda (site title posts prefix)
|
(lambda (site title posts)
|
||||||
(define (post-uri post)
|
|
||||||
(string-append "/" (or prefix "")
|
|
||||||
(site-post-slug site post) ".html"))
|
|
||||||
|
|
||||||
`(,(jumbotron
|
`(,(jumbotron
|
||||||
`((p "Haunt is a simple, functional, hackable static site
|
`((p "Haunt is a simple, functional, hackable static site
|
||||||
generator written in Guile Scheme that gives authors the ability to
|
generator written in Guile Scheme that gives authors the ability to
|
||||||
|
@ -164,7 +160,7 @@ without needing to upload the generated files to a web server.")
|
||||||
(ul
|
(ul
|
||||||
,@(map (lambda (post)
|
,@(map (lambda (post)
|
||||||
`(li
|
`(li
|
||||||
(a (@ (href ,(post-uri post)))
|
(a (@ (href ,(site-post-path site post)))
|
||||||
,(post-ref post 'title)
|
,(post-ref post 'title)
|
||||||
" — "
|
" — "
|
||||||
,(date->string* (post-date post)))))
|
,(date->string* (post-date post)))))
|
||||||
|
|
Loading…
Reference in New Issue