Skip to content

Commit

Permalink
Use explicit errors on all collection commands and add error command …
Browse files Browse the repository at this point in the history
…to generate an error

Fix yetibot/yetibot#766
  • Loading branch information
devth committed Oct 28, 2018
1 parent 395072d commit 447e9bc
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 89 deletions.
3 changes: 2 additions & 1 deletion doc/CATEGORIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ for the most up-to-date reference. Pasted here for reference:
:repl "language REPLs"
:util "utilities that help transform expressions or operate Yetibot"
:crude "may return crude, racy and potentially NSFW results (e.g. urban)"
:collection "operates on collections"
:broken "known to be broken, probably due to an API that disappeared"}
```

## Channel-based category toggle

Each category can be disabled or enabled at the channel level. By default all
categories are enabled. To disable them, use `!disable :category-name`.
categories are enabled. To disable them, use `!disable :category-name`.

> n.b. disabled categories are stored using the normal channel settings, so
> you'll see them in `!room` if you set them. `!category` is merely a
Expand Down
1 change: 1 addition & 0 deletions src/yetibot/core/commands/category.clj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
:repl "language REPLs"
:util "utilities that help transform expressions or operate Yetibot"
:crude "may return crude, racy and potentially NSFW results (e.g. urban)"
:collection "operates on collections"
:broken "known to be broken, probably due to an API that disappeared"
:async "commands that execute asynchronously"}))

Expand Down
163 changes: 94 additions & 69 deletions src/yetibot/core/commands/collections.clj
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,36 @@
[yetibot.core.util.format :refer [format-exception-log]]
[yetibot.core.util.command-info :refer [command-execution-info]]
[yetibot.core.models.users :refer [min-user-keys]]
[yetibot.core.util.command :refer [error?]]
[yetibot.core.util :refer
[psuedo-format split-kvs-with ensure-items-seqential
ensure-items-collection]]))

(defn ensure-coll
"Return nil if opts was set or return an error map otherwise"
[{:keys [opts args]}]
(or
(ensure-items-collection opts)
{:result/error
(str "Expected a collection but you gave me `" args "`")}))

(defn coll-cmd
"Helper to define commands that operate only on collections"
[f]
(with-meta
(fn [cmd-args]
(let [coll-or-error (ensure-coll cmd-args)]
(if (error? coll-or-error)
coll-or-error
(let [result (f coll-or-error)]
{:result/value result
:result/data result}))))
{:yb/cat #{:util :collection}}))

; random
(defn random
"random <list> # returns a random item where <list> is a comma-separated list of items.
Can also be used to extract a random item when a collection is piped to random."
"random <list> # returns a random item from <list>
random # generate a random number"
{:yb/cat #{:util}}
[{items :opts}]
(if (not (empty? items))
Expand All @@ -29,11 +51,9 @@
_ random)

; shuffle
(defn shuffle-cmd
(def shuffle-cmd
"shuffle <list>"
{:yb/cat #{:util}}
[{items :opts}]
(shuffle (ensure-items-collection items)))
(coll-cmd shuffle))

(cmd-hook #"shuffle"
_ shuffle-cmd)
Expand All @@ -42,9 +62,12 @@

; head / tail helpers
(defn head-or-tail
[single-fn multi-fn n items]
(let [f (if (= 1 n) single-fn (partial multi-fn n))]
(f (ensure-items-collection items))))
[single-fn multi-fn n cmd-map]
(let [coll-or-error (ensure-coll cmd-map)
f (if (= 1 n) single-fn (partial multi-fn n))]
(if (error? coll-or-error)
coll-or-error
(f coll-or-error))))

(def head (partial head-or-tail first take))

Expand All @@ -54,14 +77,14 @@
(defn head-1
"head <list> # returns the first item from the <list>"
{:yb/cat #{:util}}
[{items :opts}]
(head 1 items))
[cmd-args]
(head 1 cmd-args))

(defn head-n
"head <n> <list> # return the first <n> items from the <list>"
{:yb/cat #{:util}}
[{[_ n] :match items :opts}]
(head (read-string n) items))
[{[_ n] :match :as cmd-args}]
(head (read-string n) cmd-args))

(cmd-hook #"head"
#"(\d+)" head-n
Expand All @@ -74,36 +97,33 @@
(defn tail-1
"tail <list> # returns the last item from the <list>"
{:yb/cat #{:util}}
[{items :opts}] (tail 1 items))
[cmd-args] (tail 1 cmd-args))

(defn tail-n
"tail <n> <list> # returns the last <n> items from the <list>"
{:yb/cat #{:util}}
[{[_ n] :match items :opts}]
(tail (read-string n) items))
[{[_ n] :match :as cmd-args}]
(tail (read-string n) cmd-args))

(cmd-hook #"tail"
#"(\d+)" tail-n
_ tail-1)

; droplast
(defn drop-last-cmd
(def drop-last-cmd
"droplast <list> # drop the last item from <list>"
{:yb/cat #{:util}}
[{items :opts}]
(drop-last (ensure-items-collection items)))
(coll-cmd drop-last))

(cmd-hook ["droplast" #"^droplast$"]
_ drop-last-cmd)
_ drop-last-cmd)

; rest
(defn rest-cmd
(def rest-cmd
"rest <list> # returns the last item from the <list>"
{:yb/cat #{:util}}
[{items :opts}] (rest items))
(coll-cmd rest))

(cmd-hook #"rest"
_ rest-cmd)
_ rest-cmd)

; xargs
; example usage: !users | xargs attack
Expand Down Expand Up @@ -132,7 +152,9 @@
(error "Exception in xargs cmd-runner:" cmd-runner
(format-exception-log ex))
ex)))
itms))))
itms)
{:result/error
(str "Expected a collection")})))

(cmd-hook #"xargs"
_ xargs)
Expand All @@ -141,10 +163,12 @@
(defn join
"join <list> <separator> # joins list with optional <separator> or no separator if not specified. See also `unwords`."
{:yb/cat #{:util}}
[{match :match items :opts}]
(info (str "join with:'" match "'."))
(let [join-char (if (empty? match) "" match)]
(s/join join-char (ensure-items-collection items))))
[{match :match items :opts :as cmd-args}]
(let [coll-or-error (ensure-coll cmd-args)
join-char (if (empty? match) "" match)]
(if (error? coll-or-error)
coll-or-error
(s/join join-char coll-or-error))))

(cmd-hook #"join"
#"(?is).+" join
Expand Down Expand Up @@ -197,11 +221,14 @@
(defn flatten-cmd
"flatten <nested list> # completely flattens a nested data struture after splitting on newlines"
{:yb/cat #{:util}}
[{args :args opts :opts}]
(->> (ensure-items-collection opts)
flatten
(map s/split-lines)
flatten))
[{args :args opts :opts :as cmd-args}]
(let [error-or-coll (ensure-coll cmd-args)]
(if (error? error-or-coll)
error-or-coll
(->> error-or-coll
flatten
(map s/split-lines)
flatten))))

; letters
(defn letters
Expand All @@ -214,29 +241,25 @@
_ letters)

; unletters
(defn unletters
(def unletters
"unletters <list> # join <list> without a delimiter"
{:yb/cat #{:util}}
[{opts :opts}]
(s/join "" (ensure-items-collection opts)))
(coll-cmd (partial s/join "")))

(cmd-hook ["unletters" #"^unletters$"]
_ unletters)

; set
(defn set-cmd
(def set-cmd
"set <list> # returns the set of distinct elements in <list>"
{:yb/cat #{:util}}
[{items :opts}]
(set (ensure-items-collection items)))
(coll-cmd set))

(cmd-hook #"set"
_ set-cmd)

; list
(defn list-cmd
"list <comma-or-space-delimited-items> # construct a list"
{:yb/cat #{:util}}
{:yb/cat #{:util :collection}}
[{:keys [args]}]
(let [delimiter (if (re-find #"," args) #"," #"\s")]
(map s/trim (s/split args delimiter))))
Expand All @@ -246,21 +269,20 @@


; count
(defn count-cmd
(def count-cmd
"count <list> # count the number of items in <list>"
{:yb/cat #{:util}}
[{items :opts}]
(str (count (ensure-items-collection items))))
(coll-cmd (comp str count)))

(cmd-hook #"count"
_ count-cmd)

; sum
(defn sum-cmd
(def sum-cmd
"sum <list> # sum the items in <list>"
{:yb/cat #{:util}}
[{items :opts}]
(reduce + (map (comp read-string str) (ensure-items-collection items))))
(coll-cmd
#(->> %
(map (comp read-string str))
(reduce +))))

(cmd-hook #"sum"
_ sum-cmd)
Expand All @@ -276,11 +298,10 @@
_ sort-cmd)

; sortnum
(defn sortnum-cmd
(def sortnum-cmd
"sortnum <list> # numerically sort a list"
{:yb/cat #{:util}}
[{items :opts}]
(sort #(- (read-string %1) (read-string %2)) (ensure-items-collection items)))
(coll-cmd
(partial sort #(- (read-string %1) (read-string %2)))))

(cmd-hook #"sortnum"
_ sortnum-cmd)
Expand Down Expand Up @@ -324,15 +345,18 @@
(let [[n p] (if (sequential? match) (rest match) ["0" args])
pattern (re-pattern (str "(?i)" p))
items (-> opts ensure-items-collection ensure-items-seqential)]
(grep-data-structure pattern items {:context (read-string n)})))
(if items
(grep-data-structure pattern items {:context (read-string n)})
{:result/error
(str "Expected a collection but you only gave me `" args "`")})))

(cmd-hook #"grep"
#"-C\s+(\d+)\s+(.+)" grep-cmd
_ grep-cmd)

; tee
(defn tee-cmd
"tee <list> # output <list> to chat and return list (useful for pipes)"
"tee <list-or-args> # output <list-or-args> to chat then return it (useful for pipes)"
{:yb/cat #{:util}}
[{:keys [opts args]}]
(chat-data-structure (or opts args))
Expand All @@ -342,11 +366,9 @@
_ tee-cmd)

; reverse
(defn reverse-cmd
(def reverse-cmd
"reverse <list> # reverse the ordering of <list>"
{:yb/cat #{:util}}
[{items :opts}]
(reverse (ensure-items-collection items)))
(coll-cmd reverse))

(cmd-hook #"reverse"
_ reverse-cmd)
Expand All @@ -363,10 +385,12 @@
range 0 6 2 => 0 2 4
Results are returned as collections."
{:yb/cat #{:util}}
{:yb/cat #{:util :collection}}
[{:keys [match]}]
(let [range-args (map read-string (rest match))]
(apply range range-args)))
(->> range-args
(apply range)
(map str))))

(cmd-hook #"range"
#"(\d+)\s+(\d+)\s+(\d+)" range-cmd
Expand All @@ -376,7 +400,7 @@
; keys
(defn keys-cmd
"keys <map> # return the keys from <map>"
{:yb/cat #{:util}}
{:yb/cat #{:util :collection}}
[{items :opts}]
(timbre/debug (timbre/color-str :blue "keys")
(timbre/color-str :green (pr-str items)))
Expand All @@ -390,7 +414,7 @@
; vals
(defn vals-cmd
"vals <map> # return the vals from <map>"
{:yb/cat #{:util}}
{:yb/cat #{:util :collection}}
[{items :opts}]
(if (map? items)
(vals items)
Expand All @@ -402,13 +426,13 @@
;; raw
(defn raw-cmd
"raw <coll> | <args> # output a string representation of piped <coll> or <args>"
{:yb/cat #{:util}}
{:yb/cat #{:util :collection}}
[{:keys [opts args]}]
(pr-str (or opts args)))

(defn raw-all-cmd
"raw all <coll> | <args> # output a string representation of all command context"
{:yb/cat #{:util}}
{:yb/cat #{:util :collection}}
[{:keys [user] :as command-args}]
(let [minimal-user (select-keys user min-user-keys)
cleaned-args (merge command-args {:user minimal-user})]
Expand Down Expand Up @@ -438,7 +462,8 @@
[{:keys [data] :as opts}]
(info "show-data-cmd" (pr-str opts))
(if data
(with-out-str (pprint data))
(binding [*print-right-margin* 80]
(with-out-str (pprint data)))
"There is no `data` from the previous command 🤔"))

(defn data-cmd
Expand Down
3 changes: 1 addition & 2 deletions src/yetibot/core/commands/echo.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"echo <text> # Echos back <text>. Useful for piping."
{:yb/cat #{:util}}
[{:keys [args] :as cmd-args}]
(info "echo cmd args:" cmd-args)
args)

(cmd-hook #"echo"
_ echo-cmd)
_ echo-cmd)

0 comments on commit 447e9bc

Please sign in to comment.