;;; -*-emacs-lisp-*-
;;; ====================================================================
;;;  @Emacs-Lisp-file{
;;;     author          = "Nelson H. F. Beebe",
;;;     version         = "1.13",
;;;     date            = "09 March 2010",
;;;     time            = "08:34:44 MST",
;;;     filename        = "fmtdep.el",
;;;     address         = "University of Utah
;;;                        Department of Mathematics, 110 LCB
;;;                        155 S 1400 E RM 233
;;;                        Salt Lake City, UT 84112-0090
;;;                        USA",
;;;     telephone       = "+1 801 581 5254",
;;;     FAX             = "+1 801 581 4148",
;;;     URL             = "http://www.math.utah.edu/~beebe",
;;;     checksum        = "51226 279 1288 10687",
;;;     email           = "beebe@math.utah.edu, beebe@acm.org,
;;;                        beebe@computer.org (Internet)",
;;;     codetable       = "ISO/ASCII",
;;;     keywords        = "GNU Emacs, Makefile, make",
;;;     license         = "GNU General Public License, version 2",
;;;     supported       = "yes",
;;;     docstring       = "This file contains the GNU Emacs command
;;;                        format-dependency-list for neatly
;;;                        reformatting a long list of dependents in a
;;;                        Makefile target, and the command
;;;                        format-target-list for inserting a list of
;;;                        targets in a series of comments at point.
;;;
;;;                        The checksum field above contains a CRC-16
;;;                        checksum as the first value, followed by the
;;;                        equivalent of the standard UNIX wc (word
;;;                        count) utility output of lines, words, and
;;;                        characters.  This is produced by Robert
;;;                        Solovay's checksum utility.",
;;;  }
;;; ====================================================================

;;; Edit history (reverse chronological order):
;;; [09-Mar-2010] 1.13		Change magic marker sentinel character
;;;				from \377 to \177, because emacs-23.x
;;;				fails to match it with (string-equal ...).
;;; [24-Apr-2006] 1.12		Set indentation in
;;;				format-dependency-list automatically
;;;				according to the position of the
;;;				assignment or colon operator, if
;;;				possible.
;;; [04-Jul-2005] 1.11		Modify make-clean-and-clobber-targets
;;;				to produce separate dependencies on
;;;				each LHS.
;;; [01-Nov-2003] 1.10		Update core list in Makefile target clean.
;;; [22-Mar-2003] 1.09		Adjust format-dependency-list to properly
;;;				handle fill-column (it was off-by-3
;;;				because of unaccounted-for padding),
;;;				and make both fill-column and fill-prefix
;;;				locals, exploiting emacs-lisp dynamic binding.
;;; [14-Jul-1998] 1.08		Make minor adjustment in
;;;				format-dependency-list to handle case of
;;;				region not ending in a newline; the old
;;;				version left the \377 magic marker at
;;;				the end of the buffer in such a
;;;				case. Add (provide 'fmtdep) and define
;;;				fmtdep-date and fmtdep-version.
;;; [10-Nov-1997] 1.07		Make minor adjustment in
;;;				format-dependency-list to ensure
;;;				blanks around equals sign; previously,
;;;				if the current column was a multiple
;;;				8, they could be lost.
;;; [25-Sep-1995] 1.06		Update make-clean-and-clobber-targets
;;; 				to replace obsolete realclean target
;;; 				by maintainer-clean for GNU standard
;;; 				Makefile targets.
;;; [04-Feb-1995] 1.05		Rewrite format-dependency-list so that
;;;				it avoids region narrowing and widening,
;;;				and so that it correctly computes line
;;;				lengths during the filling operation.
;;;				The old version produced a short first
;;;				line because it used NUL for padding,
;;;				which counts as two-characters in line
;;;				width computation.
;;;				Add generation of SHELL line in
;;;				make-clean-and-clobber-targets.
;;; [26-Nov-1993] 1.04		Extend format-target-list to handle
;;;				multiple targets per line, and add
;;;				internal-split-string function.  Add
;;;				make-clean-and-clobber-targets function.
;;; [11-Oct-1993] 1.03		add support for optional argument to
;;; 				format-dependency-list
;;; [21-Oct-1992] 1.02		Add standard file header, and remove colons
;;;				from format-dependency-list output
;;; [09-Jul-1992] 1.01		Add format-target-list.
;;; [23-Jan-1991] 1.00		Initial version with format-dependency-list.

(defconst fmtdep-version "1.13")	;NB: update this at every change
(defconst fmtdep-date "[09-Mar-2010]") 	;NB: update this at every change
(provide 'fmtdep)

(defun format-dependency-list (&optional ntabs)
  "Collapse the Makefile dependency list beginning on the current line,
then refill it with continuation lines."
  (interactive "P")
  ;; version 1.12 [24-Apr-2006]: set ntabs automatically according to
  ;; position of assignment operator, if possible
  (beginning-of-line)
  (if (looking-at "^[^:=]*[:=]")
      (progn
	(beginning-of-line)
	(re-search-forward "[:=]")
	(backward-char 1)
	(setq ntabs (/ (+ (current-column) 7) 8)))
    (setq ntabs (if ntabs (prefix-numeric-value ntabs) 3)))
  (internal-collapse-Makefile-list)
  (beginning-of-line)
  (save-excursion
    (let* ((beg (point))
	   (end)
	   (start-prefix)
	   (old-fill-column fill-column)
	   (fill-column (- old-fill-column 3))
	   (fill-prefix))
      (setq fill-prefix (concat (make-string ntabs 9) "  "))
      (goto-char beg)
      (re-search-forward "[:=]")
      (backward-char 1)

      ;; We require a printable character for padding in order for
      ;; fill-region and current-column to work correctly, so we
      ;; choose a tilde as the least-likely candidate.  We therefore
      ;; check for the presence of one first, so as to avoid an
      ;; editing disaster when we later replace tildes by spaces.
      (save-excursion
	(forward-line 1)
	(setq end (point))
	(goto-char beg)
	(if (search-forward "~" end t)
	    (error "Cannot handle dependency list containing a tilde")))

      ;; Supply spaces after :, and before = by inserting
      ;; column-padding chars before filling the region.
      (cond
       ((looking-at "=")
	(delete-horizontal-space)
	(insert-char ?\~ (max 1(- (* 8 ntabs) (current-column))))
	(forward-char 1)
	(if (not (looking-at "[ \t]"))
	    (progn
	      (insert " ")
	      (forward-char -1)))
	(forward-char -1))
       ((looking-at ":")
	(delete-horizontal-space)
	(forward-char 1)
	(delete-horizontal-space)
	(insert-char ?\~ (- (+ 2 (* 8 ntabs)) (current-column)))))

      ;; The job region is now the current line, so remember its end
      ;; and insert a magic marker just past the end so that we can
      ;; detect end-of-region in the searches below.
      (forward-line 1)
      (if (not (string-equal "\n" (buffer-substring (1- (point)) (point))))
	  (insert "\n"))	      ;ensure region ends with newline
      (setq end (point))
      (insert-char ?\177 1)	   ;install magic end-of-region marker
      (fill-region beg end)		;do the formatting work

      ;; Replace tildes by spaces in our job region
      (goto-char beg)
      (while (and (< (point) end) (search-forward "~" end t))
	(replace-match " " nil t))

      ;; Finally, replace newline by space-backslash-newline in our
      ;; job region.  We use the end-of-region marker in the loop
      ;; termination condition, since the substitutions invalidate the
      ;; end variable.
      (goto-char beg)
      (catch 'DONE
	(while (search-forward "\n" nil t)
	  (if (string-equal (buffer-substring (point) (1+ (point))) "\177")
	      (throw 'DONE nil))
	  (replace-match " \\\n" nil t)))
      (if (string-equal (buffer-substring (point) (1+ (point))) "\177")
	  (delete-char 1))

      )))

(defun format-target-list (&optional do-sort)
  "Insert in the Makefile at the current line a list of targets found in
the Makefile.  With an argument, the targets are sorted alphabetically."
  (interactive)
  (let ((colon nil)
	(s "")
	(start))
    (save-excursion
      (goto-char (point-min))
      (while (re-search-forward "^[^\t #:=.][^#:=]*:" nil t)
	(setq colon (point))
	(beginning-of-line)
	(if (not (looking-at "^[.]"))	;bug workaround: re-search-forward finds
	    (mapcar '(lambda (str)	;. at start of line
		       (setq s (format "%s#\t%s\n" s str)))
		    (internal-split-string (buffer-substring
					    (point) (1- colon)) " ")))
	(forward-line 1)))
    (beginning-of-line)
    (insert "# Current target list:\n")
    (setq start (point))
    (insert s)
    (if (or do-sort current-prefix-arg)
	(sort-lines nil start (point)))))


(defun internal-collapse-Makefile-list ()
  "Collapse the Makefile dependency list beginning on the current line,
joining consecutive lines that end with a backslash."
  (catch 'EXIT
    (while t
      (end-of-line)
      (backward-char 1)
      (if (looking-at "\\\\$")
	  (progn
	    (delete-char 2)
	    (just-one-space))
	(throw 'EXIT nil)))))

(defun internal-split-string (s fs)
  "Split a string S into words separated by FS, and return it as a
list of words.  Leading and trailing FS characters are ignored."
  (let ((k 0) (n (length s)) (start) (end))
    (if (> n 0)
	(progn
	  (while (and (< k n)
		      (string-equal (substring s k (1+ k)) fs))
	    (setq k (1+ k)))
	  (setq start k)
	  (while (and (< k n)
		      (not (string-equal (substring s k (1+ k)) fs)))
	    (setq k (1+ k)))
	  (setq end k)
	  (while (and (< k n)
		      (string-equal (substring s k (1+ k)) fs))
	    (setq k (1+ k)))
	  (cons (substring s start end)
		(internal-split-string (substring s k) fs)))
      nil)))

(defun make-clean-and-clobber-targets ()
  "Insert standard clean and clobber targets in the Makefile at the
current point, along with GNU standard equivalents."
  (interactive)
  (beginning-of-line)
  (insert "clean:\n"
	"\t-$(RM) *.i\n"
	"\t-$(RM) *.o\n"
	"\t-$(RM) *~\n"
	"\t-$(RM) \\#*\n"
	"\t-$(RM) a.out\n"
	"\t-$(RM) core core.*\n"
	"\n")
  (insert "clobber:\tdistclean\n\n")
  (insert "distclean:\tmostlyclean\n\n")
  (insert "maintainer-clean:\tdistclean\n"
	  "\t@echo \"This command is intended for maintainers to use;\"\n"
          "\t@echo \"it deletes files that may require special tools to rebuild.\"\n\n")
  (insert "mostlyclean: clean\n\n")
  (save-excursion
    (goto-char (point-min))
    (if (not (re-search-forward "^SHELL[ \t]*=" nil t))
	(progn
	  (goto-char (point-min))
	  (re-search-forward "^[^#]")
	  (beginning-of-line)
	  (insert "\nSHELL\t\t= /bin/sh\n\n")))
    (if (not (re-search-forward "^RM[ \t]*=" nil t))
	(progn
	  (goto-char (point-min))
	  (re-search-forward "^[^#]")
	  (beginning-of-line)
	  (insert "\nRM\t\t= /bin/rm -f\n\n")))))