io.py
321 lines
| 9.9 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2498 | # encoding: utf-8 | ||
""" | ||||
IO related utilities. | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (C) 2008-2009 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
Fernando Perez
|
r2856 | from __future__ import print_function | ||
Brian Granger
|
r2498 | |||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
import sys | ||||
import tempfile | ||||
#----------------------------------------------------------------------------- | ||||
# Code | ||||
#----------------------------------------------------------------------------- | ||||
class IOStream: | ||||
MinRK
|
r3800 | def __init__(self,stream, fallback=None): | ||
Brian Granger
|
r2498 | if not hasattr(stream,'write') or not hasattr(stream,'flush'): | ||
MinRK
|
r3800 | if fallback is not None: | ||
stream = fallback | ||||
else: | ||||
raise ValueError("fallback required, but not specified") | ||||
Brian Granger
|
r2498 | self.stream = stream | ||
self._swrite = stream.write | ||||
MinRK
|
r3800 | |||
# clone all methods not overridden: | ||||
def clone(meth): | ||||
return not hasattr(self, meth) and not meth.startswith('_') | ||||
for meth in filter(clone, dir(stream)): | ||||
setattr(self, meth, getattr(stream, meth)) | ||||
Brian Granger
|
r2498 | def write(self,data): | ||
try: | ||||
self._swrite(data) | ||||
except: | ||||
try: | ||||
# print handles some unicode issues which may trip a plain | ||||
Fernando Perez
|
r2856 | # write() call. Emulate write() by using an empty end | ||
# argument. | ||||
print(data, end='', file=self.stream) | ||||
Brian Granger
|
r2498 | except: | ||
# if we get here, something is seriously broken. | ||||
Fernando Perez
|
r2856 | print('ERROR - failed to write data to stream:', self.stream, | ||
file=sys.stderr) | ||||
MinRK
|
r3800 | |||
def writelines(self, lines): | ||||
if isinstance(lines, basestring): | ||||
lines = [lines] | ||||
for line in lines: | ||||
self.write(line) | ||||
Brian Granger
|
r2498 | |||
Brian Granger
|
r2504 | # This class used to have a writeln method, but regular files and streams | ||
# in Python don't have this method. We need to keep this completely | ||||
# compatible so we removed it. | ||||
MinRK
|
r3800 | @property | ||
def closed(self): | ||||
return self.stream.closed | ||||
Brian Granger
|
r2498 | def close(self): | ||
pass | ||||
class IOTerm: | ||||
""" Term holds the file or file-like objects for handling I/O operations. | ||||
These are normally just sys.stdin, sys.stdout and sys.stderr but for | ||||
Windows they can can replaced to allow editing the strings before they are | ||||
displayed.""" | ||||
# In the future, having IPython channel all its I/O operations through | ||||
# this class will make it easier to embed it into other environments which | ||||
# are not a normal terminal (such as a GUI-based shell) | ||||
MinRK
|
r3800 | def __init__(self, stdin=None, stdout=None, stderr=None): | ||
self.stdin = IOStream(stdin, sys.stdin) | ||||
self.stdout = IOStream(stdout, sys.stdout) | ||||
self.stderr = IOStream(stderr, sys.stderr) | ||||
# setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr | ||||
stdin = IOStream(sys.stdin) | ||||
stdout = IOStream(sys.stdout) | ||||
stderr = IOStream(sys.stderr) | ||||
Brian Granger
|
r2498 | |||
class Tee(object): | ||||
"""A class to duplicate an output stream to stdout/err. | ||||
This works in a manner very similar to the Unix 'tee' command. | ||||
When the object is closed or deleted, it closes the original file given to | ||||
it for duplication. | ||||
""" | ||||
# Inspired by: | ||||
# http://mail.python.org/pipermail/python-list/2007-May/442737.html | ||||
def __init__(self, file_or_name, mode=None, channel='stdout'): | ||||
"""Construct a new Tee object. | ||||
Parameters | ||||
---------- | ||||
file_or_name : filename or open filehandle (writable) | ||||
File that will be duplicated | ||||
mode : optional, valid mode for open(). | ||||
If a filename was give, open with this mode. | ||||
channel : str, one of ['stdout', 'stderr'] | ||||
""" | ||||
if channel not in ['stdout', 'stderr']: | ||||
raise ValueError('Invalid channel spec %s' % channel) | ||||
if hasattr(file, 'write') and hasattr(file, 'seek'): | ||||
self.file = file_or_name | ||||
else: | ||||
self.file = open(file_or_name, mode) | ||||
self.channel = channel | ||||
self.ostream = getattr(sys, channel) | ||||
setattr(sys, channel, self) | ||||
self._closed = False | ||||
def close(self): | ||||
"""Close the file and restore the channel.""" | ||||
self.flush() | ||||
setattr(sys, self.channel, self.ostream) | ||||
self.file.close() | ||||
self._closed = True | ||||
def write(self, data): | ||||
"""Write data to both channels.""" | ||||
self.file.write(data) | ||||
self.ostream.write(data) | ||||
self.ostream.flush() | ||||
def flush(self): | ||||
"""Flush both channels.""" | ||||
self.file.flush() | ||||
self.ostream.flush() | ||||
def __del__(self): | ||||
if not self._closed: | ||||
self.close() | ||||
def file_read(filename): | ||||
"""Read a file and close it. Returns the file source.""" | ||||
fobj = open(filename,'r'); | ||||
source = fobj.read(); | ||||
fobj.close() | ||||
return source | ||||
def file_readlines(filename): | ||||
"""Read a file and close it. Returns the file source using readlines().""" | ||||
fobj = open(filename,'r'); | ||||
lines = fobj.readlines(); | ||||
fobj.close() | ||||
return lines | ||||
def raw_input_multi(header='', ps1='==> ', ps2='..> ',terminate_str = '.'): | ||||
"""Take multiple lines of input. | ||||
A list with each line of input as a separate element is returned when a | ||||
termination string is entered (defaults to a single '.'). Input can also | ||||
terminate via EOF (^D in Unix, ^Z-RET in Windows). | ||||
Lines of input which end in \\ are joined into single entries (and a | ||||
secondary continuation prompt is issued as long as the user terminates | ||||
lines with \\). This allows entering very long strings which are still | ||||
meant to be treated as single entities. | ||||
""" | ||||
try: | ||||
if header: | ||||
header += '\n' | ||||
lines = [raw_input(header + ps1)] | ||||
except EOFError: | ||||
return [] | ||||
terminate = [terminate_str] | ||||
try: | ||||
while lines[-1:] != terminate: | ||||
new_line = raw_input(ps1) | ||||
while new_line.endswith('\\'): | ||||
new_line = new_line[:-1] + raw_input(ps2) | ||||
lines.append(new_line) | ||||
return lines[:-1] # don't return the termination command | ||||
except EOFError: | ||||
Thomas Spura
|
r4430 | print() | ||
Brian Granger
|
r2498 | return lines | ||
def raw_input_ext(prompt='', ps2='... '): | ||||
"""Similar to raw_input(), but accepts extended lines if input ends with \\.""" | ||||
line = raw_input(prompt) | ||||
while line.endswith('\\'): | ||||
line = line[:-1] + raw_input(ps2) | ||||
return line | ||||
def ask_yes_no(prompt,default=None): | ||||
"""Asks a question and returns a boolean (y/n) answer. | ||||
If default is given (one of 'y','n'), it is used if the user input is | ||||
empty. Otherwise the question is repeated until an answer is given. | ||||
An EOF is treated as the default answer. If there is no default, an | ||||
exception is raised to prevent infinite loops. | ||||
Valid answers are: y/yes/n/no (match is not case sensitive).""" | ||||
answers = {'y':True,'n':False,'yes':True,'no':False} | ||||
ans = None | ||||
while ans not in answers.keys(): | ||||
try: | ||||
ans = raw_input(prompt+' ').lower() | ||||
if not ans: # response was an empty string | ||||
ans = default | ||||
except KeyboardInterrupt: | ||||
pass | ||||
except EOFError: | ||||
if default in answers.keys(): | ||||
ans = default | ||||
Thomas Spura
|
r4430 | print() | ||
Brian Granger
|
r2498 | else: | ||
raise | ||||
return answers[ans] | ||||
class NLprinter: | ||||
"""Print an arbitrarily nested list, indicating index numbers. | ||||
An instance of this class called nlprint is available and callable as a | ||||
function. | ||||
nlprint(list,indent=' ',sep=': ') -> prints indenting each level by 'indent' | ||||
and using 'sep' to separate the index from the value. """ | ||||
def __init__(self): | ||||
self.depth = 0 | ||||
def __call__(self,lst,pos='',**kw): | ||||
"""Prints the nested list numbering levels.""" | ||||
kw.setdefault('indent',' ') | ||||
kw.setdefault('sep',': ') | ||||
kw.setdefault('start',0) | ||||
kw.setdefault('stop',len(lst)) | ||||
# we need to remove start and stop from kw so they don't propagate | ||||
# into a recursive call for a nested list. | ||||
start = kw['start']; del kw['start'] | ||||
stop = kw['stop']; del kw['stop'] | ||||
if self.depth == 0 and 'header' in kw.keys(): | ||||
Fernando Perez
|
r2856 | print(kw['header']) | ||
Brian Granger
|
r2498 | |||
for idx in range(start,stop): | ||||
elem = lst[idx] | ||||
Thomas Kluyver
|
r4051 | newpos = pos + str(idx) | ||
Brian Granger
|
r2498 | if type(elem)==type([]): | ||
self.depth += 1 | ||||
Thomas Kluyver
|
r4051 | self.__call__(elem, newpos+",", **kw) | ||
Brian Granger
|
r2498 | self.depth -= 1 | ||
else: | ||||
Thomas Kluyver
|
r4051 | print(kw['indent']*self.depth + newpos + kw["sep"] + repr(elem)) | ||
Brian Granger
|
r2498 | |||
nlprint = NLprinter() | ||||
def temp_pyfile(src, ext='.py'): | ||||
"""Make a temporary python file, return filename and filehandle. | ||||
Parameters | ||||
---------- | ||||
src : string or list of strings (no need for ending newlines if list) | ||||
Source code to be written to the file. | ||||
ext : optional, string | ||||
Extension for the generated file. | ||||
Returns | ||||
------- | ||||
(filename, open filehandle) | ||||
It is the caller's responsibility to close the open file and unlink it. | ||||
""" | ||||
fname = tempfile.mkstemp(ext)[1] | ||||
f = open(fname,'w') | ||||
f.write(src) | ||||
f.flush() | ||||
return fname, f | ||||
Fernando Perez
|
r2874 | def raw_print(*args, **kw): | ||
"""Raw print to sys.__stdout__, otherwise identical interface to print().""" | ||||
Fernando Perez
|
r2856 | |||
print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'), | ||||
file=sys.__stdout__) | ||||
sys.__stdout__.flush() | ||||
Fernando Perez
|
r2874 | def raw_print_err(*args, **kw): | ||
"""Raw print to sys.__stderr__, otherwise identical interface to print().""" | ||||
Fernando Perez
|
r2838 | |||
Fernando Perez
|
r2856 | print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'), | ||
file=sys.__stderr__) | ||||
Fernando Perez
|
r2838 | sys.__stderr__.flush() | ||
Fernando Perez
|
r2874 | |||
# Short aliases for quick debugging, do NOT use these in production code. | ||||
rprint = raw_print | ||||
rprinte = raw_print_err | ||||