Skip to content

Commit

Permalink
Implement shopt -p and -o.
Browse files Browse the repository at this point in the history
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 committed Sep 6, 2017
1 parent 5949fe1 commit 8dde7f4
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 46 deletions.
63 changes: 27 additions & 36 deletions core/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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:])
Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand Down
65 changes: 55 additions & 10 deletions core/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import os

from core import args
from core import runtime
from core import util
from core.id_kind import Id
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
16 changes: 16 additions & 0 deletions spec/sh-options.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.