forked from tecosaur/org-pandoc-import
-
Notifications
You must be signed in to change notification settings - Fork 0
/
org-pandoc-import.el
386 lines (336 loc) · 17.7 KB
/
org-pandoc-import.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
;;; org-pandoc-import.el --- Stay in org-mode as much as possible -*- lexical-binding: t; -*-
;; Copyright (C) 2020 TEC
;; Author: TEC <https://github/tecosaur>
;; Maintainer: TEC <[email protected]>
;; Created: 16 Aug 2020
;; Modified: August 29, 2020
;; Version: 1.0
;; Keywords: org-mode, pandoc, convenience, files
;; Homepage: https://github.com/tecosaur/org-pandoc-import
;; Package-Requires: ((emacs "26.3"))
;;; License:
;; This file is part of org-pandoc-import, which is not part of GNU Emacs.
;;
;; org-pandoc-import is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; org-pandoc-import is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with org-pandoc-import. If not, see <https://www.gnu.org/licenses/>.
;;
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; Leverage Pandoc to make convert non-org formats to org trivial.
;; Also supplies a minor mode to do on-the-fly conversion transparently
;; and automatically, exporting to the original format on save.
;;; Code:
(require 'subr-x)
(defgroup org-pandoc-import nil
"Provides methods to convert other markup files to Org."
:tag "Org Pandoc Import"
:group 'org)
(defcustom org-pandoc-import-executable "pandoc"
"Location of the pandoc binary."
:type 'string
:group 'org-pandoc-import)
(defcustom org-pandoc-import-buffer-name "*org-pandoc-import*"
"Name of the buffer to be created for the results of pandoc's conversion.
If a function, it is called to provide a string with the input file name
as the argument."
:type '(choice string function)
:group 'org-pandoc-import)
(defcustom org-pandoc-import-setup-defaults t
"If non-nil, set up a number of default import backends."
:type 'boolean
:group 'org-pandoc-import)
(defcustom org-pandoc-import-setup-folder
(file-name-directory
(eval-when-compile
(or (bound-and-true-p byte-compile-current-file)
load-file-name)))
"The default folder to look for filters and preprocessor files in.
Affects `org-pandoc-import-filters-folder' and `org-pandoc-import-preprocessor-folder'."
:type 'string
:group 'org-pandoc-import)
(defcustom org-pandoc-import-filters-folder
(expand-file-name
"filters" org-pandoc-import-setup-folder)
"Location of Lua filters for use with pandoc.
If FITERS/backend.lua exists, it will automatically be used when backend is registered."
:type 'string
:group 'org-pandoc-import)
(defcustom org-pandoc-import-preprocessor-folder
(expand-file-name
"preprocessors" org-pandoc-import-setup-folder)
"A file to but pre-processors in.
When a new backend is defined, if PREPROCESSORS/backend.el exists it will be
loaded, and if org-pandoc-import-backend-preprocessor exists (where backend a
placeholder for the actual backend name), it will be called with the input file
as the argument, and the result used as the new input file."
:type 'string
:group 'org-pandoc-import)
(defcustom org-pandoc-import-global-args nil
"List of arguments to apply to all backends.
Accepts three types of atoms:
- strings, which are passed as-is to `call-process'
- plist keywords, which have the : replaced with single dash if the word
is one charachter, else two dashes
- functions, which are evaluated and have the result passed as one
of the arguments"
:type 'list
:group 'org-pandoc-import)
(defcustom org-pandoc-import-global-filters
(directory-files org-pandoc-import-filters-folder nil "^_")
"List of filters to apply to all backends.
Either the name of a file contained in `org-pandoc-import-filters-folder', or
an absolute path to a filter.
Filters ending in '.lua' will be called with '--lua-filter', and all other
filters with '--filter'.
By default, all files starting with '_' in `org-pandoc-import-filters-folder'
are used."
:type 'list
:group 'org-pandoc-import)
(defvar org-pandoc-import-backends nil
"List of registerd org-pandoc-import backends.")
;;;###autoload
(defmacro org-pandoc-import-backend (name &optional recognised-extensions pandoc-type filters pandoc-args)
"Create an export backend named NAME.
The backend is applied by default on files which end in a RECOGNISED-EXTENSIONS.
This calls pandoc, specifying the input format to be PANDOC-TYPE.
PANDOC-ARGS is a list of args passed to the pandoc command in the same manner
as `org-pandoc-import-global-args'.
FILTERS can be either absolute paths to pandoc filters, or names of files
within `org-pandoc-import-filters-folder'.
RECOGNISED-EXTENSIONS defaults to '(\"NAME\"), and PANDOC-TYPE to \"NAME\"."
(let* ((s-name (symbol-name name))
(recognised-extensions (or recognised-extensions `'(,s-name)))
(pandoc-type (or pandoc-type s-name))
(format-args (intern (format "org-pandoc-import-%s-args" s-name)))
(format-extensions (intern (format "org-pandoc-import-%s-extensions" s-name)))
(format-filters (intern (format "org-pandoc-import-%s-filters" s-name)))
(preprocessor (let ((preprocessor-file
(expand-file-name (concat s-name ".el") org-pandoc-import-preprocessor-folder))
(preprocerror-func (intern (format "org-pandoc-import-%s-preprocessor" s-name))))
(when (file-exists-p preprocessor-file)
(load preprocessor-file nil t)
(when (fboundp preprocerror-func)
preprocerror-func))))
(common-args (list pandoc-type 'in-file format-extensions format-args format-filters
'syncronous-p (when preprocessor `(function ,preprocessor))))
(filters (or filters
(let ((filter (expand-file-name (concat s-name ".lua")
org-pandoc-import-filters-folder)))
(when (file-exists-p filter) (list filter))))))
`(progn
(defvar ,format-args ,pandoc-args
,(format "Arguments to be passed to pandoc when processing a %s file.
This is treated the same as `org-pandoc-import-global-args'" s-name))
(defvar ,format-extensions ,recognised-extensions
,(format "File extensions to recognise as associated with %s." s-name))
(defvar ,format-filters ',filters
,(format "Either an absolute path to, or the name of a filter within `org-pandoc-import-filters-folder'
to be applied when converting from %s.
This is treated the same as `org-pandoc-import-global-filters'" s-name))
(defun ,(intern (format "org-pandoc-import-%s-as-org" s-name)) (prompty &optional in-file syncronous-p)
,(format "Parse the provided %s IN-FILE to org-mode and open in a new buffer.
Recognises files with extensions which are a member of `org-pandoc-import-%s-extensions'.
Calls pandoc with arguments listed in `org-pandoc-import-%s-args', and filters `org-pandoc-import-%s-filters'." s-name s-name s-name s-name)
(interactive "P")
(org-pandoc-import-convert prompty nil ,@common-args))
(defun ,(intern (format "org-pandoc-import-%s-to-org" s-name)) (prompty &optional in-file out-file syncronous-p)
,(format "Parse the provided %s IN-FILE to org-mode and save to OUT-FILE - defulting to (file-name-base IN-FILE).org.
Recognises files with extensions which are a member of `org-pandoc-import-%s-extensions'.
Calls pandoc with arguments listed in `org-pandoc-import-%s-args', and filters `org-pandoc-import-%s-filters'." s-name s-name s-name s-name)
(interactive "P")
(org-pandoc-import-convert prompty (or out-file t) ,@common-args))
(add-to-list 'org-pandoc-import-backends ',name))))
(defun org-pandoc-import-convert (prompty out-file pandoc-type &optional in-file expected-extensions args filters syncronous-p preprocessor)
"Call pandoc on an IN-FILE.
Determines the relevant paramaters to convert IN-FILE of type PANDOC-TYPE to
either OUT-FILE, or a buffer (when OUT-FILE is nil).
If PROMPTY is non-nill, then the value of IN-FILE and (if applicable) OUT-FILE
will be always prompted for. A prompt for IN-FILE is also triggered when
IN-FILE is nil, or its extension is not a member of EXPECTED-EXTENSIONS.
A prompt for OUT-FILE is triggered when OUT-FILE is t, or the name of a
pre-existing file. Pandoc is then called with arguments from the list ARGS,
as described in `org-pandoc-import-global-args', and filters named in the list
FILTERS --- which can be either absolute paths to pandoc filters, or names of
files within `org-pandoc-import-filters-folder'.
If preprocessor is given, and a function, it is run with the value of IN-FILE.
The value returned is used as the new IN-FILE."
(let* ((in-file (expand-file-name
(or in-file
(if (and (not prompty)
(buffer-file-name)
(if expected-extensions
(member (file-name-extension (buffer-file-name))
expected-extensions))
t)
(buffer-file-name)
(read-file-name "File to convert: " nil nil t
(when expected-extensions
(concat "." (car expected-extensions))))))))
(in-file-processed (if (and preprocessor (functionp preprocessor))
(funcall preprocessor in-file)
in-file))
(in-file-org (concat (file-name-sans-extension in-file) ".org"))
(out-file (if (eq t out-file)
(if prompty
(read-file-name "File to write: "
(file-name-directory in-file)
nil nil
(concat (file-name-base in-file) ".org"))
in-file-org)
out-file))
(filter-args nil))
(if (and out-file (file-exists-p out-file))
(unless (yes-or-no-p (format "Overwrite file %s? "
(file-relative-name
out-file
(file-name-directory in-file))))
(setq out-file
(read-file-name "File to write: "
(file-name-directory in-file)))))
(dolist (filter (append filters org-pandoc-import-global-filters))
(setq filter-args
(append filter-args
(list (pcase (file-name-extension filter)
("lua" "--lua-filter")
(_ "--filter"))
(if (= ?/ (aref filter 0)) filter
(expand-file-name filter org-pandoc-import-filters-folder))))))
(org-pandoc-import-run-convert
(org-pandoc-import-generate-convert-arguments
in-file-processed pandoc-type out-file (append args filter-args))
in-file-processed out-file syncronous-p)))
(defun org-pandoc-import-run-convert (arguments in-file &optional out-file syncronous-p)
"Call pandoc on IN-FILE with ARGUMENTS, creating OUT-FILE if given.
`call-process' is used instead of `start-process' if SYNCRONOUS-P is non-nil."
(let* ((pandoc-buffer (generate-new-buffer
(if (functionp org-pandoc-import-buffer-name)
(funcall org-pandoc-import-buffer-name in-file)
org-pandoc-import-buffer-name)))
(default-directory (file-name-directory in-file))
(process
(if syncronous-p
(apply #'call-process
org-pandoc-import-executable
nil
pandoc-buffer
nil
arguments)
(apply #'start-process
"org-pandoc-import"
pandoc-buffer
org-pandoc-import-executable
arguments))))
(unless syncronous-p
(set-process-sentinel process (org-pandoc-import-process-sentinel pandoc-buffer out-file (time-to-seconds (current-time)))))))
(defun org-pandoc-import-process-sentinel (process-buffer &optional out-file start-time-seconds)
"Creats a lambda sentinel for a pandoc process, outputing to PROCESS-BUFFER.
If OUT-FILE is given, kill the PROCESS-BUFFER and use the file in its place.
When START-TIME-SECONDS is given, a messege is generated indicating the total
time elapsed."
(lambda (process _signal)
(pcase (process-status process)
('exit (if out-file
(progn (find-file out-file)
(kill-buffer process-buffer))
(switch-to-buffer process-buffer)
(goto-char (point-min)))
(when start-time-seconds
(message "Converted docunent in %3fs" (- (time-to-seconds (current-time)) start-time-seconds)))
(org-mode))
((or 'stop 'signal 'failed)
(user-error "The pandoc process to create %s has exited unexpectedly" out-file)
(switch-to-buffer process-buffer)))))
(defun org-pandoc-import-generate-convert-arguments (in-file target-format &optional out-file arguments-list)
"Format the provided arguments to be passed to pandoc.
Have pandoc convert IN-FILE of pandoc type TARGET-FORMAT to the org file
OUT-FILE (if given), with arguments given by ARGUMENTS-LIST."
(let (arguments)
(dolist (element (reverse (append arguments-list org-pandoc-import-global-args)))
(push
(cond
((stringp element) element)
((keywordp element) (let ((keyword (substring (symbol-name element) 1)))
(pp keyword)
(concat (if (= 1 (length keyword)) "-" "--")
keyword)))
((functionp element) (funcall element in-file target-format)))
arguments))
(append (list "-f" target-format
"-t" "org")
(when out-file
(list "-o" out-file))
arguments
(list in-file))))
;;;###autoload
(defun org-pandoc-import-as-org (prompty &optional in-file syncronous-p)
"Parse the provided file to `org-mode', and open in a new buffer.
With PROMPTY (given by the universal argument), always prompt for the IN-FILE
to act on.
This only works so long as these is backend registered in
`org-pandoc-import-backends' associated with the extension of the selected file.
See org-pandoc-import-{backend}-as-org for information on a particular backend.
When SYNCRONOUS-P is set, the pandoc process is run in a blocking manner."
(interactive "P")
(if-let ((backend (org-pandoc-import-find-associated-backend (or in-file (buffer-file-name)))))
(funcall (intern (format "org-pandoc-import-%s-as-org" (symbol-name backend)))
prompty in-file syncronous-p)
(funcall #'org-pandoc-import-as-org prompty (read-file-name "File to convert: " nil nil t) syncronous-p)))
;;;###autoload
(defun org-pandoc-import-to-org (prompty &optional in-file out-file syncronous-p)
"Parse the provided file to an `org-mode' file, and open.
With PROMPTY (given by the universal argument), always prompt for the IN-FILE to
act on, and the where to save the new Org file.
The result is saved to OUT-FILE, which defaults to IN-FILE but with the .org
extension.
This only works so long as these is backend registered in
`org-pandoc-import-backends' associated with the extension of the selected file.
See org-pandoc-import-{backend}-as-org for information on a particular backend.
When SYNCRONOUS-P is set, the pandoc process is run in a blocking manner."
(interactive "P")
(if-let ((backend (org-pandoc-import-find-associated-backend (or in-file (buffer-file-name)))))
(funcall (intern (format "org-pandoc-import-%s-to-org" (symbol-name backend)))
prompty in-file out-file syncronous-p)
(funcall #'org-pandoc-import-to-org prompty (read-file-name "File to convert: " nil nil t) syncronous-p)))
(defun org-pandoc-import-find-associated-backend (file)
"Find the backend symbol from associated with FILE's extension.
The backend is found from searching `org-pandoc-import-backends', and is nil
if no such match could be found."
(when file
(let* ((ext (file-name-extension file))
(available-backends
(seq-filter (lambda (backend)
(member ext (symbol-value
(intern
(format "org-pandoc-import-%s-extensions"
(symbol-name backend))))))
org-pandoc-import-backends))
(the-backend
((lambda () (if (length> available-backends 1)
(intern (completing-read
"Import from: " available-backends nil t))
(car available-backends))))))
the-backend)))
(dont-compile
(when org-pandoc-import-setup-defaults
(org-pandoc-import-backend markdown '("md" "markdown"))
(org-pandoc-import-backend latex '("tex" "latex"))
(org-pandoc-import-backend rst)
(org-pandoc-import-backend odt)
(org-pandoc-import-backend docx)
(org-pandoc-import-backend rmarkdown '("rmd" "Rmd") "markdown")
(org-pandoc-import-backend ipynb)
(org-pandoc-import-backend csv)
(org-pandoc-import-backend tsv '("tsv") "csv")
(org-pandoc-import-backend asciidoc '("adoc" "asciidoc" "txt") "docbook")))
(provide 'org-pandoc-import)
;;; org-pandoc-import.el ends here