From 0f2df2727d2896261c17ee31a44e34a888e79c81 Mon Sep 17 00:00:00 2001 From: JAremko Date: Thu, 11 Feb 2021 19:41:39 +0200 Subject: [PATCH] Validate .spacemacs variables. --- .ci/built_in_manifest | 3 +- CHANGELOG.develop | 1 + core/core-compilation.el | 1 + core/core-configuration-layer.el | 21 +- core/core-customization.el | 130 ++++ core/core-dotspacemacs.el | 565 ++++++++++++------ core/core-spacemacs.el | 10 +- core/libs/validate.el | 213 +++++++ .../local/space-doc/space-doc.el | 2 +- layers/+tools/shell/config.el | 10 +- 10 files changed, 767 insertions(+), 189 deletions(-) create mode 100644 core/core-customization.el create mode 100644 core/libs/validate.el diff --git a/.ci/built_in_manifest b/.ci/built_in_manifest index 634757f65..0f071ac21 100644 --- a/.ci/built_in_manifest +++ b/.ci/built_in_manifest @@ -10,4 +10,5 @@ https://raw.githubusercontent.com/nashamri/spacemacs-theme/master/spacemacs-comm https://raw.githubusercontent.com/nashamri/spacemacs-theme/master/spacemacs-dark-theme.el core/libs/spacemacs-theme/spacemacs-dark-theme.el https://raw.githubusercontent.com/nashamri/spacemacs-theme/master/spacemacs-light-theme.el core/libs/spacemacs-theme/spacemacs-light-theme.el https://raw.githubusercontent.com/nashamri/spacemacs-theme/master/spacemacs-theme-pkg.el core/libs/spacemacs-theme/spacemacs-theme-pkg.el -https://raw.githubusercontent.com/sigma/mocker.el/master/mocker.el core/libs/mocker.el \ No newline at end of file +https://raw.githubusercontent.com/sigma/mocker.el/master/mocker.el core/libs/mocker.el +https://raw.githubusercontent.com/Malabarba/validate.el/master/validate.el core/libs/validate.el diff --git a/CHANGELOG.develop b/CHANGELOG.develop index 8cc1459fd..b791e1b4a 100644 --- a/CHANGELOG.develop +++ b/CHANGELOG.develop @@ -462,6 +462,7 @@ In org-agenda-mode - flow-type (replaced by LSP in =javascript= layer) - ycmd (moved to =c-c++=) *** Dotfile changes +- Added type bases validation for .spacemacs variables. - New Variables: - New dotvariable =dotspacemacs-auto-generate-layout-names= (thanks to bmag) - New dotvariables =dotspacemacs-emacs-pdumper-executable-file=, diff --git a/core/core-compilation.el b/core/core-compilation.el index 381275860..b962e2136 100644 --- a/core/core-compilation.el +++ b/core/core-compilation.el @@ -29,6 +29,7 @@ "core/libs/package-recipe-mode.el" "core/libs/package-recipe.el" "core/libs/page-break-lines.el" + "core/libs/validate.el" "core/libs/quelpa.el" "core/libs/spinner.el") "List of Spacemacs files that should be compiled. diff --git a/core/core-configuration-layer.el b/core/core-configuration-layer.el index f01cf282d..aaf87fe98 100644 --- a/core/core-configuration-layer.el +++ b/core/core-configuration-layer.el @@ -1381,6 +1381,15 @@ Returns nil if the directory is not a category." (when (string-match "^+" dirname) (intern (substring dirname 1)))))) +(defun configuration-layer//get-layer-parent-category (layer-name) + "Return a parent category symbol for given LAYER-NAME. +Returns nil if there is no layer named LAYER-NAME." + (when-let ((lp (configuration-layer/get-layer-path layer-name))) + (thread-last lp + directory-file-name + file-name-directory + configuration-layer//get-category-from-path))) + (defun configuration-layer/discover-layers (&optional refresh-index) "Initialize `configuration-layer--indexed-layers' with layer directories. If REFRESH-INDEX is non-nil, the layer index is cleared before @@ -1591,7 +1600,11 @@ RNAME is the name symbol of another existing layer." (let ((var (pop variables))) (if (consp variables) (condition-case-unless-debug err - (set-default var (eval (pop variables))) + (let ((val (eval (pop variables)))) + (when (get var 'spacemacs-customization--variable) + (spacemacs-customization//validate + val (custom-variable-type var))) + (set-default var val)) ('error (configuration-layer//error (concat "\nAn error occurred while setting layer " @@ -1636,7 +1649,11 @@ RNAME is the name symbol of another existing layer." "Configure layers with LAYER-NAMES." (let ((warning-minimum-level :error)) (dolist (layer-name layer-names) - (configuration-layer//load-layer-files layer-name '("config.el"))))) + (let ((spacemacs-customization--current-group + (spacemacs-customization//create-layer-group + layer-name + (configuration-layer//get-layer-parent-category layer-name)))) + (configuration-layer//load-layer-files layer-name '("config.el")))))) (defun configuration-layer//declare-used-packages (layers) "Declare used packages contained in LAYERS." diff --git a/core/core-customization.el b/core/core-customization.el new file mode 100644 index 000000000..7062c9d3a --- /dev/null +++ b/core/core-customization.el @@ -0,0 +1,130 @@ +;;; core-customization.el --- Spacemacs Core File -*- lexical-binding: t -*- +;; +;; Copyright (c) 2012-2021 Sylvain Benner & Contributors +;; +;; Author: Eugene "JAremko" Yaremenko +;; URL: https://github.com/syl20bnr/spacemacs +;; +;; This file is not part of GNU Emacs. +;; +;;; License: GPLv3 + +(require 'validate) +(require 'seq) +(eval-when-compile (require 'cl-lib)) + +(defalias 'spacemacs-customization//validate 'validate-value) + +(defvar spacemacs-customization--current-group nil + "Used to auto-set `spacemacs|defc' group.") + +(defgroup spacemacs-layers nil + "Spacemacs layers customizations." + :group 'spacemacs + :prefix 'spacemacs-layers-) + +(defgroup spacemacs--uncustomizable nil + "Dummy group that contains variables that can't be customized.") + +(defmacro spacemacs|defc (symbol standard doc type &optional group-override) + "Spacemacs flavored `defcustom' for .spacemacs configurations. +SYMBOL is the variable name; it should not be quoted. +STANDARD is an expression specifying the variable's standard value. +DOC is a doc-string. +TYPE should be a widget type for editing the symbol's value. + See Info node `(elisp) Customization Types' for a list of + base types and useful composite types. +GROUP-OVERRIDE should be provided if you don't want Spacemacs to infer the + configuration group from the currently configured layer name. + +NOTE: You can use interactive function `spacemacs/customization-valid-p' to +test if a variable has a proper type. +NOTE: `core-dotspacemacs' contains plenty of customization examples. +NOTE: Spacemacs checks variables using validate.el package. Currently it +doesn't support: `:inline', `plist', `coding-system', `color', `hook', +`restricted-sexp' types so more general ones should be used instead." + (declare (indent defun) (doc-string 3) (debug (name body))) + `(progn + (put ',symbol 'spacemacs-customization--variable t) + (custom-declare-variable + ',symbol + ;; Took this from the `defcustom' implementation. + ,(if lexical-binding + ``(funcall #',(lambda () ,standard)) + `',standard) + ,(format "%s\n\nTYPE: %s\n" doc type) + :type ,type + :group + (or ,group-override + spacemacs-customization--current-group + 'spacemacs--uncustomizable)))) + +(defun spacemacs/customization-valid-p (var-symbol) + "returns true if symbol refers spacemacs custom variable with valid value. +Emits message with the result when called interactively." + (interactive "v") + (let* ((defc? (get var-symbol 'spacemacs-customization--variable)) + (val (symbol-value var-symbol)) + (type (custom-variable-type var-symbol)) + (valid? (and defc? (validate-value val type t)))) + (when (called-interactively-p 'interactive) + (if valid? + (message "symbol: \"%s\" value: \"%s\" type: \"%s\" is valid." + var-symbol val type) + (if defc? + (condition-case err (validate-value val type) (error (message err))) + (message "%s is not Spacemacs customization variable" var-symbol)))) + valid?)) + +(defun spacemacs-customization//group-variables (group-symbol) + "Given customization group symbol get its variables." + (let (ret-val) + (cl-labels ((rec (gs) + (cl-dolist (el (get gs 'custom-group)) + (cl-case (cadr el) + (custom-variable (push (car el) ret-val)) + (custom-group (rec (car el))))))) + (rec group-symbol)) + ret-val)) + +(defun spacemacs-customization//validate-group-vars (group-symbol) + "Given customization group symbol validate its variables." + (dolist (var (spacemacs-customization//group-variables group-symbol)) + (let ((val (symbol-value var)) + (type (custom-variable-type var))) + (condition-case err (validate-value val type) + (error (error (concat "Variable: \"%s\" " + "has value: \"%s\" " + "that doesn't match its type: \"%s\". " + "Validator message: \"%s\"") + var val type err)))))) + +(defun spacemacs-customization//validate-dotspacemacs-init-vars () + "Validate variables set in `dotspacemacs/init' function." + (spacemacs-customization//validate-group-vars 'spacemacs-dotspacemacs-init)) + +(defun spacemacs-customization//validate-dotspacemacs-layers-vars () + "Validate variables set in `dotspacemacs/layers' function." + (spacemacs-customization//validate-group-vars 'spacemacs-dotspacemacs-layers)) + +(defun spacemacs-customization//create-layer-group (layer-name category-name) + "Create customization group heirarchy for the LAYER-NAME configurations. +Layers customization group symbol is returned." + (let* ((category-group-name (format "spacemacs-layers-%s" category-name)) + (layer-group-name (format "%s-%s" category-group-name layer-name)) + (category-group-symbol (intern category-group-name))) + (custom-declare-group category-group-symbol + nil + (format "Spacemacs %s category customizations." + category-name) + :group 'spacemacs-layers + :prefix (intern (concat category-group-name "-"))) + (custom-declare-group + (intern layer-group-name) + nil + (format "Spacemacs %s layer customizations." layer-name) + :group category-group-symbol + :prefix (intern (concat category-group-name "-" + layer-group-name "-"))))) + +(provide 'core-customization) diff --git a/core/core-dotspacemacs.el b/core/core-dotspacemacs.el index a4d850e09..21a70929d 100644 --- a/core/core-dotspacemacs.el +++ b/core/core-dotspacemacs.el @@ -8,6 +8,20 @@ ;; This file is not part of GNU Emacs. ;; ;;; License: GPLv3 + +(require 'core-customization) + +;; Those groups exists for bookkeeping. +(defgroup spacemacs-dotspacemacs-init nil + "Dotspacemacs init customizations." + :group 'spacemacs--uncustomizable + :prefix 'spacemacs--uncustomizable-spacemacs-dotspacemacs-init-) + +(defgroup spacemacs-dotspacemacs-layers nil + "Dotspacemacs layers customizations." + :group 'spacemacs--uncustomizable + :prefix 'spacemacs--uncustomizable-spacemacs-dotspacemacs-layers-) + (defconst dotspacemacs-template-directory (expand-file-name (concat spacemacs-core-directory "templates/")) "Templates directory.") @@ -53,73 +67,99 @@ exists, then this is used. If ~/.spacemacs does not exist, then check for init.el in dotspacemacs-directory and use this if it exists. Otherwise, fallback to ~/.spacemacs")) -(defvar dotspacemacs-distribution 'spacemacs +(spacemacs|defc dotspacemacs-distribution 'spacemacs "Base distribution to use. This is a layer contained in the directory `+distributions'. For now available distributions are `spacemacs-base' -or `spacemacs'.") +or `spacemacs'." + '(choice (const spacemacs-base) (const spacemacs)) + 'spacemacs-dotspacemacs-layers) -(defvar dotspacemacs-enable-emacs-pdumper nil +(spacemacs|defc dotspacemacs-enable-emacs-pdumper nil "If non-nil then enable support for the portable dumper. You'll need to compile Emacs 27 from source following the instructions in file -EXPERIMENTAL.org at the root of the git repository.") +EXPERIMENTAL.org at the root of the git repository." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-emacs-pdumper-executable-file "emacs" - "File path pointing to emacs 27 or later executable.") +(spacemacs|defc dotspacemacs-emacs-pdumper-executable-file "emacs" + "File path pointing to emacs 27 or later executable." + 'string + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-emacs-dumper-dump-file +(spacemacs|defc dotspacemacs-emacs-dumper-dump-file (format "spacemacs-%s.pdmp" emacs-version) "Name of the Spacemacs dump file. This is the file will be created by the portable dumper in the cache directory under dumps sub-directory. To load it when starting Emacs add the parameter `--dump-file' when invoking Emacs 27.1 executable on the command line, for instance: -./emacs --dump-file=$HOME/.emacs.d/.cache/dumps/spacemacs-27.1.pdmp") +./emacs --dump-file=$HOME/.emacs.d/.cache/dumps/spacemacs-27.1.pdmp" + 'string + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-gc-cons '(100000000 0.1) +(spacemacs|defc dotspacemacs-gc-cons '(100000000 0.1) "Set `gc-cons-threshold' and `gc-cons-percentage' when startup finishes. This is an advanced option and should not be changed unless you suspect -performance issues due to garbage collection operations.") +performance issues due to garbage collection operations." + '(list integer float) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-read-process-output-max (* 1024 1024) +(spacemacs|defc dotspacemacs-read-process-output-max (* 1024 1024) "Set `read-process-output-max' when startup finishes. This defines how much data is read from a foreign process. Setting this >= 1 MB should increase performance for lsp servers -in emacs 27.") +in emacs 27." + 'integer + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-elpa-https t +(spacemacs|defc dotspacemacs-elpa-https t "If non nil ELPA repositories are contacted via HTTPS whenever it's possible. Set it to nil if you have no way to use HTTPS in your -environment, otherwise it is strongly recommended to let it set to t.") +environment, otherwise it is strongly recommended to let it set to t." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-elpa-timeout 5 - "Maximum allowed time in seconds to contact an ELPA repository.") +(spacemacs|defc dotspacemacs-elpa-timeout 5 + "Maximum allowed time in seconds to contact an ELPA repository." + 'integer + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-use-spacelpa nil +(spacemacs|defc dotspacemacs-use-spacelpa nil "If non-nil then Spacelpa repository is the primary source to install a locked version of packages. If nil then Spacemacs will install the latest version of packages from MELPA. Spacelpa is currently in experimental -state and should only be used for testing.") +state and should only be used for testing." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-verify-spacelpa-archives nil - "If non-nil then verify the signature for downloaded Spacelpa archives.") +(spacemacs|defc dotspacemacs-verify-spacelpa-archives nil + "If non-nil then verify the signature for downloaded Spacelpa archives." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-elpa-subdirectory 'emacs-version +(spacemacs|defc dotspacemacs-elpa-subdirectory 'emacs-version "If non-nil, a form that evaluates to a package directory. For example, to use different package directories for different Emacs -versions, set this to `emacs-version'.") +versions, set this to `emacs-version'." + 'sexp + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-configuration-layer-path '() +(spacemacs|defc dotspacemacs-configuration-layer-path '() "List of additional paths where to look for configuration layers. -Paths must have a trailing slash (ie. `~/.mycontribs/')") +Paths must have a trailing slash (ie. `~/.mycontribs/')" + '(repeat string) + 'spacemacs-dotspacemacs-layers) -(defvar dotspacemacs-install-packages 'used-only +(spacemacs|defc dotspacemacs-install-packages 'used-only "Defines the behaviour of Spacemacs when installing packages. Possible values are `used-only', `used-but-keep-unused' and `all'. `used-only' installs only explicitly used packages and deletes any unused packages as well as their unused dependencies. `used-but-keep-unused' installs only the used packages but won't delete unused ones. `all' installs *all* -packages supported by Spacemacs and never uninstalls them.") +packages supported by Spacemacs and never uninstalls them." + '(choice (const used-only) (const used-but-keep-unused) (const all)) + 'spacemacs-dotspacemacs-layers) -(defvar dotspacemacs-enable-lazy-installation 'unused +(spacemacs|defc dotspacemacs-enable-lazy-installation 'unused "Lazy installation of layers (i.e. layers are installed only when a file with a supported type is opened). Possible values are `all', `unused' and `nil'. `unused' will lazy install only unused layers (i.e. layers not listed in @@ -127,226 +167,333 @@ variable `dotspacemacs-configuration-layers'), `all' will lazy install any layer that support lazy installation even the layers listed in `dotspacemacs-configuration-layers'. `nil' disable the lazy installation feature and you have to explicitly list a layer in the variable -`dotspacemacs-configuration-layers' to install it.") +`dotspacemacs-configuration-layers' to install it." + '(choice (const all) (const unused) (const nil)) + 'spacemacs-dotspacemacs-layers) -(defvar dotspacemacs-ask-for-lazy-installation t +(spacemacs|defc dotspacemacs-ask-for-lazy-installation t "If non-nil then Spacemacs will ask for confirmation before installing -a layer lazily.") +a layer lazily." + 'boolean + 'spacemacs-dotspacemacs-layers) -(defvar dotspacemacs-additional-packages '() +(spacemacs|defc dotspacemacs-additional-packages '() "List of additional packages that will be installed wihout being wrapped in a layer. If you need some configuration for these packages then consider to create a layer, you can also put the -configuration in `dotspacemacs/user-config'.") +configuration in `dotspacemacs/user-config'." + '(repeat symbol) + 'spacemacs-dotspacemacs-layers) (defvar dotspacemacs--additional-theme-packages '() "Same as `dotspacemacs-additional-packages' but reserved for themes declared in `dotspacemacs-themes'.") -(defvar dotspacemacs-editing-style 'vim +(spacemacs|defc dotspacemacs-editing-style 'vim "One of `vim', `emacs' or `hybrid'. `hybrid' is like `vim' except that `insert state' is replaced by the `hybrid state' with `emacs' key bindings. The value can also be a list with `:variables' keyword (similar to layers). Check the editing styles - section of the documentation for details on available variables.") + section of the documentation for details on available variables." + '(choice (const vim) (const emacs) (const hybrid)) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-startup-banner 'official +(spacemacs|defc dotspacemacs-startup-banner 'official "Specify the startup banner. Default value is `official', it displays the official spacemacs logo. An integer value is the index of text banner, `random' chooses a random text banner in `core/banners' directory. A string value must be a path to a .PNG file. -If the value is nil then no banner is displayed.") +If the value is nil then no banner is displayed." + '(choice (const official) (const random) (const nil) string integer) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-startup-buffer-show-version t - "If bound, show Spacemacs and Emacs version at the top right of the -Spacemacs buffer.") +(spacemacs|defc dotspacemacs-startup-buffer-show-version t + "If true, show Spacemacs and Emacs version at the top right of the +Spacemacs buffer." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-scratch-mode 'text-mode - "Default major mode of the scratch buffer.") +(spacemacs|defc dotspacemacs-scratch-mode 'text-mode + "Default major mode of the scratch buffer." + 'symbol + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-initial-scratch-message 'nil - "Initial message in the scratch buffer.") +(spacemacs|defc dotspacemacs-initial-scratch-message 'nil + "Initial message in the scratch buffer." + '(choice (const nil) string) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-check-for-update nil +(spacemacs|defc dotspacemacs-check-for-update nil "If non nil then spacemacs will check for updates at startup when the current branch is not `develop'. Note that checking for new versions works via git commands, thus it calls GitHub services -whenever you start Emacs.") +whenever you start Emacs." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-configuration-layers '(emacs-lisp) - "List of configuration layers to load.") +(spacemacs|defc dotspacemacs-configuration-layers '(emacs-lisp) + "List of configuration layers to load." + '(repeat (choice symbol (cons symbol sexp))) + 'spacemacs-dotspacemacs-layers) (defvar dotspacemacs--configuration-layers-saved nil "Saved value of `dotspacemacs-configuration-layers' after sync.") -(defvar dotspacemacs-themes '(spacemacs-dark - spacemacs-light) +(spacemacs|defc dotspacemacs-themes '(spacemacs-dark + spacemacs-light) "List of themes, the first of the list is loaded when spacemacs starts. Press `SPC T n' to cycle to the next theme in the list (works great -with 2 themes variants, one dark and one light") +with 2 themes variants, one dark and one light" + '(repeat symbol) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-mode-line-theme '(spacemacs - :separator wave - :separator-scale 1.5) +(spacemacs|defc dotspacemacs-mode-line-theme '(spacemacs + :separator wave + :separator-scale 1.5) "Set the theme for the Spaceline. Supported themes are `spacemacs', `all-the-icons', `custom', `doom',`vim-powerline' and `vanilla'. The first three are spaceline themes. `doom' is the doom-emacs mode-line. `vanilla' is default Emacs mode-line. `custom' is a user defined themes, refer to the DOCUMENTATION.org for more info on how to create your own spaceline theme. Value can be a symbol or a list with additional properties like '(all-the-icons -:separator-scale 1.5).") +:separator-scale 1.5)." + '(choice (const spacemacs) + (const all-the-icons) + (const custom) + (const doom) + (const vim-powerline) + (const vanilla) -(defvar dotspacemacs-frame-title-format "%I@%S" + (cons (choice (const spacemacs) + (const all-the-icons) + (const custom) + (const doom) + (const vim-powerline) + (const vanilla)) + sexp)) + 'spacemacs-dotspacemacs-init) + +(spacemacs|defc dotspacemacs-frame-title-format "%I@%S" "Default format string for a frame title bar, using the -original format spec, and additional customizations.") +original format spec, and additional customizations." + 'string + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-icon-title-format nil +(spacemacs|defc dotspacemacs-icon-title-format nil "Default format string for a icon title bar, using the -original format spec, and additional customizations.") +original format spec, and additional customizations." + '(choice (const nil) string) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-colorize-cursor-according-to-state t - "If non nil the cursor color matches the state color in GUI Emacs.") +(spacemacs|defc dotspacemacs-colorize-cursor-according-to-state t + "If non nil the cursor color matches the state color in GUI Emacs." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-leader-key "SPC" - "The leader key.") +(spacemacs|defc dotspacemacs-leader-key "SPC" + "The leader key." + 'string + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-emacs-leader-key "M-m" - "The leader key accessible in `emacs state' and `insert state'") +(spacemacs|defc dotspacemacs-emacs-leader-key "M-m" + "The leader key accessible in `emacs state' and `insert state'" + 'string + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-major-mode-leader-key "," +(spacemacs|defc dotspacemacs-major-mode-leader-key "," "Major mode leader key is a shortcut key which is the equivalent of -pressing ` m`. Set it to `nil` to disable it.") +pressing ` m`. Set it to `nil` to disable it." + 'string + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-major-mode-emacs-leader-key (if window-system "" "C-M-m") - "Major mode leader key accessible in `emacs state' and `insert state'") +(spacemacs|defc dotspacemacs-major-mode-emacs-leader-key + (if window-system "" "C-M-m") + "Major mode leader key accessible in `emacs state' and `insert state'" + 'string + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-ex-command-key ":" - "The key used for Vim Ex commands.") +(spacemacs|defc dotspacemacs-ex-command-key ":" + "The key used for Vim Ex commands." + 'string + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-command-key "SPC" - "The key used for Emacs commands (M-x) (after pressing on the leader key).") +(spacemacs|defc dotspacemacs-command-key "SPC" + "The key used for Emacs commands (M-x) (after pressing on the leader key)." + 'string + 'spacemacs-dotspacemacs-init) (defvaralias 'dotspacemacs-emacs-command-key 'dotspacemacs-command-key "New official name for `dotspacemacs-command-key'") -(defvar dotspacemacs-distinguish-gui-tab nil - "If non nil, distinguish C-i and tab in the GUI version of Emacs.") +(spacemacs|defc dotspacemacs-distinguish-gui-tab nil + "If non nil, distinguish C-i and tab in the GUI version of Emacs." + 'boolean + 'spacemacs-dotspacemacs-init) ;; (defvar dotspacemacs-distinguish-gui-ret nil ;; "If non nil, distinguish C-m and return in the GUI version of ;; emacs.") -(defvar dotspacemacs-default-font '("Source Code Pro" - :size 10.0 - :weight normal - :width normal) +(spacemacs|defc dotspacemacs-default-font '("Source Code Pro" + :size 10.0 + :weight normal + :width normal) "Default font, or prioritized list of fonts. This setting has no effect when -running Emacs in terminal.") +running Emacs in terminal." + '(choice (cons string sexp) + (repeat (cons string sexp))) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-folding-method 'evil - "Code folding method. Possible values are `evil', `origami' and `vimish'.") +(spacemacs|defc dotspacemacs-folding-method 'evil + "Code folding method. Possible values are `evil', `origami' and `vimish'." + '(choice (cosnt evil) (const origami) (const vimish)) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-default-layout-name "Default" - "Name of the default layout.") +(spacemacs|defc dotspacemacs-default-layout-name "Default" + "Name of the default layout." + 'string + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-display-default-layout nil - "If non nil the default layout name is displayed in the mode-line.") +(spacemacs|defc dotspacemacs-display-default-layout nil + "If non nil the default layout name is displayed in the mode-line." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-auto-resume-layouts nil +(spacemacs|defc dotspacemacs-auto-resume-layouts nil "If non nil then the last auto saved layouts are resume automatically upon -start.") +start." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-auto-generate-layout-names nil +(spacemacs|defc dotspacemacs-auto-generate-layout-names nil "If non-nil, auto-generate layout name when creating new layouts. -Only has effect when using the \"jump to layout by number\" commands.") +Only has effect when using the \"jump to layout by number\" commands." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-max-rollback-slots 5 - "Maximum number of rollback slots to keep in the cache.") +(spacemacs|defc dotspacemacs-max-rollback-slots 5 + "Maximum number of rollback slots to keep in the cache." + 'integer + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-large-file-size 1 +(spacemacs|defc dotspacemacs-large-file-size 1 "Size (in MB) above which spacemacs will prompt to open the large file literally to avoid performance issues. Opening a file literally means that -no major mode or minor modes are active.") +no major mode or minor modes are active." + 'integer + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-auto-save-file-location 'cache +(spacemacs|defc dotspacemacs-auto-save-file-location 'cache "Location where to auto-save files. Possible values are `original' to auto-save the file in-place, `cache' to auto-save the file to another -file stored in the cache directory and `nil' to disable auto-saving.") +file stored in the cache directory and `nil' to disable auto-saving." + '(choice (const cache) (const original) (const nil)) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-enable-paste-transient-state nil +(spacemacs|defc dotspacemacs-enable-paste-transient-state nil "If non-nil, the paste transient-state is enabled. While enabled, after you paste something, pressing `C-j' and `C-k' several times cycles through the -elements in the `kill-ring'.") +elements in the `kill-ring'." + 'boolean + 'spacemacs-dotspacemacs-init) (defvaralias 'dotspacemacs-enable-paste-micro-state 'dotspacemacs-enable-paste-transient-state "Old name of `dotspacemacs-enable-paste-transient-state'.") -(defvar dotspacemacs-which-key-delay 0.4 +(spacemacs|defc dotspacemacs-which-key-delay 0.4 "Delay in seconds starting from the last keystroke after which the which-key buffer will be shown if you have not completed a key sequence. Setting this variable is equivalent to setting -`which-key-idle-delay'.") +`which-key-idle-delay'." + 'float + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-which-key-position 'bottom +(spacemacs|defc dotspacemacs-which-key-position 'bottom "Location of the which-key popup buffer. Possible choices are bottom, right, and right-then-bottom. The last one will display on the -right if possible and fallback to bottom if not.") +right if possible and fallback to bottom if not." + '(choice (const right) (const bottom) (const right-then-bottom)) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-switch-to-buffer-prefers-purpose nil +(spacemacs|defc dotspacemacs-switch-to-buffer-prefers-purpose nil "Control where `switch-to-buffer' displays the buffer. If nil, `switch-to-buffer' displays the buffer in the current window even if another same-purpose window is available. If non nil, `switch-to-buffer' displays the buffer in a same-purpose window even if the buffer can be displayed in the current -window.") +window." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-loading-progress-bar t +(spacemacs|defc dotspacemacs-loading-progress-bar t "If non nil a progress bar is displayed when spacemacs is loading. This may increase the boot time on some systems and emacs builds, set it to nil -to boost the loading time.") +to boost the loading time." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-fullscreen-at-startup nil - "If non nil the frame is fullscreen when Emacs starts up (Emacs 24.4+ only).") +(spacemacs|defc dotspacemacs-fullscreen-at-startup nil + "If non nil the frame is fullscreen when Emacs starts up (Emacs 24.4+ only)." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-fullscreen-use-non-native nil +(spacemacs|defc dotspacemacs-fullscreen-use-non-native nil "If non nil `spacemacs/toggle-fullscreen' will not use native fullscreen. Use -to disable fullscreen animations on macOS.") +to disable fullscreen animations on macOS." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-maximized-at-startup nil +(spacemacs|defc dotspacemacs-maximized-at-startup nil "If non nil the frame is maximized when Emacs starts up (Emacs 24.4+ only). -Takes effect only if `dotspacemacs-fullscreen-at-startup' is nil.") +Takes effect only if `dotspacemacs-fullscreen-at-startup' is nil." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-undecorated-at-startup nil - "If non nil the frame is undecorated when Emacs starts up.") +(spacemacs|defc dotspacemacs-undecorated-at-startup nil + "If non nil the frame is undecorated when Emacs starts up." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-active-transparency 90 +(spacemacs|defc dotspacemacs-active-transparency 90 "A value from the range (0..100), in increasing opacity, which describes the transparency level of a frame when it's active or selected. Transparency -can be toggled through `toggle-transparency'.") +can be toggled through `toggle-transparency'." + 'integer + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-inactive-transparency 90 +(spacemacs|defc dotspacemacs-inactive-transparency 90 "A value from the range (0..100), in increasing opacity, which describes the transparency level of a frame when it's inactive or deselected. Transparency -can be toggled through `toggle-transparency'.") +can be toggled through `toggle-transparency'." + 'integer + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-show-transient-state-title t - "If non nil show the titles of transient states.") +(spacemacs|defc dotspacemacs-show-transient-state-title t + "If non nil show the titles of transient states." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-show-transient-state-color-guide t - "If non nil show the color guide hint for transient state keys.") +(spacemacs|defc dotspacemacs-show-transient-state-color-guide t + "If non nil show the color guide hint for transient state keys." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-mode-line-unicode-symbols t +(spacemacs|defc dotspacemacs-mode-line-unicode-symbols t "If non nil unicode symbols are displayed in the mode-line (eg. for lighters). If you use Emacs as a daemon and wants unicode characters only in GUI set -the value to quoted `display-graphic-p'. (default t)") +the value to quoted `display-graphic-p'. (default t)" + '(choice boolean (connst display-graphic-p)) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-smooth-scrolling t +(spacemacs|defc dotspacemacs-smooth-scrolling t "If non nil smooth scrolling (native-scrolling) is enabled. Smooth scrolling overrides the default behavior of Emacs which recenters point when it reaches the top or bottom of the -screen.") +screen." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-line-numbers nil +(spacemacs|defc dotspacemacs-line-numbers nil "Control line numbers activation. If set to `t' or `relative' line numbers are turned on in all `prog-mode' and `text-mode' derivatives. If set to `relative', line numbers are relative. @@ -360,59 +507,87 @@ This variable can also be set to a property list for finer control: text-mode :size-limit-kb 1000) The property `:enabled-for-modes' takes priority over `:disabled-for-modes' and -restricts line-number to the specified list of major-mode.") +restricts line-number to the specified list of major-mode." + '(choice boolean + (const relative) + (const visual) + (const prog-mode) + (repeat sexp)) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-enable-server nil - "If non-nil, start an Emacs server if one is not already running.") +(spacemacs|defc dotspacemacs-enable-server nil + "If non-nil, start an Emacs server if one is not already running." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-persistent-server nil - "If non nil advises quit functions to keep server open when quitting.") +(spacemacs|defc dotspacemacs-persistent-server nil + "If non nil advises quit functions to keep server open when quitting." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-server-socket-dir nil +(spacemacs|defc dotspacemacs-server-socket-dir nil "Set the emacs server socket location. If nil, uses whatever the Emacs default is, otherwise a directory path like \"~/.emacs.d/server\". -Has no effect if `dotspacemacs-enable-server' is nil.") +Has no effect if `dotspacemacs-enable-server' is nil." + '(choice (const nil) string) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-smartparens-strict-mode nil +(spacemacs|defc dotspacemacs-smartparens-strict-mode nil "If non-nil and `dotspacemacs-activate-smartparens-mode' is also non-nil, -smartparens-strict-mode will be enabled in programming modes.") +smartparens-strict-mode will be enabled in programming modes." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-activate-smartparens-mode t - "If non-nil smartparens-mode will be enabled in programming modes.") +(spacemacs|defc dotspacemacs-activate-smartparens-mode t + "If non-nil smartparens-mode will be enabled in programming modes." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-smart-closing-parenthesis nil +(spacemacs|defc dotspacemacs-smart-closing-parenthesis nil "If non-nil pressing the closing parenthesis `)' key in insert mode passes over any automatically added closing parenthesis, bracket, quote, etc... -This can be temporary disabled by pressing `C-q' before `)'. (default nil)") +This can be temporary disabled by pressing `C-q' before `)'. (default nil)" + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-zone-out-when-idle nil +(spacemacs|defc dotspacemacs-zone-out-when-idle nil "Either nil or a number of seconds. -If non-nil zone out after the specified number of seconds.") +If non-nil zone out after the specified number of seconds." + '(choice (const nil) integer) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-highlight-delimiters 'all +(spacemacs|defc dotspacemacs-highlight-delimiters 'all "Select a scope to highlight delimiters. Possible values are `any', `current', `all' or `nil'. -Default is `all' (highlight any scope and emphasize the current one.") +Default is `all' (highlight any scope and emphasize the current one." + '(choice (const all) (const any) (const current) (const nil)) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-show-trailing-whitespace t - "Show trailing whitespace. Default is `t'.") +(spacemacs|defc dotspacemacs-show-trailing-whitespace t + "Show trailing whitespace. Default is `t'." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-whitespace-cleanup nil +(spacemacs|defc dotspacemacs-whitespace-cleanup nil "Delete whitespace while saving buffer. Possible values are: `all' to aggressively delete empty lines and long sequences of whitespace, `trailing' to delete only the whitespace at end of lines, `changed' to delete only whitespace for changed lines or -`nil' to disable cleanup.") +`nil' to disable cleanup." + '(choice (const nil) (const all) (const trailing) (const changed)) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-search-tools '("rg" "ag" "pt" "ack" "grep") +(spacemacs|defc dotspacemacs-search-tools '("rg" "ag" "pt" "ack" "grep") "List of search tool executable names. Spacemacs uses the first installed -tool of the list. Supported tools are `rg', `ag', `pt', `ack' and `grep'.") +tool of the list. Supported tools are `rg', `ag', `pt', `ack' and `grep'." + '(set (const "rg") (const "ag") (const "pt") (const "ack") (const "grep")) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-startup-lists '((recents . 5) - (projects . 7)) +(spacemacs|defc dotspacemacs-startup-lists '((recents . 5) + (projects . 7)) "Association list of items to show in the startup buffer of the form `(list-type . list-size)`. If nil it is disabled. @@ -422,60 +597,94 @@ List sizes may be nil, in which case `spacemacs--buffer-startup-lists-length' takes effect. In the `recents-by-project' case, the list size should be a `cons' cell whose `car' is the maximum number of projects to show, and whose `cdr' is the maximum -number of recent files to show in each project.") +number of recent files to show in each project." + '(choice (alist :key-type (choice (const recents) + (const recents-by-project) + (const bookmarks) + (const projects) + (const agenda) + (const todos)) + :value-type (choice integer + (const nil) + ;; for `recents-by-project': + (cons integer integer))) + (const nil)) + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-startup-buffer-responsive t - "True if the home buffer should respond to resize events.") +(spacemacs|defc dotspacemacs-startup-buffer-responsive t + "True if the home buffer should respond to resize events." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-excluded-packages '() - "A list of packages that will not be installed and loaded.") +(spacemacs|defc dotspacemacs-excluded-packages '() + "A list of packages that will not be installed and loaded." + '(repeat symbol) + 'spacemacs-dotspacemacs-layers) -(defvar dotspacemacs-frozen-packages '() - "A list of packages that cannot be updated.") +(spacemacs|defc dotspacemacs-frozen-packages '() + "A list of packages that cannot be updated." + '(repeat symbol) + 'spacemacs-dotspacemacs-layers) -(defvar dotspacemacs-pretty-docs nil +(spacemacs|defc dotspacemacs-pretty-docs nil "Run `spacemacs/prettify-org-buffer' when -visiting README.org files of Spacemacs.") +visiting README.org files of Spacemacs." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-new-empty-buffer-major-mode nil - "Set the major mode for a new empty buffer.") +(spacemacs|defc dotspacemacs-new-empty-buffer-major-mode nil + "Set the major mode for a new empty buffer." + 'symbol + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-use-clean-aindent-mode t +(spacemacs|defc dotspacemacs-use-clean-aindent-mode t "Correct indentation for simple modes. If non nil activate `clean-aindent-mode' which tries to correct virtual indentation of simple modes. This can interfer with mode specific indent handling like has been reported for `go-mode'. -If it does deactivate it here. (default t)") +If it does deactivate it here. (default t)" + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-swap-number-row nil +(spacemacs|defc dotspacemacs-swap-number-row nil "Shift number row for easier access. If non-nil shift your number row to match the entered keyboard layout (only in insert mode). Currently the keyboard layouts (qwerty-us qwertz-de) are supported. New layouts can be added in `spacemacs-editing' layer. -(default nil)") +(default nil)" + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-home-shorten-agenda-source nil +(spacemacs|defc dotspacemacs-home-shorten-agenda-source nil "If nil the home buffer shows the full path of agenda items -and todos. If non nil only the file name is shown.") +and todos. If non nil only the file name is shown." + 'boolean + 'spacemacs-dotspacemacs-init) (defvar dotspacemacs--pretty-ignore-subdirs '(".cache/junk") "Subdirectories of `spacemacs-start-directory' to ignore when prettifying Org files.") -(defvar dotspacemacs-scratch-buffer-persistent nil +(spacemacs|defc dotspacemacs-scratch-buffer-persistent nil "If non-nil, *scratch* buffer will be persistent. Things you write down in - *scratch* buffer will be saved automatically.") + *scratch* buffer will be saved automatically." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-scratch-buffer-unkillable nil +(spacemacs|defc dotspacemacs-scratch-buffer-unkillable nil "If non-nil, `kill-buffer' on *scratch* buffer -will bury it instead of killing.") +will bury it instead of killing." + 'boolean + 'spacemacs-dotspacemacs-init) -(defvar dotspacemacs-byte-compile nil - "If non-nil, byte-compile some of Spacemacs files.") +(spacemacs|defc dotspacemacs-byte-compile nil + "If non-nil, byte-compile some of Spacemacs files." + 'boolean + 'spacemacs-dotspacemacs-init) (defun dotspacemacs//prettify-spacemacs-docs () "Run `spacemacs/prettify-org-buffer' if `buffer-file-name' @@ -732,8 +941,12 @@ If ARG is non nil then ask questions to the user before installing the dotfile." (unless (with-demoted-errors "Error loading .spacemacs: %S" (load dotspacemacs)) (dotspacemacs/safe-load)))) - (advice-add 'dotspacemacs/user-config - :around 'dotspacemacs//profile-user-config)) + (advice-add 'dotspacemacs/layers :after + 'spacemacs-customization//validate-dotspacemacs-layers-vars) + (advice-add 'dotspacemacs/init :after + 'spacemacs-customization//validate-dotspacemacs-init-vars) + (advice-add 'dotspacemacs/user-config :around + 'dotspacemacs//profile-user-config)) (defun spacemacs/title-prepare (title-format) "A string is printed verbatim except for %-constructs. diff --git a/core/core-spacemacs.el b/core/core-spacemacs.el index 23821b6a3..13da54b89 100644 --- a/core/core-spacemacs.el +++ b/core/core-spacemacs.el @@ -10,6 +10,11 @@ ;;; License: GPLv3 (setq message-log-max 16384) +(defgroup spacemacs nil + "Spacemacs customizations." + :group 'emacs + :prefix 'spacemacs-) + (require 'subr-x nil 'noerror) (require 'core-emacs-backports) (require 'core-env) @@ -35,11 +40,6 @@ (require 'core-spacebind) (require 'core-compilation) -(defgroup spacemacs nil - "Spacemacs customizations." - :group 'starter-kit - :prefix 'spacemacs-) - (defvar spacemacs-post-user-config-hook nil "Hook run after dotspacemacs/user-config") (defvar spacemacs-post-user-config-hook-run nil diff --git a/core/libs/validate.el b/core/libs/validate.el new file mode 100644 index 000000000..f94d598c8 --- /dev/null +++ b/core/libs/validate.el @@ -0,0 +1,213 @@ +;;; validate.el --- Schema validation for Emacs-lisp -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 Free Software Foundation, Inc. + +;; Author: Artur Malabarba +;; Keywords: lisp +;; Package-Requires: ((emacs "24.1") (cl-lib "0.5") (seq "2.16")) +;; Version: 1.0.4 + +;;; 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. +;; +;; 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 GNU Emacs. If not, see . + +;;; Code: +(require 'cl-lib) +(require 'seq) +(require 'cus-edit) + +(defun validate--check-list-contents (values schemas) + "Check that all VALUES match all SCHEMAS." + (when schemas + (if (not (= (length values) (length schemas))) + "wrong number of elements" + (seq-find #'identity (seq-mapn #'validate--check values schemas))))) + +(defun validate--indent-by-2 (x) + (replace-regexp-in-string "^" " " x)) + +(defun validate--check (value schema) + "Return nil if VALUE matches SCHEMA. +If they don't match, return an explanation." + (let ((args (cdr-safe schema)) + (expected-type (or (car-safe schema) schema)) + (props nil)) + (while (and (keywordp (car args)) (cdr args)) + (setq props `(,(pop args) ,(pop args) ,@props))) + (setq args (or (plist-get props :args) args)) + ;; :convert-widget is not supported. + (unless (plist-get props :convert-widget) + (let ((r + (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) + (variable (cond ((wtype 'symbol)) + ((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 value "") t) nil) + ((wtype 'string)) + (t "not a valid regexp"))) + (repeat (cond + ((or (not args) (cdr args)) (error "`repeat' needs exactly one argument")) + ((wtype 'list)) + (t (let ((subschema (car args))) + (seq-some (lambda (v) (validate--check v subschema)) value))))) + ((const function-item variable-item) + (unless (equal value (or (plist-get props :value) (car args))) + "not the expected value")) + (file (cond ((wtype 'string)) + ((file-exists-p value) nil) + ((plist-get props :must-match) "file does not exist") + ((not (file-writable-p value)) "file is not accessible"))) + (directory (cond ((wtype 'string)) + ((file-directory-p value) nil) + ((file-exists-p value) "path is not a directory") + ((not (file-writable-p value)) "directory is not accessible"))) + (key-sequence (and (wtype 'string) + (wtype 'vector))) + ;; TODO: `coding-system', `color' + (coding-system (wtype 'symbol)) + (color (wtype 'string)) + (cons (or (wtype 'cons) + (validate--check (car value) (car args)) + (validate--check (cdr value) (cadr args)))) + ((list group) (or (wtype 'list) + (validate--check-list-contents value args))) + (vector (or (wtype 'vector) + (validate--check-list-contents value args))) + (alist (let ((value-type (plist-get props :value-type)) + (key-type (plist-get props :key-type))) + (cond ((not value-type) (error "`alist' needs a :value-type")) + ((not key-type) (error "`alist' needs a :key-type")) + ((wtype 'list)) + (t (validate--check value + `(repeat (cons ,key-type ,value-type))))))) + ;; TODO: `plist' + ((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) + (concat "all of the options failed\n" + (mapconcat #'validate--indent-by-2 gather "\n")))))) + ;; TODO: `restricted-sexp' + (set (or (wtype 'list) + (let ((failed (list t))) + (dolist (schema args) + (let ((elem (seq-find (lambda (x) (not (validate--check x schema))) + value + failed))) + (unless (eq elem failed) + (setq value (remove elem value))))) + (when value + (concat "the following values don't match any of the options:\n " + (mapconcat (lambda (x) (format "%s" x)) value "\n ")))))))))) + (when r + (let ((print-length 5) + (print-level 2)) + (format "Looking for `%S' in `%S' failed because:\n%s" + schema value + (if (string-match "\\`Looking" r) + r + (validate--indent-by-2 r))))))))) + + +;;; Exposed API +;;;###autoload +(defun validate-value (value schema &optional noerror) + "Check that VALUE matches SCHEMA. +If it matches return VALUE, otherwise signal a `user-error'. + +If NOERROR is non-nil, return t to indicate a match and nil to +indicate a failure." + (let ((report (validate--check value schema))) + (if report + (unless noerror + (user-error "%s" report)) + value))) + +;;;###autoload +(defun validate-variable (symbol &optional noerror) + "Check that SYMBOL's value matches its schema. +SYMBOL must be the name of a custom option with a defined +`custom-type'. If SYMBOL has a value and a type, they are checked +with `validate-value'. NOERROR is passed to `validate-value'." + (let* ((val (symbol-value symbol)) + (type (custom-variable-type symbol))) + (if type + (validate-value val type) + (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)))) + +(defmacro validate-setq (&rest svs) + "Like `setq', but throw an error if validation fails. +VALUE is validated against SYMBOL's custom type. + +\(fn [SYM VAL] ...)" + (let ((out)) + (while svs + (let ((symbol (pop svs)) + (value (if (not svs) + (error "`validate-setq' takes an even number of arguments") + (pop svs)))) + (push `(if (boundp ',symbol) + (setq ,symbol (validate-value ,value (custom-variable-type ',symbol))) + (user-error "Trying to validate a variable that's not defined yet: `%s'.\nYou need to require the package before validating" + ',symbol)) + out))) + `(progn ,@(reverse out)))) + +(provide 'validate) +;;; validate.el ends here diff --git a/layers/+spacemacs/spacemacs-org/local/space-doc/space-doc.el b/layers/+spacemacs/spacemacs-org/local/space-doc/space-doc.el index c500ca7bc..38b84129b 100644 --- a/layers/+spacemacs/spacemacs-org/local/space-doc/space-doc.el +++ b/layers/+spacemacs/spacemacs-org/local/space-doc/space-doc.el @@ -30,7 +30,7 @@ (require 'org-compat) (defgroup space-doc nil "Minor mode for viewing Spacemacs documentation files." - :group 'convenience) + :group 'spacemacs) ;; NOTE: Dont forget to update Spacemacs FAQ if you modify this list! (defcustom spacemacs-space-doc-modificators diff --git a/layers/+tools/shell/config.el b/layers/+tools/shell/config.el index a636d5a82..4bfb9118a 100644 --- a/layers/+tools/shell/config.el +++ b/layers/+tools/shell/config.el @@ -24,12 +24,14 @@ "Default shell to use in Spacemacs. Possible values are `eshell' (default), `shell', `term', `ansi-term', `multi-term' and `vterm'.") -(defvar shell-default-position 'bottom +(spacemacs|defc shell-default-position 'bottom "Position of the shell. Possible values are `top', `bottom', `full', - `left' and `right'.") + `left' and `right'." + '(choice (const bottom) (const full) (const top))) -(defvar shell-default-height 30 - "Height in percents for the shell window.") +(spacemacs|defc shell-default-height 30 + "Height in percents for the shell window." + 'integer) (defvar shell-default-width 30 "Width in percents for the shell window.")