I have a couple org-mode based tools now:
- youtube.org for saving liked videos
- movies.org to keep a log of watched movies
- blog.org for this blog
- books.org to keep a reading log
Each of these has an API
heading that defines interactive helpers relevant for
the file. E.g.:
* COMMENT API
#+NAME: api
#+begin_src elisp
(define-minor-mode org-books-mode
"Helper functions for books.org"
:interactive nil
:keymap (list
(cons (kbd "C-c C-o") #'org-books-open-goodreads)))
(defun org-books-open-goodreads ()
"Open goodreads for current entry."
(interactive nil 'org-books-mode)
(browse-url (format "https://goodreads.com/book/show/%s"
(cdar (org-entry-properties nil "goodreadsid")))))
(org-books-mode 1)
#+end_src
# Local Variables:
# eval: (sarg/eval-org-src-block "api")
# End:
Where sarg/eval-org-src-block
is defined as:
(defun sarg/eval-org-src-block (name)
"Eval elisp block NAME. To be used in org files in Local variables section."
(save-excursion
(org-babel-goto-named-src-block name)
(let* ((block-info (org-babel-get-src-block-info))
(lang (nth 0 block-info))
(body (nth 1 block-info)))
(if (or (string= lang "emacs-lisp")
(string= lang "elisp"))
(cl-loop with next = 0
with maxlen = (length body)
for (sexp . next) = (read-from-string body next)
do (eval sexp)
while (< next maxlen))
(error "%s is not an emacs-lisp src block" name)))))
This way when such org file is opened, the API code block gets loaded and a
buffer-local minor mode gets activated. The mode is marked as :interactive nil
so it doesn’t pollute M-x
. Helper functions are restricted to this mode as
well with (interactive nil 'org-books-mode)
. I like that such an “API” is
contained in the file itself, making it a kind of self-hosted program.