1 module mage.util.stream; 2 3 import mage; 4 5 mixin template StreamWrite() 6 { 7 string newLine = "\n"; 8 int indentLevel = 0; 9 string indentString = " "; /// Set this to null to disable indentation, even when indent() is called. 10 bool isDirty = false; 11 12 @property bool isIndentEnabled() const { return this.indentString !is null; } 13 14 /// Increase indentation. Does not write. 15 void indent() 16 { 17 ++indentLevel; 18 } 19 20 /// Decrease indentation to a minimum of 0. Does not write. 21 void dedent() { 22 import std.algorithm : max; 23 indentLevel = max(indentLevel - 1, 0); 24 } 25 26 void write(string s) 27 { 28 import std.array; 29 if(this.isDirty && this.indentString !is null && this.indentLevel > 0) { 30 this.writeImpl(this.indentString.replicate(this.indentLevel)); 31 } 32 this.isDirty = false; 33 this.writeImpl(s); 34 } 35 36 void writeln(string s) 37 { 38 this.write(s); 39 this.writeln(); 40 } 41 42 void writeln() { 43 this.write(this.newLine); 44 this.isDirty = true; 45 } 46 } 47 48 unittest { 49 struct S { mixin StreamWrite; void writeImpl(...){} } 50 S s; 51 assert(s.indentLevel == 0); 52 s.dedent(); 53 assert(s.indentLevel == 0); 54 s.indent(); 55 assert(s.indentLevel == 1); 56 s.indent(); 57 assert(s.indentLevel == 2); 58 s.dedent(); 59 assert(s.indentLevel == 1); 60 s.dedent(); 61 assert(s.indentLevel == 0); 62 s.dedent(); 63 assert(s.indentLevel == 0); 64 } 65 66 /// Note: Untested. 67 struct ScopedIndentation(SomeStream) 68 { 69 int amount; 70 SomeStream* stream; 71 72 @disable this(); 73 74 this(ref SomeStream stream, int amount = 1) 75 { 76 this.stream = &stream; 77 this.amount = amount; 78 while (amount) { 79 stream.indent(); 80 --amount; 81 } 82 } 83 84 ~this() 85 { 86 while(this.amount) { 87 stream.dedent(); 88 --this.amount; 89 } 90 } 91 } 92 93 struct FileStream 94 { 95 import std.stdio : File; 96 97 File file; 98 99 mixin StreamWrite; 100 101 this(Path p, string mode = "wb") { 102 file = p.open(mode); 103 } 104 105 private void writeImpl(string s) 106 { 107 file.write(s); 108 } 109 } 110 111 struct StringStream 112 { 113 string content; 114 115 mixin StreamWrite; 116 117 this(string initial = "") { 118 content = initial; 119 } 120 121 private void writeImpl(string s) 122 { 123 content ~= s; 124 } 125 } 126 127 unittest 128 { 129 auto ss = StringStream(); 130 assert(ss.content == ""); 131 ss.writeln("hello"); 132 assert(ss.content == "hello\n"); 133 ss.indent(); 134 assert(ss.content == "hello\n"); 135 ss.write("world"); 136 assert(ss.content == "hello\n world"); 137 ss.dedent(); 138 assert(ss.content == "hello\n world"); 139 ss.writeln(" and goodbye"); 140 assert(ss.content == "hello\n world and goodbye\n", `"%s"`.format(ss.content)); 141 ss.write("..."); 142 assert(ss.content == "hello\n world and goodbye\n..."); 143 } 144 145 struct StdoutStream 146 { 147 import io = std.stdio; 148 149 mixin StreamWrite; 150 151 private void writeImpl(string s) 152 { 153 io.write(s); 154 } 155 }