;;; el-get-recipes.el --- Manage the external elisp bits and pieces you depend upon ;; ;; Copyright (C) 2010-2011 Dimitri Fontaine ;; ;; Author: Dimitri Fontaine ;; 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-...] *.rcp <:level> can be any valid warning level, see `warning-levels'. See `el-get-check-suppressed-warnings' for possible 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