hamacs/ha-org-journaling.org
2024-08-10 21:59:03 -07:00

7.6 KiB

Org Journaling

A literate programming configuration file for extending the Journaling capabilities.

Introduction

Using the org-journal project to easily create daily journal entries:

  (use-package org-journal
    :after org
    :config
    (setq org-journal-dir "~/journal"
          org-journal-date-format " "
          org-journal-time-format ""
          org-journal-file-type 'daily
          org-journal-file-format "%Y%m%d")

    (ha-leader "f j" '("journal" . org-journal-new-entry))


    ;; In normal Org file, I like large headers, but in my Journal,
    ;; where each task is a header, I want them smaller:
    (add-hook 'org-journal-mode-hook
          (lambda ()
            (set-face-attribute 'org-level-1 nil :height 1.2)
            (set-face-attribute 'org-level-2 nil :height 1.1)
            (set-face-attribute 'org-level-3 nil :height 1.0)))

    ;; But new files could use /my formatting/ (which is different
    ;; than the options available in the project):
    (ha-auto-insert-file (rx "journal/" (zero-or-more any) (= 8 digit)) "journal"))

This depends on the following snippet/template file:

#+title: Journal Entry- `(ha-journal-file-datestamp)`

$0

Note that when I create a new journal entry, I want a title that should insert a date to match the file's name, not necessarily the current date (see below).

Journal Filename to Date

Since the Journal's filename represents a date, I should be able to get the "date" associated with a file.

  (defun ha-journal-file-date (&optional datename)
    "Returns a Lisp date-timestamp based on the format of the current filename,
  or DATENAME if given."
    (unless datename
      (setq datename (buffer-file-name)))

    (let* ((datename-parser (rx (group (= 4 digit))
                                (group (= 2 digit))
                                (group (= 2 digit))))
           (parsed-datename (string-match datename-parser datename))
           (day (string-to-number (match-string 3 datename)))
           (month (string-to-number (match-string 2 datename)))
           (year(string-to-number (match-string 1 datename))))
      (encode-time 0 0 0 day month year)))

Using the "date" associated with a file, we can create our standard timestamp:

  (defun ha-journal-file-datestamp (&optional datename)
    "Return a string of the buffer's date (based on the file's name)."
    (format-time-string "%e %b %Y (%A)" (ha-journal-file-date datename)))

Journal Capture

Capturing a task (that when uncompleted, would then spillover to following days) could go to the daily journal entry. This requires a special function that opens today's journal, but specifies a non-nil prefix argument in order to inhibit inserting the heading, as org-capture will insert the heading.

  (defun org-journal-find-location ()
    (org-journal-new-entry t)
    (org-narrow-to-subtree)
    (goto-char (point-max)))

  (defvar org-capture-templates (list))
  (add-to-list 'org-capture-templates
               '("j" "Journal Task/Entry" plain
                 (function org-journal-find-location)
                 "* %?\n\n  %i\n\n  From: %a"
                 :empty-lines 1 :jump-to-captured t :immediate-finish t))

Next and Previous File

Sometimes it is obvious what is the next file based on the one I'm currently reading. For instance, in my journal entries, the filename is a number that can be incremented. Same with presentation files…

  (defun split-string-with-number (string)
    "Returns a list of three components of the string, the first is
    the text prior to any numbers, the second is the embedded number,
    and the third is the rest of the text in the string."
    (let* ((start (string-match "[0-9]+" string))
           (end (string-match "[^0-9]+" string start)))
      (if start
          (list (substring string 0 start)
                (substring string start end)
                (if end  (substring string end)  "")))))

Which means that the following defines this function:

  (ert-deftest split-string-default-separatorsg-with-number-test ()
    (should (equal (split-string-with-number "abc42xyz") '("abc" "42" "xyz")))
    (should (equal (split-string-with-number "42xyz")    '("" "42" "xyz")))
    (should (equal (split-string-with-number "abc42")    '("abc" "42" "")))
    (should (equal (split-string-with-number "20140424") '("" "20140424" "")))
    (should (null  (split-string-with-number "abcxyz"))))

Given this splitter function, we create a function that takes some sort of operator and return a new filename based on the conversion that happens:

  (defun find-file-number-change (f)
    (let* ((filename (buffer-file-name))
           (parts    (split-string-with-number
                      (file-name-base filename)))
           (new-name (number-to-string
                      (funcall f (string-to-number (nth 1 parts))))))
      (concat (file-name-directory filename)
              (nth 0 parts)
              new-name
              (nth 2 parts))))

And this allows us to create two simple functions that can load the "next" and "previous" files:

  (defun find-file-increment ()
    "Takes the current buffer, and loads the file that is 'one
    more' than the file contained in the current buffer. This
    requires that the current file contain a number that can be
    incremented."
    (interactive)
    (find-file (find-file-number-change '1+)))
  (defun find-file-decrement ()
    "Takes the current buffer, and loads the file that is 'one
    less' than the file contained in the current buffer. This
    requires that the current file contain a number that can be
    decremented."
    (interactive)
    (find-file (find-file-number-change '1-)))