Flexible clojure progress bar, inspired by node-progress.
- Ascii progress bar out of the box.
- Custom progress handlers support.
You can install clj-progress using clojars repository.
With Leiningen:
[intervox/clj-progress "0.2.1"]With Gradle:
compile "intervox:clj-progress:0.2.1"
With Maven:
<dependency>
<groupId>intervox</groupId>
<artifactId>clj-progress</artifactId>
<version>0.2.1</version>
</dependency>Using clj-progress is really simple.
There are three main methods defining the progress:
inittickdone
init method takes the name of the progress as its first optional argument and the total number of ticks as the second one. If the second argument is a collection it count its element.
When the third argument is provided, init returns it.
tick and done takes no arguments, returning the first argument if any provided.
If the first argument to done is an unrealized lazy sequence, then done will return new lazy sequence which will automatically call done function upon its realization.
(use 'clj-progress.core)
(defn progress []
(init 50)
(reduce + (map #(do (tick) (Thread/sleep 200) %)
(range 50)))
(done))More clojureish way to use progress:
(use 'clj-progress.core)
(defn process-item [item]
(Thread/sleep 200)
item)
(defn greedy []
(->> (range 50)
(init "Processing")
(map (comp tick process-item))
(reduce +)
done))Processing lazy sequences with progress:
(defn lazy []
(->> (iterate inc 0)
(init "Processing" 50)
(map (comp tick process-item))
(take 50)
(reduce +)
done))Calling done with unrealized lazy sequence:
(defn very-lazy []
(let [s (->> (iterate inc 0)
(init "Processing" 50)
(map (comp tick process-item))
(take 50)
done)]
(println "Nothing happened yet")
(reduce + s)))clj-progress also provides two extra tick methods:
(tick-by n)- will tick by an amount ofn(tick-to x)- will set current progress value tox
The first argument for tick-by and tick-to is mandatory.
Both tick-by and tick-to will return their second argument if any provided.
(use 'clj-progress.core)
(use 'clojure.java.io)
(defn copy-file [input-path output-path]
(init (-> (file input-path) .length))
(with-open [input (input-stream input-path )
output (output-stream output-path)]
(let [buffer (make-array Byte/TYPE 1024)]
(loop []
(let [size (.read input buffer)]
(when (pos? size)
(.write output buffer 0 size)
(tick-by size)
(recur))))))
(done))Sometimes an exact number of ticks is unknown when at progress bar initialization time.
To adjust total number of ticks without reseting the whole progress bar you may use re-init function:
(re-init 60)You can customize progress bar using set-progress-bar! and config-progress-bar! methods and with-progress-bar macro.
set-progress-bar! takes the format string as its single argument. You can use the following set of tokens to create your own progress bar:
:headername of the progress:barthe progress bar itself:wheelrotating progress indicator:donecurrent tick number:totaltotal number of ticks:elapsedelapsed time in seconds:etaestimated completion time in seconds:percentcompletion percentage
By default it set to :header [:bar] :percent :done/:total.
with-progress-bar macro allows you set progress bar format string only for some part of your code, without changing global settings.
config-progress-bar! allows you to customize the progress bar itself:
:widthwidth of the progress bar (default50):completecompletion character (default\=):incompleteincomplete character (default\space):currentcurrent tick character (default\>)
(set-progress-bar! " downloading [:bar] :percent :etas")
(config-progress-bar!
:complete \#
:current \#
:incomplete \-)
(with-progress-bar "[:wheel] :done/:total :header"
(do-something))To use clj-progress with unknown
When total number of ticks is unknown it's possible to start indeterminable progress by initializing it with any non-positive value:
(init "downloading" -1)or
(init 0)When progress state is indeterminable, following progress-bar tokens will be replaced with ? symbol:
:total:eta:percent
Indeterminable state also change :bar animation.
clj-progress limits the frequency of progress bar updates. Default configuration allows at most one update per every 20 milliseconds (maximum 50 updated per second).
clj-progress will execute :tick progress handler (reprint progress bar, or invoke user-defined handler) as soon as you'll call any tick method for the first time.
If you'll call it again any number of times during the wait period, :tick progress handler will not be executed, though progress status will be tracked internally.
You could change default behavior using set-throttle! function and with-throttle macro:
(set-throttle! wait-time-in-milliseconds)
(with-throttle wait-time-in-milliseconds
(do-something))Any non-positive value will completely disable throttling.
clj-progress allows you to use your own progress handler by defining :init, :tick and :done hooks with set-progress-handler! method or with-progress-handler macro:
(set-progress-handler!
{ :init init-handler
:tick tick-handler
:done done-handler})
(with-progress-handler my-handler
(do-something))(use 'clj-progress.core)
(defn process-item [item]
(-> (rand)
(* 1000)
int
Thread/sleep)
item)
(defn process-all []
(init 50)
(reduce + (map (comp tick process-item)
(range 50)))
(done))
(defn update-atom [state data]
(swap! state merge data))
(defn atom-handler [a]
{ :init (partial update-atom a)
:tick (partial update-atom a)
:done (fn [_] (swap! a assoc :ready true))})
(defn atomic []
(let [state (atom {:ready false})]
(with-progress-handler (atom-handler state)
(future (process-all)))
state))
(defn multi []
(let [atoms (repeatedly 5 atomic)]
(while (some (comp not :ready deref) atoms)
(dotimes [i 5]
(let [{:keys [ttl done]} (deref (nth atoms i))]
(println i ":" done "/" ttl)))
(println "==========")
(Thread/sleep 1000))
(println "All done!")))clj-progress keeps a global state to track your progress status. Sometimes it may be useful to create a local execution state (for example, if you want to execute several tasks in parallel with custom progress handler). You could do it using with-progress macro:
(with-progress
(do-something))Copyright © 2013-2014 Leonid Beschastny
Distributed under the Eclipse Public License, the same as Clojure.