]> code.delx.au - gnu-emacs-elpa/blobdiff - validate.el
Add validate-mark-safe-local
[gnu-emacs-elpa] / validate.el
index 3afe7b7a5ce3ea6666db20d30b3c33695b73131d..9eebe9a36e94cf7c6f61d1b60eb1f46bed1cd503 100644 (file)
@@ -4,25 +4,59 @@
 
 ;; Author: Artur Malabarba <emacs@endlessparentheses.com>
 ;; Keywords: lisp
+;; Package-Requires: ((emacs "24.1") (cl-lib "0.5"))
+;; Version: 0.1
 
-;; This program is free software; you can redistribute it and/or modify
+;;; Commentary:
+;;
+;; This library offers two functions that perform schema validation.
+;; Use this is your Elisp packages to provide very informative error
+;; messages when your users accidentally misconfigure a variable.
+;; For instance, if everything is fine, these do the same thing:
+;;
+;;   1.  (validate-variable 'cider-known-endpoints)
+;;   2.  cider-known-endpoints
+;;
+;; However, if the user has misconfigured this variable, option
+;; 1. will immediately give them an informative error message, while
+;; option 2. won't say anything and will lead to confusing errors down
+;; the line.
+;;
+;; The format and language of the schemas is the same one used in the
+;; `:type' property of a `defcustom'.
+;;
+;;     See: (info "(elisp) Customization Types")
+;;
+;; Both functions throw a `user-error' if the value in question
+;; doesn't match the schema, and return the value itself if it
+;; matches.  The function `validate-variable' verifies whether the value of a
+;; custom variable matches its custom-type, while `validate-value' checks an
+;; arbitrary value against an arbitrary schema.
+;;
+;; Missing features: `:inline', `plist', `coding-system', `color',
+;; `hook', `restricted-sexp'.
+
+;;; License:
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs is free software: you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
 ;; the Free Software Foundation, either version 3 of the License, or
 ;; (at your option) any later version.
-
-;; This program is distributed in the hope that it will be useful,
+;;
+;; GNU Emacs is distributed in the hope that it will be useful,
 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ;; GNU General Public License for more details.
-
-;; You should have received a copy of the GNU General Public License
-;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
 ;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Code:
+(require 'cl-lib)
+(require 'seq)
+(require 'cus-edit)
 
 (defun validate--check-list-contents (values schemas)
   "Check that all VALUES match all SCHEMAS."
@@ -33,8 +67,7 @@
 (defun validate--check (value schema)
   "Return nil if VALUE matches SCHEMA.
 If they don't match, return an explanation."
-  (let ((fail (list schema value))
-        (args (cdr-safe schema))
+  (let ((args (cdr-safe schema))
         (expected-type (or (car-safe schema) schema))
         (props nil))
     (while (and (keywordp (car args)) (cdr args))
@@ -42,9 +75,9 @@ If they don't match, return an explanation."
     (setq args (or (plist-get props :args)
                    args))
     (let ((r
-           (cl-labels ((wtype ;wrong-type
-                        (t) (unless (funcall (intern (format "%sp" t)) value)
-                              (format "not a %s" t))))
+           (cl-labels ((wtype           ;wrong-type
+                        (tt) (unless (funcall (intern (format "%sp" tt)) value)
+                               (format "not a %s" tt))))
              ;; TODO: hook (top-level only).
              (cl-case expected-type
                ((sexp other) nil)
@@ -52,7 +85,7 @@ If they don't match, return an explanation."
                                ((not (boundp value)) "this symbol has no variable binding")))
                ((integer number float string character symbol function boolean face)
                 (wtype expected-type))
-               (regexp (cond ((ignore-errors (string-match re "") t) nil)
+               (regexp (cond ((ignore-errors (string-match value "") t) nil)
                              ((wtype 'string))
                              (t "not a valid regexp")))
                (repeat (cond
@@ -90,7 +123,7 @@ If they don't match, return an explanation."
                               (t (validate--check value
                                           `(repeat (cons ,key-type ,value-type)))))))
                ;; TODO: `plist'
-               ((choice radio) (if (not (cdr choice))
+               ((choice radio) (if (not (cdr args))
                                    (error "`choice' needs at least one argument")
                                  (let ((gather (mapcar (lambda (x) (validate--check value x)) args)))
                                    (when (seq-every-p #'identity gather)
@@ -142,5 +175,12 @@ with `validate-value'. NOERROR is passed to `validate-value'."
       (if noerror val
         (error "Variable `%s' has no custom-type." symbol)))))
 
+;;;###autoload
+(defun validate-mark-safe-local (symbol)
+  "Mark SYMBOL as a safe local if its custom type is obeyed."
+  (put symbol 'safe-local-variable
+       (lambda (val)
+         (validate-value val (custom-variable-type symbol) 'noerror))))
+
 (provide 'validate)
 ;;; validate.el ends here