Integrating my publishing system using org-publish
Seems like I may need to purge my web site source, or maybe use a less-intense system.
This commit is contained in:
parent
465fb840c1
commit
0afe98086d
4 changed files with 619 additions and 2 deletions
|
@ -19,6 +19,7 @@ This creates [[file:~/.emacs.d/init.el][~/.emacs.d/init.el]] that starts the pro
|
||||||
- [[file:ha-org-word-processor.org][ha-org-word-processor.org]] :: attempts to make Org files /visually/ look like what one might see in a word processor, including turning off the colors for headers, and instead increasing their size.
|
- [[file:ha-org-word-processor.org][ha-org-word-processor.org]] :: attempts to make Org files /visually/ look like what one might see in a word processor, including turning off the colors for headers, and instead increasing their size.
|
||||||
- [[file:ha-org-clipboard.org][ha-org-clipboard.org]] :: automatically converting HTML from a clipboard into Org-formatted content.
|
- [[file:ha-org-clipboard.org][ha-org-clipboard.org]] :: automatically converting HTML from a clipboard into Org-formatted content.
|
||||||
- [[file:ha-org-journaling.org][ha-org-journaling.org]] :: for writing journal entries and tasks.
|
- [[file:ha-org-journaling.org][ha-org-journaling.org]] :: for writing journal entries and tasks.
|
||||||
|
- [[file:ha-org-publishing.org][ha-org-publishing.org]] :: code for publishing my website, [[http://howardism.org][www.howardism.org]].
|
||||||
- [[file:ha-org-sprint.org][ha-org-sprint.org]] :: functions for working with the my Org-focused sprint files.
|
- [[file:ha-org-sprint.org][ha-org-sprint.org]] :: functions for working with the my Org-focused sprint files.
|
||||||
- [[file:ha-remoting.org][ha-remoting.org]] :: my interface to systems using SSH and Vterm.
|
- [[file:ha-remoting.org][ha-remoting.org]] :: my interface to systems using SSH and Vterm.
|
||||||
- [[file:ha-feed-reader.org][ha-feed-reader.org]] :: configuration of elfeed as well as my RSS feeds.
|
- [[file:ha-feed-reader.org][ha-feed-reader.org]] :: configuration of elfeed as well as my RSS feeds.
|
||||||
|
|
|
@ -49,7 +49,8 @@ Let's get the Straight project working with =use-package=:
|
||||||
While that enables the =:straight t= extension to =use-package=, let's just have that be the default:
|
While that enables the =:straight t= extension to =use-package=, let's just have that be the default:
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(use-package straight
|
(use-package straight
|
||||||
:custom (straight-use-package-by-default t))
|
:custom (straight-use-package-by-default t
|
||||||
|
straight-default-vc 'git))
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
See the details in [[https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8][this essay]].
|
See the details in [[https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8][this essay]].
|
||||||
** Basic Libraries
|
** Basic Libraries
|
||||||
|
@ -113,7 +114,7 @@ The following loads the rest of my org-mode literate files. I add them as they a
|
||||||
"ha-org-word-processor.org"
|
"ha-org-word-processor.org"
|
||||||
"ha-org-clipboard.org"
|
"ha-org-clipboard.org"
|
||||||
"ha-org-journaling.org"
|
"ha-org-journaling.org"
|
||||||
;; "org-publishing.org"
|
"ha-org-publishing.org"
|
||||||
"ha-org-sprint.org"
|
"ha-org-sprint.org"
|
||||||
"ha-capturing-notes.org"
|
"ha-capturing-notes.org"
|
||||||
"ha-programming.org"
|
"ha-programming.org"
|
||||||
|
|
414
elisp/ox-rss.el
Normal file
414
elisp/ox-rss.el
Normal file
|
@ -0,0 +1,414 @@
|
||||||
|
;;; ox-rss.el --- RSS 2.0 Back-End for Org Export Engine
|
||||||
|
|
||||||
|
;; Copyright (C) 2013-2015 Bastien Guerry
|
||||||
|
|
||||||
|
;; Author: Bastien Guerry <bzg@gnu.org>
|
||||||
|
;; Keywords: org, wp, blog, feed, rss
|
||||||
|
|
||||||
|
;; This file is not yet part of GNU Emacs.
|
||||||
|
|
||||||
|
;; 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
;;; Commentary:
|
||||||
|
|
||||||
|
;; This library implements a RSS 2.0 back-end for Org exporter, based on
|
||||||
|
;; the `html' back-end.
|
||||||
|
;;
|
||||||
|
;; It requires Emacs 24.1 at least.
|
||||||
|
;;
|
||||||
|
;; It provides two commands for export, depending on the desired output:
|
||||||
|
;; `org-rss-export-as-rss' (temporary buffer) and `org-rss-export-to-rss'
|
||||||
|
;; (as a ".xml" file).
|
||||||
|
;;
|
||||||
|
;; This backend understands two new option keywords:
|
||||||
|
;;
|
||||||
|
;; #+RSS_EXTENSION: xml
|
||||||
|
;; #+RSS_IMAGE_URL: http://myblog.org/mypicture.jpg
|
||||||
|
;;
|
||||||
|
;; It uses #+HTML_LINK_HOME: to set the base url of the feed.
|
||||||
|
;;
|
||||||
|
;; Exporting an Org file to RSS modifies each top-level entry by adding a
|
||||||
|
;; PUBDATE property. If `org-rss-use-entry-url-as-guid', it will also add
|
||||||
|
;; an ID property, later used as the guid for the feed's item.
|
||||||
|
;;
|
||||||
|
;; The top-level headline is used as the title of each RSS item unless
|
||||||
|
;; an RSS_TITLE property is set on the headline.
|
||||||
|
;;
|
||||||
|
;; You typically want to use it within a publishing project like this:
|
||||||
|
;;
|
||||||
|
;; (add-to-list
|
||||||
|
;; 'org-publish-project-alist
|
||||||
|
;; '("homepage_rss"
|
||||||
|
;; :base-directory "~/myhomepage/"
|
||||||
|
;; :base-extension "org"
|
||||||
|
;; :rss-image-url "http://lumiere.ens.fr/~guerry/images/faces/15.png"
|
||||||
|
;; :html-link-home "http://lumiere.ens.fr/~guerry/"
|
||||||
|
;; :html-link-use-abs-url t
|
||||||
|
;; :rss-extension "xml"
|
||||||
|
;; :publishing-directory "/home/guerry/public_html/"
|
||||||
|
;; :publishing-function (org-rss-publish-to-rss)
|
||||||
|
;; :section-numbers nil
|
||||||
|
;; :exclude ".*" ;; To exclude all files...
|
||||||
|
;; :include ("index.org") ;; ... except index.org.
|
||||||
|
;; :table-of-contents nil))
|
||||||
|
;;
|
||||||
|
;; ... then rsync /home/guerry/public_html/ with your server.
|
||||||
|
;;
|
||||||
|
;; By default, the permalink for a blog entry points to the headline.
|
||||||
|
;; You can specify a different one by using the :RSS_PERMALINK:
|
||||||
|
;; property within an entry.
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(require 'ox-html)
|
||||||
|
(declare-function url-encode-url "url-util" (url))
|
||||||
|
|
||||||
|
;;; Variables and options
|
||||||
|
|
||||||
|
(defgroup org-export-rss nil
|
||||||
|
"Options specific to RSS export back-end."
|
||||||
|
:tag "Org RSS"
|
||||||
|
:group 'org-export
|
||||||
|
:version "24.4"
|
||||||
|
:package-version '(Org . "8.0"))
|
||||||
|
|
||||||
|
(defcustom org-rss-image-url "http://orgmode.org/img/org-mode-unicorn-logo.png"
|
||||||
|
"The URL of the an image for the RSS feed."
|
||||||
|
:group 'org-export-rss
|
||||||
|
:type 'string)
|
||||||
|
|
||||||
|
(defcustom org-rss-extension "xml"
|
||||||
|
"File extension for the RSS 2.0 feed."
|
||||||
|
:group 'org-export-rss
|
||||||
|
:type 'string)
|
||||||
|
|
||||||
|
(defcustom org-rss-categories 'from-tags
|
||||||
|
"Where to extract items category information from.
|
||||||
|
The default is to extract categories from the tags of the
|
||||||
|
headlines. When set to another value, extract the category
|
||||||
|
from the :CATEGORY: property of the entry."
|
||||||
|
:group 'org-export-rss
|
||||||
|
:type '(choice
|
||||||
|
(const :tag "From tags" from-tags)
|
||||||
|
(const :tag "From the category property" from-category)))
|
||||||
|
|
||||||
|
(defcustom org-rss-use-entry-url-as-guid t
|
||||||
|
"Use the URL for the <guid> metatag?
|
||||||
|
When nil, Org will create ids using `org-icalendar-create-uid'."
|
||||||
|
:group 'org-export-rss
|
||||||
|
:type 'boolean)
|
||||||
|
|
||||||
|
;;; Define backend
|
||||||
|
|
||||||
|
(org-export-define-derived-backend 'rss 'html
|
||||||
|
:menu-entry
|
||||||
|
'(?r "Export to RSS"
|
||||||
|
((?R "As RSS buffer"
|
||||||
|
(lambda (a s v b) (org-rss-export-as-rss a s v)))
|
||||||
|
(?r "As RSS file" (lambda (a s v b) (org-rss-export-to-rss a s v)))
|
||||||
|
(?o "As RSS file and open"
|
||||||
|
(lambda (a s v b)
|
||||||
|
(if a (org-rss-export-to-rss t s v)
|
||||||
|
(org-open-file (org-rss-export-to-rss nil s v)))))))
|
||||||
|
:options-alist
|
||||||
|
'((:description "DESCRIPTION" nil nil newline)
|
||||||
|
(:keywords "KEYWORDS" nil nil space)
|
||||||
|
(:with-toc nil nil nil) ;; Never include HTML's toc
|
||||||
|
(:rss-extension "RSS_EXTENSION" nil org-rss-extension)
|
||||||
|
(:rss-image-url "RSS_IMAGE_URL" nil org-rss-image-url)
|
||||||
|
(:rss-categories nil nil org-rss-categories))
|
||||||
|
:filters-alist '((:filter-final-output . org-rss-final-function))
|
||||||
|
:translate-alist '((headline . org-rss-headline)
|
||||||
|
(comment . (lambda (&rest args) ""))
|
||||||
|
(comment-block . (lambda (&rest args) ""))
|
||||||
|
(timestamp . (lambda (&rest args) ""))
|
||||||
|
(plain-text . org-rss-plain-text)
|
||||||
|
(section . org-rss-section)
|
||||||
|
(template . org-rss-template)))
|
||||||
|
|
||||||
|
;;; Export functions
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun org-rss-export-as-rss (&optional async subtreep visible-only)
|
||||||
|
"Export current buffer to a RSS buffer.
|
||||||
|
|
||||||
|
If narrowing is active in the current buffer, only export its
|
||||||
|
narrowed part.
|
||||||
|
|
||||||
|
If a region is active, export that region.
|
||||||
|
|
||||||
|
A non-nil optional argument ASYNC means the process should happen
|
||||||
|
asynchronously. The resulting buffer should be accessible
|
||||||
|
through the `org-export-stack' interface.
|
||||||
|
|
||||||
|
When optional argument SUBTREEP is non-nil, export the sub-tree
|
||||||
|
at point, extracting information from the headline properties
|
||||||
|
first.
|
||||||
|
|
||||||
|
When optional argument VISIBLE-ONLY is non-nil, don't export
|
||||||
|
contents of hidden elements.
|
||||||
|
|
||||||
|
Export is done in a buffer named \"*Org RSS Export*\", which will
|
||||||
|
be displayed when `org-export-show-temporary-export-buffer' is
|
||||||
|
non-nil."
|
||||||
|
(interactive)
|
||||||
|
(let ((file (buffer-file-name (buffer-base-buffer))))
|
||||||
|
(org-icalendar-create-uid file 'warn-user)
|
||||||
|
(org-rss-add-pubdate-property))
|
||||||
|
(org-export-to-buffer 'rss "*Org RSS Export*"
|
||||||
|
async subtreep visible-only nil nil (lambda () (text-mode))))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun org-rss-export-to-rss (&optional async subtreep visible-only)
|
||||||
|
"Export current buffer to a RSS file.
|
||||||
|
|
||||||
|
If narrowing is active in the current buffer, only export its
|
||||||
|
narrowed part.
|
||||||
|
|
||||||
|
If a region is active, export that region.
|
||||||
|
|
||||||
|
A non-nil optional argument ASYNC means the process should happen
|
||||||
|
asynchronously. The resulting file should be accessible through
|
||||||
|
the `org-export-stack' interface.
|
||||||
|
|
||||||
|
When optional argument SUBTREEP is non-nil, export the sub-tree
|
||||||
|
at point, extracting information from the headline properties
|
||||||
|
first.
|
||||||
|
|
||||||
|
When optional argument VISIBLE-ONLY is non-nil, don't export
|
||||||
|
contents of hidden elements.
|
||||||
|
|
||||||
|
Return output file's name."
|
||||||
|
(interactive)
|
||||||
|
(let ((file (buffer-file-name (buffer-base-buffer))))
|
||||||
|
(org-icalendar-create-uid file 'warn-user)
|
||||||
|
(org-rss-add-pubdate-property))
|
||||||
|
(let ((outfile (org-export-output-file-name
|
||||||
|
(concat "." org-rss-extension) subtreep)))
|
||||||
|
(org-export-to-file 'rss outfile async subtreep visible-only)))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun org-rss-publish-to-rss (plist filename pub-dir)
|
||||||
|
"Publish an org file to RSS.
|
||||||
|
|
||||||
|
FILENAME is the filename of the Org file to be published. PLIST
|
||||||
|
is the property list for the given project. PUB-DIR is the
|
||||||
|
publishing directory.
|
||||||
|
|
||||||
|
Return output file name."
|
||||||
|
(let ((bf (get-file-buffer filename)))
|
||||||
|
(if bf
|
||||||
|
(with-current-buffer bf
|
||||||
|
(org-icalendar-create-uid filename 'warn-user)
|
||||||
|
(org-rss-add-pubdate-property)
|
||||||
|
(write-file filename))
|
||||||
|
(find-file filename)
|
||||||
|
(org-icalendar-create-uid filename 'warn-user)
|
||||||
|
(org-rss-add-pubdate-property)
|
||||||
|
(write-file filename) (kill-buffer)))
|
||||||
|
(org-publish-org-to
|
||||||
|
'rss filename (concat "." org-rss-extension) plist pub-dir))
|
||||||
|
|
||||||
|
;;; Main transcoding functions
|
||||||
|
|
||||||
|
(defun org-rss-headline (headline contents info)
|
||||||
|
"Transcode HEADLINE element into RSS format.
|
||||||
|
CONTENTS is the headline contents. INFO is a plist used as a
|
||||||
|
communication channel."
|
||||||
|
(unless (or (org-element-property :footnote-section-p headline)
|
||||||
|
;; Only consider first-level headlines
|
||||||
|
(> (org-export-get-relative-level headline info) 1))
|
||||||
|
(let* ((author (and (plist-get info :with-author)
|
||||||
|
(let ((auth (plist-get info :author)))
|
||||||
|
(and auth (org-export-data auth info)))))
|
||||||
|
(htmlext (plist-get info :html-extension))
|
||||||
|
(hl-number (org-export-get-headline-number headline info))
|
||||||
|
(hl-home (file-name-as-directory (plist-get info :html-link-home)))
|
||||||
|
(hl-pdir (plist-get info :publishing-directory))
|
||||||
|
(hl-perm (org-element-property :RSS_PERMALINK headline))
|
||||||
|
(anchor (org-export-get-reference headline info))
|
||||||
|
(category (org-rss-plain-text
|
||||||
|
(or (org-element-property :CATEGORY headline) "") info))
|
||||||
|
(pubdate0 (org-element-property :PUBDATE headline))
|
||||||
|
(pubdate (let ((system-time-locale "C"))
|
||||||
|
(if pubdate0
|
||||||
|
(format-time-string
|
||||||
|
"%a, %d %b %Y %H:%M:%S %z"
|
||||||
|
(org-time-string-to-time pubdate0)))))
|
||||||
|
(title (or (org-element-property :RSS_TITLE headline)
|
||||||
|
(replace-regexp-in-string
|
||||||
|
org-bracket-link-regexp
|
||||||
|
(lambda (m) (or (match-string 3 m)
|
||||||
|
(match-string 1 m)))
|
||||||
|
(org-element-property :raw-value headline))))
|
||||||
|
(publink
|
||||||
|
(or (and hl-perm (concat (or hl-home hl-pdir) hl-perm))
|
||||||
|
(concat
|
||||||
|
(or hl-home hl-pdir)
|
||||||
|
(file-name-nondirectory
|
||||||
|
(file-name-sans-extension
|
||||||
|
(plist-get info :input-file))) "." htmlext "#" anchor)))
|
||||||
|
(guid (if org-rss-use-entry-url-as-guid
|
||||||
|
publink
|
||||||
|
(org-rss-plain-text
|
||||||
|
(or (org-element-property :ID headline)
|
||||||
|
(org-element-property :CUSTOM_ID headline)
|
||||||
|
publink)
|
||||||
|
info))))
|
||||||
|
(if (not pubdate0) "" ;; Skip entries with no PUBDATE prop
|
||||||
|
(format
|
||||||
|
(concat
|
||||||
|
"<item>\n"
|
||||||
|
"<title>%s</title>\n"
|
||||||
|
"<link>%s</link>\n"
|
||||||
|
"<author>%s</author>\n"
|
||||||
|
"<guid isPermaLink=\"false\">%s</guid>\n"
|
||||||
|
"<pubDate>%s</pubDate>\n"
|
||||||
|
(org-rss-build-categories headline info) "\n"
|
||||||
|
"<description><![CDATA[%s]]></description>\n"
|
||||||
|
"</item>\n")
|
||||||
|
title publink author guid pubdate contents)))))
|
||||||
|
|
||||||
|
(defun org-rss-build-categories (headline info)
|
||||||
|
"Build categories for the RSS item."
|
||||||
|
(if (eq (plist-get info :rss-categories) 'from-tags)
|
||||||
|
(mapconcat
|
||||||
|
(lambda (c) (format "<category><![CDATA[%s]]></category>" c))
|
||||||
|
(org-element-property :tags headline)
|
||||||
|
"\n")
|
||||||
|
(let ((c (org-element-property :CATEGORY headline)))
|
||||||
|
(format "<category><![CDATA[%s]]></category>" c))))
|
||||||
|
|
||||||
|
(defun org-rss-template (contents info)
|
||||||
|
"Return complete document string after RSS conversion.
|
||||||
|
CONTENTS is the transcoded contents string. INFO is a plist used
|
||||||
|
as a communication channel."
|
||||||
|
(concat
|
||||||
|
(format "<?xml version=\"1.0\" encoding=\"%s\"?>"
|
||||||
|
(symbol-name org-html-coding-system))
|
||||||
|
"\n<rss version=\"2.0\"
|
||||||
|
xmlns:content=\"http://purl.org/rss/1.0/modules/content/\"
|
||||||
|
xmlns:wfw=\"http://wellformedweb.org/CommentAPI/\"
|
||||||
|
xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
|
||||||
|
xmlns:atom=\"http://www.w3.org/2005/Atom\"
|
||||||
|
xmlns:sy=\"http://purl.org/rss/1.0/modules/syndication/\"
|
||||||
|
xmlns:slash=\"http://purl.org/rss/1.0/modules/slash/\"
|
||||||
|
xmlns:georss=\"http://www.georss.org/georss\"
|
||||||
|
xmlns:geo=\"http://www.w3.org/2003/01/geo/wgs84_pos#\"
|
||||||
|
xmlns:media=\"http://search.yahoo.com/mrss/\">"
|
||||||
|
"<channel>"
|
||||||
|
(org-rss-build-channel-info info) "\n"
|
||||||
|
contents
|
||||||
|
"</channel>\n"
|
||||||
|
"</rss>"))
|
||||||
|
|
||||||
|
(defun org-rss-build-channel-info (info)
|
||||||
|
"Build the RSS channel information."
|
||||||
|
(let* ((system-time-locale "C")
|
||||||
|
(title (plist-get info :title))
|
||||||
|
(email (org-export-data (plist-get info :email) info))
|
||||||
|
(author (and (plist-get info :with-author)
|
||||||
|
(let ((auth (plist-get info :author)))
|
||||||
|
(and auth (org-export-data auth info)))))
|
||||||
|
(date (format-time-string "%a, %d %b %Y %H:%M:%S %z")) ;; RFC 882
|
||||||
|
(description (org-export-data (plist-get info :description) info))
|
||||||
|
(lang (plist-get info :language))
|
||||||
|
(keywords (plist-get info :keywords))
|
||||||
|
(rssext (plist-get info :rss-extension))
|
||||||
|
(blogurl (or (plist-get info :html-link-home)
|
||||||
|
(plist-get info :publishing-directory)))
|
||||||
|
(image (url-encode-url (plist-get info :rss-image-url)))
|
||||||
|
(ifile (plist-get info :input-file))
|
||||||
|
(publink
|
||||||
|
(concat (file-name-as-directory blogurl)
|
||||||
|
(file-name-nondirectory
|
||||||
|
(file-name-sans-extension ifile))
|
||||||
|
"." rssext)))
|
||||||
|
(format
|
||||||
|
"\n<title>%s</title>
|
||||||
|
<atom:link href=\"%s\" rel=\"self\" type=\"application/rss+xml\" />
|
||||||
|
<link>%s</link>
|
||||||
|
<description><![CDATA[%s]]></description>
|
||||||
|
<language>%s</language>
|
||||||
|
<pubDate>%s</pubDate>
|
||||||
|
<lastBuildDate>%s</lastBuildDate>
|
||||||
|
<generator>%s</generator>
|
||||||
|
<webMaster>%s (%s)</webMaster>
|
||||||
|
<image>
|
||||||
|
<url>%s</url>
|
||||||
|
<title>%s</title>
|
||||||
|
<link>%s</link>
|
||||||
|
</image>
|
||||||
|
"
|
||||||
|
title publink blogurl description lang date date
|
||||||
|
(concat (format "Emacs %d.%d"
|
||||||
|
emacs-major-version
|
||||||
|
emacs-minor-version)
|
||||||
|
" Org-mode " (org-version))
|
||||||
|
email author image title blogurl)))
|
||||||
|
|
||||||
|
(defun org-rss-section (section contents info)
|
||||||
|
"Transcode SECTION element into RSS format.
|
||||||
|
CONTENTS is the section contents. INFO is a plist used as
|
||||||
|
a communication channel."
|
||||||
|
contents)
|
||||||
|
|
||||||
|
(defun org-rss-timestamp (timestamp contents info)
|
||||||
|
"Transcode a TIMESTAMP object from Org to RSS.
|
||||||
|
CONTENTS is nil. INFO is a plist holding contextual
|
||||||
|
information."
|
||||||
|
(org-html-encode-plain-text
|
||||||
|
(org-timestamp-translate timestamp)))
|
||||||
|
|
||||||
|
(defun org-rss-plain-text (contents info)
|
||||||
|
"Convert plain text into RSS encoded text."
|
||||||
|
(let (output)
|
||||||
|
(setq output (org-html-encode-plain-text contents)
|
||||||
|
output (org-export-activate-smart-quotes
|
||||||
|
output :html info))))
|
||||||
|
|
||||||
|
;;; Filters
|
||||||
|
|
||||||
|
(defun org-rss-final-function (contents backend info)
|
||||||
|
"Prettify the RSS output."
|
||||||
|
(with-temp-buffer
|
||||||
|
(xml-mode)
|
||||||
|
(insert contents)
|
||||||
|
(indent-region (point-min) (point-max))
|
||||||
|
(buffer-substring-no-properties (point-min) (point-max))))
|
||||||
|
|
||||||
|
;;; Miscellaneous
|
||||||
|
|
||||||
|
(defun org-rss-add-pubdate-property ()
|
||||||
|
"Set the PUBDATE property for top-level headlines."
|
||||||
|
(let (msg)
|
||||||
|
(org-map-entries
|
||||||
|
(lambda ()
|
||||||
|
(let* ((entry (org-element-at-point))
|
||||||
|
(level (org-element-property :level entry)))
|
||||||
|
(when (= level 1)
|
||||||
|
(unless (org-entry-get (point) "PUBDATE")
|
||||||
|
(setq msg t)
|
||||||
|
(org-set-property
|
||||||
|
"PUBDATE" (format-time-string
|
||||||
|
(cdr org-time-stamp-formats)))))))
|
||||||
|
nil nil 'comment 'archive)
|
||||||
|
(when msg
|
||||||
|
(message "Property PUBDATE added to top-level entries in %s"
|
||||||
|
(buffer-file-name))
|
||||||
|
(sit-for 2))))
|
||||||
|
|
||||||
|
(provide 'ox-rss)
|
||||||
|
|
||||||
|
;;; ox-rss.el ends here
|
201
ha-org-publishing.org
Normal file
201
ha-org-publishing.org
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
#+TITLE: Publishing my Website with Org
|
||||||
|
#+AUTHOR: Howard X. Abrams
|
||||||
|
#+EMAIL: howard.abrams@gmail.com
|
||||||
|
#+DATE: 2020-12-22
|
||||||
|
#+FILETAGS: :emacs:
|
||||||
|
|
||||||
|
A literate programming file for publishing my website using org.
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp :exports none
|
||||||
|
;;; org-publishing.el --- Publishing my website using org. -*- lexical-binding: t; -*-
|
||||||
|
;;
|
||||||
|
;; Copyright (C) 2020 Howard X. Abrams
|
||||||
|
;;
|
||||||
|
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||||||
|
;; Maintainer: Howard X. Abrams <howard.abrams@gmail.com>
|
||||||
|
;; Created: December 22, 2020
|
||||||
|
;;
|
||||||
|
;; This file is not part of GNU Emacs.
|
||||||
|
;;
|
||||||
|
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
||||||
|
;; ~/other/hamacs/org-publishing.org
|
||||||
|
;; And tangle the file to recreate this one.
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
#+END_SRC
|
||||||
|
* Introduction
|
||||||
|
While the Emacs community have a plethora of options for generating a static website from org-formatted files, I keep my pretty simple, and just use the standard =org-publish= feature.
|
||||||
|
|
||||||
|
While the following packages come with Emacs, they aren't necessarily loaded:
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp :results silent
|
||||||
|
(require 'ox-rss)
|
||||||
|
(require 'ox-publish)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Variable settings:
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq org-export-with-broken-links t
|
||||||
|
org-mode-websrc-directory "/Volumes/Personal/dropbox/website"
|
||||||
|
org-mode-publishing-directory (concat (getenv "HOME") "/website-public/"))
|
||||||
|
#+END_SRC
|
||||||
|
* The Projects
|
||||||
|
I separate my /website/ into distinct projects separately built:
|
||||||
|
|
||||||
|
- =blog-content= :: The bulk of rendering my =website= org files into HTML
|
||||||
|
- =blog-static= :: All of the assets, like images are easily copied in place
|
||||||
|
- =blog-rss= :: Regenerate the feeder files
|
||||||
|
- =org-notes= :: Optionally render a non-web site collection of notes.
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(setq org-publish-project-alist
|
||||||
|
`(("all"
|
||||||
|
:components ("blog-content" "blog-static" "org-notes" "blog-rss"))
|
||||||
|
|
||||||
|
("blog-content"
|
||||||
|
:base-directory ,org-mode-websrc-directory
|
||||||
|
:base-extension "org"
|
||||||
|
:publishing-directory ,org-mode-publishing-directory
|
||||||
|
:recursive t
|
||||||
|
:publishing-function org-html-publish-to-html
|
||||||
|
:preparation-function org-mode-blog-prepare
|
||||||
|
:export-with-tags nil
|
||||||
|
:headline-levels 4
|
||||||
|
:auto-preamble t
|
||||||
|
:auto-postamble nil
|
||||||
|
:auto-sitemap t
|
||||||
|
:sitemap-title "Howardisms"
|
||||||
|
:section-numbers nil
|
||||||
|
:table-of-contents nil
|
||||||
|
:with-toc nil
|
||||||
|
:with-author nil
|
||||||
|
:with-creator nil
|
||||||
|
:with-tags nil
|
||||||
|
:with-smart-quotes t
|
||||||
|
|
||||||
|
:html-doctype "html5"
|
||||||
|
:html-html5-fancy t
|
||||||
|
;; :html-preamble org-mode-blog-preamble
|
||||||
|
;; :html-postamble org-mode-blog-postamble
|
||||||
|
;; :html-postamble "<hr><div id='comments'></div>"
|
||||||
|
:html-head "<meta http-equiv=\"X-Clacks-Overhead\" content=\"GNU Terry Pratchett\" />
|
||||||
|
<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
|
||||||
|
<link href='http://fonts.googleapis.com/css?family=Source+Serif+Pro:400,700&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
|
||||||
|
<link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:400,700' rel='stylesheet' type='text/css'>
|
||||||
|
<link rel=\"stylesheet\" href=\"/css/styles.css\" type=\"text/css\"/>\n"
|
||||||
|
:html-head-extra "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\"></script>
|
||||||
|
<script src=\"/js/magic.js\"></script>
|
||||||
|
<link rel=\"icon\" href=\"/img/dragon.svg\">
|
||||||
|
<link rel=\"shortcut icon\" href=\"/img/dragon-head.png\">
|
||||||
|
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />"
|
||||||
|
:html-head-include-default-style nil)
|
||||||
|
|
||||||
|
("blog-static"
|
||||||
|
:base-directory ,org-mode-websrc-directory
|
||||||
|
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|svg"
|
||||||
|
:publishing-directory ,org-mode-publishing-directory
|
||||||
|
:recursive t
|
||||||
|
:publishing-function org-publish-attachment)
|
||||||
|
|
||||||
|
("blog-rss"
|
||||||
|
:base-directory ,org-mode-websrc-directory
|
||||||
|
:base-extension "org"
|
||||||
|
:rss-image-url "http://howardism.org/img/dragon-head.png"
|
||||||
|
:publishing-directory ,org-mode-publishing-directory
|
||||||
|
:publishing-function (org-rss-publish-to-rss)
|
||||||
|
:html-link-home "http://www.howardism.org/"
|
||||||
|
:html-link-use-abs-url t
|
||||||
|
:with-toc nil
|
||||||
|
:exclude ".*"
|
||||||
|
:include ("index.org"))
|
||||||
|
|
||||||
|
("org-notes"
|
||||||
|
:base-directory "~/technical/"
|
||||||
|
:base-extension "org"
|
||||||
|
:publishing-directory ,(concat org-mode-publishing-directory "/notes/")
|
||||||
|
:recursive t
|
||||||
|
:publishing-function org-html-publish-to-html
|
||||||
|
:headline-levels 4 ; Just the default for this project.
|
||||||
|
:auto-preamble t
|
||||||
|
:auto-sitemap t ; Generate sitemap.org automagically...
|
||||||
|
:makeindex t
|
||||||
|
:section-numbers nil
|
||||||
|
:style "<link rel=\"stylesheet\" href=\"../css/styles.css\" type=\"text/css\"/> <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js\" type=\"text/javascript\"></script> <link href=\"http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/smoothness/jquery-ui.css\" type=\"text/css\" rel=\"stylesheet\" /> <script src=\"https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js\" type=\"text/javascript\"></script> <script =\"text/javascript\" src=\"js/magic.js\"></script>"
|
||||||
|
:table-of-contents nil
|
||||||
|
:with-author nil
|
||||||
|
:with-creator nil
|
||||||
|
:with-tags nil)
|
||||||
|
|
||||||
|
("org-notes-static"
|
||||||
|
:base-directory "~/technical/"
|
||||||
|
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
|
||||||
|
:publishing-directory ,(concat org-mode-publishing-directory "/other/")
|
||||||
|
:recursive t
|
||||||
|
:publishing-function org-publish-attachment)))
|
||||||
|
#+END_SRC
|
||||||
|
* Including Sections
|
||||||
|
In the project definitions, I reference a =pre-= and =postamble= that allow me to inject some standard HTML file headers and footers:
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun org-mode-blog-preamble (options)
|
||||||
|
"The function that creates the preamble top section for the blog.
|
||||||
|
OPTIONS contains the property list from the org-mode export."
|
||||||
|
(message "Preamble options: %s" (princ options))
|
||||||
|
(let ((base-directory (plist-get options :base-directory)))
|
||||||
|
(org-babel-with-temp-filebuffer (expand-file-name "top-bar.html" base-directory) (buffer-string))))
|
||||||
|
|
||||||
|
(defun org-mode-blog-postamble (options)
|
||||||
|
"The function that creates the postamble, or bottom section for the blog.
|
||||||
|
OPTIONS contains the property list from the org-mode export."
|
||||||
|
(let ((base-directory (plist-get options :base-directory)))
|
||||||
|
(org-babel-with-temp-filebuffer (expand-file-name "bottom.html" base-directory) (buffer-string))))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Another helper function for the content of website is to make sure to update =index.org=, so that the RSS gets generated.
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(defun org-mode-blog-prepare (&optional options)
|
||||||
|
"`index.org' should always be exported so touch the file before publishing."
|
||||||
|
(let* ((base-directory (plist-get options :base-directory))
|
||||||
|
(buffer (find-file-noselect (expand-file-name "index.org" base-directory) t)))
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(set-buffer-modified-p t)
|
||||||
|
(save-buffer 0))
|
||||||
|
(kill-buffer buffer)))
|
||||||
|
#+END_SRC
|
||||||
|
* Keybindings
|
||||||
|
Make it easy to publish all or just some of my website:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(general-evil-define-key 'normal org-mode-map
|
||||||
|
:prefix "SPC m"
|
||||||
|
"p" '(:ignore t :which-key "publishing")
|
||||||
|
"p a" '("all" . org-publish-all)
|
||||||
|
"p p" '("project" . org-publish-project))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
And let's put a /leader key/ sequence for my favorite file on my website:
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(ha-leader
|
||||||
|
"f h" '(:ignore t :which-key "howards")
|
||||||
|
"f h w" '("website index" . (lambda ()
|
||||||
|
(find-file (expand-file-name "index.org" "~/website")))))
|
||||||
|
#+END_SRC
|
||||||
|
* Technical Artifacts :noexport:
|
||||||
|
Let's =provide= a name so we can =require= it:
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp :exports none
|
||||||
|
(provide 'ha-org-publishing)
|
||||||
|
;;; ha-org-publishing.el ends here
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
|
||||||
|
|
||||||
|
#+DESCRIPTION: A literate programming version for publishing my website using org.
|
||||||
|
|
||||||
|
#+PROPERTY: header-args:sh :tangle no
|
||||||
|
#+PROPERTY: header-args:emacs-lisp :tangle yes
|
||||||
|
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
|
||||||
|
|
||||||
|
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
||||||
|
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||||
|
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
Loading…
Reference in a new issue