;;; extension --- Extension library of FG42 ;; ;; Copyright (c) 2010-2021 Sameer Rahmani ;; ;; Author: Sameer Rahmani ;; URL: https://gitlab.com/FG42/FG42 ;; Version: 0.1.0 ;; ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;; ;;; Acknoledgement: ;; This library is heavily inspired by Kite mini library. Kudos Tung Dao ;; for his great work. ;; ;;; Commentary: ;;; Code: ;; This library provides some basic means to create a new FG42 extensions (require 'cl-lib) (require 'fg42/utils) ;; Variables ----------------------------- (defvar fg42/extensions '() "A list of official FG42 extensions.") (defvar activated-extensions () "A list of all activated extensions.") (defvar disabled-abilities (make-hash-table) "A hash of all the disabled abilities.") ;; Structures ----------------------------- (cl-defstruct fg42-extension "Each FG42 extension should implement a copy of this structure." name ;; Let's keep this field for backward compatiblity for a while docs ;; Each extension should expose a info page. doc-index ;; Projectile provides a project type that we can use to ;; activate/load the extensions based on their registered ;; type. project-types (version nil) ;; An instance of fg42-actions structure that describe the ;; different actions of the given extension (actions nil) (path nil) ;; Callbacks (on-initialize nil) (on-load) (on-unload)) ;; Functions ------------------------------ (defun active-ability? (name) "Return t if ability with the given NAME was not in disabled-abilities." (if (gethash name disabled-abilities) nil t)) (defun disable (&rest abilities) "Add the given ABILITIES to disabled-abilities hash." (dolist (abl abilities) (puthash abl t disabled-abilities))) (defun extension-path (extension) "Return the path to the given EXTENSION." (or (fg42-extension-path extension) ;; TODO: should we extract variables such as `fg42-home' to their ;; dedicated ns in order to avoid the warning ? (concat fg42-home "/lib/extensions/" (fg42-extension-name extension) ".el"))) (defun load-extension (ext) "Load the given EXT which is a `fg42-extension' instance." (load-file (extension-path ext)) (when-let ((init-fn (eval (fg42-extension-on-initialize ext)))) (apply (symbol-function init-fn) '(ext)))) ;; Macros --------------------------------- (defmacro ability (name deps &rest body) "Define an ability with the given NAME, DEPS, and BODY. *deps* should be a list of abilities with the defined ability dependens to them. *body* is a block of code which will run as the ability initializer code." (declare (doc-string 2) (indent 0)) `(if (active-ability? (intern ,(symbol-name name))) (when (null (delq t (mapcar 'active-ability? (quote ,deps)))) ,@body))) (defmacro defability (name deps &optional docs &rest body) "Define an ability with the given NAME, DEPS, DOCS and BODY. *deps* should be a list of abilities with the defined ability dependens to them. *body* is a block of code which will run as the ability initializer code." (declare (doc-string 3) (indent 2)) ;; TODO: there's no point of using `if' in the quoted code. evaluate ;; the `if' in compile time and return nil or evalute the body. `(if (active-ability? (intern ,(symbol-name name))) (when (null (delq t (mapcar 'active-ability? (quote ,deps)))) ,@body))) (defmacro extension (name &rest args) "A simple DSL to define new fg42 extension by given NAME and ARGS." ;(declare (doc-string 1) (indent 1)) `(setq ,name (apply 'make-fg42-extension :name ,(symbol-name name) (quote ,args)))) (defmacro defextension (name &optional docstring &rest args) "A simple DSL to define new fg42 extension by given NAME, DOCSTRING and ARGS." (declare (doc-string 2) (indent 1)) ;; TODO: Inject the docstring to the current `system' in order ;; to collect it later for `describe-extension' function. `(setq ,name (apply 'make-fg42-extension :name ,(symbol-name name) (quote ,args)))) (defmacro with-ability (name &rest body) "If the ability with the given NAME is not disabled, Run the BODY." `(when (active-ability? (intern ,(symbol-name name))) ,@body)) (defun describe-extension (extension) "Show the doc-string of the EXTENSION." (interactive) (let ((doc-file (fg42-extension-docs (symbol-value extension))) (b (get-buffer-create (concat "*" (symbol-name extension) " docs*")))) (set-buffer b) (insert-file-contents (concat fg42-home "/" doc-file)) (read-only-mode t) (switch-to-buffer b) (org-mode))) (comment (defextension example-extension "Very simple extention as a test" :path "~/example-extension.el" :on-initialize 'example-extention-init) (load-extension example-extension)) (provide 'fg42/extension) ;;; extension ends here