1"""
2Abstraction of CLI Input.
3"""
4from __future__ import unicode_literals
5
6from .utils import DummyContext, is_windows
7from abc import ABCMeta, abstractmethod
8from six import with_metaclass
9
10import io
11import os
12import sys
13
14if is_windows():
15    from .terminal.win32_input import raw_mode, cooked_mode
16else:
17    from .terminal.vt100_input import raw_mode, cooked_mode
18
19__all__ = (
20    'Input',
21    'StdinInput',
22    'PipeInput',
23)
24
25
26class Input(with_metaclass(ABCMeta, object)):
27    """
28    Abstraction for any input.
29
30    An instance of this class can be given to the constructor of a
31    :class:`~prompt_toolkit.interface.CommandLineInterface` and will also be
32    passed to the :class:`~prompt_toolkit.eventloop.base.EventLoop`.
33    """
34    @abstractmethod
35    def fileno(self):
36        """
37        Fileno for putting this in an event loop.
38        """
39
40    @abstractmethod
41    def read(self):
42        """
43        Return text from the input.
44        """
45
46    @abstractmethod
47    def raw_mode(self):
48        """
49        Context manager that turns the input into raw mode.
50        """
51
52    @abstractmethod
53    def cooked_mode(self):
54        """
55        Context manager that turns the input into cooked mode.
56        """
57
58
59class StdinInput(Input):
60    """
61    Simple wrapper around stdin.
62    """
63    def __init__(self, stdin=None):
64        self.stdin = stdin or sys.stdin
65
66        # The input object should be a TTY.
67        assert self.stdin.isatty()
68
69        # Test whether the given input object has a file descriptor.
70        # (Idle reports stdin to be a TTY, but fileno() is not implemented.)
71        try:
72            # This should not raise, but can return 0.
73            self.stdin.fileno()
74        except io.UnsupportedOperation:
75            if 'idlelib.run' in sys.modules:
76                raise io.UnsupportedOperation(
77                    'Stdin is not a terminal. Running from Idle is not supported.')
78            else:
79                raise io.UnsupportedOperation('Stdin is not a terminal.')
80
81    def __repr__(self):
82        return 'StdinInput(stdin=%r)' % (self.stdin,)
83
84    def raw_mode(self):
85        return raw_mode(self.stdin.fileno())
86
87    def cooked_mode(self):
88        return cooked_mode(self.stdin.fileno())
89
90    def fileno(self):
91        return self.stdin.fileno()
92
93    def read(self):
94        return self.stdin.read()
95
96
97class PipeInput(Input):
98    """
99    Input that is send through a pipe.
100    This is useful if we want to send the input programatically into the
101    interface, but still use the eventloop.
102
103    Usage::
104
105        input = PipeInput()
106        input.send('inputdata')
107    """
108    def __init__(self):
109        self._r, self._w = os.pipe()
110
111    def fileno(self):
112        return self._r
113
114    def read(self):
115        return os.read(self._r)
116
117    def send_text(self, data):
118        " Send text to the input. "
119        os.write(self._w, data.encode('utf-8'))
120
121    # Deprecated alias for `send_text`.
122    send = send_text
123
124    def raw_mode(self):
125        return DummyContext()
126
127    def cooked_mode(self):
128        return DummyContext()
129
130    def close(self):
131        " Close pipe fds. "
132        os.close(self._r)
133        os.close(self._w)
134        self._r = None
135        self._w = None
136