branch: externals/org-gnosis
commit 195b27f11de0dd11f42095e688ce819a8a1994a2
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>

    [Feature] refine template system.
    
    - Add support for using plain strings.
    - Adjust template current org heading level.
---
 org-gnosis.el | 141 +++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 121 insertions(+), 20 deletions(-)

diff --git a/org-gnosis.el b/org-gnosis.el
index f545a27c769..bc854bc87cf 100644
--- a/org-gnosis.el
+++ b/org-gnosis.el
@@ -41,6 +41,7 @@
 ;; - Backlinks and forward link tracking
 ;; - Tag-based organization
 ;; - Daily journaling (single file or separate files per day)
+;; - Customizable templates for notes and journal entries
 ;; - SQLite database for indexing
 ;; - Hierarchical note structures
 ;; - Optional .gpg encryption
@@ -50,10 +51,17 @@
 ;; - `org-gnosis-find': Find or create a note
 ;; - `org-gnosis-insert': Insert a link to a note
 ;; - `org-gnosis-journal': Open today's journal entry
+;; - `org-gnosis-insert-template': Insert template at current heading
 ;; - `org-gnosis-find-by-tag': Find notes by tag
 ;; - `org-gnosis-visit-backlinks': Visit notes linking to current note
 ;; - `org-gnosis-db-sync': Sync database with files
 ;;
+;; Templates:
+;; Customize `org-gnosis-node-templates' and
+;; `org-gnosis-journal-templates' to define reusable content structures.
+;; Templates support both static strings and dynamic functions, with
+;; automatic heading-level adjustment via %s placeholders.
+;;
 ;;; Code:
 
 (require 'cl-lib)
@@ -73,16 +81,62 @@
 
 (defcustom org-gnosis-journal-templates
   '(("Default" (lambda () (format "** Daily Notes\n\n** Goals\n%s" 
(org-gnosis-todos))))
-    ("Empty" (lambda () "")))
-  "Templates for journaling."
-  :type '(repeat (cons (string :tag "Name")
-                       (function :tag "Template Function"))))
+    ("Empty" ""))
+  "Templates for journal entries.
+
+Each entry is (NAME VALUE) where VALUE is either:
+  - A string template with \"%s\" placeholders for heading stars
+  - A function returning a string template
+
+Templates are inserted when creating new journal entries or via
+`org-gnosis-insert-template' in journal files.
+
+In string templates, \"%s\" is replaced with the appropriate
+number of stars for the current heading level + 1.  Use \"%%\"
+for a literal percent sign.
+
+Examples:
+  (\"Simple\" \"%s Todo\\n%s Notes\\n\")
+    → Creates two level-2 headings under a level-1 journal entry
+
+  (\"Dynamic\" (lambda ()
+                (concat \"%s Tasks [0%%]\\n\"
+                        (org-gnosis-todos))))
+    → Creates heading with dynamic TODO list from agenda files"
+  :type '(repeat (list (string :tag "Name")
+                       (choice (string :tag "Template String")
+                               (function :tag "Template Function")))))
 
 (defcustom org-gnosis-node-templates
-  '(("Default" (lambda () "")))
-  "Templates for nodes."
-  :type '(repeat (cons (string :tag "Name")
-                       (function :tag "Template Function"))))
+  '(("Notes" "%s Notes\n\n%s References\n")
+    ("Empty" ""))
+  "Templates for node files.
+
+Each entry is (NAME VALUE) where VALUE is either:
+  - A string template with \"%s\" placeholders for heading stars
+  - A function returning a string template
+
+Templates are inserted when creating new nodes or via
+`org-gnosis-insert-template' in node files.
+
+In string templates, \"%s\" is replaced with the appropriate
+number of stars for the current heading level + 1.  Use \"%%\"
+for a literal percent sign.
+
+Examples:
+  (\"Drug\" \"%s Mechanism\\n%s Clinical Uses\\n\")
+    → Creates two subheadings
+
+  (\"Custom\" (lambda ()
+               (let ((date (format-time-string \"%Y-%m-%d\")))
+                 (concat \"%s Created: \" date \"\\n\"))))
+    → Dynamic template with current date
+
+Note: Template functions should use `concat' rather than `format'
+to avoid escaping conflicts with \"%s\" placeholders."
+  :type '(repeat (list (string :tag "Name")
+                       (choice (string :tag "Template String")
+                               (function :tag "Template Function")))))
 
 (defcustom org-gnosis-journal-dir (expand-file-name "journal" org-gnosis-dir)
   "Gnosis journal directory."
@@ -164,7 +218,8 @@ If nil, journal entries are created as separate files in
     (goto-char (point-max))
     (insert (format "* %s\n" title))
     (org-id-get-create)
-    (insert (org-gnosis-select-template org-gnosis-journal-templates))))
+    (insert (org-gnosis--format-template
+             (org-gnosis-select-template org-gnosis-journal-templates)))))
 
 (defvar org-gnosis-db--connection nil)
 
@@ -535,7 +590,7 @@ EXTRAS: The template to be inserted at the start."
        (insert (format "#+title: %s\n#+filetags: \n" title))
        (org-mode)
        (org-id-get-create)
-       (when extras (insert extras))))
+       (when extras (insert (org-gnosis--format-template extras)))))
     (switch-to-buffer buffer)
     (org-gnosis-mode 1)))
 
@@ -602,17 +657,23 @@ Returns a list of (ID) pairs for nodes that have TAG."
         (node (completing-read "Select node: " node-titles nil t)))
     (org-gnosis-find node)))
 
-(defun org-gnosis-select-template (templates)
-  "Select journal template from TEMPLATES.
+(defun org-gnosis--eval-template (value)
+  "Evaluate template VALUE.
+If VALUE is a function, call it.  Otherwise return it as a string."
+  (if (functionp value)
+      (funcall value)
+    value))
 
-If templates is only item, return it without a prompt."
-  (let* ((template (if (= (length templates) 1)
-                       (cdar templates)
-                     (cdr (assoc
-                          (funcall org-gnosis-completing-read-func "Select 
template:"
-                                    (mapcar #'car templates))
-                           templates)))))
-    (funcall (apply #'append template))))
+(defun org-gnosis-select-template (templates)
+  "Prompt for and evaluate a template from TEMPLATES.
+If TEMPLATES has a single entry, use it without prompting."
+  (let* ((entry (if (= (length templates) 1)
+                    (car templates)
+                  (assoc (funcall org-gnosis-completing-read-func
+                                  "Select template: "
+                                  (mapcar #'car templates))
+                         templates))))
+    (org-gnosis--eval-template (cadr entry))))
 
 ;;;###autoload
 (defun org-gnosis-insert (arg &optional journal-p)
@@ -670,6 +731,46 @@ If JOURNAL-P is non-nil, retrieve/create node as a journal 
entry."
       (dolist (tag tags)
        (org-gnosis-insert-filetag tag)))))
 
+(defun org-gnosis--format-template (template)
+  "Format TEMPLATE string, replacing %s with heading stars.
+Uses current heading level + 1, defaulting to level 1 if not at a heading."
+  (let ((stars (make-string (1+ (or (org-current-level) 0)) ?*)))
+    (format-spec template `((?s . ,stars)))))
+
+(defun org-gnosis--template-list ()
+  "Return the template list appropriate for the current buffer."
+  (if (and buffer-file-name
+           (file-in-directory-p buffer-file-name org-gnosis-journal-dir))
+      org-gnosis-journal-templates
+    org-gnosis-node-templates))
+
+;;;###autoload
+(defun org-gnosis-insert-template ()
+  "Insert template content at the current org heading.
+
+Automatically selects from `org-gnosis-journal-templates' or
+`org-gnosis-node-templates' based on the current buffer's location.
+
+Template strings with \"%s\" placeholders are replaced with the
+appropriate heading stars (current level + 1).  For example, at a
+level-2 heading, \"%s Notes\" becomes \"*** Notes\".
+
+Point must be on an org heading when called.  The template is
+inserted after point, and point position is preserved.
+
+Example usage in config:
+  (setq org-gnosis-journal-templates
+        \\='((\"Daily\" \"%s Tasks\\n%s Notes\\n\")
+          (\"Planning\" my-planning-template-function)))
+
+where my-planning-template-function returns a string with %s placeholders."
+  (interactive)
+  (unless (org-at-heading-p)
+    (user-error "Point must be on an org heading"))
+  (let ((content (org-gnosis--format-template
+                  (org-gnosis-select-template (org-gnosis--template-list)))))
+    (save-excursion (insert "\n" content))))
+
 ;;;###autoload
 (defun org-gnosis-visit-backlinks ()
   "Visit backlinks for current node."

Reply via email to