;;;; command-line.lisp -- Application command line argument retrieval
;;;;                      and processing for Common Lisp.

;;;; Copyright (C) 2003 James F. Amundson

;;;; command-line.lisp 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.

;;;; command-line.lisp 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 command-line.lisp; see the file COPYING.  If not,
;;;; write to the Free Software Foundation, Inc., 59 Temple Place -
;;;; Suite 330, Boston, MA 02111-1307, USA.

;; Defined in maxima-package.
;; (defpackage "COMMAND-LINE"
;;   (:use "COMMON-LISP")
;;   (:nicknames "CMDLINE")
;;   (:export "CL-OPTION" "MAKE-CL-OPTION" "LIST-CL-OPTIONS" "PROCESS-ARGS"
;; 	   "GET-APPLICATION-ARGS"))

(in-package :command-line)

(defstruct cl-option
  (names nil)
  (argument nil)
  (action nil)
  (help-string nil))

(defun cl-option-description (name arg)
  (if arg
      (cond ((= (length name) 1) (format nil "~a ~a" name arg))
	    ((equal (subseq name 0 2) "--") (format nil "~a=~a" name arg))
	    (t (format nil "~a ~a" name arg)))
      name))

(defun list-cl-options (cl-option-list)
  (format t "options:~%")
  (dolist (opt cl-option-list)
    (let ((help-string (cl-option-help-string opt))
	  (names (cl-option-names opt))
	  (arg (cl-option-argument opt)))
      (format t "    ~a" (cl-option-description (first names) arg))
      (dolist (name (rest names))
	(format t ", ~a" (cl-option-description name arg)))
      (format t ":")
      (if help-string
	  (format t " ~a" help-string))
      (format t "~%"))))

(defun in-list (item list)
  (dolist (element list)
    (if (equal item element)
	(return-from in-list t)))
  nil)

(defun parse-args (args cl-option-list)
  (if (null args)
      nil
      (let ((arg (pop args))
	    (arg-matched nil))
	(dolist (opt cl-option-list)
	  (if (in-list arg (cl-option-names opt))
	      (progn
		(cond ((and (cl-option-action opt) (cl-option-argument opt))
		       (funcall (cl-option-action opt) (pop args)))
		      ((cl-option-action opt)
		       (funcall (cl-option-action opt)))
		      ((cl-option-argument opt)
		       (pop args)))
		(setf arg-matched t)
		(return t))))
	(if (and (not arg-matched) (not (equal arg "")))
	    (format t "Warning: argument ~a not recognized~%" arg))
	(parse-args args cl-option-list))))

(defun expand-compound-arg (arg)
  (map 'list  #'(lambda (char) (concatenate 'string "-" (string char)))
       (subseq arg 1)))

(defun expand-equals-arg (arg)
  (let ((equals-position (search "=" arg)))
    (list (subseq arg 0 equals-position) (subseq arg (+ 1 equals-position)))))

(defun expand-args (args)
  (if (null args)
      nil
      (let* ((arg (car args))
	     (rest (expand-args (cdr args)))
	     (listarg (list arg)))
	(cond ((< (length arg) 2) nil)
	      ((and (equal (subseq arg 0 2) "--") (search "=" arg))
	       (setf listarg (expand-equals-arg arg)))
	      ((equal (subseq arg 0 2) "--") nil)
	      ((equal (char arg 0) #\-)
	       (if (> (length arg) 2)
		   (setf listarg (expand-compound-arg arg)))))
	(append listarg rest))))

(defun process-args (args cl-option-list)
  (parse-args (expand-args args) cl-option-list))

(defun get-application-args ()
  #+clisp (rest ext:*args*)
    
  #+ecl (remove "" (rest (ext:command-args)))

  #+cmu (let ((result lisp::lisp-command-line-list))
	  (do ((removed-arg nil (pop result)))
	      ((or (equal removed-arg "--") (equal nil result)) result)))

  #+scl (let ((result ext:*command-line-strings*))
	  (do ((removed-arg nil (pop result)))
	      ((or (equal removed-arg "--") (equal nil result)) result)))

  #+sbcl (rest sb-ext:*posix-argv*)

  #+gcl  (let ((result  si::*command-args*))
	   (do ((removed-arg nil (pop result)))
	       ((or (equal removed-arg "--") (equal nil result)) result)))

  #+allegro
  (let ((args (system:command-line-arguments :application t)))
    ;; Skip the first arg, which is the full path to alisp.
    (rest args))
      
  #+lispworks (rest system:*line-arguments-list*)

  #+openmcl
  (rest (ccl::command-line-arguments))
  )
