From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp0 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms11 with LMTPS id qHO7Cu7QoV5SLAAA0tVLHw (envelope-from ) for ; Thu, 23 Apr 2020 17:31:26 +0000 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp0 with LMTPS id 0L2rFvTQoV5CVwAA1q6Kng (envelope-from ) for ; Thu, 23 Apr 2020 17:31:32 +0000 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 49D4A942DCD for ; Thu, 23 Apr 2020 17:31:31 +0000 (UTC) Received: from localhost ([::1]:33738 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jRfhA-0007c9-S4 for larch@yhetil.org; Thu, 23 Apr 2020 13:31:28 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:49758) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jRfgD-0007Xn-Fb for emacs-orgmode@gnu.org; Thu, 23 Apr 2020 13:30:37 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.90_1) (envelope-from ) id 1jRfgC-0003kT-HK for emacs-orgmode@gnu.org; Thu, 23 Apr 2020 13:30:29 -0400 Received: from mail-qk1-x72a.google.com ([2607:f8b0:4864:20::72a]:33007) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1jRfgC-0003k8-3G for emacs-orgmode@gnu.org; Thu, 23 Apr 2020 13:30:28 -0400 Received: by mail-qk1-x72a.google.com with SMTP id 23so2395568qkf.0 for ; Thu, 23 Apr 2020 10:30:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=user-agent:from:to:subject:message-id:date:mime-version; bh=vtdFo0Y4GGMuwpT4Z7uqEyQuec4dKGN2KDBd+H0MEiQ=; b=qL6nLmQ2ofKlkQjf6F30LyrHW0gnQjVuSgjvvrtwFGeeIom3LMD4mo0aEpQdUSU0xU BJrfppDzcnyYQhfECvUWMAOoXq5BnWd9E1QVTU1jy1eN7WUEadsJhxKFAqqe3hQ3VAAg +rC8a0Ftb/j++UtLebqLFpuw8LNOVkVsH7ZrNsszuLKjLdhcH8WyDOWrEk7fU+Sz5Crm vqVzAQOE2irL1NykjJPXlHEVK6azMHmoZ1V2vtpd1EyPXnJ/a1AvxYblTfPGCVJ3CVlU Op9O9P01TWDbwsLIFdJhagF0MvLChs2Hxivog21wE+F8lk0Y/IQ7VQZc+hAHTyQyD5se +GnA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:user-agent:from:to:subject:message-id:date :mime-version; bh=vtdFo0Y4GGMuwpT4Z7uqEyQuec4dKGN2KDBd+H0MEiQ=; b=I7yjWG7Lxn6isvjttZlt883QO7QV3jKFlDIylWqCwVd9B/rlKGBguNNvyn45dw7LNh HHQgrpCBmnwZ8vadbK8u0BwIBt6VAWvh4/fH0oHBMnuupySWFsFXxV1RalXMVhKkClPE s1xCjP4RJujqtelrBlnFayTlY4qH/zf2FO+zb+LrwThxbVF7JRu6LG13yCuL3WnJTVES a0SGH3RsBG0czcX9BtAyOgtn1Am3wnSRuYyYwyhta0r8JYKHnPtfmB9dKOiHOOWUNNuh J2vMIXtKGxYPcB5uUpecxw6XIcey2h02/i6swTbzrCTmFGVe9ogUeTq3sF+UylWA6xcn MYbQ== X-Gm-Message-State: AGi0PuYJdoNYXX8ySYwoWh15HOmHhOxgEsVK2Ab5fKV4DC9MpXn71UXB 0NLo8ylElz8kI/S/Rxoi7X5g+2UK X-Google-Smtp-Source: APiQypItdX3cEX7LDni67AgWugOzfzD6drNMYZRITCFZYkUzYIx0H7hiB+nzNIesPxrP2XCnkCfvcA== X-Received: by 2002:a05:620a:572:: with SMTP id p18mr4784204qkp.73.1587663026607; Thu, 23 Apr 2020 10:30:26 -0700 (PDT) Received: from arch ([2601:8b:c300:1024:a8ca:869c:245a:8f19]) by smtp.gmail.com with ESMTPSA id j8sm2146381qtk.85.2020.04.23.10.30.25 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Apr 2020 10:30:26 -0700 (PDT) User-agent: mu4e 1.3.10; emacs 28.0.50 From: No Wayman To: emacs-orgmode@gnu.org Subject: [RFC] DOCT: Declarative Org Capture Templates Message-ID: <87wo66t8i7.fsf@gmail.com> Date: Thu, 23 Apr 2020 13:30:25 -0400 MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="=-=-=" Received-SPF: pass client-ip=2607:f8b0:4864:20::72a; envelope-from=iarchivedmywholelife@gmail.com; helo=mail-qk1-x72a.google.com X-detected-operating-system: by eggs.gnu.org: Error: [-] PROGRAM ABORT : Malformed IPv6 address (bad octet value). Location : parse_addr6(), p0f-client.c:67 X-Received-From: 2607:f8b0:4864:20::72a X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.23 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-Scanner: scn0 X-Spam-Score: -0.81 Authentication-Results: aspmx1.migadu.com; dkim=pass header.d=gmail.com header.s=20161025 header.b=qL6nLmQ2; dmarc=pass (policy=none) header.from=gmail.com; spf=pass (aspmx1.migadu.com: domain of emacs-orgmode-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=emacs-orgmode-bounces@gnu.org X-Scan-Result: default: False [-0.81 / 13.00]; RCVD_VIA_SMTP_AUTH(0.00)[]; GENERIC_REPUTATION(0.00)[-0.56312052213927]; R_SPF_ALLOW(-0.20)[+ip4:209.51.188.0/24:c]; FREEMAIL_FROM(0.00)[gmail.com]; TO_DN_NONE(0.00)[]; URI_COUNT_ODD(1.00)[3]; IP_REPUTATION_HAM(0.00)[asn: 22989(0.22), country: US(-0.00), ip: 209.51.188.17(-0.56)]; MX_GOOD(-0.50)[cached: eggs.gnu.org]; DKIM_TRACE(0.00)[gmail.com:+]; DMARC_POLICY_ALLOW(-0.50)[gmail.com,none]; MAILLIST(-0.20)[mailman]; FORGED_RECIPIENTS_MAILLIST(0.00)[]; RCVD_IN_DNSWL_LOW(-0.10)[209.51.188.17:from]; MIME_TRACE(0.00)[0:+,1:+,2:~]; RCVD_TLS_LAST(0.00)[]; ASN(0.00)[asn:22989, ipnet:209.51.188.0/24, country:US]; MID_RHS_MATCH_FROM(0.00)[]; TAGGED_FROM(0.00)[larch=yhetil.org]; FROM_NEQ_ENVFROM(0.00)[iarchivedmywholelife@gmail.com,emacs-orgmode-bounces@gnu.org]; ARC_NA(0.00)[]; R_DKIM_ALLOW(-0.20)[gmail.com:s=20161025]; FROM_HAS_DN(0.00)[]; DWL_DNSWL_NONE(0.00)[gmail.com:dkim]; MIME_GOOD(-0.10)[multipart/alternative,text/plain]; PREVIOUSLY_DELIVERED(0.00)[emacs-orgmode@gnu.org]; HAS_LIST_UNSUB(-0.01)[]; RCPT_COUNT_ONE(0.00)[1]; RCVD_COUNT_SEVEN(0.00)[7]; FORGED_SENDER_MAILLIST(0.00)[] X-TUID: gObDFwo0z5oD --=-=-= Content-Type: text/plain; format=flowed * [RFC] DOCT: Declarative Org Capture Templates I've been working on an alternative syntax for org-capture-templates. The result is a package called "DOCT" (declarative org capture templates): https://github.com/progfolio/doct A brief list of what I believe to be improvements over the current syntax/implementation: - DOCT validates templates before runtime execution. For exmaple, you have a template with an entry type of `'entry' and you forget the leading star in the template string. Days later you go to use that template. It's borked. You have a number of options: - forget about whatever you wanted to capture and press on with your original task - manually take a note about what you originally wanted to capture and another note to fix the broken template - drop what you're doing and fix everything None of these are ideal and all of them result in distraction. DOCT performs a number of checks ahead of time when possible to prevent these types of errors. - DOCT makes the parent/child relationship between templates explicit. `org-capture-templates` is a flat list. The relationship between templates is hardcoded in each template's "keys" value. If you want to change the key for a top-level menu, you must then change it in each descendant's keys. DOCT uses nested plists and implements property inheritance. - DOCT manages per-template hooks/contexts. Currently if you want to have a hook run for a particular template, you have to filter against `org-capture-plist' to check for the appropriate :key value. As stated above, this is fragile and you have to update that key value in numerous places if it ever changes. The same goes for `org-capture-templates-contexts`. DOCT allows specifying per-template contexts and hooks with the rest of the template's configuration. - DOCT makes adding custom metadata to templates easy. A common pattern for attaching data to a template is to add to `org-capture-plist'. This pollutes `org-capture-plist' more than necessary. DOCT adds custom data to `org-capture-plist' under a single :doct keyword and allows users to access that data in the template string with familiar %-escape syntax. This example is one I use daily for taking notes in programming projects: #+begin_src emacs-lisp (doct `("Note" :keys "n" :file ,(defun my/project-note-file () (let ((file (expand-file-name "notes.org" (when (functionp 'projectile-project-root) (projectile-project-root))))) (with-current-buffer (find-file-noselect file) (org-mode) ;;set to utf-8 because we may be visiting raw file (setq buffer-file-coding-system 'utf-8-unix) (when-let ((headline (doct-get :headline))) (unless (org-find-exact-headline-in-buffer headline) (goto-char (point-max)) (insert "* " headline) (org-set-tags (downcase headline)))) (unless (file-exists-p file) (write-file file)) file))) :template (lambda () (concat "* %{todo-state} " (when (y-or-n-p "link?") "%A\n") "%?")) :todo-state "TODO" :children (("bug" :keys "b" :headline "Bugs") ("documentation" :keys "d" :headline "Documentation") ("enhancement" :keys "e" :headline "Enhancements" :todo-state "IDEA") ("feature" :keys "f" :headline "Features" :todo-state "IDEA") ("optimization" :keys "o" :headline "Optimizations") ("miscellaneous" :keys "m" :headline "Miscellaneous") ("security" :keys "s" :headline "Security")))) #+end_src Each template inherits its parent's file finding function,template string, and a default :todo-state. The template string access the child's :todo-state keyword with the "%{KEYWORD}" syntax in the template string. I would be happy to work on getting these features into Org if there is any interest. Any feedback is welcome. Thanks, nv. --=-=-= Content-Type: text/html

I've been working on an alternative syntax for org-capture-templates. The result is a package called "DOCT" (declarative org capture templates):

https://github.com/progfolio/doct

A brief list of what I believe to be improvements over the current syntax/implementation:

  • DOCT validates templates before runtime execution.

For exmaple, you have a template with an entry type of `'entry' and you forget the leading star in the template string. Days later you go to use that template. It's borked. You have a number of options:

  • forget about whatever you wanted to capture and press on with your original task
  • manually take a note about what you originally wanted to capture and another note to fix the broken template
  • drop what you're doing and fix everything

None of these are ideal and all of them result in distraction. DOCT performs a number of checks ahead of time when possible to prevent these types of errors.

  • DOCT makes the parent/child relationship between templates explicit.

`org-capture-templates` is a flat list. The relationship between templates is hardcoded in each template's "keys" value. If you want to change the key for a top-level menu, you must then change it in each descendant's keys. DOCT uses nested plists and implements property inheritance.

  • DOCT manages per-template hooks/contexts.

Currently if you want to have a hook run for a particular template, you have to filter against `org-capture-plist' to check for the appropriate :key value. As stated above, this is fragile and you have to update that key value in numerous places if it ever changes. The same goes for `org-capture-templates-contexts`. DOCT allows specifying per-template contexts and hooks with the rest of the template's configuration.

  • DOCT makes adding custom metadata to templates easy.

A common pattern for attaching data to a template is to add to `org-capture-plist'. This pollutes `org-capture-plist' more than necessary. DOCT adds custom data to `org-capture-plist' under a single :doct keyword and allows users to access that data in the template string with familiar %-escape syntax.

This example is one I use daily for taking notes in programming projects:

(doct
 `("Note"
   :keys "n"
   :file ,(defun my/project-note-file ()
            (let ((file (expand-file-name "notes.org" (when (functionp 'projectile-project-root)
                                                        (projectile-project-root)))))
              (with-current-buffer (find-file-noselect file)
                (org-mode)
                ;;set to utf-8 because we may be visiting raw file
                (setq buffer-file-coding-system 'utf-8-unix)
                (when-let ((headline (doct-get :headline)))
                  (unless (org-find-exact-headline-in-buffer headline)
                    (goto-char (point-max))
                    (insert "* " headline)
                    (org-set-tags (downcase headline))))
                (unless (file-exists-p file) (write-file file))
                file)))
   :template (lambda () (concat  "* %{todo-state} " (when (y-or-n-p "link?") "%A\n") "%?"))
   :todo-state "TODO"
   :children (("bug"           :keys "b" :headline "Bugs")
              ("documentation" :keys "d" :headline "Documentation")
              ("enhancement"   :keys "e" :headline "Enhancements" :todo-state "IDEA")
              ("feature"       :keys "f" :headline "Features"     :todo-state "IDEA")
              ("optimization"  :keys "o" :headline "Optimizations")
              ("miscellaneous" :keys "m" :headline "Miscellaneous")
              ("security"      :keys "s" :headline "Security"))))

Each template inherits its parent's file finding function,template string, and a default :todo-state. The template string access the child's :todo-state keyword with the "%{KEYWORD}" syntax in the template string.

I would be happy to work on getting these features into Org if there is any interest. Any feedback is welcome.

Thanks, nv.

--=-=-=--