;;; project-settings.el --- Define settings for project directories ;; ;; Author: Mark Triggs ;; ;; This file 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 2, or (at your option) ;; any later version. ;; ;; This file 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; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;; ;; Commentary: ;; ;; Here's an example of how I use this: ;; ;; (define-project bingrab ("~/projects/bingrab") ;; (setq unit-test-file-fn ;; (lambda (filename) ;; (if (string-match "/test_" filename) ;; filename ;; (prefix-file-name filename "tests/test_")))) ;; (setq unit-test-command ;; (lambda () ;; (test-with-compile ;; "cd ~/projects/bingrab/; make")))) ;; ;;; Code: ;; (defvar project-list '() "A list of currently defined projects. Each element is of the form (PROJECT-NAME MATCH-FN CODE). See `define-project' for more information.") (defmacro define-project (name match-element &rest settings) "Define a new project called NAME. MATCH-ELEMENT is used to decide which project a file/directory belongs to, and should either be a list of paths, or a function which returns non-nil if a given file belongs to this project. SETTINGS should be a body of code that will be executed in the buffer of the file/directory." `(add-new-project ',name ',match-element (lambda () ,@settings))) (defvar project-applied-hook '() "A hook run after a project is applied to a file/directory.") (defmacro undefine-project (name) "Remove the project named by NAME." `(delete-project ',name)) (defun delete-project (name) (setq project-list (remove* name project-list :test 'eq :key 'car))) (defun add-new-project (name match fn) (delete-project name) (push (list name match fn) project-list)) (defun project-normalise-path (path) (replace-regexp-in-string "\/*$" "" (expand-file-name path))) (defun find-path-projects (path) (let ((path (project-normalise-path path))) (remove-if-not (lambda (project) (destructuring-bind (project-name match fn) project (project-matches match path))) project-list))) (defun project-matches (match path) (if (and (listp match) (every #'stringp match)) (some #'(lambda (project-path) (eql (string-match (project-normalise-path project-path) path) 0)) match) (funcall match path))) (defun apply-project-settings () (let ((projects (find-path-projects (or (buffer-file-name) default-directory)))) (dolist (project (reverse projects)) (destructuring-bind (name match fn) project (funcall fn))) (when projects (run-hooks 'project-applied-hook)))) (add-hook 'find-file-hook 'apply-project-settings) (add-hook 'dired-mode-hook 'apply-project-settings) (provide 'project-settings) ;;; project-settings.el ends here