from __future__ import annotations import os import re import shutil import tempfile from contextlib import contextmanager from prompt_toolkit.completion import ( CompleteEvent, FuzzyWordCompleter, NestedCompleter, PathCompleter, WordCompleter, merge_completers, ) from prompt_toolkit.document import Document @contextmanager def chdir(directory): """Context manager for current working directory temporary change.""" orig_dir = os.getcwd() os.chdir(directory) try: yield finally: os.chdir(orig_dir) def write_test_files(test_dir, names=None): """Write test files in test_dir using the names list.""" names = names or range(10) for i in names: with open(os.path.join(test_dir, str(i)), "wb") as out: out.write(b"") def test_pathcompleter_completes_in_current_directory(): completer = PathCompleter() doc_text = "" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert len(completions) > 0 def test_pathcompleter_completes_files_in_current_directory(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) expected = sorted(str(i) for i in range(10)) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep with chdir(test_dir): completer = PathCompleter() # this should complete on the cwd doc_text = "" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted(c.text for c in completions) assert expected == result # cleanup shutil.rmtree(test_dir) def test_pathcompleter_completes_files_in_absolute_directory(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) expected = sorted(str(i) for i in range(10)) test_dir = os.path.abspath(test_dir) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep completer = PathCompleter() # force unicode doc_text = str(test_dir) doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted(c.text for c in completions) assert expected == result # cleanup shutil.rmtree(test_dir) def test_pathcompleter_completes_directories_with_only_directories(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # create a sub directory there os.mkdir(os.path.join(test_dir, "subdir")) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep with chdir(test_dir): completer = PathCompleter(only_directories=True) doc_text = "" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert ["subdir"] == result # check that there is no completion when passing a file with chdir(test_dir): completer = PathCompleter(only_directories=True) doc_text = "1" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # cleanup shutil.rmtree(test_dir) def test_pathcompleter_respects_completions_under_min_input_len(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # min len:1 and no text with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = "" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # min len:1 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = "1" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert [""] == result # min len:0 and text of len 2 with chdir(test_dir): completer = PathCompleter(min_input_len=0) doc_text = "1" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert [""] == result # create 10 files with a 2 char long name for i in range(10): with open(os.path.join(test_dir, str(i) * 2), "wb") as out: out.write(b"") # min len:1 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = "2" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted(c.text for c in completions) assert ["", "2"] == result # min len:2 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=2) doc_text = "2" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # cleanup shutil.rmtree(test_dir) def test_pathcompleter_does_not_expanduser_by_default(): completer = PathCompleter() doc_text = "~" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions def test_pathcompleter_can_expanduser(monkeypatch): monkeypatch.setenv('HOME', '/tmp') completer = PathCompleter(expanduser=True) doc_text = "~" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert len(completions) > 0 def test_pathcompleter_can_apply_file_filter(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # add a .csv file with open(os.path.join(test_dir, "my.csv"), "wb") as out: out.write(b"") file_filter = lambda f: f and f.endswith(".csv") with chdir(test_dir): completer = PathCompleter(file_filter=file_filter) doc_text = "" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert ["my.csv"] == result # cleanup shutil.rmtree(test_dir) def test_pathcompleter_get_paths_constrains_path(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # add a subdir with 10 other files with different names subdir = os.path.join(test_dir, "subdir") os.mkdir(subdir) write_test_files(subdir, "abcdefghij") get_paths = lambda: ["subdir"] with chdir(test_dir): completer = PathCompleter(get_paths=get_paths) doc_text = "" doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] expected = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] assert expected == result # cleanup shutil.rmtree(test_dir) def test_word_completer_static_word_list(): completer = WordCompleter(["abc", "def", "aaa"]) # Static list on empty input. completions = completer.get_completions(Document(""), CompleteEvent()) assert [c.text for c in completions] == ["abc", "def", "aaa"] # Static list on non-empty input. completions = completer.get_completions(Document("a"), CompleteEvent()) assert [c.text for c in completions] == ["abc", "aaa"] completions = completer.get_completions(Document("A"), CompleteEvent()) assert [c.text for c in completions] == [] # Multiple words ending with space. (Accept all options) completions = completer.get_completions(Document("test "), CompleteEvent()) assert [c.text for c in completions] == ["abc", "def", "aaa"] # Multiple words. (Check last only.) completions = completer.get_completions(Document("test a"), CompleteEvent()) assert [c.text for c in completions] == ["abc", "aaa"] def test_word_completer_ignore_case(): completer = WordCompleter(["abc", "def", "aaa"], ignore_case=True) completions = completer.get_completions(Document("a"), CompleteEvent()) assert [c.text for c in completions] == ["abc", "aaa"] completions = completer.get_completions(Document("A"), CompleteEvent()) assert [c.text for c in completions] == ["abc", "aaa"] def test_word_completer_match_middle(): completer = WordCompleter(["abc", "def", "abca"], match_middle=True) completions = completer.get_completions(Document("bc"), CompleteEvent()) assert [c.text for c in completions] == ["abc", "abca"] def test_word_completer_sentence(): # With sentence=True completer = WordCompleter( ["hello world", "www", "hello www", "hello there"], sentence=True ) completions = completer.get_completions(Document("hello w"), CompleteEvent()) assert [c.text for c in completions] == ["hello world", "hello www"] # With sentence=False completer = WordCompleter( ["hello world", "www", "hello www", "hello there"], sentence=False ) completions = completer.get_completions(Document("hello w"), CompleteEvent()) assert [c.text for c in completions] == ["www"] def test_word_completer_dynamic_word_list(): called = [0] def get_words(): called[0] += 1 return ["abc", "def", "aaa"] completer = WordCompleter(get_words) # Dynamic list on empty input. completions = completer.get_completions(Document(""), CompleteEvent()) assert [c.text for c in completions] == ["abc", "def", "aaa"] assert called[0] == 1 # Static list on non-empty input. completions = completer.get_completions(Document("a"), CompleteEvent()) assert [c.text for c in completions] == ["abc", "aaa"] assert called[0] == 2 def test_word_completer_pattern(): # With a pattern which support '.' completer = WordCompleter( ["abc", "a.b.c", "a.b", "xyz"], pattern=re.compile(r"^([a-zA-Z0-9_.]+|[^a-zA-Z0-9_.\s]+)"), ) completions = completer.get_completions(Document("a."), CompleteEvent()) assert [c.text for c in completions] == ["a.b.c", "a.b"] # Without pattern completer = WordCompleter(["abc", "a.b.c", "a.b", "xyz"]) completions = completer.get_completions(Document("a."), CompleteEvent()) assert [c.text for c in completions] == [] def test_fuzzy_completer(): collection = [ "migrations.py", "django_migrations.py", "django_admin_log.py", "api_user.doc", "user_group.doc", "users.txt", "accounts.txt", "123.py", "test123test.py", ] completer = FuzzyWordCompleter(collection) completions = completer.get_completions(Document("txt"), CompleteEvent()) assert [c.text for c in completions] == ["users.txt", "accounts.txt"] completions = completer.get_completions(Document("djmi"), CompleteEvent()) assert [c.text for c in completions] == [ "django_migrations.py", "django_admin_log.py", ] completions = completer.get_completions(Document("mi"), CompleteEvent()) assert [c.text for c in completions] == [ "migrations.py", "django_migrations.py", "django_admin_log.py", ] completions = completer.get_completions(Document("user"), CompleteEvent()) assert [c.text for c in completions] == [ "user_group.doc", "users.txt", "api_user.doc", ] completions = completer.get_completions(Document("123"), CompleteEvent()) assert [c.text for c in completions] == ["123.py", "test123test.py"] completions = completer.get_completions(Document("miGr"), CompleteEvent()) assert [c.text for c in completions] == [ "migrations.py", "django_migrations.py", ] # Multiple words ending with space. (Accept all options) completions = completer.get_completions(Document("test "), CompleteEvent()) assert [c.text for c in completions] == collection # Multiple words. (Check last only.) completions = completer.get_completions(Document("test txt"), CompleteEvent()) assert [c.text for c in completions] == ["users.txt", "accounts.txt"] def test_nested_completer(): completer = NestedCompleter.from_nested_dict( { "show": { "version": None, "clock": None, "interfaces": None, "ip": {"interface": {"brief"}}, }, "exit": None, } ) # Empty input. completions = completer.get_completions(Document(""), CompleteEvent()) assert {c.text for c in completions} == {"show", "exit"} # One character. completions = completer.get_completions(Document("s"), CompleteEvent()) assert {c.text for c in completions} == {"show"} # One word. completions = completer.get_completions(Document("show"), CompleteEvent()) assert {c.text for c in completions} == {"show"} # One word + space. completions = completer.get_completions(Document("show "), CompleteEvent()) assert {c.text for c in completions} == {"version", "clock", "interfaces", "ip"} # One word + space + one character. completions = completer.get_completions(Document("show i"), CompleteEvent()) assert {c.text for c in completions} == {"ip", "interfaces"} # One space + one word + space + one character. completions = completer.get_completions(Document(" show i"), CompleteEvent()) assert {c.text for c in completions} == {"ip", "interfaces"} # Test nested set. completions = completer.get_completions( Document("show ip interface br"), CompleteEvent() ) assert {c.text for c in completions} == {"brief"} def test_deduplicate_completer(): def create_completer(deduplicate: bool): return merge_completers( [ WordCompleter(["hello", "world", "abc", "def"]), WordCompleter(["xyz", "xyz", "abc", "def"]), ], deduplicate=deduplicate, ) completions = list( create_completer(deduplicate=False).get_completions( Document(""), CompleteEvent() ) ) assert len(completions) == 8 completions = list( create_completer(deduplicate=True).get_completions( Document(""), CompleteEvent() ) ) assert len(completions) == 5