Link:
Intro
Hello
This is emacs mode dabar-mode. It provides a few features which allow for integration of bible study tools into emacs. It allows for reading the bible, inserting custom bible: links, following bible: links when dabar-mode is enabled, and reading backlinks.
I'm following the method of Literate Programming to document this library.
History -
Obsidian Got Me Hooked
I created this sometime mid-2023 to try and manage knowledge systems. I initially started using obsidian to store notes sometime in 2021. I was and am interested in using knowledge management to manage learning about the bible. I found this resource: https://forum.obsidian.md/t/bible-study-in-obsidian-kit-including-the-bible-in-markdown/12503 This Joshua fellow compiled the bible into a markdown format, so that if could be loaded into Obsidian and you could link directly to files in your notes there. I liked this workflow a lot.
Being able to link directly to chapter and verse had a number of distinct advantages.
- I could immediately click through my citation and read the verse in context
- I could use the backlink feature of obsidian in order to see which of my notes was referencing a particular verse.
- The graph feature of obsidian became useful - I wanted all my notes and thoughts to be grounded in the word of God. Thus, I was able to develop a workflow where I could find nodes that were not connected to any bible verses, or to anything at all, and then either prune them or ground them in the foundation of the word.
Friendship Ended With Obsidian...
As I began using this workflow, I fell in love with it. I began to dream up additional features. The obvious low hanging fruit was to implement Strongs numbers. It would be pretty cool to have the same backlinking functionality within a strongs dictionary. Not only would it function as a full on concordance without any additional effort, it would also make it much cooler to reference Strongs in my notes. I got to work. I found a copy of the bible which had strongs numbers attached, and prepared the repo to load it in Obsidian markdown format. Of course, Obsidian immediately crashed. After some reasearch, I realized that Obsidian was absolutely not capable of handling and processing that many nodes, and the developers said online that they had no intent of ever doing so.
I was faced with a choice. I would either have to develop the Strongs capaibility as an Obsidian plugin, or develop a new solution. Obviously, my solution has to be open source. When I found out about org-roam, I was immediately intruiged. I have always loved outlining, it just works really well for my brain. I like to break things down into sections, it makes me feel like my thinking is much clearer. I was much more interested in developing this plugin out in emacs-lisp than I was in Typescript. So I switched from vim to Emacs.
...Now org-roam is my best friend
org-roam is a really wonderful piece of software. It's based on Roam Research, which I have not used.rOne of the things that I really loved about Emacs is that everything just feels stable. Not the program itself, Emacs crashes like twice a day. But I wrote this dabar-mode code three years ago, came back three years later, and it seems like it works just fine. And I'm awful at elisp. Org-roam was finished a few years back and is basically feature complete. I think there's some development happening on it now, but it seems like it's mostly maintenance. org-roam creates a SQLite database out of your notes, and stores data and backlinks in the SQLite database. This makes a lot of sense, and immediately I think we get over the performance hurdle which choked out Obsidian.
Abandon all hope, ye who enter here
I genuinely do not recommend anybody go down this road. I could have spent all this time I spent building out knowledge base tools reading the bible, and I would likely be a better man for it. I have spent far, far too much time customizing my init.el. But I have arrived at this point, I like developing stuff, and I don't have anything else I want to work on (except for my cute little CRUD framework, but that might be this). After a lot of work, I have basically acheived feature parity with my Obsidian repository, in a slightly worse fashion.
General Features and Architecture
dabar-mode reads bible information from an XML file. It does lookups to add links to bible chapters using a 'bible:Gen 1:1' format. It can insert links or excerpts in files. It produces an emacs mode that allows you to read the bible, and it tracks and shows backlinks from anywhere in your roam database where you have linked to that verse.
Future direction
I think this ultimately should probably be running from a database. I would still like to add Strongs numbers and this would be excrutiating if I was using USFX to parse this. I currently am exporting my org files using Quartz. I probably need to build a static site generator which has my bible features built in as well.
Abbreviations
In order to cite our verses, we need a little library which can translate abbreviations into the actual verse names. If we were running a database, it would probably ultimately be better to store these as book numbers.
(defvar bible-alist
'(("Genesis" "Gen." "Gen" "Ge." "Ge")
("Exodus" "Exo." "Ex." "Exo" "Ex")
("Leviticus" "Lev." "Lev" "Le." "Le")
("Numbers" "Num." "Num" "Nu." "Nu")
("Deuteronomy" "Deut." "Deut" "Deu." "Deu" "De." "De")
("Joshua" "Josh." "Josh" "Jos." "Jos")
("Judges" "Judg." "Judg" "Jud." "Jud")
("Ruth" "Rut." "Rut" "Ru." "Ru")
("1 Samuel" "1 Sam." "1 Sam" "1 Sa." "1 Sa" "1Sam." "1Sam" "1Sa." "1Sa")
("2 Samuel" "2 Sam." "2 Sam" "2 Sa." "2 Sa" "2Sam." "2Sam" "2Sa." "2Sa")
("1 Kings" "1 Kin." "1 Kin" "1 Ki." "1 Ki" "1Kin." "1Kin" "1Ki." "1Ki")
("2 Kings" "2 Kin." "2 Kin" "2 Ki." "2 Ki" "2Kin." "2Kin" "2Ki." "2Ki")
("1 Chronicles" "1 Chr." "1 Chr" "1 Ch." "1 Ch" "1Chr." "1Chr" "1Ch." "1Ch")
("2 Chronicles" "2 Chr." "2 Chr" "2 Ch." "2 Ch" "2Chr." "2Chr" "2Ch." "2Ch")
("Ezra" "Ezr." "Ezr")
("Nehemiah" "Neh." "Neh" "Ne." "Ne")
("Esther" "Esth." "Esth" "Est." "Est" "Es." "Es")
("Job")
("Psalms" "Psalm" "Psa." "Psa" "Ps." "Ps")
("Proverbs" "Prov." "Prov" "Pro." "Pro" "Pr." "Pr")
("Ecclesiastes" "Eccl." "Eccl" "Ecc." "Ecc" "Ec." "Ec")
("Song of Songs" "Song of Solomon" "Song" "Son." "Son" "So." "So")
("Isaiah" "Isa." "Isa" "Is." "Is")
("Jeremiah" "Jer." "Jer" "Je." "Je")
("Lamentations" "Lam." "Lam" "La." "La")
("Ezekiel" "Ezek." "Ezek" "Eze." "Eze")
("Daniel" "Dan." "Dan" "Da." "Da")
("Hosea" "Hos." "Hos" "Ho." "Ho")
("Joel")
("Amos" "Amo." "Amo" "Am." "Am")
("Obadiah" "Obad." "Obad" "Oba." "Oba" "Ob." "Ob")
("Jonah" "Jon." "Jon")
("Micah" "Mic." "Mic" "Mi." "Mi")
("Nahum" "Nah." "Nah" "Na." "Na")
("Habakkuk" "Hab." "Hab")
("Zephaniah" "Zeph." "Zeph" "Zep." "Zep")
("Haggai" "Hag." "Hag")
("Zechariah" "Zech." "Zech" "Zec." "Zec")
("Malachi" "Mal." "Mal")
("Matthew" "Matt." "Matt" "Mat." "Mat")
("Mark" "Mar." "Mar")
("Luke" "Luk." "Luk" "Lu." "Lu")
("John" "Joh." "Joh" "Jn." "Jn")
("Acts of the Apostles" "Acts" "Act." "Act" "Ac." "Ac")
("Romans" "Rom." "Rom" "Ro." "Ro")
("1 Corinthians" "1 Cor." "1 Cor" "1 Co." "1 Co" "1Cor." "1Cor" "1Co." "1Co")
("2 Corinthians" "2 Cor." "2 Cor" "2 Co." "2 Co" "2Cor." "2Cor" "2Co." "2Co")
("Galatians" "Gal." "Gal" "Ga." "Ga")
("Ephesians" "Eph." "Eph" "Ep." "Ep")
("Philippians" "Phil." "Phil")
("Colossians" "Col." "Col" "Co." "Co")
("1 Timothy" "1 Tim." "1 Tim" "1 Ti." "1 Ti" "1Tim." "1Tim" "1Ti." "1Ti")
("2 Timothy" "2 Tim." "2 Tim" "2 Ti." "2 Ti" "2Tim." "2Tim" "2Ti." "2Ti")
("Titus" "Tit." "Tit")
("Philemon" "Phlm." "Phlm" "Phl." "Phl")
("1 Thessalonians" "1 Thess." "1 Thess" "1 Thes." "1 Thes" "1 The." "1 The"
"1 Th." "1 Th" "1Thess." "1Thess" "1Thes." "1Thes" "1The."
"1The" "1Th." "1Th")
("2 Thessalonians" "2 Thess." "2 Thess" "2 Thes." "2 Thes" "2 The." "2 The"
"2 Th." "2 Th" "2Thess." "2Thess" "2Thes." "2Thes" "2The."
"2The" "2Th." "2Th")
("Hebrews" "Hebr." "Hebr" "Heb." "Heb" "He." "He")
("James" "Jam." "Jam" "Jas." "Jas" "Ja." "Ja")
("1 Peter" "1 Pet." "1 Pet" "1 Pe." "1 Pe" "1Pet." "1Pet" "1Pe." "1Pe")
("2 Peter" "2 Pet." "2 Pet" "2 Pe." "2 Pe" "2Pet." "2Pet" "2Pe." "2Pe")
("1 John" "1 Joh." "1 Joh" "1 Jo." "1 Jo" "1 Jn." "1 Jn"
"1Joh." "1Joh" "1Jo." "1Jo" "1Jn." "1Jn")
("2 John" "2 Joh." "2 Joh" "2 Jo." "2 Jo" "2 Jn." "2 Jn"
"2Joh." "2Joh" "2Jo." "2Jo" "2Jn." "2Jn")
("3 John" "3 Joh." "3 Joh" "3 Jo." "3 Jo" "3 Jn." "3 Jn"
"3Joh." "3Joh" "3Jo." "3Jo" "3Jn." "3Jn")
("Jude" "Jud." "Jud")
("Revelation" "Rev." "Rev" "Re." "Re")
("Apocalypse" "Apoc." "Apoc" "Apo." "Apo" "Ap." "Ap"))
"Abbreviations taken from csebold/esv.el")
Beginning of file
intro + requires + load xml file
We have to pull in common lisp in order to do our XML parsing and string manipulation. We load our XML file into a temp buffer so that we can access it. This is ultimately not great for performance, hence, why it would ultimately be better in a database.
(require 'cl-lib)
(load "abbreviations")
;; set variable
(with-temp-buffer
(insert-file-contents "~/dev/dabar/bible_web.xml")
(setq xml-string (buffer-string)))
;; parse the buffer
(defvar parsedxml nil)
(setq parsed-xml (with-temp-buffer
(insert xml-string)
(xml-parse-region (point-min) (point-max))))variable declaration
I'm pretty sure we are acutally using both of these, but I don't remember. As you add bible: links, it creates a file for the chapter which you are linking to. I honestly can't remember why, but this was a bit of a weird workaround I had to do in order to get backlinks working.
(defvar org-roam-directory "/home/roerick/org/roam/")
(defvar dabar-directory "/home/roerick/org/dabar/")
(defvar bible-buffer-name "*Dabar Browse*")
keymap for dabar mode
I don't know why this is at this point of the document
TODO do something with this
;; minor mode
(defvar dabar-mode-keymap (make-sparse-keymap)
"Keymap for dabar-mode")
define minor mode
I don't know that I defined this correctly. I think this package should serve up two modes, a major mode and a minor mode. When you're in a regular buffer, the minor mode should be enabled. When you enter into reading mode, the major mode should be enabled. Currently, there is only the minor mode, and it only functions in a regular buffer. "reading mode" is just a read only buffer.
TODO define a major mode for reading
(define-minor-mode dabar-mode
"read the word of God"
:lighter " Dabar"
:keymap dabar-mode-keymap
(if dabar-mode
(progn
(message "Activated dabar-mode"))
(message "Deactivated dabar-mode")))
activate-dabar-mode-in-directory
I don't really see any reason why Dabar mode shouldn't be active at all times. I suppose this is nice to have as an option.
TODO It seems like this should be using the org-roam-directory above, but it isn't. Fix
(defun activate-dabar-mode-in-directory ()
"Activate `dabar-mode` if the current file is in a certain directory."
(let ((dir (file-name-directory buffer-file-name))
(target-dir (expand-file-name "~/org/roam/")))
(message "Current directory: %s" dir)
(message "Target directory: %s" target-dir)
(when (string-match-p (regexp-quote target-dir) dir)
(message "Directories matched!")
(dabar-mode 1))))
Parsing XML
The challenges of storing and parsing bibles
In this emacs script we parse a simplified XML format in order to access the data in the bible. There are a surprising number of challenges involved in storing bible data. We have to store information about cahpter and verse, obviously. We also need to consider things like the name of the Lord, specifying geographic places, or (perhaps most importantly for our purposes) strongs concordance numbers.
Taking a look at the biblehub interlinear view, we see that in order to build an interlinear capability we would need to store the following at the word level:
- Strongs number
- Transliteration
- Hebrew word
- Mechanical english translation
- part of speech
It is also worth here mentioning the work of Jeff benner and his mechanical translation - he creates a method for translating literal version of tenses, e.g. "and~he~will~much~KNEEL(V)" https://www.mechanical-translation.org/mtt/G1.html
Currently we use the World English Bible translation, which is a free, public domain translation. The XML file in use doesn't store much more than chapter and verse data.
I personally really like the Bearean Standard Bible. https://github.com/Beblia/Holy-Bible-XML-Format This is an XML which is in a very similar format to this one, which would possibly allow us to swap out the BSB without much effort.
The 'Gold Standard' of Bible storage formats seems to be USFM and USFX. https://ebible.org/
get_book_list
Here we parse through the XML file and create a list of books which we store
;; list of books
(defun get_book_list ()
"Get a list of books"
(let ((root (car parsed-xml)))
(let ((children (xml-node-children root))
(book-names nil))
(dolist (child children)
(unless (stringp child)
(let ((attributes (xml-node-attributes child)))
(let ((bname (cdr (assq 'bname attributes))))
(when bname
(push bname book-names))))))
(message "Names of all books: %s" (nreverse book-names))))
)
get_book_and_chapter_info
I'm actually not entire sure what this does. Bad variable name. I think we're basically creating a list where each element car is the book and CDR is an array of all the chapters. We declare a variable book-and-chapters
(defun get_book_and_chapter_info ()
(let ((root (car parsed-xml))
(book-info-alist nil))
(let ((children (xml-node-children root)))
(dolist (child children)
(unless (stringp child)
(let ((attributes (xml-node-attributes child)))
(let ((bname (cdr (assq 'bname attributes))))
(when bname
(let ((chapters nil)
(biblebook-children (xml-node-children child)))
(dolist (chapter-node biblebook-children)
(unless (stringp chapter-node)
(let ((chapter-attributes (xml-node-attributes chapter-node)))
(let ((cnumber (cdr (assq 'cnumber chapter-attributes))))
(when cnumber
(push cnumber chapters))))))
(push (cons (intern bname) (nreverse chapters)) book-info-alist))))))))
(nreverse book-info-alist)))
(setq book-and-chapters (get_book_and_chapter_info))format-chapter
This is a little helper which formats our chapter contents for reading
(defun format-chapter (chapter-contents)
(let ((formatted-text ""))
(dolist (verse chapter-contents)
(unless (stringp verse)
(let* ((attributes (xml-node-attributes verse))
(vnumber (cdr (assq 'vnumber attributes)))
(verse-text (nth 2 verse)))
(setq formatted-text (concat formatted-text (format "%s %s\n" vnumber verse-text))))))
formatted-text))
find-chapter-contents
This retrives the contents of a chapter. This is called later on in get-chapter
(defun find-chapter-contents (target-book target-chapter)
"Return the contents of the specified chapter from the parsed XML."
(let ((root (car parsed-xml))
(chapter-contents nil)
(found nil))
(dolist (biblebook (xml-get-children root 'BIBLEBOOK) (and found chapter-contents))
(let ((bname (cdr (assq 'bname (xml-node-attributes biblebook)))))
(when (and (not found) (string= bname target-book))
(dolist (chapter (xml-get-children biblebook 'CHAPTER))
(let ((cnumber (cdr (assq 'cnumber (xml-node-attributes chapter)))))
(when (and (not found) (string= cnumber target-chapter))
(setq chapter-contents (xml-node-children chapter))
(setq found t)))))))))
find-formatted-chapter-contents
Here we call format-chapter.
(defun find-formatted-chapter-contents (target-book target-chapter)
"Return the formatted contents of the specified chapter from the parsed XML."
(let ((root (car parsed-xml))
(chapter-contents nil)
(found nil))
(dolist (biblebook (xml-get-children root 'BIBLEBOOK) (and found chapter-contents))
(let ((bname (cdr (assq 'bname (xml-node-attributes biblebook)))))
(when (and (not found) (string= bname target-book))
(dolist (chapter (xml-get-children biblebook 'CHAPTER))
(let ((cnumber (cdr (assq 'cnumber (xml-node-attributes chapter)))))
(when (and (not found) (string= cnumber target-chapter))
(setq chapter-contents (xml-node-children chapter))
(setq found t)))))))
(when found
(setq chapter-contents (format-chapter chapter-contents)))
chapter-contents))
Here's a little test function we can use to make sure that this is working
(find-formatted-chapter-contents "1 Corinthians" "1")
Navigation
open-chapter-in-dabar-buffer
This creates a read only dabar buffer, and then inputs the contents of the appropriate chapter. This function can probably be factored into get-chapter. We also are writing the current book and chapter as local variables. I'm not sure what happens here if you reach the end of the book.
(defun open-chapter-in-dabar-buffer (target-book target-chapter contents)
"Open the provided chapter contents in a Dabar buffer."
(if contents
(let ((buf (get-buffer-create bible-buffer-name))
(formatted-chapter (format-chapter contents)))
(switch-to-buffer buf)
(setq buffer-read-only nil)
(erase-buffer)
(insert (format "%s %s: \n\n" target-book target-chapter))
(insert formatted-chapter)
(goto-char (point-min))
(set (make-local-variable 'current-book) target-book)
(set (make-local-variable 'current-chapter) target-chapter)
(dabar-mode 1)
(setq buffer-read-only t))
(message "Chapter not found")))
get_chapter
This function takes a target-book and target-chapter and calls open-chapter-in-dabar-buffer in order to load the chapter. I'm not exactly sure why it's factored out into it's own function. I don't think we call open-chapter-in-dabar-buffer anywhere else.
(defun get_chapter (target-book target-chapter)
"This inputs a book and chapter and loads it into a buffer for browsing."
(let ((contents (find-chapter-contents target-book target-chapter)))
(open-chapter-in-dabar-buffer target-book target-chapter contents)))
keybind for get_chapter
This creates a keybind for get-chapter. I'm not sure this should really be configured here.
;; (get_chapter "Genesis" "1")
;; cant get this to work
(define-key dabar-mode-keymap (kbd "C-c b c") 'get_chapter)
navigation inside bible reading mode
These next four commands allow you to page back and forth between chapters. We retreive the book/chapter values which are stored as a local-variable inside the buffer. Then, we iterate on them, and redraw the buffer.
get-next-chapter
(defun get-next-chapter ()
(interactive)
;; Assuming `current-book` and `current-chapter` are buffer-local variables in the chapter display buffer
(let ((next-item (get-next-chapter-item current-chapter book-and-chapters))) ;; Assume book-alist is defined and accessible
(if next-item
(progn
;; Fetch and display the next chapter
(get_chapter (car next-item) (cdr next-item) parsed-xml) ;; Assume parsed-xml is defined and accessible
;; Update buffer-local variables
(set (make-local-variable 'current-book) (car next-item))
(set (make-local-variable 'current-chapter) (cdr next-item)))
(message "You are at the last chapter"))))
get-next-chapter-item
Here we have a function which allows you to page forward a chapter when you are in reading mode.
(defun get-next-chapter-item (current-item book-info-alist)
(let ((found nil)
(result nil)
(current-book nil))
(catch 'done
(dolist (pair book-info-alist)
(setq current-book (car pair))
(dolist (item (cdr pair))
(if found
(progn
(setq result (cons current-book item))
(throw 'done t)))
(when (equal current-item item)
(setq found t)))))
(if (not found)
(error "Item not found in list"))
result))
;; Keybinding to "turn the page"
(define-key dabar-mode-keymap (kbd "C-c n") 'get-next-chapter)
get-previous-chapter
(defun get-previous-chapter ()
(interactive)
;; Assuming `current-book` and `current-chapter` are buffer-local variables in the chapter display buffer
(let ((prev-item (get-previous-chapter-item current-chapter book-and-chapters))) ;; Assume book-and-chapters is defined and accessible
(if prev-item
(progn
;; Fetch and display the previous chapter
(get_chapter (car prev-item) (cdr prev-item) parsed-xml) ;; Assume parsed-xml is defined and accessible
;; Update buffer-local variables
(set (make-local-variable 'current-book) (car prev-item))
(set (make-local-variable 'current-chapter) (cdr prev-item)))
(message "You are at the first chapter"))))
get-previous-chapter-item
(defun get-previous-chapter-item (current-item book-info-alist)
(let ((found nil)
(last-item nil)
(last-book nil))
(catch 'done
(dolist (pair book-info-alist)
(let ((current-book (car pair)))
(dolist (item (cdr pair))
(when (equal current-item item)
(if last-item
(progn
(setq found t)
(throw 'done (cons last-book last-item)))
(throw 'done nil)))
(setq last-item item)
(setq last-book current-book)))))
(if (not found)
(error "Item not found in list"))
(cons last-book last-item)))
;; Keybinding to go to the previous chapter
(define-key dabar-mode-keymap (kbd "C-c p") 'get-previous-chapter)
extract-bible-metadata
(defun extract-bible-metadata (filename)
"Extract Bible metadata from the given FILENAME.
Returns a cons cell (BOOK . CHAPTER) or nil if not found."
(when (and filename (string-match "/Dabar/$[^/]+$/$[^/]+$\\.org$" filename))
(let ((book (match-string 1 filename))
(chapter (match-string 2 filename)))
;; If the book starts with a "1", insert a space after it
(when (string-prefix-p "1" book)
(setq book (replace-regexp-in-string "^1" "1 " book)))
(cons book chapter))))
get verse
;; find verse
(defun get_verse (target-book target-chapter target-verse xml)
(let ((root (car xml))
(verse-contents nil)
(found-chapter nil)
(found-verse nil))
(dolist (biblebook (xml-get-children root 'BIBLEBOOK) (and found-verse verse-contents))
(let ((bname (cdr (assq 'bname (xml-node-attributes biblebook)))))
(when (and (not found-chapter) (string= bname target-book))
(dolist (chapter (xml-get-children biblebook 'CHAPTER))
(let ((cnumber (cdr (assq 'cnumber (xml-node-attributes chapter)))))
(when (and (not found-chapter) (string= cnumber target-chapter))
(setq found-chapter t)
(dolist (verse (xml-get-children chapter 'VERS))
(let ((vnumber (cdr (assq 'vnumber (xml-node-attributes verse)))))
(when (and (not found-verse) (string= vnumber target-verse))
(setq verse-contents (xml-node-children verse))
(setq found-verse t))))))))))
(if found-verse
(message "%s %s:%s. %s" target-book target-chapter target-verse verse-contents)
(message "Verse not found"))))
;; (get_verse "Genesis" "1" "1" parsed-xml)
Org Features
org-bible-folow
(defun org-bible-follow (path)
"Follow a Bible link."
(message "Received path: %s" path)
;; Parse the passage from the custom URI scheme
(if (string-match "$[0-9]*[A-Za-z]+$$[0-9]+$$?::$$[0-9]+$?$$?" path)
(let* ((book (match-string 1 path))
(chapter (match-string 2 path))
;; Construct the reference for org-roam
(reference (concat "bible:" (replace-regexp-in-string " \\|:.*" "" path)))
;; Construct the org-roam file path based on the book and chapter
(roam-path (format "%s/Dabar/%s/%s.org" org-roam-directory book chapter)))
(if (file-exists-p roam-path)
;; If the org-roam file exists, open it
(find-file roam-path)
;; If the org-roam file doesn't exist, create and open it
(progn
(find-file roam-path)
(org-id-get-create)
(org-roam-ref-add reference)
(insert "\n"))))
;; If parsing fails, show a message
(message "Chapter not found")))
org-bible-export
This provides custom functions which can be used during export. I do not know if this works with ox-hugo or not. Actually I'm not positive that it works at all
(defun org-bible-export (path desc format)
"Export a Bible link."
(cond
((eq format 'html) (format "<a href=\"https://www.biblegateway.com/passage/?search=%s\">%s</a>" path (or desc path)))
((eq format 'latex) (format "\\href{https://www.biblegateway.com/passage/?search=%s}{%s}" path (or desc path)))
((eq format 'ascii) (format "%s" (or desc path)))
(t path)))
Set org-link parameters
(org-link-set-parameters "bible"
:follow #'org-bible-follow
:export #'org-bible-export)
insert-bible-text
(defun insert-bible-text ()
"Insert the related Bible chapter at the end of the buffer."
(let ((metadata (extract-bible-metadata (buffer-file-name)))
(delineator "\n--------------\n"))
(when metadata
(let* ((book (car metadata))
(chapter (cdr metadata))
(chapter-text (find-formatted-chapter-contents book chapter)))
(when chapter-text
;; Avoid duplicate insertion
(unless (save-excursion
(goto-char (point-max))
(let ((found-pos (search-backward delineator nil t)))
(message "Delineator found at position: %s" found-pos)
found-pos))
;; Move to the end of the buffer and insert
(goto-char (point-max))
(insert delineator)
(insert chapter-text)))))))
remove-inserted-bible-text
(defun remove-inserted-bible-text ()
"Remove the inserted Bible text and the delineator from the end."
(let ((delineator "\n--------------\n"))
(goto-char (point-max))
;; Check if the buffer ends with the inserted Bible text preceded by the delineator
(when (search-backward delineator nil t)
;; Delete everything from the beginning of the delineator to the end of the buffer
(delete-region (point) (point-max)))))
load-bible-chapter
(defun load-bible-chapter ()
"Identify if the current note is inside the 'Dabar' directory and manage the Bible chapter insertion."
;; Check if this note is inside the 'Dabar' directory and follows the correct structure
(when (extract-bible-metadata (buffer-file-name))
(insert-bible-text)
;; Add custom functions to the relevant hooks for this buffer
(add-hook 'before-save-hook 'remove-inserted-bible-text nil t)
(add-hook 'after-save-hook 'insert-bible-text nil t)
;; Disable auto-save for the current buffer
(set (make-local-variable 'auto-save-default) nil)))
;; add a hook so that it checks for Bible metadata every time an org-roam file is opened
(add-hook 'find-file-hook
(lambda ()
(when (derived-mode-p 'org-mode)
(load-bible-chapter))))
find-book-full-form
(defun find-book-full-form (abbr)
"Find the full form of a Bible book given its abbreviation."
(catch 'found
(dolist (entry bible-alist)
(when (member abbr entry)
(throw 'found (car entry))))))
Emacs interface
prompt-for-verse
(defun prompt-for-verse ()
"Prompt the user for a Bible verse and return the full book name and verse details."
(let* ((verse (read-string "Enter Bible verse (e.g., Genesis 1:1): "))
;; Adjust the splitting to account for books starting with numbers
(split-verse (split-string verse " "))
(book-abbr (if (string-match-p "^[0-9]$" (car split-verse))
(concat (car split-verse) " " (cadr split-verse))
(car split-verse)))
(full-form (find-book-full-form book-abbr))
(chapter-and-verse (if (string-match-p "^[0-9]$" (car split-verse))
(caddr split-verse)
(cadr split-verse)))
(chapter (car (split-string chapter-and-verse ":")))
(verse-number (string-to-number (cadr (split-string chapter-and-verse ":")))))
(unless full-form
(error "Invalid book abbreviation"))
(list (concat full-form " " chapter) verse-number)))
insert-bible-link
(defun insert-bible-link ()
"call prompt-for-verse and insert a formatted link in the current buffer "
(interactive)
(let* ((verse-details (prompt-for-verse))
(book-and-chapter (car verse-details))
(verse-number (cadr verse-details))
(full-verse (format "%s:%d" book-and-chapter verse-number))
(link-target (concat "bible:" (replace-regexp-in-string " \\|:.*" "" book-and-chapter))))
(insert (format "[[%s][%s]]" link-target full-verse))))
get-chapter-prompt
(defun get-chapter-prompt ()
"call prompt-for-verse and open the corresponding chapter in a dabar buffer"
(interactive)
(let* ((verse-details (prompt-for-verse))
(book-and-chapter (car verse-details)) ; e.g., "Genesis 1"
(verse-number (cadr verse-details)) ; e.g., 1
(chapter (string-to-number (cadr (split-string book-and-chapter " "))))
(book (car (split-string book-and-chapter " "))))
(unless chapter
(error "Invalid chapter format"))
(get-chapter book chapter)))
;; You can bind the function to a key combination if needed.
(global-set-key (kbd "C-c b b") 'insert-bible-link)
(provide 'dabar-mode)