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 enum SHOW_CURSOR = "\x1b[?25h";
19 package enum HIDE_CURSOR = "\x1b[?25l";
20 package enum LINEFEED = "\r";
21 package enum ERASE_IN_LINE = "\x1b[K";
22 package enum CURSOR_UP = "\x1b[1A";
23 
24 package class Infinite
25 {
26 private:
27     size_t sma_window = 10;
28     static if (2077 <= __VERSION__)
29     {
30         import std.datetime.stopwatch;
31 
32         StopWatch sw;
33     }
34     else
35     {
36         import std.datetime;
37 
38         StopWatch sw;
39     }
40     Duration ts;
41     Duration[] dt;
42     size_t _width;
43     size_t _height;
44     Duration last_draw;
45 
46 protected:
47     alias file = stderr;
48     void writeln(string s)
49     {
50         file.write(LINEFEED ~ ERASE_IN_LINE, repeat(CURSOR_UP ~ ERASE_IN_LINE, _height));
51         file.write(s);
52         _height = std.algorithm.count(s, "\n");
53     }
54 
55 public:
56     size_t index;
57     bool hide_cursor = false;
58     string delegate() message;
59     Duration refresh_rate = dur!"seconds"(1) / 60;
60     this()
61     {
62         this.index = 0;
63         this.message = { return ""; };
64         this.sw.start();
65         this.ts = Duration.zero;
66         this.last_draw = Duration.zero;
67         if (hide_cursor)
68             file.write(HIDE_CURSOR);
69     }
70 
71     @property Duration avg()
72     {
73         return (dt.length == 0) ? Duration.zero : std.algorithm.reduce!((a, b) {
74             return a + b;
75         })(dt) / dt.length;
76     }
77 
78     @property Duration elapsed()
79     {
80         return sw.peek().to!Duration;
81     }
82 
83     void update()
84     {
85         if (refresh_rate < sw.peek() - this.last_draw)
86         {
87             this.last_draw = sw.peek().to!Duration;
88             force_update();
89         }
90     }
91 
92     void force_update()
93     {
94     }
95 
96     void start()
97     {
98 
99     }
100 
101     void finish()
102     {
103         force_update();
104         if (hide_cursor)
105         {
106             file.write(SHOW_CURSOR);
107             file.flush();
108         }
109         file.writeln();
110     }
111 
112     void next(size_t n = 1)
113     {
114         if (n > 0)
115         {
116             immutable Duration now = sw.peek().to!Duration;
117             immutable Duration _dt = (now - ts) / n;
118             this.dt = this.dt[($ < sma_window) ? 0 : $ - sma_window + 1 .. $] ~ _dt;
119             this.ts = now;
120         }
121         this.index += n;
122         this.update();
123     }
124 
125     auto iter(R)(R it) if (isInputRange!R && !isInfinite!R)
126     {
127         return new Generator!(ElementType!R)({
128             foreach (i; 0 .. it.length)
129             {
130                 yield(it.front);
131                 it.popFront();
132                 this.next();
133             }
134             this.finish();
135         });
136     }
137 }
138 
139 class Progress : Infinite
140 {
141     size_t max;
142     this(size_t max = 100)
143     {
144         this.max = max;
145     }
146 
147     @property Duration eta()
148     {
149         return this.avg * this.remaining;
150     }
151 
152     @property real percent()
153     {
154         return this.progress * 100;
155     }
156 
157     @property real progress()
158     {
159         return std.algorithm.min(1, cast(real) this.index / this.max);
160     }
161 
162     @property size_t remaining()
163     {
164         return std.algorithm.max(this.max - this.index, 0);
165     }
166 
167     override void start()
168     {
169         this.force_update();
170     }
171 
172     void goto_index(size_t index)
173     {
174         size_t incr = index - this.index;
175         this.next(incr);
176     }
177 
178     auto iter(R)(R it) if (isInputRange!R && !isInfinite!R)
179     {
180         this.max = it.length;
181         return new Generator!(ElementType!R)({
182             foreach (i; 0 .. it.length)
183             {
184                 yield(it.front);
185                 it.popFront();
186                 this.next();
187             }
188             this.finish();
189         });
190     }
191 }
192 
193 package string repeat(string s, size_t n)
194 {
195     string result;
196     foreach (i; 0 .. n)
197     {
198         result ~= s;
199     }
200     return result;
201 }