inquirer-textual 0.4.0__py3-none-any.whl → 1.0.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.
@@ -5,7 +5,7 @@ from textual.containers import HorizontalGroup
5
5
  from textual.widgets import Input
6
6
  from typing_extensions import Self
7
7
 
8
- from inquirer_textual.common.Prompt import Prompt
8
+ from inquirer_textual.common.PromptMessage import PromptMessage
9
9
  from inquirer_textual.widgets.InquirerWidget import InquirerWidget
10
10
 
11
11
 
@@ -18,18 +18,19 @@ class InquirerSecret(InquirerWidget):
18
18
  }
19
19
  #inquirer-secret-input {
20
20
  border: none;
21
- color: $inquirer-textual-input-color;
21
+ background: transparent;
22
+ color: $input-color;
22
23
  padding: 0;
23
24
  height: 1;
24
25
  }
25
26
  """
26
27
 
27
- def __init__(self, message: str, name: str | None = None, mandatory: bool = False):
28
+ def __init__(self, message: str):
28
29
  """
29
30
  Args:
30
31
  message (str): The prompt message to display.
31
32
  """
32
- super().__init__(name=name, mandatory=mandatory)
33
+ super().__init__()
33
34
  self.message = message
34
35
  self.input: Input | None = None
35
36
 
@@ -47,7 +48,7 @@ class InquirerSecret(InquirerWidget):
47
48
 
48
49
  def compose(self) -> ComposeResult:
49
50
  with HorizontalGroup():
50
- yield Prompt(self.message)
51
+ yield PromptMessage(self.message)
51
52
  self.input = Input(id="inquirer-secret-input")
52
53
  self.input.password = True
53
54
  yield self.input
@@ -1,22 +1,31 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from textual.app import ComposeResult
4
- from textual.containers import VerticalGroup, HorizontalGroup
4
+ from textual.containers import VerticalGroup
5
5
  from textual.widgets import ListView, ListItem
6
6
  from typing_extensions import Self
7
7
 
8
- from inquirer_textual.common.Answer import Answer
9
8
  from inquirer_textual.common.Choice import Choice
10
9
  from inquirer_textual.common.ChoiceLabel import ChoiceLabel
11
- from inquirer_textual.common.Prompt import Prompt
10
+ from inquirer_textual.common.PromptMessage import PromptMessage
12
11
  from inquirer_textual.widgets.InquirerWidget import InquirerWidget
13
12
 
14
13
 
15
14
  class InquirerSelect(InquirerWidget):
16
15
  """A select widget that allows a single selection from a list of choices."""
17
16
 
18
- def __init__(self, message: str, choices: list[str | Choice], name: str | None = None,
19
- default: str | Choice | None = None, mandatory: bool = True):
17
+ DEFAULT_CSS = """
18
+ #inquirer-select-list-view {
19
+ background: transparent;
20
+ }
21
+ #inquirer-select-list-view ListItem.-highlight {
22
+ color: $select-list-item-highlight-foreground;
23
+ background: transparent;
24
+ }
25
+ """
26
+
27
+ def __init__(self, message: str, choices: list[str | Choice], default: str | Choice | None = None,
28
+ mandatory: bool = True):
20
29
  """
21
30
  Args:
22
31
  message (str): The prompt message to display.
@@ -24,15 +33,13 @@ class InquirerSelect(InquirerWidget):
24
33
  default (str | Choice | None): The default choice to pre-select.
25
34
  mandatory (bool): Whether a response is mandatory.
26
35
  """
27
- super().__init__(name=name, mandatory=mandatory)
36
+ super().__init__(mandatory)
28
37
  self.message = message
29
38
  self.choices = choices
30
39
  self.list_view: ListView | None = None
31
40
  self.selected_label: ChoiceLabel | None = None
32
41
  self.selected_item: str | Choice | None = None
33
42
  self.default = default
34
- self.selected_value: str | Choice | None = None
35
- self.show_selected_value: bool = False
36
43
 
37
44
  def on_mount(self):
38
45
  super().on_mount()
@@ -51,9 +58,9 @@ class InquirerSelect(InquirerWidget):
51
58
 
52
59
  def on_list_view_selected(self, _: ListView.Selected):
53
60
  if isinstance(self.selected_item, Choice):
54
- self.submit_current_value(self.selected_item.command)
61
+ self.post_message(InquirerWidget.Submit(self.selected_item, self.selected_item.command))
55
62
  else:
56
- self.submit_current_value()
63
+ self.post_message(InquirerWidget.Submit(self.selected_item))
57
64
 
58
65
  def focus(self, scroll_visible: bool = True) -> Self:
59
66
  if self.list_view:
@@ -64,26 +71,15 @@ class InquirerSelect(InquirerWidget):
64
71
  def current_value(self):
65
72
  return self.selected_item
66
73
 
67
- async def set_selected_value(self, value: str | Choice) -> None:
68
- self.selected_value = value
69
- self.styles.height = 1
70
- self.show_selected_value = True
71
- await self.recompose()
72
-
73
74
  def compose(self) -> ComposeResult:
74
- if self.show_selected_value:
75
- with HorizontalGroup():
76
- yield Prompt(self.message)
77
- yield Answer(str(self.selected_value))
78
- else:
79
- with VerticalGroup():
80
- initial_index = 0
81
- items: list[ListItem] = []
82
- for idx, choice in enumerate(self.choices):
83
- list_item = ListItem(ChoiceLabel(choice))
84
- items.append(list_item)
85
- if self.default and choice == self.default:
86
- initial_index = idx
87
- self.list_view = ListView(*items, id='inquirer-select-list-view', initial_index=initial_index)
88
- yield Prompt(self.message)
89
- yield self.list_view
75
+ with VerticalGroup():
76
+ initial_index = 0
77
+ items: list[ListItem] = []
78
+ for idx, choice in enumerate(self.choices):
79
+ list_item = ListItem(ChoiceLabel(choice))
80
+ items.append(list_item)
81
+ if self.default and choice == self.default:
82
+ initial_index = idx
83
+ self.list_view = ListView(*items, id='inquirer-select-list-view', initial_index=initial_index)
84
+ yield PromptMessage(self.message)
85
+ yield self.list_view
@@ -8,7 +8,7 @@ from textual.validation import Validator
8
8
  from textual.widgets import Input
9
9
  from typing_extensions import Self
10
10
 
11
- from inquirer_textual.common.Prompt import Prompt
11
+ from inquirer_textual.common.PromptMessage import PromptMessage
12
12
  from inquirer_textual.widgets.InquirerWidget import InquirerWidget
13
13
 
14
14
 
@@ -21,15 +21,14 @@ class InquirerText(InquirerWidget):
21
21
  }
22
22
  #inquirer-text-input {
23
23
  border: none;
24
- color: $inquirer-textual-input-color;
24
+ background: transparent;
25
+ color: $input-color;
25
26
  padding: 0;
26
27
  height: 1;
27
28
  }
28
29
  """
29
30
 
30
- def __init__(self, message: str, name: str | None = None, default: str = '',
31
- validators: Validator | Iterable[Validator] | None = None,
32
- mandatory: bool = False):
31
+ def __init__(self, message: str, default: str = '', validators: Validator | Iterable[Validator] | None = None):
33
32
  """
34
33
  Args:
35
34
  message (str): The prompt message to display.
@@ -37,7 +36,7 @@ class InquirerText(InquirerWidget):
37
36
  validators (Validator | Iterable[Validator] | None): A validator or list of validators to validate the
38
37
  input.
39
38
  """
40
- super().__init__(name=name, mandatory=mandatory)
39
+ super().__init__()
41
40
  self.message = message
42
41
  self.input: Input | None = None
43
42
  self.default = default
@@ -49,9 +48,7 @@ class InquirerText(InquirerWidget):
49
48
 
50
49
  def on_input_submitted(self, submitted: Input.Submitted):
51
50
  if self.validators is None or submitted.validation_result.is_valid:
52
- if self.input:
53
- self.input._cursor_visible = False
54
- self.submit_current_value()
51
+ self.post_message(InquirerWidget.Submit(submitted.value))
55
52
 
56
53
  def focus(self, scroll_visible: bool = True) -> Self:
57
54
  if self.input:
@@ -64,6 +61,6 @@ class InquirerText(InquirerWidget):
64
61
 
65
62
  def compose(self) -> ComposeResult:
66
63
  with HorizontalGroup():
67
- yield Prompt(self.message)
64
+ yield PromptMessage(self.message)
68
65
  self.input = Input(id="inquirer-text-input", validators=self.validators)
69
66
  yield self.input
@@ -13,8 +13,8 @@ class InquirerWidget(Widget):
13
13
  self.value = value
14
14
  self.command = command
15
15
 
16
- def __init__(self, name: str | None = None, mandatory: bool = False):
17
- super().__init__(name=name)
16
+ def __init__(self, mandatory: bool = True):
17
+ super().__init__()
18
18
  self.mandatory = mandatory
19
19
 
20
20
  def on_mount(self):
@@ -26,9 +26,3 @@ class InquirerWidget(Widget):
26
26
 
27
27
  def current_value(self):
28
28
  raise NotImplementedError("Subclasses must implement current_value method")
29
-
30
- async def set_selected_value(self, value: Any) -> None:
31
- pass
32
-
33
- def submit_current_value(self, command: str | None = "select"):
34
- self.post_message(InquirerWidget.Submit(self.current_value(), command))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: inquirer-textual
3
- Version: 0.4.0
3
+ Version: 1.0.0
4
4
  Summary: Inquirer based on Textual
5
5
  Project-URL: Changelog, https://github.com/robvanderleek/inquirer-textual/blob/master/CHANGELOG.md
6
6
  Project-URL: Documentation, https://robvanderleek.github.io/inquirer-textual/
@@ -9,9 +9,7 @@ Author-email: Rob van der Leek <robvanderleek@gmail.com>
9
9
  License-Expression: GPL-3.0-or-later
10
10
  License-File: LICENSE
11
11
  Requires-Python: <3.15,>=3.9
12
- Requires-Dist: textual>=7.1.0
13
- Provides-Extra: examples
14
- Requires-Dist: textual-slider>=0.2.0; extra == 'examples'
12
+ Requires-Dist: textual>=6.7.1
15
13
  Description-Content-Type: text/markdown
16
14
 
17
15
  # Inquirer-Textual
@@ -38,20 +36,11 @@ Description-Content-Type: text/markdown
38
36
 
39
37
  All terminal programs start small. Some stay small, and some become incredibly
40
38
  big. The goal of this Python library is to make user input simple for small
41
- programs, while enabling a smooth transition to a comprehensive UI library as
39
+ programs, but also support a smooth transition to a comprehensive UI library as
42
40
  your program grows.
43
41
 
44
42
  Read the [documentation here](https://robvanderleek.github.io/inquirer-textual/)
45
43
 
46
- ## Installation
47
-
48
- Create and activate a virtual environment (for example with
49
- [uv](https://docs.astral.sh/uv/)), and then install this package:
50
-
51
- ```shell
52
- pip install inquirer-textual
53
- ```
54
-
55
44
  ## Development
56
45
 
57
46
  Add this library as an editable local dependency to another project using `uv`:
@@ -59,31 +48,3 @@ Add this library as an editable local dependency to another project using `uv`:
59
48
  ```shell
60
49
  uv add --editable <path-to-inquirer-textual>
61
50
  ```
62
-
63
- ### Textual console
64
-
65
- 1. Open the Textual Development Console:
66
-
67
- ```shell
68
- uv run textual console
69
- ```
70
-
71
- 2. Run application in development mode:
72
-
73
- ```shell
74
- uv run textual run --dev examples/prompt_pattern.py
75
- ```
76
-
77
- ### Static documentation
78
-
79
- Generating the static documentation:
80
-
81
- ```shell
82
- uv run mkdocs build
83
- ```
84
-
85
- Viewing the static documentation:
86
-
87
- ```shell
88
- uv run mkdocs serve
89
- ```
@@ -0,0 +1,25 @@
1
+ inquirer_textual/InquirerApp.py,sha256=2lDgzimH0EXeRnPDlre97q24TPsgIE9mI-cJfhQ8hIk,5377
2
+ inquirer_textual/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ inquirer_textual/prompts.py,sha256=NvaU1hgLe962iSHH3EtmLkbB0K5Vlav38mcHSiYXrOg,3186
4
+ inquirer_textual/version.py,sha256=4se2-QRIPsQy0Qla6DZQFQ90RVImEWlZaaA4AT2r29U,18
5
+ inquirer_textual/common/Choice.py,sha256=ZRggsayRKcNMDLRh12Zblol_pusE2keVEXlHkaw5RsE,147
6
+ inquirer_textual/common/ChoiceCheckboxLabel.py,sha256=BZg5vLe6_qc0ipYUaonNfygSOnHyJuEVrB8mmM6tOjw,675
7
+ inquirer_textual/common/ChoiceLabel.py,sha256=Wxt5IZUSHJsVEza1Uj19HZP7oD1uNvclY1UDbaNiOOM,504
8
+ inquirer_textual/common/InquirerHeader.py,sha256=txl-TRe3DKsOPcmDKy1fhfTa-YAURsjTYePDDHzcPH8,802
9
+ inquirer_textual/common/PromptMessage.py,sha256=MCXdsE9j0XlqVKjWUhXB-9qZ24Lk7dA5QWRgUWTdF8o,831
10
+ inquirer_textual/common/Result.py,sha256=mC79hFPITwyXFrrxlHxiumdywKhqGpBM32KhxzJvm-E,255
11
+ inquirer_textual/common/Shortcut.py,sha256=9mzLVnphImnehNo1HHU_gPMv-vFi5nXsb1G2CAbpuEY,297
12
+ inquirer_textual/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ inquirer_textual/widgets/InquirerCheckbox.py,sha256=ytWNqRbJ46iItJxbKfFTHGVapFnJjF9DwG3XtKfRW0E,3008
14
+ inquirer_textual/widgets/InquirerConfirm.py,sha256=RFxSnxj1NS_kqirWWu_VLm0C2Bf4h-3zewDJ_IAtgYs,2293
15
+ inquirer_textual/widgets/InquirerMulti.py,sha256=WhjhEq2j9IL4PIT2kgvClXorazeii4Lxv1TdUDyC60c,1637
16
+ inquirer_textual/widgets/InquirerNumber.py,sha256=05ZEXHVeZ-jzvn7Ts0sJkpkP6oTlgBAWcZGEyrDG0H8,1550
17
+ inquirer_textual/widgets/InquirerSecret.py,sha256=XnMP77O8XWeHvqmttb7xj94a3pgX95a9pEgLvIehmBg,1586
18
+ inquirer_textual/widgets/InquirerSelect.py,sha256=P0_7B3945I-O2HXzXpGO5HDIqD7poR6aa0yOX6a9CGM,3259
19
+ inquirer_textual/widgets/InquirerText.py,sha256=6sQxS87pAooCOdZBcZbxWfUXxqQZA5VYv1ebLv7-3J8,2154
20
+ inquirer_textual/widgets/InquirerWidget.py,sha256=56rcnnK5vqD66WKfmv8V_kZ4YG63Vkxz3pYUq18OJKo,802
21
+ inquirer_textual/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ inquirer_textual-1.0.0.dist-info/METADATA,sha256=M4X2f99JmAvvuAlK3FIJZCoL_DSgRN6BCF1wmPzbD0g,1768
23
+ inquirer_textual-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
24
+ inquirer_textual-1.0.0.dist-info/licenses/LICENSE,sha256=fJuRou64yfkof3ZFdeHcgk7K8pqxis6SBr-vtUEgBuA,1073
25
+ inquirer_textual-1.0.0.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- from rich.text import Text
2
- from textual.app import ComposeResult
3
- from textual.widget import Widget
4
- from textual.widgets import Static
5
-
6
-
7
- class Answer(Widget):
8
- DEFAULT_CSS = """
9
- Answer {
10
- height: auto;
11
- }
12
- #inquirer-textual-answer {
13
- color: $inquirer-textual-input-color;
14
- }
15
- """
16
-
17
- def __init__(self, text: str):
18
- super().__init__()
19
- self.text = text
20
-
21
- def compose(self) -> ComposeResult:
22
- yield Static(Text(self.text), id='inquirer-textual-answer')
@@ -1,24 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from dataclasses import dataclass
5
- from typing import TypeVar, Generic
6
-
7
- T = TypeVar('T')
8
-
9
-
10
- @dataclass
11
- class InquirerResult(Generic[T]):
12
- name: str | None
13
- value: T
14
- command: str | None
15
-
16
- def json(self):
17
- d = {}
18
- if self.name:
19
- d[self.name] = self.value
20
- else:
21
- d['value'] = self.value
22
- if self.command:
23
- d['command'] = self.command
24
- return json.dumps(d)
@@ -1,24 +0,0 @@
1
- from textual.theme import Theme
2
-
3
- DEFAULT_THEME = Theme(
4
- name="inquirer-textual-default",
5
- primary="#0178D4",
6
- secondary="#004578",
7
- accent="#c678dd",
8
- warning="#ffa62b",
9
- error="#e06c75",
10
- success="#4EBF71",
11
- foreground="#e0e0e0",
12
- background="black",
13
- surface="transparent",
14
- variables={
15
- 'block-cursor-foreground': '#61afef',
16
- 'block-cursor-background': 'transparent',
17
- 'block-cursor-blurred-foreground': '#61afef',
18
- 'block-cursor-blurred-background': 'transparent',
19
- 'inquirer-textual-question-mark': '#e5c07b',
20
- 'inquirer-textual-input-color': '#98c379',
21
- }
22
- )
23
-
24
- POINTER_CHARACTER = '\u276f'
@@ -1,72 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- import tempfile
5
-
6
- from textual import events
7
- from textual.app import ComposeResult
8
- from textual.containers import HorizontalGroup
9
- from textual.widgets import Static
10
-
11
- from inquirer_textual.common.Prompt import Prompt
12
- from inquirer_textual.widgets.InquirerWidget import InquirerWidget
13
-
14
-
15
- class InquirerEditor(InquirerWidget):
16
- """An input widget that uses an external editor."""
17
-
18
- DEFAULT_CSS = """
19
- InquirerEditor {
20
- height: auto;
21
- }
22
- """
23
- can_focus = True
24
-
25
- def __init__(self, message: str | None = None, name: str | None = None):
26
- """
27
- Args:
28
- message (str): The prompt message to display.
29
- name (str): The name of the prompt.
30
- """
31
- super().__init__(name=name)
32
- self.message = message
33
-
34
- def on_mount(self):
35
- super().on_mount()
36
- if not self.message:
37
- self._launch_editor()
38
-
39
- def on_key(self, event: events.Key):
40
- if event.key == 'enter':
41
- event.stop()
42
- self._launch_editor()
43
-
44
- def _launch_editor(self):
45
- with self.app.suspend():
46
- tmp = tempfile.NamedTemporaryFile()
47
- filename = tmp.name
48
- os.system(f"{get_editor_command()} {filename}")
49
- with open(filename, 'r') as f:
50
- content = f.read()
51
- os.unlink(filename)
52
- self.post_message(InquirerWidget.Submit(content))
53
-
54
- def compose(self) -> ComposeResult:
55
- if self.message:
56
- with HorizontalGroup():
57
- yield Prompt(self.message)
58
- yield Static('[dim]Press <enter> to launch editor[/dim]')
59
- else:
60
- super().compose()
61
-
62
-
63
- def get_editor_command() -> str:
64
- editor = os.environ.get('VISUAL')
65
- if not editor:
66
- editor = os.environ.get('EDITOR')
67
- if editor == 'nano':
68
- return 'nano -R'
69
- elif editor == 'code':
70
- return 'code -w -n'
71
- else:
72
- return 'vim -f -o'
@@ -1,95 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from enum import Enum
4
- from pathlib import Path
5
-
6
- from textual.app import ComposeResult
7
- from textual.containers import HorizontalGroup
8
- from textual.widgets import Input, Static
9
- from typing_extensions import Self
10
-
11
- from inquirer_textual.common.Prompt import Prompt
12
- from inquirer_textual.widgets.InquirerWidget import InquirerWidget
13
-
14
-
15
- class PathType(Enum):
16
- FILE = 'file'
17
- DIRECTORY = 'directory'
18
- ANY = 'any'
19
-
20
-
21
- class InquirerPath(InquirerWidget):
22
- """An input prompt that allows the user to enter a file path."""
23
-
24
- DEFAULT_CSS = """
25
- InquirerPath {
26
- height: auto;
27
- }
28
- #inquirer-path-input {
29
- border: none;
30
- color: $inquirer-textual-input-color;
31
- padding: 0;
32
- height: 1;
33
- }
34
- #inquirer-path-error-message {
35
- color: $error;
36
- height: auto;
37
- }
38
- """
39
-
40
- def __init__(self, message: str, name: str | None = None, default: str = '', exists: bool = False,
41
- path_type: PathType = PathType.ANY, mandatory: bool = False):
42
- """
43
- Args:
44
- message (str): The prompt message to display.
45
- default (str): The default value if the user presses Enter without input.
46
- exists (bool): If True, validate that the entered path exists.
47
- """
48
- super().__init__(name=name, mandatory=mandatory)
49
- self.message = message
50
- self.input: Input | None = None
51
- self.default = default
52
- self.exists = exists
53
- self.path_type = path_type
54
-
55
- def on_mount(self):
56
- super().on_mount()
57
- self.input.value = self.default
58
-
59
- def on_input_submitted(self, submitted: Input.Submitted):
60
- if self.exists:
61
- if not Path(submitted.value).exists():
62
- self._show_validation_error("The specified path does not exist.")
63
- return
64
- if self.path_type != PathType.ANY:
65
- path = Path(submitted.value)
66
- if self.path_type == PathType.FILE and not path.is_file():
67
- self._show_validation_error("The specified path is not a file.")
68
- return
69
- if self.path_type == PathType.DIRECTORY and not path.is_dir():
70
- self._show_validation_error("The specified path is not a directory.")
71
- return
72
- self._show_validation_error('')
73
- if self.input:
74
- self.input._cursor_visible = False
75
- self.submit_current_value()
76
-
77
- def _show_validation_error(self, message: str):
78
- error_message = self.query_one('#inquirer-path-error-message', Static)
79
- error_message.update(message)
80
-
81
- def focus(self, scroll_visible: bool = True) -> Self:
82
- if self.input:
83
- return self.input.focus(scroll_visible)
84
- else:
85
- return super().focus(scroll_visible)
86
-
87
- def current_value(self):
88
- return self.input.value if self.input else None
89
-
90
- def compose(self) -> ComposeResult:
91
- with HorizontalGroup():
92
- yield Prompt(self.message)
93
- self.input = Input(id="inquirer-path-input")
94
- yield self.input
95
- yield Static("", id="inquirer-path-error-message")