From mboxrd@z Thu Jan 1 00:00:00 1970 From: Rasmus Subject: Re: [patch, ox] #+INCLUDE resolves links Date: Thu, 02 Oct 2014 10:55:18 +0200 Message-ID: <87bnpux349.fsf@gmx.us> References: <87k34x6bjd.fsf@gmx.us> <87lhpdurfh.fsf@gmx.us> <87bnq984hd.fsf@nicolasgoaziou.fr> <87bnq5zzp7.fsf@gmx.us> <87oau4ems5.fsf@nicolasgoaziou.fr> <87d2af1qyv.fsf@gmx.us> <87tx3po7kj.fsf@nicolasgoaziou.fr> <87mw9hcsy9.fsf@gmx.us> <87zjdh89ll.fsf@nicolasgoaziou.fr> <871tqskce9.fsf@gmx.us> <87y4sz7e1w.fsf@nicolasgoaziou.fr> <87lhoz7a5k.fsf@gmx.us> <542CFECD.5000609@gmail.com> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:47508) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XZcAm-0002S9-GS for emacs-orgmode@gnu.org; Thu, 02 Oct 2014 04:55:45 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1XZcAh-0006X3-7A for emacs-orgmode@gnu.org; Thu, 02 Oct 2014 04:55:40 -0400 Received: from mout.gmx.net ([212.227.17.20]:54511) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XZcAg-0006Wy-S3 for emacs-orgmode@gnu.org; Thu, 02 Oct 2014 04:55:35 -0400 In-Reply-To: <542CFECD.5000609@gmail.com> (Xavier Garrido's message of "Thu, 02 Oct 2014 09:29:17 +0200") List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: xavier.garrido@gmail.com Cc: emacs-orgmode@gnu.org, mail@nicolasgoaziou.fr --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Xavier Garrido writes: > Following the discussion here > http://article.gmane.org/gmane.emacs.orgmode/91322/match=3Dimproved+way, > I have quickly tested the patch and it perfectly fits my needs. So > thanks again. I'm happy that it works well for you. > Just one remark : there is an undefined @ref tag in org.texi (line > 10011) file that should referred to "search option in file > link". Compilation fails due to error in documentation generation. Shoot. Fixed in attached (tested with texi2pdf). =E2=80=94Rasmus PS: How can I get commit access? I'm starting to feel like the Nick K of the org-list. --=20 S=C3=A5dan en god dansk lagereddike kan man slet ikke bruge mere --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-ox-Allow-file-links-with-INCLUDE-keyword.patch >From 17a647c28f07a505670a7e0d0cc341d700f7bb87 Mon Sep 17 00:00:00 2001 From: Rasmus 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 | 17 +++++++ doc/orgguide.texi | 9 +++- lisp/org.el | 9 ++-- lisp/ox.el | 117 +++++++++++++++++++++++++++++++++++++++---- testing/examples/include.org | 25 +++++++++ testing/lisp/test-ox.el | 59 +++++++++++++++++++++- 6 files changed, 220 insertions(+), 16 deletions(-) diff --git a/doc/org.texi b/doc/org.texi index 7d98d51..537b21b 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -10008,6 +10008,23 @@ 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 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.} +(@pxref{Search options}). If the @code{:only-contents} property is non-nil, +only the contents of the requested element will be included, omitting +properties drawer and planning-line if present. 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 9a243b5..9228ba0 100755 --- a/lisp/org.el +++ b/lisp/org.el @@ -20525,9 +20525,12 @@ 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 90c623e..bc3792d 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -3325,13 +3325,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]+\\)?\\)\"" @@ -3391,17 +3403,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. @@ -3448,6 +3531,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..915a5a6 100644 --- a/testing/lisp/test-ox.el +++ b/testing/lisp/test-ox.el @@ -918,7 +918,64 @@ 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 + (concat + (format "#+INCLUDE: \"%s/examples/include.org::*Heading\" " org-test-dir) + ":only-contents t") + (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.2 --=-=-=--