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 } 66 67 @property Duration avg() 68 { 69 return (dt.length == 0) ? Duration.zero : std.algorithm.reduce!((a, b) { 70 return a + b; 71 })(dt) / dt.length; 72 } 73 74 @property Duration elapsed() 75 { 76 return sw.peek().to!Duration; 77 } 78 79 void update() 80 { 81 if (refresh_rate < sw.peek() - this.last_draw) 82 { 83 this.last_draw = sw.peek().to!Duration; 84 force_update(); 85 } 86 } 87 88 void force_update() 89 { 90 } 91 92 void start() 93 { 94 95 } 96 97 void finish() 98 { 99 force_update(); 100 if (hide_cursor) 101 { 102 file.write(SHOW_CURSOR); 103 file.flush(); 104 } 105 file.writeln(); 106 } 107 108 void next(size_t n = 1) 109 { 110 if (n > 0) 111 { 112 immutable Duration now = sw.peek().to!Duration; 113 immutable Duration _dt = (now - ts) / n; 114 this.dt = this.dt[($ < sma_window) ? 0 : $ - sma_window + 1 .. $] ~ _dt; 115 this.ts = now; 116 } 117 this.index += n; 118 this.update(); 119 } 120 121 auto iter(R)(R it) if (isInputRange!R && !isInfinite!R) 122 { 123 return new Generator!(ElementType!R)({ 124 foreach (i; 0 .. it.length) 125 { 126 yield(it.front); 127 it.popFront(); 128 this.next(); 129 } 130 this.finish(); 131 }); 132 } 133 } 134 135 class Progress : Infinite 136 { 137 size_t max; 138 this(size_t max = 100) 139 { 140 this.max = max; 141 } 142 143 @property Duration eta() 144 { 145 return this.avg * this.remaining; 146 } 147 148 @property real percent() 149 { 150 return this.progress * 100; 151 } 152 153 @property real progress() 154 { 155 return std.algorithm.min(1, cast(real) this.index / this.max); 156 } 157 158 @property size_t remaining() 159 { 160 return std.algorithm.max(this.max - this.index, 0); 161 } 162 163 override void start() 164 { 165 this.force_update(); 166 } 167 168 void goto_index(size_t index) 169 { 170 size_t incr = index - this.index; 171 this.next(incr); 172 } 173 174 auto iter(R)(R it) if (isInputRange!R && !isInfinite!R) 175 { 176 this.max = it.length; 177 return new Generator!(ElementType!R)({ 178 foreach (i; 0 .. it.length) 179 { 180 yield(it.front); 181 it.popFront(); 182 this.next(); 183 } 184 this.finish(); 185 }); 186 } 187 } 188 189 package string repeat(string s, size_t n) 190 { 191 string result; 192 foreach (i; 0 .. n) 193 { 194 result ~= s; 195 } 196 return result; 197 }