1
  • I am new to elisp and am trying to write an Elisp function that calls org-pandoc-export-to-html5-pdf-and-open.

  • org-pandoc-export-to-html5-pdf-and-open is an interactive function that will write to a file, and when called via M-x, it prompts the user for a filename to enter in the minibuffer.

  • I want to pass a default value for the filename so that the user does not have to enter the name manually. The file and its path is /tmp/tmp.html.

What is the simplest way for me to do this?

I want to acknowledge that many versions of this question have been asked before, if someone automatically marks this as a duplicate. Below is a table, with some comments.

I think question 4 comes closest in spirit to this question, but it wasn't answered, and marked as a duplicate of a question which is similar in the way that australiopithecus is similar to homo sapiens sapiens - similar at root level but there is a meaningful difference.

Otherwise, my question is one of approach:

  • Qs 1,6 suggest minibuffer-setup usage
  • Q 7 suggests thing-at-point.
  • I have tried various combinations of funcall, commandp, command-execute, but the user prompt remains.
  • I have read some of the manual describing interactive calls here, and have done C-h f on the function at hand, but can't quite translate what little I understand from the manual to the desired functionality.
Question Function/topic Resolved? (And probable solution?) Useful in this case?
1. Call a function and insert text in minibuffer prompt ivy-switch-buffer insert yank into minibuffer Yes, (add-hook 'minibuffer-setup-hook 'foo) Maybe
2. Call function interactively and set initial content Default value when calling find-file Yes, let ((default-directory "/home/") No
3. How to call an interactive function and pass arguments to it from within Elisp? Use call-interactively or command-execute for describe-package, pass value xref Yes-ish - wrong argument type passed No
4. Call an interactive function without asking for user input [duplicate] Call e.g. projectile-test-project with a predefined argument, without user asking anything No, marked as duplicate of 5 Maybe - same question, unresolved
5. How to use 'interactive' arguments when calling the command from code Call an interactive function which takes user input Yes, trivially: (call-interactively 'test) No, and not sure how it answers 4!
6. Inserting Text into an Active Minibuffer Insert text into minibuffer after external command is run (like Q1) Yes, (add-hook 'minibuffer-setup-hook 'foo) Maybe - statement that minbuffer-setup-hook is the "idiomatic way"
7. emacs interactive commands with default value Default values for interactive commands, thing-at-point Yes Maybe - thing-at-point-usage?
8. call write-file interactively with prompting Use write-file with default file name Yes, condition-case-nil to fallback to default value Maybe, another answer suggests (call-interactively 'write-file (vector path))
9. emacs how to use call-interactively with parameter Call wg-save, then switch window config Yes, with (call-interactively 'my-fn t (vector arg1 arg2)) Maybe
9
  • 2
    I had a look at the ox-pandoc library and this function definition uses (interactive), and when a function does not handle its interactive inputs via its own interactive form then you will need to find out where those prompts are actually coming from -- there's no standard answer in that situation; it will simply depend on what that particular code happens to be doing. I suggest you set debug-on-quit, call the command in question, type C-g at the interactive prompt, and check the backtrace to figure out where you've ended up and how you got there.
    – phils
    Aug 30 '21 at 13:43
  • Asking for the "best" way to do something invites primarily opinion-based answers. Just ask how to do something, please. You can decide which answers you consider best.
    – Drew
    Aug 30 '21 at 13:52
  • 1
    @Drew Sure, but when a newbie post an attempt at an answer, and has comments telling them what they're doing is not considered "best-practice", will you pop up to remind everyone that "best" is an inherently subjective concept? Aug 30 '21 at 14:06
  • 1
    @NickD, Cheers, looks like I'm not contravening anything mentioned therein. Aug 30 '21 at 16:56
2

Eh, messing with the minibuffer or the window config is the wrong way to go. Any solution like that would be messy, fragile, and ugly. The right thing to do is examine the source code of the function you’re calling and understand it’s features and limitations.

Incidentally, I’m assuming that the function you care about is from the ox-pandoc melpa package. I’ve never used it before, but it looks pretty straight forward.

org-pandoc-export-to-html5-and-open just calls org-pandoc-export with a couple of the options already specified. org-pandoc-export fills in a couple more arguments and calls org-export-to-file. One of those arguments is the file name, so we don’t need to follow that chain any further.

To determine the file name, it calls org-export-output-filename (which incidentally is part of org-mode rather than ox-pandoc). org-export-output-filename picks a filename automatically if it can, and only prompts the user if it cannot. It prompts the user by calling read-file-name as is right and proper. Amongst its many virtues, read-file-name takes an optional argument that specifies the default file name to use if the user leaves the prompt empty. Sadly, org-export-output-filename specifies nil for this argument.

There are three ways that it can determine the file name automatically. The cursor could be on a subtree with a EXPORT_FILE_NAME property, the document could have a #+EXPORT_FILE_NAME keyword, or the buffer could be an indirect buffer in which case the export will overwrite the file visited by the buffer’s base buffer (?! I’m going to ignore that one.).

Adding an #+EXPORT_FILE_NAME keyword to the top of your org files seems like a really easy solution to this problem, but let’s assume that that it’s not an option for some reason.

You could very easily modify org-export-output-filename. You could just copy it into your init file and edit the copy so that it specifies the default file name when calling read-file-name. You could advise it so that it specifies a default file name, but you might not know about advise so copy–and–paste would be easier. Also, given the way this function is written, the advise would have to duplicate the body of the function anyway.

Either way, you could also send your modification back to the author of org-mode, if you make it in such a way that the modification is usable by others. If you added a variable called org-export-default-output-filename, and then used it in the org-export-output-filename function, the caller could set that variable just before kicking off an export and all exporters would do the right thing. Here’s the code you would want:

(defvar org-export-default-output-filename nil)
(defun org-export-output-file-name (extension &optional subtreep pub-dir)
  "Return output file's name according to buffer specifications.

EXTENSION is a string representing the output file extension,
with the leading dot.

With a non-nil optional argument SUBTREEP, try to determine
output file's name by looking for \"EXPORT_FILE_NAME\" property
of subtree at point.

When optional argument PUB-DIR is set, use it as the publishing
directory.

Return file name as a string."
  (let* ((visited-file (buffer-file-name (buffer-base-buffer)))
     (base-name
      (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 org-export-default-output-filename 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)))
       ((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))
    (concat output-file extension)
      output-file)))

With that in your init file, you could then write your function something like this:

(defun my-export-function ()
    (interactive)
    (let ((org-export-default-output-filename "/tmp/tmp.html"))
        (org-pandoc-export-to-html5-pdf-and-open …)))

Once you’ve verified that it works, head over to https://orgmode.org/contribute.html for information about sending them a patch.

4
  • Thanks for your thorough answer! I've been trying to follow what you've described, and have looked through the source files at the definitions of the different functions first, before trying your suggested solution. As a first go, I tried your my-export-function just to see if it would work by itself, i.e. defining the variable and function in your first block, and then defining the function in the second, minus the ellipses. Unfortunately, I'm still prompted for a filename. Sep 1 '21 at 0:05
  • Second, it seems org-pandoc-export-to-html5-pdf-and-open requires two filenames to run, one for the temporary file it uses for the org buffer I'm looking at, the next for the pdf file that is produced and opened. I'm prompted for both of these, and I'm not sure how to structure the function call for both these filenames. Sep 1 '21 at 0:06
  • Oops, just realized that by "default", you meant that if nothing was entered in the minibuffer, and you just hit Enter, that "default" filename would be used. In this case, yes, it does work as above. I have a lot more tweaks to do before I fix my own personal issue (and perhaps this particular way is not actually the best), but your solution was very useful and paedegogical. Sep 1 '21 at 0:39
  • As for a pull request to the org-mode guys, I think I am some way off suggesting this fix in such a way that it works for a majority of use cases, and still have a lot to learn about elisp. But by all means suggest this idea yourself! Sep 1 '21 at 0:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.