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 }