emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Tyler Grinn <tylergrinn@gmail.com>
To: Ihor Radchenko <yantar92@gmail.com>
Cc: org-mode-email <emacs-orgmode@gnu.org>
Subject: [PATCH] Re: Concatenate properties
Date: Wed, 11 May 2022 13:47:59 -0400	[thread overview]
Message-ID: <874k1w2c6o.fsf_-_@gmail.com> (raw)
In-Reply-To: <87czgkjnit.fsf@localhost> (Ihor Radchenko's message of "Wed, 11 May 2022 19:51:06 +0800")

[-- Attachment #1: Type: text/plain, Size: 1670 bytes --]

Ihor Radchenko <yantar92@gmail.com> writes:

> Tyler Grinn <tylergrinn@gmail.com> writes:
>
>> Ihor Radchenko <yantar92@gmail.com> writes:
>>
>>> Tyler Grinn <tylergrinn@gmail.com> writes:
>>>
>>>>
>>>> Could you provide an example of what the value of that variable would be
>>>> if, for instance, I wanted PROP_A and PROP_B to be joined with a single
>>>> space and PROP_C and PROP_D to be concatenated? Or better yet, have the
>>>> default be to join with a single space for any property and have only
>>>> PROP_C and PROP_D concatenated?
>>>
>>> Say,
>>>
>>> '(("PROP_[CD]" . ""))
>>>
>>> or
>>>
>>> '(("PROP_[AB]" . "/") ("PROP_[CD]" . "") (".+" . "∿"))
>>>
>>> Best,
>>> Ihor
>>
>> I'm not sure forcing regular expressions for all use cases is ideal. Is
>> there a way to allow the option (I'm calling it org-property-separators)
>> to allow either regular expressions or exact matches, like
>> org-use-property-inheritance does?
>
> It's up to you. I merely suggested that the new option should work
> similarly to org-use-property-inheritance. It will not be the exact
> match since we also need to provide the separator string.
>
>> Other than that I'm mostly ready to send in my first patch. Should I
>> attach it in this thread or start a new one?
>
> Keeping the patch within this thread will be more reasonable for future
> readers. Just remember to prefix the subject line with [PATCH].
>
> Best,
> Ihor

OK, what I have now is that if the car of an alist item is a list, exact
matching will be done for each list item, but if it is a string, it will
be matched as a regular expression.

Best,

Tyler


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Add org-property-separators option --]
[-- Type: text/x-patch, Size: 8332 bytes --]

From c23b45dd5d0bc5a049b916a4b4003c6f55ce4b51 Mon Sep 17 00:00:00 2001
From: Tyler Grinn <tylergrinn@gmail.com>
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<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."
-- 
2.35.3


  reply	other threads:[~2022-05-11 17:50 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-05 22:37 Concatenate properties Tyler Grinn
2022-05-05 22:56 ` John Kitchin
2022-05-06 15:40   ` Tyler Grinn
2022-05-07  5:33     ` Ihor Radchenko
2022-05-07 11:44       ` John Kitchin
2022-05-07 12:29         ` Tyler Grinn
2022-05-08 12:21           ` Ihor Radchenko
2022-05-08 14:14             ` Tyler Grinn
2022-05-08 14:47               ` Ihor Radchenko
2022-05-09 20:18                 ` Tyler Grinn
2022-05-11 11:51                   ` Ihor Radchenko
2022-05-11 17:47                     ` Tyler Grinn [this message]
2022-05-12  9:59                       ` [PATCH] " Ihor Radchenko
2022-05-12 23:27                         ` Tyler Grinn
2022-05-13 12:21                           ` Ihor Radchenko
2022-05-14  2:11                             ` Tyler Grinn
2022-05-20  8:04                             ` Ihor Radchenko
2022-05-07 13:20     ` John Kitchin
2022-05-07 14:09       ` Tyler Grinn

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=874k1w2c6o.fsf_-_@gmail.com \
    --to=tylergrinn@gmail.com \
    --cc=emacs-orgmode@gnu.org \
    --cc=yantar92@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).