├── .gitignore ├── .travis.yml ├── History.md ├── README.md ├── appveyor.yml ├── project.clj ├── src └── clj_progress │ ├── bar.clj │ └── core.clj └── test └── clj_progress ├── bar_test.clj └── core_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | # Lein files 2 | pom.xml* 3 | *.jar 4 | *.class 5 | /lib/ 6 | /classes/ 7 | /target/ 8 | /checkouts/ 9 | .lein-* 10 | .nrepl-* 11 | 12 | # Code examples for readme 13 | example.clj 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: clojure 2 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 0.2.1 / 2015-05-28 2 | ================== 3 | 4 | * Improved `done` behaviour with lazy sequences [#6] 5 | * Fixed broken `re-init` function [#9] 6 | 7 | 8 | [#6]: https://github.com/Intervox/clj-progress/pull/6 9 | [#9]: https://github.com/Intervox/clj-progress/pull/9 10 | 11 | 0.2.0 / 2015-03-27 12 | ================== 13 | 14 | * Added throttling [#5] 15 | 16 | 17 | [#5]: https://github.com/Intervox/clj-progress/pull/5 18 | 19 | 0.1.6 / 2014-12-01 20 | ================== 21 | 22 | * Fixed bug with concurrent progress reporting 23 | 24 | 0.1.5 / 2014-10-08 25 | ================== 26 | 27 | * Minor visual improvements 28 | 29 | 0.1.4 / 2014-10-07 30 | ================== 31 | 32 | * Added indeterminable progress reporting [#3] 33 | 34 | 35 | [#3]: https://github.com/Intervox/clj-progress/issues/3 36 | 37 | 0.1.3 / 2014-09-24 38 | ================== 39 | 40 | * Fixed bug in `with-progress-handler` macro 41 | 42 | 0.1.2 / 2014-09-01 43 | ================== 44 | 45 | * Added tick-by and tick-to methods [#1] 46 | 47 | 48 | [#1]: https://github.com/Intervox/clj-progress/pull/1 49 | 50 | 0.1.1 / 2013-06-03 51 | ================== 52 | 53 | * Initial release 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clj-progress 2 | 3 | 4 | [![Clojars Repository][repo_badge]][repo] 5 | [![Linux Build Status][travis_badge]][travis] 6 | [![Windows Build Status][appveyor_badge]][appveyor] 7 | [![Dependency Status][versioneye_badge]][versioneye] 8 | 9 | [repo_badge]: https://img.shields.io/clojars/v/intervox/clj-progress.svg 10 | [travis_badge]: https://img.shields.io/travis/Intervox/clj-progress.svg?label=linux%20build 11 | [appveyor_badge]: https://img.shields.io/appveyor/ci/lbeschastny/clj-progress.svg?label=windows%20build 12 | [versioneye_badge]: http://www.versioneye.com/clojure/intervox:clj-progress/badge.svg 13 | [travis]: https://travis-ci.org/Intervox/clj-progress 14 | [versioneye]: http://www.versioneye.com/clojure/intervox:clj-progress 15 | [appveyor]: https://ci.appveyor.com/project/lbeschastny/clj-progress 16 | 17 | Flexible clojure progress bar, inspired by [node-progress]. 18 | 19 | [node-progress]: https://github.com/visionmedia/node-progress 20 | 21 | ### Main functionality 22 | 23 | * Ascii progress bar out of the box. 24 | * Custom progress handlers support. 25 | 26 | ## Installation 27 | 28 | You can install `clj-progress` using [clojars repository][repo]. 29 | 30 | [repo]: https://clojars.org/intervox/clj-progress 31 | 32 | With Leiningen: 33 | 34 | ```Clojure 35 | [intervox/clj-progress "0.2.1"] 36 | ``` 37 | 38 | With Gradle: 39 | 40 | ``` 41 | compile "intervox:clj-progress:0.2.1" 42 | ``` 43 | 44 | With Maven: 45 | 46 | ```xml 47 | 48 | intervox 49 | clj-progress 50 | 0.2.1 51 | 52 | ``` 53 | 54 | 55 | ## Usage 56 | 57 | Using `clj-progress` is really simple. 58 | 59 | There are three main methods defining the progress: 60 | 61 | * `init` 62 | * `tick` 63 | * `done` 64 | 65 | `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. 66 | 67 | When the third argument is provided, `init` returns it. 68 | 69 | `tick` and `done` takes no arguments, returning the first argument if any provided. 70 | 71 | 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. 72 | 73 | ### Examples 74 | 75 | ```Clojure 76 | (use 'clj-progress.core) 77 | 78 | (defn progress [] 79 | (init 50) 80 | (reduce + (map #(do (tick) (Thread/sleep 200) %) 81 | (range 50))) 82 | (done)) 83 | ``` 84 | 85 | More clojureish way to use progress: 86 | 87 | ```Clojure 88 | (use 'clj-progress.core) 89 | 90 | (defn process-item [item] 91 | (Thread/sleep 200) 92 | item) 93 | 94 | (defn greedy [] 95 | (->> (range 50) 96 | (init "Processing") 97 | (map (comp tick process-item)) 98 | (reduce +) 99 | done)) 100 | ``` 101 | 102 | Processing lazy sequences with progress: 103 | 104 | ```Clojure 105 | (defn lazy [] 106 | (->> (iterate inc 0) 107 | (init "Processing" 50) 108 | (map (comp tick process-item)) 109 | (take 50) 110 | (reduce +) 111 | done)) 112 | ``` 113 | 114 | Calling `done` with unrealized lazy sequence: 115 | 116 | ```Clojure 117 | (defn very-lazy [] 118 | (let [s (->> (iterate inc 0) 119 | (init "Processing" 50) 120 | (map (comp tick process-item)) 121 | (take 50) 122 | done)] 123 | (println "Nothing happened yet") 124 | (reduce + s))) 125 | ``` 126 | 127 | ## Other ticking methods 128 | 129 | `clj-progress` also provides two extra tick methods: 130 | 131 | * `(tick-by n)` - will tick by an amount of `n` 132 | * `(tick-to x)` - will set current progress value to `x` 133 | 134 | The first argument for `tick-by` and `tick-to` is mandatory. 135 | 136 | Both `tick-by` and `tick-to` will return their second argument if any provided. 137 | 138 | ### Examples 139 | 140 | ```Clojure 141 | (use 'clj-progress.core) 142 | (use 'clojure.java.io) 143 | 144 | (defn copy-file [input-path output-path] 145 | (init (-> (file input-path) .length)) 146 | (with-open [input (input-stream input-path ) 147 | output (output-stream output-path)] 148 | (let [buffer (make-array Byte/TYPE 1024)] 149 | (loop [] 150 | (let [size (.read input buffer)] 151 | (when (pos? size) 152 | (.write output buffer 0 size) 153 | (tick-by size) 154 | (recur)))))) 155 | (done)) 156 | ``` 157 | 158 | ## Hot re-initialization 159 | 160 | Sometimes an exact number of ticks is unknown when at progress bar initialization time. 161 | To adjust total number of ticks without reseting the whole progress bar you may use `re-init` function: 162 | 163 | ```Clojure 164 | (re-init 60) 165 | ``` 166 | 167 | ## Customizing progress bar 168 | 169 | You can customize progress bar using `set-progress-bar!` and `config-progress-bar!` methods and `with-progress-bar` macro. 170 | 171 | `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: 172 | 173 | * `:header` name of the progress 174 | * `:bar` the progress bar itself 175 | * `:wheel` rotating progress indicator 176 | * `:done` current tick number 177 | * `:total` total number of ticks 178 | * `:elapsed` elapsed time in seconds 179 | * `:eta` estimated completion time in seconds 180 | * `:percent` completion percentage 181 | 182 | By default it set to `:header [:bar] :percent :done/:total`. 183 | 184 | `with-progress-bar` macro allows you set progress bar format string only for some part of your code, without changing global settings. 185 | 186 | `config-progress-bar!` allows you to customize the progress bar itself: 187 | 188 | * `:width` width of the progress bar (default `50`) 189 | * `:complete` completion character (default `\=`) 190 | * `:incomplete` incomplete character (default `\space`) 191 | * `:current` current tick character (default `\>`) 192 | 193 | ```Clojure 194 | (set-progress-bar! " downloading [:bar] :percent :etas") 195 | 196 | (config-progress-bar! 197 | :complete \# 198 | :current \# 199 | :incomplete \-) 200 | 201 | (with-progress-bar "[:wheel] :done/:total :header" 202 | (do-something)) 203 | ``` 204 | 205 | ## Indeterminable progress bar 206 | 207 | To use `clj-progress` with unknown 208 | 209 | When total number of ticks is unknown it's possible to start indeterminable progress 210 | by initializing it with any non-positive value: 211 | 212 | ```Clojure 213 | (init "downloading" -1) 214 | ``` 215 | 216 | or 217 | 218 | ```Clojure 219 | (init 0) 220 | ``` 221 | 222 | When progress state is indeterminable, following `progress-bar` tokens will be replaced with `?` symbol: 223 | 224 | * `:total` 225 | * `:eta` 226 | * `:percent` 227 | 228 | Indeterminable state also change `:bar` animation. 229 | 230 | ## Throttling 231 | 232 | `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). 233 | 234 | `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. 235 | 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. 236 | 237 | You could change default behavior using `set-throttle!` function and `with-throttle` macro: 238 | 239 | ```Clojure 240 | (set-throttle! wait-time-in-milliseconds) 241 | 242 | (with-throttle wait-time-in-milliseconds 243 | (do-something)) 244 | ``` 245 | 246 | Any non-positive value will completely disable throttling. 247 | 248 | ## Using custom progress handlers 249 | 250 | `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: 251 | 252 | ```Clojure 253 | (set-progress-handler! 254 | { :init init-handler 255 | :tick tick-handler 256 | :done done-handler}) 257 | 258 | (with-progress-handler my-handler 259 | (do-something)) 260 | ``` 261 | 262 | ### Example 263 | 264 | ```Clojure 265 | (use 'clj-progress.core) 266 | 267 | (defn process-item [item] 268 | (-> (rand) 269 | (* 1000) 270 | int 271 | Thread/sleep) 272 | item) 273 | 274 | (defn process-all [] 275 | (init 50) 276 | (reduce + (map (comp tick process-item) 277 | (range 50))) 278 | (done)) 279 | 280 | (defn update-atom [state data] 281 | (swap! state merge data)) 282 | 283 | (defn atom-handler [a] 284 | { :init (partial update-atom a) 285 | :tick (partial update-atom a) 286 | :done (fn [_] (swap! a assoc :ready true))}) 287 | 288 | (defn atomic [] 289 | (let [state (atom {:ready false})] 290 | (with-progress-handler (atom-handler state) 291 | (future (process-all))) 292 | state)) 293 | 294 | (defn multi [] 295 | (let [atoms (repeatedly 5 atomic)] 296 | (while (some (comp not :ready deref) atoms) 297 | (dotimes [i 5] 298 | (let [{:keys [ttl done]} (deref (nth atoms i))] 299 | (println i ":" done "/" ttl))) 300 | (println "==========") 301 | (Thread/sleep 1000)) 302 | (println "All done!"))) 303 | ``` 304 | 305 | ## Handling progress state 306 | 307 | `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: 308 | 309 | ```Clojure 310 | (with-progress 311 | (do-something)) 312 | ``` 313 | 314 | ## [Changelog][history] 315 | 316 | [history]: History.md 317 | 318 | ## License 319 | 320 | Copyright © 2013-2014 Leonid Beschastny 321 | 322 | Distributed under the Eclipse Public License, the same as Clojure. 323 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - ps: cinst jdk8 3 | - ps: >- 4 | $base = "https://raw.githubusercontent.com/technomancy/leiningen/" 5 | 6 | $lein = $base + "stable/bin/lein.bat" 7 | 8 | (new-object net.webclient).DownloadFile($lein, 'lein.bat') 9 | - lein self-install 10 | 11 | install: 12 | - lein deps 13 | 14 | test_script: 15 | - lein test 16 | 17 | build: off 18 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject intervox/clj-progress "0.2.1" 2 | :description "Flexible clojure progress bar" 3 | :url "https://github.com/Intervox/clj-progress" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :lein-release {:deploy-via :clojars :scm :git} 7 | :dependencies [[org.clojure/clojure "1.6.0"]]) 8 | -------------------------------------------------------------------------------- /src/clj_progress/bar.clj: -------------------------------------------------------------------------------- 1 | (ns clj-progress.bar 2 | (:require [clojure.string :as string])) 3 | 4 | (def ^:dynamic *progress-bar-options* 5 | { :width 50 6 | :complete \= 7 | :incomplete \space 8 | :current \> }) 9 | 10 | (defn- get-bar 11 | [percent {:keys [width complete incomplete current]}] 12 | {:pre [(every? char? [complete incomplete current])]} 13 | (let [bar (new StringBuilder) 14 | dam (-> percent (* width) (/ 100) int)] 15 | (doseq [i (range width)] 16 | (cond (< i dam) (.append bar complete) 17 | (= i dam) (.append bar current) 18 | :else (.append bar incomplete))) 19 | (.toString bar))) 20 | 21 | (defn- get-indeterminable-bar 22 | [ticks {:keys [width complete incomplete current]}] 23 | {:pre [(every? char? [complete incomplete current])]} 24 | (let [bar (new StringBuilder)] 25 | (doseq [i (range width)] 26 | (if (-> i (- ticks) (mod width) (< 3)) 27 | (.append bar complete) 28 | (.append bar incomplete))) 29 | (.toString bar))) 30 | 31 | (defn- sreplace 32 | [s & {:as pairs}] 33 | (reduce (fn [s [k v]] 34 | (string/replace s (str k) (str v))) 35 | s 36 | pairs)) 37 | 38 | (defn- calc-eta 39 | [ttl done elapsed] 40 | (-> ttl (/ done) (- 1) (* elapsed) long)) 41 | 42 | (defn- update-progress-bar 43 | [fmt options done? {:keys [header start update ttl done ticks]}] 44 | (let [ttl? (pos? ttl) 45 | percent (if ttl? (-> done (/ ttl) (* 100))) 46 | opts (merge *progress-bar-options* options) 47 | elapsed (-> update (- start) (/ 1000000000))] 48 | (print 49 | (sreplace (str "\r" fmt " ") 50 | :header header 51 | :bar (cond 52 | done? (get-bar 100 opts) 53 | ttl? (get-bar percent opts) 54 | :else (get-indeterminable-bar ticks opts)) 55 | :wheel (if done? 56 | "+" 57 | (get ["/" "-" "\\" "|"] 58 | (mod ticks 4))) 59 | :done done 60 | :total (if ttl? ttl "?") 61 | :elapsed (long elapsed) 62 | :eta (cond 63 | (zero? done) "?" 64 | done? 0 65 | ttl? (calc-eta ttl done elapsed) 66 | :else "?") 67 | :percent (str (if ttl? (int percent) "?") "%"))) 68 | (if done? (println)) 69 | (flush))) 70 | 71 | (defn progress-bar 72 | [fmt & {:as options}] 73 | { :tick (partial update-progress-bar fmt options false) 74 | :done (partial update-progress-bar fmt options true )}) 75 | -------------------------------------------------------------------------------- /src/clj_progress/core.clj: -------------------------------------------------------------------------------- 1 | (ns clj-progress.core 2 | (:use clj-progress.bar)) 3 | 4 | (def ^:dynamic *progress-state* (atom {})) 5 | 6 | (def ^:dynamic *progress-handler* 7 | (progress-bar ":header [:bar] :percent :done/:total")) 8 | 9 | (def ^:dynamic *throttle* 20) 10 | 11 | (defn- handle [action] 12 | (if-let [f (get *progress-handler* action)] 13 | (f @*progress-state*))) 14 | 15 | (defn- init* [header ttl & [obj]] 16 | {:pre [(number? ttl)]} 17 | (let [now (System/nanoTime)] 18 | (swap! *progress-state* assoc 19 | :start now 20 | :update now 21 | :ttl ttl 22 | :done 0 23 | :ticks 0 24 | :header header)) 25 | (handle :init) 26 | obj) 27 | 28 | (defn init 29 | ([data] 30 | (init "" data)) 31 | ([header data] 32 | (init* header 33 | (if (coll? data) 34 | (count data) 35 | data) 36 | data)) 37 | ([header ttl obj & args] 38 | (init* header ttl obj))) 39 | 40 | (defn re-init 41 | [ttl] 42 | (swap! *progress-state* assoc-in [:ttl] ttl) 43 | (handle :tick)) 44 | 45 | (defn- tick* [obj] 46 | (let [wait (if (pos? *throttle*) 47 | (* *throttle* 1000000) 48 | -1) 49 | prev (get @*progress-state* :update) 50 | now (System/nanoTime)] 51 | (when (> (- now prev) wait) 52 | (doto *progress-state* 53 | (swap! update-in [:ticks ] inc) 54 | (swap! assoc-in [:update] now)) 55 | (handle :tick))) 56 | obj) 57 | 58 | (defn tick [& [obj]] 59 | (swap! *progress-state* update-in [:done] inc) 60 | (tick* obj)) 61 | 62 | (defn tick-by [n & [obj]] 63 | (swap! *progress-state* update-in [:done] + n) 64 | (tick* obj)) 65 | 66 | (defn tick-to [x & [obj]] 67 | {:pre [(number? x)]} 68 | (swap! *progress-state* assoc-in [:done] x) 69 | (tick* obj)) 70 | 71 | (defn done* [obj] 72 | (handle :done) 73 | (reset! *progress-state* {}) 74 | obj) 75 | 76 | (defn done [& [obj]] 77 | (if (and (instance? clojure.lang.LazySeq obj) 78 | (not (realized? obj))) 79 | (lazy-cat obj (done* nil)) 80 | (done* obj))) 81 | 82 | (defmacro with-progress [& body] 83 | "Executes body incapsulating its progress" 84 | `(binding [*progress-state* (atom {})] 85 | ~@body)) 86 | 87 | (defmacro with-progress-handler [handler & body] 88 | `(with-progress 89 | (binding [*progress-handler* ~handler] 90 | ~@body))) 91 | 92 | (defn set-progress-handler! [handler] 93 | (alter-var-root #'*progress-handler* 94 | (constantly handler))) 95 | 96 | (defmacro with-progress-bar [fmt & body] 97 | `(with-progress-handler (progress-bar ~fmt) 98 | ~@body)) 99 | 100 | (defn set-progress-bar! [fmt] 101 | (set-progress-handler! (progress-bar fmt))) 102 | 103 | (defn config-progress-bar! [& {:as options}] 104 | (alter-var-root #'*progress-bar-options* merge options)) 105 | 106 | (defmacro with-throttle [wait & body] 107 | `(binding [*throttle* ~wait] 108 | ~@body)) 109 | 110 | (defn set-throttle! [wait] 111 | (alter-var-root #'*throttle* 112 | (constantly wait))) 113 | -------------------------------------------------------------------------------- /test/clj_progress/bar_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-progress.bar-test 2 | (:require [clojure.string :as s]) 3 | (:use (clj-progress core bar) 4 | clojure.test)) 5 | 6 | (defn get-state 7 | [[header elapsed ttl done ticks]] 8 | (let [update (System/nanoTime) 9 | start (- update (* elapsed 1000000000))] 10 | (zipmap [:header :start :ttl :done :ticks :update ] 11 | [ header start ttl done ticks update ]))) 12 | 13 | (deftest test-progress-bar 14 | (are [fmt args res] 15 | (= (let [{:keys [tick]} (progress-bar fmt :width 6)] 16 | (-> args get-state tick with-out-str s/trim)) 17 | (let [{:keys [done]} (progress-bar fmt :width 6)] 18 | (-> args get-state done with-out-str s/trim)) 19 | res) 20 | ":header" [ "foo" 42 6 2 7 ] "foo" 21 | ":done" [ "foo" 42 6 2 7 ] "2" 22 | ":total" [ "foo" 42 6 2 7 ] "6" 23 | ":elapsed" [ "foo" 42 6 2 7 ] "42" 24 | ":percent" [ "foo" 42 6 2 7 ] "33%" 25 | 26 | ":header" [ "foo" 42 0 2 7 ] "foo" 27 | ":done" [ "foo" 42 0 2 7 ] "2" 28 | ":total" [ "foo" 42 0 2 7 ] "?" 29 | ":elapsed" [ "foo" 42 0 2 7 ] "42" 30 | ":percent" [ "foo" 42 0 2 7 ] "?%" 31 | 32 | ":header" [ "bar" 99 10 5 2 ] "bar" 33 | ":done" [ "bar" 99 10 5 2 ] "5" 34 | ":total" [ "bar" 99 10 5 2 ] "10" 35 | ":elapsed" [ "bar" 99 10 5 2 ] "99" 36 | ":percent" [ "bar" 99 10 5 2 ] "50%" 37 | 38 | ":header" [ "bar" 99 -1 5 2 ] "bar" 39 | ":done" [ "bar" 99 -1 5 2 ] "5" 40 | ":total" [ "bar" 99 -1 5 2 ] "?" 41 | ":elapsed" [ "bar" 99 -1 5 2 ] "99" 42 | ":percent" [ "bar" 99 -1 5 2 ] "?%" ) 43 | (are [fmt args res] 44 | (= (let [{:keys [tick]} (progress-bar fmt :width 6)] 45 | (-> args get-state tick with-out-str s/trim)) 46 | res) 47 | "[:bar]" [ "foo" 42 6 2 7 ] "[==> ]" 48 | ":wheel" [ "foo" 42 6 2 7 ] "|" 49 | ":eta" [ "foo" 42 6 2 7 ] "84" 50 | 51 | "[:bar]" [ "foo" 42 0 2 7 ] "[ === ]" 52 | ":wheel" [ "foo" 42 0 2 7 ] "|" 53 | ":eta" [ "foo" 42 0 2 7 ] "?" 54 | 55 | "[:bar]" [ "bar" 99 10 5 2 ] "[===> ]" 56 | ":wheel" [ "bar" 99 10 5 2 ] "\\" 57 | ":eta" [ "bar" 99 10 5 2 ] "99" 58 | 59 | "[:bar]" [ "bar" 99 -1 5 2 ] "[ === ]" 60 | ":wheel" [ "bar" 99 -1 5 2 ] "\\" 61 | ":eta" [ "bar" 99 -1 5 2 ] "?" ) 62 | (are [fmt args res] 63 | (= (let [{:keys [done]} (progress-bar fmt :width 6)] 64 | (-> args get-state done with-out-str s/trim)) 65 | res) 66 | "[:bar]" [ "foo" 42 6 2 7 ] "[======]" 67 | ":wheel" [ "foo" 42 6 2 7 ] "+" 68 | ":eta" [ "foo" 42 6 2 7 ] "0" 69 | 70 | "[:bar]" [ "foo" 42 0 2 7 ] "[======]" 71 | ":wheel" [ "foo" 42 0 2 7 ] "+" 72 | ":eta" [ "foo" 42 0 2 7 ] "0" ) 73 | (let [fmt "> [:bar] :percent :done/:total :etas" 74 | args [ "foo" 42 6 2 7 ]] 75 | (let [{:keys [tick done]} (progress-bar fmt :width 6)] 76 | (is (= (-> args get-state tick with-out-str s/trim) 77 | "> [==> ] 33% 2/6 84s")) 78 | (is (= (-> args get-state done with-out-str s/trim) 79 | "> [======] 33% 2/6 0s")))) 80 | (let [fmt ":header [:wheel] :percent :elapseds" 81 | args [ "bar" 99 -1 5 2 ]] 82 | (let [{:keys [tick done]} (progress-bar fmt :width 6)] 83 | (is (= (-> args get-state tick with-out-str s/trim) 84 | "bar [\\] ?% 99s")) 85 | (is (= (-> args get-state done with-out-str s/trim) 86 | "bar [+] ?% 99s"))))) 87 | 88 | (defn re-count 89 | [re s] 90 | (-> (re-pattern re) 91 | (re-seq s) 92 | count)) 93 | 94 | (deftest test-concurrent-progress 95 | (let [fmt "Lorem ipsum" 96 | n 5000 97 | s (with-out-str 98 | (with-progress-bar fmt 99 | (with-throttle 0 100 | (init n) 101 | (dorun (pmap tick (range n))) 102 | (done))))] 103 | (is (<= n (re-count (str "\r" fmt) s))) 104 | (is (= 0 (re-count "\r\r" s))))) 105 | -------------------------------------------------------------------------------- /test/clj_progress/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-progress.core-test 2 | (:use (clj-progress core bar) 3 | clojure.test)) 4 | 5 | 6 | (defn ainc 7 | [^clojure.lang.Atom a] 8 | (fn [_] (swap! a inc))) 9 | 10 | 11 | (deftest test-returned-value 12 | (are [args] 13 | (binding [*progress-handler* {}] 14 | (let [res (last args)] 15 | (= res 16 | (apply init args) 17 | (tick res) 18 | (tick-by 2 res) 19 | (tick-to 5 res) 20 | (done res)))) 21 | ["" 1 2 ] 22 | ["" 1 "" ] 23 | ["" 1 [1] ] 24 | ["" 1 {} ] 25 | ["" 1 ] 26 | ["" [2] ] 27 | ["" (list 2)] 28 | ["" #{2} ] 29 | ["" {:q 2} ] 30 | [ 1 ] 31 | [ [2] ] 32 | [ (list 2)] 33 | [ #{2} ] 34 | [ {:q 2} ] 35 | [ (range 7)])) 36 | 37 | 38 | (deftest test-init 39 | (are [args] 40 | (binding [*progress-state* (atom {})] 41 | (apply init args) 42 | (let [{:keys [ttl done header]} @*progress-state*] 43 | (and (= 1 ttl) 44 | (= 0 done) 45 | (= "" header)))) 46 | ["" 1 2 ] 47 | ["" 1 "" ] 48 | ["" 1 [1] ] 49 | ["" 1 {} ] 50 | ["" 1 ] 51 | ["" [2] ] 52 | ["" (list 2)] 53 | ["" #{2} ] 54 | ["" {:q 2} ] 55 | [ 1 ] 56 | [ [2] ] 57 | [ (list 2)] 58 | [ #{2} ] 59 | [ {:q 2} ]) 60 | (are [args res] 61 | (binding [*progress-state* (atom {})] 62 | (apply init args) 63 | (let [{:keys [ttl done header]} @*progress-state*] 64 | (and (= ttl res) 65 | (= done 0) 66 | (= header (first args))))) 67 | ["foo" 1 2 ] 1 68 | ["bar" 9 "" ] 9 69 | ["baz" 7 [1 2 3] ] 7 70 | ["foo" 3 {} ] 3 71 | ["bar" 9 ] 9 72 | ["baz" [3 1] ] 2 73 | ["foo" (list 2)] 1 74 | ["bar" #{2 3 4}] 3 75 | ["baz" {:q 2} ] 1) 76 | (is 77 | (let [c (atom 0)] 78 | (binding [*progress-handler* {:init (ainc c)}] 79 | (init 123) 80 | (init 456) 81 | (= @c 2))))) 82 | 83 | 84 | (deftest test-init 85 | (binding [*progress-state* (atom {}) 86 | *progress-handler* {}] 87 | (init 123) 88 | (are [n] 89 | (do 90 | (re-init n) 91 | (let [{:keys [ttl done]} @*progress-state*] 92 | (and (= n ttl) 93 | (= 0 done)))) 94 | 123 95 | 321 96 | 42)) 97 | (is 98 | (let [c (atom 0)] 99 | (binding [*progress-handler* {:init (ainc c)} 100 | *progress-state* (atom {})] 101 | (init 123) 102 | (re-init 456) 103 | (re-init 42) 104 | (let [{:keys [ttl]} @*progress-state*] 105 | (and (= 1 @c) 106 | (= 42 ttl))))))) 107 | 108 | 109 | (deftest test-tick 110 | (are [h nticks n args] 111 | (let [c (atom 0)] 112 | (binding [*progress-handler* {:tick (ainc c)} 113 | *progress-state* (atom {})] 114 | (init h n) 115 | (with-throttle 0 116 | (dotimes [_ nticks] 117 | (apply tick args))) 118 | (let [{:keys [ttl done ticks header]} @*progress-state*] 119 | (and (= ttl n) 120 | (= done @c ticks nticks) 121 | (= header h))))) 122 | "foo" 1 5 [3] 123 | "bar" 2 9 [0] 124 | "baz" 7 7 [ ])) 125 | 126 | 127 | (deftest test-throttle 128 | (are [nticks throttle sleep] 129 | (let [c (atom 0) 130 | n (-> nticks 131 | (* sleep) 132 | (/ throttle) 133 | int)] 134 | (binding [*progress-handler* {:tick (ainc c)} 135 | *progress-state* (atom {}) 136 | *throttle* throttle] 137 | (init n) 138 | (dotimes [_ nticks] 139 | (Thread/sleep sleep) 140 | (tick)) 141 | (let [{:keys [ttl done ticks header]} @*progress-state*] 142 | (and (= @c ticks) 143 | (>= nticks ticks n))))) 144 | 100 200 20 145 | 100 20 5 146 | 1000 200 5 )) 147 | 148 | 149 | (deftest test-tick-by 150 | (are [h bys n args res] 151 | (let [c (atom 0)] 152 | (binding [*progress-handler* {:tick (ainc c)} 153 | *progress-state* (atom {})] 154 | (init h n) 155 | (with-throttle 0 156 | (doseq [by bys] 157 | (apply tick-by by args))) 158 | (let [{:keys [ttl done ticks header]} @*progress-state*] 159 | (and (= ttl n) 160 | (= done res) 161 | (= @c ticks) 162 | (= header h))))) 163 | "foo" [ 1 ] 5 [3] 1 164 | "bar" [ 5 -7 ] 9 [0] -2 165 | "baz" [ 0 0 0 ] 7 [ ] 0 166 | "foo" [ 1 -1 1 -1] 5 [3] 0 167 | "bar" [ 1 2 3 4] 9 [0] 10 168 | "baz" [-1 -1 -1 -1] 7 [ ] -4 )) 169 | 170 | 171 | (deftest test-tick-to 172 | (are [h tos n args res] 173 | (let [c (atom 0)] 174 | (binding [*progress-handler* {:tick (ainc c)} 175 | *progress-state* (atom {})] 176 | (init h n) 177 | (with-throttle 0 178 | (doseq [to tos] 179 | (apply tick-to to args))) 180 | (let [{:keys [ttl done ticks header]} @*progress-state*] 181 | (and (= ttl n) 182 | (= done res) 183 | (= @c ticks) 184 | (= header h))))) 185 | "foo" [ 1 ] 5 [3] 1 186 | "bar" [ 5 -7 ] 9 [0] -7 187 | "baz" [ 0 -1 1 ] 7 [ ] 1 188 | "foo" [ 1 -1 1 1] 5 [3] 1 189 | "bar" [ 1 2 3 4] 9 [0] 4 )) 190 | 191 | 192 | (deftest test-done 193 | (are [args] 194 | (let [c (atom 0)] 195 | (binding [*progress-handler* {:done (ainc c)} 196 | *progress-state* (atom {})] 197 | (init "foo" 10) 198 | (tick) 199 | (apply done args) 200 | (and (= @*progress-state* {}) 201 | (= @c 1)))) 202 | [3] 203 | [ ])) 204 | 205 | 206 | (deftest test-lazy 207 | (let [c1 (atom 0) 208 | c2 (atom 0) 209 | c3 (atom 0)] 210 | (binding [*progress-handler* { :init (ainc c1) 211 | :tick (ainc c2) 212 | :done (ainc c3) } 213 | *progress-state* (atom {})] 214 | (with-throttle 0 215 | (->> (range 25) 216 | (init "Processing") 217 | (map tick) 218 | dorun 219 | done)) 220 | (is (= @*progress-state* {})) 221 | (is (= @c1 1 )) 222 | (is (= @c2 25)) 223 | (is (= @c3 1 )) 224 | (with-throttle 0 225 | (->> (range 50) 226 | (init "Processing") 227 | (map tick) 228 | done 229 | dorun)) 230 | (is (= @*progress-state* {})) 231 | (is (= @c1 2 )) 232 | (is (= @c2 75)) 233 | (is (= @c3 2 ))))) 234 | 235 | 236 | (deftest test-hooks 237 | (let [state (atom {}) 238 | log (atom []) 239 | hook (fn [-name] 240 | #(swap! log conj [-name %])) 241 | handler { :init (hook :init) 242 | :tick (hook :tick) 243 | :done (hook :done)} 244 | nops (atom 0) 245 | check (fn [expected-name expected-state] 246 | (swap! nops inc) 247 | (is (= @nops (count @log))) 248 | (let [[-name -state] (last @log)] 249 | (is (= -name expected-name )) 250 | (is (= -state expected-state))))] 251 | (binding [*progress-handler* handler 252 | *progress-state* state] 253 | (with-throttle 0 254 | (init 10) 255 | (check :init @state) 256 | (tick) 257 | (check :tick @state) 258 | (tick-by 2) 259 | (check :tick @state) 260 | (tick-to 9) 261 | (check :tick @state)) 262 | (let [-state @state] 263 | (done) 264 | (check :done -state))))) 265 | 266 | 267 | (deftest test-with-progress 268 | (let [mock { :foo :bar } 269 | state (atom mock)] 270 | (binding [*progress-handler* {} 271 | *progress-state* state] 272 | (with-progress 273 | (is (not= *progress-state* state)) 274 | (is (= @*progress-state* {})) 275 | (init 10)) 276 | (is (= *progress-state* state)) 277 | (is (= @state mock))))) 278 | 279 | 280 | (deftest test-with-progress-handler 281 | (let [h1 { :foo :bar } 282 | h2 { :foo :baz }] 283 | (binding [*progress-handler* h1] 284 | (with-progress-handler h2 285 | (is (= *progress-handler* h2))) 286 | (is (= *progress-handler* h1))))) 287 | 288 | 289 | (deftest test-set-progress-handler 290 | (let [h1 { :foo :bar } 291 | h2 { :foo :baz } 292 | curr *progress-handler*] 293 | (binding [*progress-handler* h1] 294 | (set-progress-handler! h2) 295 | (is (= *progress-handler* h1))) 296 | (is (= *progress-handler* h2)) 297 | (set-progress-handler! curr) 298 | (is (= *progress-handler* curr)))) 299 | 300 | 301 | (deftest test-with-progress-bar 302 | (let [h1 { :foo :bar } 303 | h2 { :foo :baz }] 304 | (with-redefs-fn { #'progress-bar (constantly h2) } 305 | #(binding [*progress-handler* h1] 306 | (with-progress-bar "fmt" 307 | (is (= *progress-handler* h2))) 308 | (is (= *progress-handler* h1)))))) 309 | 310 | 311 | (deftest test-set-progress-bar 312 | (let [h1 { :foo :bar } 313 | h2 { :foo :baz } 314 | curr *progress-handler*] 315 | (with-redefs-fn { #'progress-bar (constantly h2) } 316 | #(binding [*progress-handler* h1] 317 | (set-progress-bar! "fmt") 318 | (is (= *progress-handler* h1)))) 319 | (is (= *progress-handler* h2)) 320 | (set-progress-handler! curr))) 321 | 322 | 323 | (deftest test-config-progress-bar 324 | (let [opts *progress-bar-options*] 325 | (binding [*progress-bar-options* {}] 326 | (config-progress-bar! :foo :bar :baz 42) 327 | (is (= *progress-bar-options* {}))) 328 | (is (= *progress-bar-options* 329 | (assoc opts :foo :bar :baz 42))) 330 | (alter-var-root #'*progress-bar-options* 331 | (constantly opts)))) 332 | 333 | 334 | (deftest test-with-throttle 335 | (let [t1 666 t2 999] 336 | (binding [*throttle* t1] 337 | (with-throttle t2 338 | (is (= *throttle* t2))) 339 | (is (= *throttle* t1))))) 340 | 341 | 342 | (deftest test-set-throttle 343 | (let [t1 666 344 | t2 999 345 | curr *throttle*] 346 | (binding [*throttle* t1] 347 | (set-throttle! t2) 348 | (is (= *throttle* t1))) 349 | (is (= *throttle* t2)) 350 | (set-throttle! curr) 351 | (is (= *throttle* curr)))) 352 | --------------------------------------------------------------------------------