;;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*- ;;;; ************************************************************************* ;;;; FILE IDENTIFICATION ;;;; ;;;; Name: getopt.lisp ;;;; Purpose: Command line option processing a la GNU getopt_long ;;;; Programmer: Kevin M. Rosenberg ;;;; Date Started: Sep 2003 ;;;; ;;;; $Id: package.lisp 7814 2003-09-10 12:56:02Z kevin $ ;;;; ;;;; This file, part of KMRCL, is Copyright (c) 2002 by Kevin M. Rosenberg ;;;; ;;;; KMRCL users are granted the rights to distribute and use this software ;;;; as governed by the terms of the Lisp Lesser GNU Public License ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL. ;;;; ************************************************************************* (in-package #:kmrcl) (defun is-short-option (arg) (and (>= (length arg) 2) (char= #\- (schar arg 0)) (char/= #\- (schar arg 1)))) (defun is-option-terminator (arg) (and (= 2 (length arg)) (char= #\- (schar arg 0)) (char= #\- (schar arg 1)))) (defun is-long-option (arg) (and (> (length arg) 2) (char= #\- (schar arg 0)) (char= #\- (schar arg 1)) (char/= #\- (schar arg 3)))) (defun arg->base-name (arg option-type) "returns base-name,argument" (let ((start (ecase option-type (:long 2) (:short 1))) (name-end (position #\= arg))) (values (subseq arg start name-end) (when name-end (subseq arg (1+ name-end)))))) (defun analyze-arg (arg) "Analyzes an argument. Returns option-type,base-name,argument" (let* ((option-type (cond ((is-short-option arg) :short) ((is-long-option arg) :long) (t :arg)))) (if (or (eq option-type :short) (eq option-type :long)) (multiple-value-bind (base arg) (arg->base-name arg option-type) (values option-type base arg)) (values :arg arg nil)))) (defun find-option (name options) "Find an option in option list. Handles using unique abbreviations" (let* ((option-names (mapcar #'car options)) (pos (match-unique-abbreviation name option-names))) (when pos (nth pos options)))) (defun match-option (arg options) "Matches an argument to an option. Returns option-list,option-type,base-name,argument" (multiple-value-bind (option-type base-name argument) (analyze-arg arg) (let ((match (find-option base-name options))) (values match option-type (when match (car match)) argument)))) (defun getopt (args options) "Processes a list of arguments and options. Returns filtered argument list and alist of options. opts is a list of option lists. The fields of the list are - NAME name of the long option - HAS-ARG with legal values of :NONE, :REQUIRED, :OPTIONAL - VAL value to return for a option with no arguments " (do ((pos args (cdr pos)) (finished-options) (out-opts) (out-args) (errors)) ((null pos) (values (nreverse out-args) (nreverse out-opts) errors)) (cond (finished-options (push (car pos) out-args)) ((is-option-terminator (car pos)) (setq finished-options t)) (t (let ((arg (car pos))) (multiple-value-bind (option-list option-type base-name argument) (match-option (car pos) options) (cond (option-list (cond (argument (case (second option-list) (:none (push base-name errors)) (t (push (cons base-name argument) out-opts)))) ((null argument) (if (and (eq :required (second option-list)) (null (cdr pos))) (push base-name errors) (if (or (is-short-option (second pos)) (is-long-option (second pos))) (if (eq :required (second option-list)) (push base-name errors) (push (cons base-name (third option-list)) out-args)) (progn (push (cons base-name (second pos)) out-opts) (setq pos (cdr pos)))))))) (t (if (in option-type :long :short) (push (nth-value 0 (arg->base-name arg option-type)) errors) (push arg out-args))))))))))