<commit_before>(ns yetibot.core.commands.collections
  (:require
    [yetibot.core.interpreter :refer [handle-cmd]]
    [taoensso.timbre :refer [info warn error]]
    [clojure.string :as s]
    [yetibot.core.hooks :refer [cmd-hook]]
    [yetibot.core.chat :refer [chat-data-structure]]
    [yetibot.core.util.format :refer [format-exception-log]]
    [yetibot.core.util :refer [psuedo-format split-kvs-with ensure-items-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."
  [{items :opts}]
  (if (not (empty? items))
    (rand-nth (ensure-items-collection items))
    (str (rand 100000))))

(cmd-hook #"random"
          _ random)

(def head-tail-regex #"(\d+).+")

; 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))))

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

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

; head
(defn head-1
  "head <list> # returns the first item from the <list>"
  [{items :opts}]
  (head 1 items))

(defn head-n
  "head <n> <list> # return the first <n> items from the <list>"
  [{[_ n] :match items :opts}]
  (head (read-string n) items))

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

(cmd-hook #"take"
          #"(\d+)" head-n)

; tail
(defn tail-1
  "tail <list> # returns the last item from the <list>"
  [{items :opts}] (tail 1 items))

(defn tail-n
  "tail <n> <list> # returns the last <n> items from the <list>"
  [{[_ n] :match items :opts}]
  (tail (read-string n) items))

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

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

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

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

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

; xargs
; example usage: !users | xargs attack
(defn xargs
  "xargs <cmd> <list> # run <cmd> for every item in <list>; behavior is similar to xargs(1)'s xargs -n1"
  [{:keys [args opts]}]
  (if (s/blank? args)
    opts ; passthrough if no args
    (let [itms (ensure-items-collection opts)]
      (pmap
        (fn [item]
          (try
            (apply handle-cmd
                   ; item could be a collection, such as when xargs is used
                   ; on nested collections, e.g.:
                   ; repeat 5 jargon | xargs words | xargs head
                   (if (coll? item)
                     [args {:opts item}]
                     [(psuedo-format args item) {}]))
            (catch Exception ex
              (error "Exception in xargs pmap"
                     (format-exception-log ex))
              ex)))
        itms))))

(cmd-hook #"xargs"
          _ xargs)

; join
(defn join
  "join <list> # joins list with a single space or whatever character is given"
  [{match :match items :opts}]
  (let [join-char (if (= match :empty) " " match)]
    (s/join join-char (ensure-items-collection items))))

(cmd-hook #"join"
          #"(?is).+" join
          _ join)

; split
(defn split
  "split <pattern> <string> # split string with <pattern>"
  [{[_ split-by-str to-split] :match}]
  (let [split-by (re-pattern split-by-str)]
    (s/split to-split split-by)))

(cmd-hook #"split"
          #"(?is)^(\S+)\s+(.+)$" split)

; trim
(defn trim
  "trim <string> # remove whitespace from both ends of <string>"
  [{args :args}]
  (s/trim args))

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

; words
(defn words
  "words <string> # split <string> by spaces into a list"<commit_msg>Restore spaces in join; fix devth/yetibot#359<commit_after>(ns yetibot.core.commands.collections
  (:require
    [yetibot.core.interpreter :refer [handle-cmd]]
    [taoensso.timbre :refer [info warn error]]
    [clojure.string :as s]
    [yetibot.core.hooks :refer [cmd-hook]]
    [yetibot.core.chat :refer [chat-data-structure]]
    [yetibot.core.util.format :refer [format-exception-log]]
    [yetibot.core.util :refer [psuedo-format split-kvs-with ensure-items-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."
  [{items :opts}]
  (if (not (empty? items))
    (rand-nth (ensure-items-collection items))
    (str (rand 100000))))

(cmd-hook #"random"
          _ random)

(def head-tail-regex #"(\d+).+")

; 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))))

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

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

; head
(defn head-1
  "head <list> # returns the first item from the <list>"
  [{items :opts}]
  (head 1 items))

(defn head-n
  "head <n> <list> # return the first <n> items from the <list>"
  [{[_ n] :match items :opts}]
  (head (read-string n) items))

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

(cmd-hook #"take"
          #"(\d+)" head-n)

; tail
(defn tail-1
  "tail <list> # returns the last item from the <list>"
  [{items :opts}] (tail 1 items))

(defn tail-n
  "tail <n> <list> # returns the last <n> items from the <list>"
  [{[_ n] :match items :opts}]
  (tail (read-string n) items))

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

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

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

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

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

; xargs
; example usage: !users | xargs attack
(defn xargs
  "xargs <cmd> <list> # run <cmd> for every item in <list>; behavior is similar to xargs(1)'s xargs -n1"
  [{:keys [args opts]}]
  (if (s/blank? args)
    opts ; passthrough if no args
    (let [itms (ensure-items-collection opts)]
      (pmap
        (fn [item]
          (try
            (apply handle-cmd
                   ; item could be a collection, such as when xargs is used
                   ; on nested collections, e.g.:
                   ; repeat 5 jargon | xargs words | xargs head
                   (if (coll? item)
                     [args {:opts item}]
                     [(psuedo-format args item) {}]))
            (catch Exception ex
              (error "Exception in xargs pmap"
                     (format-exception-log ex))
              ex)))
        itms))))

(cmd-hook #"xargs"
          _ xargs)

; join
(defn join
  "join <list> # joins list with a single space or whatever character is given"
  [{match :match items :opts}]
  (let [join-char (if (empty? match) " " match)]
    (s/join join-char (ensure-items-collection items))))

(cmd-hook #"join"
          #"(?is).+" join
          _ join)

; split
(defn split
  "split <pattern> <string> # split string with <pattern>"
  [{[_ split-by-str to-split] :match}]
  (let [split-by (re-pattern split-by-str)]
    (s/split to-split split-by)))

(cmd-hook #"split"
          #"(?is)^(\S+)\s+(.+)$" split)

; trim
(defn trim
  "trim <string> # remove whitespace from both ends of <string>"
  [{args :args}]
  (s/trim args))

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

; words
(defn words
  "words <string> # split <string> by spaces into a list"
{'fraction_non_alphanumeric': 0.1581262939958592, 'fraction_numerical': 0.004528985507246377, 'mean_word_length': 2.5260036496350367, 'pattern_counts': {'":': 0, '<': 63, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '812019', 'n_tokens_mistral': 2755, 'n_tokens_neox': 2688, 'n_words': 852}