                      (string/join "\n")))))))
 
 
+(defn- parse-command-args
+  [usage opts specs args]
+  (if specs
+    ; Parse arguments with command specs and merge opts.
+    (let [[command-opts action-args banner]
+         (apply cli args usage specs)
+         opts (merge opts command-opts (when (:help opts) {:help true}))]
+      [banner opts action-args])
+    [usage opts args]))
+
+
+(defn- parse-opts
+  [usage cmd opts args]
+  (let [subcommands (:commands cmd)
+        subcommand-names (apply hash-set (map :name subcommands))
+        ; Test for a subcommand invocation in the argument list.
+        [command-args [subcommand & subcommand-args]]
+        (split-with (complement subcommand-names) args)
+        ; Parse command args with option specs.
+        [banner opts action-args]
+        (parse-command-args usage opts (:specs cmd) command-args)
+        ; Map subcommand to actual command map.
+        subcommand (and subcommand
+                        (some #(and (= (:name %) subcommand) %)
+                              subcommands))]
+    [banner opts action-args subcommand subcommand-args]))
+
+
+(defn- execute-action
+  [usage action opts args]
+  (cond (:help opts)
+        (do (println usage)
+            (System/exit 0))
+
+        action
+        (action opts args)
+
+        :else
+        (do (when-not (empty? args)
+              (println "Unrecognized arguments:" (string/join " " args) "\n"))
+            (println usage)
+            (System/exit 1))))
+
+
 (defn execute
-  "Parses a sequence of arguments using a command map."
+  "Parses a sequence of arguments using a command map. The action function
+  associated with a given leaf command will be called with a merged options map
+  and any remaining arguments."
   ([cmd args]
    (execute cmd {} args))
 

;; COMMAND EXECUTION

(defn- usage-banner
  "Builds a usage banner given a command map."
  [branch cmd]
  (let [{:keys [usage desc commands]} cmd]
    (str "Usage: " (string/join " " branch) " " usage "\n\n" desc

     (recur cmd branch
            (assoc opts :help true)
            (filter #(not= "help" %) args))
     ; Parse arguments, update options and find subcommands.
     (let [branch (conj branch (:name cmd))
           usage (usage-banner branch cmd)
           [usage opts action-args subcommand subcommand-args]
           (parse-opts usage cmd opts args)
           opts ((or (:init cmd) identity) opts)]
       ;(clojure.pprint/pprint {:branch branch, :opts opts, :action-args action-args, :subcommand subcommand, :subcommand-args subcommand-args})
       (if subcommand
         ; Recur on selected subcommand.
         (if-not (empty? action-args)
           (throw (IllegalArgumentException.
                    (str "Unparsed arguments before command: " action-args)))
           (recur subcommand branch opts subcommand-args))
         ; Act on current command, either to print help or execute the action.
         (execute-action usage (:action cmd) opts action-args))))))
