+\f
+;;; Handling directory local variables, aka project settings.
+
+(defvar project-class-alist '()
+ "Alist mapping project class names (symbols) to project variable lists.")
+
+(defvar project-directory-alist '()
+ "Alist mapping project directory roots to project classes.")
+
+(defsubst project-get-alist (class)
+ "Return the project variable list for project CLASS."
+ (cdr (assq class project-class-alist)))
+
+(defun project-collect-bindings-from-alist (mode-alist settings)
+ "Collect local variable settings from MODE-ALIST.
+SETTINGS is the initial list of bindings.
+Returns the new list."
+ (dolist (pair mode-alist settings)
+ (let* ((variable (car pair))
+ (value (cdr pair))
+ (slot (assq variable settings)))
+ (if slot
+ (setcdr slot value)
+ ;; Need a new cons in case we setcdr later.
+ (push (cons variable value) settings)))))
+
+(defun project-collect-binding-list (binding-list root settings)
+ "Collect entries from BINDING-LIST into SETTINGS.
+ROOT is the root directory of the project.
+Return the new settings list."
+ (let* ((file-name (buffer-file-name))
+ (sub-file-name (if file-name
+ (substring file-name (length root)))))
+ (dolist (entry binding-list settings)
+ (let ((key (car entry)))
+ (cond
+ ((stringp key)
+ ;; Don't include this in the previous condition, because we
+ ;; want to filter all strings before the next condition.
+ (when (and sub-file-name
+ (>= (length sub-file-name) (length key))
+ (string= key (substring sub-file-name 0 (length key))))
+ (setq settings (project-collect-binding-list (cdr entry)
+ root settings))))
+ ((or (not key)
+ (derived-mode-p key))
+ (setq settings (project-collect-bindings-from-alist (cdr entry)
+ settings))))))))
+
+(defun set-directory-project (directory class)
+ "Declare that the project rooted at DIRECTORY is an instance of CLASS.
+DIRECTORY is the name of a directory, a string.
+CLASS is the name of a project class, a symbol.
+
+When a file beneath DIRECTORY is visited, the mode-specific
+settings from CLASS will be applied to the buffer. The settings
+for a class are defined using `define-project-bindings'."
+ (setq directory (file-name-as-directory (expand-file-name directory)))
+ (unless (assq class project-class-alist)
+ (error "No such project class `%s'" (symbol-name class)))
+ (push (cons directory class) project-directory-alist))
+
+(defun define-project-bindings (class list)
+ "Map the project type CLASS to a list of variable settings.
+CLASS is the project class, a symbol.
+LIST is a list that declares variable settings for the class.
+An element in LIST is either of the form:
+ (MAJOR-MODE . ALIST)
+or
+ (DIRECTORY . LIST)
+
+In the first form, MAJOR-MODE is a symbol, and ALIST is an alist
+whose elements are of the form (VARIABLE . VALUE).
+
+In the second form, DIRECTORY is a directory name (a string), and
+LIST is a list of the form accepted by the function.
+
+When a file is visited, the file's class is found. A directory
+may be assigned a class using `set-directory-project'. Then
+variables are set in the file's buffer according to the class'
+LIST. The list is processed in order.
+
+* If the element is of the form (MAJOR-MODE . ALIST), and the
+ buffer's major mode is derived from MAJOR-MODE (as determined
+ by `derived-mode-p'), then all the settings in ALIST are
+ applied. A MAJOR-MODE of nil may be used to match any buffer.
+ `make-local-variable' is called for each variable before it is
+ set.
+
+* If the element is of the form (DIRECTORY . LIST), and DIRECTORY
+ is an initial substring of the file's directory, then LIST is
+ applied by recursively following these rules."
+ (let ((elt (assq class project-class-alist)))
+ (if elt
+ (setcdr elt list)
+ (push (cons class list) project-class-alist))))
+
+(defun project-find-settings-file (file)
+ "Find the settings file for FILE.
+This searches upward in the directory tree.
+If a settings file is found, the file name is returned.
+If the file is in a registered project, a cons from
+`project-directory-alist' is returned.
+Otherwise this returns nil."
+ (setq file (expand-file-name file))
+ (let* ((settings (locate-dominating-file file "\\`\\.dir-settings\\.el\\'"))
+ (pda nil))
+ ;; `locate-dominating-file' may have abbreviated the name.
+ (if settings (setq settings (expand-file-name settings)))
+ (dolist (x project-directory-alist)
+ (when (and (eq t (compare-strings file nil (length (car x))
+ (car x) nil nil))
+ (> (length (car x)) (length (car pda))))
+ (setq pda x)))
+ (if (and settings pda)
+ (if (> (length (file-name-directory settings))
+ (length (car pda)))
+ settings pda)
+ (or settings pda))))
+
+(defun project-define-from-project-file (settings-file)
+ "Load a settings file and register a new project class and instance.
+SETTINGS-FILE is the name of the file holding the settings to apply.
+The new class name is the same as the directory in which SETTINGS-FILE
+is found. Returns the new class name."
+ (with-temp-buffer
+ ;; We should probably store the modtime of SETTINGS-FILE and then
+ ;; reload it whenever it changes.
+ (insert-file-contents settings-file)
+ (let* ((dir-name (file-name-directory settings-file))
+ (class-name (intern dir-name))
+ (list (read (current-buffer))))
+ (define-project-bindings class-name list)
+ (set-directory-project dir-name class-name)
+ class-name)))
+
+(declare-function c-postprocess-file-styles "cc-mode" ())
+
+(defun hack-project-variables ()
+ "Set local variables in a buffer based on project settings."
+ (when (and (buffer-file-name) (not (file-remote-p (buffer-file-name))))
+ ;; Find the settings file.
+ (let ((settings (project-find-settings-file (buffer-file-name)))
+ (class nil)
+ (root-dir nil))
+ (cond
+ ((stringp settings)
+ (setq root-dir (file-name-directory (buffer-file-name)))
+ (setq class (project-define-from-project-file settings)))
+ ((consp settings)
+ (setq root-dir (car settings))
+ (setq class (cdr settings))))
+ (when class
+ (let ((bindings
+ (project-collect-binding-list (project-get-alist class)
+ root-dir nil)))
+ (when bindings
+ (hack-local-variables-apply bindings root-dir)
+ ;; Special case C and derived modes. Note that CC-based
+ ;; modes don't work with derived-mode-p. In general I
+ ;; think modes could use an auxiliary method which is
+ ;; called after local variables are hacked.
+ (and (boundp 'c-buffer-is-cc-mode)
+ c-buffer-is-cc-mode
+ (c-postprocess-file-styles))))))))