From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp11.migadu.com ([2001:41d0:2:c151::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms5.migadu.com with LMTPS id WK8RAHyYfWIy1gAAbAwnHQ (envelope-from ) for ; Fri, 13 May 2022 01:30:04 +0200 Received: from aspmx2.migadu.com ([2001:41d0:2:c151::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp11.migadu.com with LMTPS id qO4nAHyYfWLylgAA9RJhRA (envelope-from ) for ; Fri, 13 May 2022 01:30:04 +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 aspmx2.migadu.com (Postfix) with ESMTPS id 4A5BA781 for ; Fri, 13 May 2022 01:30:03 +0200 (CEST) Received: from localhost ([::1]:34600 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1npIDw-0003xC-RH for larch@yhetil.org; Thu, 12 May 2022 19:28:00 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:51362) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1npIDT-0003x4-C1 for emacs-orgmode@gnu.org; Thu, 12 May 2022 19:27:31 -0400 Received: from mail-io1-xd31.google.com ([2607:f8b0:4864:20::d31]:43914) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1npIDQ-0002Q0-U3 for emacs-orgmode@gnu.org; Thu, 12 May 2022 19:27:31 -0400 Received: by mail-io1-xd31.google.com with SMTP id o190so7032098iof.10 for ; Thu, 12 May 2022 16:27:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=BRsMPfDU633+XoFAEDrNBXo+SaSM3HHt/+fxJmgC0JY=; b=LhdXtCLeQv3IGMsP1R2G/1+OPPt1922Qxd6/wD7bxRxjQFKWHiBifvsGaeya2km8qc hLP5Xtb9CrThdPZSz6hi28sWpv+cbnM3SAXxHrNrwAfDkiAEfO1Zyz8IdKoiNAajDNVo CkihSCfGE4NOg02DfXxHY5K3XTeUzQSOqqMyKlBLr6Em0rN40h5tJeibGwMzp5EhMU3k jGL39zGiD484fytfGXtt6EtfSYGf0w+w01aj4YpMzxE/wUOWuYym4aP2BSXRrf8Snh52 e+ujCesLOyDtVVNf5pFHS++r3xiyE8R+Jvf62LrU520SD7s/bR/Afelf3+Sgu7GL2ClY +8Dg== 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:references:date:in-reply-to :message-id:user-agent:mime-version; bh=BRsMPfDU633+XoFAEDrNBXo+SaSM3HHt/+fxJmgC0JY=; b=nnmA8nGiBra9EkP10Y3WR1ODxVdCmjDLjK7X1Wr6ZRmJ/rGjSYwGO/ZRh2eJkjZUO4 c/ya/paw7dc2HtUKZEVyCySxUNHRhg2TJZmaekinxDgS40gdcaLpXst1z2VjKqzcG5aF oQN05jvF/npE3xd1AzVEyBHSVWg8lVIp1yG7bFvFbxNUNvYZDdPYy3PI5KCVpwOaizT9 nCh1cRFN5lKuoi2mpH9b2kioQPmToaJUFBG5d54s0jG5BYTVn4tXciRuHe+4gak2gsAg VY6G/mPGJuJlC0+v5vLjNRNLOreL9Ah1XvMzdV7oIIKKDLjs2vmV06eoSSZ8tg6SXzhp NvbQ== X-Gm-Message-State: AOAM533spMwZ+3TjEQ7nwGVvirpfYGTzXDP9M5QPYf3KNpo7RZXPKbM2 D7DITO5svoJSv/FGN7WHeoQlt2gNLGEmVw== X-Google-Smtp-Source: ABdhPJymLV31jtRj3aVAM9EGcNEKXGirLfsIUAN38NMSkl70bfaXqlA8W1QjEOJQ9M0bAFc+OXcO0A== X-Received: by 2002:a5d:9316:0:b0:657:a364:ceb with SMTP id l22-20020a5d9316000000b00657a3640cebmr1107960ion.63.1652398047077; Thu, 12 May 2022 16:27:27 -0700 (PDT) Received: from tyler-fedora ([107.161.129.166]) by smtp.gmail.com with ESMTPSA id q6-20020a056638040600b0032bd50d6fccsm254746jap.15.2022.05.12.16.27.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 12 May 2022 16:27:26 -0700 (PDT) From: Tyler Grinn To: Ihor Radchenko Cc: org-mode-email Subject: Re: [PATCH] Re: Concatenate properties 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> <874k1w2c6o.fsf_-_@gmail.com> <87czgjhy0m.fsf@localhost> Date: Thu, 12 May 2022 19:27:24 -0400 In-Reply-To: <87czgjhy0m.fsf@localhost> (Ihor Radchenko's message of "Thu, 12 May 2022 17:59:37 +0800") Message-ID: <87mtfmib6r.fsf@gmail.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/29.0.50 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2607:f8b0:4864:20::d31; envelope-from=tylergrinn@gmail.com; helo=mail-io1-xd31.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=1652398203; 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=BRsMPfDU633+XoFAEDrNBXo+SaSM3HHt/+fxJmgC0JY=; b=dozwu/XRPKoG5Mqd5fgMqn/rAizNm+p+SW9UCJTblwoVR80zyjT0sqg3ldEJSBFcOIZ9EQ Ee6yC6AjR55h71dpIpaxma40XZsUS/Hgav9i+Bbe50hwO3pBStwuEForE33nrFHnglLYHO cRNi60+QnQlCUKS0bABb5TexmjQXQ7kJ13bi0gYMoBah8N6Fyg0yqWKfwTYwH3tp/F9hlm /IUzHA3zUp2RHcNCpqbH5pgEa3vaC6Ioccj6S7kgaIEJLrqveUb0zjuOQVx+qEihj83Gni bVDM8kvYCKrBURMQAq0M/+ggHNxir7RNz+SJjjFm24hOlv7PaIr5DugF0rvhVA== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1652398203; a=rsa-sha256; cv=none; b=Da3HIFFT3SBbrDqWyiUn2ziBtj1TsqBlOdao7sSlfVjB/RpZITnZq3G+f/994g4I5e4Wyg GmAnl/3eM0iFUvBPJ3EHHVnyH8Ry7I8mt6gcf/PkD3Ys35E8Y9kIfu/Z3HJ5roMFvwoQbv XaYG1wvyUNj3p3wchUboZFjIyiBOidYNpZJ5D0yMwJyZKOQtwDcRqs4mWy6q4YUo49zren xG7sevvrfyFwyjz9I/ZbKlA/LFqeIsj9oByHMFtCFpKfbPxCUOPCswEws8uzDEMzCROkBp 0DZFGSBu5GWbanGhe2bgO3t4oI/+ykxahnqWuu48FZVbE3rE12Hgn0mTP1T+uA== ARC-Authentication-Results: i=1; aspmx2.migadu.com; dkim=pass header.d=gmail.com header.s=20210112 header.b=LhdXtCLe; dmarc=pass (policy=none) header.from=gmail.com; spf=pass (aspmx2.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: -7.52 Authentication-Results: aspmx2.migadu.com; dkim=pass header.d=gmail.com header.s=20210112 header.b=LhdXtCLe; dmarc=pass (policy=none) header.from=gmail.com; spf=pass (aspmx2.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: 4A5BA781 X-Spam-Score: -7.52 X-Migadu-Scanner: scn0.migadu.com X-TUID: gQOEwzJ+E0+E --=-=-= Content-Type: text/plain Ihor Radchenko writes: > Note that your patch is >15LOC long and you need to sign the copyright > agreement with FSF in order to contribute. See > https://orgmode.org/worg/org-contribute.html#copyright I've already submitted a copyright assignment to the FSF in order to publish on ELPA. Do I need one specific to org-mode? > Some comments on the patch: > >> * lisp/org.el (org-property-separators, org-property-get-separator): >> Created. > > I'd make the function private: org--property-get-separator. It is not > intended as an API function. I agree, that was an oversight. > >> (org-entry-get, org-entry-get-with-inheritance): Use new >> org-property-get-separator function. > >> org-property-separators is a customization option that allows for > > Please quote the function name as `org-property-get-separator'. > No problem. >> +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 > > Please, use double space " " to separate sentences. Also, see > doc/Documentation_Standards.org No problem. > >> +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 > > This example is a bit confusing because it is unclear what you want to > achieve and why you also need to set inheritance. Inheritance is the most likely scenario one would need to use the '+' property syntax, but I do agree it's kinda distracting and not absolutely necessary in order to get the correct behavior. > >> +(defcustom org-property-separators nil >> ... >> + :group 'org-properties >> + :type '(alist :key-type string :value-type sexp)) > > This defcustom type does not match what you described in the docstring. > You need something like :type '(alist :key-type (choice string (repeat string)) :value-type string) > > Best, > Ihor Setting ':value-type string' is confusing, in my opinion, because the default single space looks like: in the customization buffer, which is indistinguishable from no space: . I just found out about the restricted-sexp type, which I think makes the customization buffer more user-friendly. --=-=-= 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 f474cb25840fdc6b24618b1452cb7fdd32545092 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 | 31 ++++++++++++++++++++++++++ lisp/org.el | 47 +++++++++++++++++++++++++++++++++------- testing/lisp/test-org.el | 30 ++++++++++++++++++++++++- 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 27de6da62..9d1d2cdcf 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -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 diff --git a/lisp/org.el b/lisp/org.el index cab59b87c..643fd6b73 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -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 @@ -12355,7 +12383,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 +12494,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 +12503,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 +12515,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 +12544,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 --=-=-=--