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 }