""" open pipes, urls ... uniformly with Popen, urlopen ... open
openplus() opens pipes and some other objects that quack like files,
as well as files:
| pipe ... -- Popen() a shell
http:// ftp:// ... -- urlopen()
`ls $x`, `geturl web data` -- shell -> filename or url
~/a/b -- sh ls
.gz -- gunzip
- -- stdin / stdout
else -- the builtin open()
Users can then read e.g.
"| filter data | sort"
"| convert ... xx.jpg"
"`geturl web data`"
like files, just by importing openplus and changing open() -> openplus().
The idea is that if it walks like a file and quacks like a file,
i.e. can open/get/put a data stream, or generates a file name or object,
let it be used anywhere a "real" file can be used.
(However xxopen() may lack some methods of __builtin__.open() .)
Error handling is left up to Popen() ... open()
except for `shell expand`, which writes an error message to errout (sys.stderr)
if the result is not a url or os.path.isfile().
"""
# wibni: > awindow, < aparamwindow -- pyqt
# reinventing this wheel ...
import gzip
import os.path
import re
import subprocess # py 2.4
import sys
import urllib2
__version__ = __date__ = "11dec2008"
__author__ = "Denis Bzowy"
_urlpat = re.compile( "[a-z+]+://" ) # http:// ftp:// ... 20+ in urlparse
#-------------------------------------------------------------------------------
def openplus( filelike, rw='r', errout=sys.stderr ):
""" open pipes, urls ... uniformly with Popen, urlopen ... open """
start = filelike[0]
if start == '|':
if rw[0] == 'r':
return subprocess.Popen( filelike[1:], shell=True,
stdout=subprocess.PIPE) .stdout
# for line in a pipe: see http://bugs.python.org/issue3907
else:
return subprocess.Popen( filelike[1:], shell=True,
stdin=subprocess.PIPE) .stdin
elif _urlpat.match( filelike ):
return urllib2.urlopen( filelike )
elif start == '~': # ~/x -> $HOME/x, ~sam/x -> sam's home /x (aka glob)
ls = sh( "/bin/ls -d " + filelike, errout ) # not all shells
return open( ls, rw )
elif start == '`':
# sh: `ls ${x-default}`, `newest *.py`, `geturl web data`
# -> filename or url, not | etc.
shexpand = sh( filelike.strip('`'), errout )
if _urlpat.match( shexpand ):
return urllib2.urlopen( filelike )
if os.path.isfile( shexpand ):
return open( shexpand, rw )
if errout: # logger ?
print >>errout, "error: openplus( \"%s\" ) = \"%s\" " \
"is not a file or url" % (filelike, shexpand)
return None
# ! eval pyfunc() -> a file-like object with next() etc.
# elif start == '!':
# return eval( filelike[1:] ) # wrong globals(), unsafe, mttiw
elif filelike.endswith( ".gz" ):
return gzip.GzipFile( filelike, rw )
elif filelike == '-':
return (sys.stdin if rw[0] == 'r' else sys.stdout)
else:
return open( filelike, rw ) # a "real" file
#...............................................................................
def sh( cmd_arg_str, errout=sys.stderr ):
r""" Popen a shell -> line or "line1 \n line2 ...", trim last \n """
# crashes after pyqt QApplication() with mac py 2.5.1, pyqt 4.4.2
# subprocess.py _communicate select.error: (4, 'Interrupted system call')
# see http://bugs.python.org/issue1068268 subprocess is not EINTR-safe
# QProcess instead of Popen works
(lines, err) = subprocess.Popen( cmd_arg_str,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True ) \
.communicate() # wait til process ends
if errout and err:
print >>errout, err
# trim the last \n so sh( "ls xx" ) -> "xx" not "xx\n"
# and split( "\n" ) -> no extra ""
return lines[:-1] if (lines and lines[-1] == "\n") \
else lines
# end openplus.py