emacs-utils.scm 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2014, 2018 Mark H Weaver <mhw@netris.org>
  3. ;;; Copyright © 2014 Alex Kost <alezost@gmail.com>
  4. ;;; Copyright © 2018, 2020, 2022 Maxim Cournoyer <maxim.cournoyer@gmail.com>
  5. ;;; Copyright © 2019 Liliana Marie Prikler <liliana.prikler@gmail.com>
  6. ;;; Copyright © 2022 Fredrik Salomonsson <plattfot@posteo.net>
  7. ;;;
  8. ;;; This file is part of GNU Guix.
  9. ;;;
  10. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  11. ;;; under the terms of the GNU General Public License as published by
  12. ;;; the Free Software Foundation; either version 3 of the License, or (at
  13. ;;; your option) any later version.
  14. ;;;
  15. ;;; GNU Guix is distributed in the hope that it will be useful, but
  16. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  17. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. ;;; GNU General Public License for more details.
  19. ;;;
  20. ;;; You should have received a copy of the GNU General Public License
  21. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  22. (define-module (guix build emacs-utils)
  23. #:use-module (guix build utils)
  24. #:use-module (ice-9 format)
  25. #:use-module (ice-9 popen)
  26. #:use-module (ice-9 rdelim)
  27. #:use-module (srfi srfi-34)
  28. #:use-module (srfi srfi-35)
  29. #:export (%emacs
  30. emacs-batch-eval
  31. emacs-batch-edit-file
  32. emacs-batch-disable-compilation
  33. emacs-batch-script
  34. emacs-batch-error?
  35. emacs-batch-error-message
  36. emacs-generate-autoloads
  37. emacs-byte-compile-directory
  38. emacs-compile-directory
  39. emacs-header-parse
  40. as-display
  41. emacs-substitute-sexps
  42. emacs-substitute-variables))
  43. ;;; Commentary:
  44. ;;;
  45. ;;; Tools to programmatically edit files using Emacs,
  46. ;;; e.g. to replace entire s-expressions in elisp files.
  47. ;;;
  48. ;;; Code:
  49. (define %emacs
  50. ;; The `emacs' command.
  51. (make-parameter "emacs"))
  52. (define (expr->string expr)
  53. "Converts EXPR, an expression, into a string."
  54. (if (string? expr)
  55. expr
  56. (format #f "~s" expr)))
  57. (define* (emacs-batch-eval expr #:key dynamic?)
  58. "Run Emacs in batch mode, and execute the Elisp code EXPR. If DYNAMIC? is
  59. true, evaluate using dynamic scoping."
  60. (invoke (%emacs) "--quick" "--batch"
  61. (format #f "--eval=(eval '~a ~:[t~;nil~])"
  62. (expr->string expr) dynamic?)))
  63. (define (emacs-batch-edit-file file expr)
  64. "Load FILE in Emacs using batch mode, and execute the elisp code EXPR."
  65. (invoke (%emacs) "--quick" "--batch"
  66. (string-append "--visit=" file)
  67. (string-append "--eval=" (expr->string expr))))
  68. (define* (emacs-batch-disable-compilation file #:key native?)
  69. "Disable byte compilation for FILE.
  70. If NATIVE?, only disable native compilation."
  71. (emacs-batch-edit-file file
  72. `(progn
  73. (add-file-local-variable ',(if native?
  74. 'no-native-compile
  75. 'no-byte-compile)
  76. t)
  77. (basic-save-buffer))))
  78. (define-condition-type &emacs-batch-error &error
  79. emacs-batch-error?
  80. (message emacs-batch-error-message))
  81. (define (emacs-batch-script expr)
  82. "Execute the Elisp code EXPR in Emacs batch mode and return output."
  83. (let* ((error-pipe (pipe))
  84. (port (parameterize ((current-error-port (cdr error-pipe)))
  85. (open-pipe*
  86. OPEN_READ
  87. (%emacs) "--quick" "--batch"
  88. (string-append "--eval=" (expr->string expr)))))
  89. (output (read-string port))
  90. (status (close-pipe port)))
  91. (close-port (cdr error-pipe))
  92. (unless (zero? status)
  93. (raise (condition (&emacs-batch-error
  94. (message (read-string (car error-pipe)))))))
  95. output))
  96. (define (emacs-generate-autoloads name directory)
  97. "Generate autoloads for Emacs package NAME placed in DIRECTORY."
  98. (let* ((file (string-append directory "/" name "-autoloads.el"))
  99. (expr `(let ((backup-inhibited t)
  100. (generated-autoload-file ,file))
  101. (cond
  102. ((require 'loaddefs-gen nil t)
  103. ;; Emacs >= 29
  104. (loaddefs-generate ,directory ,file))
  105. ((fboundp 'make-directory-autoloads)
  106. ;; Emacs 28
  107. (make-directory-autoloads ,directory ,file))
  108. (t (update-directory-autoloads ,directory))))))
  109. (emacs-batch-eval expr #:dynamic? #t)))
  110. (define* (emacs-byte-compile-directory dir)
  111. "Byte compile all files in DIR and its sub-directories."
  112. (let ((expr `(progn
  113. (setq byte-compile-debug t) ; for proper exit status
  114. (byte-recompile-directory (file-name-as-directory ,dir) 0 1))))
  115. (emacs-batch-eval expr)))
  116. (define* (emacs-compile-directory dir)
  117. "Compile all files in DIR to native code.
  118. If native code is not supported, compile to bytecode instead."
  119. (emacs-batch-eval
  120. `(let ((byte-compile-debug t) ; for proper exit status
  121. (byte+native-compile (native-comp-available-p))
  122. (files (directory-files-recursively ,dir "\\.el$")))
  123. (mapc
  124. (lambda (file)
  125. (let (byte-to-native-output-file
  126. ;; First entry is the eln-cache of the homeless shelter,
  127. ;; second entry is the install directory.
  128. (eln-dir (and (native-comp-available-p)
  129. (cadr native-comp-eln-load-path))))
  130. (if byte+native-compile
  131. (native-compile file
  132. (comp-el-to-eln-filename file eln-dir))
  133. (byte-compile-file file))
  134. ;; Sadly, we can't use pcase because quasiquote works different in
  135. ;; Emacs. See `batch-byte+native-compile' in comp.el for the
  136. ;; actual shape of byte-to-native-output-file.
  137. (unless (null byte-to-native-output-file)
  138. (rename-file (car byte-to-native-output-file)
  139. (cdr byte-to-native-output-file)
  140. t))))
  141. files))
  142. #:dynamic? #t))
  143. (define (emacs-header-parse section file)
  144. "Parse the header SECTION in FILE and return it as a string."
  145. (emacs-batch-script
  146. `(progn
  147. (require 'lisp-mnt)
  148. (find-file ,file)
  149. (princ (lm-header ,section)))))
  150. (define as-display ;syntactic keyword for 'emacs-substitute-sexps'
  151. '(as display))
  152. (define-syntax replacement-helper
  153. (syntax-rules (as-display)
  154. ((_ (leading-regexp replacement (as-display)))
  155. `(progn (goto-char (point-min))
  156. (re-search-forward ,leading-regexp)
  157. (kill-sexp)
  158. (insert " ")
  159. (insert ,(format #f "~a" replacement))))
  160. ((_ (leading-regexp replacement))
  161. `(progn (goto-char (point-min))
  162. (re-search-forward ,leading-regexp)
  163. (kill-sexp)
  164. (insert " ")
  165. (insert ,(format #f "~s" replacement))))))
  166. (define-syntax emacs-substitute-sexps
  167. (syntax-rules ()
  168. "Substitute the S-expression immediately following the first occurrence of
  169. LEADING-REGEXP by the string returned by REPLACEMENT in FILE. For example:
  170. (emacs-substitute-sexps \"w3m.el\"
  171. (\"defcustom w3m-command\"
  172. (string-append w3m \"/bin/w3m\"))
  173. (\"defvar w3m-image-viewer\"
  174. (string-append imagemagick \"/bin/display\")))
  175. This replaces the default values of the `w3m-command' and `w3m-image-viewer'
  176. variables declared in `w3m.el' with the results of the `string-append' calls
  177. above. Note that LEADING-REGEXP uses Emacs regexp syntax.
  178. Here is another example that uses the '(as-display)' subform to avoid having
  179. the Elisp procedure symbol from being double quoted:
  180. (emacs-substitute-sexps \"gnugo.el\"
  181. (\"defvar gnugo-xpms\" \"#'gnugo-imgen-create-xpms\" (as-display))"
  182. ((_ file replacement-spec ...)
  183. (emacs-batch-edit-file file
  184. `(progn ,(replacement-helper replacement-spec)
  185. ...
  186. (basic-save-buffer))))))
  187. (define-syntax emacs-substitute-variables
  188. (syntax-rules ()
  189. "Substitute the default value of VARIABLE by the string returned by
  190. REPLACEMENT in FILE. For example:
  191. (emacs-substitute-variables \"w3m.el\"
  192. (\"w3m-command\" (string-append w3m \"/bin/w3m\"))
  193. (\"w3m-image-viewer\" (string-append imagemagick \"/bin/display\")))
  194. This replaces the default values of the `w3m-command' and `w3m-image-viewer'
  195. variables declared in `w3m.el' with the results of the `string-append' calls
  196. above. Similarly to `emacs-substitute-sexps', the '(as-display)' subform can
  197. be used to have the replacement formatted like `display' would, which can be
  198. useful to avoid double quotes being added when the replacement is provided as
  199. a string."
  200. ((_ file (variable replacement modifier ...) ...)
  201. (emacs-substitute-sexps file
  202. ((string-append "(def[a-z]+[[:space:]\n]+" variable "\\_>")
  203. replacement
  204. modifier ...)
  205. ...))))
  206. ;;; emacs-utils.scm ends here