From c23b45dd5d0bc5a049b916a4b4003c6f55ce4b51 Mon Sep 17 00:00:00 2001 From: Tyler Grinn Date: Mon, 9 May 2022 15:52:58 -0400 Subject: [PATCH] lisp/org.el: Add org-property-separators option * lisp/org.el (org-property-separators, org-property-get-separator): Created. (org-entry-get, org-entry-get-with-inheritance): Use new org-property-get-separator function. * testing/lisp/test-org.el (test-org/entry-get): Added tests for combining properties with custom separators org-property-separators is a customization option that allows for properties to be combined using a separator other than the default (a single space). It is an alist with the car of each element being a list of property names or regular expression and the cdr being the separator string, like '((("EXPORT_FILE_NAME") . "/")). --- etc/ORG-NEWS | 21 ++++++++++++++++++++ lisp/org.el | 43 ++++++++++++++++++++++++++++++++-------- testing/lisp/test-org.el | 30 +++++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 27de6da62..0297a9d49 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -141,6 +141,27 @@ discouraged when working with Org files. ** New features +*** New customization option =org-property-separators= +A new alist variable to control how properties are combined. + +If a property is specified multiple times with a =+=, like +=:EXPORT_FILE_NAME+:=, the old behavior was to always combine them +with a single space. For the new variable, the car of each item in the +alist should be either a list of property names or a regular +expression, while the cdr should be the separator to use when +combining that property. + +The default value for the separator is a single space, if none of the +provided items in the alist match a given property. + +For example, in order to combine =EXPORT_FILE_NAME= properties with a +forward slash =/=, one can use + +#+begin_src emacs-lisp +(setq org-use-property-inheritance '("EXPORT_FILE_NAME") + org-property-separators '((("EXPORT_FILE_NAME") . "/"))) +#+end_src + *** New library =org-persist.el= implements variable persistence across Emacs sessions The library stores variable data in ~org-persist-directory~ (set to XDG diff --git a/lisp/org.el b/lisp/org.el index cab59b87c..a53505752 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -2850,6 +2850,30 @@ in this variable)." (member-ignore-case property org-use-property-inheritance)) (t (error "Invalid setting of `org-use-property-inheritance'")))) +(defcustom org-property-separators nil + "An alist to control how properties are combined. + +The car of each item should be either a list of property names or +a regular expression, while the cdr should be the separator to +use when combining that property. + +If an alist item cannot be found that matches a given property, a +single space will be used as the separator." + :group 'org-properties + :type '(alist :key-type string :value-type sexp)) + +(defun org-property-get-separator (property) + "Get the separator to use for combining PROPERTY." + (or + (catch 'separator + (dolist (spec org-property-separators) + (if (listp (car spec)) + (if (member property (car spec)) + (throw 'separator (cdr spec))) + (if (string-match-p (car spec) property) + (throw 'separator (cdr spec)))))) + " ")) + (defcustom org-columns-default-format "%25ITEM %TODO %3PRIORITY %TAGS" "The default column format, if no other format has been defined. This variable can be set on the per-file basis by inserting a line @@ -12355,7 +12379,9 @@ value higher up the hierarchy." (org-entry-get-with-inheritance property literal-nil)) (t (let* ((local (org--property-local-values property literal-nil)) - (value (and local (mapconcat #'identity (delq nil local) " ")))) + (value (and local (mapconcat #'identity + (delq nil local) + (org-property-get-separator property))))) (if literal-nil value (org-not-nil value))))))) (defun org-property-or-variable-value (var &optional inherit) @@ -12464,7 +12490,8 @@ However, if LITERAL-NIL is set, return the string value \"nil\" instead." (catch 'exit (let ((element (or element (and (org-element--cache-active-p) - (org-element-at-point nil 'cached))))) + (org-element-at-point nil 'cached)))) + (separator (org-property-get-separator property))) (if element (let ((element (org-element-lineage element '(headline org-data inlinetask) 'with-self))) (while t @@ -12472,8 +12499,8 @@ However, if LITERAL-NIL is set, return the string value \"nil\" instead." (v (if (listp v) v (list v)))) (when v (setq value - (concat (mapconcat #'identity (delq nil v) " ") - (and value " ") + (concat (mapconcat #'identity (delq nil v) separator) + (and value separator) value))) (cond ((car v) @@ -12484,15 +12511,15 @@ However, if LITERAL-NIL is set, return the string value \"nil\" instead." (t (let ((global (org--property-global-or-keyword-value property literal-nil))) (cond ((not global)) - (value (setq value (concat global " " value))) + (value (setq value (concat global separator value))) (t (setq value global)))) (throw 'exit nil)))))) (while t (let ((v (org--property-local-values property literal-nil))) (when v (setq value - (concat (mapconcat #'identity (delq nil v) " ") - (and value " ") + (concat (mapconcat #'identity (delq nil v) separator) + (and value separator) value))) (cond ((car v) @@ -12513,7 +12540,7 @@ However, if LITERAL-NIL is set, return the string value \"nil\" instead." (t (let ((global (org--property-global-or-keyword-value property literal-nil))) (cond ((not global)) - (value (setq value (concat global " " value))) + (value (setq value (concat global separator value))) (t (setq value global)))) (throw 'exit nil)))))))) (if literal-nil value (org-not-nil value))))) diff --git a/testing/lisp/test-org.el b/testing/lisp/test-org.el index 0cee31d4e..c0c13f586 100644 --- a/testing/lisp/test-org.el +++ b/testing/lisp/test-org.el @@ -5997,7 +5997,35 @@ Paragraph" (org-test-with-temp-text ":PROPERTIES:\n:A: 0\n:END:\n#+PROPERTY: A 1\n* H\n:PROPERTIES:\n:A+: 2\n:END:" (org-mode-restart) - (org-entry-get (point-max) "A" t))))) + (org-entry-get (point-max) "A" t)))) + ;; Use alternate separators + (should + (equal "0~2" + (org-test-with-temp-text + ":PROPERTIES:\n:A: 0\n:A+: 2\n:END:" + (let ((org-property-separators '((("A") . "~")))) + (org-entry-get (point) "A"))))) + ;; Default separator is single space + (should + (equal "0 2" + (org-test-with-temp-text + ":PROPERTIES:\n:A: 0\n:B: 1\n:A+: 2\n:B+: 3\n:END:" + (let ((org-property-separators '((("B") . "~")))) + (org-entry-get (point) "A"))))) + ;; Regular expression matching for separator + (should + (equal "0/2" + (org-test-with-temp-text + ":PROPERTIES:\n:A: 0\n:A+: 2\n:END:" + (let ((org-property-separators '((("B") . "~") ("[AC]" . "/")))) + (org-entry-get (point) "A"))))) + ;; Separator works with inheritance + (should + (equal "1~2" + (org-test-with-temp-text + "* H\n:PROPERTIES:\n:A: 1\n:END:\n** H2\n:PROPERTIES:\n:A+: 2\n:END:" + (let ((org-property-separators '((("A") . "~")))) + (org-entry-get (point-max) "A" t)))))) (ert-deftest test-org/entry-properties () "Test `org-entry-properties' specifications." -- 2.35.3