std.task
process and bulk task execution
std.task is a higher-level standard library family in foundation-base. This page explains when to use it, how it fits internally, and where to find the API surface.
1 Motivation
Use this layer when application or tooling code needs the behavior described by the page title without reaching directly into implementation namespaces. The top-level namespace is the starting point; subnamespaces expose more focused building blocks.
2 How to use it
Require the top-level namespace for common workflows, then move to subnamespaces when you need a lower-level primitive. Existing tests under test/std/task and test/std/task_test.clj are the best executable examples for edge cases.
create a task
(task? (task :namespace "list-interns" ns-interns))
=> true
define a task with deftask
(macroexpand-1
'(deftask -list-aliases-
{:template :namespace
:main clojure.core/ns-aliases
:item {:post (comp vec sort keys)}
:doc "returns all aliases"}))
=> '(def -list-aliases-
(std.task/task
:namespace "-list-aliases-"
{:template :namespace,
:main clojure.core/ns-aliases,
:item {:post (comp vec sort keys)},
:doc "returns all aliases"}))
parse CLI args
(process-ns-args [":only" "foo"])
=> {:ns 'foo}
(process-ns-args [":verbose" ":timeout" "100"])
=> {:verbose true :timeout 100}
3 Internal usage
This library family is used across source, tests, generated examples, and docs tooling. During detailed documentation passes, collect concrete usage with code.manage/find-usages and code.manage/locate-code, then keep only high-signal examples in the page narrative.
4 API
- ->Task
- Task
- collapse-only
- deftask
- invoke-intern-task
- keyword-cli-arg?
- map->Task
- parse-cli-value
- process-ns-args
- read-cli-arg
- single-function-print
- task
- task-defaults
- task-info
- task-status
- task-string
- task?
NONE
(impl/defimpl Task [type name main construct arglists item result summary]
:invoke process/invoke
:string task-string
:final true)
link
Task ^
NONE
(impl/defimpl Task [type name main construct arglists item result summary]
:invoke process/invoke
:string task-string
:final true)
link
v 4.1
(defn collapse-only
[args]
(loop [result []
[k & more] args]
(if-not k
result
(if (= :only (read-cli-arg k))
(let [vals (take-while (complement keyword-cli-arg?) more)]
(if (seq vals)
(recur (conj result k (pr-str (mapv read-cli-arg vals)))
(drop (count vals) more))
(recur (conj result k) more)))
(recur (conj result k) more)))))
link
(collapse-only [":only" "foo"]) => [":only" "[foo]"] (collapse-only [":only" "foo" "bar" "baz"]) => [":only" "[foo bar baz]"] (collapse-only [":only" "foo" "bar" ":timeout" "100"]) => [":only" "[foo bar]" ":timeout" "100"] (collapse-only [":only"]) => [":only"] (collapse-only [":only" ":verbose"]) => [":only" ":verbose"] (process-ns-args (collapse-only [":only" "foo"])) => {:ns '[foo]} (process-ns-args (collapse-only [":only" "foo" "bar" "baz"])) => {:ns '[foo bar baz]} (process-ns-args (collapse-only [":only" "foo" "bar" ":timeout" "100"])) => {:ns '[foo bar] :timeout 100}
v 3.0
(defmacro deftask
([name config & body]
(invoke-intern-task :task name config body)))
link
(macroexpand-1 '(deftask -list-aliases- {:template :namespace :main clojure.core/ns-aliases :item {:post (comp vec sort keys)} :doc "returns all aliases"})) => '(def -list-aliases- (std.task/task :namespace "-list-aliases-" {:template :namespace, :main clojure.core/ns-aliases, :item {:post (comp vec sort keys)}, :doc "returns all aliases"}))
v 3.0
(invoke/definvoke invoke-intern-task
[:method {:multi protocol.invoke/-invoke-intern
:val :task}]
([name config]
(invoke-intern-task :task name config nil))
([_ name config _]
(let [template (:template config)
body `(task ~template ~(str name) ~config)
arglists (or (:arglists config)
(-> (task-defaults template) :arglists))
name (with-meta name
(merge (meta name)
(cond-> config
arglists (assoc :arglists (list 'quote arglists)))))]
(list 'def name body))))
link
(invoke-intern-task '-task- '{:template :namespace :main {:fn clojure.core/ns-aliases}}) => '(def -task- (std.task/task :namespace "-task-" {:template :namespace, :main {:fn clojure.core/ns-aliases}}))
v 4.1
(defn keyword-cli-arg?
[s]
(keyword? (read-cli-arg s)))
link
(keyword-cli-arg? ":foo") => true (keyword-cli-arg? ":foo/bar") => true (keyword-cli-arg? "foo") => false (keyword-cli-arg? "1") => false (keyword-cli-arg? "[1 2]") => false
v 3.0
(impl/defimpl Task [type name main construct arglists item result summary]
:invoke process/invoke
:string task-string
:final true)
link
(-> (map->Task {:type :namespace :name "list-interns" :main {:fn clojure.core/ns-interns}}) (task?)) => true
NONE
(defn- parse-cli-value
[k raw]
(if (= k :files)
(let [raw (str/trim raw)]
(cond
(empty? raw)
[]
(re-find #"^(?:[[({]|#{|")" raw)
(try (read-string raw)
(catch Throwable _
raw))
(re-find #"s+" raw)
(vec (remove empty?
(str/split raw #"s+")))
:else
raw))
(try (read-string raw)
(catch Throwable _
raw))))
link
v 4.0
(defn process-ns-args
[args]
(loop [m {}
[k v :as args] args]
(if (not k)
m
(let [k (try (read-string k)
(catch Throwable _
k))
v (when v
(parse-cli-value k v))]
(cond (not (keyword? k))
(recur m (rest args))
(or (keyword? v)
(= 1 (count args)))
(recur (assoc m k true)
(rest args))
:else
(recur (case k
:only (assoc m :ns v)
:with (assoc m :ns [v])
(assoc m k v))
(rest (rest args))))))))
link
(process-ns-args [":only" "foo"]) => {:ns 'foo} (process-ns-args [":verbose"]) => {:verbose true} (process-ns-args [":verbose" ":other"]) => {:verbose true :other true} (process-ns-args [":timeout" "100"]) => {:timeout 100} (process-ns-args [":files" "test/code/project_test.clj test/std/task_test.clj"]) => {:files ["test/code/project_test.clj" "test/std/task_test.clj"]} (process-ns-args [":files" "["test/code/project_test.clj" "test/std/task_test.clj"]"]) => {:files ["test/code/project_test.clj" "test/std/task_test.clj"]}
v 4.1
(defn read-cli-arg
[s]
(try (read-string s)
(catch Throwable _
s)))
link
(read-cli-arg "1") => 1 (read-cli-arg ":foo") => :foo (read-cli-arg "[1 2 3]") => [1 2 3] (read-cli-arg "hello") => 'hello (read-cli-arg "hello world") => 'hello
v 3.0
(defn single-function-print
([params]
(if (and (not (:bulk params))
(-> params :print :function nil?))
(assoc-in params [:print :function] true)
params)))
link
(single-function-print {}) => {:print {:function true}}
v 3.0
(defn task
([m]
(map->Task m))
([type name arg]
(let [[params main] (if (map? arg)
[arg (-> arg :main :fn)]
[{} arg])
defaults (task-defaults type)
params (collection/merge-nested defaults params)
count (or (-> params :main :argcount) 4)
[main args?] (process/main-function main count)]
(task (collection/merge-nested defaults
params
{:main {:fn main
:args? args?}
:name name
:type type})))))
link
(task? (task :namespace "list-interns" ns-interns)) => true (task? (task :namespace "list-interns" {:main {:fn clojure.core/ns-interns}})) => true
v 3.0
(defmulti task-defaults
identity)
link
(task-defaults :default) => {:main {:arglists '([] [entry])}}
v 3.0
(defn task-info
([^Task task]
{:fn (symbol (.name task))}))
link
(-> (task :namespace "list-interns" ns-interns) (task-info)) => '{:fn list-interns}
v 3.0
(defn task-status
([^Task task]
(.type task)))
link
(-> (task :namespace "list-interns" ns-interns) (task-status)) => :namespace
NONE
(defn- task-string
([task]
(str "#task" (task-status task) " " (task-info task))))
link
v 4.0
(defn invoke
([task & args]
(let [idx (collection/index-at #{:args} args)
_ (if (and (neg? idx) (-> task :main :args?))
(throw (ex-info "Require `:args` keyword to specify additional arguments"
{:input args})))
[task-args func-args] (if (neg? idx)
[args []]
[(take idx args) (drop (inc idx) args)])
[input params lookup env] (apply task-inputs task task-args)
f (-> (-> task :main :fn)
(wrap-execute task)
(wrap-input task))
params (collection/merge-nested (:params task) params)
result (apply f input params lookup env func-args)]
(alter-var-root #'*interrupt*
(fn [_] false))
result)))
link
(keys (invoke (task/task :default "ns-interns" {:construct {:env (fn [_] {}) :lookup (fn [_ _] '{std.task []})} :main {:fn (fn [ns _ _ _] (ns-interns ns))}}) 'std.task.process-test)) => (contains '[process-test-fn] :in-any-order :gaps-ok)
v 4.0
(defn main-function
([func count]
(let [fcounts (fn/arg-count func)
fcount (if-not (empty? fcounts)
(apply min fcounts)
4)
args? (> fcount count)
main (cond (= count 4) (fn [input params lookup env & args]
(try
(apply func input params lookup env args)
(catch clojure.lang.ArityException e
;; Fallback for functions detected as 4-arity but actually 3-arity
(if (= (.-actual e) 4)
(apply func input params env args)
(throw e)))))
(= count 3) (fn [input params _ env & args]
(apply func input params env args))
(= count 2) (fn [input params _ _ & args]
(apply func input params args))
(= count 1) (fn [input _ _ _ & args]
(apply func input args))
:else (throw (ex-info "`count` is a value between 1 to 4" {:count count})))]
[main args?])))
link
(main-function ns-aliases 1) => (contains [fn/vargs? false])
v 4.0
(defn select-filter
[selector id]
(cond (or (fn? selector)
(var? selector))
(f/suppress (selector id))
(or (string? selector)
(symbol? selector)
(keyword? selector))
(.startsWith (str id) (str selector))
(f/regexp? selector)
(boolean (re-find selector (str id)))
(set? selector) (selector id)
(collection/form? selector) (every? #(select-filter % id)
selector)
(vector? selector) (some #(select-filter % id)
selector)
:else
(throw (ex-info "Selector not valid" {:selector selector}))))
link
(select-filter #"ello" 'hello) => true (select-filter #"^ello" 'hello) => false
v 4.0
(defn select-inputs
([task lookup env selector]
(let [list-fn (or (-> task :item :list)
(throw (ex-info "No `:list` function defined" {:key [:item :list]})))]
(cond (= selector :all)
(list-fn lookup env)
:else
(->> (list-fn lookup env)
(filter #(select-filter selector %)))))))
link
(select-inputs {:item {:list (fn [_ _] ['code.test 'spirit.common])}} {} {} ['code]) => ['code.test]
task-inputs ^
[task] [task input] [task input params] [task input params env] [task input params lookup env]
constructs inputs to the task given a set of parameters
v 4.0
(defn task-inputs
([task]
(let [input-fn (or (-> task :construct :input) (constantly nil))]
(task-inputs task (input-fn task) task)))
([task input]
(let [input-fn (or (-> task :construct :input) (constantly nil))
[input params] (cond (map? input)
[(input-fn task) input]
:else [input {}])]
(task-inputs task input params)))
([task input params]
(let [env-fn (or (-> task :construct :env) (constantly {}))]
(task-inputs task input params (env-fn (merge task params)))))
([task input params env]
(let [lookup-fn (or (-> task :construct :lookup) (constantly {}))]
(task-inputs task input params (lookup-fn task (merge env params)) env)))
([task input params lookup env]
[input params lookup env]))
link
(task-inputs (task/task :default "ns-interns" {:construct {:env (fn [_] {}) :lookup (fn [_ _] '{std.task [a b c]})} :main {:fn ns-interns}}) 'std.task) => '[std.task {} {std.task [a b c]} {}]
v 3.0
(defn wrap-execute
([f task]
(fn [input params lookup env & args]
(let [pre-fn (or (-> task :item :pre) identity)
post-fn (or (-> task :item :post) identity)
output-fn (or (-> task :item :output) identity)
input (pre-fn input)
result (apply f input params lookup env args)
result (post-fn result)]
(if (:bulk params)
[input (res/->result input result)]
(output-fn result))))))
link
((wrap-execute process-test-fn +task+) 1 {} {} {}) => 3
v 3.0
(defn wrap-input
([f task]
(fn [input params lookup env & args]
(cond (= :list input)
(let [list-fn (or (-> task :item :list)
(throw (ex-info "No `:list` function defined" {:key [:item :list]})))]
(list-fn lookup env))
(or (keyword? input)
(vector? input)
(set? input)
(collection/form? input))
(let [inputs (select-inputs task lookup env input)]
(apply bulk/bulk task f inputs params lookup env args))
:else
(apply f input params lookup env args)))))
link
((wrap-input process-test-fn +task+) 1 {} {} {}) => 2 (let [task (assoc-in +task+ [:item :list] (constantly [1 2 3])) f (wrap-execute process-test-fn task) res ((wrap-input f task) :all {} {} {})] (get res 2) => 3 (get res 3) => 5 (get res 4) => 7)
- bulk
- bulk-display
- bulk-errors
- bulk-items
- bulk-items-parallel
- bulk-items-single
- bulk-package
- bulk-process-item
- bulk-results
- bulk-summary
- bulk-warnings
- prepare-columns
bulk ^
[task f inputs {:keys [print package title return], :as params} lookup env & args]
process and output results for a group of inputs
v 3.0
(defn bulk
([task f inputs {:keys [print package title return] :as params} lookup env & args]
(let [params (assoc params :bulk true)
_ (when (and (or (:function print) (:item print) (:result print) (:summary print))
title)
(print/print-title (if (fn? title)
(title params env)
title)))
start (time/time-ms)
items (bulk-items task f inputs params lookup env args)
elapsed (time/elapsed-ms start)
warnings (bulk-warnings params items)
errors (bulk-errors params items)
results (bulk-results task params items)
summary (bulk-summary task params items results warnings errors elapsed)]
(bulk-package task
{:items items
:warnings warnings
:errors errors
:results results
:summary summary}
(or return :results)
package))))
link
(bulk +task+ bulk-test-fn [1 2 3] {:print {}} {} {} {}) => map?
v 3.0
(defn bulk-display
([index-len input-len]
{:padding 1
:spacing 1
:columns [{:id :index :length index-len
:color #{:blue}
:align :right}
{:id :input :length input-len}
{:id :data :length 60 :color #{:white}}
{:id :time :length 10 :color #{:bold}}]}))
link
(bulk-display 10 20) => map?
bulk-errors ^
[{:keys [print], :as params} items]
outputs errors that have been processed
v 3.0
(defn bulk-errors
([{:keys [print] :as params} items]
(let [errors (filter #(-> % second :status #{:critical :error}) items)]
(when (and (:result print) (seq errors))
(print/print "n")
(print/print-subtitle (format "ERRORS (%s)" (count errors)))
(print/print "n")
(print/print-column errors :data #{:error}))
errors)))
link
(bulk-errors {:print {}} [[1 {:status :error}]]) => [[1 {:status :error}]]
bulk-items ^
[task f inputs {:keys [print parallel random], :as params} lookup env args]
processes each item given a input
v 3.0
(defn bulk-items
([task f inputs {:keys [print parallel random] :as params} lookup env args]
(when (:item print)
(print/print "n")
(print/print-subtitle (format "ITEMS (%s)" (count inputs))))
(cond (empty? inputs) []
:else
(let [inputs (if random (shuffle inputs) inputs)
total (count inputs)
index-len (let [digits (if (pos? total)
(inc (long (Math/log10 total)))
1)]
(+ 2 (* 2 digits)))
input-len (->> inputs (map (comp count str)) (apply max) (+ 2))
display-fn (or (-> task :item :display) identity)
display (bulk-display index-len input-len)
context {:total total
:idxs (range total)
:display display
:display-fn display-fn}]
(if (:item print) (print/print "n"))
(if parallel
(bulk-items-parallel f inputs context params lookup env args)
(bulk-items-single f inputs context params lookup env args))))))
link
(let [results (bulk-items +task+ bulk-test-fn [1 2 3] {:print {}} {} {} {})] (and (= (count results) 3) (every? (fn [[k v]] (and (number? k) (= (:status v) :return))) results))) => true
bulk-items-parallel ^
[f inputs {:keys [idxs], :as context} params lookup env args]
bulk operation processing in parallel
v 3.0
(defn bulk-items-parallel
([f inputs {:keys [idxs] :as context} params lookup env args]
(->> (pmap (fn [idx input]
(let [output (volatile! nil)
out-str (env/with-out-str
(bulk-process-item f (assoc context
:idx idx
:input input
:output output)
params lookup env args))]
[@output out-str]))
idxs
inputs)
(mapv (fn [[output out-str]]
(print/print out-str)
output)))))
link
(let [results (bulk-items-parallel bulk-test-fn [1 2 3] {:idxs (range 3) :display-fn identity} {:print {}} {} {} {})] (and (= (count results) 3) (every? (fn [[k v]] (and (number? k) (= (:status v) :return))) results))) => true
bulk-items-single ^
[f inputs {:keys [idxs], :as context} params lookup env args]
bulk operation processing in single
v 3.0
(defn bulk-items-single
([f inputs {:keys [idxs] :as context} params lookup env args]
(mapv (fn [idx input]
(bulk-process-item f (assoc context
:idx idx
:input input)
params lookup env args))
idxs
inputs)))
link
(let [results (bulk-items-single bulk-test-fn [1 2 3] {:idxs (range 3) :display-fn identity} {:print {}} {} {} {})] (and (= (count results) 3) (every? (fn [[k v]] (and (number? k) (= (:status v) :return))) results))) => true
bulk-package ^
[task {:keys [items warnings errors results summary], :as bundle} return package]
packages results for return
v 3.0
(defn bulk-package
([task {:keys [items warnings errors results summary] :as bundle} return package]
(cond (= return :all)
(bulk-package task bundle #{:items :warnings :errors :results :summary} package)
(keyword? return)
(first (vals (bulk-package task bundle #{return} package)))
:else
(let [items-fn (or (-> task :item :output) identity)
results-fn (or (-> task :result :output) identity)]
(reduce (fn [out kw]
(cond (#{:summary :warnings :errors} kw)
(assoc out kw (get bundle kw))
(= :items kw)
(cond->> (get bundle kw)
:then (map (fn [[key v]] [key (items-fn (:data v))]))
(not= package :vector) (into {})
:then (assoc out :items))
(= :results kw)
(cond->> (get bundle kw)
:then (map (fn [v] [(:key v) (results-fn (:data v))]))
(not= package :vector) (into {})
:then (assoc out :results))
:else out))
{}
return)))))
link
(bulk-package +task+ {:items [[1 {:data 1}]] :results [{:key 1 :data 1}] :warnings [] :errors [] :summary {}} :all :map) => (contains {:items {1 1} :results {1 1}})
bulk-process-item ^
[f {:keys [idx total input output display display-fn]} {:keys [print], :as params} lookup env args]
bulk operation processing each item
v 3.0
(defn bulk-process-item
([f
{:keys [idx total input output display display-fn]}
{:keys [print] :as params} lookup env args]
(let [start (System/currentTimeMillis)
[key result] (try (apply f input params lookup env args)
(catch Exception e
(print/println ">>" (.getMessage e))
(let [end (System/currentTimeMillis)]
[input (res/result {:status :error
:time (- end start)
:data :errored})])))
end (System/currentTimeMillis)
result (assoc result :time (- end start))
{:keys [status data time]} result
_ (if (:item print)
(let [index (format "%s/%s" (inc idx) total)
item (if (= status :return)
(display-fn data)
result)
time (format "%.2fs" (/ time 1000.0))]
(print/print-row [index key item time] display)))
_ (if output (vreset! output [key result]))]
[key result])))
link
(let [[k v] (bulk-process-item bulk-test-fn {:idx 0 :total 1 :input 1 :output (volatile! nil) :display-fn identity} {:print {}} {} {} {})] (and (= k 1) (= (:status v) :return) (= (:data v) 2))) => true
bulk-results ^
[task {:keys [print order-by], :as params} items]
outputs results that have been processed
v 3.0
(defn bulk-results
([task {:keys [print order-by] :as params} items]
(let [ignore-fn (-> task :result :ignore)
remove-fn (fn [[key {:keys [data status] :as result}]]
(or (#{:error :warn :info :critical} status)
(and ignore-fn
(ignore-fn data))))
results (remove remove-fn items)
_ (when (:result print)
(print/print "n")
(print/print-subtitle (format "RESULTS (%s)" (count results))))]
(cond (empty? results)
[]
:else
(let [key-fns (-> task :result :keys)
format-fns (-> task :result :format)
sort-by-fn (-> task :result :sort-by)
outputs (mapv (fn [[key {:keys [id data] :as result}]]
(->> key-fns
(map (fn [[k f]] [k (f data)]))
(into result)))
results)
outputs (if order-by
(clojure.core/sort-by order-by outputs)
outputs)
columns (-> task :result :columns)
display {:padding 1
:spacing 1
:columns (prepare-columns columns outputs)}
row-keys (map :key columns)]
(when (:result print)
(print/print "n")
(print/print-header row-keys display)
(mapv (fn [output]
(let [row (mapv (fn [k]
(let [data (get output k)
format-fn (get format-fns k)]
(cond-> data format-fn (format-fn params))))
row-keys)]
(print/print-row row display)))
outputs))
outputs)))))
link
(bulk-results +task+ {:print {}} [[1 {:status :return :data 1}]]) => (every-pred not-empty (partial every? map?))
bulk-summary ^
[task {:keys [print], :as params} items results warnings errors elapsed]
prints deferred summary notices after the summary output
v 4.1
(defn bulk-summary
([task {:keys [print] :as params} items results warnings errors elapsed]
(let [aggregate-fns (-> task :summary :aggregate)
finalise-fn (-> task :summary :finalise)
cumulative (apply + (map (comp :time second) items))
summary (merge {:errors (count errors)
:warnings (count warnings)
:items (count items)
:results (count results)}
(->> aggregate-fns
(collection/map-vals (fn [[sel acc init]]
(reduce (fn [out v]
(let [sv (sel v)]
(if (nil? sv)
out
(acc out sv))))
init
results)))))
summary (if finalise-fn
(finalise-fn summary items results)
summary)
display (->> summary
(remove (comp #(and (number? %)
(zero? %))
second))
(into {}))
_ (when (:summary print)
(print/print "n")
(print/print-subtitle (format "SUMMARY %s"
(str (assoc display
:cumulative (time/format-ms cumulative)
:elapsed (time/format-ms elapsed)))))
(print/println))
_ (doseq [line (-> summary meta :std.task/after-summary)]
(print/println line))]
(assoc summary :cumulative cumulative :elapsed elapsed))))
link
(let [calls (atom []) task {:summary {:finalise (fn [& _] (with-meta {:items 1 :results 1} {:std.task/after-summary ["saved"]}))}}] (with-redefs [std.print/print (fn [& args] (swap! calls conj (vec (cons :print args)))) std.print/print-subtitle (fn [& args] (swap! calls conj (vec (cons :subtitle args)))) std.print/println (fn [& args] (swap! calls conj (vec (cons :println args))))] (bulk-summary task {:print {:summary true}} [[1 {:time 10 :status :return :data 1}]] [] [] [] 100) [(-> @calls last first) (-> @calls last second)])) => [:println "saved"]
bulk-warnings ^
[{:keys [print], :as params} items]
outputs warnings that have been processed
v 3.0
(defn bulk-warnings
([{:keys [print] :as params} items]
(let [warnings (filter #(-> % second :status (= :warn)) items)]
(when (and (:result print) (seq warnings))
(print/print "n")
(print/print-subtitle (format "WARNINGS (%s)" (count warnings)))
(print/print "n")
(print/print-column warnings :data #{:warn}))
warnings)))
link
(bulk-warnings {:print {}} [[1 {:status :warn}]]) => [[1 {:status :warn}]]
v 4.0
(defn prepare-columns
([columns outputs]
(mapv (fn [{:keys [length key] :as column}]
(let [id key
length (cond (number? length)
length
:else
(->> outputs (map key) (map (comp count str)) (apply max) (+ 2)))]
(assoc column :id key :length length)))
columns)))
link
(prepare-columns [{:key :name} {:key :data}] [{:name "Chris" :data "1"} {:name "Bob" :data "100"}]) => [{:key :name, :id :name, :length 7} {:key :data, :id :data, :length 5}]
5 std.task Guide
std.task defines a protocol and structure for executing operations, managing their inputs/outputs, and formatting their results. It is the engine behind code.manage commands.
5.1 Core Concepts
- Task: An invokable object with a defined lifecycle.n- Template: A set of defaults for a class of tasks (e.g.,
:namespace,:file).n- Process: The underlying execution logic (handling parallel/serial execution, argument parsing).
5.2 Usage
5.2.1 Defining a Task
(require '[std.task :as task])\n\n;; Simple task\n(task/deftask my-task\n {:template :default\n :main {:fn +}\n :doc \"Adds numbers\"})\n\n(my-task 1 2) ;; => 3
5.2.2 Scenarios
5.2.2.1 1. Defining a Custom Task Template
If you have a set of tasks that share common behavior (e.g., they all operate on database records), you can define a template.
;; Define defaults for :db-task\n(defmethod task/task-defaults :db-task\n [_]\n {:main {:arglists '([id] [id options])}\n :result {:columns [{:key :id :align :left}\n {:key :status :align :center}]}})\n\n;; Create a task using this template\n(task/deftask get-user\n {:template :db-task\n :main {:fn (fn [id] {:id id :status :active})}\n :doc \"Fetches a user\"})
5.2.2.2 2. Parallel Processing
Tasks can be configured to run in parallel, which is useful for operations over many items (like files or namespaces).
(task/deftask process-files\n {:template :default\n :params {:parallel true\n :title \"PROCESSING FILES\"}\n :main {:fn (fn [file] (slurp file))} ;; Simplified example\n })
When invoked with a collection, the task runner (via std.task.process) will utilize threads if :parallel is set.
5.2.2.3 3. Customizing Output/Result
You can control how the result is displayed, which is crucial for CLI tools.
(task/deftask analyze-data\n {:template :default\n :main {:fn (fn [x] {:score (rand-int 100) :name x})}\n\n ;; Define output columns\n :result {:keys {:count count} ;; Summary key\n :columns [{:key :name :length 20 :align :left}\n {:key :score :length 10 :align :right :color #{:bold}}]}})\n\n;; Invocation might look like:\n;; (analyze-data [\"A\" \"B\"])\n;; => Prints table with Name and Score
5.2.2.4 4. Item Processing Configuration
The :item key allows transformation of individual inputs or outputs.
(task/deftask list-sorted\n {:template :default\n :main {:fn identity}\n :item {:post (comp vec sort)} ;; Sort the output of the main function\n })
5.2.2.5 5. Handling Command Line Arguments
If you are building a CLI entry point (like code.manage/-main), process-ns-args helps parse arguments.
(defn -main [& args]\n (let [opts (task/process-ns-args args)]\n ;; opts will look like {:only \"my.ns\" :verbose true}\n ;; if called as: ... :only \"my.ns\" :verbose\n ...))
6 std.task: A Comprehensive Summary
The std.task module in foundation-base provides a framework for defining, executing, and managing tasks, particularly focusing on bulk operations and processing items in a structured manner. It offers utilities for handling task inputs, executing functions with transformations, and presenting results, warnings, errors, and summaries in a user-friendly format.
The module is organized into two main sub-namespaces:
6.1 std.task.bulk
This namespace provides the core logic for performing bulk operations, processing items, and presenting the results. It's designed to handle collections of inputs, execute a given function for each, and then aggregate and display the outcomes.
bulk-display [index-len input-len]: Constructs display options for bulk output, defining column widths, colors, and alignment for index, input, data, and time.nbulk-process-item [f context params lookup env args]: Processes a single item within a bulk operation. It executes the functionfwith the item's input, captures exceptions, measures execution time, and optionally prints item-specific output.nbulk-items-parallel [f inputs context params lookup env args]: Processes a collection of inputs in parallel usingpmap.nbulk-items-single [f inputs context params lookup env args]: Processes a collection of inputs sequentially.nbulk-items [task f inputs params lookup env args]: The main function for orchestrating item processing. It handles input shuffling (ifrandomis true), calculates display lengths, and dispatches to either parallel or single-threaded processing based onparallelparameter. It also prints a subtitle for the items section.nbulk-warnings [params items]: Filters processed items for warnings (status:warn) and optionally prints them in a formatted column.nbulk-errors [params items]: Filters processed items for errors (status:criticalor:error) and optionally prints them in a formatted column.nprepare-columns [columns outputs]: Prepares column definitions for printing, dynamically calculating column lengths based on output data if not explicitly provided.nbulk-results [task params items]: Filters and sorts processed items to present the final results. It applies ignore functions, formats output based on task definitions, and optionally prints results in a formatted table.nbulk-summary [task params items results warnings errors elapsed]: Generates and optionally prints a summary of the bulk operation, including counts of items, results, warnings, errors, and cumulative/elapsed times. It can also aggregate custom metrics defined in the task.nbulk-package [task bundle return package]: Packages the results of the bulk operation into a specified format (e.g.,:all,:results,:summary,:items) and structure (e.g.,:vector,:map).n*bulk [task f inputs params lookup env & args]: The top-level function for executing a bulk task. It orchestrates the entire process: printing titles, processing items, collecting warnings/errors/results, generating a summary, and packaging the final output.
6.2 std.task.process
This namespace provides utilities for constructing and invoking tasks, including handling function arity, input selection, and execution wrapping.
*interrupt*: A dynamic var (atom) used as a flag to signal task interruption.nmain-function [func count]: Creates a wrapper function that adapts a givenfuncto a standardized task execution signature (takinginput,params,lookup,env, andargs). It handles different arities of the original function.nselect-filter [selector id]: A flexible filtering mechanism that matches anidagainst variousselectortypes (functions, vars, strings, symbols, keywords, regex, sets, forms, vectors).nselect-inputs [task lookup env selector]: Selects inputs for a task based on aselector. Ifselectoris:all, it uses the task's:item :listfunction to get all possible inputs. Otherwise, it filters the list of inputs usingselect-filter.nwrap-execute [f task]: Wraps a task's execution functionfwith pre- and post-processing steps defined in the task's:itemconfiguration. It also handles converting results tostd.lib.resultformat for bulk operations.nwrap-input [f task]: Wraps a task's execution functionfto handle different input types. If the input is a selector (keyword, vector, set, form), it triggers a bulk operation usingbulk/bulk. If the input is:list, it returns the list of all possible inputs. Otherwise, it executesfwith the single input.ntask-inputs [task & args]: Constructs the complete set of inputs (input,params,lookup,env) for a task based on its configuration and provided arguments.n*invoke [task & args]: The primary function for executing a task. It prepares the task's inputs, wraps the main function with execution and input handling logic, and then executes it. It also manages the*interrupt*flag.
Overall Importance:
The std.task module is crucial for building automated workflows, command-line tools, and data processing pipelines within the foundation-base project. It provides:
- Structured Task Definition: Allows tasks to be defined with clear configurations for input listing, execution logic, pre/post-processing, and result presentation.n Efficient Bulk Operations: Facilitates processing large collections of data or performing repetitive actions, with options for parallel execution and detailed output.n User-Friendly Output: Generates formatted tables for results, warnings, and errors, along with comprehensive summaries, enhancing the user experience for CLI tools.n Extensibility: The modular design with configurable functions and wrappers allows for easy customization and extension of task behavior.n Robust Error Handling: Integrates with
std.lib.resultto capture and report execution outcomes, including exceptions.
By offering a robust and flexible task management framework, std.task significantly contributes to the foundation-base project's ability to automate complex software engineering tasks and manage its multi-language development ecosystem effectively.