forked from dimitri/el-get
-
Notifications
You must be signed in to change notification settings - Fork 0
/
el-get-recipes.el
335 lines (288 loc) · 12.8 KB
/
el-get-recipes.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
;;; 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: https://www.emacswiki.org/emacs/el-get
;; GIT: https://github.com/dimitri/el-get
;; Licence: WTFPL, grab your copy here: https://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-recipes provides the API to manage the el-get recipes.
;;
;; el-get-core provides basic el-get API, intended for developpers of el-get
;; and its methods. See the methods directory for implementation of them.
;;
(require 'el-get-core)
(require 'el-get-byte-compile)
(defcustom el-get-recipe-path-emacswiki
(concat (file-name-directory el-get-dir) "el-get/recipes/emacswiki/")
"Define where to keep a local copy of emacswiki recipes"
:group 'el-get
:type 'directory)
(defcustom el-get-recipe-path-elpa
(concat (file-name-directory el-get-dir) "el-get/recipes/elpa/")
"Define where to keep a local copy of elpa recipes"
:group 'el-get
:type 'directory)
(defvar el-get-recipe-path
(list (concat (file-name-directory el-get-script) "recipes")
el-get-recipe-path-elpa
el-get-recipe-path-emacswiki)
"List of directories in which to look for el-get recipes.
Directories that contain automatically-generated recipes, such as
`el-get-recipe-path-emacswiki' and `el-get-recipe-path-elpa',
should be placed last in this list.
This variable is not customizable, as it needs to be set before
el-get is loaded, while customizations should be loaded after
el-get, so that they can affect pacakages loaded by el-get.
It is recommended to add new directories using code like:
(add-to-list 'el-get-recipe-path \"~/.emacs.d/el-get-user/recipes/\")")
(defcustom el-get-user-package-directory nil
"Define where to look for init-pkgname.el configurations. Disabled if nil."
:group 'el-get
:type '(choice (const :tag "Off" nil) directory))
(defun el-get-load-package-user-init-file (package)
"Load the user init file for PACKAGE, called init-package.el
and to be found in `el-get-user-package-directory'. Do nothing
when this custom is nil.
Will automatically compile the init file as needed and load the
compiled version."
(when el-get-user-package-directory
(let* ((init-file-name (format "init-%s.el" package))
(package-init-file
(expand-file-name init-file-name el-get-user-package-directory))
(file-name-no-extension (file-name-sans-extension package-init-file))
(compiled-init-file (concat file-name-no-extension ".elc")))
(when (file-exists-p package-init-file)
(when el-get-byte-compile
(el-get-byte-compile-file package-init-file))
(el-get-verbose-message "el-get: load %S" file-name-no-extension)
(load file-name-no-extension 'noerror)))))
(defun el-get-recipe-dirs ()
"Return the elements of el-get-recipe-path that actually exist.
Used to avoid errors when exploring the path for recipes"
(reduce (lambda (dir result)
(if (file-directory-p dir) (cons dir result) result))
el-get-recipe-path :from-end t :initial-value nil))
;; recipe files are elisp data, you can't byte-compile or eval them on their
;; own, but having elisp indenting and colors make sense
(eval-and-compile
(add-to-list 'auto-mode-alist '("\\.rcp\\'" . emacs-lisp-mode)))
;;
;; recipes
;;
(defun el-get-read-recipe-file (filename)
"Read given filename and return its content (a valid form is expected)"
(condition-case err
(with-temp-buffer
(insert-file-contents filename)
(read (current-buffer)))
((debug error)
(error "Error reading recipe %s: %S" filename err))))
(defun el-get-recipe-filename (package)
"Return the name of the file that contains the recipe for PACKAGE, if any."
(let ((package-el (concat (el-get-as-string package) ".el"))
(package-rcp (concat (el-get-as-string package) ".rcp")))
(loop for dir in el-get-recipe-path
for recipe-el = (expand-file-name package-el dir)
for recipe-rcp = (expand-file-name package-rcp dir)
if (file-exists-p recipe-el) return recipe-el
if (file-exists-p recipe-rcp) return recipe-rcp)))
(defun el-get-read-recipe (package)
"Return the source definition for PACKAGE, from the recipes."
(let ((filename (el-get-recipe-filename package)))
(if filename
(el-get-read-recipe-file filename)
(error "el-get can not find a recipe for package \"%s\"." package))))
(defun el-get-read-all-recipe-files ()
"Return the list of all the file based recipes, formated like
`el-get-sources'
Only consider any given recipe only once even if present in
multiple dirs from `el-get-recipe-path'. The first recipe found
is the one considered."
(let (packages)
(loop
for dir in (el-get-recipe-dirs)
nconc (loop
for recipe in (directory-files dir nil "^[^.].*\.\\(rcp\\|el\\)$")
for filename = (concat (file-name-as-directory dir) recipe)
for pname = (file-name-sans-extension
(file-name-nondirectory recipe))
for package = (el-get-as-symbol pname)
unless (member package packages)
do (push package packages)
and collect (ignore-errors (el-get-read-recipe-file filename))))))
(defun el-get-read-all-recipes ()
"Return the list of all the recipes, formatted like `el-get-sources'.
We first look in `el-get-sources' then in each directory listed
in `el-get-recipe-path' in order."
(let ((packages (mapcar 'el-get-source-name el-get-sources)))
(append
el-get-sources
(remove-if (lambda (recipe) (member (el-get-source-name recipe) packages))
(el-get-read-all-recipe-files)))))
(defun el-get-package-def (package)
"Return a single `el-get-sources' entry for PACKAGE."
(let ((source (loop for src in el-get-sources
when (string= package (el-get-source-name src))
return src)))
(cond ((or (null source) (symbolp source))
;; not in `el-get-sources', or only mentioned by name
;; (compatibility from pre 3.1 era)
(el-get-read-recipe package))
((null (plist-get source :type))
;; we got a list with no :type, that's an override plist
(loop with def = (el-get-read-recipe package)
for (prop override) on source by 'cddr
do (plist-put def prop override)
finally return def))
;; none of the previous, must be a full definition
(t source))))
(defun el-get-package-method (package-or-source)
"Return the :type property (called method) of PACKAGE-OR-SOURCE.
If the package is built in to the current major version of Emacs,
return 'builtin."
(let* ((def (if (or (symbolp package-or-source) (stringp package-or-source))
(el-get-package-def package-or-source)
package-or-source))
(builtin (plist-get def :builtin)))
(when (integerp builtin)
(warn "Integer argument for :builtin is obsolete. Use strings instead.")
(setq builtin (number-to-string builtin)))
(if (and builtin (version<= builtin emacs-version))
'builtin
(plist-get def :type))))
(defalias 'el-get-package-type #'el-get-package-method)
(defun el-get-package-types-alist (statuses &rest types)
"Return an alist of package names that are of given types.
Only consider packages whose status is `member' of STATUSES,
which defaults to installed, required and removed. Example:
(el-get-package-types-alist \"installed\" 'http 'cvs)
"
(loop for src in (apply 'el-get-list-package-names-with-status
(cond ((stringp statuses) (list statuses))
((null statuses) '("installed" "required" "removed"))
(t statuses)))
for name = (el-get-as-symbol src)
for type = (el-get-package-type name)
when (or (null types) (memq 'all types) (memq type types))
collect (cons name type)))
(defun el-get-package-required-emacs-version (package-or-source)
(let* ((def (if (or (symbolp package-or-source) (stringp package-or-source))
(el-get-package-def package-or-source)
package-or-source)))
(el-get-plist-get-with-default
def :minimum-emacs-version
0)))
(defun el-get-version-to-list (version)
"Convert VERSION to a standard version list.
Like the builtin `version-to-list', this function accepts a
string. Unlike the builtin, it will also accept a single number,
which will be wrapped into a single-element list, or a or a list
of numbers, which will be returned unmodified."
(cond
;; String
((stringp version)
(version-to-list version))
;; Single number
((numberp version)
(list version))
;; List of numbers
((and (listp version)
(null (remove-if 'numberp version)))
version)
(t (error "Unrecognized version specification: %S" version))))
(defun el-get-error-unless-required-emacs-version (package-or-source)
"Raise an error if `emacs-major-version' is less than package's requirement.
Second argument PACKAGE is optional and only used to construct the error message."
(let* ((pname (el-get-source-name package-or-source))
(required-version (el-get-package-required-emacs-version package-or-source))
(required-version-list (el-get-version-to-list required-version)))
(when (version-list-< (version-to-list emacs-version) required-version-list)
(error "Package %s requires Emacs version %s or higher, but the current emacs is only version %s"
pname required-version emacs-version))))
(defun el-get-envpath-prepend (envname head)
"Prepend HEAD in colon-separated environment variable ENVNAME.
This is effectively the same as doing the following in shell:
export ENVNAME=HEAD:$ENVNAME
Use this to modify environment variable such as $PATH or $PYTHONPATH."
(setenv envname (el-get-envpath-prepend-1 (getenv envname) head)))
(defun el-get-envpath-prepend-1 (paths head)
"Return \"HEAD:PATHS\" omitting duplicates in it."
(let ((pplist (split-string (or paths "") ":" 'omit-nulls)))
(mapconcat 'identity
(remove-duplicates (cons head pplist)
:test #'string= :from-end t)
":")))
(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)))
(if (bufferp file-or-buffer)
(with-current-buffer file-or-buffer
(el-get-check-recipe-in-current-buffer))
(with-temp-buffer
(erase-buffer)
(insert-file-contents file-or-buffer)
(el-get-check-recipe-in-current-buffer))))
(defun el-get-check-recipe-in-current-buffer ()
(let ((recipe (save-excursion
(goto-char (point-min))
(read (current-buffer))))
(numerror 0)
(buffer (get-buffer-create "*el-get check recipe*")))
(display-buffer buffer)
(with-current-buffer buffer
(erase-buffer)
;; Check if userspace property is used.
(loop for key in '(:before :after)
for alt in '(:prepare :post-init)
when (plist-get recipe key)
do (progn
(insert (format
"* Property %S is for user. Use %S instead.\n"
key alt))
(incf numerror)))
(destructuring-bind (&key type url autoloads features builtin
&allow-other-keys)
recipe
;; Is github type used?
(when (and (eq type 'git) (string-match "//github.com/" url))
(insert "* Use `:type github' for github type recipe\n")
(incf numerror))
;; Warn when `:autoloads nil' is specified.
(when (and (null autoloads) (plist-member recipe :autoloads))
(insert "* WARNING: Are you sure you don't need autoloads?
This property should be used only when the library takes care of
the autoload.\n"))
;; Warn when `:features t' is specified
(when features
(insert "* WARNING: Are you sure you need features?
If this library has `;;;###autoload' comment (a.k.a autoload cookie),
you don't need `:features'.\n"))
;; Check if `:builtin' is used with an integer
(when (integerp builtin)
(insert "* WARNING: Usage of integers for :builtin is obsolete.
Use a version string like \"24.3\" instead.\n")))
;; Check for required properties.
(loop for key in '(:description :name)
unless (plist-get recipe key)
do (progn
(insert (format
"* Required property %S is not defined.\n"
key))
(incf numerror)))
(insert (format "%s error(s) found." numerror)))
numerror))
(provide 'el-get-recipes)