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 }