Skip to content

Latest commit

 

History

History
160 lines (128 loc) · 5.15 KB

README.md

File metadata and controls

160 lines (128 loc) · 5.15 KB

commander...
declarative cli's via the "commander" idiom

Overview

packard aims to be a composable approach to building command line applications with Clojure.

Targeting ClojureScript is not supported at this time, in order to keep the library simple (and frankly, I hate writing cljc files).

This library takes inspiration from cobra, while not being strict around POSIX compatability.

Example

(ns atlantis.core
  (:gen-class)
  (:require
    [packard.core :as commander]))
 
(def commands
  {:usage "example"
   :desc  "an example cli http client"
   ; Flags can be one of:
   ;
   ;  :bool
   ;  :double
   ;  :float
   ;  :int
   ;  :map  -> `-m key:value --map key:value` -> {:key "value"}
   ;  :set  -> `-s value -s other -s value` -> #{"value" "other"}
   ;  :seq  ->               ""             -> '("value" "other" "value")
   ;  :str
   ;  :vec  -> `-v value -v other -s value` -> ["value" "other" "value"]
   ;
   ; their "types" are signaled using the :as keyword in an optional map.
   ; defualt type is a string (:str)
   :flags {:verbose? [:v :verbose {:as :bool}]}
   :enter (fn [_] (println "example enter")) ; called when entering root command
   :leave (fn [_] (println "example leave")) ; called when exiting root command after all sub commands handled
   :run   (fn [_] (println "example run"))
   :commands
   {:crew {:commands
           {:add {:run #(println "added" (:argv %))}
            :del {:run #(println "removed" (:argv %))}}}
    :ships {:commands
            {:commission    {:run #(println "commisioned ship" (:argv %))}
             :decommission {:run #(println "decomissioned ship" (:argv %))}
             :list         {:run #(println "all ships..." %)}}}
    :events {:flags    {:sensitive? [:s :sensitive {:as :bool}]
                        :recipients [:r :recipient {:as :set}]}
             :commands {:list {:flags {:last [:l :last {:as :int :default 10}]}
                               :run   #(println "last"
                                                (-> % :flags :last)
                                                "events"
                                                (:flags %))}}}}})

(defn -main [& args]
  (commander/exec commands args))

Then in cli:

$ clj -M:atlantis crew add milo thatch
example enter
example run
added [milo thatch]
example leave

$ clj -M:atlantis create del gunner #1
example enter
example run
removed [gunner #1]]
example leave

$ clj -M:atlantis -v ships list
example enter
example run
all ships... {:argv [], :flags {:verbose? true}, :command {:run #object[atlantis.core$fn__382 0x325bb9a6 atlantis.core$fn__382@325bb9a6]}, :state {}}
example leave

$ clj -M:atlantis events list -s -r rourke --recipient milo --recipient whitmore -r milo "we should try and sell the leviathan"
example enter
example run
last 10 events {:sensitive? true, :recipients #{whitmore rourke milo}, :last 10} [we should try and sell the leviathan]
example leave

Installation

This will be pushed to clojars once a suitable implementation has been reached

Features

  • nested commands (i.e. sub commands)
  • flag parsing
    • bool type
    • int, double, float type
    • map type
    • seq (set, seq, vec)
    • strings
  • stop execution (via packard.core/stop)
  • auto help sub command
    • No auto -h/--help flag, given -h could be useful for different commands. May revisit to allow, but for now, this should be fine.
  • auto tree sub command
    • able to print out sub command tree from any sub-command
    • able to limit depth
  • auto completion generation
    • bash
    • fish
    • zsh

On thing to note, this library is not aiming for POSIX compliance or for the -xyz syntax. That would only complicate the implemntation and subsequently make cli's more complicated. Short/long or only short are good enough for almost all instances.

Examples

Documentation

Will get a docs site up once library has reached a stable point w/ base features.

Design Outline

packard cli's are progressively parsed via a spec on recursively nested maps. Meaning on each invocation, a cli takes in the argv provided and builds up a context for sub commands, parsing flags along the way.

The primary advantage of progressively parsing commands are model as a tree, with each node in the tree being a homogenous structure.

It also has a side effect that predictable structure is enforced for the command line. The predictable structure is that a flag for a command cannot be put in the context of a parent command. Which also means that flags can be repeated in different branches of the tree, allowing for flexible definition. The method also accounts for discoverablility. The entire corpus of flags and/or commands need not be memorized, but is at hand.

The downside of the listed approach can also be viewed as a downside. However, I don't think it is.