branch: externals/denote-journal
commit 69fdb3c027c882a3cb8bc8a2be50ad93f260b38c
Author: Protesilaos Stavrou <i...@protesilaos.com>
Commit: Protesilaos Stavrou <i...@protesilaos.com>

    Move file out of the main Denote directory WORK-IN-PROGRESS
---
 README.md         |   3 +
 denote-journal.el | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 290 insertions(+)

diff --git a/README.md b/README.md
new file mode 100644
index 0000000000..adf57cfad4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# Denote Journal extras
+
+WORK-IN-PROGRESS: 
<https://protesilaos.com/codelog/2025-02-11-emacs-splitting-denote-many-packages/>.
diff --git a/denote-journal.el b/denote-journal.el
new file mode 100644
index 0000000000..4de9e06b80
--- /dev/null
+++ b/denote-journal.el
@@ -0,0 +1,287 @@
+;;; denote-journal-extras.el --- Convenience functions for daily journaling  
-*- lexical-binding: t; -*-
+
+;; Copyright (C) 2023-2025  Free Software Foundation, Inc.
+
+;; Author: Protesilaos Stavrou <i...@protesilaos.com>
+;; Maintainer: Protesilaos Stavrou <i...@protesilaos.com>
+;; URL: https://github.com/protesilaos/denote
+
+;; This file is NOT part of GNU Emacs.
+
+;; 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 <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This is a set of optional convenience functions that used to be
+;; provided in the Denote manual.  They facilitate the use of Denote
+;; for daily journaling.
+
+;;; Code:
+
+(require 'denote)
+
+(defgroup denote-journal-extras nil
+  "Denote for daily journaling."
+  :group 'denote
+  :link '(info-link "(denote) Top")
+  :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote";))
+
+(defcustom denote-journal-extras-directory
+  (expand-file-name "journal" denote-directory)
+  "Directory for storing daily journal entries.
+This can either be the same as the variable `denote-directory' or
+a subdirectory of it.
+
+A value of nil means to use the variable `denote-directory'.
+Journal entries will thus be in a flat listing together with all
+other notes.  They can still be retrieved easily by searching for
+the variable `denote-journal-extras-keyword'."
+  :group 'denote-journal-extras
+  :type '(choice (directory :tag "Provide directory path (is created if 
missing)")
+                 (const :tag "Use the `denote-directory'" nil)))
+
+(defcustom denote-journal-extras-keyword "journal"
+  "Single word keyword or list of keywords to tag journal entries.
+It is used by `denote-journal-extras-new-entry' (or related)."
+  :group 'denote-journal-extras
+  :type '(choice (string :tag "Keyword")
+                 (repeat :tag "List of keywords" string)))
+
+(defcustom denote-journal-extras-title-format 'day-date-month-year-24h
+  "Date format to construct the title with `denote-journal-extras-new-entry'.
+The value is either a symbol or an arbitrary string that is
+passed to `format-time-string' (consult its documentation for the
+technicalities).
+
+Acceptable symbols and their corresponding styles are:
+
+| Symbol                  | Style                             |
+|-------------------------+-----------------------------------|
+| day                     | Monday                            |
+| day-date-month-year     | Monday 19 September 2023          |
+| day-date-month-year-24h | Monday 19 September 2023 20:49    |
+| day-date-month-year-12h | Monday 19 September 2023 08:49 PM |
+
+With a nil value, make `denote-journal-extras-new-entry' prompt
+for a title."
+  :group 'denote-journal-extras
+  :type '(choice
+          (const :tag "Prompt for title with 
`denote-journal-extras-new-entry'" nil)
+          (const :tag "Monday"
+                 :doc "The `format-time-string' is: %A"
+                 day)
+          (const :tag "Monday 19 September 2023"
+                 :doc "The `format-time-string' is: %A %e %B %Y"
+                 day-date-month-year)
+          (const :tag "Monday 19 September 2023 20:49"
+                 :doc "The `format-time-string' is: %A %e %B %Y %H:%M"
+                 day-date-month-year-24h)
+          (const :tag "Monday 19 September 2023 08:49 PM"
+                 :doc "The `format-time-string' is: %A %e %B %Y %I:%M %^p"
+                 day-date-month-year-12h)
+          (string :tag "Custom string with `format-time-string' specifiers")))
+
+(defcustom denote-journal-extras-hook nil
+  "Normal hook called after `denote-journal-extras-new-entry'.
+Use this to, for example, set a timer after starting a new
+journal entry (refer to the `tmr' package on GNU ELPA)."
+  :group 'denote-journal-extras
+  :type 'hook)
+
+(defun denote-journal-extras-directory ()
+  "Make the variable `denote-journal-extras-directory' and its parents."
+  (if-let* (((stringp denote-journal-extras-directory))
+            (directory (file-name-as-directory (expand-file-name 
denote-journal-extras-directory))))
+      (progn
+        (when (not (file-directory-p denote-journal-extras-directory))
+          (make-directory directory :parents))
+        directory)
+    (denote-directory)))
+
+(defun denote-journal-extras-keyword ()
+  "Return the value of the variable `denote-journal-extras-keyword' as a list."
+  (if (stringp denote-journal-extras-keyword)
+      (list denote-journal-extras-keyword)
+    denote-journal-extras-keyword))
+
+(defun denote-journal-extras--keyword-regex ()
+  "Return a regular expression string that matches the journal keyword(s)."
+  (let ((keywords-sorted (mapcar #'regexp-quote (denote-keywords-sort 
(denote-journal-extras-keyword)))))
+    (concat "_" (string-join keywords-sorted ".*_"))))
+
+(defun denote-journal-extras-file-is-journal-p (file)
+  "Return non-nil if FILE is a journal entry."
+  (and (denote-file-is-note-p file)
+       (string-match-p (denote-journal-extras--keyword-regex) 
(file-name-nondirectory file))))
+
+(defun denote-journal-extras-filename-is-journal-p (filename)
+  "Return non-nil if FILENAME is a valid name for a journal entry."
+  (and (denote-filename-is-note-p filename)
+       (string-match-p (denote-journal-extras--keyword-regex) 
(file-name-nondirectory filename))))
+
+(defun denote-journal-extras-daily--title-format (&optional date)
+  "Return present date in `denote-journal-extras-title-format' or prompt for 
title.
+With optional DATE, use it instead of the present date.  DATE has
+the same format as that returned by `current-time'."
+  (format-time-string
+   (if (and denote-journal-extras-title-format
+            (stringp denote-journal-extras-title-format))
+       denote-journal-extras-title-format
+     (pcase denote-journal-extras-title-format
+       ('day "%A")
+       ('day-date-month-year "%A %e %B %Y")
+       ('day-date-month-year-24h "%A %e %B %Y %H:%M")
+       ('day-date-month-year-12h "%A %e %B %Y %I:%M %^p")
+       (_ (denote-title-prompt (format-time-string "%F" date)))))
+   date))
+
+(defun denote-journal-extras--get-template ()
+  "Return template that has `journal' key in `denote-templates'.
+If no template with `journal' key exists but `denote-templates'
+is non-nil, prompt the user for a template among
+`denote-templates'.  Else return nil.
+
+Also see `denote-journal-extras-new-entry'."
+  (if-let* ((template (alist-get 'journal denote-templates)))
+      template
+    (when denote-templates
+      (denote-template-prompt))))
+
+;;;###autoload
+(defun denote-journal-extras-new-entry (&optional date)
+  "Create a new journal entry in variable `denote-journal-extras-directory'.
+Use the variable `denote-journal-extras-keyword' as a keyword for the
+newly created file.  Set the title of the new entry according to the
+value of the user option `denote-journal-extras-title-format'.
+
+With optional DATE as a prefix argument, prompt for a date.  If
+`denote-date-prompt-use-org-read-date' is non-nil, use the Org
+date selection module.
+
+When called from Lisp DATE is a string and has the same format as
+that covered in the documentation of the `denote' function.  It
+is internally processed by `denote-valid-date-p'."
+  (interactive (list (when current-prefix-arg (denote-date-prompt))))
+  (let ((internal-date (or (denote-valid-date-p date) (current-time)))
+        (denote-directory (denote-journal-extras-directory)))
+    (denote
+     (denote-journal-extras-daily--title-format internal-date)
+     (denote-journal-extras-keyword)
+     nil nil date
+     (denote-journal-extras--get-template))
+    (run-hooks 'denote-journal-extras-hook)))
+
+(defun denote-journal-extras--filename-date-regexp (&optional date)
+  "Regular expression to match journal entries for today or optional DATE.
+DATE has the same format as that returned by `denote-valid-date-p'."
+  (let* ((identifier (format "%sT[0-9]\\{6\\}" (format-time-string "%Y%m%d" 
date)))
+         (order denote-file-name-components-order)
+         (id-index (seq-position order 'identifier))
+         (kw-index (seq-position order 'keywords)))
+    (if (> kw-index id-index)
+        (format "%s.*?_%s" identifier (denote-journal-extras--keyword-regex))
+      (format "_%s.*?@@%s" (denote-journal-extras--keyword-regex) 
identifier))))
+
+(defun denote-journal-extras--entry-today (&optional date)
+  "Return list of files matching a journal for today or optional DATE.
+DATE has the same format as that returned by `denote-valid-date-p'."
+  (denote-directory-files (denote-journal-extras--filename-date-regexp date)))
+
+(define-obsolete-function-alias
+  'denote-journal-extra-path-to-new-or-existing-entry
+  'denote-journal-extras-path-to-new-or-existing-entry
+  "3.2.0")
+
+;;;###autoload
+(defun denote-journal-extras-path-to-new-or-existing-entry (&optional date)
+  "Return path to existing or new journal file.
+With optional DATE, do it for that date, else do it for today.  DATE is
+a string and has the same format as that covered in the documentation of
+the `denote' function.  It is internally processed by
+`denote-valid-date-p'.
+
+If there are multiple journal entries for the date, prompt for one among
+them using minibuffer completion.  If there is only one, return it.  If
+there is no journal entry, create it."
+  (let* ((internal-date (or (denote-valid-date-p date) (current-time)))
+         (files (denote-journal-extras--entry-today internal-date)))
+    (cond
+     ((length> files 1)
+      (completing-read "Select journal entry: " files nil t))
+     (files
+      (car files))
+     (t
+      (save-window-excursion
+        (denote-journal-extras-new-entry date)
+        (save-buffer)
+        (buffer-file-name))))))
+
+;;;###autoload
+(defun denote-journal-extras-new-or-existing-entry (&optional date)
+  "Locate an existing journal entry or create a new one.
+A journal entry is one that has the value of the variable
+`denote-journal-extras-keyword' as part of its file name.
+
+If there are multiple journal entries for the current date,
+prompt for one using minibuffer completion.  If there is only
+one, visit it outright.  If there is no journal entry, create one
+by calling `denote-journal-extra-new-entry'.
+
+With optional DATE as a prefix argument, prompt for a date.  If
+`denote-date-prompt-use-org-read-date' is non-nil, use the Org
+date selection module.
+
+When called from Lisp, DATE is a string and has the same format
+as that covered in the documentation of the `denote' function.
+It is internally processed by `denote-valid-date-p'."
+  (interactive
+   (list
+    (when current-prefix-arg
+      (denote-date-prompt))))
+  (find-file (denote-journal-extras-path-to-new-or-existing-entry date)))
+
+;;;###autoload
+(defun denote-journal-extras-link-or-create-entry (&optional date id-only)
+  "Use `denote-link' on journal entry, creating it if necessary.
+A journal entry is one that has the value of the variable
+`denote-journal-extras-keyword' as part of its file name.
+
+If there are multiple journal entries for the current date,
+prompt for one using minibuffer completion.  If there is only
+one, link to it outright.  If there is no journal entry, create one
+by calling `denote-journal-extra-new-entry' and link to it.
+
+With optional DATE as a prefix argument, prompt for a date.  If
+`denote-date-prompt-use-org-read-date' is non-nil, use the Org
+date selection module.
+
+When called from Lisp, DATE is a string and has the same format
+as that covered in the documentation of the `denote' function.
+It is internally processed by `denote-valid-date-p'.
+
+With optional ID-ONLY as a prefix argument create a link that
+consists of just the identifier.  Else try to also include the
+file's title.  This has the same meaning as in `denote-link'."
+  (interactive
+   (pcase current-prefix-arg
+     ('(16) (list (denote-date-prompt) :id-only))
+     ('(4) (list (denote-date-prompt)))))
+  (let ((path (denote-journal-extras-path-to-new-or-existing-entry date)))
+    (denote-link path
+                 (denote-filetype-heuristics (buffer-file-name))
+                 (denote-get-link-description path)
+                 id-only)))
+
+(provide 'denote-journal-extras)
+;;; denote-journal-extras.el ends here

Reply via email to