From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp12.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id UK6rLUj3e2I3GgAAbAwnHQ (envelope-from ) for ; Wed, 11 May 2022 19:50:00 +0200 Received: from aspmx1.migadu.com ([2001:41d0:2:bcc0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp12.migadu.com with LMTPS id UGN5LUj3e2LgPAAAauVa8A (envelope-from ) for ; Wed, 11 May 2022 19:50:00 +0200 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 444BE3165F for ; Wed, 11 May 2022 19:50:00 +0200 (CEST) Received: from localhost ([::1]:47518 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1noqTH-0007Ol-AV for larch@yhetil.org; Wed, 11 May 2022 13:49:59 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:59018) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1noqRS-0007N0-9Y for emacs-orgmode@gnu.org; Wed, 11 May 2022 13:48:07 -0400 Received: from mail-io1-xd32.google.com ([2607:f8b0:4864:20::d32]:45650) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1noqRQ-00042y-3r for emacs-orgmode@gnu.org; Wed, 11 May 2022 13:48:05 -0400 Received: by mail-io1-xd32.google.com with SMTP id h85so2811382iof.12 for ; Wed, 11 May 2022 10:48:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:in-reply-to:references:user-agent:date :message-id:mime-version; bh=xOUp4SbehNY/q2y8xdu1C3J5umLPaIYCL8a1WgnDi7A=; b=TjJN6nr/KTbcWA4ZvfN7AedmWayWAWo5+NJ36CwlVhe7sQbNaM+BRD9YHZYT6cJJF1 IY0hV882YBKDnva6iJ/raUpbdvfZYr3sKrCPtcUsvtlsxqOubqF8QbYdXlqluPp8aBw4 oyzqnIJOZz+8bZBJj4jiUXd0DR8k9E7vBpVOJFGzKCD+XNi8AQYLMLbdpGNlr286NOSo sOp21KWtsM1xaVTazj5y80RkbKG87/mVZhxXcqn3JoeAAey9bhO9GsGSTdUj0iWTMe32 UbZHHAq6uQa8FCKAhc3a1AgJiLXFyMlnrfvRfg7Qx4fUPnUJhuBFFToAOlsAWZmYHVI2 9t/Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:in-reply-to:references :user-agent:date:message-id:mime-version; bh=xOUp4SbehNY/q2y8xdu1C3J5umLPaIYCL8a1WgnDi7A=; b=0bOBi3i/lwTjGCYvVhKFl2Xobx3mutNbQ/jGhpLMn+JFFwEv/k9Ckq38CcTm6d4vnr 92m/bYNJszHWBM0nmyiY/QfcSXmTa75m2dtNlu5PfRGvE1CZTzoKRKgTjk9kZY6rMJ/J 0RVb13leVBrafDx0w9i2w8WsUBep206hTLFanMdFqYwvmAbLxjUCQsQvOwSDc/IbTb7W vETMIkGONAYYbvLQTC9dQRfVrqJgRbmwD/BqqPscim3y0jhRB7h/pI/ClvJ3g/H8xU1g OVZT+c3PhmzQMq7ya5SdX5nwM+Bein3qs1BC6H8moF6fg0fRmApg8IB+erOkGmG0zZ09 5GnQ== X-Gm-Message-State: AOAM530hl39tnk262Vfq0MH6lPVZDHMqaOh8ZLtIFRtJUSALWYK6f8Zv /J3tjug4nnCcwhCCQie4aTdg2r/k2v1pag== X-Google-Smtp-Source: ABdhPJzT+SOKAxVrUat75di/iKuSWbmsjAJunjGo5rf2R41gEkj56Re6TuMkWgve4zTgarMT6Nn5hQ== X-Received: by 2002:a05:6638:4189:b0:32b:a06a:b007 with SMTP id az9-20020a056638418900b0032ba06ab007mr13071520jab.101.1652291281831; Wed, 11 May 2022 10:48:01 -0700 (PDT) Received: from tyler-fedora ([107.161.129.166]) by smtp.gmail.com with ESMTPSA id n13-20020a056638120d00b0032b3a7817afsm745270jas.115.2022.05.11.10.48.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 11 May 2022 10:48:01 -0700 (PDT) From: Tyler Grinn To: Ihor Radchenko Cc: org-mode-email Subject: [PATCH] Re: Concatenate properties In-Reply-To: <87czgkjnit.fsf@localhost> (Ihor Radchenko's message of "Wed, 11 May 2022 19:51:06 +0800") References: <87y1zfk3ls.fsf@gmail.com> <87zgjuis8z.fsf@gmail.com> <87pmkpykiz.fsf@localhost> <87v8uhikzx.fsf@gmail.com> <87sfpkqkos.fsf@localhost> <87zgjst8lq.fsf@gmail.com> <877d6wqdxs.fsf@localhost> <87mtfq8np8.fsf@gmail.com> <87czgkjnit.fsf@localhost> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/29.0.50 (gnu/linux) Date: Wed, 11 May 2022 13:47:59 -0400 Message-ID: <874k1w2c6o.fsf_-_@gmail.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2607:f8b0:4864:20::d32; envelope-from=tylergrinn@gmail.com; helo=mail-io1-xd32.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: "Emacs-orgmode" X-Migadu-Flow: FLOW_IN X-Migadu-To: larch@yhetil.org X-Migadu-Country: US ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1652291400; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type:in-reply-to:in-reply-to: references:references:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=xOUp4SbehNY/q2y8xdu1C3J5umLPaIYCL8a1WgnDi7A=; b=e5GvnCooZPkKMSBv9VyaRMNijn3CNa6Batq50Bz7HCIl0iuCDe0cy6YDNavpW6UmA+2cd0 Qe9CclRItbzveDYEODe8paPf3IEcx47KU1K3Od/UtmBc1gn+UyUPuq7EIzHhCDJjR5QMpP Xj7vDs585zAUtXsL0/rELyL/ZJbuo8CHJYlkFeHJy305FmnOsUXgTvxZ5ueCMIYk5Ivi0W r1PAw6rA+lNuHV+XLcD7excEamWclwj+iTR5tE/ATkiMq86QFlO4/6YM9rzI/W4Ec+yKVP YoF4YXmOTUeNu/1pJoH42tfLNjUvDcvkjV4rP6Y1rzDuSMxzyRduQCfdi03Dmg== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1652291400; a=rsa-sha256; cv=none; b=pfxG5JrChs5Po0MLoM/l1g2mNwR5CXKQzMhynbDtqIiK2JP3+Uk7+mybMpaOHIzfhfKSaS LYNgwNvivdoxpXauA7doyt108H1DHrSfybdqUe5o2SzeV6C8E1Xo6d21KJPbyzKnu3b65I 7YKJL6rhYEIh4/cdjA4qWEyQ1wD+B/vu2FZcAJ4/P7KCADf1MXRODBjsCK3BVfR1RhtYSR mWRKgfteXWm3bs9WeZ6keWTpH9UFJBJxjLb8/E1TE8AbwOZQ91h3hxrtz101SnKfQIVFkA RDv3ln3pL5fDGWMKptGxnaxcWlwcrtl23/nuMD5K+XuTh8zU1ok7z3ATaQ0YtA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=pass header.d=gmail.com header.s=20210112 header.b="TjJN6nr/"; dmarc=pass (policy=none) header.from=gmail.com; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" X-Migadu-Spam-Score: -4.01 Authentication-Results: aspmx1.migadu.com; dkim=pass header.d=gmail.com header.s=20210112 header.b="TjJN6nr/"; dmarc=pass (policy=none) header.from=gmail.com; spf=pass (aspmx1.migadu.com: domain of "emacs-orgmode-bounces+larch=yhetil.org@gnu.org" designates 209.51.188.17 as permitted sender) smtp.mailfrom="emacs-orgmode-bounces+larch=yhetil.org@gnu.org" X-Migadu-Queue-Id: 444BE3165F X-Spam-Score: -4.01 X-Migadu-Scanner: scn1.migadu.com X-TUID: yiuSc9ZFaAK/ --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Ihor Radchenko writes: > Tyler Grinn writes: > >> Ihor Radchenko writes: >> >>> Tyler Grinn 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]" . "") (".+" . "=E2=88=BF")) >>> >>> 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 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-lisp-org.el-Add-org-property-separators-option.patch Content-Description: Add org-property-separators option >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 --=-=-=--