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 }