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, __va_argsave);
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, __va_argsave);
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 
211                     version (DigitalMarsX64)
212                     {
213                         va_list ap;
214 
215                         va_start(ap, __va_argsave);
216 
217                         scope(exit) va_end(ap);
218 
219                         convert (&emit, _arguments, ap, slice[0 .. _arguments.length * 4 - 2]);
220                     }
221                     else
222                         convert (&emit, _arguments, _argptr, slice[0 .. _arguments.length * 4 - 2]);
223                 }
224                 return this;
225         }
226 
227         /***********************************************************************
228 
229                 Output a newline and optionally flush.
230 
231         ***********************************************************************/
232 
233         @property final FormatOutput newline ()
234         {
235                 sink.write (eol);
236                 if (flushLines)
237                     sink.flush();
238                 return this;
239         }
240 
241         /**********************************************************************
242 
243                 Control implicit flushing of newline(), where true enables
244                 flushing. An explicit flush() will always flush the output.
245 
246         **********************************************************************/
247 
248         final FormatOutput flush (bool yes)
249         {
250                 flushLines = yes;
251                 return this;
252         }
253 
254         /**********************************************************************
255 
256                 Return the associated output stream.
257 
258         **********************************************************************/
259 
260         @property final OutputStream stream ()
261         {
262                 return sink;
263         }
264 
265         /**********************************************************************
266 
267                 Set the associated output stream.
268 
269         **********************************************************************/
270 
271         @property final FormatOutput stream (OutputStream output)
272         {
273                 sink = output;
274                 return this;
275         }
276 
277         /**********************************************************************
278 
279                 Return the associated Layout.
280 
281         **********************************************************************/
282 
283         @property final Layout!(T) layout ()
284         {
285                 return convert;
286         }
287 
288         /**********************************************************************
289 
290                 Set the associated Layout.
291 
292         **********************************************************************/
293 
294         @property final FormatOutput layout (Layout!(T) layout)
295         {
296                 convert = layout;
297                 return this;
298         }
299 
300         /**********************************************************************
301 
302                 Sink for passing to the formatter.
303 
304         **********************************************************************/
305 
306         private final size_t emit (const(T)[] s)
307         {
308                 auto count = sink.write (s);
309                 if (count is Eof)
310                     conduit.error ("FormatOutput :: unexpected Eof");
311                 return count;
312         }
313 }
314 
315 
316 /*******************************************************************************
317 
318 *******************************************************************************/
319 
320 debug (Format)
321 {
322         import tango.io.device.Array;
323 
324         void main()
325         {
326                 auto print = new FormatOutput!(char) (new Array(1024, 1024));
327 
328                 for (int i=0;i < 1000; i++)
329                      print(i).newline;
330         }
331 }