Table of Contents _________________ 1. mic: Minimal and combinable configuration manager for Emacs 2. How to Use? 3. Use `mic-core', minimum one .. 1. `:eval', `:eval-after-others', `:eval-before-all' .. 2. `:eval-after-load', `:eval-after-others-after-load' .. 3. `:eval-installation' 4. Use default `mic' .. 1. `:autoload-interactive', `:autoload-noninteractive' .. 2. `:auto-mode' .. 3. `:custom', `:custom-after-load' .. 4. `declare-function', `defvar-noninitial' .. 5. `:define-key', `:define-key-after-load', `:define-key-with-feature' .. 6. `:face' .. 7. `:hook' .. 8. `:package' .. 9. `:require' .. 10. `:require-after' 5. Define your own `mic' .. 1. Define your own `mic' with `mic-defmic' ..... 1. What is a filter? ..... 2. Pre-defined filters ..... 3. Helper for defining a filter ..... 4. Define `mic' with `mic-defmic' .. 2. Define your own `mic' with `defmacro' 6. Alternative 7. Contribute 8. License [https://img.shields.io/github/tag/ROCKTAKEY/mic.svg?style=flat-square] [https://img.shields.io/github/license/ROCKTAKEY/mic.svg?style=flat-square] [https://img.shields.io/codecov/c/github/ROCKTAKEY/mic.svg?style=flat-square] [https://img.shields.io/github/actions/workflow/status/ROCKTAKEY/mic/test.yml.svg?branch=master&style=flat-square] [https://img.shields.io/github/tag/ROCKTAKEY/mic.svg?style=flat-square] [https://img.shields.io/github/license/ROCKTAKEY/mic.svg?style=flat-square] [https://img.shields.io/codecov/c/github/ROCKTAKEY/mic.svg?style=flat-square] [https://img.shields.io/github/actions/workflow/status/ROCKTAKEY/mic/test.yml.svg?branch=master&style=flat-square] 1 mic: Minimal and combinable configuration manager for Emacs ============================================================= `mic' is uncustomizable. Define your own `mic'. `mic' is minimal and combinable configuration manager for Emacs. This package is yet another `use-package' and `leaf', but is also used with them (See [Alternative]). `mic' is minimal, so if you would like to write complex configuration, `mic' is a little redundant. However, there is no problem. `mic' is combinable, in the other words, thought to be used as core to define your own, and more convenient `mic'. There are some functions to define your own `mic'. See [Define your own mic]. [Alternative] See section 6 [Define your own mic] See section 5 2 How to Use? ============= For Emacs Lisp beginners, original `mic' macro is useful to configure your `init.el'. ,---- | (mic lsp-mode | ;; These are transformed to `define-key' sexp. | ;; Each argument is `(KEYMAP (KEYS . COMMAND)...)'. | ;; KEYS is passed to `kbd'. | :define-key | ((global-map | ("M-l" . #'lsp))) | | ;; These are same as `:define-key' argument, | ;; but evaluated after loading the feature (`lsp-mode' for this example). | ;; This is needed because `lsp-mode-map' is unavailable before `lsp' | ;; loading. | :define-key-after-load | ((lsp-mode-map | ("M-r" . #'lsp-rename) | ("M-c" . #'lsp-execute-code-action))) | | ;; These are transformed to `with-eval-after-load' and `define-key' sexp. | ;; Each argument is `(FEATURE (KEYMAP (KEYS . COMMAND)...))'. | ;; `cdr' is same as `:define-key' arguments. Each `define-key' sexp is | ;; evaluated after FEATURE is loaded. | ;; This is needed because `dired-mode-map' is unavailable before `dired' | ;; loading. | :define-key-with-feature | ((`dired' | (dired-mode-map | ("M-q" . #'lsp-dired-mode)))) | | ;; These are transformed to `customize-set-variable' sexp. | ;; Each argument is `(VARIABLE . VALUE)'. | :custom | ((lsp-sesstion-file . (expand-file-name "etc/.lsp-session-v1" user-emacs-directory)) | (lsp-log-io . t)) | | ;; These are transformed to `add-hook' sexp. | ;; Each argument is `(HOOK . FUNCTION)'. | :hook | ((c-mode-hook . #'lsp) | (c++-mode-hook . #'lsp) | (tex-mode-hook . #'lsp) | (latex-mode-hook . #'lsp) | (bibtex-mode-hook . #'lsp) | (rust-mode-hook . #'lsp)) | | ;; Each element is evaluated immediately when this `mic' sexp is evaluated. | :eval | ((message "This is evaluated when this `mic' sexp is evaluated.") | (message "This is also evaluated.")) | | ;; Each element will be evaluated after the package (`lsp-mode' for this example) is loaded. | :eval-after-load | ((message "This is evaluated when `lsp-mode' is loaded.")) | | ;; Each element is evaluated immediately when this `mic' sexp is evaluated. | ;; These are evaluated before `:eval' and `:eval-after-load' elements. | ;; This is for such use as defining function to use `:custom' argument. | :eval-before-all | ((message "This is evaluated when this `mic' sexp is evaluated.") | (message "These are evaluated before `:eval' and `:eval-after-load' sexp."))) | | | ;; `mic' sexp above is expanded to: | (prog1 'lsp-mode | ;; `:eval-before-all' | (message "This is evaluated when this `mic' sexp is evaluated.") | (message "These are evaluated before `:eval' and `:eval-after-load' sexp.") | | ;; `:eval-after-load' | (with-eval-after-load 'lsp-mode | (message "This is evaluated when `lsp-mode' is loaded.") | ;; `:define-key-after-load' | (define-key lsp-mode-map | (kbd "M-r") | (function lsp-rename)) | (define-key lsp-mode-map | (kbd "M-c") | (function lsp-execute-code-action))) | | ;; `:eval' | (message "This is evaluated when this `mic' sexp is evaluated.") | (message "This is also evaluated.") | | ;; `:custom' | (customize-set-variable 'lsp-sesstion-file | (expand-file-name "etc/.lsp-session-v1" user-emacs-directory)) | (customize-set-variable 'lsp-log-io t) | | ;; `:define-key' | (define-key global-map (kbd "M-l") #'lsp) | | ;; `:define-key-with-feature' | (with-eval-after-load '`dired' | (define-key dired-mode-map (kbd "M-q") #'lsp-dired-mode)) | | ;; `:hook' | (add-hook 'c-mode-hook #'lsp) | (add-hook 'c++-mode-hook #'lsp) | (add-hook 'tex-mode-hook #'lsp) | (add-hook 'latex-mode-hook #'lsp) | (add-hook 'bibtex-mode-hook #'lsp) | (add-hook 'rust-mode-hook #'lsp)) `---- For Emacs Lisp expert, original `mic' is a little unsatisfactory or redundant. `mic' is not customizable, but you can define your own `mic' easily. 1. Determine parent. You can use as parent `mic', `mic-core', which is simpler `mic'. `mic-core' recieves only keywords start from `:eval', such as `:eval', `eval-after-load'. 2. Define filter functions. Each one recieves plist (property list) and returns plist. returned plist is passed to parent (such as `mic', `mic-core') or next filter. Note that filter function can get feature name as value of property `:name'. Of course, you can use pre-defined filters. `mic' is defined by some filters from the parent `mic-core'. 3. Define your own mic by `mic-defmic'. It recieves `NAME', optional `DOCSTRING', and keyword argument `FILTERS'. `NAME' is name of your own `mic'. `DOCSTRING' is the document string of yours. `FILTERS' are list of filter. As explained, filter recieves plist and returns plist. It filter plist to get desired behavior. ,---- | (defun my-filter-global-set-key-without-quote (plist) | (let ((alist | ;; Get value from your own keyword | (plist-get plist :bind)) | sexps) | (setq sexps | ;; Transform each element | (mapcar | (lambda (arg) | (let ((keys (car arg)) | (command (cdr arg))) | `(global-set-key (kbd ,keys) #',command))) | alist)) | ;; Put sexps to `:eval' arguments | (mic-plist-put-append plist :eval sexps) | ;; Don't forget to delete your own keyword! | ;; When forget it, parent recieves it and may cause unexpected result. | (mic-plist-delete plist :bind) | plist)) | | (mic-defmic mymic | ;; Parent is here. You can also use `mic-core'. | mic | :filters | '(my-filter-global-set-key-without-quote | ;; You can add other filters below | )) | | ;; Then you can use `mymic' like: | (mymic simple | :bind | (("C-d" . delete-forward-char) | ("C-x l" . toggle-truncate-lines)) | ;; Of course parent keywords are accepted. | :custom | ((kill-whole-line . t) | (set-mark-command-repeat-pop . t) | (mark-ring-max . 50))) | | ;; `mymic' sexp is expanded to: | (mic simple | :custom | ((kill-whole-line . t) | (set-mark-command-repeat-pop . t) | (mark-ring-max . 50)) | :eval | ((global-set-key (kbd "C-d") #'delete-forward-char) | (global-set-key (kbd "C-x l") #'toggle-truncate-lines))) | | ;; Expanded to: | (mic-core simple | :eval | ((global-set-key (kbd "C-d") #'delete-forward-char) | (global-set-key (kbd "C-x l") #'toggle-truncate-lines) | (customize-set-variable 'kill-whole-line t) | (customize-set-variable 'set-mark-command-repeat-pop t) | (customize-set-variable 'mark-ring-max 50)) | :eval-after-load nil) | | ;; Expanded to: | (prog1 'simple | (global-set-key (kbd "C-d") #'delete-forward-char) | (global-set-key (kbd "C-x l") #'toggle-truncate-lines) | (customize-set-variable 'kill-whole-line t) | (customize-set-variable 'set-mark-command-repeat-pop t) | (customize-set-variable 'mark-ring-max 50)) `---- 3 Use `mic-core', minimum one ============================= `mic-core' is minimum. It can recieves only several keywords: - `:eval' - `:eval-after-load' - `:eval-after-others' - `:eval-after-others-after-load' - `:eval-before-all' - `:eval-installation' Each element of `:eval' arguments are evaluated. Time to evaluate is different. 3.1 `:eval', `:eval-after-others', `:eval-before-all' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Each element of these arguments are evaluated when the `mic' sexp is evaluated. The order is: - `:eval-before-all' - (`with-eval-after-load' sexp, explained on [`eval-after-load' keyword section], is evaluated) - `:eval' - `:eval-after-others' ,---- | (mic-core feature-name | :eval | ((message "eval1") | (message "eval2")) | :eval-after-others | ((message "eval-after-others1") | (message "eval-after-others2")) | :eval-before-all | ((message "eval-before-all1") | (message "eval-before-all2")) | :eval-after-load | ((message "eval-after-load1") | (message "eval-after-load2"))) | | ;; Expanded to: | (prog1 'feature-name | (message "eval-before-all1") | (message "eval-before-all2") | (with-eval-after-load 'feature-name | (message "eval-after-load1") | (message "eval-after-load2")) | (message "eval1") | (message "eval2") | (message "eval-after-others1") | (message "eval-after-others2")) `---- `:eval-before-all' exists because a filter function appends sexp to `:eval' argument. When some action should be evaluated before all action added by other filters, you can put it to `:eval-before-all' argument. *Note that it should NOT be used by filters.* Any filter should not use this. If it is used by filters, users cannot make their sexp to be evaluate before filter sexps. `:eval-after-others' exists because similar reason to `:eval-before-all'. Some action should be evaluated after all action added by other filters. Because of same reasons as `:eval-before-all', *it should NOT be used by filters*. [`eval-after-load' keyword section] See section 3.2 3.2 `:eval-after-load', `:eval-after-others-after-load' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Each element of these arguments are evaluated after the package is loaded. The evaluated order is: - `:eval-after-load' - `:eval-after-others-after-load' ,---- | (mic-core feature-name | :eval-after-load | ((message "eval-after-load1") | (message "eval-after-load2")) | :eval-after-others-after-load | ((message "eval-after-others-after-load1") | (message "eval-after-others-after-load2"))) | | ;; Expanded to: | (prog1 'feature-name | (with-eval-after-load 'feature-name | (message "eval-after-load1") | (message "eval-after-load2") | (message "eval-aftepr-others-after-load1") | (message "eval-after-others-after-load2"))) `---- `:eval-after-others-after-load' exists because similar reason to `:eval-after-others'. Some action should be evaluated after all action added by other filters. Because of same reasons as `:eval-before-all', *it should NOT be used by filters*. 3.3 `:eval-installation' ~~~~~~~~~~~~~~~~~~~~~~~~ Each element of this argument is evaluated before evaluation of other `:eval*' argument except `:eval-before-all'. This exists because sexp to install the package is evaluated before sexp which uses package features. ,---- | (mic-core feature-name | :eval-before-all | ((message "before all2") | (message "before all1")) | :eval-installation | ((message "install1") | (message "install2")) | :eval-after-load | ((message "eval-after-load1") | (message "eval-after-load2")) | :eval-after-others-after-load | ((message "eval-after-others-after-load1") | (message "eval-after-others-after-load2")) | :eval | ((message "eval1") | (message "eval2"))) | | ;; Expanded to: | (prog1 'feature-name | (message "before all2") | (message "before all1") | (message "install1") | (message "install2") | (with-eval-after-load 'feature-name | (message "eval-after-load1") | (message "eval-after-load2") | (message "eval-after-others-after-load1") | (message "eval-after-others-after-load2")) | (message "eval1") | (message "eval2")) `---- `:eval-after-others-after-load' exists because similar reason to `:eval-after-others'. Some action should be evaluated after all action added by other filters. Because of same reasons as `:eval-before-all', *it should NOT be used by filters*. 4 Use default `mic' =================== `mic' is minimal for use. `mic-core' is minimum core, but it is not enough to use as it is. In addition to keywords allowed by [`mic-core'], it allows some keyword arguments: - `:autoload-interactive' - `:autoload-noninteractive' - `:auto-mode' - `:custom' - `:custom-after-load' - `:declare-function' - `:define-key' - `:define-key-after-load' - `:define-key-with-feature' - `:defvar-noninitial' - `:face' - `:hook' - `:package' - `:require' - `:require-after' [`mic-core'] See section 3 4.1 `:autoload-interactive', `:autoload-noninteractive' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These are transformed to `autoload' sexps. Each element is function to autoload. Since `autoload' should be informed whether the function is `interactive' or not, both `:autoload-interactive' and `:autoload-noninteractive' exist. ,---- | (mic feature-name | :autoload-interactive | (interactive-func1 | interactive-func2) | :autoload-noninteractive | (noninteractive-func3 | noninteractive-func4)) | | ;; Expanded to: | (mic-core feature-name :eval | ((autoload #'interactive-func1 "feature-name" nil t) | (autoload #'interactive-func2 "feature-name" nil t) | (autoload #'noninteractive-func3 "feature-name") | (autoload #'noninteractive-func4 "feature-name")) | :eval-after-load nil) | | ;; Expanded to: | (prog1 'feature-name | (autoload #'interactive-func1 "feature-name" nil t) | (autoload #'interactive-func2 "feature-name" nil t) | (autoload #'noninteractive-func3 "feature-name") | (autoload #'noninteractive-func4 "feature-name")) `---- 4.2 `:auto-mode' ~~~~~~~~~~~~~~~~ It is transformed to sexp like `(add-to-list 'auto-mode-alist ...)'. Each element of the value should be valid as an element of `auto-mode-alist'. ,---- | (mic feature-name | :auto-mode | (("\\.html?\\'" . web-mode) | ("\\.css\\'" . web-mode))) | | ;; Expanded to: | (mic-core feature-name :eval-installation | ((add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) | (add-to-list 'auto-mode-alist '("\\.css\\'" . web-mode))) | :eval nil :eval-after-load nil) | | ;; Expanded to: | (prog1 'feature-name | (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) | (add-to-list 'auto-mode-alist '("\\.css\\'" . web-mode))) | `---- 4.3 `:custom', `:custom-after-load' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These are transformed to `customize-set-variable' sexps. Each element is `(VARIABLE . VALUE)'. Each `VARIABLE' is set to `VALUE'. Sexp from `:custom' argument are evaluated when the `mic' sexp is evaluated, while sexp from `:custom-after-load' argument are evaluated after the feature is loaded. `:custom-after-load' is used when you want to use initial value of customized variable or function defined in the feature. ,---- | (mic feature-name | :custom | ((variable1 . 1) | ;; VALUE is evaluated | (variable2 . (+ 1 1))) | :custom-after-load | ;; You can use the initial value of `variable3' | ((variable3 . (+ variable3 1)) | ;; You can use function defined in the feature (for this example `feature-name') | (variable2 . (function-defined-in-feature-name)))) | | ;; Expanded to: | (mic-core feature-name | :eval | ((customize-set-variable 'variable1 1) | (customize-set-variable 'variable2 | (+ 1 1))) | :eval-after-load | ((customize-set-variable 'variable3 | (+ variable3 1)) | (customize-set-variable 'variable2 | (function-defined-in-feature-name)))) | | ;; Expanded to: | (prog1 'feature-name | (with-eval-after-load 'feature-name | ;; `variable3' is already defined. | (customize-set-variable 'variable3 | (+ variable3 1)) | ;; `function-defined-in-feature-name' is already defined. | (customize-set-variable 'variable2 | (function-defined-in-feature-name))) | (customize-set-variable 'variable1 1) | (customize-set-variable 'variable2 | (+ 1 1))) `---- 4.4 `declare-function', `defvar-noninitial' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These arguments declare functions and variables. Each element of `declare-function' / `defvar-noninitial' is symbol as function/variable. They exist in order to suppress warning of undefined functions/variables. ,---- | (mic feature-name | :declare-function | (function1 | function2) | :defvar-noninitial | (variable1 | variable2)) | | ;; Expanded to: | (mic-core feature-name | :eval | ((declare-function function1 "ext:feature-name") | (declare-function function2 "ext:feature-name") | (defvar variable1) | (defvar variable2)) | :eval-after-load nil) | | ;; Expanded to: | (prog1 'feature-name | ;; They declare that the functions `function1' and `function2' is defined in | ;; the feature `feature-name'. | (declare-function function1 "ext:feature-name") | (declare-function function2 "ext:feature-name") | ;; They declare that the variables `variable1' and `variable2' will be defined. | ;; `defvar' without initial value declares symbol as variable. | (defvar variable1) | (defvar variable2)) `---- 4.5 `:define-key', `:define-key-after-load', `:define-key-with-feature' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These arguments is transformed to `define-key' sexps. On `:define-key' or `:define-key-after-load', each element of the argument is `(KEYMAP (KEYS . COMMAND)...)'. `KEYMAP' is keymap. `KEYS' is passed to `kbd'. `COMMAND' is interactive function. On `:define-key-with-feature', each element is `(FEATURE (KEYMAP (KEYS . COMMAND)...))'. `FEATURE' is feature, and the `define-key' sexp is evaluated after loading the `FEATURE'. This exists in order to define `COMMAND' in the feature with `KEYS' to `KEYMAP' defined in `FEATURE'. Use it to make sure that `KEYMAP' is defined. ,---- | (mic feature-name | :define-key | ;; (KEYMAP (KEYS . COMMAND)...) | ((global-map | ;; #' is needed | ("M-l" . #'feature-name-command1)) | (prog-mode-map | ;; #' is needed | ("M-a" . #'feature-name-comman2))) | | :define-key-after-load | ;; When `feature-name-mode-map' is defined in `feature-name', | ;; use `:define-key-after-load'. | ((feature-name-mode-map | ("M-r" . #'feature-name-command3) | ("M-c" . #'feature-name-command4))) | | | ;; When `other-feature-mode-map' is defined in `other-feature', which is not `feature-name', | ;; use `:define-key-with-feature'. | :define-key-with-feature | ((other-feature | (other-feature-mode-map | ("M-q" . #'feature-name-command5))))) | | ;; Expanded to: | (mic-core feature-name | :eval | ((define-key global-map (kbd "M-l") #'feature-name-command1) | (define-key prog-mode-map (kbd "M-a") #'feature-name-comman2) | (with-eval-after-load 'other-feature | (define-key other-feature-mode-map (kbd "M-q") #'feature-name-command5))) | :eval-after-load | ((define-key feature-name-mode-map (kbd "M-r") #'feature-name-command3) | (define-key feature-name-mode-map (kbd "M-c") #'feature-name-command4))) | | ;; Expanded to: | (prog1 'feature-name | (with-eval-after-load 'feature-name | ;; `:define-key-after-load' | (define-key feature-name-mode-map (kbd "M-r") #'feature-name-command3) | (define-key feature-name-mode-map (kbd "M-c") #'feature-name-command4)) | ;; `:define-key' | (define-key global-map (kbd "M-l") #'feature-name-command1) | (define-key prog-mode-map (kbd "M-a") #'feature-name-comman2) | ;; `:define-key-with-feature' | (with-eval-after-load 'other-feature | (define-key other-feature-mode-map (kbd "M-q") #'feature-name-command5))) `---- 4.6 `:face' ~~~~~~~~~~~ This is transformed to `custom-set-faces' sexp. Each element is `(FACE-SYMBOL . FACE-DEFINITION)'. ,---- | (mic feature-name | :face | ((face-1 | . ((t (:foreground "red" :height 10.0)))) | (face-2 | . ((t (:background "#006000" :foreground "white" :bold t)))))) | | ;; Expanded to: | (mic-core feature-name | :eval | ((custom-set-faces | '(face-1 | ((t (:foreground "red" :height 10.0)))) | '(face-2 | ((t (:background "#006000" :foreground "white" :bold t)))))) | :eval-after-load nil) | | ;; Expanded to: | (prog1 'feature-name | (custom-set-faces | '(face-1 | ((t (:foreground "red" :height 10.0)))) | '(face-2 | ((t (:background "#006000" :foreground "white" :bold t)))))) `---- 4.7 `:hook' ~~~~~~~~~~~ This is transformed to `add-hook' sexp. Each element is `(HOOK . FUNCTION)'. ,---- | (mic feature-name | :hook | ;; #' is needed | ((hook1 . #'function1) | (hook2 . #'function2) | ;; `lambda' is allowed (but not recommended) | (hook3 . (lambda (arg) 1)))) | | ;; Expanded to: | (mic-core feature-name | :eval | ((add-hook 'hook1 #'function1) | (add-hook 'hook2 #'function2) | (add-hook 'hook3 (lambda (arg) 1))) | :eval-after-load nil) | | ;; Expanded to: | (prog1 'feature-name | (add-hook 'hook1 #'function1) | (add-hook 'hook2 #'function2) | (add-hook 'hook3 (lambda (arg) 1))) `---- 4.8 `:package' ~~~~~~~~~~~~~~ This is transformed to `package-install' sexps. Each arguments are `PKG' used by `package-install'. The expandation result is complicated, because it is annoying to fetch package archives many times. ,---- | (mic feature-name | :package | (package-name1 | package-name2)) | | ;; Expanded to: | (mic-core feature-name | :eval | ;; When package is not installed | ((unless (package-installed-p 'package-name1) | ;; Ensure package is exists in archive | (when (assq 'package-name1 package-archive-contents) | (ignore-errors | (package-install 'package-name1))) | (unless (package-installed-p 'package-name1) | ;; Refresh (fetch) new archive | (package-refresh-contents) | (condition-case _ | (package-install 'package-name1) | (error | (warn "Package %s is not found" 'package-name1))))) | | (unless (package-installed-p 'package-name2) | (when (assq 'package-name2 package-archive-contents) | (ignore-errors | (package-install 'package-name2))) | (unless (package-installed-p 'package-name2) | (package-refresh-contents) | (condition-case _ | (package-install 'package-name2) | (error | (warn "Package %s is not found" 'package-name2)))))) | :eval-after-load nil) | | ;; Expand to: | (prog1 'feature-name | (unless (package-installed-p 'package-name1) | (when (assq 'package-name1 package-archive-contents) | (ignore-errors | (package-install 'package-name1))) | (unless (package-installed-p 'package-name1) | (package-refresh-contents) | (condition-case _ | (package-install 'package-name1) | (error | (warn "Package %s is not found" 'package-name1))))) | (unless (package-installed-p 'package-name2) | (when (assq 'package-name2 package-archive-contents) | (ignore-errors | (package-install 'package-name2))) | (unless (package-installed-p 'package-name2) | (package-refresh-contents) | (condition-case _ | (package-install 'package-name2) | (error | (warn "Package %s is not found" 'package-name2)))))) `---- 4.9 `:require' ~~~~~~~~~~~~~~ This is transformed to `require' sexps. Each element is feature symbol and required on `:eval'. ,---- | (mic feature-name | :require | (feat1 | feat2)) | | ;; Expand to: | (mic-core feature-name | :eval-installation nil | :eval | ((require 'feat1) | (require 'feat2)) | :eval-after-load nil) | | ;; Expand to: | (prog1 'feature-name | (require 'feat1) | (require 'feat2)) `---- 4.10 `:require-after' ~~~~~~~~~~~~~~~~~~~~~ This is transformed to `require' sexps in `with-eval-after-load' section. Each element is alist. `car' of each element is feature symbol which is used as first argument of `with-eval-after-load'. `cdr' of each element is list of features required after the `car'. This is used when you should require package after another one but there is no functions to call so `autoload' cannot be used. ,---- | (mic feature-name | :require-after | ((feat-after1 | . (feat1 feat2)) | (feat-after2 | feat3 | feat4))) | | ;; Expand to: | (mic-core feature-name | :eval-installation nil | :eval | ((with-eval-after-load 'feat-after1 | (require 'feat1) | (require 'feat2)) | (with-eval-after-load 'feat-after2 | (require 'feat3) | (require 'feat4))) | :eval-after-load nil) | | ;; Expand to: | (prog1 'feature-name | (with-eval-after-load 'feat-after1 | (require 'feat1) | (require 'feat2)) | (with-eval-after-load 'feat-after2 | (require 'feat3) | (require 'feat4))) `---- 5 Define your own `mic' ======================= You do not like `mic' behavior? It is OK. You can define your own `mic'! There are some ways to define it: - Use `mic-defmic' - Use `defmacro' 5.1 Define your own `mic' with `mic-defmic' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you would like to add keywords, or to make some keywords more simple, you can define `filter' and apply it to `mic' (or `mic-core', and another `mic', any parent is allowed). 5.1.1 What is a filter? ----------------------- The filter recieves one argument, `PLIST' (plist, property list), and returns `RETURNED-PLIST'. It filters or transforms it into returned plist. It is better to divide filters by every keyword, because of reusability. 1. Each filter recieves 1 argument `PLIST', which is plist (property list). 2. Each filter returns `RETURNED-PLIST', which is plist. 3. `PLIST' is given by user or filter before. 4. `PLIST' have feature name `:name' property. 5. `RETURNED-PLIST' is passed to next filter or parent `mic' (`mic', `mic-core', or another). 6. `RETURNED-PLIST' should have same value of `:name' property. 7. The property only used by your filter should be removed in `RETURNED-PLIST'. Here is example: ,---- | (defun my-filter-global-set-key-without-quote (plist) | (let ((alist | ;; Get value from your own keyword | (plist-get plist :bind)) | sexps) | (setq sexps | ;; Transform each element | (mapcar | (lambda (arg) | (let ((keys (car arg)) | (command (cdr arg))) | `(global-set-key (kbd ,keys) #',command))) | alist)) | ;; Put sexps to `:eval' arguments | (mic-plist-put-append plist :eval sexps) | ;; Don't forget to delete your own keyword! | ;; When forget it, parent recieves it and may cause unexpected result. | (mic-plist-delete plist :bind) | plist)) | | ;; `defmic' defines new `mic' (see "Define mic with mic-defmic" section for more infomation) | (mic-defmic yourmic | mic ; Derived from `mic' | :filters '(my-filter-global-set-key-without-quote)) | | ;; Here is `yourmic' expression | (yourmic package-name | ;; New keyword you added by `my-filter-global-set-key-without-quote' | :bind | (("M-a" . beginning-of-defun) | ("M-e" . end-of-defun)) | ;; Of course keywords for `mic', which is original of `yourmic', is allowed. | :hook ((after-init-hook . #'ignore))) | | ;; Then first `PLIST' is: | '( :name package-name | :bind (("M-a" . beginning-of-defun) | ("M-e" . end-of-defun)) | :hook ((after-init-hook . #'ignore))) | | ;; When you expand the sexp before, the filter you defined is called like: | (my-filter-global-set-key-without-quote | '( :name package-name | :bind (("M-a" . beginning-of-defun) | ("M-e" . end-of-defun)) | :hook ((after-init-hook . #'ignore)))) | | ;; It returns `RETURNED-PLIST': | '( :name package-name | :hook ((after-init-hook function ignore)) | :eval | ((global-set-key (kbd "M-a") #'beginning-of-defun) | (global-set-key (kbd "M-e") #'end-of-defun))) | | ;; The `RETURNED-PLIST' is passed to a next filter if exists. | ;; You use only one filter in definition, | ;; so it is expanded to: | (mic package-name | :hook ((after-init-hook . #'ignore)) | :eval | ((global-set-key (kbd "M-a") #'beginning-of-defun) | (global-set-key (kbd "M-e") #'end-of-defun))) `---- 5.1.2 Pre-defined filters ------------------------- Some pre-defined filter, unused by `mic' definition, are available in `mic-filter.el'. * 5.1.2.1 Filters for package manager - `mic-filter-ell-get' - `mic-filter-straight' - `mic-filter-quelpa' For more infomation, see docstring of each filter. ,---- | ;;; el-get | (mic-defmic mic-with-el-get mic | :filters '(mic-filter-el-get)) | | (mic-with-el-get hydra | :el-get ((hydra :repo "abo-abo/hydra" :fetcher github))) | | ;; Expanded to: | (mic hydra | :eval-installation | ((el-get-bundle hydra :repo "abo-abo/hydra" :fetcher github))) `---- ,---- | ;;; quelpa | (mic-defmic mic-with-quelpa mic | :filters '(mic-filter-quelpa)) | | (mic-with-quelpa hydra | :quelpa ((hydra :repo "abo-abo/hydra" :fetcher github))) | | ;; Expanded to: | (mic hydra | :eval-installation | ((quelpa | '(hydra :repo "abo-abo/hydra" :fetcher github)))) `---- ,---- | ;;; straight | (mic-defmic mic-with-straight mic | :filters '(mic-filter-straight)) | | (mic-with-straight hydra | :straight ((hydra :repo "abo-abo/hydra" :host github))) | | ;; Expanded to: | (mic hydra | :eval-installation | ((straight-use-package | '(hydra :repo "abo-abo/hydra" :host github)))) `---- * 5.1.2.2 Key definition - `mic-filter-define-key-general', `mic-filter-general-define-key' - `mic-filter-mykie' - `mic-filter-hydra' - `mic-filter-pretty-hydra', `mic-filter-pretty-hydra+' - `mic-filter-mode-hydra' Here is summaries and examples for these filters. See a docstring and definition of each filter for more information. + 5.1.2.2.1 general.el [general.el] makes key definition more convenient. There are some filters for integration with it: - `mic-filter-define-key-general' - `mic-filter-general-define-key' The both are expanded to `general-define-key' call. `mic-filter-define-key-general', which uses a `:define-key-general' keyword, is compatible with `:define-key' keyword. In the other words, the syntax like `((keymap (key . function)...)...)' is allowed but `general-define-key' is used as backend. On the other hand, `mic-filter-general-define-key', which uses `:general-define-key' keyword, uses `general-define-key' syntax. So you can use `:keymap' or `:prefix' keyword. Each element of the value of `:general-define-key' is directly passed to `general-define-key'. ,---- | (mic-defmic mic-with-define-key-general mic | :filters | '(mic-filter-define-key-general)) | | (mic-with-define-key-general package-name | :define-key-general | ((keymap1 | ("C-d" . #'func1) | ("C-q" . #'func2)) | (override | ("C-a" . #'func3) | ("C-e" . #'func4)))) | | ;; Expanded to: | (mic package-name | :eval | ((general-define-key :keymaps 'keymap1 | "C-d" (function func1) | "C-q" (function func2)) | (general-define-key :keymaps 'override | "C-a" (function func3) | "C-e" (function func4)))) `---- [general.el] + 5.1.2.2.2 Mykie.el [Mykie.el] is is multiplexer of key definition. There is filter for mykie: - `mic-filter-mykie' `mic-filter-mykie', which uses a `:mykie' keyword, creates `mykie:define-key' sexp. Each element of the value on `:mykie' keyword is a cons cell like `((keymap (key [:keyword function1] ...)...)...)'. `car' of each element, which is keymap, and each element of `cdr' of each element of the value is passed to `mykie:define-key'. ,---- `---- ,---- | (mic-defmic mic-with-filter-mykie mic | :filters | '(mic-filter-mykie)) | | (mic-with-filter-mykie package-name | :mykie | ((global-map | ("C-w" :default hydra-window-resizer/body :region kill-region)))) | | ;; Expanded to: | (mic package-name | :eval | ((mykie:define-key global-map "C-w" :default hydra-window-resizer/body :region kill-region))) `---- [Mykie.el] + 5.1.2.2.3 Hydra [Hydra] makes Emacs bindings stick around. There is a filter for integration of Hydra: - `mic-filter-hydra' `mic-filter-hydra', which uses a `:hydra' keyword, creates `defhydra' sexp. Each element of the value on the `:hydra' keyword is passed to `defhydra' directly. ,---- | (mic-defmic mic-with-hydra mic | :filters '(mic-filter-hydra)) | | (mic-with-hydra package-name | :hydra | ;; Spacing induces good indent | (( hydra-window-resizer () | ("p" shrink-window "shrink") | ("n" enlarge-window "enlarge") | ("f" enlarge-window-horizontally "enlarge-horizontally") | ("b" shrink-window-horizontally "shrink-horizontally") | ("" shrink-window) | ("" enlarge-window) | ("" enlarge-window-horizontally) | ("" shrink-window-horizontally) | ("q" nil "quit")))) | | ;; Expanded to: | (mic package-name | :eval | ((defhydra hydra-window-resizer nil | ("p" shrink-window "shrink" :exit nil :cmd-name hydra-window-resizer/shrink-window :column nil) | ("n" enlarge-window "enlarge") | ("f" enlarge-window-horizontally "enlarge-horizontally") | ("b" shrink-window-horizontally "shrink-horizontally") | ("" shrink-window) | ("" enlarge-window) | ("" enlarge-window-horizontally) | ("" shrink-window-horizontally) | ("q" nil "quit")))) `---- [Hydra] + 5.1.2.2.4 pretty-hydra [Pretty Hydra] defines prettier hydra. There is some filters for integration of it: - `mic-filter-pretty-hydra' - `mic-filter-pretty-hydra+' `mic-filter-pretty-hydra' uses `:pretty-hydra', whereas `mic-filter-pretty-hydra+' uses `:pretty-hydra+'. Each element is passed to `pretty-hydra-define', which defines new hydra, or `pretty-hydra-define+', which appends to existing hydra if exist. The both have absolutely same syntax. Each element is passed to each defining macros directly. ,---- | (mic-defmic mic-with-pretty-hydra mic | :filters '(mic-filter-pretty-hydra | mic-filter-pretty-hydra+)) | | ;;; `:pretty-hydra' | (mic-with-pretty-hydra package-name | :pretty-hydra | (( hydra-window-resizer () | ("Alphabet" | (("p" shrink-window "shrink") | ("n" enlarge-window "enlarge") | ("f" enlarge-window-horizontally "enlarge-horizontally") | ("b" shrink-window-horizontally "shrink-horizontally")) | "Arrow" | (("" shrink-window) | ("" enlarge-window) | ("" enlarge-window-horizontally) | ("" shrink-window-horizontally)) | "Quit" | ("q" nil "quit"))))) | | ;; Expanded to: | (mic package-name | :eval | ((pretty-hydra-define hydra-window-resizer nil | ("Alphabet" | (("p" shrink-window "shrink") | ("n" enlarge-window "enlarge") | ("f" enlarge-window-horizontally "enlarge-horizontally") | ("b" shrink-window-horizontally "shrink-horizontally")) | "Arrow" | (("" shrink-window "shrink-window") | ("" enlarge-window "enlarge-window") | ("" enlarge-window-horizontally "enlarge-window-horizontally") | ("" shrink-window-horizontally "shrink-window-horizontally")) | "Quit" | ("q" nil "quit"))))) | | | ;;; `:pretty-hydra+' | (mic-with-pretty-hydra package-name | :pretty-hydra+ | (( hydra-window-resizer () | ("Vim-like" | (("h" enlarge-window-horizontally "enlarge-horizontally") | ("j" shrink-window "shrink") | ("k" enlarge-window "enlarge") | ("l" shrink-window-horizontally "shrink-horizontally")))))) | | ;; Expanded to: | (mic package-name | :eval | ((pretty-hydra-define+ hydra-window-resizer nil | ("Vim-like" | (("h" enlarge-window-horizontally "enlarge-horizontally") | ("j" shrink-window "shrink") | ("k" enlarge-window "enlarge") | ("l" shrink-window-horizontally "shrink-horizontally")))))) `---- [Pretty Hydra] + 5.1.2.2.5 major-mode-hydra [Major Mode Hydra] defines major-mode specific hydra function, `major-mode-hydra'. There is a filter for integration of it: - `mic-filter-mode-hydra' `mic-filter-mode-hydra' uses a `:mode-hydra' keyword. Each element of the value of the keyword is passed to `major-mode-hydra-define' directly. ,---- | (mic-defmic mic-with-mode-hydra mic | :filters '(mic-filter-mode-hydra)) | | (mic-with-mode-hydra package-name | :mode-hydra | (( c-mode (:title "C Mode" :quit-key "q") | ("Alphabet" | (("p" shrink-window "shrink") | ("n" enlarge-window "enlarge") | ("f" enlarge-window-horizontally "enlarge-horizontally") | ("b" shrink-window-horizontally "shrink-horizontally")) | "Arrow" | (("" shrink-window) | ("" enlarge-window) | ("" enlarge-window-horizontally) | ("" shrink-window-horizontally)))))) | | ;; Expanded to: | (mic package-name | :eval | ((major-mode-hydra-define c-mode | (:title "C Mode" :quit-key "q") | ("Alphabet" | (("p" shrink-window "shrink") | ("n" enlarge-window "enlarge") | ("f" enlarge-window-horizontally "enlarge-horizontally") | ("b" shrink-window-horizontally "shrink-horizontally")) | "Arrow" | (("" shrink-window "shrink-window") | ("" enlarge-window "enlarge-window") | ("" enlarge-window-horizontally "enlarge-window-horizontally") | ("" shrink-window-horizontally "shrink-window-horizontally")))))) `---- [Major Mode Hydra] * 5.1.2.3 Alternative of filters + 5.1.2.3.1 Hook - `mic-filter-hook-quote' This is almost same as `mic-filter-hook', but `cdr' of each element of the value should not be quoted. `:hook-quote' is used as keyword. 5.1.3 Helper for defining a filter ---------------------------------- There are some helpers for defining a filter. * 5.1.3.1 Utilities Usually, a filter proceeds filtering by 4 steps: 1. Get data on a specific keyword in `PLIST' 2. Convert data to sexp 3. Append the sexp to value on `:eval' in `PLIST' 4. Delete the specific keyword from `PLIST' There are some macros to help step 3. and 4. in `mic-utils.el'. - `mic-plist-put-append', which helps step 3., takes three arguments, `PLIST', `PROP', which means keyword, and `VAL'. It get a value on `PROP' in `PLIST', and appends `VAL' to the value. - `mic-plist-delete', which helps step 4., takes one obligatory argument `PLIST', and extra arguments `PROPS'. It removes `PROPS' keywords from `PLIST' and return it. * 5.1.3.2 `deffilter' To define a simple filter or to modify an existing filter, you can use `mic-deffilter-*' macros in `mic-deffilter.el'. See each macro definition and docstring for more information. `mic-deffilter-alias' Induce alias keyword. ,---- | (mic-deffilter-alias example-filter-alias :alias :origin) | | (example-filter-alias '(:alias "Hello")) | ;; => | (:origin "Hello") `---- `mic-deffilter-const' Put constant value on keyword. ,---- | (mic-deffilter-const example-filter-const | "Optional docstring." | :eval '((message "Hello"))) | | ;; Add a :eval keyword when it does not exist. | (example-filter-const '(:other-keyword "Hi")) | ;; => | (:other-keyword "Hi" :eval ((message "Hello"))) | | ;; Overwrite when a :eval keyword exists. | (example-filter-const '(:eval ((message "Good bye")) :other-keyword "Hi")) | ;; => | (:eval ((message "Hello")) :other-keyword "Hi") `---- `mic-deffilter-const-append' Append constant value on keyword. ,---- | (mic-deffilter-const-append example-filter-const-append | :eval '((message "Hello"))) | | ;; Same as `mic-deffilter-const' when any :eval keyword does not exist. | (example-filter-const-append '(:other-keyword "Hi")) | ;; => | (:other-keyword "Hi" :eval ((message "Hello"))) | | ;; Append the value when the a :eval keyword exists. | (example-filter-const-append '(:eval ((message "Good bye")) :other-keyword "Hi")) | ;; => | (:eval ((message "Good bye") (message "Hello")) :other-keyword "Hi") `---- `mic-deffilter-ignore' Just remove value on keyword. ,---- | (mic-deffilter-ignore example-filter-ignore | :ignore-me) | | (example-filter-ignore '(:ignore-me "Ignored" :remain-me "Remained")) | ;; => | (:remain-me "Remained") `---- `mic-deffilter-nonlist-to-list' If value is not list, wrap it into list. ,---- | (mic-deffilter-nonlist-to-list example-filter-nonlist-to-list | :package) | | (example-filter-nonlist-to-list '(:package t)) | ;; => | (:package (t)) `---- `mic-deffilter-replace-keyword-append' From an existing filter, define a new filter which uses another keywords as input and output. Value is appended to the keyword for output. ,---- | ;; Original filter: `mic-filter-mykie' | (mic-filter-mykie '(:mykie ((global-map ("C-a" :default beginning-of-line))))) | ;; => | (:eval ((mykie:define-key global-map "C-a" :default beginning-of-line))) | | | (mic-deffilter-replace-keyword-append example-filter-replace-keyword-append | mic-filter-mykie | :mykie-after-load :mykie | '((:eval . :eval-after-load))) | | ;; An input keyword and an output keyword is replaced | (example-filter-replace-keyword-append '(:mykie-after-load ((global-map ("C-a" :default beginning-of-line))))) | ;; => | (:eval-after-load ((mykie:define-key global-map "C-a" :default beginning-of-line))) `---- `mic-deffilter-convert-after-load' From an existing filter, define a new filter which outputs an `:eval-after-load' keyword instead of `:eval'. It is same as `(mic-deffilter-replace-keyword-append name filter old-keyword new-keyword '((:eval . :eval-after-load)))'. ,---- | ;; Original filter: `mic-filter-mykie' | (mic-filter-mykie '(:mykie ((global-map ("C-a" :default beginning-of-line))))) | ;; => | (:eval ((mykie:define-key global-map "C-a" :default beginning-of-line))) | | | (mic-deffilter-convert-after-load example-filter-convert-after-load | mic-filter-mykie | :mykie-after-load :mykie) | | ;; An input keyword and an output keyword is replaced | (example-filter-convert-after-load '(:mykie-after-load ((global-map ("C-a" :default beginning-of-line))))) | ;; => | (:eval-after-load ((mykie:define-key global-map "C-a" :default beginning-of-line))) `---- `mic-deffilter-t-to-name' Replace `t' with feature name in a list keyword. ,---- | (mic-deffilter-t-to-name example-filter-t-to-name | :replace) | | ;; :name keyword is needed in addition to :replace keyword | (example-filter-t-to-name '(:name feature-name :replace (1 2 3 t 5 6 t))) | ;; => | (:name feature-name :replace (1 2 3 feature-name 5 6 feature-name)) `---- `mic-deffilter-validate' Return a recieved plist except that it validates and sieves keyword in the plist to confirm the returned plist has no invalid keywords. ,---- | (mic-deffilter-validate example-filter-validate | :name :key1 :key2) | | (example-filter-validate '(:name feature-name :key1 "Hello" :key2 "Hi" :key3 "Bad" :key4 "Sad")) | ;; => | (:name feature-name :key1 "Hello" :key2 "Hi") | ;; In addition, warnings are displayed like: | ;; Warning (Emacs): 'mic' feature-name: The keyword :key3 is not allowed by filter 'example-filter-validate' | ;; Warning (Emacs): 'mic' feature-name: The keyword :key4 is not allowed by filter 'example-filter-validate' `---- 5.1.4 Define `mic' with `mic-defmic' ------------------------------------ `mic-defmic' recieves arguments: `NAME', `PANRENT', optional `DOCSTRING', keyword argument `FILTERS'. `NAME' is your new `mic' macro name. `PARENT' is parent `mic', which recieves `RETURNED-PLIST' at last. `FILTERS' is list of your filters. When your `mic' recieves plist, the plist is filtered by all of your `FILTERS' in order, then the plist is passed to `PARENT'. Here is example: ,---- | ;; Define `mymic' | (mic-defmic mymic | ;; Parent is here. You can also use `mic-core'. | mic | :filters | '(my-filter-global-set-key-without-quote | ;; You can add other filters below | ) | ;; You can comment out the line below to catch, warn and ignore errors. | ;; :error-protection? t | ) | | ;; Then you can use `mymic' like: | (mymic simple | :bind | (("C-d" . delete-forward-char) | ("C-x l" . toggle-truncate-lines)) | ;; Of course parent keywords are accepted. | :custom | ((kill-whole-line . t) | (set-mark-command-repeat-pop . t) | (mark-ring-max . 50))) | | ;; Expanded to: | (mic simple | :custom | ((kill-whole-line . t) | (set-mark-command-repeat-pop . t) | (mark-ring-max . 50)) | :eval | ((global-set-key (kbd "C-d") #'delete-forward-char) | (global-set-key (kbd "C-x l") #'toggle-truncate-lines))) `---- When you would like to use `mic-core' as `PARENT', `mic-filter-core-validate' is useful to validate plist. *Please put it tail of `FILTERS' if you use it.* * 5.1.4.1 Error protection If you want your `mic' to catch, warn and dismiss errors and to continue evaluation, set `:error-protection?' `t'. ,---- | (mic-defmic mymic-with-error-protection | ;; Parent is here. You can also use `mic-core'. | mic | :filters | '(my-filter-global-set-key-without-quote) | :error-protection? t) | | (mymic-with-error-protection simple | :bind | (("C-d" . delete-forward-char) | ("C-x l" . toggle-truncate-lines)) | ;; Of course parent keywords are accepted. | :custom | ((kill-whole-line . t) | (set-mark-command-repeat-pop . t) | (mark-ring-max . 50))) | | ;; Expanded to: | (condition-case-unless-debug error ; Catch error | (mic simple | :custom | ((kill-whole-line . t) | (set-mark-command-repeat-pop . t) | (mark-ring-max . 50)) | :eval | ((global-set-key (kbd "C-d") (function delete-forward-char)) | (global-set-key (kbd "C-x l") (function toggle-truncate-lines)))) | ;; Warn caught error but continue evaluation | (error | (warn "`%s' %s: evaluation error: %s" 'mymic-with-error-protection 'simple | (error-message-string error)))) `---- * 5.1.4.2 Adopt a parent other than `mic', `mic-core' and its derivation You can use other configuration managers, such as [use-package] and [leaf.el]. However, filters defined by `mic' output keyword for `mic' family, such as `:eval', `:eval-after-load'. So you should tell `mic-defmic' how to adapt outputs to its parent by `:adapter' option. The adapter takes one argument `PLIST', and returns a list to pass to the parent. Two adapter are pre-defined: `mic-adapter-use-package' Adapter for `use-package'. `mic-adapter-leaf' Adapter for `leaf'. ,---- | (mic-defmic mic-with-use-package use-package | :filters '(mic-filter-define-key-with-feature) | :adapter #'mic-adapter-use-package) | | (mic-with-use-package feature-name | :define-key-with-feature | ((org | (org-mode-map | ("M-a" . #'feature-name-command)))) | ;; You can use `use-package' feature | :bind | (("M-a" . beginning-of-defun) | ("M-e" . end-of-defun))) | | ;; Expanded to: | (use-package feature-name | :bind | (("M-a" . beginning-of-defun) | ("M-e" . end-of-defun)) | ;; :defer is needed to wrap :config section around `eval-after-load' | :defer t | :init | (with-eval-after-load 'org | (define-key org-mode-map (kbd "M-a") (function feature-name-command)))) `---- [use-package] [leaf.el] 5.2 Define your own `mic' with `defmacro' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When you read here, you should know `defmacro'. You can do anything with `defmacro'. `mic-defmic' is easy way to define your `mic', but may be not enough for you, because of restriction. Then *I RECOMMEND to use `defmacro'*. I am looking forward to seeing your `mic' defined by `defmacro'! 6 Alternative ============= There are some alternatives: - [`use-package'] - [`leaf'] They are more easy to use, but sometimes have less expressive ability. `mic' is more simple and has more expressive ability, but sometimes more redundant. It is just your preference. In addition, they are customizable, while `mic' is not customizable, but re-definable. You can define your own `mic' according to your preference, with `mic' help. Of course you can define your own `mic' with `use-package' or `leaf' as backend. [`use-package'] [`leaf'] 7 Contribute ============ When you think you would like to share your filter or your own `mic', use GitHub Discussion. Of course your `mic' defined by `defmacro'. Any issue is welcome. 8 License ========= This package is licensed by GPLv3. See [LICENSE]. [LICENSE]