Uploaded Test files

This commit is contained in:
Batuhan Berk Başoğlu 2020-11-12 11:05:57 -05:00
parent f584ad9d97
commit 2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions

View file

@ -0,0 +1,36 @@
from .base import (
CompleteEvent,
Completer,
Completion,
DummyCompleter,
DynamicCompleter,
ThreadedCompleter,
get_common_complete_suffix,
merge_completers,
)
from .filesystem import ExecutableCompleter, PathCompleter
from .fuzzy_completer import FuzzyCompleter, FuzzyWordCompleter
from .nested import NestedCompleter
from .word_completer import WordCompleter
__all__ = [
# Base.
"Completion",
"Completer",
"ThreadedCompleter",
"DummyCompleter",
"DynamicCompleter",
"CompleteEvent",
"merge_completers",
"get_common_complete_suffix",
# Filesystem.
"PathCompleter",
"ExecutableCompleter",
# Fuzzy
"FuzzyCompleter",
"FuzzyWordCompleter",
# Nested.
"NestedCompleter",
# Word completer.
"WordCompleter",
]

View file

@ -0,0 +1,349 @@
"""
"""
from abc import ABCMeta, abstractmethod
from typing import AsyncGenerator, Callable, Iterable, Optional, Sequence
from prompt_toolkit.document import Document
from prompt_toolkit.eventloop import generator_to_async_generator
from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples
__all__ = [
"Completion",
"Completer",
"ThreadedCompleter",
"DummyCompleter",
"DynamicCompleter",
"CompleteEvent",
"merge_completers",
"get_common_complete_suffix",
]
class Completion:
"""
:param text: The new string that will be inserted into the document.
:param start_position: Position relative to the cursor_position where the
new text will start. The text will be inserted between the
start_position and the original cursor position.
:param display: (optional string or formatted text) If the completion has
to be displayed differently in the completion menu.
:param display_meta: (Optional string or formatted text) Meta information
about the completion, e.g. the path or source where it's coming from.
This can also be a callable that returns a string.
:param style: Style string.
:param selected_style: Style string, used for a selected completion.
This can override the `style` parameter.
"""
def __init__(
self,
text: str,
start_position: int = 0,
display: Optional[AnyFormattedText] = None,
display_meta: Optional[AnyFormattedText] = None,
style: str = "",
selected_style: str = "",
) -> None:
from prompt_toolkit.formatted_text import to_formatted_text
self.text = text
self.start_position = start_position
self._display_meta = display_meta
if display is None:
display = text
self.display = to_formatted_text(display)
self.style = style
self.selected_style = selected_style
assert self.start_position <= 0
def __repr__(self) -> str:
if isinstance(self.display, str) and self.display == self.text:
return "%s(text=%r, start_position=%r)" % (
self.__class__.__name__,
self.text,
self.start_position,
)
else:
return "%s(text=%r, start_position=%r, display=%r)" % (
self.__class__.__name__,
self.text,
self.start_position,
self.display,
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Completion):
return False
return (
self.text == other.text
and self.start_position == other.start_position
and self.display == other.display
and self._display_meta == other._display_meta
)
def __hash__(self) -> int:
return hash((self.text, self.start_position, self.display, self._display_meta))
@property
def display_text(self) -> str:
" The 'display' field as plain text. "
from prompt_toolkit.formatted_text import fragment_list_to_text
return fragment_list_to_text(self.display)
@property
def display_meta(self) -> StyleAndTextTuples:
" Return meta-text. (This is lazy when using a callable). "
from prompt_toolkit.formatted_text import to_formatted_text
return to_formatted_text(self._display_meta or "")
@property
def display_meta_text(self) -> str:
" The 'meta' field as plain text. "
from prompt_toolkit.formatted_text import fragment_list_to_text
return fragment_list_to_text(self.display_meta)
def new_completion_from_position(self, position: int) -> "Completion":
"""
(Only for internal use!)
Get a new completion by splitting this one. Used by `Application` when
it needs to have a list of new completions after inserting the common
prefix.
"""
assert position - self.start_position >= 0
return Completion(
text=self.text[position - self.start_position :],
display=self.display,
display_meta=self._display_meta,
)
class CompleteEvent:
"""
Event that called the completer.
:param text_inserted: When True, it means that completions are requested
because of a text insert. (`Buffer.complete_while_typing`.)
:param completion_requested: When True, it means that the user explicitly
pressed the `Tab` key in order to view the completions.
These two flags can be used for instance to implement a completer that
shows some completions when ``Tab`` has been pressed, but not
automatically when the user presses a space. (Because of
`complete_while_typing`.)
"""
def __init__(
self, text_inserted: bool = False, completion_requested: bool = False
) -> None:
assert not (text_inserted and completion_requested)
#: Automatic completion while typing.
self.text_inserted = text_inserted
#: Used explicitly requested completion by pressing 'tab'.
self.completion_requested = completion_requested
def __repr__(self) -> str:
return "%s(text_inserted=%r, completion_requested=%r)" % (
self.__class__.__name__,
self.text_inserted,
self.completion_requested,
)
class Completer(metaclass=ABCMeta):
"""
Base class for completer implementations.
"""
@abstractmethod
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
"""
This should be a generator that yields :class:`.Completion` instances.
If the generation of completions is something expensive (that takes a
lot of time), consider wrapping this `Completer` class in a
`ThreadedCompleter`. In that case, the completer algorithm runs in a
background thread and completions will be displayed as soon as they
arrive.
:param document: :class:`~prompt_toolkit.document.Document` instance.
:param complete_event: :class:`.CompleteEvent` instance.
"""
while False:
yield
async def get_completions_async(
self, document: Document, complete_event: CompleteEvent
) -> AsyncGenerator[Completion, None]:
"""
Asynchronous generator for completions. (Probably, you won't have to
override this.)
Asynchronous generator of :class:`.Completion` objects.
"""
for item in self.get_completions(document, complete_event):
yield item
class ThreadedCompleter(Completer):
"""
Wrapper that runs the `get_completions` generator in a thread.
(Use this to prevent the user interface from becoming unresponsive if the
generation of completions takes too much time.)
The completions will be displayed as soon as they are produced. The user
can already select a completion, even if not all completions are displayed.
"""
def __init__(self, completer: Completer) -> None:
self.completer = completer
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
return self.completer.get_completions(document, complete_event)
async def get_completions_async(
self, document: Document, complete_event: CompleteEvent
) -> AsyncGenerator[Completion, None]:
"""
Asynchronous generator of completions.
"""
async for completion in generator_to_async_generator(
lambda: self.completer.get_completions(document, complete_event)
):
yield completion
def __repr__(self) -> str:
return "ThreadedCompleter(%r)" % (self.completer,)
class DummyCompleter(Completer):
"""
A completer that doesn't return any completion.
"""
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
return []
def __repr__(self) -> str:
return "DummyCompleter()"
class DynamicCompleter(Completer):
"""
Completer class that can dynamically returns any Completer.
:param get_completer: Callable that returns a :class:`.Completer` instance.
"""
def __init__(self, get_completer: Callable[[], Optional[Completer]]) -> None:
self.get_completer = get_completer
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
completer = self.get_completer() or DummyCompleter()
return completer.get_completions(document, complete_event)
async def get_completions_async(
self, document: Document, complete_event: CompleteEvent
) -> AsyncGenerator[Completion, None]:
completer = self.get_completer() or DummyCompleter()
async for completion in completer.get_completions_async(
document, complete_event
):
yield completion
def __repr__(self) -> str:
return "DynamicCompleter(%r -> %r)" % (self.get_completer, self.get_completer())
class _MergedCompleter(Completer):
"""
Combine several completers into one.
"""
def __init__(self, completers: Sequence[Completer]) -> None:
self.completers = completers
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
# Get all completions from the other completers in a blocking way.
for completer in self.completers:
for c in completer.get_completions(document, complete_event):
yield c
async def get_completions_async(
self, document: Document, complete_event: CompleteEvent
) -> AsyncGenerator[Completion, None]:
# Get all completions from the other completers in a blocking way.
for completer in self.completers:
async for item in completer.get_completions_async(document, complete_event):
yield item
def merge_completers(completers: Sequence[Completer]) -> _MergedCompleter:
"""
Combine several completers into one.
"""
return _MergedCompleter(completers)
def get_common_complete_suffix(
document: Document, completions: Sequence[Completion]
) -> str:
"""
Return the common prefix for all completions.
"""
# Take only completions that don't change the text before the cursor.
def doesnt_change_before_cursor(completion: Completion) -> bool:
end = completion.text[: -completion.start_position]
return document.text_before_cursor.endswith(end)
completions2 = [c for c in completions if doesnt_change_before_cursor(c)]
# When there is at least one completion that changes the text before the
# cursor, don't return any common part.
if len(completions2) != len(completions):
return ""
# Return the common prefix.
def get_suffix(completion: Completion) -> str:
return completion.text[-completion.start_position :]
return _commonprefix([get_suffix(c) for c in completions2])
def _commonprefix(strings: Iterable[str]) -> str:
# Similar to os.path.commonprefix
if not strings:
return ""
else:
s1 = min(strings)
s2 = max(strings)
for i, c in enumerate(s1):
if c != s2[i]:
return s1[:i]
return s1

View file

@ -0,0 +1,113 @@
import os
from typing import Callable, Iterable, List, Optional
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.document import Document
__all__ = [
"PathCompleter",
"ExecutableCompleter",
]
class PathCompleter(Completer):
"""
Complete for Path variables.
:param get_paths: Callable which returns a list of directories to look into
when the user enters a relative path.
:param file_filter: Callable which takes a filename and returns whether
this file should show up in the completion. ``None``
when no filtering has to be done.
:param min_input_len: Don't do autocompletion when the input string is shorter.
"""
def __init__(
self,
only_directories: bool = False,
get_paths: Optional[Callable[[], List[str]]] = None,
file_filter: Optional[Callable[[str], bool]] = None,
min_input_len: int = 0,
expanduser: bool = False,
) -> None:
self.only_directories = only_directories
self.get_paths = get_paths or (lambda: ["."])
self.file_filter = file_filter or (lambda _: True)
self.min_input_len = min_input_len
self.expanduser = expanduser
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
text = document.text_before_cursor
# Complete only when we have at least the minimal input length,
# otherwise, we can too many results and autocompletion will become too
# heavy.
if len(text) < self.min_input_len:
return
try:
# Do tilde expansion.
if self.expanduser:
text = os.path.expanduser(text)
# Directories where to look.
dirname = os.path.dirname(text)
if dirname:
directories = [
os.path.dirname(os.path.join(p, text)) for p in self.get_paths()
]
else:
directories = self.get_paths()
# Start of current file.
prefix = os.path.basename(text)
# Get all filenames.
filenames = []
for directory in directories:
# Look for matches in this directory.
if os.path.isdir(directory):
for filename in os.listdir(directory):
if filename.startswith(prefix):
filenames.append((directory, filename))
# Sort
filenames = sorted(filenames, key=lambda k: k[1])
# Yield them.
for directory, filename in filenames:
completion = filename[len(prefix) :]
full_name = os.path.join(directory, filename)
if os.path.isdir(full_name):
# For directories, add a slash to the filename.
# (We don't add them to the `completion`. Users can type it
# to trigger the autocompletion themselves.)
filename += "/"
elif self.only_directories:
continue
if not self.file_filter(full_name):
continue
yield Completion(completion, 0, display=filename)
except OSError:
pass
class ExecutableCompleter(PathCompleter):
"""
Complete only executable files in the current path.
"""
def __init__(self) -> None:
super().__init__(
only_directories=False,
min_input_len=1,
get_paths=lambda: os.environ.get("PATH", "").split(os.pathsep),
file_filter=lambda name: os.access(name, os.X_OK),
expanduser=True,
),

View file

@ -0,0 +1,199 @@
import re
from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union
from prompt_toolkit.document import Document
from prompt_toolkit.filters import FilterOrBool, to_filter
from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples
from .base import CompleteEvent, Completer, Completion
from .word_completer import WordCompleter
__all__ = [
"FuzzyCompleter",
"FuzzyWordCompleter",
]
class FuzzyCompleter(Completer):
"""
Fuzzy completion.
This wraps any other completer and turns it into a fuzzy completer.
If the list of words is: ["leopard" , "gorilla", "dinosaur", "cat", "bee"]
Then trying to complete "oar" would yield "leopard" and "dinosaur", but not
the others, because they match the regular expression 'o.*a.*r'.
Similar, in another application "djm" could expand to "django_migrations".
The results are sorted by relevance, which is defined as the start position
and the length of the match.
Notice that this is not really a tool to work around spelling mistakes,
like what would be possible with difflib. The purpose is rather to have a
quicker or more intuitive way to filter the given completions, especially
when many completions have a common prefix.
Fuzzy algorithm is based on this post:
https://blog.amjith.com/fuzzyfinder-in-10-lines-of-python
:param completer: A :class:`~.Completer` instance.
:param WORD: When True, use WORD characters.
:param pattern: Regex pattern which selects the characters before the
cursor that are considered for the fuzzy matching.
:param enable_fuzzy: (bool or `Filter`) Enabled the fuzzy behavior. For
easily turning fuzzyness on or off according to a certain condition.
"""
def __init__(
self,
completer: Completer,
WORD: bool = False,
pattern: Optional[str] = None,
enable_fuzzy: FilterOrBool = True,
):
assert pattern is None or pattern.startswith("^")
self.completer = completer
self.pattern = pattern
self.WORD = WORD
self.pattern = pattern
self.enable_fuzzy = to_filter(enable_fuzzy)
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
if self.enable_fuzzy():
return self._get_fuzzy_completions(document, complete_event)
else:
return self.completer.get_completions(document, complete_event)
def _get_pattern(self) -> str:
if self.pattern:
return self.pattern
if self.WORD:
return r"[^\s]+"
return "^[a-zA-Z0-9_]*"
def _get_fuzzy_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
word_before_cursor = document.get_word_before_cursor(
pattern=re.compile(self._get_pattern())
)
# Get completions
document2 = Document(
text=document.text[: document.cursor_position - len(word_before_cursor)],
cursor_position=document.cursor_position - len(word_before_cursor),
)
completions = list(self.completer.get_completions(document2, complete_event))
fuzzy_matches: List[_FuzzyMatch] = []
pat = ".*?".join(map(re.escape, word_before_cursor))
pat = "(?=({0}))".format(pat) # lookahead regex to manage overlapping matches
regex = re.compile(pat, re.IGNORECASE)
for compl in completions:
matches = list(regex.finditer(compl.text))
if matches:
# Prefer the match, closest to the left, then shortest.
best = min(matches, key=lambda m: (m.start(), len(m.group(1))))
fuzzy_matches.append(
_FuzzyMatch(len(best.group(1)), best.start(), compl)
)
def sort_key(fuzzy_match: "_FuzzyMatch") -> Tuple[int, int]:
" Sort by start position, then by the length of the match. "
return fuzzy_match.start_pos, fuzzy_match.match_length
fuzzy_matches = sorted(fuzzy_matches, key=sort_key)
for match in fuzzy_matches:
# Include these completions, but set the correct `display`
# attribute and `start_position`.
yield Completion(
match.completion.text,
start_position=match.completion.start_position
- len(word_before_cursor),
display_meta=match.completion.display_meta,
display=self._get_display(match, word_before_cursor),
style=match.completion.style,
)
def _get_display(
self, fuzzy_match: "_FuzzyMatch", word_before_cursor: str
) -> AnyFormattedText:
"""
Generate formatted text for the display label.
"""
m = fuzzy_match
word = m.completion.text
if m.match_length == 0:
# No highlighting when we have zero length matches (no input text).
return word
result: StyleAndTextTuples = []
# Text before match.
result.append(("class:fuzzymatch.outside", word[: m.start_pos]))
# The match itself.
characters = list(word_before_cursor)
for c in word[m.start_pos : m.start_pos + m.match_length]:
classname = "class:fuzzymatch.inside"
if characters and c.lower() == characters[0].lower():
classname += ".character"
del characters[0]
result.append((classname, c))
# Text after match.
result.append(
("class:fuzzymatch.outside", word[m.start_pos + m.match_length :])
)
return result
class FuzzyWordCompleter(Completer):
"""
Fuzzy completion on a list of words.
(This is basically a `WordCompleter` wrapped in a `FuzzyCompleter`.)
:param words: List of words or callable that returns a list of words.
:param meta_dict: Optional dict mapping words to their meta-information.
:param WORD: When True, use WORD characters.
"""
def __init__(
self,
words: Union[List[str], Callable[[], List[str]]],
meta_dict: Optional[Dict[str, str]] = None,
WORD: bool = False,
) -> None:
self.words = words
self.meta_dict = meta_dict or {}
self.WORD = WORD
self.word_completer = WordCompleter(
words=self.words, WORD=self.WORD, meta_dict=self.meta_dict
)
self.fuzzy_completer = FuzzyCompleter(self.word_completer, WORD=self.WORD)
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
return self.fuzzy_completer.get_completions(document, complete_event)
_FuzzyMatch = NamedTuple(
"_FuzzyMatch",
[("match_length", int), ("start_pos", int), ("completion", Completion)],
)

View file

@ -0,0 +1,109 @@
"""
Nestedcompleter for completion of hierarchical data structures.
"""
from typing import Any, Dict, Iterable, Mapping, Optional, Set, Union
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.completion.word_completer import WordCompleter
from prompt_toolkit.document import Document
__all__ = ["NestedCompleter"]
# NestedDict = Mapping[str, Union['NestedDict', Set[str], None, Completer]]
NestedDict = Mapping[str, Union[Any, Set[str], None, Completer]]
class NestedCompleter(Completer):
"""
Completer which wraps around several other completers, and calls any the
one that corresponds with the first word of the input.
By combining multiple `NestedCompleter` instances, we can achieve multiple
hierarchical levels of autocompletion. This is useful when `WordCompleter`
is not sufficient.
If you need multiple levels, check out the `from_nested_dict` classmethod.
"""
def __init__(
self, options: Dict[str, Optional[Completer]], ignore_case: bool = True
) -> None:
self.options = options
self.ignore_case = ignore_case
def __repr__(self) -> str:
return "NestedCompleter(%r, ignore_case=%r)" % (self.options, self.ignore_case)
@classmethod
def from_nested_dict(cls, data: NestedDict) -> "NestedCompleter":
"""
Create a `NestedCompleter`, starting from a nested dictionary data
structure, like this:
.. code::
data = {
'show': {
'version': None,
'interfaces': None,
'clock': None,
'ip': {'interface': {'brief'}}
},
'exit': None
'enable': None
}
The value should be `None` if there is no further completion at some
point. If all values in the dictionary are None, it is also possible to
use a set instead.
Values in this data structure can be a completers as well.
"""
options: Dict[str, Optional[Completer]] = {}
for key, value in data.items():
if isinstance(value, Completer):
options[key] = value
elif isinstance(value, dict):
options[key] = cls.from_nested_dict(value)
elif isinstance(value, set):
options[key] = cls.from_nested_dict({item: None for item in value})
else:
assert value is None
options[key] = None
return cls(options)
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
# Split document.
text = document.text_before_cursor.lstrip()
stripped_len = len(document.text_before_cursor) - len(text)
# If there is a space, check for the first term, and use a
# subcompleter.
if " " in text:
first_term = text.split()[0]
completer = self.options.get(first_term)
# If we have a sub completer, use this for the completions.
if completer is not None:
remaining_text = text[len(first_term) :].lstrip()
move_cursor = len(text) - len(remaining_text) + stripped_len
new_document = Document(
remaining_text,
cursor_position=document.cursor_position - move_cursor,
)
for c in completer.get_completions(new_document, complete_event):
yield c
# No space in the input: behave exactly like `WordCompleter`.
else:
completer = WordCompleter(
list(self.options.keys()), ignore_case=self.ignore_case
)
for c in completer.get_completions(document, complete_event):
yield c

View file

@ -0,0 +1,84 @@
from typing import Callable, Dict, Iterable, List, Optional, Pattern, Union
from prompt_toolkit.completion import CompleteEvent, Completer, Completion
from prompt_toolkit.document import Document
__all__ = [
"WordCompleter",
]
class WordCompleter(Completer):
"""
Simple autocompletion on a list of words.
:param words: List of words or callable that returns a list of words.
:param ignore_case: If True, case-insensitive completion.
:param meta_dict: Optional dict mapping words to their meta-text. (This
should map strings to strings or formatted text.)
:param WORD: When True, use WORD characters.
:param sentence: When True, don't complete by comparing the word before the
cursor, but by comparing all the text before the cursor. In this case,
the list of words is just a list of strings, where each string can
contain spaces. (Can not be used together with the WORD option.)
:param match_middle: When True, match not only the start, but also in the
middle of the word.
:param pattern: Optional compiled regex for finding the word before
the cursor to complete. When given, use this regex pattern instead of
default one (see document._FIND_WORD_RE)
"""
def __init__(
self,
words: Union[List[str], Callable[[], List[str]]],
ignore_case: bool = False,
meta_dict: Optional[Dict[str, str]] = None,
WORD: bool = False,
sentence: bool = False,
match_middle: bool = False,
pattern: Optional[Pattern[str]] = None,
) -> None:
assert not (WORD and sentence)
self.words = words
self.ignore_case = ignore_case
self.meta_dict = meta_dict or {}
self.WORD = WORD
self.sentence = sentence
self.match_middle = match_middle
self.pattern = pattern
def get_completions(
self, document: Document, complete_event: CompleteEvent
) -> Iterable[Completion]:
# Get list of words.
words = self.words
if callable(words):
words = words()
# Get word/text before cursor.
if self.sentence:
word_before_cursor = document.text_before_cursor
else:
word_before_cursor = document.get_word_before_cursor(
WORD=self.WORD, pattern=self.pattern
)
if self.ignore_case:
word_before_cursor = word_before_cursor.lower()
def word_matches(word: str) -> bool:
""" True when the word before the cursor matches. """
if self.ignore_case:
word = word.lower()
if self.match_middle:
return word_before_cursor in word
else:
return word.startswith(word_before_cursor)
for a in words:
if word_matches(a):
display_meta = self.meta_dict.get(a, "")
yield Completion(a, -len(word_before_cursor), display_meta=display_meta)