;;; 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 "\\(.+\\)" top-level top-level) (format "\\1" 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