mininterface 0.6.2rc2__tar.gz → 0.7.0__tar.gz
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.
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/PKG-INFO +40 -29
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/README.md +35 -26
- mininterface-0.7.0/mininterface/ValidationFail.py +5 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/__init__.py +63 -48
- mininterface-0.7.0/mininterface/__main__.py +78 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/auxiliary.py +11 -4
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/cli_parser.py +117 -60
- mininterface-0.7.0/mininterface/exceptions.py +30 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/facet.py +36 -8
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/form_dict.py +68 -16
- mininterface-0.7.0/mininterface/interfaces.py +46 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/mininterface.py +63 -33
- mininterface-0.7.0/mininterface/showcase.py +42 -0
- mininterface-0.7.0/mininterface/start.py +95 -0
- mininterface-0.7.0/mininterface/subcommands.py +141 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tag.py +57 -23
- mininterface-0.7.0/mininterface/tag_factory.py +51 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/text_interface.py +9 -3
- mininterface-0.7.0/mininterface/textual_interface/__init__.py +45 -0
- mininterface-0.7.0/mininterface/textual_interface/textual_adaptor.py +95 -0
- mininterface-0.7.0/mininterface/textual_interface/textual_app.py +105 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/textual_interface/textual_button_app.py +3 -3
- mininterface-0.7.0/mininterface/textual_interface/textual_facet.py +31 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/textual_interface/widgets.py +2 -6
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tk_interface/__init__.py +17 -11
- mininterface-0.7.0/mininterface/tk_interface/tk_facet.py +21 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tk_interface/tk_window.py +17 -15
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tk_interface/utils.py +4 -5
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/type_stubs.py +3 -2
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/types.py +5 -1
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/validators.py +1 -1
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/pyproject.toml +10 -4
- mininterface-0.6.2rc2/mininterface/__main__.py +0 -39
- mininterface-0.6.2rc2/mininterface/common.py +0 -8
- mininterface-0.6.2rc2/mininterface/tag_factory.py +0 -28
- mininterface-0.6.2rc2/mininterface/textual_interface/__init__.py +0 -59
- mininterface-0.6.2rc2/mininterface/textual_interface/textual_app.py +0 -147
- mininterface-0.6.2rc2/mininterface/textual_interface/textual_facet.py +0 -21
- mininterface-0.6.2rc2/mininterface/tk_interface/tk_facet.py +0 -20
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/LICENSE +0 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/experimental.py +0 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/redirectable.py +0 -0
- {mininterface-0.6.2rc2 → mininterface-0.7.0}/mininterface/tk_interface/redirect_text_tkinter.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mininterface
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A minimal access to GUI, TUI, CLI and config
|
|
5
5
|
Home-page: https://github.com/CZ-NIC/mininterface
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -13,24 +13,26 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Provides-Extra: all
|
|
17
|
+
Provides-Extra: web
|
|
16
18
|
Requires-Dist: autocombobox (==1.4.2)
|
|
17
19
|
Requires-Dist: pyyaml
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
Requires-Dist: textual
|
|
20
|
+
Requires-Dist: textual (>=0.84,<0.85)
|
|
20
21
|
Requires-Dist: tkinter-tooltip
|
|
21
22
|
Requires-Dist: tkinter_form (==0.1.5.2)
|
|
23
|
+
Requires-Dist: typing_extensions
|
|
22
24
|
Requires-Dist: tyro
|
|
23
25
|
Description-Content-Type: text/markdown
|
|
24
26
|
|
|
25
27
|
# Mininterface – access to GUI, TUI, CLI and config files
|
|
26
28
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
27
29
|
[](https://github.com/CZ-NIC/mininterface/actions)
|
|
28
|
-
[](https://pepy.tech/project/mininterface)
|
|
30
|
+
[](https://pepy.tech/project/mininterface)
|
|
29
31
|
|
|
30
32
|
Write the program core, do not bother with the input/output.
|
|
31
33
|
|
|
32
|
-

|
|
35
|
+

|
|
34
36
|
|
|
35
37
|
Check out the code, which is surprisingly short, that displays such a window or its textual fallback.
|
|
36
38
|
|
|
@@ -49,10 +51,11 @@ class Env:
|
|
|
49
51
|
""" This number is very important """
|
|
50
52
|
|
|
51
53
|
if __name__ == "__main__":
|
|
52
|
-
|
|
54
|
+
m = run(Env, prog="My application")
|
|
55
|
+
m.form()
|
|
53
56
|
# Attributes are suggested by the IDE
|
|
54
57
|
# along with the hint text 'This number is very important'.
|
|
55
|
-
print(env.my_number)
|
|
58
|
+
print(m.env.my_number)
|
|
56
59
|
```
|
|
57
60
|
|
|
58
61
|
# Contents
|
|
@@ -69,16 +72,18 @@ It was all the code you need. No lengthy blocks of code imposed by an external d
|
|
|
69
72
|
|
|
70
73
|
|
|
71
74
|
```bash
|
|
72
|
-
$ ./hello.py
|
|
73
|
-
usage: My application [-h] [--
|
|
75
|
+
$ ./hello.py --help
|
|
76
|
+
usage: My application [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]
|
|
74
77
|
|
|
75
78
|
This calculates something.
|
|
76
79
|
|
|
77
|
-
╭─ options
|
|
78
|
-
│ -h, --help
|
|
79
|
-
│
|
|
80
|
-
│ --
|
|
81
|
-
|
|
80
|
+
╭─ options ───────────────────────────────────────────────────────────────╮
|
|
81
|
+
│ -h, --help show this help message and exit │
|
|
82
|
+
│ -v, --verbose Verbosity level. Can be used twice to increase. │
|
|
83
|
+
│ --my-flag, --no-my-flag │
|
|
84
|
+
│ This switches the functionality (default: False) │
|
|
85
|
+
│ --my-number INT This number is very important (default: 4) │
|
|
86
|
+
╰─────────────────────────────────────────────────────────────────────────╯
|
|
82
87
|
```
|
|
83
88
|
|
|
84
89
|
## You got config file management
|
|
@@ -120,11 +125,22 @@ Install with a single command from [PyPi](https://pypi.org/project/mininterface/
|
|
|
120
125
|
pip install mininterface
|
|
121
126
|
```
|
|
122
127
|
|
|
128
|
+
## Minimal installation
|
|
129
|
+
|
|
130
|
+
Should you need just the CLI part and you are happy with basic text dialogs, use these commands instead:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pip install --no-dependencies mininterface
|
|
134
|
+
pip install tyro typing_extensions pyyaml
|
|
135
|
+
```
|
|
136
|
+
|
|
123
137
|
# Docs
|
|
124
138
|
See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic.github.io/mininterface/Overview/).
|
|
125
139
|
|
|
126
140
|
# Examples
|
|
127
141
|
|
|
142
|
+
A powerful [`m.form`][mininterface.Mininterface.form] dialog method accepts either a dataclass or a dict. Take a look on both.
|
|
143
|
+
|
|
128
144
|
## A complex dataclass.
|
|
129
145
|
|
|
130
146
|
```python3
|
|
@@ -143,7 +159,7 @@ class Env:
|
|
|
143
159
|
nested_config: NestedEnv
|
|
144
160
|
|
|
145
161
|
mandatory_str: str
|
|
146
|
-
""" As there is
|
|
162
|
+
""" As there is no default value, you will be prompted automatically to fill up the field """
|
|
147
163
|
|
|
148
164
|
my_number: int | None = None
|
|
149
165
|
""" This is not just a dummy number, if left empty, it is None. """
|
|
@@ -167,6 +183,14 @@ print(m.env)
|
|
|
167
183
|
m.form()
|
|
168
184
|
```
|
|
169
185
|
|
|
186
|
+
As the attribute `mandatory_str` requires a value, a prompt appears automatically:
|
|
187
|
+
|
|
188
|
+

|
|
189
|
+
|
|
190
|
+
Then, full form appears:
|
|
191
|
+
|
|
192
|
+

|
|
193
|
+
|
|
170
194
|
## Form with paths
|
|
171
195
|
|
|
172
196
|
We have a dict with some paths. Here is how it looks.
|
|
@@ -187,16 +211,3 @@ m.form(my_dictionary)
|
|
|
187
211
|
|
|
188
212
|

|
|
189
213
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Mininterface – access to GUI, TUI, CLI and config files
|
|
2
2
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
3
3
|
[](https://github.com/CZ-NIC/mininterface/actions)
|
|
4
|
-
[](https://pepy.tech/project/mininterface)
|
|
4
|
+
[](https://pepy.tech/project/mininterface)
|
|
5
5
|
|
|
6
6
|
Write the program core, do not bother with the input/output.
|
|
7
7
|
|
|
8
|
-

|
|
9
|
+

|
|
10
10
|
|
|
11
11
|
Check out the code, which is surprisingly short, that displays such a window or its textual fallback.
|
|
12
12
|
|
|
@@ -25,10 +25,11 @@ class Env:
|
|
|
25
25
|
""" This number is very important """
|
|
26
26
|
|
|
27
27
|
if __name__ == "__main__":
|
|
28
|
-
|
|
28
|
+
m = run(Env, prog="My application")
|
|
29
|
+
m.form()
|
|
29
30
|
# Attributes are suggested by the IDE
|
|
30
31
|
# along with the hint text 'This number is very important'.
|
|
31
|
-
print(env.my_number)
|
|
32
|
+
print(m.env.my_number)
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
# Contents
|
|
@@ -45,16 +46,18 @@ It was all the code you need. No lengthy blocks of code imposed by an external d
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
```bash
|
|
48
|
-
$ ./hello.py
|
|
49
|
-
usage: My application [-h] [--
|
|
49
|
+
$ ./hello.py --help
|
|
50
|
+
usage: My application [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]
|
|
50
51
|
|
|
51
52
|
This calculates something.
|
|
52
53
|
|
|
53
|
-
╭─ options
|
|
54
|
-
│ -h, --help
|
|
55
|
-
│
|
|
56
|
-
│ --
|
|
57
|
-
|
|
54
|
+
╭─ options ───────────────────────────────────────────────────────────────╮
|
|
55
|
+
│ -h, --help show this help message and exit │
|
|
56
|
+
│ -v, --verbose Verbosity level. Can be used twice to increase. │
|
|
57
|
+
│ --my-flag, --no-my-flag │
|
|
58
|
+
│ This switches the functionality (default: False) │
|
|
59
|
+
│ --my-number INT This number is very important (default: 4) │
|
|
60
|
+
╰─────────────────────────────────────────────────────────────────────────╯
|
|
58
61
|
```
|
|
59
62
|
|
|
60
63
|
## You got config file management
|
|
@@ -96,11 +99,22 @@ Install with a single command from [PyPi](https://pypi.org/project/mininterface/
|
|
|
96
99
|
pip install mininterface
|
|
97
100
|
```
|
|
98
101
|
|
|
102
|
+
## Minimal installation
|
|
103
|
+
|
|
104
|
+
Should you need just the CLI part and you are happy with basic text dialogs, use these commands instead:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pip install --no-dependencies mininterface
|
|
108
|
+
pip install tyro typing_extensions pyyaml
|
|
109
|
+
```
|
|
110
|
+
|
|
99
111
|
# Docs
|
|
100
112
|
See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic.github.io/mininterface/Overview/).
|
|
101
113
|
|
|
102
114
|
# Examples
|
|
103
115
|
|
|
116
|
+
A powerful [`m.form`][mininterface.Mininterface.form] dialog method accepts either a dataclass or a dict. Take a look on both.
|
|
117
|
+
|
|
104
118
|
## A complex dataclass.
|
|
105
119
|
|
|
106
120
|
```python3
|
|
@@ -119,7 +133,7 @@ class Env:
|
|
|
119
133
|
nested_config: NestedEnv
|
|
120
134
|
|
|
121
135
|
mandatory_str: str
|
|
122
|
-
""" As there is
|
|
136
|
+
""" As there is no default value, you will be prompted automatically to fill up the field """
|
|
123
137
|
|
|
124
138
|
my_number: int | None = None
|
|
125
139
|
""" This is not just a dummy number, if left empty, it is None. """
|
|
@@ -143,6 +157,14 @@ print(m.env)
|
|
|
143
157
|
m.form()
|
|
144
158
|
```
|
|
145
159
|
|
|
160
|
+
As the attribute `mandatory_str` requires a value, a prompt appears automatically:
|
|
161
|
+
|
|
162
|
+

|
|
163
|
+
|
|
164
|
+
Then, full form appears:
|
|
165
|
+
|
|
166
|
+

|
|
167
|
+
|
|
146
168
|
## Form with paths
|
|
147
169
|
|
|
148
170
|
We have a dict with some paths. Here is how it looks.
|
|
@@ -162,16 +184,3 @@ m.form(my_dictionary)
|
|
|
162
184
|
```
|
|
163
185
|
|
|
164
186
|

|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
from dataclasses import dataclass
|
|
2
3
|
from pathlib import Path
|
|
3
|
-
from
|
|
4
|
+
from types import UnionType
|
|
5
|
+
from typing import TYPE_CHECKING, Literal, Optional, Sequence, Type, Union
|
|
4
6
|
|
|
5
|
-
from .
|
|
6
|
-
|
|
7
|
-
from .
|
|
7
|
+
from .exceptions import Cancelled, InterfaceNotAvailable
|
|
8
|
+
|
|
9
|
+
from .interfaces import get_interface
|
|
10
|
+
|
|
11
|
+
from . import validators
|
|
12
|
+
from .cli_parser import _parse_cli, assure_args
|
|
13
|
+
from .subcommands import Command, SubcommandPlaceholder
|
|
8
14
|
from .form_dict import DataClass, EnvClass
|
|
9
|
-
from .tag import Tag
|
|
10
15
|
from .mininterface import EnvClass, Mininterface
|
|
16
|
+
from .start import Start
|
|
17
|
+
from .tag import Tag
|
|
11
18
|
from .text_interface import ReplInterface, TextInterface
|
|
12
|
-
from . import
|
|
13
|
-
|
|
14
|
-
# Import optional interfaces
|
|
15
|
-
try:
|
|
16
|
-
from mininterface.tk_interface import TkInterface
|
|
17
|
-
except ImportError:
|
|
18
|
-
if TYPE_CHECKING:
|
|
19
|
-
pass # Replace TYPE_CHECKING with `type GuiInterface = None` since Python 3.12
|
|
20
|
-
else:
|
|
21
|
-
TkInterface = None
|
|
22
|
-
try:
|
|
23
|
-
from mininterface.textual_interface import TextualInterface
|
|
24
|
-
except ImportError:
|
|
25
|
-
TextualInterface = None
|
|
26
|
-
|
|
27
|
-
GuiInterface = TkInterface
|
|
28
|
-
TuiInterface = TextualInterface or TextInterface
|
|
19
|
+
from .types import Choices, PathTag, Validation
|
|
29
20
|
|
|
30
21
|
# NOTE:
|
|
31
22
|
# ask_for_missing does not work with tyro Positional, stays missing.
|
|
@@ -33,14 +24,22 @@ TuiInterface = TextualInterface or TextInterface
|
|
|
33
24
|
# class Env:
|
|
34
25
|
# files: Positional[list[Path]]
|
|
35
26
|
|
|
27
|
+
# NOTE: imgs missing in Interfaces.md
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class _Empty:
|
|
32
|
+
pass
|
|
33
|
+
|
|
36
34
|
|
|
37
|
-
def run(
|
|
35
|
+
def run(env_or_list: Type[EnvClass] | list[Type[Command]] | None = None,
|
|
38
36
|
ask_on_empty_cli: bool = False,
|
|
39
37
|
title: str = "",
|
|
40
38
|
config_file: Path | str | bool = True,
|
|
41
39
|
add_verbosity: bool = True,
|
|
42
40
|
ask_for_missing: bool = True,
|
|
43
|
-
interface: Type[Mininterface]
|
|
41
|
+
interface: Type[Mininterface] | Literal["gui"] | Literal["tui"] = None,
|
|
42
|
+
args: Optional[Sequence[str]] = None,
|
|
44
43
|
**kwargs) -> Mininterface[EnvClass]:
|
|
45
44
|
""" The main access, start here.
|
|
46
45
|
Wrap your configuration dataclass into `run` to access the interface. An interface is chosen automatically,
|
|
@@ -51,7 +50,12 @@ def run(env_class: Type[EnvClass] | None = None,
|
|
|
51
50
|
with the program name ending on *.yaml*, ex: `program.py` will fetch `./program.yaml`.
|
|
52
51
|
|
|
53
52
|
Args:
|
|
54
|
-
|
|
53
|
+
env_or_list:
|
|
54
|
+
* `dataclass` Dataclass with the configuration. Their values will be modified with the CLI arguments.
|
|
55
|
+
* `list` of [Commands][mininterface.subcommands.Command] let you create multiple commands within a single program, each with unique options.
|
|
56
|
+
* `None` You need just the dialogs, no CLI/config file parsing.
|
|
57
|
+
|
|
58
|
+
|
|
55
59
|
ask_on_empty_cli: If program was launched with no arguments (empty CLI), invokes self.form() to edit the fields.
|
|
56
60
|
(Withdrawn when `ask_for_missing` happens.)
|
|
57
61
|
```python
|
|
@@ -106,8 +110,10 @@ def run(env_class: Type[EnvClass] | None = None,
|
|
|
106
110
|
$ program.py # omitting --required-number
|
|
107
111
|
# Dialog for `required_number` appears
|
|
108
112
|
```
|
|
109
|
-
interface: Which interface to prefer. By default, we use the GUI, the fallback is the TUI.
|
|
110
|
-
|
|
113
|
+
interface: Which interface to prefer. By default, we use the GUI, the fallback is the TUI.
|
|
114
|
+
You may write "gui" or "tui" literal or pass a specific Mininterface type,
|
|
115
|
+
see the full [list](Interfaces.md) of possible interfaces.
|
|
116
|
+
args: Parse arguments from a sequence instead of the command line.
|
|
111
117
|
Kwargs:
|
|
112
118
|
The same as for [argparse.ArgumentParser](https://docs.python.org/3/library/argparse.html).
|
|
113
119
|
|
|
@@ -119,8 +125,15 @@ def run(env_class: Type[EnvClass] | None = None,
|
|
|
119
125
|
The stdout will be redirected to the interface (ex. a GUI window).
|
|
120
126
|
|
|
121
127
|
```python
|
|
128
|
+
from dataclasses import dataclass
|
|
129
|
+
from mininterface import run
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
132
|
+
class Env:
|
|
133
|
+
my_number: int = 4
|
|
134
|
+
|
|
122
135
|
with run(Env) as m:
|
|
123
|
-
print(f"Your important number is {m.env.
|
|
136
|
+
print(f"Your important number is {m.env.my_number}")
|
|
124
137
|
boolean = m.is_yes("Is that alright?")
|
|
125
138
|
```
|
|
126
139
|
|
|
@@ -139,7 +152,7 @@ def run(env_class: Type[EnvClass] | None = None,
|
|
|
139
152
|
# Undocumented experimental: `default` keyword argument for tyro may serve for default values instead of a config file.
|
|
140
153
|
|
|
141
154
|
# Prepare the config file
|
|
142
|
-
if config_file is True and not kwargs.get("default") and
|
|
155
|
+
if config_file is True and not kwargs.get("default") and env_or_list:
|
|
143
156
|
# Undocumented feature. User put a namespace into kwargs["default"]
|
|
144
157
|
# that already serves for defaults. We do not fetch defaults yet from a config file.
|
|
145
158
|
try:
|
|
@@ -155,26 +168,29 @@ def run(env_class: Type[EnvClass] | None = None,
|
|
|
155
168
|
elif isinstance(config_file, str):
|
|
156
169
|
config_file = Path(config_file)
|
|
157
170
|
|
|
158
|
-
#
|
|
171
|
+
# Determine title
|
|
172
|
+
title = title or kwargs.get("prog") or Path(sys.argv[0]).name
|
|
173
|
+
start = Start(title, interface)
|
|
174
|
+
|
|
175
|
+
# Hidden meta-commands in args
|
|
176
|
+
args = assure_args(args)
|
|
177
|
+
if len(args) == 1 and args[0] == "--integrate-to-system":
|
|
178
|
+
start.integrate(env_or_list or _Empty)
|
|
179
|
+
quit()
|
|
180
|
+
|
|
159
181
|
env, wrong_fields = None, {}
|
|
160
|
-
if
|
|
161
|
-
|
|
162
|
-
|
|
182
|
+
if isinstance(env_or_list, list) and SubcommandPlaceholder in env_or_list and args and args[0] == "subcommand":
|
|
183
|
+
start.choose_subcommand(env_or_list, args=args[1:])
|
|
184
|
+
elif isinstance(env_or_list, list) and not args:
|
|
185
|
+
start.choose_subcommand(env_or_list)
|
|
186
|
+
elif env_or_list:
|
|
187
|
+
# Load configuration from CLI and a config file
|
|
188
|
+
env, wrong_fields = _parse_cli(env_or_list, config_file, add_verbosity, ask_for_missing, args, **kwargs)
|
|
189
|
+
else: # even though there is no configuration, yet we need to parse CLI for meta-commands like --help or --verbose
|
|
190
|
+
_parse_cli(_Empty, None, add_verbosity, ask_for_missing, args)
|
|
163
191
|
|
|
164
192
|
# Build the interface
|
|
165
|
-
|
|
166
|
-
if "prog" not in kwargs:
|
|
167
|
-
kwargs["prog"] = title
|
|
168
|
-
try:
|
|
169
|
-
if interface == "tui": # undocumented feature
|
|
170
|
-
interface = TuiInterface
|
|
171
|
-
elif interface == "gui": # undocumented feature
|
|
172
|
-
interface = GuiInterface
|
|
173
|
-
if interface is None:
|
|
174
|
-
raise InterfaceNotAvailable # GuiInterface might be None when import fails
|
|
175
|
-
interface = interface(title, env)
|
|
176
|
-
except InterfaceNotAvailable: # Fallback to a different interface
|
|
177
|
-
interface = TuiInterface(title, env)
|
|
193
|
+
interface = get_interface(title, interface, env)
|
|
178
194
|
|
|
179
195
|
# Empty CLI → GUI edit
|
|
180
196
|
if ask_for_missing and wrong_fields:
|
|
@@ -189,5 +205,4 @@ def run(env_class: Type[EnvClass] | None = None,
|
|
|
189
205
|
|
|
190
206
|
__all__ = ["run", "Tag", "validators", "InterfaceNotAvailable", "Cancelled",
|
|
191
207
|
"Validation", "Choices", "PathTag",
|
|
192
|
-
"Mininterface"
|
|
193
|
-
]
|
|
208
|
+
"Mininterface"]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Literal
|
|
4
|
+
from tyro.conf import FlagConversionOff
|
|
5
|
+
|
|
6
|
+
from .exceptions import DependencyRequired
|
|
7
|
+
|
|
8
|
+
from . import run, Mininterface
|
|
9
|
+
from .showcase import showcase
|
|
10
|
+
|
|
11
|
+
__doc__ = """Simple GUI dialog. Outputs the value the user entered."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Web:
|
|
16
|
+
""" Experimenal undocumented feature. """
|
|
17
|
+
|
|
18
|
+
cmd: str = ""
|
|
19
|
+
""" Launch a miniterface program, while the TextualInterface will be exposed to the web."""
|
|
20
|
+
# NOTE: The textual app ends after the first submit. We have to correct that before the web makes sense.
|
|
21
|
+
# with run(interface=TextualInterface) as m:
|
|
22
|
+
# m.form({"hello": 1}) # the app ends here
|
|
23
|
+
# m.form({"hello": 2}) # we never get here
|
|
24
|
+
|
|
25
|
+
port: int = 64646
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class CliInteface:
|
|
30
|
+
web: Web
|
|
31
|
+
alert: str = ""
|
|
32
|
+
""" Display the OK dialog with text. """
|
|
33
|
+
ask: str = ""
|
|
34
|
+
""" Prompt the user to input a text. """
|
|
35
|
+
ask_number: str = ""
|
|
36
|
+
""" Prompt the user to input a number. Empty input = 0. """
|
|
37
|
+
is_yes: str = ""
|
|
38
|
+
""" Display confirm box, focusing 'yes'. """
|
|
39
|
+
is_no: str = ""
|
|
40
|
+
""" Display confirm box, focusing 'no'. """
|
|
41
|
+
|
|
42
|
+
showcase: Literal["gui"] | Literal["tui"] | Literal["all"] = None
|
|
43
|
+
""" Prints various form just to show what's possible. """
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def web(m: Mininterface):
|
|
47
|
+
try:
|
|
48
|
+
from textual_serve.server import Server
|
|
49
|
+
except ImportError:
|
|
50
|
+
raise DependencyRequired("web")
|
|
51
|
+
server = Server(m.env.web.cmd, port=m.env.web.port)
|
|
52
|
+
server.serve()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main():
|
|
56
|
+
result = []
|
|
57
|
+
# We tested both GuiInterface and TextualInterface are able to pass a variable to i.e. a bash script.
|
|
58
|
+
# NOTE TextInterface fails (`mininterface --ask Test | grep Hello` – pipe causes no visible output).
|
|
59
|
+
with run(CliInteface, prog="Mininterface", description=__doc__) as m:
|
|
60
|
+
for method, label in vars(m.env).items():
|
|
61
|
+
if method in ["web", "showcase"]: # processed later
|
|
62
|
+
continue
|
|
63
|
+
if label:
|
|
64
|
+
result.append(getattr(m, method)(label))
|
|
65
|
+
|
|
66
|
+
# Displays each result on a new line. Currently, this is an undocumented feature.
|
|
67
|
+
# As we use the script for a single value only and it is not currently possible
|
|
68
|
+
# to ask two numbers or determine a dialog order etc.
|
|
69
|
+
[print(val) for val in result]
|
|
70
|
+
|
|
71
|
+
if m.env.web.cmd:
|
|
72
|
+
web(m)
|
|
73
|
+
if m.env.showcase:
|
|
74
|
+
showcase(m.env.showcase)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
main()
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
from dataclasses import is_dataclass
|
|
1
2
|
import os
|
|
2
3
|
import re
|
|
3
4
|
from argparse import ArgumentParser
|
|
4
|
-
from typing import Iterable, TypeVar
|
|
5
|
+
from typing import Callable, Iterable, Optional, TypeVar
|
|
5
6
|
|
|
6
7
|
from tyro.extras import get_parser
|
|
7
8
|
|
|
@@ -11,10 +12,12 @@ common_iterables = list, tuple, set
|
|
|
11
12
|
""" collections, and not a str """
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
def flatten(d: dict[str, T | dict]) -> Iterable[T]:
|
|
15
|
+
def flatten(d: dict[str, T | dict], include_keys: Optional[Callable[[str], list]] = None) -> Iterable[T]:
|
|
15
16
|
""" Recursively traverse whole dict """
|
|
16
|
-
for v in d.
|
|
17
|
+
for k, v in d.items():
|
|
17
18
|
if isinstance(v, dict):
|
|
19
|
+
if include_keys:
|
|
20
|
+
yield from include_keys(k)
|
|
18
21
|
yield from flatten(v)
|
|
19
22
|
else:
|
|
20
23
|
yield v
|
|
@@ -53,9 +56,13 @@ def get_terminal_size():
|
|
|
53
56
|
def get_descriptions(parser: ArgumentParser) -> dict:
|
|
54
57
|
""" Load descriptions from the parser. Strip argparse info about the default value as it will be editable in the form. """
|
|
55
58
|
# clean-up tyro stuff that may have a meaning in the CLI, but not in the UI
|
|
56
|
-
return {action.dest.replace("-", "_"): re.sub(r"\((default|fixed to).*\)", "", action.help or "")
|
|
59
|
+
return {action.dest.replace("-", "_"): re.sub(r"\((default|fixed to|required).*\)", "", action.help or "")
|
|
57
60
|
for action in parser._actions}
|
|
58
61
|
|
|
59
62
|
|
|
60
63
|
def get_description(obj, param: str) -> str:
|
|
61
64
|
return get_descriptions(get_parser(obj))[param]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def yield_annotations(dataclass):
|
|
68
|
+
yield from (cl.__annotations__ for cl in dataclass.__mro__ if is_dataclass(cl))
|