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 }