emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Rasmus <rasmus@gmx.us>
To: emacs-orgmode@gnu.org
Subject: Re: [patch, ox] #+INCLUDE resolves links
Date: Sun, 28 Sep 2014 21:32:08 +0200	[thread overview]
Message-ID: <87d2af1qyv.fsf@gmx.us> (raw)
In-Reply-To: <87oau4ems5.fsf@nicolasgoaziou.fr> (Nicolas Goaziou's message of "Wed, 24 Sep 2014 23:22:50 +0200")

[-- Attachment #1: Type: text/plain, Size: 5293 bytes --]

Hi,

Thanks for the comments.  I hope I addressed the previous comments and
did not introduce new reasons bugs.
I added tests.

Comments on comments follow.

Nicolas Goaziou <mail@nicolasgoaziou.fr> writes:

>> Okay, I hope I got it now.  It's a rather forgiving regexp in terms of
>> mistakes.  Is that OK?
>
> Please no ":only-contents yes", ":only-contents true", ":only-contents
> of_course!" in the regexp. If :only-contents is followed by anything but
> nil or another keyword, its value is non-nil. See below.

Good catch; I added explicit support for

     ¡?[oO][fF][-_]?[cC][Oo][uU][rR][sS][\.!¡]?

in the regexp!  ¡Gotta catch 'em all!

> The sentence is not complete. Also, it should be something like "If you
> set @code{:only-contents} property to a non-nil value, only...".
>> [...]
> This is not true anymore about the drawers. This should be merged with
> the previous phrase to avoid duplicating "@code{:only-contents}" (e.g.,
> only the contents of the matched element are inserted, without any
> planning line or property drawer).

Fixed this and other documentation bugs — hopefully.  Let me know if
it's clear.

>> +			    (let ((matched (save-match-data
>> +					     (org-split-string
>> + (org-remove-double-quotes (match-string 1 value)) "::"))))
>
> There's no reason to use `org-split-string' here since you only want to
> match the last "::". You can use the same regexp used by
> "org-element.el", i.e.
>
>   (when (string-match "::\\(.*\\)\\'" value)
>     (setq location (match-string 1 value)
>           value (replace-match "" nil nil value)))


 Custom_ID is very flexible.  I've use a similar regexp.

>> +		 (only-contents
>> +		  (and (string-match
>> + ":only-?contents?[[:space:]]*\"?\\(t\\|true\\|yes\\)?\"?"
>> +			value)
>> +		       (prog1 (and (match-string 1 value) t)
>> +			 (setq value (replace-match "" nil nil value)))))
>
>   (only-contents
>    (and (string-match ":only-contents +\\([^: \r\t\n]\\S-*\\)" value)
>         (org-not-nil (match-string 1 value))))

I have removed flexibility in speling.

>> +      (narrow-to-region
>> +       (org-element-property
>> +	(if only-contents :contents-begin :begin) (org-element-at-point))
>> + (org-element-property (if only-contents :contents-end :end)
>> (org-element-at-point))))
>
>   (let ((element (org-element-at-point)))
>     (let ((contents-beg
>            (and only-contents
>                 (org-element-property :contents-begin element))))
>       (narrow-to-region
>        (or contents-beg (org-element-property :begin element))
>        (org-element-property (if contents-beg :contents-end :end) element))))

Just out of curiosity, what is an example of a element that can be
named and does not have a :contents-begin?

>> +    (when only-contents
>> +      ;; skip drawers and property-drawers
>> +      ;; these are removed as needed in `org-export--prepare-file-contents'
>> +      ;; TODO: How to actually do this?  Only line numbers are send to
>> +      ;; `org-export--prepare-file-contents'.  split in two?
>> +      (goto-char (point-min))
>> +      (org-skip-whitespace)
>> +      (beginning-of-line)
>> +      (let ((element (org-element-at-point)))
>> +	(while (memq (org-element-type element) '(drawer property-drawer))
>> +	  (goto-char (org-element-property :end element))
>> +	  (setq element (org-element-at-point)))))
>
> Regular drawers are not expected to be skipped. Also, the following
> should be better
>
>   (when (and only-contents
>              (memq (org-element-type element) '(headline inlinetask)))
>     (goto-char (point-min))
>     (when (org-looking-at-p org-planning-line-re) (forward-line))
>     (when (looking-at org-property-drawer-re) (goto-char (match-end 0)))
>     (unless (bolp) (forward-line)))
>
> This should be obviously included within the previous `let'.

Okay, there's a lot of improvements in that suggestion.  However, it
misses this case which created using only "official" shortcuts

     * head
     SCHEDULED: <2014-09-28 sun>
     :LOGBOOK:
     - Note taken on [2014-09-28 sat 12:21] \\
       a drawer
     :END:
     :PROPERTIES:
     :CUSTOM_ID: h
     :END:

The patch handles something like this now cf. the last test.

>> +    (apply (lambda (beg end) (format "%s-%s" beg end))
>> +	   ;; `line-number-at-pos' returns the narrowed line-number
>> + (mapcar 'line-number-at-pos (prog1 (list (point-min) (point-max))
>> (widen))))))
>
> This is inefficient because `line-number-at-pos' will start counting
> twice from line 1.
>
>   (goto-char beg)
>   (widen)
>   (let ((start-line (line-number-at-pos)))
>     (format "%d-%d"
>             start-line
>             (+ start-line
>                (let ((c 0)) (while (< (point) end) (incf c) (forward-line)) c))))
>
> I didn't check, there may an off-by-one error. Anyway, all this needs
> tests.

Fine with me.  It's a bit less elegant IMO, but you are right.  I had
to do it slightly differently since the line number needs to appear
irrespective of whether lines are included in the call initially.
That being said, I could have very well overlooked some obvioues way
of doing it.

—Rasmus

-- 
Hooray!

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-ox-Allow-file-links-with-INCLUDE-keyword.patch --]
[-- Type: text/x-diff, Size: 12731 bytes --]

From 0c0b4a4b9ea0db7ce3f8c08c9a2947db85c54470 Mon Sep 17 00:00:00 2001
From: Rasmus <rasmus@gmx.us>
Date: Sun, 28 Sep 2014 21:05:17 +0200
Subject: [PATCH] ox: Allow file-links with #+INCLUDE-keyword

* org.el (org-edit-special): Handle file-links for INCLUDE.
* ox.el (org-export--prepare-file-contents): Handle links and
add option no-heading.
* ox.el (org-export-expand-include-keyword): Resolve headline
links and add option :only-contents.
* orgguide.texi (Include files)
org.texi (Include files): Updated.
* testing/examples/include.org: New examples.
* test-ox.el (test-org-export/expand-include): New tests.
---
 doc/org.texi                 |  18 +++++++
 doc/orgguide.texi            |   9 +++-
 lisp/org.el                  |   8 +--
 lisp/ox.el                   | 117 +++++++++++++++++++++++++++++++++++++++----
 testing/examples/include.org |  25 +++++++++
 testing/lisp/test-ox.el      |  57 ++++++++++++++++++++-
 6 files changed, 218 insertions(+), 16 deletions(-)

diff --git a/doc/org.texi b/doc/org.texi
index 7d98d51..5a9c0c5 100644
--- a/doc/org.texi
+++ b/doc/org.texi
@@ -10008,6 +10008,24 @@ to use the obvious defaults.
 #+INCLUDE: "~/.emacs" :lines "10-"    @r{Include lines from 10 to EOF}
 @end example
 
+Finally, you may use a file-link, see @ref{search option in file links}, to
+extract an object as matched by @code{org-link-search}@footnote{Note that
+@code{org-link-search-must-match-exact-headline} is locally bound to non-nil.
+Therefore, @code{org-link-search} only matches headlines and named
+elements.}.  If the @code{:only-contents} property is non-nil, only the
+contents of the requested element will be included, omitting any
+property-drawers, planning-lines, attributes, captions etc.  The
+@code{:lines} keyword operates locally with respect to the requested element.
+Some examples:
+
+@example
+#+INCLUDE: "./paper.org::#theory" :only-contents t
+   @r{Include the body of the heading with the custom id @code{theory}}
+#+INCLUDE: "./paper.org::mytable"  @r{Include named element.}
+#+INCLUDE: "./paper.org::*conclusion" :lines 1-20
+   @r{Include the first 20 lines of the headline named conclusion.}
+@end example
+
 @table @kbd
 @kindex C-c '
 @item C-c '
diff --git a/doc/orgguide.texi b/doc/orgguide.texi
index ca8e052..4feeaca 100644
--- a/doc/orgguide.texi
+++ b/doc/orgguide.texi
@@ -2264,8 +2264,13 @@ include your @file{.emacs} file, you could use:
 The optional second and third parameter are the markup (i.e., @samp{example}
 or @samp{src}), and, if the markup is @samp{src}, the language for formatting
 the contents.  The markup is optional, if it is not given, the text will be
-assumed to be in Org mode format and will be processed normally. @kbd{C-c '}
-will visit the included file.
+assumed to be in Org mode format and will be processed normally.  File-links
+will be interpreted as well:
+@smallexample
+#+INCLUDE: "./otherfile.org::#my_custom_id" :only-contents t
+@end smallexample
+@noindent
+@kbd{C-c '} will visit the included file.
 
 @node Embedded @LaTeX{},  , Include files, Markup
 @section Embedded @LaTeX{}
diff --git a/lisp/org.el b/lisp/org.el
index b09e72d..c70e44a 100755
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -20522,9 +20522,11 @@ Otherwise, return a user error."
 		       session params))))))
       (keyword
        (if (member (org-element-property :key element) '("INCLUDE" "SETUPFILE"))
-           (find-file-other-window
-            (org-remove-double-quotes
-             (car (org-split-string (org-element-property :value element)))))
+           (org-open-link-from-string
+	    (format "[[%s]]"
+		    (expand-file-name
+		     (org-remove-double-quotes
+		      (car (org-split-string (org-element-property :value element)))))))
          (user-error "No special environment to edit here")))
       (table
        (if (eq (org-element-property :type element) 'table.el)
diff --git a/lisp/ox.el b/lisp/ox.el
index 59091fc..ed1024b 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -3321,13 +3321,25 @@ paths."
 	  ;; Extract arguments from keyword's value.
 	  (let* ((value (org-element-property :value element))
 		 (ind (org-get-indentation))
+		 location
 		 (file (and (string-match
 			     "^\\(\".+?\"\\|\\S-+\\)\\(?:\\s-+\\|$\\)" value)
-			    (prog1 (expand-file-name
-				    (org-remove-double-quotes
-				     (match-string 1 value))
-				    dir)
+			    (prog1
+				(save-match-data
+				  (let ((matched (match-string 1 value)))
+				    (when (string-match "\\(::\\(.*?\\)\\)\"?\\'" matched)
+				      (setq location (match-string 2 matched))
+				      (setq matched
+					    (replace-match "" nil nil  matched 1)))
+				    (expand-file-name
+				     (org-remove-double-quotes
+				      matched)
+				     dir)))
 			      (setq value (replace-match "" nil nil value)))))
+		 (only-contents
+		  (and (string-match ":only-contents +\\([^: \r\t\n]\\S-*\\)" value)
+		       (prog1 (org-not-nil (match-string 1 value))
+			 (setq value (replace-match "" nil nil value)))))
 		 (lines
 		  (and (string-match
 			":lines +\"\\(\\(?:[0-9]+\\)?-\\(?:[0-9]+\\)?\\)\""
@@ -3387,17 +3399,88 @@ paths."
 	       (t
 		(insert
 		 (with-temp-buffer
-		   (let ((org-inhibit-startup t)) (org-mode))
-		   (insert
-		    (org-export--prepare-file-contents
-		     file lines ind minlevel
-		     (or (gethash file file-prefix)
-			 (puthash file (incf current-prefix) file-prefix))))
+		   (let ((org-inhibit-startup t)
+			 (lines
+			  (if location
+			      (org-export--inclusion-absolute-lines
+			       file  location only-contents lines)
+			    lines)))
+		     (org-mode)
+		     (insert
+		      (org-export--prepare-file-contents
+		       file lines ind minlevel
+		       (or (gethash file file-prefix)
+			   (puthash file (incf current-prefix) file-prefix)))))
 		   (org-export-expand-include-keyword
 		    (cons (list file lines) included)
 		    (file-name-directory file))
 		   (buffer-string)))))))))))))
 
+(defun org-export--inclusion-absolute-lines (file location only-contents lines)
+  "Resolve absolute lines for an included file with file-link.
+
+FILE is string file-name of the file to include.  LOCATION is a
+string name within FILE to be included (located via
+`org-link-search').  If ONLY-CONTENTS is non-nil only the
+contents of the named element will be included, as determined
+Org-Element.  If LINES is non-nil only those lines are included.
+
+Return a string of lines to be included in the format expected by
+`org-export--prepare-file-contents'."
+  (with-temp-buffer
+    (insert-file-contents file)
+    (unless (eq major-mode 'org-mode)
+      (let ((org-inhibit-startup t)) (org-mode)))
+    (condition-case err
+	;; Enforce consistent search.
+	(let ((org-link-search-must-match-exact-headline t))
+	  (org-link-search location))
+      (error
+       (error (format "%s for %s::%s" (error-message-string err) file location))))
+    (let* ((element (org-element-at-point))
+	   (contents-begin
+	    (and only-contents (org-element-property :contents-begin element))))
+      (narrow-to-region
+       (or contents-begin (org-element-property :begin element))
+       (org-element-property (if contents-begin :contents-end :end) element))
+      (when (and only-contents
+		 (memq (org-element-type element) '(headline inlinetask)))
+	;; skip planning line and property-drawer.  If a normal drawer
+	;; precedes a property-drawer both will be included.
+	;; Remaining property-drawers are removed as needed in
+	;; `org-export--prepare-file-contents'
+	(goto-char (point-min))
+	(when (org-looking-at-p org-planning-line-re) (forward-line))
+	(when (looking-at org-property-drawer-re) (goto-char (match-end 0)))
+	(unless (bolp) (forward-line))
+	(narrow-to-region (point) (point-max))))
+    (when lines
+      (org-skip-whitespace)
+      (beginning-of-line)
+      (let* ((lines (split-string lines "-"))
+	     (lbeg (string-to-number (car lines)))
+	     (lend (string-to-number (cadr lines)))
+	     (beg (if (zerop lbeg) (point-min)
+		    (goto-char (point-min))
+		    (forward-line (1- lbeg))
+		    (point)))
+	     (end (if (zerop lend) (point-max)
+		    (goto-char beg)
+		    (forward-line (1-  lend))
+		    (point))))
+	(narrow-to-region beg end)))
+    (let ((end (point-max)))
+      (goto-char (point-min))
+      (widen)
+      (let ((start-line (line-number-at-pos)))
+	(format "%d-%d"
+		start-line
+		(save-excursion
+		  (+ start-line
+		     (let ((counter 0))
+		       (while (< (point) end) (incf counter) (forward-line))
+		       counter))))))))
+
 (defun org-export--prepare-file-contents (file &optional lines ind minlevel id)
   "Prepare the contents of FILE for inclusion and return them as a string.
 
@@ -3444,6 +3527,20 @@ with footnotes is included in a document."
     (skip-chars-backward " \r\t\n")
     (forward-line)
     (delete-region (point) (point-max))
+    ;; Remove property-drawers after drawers.
+    (when (or ind minlevel)
+      (unless (eq major-mode 'org-mode)
+    	(let ((org-inhibit-startup t)) (org-mode)))
+      (goto-char (point-min))
+      (when (looking-at org-drawer-regexp)
+	(goto-char (match-end 0))
+	(search-forward-regexp org-drawer-regexp)
+	(forward-line 1)
+	(beginning-of-line))
+      (when (looking-at org-property-drawer-re)
+	(delete-region (match-beginning 0) (match-end 0))
+	(beginning-of-line))
+      (delete-region (point) (save-excursion (and (org-skip-whitespace) (point)))))
     ;; If IND is set, preserve indentation of include keyword until
     ;; the first headline encountered.
     (when ind
diff --git a/testing/examples/include.org b/testing/examples/include.org
index 186facb..c04c942 100644
--- a/testing/examples/include.org
+++ b/testing/examples/include.org
@@ -8,3 +8,28 @@ Small Org file with an include keyword.
 
 * Heading
 body
+
+* Another heading
+:PROPERTIES:
+:CUSTOM_ID: ah
+:END:
+1
+2
+3
+
+* A headline with a table
+:PROPERTIES:
+:CUSTOM_ID: ht
+:END:
+#+CAPTION: a table
+#+NAME: tbl
+| 1 |
+
+* drawer-headline
+:LOGBOOK:
+drawer
+:END:
+:PROPERTIES:
+:CUSTOM_ID: dh
+:END:
+content
diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el
index 4af3510..4706ec2 100644
--- a/testing/lisp/test-ox.el
+++ b/testing/lisp/test-ox.el
@@ -918,7 +918,62 @@ Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
 		(org-export-expand-include-keyword)
 		(org-element-map (org-element-parse-buffer)
 		    'footnote-reference
-		  (lambda (ref) (org-element-property :label ref))))))))))))
+		  (lambda (ref) (org-element-property :label ref)))))))))))
+  ;; If only-contents is non-nil only include contents of element
+  (should
+   (equal
+    "body\n"
+    (org-test-with-temp-text
+	(format "#+INCLUDE: \"%s/examples/include.org::*Heading\" :only-contents t" org-test-dir)
+      (org-export-expand-include-keyword)
+      (buffer-string))))
+  ;; Headings can be included via CUSTOM_ID
+  (should
+   (org-test-with-temp-text
+	(format "#+INCLUDE: \"%s/examples/include.org::#ah\"" org-test-dir)
+     (org-export-expand-include-keyword)
+     (goto-char (point-min))
+     (looking-at "* Another heading")))
+  ;; Named objects can be included
+  (should
+   (equal
+    "| 1 |\n"
+    (org-test-with-temp-text
+	(format "#+INCLUDE: \"%s/examples/include.org::tbl\" :only-contents t" org-test-dir)
+      (org-export-expand-include-keyword)
+      (buffer-string))))
+  ;; Including non-existing elements should result in an error
+  (should-error
+   (org-test-with-temp-text
+	(format "#+INCLUDE: \"%s/examples/include.org::*non-existing heading\"" org-test-dir)
+     (org-export-expand-include-keyword)))
+  ;; Lines work relatively to an included element
+  (should
+   (equal
+    "2\n3\n"
+    (org-test-with-temp-text
+	(format "#+INCLUDE: \"%s/examples/include.org::#ah\" :only-contents t :lines \"2-3\"" org-test-dir)
+      (org-export-expand-include-keyword)
+      (buffer-string))))
+  ;; Properties should be dropped from headlines
+  (should
+   (equal
+    (org-test-with-temp-text
+	(format "#+INCLUDE: \"%s/examples/include.org::#ht\" :only-contents t" org-test-dir)
+      (org-export-expand-include-keyword)
+      (buffer-string))
+    (org-test-with-temp-text
+	(format "#+INCLUDE: \"%s/examples/include.org::tbl\"" org-test-dir)
+      (org-export-expand-include-keyword)
+      (buffer-string))))
+  ;; Properties should be dropped, drawers should not be
+  (should
+   (equal
+    ":LOGBOOK:\ndrawer\n:END:\ncontent\n"
+    (org-test-with-temp-text
+	(format "#+INCLUDE: \"%s/examples/include.org::#dh\" :only-contents t" org-test-dir)
+      (org-export-expand-include-keyword)
+      (buffer-string)))))
 
 (ert-deftest test-org-export/expand-macro ()
   "Test macro expansion in an Org buffer."
-- 
2.1.1


  reply	other threads:[~2014-09-28 19:32 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-09-21  0:51 [patch, ox] #+INCLUDE resolves links Rasmus
2014-09-21 11:46 ` Rasmus
2014-09-21 13:53   ` Nicolas Goaziou
2014-09-21 14:46     ` Rasmus
2014-09-21 19:51       ` Nicolas Goaziou
2014-09-23 23:25     ` Rasmus
2014-09-24 21:22       ` Nicolas Goaziou
2014-09-28 19:32         ` Rasmus [this message]
2014-09-30  8:07           ` Nicolas Goaziou
2014-09-30 10:18             ` Rasmus
2014-09-30 14:29               ` Nicolas Goaziou
2014-09-30 21:48                 ` Rasmus
2014-10-01 20:03                   ` Nicolas Goaziou
2014-10-01 21:27                     ` Rasmus
2014-10-02  7:29                       ` Xavier Garrido
2014-10-02  8:55                         ` Rasmus
2014-10-02 16:30                           ` Aaron Ecay
2014-10-02 16:53                           ` Nicolas Goaziou
2014-10-02 17:47                             ` Rasmus
2014-10-02 19:11                               ` Achim Gratz
2014-10-02 20:58                                 ` Rasmus

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87d2af1qyv.fsf@gmx.us \
    --to=rasmus@gmx.us \
    --cc=emacs-orgmode@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).