shvcli 0.6.2__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.
- {shvcli-0.6.2/shvcli.egg-info → shvcli-0.7.0}/PKG-INFO +37 -17
- {shvcli-0.6.2 → shvcli-0.7.0}/README.rst +32 -13
- {shvcli-0.6.2 → shvcli-0.7.0}/pyproject.toml +3 -3
- shvcli-0.7.0/shvcli/__init__.py +55 -0
- shvcli-0.7.0/shvcli/__main__.py +36 -0
- shvcli-0.7.0/shvcli/afterconn.py +55 -0
- shvcli-0.7.0/shvcli/args.py +56 -0
- shvcli-0.7.0/shvcli/builtin.py +86 -0
- shvcli-0.7.0/shvcli/builtins/__init__.py +27 -0
- shvcli-0.7.0/shvcli/builtins/broker.py +100 -0
- shvcli-0.7.0/shvcli/builtins/cd.py +37 -0
- shvcli-0.7.0/shvcli/builtins/file.py +128 -0
- shvcli-0.7.0/shvcli/builtins/help.py +58 -0
- shvcli-0.7.0/shvcli/builtins/scan.py +41 -0
- shvcli-0.7.0/shvcli/builtins/set.py +70 -0
- shvcli-0.7.0/shvcli/builtins/tree.py +66 -0
- shvcli-0.7.0/shvcli/cli.py +184 -0
- shvcli-0.7.0/shvcli/client.py +133 -0
- shvcli-0.7.0/shvcli/cliitems.py +88 -0
- shvcli-0.7.0/shvcli/complet.py +86 -0
- shvcli-0.7.0/shvcli/config.py +106 -0
- shvcli-0.7.0/shvcli/file.py +68 -0
- shvcli-0.7.0/shvcli/lsdir.py +127 -0
- shvcli-0.7.0/shvcli/option.py +267 -0
- shvcli-0.7.0/shvcli/options.py +168 -0
- {shvcli-0.6.2 → shvcli-0.7.0}/shvcli/scan.py +7 -7
- shvcli-0.7.0/shvcli/state.py +96 -0
- shvcli-0.7.0/shvcli/tools/__init__.py +1 -0
- shvcli-0.7.0/shvcli/tools/complet.py +60 -0
- shvcli-0.6.2/shvcli/tools.py → shvcli-0.7.0/shvcli/tools/print.py +15 -28
- shvcli-0.7.0/shvcli/tree.py +126 -0
- shvcli-0.7.0/shvcli/url.py +110 -0
- shvcli-0.7.0/shvcli/valid.py +78 -0
- {shvcli-0.6.2 → shvcli-0.7.0/shvcli.egg-info}/PKG-INFO +37 -17
- shvcli-0.7.0/shvcli.egg-info/SOURCES.txt +42 -0
- {shvcli-0.6.2 → shvcli-0.7.0}/shvcli.egg-info/requires.txt +2 -1
- shvcli-0.6.2/shvcli/__init__.py +0 -5
- shvcli-0.6.2/shvcli/__main__.py +0 -62
- shvcli-0.6.2/shvcli/builtin.py +0 -132
- shvcli-0.6.2/shvcli/builtin_impl.py +0 -193
- shvcli-0.6.2/shvcli/cli.py +0 -138
- shvcli-0.6.2/shvcli/client.py +0 -210
- shvcli-0.6.2/shvcli/complet.py +0 -78
- shvcli-0.6.2/shvcli/complet_tools.py +0 -64
- shvcli-0.6.2/shvcli/config.py +0 -176
- shvcli-0.6.2/shvcli/lsdir.py +0 -145
- shvcli-0.6.2/shvcli/parse.py +0 -79
- shvcli-0.6.2/shvcli/valid.py +0 -41
- shvcli-0.6.2/shvcli.egg-info/SOURCES.txt +0 -26
- {shvcli-0.6.2 → shvcli-0.7.0}/LICENSE +0 -0
- {shvcli-0.6.2 → shvcli-0.7.0}/setup.cfg +0 -0
- {shvcli-0.6.2 → shvcli-0.7.0}/shvcli/__version__.py +0 -0
- {shvcli-0.6.2 → shvcli-0.7.0}/shvcli/plugins.py +0 -0
- {shvcli-0.6.2 → shvcli-0.7.0}/shvcli/py.typed +0 -0
- {shvcli-0.6.2 → shvcli-0.7.0}/shvcli.egg-info/dependency_links.txt +0 -0
- {shvcli-0.6.2 → shvcli-0.7.0}/shvcli.egg-info/entry_points.txt +0 -0
- {shvcli-0.6.2 → shvcli-0.7.0}/shvcli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: shvcli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Silicon Heaven CLI access application
|
|
5
5
|
Author: Elektroline a.s.
|
|
6
6
|
License: MIT License
|
|
@@ -27,7 +27,6 @@ License: MIT License
|
|
|
27
27
|
|
|
28
28
|
Project-URL: repository, https://gitlab.com/silicon-heaven/shvcli
|
|
29
29
|
Classifier: Development Status :: 4 - Beta
|
|
30
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
31
30
|
Classifier: Programming Language :: Python :: 3
|
|
32
31
|
Classifier: Operating System :: OS Independent
|
|
33
32
|
Classifier: Environment :: Console
|
|
@@ -38,10 +37,12 @@ Classifier: Topic :: Terminals
|
|
|
38
37
|
Requires-Python: >=3.11
|
|
39
38
|
Description-Content-Type: text/x-rst
|
|
40
39
|
License-File: LICENSE
|
|
41
|
-
Requires-Dist: pyshv>=0.
|
|
40
|
+
Requires-Dist: pyshv>=0.9.0
|
|
42
41
|
Requires-Dist: prompt_toolkit
|
|
43
42
|
Requires-Dist: pygments
|
|
44
43
|
Requires-Dist: pyxdg
|
|
44
|
+
Requires-Dist: more-itertools
|
|
45
|
+
Dynamic: license-file
|
|
45
46
|
|
|
46
47
|
=====================================
|
|
47
48
|
Silicon Heaven CLI access application
|
|
@@ -101,20 +102,25 @@ An example of usage:
|
|
|
101
102
|
.app> /:
|
|
102
103
|
>
|
|
103
104
|
|
|
105
|
+
.. TIP::
|
|
106
|
+
The validation that is performed by SHVCLI is best effort and sometimes you
|
|
107
|
+
might need to submit call that is considered invalid by SHVCLI. For that
|
|
108
|
+
reason you can use `Ctrl+o` shortcut instead of pressing enter key.
|
|
109
|
+
|
|
104
110
|
|
|
105
111
|
Configuration file
|
|
106
112
|
------------------
|
|
107
113
|
|
|
108
|
-
Tool reads configuration from files ``/etc/shvcli.
|
|
109
|
-
They are in
|
|
114
|
+
Tool reads configuration from files ``/etc/shvcli.toml`` and ``~/.shvcli.toml``.
|
|
115
|
+
They are in TOML file format and the following sections are supported:
|
|
110
116
|
|
|
111
117
|
**hosts**: That provides mapping from some name to RPC URL.
|
|
112
118
|
|
|
113
119
|
**hosts-shell**: That is same as **hosts** with exception that URL is passed
|
|
114
120
|
through your local Shell to expand any variables or command substitutions.
|
|
115
121
|
|
|
116
|
-
**
|
|
117
|
-
|
|
122
|
+
**option**: That allows you to set initial setting for runtime options. The
|
|
123
|
+
following options are available without any plugins:
|
|
118
124
|
|
|
119
125
|
* **vimode**: If Vi input mode should be used for command line input. The
|
|
120
126
|
default is ``false``.
|
|
@@ -137,17 +143,15 @@ switched in runtime. The following options are available:
|
|
|
137
143
|
be also overwhelming. The default is ``false``. It is beneficial to disable
|
|
138
144
|
the **autobrobe** once you enable debug because otherwise output on CLI will
|
|
139
145
|
be mangled on completion.
|
|
140
|
-
* **
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* **
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
* **autoget_timeout**: Timeout in seconds before call that is part of autoget
|
|
146
|
+
* **call_query_timeout**: Timeout in seconds for status request query. The
|
|
147
|
+
shorter time will result in faster request lost detection while very short
|
|
148
|
+
time will load SHV network too much.
|
|
149
|
+
* **call_retry_timeout**: Timeout in seconds when call request is sent again if
|
|
150
|
+
no response is received. This is not applied if query is and thus this mostly
|
|
151
|
+
is used only in case when method doesn't support delayed responses.
|
|
152
|
+
* **autoget_timeout**: Timeout in seconds for call that is part of autoget
|
|
148
153
|
functionality.
|
|
149
154
|
|
|
150
|
-
|
|
151
155
|
Example configuration file:
|
|
152
156
|
|
|
153
157
|
.. code-block:: ini
|
|
@@ -200,6 +204,15 @@ disable ``raw`` you use ``noraw``). You can also add ``=true`` or ``=false``.
|
|
|
200
204
|
The equal sign is also used with integer and floating point options. Without any
|
|
201
205
|
configuration option it simply prints the current configuration.
|
|
202
206
|
|
|
207
|
+
**upload**: provides a way to copy local file to the RPC File node. The
|
|
208
|
+
parameter must be path to the local file to be uploaded.
|
|
209
|
+
|
|
210
|
+
**download**: provides a way to copy RPC File node data to local file. The
|
|
211
|
+
parameter must be path to the local file where data will be stored.
|
|
212
|
+
|
|
213
|
+
**verify**: provides a way to verify RPC File node against local file. The
|
|
214
|
+
parameter must be path to the local file used for the verification.
|
|
215
|
+
|
|
203
216
|
|
|
204
217
|
Special methods ``ls`` and ``dir``
|
|
205
218
|
----------------------------------
|
|
@@ -211,3 +224,10 @@ the fullest content.
|
|
|
211
224
|
Their parameter is also handled in a special way. It is considered to be
|
|
212
225
|
additional path suffix unless it is a valid CPON. This is allowed to match the
|
|
213
226
|
common shells.
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
Plugins
|
|
230
|
+
-------
|
|
231
|
+
|
|
232
|
+
The support for external plugins is provided. These plugins are discovered using
|
|
233
|
+
Python package metadata using the entry point ``shvcli.plugins``.
|
|
@@ -56,20 +56,25 @@ An example of usage:
|
|
|
56
56
|
.app> /:
|
|
57
57
|
>
|
|
58
58
|
|
|
59
|
+
.. TIP::
|
|
60
|
+
The validation that is performed by SHVCLI is best effort and sometimes you
|
|
61
|
+
might need to submit call that is considered invalid by SHVCLI. For that
|
|
62
|
+
reason you can use `Ctrl+o` shortcut instead of pressing enter key.
|
|
63
|
+
|
|
59
64
|
|
|
60
65
|
Configuration file
|
|
61
66
|
------------------
|
|
62
67
|
|
|
63
|
-
Tool reads configuration from files ``/etc/shvcli.
|
|
64
|
-
They are in
|
|
68
|
+
Tool reads configuration from files ``/etc/shvcli.toml`` and ``~/.shvcli.toml``.
|
|
69
|
+
They are in TOML file format and the following sections are supported:
|
|
65
70
|
|
|
66
71
|
**hosts**: That provides mapping from some name to RPC URL.
|
|
67
72
|
|
|
68
73
|
**hosts-shell**: That is same as **hosts** with exception that URL is passed
|
|
69
74
|
through your local Shell to expand any variables or command substitutions.
|
|
70
75
|
|
|
71
|
-
**
|
|
72
|
-
|
|
76
|
+
**option**: That allows you to set initial setting for runtime options. The
|
|
77
|
+
following options are available without any plugins:
|
|
73
78
|
|
|
74
79
|
* **vimode**: If Vi input mode should be used for command line input. The
|
|
75
80
|
default is ``false``.
|
|
@@ -92,17 +97,15 @@ switched in runtime. The following options are available:
|
|
|
92
97
|
be also overwhelming. The default is ``false``. It is beneficial to disable
|
|
93
98
|
the **autobrobe** once you enable debug because otherwise output on CLI will
|
|
94
99
|
be mangled on completion.
|
|
95
|
-
* **
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
* **
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* **autoget_timeout**: Timeout in seconds before call that is part of autoget
|
|
100
|
+
* **call_query_timeout**: Timeout in seconds for status request query. The
|
|
101
|
+
shorter time will result in faster request lost detection while very short
|
|
102
|
+
time will load SHV network too much.
|
|
103
|
+
* **call_retry_timeout**: Timeout in seconds when call request is sent again if
|
|
104
|
+
no response is received. This is not applied if query is and thus this mostly
|
|
105
|
+
is used only in case when method doesn't support delayed responses.
|
|
106
|
+
* **autoget_timeout**: Timeout in seconds for call that is part of autoget
|
|
103
107
|
functionality.
|
|
104
108
|
|
|
105
|
-
|
|
106
109
|
Example configuration file:
|
|
107
110
|
|
|
108
111
|
.. code-block:: ini
|
|
@@ -155,6 +158,15 @@ disable ``raw`` you use ``noraw``). You can also add ``=true`` or ``=false``.
|
|
|
155
158
|
The equal sign is also used with integer and floating point options. Without any
|
|
156
159
|
configuration option it simply prints the current configuration.
|
|
157
160
|
|
|
161
|
+
**upload**: provides a way to copy local file to the RPC File node. The
|
|
162
|
+
parameter must be path to the local file to be uploaded.
|
|
163
|
+
|
|
164
|
+
**download**: provides a way to copy RPC File node data to local file. The
|
|
165
|
+
parameter must be path to the local file where data will be stored.
|
|
166
|
+
|
|
167
|
+
**verify**: provides a way to verify RPC File node against local file. The
|
|
168
|
+
parameter must be path to the local file used for the verification.
|
|
169
|
+
|
|
158
170
|
|
|
159
171
|
Special methods ``ls`` and ``dir``
|
|
160
172
|
----------------------------------
|
|
@@ -166,3 +178,10 @@ the fullest content.
|
|
|
166
178
|
Their parameter is also handled in a special way. It is considered to be
|
|
167
179
|
additional path suffix unless it is a valid CPON. This is allowed to match the
|
|
168
180
|
common shells.
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
Plugins
|
|
184
|
+
-------
|
|
185
|
+
|
|
186
|
+
The support for external plugins is provided. These plugins are discovered using
|
|
187
|
+
Python package metadata using the entry point ``shvcli.plugins``.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "shvcli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "Silicon Heaven CLI access application"
|
|
5
5
|
readme = "README.rst"
|
|
6
6
|
license = {file = "LICENSE"}
|
|
@@ -9,7 +9,6 @@ authors = [
|
|
|
9
9
|
]
|
|
10
10
|
classifiers = [
|
|
11
11
|
"Development Status :: 4 - Beta",
|
|
12
|
-
"License :: OSI Approved :: MIT License",
|
|
13
12
|
"Programming Language :: Python :: 3",
|
|
14
13
|
"Operating System :: OS Independent",
|
|
15
14
|
"Environment :: Console",
|
|
@@ -20,10 +19,11 @@ classifiers = [
|
|
|
20
19
|
]
|
|
21
20
|
requires-python = ">=3.11"
|
|
22
21
|
dependencies = [
|
|
23
|
-
"pyshv >= 0.
|
|
22
|
+
"pyshv >= 0.9.0",
|
|
24
23
|
"prompt_toolkit",
|
|
25
24
|
"pygments",
|
|
26
25
|
"pyxdg",
|
|
26
|
+
"more-itertools",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.urls]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Silicon Heaven Command Line Interface."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
from .__version__ import VERSION
|
|
6
|
+
from .args import register_argparser
|
|
7
|
+
from .builtin import Builtin, Builtins
|
|
8
|
+
from .client import Client
|
|
9
|
+
from .cliitems import CliItems
|
|
10
|
+
from .config import Config, ConfigError, register_config
|
|
11
|
+
from .option import BoolOption, FloatOption, IntOption, Option
|
|
12
|
+
from .options import (
|
|
13
|
+
AutoGetOption,
|
|
14
|
+
AutoGetTimeoutOption,
|
|
15
|
+
AutoProbeOption,
|
|
16
|
+
CallDuration,
|
|
17
|
+
CallQueryTimeout,
|
|
18
|
+
CallRetryTimeout,
|
|
19
|
+
DebugOption,
|
|
20
|
+
RawOption,
|
|
21
|
+
ViModeOption,
|
|
22
|
+
)
|
|
23
|
+
from .state import State, StateVar
|
|
24
|
+
from .tree import Node, Tree
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"VERSION",
|
|
28
|
+
"AutoGetOption",
|
|
29
|
+
"AutoGetTimeoutOption",
|
|
30
|
+
"AutoProbeOption",
|
|
31
|
+
"BoolOption",
|
|
32
|
+
"Builtin",
|
|
33
|
+
"Builtins",
|
|
34
|
+
"CallDuration",
|
|
35
|
+
"CallQueryTimeout",
|
|
36
|
+
"CallRetryTimeout",
|
|
37
|
+
"CliItems",
|
|
38
|
+
"Client",
|
|
39
|
+
"Config",
|
|
40
|
+
"ConfigError",
|
|
41
|
+
"DebugOption",
|
|
42
|
+
"FloatOption",
|
|
43
|
+
"IntOption",
|
|
44
|
+
"Node",
|
|
45
|
+
"Option",
|
|
46
|
+
"RawOption",
|
|
47
|
+
"State",
|
|
48
|
+
"StateVar",
|
|
49
|
+
"Tree",
|
|
50
|
+
"ViModeOption",
|
|
51
|
+
"register_argparser",
|
|
52
|
+
"register_config",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
importlib.import_module(".builtins", package=__package__)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Implementation of command line application."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from .afterconn import Afterconn
|
|
6
|
+
from .args import args_apply, args_parse
|
|
7
|
+
from .cli import run
|
|
8
|
+
from .client import Client
|
|
9
|
+
from .config import load_config
|
|
10
|
+
from .plugins import load_plugins
|
|
11
|
+
from .state import State
|
|
12
|
+
from .url import Url
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> None:
|
|
16
|
+
"""Application's entrypoint."""
|
|
17
|
+
load_plugins()
|
|
18
|
+
argsgens, args = args_parse()
|
|
19
|
+
state = State()
|
|
20
|
+
load_config(args.config, state)
|
|
21
|
+
args_apply(argsgens, args, state)
|
|
22
|
+
state.cache_load(Url(state).cache_path())
|
|
23
|
+
|
|
24
|
+
async def async_main() -> None:
|
|
25
|
+
client = await Client.connect(Url(state).url, state=state)
|
|
26
|
+
await Afterconn(state).run(client)
|
|
27
|
+
await run(client)
|
|
28
|
+
if client.client.connected:
|
|
29
|
+
await client.disconnect()
|
|
30
|
+
|
|
31
|
+
asyncio.run(async_main())
|
|
32
|
+
state.cache_dump(Url(state).cache_path())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
main()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""The after connection tasks."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import collections.abc
|
|
5
|
+
import typing
|
|
6
|
+
|
|
7
|
+
from .args import ArgsParseFuncGenT, register_argparser
|
|
8
|
+
from .client import Client, SHVPath
|
|
9
|
+
from .cliitems import CliItems
|
|
10
|
+
from .scan import scan_nodes
|
|
11
|
+
from .state import StateVar
|
|
12
|
+
|
|
13
|
+
AfterConnT: typing.TypeAlias = collections.abc.Callable[
|
|
14
|
+
[Client], collections.abc.Coroutine[None, None, None]
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Afterconn(StateVar, list[AfterConnT]):
|
|
19
|
+
"""The set of operations to be performed right after connection."""
|
|
20
|
+
|
|
21
|
+
async def run(self, client: Client) -> None:
|
|
22
|
+
"""Run the steps setup to be performed after connection at startup."""
|
|
23
|
+
for func in self:
|
|
24
|
+
await func(client)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@register_argparser
|
|
28
|
+
def _argparser(parser: argparse.ArgumentParser) -> ArgsParseFuncGenT:
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"-s",
|
|
31
|
+
"--subscribe",
|
|
32
|
+
action="append",
|
|
33
|
+
default=[],
|
|
34
|
+
help="Automatic subscription right after connection.",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--scan",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Perform scan as a first action right after connect.",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--scan-depth",
|
|
43
|
+
type=int,
|
|
44
|
+
default=3,
|
|
45
|
+
help="Depth of the scan performed if --scan is used.",
|
|
46
|
+
)
|
|
47
|
+
args, state = yield
|
|
48
|
+
|
|
49
|
+
async def afterconn(client: Client) -> None:
|
|
50
|
+
for ri in args.subscribe:
|
|
51
|
+
await client.subscribe(CliItems.extend_ri(ri))
|
|
52
|
+
if args.scan:
|
|
53
|
+
await scan_nodes(client, SHVPath(""), args.scan_dept)
|
|
54
|
+
|
|
55
|
+
Afterconn(state).append(afterconn)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Argument parser wrapper for the SHVCLI."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import collections.abc
|
|
5
|
+
import contextlib
|
|
6
|
+
import pathlib
|
|
7
|
+
import typing
|
|
8
|
+
|
|
9
|
+
from .__version__ import VERSION
|
|
10
|
+
from .state import State
|
|
11
|
+
|
|
12
|
+
ArgsParseFuncGenT: typing.TypeAlias = collections.abc.Generator[
|
|
13
|
+
None, tuple[argparse.Namespace, State], None
|
|
14
|
+
]
|
|
15
|
+
ArgsparseFuncT: typing.TypeAlias = collections.abc.Callable[
|
|
16
|
+
[argparse.ArgumentParser], ArgsParseFuncGenT
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
registered_functions: list[ArgsparseFuncT] = []
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def register_argparser(func: ArgsparseFuncT) -> None:
|
|
23
|
+
"""Decorate function to register it to be called when arguments are being parsed."""
|
|
24
|
+
registered_functions.append(func)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def args_parse() -> tuple[tuple[ArgsParseFuncGenT, ...], argparse.Namespace]:
|
|
28
|
+
"""Parse passed arguments and return result."""
|
|
29
|
+
parser = argparse.ArgumentParser(prog="shvcli", description="Silicon Heaven CLI")
|
|
30
|
+
parser.add_argument("--version", action="version", version="%(prog)s " + VERSION)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"-c",
|
|
33
|
+
"--config",
|
|
34
|
+
action="append",
|
|
35
|
+
type=pathlib.Path,
|
|
36
|
+
default=[pathlib.Path("/etc/shvcli.toml"), pathlib.Path("~/.shvcli.toml")],
|
|
37
|
+
)
|
|
38
|
+
gens = tuple(func(parser) for func in registered_functions)
|
|
39
|
+
for gen in gens:
|
|
40
|
+
next(gen)
|
|
41
|
+
args = parser.parse_args()
|
|
42
|
+
return gens, args
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def args_apply(
|
|
46
|
+
gens: tuple[ArgsParseFuncGenT, ...], args: argparse.Namespace, state: State
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Apply parsed arguments on the given state.
|
|
49
|
+
|
|
50
|
+
The parsed arguments should be applied after configuration but
|
|
51
|
+
configuration loading needs to be influenced by arguments and thus split
|
|
52
|
+
arguments parsing to the parsing and application phase.
|
|
53
|
+
"""
|
|
54
|
+
for gen in gens:
|
|
55
|
+
with contextlib.suppress(StopIteration):
|
|
56
|
+
gen.send((args, state))
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Builtin methods glue code so they can be defined separatelly."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
import collections.abc
|
|
7
|
+
import inspect
|
|
8
|
+
import typing
|
|
9
|
+
|
|
10
|
+
from prompt_toolkit.completion import Completion
|
|
11
|
+
|
|
12
|
+
from .client import Client
|
|
13
|
+
from .cliitems import CliItems
|
|
14
|
+
from .state import State, StateVar
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Builtin(abc.ABC):
|
|
18
|
+
"""The single builtin definition."""
|
|
19
|
+
|
|
20
|
+
builtins: typing.Final[list[type[Builtin]]] = []
|
|
21
|
+
|
|
22
|
+
def __init_subclass__(cls, *args: typing.Any, **kwargs: typing.Any) -> None: # noqa: ANN401
|
|
23
|
+
super().__init_subclass__(*args, **kwargs)
|
|
24
|
+
Builtin.builtins.append(cls)
|
|
25
|
+
|
|
26
|
+
def __init__(self, builtins: Builtins, state: State) -> None:
|
|
27
|
+
self.state = state
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@abc.abstractmethod
|
|
31
|
+
def description(self) -> tuple[str, str]:
|
|
32
|
+
"""The provider of the description of this builtin.
|
|
33
|
+
|
|
34
|
+
:return: The first string is description of arguments expected by
|
|
35
|
+
builtin and the second one is the builtin description.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def completion( # noqa: PLR6301
|
|
39
|
+
self, items: CliItems, client: Client
|
|
40
|
+
) -> collections.abc.Iterable[Completion]:
|
|
41
|
+
"""Parameter completion for this builtin."""
|
|
42
|
+
return tuple()
|
|
43
|
+
|
|
44
|
+
async def completion_async( # noqa: PLR6301
|
|
45
|
+
self, items: CliItems, client: Client
|
|
46
|
+
) -> collections.abc.AsyncGenerator[Completion, None]:
|
|
47
|
+
"""Asynchronous parameter completion for this builtin.
|
|
48
|
+
|
|
49
|
+
This is used only if :class:`AutoProbeOption` is enabled.
|
|
50
|
+
|
|
51
|
+
It is common that this only fetches the required data with client and
|
|
52
|
+
the completion is performed in :meth:`completion` only.
|
|
53
|
+
"""
|
|
54
|
+
return
|
|
55
|
+
yield # type: ignore
|
|
56
|
+
|
|
57
|
+
def validate(self, items: CliItems, client: Client) -> None: # noqa: PLR6301
|
|
58
|
+
"""Validate the parameter for this builtin."""
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
async def validate_async(self, items: CliItems, client: Client) -> None: # noqa: PLR6301
|
|
62
|
+
"""Asynchronously validate the parameter for this builtin.
|
|
63
|
+
|
|
64
|
+
This is used only if :class:`AutoProbeOption` is enabled.
|
|
65
|
+
|
|
66
|
+
It is common that this only fetches the required data with client and
|
|
67
|
+
the validation is performed in :meth:`validate` only. SHVCLI also
|
|
68
|
+
always uses completion and validation together and because completion
|
|
69
|
+
happens alongside with validation it is rather common to do this
|
|
70
|
+
probing in the :meth:`completion_async` only.
|
|
71
|
+
"""
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
@abc.abstractmethod
|
|
75
|
+
async def run(self, items: CliItems, client: Client) -> None:
|
|
76
|
+
"""Run the builtin."""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Builtins(StateVar, dict[str, Builtin]):
|
|
80
|
+
"""All initialized builtins."""
|
|
81
|
+
|
|
82
|
+
def __init__(self, state: State) -> None:
|
|
83
|
+
super().__init__(state)
|
|
84
|
+
for builtin in Builtin.builtins:
|
|
85
|
+
if not inspect.isabstract(builtin):
|
|
86
|
+
builtin(self, state)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""All standard builtins.
|
|
2
|
+
|
|
3
|
+
This module is imported in the top level init and thus all builtins imported
|
|
4
|
+
here are automatically registered.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .broker import BuiltinSubscribe, BuiltinSubscriptions, BuiltinUnsubscribe
|
|
8
|
+
from .cd import BuiltinCD
|
|
9
|
+
from .file import BuiltinDownload, BuiltinUpload, BuiltinVerify
|
|
10
|
+
from .help import BuiltinHelp
|
|
11
|
+
from .scan import BuiltinScan
|
|
12
|
+
from .set import BuiltinSet
|
|
13
|
+
from .tree import BuiltinTree
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"BuiltinCD",
|
|
17
|
+
"BuiltinDownload",
|
|
18
|
+
"BuiltinHelp",
|
|
19
|
+
"BuiltinScan",
|
|
20
|
+
"BuiltinSet",
|
|
21
|
+
"BuiltinSubscribe",
|
|
22
|
+
"BuiltinSubscriptions",
|
|
23
|
+
"BuiltinTree",
|
|
24
|
+
"BuiltinUnsubscribe",
|
|
25
|
+
"BuiltinUpload",
|
|
26
|
+
"BuiltinVerify",
|
|
27
|
+
]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Broker specific builtins."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import collections.abc
|
|
6
|
+
|
|
7
|
+
from prompt_toolkit.completion import Completion
|
|
8
|
+
from prompt_toolkit.validation import ValidationError
|
|
9
|
+
|
|
10
|
+
from ..builtin import Builtin, Builtins
|
|
11
|
+
from ..client import Client
|
|
12
|
+
from ..cliitems import CliItems
|
|
13
|
+
from ..state import State
|
|
14
|
+
from ..tools.complet import comp_from, comp_signal_ri
|
|
15
|
+
from ..tools.print import print_flist
|
|
16
|
+
from ..tree import Tree
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BuiltinSubscribe(Builtin):
|
|
20
|
+
"""The implementation of ``subscribe`` builtin."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, builtins: Builtins, state: State) -> None:
|
|
23
|
+
super().__init__(builtins, state)
|
|
24
|
+
builtins["subscribe"] = self
|
|
25
|
+
builtins["sub"] = self
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def description(self) -> tuple[str, str]: # noqa: D102
|
|
29
|
+
return "[PATH:METHOD:SIGNAL]...", "Subscribe to given RPC RIs."
|
|
30
|
+
|
|
31
|
+
def completion( # noqa: D102
|
|
32
|
+
self, items: CliItems, client: Client
|
|
33
|
+
) -> collections.abc.Iterable[Completion]:
|
|
34
|
+
yield from comp_signal_ri(
|
|
35
|
+
items.param.rpartition(" ")[2], self.state.path, Tree(self.state)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
async def completion_async( # noqa: D102
|
|
39
|
+
self, items: CliItems, client: Client
|
|
40
|
+
) -> collections.abc.AsyncGenerator[Completion, None]:
|
|
41
|
+
last = items.param.rpartition(" ")[2]
|
|
42
|
+
if ":" not in last:
|
|
43
|
+
path = items.path / last
|
|
44
|
+
await client.probe(path if last.endswith("/") else path.parent)
|
|
45
|
+
|
|
46
|
+
async for res in super().completion_async(items, client):
|
|
47
|
+
yield res
|
|
48
|
+
|
|
49
|
+
async def run(self, items: CliItems, client: Client) -> None: # noqa: PLR6301, D102
|
|
50
|
+
for ri in items.ri_param:
|
|
51
|
+
await client.subscribe(ri)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class BuiltinUnsubscribe(Builtin):
|
|
55
|
+
"""The implementation of ``unsubscribe`` builtin."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, builtins: Builtins, state: State) -> None:
|
|
58
|
+
super().__init__(builtins, state)
|
|
59
|
+
builtins["unsubscribe"] = self
|
|
60
|
+
builtins["usub"] = self
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def description(self) -> tuple[str, str]: # noqa: D102
|
|
64
|
+
return "[PATH:METHOD:SIGNAL]...", "Unsubscribe to given or all RPC RIs."
|
|
65
|
+
|
|
66
|
+
def completion( # noqa: PLR6301, D102
|
|
67
|
+
self, items: CliItems, client: Client
|
|
68
|
+
) -> collections.abc.Iterable[Completion]:
|
|
69
|
+
yield from comp_from(items.param.rpartition(" ")[2], client.subscriptions())
|
|
70
|
+
|
|
71
|
+
def validate(self, items: CliItems, client: Client) -> None: # noqa: PLR6301, D102
|
|
72
|
+
if missing := set(items.param.split()) - set(client.subscriptions()):
|
|
73
|
+
raise ValidationError(message=f"Not subscribed: {' '.join(missing)}")
|
|
74
|
+
|
|
75
|
+
async def run(self, items: CliItems, client: Client) -> None: # noqa: PLR6301, D102
|
|
76
|
+
for ri in items.ri_param if items.param else client.subscriptions():
|
|
77
|
+
await client.unsubscribe(ri)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class BuiltinSubscriptions(Builtin):
|
|
81
|
+
"""The implementation of ``subscriptions`` builtin."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, builtins: Builtins, state: State) -> None:
|
|
84
|
+
super().__init__(builtins, state)
|
|
85
|
+
builtins["subscriptions"] = self
|
|
86
|
+
builtins["subs"] = self
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def description(self) -> tuple[str, str]: # noqa: D102
|
|
90
|
+
return "", "Display current subscriptions."
|
|
91
|
+
|
|
92
|
+
async def run(self, items: CliItems, client: Client) -> None: # noqa: PLR6301, D102
|
|
93
|
+
subs = await client.call(".broker/currentClient", "subscriptions")
|
|
94
|
+
if isinstance(subs, dict):
|
|
95
|
+
print_flist(
|
|
96
|
+
("", sub + (f"?{timeout}secs" if timeout is not None else ""))
|
|
97
|
+
for sub, timeout in subs.items()
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
print(subs)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""The cd builtin."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import collections.abc
|
|
6
|
+
|
|
7
|
+
from prompt_toolkit.completion import Completion
|
|
8
|
+
|
|
9
|
+
from ..builtin import Builtin, Builtins
|
|
10
|
+
from ..client import Client
|
|
11
|
+
from ..cliitems import CliItems
|
|
12
|
+
from ..state import State
|
|
13
|
+
from ..tools.complet import comp_path
|
|
14
|
+
from ..tree import Tree
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BuiltinCD(Builtin):
|
|
18
|
+
"""The implementation of ``cd`` builtin that allows change to invalid path."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, builtins: Builtins, state: State) -> None:
|
|
21
|
+
super().__init__(builtins, state)
|
|
22
|
+
builtins["cd"] = self
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def description(self) -> tuple[str, str]: # noqa: D102
|
|
26
|
+
return "[PATH]", "Change to given path even if it is invalid."
|
|
27
|
+
|
|
28
|
+
def completion( # noqa: D102
|
|
29
|
+
self, items: CliItems, client: Client
|
|
30
|
+
) -> collections.abc.Iterable[Completion]:
|
|
31
|
+
yield from comp_path(items.param, self.state.path, Tree(self.state), "")
|
|
32
|
+
|
|
33
|
+
async def run(self, items: CliItems, client: Client) -> None: # noqa: D102
|
|
34
|
+
try:
|
|
35
|
+
self.state.path = items.path_param
|
|
36
|
+
except ValueError as exc:
|
|
37
|
+
print(exc)
|