Hi!

I'm working on the :noweb-wrap header argument which controls the syntax
of noweb references in a babel src block. For example:

#+name: foo
#+begin_src elisp
  :foo
#+end_src

#+begin_src elisp :noweb yes :noweb-wrap <<< >>>
  <<<foo>>>
#+end_src

And I would like some feedback...

First of all, I would like to change (defalias) the function name
org-babel-noweb-wrap to org-babel-noweb-make-regexp. I think this in
more in line with other functions which create regular expressions.

The new way to retrieve the regular expression matching a noweb
reference in the source block at point would be:

(org-babel-noweb-make-regexp nil (org-babel-get-noweb-wrap info))

Where info can be nil or the result of calling
(org-babel-get-src-block-info).

Second, the command org-babel-tangle-clean is not able to determine
which noweb syntax is being used in any tangled source file because the
header arguments are not tangled along with the source code.

My proposal is to add an additional warning to this command, stating:

"""
Warning, this command removes any lines containing constructs which
resemble Org file links or noweb references.  It also cannot determine
which noweb syntax is being used for any given source file, if
:noweb-wrap was specified in the original Org file.
"""

Best,

Amy

>From 1dc8aebcc45447d3b5b38ea3c7700ae2b2686c9d Mon Sep 17 00:00:00 2001
From: Amy Grinn <grinn....@gmail.com>
Date: Mon, 8 Apr 2024 09:05:02 -0400
Subject: [PATCH] (WIP) lisp/ob-core.el: New :noweb-wrap header arg

* lisp/ob-core: (org-babel-noweb-wrap): Add optional third parameter
'wrap'.
* lisp/ob-core: (org-babel-get-noweb-wrap): New function for parsing
:noweb-wrap header arg.
* etc/ORG-NEWS (New =:noweb-wrap= babel header argument): Describe new
argument.
* others...
---
 etc/ORG-NEWS                | 14 ++++++++++
 lisp/ob-core.el             | 51 ++++++++++++++++++++++++++++------
 lisp/ob-exp.el              |  3 +-
 lisp/ob-tangle.el           |  6 +++-
 testing/examples/babel.org  | 17 ++++++++++++
 testing/lisp/test-ob-exp.el | 55 +++++++++++++++++++++++++++++++++++++
 testing/lisp/test-ob.el     | 15 ++++++++++
 7 files changed, 150 insertions(+), 11 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index aeb7ffd4b..162e7f035 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -621,6 +621,20 @@ link when storing any type of external link type in an Org file, not
 just =id:= links.
 
 ** New and changed options
+*** New =:noweb-wrap= babel header argument
+
+This argument changes the default noweb reference syntax by masking
+the options ~org-babel-noweb-wrap-start~ and
+~org-babel-noweb-wrap-end~.
+
+=:noweb-wrap= takes two parameters, start and end, corresponding to
+each option.
+
+For example:
+: #+begin_src sh :noweb-wrap <<< >>>
+:   echo <<<message>>>
+: #+end_src
+
 *** =.avif= images are now recognized in ~org-html-inline-image-rules~
 
 In =ox-html=, =.avif= image links are now inlined by default.
diff --git a/lisp/ob-core.el b/lisp/ob-core.el
index 8dfc07a4e..843794322 100644
--- a/lisp/ob-core.el
+++ b/lisp/ob-core.el
@@ -194,15 +194,21 @@ This string must include a \"%s\" which will be replaced by the results."
   :package-version '(Org . "9.1")
   :safe #'booleanp)
 
-(defun org-babel-noweb-wrap (&optional regexp)
+(defun org-babel-noweb-wrap (&optional regexp wrap)
   "Return regexp matching a Noweb reference.
 
 Match any reference, or only those matching REGEXP, if non-nil.
 
+If WRAP is provided, it should be a list of 2 strings describing
+the start and end of a noweb reference, such as that returned by
+`org-babel-get-noweb-wrap'.  Otherwise
+`org-babel-noweb-wrap-start' and `org-babel-noweb-wrap-end' will
+be used.
+
 When matching, reference is stored in match group 1."
-  (concat (regexp-quote org-babel-noweb-wrap-start)
-	  (or regexp "\\([^ \t\n]\\(?:.*?[^ \t\n]\\)?\\)")
-	  (regexp-quote org-babel-noweb-wrap-end)))
+  (concat (regexp-quote (or (car wrap) org-babel-noweb-wrap-start))
+	    (or regexp "\\([^ \t\n]\\(?:.*?[^ \t\n]\\)?\\)")
+	    (regexp-quote (or (cadr wrap) org-babel-noweb-wrap-end))))
 
 (defvar org-babel-src-name-regexp
   "^[ \t]*#\\+name:[ \t]*"
@@ -1963,6 +1969,27 @@ src block, then return nil."
   (let ((head (org-babel-where-is-src-block-head)))
     (if head (goto-char head) (error "Not currently in a code block"))))
 
+(defun org-babel-get-noweb-wrap (&optional info)
+  "Retrieve a description the :noweb-wrap header arg from INFO.
+
+The description will be in the form of a list of two of strings
+for the start and end of a reference.  INFO can be the result of
+`org-babel-get-src-block-info' otherwise this function will parse
+info at point."
+  (unless info
+    (setq info (org-babel-get-src-block-info 'no-eval)))
+  (when-let ((raw (cdr (assq :noweb-wrap (nth 2 info)))))
+    (let (result)
+      (with-temp-buffer
+        (insert raw)
+        (goto-char (point-min))
+        (while (< (point) (point-max))
+          (unless (looking-at " *\"\\([^\"]+\\)\" *")
+            (looking-at " *\\([^ ]+\\)"))
+          (goto-char (match-end 0))
+          (push (match-string 1) result)))
+      (reverse result))))
+
 ;;;###autoload
 (defun org-babel-goto-named-src-block (name)
   "Go to a source-code block with NAME."
@@ -1974,14 +2001,18 @@ src block, then return nil."
 	    "source-block name: " all-block-names nil t
 	    (let* ((context (org-element-context))
 		   (type (org-element-type context))
+                   (noweb-wrap (org-babel-get-noweb-wrap))
 		   (noweb-ref
 		    (and (memq type '(inline-src-block src-block))
-			 (org-in-regexp (org-babel-noweb-wrap)))))
+			 (org-in-regexp (org-babel-noweb-wrap
+                                         nil noweb-wrap)))))
 	      (cond
 	       (noweb-ref
 		(buffer-substring
-		 (+ (car noweb-ref) (length org-babel-noweb-wrap-start))
-		 (- (cdr noweb-ref) (length org-babel-noweb-wrap-end))))
+		 (+ (car noweb-ref) (length (or (car noweb-wrap)
+                                                org-babel-noweb-wrap-start)))
+		 (- (cdr noweb-ref) (length (or (cadr noweb-wrap)
+                                                org-babel-noweb-wrap-end)))))
 	       ((memq type '(babel-call inline-babel-call)) ;#+CALL:
 		(org-element-property :call context))
 	       ((car (org-element-property :results context))) ;#+RESULTS:
@@ -3125,7 +3156,8 @@ block but are passed literally to the \"example-block\"."
                                   (not (equal (cdr v) "no"))))))
 	 (noweb-re (format "\\(.*?\\)\\(%s\\)"
 			   (with-current-buffer parent-buffer
-			     (org-babel-noweb-wrap)))))
+			     (org-babel-noweb-wrap
+                              nil (org-babel-get-noweb-wrap info))))))
     (unless (equal (cons parent-buffer
                          (with-current-buffer parent-buffer
                            (buffer-chars-modified-tick)))
@@ -3175,7 +3207,8 @@ block but are passed literally to the \"example-block\"."
 	              ((guard (or org-babel-noweb-error-all-langs
 			          (member lang org-babel-noweb-error-langs)))
 	               (error "Cannot resolve %s (see `org-babel-noweb-error-langs')"
-		              (org-babel-noweb-wrap ,ref)))
+		              (org-babel-noweb-wrap
+                               ,ref (org-babel-get-noweb-wrap))))
 	              (_ ""))))
       (replace-regexp-in-string
        noweb-re
diff --git a/lisp/ob-exp.el b/lisp/ob-exp.el
index af726dc2c..1311813c5 100644
--- a/lisp/ob-exp.el
+++ b/lisp/ob-exp.el
@@ -414,7 +414,8 @@ replaced with its value."
   (setf (nth 1 info)
 	(if (string= "strip-export" (cdr (assq :noweb (nth 2 info))))
 	    (replace-regexp-in-string
-	     (org-babel-noweb-wrap) "" (nth 1 info))
+	     (org-babel-noweb-wrap nil (org-babel-get-noweb-wrap info))
+             "" (nth 1 info))
 	  (if (org-babel-noweb-p (nth 2 info) :export)
 	      (org-babel-expand-noweb-references
 	       info org-babel-exp-reference-buffer)
diff --git a/lisp/ob-tangle.el b/lisp/ob-tangle.el
index 79fe6448b..17c4e7096 100644
--- a/lisp/ob-tangle.el
+++ b/lisp/ob-tangle.el
@@ -580,7 +580,11 @@ non-nil, return the full association list to be used by
 	  ;; Run the tangle-body-hook.
           (let ((body (if (org-babel-noweb-p params :tangle)
                           (if (string= "strip-tangle" (cdr (assq :noweb (nth 2 info))))
-                            (replace-regexp-in-string (org-babel-noweb-wrap) "" (nth 1 info))
+                              (replace-regexp-in-string
+			       (org-babel-noweb-wrap
+				nil (org-babel-get-noweb-wrap info))
+			       ""
+			       (nth 1 info))
 			    (org-babel-expand-noweb-references info))
 			(nth 1 info))))
 	    (with-temp-buffer
diff --git a/testing/examples/babel.org b/testing/examples/babel.org
index d46afeb5e..680d4bf3e 100644
--- a/testing/examples/babel.org
+++ b/testing/examples/babel.org
@@ -346,6 +346,23 @@ Here is a call line with more than just the results exported.
   echo "1$i"
 #+END_SRC
 
+* strip noweb references with alternative wrap
+  :PROPERTIES:
+  :ID:       da9bcfdd-c1bd-47b4-b520-67974b9f9856
+  :END:
+
+#+name: strip-export-2
+#+BEGIN_SRC sh :exports none
+  i="10"
+#+END_SRC
+
+#+RESULTS: strip-export-2
+
+#+BEGIN_SRC sh :noweb strip-export :noweb-wrap #[[ ]] :exports code :results silent
+  #[[strip-export-2]]
+  echo "1$i"
+#+END_SRC
+
 * use case of reading entry properties
   :PROPERTIES:
   :ID:       cc5fbc20-bca5-437a-a7b8-2b4d7a03f820
diff --git a/testing/lisp/test-ob-exp.el b/testing/lisp/test-ob-exp.el
index e6fbf14a1..8c4f4e9a1 100644
--- a/testing/lisp/test-ob-exp.el
+++ b/testing/lisp/test-ob-exp.el
@@ -394,6 +394,61 @@ be evaluated."
 		 (regexp-quote " :foo  :bar \n")
 		 ascii))))))
 
+(ert-deftest ob-exp/noweb-wrap-header-arg ()
+  (let ((org-export-use-babel t))
+    (org-test-with-temp-text
+	"
+#+Title: exporting from a temporary buffer
+
+#+name: foo
+#+BEGIN_SRC emacs-lisp
+  :foo
+#+END_SRC
+
+#+BEGIN_SRC emacs-lisp :noweb yes :noweb-wrap {{ }} :exports results
+  (list {{foo}})
+#+END_SRC
+"
+      (let* ((ascii (org-export-as 'ascii)))
+	(should (string-match
+		 (regexp-quote " :foo \n")
+		 ascii))))))
+
+(ert-deftest ob-exp/noweb-strip-export-with-wrap ()
+  (org-test-at-id "da9bcfdd-c1bd-47b4-b520-67974b9f9856"
+    (org-narrow-to-subtree)
+    (org-babel-next-src-block 1)
+    (org-babel-execute-src-block)
+    (let ((result (org-test-with-expanded-babel-code (buffer-string))))
+      (should-not (string-match (regexp-quote "#[[strip-export-2]]") result))
+      (should-not (string-match (regexp-quote "i=\"10\"") result)))))
+
+(ert-deftest ob-exp/noweb-wrap-strip-export ()
+  (let ((org-export-use-babel t))
+    (org-test-with-temp-text
+	"
+#+Title: exporting from a temporary buffer
+
+#+name: foo
+#+BEGIN_SRC emacs-lisp
+  :foo
+#+END_SRC
+
+#+name: bar
+#+BEGIN_SRC emacs-lisp
+  :bar
+#+END_SRC
+
+#+BEGIN_SRC emacs-lisp :noweb yes :noweb-wrap {{ }} :exports results
+  (list {{foo}} {{bar}})
+#+END_SRC
+"
+      (let* ((ascii (org-export-as 'ascii)))
+        
+	(should (string-match
+		 (regexp-quote ":foo  :bar")
+		 ascii))))))
+
 (ert-deftest ob-export/export-with-results-before-block ()
   "Test export when results are inserted before source block."
   (let ((org-export-use-babel t))
diff --git a/testing/lisp/test-ob.el b/testing/lisp/test-ob.el
index c088af7c8..bd143be8b 100644
--- a/testing/lisp/test-ob.el
+++ b/testing/lisp/test-ob.el
@@ -988,6 +988,21 @@ x
             (search-forward "begin_src")
             (org-babel-expand-noweb-references)))))
 
+(ert-deftest test-ob/noweb-wrap ()
+  ;; Standard test.
+  (should
+   (string=
+    "bar"
+    (org-test-with-temp-text "#+begin_src sh :results output :tangle yes :noweb-wrap <<< >>>
+  <<<foo>>>
+#+end_src
+
+#+name: foo
+#+begin_src sh
+  bar
+#+end_src"
+        (org-babel-expand-noweb-references)))))
+
 (ert-deftest test-ob/splitting-variable-lists-in-references ()
   (org-test-with-temp-text ""
     (should (= 1 (length (org-babel-ref-split-args
-- 
2.39.2

Reply via email to