;;; holy-books.el --- Org-mode links/tooltips/lookups for Quran & Bible -*- lexical-binding: t; -*- ;; Copyright (c) 2021-2026 Musa Al-hassy ;; Author: Musa Al-hassy ;; Version: 26.10.14 ;; Package-Requires: ((s "1.12.0") (dash "2.16.0") (emacs "27.1") (org "9.1")) ;; Keywords: quran, bible, links, tooltips, convenience, comm, hypermedia ;; Repo: https://github.com/alhassy/holy-books ;; Homepage: https://alhassy.github.io/holy-books/ ;; 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 this program. If not, see . ;;; Commentary: ;; This library provides common desirable features using the Org interface for ;; when writing about the Quran and the Bible: ;; ;; 0. Links “quran:chapter:verse|colour|size|no-info-p”, or just “quran:chapter:verse” ;; for retrieving a verse from the Quran. Use “Quran:chapter:verse” to HTML export ;; as a tooltip. The particular translation can be selected by altering the ;; HOLY-BOOKS-QURAN-TRANSLAITON variable. ;; ;; 1. Likewise, “bible:book:chapter:verse”. ;; The particular version can be selected by altering the ;; HOLY-BOOKS-BIBLE-VERSION variable. ;; ;; 2. Two functions, HOLY-BOOKS-QURAN and HOLY-BOOKS-BIBLE that do the heavy ;; work of the link types. ;; ;; 3. A link type to produce the Arabic basmallah; e.g., “basmala:darkgreen|20px|span”. ;; ;; Minimal Working Example: ;; ;; Sometimes I want to remember the words of the God of Abraham. In English Bibles, ;; His name is “Elohim”, whereas in Arabic Bibles and the Quran, His name is ;; “Allah”. We can use links to quickly access them, such as Quran:7:157|darkgreen ;; and bible:Deuteronomy:18:18-22|darkblue. Arab-speaking Christians and Muslims ;; use the Unicode symbol [[green:ﷲ]] to refer to Him ---e.g., they would write ﷲ ﷳ , ;; “Allah akbar”, to declare the greatness of God-- and, as the previous passage ;; says “in the name of the Lord”, there is a nice calligraphic form that is used ;; by Arabic speakers when starting a task, namely [[basmala:darkgreen|20px|span]] ;; ---this is known as the ‘basmalallah’, which is Arabic for “name of God”. ;; (Using capitalised ‘Quran:⋯’ and ‘Bible:⋯’ results in tooltips.) ;; ;; This file has been tangled from a literate, org-mode, file. ;;; Code: ;; String and list manipulation libraries ;; https://github.com/magnars/dash.el ;; https://github.com/magnars/s.el (require 's) ;; “The long lost Emacs string manipulation library” (require 'dash) ;; “A modern list library for Emacs” (require 'cl-lib) ;; New Common Lisp library; ‘cl-???’ forms. (require 'org) (require 'url) (defvar url-http-end-of-headers) (defun holy-books--fetch (url) "URL ↦ the body of its response, decoded as UTF-8." (with-current-buffer (url-retrieve-synchronously url t t 30) (prog1 (decode-coding-string (buffer-substring (1+ url-http-end-of-headers) (point-max)) 'utf-8) (kill-buffer)))) (defconst holy-books-version (package-get-version)) (defun holy-books-version () "Print the current holy-books version in the minibuffer." (interactive) (message holy-books-version)) ;;;###autoload (define-minor-mode holy-books-mode "Org-mode links, tooltips, and Lisp look-ups for the Quran & Bible." nil nil nil) (org-link-set-parameters "basmala" :follow (lambda (label) nil) :export (lambda (label description backend) (-let [(color size html-tag) (s-split "|" label)] (format "<%s style=\"color:%s;font-size:%s;padding:25px\"> ﷽ " (or html-tag "center") (or color 'darkgreen) (or size '60px) (or html-tag "center")))) :face '(:foreground "green" :weight bold)) (defvar holy-books-quran-cache nil "A plist storing the verses looked up by ‘holy-books-quran’ for faster reuse. Each key in the plist refers to a chapter, and the values are plists: Keys are verses numbers and values are the actual verses ---but there is a special key ‘:name’ whose value is the Arabic-English name of the chapter.") (defvar holy-books-quran-translation "131" "The translation code of the Quran; a string. Possible codes include Code Translation -------------------- 131 Dr. Mustafa Khattab, The Clear Quran (Default) 20 Saheeh International 85 M.A.S. Abdel Haleem 19 M. Pickthall 22 A. Yusuf Ali 95 A. Maududi (Tafhim commentary) 84 T. Usmani 203 Al-Hilali & Khan 57 Transliteration Any translation in quran.com's “Translations” menu works; most codes are listed here: https://api.quran.com/api/v4/resources/translations") (defun holy-books-quran (chapter verse) "Lookup a verse, as a string, from the Quran. CHAPTER and VERSE are both numbers, referring to a chapter in the Quran and a verse it contains. In the associated Org link, both are treated as strings. + Lookups are stored in the variable `holy-books-quran-cache' for faster reuse. + Quran lookup is based on https://quran.com . + Examples: ;; Get verse 2 of chapter 7 of the Quran (holy-books-quran 7 2) ;; Get English-Arabic name of 7th chapter (cl-getf (cl-getf holy-books-quran 7) :name) The particular translation can be selected by altering the HOLY-BOOKS-QURAN-TRANSLAITON variable. -------------------------------------------------------------------------------- There is an Org link form: “quran:chapter:verse|color|size|no-info-p” Only ‘chapter’ and ‘verse’ are mandatory; when ‘no-info-p’ is given, the chapter and verse numbers are not mentioned in the resulting output. Examples: quran:7:157|darkgreen|30px|t quran:7:157 For now, only Org HTML export is supported. -------------------------------------------------------------------------------- Finally, there is also an HTML tooltip version with a captial ‘Q’; it takes the same arguments but only the chapter and verse are actually used. E.g. Quran:7:157 results in text “Quran 7:157” with a tooltip showing the verse." (unless (cl-getf (cl-getf holy-books-quran-cache chapter) verse) (-let [(name . text) (or (ignore-errors (holy-books--quran-from-page chapter verse)) (holy-books--quran-from-api chapter verse))] (setf (cl-getf (cl-getf holy-books-quran-cache chapter) :name) name) (setf (cl-getf (cl-getf holy-books-quran-cache chapter) verse) (s-replace-regexp "]*>[^<]*" "" text)))) ;; Footnote markers (cl-getf (cl-getf holy-books-quran-cache chapter) verse)) ;; quran.com's public API lacks some translations, such as 131, so we first read the JSON its pages embed; ;; that is the site's internal Next.js state, not a published API, hence the fallback. (defun holy-books--quran-from-page (chapter verse) "(chapter, verse) ↦ (chapter-name . verse-text), from the JSON embedded in quran.com's page for the verse." (let-alist (let* ((page (holy-books--fetch (format "https://quran.com/%s:%s?translations=%s" chapter verse holy-books-quran-translation))) (json (cadr (s-match "" page)))) (alist-get 'pageProps (alist-get 'props (json-parse-string json :object-type 'alist)))) (-when-let (text (alist-get 'text (seq-find #'always (alist-get 'translations (seq-first .versesResponse.verses))))) (cons (format "%s %s" .chapterResponse.chapter.transliteratedName .chapterResponse.chapter.translatedName) text)))) (defun holy-books--quran-from-api (chapter verse) "(chapter, verse) ↦ (chapter-name . verse-text), from quran.com's public API; if it lacks our translation, use 20." (cl-labels ((api (path) (json-parse-string (holy-books--fetch (concat "https://api.quran.com/api/v4/" path)) :object-type 'alist)) (verse-in (translation) (thread-last (format "quran/translations/%s?verse_key=%s:%s" translation chapter verse) (api) (alist-get 'translations) (seq-find #'always) (alist-get 'text)))) (let-alist (alist-get 'chapter (api (format "chapters/%s" chapter))) (cons (format "%s %s" .name_simple .translated_name.name) (or (verse-in holy-books-quran-translation) (verse-in "20")))))) ;; quran:chapter:verse|color|size|no-info-p (org-link-set-parameters "quran" :follow (lambda (_) nil) :export (lambda (label _ backend) (-let* (((chapter:verse color size no-info-p) (s-split "|" label)) ((chapter verse) (mapcar #'string-to-number (s-split ":" chapter:verse)))) (cond ((eq 'html backend) (format " ﴾ %s﴿ %s " color size (holy-books-quran chapter verse) (if no-info-p "" (format (concat "" "" "Quran %s:%s, %s" "" "") chapter chapter verse (cl-getf (cl-getf holy-books-quran-cache chapter) :name))))) ((eq 'md backend) (format "\n> %s\n>\n> %s\n" (holy-books-quran chapter verse) (if no-info-p "" (format "[Quran %s:%s %s](https://quran.com/chapter_info/%s)" chapter verse (cl-getf (cl-getf holy-books-quran-cache chapter) :name) chapter))))))) :face '(:foreground "green" :weight bold)) ;; Quran:chapter:verse|color|size|no-info-p (org-link-set-parameters "Quran" :follow (lambda (_) nil) :export (lambda (label _ backend) (-let* (((chapter:verse _ __ ___) (s-split "|" label)) ((chapter verse) (mapcar #'string-to-number (s-split ":" chapter:verse)))) (cond ((eq 'html backend) (format " %s﴿

%s

%s\"> Quran %s:%s
 " (holy-books-quran chapter verse) (cl-getf (cl-getf holy-books-quran-cache chapter) :name) (format "https://quran.com/%s" chapter) chapter verse)) ((eq 'md backend) (format "[Quran %s:%s](%s \"%s - %s\")" chapter verse (format "https://quran.com/%s" chapter) (s-replace "\"" "″" (holy-books-quran chapter verse)) (cl-getf (cl-getf holy-books-quran-cache chapter) :name)))))) :face '(:foreground "green" :weight bold)) (defun holy-books--remove-html-markup (html) (s-replace-regexp " " "" (s-replace-regexp "<.*?>" "" html))) (defun holy-books-insert-quran () "Insert a Quranic verse at point; prompt user for details." (interactive) (let ((chapter (string-to-number (read-string "Quran Chapter: "))) (verse (string-to-number (read-string "Quran Verse: ")))) (if (member 0 (list chapter verse)) (error (concat "holy-books ∷ There seems to be a typo;" "please enter appropriate numbers.")) (insert (holy-books--remove-html-markup (holy-books-quran chapter verse))) (fill-paragraph)))) (defvar holy-books-bible-version 'niv "The version code of the Holy Bible; a symbol or string. Possible version codes include: Code Version --------------------------------------- niv New International Version, DEFAULT asv American Standard Version bbe Bible in Basic English drb Darby's Translation esv English Standard Version kjv King James Version nas New American Standard nkjv New King James Version nlt New Living Translation nrs New Revised Standard Version rsv Revised Standard Version msg The Message Bible web World English Bible ylt Young's Literal") (defun holy-books-bible (book chapter verses) "Retrive a verse from the Christian Bible. CHAPTER is a number. VERSES is either a number or a string “x-y” of numbers. BOOK is any of the books of the Bible, with ‘+’ instead of spaces! Examples: (holy-books-bible \"Deuteronomy\" 18 \"18-22\") ;; Lisp bible:Deuteronomy:18:18-22|darkblue ;; Org-mode Bible:Deuteronomy:18:18-22 ;; Tooltip There is also an Org HTML export link, “bible:book:chapter:verse” sharing the same optional arguments and variations as the “quran:” link; see the documentation of the method HOLY-BOOKS-QURAN for details. The particular version can be selected by altering the HOLY-BOOKS-BIBLE-VERSION variable. Currently, Bible lookups are not cached and Quran lookups do not support the “x-y” verse lookup style. Possible books include: ;; Old Testament Genesis Exodus Leviticus Numbers Joshua Judges Ruth 1+Samuel 2+Samuel 1+Kings 2+Kings 1+Chronicles 2+Chronicles Ezra Nehemiah Esther Job Psalms Proverbs Ecclesiastes Song+of+Solomon Isaiah Jeremiah Lamentations Ezekiel Daniel Hosea Joel Amos Obadiah Jonah Micah Nahum Habakkuk Zephaniah Haggai Zechariah Malachi ;; New Testament Matthew Mark Luke John Acts Romans 1+Corinthians 2+Corinthians Galatians Ephesians Philippians Colossians 1+Thessalonians 2+Thessalonians 1+Timothy 2+Timothy Titus Philemon Hebrews James 1+Peter 2+Peter 1+John 2+John 3+John Jude Revelation For example, the following incantation yields the first verse of the first chapter of each book. (s-join \"\n\n
\" (--map (holy-books-bible it 1 1) '(...above list...)))" (thread-last (holy-books--fetch (format "https://www.christianity.com/bible/bible.php?q=%s+%s%%3A%s&ver=%s" book chapter verses holy-books-bible-version)) (s-match "passage-text\">\\(?:.\\|\n\\)*?]*>\\(\\(?:.\\|\n\\)*?\\)

") (cadr) (s-replace-regexp "]*class=\"\\(big-chapter\\|verse-num\\)[^>]*>.*?\\( \\)?" "") (s-replace-regexp ".*?" "") (s-replace-regexp "<.*?>" "") ;; Small-caps, etc. (s-trim))) (defvar holy-books-bible-format-for-verses "﴾ %s﴿" "How should verses be styled?") ;; bible:book:chapter:verses|color|size|no-info-p ;; Ex. bible:Deuteronomy:18:18-22|darkblue|40px (org-link-set-parameters "bible" :follow (lambda (_) nil) :export (lambda (label _ backend) (-let* (((book:chapter:verse color size no-info-p) (s-split "|" label)) ((book chapter verse) (s-split ":" book:chapter:verse))) (cond ((eq 'html backend) (format (concat "" holy-books-bible-format-for-verses ;; ≈ ﴾ %s﴿ "%s ") color size (holy-books-bible book chapter verse) (if no-info-p "" (format (concat "" "" "%s %s:%s" "" "") book chapter book chapter verse)))) ((eq 'md backend) (format "\n> %s\n>\n>%s\n" (holy-books-bible book chapter verse) (if no-info-p "" (format "[%s %s:%s](https://www.christianity.com/bible/bible.php?q=%s+%s&ver=niv)" book chapter verse book chapter verse)))) (t (format "﴾%s﴿ [%s %s:%s]" (s-replace-regexp " " "" (s-replace-regexp "<.*?>" "" (s-collapse-whitespace (holy-books-bible book chapter verse)))) book chapter verse))))) :face '(:foreground "green" :weight bold)) ;; Bible:book:chapter:verses|color|size|no-info-p ;; Ex. Bible:Deuteronomy:18:18-22|darkblue|40px (org-link-set-parameters "Bible" :follow (lambda (_) nil) :export (lambda (label _ backend) (-let* (((book:chapter:verse _ __ ___) (s-split "|" label)) ((book chapter verse) (s-split ":" book:chapter:verse))) (cond ((eq 'html backend) (format " %s﴿

%s\"> %s %s:%s
 " (s-replace "\"" "″" (holy-books-bible book chapter verse)) (format (concat "https://www.christianity.com/" "bible/bible.php?q=%s+%s") book chapter) book chapter verse)) ((eq 'md backend) (format "[%s %s:%s](%s \"%s\")" book chapter verse (format (concat "https://www.christianity.com/" "bible/bible.php?q=%s+%s") book chapter) (s-replace "\"" "″" (holy-books-bible book chapter verse)) )) (t (format "﴾%s﴿ [%s %s:%s]" (s-replace-regexp " " "" (s-replace-regexp "<.*?>" "" (s-collapse-whitespace (holy-books-bible book chapter verse)))) book chapter verse))))) :face '(:foreground "green" :weight bold)) (defun holy-books-insert-bible () "Insert a Biblical verse at point; prompt user for details. See the documentation of HOLY-BOOKS-BIBLE for the appropriate names of books." (interactive) (let ((book (read-string "Bible Book: ")) (chapter (string-to-number (read-string "Bible Chapter: "))) (verse (read-string "Bible Verse: "))) ;; Can be a range; e.g., "18-22" (if (member 0 (list chapter verse)) (error (concat "holy-books ∷ There seems to be a typo;" "please enter appropriate numbers.")) (insert (holy-books--remove-html-markup (s-trim (holy-books-bible book chapter verse)))) (fill-paragraph)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (provide 'holy-books) ;;; holy-books.el ends here