main.rkt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; main.rkt
;; Richard Cobbe
;; January 2011
;;
;; Main interface for functional command line library.  Defines the
;; command-line macro that is the primary entry point.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

#lang racket

(require "parser.rkt"
         "match.rkt")

(provide functional-command-line
         (struct-out exn:functional-command:parse))

;; XXX we call this functional-command-line to avoid a conflict with
;; command-line.  Is there a way to resolve conflicts w/ e.g. module prefixes
;; in a Scribble manual?

;; functional-command-line :: SExpr (U (Listof String) (Vectorof String))
;;                         -> Anything
;; raises exn:functional-command:parse
;; on match failure, prints relevant usage & terminates
(define functional-command-line
  (lambda (spec args)
    (with-handlers ([exn:fc:help-request?
                     (print-exn (current-output-port) (current-output-port))]
                    [exn:fc:match?
                     (print-exn (current-error-port) (current-output-port))])
      (functional-command-line* spec args))))

;; functional-command-line* :: SExpr (U (Listof String) (Vectorof String))
;;                          -> Anything
;; raises exn:functional-command:match, exn:functional-command:parse
(define functional-command-line*
  (lambda (spec args)
    (let ([args (if (vector? args) (vector->list args) args)])
      (match-spec (parse-spec spec) args))))

;; print-exn :: Output-Port Output-Port -> Exn:FC:Match -> a
;; prints e's message to error-port (unless empty), prints e's usage info to
;; usage-port, and exits.
(define print-exn
  (lambda (error-port usage-port)
    (lambda (e)
      (let ([msg (exn-message e)])
        (unless (string=? msg "")
          (fprintf error-port msg))
        (fprintf usage-port (exn:fc:match-usage-info e))
        (exit 1)))))

;; XXX thought about simple macro interface with lousy error checking, but I'll
;; have to think very carefully about the right way to do error checking here.
;;   - how to verify that, e.g., the first element of a spec is a string, while
;;     still allowing it to be an arbitrary expression?