1 module progress; 2 3 import core.time : dur, Duration; 4 5 public import progress.bar; 6 public import progress.counter; 7 public import progress.spinner; 8 9 static import std.algorithm; 10 import std.concurrency : Generator, yield; 11 import std.conv : to; 12 import std.datetime : StopWatch; 13 import std.math : ceil; 14 import std.range.primitives : walkLength; 15 import std.range : ElementType, isInfinite, isInputRange; 16 import std.stdio : stderr; 17 import std..string : countchars, leftJustify; 18 19 package immutable SHOW_CURSOR = "\x1b[?25h"; 20 package immutable HIDE_CURSOR = "\x1b[?25l"; 21 22 package class Infinite 23 { 24 private: 25 size_t sma_window = 10; 26 StopWatch sw; 27 Duration ts; 28 Duration[] dt; 29 size_t _width; 30 size_t _height; 31 Duration last_draw; 32 33 protected: 34 alias file = stderr; 35 void write(string s) 36 { 37 string message = this.message(); 38 string result = (message ~ s).leftJustify(this._width); 39 file.write("\r", result); 40 this._width = std.algorithm.max(this._width, (message ~ s).walkLength); 41 file.flush(); 42 } 43 44 void writeln(string s) 45 { 46 file.write("\r\x1b[K", repeat("\x1b[1A\x1b[K", _height)); 47 file.write(s); 48 _height = s.countchars("\n"); 49 } 50 51 public: 52 size_t index; 53 bool hide_cursor = false; 54 string delegate() message; 55 Duration refresh_rate = dur!"seconds"(1) / 60; 56 this() 57 { 58 this.index = 0; 59 this.message = { return ""; }; 60 this.sw.start(); 61 this.ts = Duration.zero; 62 this.last_draw = Duration.zero; 63 if (hide_cursor) 64 file.write(HIDE_CURSOR); 65 stderr.writeln(refresh_rate - last_draw); 66 } 67 68 @property Duration avg() 69 { 70 return (dt.length == 0) ? Duration.zero : std.algorithm.reduce!((a, b) { 71 return a + b; 72 })(dt) / dt.length; 73 } 74 75 @property Duration elapsed() 76 { 77 return sw.peek().to!Duration; 78 } 79 80 void update() 81 { 82 if (refresh_rate < sw.peek() - this.last_draw) 83 { 84 this.last_draw = sw.peek().to!Duration; 85 force_update(); 86 } 87 } 88 89 void force_update() 90 { 91 } 92 93 void start() 94 { 95 96 } 97 98 void finish() 99 { 100 force_update(); 101 if (hide_cursor) 102 { 103 file.write(SHOW_CURSOR); 104 file.flush(); 105 } 106 file.writeln(); 107 } 108 109 void next(size_t n = 1) 110 { 111 if (n > 0) 112 { 113 immutable Duration now = sw.peek().to!Duration; 114 immutable Duration _dt = (now - ts) / n; 115 this.dt = this.dt[($ < sma_window) ? 0 : $ - sma_window + 1 .. $] ~ _dt; 116 this.ts = now; 117 } 118 this.index += n; 119 this.update(); 120 } 121 122 auto iter(R)(R it) if (isInputRange!R && !isInfinite!R) 123 { 124 return new Generator!(ElementType!R)({ 125 foreach (i; 0 .. it.length) 126 { 127 yield(it.front); 128 it.popFront(); 129 this.next(); 130 } 131 this.finish(); 132 }); 133 } 134 } 135 136 class Progress : Infinite 137 { 138 size_t max; 139 this(size_t max = 100) 140 { 141 this.max = max; 142 } 143 144 @property Duration eta() 145 { 146 return this.avg * this.remaining; 147 } 148 149 @property real percent() 150 { 151 return this.progress * 100; 152 } 153 154 @property real progress() 155 { 156 return std.algorithm.min(1, cast(real) this.index / this.max); 157 } 158 159 @property size_t remaining() 160 { 161 return std.algorithm.max(this.max - this.index, 0); 162 } 163 164 override void start() 165 { 166 this.update(); 167 } 168 169 void goto_index(size_t index) 170 { 171 size_t incr = index - this.index; 172 this.next(incr); 173 } 174 175 auto iter(R)(R it) if (isInputRange!R && !isInfinite!R) 176 { 177 this.max = it.length; 178 return new Generator!(ElementType!R)({ 179 foreach (i; 0 .. it.length) 180 { 181 yield(it.front); 182 it.popFront(); 183 this.next(); 184 } 185 this.finish(); 186 }); 187 } 188 } 189 190 package string repeat(string s, size_t n) 191 { 192 string result; 193 foreach (i; 0 .. n) 194 { 195 result ~= s; 196 } 197 return result; 198 }