Skip to content

Commit

Permalink
Add recursive files completer
Browse files Browse the repository at this point in the history
  • Loading branch information
igrek51 committed Jun 21, 2020
1 parent 7e5c3ff commit 0b5fa49
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 38 deletions.
9 changes: 5 additions & 4 deletions cliglue/autocomplete/autocomplete.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,15 @@ def _find_available_completions(rules: List[CliRule], args: List[str], current_w
for rule in parameters:
for keyword in rule.keywords:
if previous == keyword:
possible_choices: List[str] = generate_value_choices(rule)
possible_choices: List[str] = generate_value_choices(rule, current=current_word)
return possible_choices

# "--param=value" autocompletion
for rule in parameters:
for keyword in rule.keywords:
if current_word.startswith(keyword + '='):
possible_choices: List[str] = list(map(lambda c: keyword + '=' + c, generate_value_choices(rule)))
possible_choices: List[str] = list(map(lambda c: keyword + '=' + c,
generate_value_choices(rule, current=current_word)))
return possible_choices

completions: List[str] = []
Expand All @@ -102,10 +103,10 @@ def _find_available_completions(rules: List[CliRule], args: List[str], current_w

# positional arguments
for rule in pos_arguments:
possible_choices: List[str] = generate_value_choices(rule)
possible_choices: List[str] = generate_value_choices(rule, current=current_word)
completions.extend(possible_choices)
for rule in many_args:
possible_choices: List[str] = generate_value_choices(rule)
possible_choices: List[str] = generate_value_choices(rule, current=current_word)
completions.extend(possible_choices)

return completions
11 changes: 0 additions & 11 deletions cliglue/autocomplete/completers.py

This file was deleted.

4 changes: 2 additions & 2 deletions cliglue/builder/typedef.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Union, Callable, Iterable, List, Type, Any
from typing import Union, Callable, Iterable, List, Type, Any, Optional

Action = Callable[..., None]
ChoiceProvider = Union[Iterable[Any], Callable[..., List[Any]]]
ChoiceProvider = Union[Iterable[Any], Callable[[Optional[str]], List[Any]]]
TypeOrParser = Union[Type, Callable[[str], Any]]
1 change: 1 addition & 0 deletions cliglue/completers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .file import file_completer
30 changes: 30 additions & 0 deletions cliglue/completers/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
from typing import Optional, Tuple


def file_completer(current: Optional[str]):
listdir, prefixdir = _current_listing_dir(current)
names = []
for file in os.listdir(listdir):
filepath = f'{prefixdir}{file}'
if os.path.isdir(filepath):
# prevent from immediate resolving
names.append(f'{filepath}/')
names.append(f'{filepath}/ ')
else:
names.append(filepath)
return sorted(names)


def _current_listing_dir(current: Optional[str]) -> Tuple[str, str]:
if not current:
return '.', ''

current_path, current_node = os.path.split(current)
if not current_path or current_path == '.':
return '.', ''

if not os.path.isdir(current_path):
return '.', ''

return current_path, f'{current_path}/'
12 changes: 9 additions & 3 deletions cliglue/parser/value.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import inspect
from collections.abc import Iterable
from typing import List, Any
from typing import List, Any, Optional

from cliglue.builder.rule import ValueRule
from cliglue.builder.typedef import TypeOrParser
Expand All @@ -16,12 +17,17 @@ def parse_typed_value(_type: TypeOrParser, arg: str) -> Any:
return _type(arg)


def generate_value_choices(rule: ValueRule) -> List[Any]:
def generate_value_choices(rule: ValueRule, current: Optional[str] = None) -> List[Any]:
if not rule.choices:
return []
elif isinstance(rule.choices, list):
return rule.choices
elif isinstance(rule.choices, Iterable):
return [choice for choice in rule.choices]
else:
return list(rule.choices())
(args, _, _, _, _, _, annotations) = inspect.getfullargspec(rule.choices)
if len(args) >= 1:
results = rule.choices(current=current)
else:
results = rule.choices()
return list(results)
2 changes: 1 addition & 1 deletion cliglue/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.1.1"
__version__ = "1.1.2"
17 changes: 0 additions & 17 deletions tests/autocomplete/test_completers.py

This file was deleted.

Empty file added tests/completers/__init__.py
Empty file.
108 changes: 108 additions & 0 deletions tests/completers/test_file_completer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from pathlib import Path

from cliglue import *
from cliglue.completers import file_completer
from tests.asserts import MockIO


def test_file_completer_empty():
with MockIO('--autocomplete', '"app "') as mockio:
CliBuilder(reraise_error=True).has(
arguments('f', choices=file_completer),
).run()
proposals = mockio.stripped().splitlines()
assert '.gitignore' in proposals
assert 'tests/' in proposals

assert 'tests' not in proposals
assert 'tests/autocomplete/' not in proposals
assert 'tests/__init__.py' not in proposals
assert '.' not in proposals
assert '..' not in proposals


def test_file_completer_file():
with MockIO('--autocomplete', '"app .gitignore"') as mockio:
CliBuilder(reraise_error=True).has(
arguments('f', choices=file_completer),
).run()
assert mockio.stripped() == '.gitignore'


def test_file_completer_dir1():
with MockIO('--autocomplete', '"app tests"') as mockio:
CliBuilder(reraise_error=True).has(
arguments('f', choices=file_completer),
).run()
proposals = set(mockio.stripped().splitlines())
assert proposals == {'tests/'}


def test_file_completer_dir_content():
with MockIO('--autocomplete', '"app tests/"') as mockio:
CliBuilder(reraise_error=True).has(
arguments('f', choices=file_completer),
).run()
proposals = mockio.stripped().splitlines()
assert 'tests/autocomplete/' in proposals
assert 'tests/__init__.py' in proposals

assert 'tests/' not in proposals
assert 'tests/autocomplete' not in proposals
assert '.gitignore' not in proposals
assert '.' not in proposals
assert '..' not in proposals


def test_file_completer_subdir():
with MockIO('--autocomplete', '"app tests/autocomplete"') as mockio:
CliBuilder(reraise_error=True).has(
arguments('f', choices=file_completer),
).run()
proposals = set(mockio.stripped().splitlines())
assert proposals == {'tests/autocomplete/'}


def test_file_completer_subdir_content():
with MockIO('--autocomplete', '"app tests/autocomplete/"') as mockio:
CliBuilder(reraise_error=True).has(
arguments('f', choices=file_completer),
).run()
proposals = mockio.stripped().splitlines()
assert 'tests/autocomplete/__init__.py' in proposals

assert 'tests/autocomplete/' not in proposals
assert 'tests/autocomplete' not in proposals
assert 'tests/__init__.py' not in proposals
assert 'tests/' not in proposals
assert '.gitignore' not in proposals
assert '.' not in proposals
assert '..' not in proposals


def test_file_completer_subdir_file():
with MockIO('--autocomplete', '"app tests/autocomplete/__init__.py"') as mockio:
CliBuilder(reraise_error=True).has(
arguments('f', choices=file_completer),
).run()
assert mockio.stripped() == 'tests/autocomplete/__init__.py'


def test_file_completer_notexisting_dir():
with MockIO('--autocomplete', '"app tests/there_is_no_dir/__init__.py"') as mockio:
CliBuilder(reraise_error=True).has(
arguments('f', choices=file_completer),
).run()
assert mockio.stripped() == ''


def test_complete_absolute_files():
Path('/tmp/cliglue_test_autocomplete_425_896').write_text('')

with MockIO('--autocomplete', '"app /tmp/cliglue_test_autocomplete_425"') as mockio:
CliBuilder(reraise_error=True).has(
arguments('f', choices=file_completer),
).run()
assert mockio.stripped() == '/tmp/cliglue_test_autocomplete_425_896'

Path('/tmp/cliglue_test_autocomplete_425_896').unlink()

0 comments on commit 0b5fa49

Please sign in to comment.