1 module snck; 2 3 import std.range.primitives : isInputRange; 4 import std.stdio : File, stderr; 5 6 struct SnckConf { 7 File file; 8 double minSeconds = 0.1; 9 bool showPercent = true; 10 bool showCounter = true; 11 bool showProgressBar = true; 12 size_t barBlocks = 10; 13 bool showElapsedTime = true; 14 bool showETA = true; 15 bool showSpeed = true; 16 bool eraseLast = true; 17 18 @property 19 ref fd() { 20 if (!this.file.isOpen) { 21 this.file = stderr; 22 } 23 return this.file; 24 } 25 26 @property 27 bool showAnyTimeStats() { 28 return showElapsedTime || showETA || showSpeed; 29 } 30 } 31 32 struct Snck(R) if (isInputRange!R) { 33 import std.range.primitives : ElementType, hasLength; 34 import std.array; // : empty, front, popFront; 35 import std.conv : to; 36 import std.datetime.stopwatch; 37 38 R range; 39 StopWatch watch = StopWatch(AutoStart.no); 40 alias E = ElementType!R; 41 size_t count = 0; 42 Duration previous; 43 SnckConf conf; 44 enum rewriteLine = "\r\033[K"; 45 46 this(R range) { 47 this.range = range; 48 this.watch.start(); 49 } 50 51 @property empty() const { 52 return range.empty; 53 } 54 55 auto front() { 56 return range.front(); 57 } 58 59 void popFront() { 60 range.popFront; 61 ++count; 62 63 with (this.conf) { 64 // prevent too frequent message 65 auto now = watch.peek; 66 auto secs = (now - previous).total!"nsecs" * 1e-9; 67 if (!range.empty && secs < minSeconds) return; 68 fd.write(this.rewriteLine); 69 70 static if (hasLength!R) { 71 auto total = count + range.length; 72 } 73 74 if (showPercent) { 75 static if (hasLength!R) { 76 fd.writef!"%3d%s: "(100 * count / total, "%"); 77 } 78 fd.writef!"%d"(this.count); 79 static if (hasLength!R) { 80 fd.writef!"/%d"(total); 81 } 82 } 83 84 if (showProgressBar) { 85 static if (hasLength!R) { 86 fd.write("|"); 87 auto passed = barBlocks * count / total; 88 foreach (i; 0 .. barBlocks) { 89 if (i <= passed) { 90 fd.write("█"); 91 } else { 92 fd.write(" "); 93 } 94 } 95 fd.write("|"); 96 } 97 } 98 99 if (showAnyTimeStats) { 100 fd.writef!" ["; 101 if (showElapsedTime) this.printTime(now); 102 103 static if (hasLength!R) { 104 auto fps = 1e9 * count / now.total!"nsecs"; 105 if (showETA) { 106 auto remained = dur!"seconds"(to!long(range.length.to!double / fps)); 107 fd.write("<"); 108 this.printTime(remained); 109 } 110 if (showSpeed) fd.writef!", %.2fit/s"(fps); 111 } 112 fd.writef!"]"; 113 } 114 115 if (this.range.empty) { 116 fd.writef(eraseLast ? this.rewriteLine : "\n"); 117 } 118 this.previous = now; 119 } 120 } 121 122 void printTime(Duration d) { 123 auto s = d.split!("hours", "minutes", "seconds"); 124 if (s.hours > 0) { 125 this.conf.fd.writef!"%02d"(s.hours); 126 } else { 127 this.conf.fd.writef!"%02d:%02d"(s.minutes, s.seconds); 128 } 129 } 130 } 131 132 auto snck(R)(R range) { 133 return Snck!R(range); 134 } 135 136 auto snck(R)(R range, SnckConf conf) { 137 auto ret = Snck!R(range); 138 ret.conf = conf; 139 return ret; 140 } 141 142 143 144 unittest 145 { 146 import core.thread; 147 import std.range; 148 foreach (i; [1, 2, 3].snck) { 149 Thread.sleep(dur!"msecs"(i * 300)); 150 } 151 152 foreach (i; iota(1000).snck) { 153 Thread.sleep(dur!"msecs"(1)); 154 } 155 156 SnckConf conf = { 157 barBlocks: 20, 158 minSeconds: 0.001, 159 eraseLast: false, 160 }; 161 foreach (i; iota(2000).snck(conf)) { 162 Thread.sleep(dur!"msecs"(1)); 163 } 164 }