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 }