Commit 26058b5b authored by Kirill Smelkov's avatar Kirill Smelkov

gpython: Factor-out options parsing into getopt-style _IGetOpt helper

Python allows multiple single-letter options and their arguments to be
coming on single argument, for example:

	python -OQc'print 1'
	python -OQc 'print 1'
	python -OQ -c 'print 1'
	etc...

We are currently trying to handle that at every option, but even though
it kind of works, it is limited and will break once we will start adding
options.

-> Refactor options parsing into getopt-style helper. We cannot use
getopt itself because it will complain e.g. on `gpython file.py
--my-custom-opt` that my-custom-opt is unexpected.
parent 167912d3
......@@ -41,6 +41,9 @@ $GPYTHON_RUNTIME=threads.
from __future__ import print_function, absolute_import
_pyopt = "c:m:VW:X:"
_pyopt_long = ('version',)
# pymain mimics `python ...`
#
# argv is what comes via `...` without first [0] for python.
......@@ -53,20 +56,17 @@ def pymain(argv, init=None):
version = False # set if `-V`
warnoptions = [] # collected `-W arg`
while len(argv) > 0:
igetopt = _IGetOpt(argv, _pyopt, _pyopt_long)
for (opt, arg) in igetopt:
# -V / --version
if argv[0] in ('-V', '--version'):
if opt in ('-V', '--version'):
version = True
break
# -c command
elif argv[0].startswith('-c'):
cmd = argv[0][2:] # -c<command> also works
argv = argv[1:]
if cmd == '':
cmd = argv[0]
argv = argv[1:]
sys.argv = ['-c'] + argv # python leaves '-c' as argv[0]
elif opt == '-c':
cmd = arg
sys.argv = ['-c'] + igetopt.argv # python leaves '-c' as argv[0]
sys.path.insert(0, '') # cwd
def run():
import six
......@@ -78,13 +78,9 @@ def pymain(argv, init=None):
break
# -m module
elif argv[0].startswith('-m'):
mod = argv[0][2:] # -m<module> also works
argv = argv[1:]
if mod == '':
mod = argv[0]
argv = argv[1:]
sys.argv = [mod] + argv
elif opt == '-m':
mod = arg
sys.argv = [mod] + igetopt.argv
# sys.path <- cwd
# NOTE python2 injects '', while python3 injects realpath('')
# we stick to python3 behaviour, as it is more sane because e.g.
......@@ -98,20 +94,17 @@ def pymain(argv, init=None):
break
# -W arg (warning control)
elif argv[0].startswith('-W'):
wopt = argv[0][2:] # -W<arg> also works
argv = argv[1:]
if wopt == '':
wopt = argv[0]
argv = argv[1:]
warnoptions.append(wopt)
elif argv[0].startswith('-'):
print("unknown option: '%s'" % argv[0], file=sys.stderr)
elif opt == '-W':
warnoptions.append(arg)
else:
print("unknown option: '%s'" % opt, file=sys.stderr)
sys.exit(2)
argv = igetopt.argv
if run is None:
# file
else:
if len(argv) > 0:
sys.argv = argv
filepath = argv[0]
......@@ -124,33 +117,32 @@ def pymain(argv, init=None):
'__doc__': None,
'__package__': None}
_execfile(filepath, g)
break
# interactive console
if run is None:
sys.argv = ['']
sys.path.insert(0, '') # cwd
def run():
import code
from six.moves import input as raw_input
# like code.interact() but with overridden console.raw_input _and_
# readline imported (code.interact mutually excludes those two).
try:
import readline # enable interactive editing
except ImportError:
pass
console = code.InteractiveConsole()
def _(prompt):
# python behaviour: don't print '>>>' if stdin is not a tty
# (builtin raw_input always prints prompt)
if not sys.stdin.isatty():
prompt=''
return raw_input(prompt)
console.raw_input = _
console.interact()
# interactive console
else:
sys.argv = ['']
sys.path.insert(0, '') # cwd
def run():
import code
from six.moves import input as raw_input
# like code.interact() but with overridden console.raw_input _and_
# readline imported (code.interact mutually excludes those two).
try:
import readline # enable interactive editing
except ImportError:
pass
console = code.InteractiveConsole()
def _(prompt):
# python behaviour: don't print '>>>' if stdin is not a tty
# (builtin raw_input always prints prompt)
if not sys.stdin.isatty():
prompt=''
return raw_input(prompt)
console.raw_input = _
console.interact()
# ---- options processed -> start the interpreter ----
......@@ -299,35 +291,33 @@ def main():
raise RuntimeError('gpython: internal error: sys.path does not contain dirname(gpython):'
'\n\n\tgpython:\t%s\n\tsys.path:\t%s' % (exe, sys.path))
# extract and process -X
# extract and process `-X gpython.*`
# -X gpython.runtime=(gevent|threads) + $GPYTHON_RUNTIME
sys._xoptions = getattr(sys, '_xoptions', {})
argv_ = []
gpy_runtime = os.getenv('GPYTHON_RUNTIME', 'gevent')
while len(argv) > 0:
arg = argv[0]
argv = argv[1:]
igetopt = _IGetOpt(argv, _pyopt, _pyopt_long)
for (opt, arg) in igetopt:
if opt == '-X':
if arg.startswith('gpython.'):
if arg.startswith('gpython.runtime='):
gpy_runtime = arg[len('gpython.runtime='):]
sys._xoptions['gpython.runtime'] = gpy_runtime
if not arg.startswith('-X'):
argv_.append(arg)
# continue looking for -X only until options end
if not arg.startswith('-'):
break
continue
else:
raise RuntimeError('gpython: unknown -X option %s' % opt)
# -X <opt>
opt = arg[2:] # -X<opt>
if opt == '':
opt = argv[0] # -X <opt>
argv = argv[1:]
continue
if opt.startswith('gpython.runtime='):
gpy_runtime = opt[len('gpython.runtime='):]
sys._xoptions['gpython.runtime'] = gpy_runtime
argv_.append(opt)
if arg is not None:
argv_.append(arg)
else:
raise RuntimeError('gpython: unknown -X option %s' % opt)
argv = argv_ + argv
# options after -c / -m are not for python itself
if opt in ('-c', '-m'):
break
argv = argv_ + igetopt.argv
# init initializes according to selected runtime
# it is called after options are parsed and sys.path is setup correspondingly.
......@@ -382,5 +372,106 @@ def _is_buildout_script(path):
return ('\nsys.path[0:0] = [\n' in src)
# _IGetOpt provides getopt-style incremental options parsing.
# ( we cannot use getopt directly, because it complains about "unrecognized options"
# on e.g. `gpython file.py -opt` )
class _IGetOpt:
def __init__(self, argv, shortopts, longopts):
self.argv = argv
self._opts = {} # opt -> bool(arg-required)
self._shortopttail = '' # current tail of short options from e.g. -abcd
# parse shortopts -> ._opts
opt = None
for _ in shortopts:
if _ == ':':
if opt is None:
raise RuntimeError("invalid shortopts: unexpected ':'")
self._opts['-'+opt] = True
opt = None # prevent ::
else:
opt = _
if opt in self._opts:
raise RuntimeError("invalid shortopts: double '%s'" % opt)
self._opts['-'+opt] = False
# parse longopts -> ._opts
for opt in longopts:
arg_required = (opt[-1:] == '=')
if arg_required:
opt = opt[:-1]
self._opts['--'+opt] = arg_required
def __iter__(self):
return self
def __next__(self):
# yield e.g. -b -c -d from -abcd
if len(self._shortopttail) > 0:
opt = '-'+self._shortopttail[0]
self._shortopttail = self._shortopttail[1:]
if opt not in self._opts:
raise RuntimeError('unexpected option %s' % opt)
arg = None
if self._opts[opt]: # arg required
if len(self._shortopttail) > 0:
# -o<arg>
arg = self._shortopttail
self._shortopttail = ''
else:
# -o <arg>
if len(self.argv) == 0:
raise RuntimeError('option %s requires an argument' % opt)
arg = self.argv[0]
self.argv = self.argv[1:]
return (opt, arg)
# ._shortopttail is empty - proceed with .argv
if len(self.argv) == 0:
raise StopIteration # end of argv
opt = self.argv[0]
if not opt.startswith('-'):
raise StopIteration # not an option
if opt == '-':
raise StopIteration # not an option
self.argv = self.argv[1:]
if opt == '--':
raise StopIteration # options -- args delimiter
# short option
if not opt.startswith('--'):
self._shortopttail = opt[1:]
return self.__next__()
# long option
arg = None
if '=' in opt:
opt, arg = opt.split('=')
if opt not in self._opts:
raise RuntimeError('unexpected option %s' % opt)
arg_required = self._opts[opt]
if not arg_required:
if arg is not None:
raise RuntimeError('option %s requires no argument' % opt)
else:
if arg is None:
if len(self.argv) == 0:
raise RuntimeError('option %s requires no argument' % opt)
arg = self.argv[0]
self.argv[0] = self.argv[1:]
return (opt, arg)
next = __next__ # for py2
if __name__ == '__main__':
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment