1 module progress;
2 
3 import std.stdio;
4 import std.range;
5 import std.format;
6 import std.datetime;
7 import core.sys.posix.unistd;
8 import core.sys.posix.sys.ioctl;
9 
10 class Progress
11 {
12   private:
13 
14     immutable static size_t default_width = 80;
15     size_t max_width = 40;
16     size_t width = default_width;
17 
18     ulong start_time;
19     string caption = "Progress";
20     size_t iterations;
21     size_t counter;
22 
23 
24     size_t getTerminalWidth() {
25       size_t column;
26       winsize ws;
27       if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) {
28         column = ws.ws_col;
29       }
30       if(column == 0) column = default_width;
31 
32       return column;
33     }
34 
35 
36     void clear() {
37       write("\r");
38       for(auto i = 0; i < width; i++) write(" ");
39       write("\r");
40     }
41 
42 
43     int calc_eta() {
44       immutable auto ratio = cast(double)counter / iterations;
45       auto current_time = Clock.currTime.toUnixTime();
46       auto duration = (current_time - start_time);
47       int hours, minutes, seconds;
48       double elapsed = (current_time - start_time);
49       int eta_sec = cast(int)((elapsed / ratio) - elapsed);
50 
51       return eta_sec;
52     }
53 
54 
55     string progressbarText(string header_text, string footer_text) {
56       immutable auto ratio = cast(double)counter / iterations;
57       string result = "";
58 
59       double bar_length = width - header_text.length - footer_text.length;
60       if(bar_length > max_width && max_width > 0) {
61         bar_length = max_width;
62       }
63       size_t i = 0;
64       for(; i < ratio * bar_length; i++) result ~= "o";
65       for(; i < bar_length; i++) result ~= " ";
66 
67       return header_text ~ result ~ footer_text;
68     }
69 
70 
71     void print() {
72       immutable auto ratio = cast(double)counter / iterations;
73       auto header = appender!string();
74       auto footer = appender!string();
75 
76       header.formattedWrite("%s %3d%% |", caption, cast(int)(ratio * 100));
77 
78       if(counter <= 1 || ratio == 0.0) {
79         footer.formattedWrite("| ETA --:--:--:");
80       } else {
81         int h, m, s;
82         dur!"seconds"(calc_eta())
83           .split!("hours", "minutes", "seconds")(h, m, s);
84         footer.formattedWrite("| ETA %02d:%02d:%02d ", h, m, s);
85       }
86 
87       write(progressbarText(header.data, footer.data));
88     }
89 
90 
91     void update() {
92       width = getTerminalWidth();
93  
94       clear();
95 
96       print();
97       stdout.flush();
98     }
99 
100 
101   public:
102 
103     this(size_t iterations) {
104       if(iterations <= 0) iterations = 1;
105 
106       counter = 0;
107       this.iterations = iterations;
108       start_time = Clock.currTime.toUnixTime;
109     }
110 
111     @property {
112       string title() { return caption; }
113       string title(string text) { return caption = text; }
114     }
115 
116     @property {
117       size_t count() { return counter; }
118       size_t count(size_t val) {
119         if(val > iterations) val = iterations;
120         return counter = val;
121       }
122     }
123 
124     @property {
125       size_t maxWidth() { return max_width; }
126       size_t maxWidth(size_t w) {
127         return max_width = w;
128       }
129     }
130 
131     void reset() {
132       counter = 0;
133       start_time = Clock.currTime.toUnixTime;
134     }
135 
136     void next() {
137       counter++;
138       if(counter > iterations) counter = iterations;
139 
140       update();
141     }
142 
143 
144 }
145