diff --git a/doc/org.texi b/doc/org.texi index b3f094b83..ca92d8742 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -10465,6 +10465,12 @@ be executed during export even though the subtree is not exported. The title to be shown. You can use several such keywords for long titles. @end table +@item EXPORT_FILE_NAME +@cindex #+EXPORT_FILE_NAME +The name of the output file to be generated. By default, it is taken from +the file associated to the buffer, when possible, or asked to you otherwise. +In all cases, the extension is ignored, and a back-end specific one is added. + The @code{#+OPTIONS} keyword is a compact@footnote{If you want to configure many options this way, you can use several @code{#+OPTIONS} lines.} form that recognizes the following arguments: @@ -10636,9 +10642,9 @@ Toggle inclusion of tables (@code{org-export-with-tables}). When exporting only a subtree, each of the previous keywords@footnote{With the exception of @samp{SETUPFILE}.} can be overridden locally by special node properties. These begin with @samp{EXPORT_}, followed by the name of the -keyword they supplant. For example, @samp{DATE} and @samp{OPTIONS} keywords -become, respectively, @samp{EXPORT_DATE} and @samp{EXPORT_OPTIONS} -properties. +keyword they supplant, unless the keyword already beging with @samp{EXPORT_}. +For example, @samp{DATE} and @samp{EXPORT_FILE_NAME} keywords become, +respectively, @samp{EXPORT_DATE} and @samp{EXPORT_FILE_NAME} properties. @cindex #+BIND @vindex org-export-allow-bind-keywords @@ -10647,13 +10653,6 @@ can become buffer-local during export by using the BIND keyword. Its syntax is @samp{#+BIND: variable value}. This is particularly useful for in-buffer settings that cannot be changed using specific keywords. -@cindex property, EXPORT_FILE_NAME -The name of the output file to be generated is taken from the file associated -to the buffer, when possible, or asked to you otherwise. For subtree export, -you can also set @code{EXPORT_FILE_NAME} property. In all cases, only the -base name of the file is retained, and a back-end specific extension is -added. - @node Table of contents @section Table of contents @cindex table of contents diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index dceecfefd..7028893d1 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -128,6 +128,10 @@ as descriptions of links, a.k.a. image links. See its docstring for details. **** Add global macros through ~org-export-global-macros~ With this variable, one can define macros available for all documents. +**** New keyword ~#+EXPORT_FILE_NAME~ +Simiralry to ~:EXPORT_FILE_NAME:~ property, this keyword allow to +specify the name of the output file upon exporting the document. This +has also an effect on publishing. **** Horizontal rules are no longer ignored in LaTeX table math mode *** ~org-list-to-generic~ includes a new property: ~:ifmt~ diff --git a/lisp/ox.el b/lisp/ox.el index e2ad885b3..674bcf5b9 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -6184,29 +6184,37 @@ directory. Return file name as a string." (let* ((visited-file (buffer-file-name (buffer-base-buffer))) (base-name - ;; File name may come from EXPORT_FILE_NAME subtree - ;; property. - (file-name-sans-extension - (or (and subtreep (org-entry-get nil "EXPORT_FILE_NAME" 'selective)) - ;; File name may be extracted from buffer's associated - ;; file, if any. - (and visited-file (file-name-nondirectory visited-file)) - ;; Can't determine file name on our own: Ask user. - (read-file-name - "Output file: " pub-dir nil nil nil - (lambda (name) - (string= (file-name-extension name t) extension)))))) + (concat + (file-name-sans-extension + (or + ;; Check EXPORT_FILE_NAME subtree property. + (and subtreep (org-entry-get nil "EXPORT_FILE_NAME" 'selective)) + ;; Check #+EXPORT_FILE_NAME keyword. + (org-with-point-at (point-min) + (catch :found + (let ((case-fold-search t)) + (while (re-search-forward + "^[ \t]*#\\+EXPORT_FILE_NAME:[ \t]+\\S-" nil t) + (let ((element (org-element-at-point))) + (when (eq 'keyword (org-element-type element)) + (throw :found + (org-element-property :value element)))))))) + ;; Extract from buffer's associated file, if any. + (and visited-file (file-name-nondirectory visited-file)) + ;; Can't determine file name on our own: ask user. + (read-file-name + "Output file: " pub-dir nil nil nil + (lambda (n) (string= extension (file-name-extension n t)))))) + extension)) (output-file ;; Build file name. Enforce EXTENSION over whatever user ;; may have come up with. PUB-DIR, if defined, always has ;; precedence over any provided path. (cond - (pub-dir - (concat (file-name-as-directory pub-dir) - (file-name-nondirectory base-name) - extension)) - ((file-name-absolute-p base-name) (concat base-name extension)) - (t (concat (file-name-as-directory ".") base-name extension))))) + (pub-dir (concat (file-name-as-directory pub-dir) + (file-name-nondirectory base-name))) + ((file-name-absolute-p base-name) base-name) + (t base-name)))) ;; If writing to OUTPUT-FILE would overwrite original file, append ;; EXTENSION another time to final name. (if (and visited-file (file-equal-p visited-file output-file)) diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el index c1fbcda84..4238a7063 100644 --- a/testing/lisp/test-ox.el +++ b/testing/lisp/test-ox.el @@ -961,20 +961,34 @@ Text" ;; Export from a file: name is built from original file name. (should (org-test-with-temp-text-in-file "Test" - (equal (concat (file-name-as-directory ".") - (file-name-nondirectory - (file-name-sans-extension (buffer-file-name)))) - (file-name-sans-extension (org-export-output-file-name ".ext"))))) + (equal (file-name-base (buffer-file-name)) + (file-name-base (org-export-output-file-name ".ext"))))) + ;; When #+EXPORT_FILE_NAME is defined, use it. + (should + (equal "test.ext" + (org-test-with-temp-text-in-file "#+EXPORT_FILE_NAME: test" + (org-export-output-file-name ".ext" t)))) ;; When exporting to subtree, check EXPORT_FILE_NAME property first. (should - (org-test-with-temp-text-in-file - "* Test\n :PROPERTIES:\n :EXPORT_FILE_NAME: test\n :END:" - (equal (org-export-output-file-name ".ext" t) "./test.ext"))) + (equal "test.ext" + (org-test-with-temp-text-in-file + "* Test\n :PROPERTIES:\n :EXPORT_FILE_NAME: test\n :END:" + (org-export-output-file-name ".ext" t)))) + (should + (equal "property.ext" + (org-test-with-temp-text + "#+EXPORT_FILE_NAME: keyword +* Test +:PROPERTIES: +:EXPORT_FILE_NAME: property +:END:" + (org-export-output-file-name ".ext" t)))) ;; From a buffer not associated to a file, too. (should - (org-test-with-temp-text - "* Test\n :PROPERTIES:\n :EXPORT_FILE_NAME: test\n :END:" - (equal (org-export-output-file-name ".ext" t) "./test.ext"))) + (equal "test.ext" + (org-test-with-temp-text + "* Test\n :PROPERTIES:\n :EXPORT_FILE_NAME: test\n :END:" + (org-export-output-file-name ".ext" t)))) ;; When provided name is absolute, preserve it. (should (org-test-with-temp-text @@ -983,15 +997,23 @@ Text" (file-name-absolute-p (org-export-output-file-name ".ext" t)))) ;; When PUB-DIR argument is provided, use it. (should - (org-test-with-temp-text-in-file "Test" - (equal (file-name-directory - (org-export-output-file-name ".ext" nil "dir/")) - "dir/"))) + (equal "dir/" + (org-test-with-temp-text-in-file "Test" + (file-name-directory + (org-export-output-file-name ".ext" nil "dir/"))))) + ;; PUB-DIR has precedence over EXPORT_FILE_NAME keyword or property. + (should + (equal "pub-dir/" + (org-test-with-temp-text-in-file + "#+EXPORT_FILE_NAME: /dir/keyword\nTest" + (file-name-directory + (org-export-output-file-name ".ext" nil "pub-dir/"))))) ;; When returned name would overwrite original file, add EXTENSION ;; another time. (should - (org-test-at-id "75282ba2-f77a-4309-a970-e87c149fe125" - (equal (org-export-output-file-name ".org") "./normal.org.org")))) + (equal "normal.org.org" + (org-test-at-id "75282ba2-f77a-4309-a970-e87c149fe125" + (org-export-output-file-name ".org"))))) (ert-deftest test-org-export/expand-include () "Test file inclusion in an Org buffer."