;;; debian-startup.el --- Debian specific emacsen startup code.

;; Copyright (C) 2006 R.Ramkumar

;; Maintainer: R.Ramkumar <andyetitmoves@gmail.com>
;; Keywords: debian

;; This file is submitted for inclusion into the debian release of
;; GNU Emacs. It is released under the same terms, namely the GPL v2
;; or later.

;; GNU Emacs 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.

;; GNU Emacs 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., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:

;; This file contains startup code needed by all the various flavors
;; of Emacs for a Debian system. This package extends the original
;; package by Rob Browning. Some bits of code have been taken from the
;; original, but the implementation is largely from scratch. The
;; interface provided by this package is compatible with the original
;; with the following additions:
;;
;; * Per-user caching of all startup bits to a single byte-compiled file
;;   so that future invocations get speeded up, while not losing the
;;   flexibility that the "run-parts with bits for each package" scheme
;;   offers.
;; * An optimized `debian-pkg-add-load-path-item' implementation.
;; * Ability for local configuration of packages to be loaded.
;; * Ability to run a part of the local startup procedure before debian
;;   startup. Well, isn't Emacs all about flexibility? ;)

(defconst debian-startup-site "/etc/emacs/site-debian-startup")
(defconst debian-startup-cache-directory "~/.emacs.d")

;; Variables "customizable" from `debian-startup-site'

(defvar debian-startup-selection-mode 'disable
  "Decide how packages to load are decided.
This can be one of the two values `disable' or `enable', and affects how the
variable `debian-startup-selected-packages' is interpreted. If the former is
chosen, `debian-startup-selected-packages' is a black-list of packages to
prevent from loading (of all startup bits in the startup directories), while
`enable' makes it a white-list of bits to load.")

(defvar debian-startup-selected-packages nil
  "Select packages to white/black-list during loading.
A package is essentially the name of a package in the directories passed to
`debian-run-directories', without the extension and the leading priority
number. How this variable is used for selecting packages is decided by
`debian-startup-selection-mode'.")

;; Customization ends here

(defvar debian-startup-cache "")

(defsubst debian-pkg-add-load-path-item (item)
  (or (stringp item) (signal 'wrong-type-argument (list 'stringp item)))
  (let ((ptr load-path) last)
    (while ptr
      (and (>= (length (car ptr)) 10)
	   (string-equal (substring (car ptr) 0 10) "/usr/local")
	   (setq last ptr))
      (setq ptr (cdr ptr)))
    (if last
	(setcdr last (cons item (cdr last)))
      (setq load-path (cons item load-path))) load-path))

(defsubst xnor (a b) (or (and a b) (not (or a b))))

(defmacro debian-startup-ccheck (var check cache)
  (list 'setq var (list 'or var (list 'file-newer-than-file-p check cache))))

(eval-and-compile
  (if (fboundp 'display-warning)
      (defun debian-startup-error (fmt &rest args)
	(display-warning 'debian-startup (apply 'format fmt args) :error))
    (defun debian-startup-error (fmt &rest args)
      (apply 'message (concat "Error (debian-startup): " fmt) args))))

(defun debian-run-directories (&rest dirs)
  (let ((ptr dirs) cur basename package files dirty
	(scache (concat debian-startup-cache ".el")))
    ;; Check if I have changed since the last cache build
    (debian-startup-ccheck dirty (locate-library "debian-startup") scache)
    ;; Check if debian-startup-site has changed: this can decide files in cache
    (debian-startup-ccheck dirty (concat debian-startup-site ".el") scache)
    (debian-startup-ccheck dirty (concat debian-startup-site ".elc") scache)
    ;; Safety first! :)
    (unless (listp debian-startup-selected-packages)
      (setq debian-startup-selection-mode 'disable)
      (setq debian-startup-selected-packages nil))
    (while ptr
      ;; Check if something has been added to or deleted from the directory
      (debian-startup-ccheck dirty (car ptr) scache)
      (setq cur (directory-files (car ptr) t "^[0-9][0-9].*\\.el$" t))
      (while cur
	(setq basename (file-name-nondirectory (car cur)))
	(string-match "^[0-9][0-9]\\(.*\\)\\.el$" basename)
	(when (xnor
	       (eq debian-startup-selection-mode 'enable)
	       (member (match-string 1 basename)
		       debian-startup-selected-packages))
	  ;; Never trust cache if we have unreadable files.
	  ;; File mode changes cannot be captured by cache validation.
	  (setq dirty (or dirty (not (file-readable-p (car cur)))))
	  ;; Check if the file has changed since the last build
	  (debian-startup-ccheck dirty (car cur) scache)
	  (setq files (cons (cons (car cur) basename) files)))
	(setq cur (cdr cur)))
      (setq ptr (cdr ptr)))
    ;; Sort by basenames leaving the directories intact.
    ;; Stable sorting, so even if there are duplicates,
    ;; the (dir . basename) to be loaded stays at the top.
    (setq files (sort files '(lambda (first second)
			       (string-lessp (cdr first) (cdr second)))))
    (if (not dirty)
	;; We got lucky! Load the cache and be happy!!
	(let (debian-startup-module)
	  (condition-case error
	      (load debian-startup-cache)
	    (error (debian-startup-error
		    "Unable to load %s: %s"
		    (or debian-startup-module debian-startup-cache)
		    (error-message-string error)))))
      (message "Startup cache needs to be built.
This might take some time, but future invocations would be faster.")
      (setq ptr "")
      (setq dirty nil)
      (with-temp-buffer
	(insert ";;; This is an automatically generated startup cache file
;;; Do not edit this file\n\n(defvar debian-startup-module)\n")
	(while files
	  (setq cur (caar files) basename (cdar files))
	  ;; Weed out duplicates
	  (unless (string-equal ptr basename)
	    ;; Load the file
	    (condition-case error
		(load cur)
	      (error
	       (debian-startup-error
		"Unable to load %s: %s" cur (error-message-string error))
	       (or dirty (debian-startup-error "Stopping cache build"))
	       (setq dirty t) nil))
	    (unless dirty
	      ;; Write file to cache buffer
	      (insert (format "\n(setq debian-startup-module \"%s\")\n\n" cur))
	      (condition-case error
		  (insert-file-contents cur)
		(error
		 (debian-startup-error "Unable to include %s in cache: %s"
				       cur (error-message-string error))
		 (or dirty (debian-startup-error "Stopping cache build"))
		 (setq dirty t) nil))
	      (goto-char (point-max)))
	    (setq ptr basename))
	  (setq files (cdr files)))
	(unless dirty
	  ;; Write and byte-compile cache for future use
	  (let ((state "write"))
	    (condition-case error
		(progn
		  (write-region (point-min) (point-max) scache)
		  (setq state "compile")
		  (require 'bytecomp)
		  (byte-compile-file scache))
	      (error
	       (debian-startup-error "Unable to %s cache: %s"
				     state (error-message-string error))))))))))

(defun debian-startup (flavor)
  (if (not (boundp 'debian-emacs-flavor))
      (defconst debian-emacs-flavor flavor
        "A symbol representing the particular debian flavor of emacs that's
running.  Something like 'emacs20, 'xemacs20, etc.")
    (load debian-startup-site t)
    (condition-case error
	(make-directory debian-startup-cache-directory t)
      (error
       (debian-startup-error
	"Unable to create cache directory %s: %s. \
Speeding up of startup by caching might not be possible."
	debian-startup-cache-directory (error-message-string error))))
    (setq debian-startup-cache
	  (expand-file-name
	   (concat debian-startup-cache-directory "/"
		   (symbol-name flavor) "-startup-cache")))
    (let ((common-dir "/etc/emacs/site-start.d")
          (flavor-dir (concat "/etc/" (symbol-name flavor) "/site-start.d")))
      (debian-run-directories flavor-dir common-dir))))

(provide 'debian-startup)
