Skip to content

Instantly share code, notes, and snippets.

@JohnnyJayJay
Last active June 29, 2020 12:55
Show Gist options
  • Select an option

  • Save JohnnyJayJay/3a367973b289dcc9ba00417812f72b71 to your computer and use it in GitHub Desktop.

Select an option

Save JohnnyJayJay/3a367973b289dcc9ba00417812f72b71 to your computer and use it in GitHub Desktop.
A simple and generic command handler for clojure apps using core.match pattern matching.
(ns commands.core
(:require [clojure.string :refer [split]]
[clojure.core.match :refer [match]]))
(defmacro defcommand
"Defines a command function that takes a context argument and any number of additional arguments.
If the `name` symbol does not have explicit label metadata attached to it, the name of the symbol will be used as the label.
The `context-binding` can be any valid `let` binding form. It will be bound to the context argument at execution time.
The `patterns` are a variable number of [[clojure.core.match/match]] matching forms that represent the different arguments that
can be passed to this command."
[name context-binding & patterns]
`(defn ~(if (contains? (meta name) :label)
name
(vary-meta name assoc :label (clojure.core/name name)))
[~context-binding & args#]
(match (vec args#) ~@patterns)))
(defn commands
"Creates a map of label -> command based on the inputted command function vars."
[& commands]
(zipmap (map :label (map meta commands)) commands))
(defn dispatch
"Finds and dispatches the correct command based on the given command map and input.
Throws an [[IllegalArgumentException]] if no matching command could be found."
[commands context input]
(let [[label & args] (split input #"\s+")]
(if-let [command (get commands label)]
(-> command var-get (apply context args))
(throw (IllegalArgumentException. (str "No matching command found for label " label))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment