Skip to content

Commit

Permalink
Refactored new CLI code to be more easily extendable.
Browse files Browse the repository at this point in the history
  • Loading branch information
ryugi committed Jun 5, 2017
1 parent d259203 commit 87301cb
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 105 deletions.
53 changes: 26 additions & 27 deletions src/clj/timi/server/cli/commands/config.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,44 @@
[timi.server.util :as util]
[trifl.docs :as docs]))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Supporting Constants/Functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn show-config
[config]
(-> config
(pprint)
(with-out-str)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Tímı CLI API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(def options
;; Note that any options added here need to be named differently than those
;; in timi.server.cli.core/options.
[])

(defn validate-subcommand
[subcommand]
(log/info "Validating subcommand ...")
(log/trace "Command:" subcommand)
(#{:help :show} subcommand))

(defn help
"This function generates the output for the `help` options and/or commands."
[]
(docs/get-docstring 'timi.server.cli.commands.config 'run))

(defn show-config
[config]
(-> config
(pprint)
(with-out-str)))

(defn handle-unknown-subcommand
[subcommand]
(let [msg (format "The subcommand '%s' is not supported."
(name subcommand))]
(log/error msg)
(format "\nERROR: %s\n\n%s" msg (help))))

(defn dispatch
[config {:keys [options arguments errors data subcommands]}]
(let [subcommand (or (first subcommands) :show)]
"Dispatch on the config subcommands."
[config valid-subcommands
{:keys [options arguments errors data subcommands]}]
(log/trace "Valid subcommands:" valid-subcommands)
(let [subcommand (parser/get-default-subcommand valid-subcommands
(first subcommands)
:show)]
(log/infof "Running '%s' subcommand ..." subcommand)
(log/debug "dispatch subcommands:" subcommands)
(case (validate-subcommand subcommand)
(case subcommand
:help (help)
:show (show-config config)
(handle-unknown-subcommand subcommand))))
(parser/handle-unknown-subcommand subcommand help))))

(defn run
"
Expand All @@ -55,7 +55,6 @@
```
help Display this help text
show Display the complete current configuration values
```
"
[config parsed]
(dispatch config parsed))
```"
[config valid-subcommands parsed]
(dispatch config (keys valid-subcommands) parsed))
57 changes: 27 additions & 30 deletions src/clj/timi/server/cli/commands/db.clj
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,9 @@
(clojure.lang PersistentHashMap)
(java.lang Object String)))

(def options
;; Note that any options added here need to be named differently than those
;; in timi.server.cli.core/options.
[])

(defn validate-subcommand
[subcommand]
(log/info "Validating subcommand ...")
(log/trace "Command:" subcommand)
(#{:help :init :dump} subcommand))

(defn help
"This function generates the output for the `help` options and/or commands."
[]
(docs/get-docstring 'timi.server.cli.commands.db 'run))

(defn handle-unknown-subcommand
[subcommand]
(let [msg (format "The subcommand '%s' is not supported."
(name subcommand))]
(log/error msg)
(format "\nERROR: %s\n\n%s" msg (help))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Supporting Constants/Functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; XXX this will be generalized later (and moved) when different db backends
;; are supported
Expand Down Expand Up @@ -87,18 +68,35 @@
(init-db (get-connection-data config))
(init-db (get-connection-data filename)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Tímı CLI API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(def options
;; Note that any options added here need to be named differently than those
;; in timi.server.cli.core/options.
[])

(defn help
"This function generates the output for the `help` options and/or commands."
[]
(docs/get-docstring 'timi.server.cli.commands.db 'run))

(defn dispatch
[config {:keys [options arguments errors data subcommands]}]
(let [subcommand (or (first subcommands) :help)]
"Dispatch on the db subcommands."
[config valid-subcommands
{:keys [options arguments errors data subcommands]}]
(let [subcommand (parser/get-default-subcommand valid-subcommands
(first subcommands))]
(log/infof "Running '%s' subcommand ..." subcommand)
(log/trace "Using config:" config)
(log/debug "dispatch subcommands:" subcommands)
(log/debug "dispatch arguments:" arguments)
(case (validate-subcommand subcommand)
(case subcommand
:help (help)
:dump :not-implemented
:init (init-db config (nth arguments 2 nil))
(handle-unknown-subcommand subcommand))))
(parser/handle-unknown-subcommand subcommand help))))

(defn run
"
Expand All @@ -111,7 +109,6 @@
init CONNECTION-STRING Initialize new app storage with the given
connection string (for sqlite this is just
a filename)
```
"
[config parsed]
(dispatch config parsed))
```"
[config valid-subcommands parsed]
(dispatch config (keys valid-subcommands) parsed))
69 changes: 33 additions & 36 deletions src/clj/timi/server/cli/commands/project.clj
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,14 @@
(clojure.lang PersistentHashMap)
(java.lang Object String)))

(def options
;; Note that any options added here need to be named differently than those
;; in timi.server.cli.core/options.
[])

(defn validate-subcommand
[subcommand]
(log/info "Validating subcommand ...")
(log/trace "Command:" subcommand)
(#{:help :list :create} subcommand))

(defn validate-billing-method
[method]
(log/info "Validating billing method ...")
(log/trace "method:" method)
(#{:fixed-price :hourly :overhead} method))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Supporting Constants/Functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn help
"This function generates the output for the `help` options and/or commands."
[]
(docs/get-docstring 'timi.server.cli.commands.project 'run))

(defn handle-unknown-subcommand
[subcommand]
(let [msg (format "The subcommand '%s' is not supported."
(name subcommand))]
(log/error msg)
(format "\nERROR: %s\n\n%s" msg (help))))
(def billing-methods
{:fixed-price true
:hourly true
:overhead true})

(defn list-projects
[config]
Expand All @@ -52,27 +32,45 @@
[config project-name billing-method]
(log/info "Creating new project %s with billing method %s ..."
project-name billing-method)
;; XXX use the validate-billing-method function above
;; XXX use validation for billing methods ...
;; (parser/validate-member billing-methods billing-method)
(let [cmd {:project-name project-name
:billing-method billing-method}
project-id (projects/new-project! cmd)]
(log/debug "Created project.")
(str ":project-id " project-id)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Tímı CLI API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(def options
;; Note that any options added here need to be named differently than those
;; in timi.server.cli.core/options.
[])

(defn help
"This function generates the output for the `help` options and/or commands."
[]
(docs/get-docstring 'timi.server.cli.commands.project 'run))

(defn dispatch
[config {:keys [options arguments errors data subcommands]}]
(let [subcommand (or (first subcommands) :help)]
"Dispatch on the project subcommands."
[config valid-subcommands
{:keys [options arguments errors data subcommands]}]
(let [subcommand (parser/get-default-subcommand valid-subcommands
(first subcommands))]
(log/infof "Running '%s' subcommand ..." subcommand)
(log/trace "Using config:" config)
(log/debug "dispatch subcommands:" subcommands)
(log/debug "dispatch arguments:" arguments)
(case (validate-subcommand subcommand)
(case subcommand
:help (help)
:list (list-projects config)
:create (create-project config
(nth arguments 2 nil)
(nth subcommands 2 :hourly))
(handle-unknown-subcommand subcommand))))
(parser/handle-unknown-subcommand subcommand help))))

(defn run
"
Expand All @@ -83,7 +81,6 @@
help Display this help text
list List all projects
create NAME [BILLING-METHOD] Create a new project to track
```
"
[config parsed]
(dispatch config parsed))
```"
[config valid-subcommands parsed]
(dispatch config (keys valid-subcommands) parsed))
47 changes: 39 additions & 8 deletions src/clj/timi/server/cli/core.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
(ns timi.server.cli.core
"This namespace is responsible for handling initial command line arguments to
the Tímı CLI and performing the top-level dispatching. Additional command
namespaces handle dispatching via allowed subcommands.
`cli.core` and all command namespaces (e.g., `cli.commands.config`,
`cli.commands.db`, etc.) follow some basic `clojure.tools.cli` conventions:
* Defining any valid options (e.g., `-h` and `--help`)
* Validating those options (via the `parser` namespace)
Additionally, `cli.core` makes calls that perform the initial parsing of the
command line arguments, which are expected in the format of a string, just as
typed in a terminal.
The Tímı CLI establishes the following additional conventions for all CLI
namespaces:
* Every namespace has a `run` function that acts as the entry point for the
CLI command in question, even if all the run function does is hand off to
the dispatch function. The docstring for the `run` function is also the CLI
documentation that is displayed when a `help` option or subcommand is
passed by the user.
* Every namespace defines a `dispatch` function that checks for the parsed
command or subcommand and calls the appropriate associated function.
* Every namespace has a `help` function that parses the docstring in the
`run` function; this is what is invoked when a `help` CLI option or
subcommand is parsed."
(:require
[clojure.string :as string]
[clojure.tools.cli :as cli]
Expand Down Expand Up @@ -32,9 +59,11 @@
:init true}
:project {
:help true
:list true
:create true}
:task {
:help true
:list true
:create true}})

(defn help
Expand All @@ -43,6 +72,7 @@
(docs/get-docstring 'timi.server.cli.core 'run))

(defn dispatch
"Dispatch on the top-level CLI commands."
[config {:keys [options arguments errors data command] :as parsed}]
(log/trace "dispatch keys:" parsed)
(log/trace "options:" options)
Expand All @@ -66,17 +96,18 @@
data))
(case command
:help (help)
:config (config-cmd/run config parsed)
:db (db-cmd/run config parsed)
:project (project-cmd/run config parsed)
:config (config-cmd/run config (:config valid-commands) parsed)
:db (db-cmd/run config (:db valid-commands) parsed)
:project (project-cmd/run config (:project valid-commands) parsed)
:task "not implemented")))

;; Note that the option summary and commands are "hard-documented" here due
;; to the fact that we want to have the Codox-generated documentation match
;; what is sent to stdout (when using the CLI client). For this reason, we
;; offer the `--summary` option, for easily copying-and-pasting an update
;; into the docstring. The "Commands" section, on the other hand, does
;; require manual curation.
;; offer the `--summary` option, for easily copying-and-pasting an updated
;; summary into the docstring. The "Commands" section, on the other hand,
;; does require manual curation, as the Clojure cli library has no mechanism
;; for generating this.
(defn run
"
Usage: `timi [options] command [subcommands [options]]`
Expand Down Expand Up @@ -105,10 +136,10 @@
or, e.g.:
```
$ timi db help
```
"
```"
[config msg]
(-> msg
(string/trim)
(string/split #"\s")
(cli/parse-opts options :in-order true)
(parser/validate
Expand Down
Loading

0 comments on commit 87301cb

Please sign in to comment.