forked from dimitri/el-get
-
Notifications
You must be signed in to change notification settings - Fork 1
/
el-get-check.el
226 lines (204 loc) · 9.63 KB
/
el-get-check.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
;;; el-get-recipes.el --- Manage the external elisp bits and pieces you depend upon
;;
;; Copyright (C) 2010-2011 Dimitri Fontaine
;;
;; Author: Dimitri Fontaine <[email protected]>
;; URL: http://www.emacswiki.org/emacs/el-get
;; GIT: https://github.com/dimitri/el-get
;; Licence: WTFPL, grab your copy here: http://sam.zoy.org/wtfpl/
;;
;; This file is NOT part of GNU Emacs.
;;
;; Install
;; Please see the README.md file from the same distribution
;;; Commentary:
;;
;; el-get-check provides some functions to check for some errors in recipes.
;;
;;; Code:
(require 'cl-lib)
(require 'el-get-recipes)
(require 'el-get-build)
(defvar warning-minimum-log-level)
(defvar warning-minimum-level)
(declare-function warning-numeric-level "warnings" (level))
(defvar el-get-check--last-file-or-buffer nil
"The last file-or-buffer checked.")
(defun el-get-check-redo ()
"Rerun `el-get-check-recipe' with last recipe."
(interactive)
(when el-get-check--last-file-or-buffer
(el-get-check-recipe
el-get-check--last-file-or-buffer)))
(defvar el-get-check-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map special-mode-map)
(define-key map "g" #'el-get-check-redo)
map)
"Mode map for `el-get-check-mode'.")
(define-derived-mode el-get-check-mode special-mode "El-Get Check"
"Special mode for `el-get-check-recipe' buffers.
See Info node `(el-get) Authoring Recipes'.")
(defvar el-get-check-suppressed-warnings ()
"List of `el-get-check-recipe' warnings to suppress.
Current possibe elements are:
`features', `github', `autoloads'")
(defun el-get-check-recipe-batch-1 (recipe-file)
(let ((warning-prefix-function
(lambda (level entry)
(list level (format "%s:%s" el-get-check--last-file-or-buffer
(format (nth 1 entry) ""))))))
(condition-case err
(el-get-check-recipe (file-relative-name recipe-file))
(error (lwarn '(el-get) :emergency "%s" (error-message-string err))
1))))
(defun el-get-check-recipe-batch ()
"emacs -Q -batch -f el-get-check-recipe-batch [-W<:level>]
[-Wno-<warning>...] *.rcp
<:level> can be any valid warning level, see `warning-levels'.
See `el-get-check-suppressed-warnings' for possible <warning> values."
(cl-assert noninteractive nil
"`el-get-check-recipe-batch' should only be used with -batch")
(setq vc-handled-backends nil) ; avoid loading VC during batch mode
(cl-loop for arg in command-line-args-left
if (string-match "\\`-Wno-\\(.*\\)" arg)
do (push (intern (match-string 1 arg)) el-get-check-suppressed-warnings)
else if (string-match "\\`-W\\(:[-a-z]*\\)" arg)
do (setq warning-minimum-log-level
(setq warning-minimum-level (intern (match-string 1 arg))))
else summing
(if (file-directory-p arg)
(cl-reduce #'+ (directory-files arg t "\\.rcp$" t)
:key #'el-get-check-recipe-batch-1 :initial-value 0)
(el-get-check-recipe-batch-1 arg))
into errors
finally (progn (message "%d warning/error(s) total." errors)
(kill-emacs (if (zerop errors) 0 1)))))
;;;###autoload
(defun el-get-check-recipe (file-or-buffer)
"Check the format of the recipe.
Please run this command before sending a pull request.
Usage: M-x el-get-check-recipe RET
You can run this function from checker script like this:
test/check-recipe.el PATH/TO/RECIPE.rcp
When used as a lisp function, FILE-OR-BUFFER must be a buffer
object or a file path."
(interactive (list (current-buffer)))
(setq el-get-check--last-file-or-buffer file-or-buffer)
(if (bufferp file-or-buffer)
(with-current-buffer file-or-buffer
(el-get-check-recipe-in-current-buffer (buffer-file-name)))
(with-temp-buffer
(insert-file-contents file-or-buffer)
(el-get-check-recipe-in-current-buffer file-or-buffer))))
(eval-and-compile
(unless (fboundp 'file-name-base) ; new in 24.3
(defun file-name-base (&optional filename)
"Return the base name of the FILENAME: no directory, no extension.
FILENAME defaults to `buffer-file-name'."
(file-name-sans-extension
(file-name-nondirectory (or filename (buffer-file-name)))))))
(defvar el-get-check-warning-buffer)
(defvar el-get-check-error-count)
(defun el-get-check-warning (level message &rest args)
(declare (indent 1))
(display-warning '(el-get recipe) (apply #'format message args)
level el-get-check-warning-buffer)
(when (>= (warning-numeric-level level)
(warning-numeric-level warning-minimum-level))
(cl-incf el-get-check-error-count)))
(defun el-get-check-recipe-in-current-buffer (recipe-file-name)
(let ((inhibit-read-only t)
(el-get-check-error-count 0)
(el-get-check-warning-buffer (get-buffer-create "*el-get check recipe*")))
(display-buffer el-get-check-warning-buffer)
(with-current-buffer el-get-check-warning-buffer
(erase-buffer)
(el-get-check-mode))
(let* ((recipe (save-excursion
(goto-char (point-min))
(prog1 (read (current-buffer))
(let ((lvl-err (condition-case err
(progn (read (current-buffer))
`(:warning . "Extra data following recipe"))
(end-of-file nil)
(error `(:error . ,(error-message-string err))))))
(when lvl-err
(let ((el-get-check--last-file-or-buffer
(format "%s:%d:%d" recipe-file-name
(line-number-at-pos) (current-column))))
(el-get-check-warning (car lvl-err) (cdr lvl-err))))))))
(el-get-sources (list recipe))
(pkg-name (plist-get recipe :name)))
(when (and recipe-file-name
(not (string= (file-name-base recipe-file-name) pkg-name)))
(el-get-check-warning :error
"File name should match recipe name."))
;; Check if userspace property is used.
(cl-loop for key in '(:before :after)
for alt in '(:prepare :post-init)
when (plist-get recipe key)
do (el-get-check-warning :warning
"Property %S is for user. Use %S instead."
key alt))
;; Check for misformatted plists
(cl-loop for key in recipe by #'cddr
unless (keywordp key)
do (el-get-check-warning :warning
"Property %S is not a keyword!"
key))
(cl-destructuring-bind (&key type url autoloads feats builtin
&allow-other-keys)
recipe
;; let-binding `features' causes `provide' to throw error
(setq feats (plist-get recipe :features))
;; Is github type used?
(when (and (not (memq 'github el-get-check-suppressed-warnings))
(eq type 'git) (string-match "//github.com/" url))
(el-get-check-warning :warning
"Use `:type github' for github type recipe"))
;; Warn when `:autoloads nil' is specified.
(when (and (not (memq 'autoloads el-get-check-suppressed-warnings))
(null autoloads) (plist-member recipe :autoloads))
(el-get-check-warning :warning
"Are you sure you don't need autoloads?
This property should be used only when the library takes care of
the autoload."))
;; Warn when `:features t' is specified
(when (and (not (memq 'features el-get-check-suppressed-warnings))
feats)
(el-get-check-warning :warning
"Are you sure you need features?
If this library has `;;;###autoload' comment (a.k.a autoload cookie),
you don't need `:features'."))
;; Check if `:builtin' is used with an integer
(when (integerp builtin)
(el-get-check-warning :warning
"Usage of integers for :builtin is obsolete.
Use a version string like \"24.3\" instead.")))
;; Check for shell interpolated :build commands
(let ((safe-functions '(backquote-list*
el-get-load-path el-get-package-exists-p
el-get-package-directory el-get-print-to-string
el-get-verbose-message
with-temp-buffer insert-file-contents
directory-files file-name-as-directory
expand-file-name shell-quote-argument)))
(dolist (sys '("" "/darwin" "/berkeley-unix" "/windows-nt"))
(let ((unsafe (catch 'unsafe-build
(when (cl-some #'stringp (el-get-build-commands pkg-name 'safe-eval sys))
(el-get-check-warning :warning
":build%s should be a *list* of string lists." sys))
nil)))
(when unsafe
(el-get-check-warning :debug ":build%s is unsafep: %s" sys unsafe)))))
;; Check for required properties.
(cl-loop for key in '(:description :name)
unless (plist-get recipe key)
do (el-get-check-warning
:error "Required property %S is not defined." key))
(with-current-buffer el-get-check-warning-buffer
(insert (format "\n%s: %s error(s) found." recipe-file-name el-get-check-error-count))))
el-get-check-error-count))
(provide 'el-get-check)
;;; el-get-check.el ends here