;;; smart-compile+.el --- an interface to `compile' -*- folded-file: t -*-

;; Copyright (C) 1998-2003  Seiji Zenitani <>

;; Author: Seiji Zenitani <>
;; Version: 3.0
;; Keywords: tools, unix
;; Created: 1998-12-27
;; Compatibility: Emacs 20, 21
;; URL:

;; Modified by: William XWL <>
;; Date: 2005/05/07 00:43:51

;; Mainly add `smart-run' function, along with many minor
;; modifications.

;;{{{ GPL 

;; 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
;; 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 package provides `smart-compile' function.
;; You can associates a particular file with a paticular compile functions,
;; by editing `smart-compile-alist'.
;; To use this package, add these lines to your .emacs file:
;;     (require 'smart-compile)
;; Note that it requires emacs 20 or later.

;;; Code:

;;   List of compile commands. In argument of `compile', some keywords
;; beginning with '%' will be replaced by:

;;   %F  absolute pathname            ( /usr/local/bin/netscape.bin )
;;   %f  file name without directory  ( netscape.bin )
;;   %n  file name without extention  ( netscape )
;;   %e  extention of file name       ( bin )
(defvar smart-compile-alist
  '(("\\.c$"          . "gcc -O2 %f -lm -o %n")
    ("\\.[Cc]+[Pp]*$" . "g++ -O2 %f -lm -o %n")
    ("\\.java$"       . "javac %f")
    ("\\.f90$"        . "f90 %f -o %n")
    ("\\.[Ff]$"       . "f77 %f -o %n")
    ("\\.pl$"         . "perl -cw %f")
    ("\\.mp$"	      . "mptopdf %f")
    ("\\.php$"        . "php %f")
    ("\\.tex$"        . "latex %f")
    ("\\.texi$"       . "makeinfo %f")
    (emacs-lisp-mode  . (emacs-lisp-byte-compile))))

(defvar smart-compile-replace-alist
  '(("%F" . (buffer-file-name))
    ("%f" . (file-name-nondirectory (buffer-file-name)))
    ("%n" . (file-name-sans-extension (file-name-nondirectory (buffer-file-name))))
    ("%e" . (file-name-extension (buffer-file-name)))))

(defvar smart-compile-check-makefile t)
(make-variable-buffer-local 'smart-compile-check-makefile)

(defvar smart-run-alist
  '(("\\.c$"          . "./%n")
    ("\\.[Cc]+[Pp]*$" . "./%n")
    ("\\.java$"       . "java %n")
    ("\\.php$"	      . "php %f")
    ("\\.m$"	      . "./%f")
    ("\\.scm"         . "./%f")
    ("\\.tex$"        . "dvisvga %n.dvi")
    ;;    ("\\.texi$"       . "info")))
    (texinfo-mode     . (info (smart-compile-replace "")))))

(defvar smart-executable-alist

(defun smart-compile-replace (str)
  "Replace the smart-compile-replace-alist."
  (let ((rlist smart-compile-replace-alist))
    (while rlist
      (while (string-match (caar rlist) str)
	(setq str (replace-match (eval (cdar rlist)) t nil str)))
      (setq rlist (cdr rlist))))

(defun smart-compile (&optional arg)
  "An interface to `compile'.
It calls `compile' or other compile functions.
If you set `smart-compile-alist' in your .emacs,
you can define your own compile commands.
If arg, do compile interactively."
  (let ((name (buffer-file-name))
        (not-yet t))

    (if (not name)(error "cannot get filename."))

    ;; any project tools exist?
     ;; make ?
     ((and (or (file-readable-p "Makefile")
	       (file-readable-p "makefile"))
	   (not (and (local-variable-p 'compile-command)
      (if (y-or-n-p "Makefile is found. Try 'make'? ")
	  (set (make-local-variable 'compile-command) "make ")
	(setq smart-compile-check-makefile nil)))

     ;; ant ?
     ((and (or (file-readable-p "build.xml"))
	   (not (and (local-variable-p 'compile-command)
      (if (y-or-n-p "build.xml is found. Try 'ant'? ")
	  (set (make-local-variable 'compile-command) "ant ")
	(setq smart-compile-check-makefile nil)))

     ;; else
      (if (and (local-variable-p 'compile-command)
	    (call-interactively 'compile)
	    (setq not-yet nil)))))

    ;; compile
    (let( (alist smart-compile-alist)
          (rlist smart-compile-replace-alist)
          (case-fold-search nil)
          (function nil) )
      (while (and alist not-yet)
        (if (or
             (and (symbolp (car (car alist)))
                  (eq (car (car alist)) major-mode))
             (and (stringp (car (car alist)))
                  (string-match (car (car alist)) name)))
              (setq function (cdr (car alist)))
              (if (stringp function)
                    (if (not
                         (and (local-variable-p 'compile-command)
                        (let ((command function))
			  (setq command (smart-compile-replace command))
                          (set (make-local-variable 'compile-command)
 			(call-interactively 'compile))
                (eval function))
              (setq alist nil)
              (setq not-yet nil))
          (setq alist (cdr alist)))))

    ;; If compile-command is not defined and the contents begins with "#!",
    ;; set file name to the default compile-command.
    (if (and not-yet
             (not (memq system-type '(windows-nt ms-dos)))
             (not (string-match "/\\.[^/]+$" name))
              (and (local-variable-p 'compile-command)
          (if (equal "#!" (buffer-substring 1 (min 3 (point-max))))
              (set (make-local-variable 'compile-command) name))))

    ;; compile
    (if not-yet (call-interactively 'compile))))

(defun smart-run ()
  "Run the executable program according to the file type. If you set
`smart-run-alist' and `smart-executable-alist', then you can add new run
commands to new file types."
  (let ((name (buffer-file-name))
	(alist smart-run-alist)
	(rlist smart-compile-replace-alist)
	(elist smart-executable-alist)
	(executable nil)
	(update t)
	(case-fold-search nil))

    (if (not name) (error "cannot get filename."))

    ;; dose the executable file exist and update?
    (let ((exfile (car elist)))
      (while (and elist (not executable))
	;; r is a local rlist
	(let ((r smart-compile-replace-alist))
	  (while r
	    (while (string-match (caar r) exfile)
	      (setq exfile
		     (eval (cdar r)) t nil exfile)))
	    (setq r (cdr r))))
	(let ((file (concat (file-name-directory (buffer-file-name))
	  (if (file-readable-p file)
		(if (file-newer-than-file-p name file)
		    (setq update nil))
		(setq executable t))
	    (setq exfile (cadr elist))))
	(setq elist (cdr elist))))

    (if (and executable update)
	(while alist
	  (let ((run-type (caar alist))
		(run-cmd (cdar alist)))
	    (cond ((and (symbolp run-type)
			(eq run-type major-mode))
		   (eval run-cmd))
		  ((and (stringp run-type)
			(string-match run-type name))
		   (progn (shell-command (smart-compile-replace run-cmd))
			  (setq alist nil)))
		  (t (setq alist (cdr alist))))))

      (if (and (not update) (y-or-n-p "File out of date, recompile? "))
	(if (y-or-n-p "Compile first? ")

(provide 'smart-compile+)

;;; smart-compile+.el ends here