From 3278abfd203b8b720e518e8f3653b61f9c94a875 Mon Sep 17 00:00:00 2001 From: MaXFeeD Date: Fri, 18 Apr 2025 16:58:29 +0300 Subject: [PATCH] Requesting review from valuable guests with example, fallbacks, hotfixes --- src/icmtoolchain/prompt.py | 131 +++++++++++++++++++++++++++++++++---- src/icmtoolchain/shell.py | 3 +- 2 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/icmtoolchain/prompt.py b/src/icmtoolchain/prompt.py index 408eaef..6aa6129 100644 --- a/src/icmtoolchain/prompt.py +++ b/src/icmtoolchain/prompt.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod -from typing import Any, Callable, Iterable, Optional, Sequence, Union +from typing import Any, Callable, Dict, Iterable, Optional, Sequence, Union from prompt_toolkit import Application from prompt_toolkit.buffer import Buffer @@ -75,6 +75,10 @@ class Feedback(metaclass=ABCMeta): self._application = self.create_application() return self._application + def get_result(self) -> Optional[object]: + self.inform_if_already_busy() + return self.result if hasattr(self, "result") else self.fallback + def pre_run(self) -> None: pass @@ -84,11 +88,13 @@ class Feedback(metaclass=ABCMeta): async def request_async(self) -> Any: self.inform_if_already_busy() - return await self.application.run_async(pre_run=self.pre_run) + self.result = await self.application.run_async(pre_run=self.pre_run) + return self.result def request(self) -> Any: self.inform_if_already_busy() - return self.application.run(pre_run=self.pre_run) + self.result = self.application.run(pre_run=self.pre_run) + return self.result async def request_async_safe(self, prints_abort: bool = True) -> Any: try: @@ -122,10 +128,10 @@ class Feedback(metaclass=ABCMeta): pretty_print(result, style="class:print.answer") def complete(self, *, result: object = None, print_result: object = None) -> None: - if result is not None: - self.print_result(print_result if print_result else result) if self.application.is_running: self.application.exit(result=result) + if result is not None: + self.print_result(print_result if print_result else result) class Input(Feedback): def __init__( @@ -135,11 +141,13 @@ class Input(Feedback): explanation: AnyFormattedText = None, default_text: Optional[str] = None, use_hint_as_fallback: bool = True, + fallback: object = None, on_input: Optional[Callable[['Input', str], None]] = None, on_validate: Optional[Callable[['Input', str], Optional[bool]]] = None, on_accept: Optional[Callable[['Input', str], Optional[bool]]] = None, ): - Feedback.__init__(self, prompt=prompt, fallback=default_text or hint) + fallback = fallback if fallback is not None else (default_text or hint) + Feedback.__init__(self, prompt=prompt, fallback=fallback) self.control_prompt = prompt self.hint = hint self.use_hint_as_fallback = use_hint_as_fallback @@ -199,7 +207,7 @@ class Confirm(Input): yes_or_no: AnyFormattedText = " (Y/n)", no_or_yes: AnyFormattedText = " (N/y)", ): - Input.__init__(self, prompt=prompt, hint="", explanation=explanation) + Input.__init__(self, prompt=prompt, hint="", explanation=explanation, fallback=default_value) self.control_prompt = merge_formatted_text([prompt, yes_or_no if default_value else no_or_yes]) self.default_value = default_value self.read_only = True @@ -228,14 +236,14 @@ class Select(Feedback): def __init__( self, prompt: AnyFormattedText = "What do you like?", - variants: Iterable[Optional[str]] = ["Rides", "Guide", "Rides", "Guide", "Rides", "Guide", "Rides", "Guide", "Rides", "Guide", "Rides", "Guide", "Rides", "Guide", "Both"], + variants: Iterable[Optional[str]] = ["Rides", "Guide", "Both"], selected_variant: Optional[Union[int, str]] = None, default_variant: Optional[Union[int, str]] = None, - returns_what: bool = True, + returns_what: bool = False, explanation: AnyFormattedText = None, on_accept: Optional[Callable[['Select', str], Optional[bool]]] = None, ): - Feedback.__init__(self, prompt=prompt) + Feedback.__init__(self, prompt=prompt, fallback=default_variant) self.variants = variants self.selected_variant = selected_variant self.default_variant = default_variant @@ -308,7 +316,106 @@ class Select(Feedback): return bindings -# REVIEW +class Review: + def __init__(self, **feedback: Union[Optional[Feedback], Callable[['Review'], Optional[Feedback]]]) -> None: + self.current_feedback = None + self.feedback_keys = [entry for entry in feedback] + self.feedback_callables = { + entry: feedback[entry] for entry in feedback + } + + def request_feedback(self) -> Optional[Feedback]: + if not hasattr(self, "current_offset"): + raise ValueError("Review#request_feedback: Review.current_offset") + if hasattr(self, "current_feedback") and self.current_feedback is not None: + self.current_feedback.inform_if_already_busy() + + self.current_feedback = None + self.current_key = None + while self.current_feedback is None: + self.current_offset += 1 + if len(self.feedback_keys) <= self.current_offset: + break + + feedback_key = self.feedback_keys[self.current_offset] + if not feedback_key or not feedback_key in self.feedback_callables: + continue + + feedback = self.feedback_callables[feedback_key] + if callable(feedback): + feedback = feedback(self) + if feedback is not None: + self.current_key = feedback_key + self.current_feedback = feedback + + return self.current_feedback + + def receive_feedback(self, returns_empty_properties: bool = False) -> Optional[object]: + if not hasattr(self, "current_feedback") or self.current_feedback is None or \ + not hasattr(self, "current_key") or self.current_key is None: + raise ValueError("Review#receive_feedback: Review.current_feedback or Review.current_key") + if not hasattr(self, "results") or self.results is None: + raise ValueError("Review#receive_feedback: Review.results") + self.current_feedback.inform_if_already_busy() + + result = self.current_feedback.get_result() + if result is not None or returns_empty_properties: + self.results[self.current_key] = result + return result + + def request(self, returns_empty_properties: bool = False) -> Dict[str, Any]: + assert not self.current_feedback + self.results = {} + self.current_offset = -1 + while True: + feedback = self.request_feedback() + if not feedback: + break + feedback.request() + self.receive_feedback(returns_empty_properties=returns_empty_properties) + self.current_feedback = None + return self.results + + async def request_async(self, returns_empty_properties: bool = False) -> Any: + assert not self.current_feedback + self.results = {} + self.current_offset = -1 + while True: + feedback = self.request_feedback() + if not feedback: + break + await feedback.request_async() + self.receive_feedback(returns_empty_properties=returns_empty_properties) + self.current_feedback = None + return self.results + + def request_safe(self, prints_abort: bool = True, returns_empty_properties: bool = False) -> Optional[Dict[str, Any]]: + try: + return self.request(returns_empty_properties=returns_empty_properties) + except (KeyboardInterrupt, EOFError): + if prints_abort: + pretty_print_attention("Abort.") + + async def request_async_safe(self, prints_abort: bool = True, returns_empty_properties: bool = False) -> Optional[Dict[str, Any]]: + try: + return await self.request_async(returns_empty_properties=returns_empty_properties) + except (KeyboardInterrupt, EOFError): + if prints_abort: + pretty_print_attention("Abort.") if __name__ == "__main__": - Select(selected_variant="Both", default_variant="Guide").request_safe() + from random import randint + templates_available = randint(0, 1) == 0 + results = Review( + template=lambda review: Select( + "Which template should be used?", + variants=["Mod Template (../toolchain-mod-template)", "Modding Tools Template (../template-assistant)"], + explanation="Templates are used to provide initial project description, create initial files, and more." + ) if templates_available else None, + name=Input("Decide a name for your project:", hint="Template Mod"), + author=Input("Author who crafted this creation:", hint="Reider746"), + version=Input("What version a project starts from:", hint="1.0"), + description=Input("Describe this masterpiece in one sentence:"), + client_side=Confirm("Is it a client mod that not requires server?", default_value=False) + ).request_safe() + print(results) diff --git a/src/icmtoolchain/shell.py b/src/icmtoolchain/shell.py index 1a2182f..8bd67db 100644 --- a/src/icmtoolchain/shell.py +++ b/src/icmtoolchain/shell.py @@ -697,8 +697,9 @@ def pretty_print(*values: object, style: str = "", sep: Optional[str] = " ", end if print_something and sep is not None and len(sep) > 0: print_formatted_text(to_formatted_text(sep, style=style), sep="", end="", file=file, flush=flush, style=baked_style, include_default_pygments_style=include_default_pygments_style) text = to_formatted_text(value, style=style, auto_convert=True) # type: ignore - print_formatted_text(text, end=end if end is not None else "\n", file=file, flush=flush, style=baked_style, include_default_pygments_style=include_default_pygments_style) + print_formatted_text(text, end="", file=file, flush=flush, style=baked_style, include_default_pygments_style=include_default_pygments_style) print_something = True + print_formatted_text(end if end is not None else "\n", end="", file=file, flush=flush, style=baked_style, include_default_pygments_style=include_default_pygments_style) def debug(*values: object, sep: Optional[str] = " ", end: Optional[str] = "\n", file: Optional[Any] = None, flush: bool = False, include_default_pygments_style: bool = False) -> None: pretty_print(*values, sep=sep, end=end, file=file, flush=flush, style="class:print.debug", include_default_pygments_style=include_default_pygments_style)