258 lines
11 KiB
EmacsLisp
258 lines
11 KiB
EmacsLisp
;;; org-jekyll.el --- Export jekyll-ready posts form org-mode entries
|
|
;;;
|
|
;;; Author: Juan Reyero
|
|
;;; Version: 0.4
|
|
;; Package-Version: 20130508.239
|
|
;;; Keywords: hypermedia
|
|
;;; Package-Requires: ((org "8.0"))
|
|
;;; Homepage: http://juanreyero.com/open/org-jekyll/
|
|
;;; Repository: http://github.com/juanre/org-jekyll
|
|
;;; Public clone: git://github.com/juanre/org-jekyll.git
|
|
;;;
|
|
;;; Commentary:
|
|
;;;
|
|
;;; Extract subtrees from your org-publish project files that have
|
|
;;; a :blog: keyword and an :on: property with a timestamp, and
|
|
;;; export them to a subdirectory _posts of your project's publishing
|
|
;;; directory in the year-month-day-title.html format that Jekyll
|
|
;;; expects. Properties are passed over as yaml front-matter in the
|
|
;;; exported files. The title of the subtree is the title of the
|
|
;;; entry. The title of the post is a link to the post's page.
|
|
;;;
|
|
;;; Look at http://orgmode.org/worg/org-tutorials/org-jekyll.html for
|
|
;;; more info on how to integrate org-mode with Jekyll, and for the
|
|
;;; inspiration of the main function down there.
|
|
;;;
|
|
;;; Code:
|
|
|
|
;;(require 'ox-html)
|
|
|
|
(defvar org-jekyll-category nil
|
|
"Specify a property which, if defined in the entry, is used as
|
|
a category: the post is written to category/_posts. Ignored if
|
|
nil. Use \"lang\" if you want to send posts in different
|
|
languages to different directories.")
|
|
|
|
(defvar org-jekyll-lang-subdirs nil
|
|
"Make it an assoc list indexed by language if you want to
|
|
bypass the category subdir definition and build blog subdirs per
|
|
language.")
|
|
|
|
(defvar org-jekyll-localize-dir nil
|
|
"If non-nil and the lang property is set in the entry,
|
|
org-jekyll will look for a lang.yml file in this directory and
|
|
include it in the front matter of the exported entry.")
|
|
|
|
(defvar org-jekyll-new-buffers nil
|
|
"Buffers created to visit org-publish project files looking for blog posts.")
|
|
|
|
(defun org-jekyll-publish-dir (project &optional category)
|
|
"Where does the project go, by default a :blog-publishing-directory
|
|
entry in the org-publish-project-alist."
|
|
(princ category)
|
|
(if org-jekyll-lang-subdirs
|
|
(let ((pdir (plist-get (cdr project) :blog-publishing-directory))
|
|
(langdir (cdr (assoc category org-jekyll-lang-subdirs))))
|
|
(if langdir
|
|
(concat pdir (cdr (assoc category org-jekyll-lang-subdirs))
|
|
"_posts/")
|
|
(let ((ppdir (plist-get (cdr project) :blog-publishing-directory)))
|
|
(unless ppdir
|
|
(setq ppdir (plist-get (cdr project) :publishing-directory)))
|
|
(concat ppdir
|
|
(if category (concat category "/") "")
|
|
"_posts/"))))
|
|
(let ((pdir (plist-get (cdr project) :blog-publishing-directory)))
|
|
(unless pdir
|
|
(setq pdir (plist-get (cdr project) :publishing-directory)))
|
|
(concat pdir
|
|
(if category (concat category "/") "")
|
|
"_posts/"))))
|
|
|
|
(defun org-jekyll-site-root (project)
|
|
"Site root, like http://yoursite.com, from which blog
|
|
permalinks follow. Needed to replace entry titles with
|
|
permalinks that RSS agregators and google buzz know how to
|
|
follow. Looks for a :site-root entry in the org-publish-project-alist."
|
|
(or (plist-get (cdr project) :site-root)
|
|
""))
|
|
|
|
|
|
(defun org-get-jekyll-file-buffer (file)
|
|
"Get a buffer visiting FILE. If the buffer needs to be
|
|
created, add it to the list of buffers which might be released
|
|
later. Copied from org-get-agenda-file-buffer, and modified
|
|
the list that holds buffers to release."
|
|
(let ((buf (org-find-base-buffer-visiting file)))
|
|
(if buf
|
|
buf
|
|
(progn (setq buf (find-file-noselect file))
|
|
(if buf (push buf org-jekyll-new-buffers))
|
|
buf))))
|
|
|
|
(defun org-jekyll-slurp-yaml (fname)
|
|
(remove "---" (if (file-exists-p fname)
|
|
(split-string (with-temp-buffer
|
|
(insert-file-contents fname)
|
|
(buffer-string))
|
|
"\n" t))))
|
|
|
|
(defun ensure-directories-exist (fname)
|
|
(let ((dir (file-name-directory fname)))
|
|
(unless (file-accessible-directory-p dir)
|
|
(make-directory dir t)))
|
|
fname)
|
|
|
|
(defun org-jekyll-sanitize-string (str project)
|
|
(if (plist-get (cdr project) :jekyll-sanitize-permalinks)
|
|
(progn (setq str (downcase str))
|
|
(dolist (c '(("á" . "a")
|
|
("é" . "e")
|
|
("í" . "i")
|
|
("ó" . "o")
|
|
("ú" . "u")
|
|
("à" . "a")
|
|
("è" . "e")
|
|
("ì" . "i")
|
|
("ò" . "o")
|
|
("ù" . "u")
|
|
("ñ" . "n")
|
|
("ç" . "s")
|
|
("\\$" . "S")
|
|
("€" . "E")))
|
|
(setq str (replace-regexp-in-string (car c) (cdr c) str)))
|
|
(replace-regexp-in-string "[^abcdefghijklmnopqrstuvwxyz-]" ""
|
|
(replace-regexp-in-string " +" "-" str)))
|
|
str))
|
|
|
|
(defun org-jekyll-export-entry (project)
|
|
(let* ((props (org-entry-properties nil 'standard))
|
|
(time (cdr (or (assoc "on" props)
|
|
(assoc "ON" props))))
|
|
(lang (cdr (or (assoc "lang" props)
|
|
(assoc "LANG" props))))
|
|
(category (if org-jekyll-category
|
|
(cdr (assoc org-jekyll-category props))
|
|
nil))
|
|
(yaml-front-matter (copy-alist props)))
|
|
(unless (assoc "layout" yaml-front-matter)
|
|
(push '("layout" . "post") yaml-front-matter))
|
|
(when time
|
|
(let* ((heading (org-get-heading t))
|
|
(title (replace-regexp-in-string "[:=\(\)\?]" ""
|
|
(replace-regexp-in-string
|
|
"[ \t]" "-" heading)))
|
|
(str-time (and (string-match "\\([[:digit:]\-]+\\) " time)
|
|
(match-string 1 time)))
|
|
(to-file (format "%s-%s.html" str-time
|
|
(org-jekyll-sanitize-string title project)))
|
|
(org-buffer (current-buffer))
|
|
(yaml-front-matter (cons (cons "title" heading)
|
|
yaml-front-matter))
|
|
html)
|
|
(org-narrow-to-subtree)
|
|
(let ((level (- (org-reduced-level (org-outline-level)) 1))
|
|
(top-level org-html-toplevel-hlevel)
|
|
(contents (buffer-substring (point-min) (point-max)))
|
|
(site-root (org-jekyll-site-root project)))
|
|
;; Without the promotion the header with which the headline
|
|
;; is exported depends on the level. With the promotion it
|
|
;; fails when the entry is not visible (ie, within a folded
|
|
;; entry).
|
|
(dotimes (n level nil) (org-promote-subtree))
|
|
(setq html
|
|
(replace-regexp-in-string
|
|
(format "<h%d id=\"sec-1\">\\(.+\\)</h%d>"
|
|
top-level top-level)
|
|
(format
|
|
"<h%d id=\"sec-1\"><a href=\"%s{{ page.url }}\">\\1</a></h%d>"
|
|
top-level site-root top-level)
|
|
(with-current-buffer
|
|
(org-html-export-as-html nil t t t
|
|
'(:tags nil
|
|
:table-of-contents nil))
|
|
(buffer-string))))
|
|
(set-buffer org-buffer)
|
|
(delete-region (point-min) (point-max))
|
|
(insert contents)
|
|
(save-buffer))
|
|
(widen)
|
|
(with-temp-file (ensure-directories-exist
|
|
(expand-file-name
|
|
to-file (org-jekyll-publish-dir project category)))
|
|
(when yaml-front-matter
|
|
(insert "---\n")
|
|
(mapc (lambda (pair)
|
|
(insert (format "%s: %s\n" (car pair) (cdr pair))))
|
|
yaml-front-matter)
|
|
(if (and org-jekyll-localize-dir lang)
|
|
(mapc (lambda (line)
|
|
(insert (format "%s\n" line)))
|
|
(org-jekyll-slurp-yaml (concat org-jekyll-localize-dir
|
|
lang ".yml"))))
|
|
(insert "---\n\n"))
|
|
(insert html))))))
|
|
|
|
; Evtl. needed to keep compiler happy:
|
|
(declare-function org-publish-get-project-from-filename "org-publish"
|
|
(filename &optional up))
|
|
|
|
;;;###autoload
|
|
(defun org-jekyll-export-current-entry ()
|
|
(interactive)
|
|
(save-excursion
|
|
(let ((project (org-publish-get-project-from-filename buffer-file-name)))
|
|
(org-back-to-heading t)
|
|
(org-jekyll-export-entry project))))
|
|
|
|
;;;###autoload
|
|
(defun org-jekyll-export-blog ()
|
|
"Export all entries in project files that have a :blog: keyword
|
|
and an :on: datestamp. Property drawers are exported as
|
|
front-matters, outline entry title is the exported document
|
|
title. "
|
|
(interactive)
|
|
(save-excursion
|
|
(setq org-jekyll-new-buffers nil)
|
|
(let ((project (org-publish-get-project-from-filename (buffer-file-name))))
|
|
(mapc
|
|
(lambda (jfile)
|
|
(if (string= (file-name-extension jfile) "org")
|
|
(with-current-buffer (org-get-jekyll-file-buffer jfile)
|
|
;; It fails for non-visible entries, CONTENT visibility
|
|
;; mode ensures that all of them are visible.
|
|
(message (concat "org-jekyll: publishing " jfile ))
|
|
(org-content)
|
|
(org-map-entries (lambda () (org-jekyll-export-entry project))
|
|
"blog|BLOG"))))
|
|
(org-publish-get-base-files project)))
|
|
(org-release-buffers org-jekyll-new-buffers)))
|
|
|
|
;;;###autoload
|
|
(defun org-jekyll-export-project (project-name)
|
|
"Export all entries in project files that have a :blog: keyword
|
|
and an :on: datestamp. Property drawers are exported as
|
|
front-matters, outline entry title is the exported document
|
|
title. "
|
|
(interactive)
|
|
(save-excursion
|
|
(setq org-jekyll-new-buffers nil)
|
|
(let ((project (assoc project-name org-publish-project-alist)))
|
|
(mapc
|
|
(lambda (jfile)
|
|
(if (string= (file-name-extension jfile) (plist-get (cdr project)
|
|
:base-extension))
|
|
(with-current-buffer (org-get-jekyll-file-buffer jfile)
|
|
;; It fails for non-visible entries, CONTENT visibility
|
|
;; mode ensures that all of them are visible.
|
|
(message (concat "org-jekyll: publishing " jfile ))
|
|
(org-content)
|
|
(org-map-entries (lambda () (org-jekyll-export-entry project))
|
|
"blog|BLOG"))))
|
|
(org-publish-get-base-files project)))
|
|
(org-release-buffers org-jekyll-new-buffers)))
|
|
|
|
(provide 'org-jekyll)
|
|
|
|
;;; org-jekyll.el ends here
|