;;; dir-var.el --- Directory variables -*- lexical-binding: t; -*- ;;; Copyright (C) 2023 BTuin ;;; Version: 0.1 ;;; Package-Requires: ((emacs "28.1")) ;;; Homepage: https://gitlab.com/btuin2/builder ;; 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 . ;;; Commentary: ;;; This package provides project variables. ;;; Code: (require 'project) (require 'map) (require 'cl-lib) (defvar dir-var--table (make-hash-table :test 'equal)) (defcustom dir-var-file-name "emacs-project-variables-cache.el" "Name of file in which project variable are written." :type 'file :group 'builder) (defvar dir-var--tree '("" nil)) ;; node structure : (name list-of-subnodes plist-of-variables-with-values) (defun dir-var--make-node (id &optional subnodes variables) "Create a node. ID is the identifier of the node, a string. SUBNODES if a list of nodes. VARIABLES is a plist, the properties are the name of the variable (a symbol) and the values are their value." (cons id (cons subnodes variables))) (defun dir-var--get-node-subnodes (node) "Return the subnodes of NODE." (cadr node)) (defun dir-var--get-node-name (node) "Return the name of NODE (its ID)." (car node)) (defun dir-var--get-node-variables (node) "Return the plist containing the variables of NODE." (caddr node)) (defun dir-var--node-equal-p (node1 node2) "Check if the nodes NODE1 and NODE2 are equal. Two nodes are equal when their names are equal." (string-equal (dir-var--get-node-name node1) (dir-var--get-node-name node2))) (defun dir-var--find-subnode (node name) "Find a subnode in the list of subnodes of NODE. NAME is the name of the subnode to find." (seq-find (lambda (x) (string-equal (dir-var--get-node-name x) name)) (dir-var--get-node-subnodes node))) (defun dir-var--insert-node-in-parent (parent-node node) "Insert NODE in PARENT-NODE subnodes list." (let ((subnodes (dir-var--get-node-subnodes parent-node))) (let ((subnodes (if node (cons node (cl-remove-if (lambda (x) (dir-var--node-equal-p x node)) subnodes)) subnodes))) (setf parent-node (dir-var--make-node (dir-var--get-node-name parent-node) subnodes (dir-var--get-node-variables parent-node)))))) (defun dir-var--set-node-variable (node variable value) "Return NODE with the variable VARIABLE set to VALUE." (let ((variable-plist (dir-var--get-node-variables node))) (dir-var--make-node (dir-var--get-node-name node) (dir-var--get-node-subnodes node) (list (plist-put variable-plist variable value))))) (defun dir-var--get-node-variable-value (node variable) "Get the value of VARIABLE stored in NODE." (plist-get (dir-var--get-node-variables node) variable)) (defun dir-var--node-variable-exists-p (node variable) "Return non-nil if VARIABLE is stored in NODE." (plist-member (dir-var--get-node-variables node) variable)) (defun dir-var--remove-node-variable (node variable) "Return NODE without the variable VARIABLE." (let ((variable-plist (dir-var--get-node-variables node))) (dir-var--make-node (dir-var--get-node-name node) (dir-var--get-node-subnodes node) (list (map-delete variable-plist variable))))) (defun dir-var--insert-variable (root dir-split variable value) "Return a new tree with the variable inserted. ROOT is the root node of the tree. DIR-SPLIT is a list of string. It is the full path of the directory splitted a each file separator. For example, on UNIX systems, the corresponding splitted path of \"/home/user/project/source\" is the list (\"home\" \"user\" \"project\" \"source\"). VARIABLE is the symbol to refer to the variable. VALUE is the value of the variable." (if (car dir-split) (let ((subnode (or (dir-var--find-subnode root (car dir-split)) (dir-var--make-node (car dir-split))))) (dir-var--insert-node-in-parent root (dir-var--insert-variable subnode (cdr dir-split) variable value))) (dir-var--set-node-variable root variable value))) (defun dir-var--variable-get-value (root dir-split variable) "Return the value of VARIABLE. ROOT is the root node of the tree DIR-SPLIT is a list of string. It is the full path of the directory splitted a each file separator. For example, on UNIX systems, the corresponding splitted path of \"/home/user/project/source\" is the list (\"home\" \"user\" \"project\" \"source\")." (let ((value nil) (node root)) (while (and dir-split node) (setq node (dir-var--find-subnode node (car dir-split))) (setq value (or (dir-var--get-node-variable-value node variable) value)) (setq dir-split (cdr dir-split))) value)) (defun dir-var--remove-empty-subnodes (node) "Return NODE without its empty subnodes. A subnode is empty if it does not contain any variable or subnode." (dir-var--make-node (dir-var--get-node-name node) (cl-remove-if-not (lambda (node)(or (dir-var--get-node-subnodes node) (dir-var--get-node-variables node))) (dir-var--get-node-subnodes node)) (dir-var--get-node-variables node))) (defun dir-var--remove-variable (root dir-split variable) "Return the tree without the variable VARIABLE stored at DIR-SPLIT. ROOT is the root node of the tree. DIR-SPLIT is a list of string. It is the full path of the directory splitted at each file separator. For example, on UNIX systems, the corresponding splitted path of \"/home/user/project/source\" is the list (\"home\" \"user\" \"project\" \"source\")." (if (car dir-split) (let ((subnode (or (dir-var--find-subnode root (car dir-split)) (dir-var--make-node (car dir-split))))) (dir-var--remove-empty-subnodes (dir-var--insert-node-in-parent root (dir-var--remove-variable subnode (cdr dir-split) variable)))) (dir-var--remove-node-variable root variable))) (defun dir-var--split-directory (&optional directory) "Split the full path of DIRECTORY in a list. If DIRECTORY is nil, the variable DEFAULT-DIRECTORY is used instead. If DIRECTORY is not an absolute path, it is relative to DEFAULT-DIRECTORY." (cl-remove-if #'string-empty-p (file-name-split (expand-file-name (or directory default-directory))))) (defun dir-var-insert (variable value &optional directory) "Set a variable for DIRECTORY and its subdirectories. VARIABLE is the name of a variable, a symbol. VALUE is the value of the variable, it can be anything. DIRECTORY is an optional parameter. If it is not set, the current directory is used." (let ((splitted-path (dir-var--split-directory directory))) (setq dir-var--tree (dir-var--insert-variable dir-var--tree splitted-path variable value)))) (defun dir-var-get (variable &optional directory) "Get the value of VARIABLE at DIRECTORY. DIRECTORY is an optional parameter. If it is not set, the current directory is used." (let ((splitted-path (dir-var--split-directory directory))) (dir-var--variable-get-value dir-var--tree splitted-path variable))) (defun dir-var-variable-exists-p (variable &optional directory) "Return non-nil if the variable VARIABLE is defined in DIRECTORY or parents. DIRECTORY is an optional parameter. If it is not set, the current directory is used. For exemple, with the path \"/home/user/project/source\", if the variable \"var1\" is set in the directory \"project\", this function returns non-nil for the directory \"source\"." (let ((dir-split (dir-var--split-directory directory)) (node dir-var--tree) (exists nil)) (while (and (not exists) dir-split node) (setq exists (dir-var--node-variable-exists-p node variable)) (setq node (dir-var--find-subnode node (car dir-split))) (setq dir-split (cdr dir-split))))) (provide 'dir-var) ;;; dir-var.el ends here