|
From: Phil S. <phi...@ho...> - 2001-11-02 04:40:52
|
Thanks Kevin! Great stuff. I tested it out on my Win 2000 box. My linux box is unavailable right now, but I can test it on that soon if no one else does. I'm a version of the script with the following modifications: The os checks were limited to Windows NT, so I went over to http://www.tolstoy.com/samizdat/sysprops.html and got a list of the os.name strings there and added them in along with some guesses. Under the Win95 stream I believe the correct command is 'command /c' so I poked that in, hope someone can verify that this works. I'm relying on http://www.easydos.com/dosindex.html for this info. Under win32, os.environ should be case-insensitive, so I added that in as well (this follows cpython's behaviour). Case-sensitivity should be maintained for everything else (but this is untested). I didn't really take an in-depth look at the module other than to verify that it seems to work as expected. Kevin Butler wrote: > > Finn Bock wrote: > > > > [Phil Surette] > > > > >I am planning to look into adding os.environment and os.system > > >support in, basically cribbing from how ant does this, but > > >I have not had time yet. > > > > I hope you and/or someone else find some time to look into it, it would > > be a valuable addition. I'm not sure what the right way to enable the > > os.environment should be. It isn't right to fill the os.environment dict > > whenever "os" is imported, that would be way to slow for all the program > > that never uses any enviroment variables. Any thoughts? > > So I started playing with this, and didn't stop until I was basically functional. > > Here's the code I came up with (it even has tests!) > > I'm afraid I wrote in on NT, so testing on Unixes would be appreciated... > > Comments/suggestions appreciated. > > kb > > ------------------------------------------------------------------------ > # os module enhancements for Jython > # includes System command and lazily populated environ object > # Kevin Butler <kev...@bi...> > # > # Features: > # - transparently populates environment on first access > # - transparently disables lazy evaluation for subsequent accesses > # - passes changed environment to system command > > # > # Does not: > # - ensure thread-safe initial population of environment > # - read both stderror & stdin, probably in separate threads > # - pass changed environment to child processes started with Runtime.exec (is this possible?) > # - handle commands with '"' in them correctly on systems that use sh > from java.lang import Runtime, System > from java.io import BufferedReader, InputStreamReader > from UserDict import UserDict > > class ShellExec: > defaultTemplate = 'sh -c "%s"' > templates = { > "Windows NT": 'cmd /c %s', > } > > def __init__( self, template=None ): > """template is a string formatting template to format command to execute.""" > if template: > self.template = template > else: > # determine the appropriate command template for the current OS > self.template = ShellExec.templates.get( > System.getProperty( "os.name" ), > ShellExec.defaultTemplate > ) > > def execute( self, cmd ): > """Execute cmd in a shell, and then return process""" > shellCmd = self.formatCmd( cmd ) > if _environPopulated: > # already populated environment, so pass environment to subprocess > env = self.formatEnvironment( environ ) > p = Runtime.getRuntime().exec( shellCmd, env ) > else: > # use default environment > p = Runtime.getRuntime().exec( shellCmd ) > return p > > def system( self, cmd ): > """Act like the standard library 'system' call. > Execute a command in a shell, and send output to stdout. > """ > p = self.execute( cmd ) > > # should read both stderror and stdout in separate threads... > stream = BufferedReader( InputStreamReader( p.getInputStream() )) > # we want immediate output, so don't call readLines > while 1: > line = stream.readLine() > if line is None: break > print line > > return p.waitFor() > > def readLines( self, stream ): > lines = [] > while 1: > line = stream.readLine() > if line is None: break > lines.append( line ) > return lines > > def formatCmd( self, cmd ): > """Format a command for execution in a shell. > """ > return self.template % cmd > > def formatEnvironment( self, env ): > """Format enviroment in lines suitable for Runtime.exec""" > lines = [] > for keyValue in env.items(): > lines.append( "%s=%s" % keyValue ) > return lines > > _shellExec = ShellExec() > > def system( cmd ): > return _shellExec.system( cmd ) > > def _populateEnviron(): > """Populate this module's 'environ' variable. > This unwraps the 'envelope' pattern of the default lazy dictionary. > """ > defaultEnvironCmd = "env" > environCmds = { > "Windows NT": "set" > } > # determine the appropriate command template for the current OS > environCmd = environCmds.get( > System.getProperty( "os.name" ), > defaultEnvironCmd > ) > > p = _shellExec.execute( environCmd ) > env = {} > stream = BufferedReader( InputStreamReader( p.getInputStream())) > for line in _shellExec.readLines( stream ): > i = line.index( '=' ) > env[ line[:i]] = line[i+1:] > > # assign to os.environ, so future accesses incur no overhead > global environ, _environPopulated > environ = env > _environPopulated = 1 > return env > > class LazyDict( UserDict ): > """A lazy-populating User Dictionary. > Lazy initialization is not thread-safe. > """ > def __init__( self, dict=None, populate=lambda: {} ): > """populate is a function that returns the populated dictionary""" > UserDict.__init__( self, dict ) > self.initialized = 0 > self.populate = populate > > def __populate( self ): > if not self.initialized: > self.initialized = 1 # not thread-safe! > # store as self.data so any 'set' sets in original as well > self.data = self.populate() > > ########## extend methods from UserDict by pre-populating > def __repr__(self): > self.__populate() > return UserDict.__repr__( self ) > def __cmp__(self, dict): > self.__populate() > return UserDict.__cmp__( self, dict ) > def __len__(self): > self.__populate() > return UserDict.__len__( self ) > def __getitem__(self, key): > self.__populate() > return UserDict.__getitem__( self, key ) > def __setitem__(self, key, item): > self.__populate() > UserDict.__setitem__( self, key, item ) > def __delitem__(self, key): > self.__populate() > UserDict.__delitem__( self, key ) > def clear(self): > self.__populate() > UserDict.clear( self ) > def copy(self): > self.__populate() > return UserDict.copy( self ) > def keys(self): > self.__populate() > return UserDict.keys( self ) > def items(self): > self.__populate() > return UserDict.items( self ) > def values(self): > self.__populate() > return UserDict.values( self ) > def has_key(self, key): > self.__populate() > return UserDict.has_key( self, key ) > def update(self, dict): > self.__populate() > UserDict.update( self, dict ) > def get(self, key, failobj=None): > self.__populate() > return UserDict.get( self, key, failobj ) > def setdefault(self, key, failobj=None): > self.__populate() > return UserDict.setdefault( self, key, failobj ) > def popitem(self): > self.__populate() > return UserDict.popitem( self ) > > # initialize environ to the placeholder > environ = LazyDict( populate=_populateEnviron ) > _environPopulated = 0 > > def putenv( key, value ): > environ[ key ] = value > > def getenv( key ): > return environ[ key ] > > def __test(): > key, value = "testKey", "testValue" > org = environ > testCmds = [ > "echo hello there", # no quotes, should output both words > "echo PATH=%PATH%", # should print PATH (on NT) > "echo %s=%%%s%%" % (key,key), # should print 'testKey=%testKey%' on NT before initialization, and 'testKey=testValue' after > "echo PATH=$PATH", # should print PATH (on Unix) > "echo %s=$%s" % (key,key), # should print 'testKey=testValue' on Unix after initialization > > # the following tests have double quotes in the commands. They work w/ NT's cmd shell, but not w/ sh. Escaping the quotes doesn't seem to help. > # 'echo "hello there"', # should output quotes on NT, no quotes on Unix. > # r'''python -c "import sys;sys.stdout.write( 'why\n' )"''', # should print 'why' to stdout. > # r'''python -c "import sys;sys.stderr.write( 'why\n' )"''', # should print 'why' to stderr, but it won't right now. Should complete, though! :-) > ] > > assert not _environPopulated, "before population, _environPopulated should be false" > > # test system - we should really grab the output of the system command, but we aren't > for cmd in testCmds: > print "\nExecuting %s with default environment" % cmd > assert not __shellExec.system( cmd ), "%s failed with default environment" % cmd > > # trigger initialization of environment > environ[ key ] = value > > assert _environPopulated, "after population, _environPopulated should be true" > assert org.get( key, None ) == value, "expected stub to have %s set" % key > assert environ.get( key, None ) == value, "expected real environment to have %s set" % key > > # test system using the non-default environment - should really grab output, but oh, well. > for cmd in testCmds: > print "\nExecuting %s with initialized environment" % cmd > assert not __shellExec.system( cmd ), "%s failed with initialized environment" % cmd > > assert environ.has_key( "PATH" ), "expected environment to have PATH attribute (this may not apply to all platforms!)" > > |