insert-shebang.el 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. ;;; insert-shebang.el --- Insert shebang line automatically.
  2. ;; Copyright (C) 2013-2017 Sachin Patil
  3. ;; Author: Sachin Patil <iclcoolster@gmail.com>
  4. ;; URL: http://github.com/psachin/insert-shebang
  5. ;; Keywords: shebang, tool, convenience
  6. ;; Version: 0.9.6
  7. ;; This file is NOT a part of GNU Emacs.
  8. ;; `insert-shebang' is free software distributed under the terms of
  9. ;; the GNU General Public License, version 3. For details, see the
  10. ;; file COPYING.
  11. ;;; Commentary:
  12. ;; Inserts shebang line automatically
  13. ;; URL: http://github.com/psachin/insert-shebang
  14. ;; Install
  15. ;; Using `package'
  16. ;; M-x package-install insert-shebang
  17. ;; Unless installed from a `package', add the directory containing
  18. ;; this file to `load-path', and then:
  19. ;; (require 'insert-shebang)
  20. ;;
  21. ;; Customize
  22. ;; M-x customize-group RET insert-shebang RET
  23. ;;
  24. ;; See ReadMe.org for more info.
  25. ;;; Code:
  26. (defgroup insert-shebang nil
  27. "Inserts shebang line automatically."
  28. :group 'extensions
  29. :link '(url-link :tag "Github" "https://github.com/psachin/insert-shebang"))
  30. (defcustom insert-shebang-env-path "/usr/bin/env"
  31. "Full path to `env' binary.
  32. You can find the path to `env' by typing `which env' in the
  33. terminal."
  34. :type '(string)
  35. :group 'insert-shebang)
  36. (defcustom insert-shebang-file-types
  37. '(("py" . "python")
  38. ("groovy" . "groovy")
  39. ("fish" . "fish")
  40. ("robot" . "robot")
  41. ("rb" . "ruby")
  42. ("lua" . "lua")
  43. ("php" . "php")
  44. ("sh" . "bash")
  45. ("pl" . "perl"))
  46. "*If nil, add all your file extensions and file types here."
  47. :type '(alist :key-type (string :tag "Extension")
  48. :value-type (string :tag "Interpreter"))
  49. :group 'insert-shebang)
  50. (defcustom insert-shebang-ignore-extensions
  51. '("txt" "org")
  52. "*Add extensions you want to ignore.
  53. List of file extensions to be ignored by default."
  54. :type '(repeat (string :tag "extn"))
  55. :group 'insert-shebang)
  56. (defcustom insert-shebang-custom-headers nil
  57. "Put your custom headers for other file types here.
  58. For example '#include <stdio.h>' for c file etc.
  59. Example:
  60. File type: c
  61. Header: #include <stdio.h>
  62. File type: f90
  63. Header: program
  64. File type: f95
  65. Header: program"
  66. :type '(alist :key-type (string :tag "Extension")
  67. :value-type (string :tag "Header"))
  68. :group 'insert-shebang)
  69. (defcustom insert-shebang-header-scan-limit 6
  70. "Define how much initial characters to scan from starting for custom headers.
  71. This is to avoid differentiating header `#include <stdio.h>` with
  72. `#include <linux/modules.h>` or `#include <strings.h>`."
  73. :type '(integer :tag "Limit")
  74. :group 'insert-shebang)
  75. (defcustom insert-shebang-track-ignored-filename "~/.insert-shebang.log"
  76. "Filepath where list of ignored files are stored.
  77. Set to nil if you do not want to keep log of ignored files."
  78. :type '(string)
  79. :group 'insert-shebang)
  80. (defun insert-shebang-get-extension-and-insert (filename)
  81. "Get extension from FILENAME and insert shebang.
  82. FILENAME is a buffer name from which the extension is to be
  83. extracted."
  84. (if (file-name-extension filename)
  85. (let ((file-extn (replace-regexp-in-string "[\<0-9\>]" ""
  86. (file-name-extension filename))))
  87. ;; check if this extension is ignored
  88. (if (car (member file-extn insert-shebang-ignore-extensions))
  89. (progn (message "Extension ignored"))
  90. ;; if not, check in extension list
  91. (progn
  92. (if (car (assoc file-extn insert-shebang-custom-headers))
  93. (progn ;; insert custom header
  94. (let ((val (cdr (assoc file-extn insert-shebang-custom-headers))))
  95. (if (= (point-min) (point-max))
  96. ;; insert custom-header at (point-min)
  97. (insert-shebang-custom-header val)
  98. (progn
  99. (insert-shebang-scan-first-line-custom-header val)))))
  100. (progn
  101. ;; get value against the key
  102. (if (car (assoc file-extn insert-shebang-file-types))
  103. ;; if key exists in list 'insert-shebang-file-types'
  104. (progn
  105. ;; set variable val to value of key
  106. (let ((val (cdr (assoc file-extn insert-shebang-file-types))))
  107. ;; if buffer is new
  108. (if (= (point-min) (point-max))
  109. (insert-shebang-eval val)
  110. ;; if buffer has something, then
  111. (progn
  112. (insert-shebang-scan-first-line-eval val)))))
  113. ;; if key don't exists
  114. (progn
  115. (message "Can't guess file type. Type: 'M-x customize-group RET \
  116. insert-shebang' to add/customize"))))))))))
  117. (defun insert-shebang-eval (val)
  118. "Insert shebang with prefix 'eval' string in current buffer.
  119. With VAL as an argument."
  120. (with-current-buffer (buffer-name)
  121. (goto-char (point-min))
  122. (insert (format "#!%s %s" insert-shebang-env-path val))
  123. (newline)
  124. (goto-char (point-min))
  125. (end-of-line)))
  126. (defun insert-shebang-custom-header (val)
  127. "Insert custom header.
  128. With VAL as an argument."
  129. (with-current-buffer (buffer-name)
  130. (goto-char (point-min))
  131. (insert val)
  132. (newline)
  133. (goto-char (point-min))
  134. (end-of-line)))
  135. (defun insert-shebang-scan-first-line-eval (val)
  136. "Scan very first line of the file.
  137. With VAL as an argument and look if it has matching shebang-line."
  138. (save-excursion
  139. (goto-char (point-min))
  140. ;; search for shebang pattern
  141. (if (ignore-errors (re-search-forward "^#![ ]?\\([a-zA-Z_./]+\\)"))
  142. (message "insert-shebang: File has shebang line")
  143. ;; prompt user
  144. (if (y-or-n-p "File do not have shebang line, \
  145. do you want to insert it now? ")
  146. (progn
  147. (insert-shebang-eval val))
  148. (progn
  149. (insert-shebang-log-ignored-files
  150. (replace-regexp-in-string "[\<0-9\>]" "" (original-buffer-name))))))))
  151. (defun insert-shebang-scan-first-line-custom-header (val)
  152. "Scan very first line of the file and look if it has matching header.
  153. With VAL as an argument."
  154. (save-excursion
  155. (goto-char (point-min))
  156. ;; search for shebang pattern
  157. (if (ignore-errors
  158. (re-search-forward
  159. (format "^%s"
  160. (substring val 0 insert-shebang-header-scan-limit))))
  161. (message "insert-shebang: File has header")
  162. ;; prompt user
  163. (if (y-or-n-p "File do not have header, do you want to insert it now? ")
  164. (progn
  165. (goto-char (point-min))
  166. (insert-shebang-custom-header val))
  167. (progn
  168. (insert-shebang-log-ignored-files
  169. (replace-regexp-in-string "[\<0-9\>]" "" (original-buffer-name))))))))
  170. (defun insert-shebang-read-log-file (log-file-path)
  171. "Return a list of ignored files.
  172. LOG-FILE-PATH is set in `insert-shebang-track-ignored-filename'"
  173. (with-temp-buffer
  174. (insert-file-contents log-file-path)
  175. ;; every new line is treated as an element.
  176. (split-string (buffer-string) "\n" t)))
  177. (defun insert-shebang-write-log-file (log-file-path log-file-list)
  178. "Write list of files to be ignored to log file.
  179. LOG-FILE-PATH is set in `insert-shebang-track-ignored-filename'
  180. and LOG-FILE-LIST is a list of ignored files with fullpath."
  181. (with-temp-buffer
  182. ;; every element on a new line.
  183. (insert (mapconcat 'identity log-file-list "\n"))
  184. (when (file-writable-p log-file-path)
  185. (write-region (point-min)
  186. (point-max)
  187. log-file-path))))
  188. (defun insert-shebang-create-log-file (logfile)
  189. "Function to create log file if does not exist.
  190. LOGFILE name is defined in `insert-shebang-track-ignored-filename'."
  191. (if (not (file-exists-p (expand-file-name logfile)))
  192. (write-region 1 1 (expand-file-name logfile) t)))
  193. (defun insert-shebang-log-ignored-files (filename)
  194. "Keep log of ignored files.
  195. Ignore them on next visit.
  196. FILENAME is `buffer-name'."
  197. ;; if `insert-shebang-track-ignored-filename' is `nil', don't track
  198. ;; ignored files.
  199. (if (not (equal insert-shebang-track-ignored-filename nil))
  200. (progn
  201. ;; if file not exist, create it
  202. (insert-shebang-create-log-file insert-shebang-track-ignored-filename)
  203. ;; set variables
  204. (let* ((log-file-path (expand-file-name
  205. insert-shebang-track-ignored-filename))
  206. (log-file-list (insert-shebang-read-log-file log-file-path)))
  207. ;; add new 'ignored' file to the list
  208. (add-to-list 'log-file-list (expand-file-name filename))
  209. ;; Updated list in the log-file
  210. ;; (message "%s" log-file-list)
  211. (insert-shebang-write-log-file log-file-path log-file-list)))))
  212. (defun insert-shebang-open-log-buffer ()
  213. "Open log of ignored file(s) in a separate buffer for editing."
  214. (interactive "*")
  215. (when (file-readable-p insert-shebang-track-ignored-filename)
  216. (find-file-other-window insert-shebang-track-ignored-filename)))
  217. (defun original-buffer-name ()
  218. "Get un-uniquified buffer name."
  219. (file-name-nondirectory (buffer-file-name)))
  220. ;;;###autoload
  221. (defun insert-shebang ()
  222. "Insert shebang line automatically.
  223. Calls function `insert-shebang-get-extension-and-insert'. With argument as
  224. `buffer-name'."
  225. (interactive "*")
  226. ;; if `insert-shebang-track-ignored-filename' is `nil', don't track
  227. ;; ignored files.
  228. (if (not (equal insert-shebang-track-ignored-filename nil))
  229. (progn
  230. ;; if file not exist, create it
  231. (insert-shebang-create-log-file insert-shebang-track-ignored-filename)
  232. ;; ignore current-buffer, if it's path exist in ignored file list.
  233. (let* ((log-file-path (expand-file-name
  234. insert-shebang-track-ignored-filename))
  235. (log-file-list (insert-shebang-read-log-file log-file-path))
  236. (filename (replace-regexp-in-string "[\<0-9\>]" ""
  237. (expand-file-name
  238. (original-buffer-name)))))
  239. (if (member filename log-file-list)
  240. ;; do nothing.
  241. (progn)
  242. ;; call `insert-shebang-get-extension-and-insert'.
  243. (progn
  244. (insert-shebang-get-extension-and-insert (original-buffer-name))))))
  245. (insert-shebang-get-extension-and-insert (original-buffer-name))))
  246. ;;;###autoload(add-hook 'find-file-hook 'insert-shebang)
  247. (provide 'insert-shebang)
  248. ;;; insert-shebang.el ends here