                  (s/join (str \newline\newline))))
           ((or print-help-fn identity))))))
 
+(defn multi-action-context
+  "Create a multi-action context with map **action**.  These don't include the
+  name of the action on the command line and are typical UNIX like command
+  lines (ex: `ls`, `grep`, etc).
+
+  The parameter is **action** is a list of list of symbols takes the form:
+```
+'((:action-name1 package1 action1)
+  ...)
+```
+  where **action-name** is a key, **package** is the package and **action** is
+  the action definition.
+
+Keys
+----
+* **help-option:** the help action command, which defaults to [[help-option]]
+* **version-option:** usually created with [[version-option]]
+* **global-actions:** addition global actions in addition to the help and
+  version options listed above
+* **action-print-order:** an optional sequence of action name keys indicating
+  what order to print action help
+* **print-help-fn:** an optional function that takes a string argument of the
+  generated option help to provide a way to customize the help message
+* **usage-format-fn:** generate the usage portion of the help message that
+  takes the names of all action if generating multi-action usage or nil for
+  single action usage messages
+* **:no-action-fn** a function to call when no action is given, defaults to
+  printing the contents of [[help-message]]; set to `nil` to continue
+  processing without an action"
+  [actions &
+   {:keys [global-actions help-option version-option
+           action-print-order print-help-fn usage-format-fn
+           no-action-fn]
+    :or {help-option (help-option)
+         no-action-fn (fn [] (println (help-message)))}}]
+  (merge (if action-print-order {:action-print-order action-print-order})
+         (if print-help-fn {:print-help-fn print-help-fn})
+         {:usage-format-fn (or usage-format-fn (create-default-usage-format))}
+         {:action-definitions actions
+          :global-actions (concat global-actions
+                                  (if help-option [help-option])
+                                  (if version-option [version-option]))
+          
+          :no-action-fn no-action-fn
+          :action-mode 'multi}))
+
+(defn single-action-context
+  "Create a single action context with map **action**.  These don't include the
+  name of the action on the command line and are typical UNIX like command
+  lines (ex: `ls`, `grep`, etc).
+
+  The parameter is **action** is a list of symbols that takes the form:
+```
+'(package action)
+```
+  where **package** is the package and **action** is the action definition.
+
+  See [[multi-action-context]] for the description of **options**."
+  [action & options]
+  (-> (cons :single-action action)
+      list
+      (#(apply multi-action-context % options))
+      (merge {:action-mode 'single})))
+
+(defn create-actions [action-context]
+  (->> (:action-definitions action-context)
+       (map (fn [[key package action-def]]
+              (eval (list 'require `(quote [~package])))
+              {key (-> (format "%s/%s" package action-def)
+                       symbol
+                       eval
+                       (assoc :name (name key)))}))
+       (apply merge)))
+
+(defn- execute-action [key opts args]
+  (let [action (:app (get (create-actions) key))]
+    (apply action opts args)))
+
+(defn handle-exception
+  "Handle exceptions thrown from CLI actions."
+  [e]
+  (log/debugf "handle exception: <%s>" e)
+  (let [msg (.getMessage e)]
+    (if *log-error* (log/error e "action line parse error"))
+    (if (instance? java.io.FileNotFoundException e)
+      (binding [*out* *err*]
+        (println (format "%sio error: %s" (program-fmt) msg)))
+      (binding [*out* *err*]
+        (println (format "%serror: %s"
+                         (program-fmt)
+                         (if ex-data
+                           (.getMessage e)
+                           (.toString e)))))))
+  (log/debugf "dumping JVM: %s, rethrow: %s"
+              *dump-jvm-on-error* *rethrow-error*)
+  (if *dump-jvm-on-error*
+    (System/exit 1)
+    (if *rethrow-error* (throw e))))
+
+(defmacro with-exception
+  "Wrap a CLI forms in a `try` and call [[handle-exception]] if an exception
+  occurs."
+  {:style/indent 0}
+  [& forms]
+  (let [ex (gensym)]
+    `(try
+       ~@forms
+       (catch Exception ~ex
+         (handle-exception ~ex)))))
+
+(defn error-msg
+  "Print every element of the sequence **errors**.  If there **errors** is a
+  singleton then only print one in the common usage format."
+  [errors]
+  (if (= (count errors) 1)
+    (str (program-fmt) (first errors))
+    (str "The following errors occurred while parsing your action:\n\n"
+         (s/join \newline errors))))
+
 (defn- parse-single [action-context actions action action-key
                      arguments single-action-mode?
                      & {:keys [print-errors?]

(defn- parse-multi [action-context actions arguments]
  (let [action-keys (action-keys action-context)
        action-key (keyword (first arguments))
        action (get actions action-key)]
    (parse-single action-context actions action action-key (rest arguments) false)))

(defn- parse-global [action-context actions arguments]
  (let [{:keys [global-actions]} action-context]

        description
        (if-not (empty? options) \newline)
        (:summary (cli/parse-opts nil options)))))

(defn help-message
  "Generate the help message text and return it.
