From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp1.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms13.migadu.com with LMTPS id uJCFNuIYzWaQcwAA62LTzQ:P1 (envelope-from ) for ; Tue, 27 Aug 2024 00:08:03 +0000 Received: from aspmx1.migadu.com ([2001:41d0:403:58f0::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp1.migadu.com with LMTPS id uJCFNuIYzWaQcwAA62LTzQ (envelope-from ) for ; Tue, 27 Aug 2024 02:08:02 +0200 X-Envelope-To: larch@yhetil.org Authentication-Results: aspmx1.migadu.com; dkim=pass header.d=gmail.com header.s=20230601 header.b=QHsZmvLC; 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" ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1724717282; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type:list-id:list-help:list-unsubscribe: list-subscribe:list-post:dkim-signature; bh=+AlJxKl6tEz2/UAgv5KfwJfhMu1V97SeiUrdOiGM8yU=; b=gbqqAcEQP6OVQxApEmF7srmwNPqbGpDXlnYLpPOkUVGv3KPPBVSHNvRCeDqqJF1YXlI3YS vmSPOuNHHHZvX6jKfTeQvDgUJ4yZfdeVxozlv5aCfoeYNQIBGiP+8C2X8HzJwreME3cdSa iR7ENNF0R0iA43lXZweWzdYJc1SpdHE7YTBF5Qn8tlKOsOSPuSisTfRrNZIziuenrTWFrF q/zggRnYrH0z+saNiwXz33aAQVn7vkduN/bL/VKt1RwhXG6GIScjTvqQq0Pr1c4WwDdjcP 1YhifLqhAwRCFXbMPcgN9rbOm3K9fAMRRmHzrJlq6SMNNSxb0sPgHVJYqC6Mxw== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1724717282; a=rsa-sha256; cv=none; b=t+8iha2EVrGxjIoPuFe1ruEiXnfRHNooVL54hDPKv5k13H74JtJ2oemZSYhAc6rCodj6zD VAVZuxv0ZkGrSoILSlIXS9vjkCBT7cf6e8R8GqliPeN6cTqj+HDbxJiwoI4YfJlTWydTny eYUBKQ+yZYSGKPELNQVaMkpoXJSRnMCIHVgokTBfnGXjJBHgXut2dJCwEe4PR4wbZj3QHn yWoW7R+nb8HmQ7sNEFUhiEJmKrw9KC+wb4xlvTl03BWTpkCP82t09UFocEq4DSvjfRhbDN iXSBiLWlLxb7VruuhQnfVMym0t85cycd5JMwsnPUIq7DpmnUw0Kmm1q1IDAsVQ== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=pass header.d=gmail.com header.s=20230601 header.b=QHsZmvLC; 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" 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 35E5771C4F for ; Tue, 27 Aug 2024 02:08:00 +0200 (CEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sijji-0000Sp-KH; Mon, 26 Aug 2024 20:07:04 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sijjd-0000Qd-36 for emacs-orgmode@gnu.org; Mon, 26 Aug 2024 20:06:57 -0400 Received: from mail-wm1-x329.google.com ([2a00:1450:4864:20::329]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sijjY-0004A5-HL for emacs-orgmode@gnu.org; Mon, 26 Aug 2024 20:06:55 -0400 Received: by mail-wm1-x329.google.com with SMTP id 5b1f17b1804b1-429e29933aaso40154895e9.0 for ; Mon, 26 Aug 2024 17:06:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1724717208; x=1725322008; darn=gnu.org; h=mime-version:message-id:date:subject:to:from:from:to:cc:subject :date:message-id:reply-to; bh=+AlJxKl6tEz2/UAgv5KfwJfhMu1V97SeiUrdOiGM8yU=; b=QHsZmvLC3d72X5LCTLI17AixnVnwa6V2sgkoLcCN5vhz3GPQ5jGP3ebO1se+JTFcT7 7ZTGeiiMePtMiPz8kgIbWK50ijc/dwGz7yuwPEMXy8PR5/DOg+NBFy8ad0fuc93kFvhf 7sc8TUfnUP/4Fcbcoq8bP6vzRyba8Dk6FJ6+J23Z97N5aUsNt0t6eQc1zvpVL9CTBuyX JjeCB1XzYgUTL+3C4OlkJXm1PEdH5RwRQ7pSjXvz5/czJmk5mNhbsCafdxNNBSe5d6dd gPoqtNn6wpgW03ILn3NB7lz4MTKPIaLw/sSks1rnl635z9sJlNUO8Y1wbAPUpkZmzIQ3 wLCA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1724717208; x=1725322008; h=mime-version:message-id:date:subject:to:from:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=+AlJxKl6tEz2/UAgv5KfwJfhMu1V97SeiUrdOiGM8yU=; b=aHNfBxIqr0P2QiZhHOZ/y0z/va4QOfSajxA/jucq0U1xlvn3rafpFvtRt/s3dQwlGl uY8SG3jqKr9pASTX6FytdDXwVskj+JVrDpe03UCv5hrH3OOW9OBoQFGzlTwUQ3grHJ/a DSaD66aAUNDI/iKFHLSf2hseNC18xsW4ejdWcRU9KYe2ncmuO8KivFatkqptQIFRFuA8 DuxDbVrJDt8T4ArLMBlGakP9DU5fB1Jdp4VAODp71oA2BKghm16wT6I6ndcD2xh1Q7AM iMWTYhQc+l089tD0ieVxVuGjuwzXWALBHSm7iThdsxWwAJ40nBekmS7r4dSKnYn9+y9c ESjw== X-Gm-Message-State: AOJu0YyuEcKrdZUwYUxW8tGZ1vwSPww65Fd1NIFz4ET1ZJGVFUZWkvAK EyMBvkrhqF/oGCfgbuqtMXGnWoh5ogvCJ72L0na/OwpVf/sXfyZizjr06A== X-Google-Smtp-Source: AGHT+IG+IlWjMgSN9vyzty78U4tQ9IeAW4m8iWJ0ifkMAE2eiiR28MxSarV7EGa76njqFgXY+4sttA== X-Received: by 2002:a05:600c:4e01:b0:426:654e:16da with SMTP id 5b1f17b1804b1-42acd39e07cmr87933155e9.0.1724717207796; Mon, 26 Aug 2024 17:06:47 -0700 (PDT) Received: from thuna-lis3 ([85.104.179.223]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3730817a548sm11712978f8f.63.2024.08.26.17.06.46 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 26 Aug 2024 17:06:47 -0700 (PDT) From: Thuna To: emacs-orgmode@gnu.org Subject: Multiline Macros Date: Tue, 27 Aug 2024 02:06:45 +0200 Message-ID: <87frqqj0bu.fsf@gmail.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2a00:1450:4864:20::329; envelope-from=thuna.cing@gmail.com; helo=mail-wm1-x329.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-bounces+larch=yhetil.org@gnu.org X-Migadu-Flow: FLOW_IN X-Migadu-Country: US X-Migadu-Spam-Score: -6.69 X-Spam-Score: -6.69 X-Migadu-Queue-Id: 35E5771C4F X-Migadu-Scanner: mx11.migadu.com X-TUID: bn4Ur2ZaPC6M --=-=-= Content-Type: text/plain Here's a package which allows defining multiline Org macros via #+BEGIN_MACRO ... #+END_MACRO: https://git.sr.ht/~thuna/org-multiline-macro (I've also attached the elisp file here.) The README should explain it (hopefully) well enough, but I'll also try explain it here: - To define a multiline macro, do #+BEGIN_MACRO foo Some text which will be inserted, plus some more in a line. #+END_MACRO and {{{foo}}} will be replaced by that body, newlines included. - Arguments can be used in the text much the same way: #+BEGIN_MACRO license Copyright (C) $1 {{{author}}} <{{{email}}}> #+END_MACRO - You can create an eval macro by starting the macro contents with `(eval': #+BEGIN_MACRO add-one (eval (number-to-string (1+ (string-to-number $1)))) #+END_MACRO and arguments work here as usual as well. - You can bypass the need for the leading `(eval' by giving the block an `:eval yes' parameter, #+BEGIN_MACRO add-one :eval yes (number-to-string (1+ (string-to-number $1))) #+END_MACRO This also allows more than one form. All the forms are evaluated in order, and the last one's result is the result of the macro. #+BEGIN_MACRO add-one-loudly :eval yes (setq $1 (string-to-number $1)) (message "adding one to: %d" $1) (number-to-string (1+ $1)) #+END_MACRO This is basically all this package does. There are a couple known problems, which are listed in the README but which I will also repeat here for completeness: - #+BEGIN_MACRO is not a distinct element type but a special block which I am hacking org-macro.el to handle. This comes with its fair share of problems, export being the most relevant one that I know of right now. The fix is to just make it a distinct element, but I want to tackle that only after I have figured out all the other kinks. - Macro blocks are not editable. This is doable however there is an additional challenge in that they should be editable as emacs lisp source blocks when they are eval macro blocks (maybe not if they are the implicit kind [aka without the :eval yes]), and I have no idea where to even start with this. Some help from people familiar with the relevant code would be appreciated. - :eval no should technically be forcing a macro block as non-eval, even if it starts with `(eval'. This is currently not the case, because I didn't want to duplicate the code which makes it so. - Some testing for SETUPFILE following would be appreciated. I did a *very* light test, but I don't know if it actually works or not. I just copied the code from a different function which handles it. All thoughts, ideas, suggestions, and bug reports are welcome. --=-=-= Content-Type: application/x-emacs-lisp Content-Disposition: attachment; filename=org-multiline-macro.el Content-Transfer-Encoding: quoted-printable ;;; org-multiline-macro.el --- Define block macros with #+begin_macro -*- = lexical-binding: t; -*- ;; Copyright (C) 2024 Umut Tuna Akg=C3=BCl ;; This program is free software: you can redistribute it and or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation, either version 3 of the ;; License, or (at your option) any later version. ;; This program is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see ;; . ;;; Commentary: ;; This package allows defining multiline macros via #+BEGIN_MACRO ;; #END_MACRO blocks. The syntax of such a block is simply: ;;=20 ;; #+BEGIN_MACRO MACRO-NAME [PARAMETERS...] ;; ... ;; #+END_MACRO ;; ;; MACRO-NAME is any sequence of non-whitespace characters, and it is ;; equivalent to the way it is used in ;; ;; #+MACRO: MACRO-NAME BODY... ;; ;; PARAMETERS is in the same format as it is in source blocks, but the ;; only meaningful parameter is currently `:eval'. ;; ;; If the `:eval' parameter is "yes" or the body of the macro starts ;; with "(eval", the macro is taken to be emacs lisp code, and the ;; macro is defined the way it is via ;; ;; #+MACRO: FOO (eval BODY) ;; ;; Otherwise, the body of the macro is what the macro expands to, the ;; same way it is when defined via ;; ;; #+MACRO: FOO BODY... ;;; TODOs: ;; - Have emacs lisp macro blocks be fontified and editable the same ;; way emacs lisp source blocks are. ;; - Maybe define a minor mode to turn macro blocks on and off? I ;; don't think it's a good idea but who knows. ;; - Make ":eval no" work even with an "(eval" macro. (require 'advice) (require 'org-element) (require 'ob-core) (defun org-multiline-macro--collect-1 (element) "Return a cons cell of the form (NAME . MACRO-BODY) from ELEMENT. If ELEMENT is not a valid macro block, return nil." (when-let (((org-element-type-p element 'special-block)) (type (org-element-property :type element)) ((string-equal-ignore-case "macro" type))) (let* ((parameters (org-element-property :parameters element)) (name (prog2 (or (string-match "^\\s-*\\(\\S-+\\)" parameters) (error "Macro name missing")) (match-string 1 parameters) (setq parameters (org-babel-parse-header-arguments (substring parameters (match-end 1)) t)))) (eval (cdr (assq 'eval parameters))) (contents (string-trim (with-current-buffer (org-element-property :buffer element) (without-restriction (buffer-substring (org-element-property :contents-begin el= ement) (org-element-property :contents-end elem= ent))))))) (cons name (if (and eval (string-equal-ignore-case "yes" eval) (not (string-match-p "\\`(eval\\>" contents))) (concat "(eval (progn " contents "\n))") contents))))) (defun org-multiline-macro--collect () "Collect block macro definitions in the current buffer and setup files. Return an alist containing all macro templates found." (append (org-element-map (org-element-parse-buffer 'element nil t) 'special-block #'org-multiline-macro--collect-1) (org-multiline-macro--collect-setupfiles))) ;; This was pulled from `org--collect-keywords-1' and lightly ;; modified. (defun org-multiline-macro--collect-setupfiles () "Collect block macro definitions in the SETUPFILES of the current buffer. This macro recursively descends down SETUPFILES, skipping any that have already been visited." (let (alist) (save-excursion (without-restriction (goto-char (point-min)) (let ((case-fold-search t) (regexp (org-make-options-regexp '("SETUPFILE")))) (while (re-search-forward regexp nil t) (when-let ((element (org-element-at-point)) ((org-element-type-p element 'keyword)) ((string-equal-ignore-case (org-element-property :key element) "SETUPFILE")) (value (org-element-property :value element))) (setq alist (append alist (org-multiline-macro--collect-uri v= alue)))))))) alist)) (defvar org-multiline-macro--visiting-files nil) (defun org-multiline-macro--collect-uri (uri) (when (org-string-nw-p uri) (let* ((uri (org-strip-quotes uri)) (uri-is-url (org-url-p uri)) (uri (if uri-is-url uri ;; In case of error, be safe. ;; See bug#68976. (ignore-errors ; return nil when expansion fails. (expand-file-name uri))))) (when (and uri (not (member uri org-multiline-macro--visiting-files))) (let ((org-multiline-macro--visiting-files (cons uri org-multiline-macro--visiting-files))) (with-temp-buffer (unless uri-is-url (setq default-directory (file-name-directory uri))) (let ((contents (org-file-contents uri :noerror))) (when contents (insert contents) ;; CHECK: Do we still not need to setup org mode? (let ((major-mode 'org-mode)) (org-multiline-macro--collect)))))))))) (define-advice org-macro--collect-macros (:filter-return (templates) collect-multiline) (append (org-multiline-macro--collect) templates)) (provide 'org-multiline-macro) ;;; org-multiline-macro.el ends here --=-=-=--