Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interp: display all Bash's shopt option #883

Merged
merged 1 commit into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 128 additions & 15 deletions interp/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ func New(opts ...RunnerOption) (*Runner, error) {
return nil, err
}
}

// turn "on" the default Bash options
for i, opt := range bashOptsTable {
r.opts[len(shellOptsTable)+i] = opt.defaultState
}

// Set the default fallbacks, if necessary.
if r.Env == nil {
Env(nil)(r)
Expand Down Expand Up @@ -274,7 +280,7 @@ func Params(args ...string) RunnerOption {
value := fp.value()
if value == "" && enable {
for i, opt := range &shellOptsTable {
r.printOptLine(opt.name, r.opts[i])
r.printOptLine(opt.name, r.opts[i], true)
}
continue
}
Expand All @@ -288,7 +294,7 @@ func Params(args ...string) RunnerOption {
}
continue
}
opt := r.optByName(value, false)
_, opt := r.optByName(value, false)
if opt == nil {
return fmt.Errorf("invalid option: %q", value)
}
Expand Down Expand Up @@ -366,28 +372,38 @@ func StdIO(in io.Reader, out, err io.Writer) RunnerOption {
}
}

func (r *Runner) optByName(name string, bash bool) *bool {
// optByName returns the matching runner's option index and status
func (r *Runner) optByName(name string, bash bool) (index int, status *bool) {
if bash {
for i, optName := range bashOptsTable {
if optName == name {
return &r.opts[len(shellOptsTable)+i]
for i, opt := range bashOptsTable {
if opt.name == name {
index = len(shellOptsTable) + i
return index, &r.opts[index]
}
}
}
for i, opt := range &shellOptsTable {
if opt.name == name {
return &r.opts[i]
return i, &r.opts[i]
}
}
return nil
return 0, nil
}

type runnerOpts [len(shellOptsTable) + len(bashOptsTable)]bool

var shellOptsTable = [...]struct {
type shellOpt struct {
flag byte
name string
}{
}

type bashOpt struct {
name string
defaultState bool // Bash's default value for this option
supported bool // whether we support the option's non-default state
}

var shellOptsTable = [...]shellOpt{
// sorted alphabetically by name; use a space for the options
// that have no flag form
{'a', "allexport"},
Expand All @@ -399,11 +415,108 @@ var shellOptsTable = [...]struct {
{' ', "pipefail"},
}

var bashOptsTable = [...]string{
// sorted alphabetically by name
"expand_aliases",
"globstar",
"nullglob",
var bashOptsTable = [...]bashOpt{
// supported options, sorted alphabetically by name
{
name: "expand_aliases",
defaultState: false,
supported: true,
},
{
name: "globstar",
defaultState: false,
supported: true,
},
{
name: "nullglob",
defaultState: false,
supported: true,
},
// unsupported options, sorted alphabetically by name
{name: "assoc_expand_once"},
{name: "autocd"},
{name: "cdable_vars"},
{name: "cdspell"},
{name: "checkhash"},
{name: "checkjobs"},
{
name: "checkwinsize",
defaultState: true,
},
{
name: "cmdhist",
defaultState: true,
},
{name: "compat31"},
{name: "compat32"},
{name: "compat40"},
{name: "compat41"},
{name: "compat42"},
{name: "compat44"},
{name: "compat43"},
{name: "compat44"},
{
name: "complete_fullquote",
defaultState: true,
},
{name: "direxpand"},
{name: "dirspell"},
{name: "dotglob"},
{name: "execfail"},
{name: "extdebug"},
{name: "extglob"},
{
name: "extquote",
defaultState: true,
},
{name: "failglob"},
{
name: "force_fignore",
defaultState: true,
},
{name: "globasciiranges"},
{name: "gnu_errfmt"},
{name: "histappend"},
{name: "histreedit"},
{name: "histverify"},
{
name: "hostcomplete",
defaultState: true,
},
{name: "huponexit"},
{
name: "inherit_errexit",
defaultState: true,
},
{
name: "interactive_comments",
defaultState: true,
},
{name: "lastpipe"},
{name: "lithist"},
{name: "localvar_inherit"},
{name: "localvar_unset"},
{name: "login_shell"},
{name: "mailwarn"},
{name: "no_empty_cmd_completion"},
{name: "nocaseglob"},
{name: "nocasematch"},
{
name: "progcomp",
defaultState: true,
},
{name: "progcomp_alias"},
{
name: "promptvars",
defaultState: true,
},
{name: "restricted_shell"},
{name: "shift_verbose"},
{
name: "sourcepath",
defaultState: true,
},
{name: "xpg_echo"},
}

// To access the shell options arrays without a linear search when we
Expand Down
46 changes: 35 additions & 11 deletions interp/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,29 +668,44 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
}
}
args := fp.args()
bash := !posixOpts
if len(args) == 0 {
if !posixOpts {
for i, name := range bashOptsTable {
r.printOptLine(name, r.opts[len(shellOptsTable)+i])
if bash {
for i, opt := range bashOptsTable {
r.printOptLine(opt.name, r.opts[len(shellOptsTable)+i], opt.supported)
}
break
}
for i, opt := range &shellOptsTable {
r.printOptLine(opt.name, r.opts[i])
r.printOptLine(opt.name, r.opts[i], true)
}
break
}
for _, arg := range args {
opt := r.optByName(arg, !posixOpts)
i, opt := r.optByName(arg, bash)
if opt == nil {
r.errf("shopt: invalid option name %q\n", arg)
return 1
}

var (
bo *bashOpt
supported = true // default for shell options
)
if bash {
bo = &bashOptsTable[i-len(shellOptsTable)]
supported = bo.supported
}

switch mode {
case "-s", "-u":
if bash && !supported {
r.errf("shopt: invalid option name %q %q (%q not supported)\n", arg, r.optStatusText(bo.defaultState), r.optStatusText(!bo.defaultState))
return 1
}
*opt = mode == "-s"
default: // ""
r.printOptLine(arg, *opt)
r.printOptLine(arg, *opt, supported)
}
}
r.updateExpandOpts()
Expand Down Expand Up @@ -890,12 +905,13 @@ func mapfileSplit(delim byte, dropDelim bool) func(data []byte, atEOF bool) (adv
}
}

func (r *Runner) printOptLine(name string, enabled bool) {
status := "off"
if enabled {
status = "on"
func (r *Runner) printOptLine(name string, enabled, supported bool) {
state := r.optStatusText(enabled)
if supported {
r.outf("%s\t%s\n", name, state)
return
}
r.outf("%s\t%s\n", name, status)
r.outf("%s\t%s\t(%q not supported)\n", name, state, r.optStatusText(!enabled))
}

func (r *Runner) readLine(raw bool) ([]byte, error) {
Expand Down Expand Up @@ -1070,3 +1086,11 @@ func (g *getopts) next(optstr string, args []string) (opt rune, optarg string, d

return opt, optarg, false
}

// optStatusText returns a shell option's status text display
func (r *Runner) optStatusText(status bool) string {
if status {
return "on"
}
return "off"
}
17 changes: 17 additions & 0 deletions interp/interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2072,6 +2072,23 @@ set +o pipefail
{"shopt -u -o noexec; echo foo_interp_missing", "foo_interp_missing\n"},
{"shopt -u globstar; shopt globstar | grep 'off$' | wc -l | tr -d ' '", "1\n"},
{"shopt -s globstar; shopt globstar | grep 'off$' | wc -l | tr -d ' '", "0\n"},
{"shopt extglob | grep 'off' | wc -l | tr -d ' '", "1\n"},
{
"shopt inherit_errexit",
"inherit_errexit\ton\t(\"off\" not supported)\n #JUSTERR",
},
{
"shopt -s extglob",
"shopt: invalid option name \"extglob\" \"off\" (\"on\" not supported)\nexit status 1 #IGNORE",
},
{
"shopt -s interactive_comments",
"shopt: invalid option name \"interactive_comments\" \"on\" (\"off\" not supported)\nexit status 1 #IGNORE",
},
{
"shopt -s foo",
"shopt: invalid option name \"foo\"\nexit status 1 #JUSTERR",
},

// IFS
{`echo -n "$IFS"`, " \t\n"},
Expand Down
2 changes: 1 addition & 1 deletion interp/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string)
case syntax.TsNempStr:
return x != ""
case syntax.TsOptSet:
if opt := r.optByName(x, false); opt != nil {
if _, opt := r.optByName(x, false); opt != nil {
return *opt
}
return false
Expand Down