1# encoding: utf-8
2"""
3IO related utilities.
4"""
5
6# Copyright (c) IPython Development Team.
7# Distributed under the terms of the Modified BSD License.
8
9
10
11import atexit
12import os
13import sys
14import tempfile
15import warnings
16from warnings import warn
17
18from IPython.utils.decorators import undoc
19from .capture import CapturedIO, capture_output
20
21@undoc
22class IOStream:
23
24    def __init__(self, stream, fallback=None):
25        warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
26             DeprecationWarning, stacklevel=2)
27        if not hasattr(stream,'write') or not hasattr(stream,'flush'):
28            if fallback is not None:
29                stream = fallback
30            else:
31                raise ValueError("fallback required, but not specified")
32        self.stream = stream
33        self._swrite = stream.write
34
35        # clone all methods not overridden:
36        def clone(meth):
37            return not hasattr(self, meth) and not meth.startswith('_')
38        for meth in filter(clone, dir(stream)):
39            try:
40                val = getattr(stream, meth)
41            except AttributeError:
42                pass
43            else:
44                setattr(self, meth, val)
45
46    def __repr__(self):
47        cls = self.__class__
48        tpl = '{mod}.{cls}({args})'
49        return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
50
51    def write(self,data):
52        warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
53             DeprecationWarning, stacklevel=2)
54        try:
55            self._swrite(data)
56        except:
57            try:
58                # print handles some unicode issues which may trip a plain
59                # write() call.  Emulate write() by using an empty end
60                # argument.
61                print(data, end='', file=self.stream)
62            except:
63                # if we get here, something is seriously broken.
64                print('ERROR - failed to write data to stream:', self.stream,
65                      file=sys.stderr)
66
67    def writelines(self, lines):
68        warn('IOStream is deprecated since IPython 5.0, use sys.{stdin,stdout,stderr} instead',
69             DeprecationWarning, stacklevel=2)
70        if isinstance(lines, str):
71            lines = [lines]
72        for line in lines:
73            self.write(line)
74
75    # This class used to have a writeln method, but regular files and streams
76    # in Python don't have this method. We need to keep this completely
77    # compatible so we removed it.
78
79    @property
80    def closed(self):
81        return self.stream.closed
82
83    def close(self):
84        pass
85
86# setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
87devnull = open(os.devnull, 'w')
88atexit.register(devnull.close)
89
90# io.std* are deprecated, but don't show our own deprecation warnings
91# during initialization of the deprecated API.
92with warnings.catch_warnings():
93    warnings.simplefilter('ignore', DeprecationWarning)
94    stdin = IOStream(sys.stdin, fallback=devnull)
95    stdout = IOStream(sys.stdout, fallback=devnull)
96    stderr = IOStream(sys.stderr, fallback=devnull)
97
98class Tee(object):
99    """A class to duplicate an output stream to stdout/err.
100
101    This works in a manner very similar to the Unix 'tee' command.
102
103    When the object is closed or deleted, it closes the original file given to
104    it for duplication.
105    """
106    # Inspired by:
107    # http://mail.python.org/pipermail/python-list/2007-May/442737.html
108
109    def __init__(self, file_or_name, mode="w", channel='stdout'):
110        """Construct a new Tee object.
111
112        Parameters
113        ----------
114        file_or_name : filename or open filehandle (writable)
115          File that will be duplicated
116
117        mode : optional, valid mode for open().
118          If a filename was give, open with this mode.
119
120        channel : str, one of ['stdout', 'stderr']
121        """
122        if channel not in ['stdout', 'stderr']:
123            raise ValueError('Invalid channel spec %s' % channel)
124
125        if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
126            self.file = file_or_name
127        else:
128            self.file = open(file_or_name, mode)
129        self.channel = channel
130        self.ostream = getattr(sys, channel)
131        setattr(sys, channel, self)
132        self._closed = False
133
134    def close(self):
135        """Close the file and restore the channel."""
136        self.flush()
137        setattr(sys, self.channel, self.ostream)
138        self.file.close()
139        self._closed = True
140
141    def write(self, data):
142        """Write data to both channels."""
143        self.file.write(data)
144        self.ostream.write(data)
145        self.ostream.flush()
146
147    def flush(self):
148        """Flush both channels."""
149        self.file.flush()
150        self.ostream.flush()
151
152    def __del__(self):
153        if not self._closed:
154            self.close()
155
156
157def ask_yes_no(prompt, default=None, interrupt=None):
158    """Asks a question and returns a boolean (y/n) answer.
159
160    If default is given (one of 'y','n'), it is used if the user input is
161    empty. If interrupt is given (one of 'y','n'), it is used if the user
162    presses Ctrl-C. Otherwise the question is repeated until an answer is
163    given.
164
165    An EOF is treated as the default answer.  If there is no default, an
166    exception is raised to prevent infinite loops.
167
168    Valid answers are: y/yes/n/no (match is not case sensitive)."""
169
170    answers = {'y':True,'n':False,'yes':True,'no':False}
171    ans = None
172    while ans not in answers.keys():
173        try:
174            ans = input(prompt+' ').lower()
175            if not ans:  # response was an empty string
176                ans = default
177        except KeyboardInterrupt:
178            if interrupt:
179                ans = interrupt
180            print("\r")
181        except EOFError:
182            if default in answers.keys():
183                ans = default
184                print()
185            else:
186                raise
187
188    return answers[ans]
189
190
191def temp_pyfile(src, ext='.py'):
192    """Make a temporary python file, return filename and filehandle.
193
194    Parameters
195    ----------
196    src : string or list of strings (no need for ending newlines if list)
197      Source code to be written to the file.
198
199    ext : optional, string
200      Extension for the generated file.
201
202    Returns
203    -------
204    (filename, open filehandle)
205      It is the caller's responsibility to close the open file and unlink it.
206    """
207    fname = tempfile.mkstemp(ext)[1]
208    with open(fname,'w') as f:
209        f.write(src)
210        f.flush()
211    return fname
212
213@undoc
214def atomic_writing(*args, **kwargs):
215    """DEPRECATED: moved to notebook.services.contents.fileio"""
216    warn("IPython.utils.io.atomic_writing has moved to notebook.services.contents.fileio since IPython 4.0", DeprecationWarning, stacklevel=2)
217    from notebook.services.contents.fileio import atomic_writing
218    return atomic_writing(*args, **kwargs)
219
220@undoc
221def raw_print(*args, **kw):
222    """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
223    warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
224
225    print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
226          file=sys.__stdout__)
227    sys.__stdout__.flush()
228
229@undoc
230def raw_print_err(*args, **kw):
231    """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
232    warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
233
234    print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
235          file=sys.__stderr__)
236    sys.__stderr__.flush()
237
238# used by IPykernel <- 4.9. Removed during IPython 7-dev period and re-added
239# Keep for a version or two then should remove
240rprint = raw_print
241rprinte = raw_print_err
242
243@undoc
244def unicode_std_stream(stream='stdout'):
245    """DEPRECATED, moved to nbconvert.utils.io"""
246    warn("IPython.utils.io.unicode_std_stream has moved to nbconvert.utils.io since IPython 4.0", DeprecationWarning, stacklevel=2)
247    from nbconvert.utils.io import unicode_std_stream
248    return unicode_std_stream(stream)
249