Permalink
Browse files

Implement shopt -p and -o.

We can print options in two formats, as well as set the options from the
'set' builtin.  There are still a few cases left to do, like 'set -o'.

- Tests for shopt -p and shopt -po

Addresses issue #26.
  • Loading branch information...
Andy Chu
Andy Chu committed Sep 6, 2017
1 parent 5949fe1 commit 8dde7f46777d5dc233e11e162b9990f1fa6f4301
Showing with 98 additions and 46 deletions.
  1. +27 −36 core/builtin.py
  2. +55 −10 core/state.py
  3. +16 −0 spec/sh-options.test.sh
View
@@ -530,26 +530,18 @@ def Export(argv, mem):
def AddOptionsToArgSpec(spec):
"""Shared between 'set' builtin and the shell's own arg parser."""
spec.Option('e', 'errexit')
spec.Option('n', 'noexec')
spec.Option('u', 'nounset')
spec.Option('x', 'xtrace')
spec.Option('f', 'noglob')
spec.Option(None, 'pipefail')
for short_flag, opt_name in state.SET_OPTIONS:
spec.Option(short_flag, opt_name)
spec.Option(None, 'debug-completion')
set_spec = args.FlagsAndOptions()
AddOptionsToArgSpec(set_spec)
SET_SPEC = args.FlagsAndOptions()
AddOptionsToArgSpec(SET_SPEC)
def SetExecOpts(exec_opts, opt_changes):
for name, val in opt_changes:
if name == 'errexit':
exec_opts.errexit.Set(val)
else:
setattr(exec_opts, name, val)
"""Used by bin/oil.py too."""
for opt_name, b in opt_changes:
exec_opts.SetOption(opt_name, b)
def Set(argv, exec_opts, mem):
@@ -558,14 +550,15 @@ def Set(argv, exec_opts, mem):
if not argv: # empty
# TODO:
# - set -o is different than plain 'set'.
# If no arguments are given, it shows functions/vars? Why not show other
# state?
exec_opts.Show(sys.stdout)
# - This should be set -o, not plain 'set'.
# - When no arguments are given, it shows functions/vars? Why not show
# other state?
exec_opts.ShowOptions([])
return 0
arg, i = set_spec.Parse(argv)
arg, i = SET_SPEC.Parse(argv)
# TODO: exec_opts.SetOption()
SetExecOpts(exec_opts, arg.opt_changes)
if arg.saw_double_dash or i != len(argv): # set -u shouldn't affect argv
mem.SetArgv(argv[i:])
@@ -605,27 +598,23 @@ def Set(argv, exec_opts, mem):
# in functions! In that case, it's really the settings in main() that matter
# (as long as nobody later turns things off.)
# Oil-specific
if name == 'strict-arith':
exec_opts.strict_arith = True
elif name == 'strict-array':
exec_opts.strict_array = True
elif name == 'strict-command':
exec_opts.strict_command = True
elif name == 'strict-word':
exec_opts.strict_word = True
elif name == 'strict-scope':
exec_opts.strict_scope = True
SHOPT_SPEC = _Register('shopt')
SHOPT_SPEC.ShortFlag('-s') # set
SHOPT_SPEC.ShortFlag('-u') # unset
SHOPT_SPEC.ShortFlag('-o') # use 'set -o' up names
SHOPT_SPEC.ShortFlag('-p') # print
def Shopt(argv, exec_opts):
arg, i = SHOPT_SPEC.Parse(argv)
#log('%s', arg)
if arg.p: # print values
if arg.o: # use set -o names
exec_opts.ShowOptions(argv[i:])
else:
exec_opts.ShowShoptOptions(argv[i:])
return 0
b = None
if arg.s:
@@ -636,10 +625,12 @@ def Shopt(argv, exec_opts):
if b is None:
raise NotImplementedError # Display options
# TODO: exec_opts.SetShoptOption()
for opt_name in argv[i:]:
if opt_name not in ('nullglob', 'failglob'):
raise args.UsageError('shopt: Invalid option %r' % opt_name)
setattr(exec_opts, opt_name, b)
if arg.o:
exec_opts.SetOption(opt_name, b)
else:
exec_opts.SetShoptOption(opt_name, b)
return 0
View
@@ -12,6 +12,7 @@
import os
from core import args
from core import runtime
from core import util
from core.id_kind import Id
@@ -62,13 +63,27 @@ def Set(self, b):
self.errexit = b
# Used by builtin
SET_OPTIONS = [
('e', 'errexit'),
('n', 'noexec'),
('u', 'nounset'),
('x', 'xtrace'),
('f', 'noglob'),
(None, 'pipefail'),
(None, 'debug-completion'),
]
_SET_OPTION_NAMES = set(name for _, name in SET_OPTIONS)
class ExecOpts(object):
def __init__(self):
self.errexit = _ErrExit()
# TODO: Set from flags
self.nounset = False
# set -o / set +o
self.errexit = _ErrExit() # -e
self.nounset = False # -u
self.pipefail = False
self.xtrace = False # NOTE: uses PS4
self.noglob = False # -f
@@ -109,14 +124,44 @@ def GetDollarHyphen(self):
# - B for brace expansion
return ''.join(chars)
def Show(self, f):
# TODO: Share with the other spec
SET_OPTIONS = ('errexit',)
def SetOption(self, opt_name, b):
""" For set -o, set +o, or shopt -s/-u -o. """
if opt_name not in _SET_OPTION_NAMES:
raise args.UsageError('Invalid option %r' % opt_name)
if opt_name == 'errexit':
self.errexit.Set(b)
else:
setattr(self, opt_name, b)
SHOPT_OPTIONS = ('nullglob', 'failglob')
def SetShoptOption(self, opt_name, b):
""" For shopt -s/-u. """
if opt_name not in self.SHOPT_OPTIONS:
raise args.UsageError('Invalid option %r' % opt_name)
setattr(self, opt_name, b)
def ShowOptions(self, opt_names):
""" For 'set -o' and 'shopt -p -o' """
# TODO: Maybe sort them differently?
for name in sorted(self.__dict__):
if name == 'errexit':
val = self.errexit.errexit
opt_names = opt_names or _SET_OPTION_NAMES
for opt_name in opt_names:
if opt_name == 'errexit':
b = self.errexit.errexit
else:
val = getattr(self, name)
print('%-20s%s' % (name, val), file=f)
attr = opt_name.replace('-', '_')
b = getattr(self, attr)
print('set %so %s' % ('-' if b else '+', opt_name))
def ShowShoptOptions(self, opt_names):
""" For 'shopt -p' """
opt_names = opt_names or self.SHOPT_OPTIONS # show all
for opt_name in opt_names:
b = getattr(self, opt_name)
print('shopt -%s %s' % ('s' if b else 'u', opt_name))
class _ArgFrame(object):
View
@@ -366,3 +366,19 @@ echo $?
# status: 0
# N-I dash stdout-json: "0\n"
# N-I dash status: 2
### shopt -p -o
shopt -po nounset
set -u
shopt -po nounset
# stdout-json: "set +o nounset\nset -o nounset\n"
# N-I dash/mksh stdout-json: ""
# N-I dash/mksh status: 127
### shopt -p
shopt -p nullglob
shopt -s nullglob
shopt -p nullglob
# stdout-json: "shopt -u nullglob\nshopt -s nullglob\n"
# N-I dash/mksh stdout-json: ""
# N-I dash/mksh status: 127

0 comments on commit 8dde7f4

Please sign in to comment.