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