1 /**
2  * Copyright: Copyright Jason White, 2014-2016
3  * License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4  * Authors:   Jason White
5  */
6 module io.text;
7 
8 import io.stream;
9 import io.file.stdio : stdout;
10 import io.buffer.traits : isFlushable;
11 
12 import std.functional : forward;
13 
14 /**
15  * Serializes the given arguments to a text representation without a trailing
16  * new line.
17  */
18 size_t print(Stream, T...)(Stream stream, auto ref T args)
19     if (isSink!Stream)
20 {
21     import std.conv : to;
22 
23     size_t length;
24 
25     foreach (arg; args)
26         length += stream.write(arg.to!string);
27 
28     static if (isFlushable!Stream)
29         stream.flush();
30 
31     return length;
32 }
33 
34 /// Ditto
35 size_t print(T...)(auto ref T args)
36     if (T.length > 0 && !isSink!(T[0]))
37 {
38     return stdout.print(forward!args);
39 }
40 
41 unittest
42 {
43     import io.file.stream;
44     import std.typecons : tuple;
45     import std.typetuple : TypeTuple;
46 
47     // First tuple value is expected output. Remaining are the types to be
48     // printed.
49     alias tests = TypeTuple!(
50         tuple(""),
51         tuple("Test", "Test"),
52         tuple("[4, 8, 15, 16, 23, 42]", [4, 8, 15, 16, 23, 42]),
53         tuple("The answer is 42", "The answer is ", 42),
54         tuple("01234567890", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0),
55         );
56 
57     auto tf = testFile();
58 
59     foreach (t; tests)
60     {
61         immutable output = t[0];
62 
63         {
64             auto f = File(tf.name, FileFlags.writeEmpty);
65             assert(f.print(t[1 .. $]) == output.length);
66         }
67 
68         {
69             char[output.length] buf;
70             auto f = File(tf.name, FileFlags.readExisting);
71             assert(f.read(buf) == buf.length);
72             assert(buf == output);
73         }
74     }
75 }
76 
77 /**
78  * Serializes the given arguments to a text representation followed by a new
79  * line.
80  */
81 size_t println(T...)(auto ref T args)
82 {
83     return print(forward!args, '\n');
84 }
85 
86 unittest
87 {
88     import io.file.stream;
89     import std.typecons : tuple;
90     import std.typetuple : TypeTuple;
91 
92     // First tuple value is expected output. Remaining are the types to be
93     // printed.
94     alias tests = TypeTuple!(
95         tuple("\n"),
96         tuple("Test\n", "Test"),
97         tuple("[4, 8, 15, 16, 23, 42]\n", [4, 8, 15, 16, 23, 42]),
98         tuple("The answer is 42\n", "The answer is ", 42),
99         tuple("01234567890\n", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0),
100         );
101 
102     auto tf = testFile();
103 
104     foreach (t; tests)
105     {
106         immutable output = t[0];
107 
108         {
109             auto f = File(tf.name, FileFlags.writeEmpty);
110             assert(f.println(t[1 .. $]) == output.length);
111         }
112 
113         {
114             char[output.length] buf;
115             auto f = File(tf.name, FileFlags.readExisting);
116             assert(f.read(buf) == buf.length);
117             assert(buf == output);
118         }
119     }
120 }
121 
122 /**
123  * Serializes the given arguments according to the given format specifier
124  * string.
125  */
126 void printf(Stream, T...)(Stream stream, string format, auto ref T args)
127     if (isSink!Stream)
128 {
129     import std.format : formattedWrite;
130     formattedWrite(stream, forward!(format, args));
131 }
132 
133 /// Ditto
134 void printf(T...)(string format, auto ref T args)
135 {
136     stdout.printf(forward!(format, args));
137 }
138 
139 unittest
140 {
141     import io.file.stream;
142     import std.typecons : tuple;
143     import std.typetuple : TypeTuple;
144 
145     // First tuple value is expected output. Remaining are the types to be
146     // printed.
147     alias tests = TypeTuple!(
148         tuple("", ""),
149         tuple("Test", "Test"),
150         tuple("The answer is 42.", "The answer is %d.", 42),
151         tuple("Hello, my name is Inigo Montoya", "Hello, my name is %s %s...",
152             "Inigo", "Montoya")
153         );
154 
155     auto tf = testFile();
156 
157     foreach (t; tests)
158     {
159         immutable output = t[0];
160 
161         {
162             auto f = File(tf.name, FileFlags.writeEmpty);
163             f.printf(t[1 .. $]);
164         }
165 
166         {
167             char[output.length] buf;
168             auto f = File(tf.name, FileFlags.readExisting);
169             assert(f.read(buf) == buf.length);
170             assert(buf == output);
171         }
172     }
173 }
174 
175 /**
176  * Like $(D printf), but also writes a new line.
177  */
178 void printfln(Stream, T...)(Stream stream, string format, auto ref T args)
179     if (isSink!Stream)
180 {
181     stream.printf(forward!(format, args));
182     stream.print('\n');
183 }
184 
185 /// Ditto
186 void printfln(T...)(string format, auto ref T args)
187 {
188     stdout.printfln(forward!(format, args));
189 }
190 
191 /**
192  * Convenience function for returning a delimiter range that iterates over
193  * lines.
194  */
195 @property auto byLine(T = char, Stream)(Stream stream)
196     if (isSource!Stream)
197 {
198     import io.range : splitter;
199     return splitter!T(stream, '\n');
200 }
201 
202 /**
203  * Like $(D byLine), but duplicates each line. Obviously, this is less efficient
204  * than using $(D byLine).
205  */
206 @property auto byLineCopy(T = char, Stream)(Stream stream)
207     if (isSource!Stream)
208 {
209     import io.range : splitter;
210     import std.algorithm.iteration : map;
211     return splitter!T(stream, '\n').map!(l => l.idup);
212 }