Initial commit: Tamigo CLI with Gitea Actions and global installation support
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Union
|
||||
|
||||
from prompt_toolkit.completion import CompleteEvent
|
||||
from prompt_toolkit.completion import Completer
|
||||
from prompt_toolkit.completion import Completion
|
||||
from prompt_toolkit.document import Document
|
||||
from prompt_toolkit.formatted_text import HTML
|
||||
from prompt_toolkit.lexers import SimpleLexer
|
||||
from prompt_toolkit.shortcuts.prompt import CompleteStyle
|
||||
from prompt_toolkit.shortcuts.prompt import PromptSession
|
||||
from prompt_toolkit.styles import Style
|
||||
|
||||
from questionary.constants import DEFAULT_QUESTION_PREFIX
|
||||
from questionary.prompts.common import build_validator
|
||||
from questionary.question import Question
|
||||
from questionary.styles import merge_styles_default
|
||||
|
||||
|
||||
class WordCompleter(Completer):
|
||||
choices_source: Union[List[str], Callable[[], List[str]]]
|
||||
ignore_case: bool
|
||||
meta_information: Dict[str, Any]
|
||||
match_middle: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
choices: Union[List[str], Callable[[], List[str]]],
|
||||
ignore_case: bool = True,
|
||||
meta_information: Optional[Dict[str, Any]] = None,
|
||||
match_middle: bool = True,
|
||||
) -> None:
|
||||
self.choices_source = choices
|
||||
self.ignore_case = ignore_case
|
||||
self.meta_information = meta_information or {}
|
||||
self.match_middle = match_middle
|
||||
|
||||
def _choices(self) -> Iterable[str]:
|
||||
return (
|
||||
self.choices_source()
|
||||
if callable(self.choices_source)
|
||||
else self.choices_source
|
||||
)
|
||||
|
||||
def _choice_matches(self, word_before_cursor: str, choice: str) -> int:
|
||||
"""Match index if found, -1 if not."""
|
||||
|
||||
if self.ignore_case:
|
||||
choice = choice.lower()
|
||||
|
||||
if self.match_middle:
|
||||
return choice.find(word_before_cursor)
|
||||
elif choice.startswith(word_before_cursor):
|
||||
return 0
|
||||
else:
|
||||
return -1
|
||||
|
||||
@staticmethod
|
||||
def _display_for_choice(choice: str, index: int, word_before_cursor: str) -> HTML:
|
||||
return HTML("{}<b><u>{}</u></b>{}").format(
|
||||
choice[:index],
|
||||
choice[index : index + len(word_before_cursor)], # noqa: E203
|
||||
choice[index + len(word_before_cursor) : len(choice)], # noqa: E203
|
||||
)
|
||||
|
||||
def get_completions(
|
||||
self, document: Document, complete_event: CompleteEvent
|
||||
) -> Iterable[Completion]:
|
||||
choices = self._choices()
|
||||
|
||||
# Get word/text before cursor.
|
||||
word_before_cursor = document.text_before_cursor
|
||||
|
||||
if self.ignore_case:
|
||||
word_before_cursor = word_before_cursor.lower()
|
||||
|
||||
for choice in choices:
|
||||
index = self._choice_matches(word_before_cursor, choice)
|
||||
if index == -1:
|
||||
# didn't find a match
|
||||
continue
|
||||
|
||||
display_meta = self.meta_information.get(choice, "")
|
||||
display = self._display_for_choice(choice, index, word_before_cursor)
|
||||
|
||||
yield Completion(
|
||||
choice,
|
||||
start_position=-len(choice),
|
||||
display=display.formatted_text,
|
||||
display_meta=display_meta,
|
||||
style="class:answer",
|
||||
selected_style="class:selected",
|
||||
)
|
||||
|
||||
|
||||
def autocomplete(
|
||||
message: str,
|
||||
choices: List[str],
|
||||
default: str = "",
|
||||
qmark: str = DEFAULT_QUESTION_PREFIX,
|
||||
completer: Optional[Completer] = None,
|
||||
meta_information: Optional[Dict[str, Any]] = None,
|
||||
ignore_case: bool = True,
|
||||
match_middle: bool = True,
|
||||
complete_style: CompleteStyle = CompleteStyle.COLUMN,
|
||||
validate: Any = None,
|
||||
style: Optional[Style] = None,
|
||||
**kwargs: Any,
|
||||
) -> Question:
|
||||
"""Prompt the user to enter a message with autocomplete help.
|
||||
|
||||
Example:
|
||||
>>> import questionary
|
||||
>>> questionary.autocomplete(
|
||||
... 'Choose ant species',
|
||||
... choices=[
|
||||
... 'Camponotus pennsylvanicus',
|
||||
... 'Linepithema humile',
|
||||
... 'Eciton burchellii',
|
||||
... "Atta colombica",
|
||||
... 'Polyergus lucidus',
|
||||
... 'Polyergus rufescens',
|
||||
... ]).ask()
|
||||
? Choose ant species Atta colombica
|
||||
'Atta colombica'
|
||||
|
||||
.. image:: ../images/autocomplete.gif
|
||||
|
||||
This is just a really basic example, the prompt can be customised using the
|
||||
parameters.
|
||||
|
||||
|
||||
Args:
|
||||
message: Question text
|
||||
|
||||
choices: Items shown in the selection, this contains items as strings
|
||||
|
||||
default: Default return value (single value).
|
||||
|
||||
qmark: Question prefix displayed in front of the question.
|
||||
By default this is a ``?``
|
||||
|
||||
completer: A prompt_toolkit :class:`prompt_toolkit.completion.Completion`
|
||||
implementation. If not set, a questionary completer implementation
|
||||
will be used.
|
||||
|
||||
meta_information: A dictionary with information/anything about choices.
|
||||
|
||||
ignore_case: If true autocomplete would ignore case.
|
||||
|
||||
match_middle: If true autocomplete would search in every string position
|
||||
not only in string begin.
|
||||
|
||||
complete_style: How autocomplete menu would be shown, it could be ``COLUMN``
|
||||
``MULTI_COLUMN`` or ``READLINE_LIKE`` from
|
||||
:class:`prompt_toolkit.shortcuts.CompleteStyle`.
|
||||
|
||||
validate: Require the entered value to pass a validation. The
|
||||
value can not be submitted until the validator accepts
|
||||
it (e.g. to check minimum password length).
|
||||
|
||||
This can either be a function accepting the input and
|
||||
returning a boolean, or an class reference to a
|
||||
subclass of the prompt toolkit Validator class.
|
||||
|
||||
style: A custom color and style for the question parts. You can
|
||||
configure colors as well as font types for different elements.
|
||||
|
||||
Returns:
|
||||
:class:`Question`: Question instance, ready to be prompted (using ``.ask()``).
|
||||
"""
|
||||
merged_style = merge_styles_default([style])
|
||||
|
||||
def get_prompt_tokens() -> List[Tuple[str, str]]:
|
||||
return [("class:qmark", qmark), ("class:question", " {} ".format(message))]
|
||||
|
||||
def get_meta_style(meta: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
if meta:
|
||||
for key in meta:
|
||||
meta[key] = HTML("<text>{}</text>").format(meta[key])
|
||||
|
||||
return meta
|
||||
|
||||
validator = build_validator(validate)
|
||||
|
||||
if completer is None:
|
||||
if not choices:
|
||||
raise ValueError("No choices is given, you should use Text question.")
|
||||
# use the default completer
|
||||
completer = WordCompleter(
|
||||
choices,
|
||||
ignore_case=ignore_case,
|
||||
meta_information=get_meta_style(meta_information),
|
||||
match_middle=match_middle,
|
||||
)
|
||||
|
||||
p: PromptSession = PromptSession(
|
||||
get_prompt_tokens,
|
||||
lexer=SimpleLexer("class:answer"),
|
||||
style=merged_style,
|
||||
completer=completer,
|
||||
validator=validator,
|
||||
complete_style=complete_style,
|
||||
**kwargs,
|
||||
)
|
||||
p.default_buffer.reset(Document(default))
|
||||
|
||||
return Question(p.app)
|
||||
Reference in New Issue
Block a user