inquirer-textual 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,61 @@
1
+ from typing import TypeVar
2
+
3
+ from textual.app import App
4
+ from textual.app import ComposeResult
5
+ from textual.widgets import Footer
6
+
7
+ from inquirer_textual.common.Result import Result
8
+ from inquirer_textual.common.Shortcut import Shortcut
9
+ from inquirer_textual.widgets.InquirerWidget import InquirerWidget
10
+
11
+ T = TypeVar('T')
12
+
13
+
14
+ class InquirerApp(App[Result[T]], inherit_bindings=False): # type: ignore[call-arg]
15
+ CSS = """
16
+ App {
17
+ background: black;
18
+ }
19
+ Screen {
20
+ border-top: none;
21
+ border-bottom: none;
22
+ background: transparent;
23
+ height: auto;
24
+ }
25
+ """
26
+ ENABLE_COMMAND_PALETTE = False
27
+ INLINE_PADDING = 0
28
+
29
+ def __init__(self, widget: InquirerWidget, shortcuts: list[Shortcut] | None = None, show_footer: bool = False):
30
+ super().__init__()
31
+ self.widget = widget
32
+ self.shortcuts = shortcuts
33
+ self.show_footer = show_footer
34
+
35
+ def on_mount(self) -> None:
36
+ if self.shortcuts:
37
+ for shortcut in self.shortcuts:
38
+ self._bindings.bind(shortcut.key, f'shortcut("{shortcut.command}")',
39
+ description=shortcut.description,
40
+ show=shortcut.show)
41
+
42
+ def action_shortcut(self, command: str):
43
+ self._exit_select(command)
44
+
45
+ def on_inquirer_widget_submit(self, event: InquirerWidget.Submit) -> None:
46
+ self.call_after_refresh(lambda: self.app.exit(Result(event.command, event.value)))
47
+
48
+ def _exit_select(self, command: str):
49
+ self.call_after_refresh(lambda: self.app.exit(Result(command, self.widget.current_value())))
50
+
51
+ def compose(self) -> ComposeResult:
52
+ yield self.widget
53
+ if self.show_footer:
54
+ yield Footer()
55
+
56
+ def get_theme_variable_defaults(self) -> dict[str, str]:
57
+ return {
58
+ 'select-question-mark': '#e5c07b',
59
+ 'select-list-item-highlight-foreground': '#61afef',
60
+ 'input-color': '#98c379'
61
+ }
File without changes
@@ -0,0 +1,9 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+
5
+ @dataclass
6
+ class Choice:
7
+ name: str
8
+ data: Any = None
9
+ command: str = 'select'
@@ -0,0 +1,22 @@
1
+ from textual.widgets import Label
2
+
3
+ from inquirer_textual.common.Choice import Choice
4
+
5
+
6
+ class ChoiceCheckboxLabel(Label):
7
+ def __init__(self, item: str | Choice):
8
+ super().__init__(f'\u25cb {item if isinstance(item, str) else item.name}')
9
+ self._text = item if isinstance(item, str) else item.name
10
+ self.item = item
11
+ self.checked = False
12
+
13
+ def check(self):
14
+ self.checked = True
15
+ self.update(f'\u25c9 {self._text}')
16
+
17
+ def uncheck(self):
18
+ self.checked = False
19
+ self.update(f'\u25cb {self._text}')
20
+
21
+ def toggle(self):
22
+ self.uncheck() if self.checked else self.check()
@@ -0,0 +1,16 @@
1
+ from textual.widgets import Label
2
+
3
+ from inquirer_textual.common.Choice import Choice
4
+
5
+
6
+ class ChoiceLabel(Label):
7
+ def __init__(self, item: str | Choice):
8
+ super().__init__(f' {item if isinstance(item, str) else item.name}')
9
+ self._text = item if isinstance(item, str) else item.name
10
+ self.item = item
11
+
12
+ def add_pointer(self):
13
+ self.update(f'\u276f {self._text}')
14
+
15
+ def remove_pointer(self):
16
+ self.update(f' {self._text}')
@@ -0,0 +1,30 @@
1
+ from textual.app import ComposeResult
2
+ from textual.containers import HorizontalGroup
3
+ from textual.widget import Widget
4
+ from textual.widgets import Static, Label
5
+
6
+
7
+ class PromptMessage(Widget):
8
+ DEFAULT_CSS = """
9
+ PromptMessage {
10
+ width: auto;
11
+ height: auto;
12
+ }
13
+ #prompt-message-container {
14
+ width: auto;
15
+ margin-right: 1;
16
+ }
17
+ #prompt-message-question-mark {
18
+ width: auto;
19
+ color: $select-question-mark;
20
+ }
21
+ """
22
+
23
+ def __init__(self, message: str):
24
+ super().__init__()
25
+ self.message = message
26
+
27
+ def compose(self) -> ComposeResult:
28
+ with HorizontalGroup(id='prompt-message-container'):
29
+ yield Static('? ', id='prompt-message-question-mark')
30
+ yield Label(self.message)
@@ -0,0 +1,13 @@
1
+ from dataclasses import dataclass
2
+ from typing import TypeVar
3
+
4
+ T = TypeVar('T')
5
+
6
+
7
+ @dataclass
8
+ class Result[T]:
9
+ command: str | None
10
+ value: T
11
+
12
+ def __str__(self):
13
+ return str(self.value)
@@ -0,0 +1,13 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Shortcut:
6
+ key: str
7
+ command: str
8
+ description: str | None = None
9
+ show: bool = True
10
+
11
+ def __post_init__(self):
12
+ if self.description is None:
13
+ self.description = self.command
File without changes
@@ -0,0 +1,61 @@
1
+ from typing import Any, Iterable
2
+
3
+ from inquirer_textual.InquirerApp import InquirerApp
4
+ from inquirer_textual.common.Choice import Choice
5
+ from inquirer_textual.widgets.InquirerConfirm import InquirerConfirm
6
+ from inquirer_textual.widgets.InquirerMulti import InquirerMulti
7
+ from inquirer_textual.widgets.InquirerNumber import InquirerNumber
8
+ from inquirer_textual.widgets.InquirerSecret import InquirerSecret
9
+ from inquirer_textual.widgets.InquirerText import InquirerText
10
+ from inquirer_textual.widgets.InquirerWidget import InquirerWidget
11
+ from inquirer_textual.common.Result import Result
12
+ from inquirer_textual.common.Shortcut import Shortcut
13
+ from inquirer_textual.widgets.InquirerCheckbox import InquirerCheckbox
14
+ from inquirer_textual.widgets.InquirerSelect import InquirerSelect
15
+ from textual.validation import Validator
16
+
17
+
18
+ def text(message: str, shortcuts: list[Shortcut] | None = None,
19
+ validators: Validator | Iterable[Validator] | None = None) -> Result[str]:
20
+ widget = InquirerText(message, validators=validators)
21
+ app: InquirerApp[str] = InquirerApp(widget, shortcuts, show_footer=bool(shortcuts))
22
+ return app.run(inline=True)
23
+
24
+
25
+ def secret(message: str, shortcuts: list[Shortcut] | None = None) -> Result[str]:
26
+ widget = InquirerSecret(message)
27
+ app: InquirerApp[str] = InquirerApp(widget, shortcuts, show_footer=bool(shortcuts))
28
+ return app.run(inline=True)
29
+
30
+
31
+ def number(message: str, shortcuts: list[Shortcut] | None = None) -> Result[int]:
32
+ widget = InquirerNumber(message)
33
+ app: InquirerApp[int] = InquirerApp(widget, shortcuts, show_footer=bool(shortcuts))
34
+ return app.run(inline=True)
35
+
36
+
37
+ def confirm(message: str, shortcuts: list[Shortcut] | None = None, default: bool = False, mandatory: bool = True) -> \
38
+ Result[bool]:
39
+ widget = InquirerConfirm(message, default=default, mandatory=mandatory)
40
+ app: InquirerApp[bool] = InquirerApp(widget, shortcuts, show_footer=bool(shortcuts))
41
+ return app.run(inline=True)
42
+
43
+
44
+ def select(message: str, choices: list[str | Choice], shortcuts: list[Shortcut] | None = None,
45
+ default: str | Choice | None = None, mandatory: bool = True) -> Result[str | Choice]:
46
+ widget = InquirerSelect(message, choices, default, mandatory)
47
+ app: InquirerApp[str | Choice] = InquirerApp(widget, shortcuts, show_footer=bool(shortcuts))
48
+ return app.run(inline=True)
49
+
50
+
51
+ def checkbox(message: str, choices: list[str | Choice], shortcuts: list[Shortcut] | None = None,
52
+ enabled: list[str | Choice] | None = None) -> Result[list[str | Choice]]:
53
+ widget = InquirerCheckbox(message, choices, enabled)
54
+ app: InquirerApp[list[str | Choice]] = InquirerApp(widget, shortcuts, show_footer=bool(shortcuts))
55
+ return app.run(inline=True)
56
+
57
+
58
+ def multi(widgets: list[InquirerWidget], shortcuts: list[Shortcut] | None = None) -> Result[list[Any]]:
59
+ widget = InquirerMulti(widgets)
60
+ app: InquirerApp[list[Any]] = InquirerApp(widget, shortcuts, show_footer=bool(shortcuts))
61
+ return app.run(inline=True)
@@ -0,0 +1 @@
1
+ version = "0.1.0"
@@ -0,0 +1,77 @@
1
+ from typing import Self
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.containers import VerticalGroup
5
+ from textual.widgets import ListItem, ListView
6
+
7
+ from inquirer_textual.common.Choice import Choice
8
+ from inquirer_textual.common.ChoiceCheckboxLabel import ChoiceCheckboxLabel
9
+ from inquirer_textual.common.PromptMessage import PromptMessage
10
+ from inquirer_textual.widgets.InquirerWidget import InquirerWidget
11
+
12
+
13
+ class InquirerCheckbox(InquirerWidget):
14
+ """A checkbox widget that allows multiple selections from a list of choices."""
15
+
16
+ DEFAULT_CSS = """
17
+ #inquirer-checkbox-list-view {
18
+ background: transparent;
19
+ }
20
+ #inquirer-checkbox-list-view ListItem.-highlight {
21
+ color: $select-list-item-highlight-foreground;
22
+ background: transparent;
23
+ }
24
+ """
25
+ BINDINGS = [
26
+ ("space", "toggle_selected", "Toggle selection"),
27
+ ]
28
+
29
+ def __init__(self, message: str, choices: list[str | Choice], enabled: list[str | Choice] | None = None):
30
+ """
31
+ Args:
32
+ message (str): The prompt message to display.
33
+ choices (list[str | Choice]): A list of choices to present to the user.
34
+ enabled (list[str | Choice] | None): A list of choices that should be pre-selected.
35
+ """
36
+ super().__init__()
37
+ self.message = message
38
+ self.choices = choices
39
+ self.enabled = enabled
40
+ self.list_view: ListView | None = None
41
+ self.selected_label: ChoiceCheckboxLabel | None = None
42
+ self.selected_item: str | Choice | None = None
43
+
44
+ def on_mount(self):
45
+ self.styles.height = min(10, len(self.choices) + 1)
46
+
47
+ def action_toggle_selected(self) -> None:
48
+ if self.selected_label:
49
+ self.selected_label.toggle()
50
+
51
+ def on_list_view_highlighted(self, event: ListView.Highlighted) -> None:
52
+ label = event.item.query_one(ChoiceCheckboxLabel)
53
+ self.selected_label = label
54
+ self.selected_item = label.item
55
+
56
+ def on_list_view_selected(self, _: ListView.Selected) -> None:
57
+ self.post_message(InquirerWidget.Submit(self.current_value()))
58
+
59
+ def focus(self, scroll_visible: bool = True) -> Self:
60
+ if self.list_view:
61
+ return self.list_view.focus(scroll_visible)
62
+ else:
63
+ return super().focus(scroll_visible)
64
+
65
+ def current_value(self):
66
+ labels = self.query(ChoiceCheckboxLabel)
67
+ return [label.item for label in labels if label.checked]
68
+
69
+ def compose(self) -> ComposeResult:
70
+ with VerticalGroup():
71
+ items: list[ListItem] = []
72
+ for idx, choice in enumerate(self.choices):
73
+ list_item = ListItem(ChoiceCheckboxLabel(choice))
74
+ items.append(list_item)
75
+ self.list_view = ListView(*items, id='inquirer-checkbox-list-view')
76
+ yield PromptMessage(self.message)
77
+ yield self.list_view
@@ -0,0 +1,57 @@
1
+ from inquirer_textual.widgets.InquirerWidget import InquirerWidget
2
+ from inquirer_textual.common.PromptMessage import PromptMessage
3
+ from textual import events
4
+ from textual.app import ComposeResult
5
+ from textual.containers import HorizontalGroup
6
+ from textual.widgets import Label
7
+
8
+
9
+ class InquirerConfirm(InquirerWidget):
10
+ """A confirmation prompt that allows the user to confirm or reject."""
11
+
12
+ DEFAULT_CSS = """
13
+ InquirerConfirm {
14
+ height: auto;
15
+ }
16
+ """
17
+ can_focus = True
18
+
19
+ def __init__(self, message: str, confirm_character: str = 'y', reject_character: str = 'n', default=False,
20
+ mandatory: bool = True):
21
+ """
22
+ Args:
23
+ message (str): The prompt message to display.
24
+ confirm_character (str): The character to use for confirmation.
25
+ reject_character (str): The character to use for rejection.
26
+ default (bool): The default value if the user presses Enter without input.
27
+ mandatory (bool): Whether a response is mandatory.
28
+ """
29
+ super().__init__(mandatory)
30
+ if len(confirm_character) != 1 or len(reject_character) != 1:
31
+ raise ValueError("confirm_character and reject_character must be a single character")
32
+ if confirm_character.lower() == reject_character.lower():
33
+ raise ValueError("confirm_character and reject_character must be different")
34
+ self.message = message
35
+ c = confirm_character if not default else confirm_character.upper()
36
+ r = reject_character if default else reject_character.upper()
37
+ self.label = Label(f'({c}/{r})')
38
+ self.value: bool = default
39
+
40
+ def on_key(self, event: events.Key):
41
+ if event.key.lower() == 'y':
42
+ self.value = True
43
+ self.post_message(InquirerWidget.Submit(self.value))
44
+ elif event.key.lower() == 'n':
45
+ self.value = False
46
+ self.post_message(InquirerWidget.Submit(self.value))
47
+ elif event.key == 'enter':
48
+ event.stop()
49
+ self.post_message(InquirerWidget.Submit(self.value))
50
+
51
+ def current_value(self):
52
+ return self.value
53
+
54
+ def compose(self) -> ComposeResult:
55
+ with HorizontalGroup():
56
+ yield PromptMessage(self.message)
57
+ yield self.label
@@ -0,0 +1,46 @@
1
+ from typing import Any
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.widgets import ContentSwitcher
5
+
6
+ from inquirer_textual.widgets.InquirerWidget import InquirerWidget
7
+
8
+
9
+ class InquirerMulti(InquirerWidget):
10
+ """A prompt that allows the user to answer multiple prompts in sequence."""
11
+
12
+ DEFAULT_CSS = """
13
+ InquirerMulti {
14
+ height: auto;
15
+ }
16
+ """
17
+
18
+ def __init__(self, widgets: list[InquirerWidget]) -> None:
19
+ """
20
+ Args:
21
+ widgets (list[InquirerWidget]): A list of InquirerWidget instances to present in sequence.
22
+ """
23
+ super().__init__()
24
+ self.widgets = widgets
25
+ self._current_widget_index = 0
26
+ self._return_values: list[Any] = []
27
+
28
+ def on_mount(self):
29
+ self.query_one(ContentSwitcher).current = f'widget-{self._current_widget_index}'
30
+ self.query_one(ContentSwitcher).visible_content.focus()
31
+
32
+ def on_inquirer_widget_submit(self, message: InquirerWidget.Submit) -> None:
33
+ self._return_values.append(message.value)
34
+ self._current_widget_index += 1
35
+ if self._current_widget_index < len(self.widgets):
36
+ message.stop()
37
+ self.query_one(ContentSwitcher).current = f'widget-{self._current_widget_index}'
38
+ self.query_one(ContentSwitcher).visible_content.focus()
39
+ else:
40
+ message.value = self._return_values
41
+
42
+ def compose(self) -> ComposeResult:
43
+ with ContentSwitcher(initial=f'widget-{self._current_widget_index}'):
44
+ for idx, widget in enumerate(self.widgets):
45
+ widget.id = f'widget-{idx}'
46
+ yield widget
@@ -0,0 +1,52 @@
1
+ from typing import Self
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.containers import HorizontalGroup
5
+ from textual.widgets import Input
6
+
7
+ from inquirer_textual.widgets.InquirerWidget import InquirerWidget
8
+ from inquirer_textual.common.PromptMessage import PromptMessage
9
+
10
+
11
+ class InquirerNumber(InquirerWidget):
12
+ """A number input widget that allows the user to input a numerical value."""
13
+
14
+ DEFAULT_CSS = """
15
+ InquirerNumber {
16
+ height: auto;
17
+ }
18
+ #inquirer-number-input {
19
+ border: none;
20
+ background: transparent;
21
+ color: $input-color;
22
+ padding: 0;
23
+ height: 1;
24
+ }
25
+ """
26
+
27
+ def __init__(self, message: str):
28
+ """
29
+ Args:
30
+ message (str): The prompt message to display.
31
+ """
32
+ super().__init__()
33
+ self.message = message
34
+ self.input: Input | None = None
35
+
36
+ def on_input_submitted(self, submitted: Input.Submitted):
37
+ self.post_message(InquirerWidget.Submit(submitted.value))
38
+
39
+ def focus(self, scroll_visible: bool = True) -> Self:
40
+ if self.input:
41
+ return self.input.focus(scroll_visible)
42
+ else:
43
+ return super().focus(scroll_visible)
44
+
45
+ def current_value(self):
46
+ return self.input.value if self.input else None
47
+
48
+ def compose(self) -> ComposeResult:
49
+ with HorizontalGroup():
50
+ yield PromptMessage(self.message)
51
+ self.input = Input(id="inquirer-number-input", type="integer")
52
+ yield self.input
@@ -0,0 +1,53 @@
1
+ from typing import Self
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.containers import HorizontalGroup
5
+ from textual.widgets import Input
6
+
7
+ from inquirer_textual.common.PromptMessage import PromptMessage
8
+ from inquirer_textual.widgets.InquirerWidget import InquirerWidget
9
+
10
+
11
+ class InquirerSecret(InquirerWidget):
12
+ """A secret input prompt that allows the user to enter a secret value (e.g., password)."""
13
+
14
+ DEFAULT_CSS = """
15
+ InquirerSecret {
16
+ height: auto;
17
+ }
18
+ #inquirer-secret-input {
19
+ border: none;
20
+ background: transparent;
21
+ color: $input-color;
22
+ padding: 0;
23
+ height: 1;
24
+ }
25
+ """
26
+
27
+ def __init__(self, message: str):
28
+ """
29
+ Args:
30
+ message (str): The prompt message to display.
31
+ """
32
+ super().__init__()
33
+ self.message = message
34
+ self.input: Input | None = None
35
+
36
+ def on_input_submitted(self, submitted: Input.Submitted):
37
+ self.post_message(InquirerWidget.Submit(submitted.value))
38
+
39
+ def focus(self, scroll_visible: bool = True) -> Self:
40
+ if self.input:
41
+ return self.input.focus(scroll_visible)
42
+ else:
43
+ return super().focus(scroll_visible)
44
+
45
+ def current_value(self):
46
+ return self.input.value if self.input else None
47
+
48
+ def compose(self) -> ComposeResult:
49
+ with HorizontalGroup():
50
+ yield PromptMessage(self.message)
51
+ self.input = Input(id="inquirer-secret-input")
52
+ self.input.password = True
53
+ yield self.input
@@ -0,0 +1,80 @@
1
+ from typing import Self
2
+
3
+ from inquirer_textual.common.Choice import Choice
4
+ from inquirer_textual.widgets.InquirerWidget import InquirerWidget
5
+ from inquirer_textual.common.PromptMessage import PromptMessage
6
+ from inquirer_textual.common.ChoiceLabel import ChoiceLabel
7
+ from textual.app import ComposeResult
8
+ from textual.containers import VerticalGroup
9
+ from textual.widgets import ListView, ListItem
10
+
11
+
12
+ class InquirerSelect(InquirerWidget):
13
+ """A select widget that allows a single selection from a list of choices."""
14
+
15
+ DEFAULT_CSS = """
16
+ #inquirer-select-list-view {
17
+ background: transparent;
18
+ }
19
+ #inquirer-select-list-view ListItem.-highlight {
20
+ color: $select-list-item-highlight-foreground;
21
+ background: transparent;
22
+ }
23
+ """
24
+
25
+ def __init__(self, message: str, choices: list[str | Choice], default: str | Choice | None = None,
26
+ mandatory: bool = True):
27
+ """
28
+ Args:
29
+ message (str): The prompt message to display.
30
+ choices (list[str | Choice]): A list of choices to present to the user.
31
+ default (str | Choice | None): The default choice to pre-select.
32
+ mandatory (bool): Whether a response is mandatory.
33
+ """
34
+ super().__init__(mandatory)
35
+ self.message = message
36
+ self.choices = choices
37
+ self.list_view: ListView | None = None
38
+ self.selected_label: ChoiceLabel | None = None
39
+ self.selected_item: str | Choice | None = None
40
+ self.default = default
41
+
42
+ def on_mount(self):
43
+ super().on_mount()
44
+ self.styles.height = min(10, len(self.choices) + 1)
45
+
46
+ def on_list_view_highlighted(self, event: ListView.Highlighted) -> None:
47
+ if self.selected_label:
48
+ self.selected_label.remove_pointer()
49
+ label = event.item.query_one(ChoiceLabel)
50
+ label.add_pointer()
51
+ self.selected_label = label
52
+ self.selected_item = label.item
53
+
54
+ def on_list_view_selected(self, _: ListView.Selected):
55
+ if isinstance(self.selected_item, Choice):
56
+ self.post_message(InquirerWidget.Submit(self.selected_item, self.selected_item.command))
57
+ else:
58
+ self.post_message(InquirerWidget.Submit(self.selected_item))
59
+
60
+ def focus(self, scroll_visible: bool = True) -> Self:
61
+ if self.list_view:
62
+ return self.list_view.focus(scroll_visible)
63
+ else:
64
+ return super().focus(scroll_visible)
65
+
66
+ def current_value(self):
67
+ return self.selected_item
68
+
69
+ def compose(self) -> ComposeResult:
70
+ with VerticalGroup():
71
+ initial_index = 0
72
+ items: list[ListItem] = []
73
+ for idx, choice in enumerate(self.choices):
74
+ list_item = ListItem(ChoiceLabel(choice))
75
+ items.append(list_item)
76
+ if self.default and choice == self.default:
77
+ initial_index = idx
78
+ self.list_view = ListView(*items, id='inquirer-select-list-view', initial_index=initial_index)
79
+ yield PromptMessage(self.message)
80
+ yield self.list_view
@@ -0,0 +1,63 @@
1
+ from typing import Self, Iterable
2
+
3
+ from textual.app import ComposeResult
4
+ from textual.containers import HorizontalGroup
5
+ from textual.validation import Validator
6
+ from textual.widgets import Input
7
+
8
+ from inquirer_textual.widgets.InquirerWidget import InquirerWidget
9
+ from inquirer_textual.common.PromptMessage import PromptMessage
10
+
11
+
12
+ class InquirerText(InquirerWidget):
13
+ """A text input prompt that allows the user to enter a string."""
14
+
15
+ DEFAULT_CSS = """
16
+ InquirerText {
17
+ height: auto;
18
+ }
19
+ #inquirer-text-input {
20
+ border: none;
21
+ background: transparent;
22
+ color: $input-color;
23
+ padding: 0;
24
+ height: 1;
25
+ }
26
+ """
27
+
28
+ def __init__(self, message: str, default: str = '', validators: Validator | Iterable[Validator] | None = None):
29
+ """
30
+ Args:
31
+ message (str): The prompt message to display.
32
+ default (str): The default value if the user presses Enter without input.
33
+ validators (Validator | Iterable[Validator] | None): A validator or list of validators to validate the
34
+ input.
35
+ """
36
+ super().__init__()
37
+ self.message = message
38
+ self.input: Input | None = None
39
+ self.default = default
40
+ self.validators = validators
41
+
42
+ def on_mount(self):
43
+ super().on_mount()
44
+ self.input.value = self.default
45
+
46
+ def on_input_submitted(self, submitted: Input.Submitted):
47
+ if self.validators is None or submitted.validation_result.is_valid:
48
+ self.post_message(InquirerWidget.Submit(submitted.value))
49
+
50
+ def focus(self, scroll_visible: bool = True) -> Self:
51
+ if self.input:
52
+ return self.input.focus(scroll_visible)
53
+ else:
54
+ return super().focus(scroll_visible)
55
+
56
+ def current_value(self):
57
+ return self.input.value if self.input else None
58
+
59
+ def compose(self) -> ComposeResult:
60
+ with HorizontalGroup():
61
+ yield PromptMessage(self.message)
62
+ self.input = Input(id="inquirer-text-input", validators=self.validators)
63
+ yield self.input
@@ -0,0 +1,26 @@
1
+ from typing import Any
2
+
3
+ from textual.message import Message
4
+ from textual.widget import Widget
5
+
6
+
7
+ class InquirerWidget(Widget):
8
+ class Submit(Message):
9
+ def __init__(self, value: Any, command: str | None = "select"):
10
+ super().__init__()
11
+ self.value = value
12
+ self.command = command
13
+
14
+ def __init__(self, mandatory: bool = True):
15
+ super().__init__()
16
+ self.mandatory = mandatory
17
+
18
+ def on_mount(self):
19
+ if not self.mandatory:
20
+ self._bindings.bind('ctrl+c', 'exit_now', show=False)
21
+
22
+ def action_exit_now(self):
23
+ self.post_message(InquirerWidget.Submit(None, None))
24
+
25
+ def current_value(self):
26
+ raise NotImplementedError("Subclasses must implement current_value method")
File without changes
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: inquirer-textual
3
+ Version: 0.1.0
4
+ Summary: Inquirer based on Textual
5
+ Project-URL: Changelog, https://github.com/robvanderleek/inquirer-textual/blob/master/CHANGELOG.md
6
+ Project-URL: Documentation, https://robvanderleek.github.io/inquirer-textual/
7
+ Project-URL: Source, https://github.com/robvanderleek/inquirer-textual
8
+ Author-email: Rob van der Leek <robvanderleek@gmail.com>
9
+ License-Expression: GPL-3.0-or-later
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.12
12
+ Requires-Dist: textual>=6.2.1
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Inquirer-Textual
16
+
17
+ <div align="center">
18
+
19
+ ![Logo](https://raw.githubusercontent.com/robvanderleek/inquirer-textual/main/docs/assets/logo-light.png)
20
+
21
+ </div>
22
+
23
+ <div align="center">
24
+
25
+ [![main](https://github.com/robvanderleek/inquirer-textual/actions/workflows/main.yml/badge.svg)](https://github.com/robvanderleek/inquirer-textual/actions/workflows/main.yml)
26
+ [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
27
+ [![Linting: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
28
+
29
+ </div>
30
+
31
+ ## Development
32
+
33
+ Add this library as an editable local dependency to another project using `uv`:
34
+
35
+ ```shell
36
+ uv add --editable <path-to-inquirer-textual>
37
+ ```
@@ -0,0 +1,24 @@
1
+ inquirer_textual/InquirerApp.py,sha256=jKni2W65cfthR9mVU-jljJjtYr9Zhk2yLUgjcmZegOg,2016
2
+ inquirer_textual/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ inquirer_textual/prompts.py,sha256=Rb4pwVje09wcIVXUCep_Qm_pCb5adQZ3iv2yGGiBsPc,3003
4
+ inquirer_textual/version.py,sha256=aOHawL1zuHMfBWKXqwUkXcW96oXLNCY-CXdHDqkz4g4,18
5
+ inquirer_textual/common/Choice.py,sha256=ZRggsayRKcNMDLRh12Zblol_pusE2keVEXlHkaw5RsE,147
6
+ inquirer_textual/common/ChoiceCheckboxLabel.py,sha256=lUIiR9STUxRsI6psH6y7-MFgK1shDG6W7oPhkdl_3e8,639
7
+ inquirer_textual/common/ChoiceLabel.py,sha256=SMsXjMzGngFrwxkSxtthnX2XdSvBShZVnDxJEttIusY,468
8
+ inquirer_textual/common/PromptMessage.py,sha256=MCXdsE9j0XlqVKjWUhXB-9qZ24Lk7dA5QWRgUWTdF8o,831
9
+ inquirer_textual/common/Result.py,sha256=HE-LkUQwRSy_bdnR1aT9b50Ym0oFs0T-qIMmuwokG44,201
10
+ inquirer_textual/common/Shortcut.py,sha256=Mvd0pbSC1GKphhLqjVZ-i54Nd_bEjoEYmR_Xjb6WyCo,260
11
+ inquirer_textual/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ inquirer_textual/widgets/InquirerCheckbox.py,sha256=CuJYe6gVM6ahxel2rsX9tYJD1LhUbc3tmX5B9LINSJ0,2962
13
+ inquirer_textual/widgets/InquirerConfirm.py,sha256=RFxSnxj1NS_kqirWWu_VLm0C2Bf4h-3zewDJ_IAtgYs,2293
14
+ inquirer_textual/widgets/InquirerMulti.py,sha256=WhjhEq2j9IL4PIT2kgvClXorazeii4Lxv1TdUDyC60c,1637
15
+ inquirer_textual/widgets/InquirerNumber.py,sha256=dFAP2GqqT7Dh0Lb1aZLfjyjbLbSBXqEbMghZXpeplq8,1503
16
+ inquirer_textual/widgets/InquirerSecret.py,sha256=CjPb_u32cOxK5So2GRVhxPqx2-3pjT4Ic-7NmhgwxSo,1540
17
+ inquirer_textual/widgets/InquirerSelect.py,sha256=__T_2qUSPFK7qR82UkRaKeNBjb6-hOaJ3oVQE7_9PBE,3124
18
+ inquirer_textual/widgets/InquirerText.py,sha256=IMev5EmYLs90M0TrX0tuhXkMyp8wBHIuyaHqwJzhqiY,2089
19
+ inquirer_textual/widgets/InquirerWidget.py,sha256=yxQ2qcqp1dsBXZUBqiqKzcSt3aTUjEPi2I0MbQ7E7Z0,762
20
+ inquirer_textual/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ inquirer_textual-0.1.0.dist-info/METADATA,sha256=fBixuYNdQCuU-nA09XmImFHeakveZwb5M_r8lvR_Pho,1339
22
+ inquirer_textual-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ inquirer_textual-0.1.0.dist-info/licenses/LICENSE,sha256=fJuRou64yfkof3ZFdeHcgk7K8pqxis6SBr-vtUEgBuA,1073
24
+ inquirer_textual-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Rob van der Leek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.