1 module snck;
2 
3 import std.range.primitives : isInputRange;
4 
5 
6 struct Snck(R) if (isInputRange!R) {
7     import std.range.primitives : ElementType, hasLength;
8     import std.stdio : File, stderr;
9     import std.array; // : empty, front, popFront;
10     import std.conv : to;
11     import std.datetime.stopwatch;
12 
13     R range;
14     File file;
15     StopWatch watch = StopWatch(AutoStart.no);
16     alias E = ElementType!R;
17     size_t count = 0;
18     size_t nblocks = 10;
19     double minsecs = 0.1;
20     Duration previous;
21 
22     this(R range) {
23         this.range = range;
24         this.file = stderr;
25         this.watch.start();
26     }
27 
28     @property empty() const {
29         return range.empty;
30     }
31 
32     auto front() {
33         return range.front();
34     }
35 
36     void popFront() {
37         range.popFront;
38         ++count;
39 
40         // prevent too frequent message
41         auto now = watch.peek;
42         auto secs = (now - previous).total!"nsecs" * 1e-9;
43         if (!range.empty && secs < minsecs) return;
44 
45         file.write("\r");
46 
47         // display percentage
48         static if (hasLength!R) {
49             auto total = count + range.length;
50             file.writef!"%3d%s: "(100 * count / total, "%");
51         }
52         file.writef!"%d"(this.count);
53         static if (hasLength!R) {
54             file.writef!"/%d"(total);
55         }
56 
57         // TODO display progress bar
58         static if (hasLength!R) {
59             file.write("|");
60             auto passed = nblocks * count / total;
61             foreach (i; 0 .. nblocks) {
62                 if (i <= passed) {
63                     file.write("█");
64                 } else {
65                     file.write(" ");
66                 }
67             }
68             file.write("|");
69         }
70 
71         // display elapsed time
72         file.writef!" [";
73         this.printTime(now);
74         // display estimated amount of remaining time
75         static if (hasLength!R) {
76             auto fps = 1e9 * count / now.total!"nsecs";
77             auto remained = dur!"seconds"(to!long(range.length.to!double / fps));
78             file.write("<");
79             this.printTime(remained);
80             file.writef!", %.2fit/s"(fps);
81         }
82         file.writef!"]";
83         // file.writef("\n");
84 
85         if (this.range.empty) {
86             file.writef("\n");
87         }
88         file.flush();
89         this.previous = now;
90     }
91 
92     void printTime(Duration d) {
93         auto s = d.split!("hours", "minutes", "seconds");
94         if (s.hours > 0) {
95             file.writef!"%02d"(s.hours);
96         } else {
97             file.writef!"%02d:%02d"(s.minutes, s.seconds);
98         }
99     }
100 }
101 
102 auto snck(R)(R range) {
103     return Snck!R(range);
104 }
105 
106 
107 
108 unittest
109 {
110     import core.thread;
111     import std.range;
112     foreach (i; [1, 2, 3].snck) {
113         Thread.sleep(dur!"msecs"(i * 300));
114     }
115 
116     foreach (i; iota(100).snck) {
117         Thread.sleep(dur!"msecs"(10));
118     }
119 }