1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2004 Kris Bell. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Feb 2005: Initial release
8                         Nov 2005: Heavily revised for unicode
9                         Dec 2006: Outback release
10 
11         author:         Kris
12 
13 *******************************************************************************/
14 
15 module tango.io.Console;
16 
17 private import  tango.sys.Common;
18 
19 private import  tango.io.device.Device,
20                 tango.io.stream.Buffered;
21 
22 version (Posix)
23          private import tango.stdc.posix.unistd;  // needed for isatty()
24 
25 /*******************************************************************************
26 
27         Low-level console IO support.
28 
29         Note that for a while this was templated for each of char, wchar,
30         and dchar. It became clear after some usage that the console is
31         more useful if it sticks to UTF8 only. See Console.Conduit below
32         for details.
33 
34         Redirecting the standard IO handles (via a shell) operates as one
35         would expect, though the redirected content should likely restrict
36         itself to UTF8.
37 
38 *******************************************************************************/
39 
40 struct Console
41 {
42         version (Win32)
43                  __gshared immutable immutable(char)[] Eol = "\r\n";
44         else
45                  __gshared immutable immutable(char)[] Eol = "\n";
46 
47 
48         /**********************************************************************
49 
50                 Model console input as a buffer. Note that we read UTF8
51                 only.
52 
53         **********************************************************************/
54 
55         class Input
56         {
57                 private Bin     buffer;
58                 private bool    redirect;
59 
60                 public alias    copyln get;
61 
62                 /**************************************************************
63 
64                         Attach console input to the provided device.
65 
66                 **************************************************************/
67 
68                 private this (Conduit conduit, bool redirected)
69                 {
70                         redirect = redirected;
71                         buffer = new Bin (conduit);
72                 }
73 
74                 /**************************************************************
75 
76                         Return the next line available from the console,
77                         or null when there is nothing available. The value
78                         returned is a duplicate of the buffer content (it
79                         has .dup applied).
80 
81                         Each line ending is removed unless parameter raw is
82                         set to true.
83 
84                 **************************************************************/
85 
86                 final char[] copyln (bool raw = false)
87                 {
88                         char[] line;
89 
90                         return readln (line, raw) ? line.dup : null;
91                 }
92 
93                 /**************************************************************
94 
95                         Retreive a line of text from the console and map
96                         it to the given argument. The input is sliced,
97                         not copied, so use .dup appropriately. Each line
98                         ending is removed unless parameter raw is set to
99                         true.
100 
101                         Returns false when there is no more input.
102 
103                 **************************************************************/
104 
105                 final bool readln (ref char[] content, bool raw=false)
106                 {
107                         size_t line (const(void)[] input)
108                         {
109                                 auto text = cast(const(char[])) input;
110                                 foreach (i, c; text)
111                                          if (c is '\n')
112                                             {
113                                             auto j = i;
114                                             if (raw)
115                                                 ++j;
116                                             else
117                                                if (j && (text[j-1] is '\r'))
118                                                    --j;
119                                             content = cast(char[])text [0 .. j];
120                                             return i+1;
121                                             }
122                                 return IConduit.Eof;
123                         }
124 
125                         // get next line, return true
126                         if (buffer.next (&line))
127                             return true;
128 
129                         // assign trailing content and return false
130                         content = cast(char[]) buffer.slice (buffer.readable());
131                         return false;
132                 }
133 
134                 /**************************************************************
135 
136                         Return the associated stream.
137 
138                 **************************************************************/
139 
140                 @property final InputStream stream ()
141                 {
142                         return buffer;
143                 }
144 
145                 /**************************************************************
146 
147                         Is this device redirected?
148 
149                         Returns:
150                         True if redirected, false otherwise.
151 
152                         Remarks:
153                         Reflects the console redirection status from when
154                         this module was instantiated.
155 
156                 **************************************************************/
157 
158                 @property final bool redirected ()
159                 {
160                         return redirect;
161                 }
162 
163                 /**************************************************************
164 
165                         Set redirection state to the provided boolean.
166 
167                         Remarks:
168                         Configure the console redirection status, where
169                         a redirected console is more efficient (dictates
170                         whether newline() performs automatic flushing or
171                         not.)
172 
173                 **************************************************************/
174 
175                 @property final Input redirected (bool yes)
176                 {
177                          redirect = yes;
178                          return this;
179                 }
180 
181                 /**************************************************************
182 
183                         Returns the configured source
184 
185                         Remarks:
186                         Provides access to the underlying mechanism for
187                         console input. Use this to retain prior state
188                         when temporarily switching inputs.
189 
190                 **************************************************************/
191 
192                 @property final InputStream input ()
193                 {
194                         return buffer.input;
195                 }
196 
197                 /**************************************************************
198 
199                         Divert input to an alternate source.
200 
201                 **************************************************************/
202 
203                 @property final Input input (InputStream source)
204                 {
205                         buffer.input = source;
206                         return this;
207                 }
208         }
209 
210 
211         /**********************************************************************
212 
213                 Console output accepts UTF8 only.
214 
215         **********************************************************************/
216 
217         class Output
218         {
219                 private Bout    buffer;
220                 private bool    redirect;
221 
222                 public  alias   append opCall;
223                 public  alias   flush  opCall;
224 
225                 /**************************************************************
226 
227                         Attach console output to the provided device.
228 
229                 **************************************************************/
230 
231                 private this (Conduit conduit, bool redirected)
232                 {
233                         redirect = redirected;
234                         buffer = new Bout (conduit);
235                 }
236 
237                 /**************************************************************
238 
239                         Append to the console. We accept UTF8 only, so
240                         all other encodings should be handled via some
241                         higher level API.
242 
243                 **************************************************************/
244 
245                 final Output append (const(char[]) x)
246                 {
247                         buffer.append (x.ptr, x.length);
248                         return this;
249                 }
250 
251                 /**************************************************************
252 
253                         Append content.
254 
255                         Params:
256                         other = An object with a useful toString() method.
257 
258                         Returns:
259                         Returns a chaining reference if all content was
260                         written. Throws an IOException indicating Eof or
261                         Eob if not.
262 
263                         Remarks:
264                         Append the result of other.toString() to the console.
265 
266                 **************************************************************/
267 
268                 final Output append (Object other)        
269                 {           
270                         return append (other.toString());
271                 }
272 
273                 /**************************************************************
274 
275                         Append a newline and flush the console buffer. If
276                         the output is redirected, flushing does not occur
277                         automatically.
278 
279                         Returns:
280                         Returns a chaining reference if content was written.
281                         Throws an IOException indicating Eof or Eob if not.
282 
283                         Remarks:
284                         Emit a newline into the buffer, and autoflush the
285                         current buffer content for an interactive console.
286                         Redirected consoles do not flush automatically on
287                         a newline.
288 
289                 **************************************************************/
290 
291                 @property final Output newline ()
292                 {
293                         buffer.append (Eol);
294                         if (redirect is false)
295                             buffer.flush();
296 
297                         return this;
298                 }
299 
300                 /**************************************************************
301 
302                         Explicitly flush console output.
303 
304                         Returns:
305                         Returns a chaining reference if content was written.
306                         Throws an IOException indicating Eof or Eob if not.
307 
308                         Remarks:
309                         Flushes the console buffer to attached conduit.
310 
311                 **************************************************************/
312 
313                 final Output flush ()
314                 {
315                         buffer.flush();
316                         return this;
317                 }
318 
319                 /**************************************************************
320 
321                         Return the associated stream.
322 
323                 **************************************************************/
324 
325                 @property final OutputStream stream ()
326                 {
327                         return buffer;
328                 }
329 
330                 /**************************************************************
331 
332                         Is this device redirected?
333 
334                         Returns:
335                         True if redirected, false otherwise.
336 
337                         Remarks:
338                         Reflects the console redirection status.
339 
340                 **************************************************************/
341 
342                 @property final bool redirected ()
343                 {
344                         return redirect;
345                 }
346 
347                 /**************************************************************
348 
349                         Set redirection state to the provided boolean.
350 
351                         Remarks:
352                         Configure the console redirection status, where
353                         a redirected console is more efficient (dictates
354                         whether newline() performs automatic flushing or
355                         not.)
356 
357                 **************************************************************/
358 
359                 @property final Output redirected (bool yes)
360                 {
361                          redirect = yes;
362                          return this;
363                 }
364 
365                 /**************************************************************
366 
367                         Returns the configured output sink.
368 
369                         Remarks:
370                         Provides access to the underlying mechanism for
371                         console output. Use this to retain prior state
372                         when temporarily switching outputs.
373 
374                 **************************************************************/
375 
376                 @property final OutputStream output ()
377                 {
378                         return buffer.output;
379                 }
380 
381                 /**************************************************************
382 
383                         Divert output to an alternate sink.
384 
385                 **************************************************************/
386 
387                 @property final Output output (OutputStream sink)
388                 {
389                         buffer.output = sink;
390                         return this;
391                 }
392         }
393 
394 
395         /***********************************************************************
396 
397                 Conduit for specifically handling the console devices. This
398                 takes care of certain implementation details on the Win32
399                 platform.
400 
401                 Note that the console is fixed at UTF8 for both linux and
402                 Win32. The latter is actually UTF16 native, but it's just
403                 too much hassle for a developer to handle the distinction
404                 when it really should be a no-brainer. In particular, the
405                 Win32 console functions don't work with redirection. This
406                 causes additional difficulties that can be ameliorated by
407                 asserting console I/O is always UTF8, in all modes.
408 
409         ***********************************************************************/
410 
411         class Conduit : Device
412         {
413                 private bool redirected = false;
414 
415                 /***********************************************************************
416 
417                         Return the name of this conduit.
418 
419                 ***********************************************************************/
420 
421                 override immutable(char)[] toString()
422                 {
423                         return "<console>";
424                 }
425 
426                 /***************************************************************
427 
428                         Windows-specific code.
429 
430                 ***************************************************************/
431 
432                 version (Win32)
433                         {
434                         private wchar[] input;
435                         private wchar[] output;
436 
437                         /*******************************************************
438 
439                                 Associate this device with a given handle.
440 
441                                 This is strictly for adapting existing
442                                 devices such as Stdout and friends.
443 
444                         *******************************************************/
445 
446                         this (size_t handle)
447                         {
448                                 input = new wchar [1024 * 1];
449                                 output = new wchar [1024 * 1];
450                                 reopen (handle);
451                         }
452 
453                         /*******************************************************
454 
455                                 Gain access to the standard IO handles.
456 
457                         *******************************************************/
458 
459                         private void reopen (size_t handle_)
460                         {
461                                 __gshared immutable DWORD[] id = [
462                                                                  cast(DWORD) -10,
463                                                                  cast(DWORD) -11,
464                                                                  cast(DWORD) -12
465                                                                  ];
466                                 __gshared immutable char[][] f = [
467                                                                  "CONIN$\0",
468                                                                  "CONOUT$\0",
469                                                                  "CONOUT$\0"
470                                                                  ];
471 
472                                 assert (handle_ < 3);
473                                 io.handle = GetStdHandle (id[handle_]);
474                                 if (io.handle is null || io.handle is INVALID_HANDLE_VALUE)
475                                     io.handle = CreateFileA ( cast(PCHAR) f[handle_].ptr,
476                                                 GENERIC_READ | GENERIC_WRITE,
477                                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
478                                                 null, OPEN_EXISTING, 0, cast(HANDLE) 0);
479 
480                                 // allow invalid handles to remain, since it
481                                 // may be patched later in some special cases
482                                 if (io.handle != INVALID_HANDLE_VALUE)
483                                    {
484                                    DWORD mode;
485                                    // are we redirecting? Note that we cannot
486                                    // use the 'appending' mode triggered via
487                                    // setting overlapped.Offset to -1, so we
488                                    // just track the byte-count instead
489                                    if (! GetConsoleMode (io.handle, &mode))
490                                          redirected = io.track = true;
491                                    }
492                         }
493 
494                         /*******************************************************
495 
496                                 Write a chunk of bytes to the console from
497                                 the provided array.
498 
499                         *******************************************************/
500 
501                         version (Win32SansUnicode)
502                                 {}
503                              else
504                                 {
505                                 override size_t write (const(void)[] src)
506                                 {
507                                 if (redirected)
508                                     return super.write (src);
509                                 else
510                                    {
511                                    DWORD i = src.length;
512 
513                                    // protect conversion from empty strings
514                                    if (i is 0)
515                                        return 0;
516 
517                                    // expand buffer appropriately
518                                    if (output.length < i)
519                                        output.length = i;
520 
521                                    // convert into output buffer
522                                    i = MultiByteToWideChar (CP_UTF8, 0, cast(PCHAR) src.ptr, i,
523                                                             output.ptr, output.length);
524 
525                                    // flush produced output
526                                    for (wchar* p=output.ptr, end=output.ptr+i; p < end; p+=i)
527                                        {
528                                        const int MAX = 16 * 1024;
529 
530                                        // avoid console limitation of 64KB
531                                        DWORD len = end - p;
532                                        if (len > MAX)
533                                           {
534                                           len = MAX;
535                                           // check for trailing surrogate ...
536                                           if ((p[len-1] & 0xfc00) is 0xdc00)
537                                                --len;
538                                           }
539                                        if (! WriteConsoleW (io.handle, p, len, &i, null))
540                                              error();
541                                        }
542                                    return src.length;
543                                    }
544                                 }
545                                 }
546 
547                         /*******************************************************
548 
549                                 Read a chunk of bytes from the console into
550                                 the provided array.
551 
552                         *******************************************************/
553 
554                         version (Win32SansUnicode)
555                                 {}
556                              else
557                                 {
558                                 protected override size_t read (void[] dst)
559                                 {
560                                 if (redirected)
561                                     return super.read (dst);
562                                 else
563                                    {
564                                    DWORD i = dst.length / 4;
565 
566                                    assert (i);
567 
568                                    if (i > input.length)
569                                        i = input.length;
570 
571                                    // read a chunk of wchars from the console
572                                    if (! ReadConsoleW (io.handle, input.ptr, i, &i, null))
573                                          error();
574 
575                                    // no input ~ go home
576                                    if (i is 0)
577                                        return Eof;
578 
579                                    // translate to utf8, directly into dst
580                                    i = WideCharToMultiByte (CP_UTF8, 0, input.ptr, i,
581                                                             cast(PCHAR) dst.ptr, dst.length, null, null);
582                                    if (i is 0)
583                                        error ();
584 
585                                    return i;
586                                    }
587                                 }
588                                 }
589 
590                         }
591                      else
592                         {
593                         /*******************************************************
594 
595                                 Associate this device with a given handle.
596 
597                                 This is strictly for adapting existing
598                                 devices such as Stdout and friends.
599 
600                         *******************************************************/
601 
602                         private this (size_t handle)
603                         {
604                                 this.handle = cast(Handle) handle;
605                                 redirected = (isatty(cast(int)handle) is 0);
606                         }
607                         }
608         }
609 }
610 
611 
612 /******************************************************************************
613 
614         Globals representing Console IO.
615 
616 ******************************************************************************/
617 
618 static __gshared Console.Input    Cin;  /// The standard input stream.
619 static __gshared Console.Output   Cout; /// The standard output stream.
620 static __gshared Console.Output   Cerr; /// The standard error stream.
621 
622 
623 /******************************************************************************
624 
625         Instantiate Console access.
626 
627 ******************************************************************************/
628 
629 shared static this ()
630 {
631         auto conduit = new Console.Conduit (0);
632         Cin  = new Console.Input (conduit, conduit.redirected);
633 
634         conduit = new Console.Conduit (1);
635         Cout = new Console.Output (conduit, conduit.redirected);
636 
637         conduit = new Console.Conduit (2);
638         Cerr = new Console.Output (conduit, conduit.redirected);
639 }
640 
641 /******************************************************************************
642 
643         Flush outputs before we exit.
644 
645         (Good idea from Frits Van Bommel.)
646 
647 ******************************************************************************/
648 
649 static ~this()
650 {
651    synchronized (Cout.stream)
652         Cout.flush();
653 
654    synchronized (Cerr.stream)
655         Cerr.flush();
656 }
657 
658 
659 /******************************************************************************
660 
661 ******************************************************************************/
662 
663 debug (Console)
664 {
665         void main()
666         {
667             Cout ("hello world").newline;
668         }
669 }