Skip to content

Commit

Permalink
[completion] Stub for the _init_completion builtin.
Browse files Browse the repository at this point in the history
With scripts to generate test cases from the bash oracle in Python
format.
  • Loading branch information
Andy Chu committed Dec 21, 2018
1 parent 725dfbf commit 41492ff
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 13 deletions.
1 change: 1 addition & 0 deletions osh/builtin.py
Expand Up @@ -125,6 +125,7 @@

# OSH only
"repr": builtin_e.REPR,
"_init_completion": builtin_e.INIT_COMPLETION,
}

BUILTIN_NAMES = _SPECIAL_BUILTINS.keys() + _NORMAL_BUILTINS.keys()
Expand Down
28 changes: 28 additions & 0 deletions osh/builtin_comp.py
Expand Up @@ -326,3 +326,31 @@ def CompOpt(argv, comp_state):
#log('compopt: %s', arg)
#log('compopt %s', comp_opts)
return 0


INIT_COMPLETION_SPEC = args.FlagsAndOptions()

INIT_COMPLETION_SPEC.ShortFlag('-n', args.Str,
help='Do NOT split by these characters. It omits them from COMP_WORDBREAKS.')
INIT_COMPLETION_SPEC.ShortFlag('-s',
help='Treat --foo=bar and --foo bar the same way.')


def InitCompletion(argv, mem):
arg_r = args.Reader(argv)
arg = INIT_COMPLETION_SPEC.Parse(arg_r)
print(arg)
state.SetArrayDynamic(mem, 'words', ['a', 'b'])

cur = 'CUR'
prev = 'PREV'
cword = 'CWORD'
state.SetStringDynamic(mem, 'cur', cur)
state.SetStringDynamic(mem, 'prev', prev)
state.SetStringDynamic(mem, 'cword', cword)

if arg.s:
# or false
state.SetStringDynamic(mem, 'split', 'true')

return 0
38 changes: 38 additions & 0 deletions osh/builtin_comp_test.py
@@ -0,0 +1,38 @@
#!/usr/bin/python -S
"""
builtin_comp_test.py: Tests for builtin_comp.py
"""
from __future__ import print_function

import unittest

from core import alloc

from osh import state
from osh import builtin_comp # module under test

from testdata.init_completion_testdata import CASES # generated data


class BuiltinCompTest(unittest.TestCase):

def testInitCompletion(self):
arena = alloc.SideArena('<MakeTestEvaluator>')
mem = state.Mem('', [], {}, arena)

print(CASES)
argv = []

# TODO: Look at variables

builtin_comp.InitCompletion(['-s'], mem)

print(mem.GetVar('words'))
print(mem.GetVar('cur'))
print(mem.GetVar('prev'))
print(mem.GetVar('cword'))
print(mem.GetVar('split'))


if __name__ == '__main__':
unittest.main()
3 changes: 3 additions & 0 deletions osh/cmd_exec.py
Expand Up @@ -378,6 +378,9 @@ def _RunBuiltin(self, builtin_id, argv, span_id):
elif builtin_id == builtin_e.REPR:
status = builtin.Repr(argv, self.mem)

elif builtin_id == builtin_e.INIT_COMPLETION:
status = builtin_comp.InitCompletion(argv, self.mem)

else:
raise AssertionError('Unhandled builtin: %s' % builtin_id)

Expand Down
2 changes: 1 addition & 1 deletion osh/runtime.asdl
Expand Up @@ -81,7 +81,7 @@ module runtime
| COMMAND | TYPE | HELP
| DECLARE | TYPESET | ALIAS | UNALIAS
| PWD
| REPR
| REPR | INIT_COMPLETION

-- word_eval.py: SliceParts is for ${a-} and ${a+}, Error is for ${a?}, and
-- SliceAndAssign is for ${a=}.
Expand Down
9 changes: 9 additions & 0 deletions osh/state.py
Expand Up @@ -1071,6 +1071,15 @@ def SetStringDynamic(mem, name, s):
mem.SetVar(lhs_expr.LhsName(name), value.Str(s), (), scope_e.Dynamic)


def SetArrayDynamic(mem, name, a):
"""Set an array by looking up the stack.
Used for _init_completion.
"""
assert isinstance(a, list)
mem.SetVar(lhs_expr.LhsName(name), value.StrArray(a), (), scope_e.Dynamic)


def SetGlobalString(mem, name, s):
"""Helper for completion, $PWD, etc."""
assert isinstance(s, str)
Expand Down
Empty file added testdata/__init__.py
Empty file.
44 changes: 38 additions & 6 deletions gold/init_completion.sh → testdata/init_completion.sh
Expand Up @@ -5,14 +5,23 @@
# Usage:
# ./init_completion.sh <function name>

SH=${SH:-bash}

argv1() {
python -c 'import sys; print(repr(sys.argv[1]))' "$@"
}

tab-complete() {
local code=$1
local sh=bash
#local sh=bin/osh

local pat='^FLAGS|^COMP_WORDS|^WORDS|^VAR'
{ cat gold/init_completion_lib.sh; echo "$code"; } |
$sh --rcfile /dev/null -i 2>&1 |
echo
echo 'case = {}'
echo 'CASES.append(case)'
echo -n 'case["code"] = '; argv1 "$code"

local pat='^case'
{ cat testdata/init_completion_lib.sh; echo "$code"; } |
$SH --rcfile /dev/null -i 2>&1 |
egrep "$pat" || echo "ERROR: output didn't match $pat"
}

Expand All @@ -30,7 +39,15 @@ tab-complete() {
# Also I see '-n =+!' once, but that may be a mistake. The most common cases
# are : and =.

test-init() {
gen-cases() {
cat << EOF
#
# DO NOT EDIT -- Generated by testdata/init_completion.sh
#
CASES = []
EOF

tab-complete $'echo foo:bar --color=auto\t'

# readline includes quotes, and _init_completion doesn't do anything about this.
Expand Down Expand Up @@ -58,4 +75,19 @@ test-init() {
tab-complete $'n2 foo:bar --color=auto\t'
}

# Write a checked in file
write-cases() {
local out=testdata/init_completion_testdata.py
gen-cases > $out
wc -l $out
echo "Wrote $out"
}

# TODO: osh -i isn't easily scraped, for some reason. Does it have something
# to do with terminal modes?
compare() {
SH=bash tab-complete $'echo f\t'
SH=bin/osh tab-complete $'echo f\t'
}

"$@"
17 changes: 11 additions & 6 deletions gold/init_completion_lib.sh → testdata/init_completion_lib.sh
Expand Up @@ -8,18 +8,23 @@ fi
argv() {
python -c 'import sys; print(sys.argv[1:])' "$@"
}

argv1() {
python -c 'import sys; print(repr(sys.argv[1]))' "$@"
}

comp_echo() {
echo
echo -n 'COMP_WORDS '; argv "${COMP_WORDS[@]}"
echo -n 'case["COMP_WORDS"] = '; argv "${COMP_WORDS[@]}"
}
complete -F comp_echo echo

showvars() {
echo -n 'WORDS '; argv "${words[@]}"
echo -n 'VAR '; argv cur "$cur"
echo -n 'VAR '; argv prev "$prev"
echo -n 'VAR '; argv cword "$cword"
echo -n 'VAR '; argv split "$split"
echo -n 'case["words"] = '; argv "${words[@]}"
echo -n 'case["cur"] = '; argv1 "$cur"
echo -n 'case["prev"] = '; argv1 "$prev"
echo -n 'case["cword"] = '; argv1 "$cword"
echo -n 'case["split"] = '; argv1 "$split"
}

_comp_init_completion() {
Expand Down
82 changes: 82 additions & 0 deletions testdata/init_completion_testdata.py
@@ -0,0 +1,82 @@
#
# DO NOT EDIT -- Generated by testdata/init_completion.sh
#

CASES = []

case = {}
CASES.append(case)
case["code"] = 'echo foo:bar --color=auto\t'
case["COMP_WORDS"] = ['echo', 'foo', ':', 'bar', '--color', '=', 'auto']

case = {}
CASES.append(case)
case["code"] = 'echo "foo:bar|" --color=auto\t'
case["COMP_WORDS"] = ['echo', '"foo:bar|"', '--color', '=', 'auto']


case = {}
CASES.append(case)
case["code"] = 'noflags foo:bar --color=auto\t'
case["words"] = ['noflags', 'foo', ':', 'bar', '--color', '=', 'auto']
case["cur"] = 'auto'
case["prev"] = '='
case["cword"] = '6'
case["split"] = ''

case = {}
CASES.append(case)
case["code"] = 'noflags "foo:bar|" --color=auto\t'
case["words"] = ['noflags', '"foo:bar|"', '--color', '=', 'auto']
case["cur"] = 'auto'
case["prev"] = '='
case["cword"] = '4'
case["split"] = ''

case = {}
CASES.append(case)
case["code"] = 'noflags "foo:bar|\t'
case["words"] = ['noflags', '"foo:bar|']
case["cur"] = '"foo:bar|'
case["prev"] = 'noflags'
case["cword"] = '1'
case["split"] = ''


case = {}
CASES.append(case)
case["code"] = 's foo:bar --color=auto\t'
case["words"] = ['s', 'foo', ':', 'bar', '--color=auto']
case["cur"] = 'auto'
case["prev"] = '--color'
case["cword"] = '4'
case["split"] = 'true'

case = {}
CASES.append(case)
case["code"] = 's foo:bar --color auto\t'
case["words"] = ['s', 'foo', ':', 'bar', '--color', 'auto']
case["cur"] = 'auto'
case["prev"] = '--color'
case["cword"] = '5'
case["split"] = 'false'


case = {}
CASES.append(case)
case["code"] = 'n foo:bar --color=auto\t'
case["words"] = ['n', 'foo', ':', 'bar', '--color=auto']
case["cur"] = '--color=auto'
case["prev"] = 'bar'
case["cword"] = '4'
case["split"] = ''


case = {}
CASES.append(case)
case["code"] = 'n2 foo:bar --color=auto\t'
case["words"] = ['n2', 'foo:bar', '--color=auto']
case["cur"] = '--color=auto'
case["prev"] = 'foo:bar'
case["cword"] = '2'
case["split"] = ''

0 comments on commit 41492ff

Please sign in to comment.