1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2007 Kris Bell. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Initial release: Oct 2007
8 
9         author:         Kris
10 
11 *******************************************************************************/
12 
13 module tango.io.stream.Format;
14 
15 private import tango.io.device.Conduit;
16 
17 private import tango.text.convert.Layout;
18 
19 version(DigitalMars)
20 {
21     version(X86_64) version=DigitalMarsX64;
22 
23     private import tango.core.Vararg;
24 }
25 else version (GNU)
26 {
27     private import tango.core.Vararg;
28 }
29 
30 
31 /*******************************************************************************
32 
33         A bridge between a Layout instance and a stream. This is used for
34         the Stdout & Stderr globals, but can be used for general purpose
35         buffer-formatting as desired. The Template type 'T' dictates the
36         text arrangement within the target buffer ~ one of char, wchar or
37         dchar (UTF8, UTF16, or UTF32).
38 
39         FormatOutput exposes this style of usage:
40         ---
41         auto print = new FormatOutput!(char) (...);
42 
43         print ("hello");                    // => hello
44         print (1);                          // => 1
45         print (3.14);                       // => 3.14
46         print ('b');                        // => b
47         print (1, 2, 3);                    // => 1, 2, 3
48         print ("abc", 1, 2, 3);             // => abc, 1, 2, 3
49         print ("abc", 1, 2) ("foo");        // => abc, 1, 2foo
50         print ("abc") ("def") (3.14);       // => abcdef3.14
51 
52         print.format ("abc {}", 1);         // => abc 1
53         print.format ("abc {}:{}", 1, 2);   // => abc 1:2
54         print.format ("abc {1}:{0}", 1, 2); // => abc 2:1
55         print.format ("abc ", 1);           // => abc
56         ---
57 
58         Note that the last example does not throw an exception. There
59         are several use-cases where dropping an argument is legitimate,
60         so we're currently not enforcing any particular trap mechanism.
61 
62         Flushing the output is achieved through the flush() method, or
63         via an empty pair of parens:
64         ---
65         print ("hello world") ();
66         print ("hello world").flush;
67 
68         print.format ("hello {}", "world") ();
69         print.format ("hello {}", "world").flush;
70         ---
71 
72         Special character sequences, such as "\n", are written directly to
73         the output without any translation (though an output-filter could
74         be inserted to perform translation as required). Platform-specific
75         newlines are generated instead via the newline() method, which also
76         flushes the output when configured to do so:
77         ---
78         print ("hello ") ("world").newline;
79         print.format ("hello {}", "world").newline;
80         print.formatln ("hello {}", "world");
81         ---
82 
83         The format() method supports the range of formatting options
84         exposed by tango.text.convert.Layout and extensions thereof;
85         including the full I18N extensions where configured in that
86         manner. To create a French instance of FormatOutput:
87         ---
88         import tango.text.locale.Locale;
89 
90         auto locale = new Locale (Culture.getCulture ("fr-FR"));
91         auto print = new FormatOutput!(char) (locale, ...);
92         ---
93 
94         Note that FormatOutput is *not* intended to be thread-safe.
95 
96 *******************************************************************************/
97 
98 class FormatOutput(T) : OutputFilter
99 {
100         public  alias OutputFilter.flush flush;
101 
102         private const(T[])      eol;
103         private Layout!(T)      convert;
104         private bool            flushLines;
105 
106         public alias print      opCall;         /// opCall -> print
107         public alias newline    nl;             /// nl -> newline
108 
109         version (Win32)
110                  private __gshared immutable immutable(T)[] Eol = "\r\n";
111         else
112                  private __gshared immutable immutable(T)[] Eol = "\n";
113 
114         /**********************************************************************
115 
116                 Construct a FormatOutput instance, tying the provided stream
117                 to a layout formatter.
118 
119         **********************************************************************/
120 
121         this (OutputStream output, const(T[]) eol = Eol)
122         {
123                 this (Layout!(T).instance, output, eol);
124         }
125 
126         /**********************************************************************
127 
128                 Construct a FormatOutput instance, tying the provided stream
129                 to a layout formatter.
130 
131         **********************************************************************/
132 
133         this (Layout!(T) convert, OutputStream output, const(T[]) eol = Eol)
134         {
135                 assert (convert);
136                 assert (output);
137 
138                 this.convert = convert;
139                 this.eol = eol;
140                 super (output);
141         }
142 
143         /**********************************************************************
144 
145                 Layout using the provided formatting specification.
146 
147         **********************************************************************/
148 
149         final FormatOutput format (const(T[]) fmt, ...)
150         {
151             version (DigitalMarsX64)
152             {
153                 va_list ap;
154 
155                 va_start(ap, fmt);
156 
157                 scope(exit) va_end(ap);
158 
159                 convert (&emit, _arguments, ap, fmt);
160             }
161             else
162                 convert (&emit, _arguments, _argptr, fmt);
163 
164             return this;
165         }
166 
167         /**********************************************************************
168 
169                 Layout using the provided formatting specification.
170 
171         **********************************************************************/
172 
173         final FormatOutput formatln (const(T[]) fmt, ...)
174         {
175             version (DigitalMarsX64)
176             {
177                 va_list ap;
178 
179                 va_start(ap, fmt);
180 
181                 scope(exit) va_end(ap);
182 
183                 convert (&emit, _arguments, ap, fmt);
184             }
185             else
186                 convert (&emit, _arguments, _argptr, fmt);
187 
188            return newline;
189         }
190 
191         /**********************************************************************
192 
193                 Unformatted layout, with commas inserted between args.
194                 Currently supports a maximum of 24 arguments.
195 
196         **********************************************************************/
197 
198         final FormatOutput print ( ... )
199         {
200                 __gshared immutable immutable(T)[] slice =  "{}, {}, {}, {}, {}, {}, {}, {}, " ~
201                                                             "{}, {}, {}, {}, {}, {}, {}, {}, " ~
202                                                             "{}, {}, {}, {}, {}, {}, {}, {}, ";
203 
204                 assert (_arguments.length <= slice.length/4, "FormatOutput :: too many arguments");
205 
206                 if (_arguments.length == 0)
207                     sink.flush();
208                 else
209                 {
210                     convert (&emit, _arguments, _argptr, slice[0 .. _arguments.length * 4 - 2]);
211                 }
212                 return this;
213         }
214 
215         /***********************************************************************
216 
217                 Output a newline and optionally flush.
218 
219         ***********************************************************************/
220 
221         @property final FormatOutput newline ()
222         {
223                 sink.write (eol);
224                 if (flushLines)
225                     sink.flush();
226                 return this;
227         }
228 
229         /**********************************************************************
230 
231                 Control implicit flushing of newline(), where true enables
232                 flushing. An explicit flush() will always flush the output.
233 
234         **********************************************************************/
235 
236         final FormatOutput flush (bool yes)
237         {
238                 flushLines = yes;
239                 return this;
240         }
241 
242         /**********************************************************************
243 
244                 Return the associated output stream.
245 
246         **********************************************************************/
247 
248         @property final OutputStream stream ()
249         {
250                 return sink;
251         }
252 
253         /**********************************************************************
254 
255                 Set the associated output stream.
256 
257         **********************************************************************/
258 
259         @property final FormatOutput stream (OutputStream output)
260         {
261                 sink = output;
262                 return this;
263         }
264 
265         /**********************************************************************
266 
267                 Return the associated Layout.
268 
269         **********************************************************************/
270 
271         @property final Layout!(T) layout ()
272         {
273                 return convert;
274         }
275 
276         /**********************************************************************
277 
278                 Set the associated Layout.
279 
280         **********************************************************************/
281 
282         @property final FormatOutput layout (Layout!(T) layout)
283         {
284                 convert = layout;
285                 return this;
286         }
287 
288         /**********************************************************************
289 
290                 Sink for passing to the formatter.
291 
292         **********************************************************************/
293 
294         private final size_t emit (const(T)[] s)
295         {
296                 auto count = sink.write (s);
297                 if (count is Eof)
298                     conduit.error ("FormatOutput :: unexpected Eof");
299                 return count;
300         }
301 }
302 
303 
304 /*******************************************************************************
305 
306 *******************************************************************************/
307 
308 debug (Format)
309 {
310         import tango.io.device.Array;
311 
312         void main()
313         {
314                 auto print = new FormatOutput!(char) (new Array(1024, 1024));
315 
316                 for (int i=0;i < 1000; i++)
317                      print(i).newline;
318         }
319 }