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