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