1 module dstatus.progress;
2 
3 import std.algorithm.comparison : max, min;
4 import std.array : array;
5 import std.conv : to, text;
6 import std.format : format;
7 import std.range : repeat;
8 import std.string : leftJustify, rightJustify;
9 
10 import dstatus.status;
11 import dstatus.terminal;
12 
13 class ProgressBar(alias mkProgressBar) : Status {
14     private {
15         size_t _width;
16     }
17 
18     this(in size_t width) {
19         super();
20         _width = width - 6;
21     }
22 
23     final void progress(in short percent) {
24         auto percentText = "%d%%".format(percent).leftJustify(4);
25 
26         report(text(mkProgressBar(_width, percent), " ", percentText));
27     }
28 }
29 
30 class OperationProgressIndicator(alias mkProgressBar, alias mkStepCounter) : Status {
31     private {
32         size_t _stepWidth;
33         size_t _descriptionWidth;
34         size_t _percentTextWidth;
35         size_t _progressBarWidth;
36 
37         size_t _stepCount;
38         size_t _currentStep;
39         string _stepDescription;
40     }
41 
42     this(in size_t width, in size_t stepCount) {
43         _stepCount = stepCount;
44 
45         _stepWidth = mkStepCounter(_stepCount, _stepCount).length + 1;
46         _descriptionWidth = (width / 2) - _stepWidth;
47 
48         auto progressWidth = (width / 2) - 2;
49         _percentTextWidth = 4;
50         _progressBarWidth = progressWidth - _percentTextWidth - 1;
51     }
52 
53     final void step(in string description) {
54         ++_currentStep;
55         _stepDescription = description;
56     }
57 
58     final void progress(in short percent) {
59         auto percentText = "%d%%".format(percent).leftJustify(_percentTextWidth);
60 
61         auto indicator = text(
62             mkStepCounter(_currentStep, _stepCount),
63             " ",
64             makeFixedWidth(_descriptionWidth, _stepDescription),
65             " ",
66             mkProgressBar(_progressBarWidth, percent),
67             " ",
68             percentText);
69 
70         report(indicator);
71     }
72 }
73 
74 auto progressBar(alias mkProgressBar = makeProgressBar)(in size_t width) {
75     return new ProgressBar!(mkProgressBar)(width);
76 }
77 
78 auto operationProgressIndicator(alias mkProgressBar = makeProgressBar, alias mkStepCounter = makeStepCounter)(in size_t width, in size_t stepCount) {
79     return new OperationProgressIndicator!(mkProgressBar, mkStepCounter)(width, stepCount);
80 }
81 
82 @safe:
83 
84 pure string makeProgressBar(char leftEndChar = '[', char fillChar = '=', char tipChar = '>', char blankChar = ' ', char rightEndChar = ']')(in size_t width, in short percent) {
85     auto maxFillLength = width - 2;
86     auto fillLength = ((percent.to!float / 100) * maxFillLength).to!size_t;
87     auto actualFillLength = min(fillLength, maxFillLength);
88 
89     auto fill = fillChar.repeat(actualFillLength).array();
90     auto blank = blankChar.repeat(maxFillLength - actualFillLength);
91 
92     if (actualFillLength > 0 && actualFillLength < maxFillLength) {
93         fill[$-1] = tipChar;
94     }
95 
96     return text(leftEndChar, fill, blank, rightEndChar);
97 }
98 
99 pure string makeStepCounter(string divider = "/")(in size_t currentStep, in size_t stepCount) {
100     auto stepCountText = text(stepCount);
101     auto currentStepText = text(currentStep).rightJustify(stepCountText.length);
102 
103     return currentStepText ~ divider ~ stepCountText;
104 }
105 
106 unittest {
107     assert(makeProgressBar(10, 0).length == 10);
108     assert(makeProgressBar(10, 100).length == 10);
109 }