Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

281 lines
11 KiB

  1. ;;; polymode-weave.el --- Weaving facilities for polymodes -*- lexical-binding: t -*-
  2. ;;
  3. ;; Copyright (C) 2013-2019, Vitalie Spinu
  4. ;; Author: Vitalie Spinu
  5. ;; URL: https://github.com/polymode/polymode
  6. ;;
  7. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  8. ;;
  9. ;; This file is *NOT* part of GNU Emacs.
  10. ;;
  11. ;; This program is free software; you can redistribute it and/or
  12. ;; modify it under the terms of the GNU General Public License as
  13. ;; published by the Free Software Foundation; either version 3, or
  14. ;; (at your option) any later version.
  15. ;;
  16. ;; This program is distributed in the hope that it will be useful,
  17. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. ;; General Public License for more details.
  20. ;;
  21. ;; You should have received a copy of the GNU General Public License
  22. ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
  23. ;;
  24. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  25. ;;
  26. ;;; Commentary:
  27. ;;
  28. ;;; Code:
  29. (require 'polymode-core)
  30. (require 'polymode-classes)
  31. (defgroup polymode-weave nil
  32. "Polymode Weavers"
  33. :group 'polymode)
  34. (define-obsolete-variable-alias 'polymode-weave-output-file-format 'polymode-weaver-output-file-format "2018-08")
  35. (defcustom polymode-weaver-output-file-format "%s-woven"
  36. "Format of the weaved files.
  37. %s is substituted with the current file name sans extension."
  38. :group 'polymode-weave
  39. :type 'string)
  40. (defclass pm-weaver (pm-root)
  41. ((from-to
  42. :initarg :from-to
  43. :initform '()
  44. :type list
  45. :custom list
  46. :documentation
  47. "
  48. Input-output specifications. An alist with elements of the
  49. form (id reg-from ext-to doc command) or (id . selector).
  50. In both cases ID is the unique identifier of the spec. In
  51. the former case REG-FROM is a regexp used to identify if
  52. current file can be weaved with the spec. EXT-TO is the
  53. extension of the output file. DOC is a short help string
  54. used for interactive completion and messages. COMMAND is a
  55. weaver specific specific command. It can contain the
  56. following format specs:
  57. %i - input file (no dir)
  58. %I - input file (full path)
  59. %o - output file (no dir)
  60. %O - output file (full path)
  61. %b - output file (base name only)
  62. %t - 4th element of the :to spec
  63. When specification is of the form (id . selector), SELECTOR
  64. is a function of variable arguments with first two arguments
  65. being ACTION and ID of the specification. This function is
  66. called in a buffer visiting input file. ACTION is a symbol
  67. and can one of the following:
  68. match - must return non-nil if this specification
  69. applies to the file that current buffer is visiting,
  70. or :nomatch if specification does not apply.
  71. regexp - return a string which is used to match input
  72. file name. If nil, `match' selector must return
  73. non-nil value. This selector is ignored if `match'
  74. returned non-nil.
  75. output-file - return an output file name or a list of
  76. file names. Receives input-file as argument. If this
  77. command returns nil, the output is built from the
  78. input file name and value of 'output-ext command.
  79. This selector can also return a function. This
  80. function will be called in the callback or sentinel of
  81. the weaving process after the weaving was
  82. completed. This function should sniff the output of
  83. the process for errors or file names. It must return a
  84. file name, a list of file names or nil if no such
  85. files have been detected.
  86. ext - extension of output file. If nil and
  87. `output' also returned nil, the exporter won't be able
  88. to identify the output file and no automatic display
  89. or preview will be available.
  90. doc - return documentation string
  91. command - return a string to be used instead of
  92. the :from command. If nil, :from spec command is used.")
  93. (function
  94. :initarg :function
  95. :initform (lambda (command id)
  96. (error "No weaving function declared for this weaver"))
  97. :type (or symbol function)
  98. :documentation
  99. "Function to perform the weaving. Must take 2 arguments
  100. COMMAND and ID. COMMAND is the 5th argument of :from-to spec
  101. with all the formats substituted. ID is the id the
  102. corresponding element in :from-to spec.
  103. If this function returns a filename that file will be
  104. displayed to the user."))
  105. "Root weaver class.")
  106. (defclass pm-callback-weaver (pm-weaver)
  107. ((callback
  108. :initarg :callback
  109. :initform nil
  110. :type (or symbol function)
  111. :documentation
  112. "Callback function to be called by :function. There is no
  113. default callback. Callbacks must return the output file."))
  114. "Class to represent weavers that call processes spanned by
  115. Emacs.")
  116. (defclass pm-shell-weaver (pm-weaver)
  117. ((function
  118. :initform 'pm-default-shell-weave-function)
  119. (sentinel
  120. :initarg :sentinel
  121. :initform 'pm-default-shell-weave-sentinel
  122. :type (or symbol function)
  123. :documentation
  124. "Sentinel function to be called by :function when a shell
  125. call is involved. Sentinel must return the output file
  126. name.")
  127. (quote
  128. :initarg :quote
  129. :initform nil
  130. :type boolean
  131. :documentation "Non-nil when file arguments must be quoted
  132. with `shell-quote-argument'."))
  133. "Class for weavers that call external processes.")
  134. (defun pm-default-shell-weave-function (command sentinel from-to-id &rest _args)
  135. "Run weaving COMMAND interactively with SENTINEL.
  136. Run command in a buffer (in comint-shell-mode) so that it accepts
  137. user interaction. This is a default function in all weavers that
  138. call a shell command. FROM-TO-ID is the idea of the weaver. ARGS
  139. are ignored."
  140. (pm--run-shell-command command sentinel "*polymode weave*"
  141. (concat "weaving " from-to-id " with command:\n\n "
  142. command "\n\n")))
  143. ;;; METHODS
  144. (declare-function pm-export "polymode-export")
  145. (cl-defgeneric pm-weave (weaver from-to-id &optional ifile)
  146. "Weave current FILE with WEAVER.
  147. WEAVER is an object of class `pm-weaver'. EXPORT is a list of the
  148. form (FROM TO) suitable to be passed to `polymode-export'. If
  149. EXPORT is provided, corresponding exporter's (from to)
  150. specification will be called.")
  151. (cl-defmethod pm-weave ((weaver pm-weaver) from-to-id &optional ifile)
  152. (pm--process-internal weaver from-to-id nil ifile))
  153. (cl-defmethod pm-weave ((weaver pm-callback-weaver) fromto-id &optional ifile)
  154. (let ((cb (pm--wrap-callback weaver :callback ifile))
  155. ;; with transitory output, callback might not run
  156. (pm--export-spec (and pm--output-not-real pm--export-spec)))
  157. (pm--process-internal weaver fromto-id nil ifile cb)))
  158. (cl-defmethod pm-weave ((weaver pm-shell-weaver) fromto-id &optional ifile)
  159. (let ((cb (pm--wrap-callback weaver :sentinel ifile))
  160. ;; with transitory output, callback might not run
  161. (pm--export-spec (and pm--output-not-real pm--export-spec)))
  162. (pm--process-internal weaver fromto-id nil ifile cb (eieio-oref weaver 'quote))))
  163. ;; UI
  164. (defvar-local pm--weaver-hist nil)
  165. (defvar-local pm--weave:fromto-hist nil)
  166. (defvar-local pm--weave:fromto-last nil)
  167. (defun polymode-weave (&optional from-to)
  168. "Weave current file.
  169. First time this command is called in a buffer the user is asked
  170. for the weaver to use from a list of known weavers.
  171. FROM-TO is the id of the specification declared in :from-to slot
  172. of the current weaver. If the weaver hasn't been set yet, set the
  173. weaver with `polymode-set-weaver'. You can always change the
  174. weaver manually by invoking `polymode-set-weaver'.
  175. If `from-to' dismissing detect automatically based on current
  176. weaver :from-to specifications. If this detection is ambiguous
  177. ask the user.
  178. When `from-to' is universal argument ask user for specification
  179. for the specification. See also `pm-weaveer' for the complete
  180. specification."
  181. (interactive "P")
  182. (cl-flet ((name.id (el) (cons (funcall (cdr el) 'doc (car el)) (car el))))
  183. (let* ((weaver (symbol-value (or (eieio-oref pm/polymode 'weaver)
  184. (polymode-set-weaver))))
  185. (case-fold-search t)
  186. (opts (mapcar #'name.id (pm--selectors weaver :from-to)))
  187. (ft-id
  188. (cond
  189. ;; A. guess from-to spec
  190. ((null from-to)
  191. (or
  192. ;; 1. repeated weaving; don't ask
  193. pm--weave:fromto-last
  194. ;; 2. select :from entries which match to current file
  195. (let ((matched (pm--matched-selectors weaver :from-to)))
  196. (when matched
  197. (if (> (length matched) 1)
  198. (cdr (pm--completing-read "Multiple `from-to' specs matched. Choose one: "
  199. (mapcar #'name.id matched)))
  200. (caar matched))))
  201. ;; 3. nothing matched, ask
  202. (let* ((prompt "No `from-to' specs matched. Choose one: ")
  203. (sel (pm--completing-read prompt opts nil t nil 'pm--weave:fromto-hist)))
  204. (cdr sel))))
  205. ;; B. C-u, force a :from-to spec
  206. ((equal from-to '(4))
  207. (cdr (if (> (length opts) 1)
  208. (pm--completing-read "Weaver type: " opts nil t nil 'pm--weave:fromto-hist)
  209. (car opts))))
  210. ;; C. string
  211. ((stringp from-to)
  212. (if (assoc from-to (eieio-oref weaver 'from-to))
  213. from-to
  214. (error "Cannot find `from-to' spec '%s' in %s weaver"
  215. from-to (eieio-object-name weaver))))
  216. (t (error "'from-to' argument must be nil, universal argument or a string")))))
  217. (setq-local pm--weave:fromto-last ft-id)
  218. (pm-weave weaver ft-id))))
  219. (defmacro polymode-register-weaver (weaver default &rest configs)
  220. "Add WEAVER to :weavers slot of all config objects in CONFIGS.
  221. When DEFAULT is non-nil, also make weaver the default WEAVER for
  222. each polymode in CONFIGS."
  223. `(dolist (pm ',configs)
  224. (object-add-to-list (symbol-value pm) :weavers ',weaver)
  225. (when ,default (oset (symbol-value pm) :weaver ',weaver))))
  226. (defun polymode-set-weaver ()
  227. "Set the current weaver for this polymode."
  228. (interactive)
  229. (unless pm/polymode
  230. (error "No pm/polymode object found. Not in polymode buffer?"))
  231. (let* ((weavers (pm--abrev-names
  232. "pm-weaver/\\|-weaver$"
  233. (delete-dups (pm--oref-with-parents pm/polymode :weavers))))
  234. (sel (pm--completing-read "Choose weaver: " weavers nil t nil 'pm--weaver-hist))
  235. (out (intern (cdr sel))))
  236. (setq pm--weaver-hist (delete-dups pm--weaver-hist))
  237. (setq-local pm--weave:fromto-last nil)
  238. (oset pm/polymode :weaver out)
  239. out))
  240. (provide 'polymode-weave)
  241. ;;; polymode-weave.el ends here