dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

denote-journal-extras.el (10303B)


      1 ;;; denote-journal-extras.el --- Convenience functions for daily journaling  -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2023-2024  Free Software Foundation, Inc.
      4 
      5 ;; Author: Protesilaos Stavrou <info@protesilaos.com>
      6 ;; Maintainer: Protesilaos Stavrou <info@protesilaos.com>
      7 ;; URL: https://github.com/protesilaos/denote
      8 
      9 ;; This file is NOT part of GNU Emacs.
     10 
     11 ;; This program is free software; you can redistribute it and/or modify
     12 ;; it under the terms of the GNU General Public License as published by
     13 ;; the Free Software Foundation, either version 3 of the License, or
     14 ;; (at your option) any later version.
     15 
     16 ;; This program is distributed in the hope that it will be useful,
     17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     19 ;; GNU General Public License for more details.
     20 
     21 ;; You should have received a copy of the GNU General Public License
     22 ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
     23 
     24 ;;; Commentary:
     25 
     26 ;; This is a set of optional convenience functions that used to be
     27 ;; provided in the Denote manual.  They facilitate the use of Denote
     28 ;; for daily journaling.
     29 
     30 ;;; Code:
     31 
     32 (require 'denote)
     33 
     34 (defgroup denote-journal-extras nil
     35   "Denote for daily journaling."
     36   :group 'denote
     37   :link '(info-link "(denote) Top")
     38   :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote"))
     39 
     40 (defcustom denote-journal-extras-directory
     41   (expand-file-name "journal" denote-directory)
     42   "Directory for storing daily journal entries.
     43 This can either be the same as the variable `denote-directory' or
     44 a subdirectory of it.
     45 
     46 A value of nil means to use the variable `denote-directory'.
     47 Journal entries will thus be in a flat listing together with all
     48 other notes.  They can still be retrieved easily by searching for
     49 the `denote-journal-extras-keyword'."
     50   :group 'denote-journal-extras
     51   :type '(choice (directory :tag "Provide directory path (is created if missing)")
     52                  (const :tag "Use the `denote-directory'" nil)))
     53 
     54 (defcustom denote-journal-extras-keyword "journal"
     55   "Single word keyword to tag journal entries.
     56 It is used by `denote-journal-extras-new-entry' to add a keyword
     57 to the newly created file."
     58   :group 'denote-journal-extras
     59   :type 'string)
     60 
     61 (defcustom denote-journal-extras-title-format 'day-date-month-year-24h
     62   "Date format to construct the title with `denote-journal-extras-new-entry'.
     63 The value is either a symbol or an arbitrary string that is
     64 passed to `format-time-string' (consult its documentation for the
     65 technicalities).
     66 
     67 Acceptable symbols and their corresponding styles are:
     68 
     69 | Symbol                  | Style                             |
     70 |-------------------------+-----------------------------------|
     71 | day                     | Monday                            |
     72 | day-date-month-year     | Monday 19 September 2023          |
     73 | day-date-month-year-24h | Monday 19 September 2023 20:49    |
     74 | day-date-month-year-12h | Monday 19 September 2023 08:49 PM |
     75 
     76 With a nil value, make `denote-journal-extras-new-entry' prompt
     77 for a title."
     78   :group 'denote-journal-extras
     79   :type '(choice
     80           (const :tag "Prompt for title with `denote-journal-extras-new-entry'" nil)
     81           (const :tag "Monday"
     82                  :doc "The `format-time-string' is: %A"
     83                  day)
     84           (const :tag "Monday 19 September 2023"
     85                  :doc "The `format-time-string' is: %A %e %B %Y"
     86                  day-date-month-year)
     87           (const :tag "Monday 19 September 2023 20:49"
     88                  :doc "The `format-time-string' is: %A %e %B %Y %H:%M"
     89                  day-date-month-year-24h)
     90           (const :tag "Monday 19 September 2023 08:49 PM"
     91                  :doc "The `format-time-string' is: %A %e %B %Y %I:%M %^p"
     92                  day-date-month-year-12h)
     93           (string :tag "Custom string with `format-time-string' specifiers")))
     94 
     95 (defcustom denote-journal-extras-hook nil
     96   "Normal hook called after `denote-journal-extras-new-entry'.
     97 Use this to, for example, set a timer after starting a new
     98 journal entry (refer to the `tmr' package on GNU ELPA)."
     99   :group 'denote-journal-extras
    100   :type 'hook)
    101 
    102 (defun denote-journal-extras-directory ()
    103   "Make the variable `denote-journal-extras-directory' and its parents."
    104   (if-let (((stringp denote-journal-extras-directory))
    105            (directory (file-name-as-directory (expand-file-name denote-journal-extras-directory))))
    106       (progn
    107         (when (not (file-directory-p denote-journal-extras-directory))
    108           (make-directory directory :parents))
    109         directory)
    110     (denote-directory)))
    111 
    112 (defun denote-journal-extras-daily--title-format (&optional date)
    113   "Return present date in `denote-journal-extras-title-format' or prompt for title.
    114 With optional DATE, use it instead of the present date.  DATE has
    115 the same format as that returned by `current-time'."
    116   (format-time-string
    117    (if (and denote-journal-extras-title-format
    118             (stringp denote-journal-extras-title-format))
    119        denote-journal-extras-title-format
    120      (pcase denote-journal-extras-title-format
    121        ('day "%A")
    122        ('day-date-month-year "%A %e %B %Y")
    123        ('day-date-month-year-24h "%A %e %B %Y %H:%M")
    124        ('day-date-month-year-12h "%A %e %B %Y %I:%M %^p")
    125        (_ (denote-title-prompt (format-time-string "%F" date)))))
    126    date))
    127 
    128 (defun denote-journal-extras--get-template ()
    129   "Return template that has `journal' key in `denote-templates'.
    130 If no template with `journal' key exists but `denote-templates'
    131 is non-nil, prompt the user for a template among
    132 `denote-templates'.  Else return nil.
    133 
    134 Also see `denote-journal-extras-new-entry'."
    135   (if-let ((template (alist-get 'journal denote-templates)))
    136       template
    137     (when denote-templates
    138       (denote-template-prompt))))
    139 
    140 ;;;###autoload
    141 (defun denote-journal-extras-new-entry (&optional date)
    142   "Create a new journal entry in variable `denote-journal-extras-directory'.
    143 Use `denote-journal-extras-keyword' as a keyword for the newly
    144 created file.  Set the title of the new entry according to the
    145 value of the user option `denote-journal-extras-title-format'.
    146 
    147 With optional DATE as a prefix argument, prompt for a date.  If
    148 `denote-date-prompt-use-org-read-date' is non-nil, use the Org
    149 date selection module.
    150 
    151 When called from Lisp DATE is a string and has the same format as
    152 that covered in the documentation of the `denote' function.  It
    153 is internally processed by `denote-parse-date'."
    154   (interactive (list (when current-prefix-arg (denote-date-prompt))))
    155   (let ((internal-date (denote-parse-date date))
    156         (denote-directory (denote-journal-extras-directory)))
    157     (denote
    158      (denote-journal-extras-daily--title-format internal-date)
    159      `(,denote-journal-extras-keyword)
    160      nil nil date
    161      (denote-journal-extras--get-template))
    162     (run-hooks 'denote-journal-extras-hook)))
    163 
    164 (defun denote-journal-extras--entry-today (&optional date)
    165   "Return list of files matching a journal for today or optional DATE.
    166 DATE has the same format as that returned by `denote-parse-date'."
    167   (denote-directory-files
    168    (format "%sT[0-9]\\{6\\}.*_%s"
    169            (format-time-string "%Y%m%d" date)
    170            denote-journal-extras-keyword)))
    171 
    172 ;;;###autoload
    173 (defun denote-journal-extras-new-or-existing-entry (&optional date)
    174   "Locate an existing journal entry or create a new one.
    175 A journal entry is one that has `denote-journal-extras-keyword' as
    176 part of its file name.
    177 
    178 If there are multiple journal entries for the current date,
    179 prompt for one using minibuffer completion.  If there is only
    180 one, visit it outright.  If there is no journal entry, create one
    181 by calling `denote-journal-extra-new-entry'.
    182 
    183 With optional DATE as a prefix argument, prompt for a date.  If
    184 `denote-date-prompt-use-org-read-date' is non-nil, use the Org
    185 date selection module.
    186 
    187 When called from Lisp, DATE is a string and has the same format
    188 as that covered in the documentation of the `denote' function.
    189 It is internally processed by `denote-parse-date'."
    190   (interactive
    191    (list
    192     (when current-prefix-arg
    193       (denote-date-prompt))))
    194   (let* ((internal-date (denote-parse-date date))
    195          (files (denote-journal-extras--entry-today internal-date)))
    196     (cond
    197      ((length> files 1)
    198       (find-file (completing-read "Select journal entry: " files nil :require-match)))
    199      (files
    200       (find-file (car files)))
    201      (t
    202       (denote-journal-extras-new-entry date)))))
    203 
    204 ;;;###autoload
    205 (defun denote-journal-extras-link-or-create-entry (&optional date id-only)
    206   "Use `denote-link' on journal entry, creating it if necessary.
    207 A journal entry is one that has `denote-journal-extras-keyword' as
    208 part of its file name.
    209 
    210 If there are multiple journal entries for the current date,
    211 prompt for one using minibuffer completion.  If there is only
    212 one, link to it outright.  If there is no journal entry, create one
    213 by calling `denote-journal-extra-new-entry' and link to it.
    214 
    215 With optional DATE as a prefix argument, prompt for a date.  If
    216 `denote-date-prompt-use-org-read-date' is non-nil, use the Org
    217 date selection module.
    218 
    219 When called from Lisp, DATE is a string and has the same format
    220 as that covered in the documentation of the `denote' function.
    221 It is internally processed by `denote-parse-date'.
    222 
    223 With optional ID-ONLY as a prefix argument create a link that
    224 consists of just the identifier.  Else try to also include the
    225 file's title.  This has the same meaning as in `denote-link'."
    226   (interactive
    227    (pcase current-prefix-arg
    228      ('(16) (list (denote-date-prompt) :id-only))
    229      ('(4) (list (denote-date-prompt)))))
    230   (let* ((internal-date (denote-parse-date date))
    231          (files (denote-journal-extras--entry-today internal-date))
    232          (path))
    233     (cond
    234      ((length> files 1)
    235       (setq path (completing-read "Select journal entry: " files nil :require-match)))
    236      (files
    237       (setq path (car files)))
    238      (t
    239       (save-window-excursion
    240         (denote-journal-extras-new-entry date)
    241         (save-buffer)
    242         (setq path (buffer-file-name)))))
    243     (denote-link path
    244                  (denote-filetype-heuristics (buffer-file-name))
    245                  (denote--link-get-description path)
    246                  id-only)))
    247 
    248 (provide 'denote-journal-extras)
    249 ;;; denote-journal-extras.el ends here