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 }