# interp.py - shell interpreter for pysh.
#
# Copyright 2007 Patrick Mezard
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
"""Implement the shell interpreter.
Most references are made to "The Open Group Base Specifications Issue 6".
<http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html>
"""
# TODO: document the fact input streams must implement fileno() so Popen will work correctly.
# it requires non-stdin stream to be implemented as files. Still to be tested...
# DOC: pathsep is used in PATH instead of ':'. Clearly, there are path syntax issues here.
# TODO: stop command execution upon error.
# TODO: sort out the filename/io_number mess. It should be possible to use filenames only.
# TODO: review subshell implementation
# TODO: test environment cloning for non-special builtins
# TODO: set -x should not rebuild commands from tokens, assignments/redirections are lost
# TODO: unit test for variable assignment
# TODO: test error management wrt error type/utility type
# TODO: test for binary output everywhere
# BUG: debug-parsing does not pass log file to PLY. Maybe a PLY upgrade is necessary.
import base64
import cPickle as pickle
import errno
import glob
import os
import re
import subprocess
import sys
import tempfile
try:
s = set()
del s
except NameError:
from Set import Set as set
import builtin
from sherrors import *
import pyshlex
import pyshyacc
def mappend(func, *args, **kargs):
"""Like map but assume func returns a list. Returned lists are merged into
a single one.
"""
return reduce(lambda a,b: a+b, map(func, *args, **kargs), [])
class FileWrapper:
"""File object wrapper to ease debugging.
Allow mode checking and implement file duplication through a simple
reference counting scheme. Not sure the latter is really useful since
only real file descriptors can be used.
"""
def __init__(self, mode, file, close=True):
if mode not in ('r', 'w', 'a'):
raise IOError('invalid mode: %s' % mode)
self._mode = mode
self._close = close
if isinstance(file, FileWrapper):
if file._refcount[0] <= 0:
raise IOError(0, 'Error')
self._refcount = file._refcount
self._refcount[0] += 1
self._file = file._file
else:
self._refcount = [1]
self._file = file
def dup(self):
return FileWrapper(self._mode, self, self._close)
def fileno(self):
"""fileno() should be only necessary for input streams."""
return self._file.fileno()
def read(self, size=-1):
if self._mode!='r':
raise IOError(0, 'Error')
return self._file.read(size)
def readlines(self, *args, **kwargs):
return self._file.readlines(*args, **kwargs)
def write(self, s):
if self._mode not in ('w', 'a'):
raise IOError(0, 'Error')
return self._file.write(s)
def flush(self):
self._file.flush()
def close(self):
if not self._refcount:
return
assert self._refcount[0] > 0
self._refcount[0] -= 1
if self._refcount[0] == 0:
self._mode = 'c'
if self._close:
self._file.close()
self._refcount = None
def mode(self):
return self._mode
def __getattr__(self, name):
if name == 'name':
self.name = getattr(self._file, name)
return self.name
else:
raise AttributeError(name)
def __del__(self):
self.close()
def win32_open_devnull(mode):
return open('NUL', mode)
class Redirections:
"""Stores open files and their mapping to pseudo-sh file descriptor.
"""
# BUG: redirections are not handled correctly: 1>&3 2>&3 3&g
|