225 lines
9.4 KiB
EmacsLisp
225 lines
9.4 KiB
EmacsLisp
;;; sword-to-org.el --- Convert Sword modules to Org outlines
|
|
|
|
;; Author: Adam Porter <adam@alphapapa.net>
|
|
;; Url: http://github.com/alphapapa/sword-to-org
|
|
;; Version: 0.0.1-pre
|
|
;; Package-Requires: ((emacs "24.4") (dash "2.11") (s "1.10.0"))
|
|
;; Keywords: outlines, org-mode, sword, research, bible
|
|
|
|
;;; Commentary:
|
|
|
|
;; This package uses the `diatheke' program to convert Sword modules
|
|
;; to Org-mode outlines. For example, you can make an Org file
|
|
;; containing the entire text of the ESV module as an outline
|
|
;; structured by book/chapter/verse. Then you can add top-level
|
|
;; headings for Old/New Testaments, and then you have the whole Bible
|
|
;; as an Org file. Then you can do everything you can do in Org with
|
|
;; the text of the Bible! Add footnotes, links, tags, properties,
|
|
;; write your own commentaries under subheadings, organize research
|
|
;; with TODO items, export with `org-export', search with
|
|
;; `helm-org-rifle', etc. The list is endless.
|
|
|
|
;;; Usage:
|
|
|
|
;; First install `diatheke'. On Debian/Ubuntu it's in the `diatheke'
|
|
;; package.
|
|
|
|
;; Open a buffer and run the command `sword-to-org-insert-outline'.
|
|
;; Choose the module (e.g. Bible translation) to use, then input a
|
|
;; passage reference or range (e.g. "Gen 1", "Jn 1:1", or even
|
|
;; "Gen-Rev"--that last one will take a few moments), and an Org
|
|
;; outline will be inserted in book/chapter/verse/text structure.
|
|
|
|
;; You may customize `sword-to-org-default-module' so you don't have
|
|
;; to pick a module every time, and you can call the command with a
|
|
;; universal prefix (`C-u') to choose a different module.
|
|
|
|
;; You may also use any of the `sword-to-org--' support functions in
|
|
;; your own programs. Consult the docstrings for instructions and
|
|
;; examples.
|
|
|
|
;;; Code:
|
|
|
|
;;;; Requirements
|
|
|
|
(require 'cl-lib)
|
|
(require 'dash)
|
|
(require 's)
|
|
|
|
;;;; Variables
|
|
|
|
(defconst sword-to-org--diatheke-parse-line-regexp
|
|
(rx bol
|
|
;; Book name
|
|
(group-n 1 (minimal-match (1+ anything)))
|
|
space
|
|
;; chapter:verse
|
|
(group-n 2 (1+ digit)) ":" (group-n 3 (1+ digit)) ":"
|
|
;; Passage text (which may start with a newline, in which case
|
|
;; no text will be on the same line after chapter:verse)
|
|
(optional (1+ space)
|
|
(group-n 4 (1+ anything))))
|
|
"Regexp to parse each line of output from `diatheke'.")
|
|
|
|
(defgroup sword-to-org nil
|
|
"Settings for `sword-to-org'."
|
|
:link '(url-link "http://github.com/alphapapa/sword-to-org")
|
|
:group 'org)
|
|
|
|
(defcustom sword-to-org-default-module nil
|
|
"Default module (e.g. Bible translation, like \"ESV\") to use."
|
|
:type '(choice (const :tag "None" nil)
|
|
(string :tag "Module abbreviation (e.g. \"ESV\")")))
|
|
|
|
;;;; Functions
|
|
|
|
;;;;; Commands
|
|
|
|
;;;###autoload
|
|
(defun sword-to-org-insert-outline (module key)
|
|
"Insert Org outline in current buffer for Sword MODULE and KEY.
|
|
The buffer will be switched to `text-mode' before inserting, to
|
|
improve performance, and then switched back to `org-mode' if
|
|
it was active."
|
|
(interactive (list (if (or current-prefix-arg
|
|
(not sword-to-org-default-module))
|
|
(completing-read "Module: " (sword-to-org--diatheke-get-modules))
|
|
sword-to-org-default-module)
|
|
(substring (buffer-name) nil -4)))
|
|
(let ((was-org-mode (eq major-mode 'org-mode)))
|
|
(when was-org-mode
|
|
(text-mode))
|
|
(cl-loop with last-book
|
|
with last-chapter
|
|
for passage in (sword-to-org--diatheke-parse-text
|
|
(sword-to-org--diatheke-get-text module key))
|
|
do (-let (((&plist :book book :chapter chapter :verse verse :text text) passage))
|
|
;; (unless (equal book last-book)
|
|
;; (insert (format "%s\n\n" book))
|
|
;; (setq last-chapter nil)
|
|
;; (setq last-book book))
|
|
(unless (equal chapter last-chapter)
|
|
(insert (format "* %s %s\n\n" book chapter))
|
|
(setq last-chapter chapter))
|
|
(insert (format "** %s %s:%s\n\n%s\n\n" book chapter verse text))))
|
|
(when was-org-mode
|
|
(org-mode))))
|
|
|
|
;;;###autoload
|
|
(defun sword-to-org-insert-passage (key &optional separate-lines module)
|
|
"Insert passage for reference KEY as plain text.
|
|
With prefix, prompt for module, otherwise use default module.
|
|
With double-prefix, insert each verse on its own line with
|
|
reference; otherwise, insert as single paragraph with reference
|
|
at the end."
|
|
(interactive (list (read-from-minibuffer "Passage: ")
|
|
(equal current-prefix-arg '(16))
|
|
(if (or current-prefix-arg
|
|
(not sword-to-org-default-module))
|
|
(completing-read "Module: " (sword-to-org--diatheke-get-modules))
|
|
sword-to-org-default-module)))
|
|
(insert (sword-to-org--passage key :module module :paragraph (not separate-lines)))
|
|
(when (not separate-lines)
|
|
(insert " (" key ")")))
|
|
|
|
;;;;; Support
|
|
|
|
(cl-defun sword-to-org--passage (key &key module paragraph)
|
|
"Return string for passage reference KEY.
|
|
If MODULE is nil, use default module. If PARAGRAPH is non-nil,
|
|
join all verses into a paragraph; otherwise put each verse on its
|
|
own line with reference."
|
|
(unless module
|
|
(setq module sword-to-org-default-module))
|
|
(if paragraph
|
|
(s-join " " (cl-loop for passage in (sword-to-org--diatheke-parse-text (sword-to-org--diatheke-get-text module key))
|
|
collect (plist-get passage :text)))
|
|
;; NOTE: Using double-newline as verse separator so the verses
|
|
;; can appear separately in Org exports from Org Babel blocks
|
|
;; (for some reason, single newlines are replaced with spaces)
|
|
(s-join "\n\n" (cl-loop for passage in (sword-to-org--diatheke-parse-text (sword-to-org--diatheke-get-text module key))
|
|
collect (-let (((&plist :book book :chapter chapter :verse verse :text text) passage))
|
|
(format "%s %s:%s %s" book chapter verse text))))))
|
|
|
|
(defun sword-to-org--diatheke-get-modules ()
|
|
"Return list of Sword modules from diatheke.
|
|
Only the module abbreviation is returned."
|
|
(cl-loop for line in (s-lines (with-temp-buffer
|
|
(call-process "diatheke" nil '(t nil) nil
|
|
"-b" "system" "-k" "modulelist")
|
|
(buffer-string)))
|
|
when (string-match (rx (group-n 1 (minimal-match (1+ (not (any ":"))))) " : ") line)
|
|
collect (match-string 1 line)))
|
|
|
|
(defun sword-to-org--diatheke-get-text (module key)
|
|
"Return raw text from diatheke MODULE for KEY.
|
|
This simply calls `diatheke -b MODULE -k KEY' and returns the raw output.
|
|
|
|
Examples:
|
|
|
|
\(sword-to-org--diatheke-get-text \"ESV\" \"gen 1:1\")"
|
|
(with-temp-buffer
|
|
(call-process "diatheke" nil '(t nil) nil
|
|
"-b" module "-k" key)
|
|
(buffer-substring (point-min) (save-excursion
|
|
(goto-char (point-max))
|
|
(forward-line -2)
|
|
(end-of-line)
|
|
(point)))))
|
|
|
|
(defun sword-to-org--diatheke-parse-text (text &optional &key keep-newlines)
|
|
"Parse TEXT line-by-line, returning list of verse plists.
|
|
When KEEP-NEWLINES is non-nil, keep blank lines in text.
|
|
|
|
Plists are in format (:book \"Genesis\" :chapter 1 :verse 1
|
|
:text \"In the beginning...\").
|
|
|
|
Example:
|
|
|
|
\(sword-to-org--diatheke-parse-text
|
|
(sword-to-org--diatheke-get-text \"ESV\" \"Philemon 1:1-3\")
|
|
:keep-newlines t)"
|
|
(cl-loop with result
|
|
with new-verse
|
|
for line in (s-lines text)
|
|
for parsed = (sword-to-org--diatheke-parse-line line)
|
|
if parsed
|
|
do (progn
|
|
(push new-verse result)
|
|
(setq new-verse parsed))
|
|
else do (let* ((text (plist-get new-verse :text))
|
|
(new-text (concat text
|
|
(if (s-present? line)
|
|
line
|
|
(when keep-newlines "\n")))))
|
|
(plist-put new-verse :text new-text))
|
|
finally return (cdr (progn
|
|
(push new-verse result)
|
|
(nreverse result)))))
|
|
|
|
(defun sword-to-org--diatheke-parse-line (line)
|
|
"Return plist from LINE. If LINE is not the beginning of a verse, return nil.
|
|
You generally don't want to use this directly. Instead use
|
|
`sword-to-org--diatheke-parse-text'.
|
|
|
|
Plist is in format (:book \"Genesis\" :chapter 1 :verse 1
|
|
:text \"In the beginning...\").
|
|
|
|
For a complete example, see how
|
|
`sword-to-org--diatheke-parse-text' calls this function."
|
|
(if (s-present? line)
|
|
(when (string-match sword-to-org--diatheke-parse-line-regexp line)
|
|
(let ((book (match-string 1 line))
|
|
(chapter (string-to-number (match-string 2 line)))
|
|
(verse (string-to-number (match-string 3 line)))
|
|
;; Ensure text is present, which may not be the case if
|
|
;; a verse starts with a newline. See
|
|
;; <https://github.com/alphapapa/sword-to-org/issues/2>
|
|
(text (when (s-present? (match-string 4 line))
|
|
(s-trim (match-string 4 line)))))
|
|
(list :book book :chapter chapter :verse verse :text text)))))
|
|
|
|
(provide 'sword-to-org)
|
|
|
|
;;; sword-to-org.el ends here
|