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