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") . "/")).
This commit is contained in:
Tyler Grinn 2022-05-09 15:52:58 -04:00 committed by Ihor Radchenko
parent 4f0f244477
commit e268e47971
No known key found for this signature in database
GPG Key ID: 6470762A7DA11D8B
3 changed files with 99 additions and 9 deletions

View File

@ -141,6 +141,37 @@ 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
#+begin_src org
:PROPERTIES:
:EXPORT_FILE_NAME: some/path
:EXPORT_FILE_NAME+: to/file
:END:
#+end_src
the old behavior was to always combine them with a single space
(=some/path to/file=). 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-property-separators '((("EXPORT_FILE_NAME") . "/")))
#+end_src
The example above would then produce the property value
=some/path/to/file=.
*** New library =org-persist.el= implements variable persistence across Emacs sessions
The library stores variable data in ~org-persist-directory~ (set to XDG

View File

@ -2850,6 +2850,34 @@ 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 (choice (repeat :tag "Properties" string)
(string :tag "Regular Expression"))
:value-type (restricted-sexp :tag "Separator"
:match-alternatives (stringp)
:value " ")))
(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
@ -12358,7 +12386,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)
@ -12467,7 +12497,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
@ -12475,8 +12506,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)
@ -12487,15 +12518,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)
@ -12516,7 +12547,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)))))

View File

@ -6014,7 +6014,35 @@ Paragraph<point>"
(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."