Add journal system as well as auto inserting

One big feature of the org-journal is the ability to auto generate
empty files, and I realized that I needed to kick it up a notch with
the auto-insert. Actually brought my old code from years ago, as it
still works.
This commit is contained in:
Howard Abrams 2021-11-10 14:24:55 -08:00
parent 1b5ad5a96c
commit 465fb840c1
11 changed files with 314 additions and 15 deletions

View file

@ -18,8 +18,11 @@ This creates [[file:~/.emacs.d/init.el][~/.emacs.d/init.el]] that starts the pro
- [[file:ha-org.org][ha-org.org]] :: configures the basics for org-mode formatted files. Specific features, however, come from their own files, however.
- [[file:ha-org-word-processor.org][ha-org-word-processor.org]] :: attempts to make Org files /visually/ look like what one might see in a word processor, including turning off the colors for headers, and instead increasing their size.
- [[file:ha-org-clipboard.org][ha-org-clipboard.org]] :: automatically converting HTML from a clipboard into Org-formatted content.
- [[file:ha-org-journaling.org][ha-org-journaling.org]] :: for writing journal entries and tasks.
- [[file:ha-org-sprint.org][ha-org-sprint.org]] :: functions for working with the my Org-focused sprint files.
- [[file:ha-programming.org][ha-programming.org]] :: configuration for /all/ programming languages, or at least, the simple ones.
- [[file:ha-remoting.org][ha-remoting.org]] :: my interface to systems using SSH and Vterm.
- [[file:ha-feed-reader.org][ha-feed-reader.org]] :: configuration of elfeed as well as my RSS feeds.
- [[file:ha-capturing-notes.org][ha-capturing-notes.org]] :: my engineering notebook.
- [[file:ha-programming.org][ha-programming.org]] :: configuration for /all/ programming languages, or at least, the simple ones.
*Note:* Other functions and files come from essays written on [[http://www.howardism.org][my blog]]. To help with this, see [[file:support/final-initialize.el][support/final-initialize.el]] file.

View file

@ -112,7 +112,7 @@ The following loads the rest of my org-mode literate files. I add them as they a
"ha-org.org"
"ha-org-word-processor.org"
"ha-org-clipboard.org"
;; "org-journaling.org"
"ha-org-journaling.org"
;; "org-publishing.org"
"ha-org-sprint.org"
"ha-capturing-notes.org"

View file

@ -18,7 +18,7 @@ A literate programming file for configuring org for capturing notes.
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; /home/howard/other/hamacs/ha-capturing-notes.org
;; ~/other/hamacs/ha-capturing-notes.org
;; And tangle the file to recreate this one.
;;
;;; Code:

View file

@ -80,9 +80,44 @@ Using [[https://github.com/joaotavora/yasnippet][yasnippet]] to convert template
#+BEGIN_SRC emacs-lisp
(use-package yasnippet
:config
(add-to-list 'yas-snippet-dirs "~/.emacs.d/snippets")
(add-to-list 'yas-snippet-dirs (expand-file-name "snippets" user-emacs-directory))
(yas-global-mode +1))
#+END_SRC
** Auto Insert Templates
The [[https://www.emacswiki.org/emacs/AutoInsertMode][auto-insert]] feature is a wee bit complicated. All I want is to associate a filename regular expression with a YASnippet template. I'm stealing some ideas from Henrik Lissner's [[https://github.com/hlissner/doom-emacs/blob/develop/modules/editor/file-templates/autoload.el][set-file-template!]] macro, but maybe simpler?
#+BEGIN_SRC emacs-lisp
(use-package autoinsert
:init
(setq auto-insert-directory (expand-file-name "templates" user-emacs-directory))
;; Don't want to be prompted before insertion:
(setq auto-insert-query nil)
(add-hook 'find-file-hook 'auto-insert)
(auto-insert-mode t))
#+END_SRC
However, auto insertion requires entering data for particular fields, and for that Yasnippet is better, so in this case, we combine them:
#+BEGIN_SRC emacs-lisp
(defun ha-autoinsert-yas-expand()
"Replace text in yasnippet template."
(yas-expand-snippet (buffer-string) (point-min) (point-max)))
#+END_SRC
And since I'll be associating snippets with new files all over my configuration, let's make a helper function:
#+BEGIN_SRC emacs-lisp
(defun ha-auto-insert-file (filename-re snippet-name)
"Autofill file buffer matching FILENAME-RE regular expression.
The contents inserted from the YAS SNIPPET-NAME."
;; The define-auto-insert takes a regular expression and an ACTION:
;; ACTION may also be a vector containing several successive single
;; actions as described above, e.g. ["header.insert" author-update].
(define-auto-insert filename-re
(vector snippet-name 'ha-autoinsert-yas-expand)))
#+END_SRC
As an example of its use, any Org files loaded in /this project/ should insert my config file:
#+BEGIN_SRC emacs-lisp
(ha-auto-insert-file (rx "hamacs/" (one-or-more any) ".org" eol) "hamacs-config")
#+END_SRC
** Request System
The above code (and other stuff) needs the [[https://github.com/tkf/emacs-request][request]] package:
#+BEGIN_SRC emacs-lisp

View file

@ -19,7 +19,7 @@ A literate programming file of functions for formatting the clipboard.
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; /home/howard/other/hamacs/org-clipboard.org
;; ~/other/hamacs/org-clipboard.org
;; And tangle the file to recreate this one.
;;
;;; Code:

201
ha-org-journaling.org Normal file
View file

@ -0,0 +1,201 @@
#+TITLE: Org Journaling
#+AUTHOR: Howard X. Abrams
#+EMAIL: howard.abrams@gmail.com
#+DATE: 2020-09-24
#+FILETAGS: :emacs:
A literate programming configuration file for extending the Journaling capabilities.
#+BEGIN_SRC emacs-lisp :exports none
;;; org-journaling.el --- A literate programming configuration file for extending the Journaling capabilities. -*- lexical-binding: t; -*-
;;
;; Copyright (C) 2020 Howard X. Abrams
;;
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
;; Maintainer: Howard X. Abrams <howard.abrams@gmail.com>
;; Created: September 24, 2020
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; ~/other/hamacs/org-journaling.org
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
* Introduction
Using the [[https://github.com/bastibe/org-journal][org-journal]] project to easily create /daily/ journal entries:
#+BEGIN_SRC emacs-lisp
(use-package org-journal
:init
(setq org-journal-dir "~/journal"
org-journal-date-format "TODO "
org-journal-time-format ""
org-journal-file-type 'daily
org-journal-file-format "%Y%m%d")
:config
#+END_SRC
Notice that the rest of this file's contents is /contained/ in this =config= section!
And let's put a /leader key/ sequence for it (Doom-specific):
#+BEGIN_SRC emacs-lisp
(ha-leader "f j" '("journal" . org-journal-new-entry))
#+END_SRC
In normal Org file, I like large headers, but in my Journal, where each task is a header, I want them smaller:
#+BEGIN_SRC emacs-lisp
(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)))
#+END_SRC
But new files could use /my formatting/ (which is different than the options available in the project):
#+BEGIN_SRC emacs-lisp
(ha-auto-insert-file (rx "journal/" (zero-or-more any) (= 8 digit)) "journal")
#+END_SRC
This depends on the following [[file:~/.doom.d/snippets/org-journal-mode/__journal][snippet/template file]]:
#+BEGIN_SRC snippet :tangle ~/other/hamacs/templates/journal
#+TITLE: Journal Entry- `(ha-journal-file-datestamp)`
$0
#+END_SRC
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.
#+BEGIN_SRC emacs-lisp
(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)))
#+END_SRC
Using the "date" associated with a file, we can create our standard timestamp:
#+BEGIN_SRC emacs-lisp
(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)))
#+END_SRC
Close the =use-package= call:
#+BEGIN_SRC emacs-lisp
)
#+END_SRC
* 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.
#+BEGIN_SRC emacs-lisp
(defun org-journal-find-location ()
(org-journal-new-entry t)
(org-narrow-to-subtree)
(goto-char (point-max)))
(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))
#+END_SRC
* 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...
#+BEGIN_SRC emacs-lisp
(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) "")))))
#+END_SRC
Which means that the following defines this function:
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest split-string-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"))))
#+END_SRC
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:
#+BEGIN_SRC emacs-lisp
(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))))
#+END_SRC
And this allows us to create two simple functions that can load the "next" and "previous" files:
#+BEGIN_SRC emacs-lisp
(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+)))
#+END_SRC
#+BEGIN_SRC emacs-lisp
(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-)))
#+END_SRC
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file.
#+BEGIN_SRC emacs-lisp :exports none
(provide 'ha-org-journaling)
;;; ha-org-journaling.el ends here
#+END_SRC
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
#+DESCRIPTION: A literate programming configuration file for extending the Journaling capabilities.
#+PROPERTY: header-args:sh :tangle no
#+PROPERTY: header-args:emacs-lisp :tangle yes
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js

View file

@ -18,7 +18,7 @@ A literate programming file for configuring org-mode and those files.
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; /home/howard/other/hamacs/ha-org.org
;; ~/other/hamacs/ha-org.org
;; And tangle the file to recreate this one.
;;
;;; Code:

View file

@ -1,10 +1,18 @@
#!/usr/bin/env bash
# ----------------------------------------------------------------------
# INITIALIZE the HAMACS SYSTEM
# ----------------------------------------------------------------------
HAMACS_DIR=$(cd "$(dirname "$0")"; pwd)
HAMACS_DEST=$HOME/.emacs.hamacs # A symlink to ~/.emacs.d
mkdir -p $HAMACS_DEST
mkdir -p $HAMACS_DEST/elisp
for LINK in snippets templates elisp
do
rm -rf $HAMACS_DEST/$LINK
ln -s --force $HAMACS_DIR/$LINK $HAMACS_DEST
done
cat > $HAMACS_DEST/init.el <<EOF
;;; init.el --- Hamacs Init -*- lexical-binding: t; -*-

View file

@ -61,15 +61,8 @@ non-tangled files."
(make-directory hamacs-private-code-dir))
(ha/install-code-from-essays)
(ha/install-code-from-hamacs)
;; (ha/install-code-from-hamacs)
;; Link some source-controlled directories into ~/.doom.d ...
;; This used to be a longer list. ;-)
(dolist (something '("snippets"))
(let ((dir (f-join user-emacs-directory something))
(src (f-join default-directory something)))
(unless (f-symlink? dir)
(f-symlink src dir)))))
(ha/install-configuration)
(provide 'final-initialize)

52
templates/hamacs-config Normal file
View file

@ -0,0 +1,52 @@
#+TITLE: ${1:`(->> (buffer-file-name)
(file-name-base)
(s-split-words)
(--map (s-capitalize it))
(s-join " "))`}
#+AUTHOR: `user-full-name`
#+EMAIL: `user-mail-address`
#+DATE: `(format-time-string "%Y-%m-%d")`
#+FILETAGS: :emacs:
${2:A literate programming file configuring Emacs.}
#+BEGIN_SRC emacs-lisp :exports none
;;; `(file-name-base (buffer-file-name)))`.el --- $2 -*- lexical-binding: t; -*-
;;
;; Copyright (C) `(format-time-string "%Y")` `user-full-name`
;;
;; Author: `user-full-name` <http://gitlab.com/howardabrams>
;; Maintainer: `user-full-name` <`user-mail-address`>
;; Created: `(format-time-string "%B %e, %Y")`
;;
;; This file is not part of GNU Emacs.
;;
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
;; `(buffer-file-name)`
;; And tangle the file to recreate this one.
;;
;;; Code:
#+END_SRC
* Introduction
$0
* Technical Artifacts :noexport:
Let's =provide= a name so we can =require= this file:
#+BEGIN_SRC emacs-lisp :exports none
(provide '`(file-name-base (buffer-file-name)))`)
;;; `(file-name-base (buffer-file-name)))`.el ends here
#+END_SRC
#+DESCRIPTION: $2
#+PROPERTY: header-args:sh :tangle no
#+PROPERTY: header-args:emacs-lisp :tangle yes
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js

7
templates/journal Normal file
View file

@ -0,0 +1,7 @@
# -*- mode: snippet -*-
# name: journal
# key: journal
# --
#+TITLE: Journal Entry- `(ha-journal-file-datestamp)`
$0