Ce serveur Gitlab sera éteint le 30 juin 2020, pensez à migrer vos projets vers les serveurs gitlab-research.centralesupelec.fr et gitlab-student.centralesupelec.fr !

el-get-recipes.el 12.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
;;; el-get-recipes.el --- Manage the external elisp bits and pieces you depend upon
;;
;; Copyright (C) 2010-2011 Dimitri Fontaine
;;
;; Author: Dimitri Fontaine <dim@tapoueh.org>
;; 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.asciidoc 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)
25
(require 'el-get-byte-compile)
26 27 28 29 30 31 32

(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)

33 34 35 36 37 38 39
(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)


40 41
(defcustom el-get-recipe-path
  (list (concat (file-name-directory el-get-script) "recipes")
42 43 44 45 46 47 48
        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."
49 50 51 52 53 54 55 56 57 58 59
  :group 'el-get
  :type '(repeat (directory)))

(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
60 61 62 63
when this custom is nil.

Will automatically compile the init file as needed and load the
compiled version."
64
  (when el-get-user-package-directory
65
    (let* ((init-file-name (format "init-%s.el" package))
66
	   (package-init-file
67
	    (expand-file-name init-file-name el-get-user-package-directory))
Damien Cassou's avatar
Damien Cassou committed
68
	   (file-name-no-extension (file-name-sans-extension package-init-file))
69
	   (compiled-init-file (concat file-name-no-extension ".elc")))
70
      (when (file-exists-p package-init-file)
Damien Cassou's avatar
Damien Cassou committed
71 72
	(when el-get-byte-compile
	  (el-get-byte-compile-file package-init-file))
73 74
	(el-get-verbose-message "el-get: load %S" file-name-no-extension)
	(load file-name-no-extension 'noerror)))))
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93

(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)"
94 95
  (condition-case err
      (with-temp-buffer
96
        (insert-file-contents filename)
97 98 99
        (read (current-buffer)))
    ((debug error)
     (error "Error reading recipe %s: %S" filename err))))
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117

(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))))

118 119 120
(defun el-get-read-all-recipe-files ()
  "Return the list of all the file based recipes, formated like
   `el-get-sources'
121 122 123

Only consider any given recipe only once even if present in
multiple dirs from `el-get-recipe-path'. The first recipe found
124
is the one considered."
125 126 127 128 129 130 131 132 133 134 135 136
  (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))))))
137 138 139 140 141 142

(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."
143 144 145
  (let ((packages (mapcar 'el-get-source-name el-get-sources)))
    (append
     el-get-sources
146 147
     (remove-if (lambda (recipe) (member (el-get-source-name recipe) packages))
                (el-get-read-all-recipe-files)))))
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170

(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)
171 172 173 174 175 176 177 178 179
  "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)))

180 181 182
    (when (integerp builtin)
      (warn "Integer argument for :builtin is obsolete.  Use strings instead.")
      (setq builtin (number-to-string builtin)))
183
    (if (and builtin (version<= builtin emacs-version))
184
        'builtin
185
      (plist-get def :type))))
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205

(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)))

206 207 208 209 210
(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
211
        def :minimum-emacs-version
212 213
      0)))

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
(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))))

234 235 236 237
(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."
238 239 240 241 242 243
  (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))))
244

245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
(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)
               ":")))

261 262 263 264 265 266 267 268 269 270 271 272 273
(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
274
        (el-get-check-recipe-in-current-buffer (buffer-file-name)))
275 276 277
    (with-temp-buffer
      (erase-buffer)
      (insert-file-contents file-or-buffer)
278
      (el-get-check-recipe-in-current-buffer file-or-buffer))))
279

280
(defun el-get-check-recipe-in-current-buffer (recipe-file-name)
281 282 283 284 285 286 287 288
  (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)
289 290 291 292 293
      (when (and recipe-file-name
                 (not (string= (file-name-base recipe-file-name)
                               (plist-get recipe :name))))
        (incf numerror)
        (insert "* File name should match recipe name.\n"))
294 295 296 297 298 299 300 301 302
      ;; 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)))
303
      (destructuring-bind (&key type url autoloads features builtin
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
                                &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),
319 320 321 322 323
  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")))
324 325 326 327 328 329 330 331
      ;; 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)))
332 333 334
      (insert (format "%s error(s) found." numerror)))
    numerror))

335
(provide 'el-get-recipes)