shvcli 0.3.0__tar.gz → 0.4.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.3.0/shvcli.egg-info → shvcli-0.4.0}/PKG-INFO +7 -12
- {shvcli-0.3.0 → shvcli-0.4.0}/README.rst +3 -3
- shvcli-0.4.0/pyproject.toml +101 -0
- shvcli-0.4.0/shvcli/__init__.py +5 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/__main__.py +1 -0
- shvcli-0.4.0/shvcli/__version__.py +25 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/builtin.py +1 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/builtin_impl.py +5 -6
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/cli.py +4 -3
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/client.py +29 -18
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/complet.py +3 -2
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/complet_tools.py +1 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/config.py +3 -2
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/lsdir.py +11 -7
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/parse.py +5 -5
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/scan.py +1 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/tools.py +4 -3
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/valid.py +3 -2
- {shvcli-0.3.0 → shvcli-0.4.0/shvcli.egg-info}/PKG-INFO +7 -12
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli.egg-info/SOURCES.txt +1 -0
- shvcli-0.4.0/shvcli.egg-info/requires.txt +4 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli.egg-info/top_level.txt +1 -0
- shvcli-0.3.0/pyproject.toml +0 -89
- shvcli-0.3.0/shvcli/__init__.py +0 -5
- shvcli-0.3.0/shvcli.egg-info/requires.txt +0 -10
- {shvcli-0.3.0 → shvcli-0.4.0}/LICENSE +0 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/setup.cfg +0 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/py.typed +0 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli/version +0 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli.egg-info/dependency_links.txt +0 -0
- {shvcli-0.3.0 → shvcli-0.4.0}/shvcli.egg-info/entry_points.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: shvcli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Silicon Heaven CLI access application
|
|
5
5
|
Author: Elektroline a.s.
|
|
6
6
|
License: MIT License
|
|
@@ -25,7 +25,7 @@ License: MIT License
|
|
|
25
25
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
|
-
Project-URL: repository, https://gitlab.com/
|
|
28
|
+
Project-URL: repository, https://gitlab.com/silicon-heaven/shvcli
|
|
29
29
|
Classifier: Development Status :: 4 - Beta
|
|
30
30
|
Classifier: License :: OSI Approved :: MIT License
|
|
31
31
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -38,27 +38,22 @@ Classifier: Topic :: Terminals
|
|
|
38
38
|
Requires-Python: >=3.11
|
|
39
39
|
Description-Content-Type: text/x-rst
|
|
40
40
|
License-File: LICENSE
|
|
41
|
-
Requires-Dist: pyshv>=0.
|
|
41
|
+
Requires-Dist: pyshv>=0.6.0
|
|
42
42
|
Requires-Dist: prompt_toolkit
|
|
43
|
+
Requires-Dist: pygments
|
|
43
44
|
Requires-Dist: pyxdg
|
|
44
|
-
Provides-Extra: lint
|
|
45
|
-
Requires-Dist: black; extra == "lint"
|
|
46
|
-
Requires-Dist: isort; extra == "lint"
|
|
47
|
-
Requires-Dist: mypy; extra == "lint"
|
|
48
|
-
Requires-Dist: pydocstyle; extra == "lint"
|
|
49
|
-
Requires-Dist: pylint; extra == "lint"
|
|
50
45
|
|
|
51
46
|
=====================================
|
|
52
47
|
Silicon Heaven CLI access application
|
|
53
48
|
=====================================
|
|
54
|
-
.. image:: https://gitlab.com/
|
|
49
|
+
.. image:: https://gitlab.com/silicon-heaven/shvcli/-/raw/master/logo.svg
|
|
55
50
|
:align: right
|
|
56
51
|
:height: 128px
|
|
57
52
|
|
|
58
53
|
This provides an easy to use CLI interfase to access the SHV network.
|
|
59
54
|
|
|
60
|
-
* `📃 Sources <https://gitlab.com/
|
|
61
|
-
* `⁉️ Issue tracker <https://gitlab.com/
|
|
55
|
+
* `📃 Sources <https://gitlab.com/silicon-heaven/shvcli>`__
|
|
56
|
+
* `⁉️ Issue tracker <https://gitlab.com/silicon-heaven/shvcli/-/issues>`__
|
|
62
57
|
* `📕 Silicon Heaven protocol documentation <https://silicon-heaven.github.io/shv-doc/>`__
|
|
63
58
|
|
|
64
59
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
=====================================
|
|
2
2
|
Silicon Heaven CLI access application
|
|
3
3
|
=====================================
|
|
4
|
-
.. image:: https://gitlab.com/
|
|
4
|
+
.. image:: https://gitlab.com/silicon-heaven/shvcli/-/raw/master/logo.svg
|
|
5
5
|
:align: right
|
|
6
6
|
:height: 128px
|
|
7
7
|
|
|
8
8
|
This provides an easy to use CLI interfase to access the SHV network.
|
|
9
9
|
|
|
10
|
-
* `📃 Sources <https://gitlab.com/
|
|
11
|
-
* `⁉️ Issue tracker <https://gitlab.com/
|
|
10
|
+
* `📃 Sources <https://gitlab.com/silicon-heaven/shvcli>`__
|
|
11
|
+
* `⁉️ Issue tracker <https://gitlab.com/silicon-heaven/shvcli/-/issues>`__
|
|
12
12
|
* `📕 Silicon Heaven protocol documentation <https://silicon-heaven.github.io/shv-doc/>`__
|
|
13
13
|
|
|
14
14
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "shvcli"
|
|
3
|
+
version = "0.4.0"
|
|
4
|
+
description = "Silicon Heaven CLI access application"
|
|
5
|
+
readme = "README.rst"
|
|
6
|
+
license = {file = "LICENSE"}
|
|
7
|
+
authors = [
|
|
8
|
+
{ name="Elektroline a.s." },
|
|
9
|
+
]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: End Users/Desktop",
|
|
17
|
+
"Topic :: Home Automation",
|
|
18
|
+
"Topic :: System :: Shells",
|
|
19
|
+
"Topic :: Terminals",
|
|
20
|
+
]
|
|
21
|
+
requires-python = ">=3.11"
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pyshv >= 0.6.0",
|
|
24
|
+
"prompt_toolkit",
|
|
25
|
+
"pygments",
|
|
26
|
+
"pyxdg",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
repository = "https://gitlab.com/silicon-heaven/shvcli"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
shvcli = "shvcli.__main__:main"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["setuptools"]
|
|
37
|
+
build-backend = "setuptools.build_meta"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
[tool.setuptools.package-data]
|
|
41
|
+
"*" = ["py.typed"]
|
|
42
|
+
|
|
43
|
+
[tool.coverage.run]
|
|
44
|
+
branch = true
|
|
45
|
+
[tool.coverage.report]
|
|
46
|
+
exclude_lines = [
|
|
47
|
+
"pragma: no cover",
|
|
48
|
+
"if typing.TYPE_CHECKING:"
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.ruff]
|
|
52
|
+
target-version = "py311"
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = [
|
|
55
|
+
"F", "E", "W", "I", "N", "D", "UP", "YTT", "ANN", "ASYNC", "TRIO", "S", "BLE",
|
|
56
|
+
"B", "A", "COM", "PL", "RUF"
|
|
57
|
+
]
|
|
58
|
+
ignore = [
|
|
59
|
+
# Magic methods have defined behavior so what is there to document. __init__
|
|
60
|
+
# should be documented in class docstring.
|
|
61
|
+
"D105", "D107",
|
|
62
|
+
# These have conflicting rules.
|
|
63
|
+
"D203", "D213",
|
|
64
|
+
# Ignore too long lines (format tackles that)
|
|
65
|
+
"E501",
|
|
66
|
+
# It is nice that assert should not be used for input checking but it can be
|
|
67
|
+
# also used for internal consistency checking.
|
|
68
|
+
"S101", "B011",
|
|
69
|
+
# subprocess can be insecurely used but not is insecure by design
|
|
70
|
+
"S404", "S603", "S607",
|
|
71
|
+
# Catching Exception is valid and safe in some cases and thus should be allowed
|
|
72
|
+
"BLE001",
|
|
73
|
+
# Conflict with formatter
|
|
74
|
+
"COM812",
|
|
75
|
+
# Remove too-many limit for more complex code.
|
|
76
|
+
"PLR0904", "PLR0911", "PLR0912", "PLR0913", "PLR0917", "PLR2004", "PLR1702",
|
|
77
|
+
"PLR0916",
|
|
78
|
+
# This seems like a wrong rule
|
|
79
|
+
"PLW1641",
|
|
80
|
+
# Deprecated
|
|
81
|
+
"ANN101", "ANN102",
|
|
82
|
+
]
|
|
83
|
+
preview = true
|
|
84
|
+
[tool.ruff.lint.per-file-ignores]
|
|
85
|
+
"tests/**" = [
|
|
86
|
+
"D100", "D101", "D102", "D103", "D104", "S101", "S106", "ANN001", "ANN002",
|
|
87
|
+
"ANN003", "ANN201", "ANN202", "ANN204", "ANN205", "PLR6301",
|
|
88
|
+
]
|
|
89
|
+
"docs/conf.py" = ["D100", "D103", "A001", "ANN201", "ANN001"]
|
|
90
|
+
[tool.ruff.format]
|
|
91
|
+
docstring-code-format = true
|
|
92
|
+
preview = true
|
|
93
|
+
|
|
94
|
+
[tool.mypy]
|
|
95
|
+
python_version = "3.11"
|
|
96
|
+
ignore_missing_imports = true
|
|
97
|
+
disallow_untyped_defs = true
|
|
98
|
+
warn_redundant_casts = true
|
|
99
|
+
warn_unused_ignores = true
|
|
100
|
+
warn_return_any = true
|
|
101
|
+
warn_unreachable = true
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""The smarter loader of the version.
|
|
2
|
+
|
|
3
|
+
It identifies if this package is installed and returns version from egg info or
|
|
4
|
+
it looks for pyproject.toml file in upper directory.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import importlib.metadata
|
|
8
|
+
import pathlib
|
|
9
|
+
import tomllib
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _get_version() -> str:
|
|
13
|
+
for dist_name in importlib.metadata.packages_distributions().get(__package__, []):
|
|
14
|
+
dist = importlib.metadata.distribution(dist_name)
|
|
15
|
+
if str(dist.locate_file(f"{__package__}/__version__.py")) == __file__:
|
|
16
|
+
return dist.version
|
|
17
|
+
pyproject = pathlib.Path(__file__).parent.parent / "pyproject.toml"
|
|
18
|
+
with pyproject.open("rb") as f:
|
|
19
|
+
res = tomllib.load(f)["project"]["version"]
|
|
20
|
+
if isinstance(res, str):
|
|
21
|
+
return res
|
|
22
|
+
return "unknown"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
VERSION = _get_version()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Implementations of our builtin methods."""
|
|
2
|
+
|
|
2
3
|
import collections.abc
|
|
3
4
|
import inspect
|
|
4
5
|
import itertools
|
|
@@ -95,12 +96,10 @@ async def tree(shvclient: SHVClient, config: CliConfig, items: CliItems) -> None
|
|
|
95
96
|
print_ftext(
|
|
96
97
|
itertools.chain(
|
|
97
98
|
(("", "│ " if c else " ") for c in cols),
|
|
98
|
-
iter(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
]
|
|
103
|
-
),
|
|
99
|
+
iter([
|
|
100
|
+
("", "├─" if hasnext else "└─"),
|
|
101
|
+
ls_node_format(name, parent_node=node),
|
|
102
|
+
]),
|
|
104
103
|
)
|
|
105
104
|
)
|
|
106
105
|
print_node(node[name], [*cols, hasnext])
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Command line interface."""
|
|
2
|
+
|
|
2
3
|
import asyncio
|
|
3
4
|
import json
|
|
4
5
|
import pathlib
|
|
@@ -8,9 +9,9 @@ import xdg.BaseDirectory
|
|
|
8
9
|
from prompt_toolkit import PromptSession
|
|
9
10
|
from prompt_toolkit.history import FileHistory
|
|
10
11
|
from prompt_toolkit.patch_stdout import patch_stdout
|
|
11
|
-
from shv import RpcError, RpcUrl
|
|
12
|
+
from shv import RpcError, RpcLogin, RpcUrl
|
|
12
13
|
|
|
13
|
-
from . import builtin_impl as _
|
|
14
|
+
from . import builtin_impl as _ # noqa F401
|
|
14
15
|
from .builtin import call_builtin
|
|
15
16
|
from .client import Node, SHVClient
|
|
16
17
|
from .complet import CliCompleter
|
|
@@ -68,7 +69,7 @@ async def run(config: CliConfig) -> None:
|
|
|
68
69
|
location=config.url.location,
|
|
69
70
|
port=config.url.port,
|
|
70
71
|
protocol=config.url.protocol,
|
|
71
|
-
username=config.url.username,
|
|
72
|
+
login=RpcLogin(username=config.url.login.username),
|
|
72
73
|
)
|
|
73
74
|
cpath = xdg.BaseDirectory.save_cache_path("shvcli")
|
|
74
75
|
fname = re.sub(r"[^\w_. -]", "_", cacheurl.to_url())
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""SHV client for shvcli."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import collections.abc
|
|
@@ -6,7 +7,6 @@ import typing
|
|
|
6
7
|
from pathlib import PurePosixPath
|
|
7
8
|
|
|
8
9
|
from shv import (
|
|
9
|
-
RpcClient,
|
|
10
10
|
RpcError,
|
|
11
11
|
RpcMessage,
|
|
12
12
|
RpcMethodDesc,
|
|
@@ -31,7 +31,7 @@ class Node(collections.abc.Mapping[str, "Node"]):
|
|
|
31
31
|
self.nodes_probed = False
|
|
32
32
|
self.methods_probed = False
|
|
33
33
|
|
|
34
|
-
def __getitem__(self, key: str) ->
|
|
34
|
+
def __getitem__(self, key: str) -> Node:
|
|
35
35
|
"""Get node from nodes."""
|
|
36
36
|
return self.nodes[key]
|
|
37
37
|
|
|
@@ -43,7 +43,7 @@ class Node(collections.abc.Mapping[str, "Node"]):
|
|
|
43
43
|
"""Get number of child nodes."""
|
|
44
44
|
return len(self.nodes)
|
|
45
45
|
|
|
46
|
-
def valid_path(self, path: str | PurePosixPath) ->
|
|
46
|
+
def valid_path(self, path: str | PurePosixPath) -> Node:
|
|
47
47
|
"""Add valid path relative to this node."""
|
|
48
48
|
if isinstance(path, str):
|
|
49
49
|
path = PurePosixPath(path)
|
|
@@ -68,7 +68,7 @@ class Node(collections.abc.Mapping[str, "Node"]):
|
|
|
68
68
|
if pnode is not None:
|
|
69
69
|
pnode.nodes.pop(path.name)
|
|
70
70
|
|
|
71
|
-
def get_path(self, path: str | PurePosixPath) ->
|
|
71
|
+
def get_path(self, path: str | PurePosixPath) -> None | Node:
|
|
72
72
|
"""Get node on given path."""
|
|
73
73
|
if isinstance(path, str):
|
|
74
74
|
path = PurePosixPath(path)
|
|
@@ -79,7 +79,7 @@ class Node(collections.abc.Mapping[str, "Node"]):
|
|
|
79
79
|
node = node[n]
|
|
80
80
|
return node
|
|
81
81
|
|
|
82
|
-
def dump(self) ->
|
|
82
|
+
def dump(self) -> dict[str, object]:
|
|
83
83
|
"""Dump the data to basic types."""
|
|
84
84
|
return {
|
|
85
85
|
"nodes": {n: v.dump() for n, v in self.nodes.items()},
|
|
@@ -90,15 +90,18 @@ class Node(collections.abc.Mapping[str, "Node"]):
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
@classmethod
|
|
93
|
-
def load(cls, data:
|
|
93
|
+
def load(cls, data: collections.abc.Mapping[str, object]) -> Node:
|
|
94
94
|
"""Load node tree from dump."""
|
|
95
95
|
assert isinstance(data, collections.abc.Mapping)
|
|
96
96
|
res = cls()
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
if isinstance(data["nodes"], collections.abc.Mapping):
|
|
98
|
+
res.nodes = {n: cls.load(v) for n, v in data["nodes"].items()}
|
|
99
|
+
if isinstance(data["methods"], collections.abc.Sequence):
|
|
100
|
+
res.methods = set(data["methods"])
|
|
101
|
+
if isinstance(data["signals"], collections.abc.Sequence):
|
|
102
|
+
res.signals = set(data["signals"])
|
|
103
|
+
res.nodes_probed = bool(data["nodes_probed"])
|
|
104
|
+
res.methods_probed = bool(data["methods_probed"])
|
|
102
105
|
return res
|
|
103
106
|
|
|
104
107
|
|
|
@@ -108,11 +111,11 @@ class SHVClient(SimpleClient):
|
|
|
108
111
|
APP_NAME = "shvcli"
|
|
109
112
|
APP_VERSION = VERSION
|
|
110
113
|
|
|
111
|
-
def __init__(self,
|
|
114
|
+
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: # noqa ANN401
|
|
112
115
|
"""Initialize client and set create reference to tree."""
|
|
113
116
|
self.tree = Node()
|
|
114
117
|
self.tree.valid_path(".app")
|
|
115
|
-
super().__init__(
|
|
118
|
+
super().__init__(*args, **kwargs)
|
|
116
119
|
|
|
117
120
|
async def _loop(self) -> None:
|
|
118
121
|
await super()._loop()
|
|
@@ -150,19 +153,27 @@ class SHVClient(SimpleClient):
|
|
|
150
153
|
self.tree.invalid_path(path)
|
|
151
154
|
raise exc
|
|
152
155
|
node = self.tree.valid_path(path)
|
|
153
|
-
node.methods = {
|
|
154
|
-
|
|
156
|
+
node.methods = {
|
|
157
|
+
d.name for d in res if RpcMethodFlags.NOT_CALLABLE not in d.flags
|
|
158
|
+
}
|
|
159
|
+
node.signals = {d.name for d in res if RpcMethodFlags.NOT_CALLABLE in d.flags}
|
|
155
160
|
node.methods_probed = True
|
|
156
161
|
return res
|
|
157
162
|
|
|
158
|
-
async def call(
|
|
163
|
+
async def call(
|
|
164
|
+
self,
|
|
165
|
+
path: str,
|
|
166
|
+
method: str,
|
|
167
|
+
*args: typing.Any, # noqa ANN401
|
|
168
|
+
**kwargs: typing.Any, # noqa ANN401
|
|
169
|
+
) -> SHVType:
|
|
159
170
|
"""Perform call same as ValueClient.
|
|
160
171
|
|
|
161
172
|
This uses every call to get insite into existence of method and node and records
|
|
162
173
|
that in the tree.
|
|
163
174
|
"""
|
|
164
175
|
try:
|
|
165
|
-
res = await super().call(path, method,
|
|
176
|
+
res = await super().call(path, method, *args, **kwargs)
|
|
166
177
|
except RpcMethodNotFoundError as exc:
|
|
167
178
|
raise exc
|
|
168
179
|
except RpcError as exc:
|
|
@@ -190,7 +201,7 @@ class SHVClient(SimpleClient):
|
|
|
190
201
|
async def path_is_valid(self, path: str) -> bool:
|
|
191
202
|
"""Check if given path is valid by using ls command."""
|
|
192
203
|
path = path.strip("/")
|
|
193
|
-
if path
|
|
204
|
+
if not path:
|
|
194
205
|
return True # top level is always valid
|
|
195
206
|
if "/" in path:
|
|
196
207
|
pth, name = path.rsplit("/", maxsplit=1)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Completion for CLI."""
|
|
2
|
+
|
|
2
3
|
import collections.abc
|
|
3
4
|
import typing
|
|
4
5
|
|
|
@@ -28,7 +29,7 @@ class CliCompleter(Completer):
|
|
|
28
29
|
|
|
29
30
|
# Parameters
|
|
30
31
|
if CliFlags.COMPLETE_CALL in items.flags:
|
|
31
|
-
if items.method in
|
|
32
|
+
if items.method in {"ls", "dir"} and not self.config.raw:
|
|
32
33
|
yield from comp_path(self.shvclient, self.config, items)
|
|
33
34
|
elif bmethod := builtin.get_builtin(items.method[1:]):
|
|
34
35
|
if bmethod.argument:
|
|
@@ -58,7 +59,7 @@ class CliCompleter(Completer):
|
|
|
58
59
|
items = parse_line(document.text)
|
|
59
60
|
if self.config.autoprobe and (
|
|
60
61
|
CliFlags.COMPLETE_CALL not in items.flags
|
|
61
|
-
or items.method in
|
|
62
|
+
or items.method in {"ls", "dir"}
|
|
62
63
|
or (
|
|
63
64
|
items.method[0] == "!"
|
|
64
65
|
and (bmethod := builtin.get_builtin(items.method[1:])) is not None
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Configuration state of the CLI."""
|
|
2
|
+
|
|
2
3
|
import collections.abc
|
|
3
4
|
import configparser
|
|
4
5
|
import enum
|
|
@@ -102,7 +103,7 @@ class CliConfig:
|
|
|
102
103
|
self.__rurl = RpcUrl.parse(
|
|
103
104
|
subprocess.run(
|
|
104
105
|
f"printf '%s' \"{self.__url}\"",
|
|
105
|
-
shell=True,
|
|
106
|
+
shell=True, # noqa S602
|
|
106
107
|
stdout=subprocess.PIPE,
|
|
107
108
|
check=True,
|
|
108
109
|
).stdout.decode()
|
|
@@ -128,7 +129,7 @@ class CliConfig:
|
|
|
128
129
|
return logging.root.level <= logging.DEBUG
|
|
129
130
|
|
|
130
131
|
@debug.setter
|
|
131
|
-
def debug(self, value: bool) -> None:
|
|
132
|
+
def debug(self, value: bool) -> None: # noqa PLR6301
|
|
132
133
|
logging.root.setLevel(logging.DEBUG if value else logging.WARNING)
|
|
133
134
|
|
|
134
135
|
def shvpath(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Special handling of ls and dir methods."""
|
|
2
|
+
|
|
2
3
|
import itertools
|
|
3
4
|
import pathlib
|
|
4
5
|
import string
|
|
@@ -32,14 +33,17 @@ def ls_node_format(
|
|
|
32
33
|
def dir_method_format(method: RpcMethodDesc) -> tuple[str, str]:
|
|
33
34
|
"""Print format for single method info."""
|
|
34
35
|
methstyle = ""
|
|
35
|
-
if
|
|
36
|
-
methstyle = "
|
|
36
|
+
if method.name in {"ls", "dir"}:
|
|
37
|
+
methstyle = "ansibrightblack"
|
|
37
38
|
elif RpcMethodFlags.SETTER in method.flags:
|
|
38
39
|
methstyle = "ansiyellow"
|
|
39
40
|
elif RpcMethodFlags.GETTER in method.flags:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
if method.signals:
|
|
42
|
+
methstyle = "ansimagenta"
|
|
43
|
+
else:
|
|
44
|
+
methstyle = "ansigreen"
|
|
45
|
+
elif method.signals or RpcMethodFlags.NOT_CALLABLE in method.flags:
|
|
46
|
+
methstyle = "ansipurple"
|
|
43
47
|
name = method.name
|
|
44
48
|
return (
|
|
45
49
|
methstyle,
|
|
@@ -74,7 +78,7 @@ async def ls_method(shvclient: SHVClient, config: CliConfig, items: CliItems) ->
|
|
|
74
78
|
else:
|
|
75
79
|
print_row(
|
|
76
80
|
itertools.chain(
|
|
77
|
-
iter(n
|
|
81
|
+
iter([*n, ("", " ")]), cpon_ftext(Cpon.pack(resp))
|
|
78
82
|
)
|
|
79
83
|
)
|
|
80
84
|
continue
|
|
@@ -99,7 +103,7 @@ async def dir_method(shvclient: SHVClient, config: CliConfig, items: CliItems) -
|
|
|
99
103
|
else:
|
|
100
104
|
print_row(
|
|
101
105
|
itertools.chain(
|
|
102
|
-
iter(n
|
|
106
|
+
iter([*n, ("", " ")]), cpon_ftext(Cpon.pack(resp))
|
|
103
107
|
)
|
|
104
108
|
)
|
|
105
109
|
continue
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Implementation of CLI parser."""
|
|
2
|
+
|
|
2
3
|
import dataclasses
|
|
3
4
|
import enum
|
|
4
5
|
|
|
@@ -28,7 +29,7 @@ class CliItems:
|
|
|
28
29
|
param_raw: str = ""
|
|
29
30
|
"""Raw parameter passed from CLI."""
|
|
30
31
|
|
|
31
|
-
flags: CliFlags = CliFlags(0)
|
|
32
|
+
flags: CliFlags = dataclasses.field(default_factory=lambda: CliFlags(0))
|
|
32
33
|
"""Flags signaling the presence of some important dividers."""
|
|
33
34
|
|
|
34
35
|
@property
|
|
@@ -84,9 +85,8 @@ def parse_line(line: str) -> CliItems:
|
|
|
84
85
|
if ":" in line:
|
|
85
86
|
res.flags |= CliFlags.HAS_COLON
|
|
86
87
|
res.path, res.method = line.split(":", maxsplit=1)
|
|
88
|
+
elif "/" in line:
|
|
89
|
+
res.path = line
|
|
87
90
|
else:
|
|
88
|
-
|
|
89
|
-
res.path = line
|
|
90
|
-
else:
|
|
91
|
-
res.method = line
|
|
91
|
+
res.method = line
|
|
92
92
|
return res
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Various tools used in the code."""
|
|
2
|
+
|
|
2
3
|
import collections.abc
|
|
3
4
|
import os
|
|
4
5
|
import textwrap
|
|
@@ -39,7 +40,7 @@ def intersperse(what: T, iterin: typing.Iterable[T]) -> typing.Iterator[T]:
|
|
|
39
40
|
|
|
40
41
|
def print_ftext(
|
|
41
42
|
fstrs: collections.abc.Iterable[tuple[str, str]]
|
|
42
|
-
| collections.abc.Iterator[tuple[str, str]]
|
|
43
|
+
| collections.abc.Iterator[tuple[str, str]],
|
|
43
44
|
) -> None:
|
|
44
45
|
"""Call :meth:`print_formatted_text` with :class:`FormattedText`."""
|
|
45
46
|
print_formatted_text(FormattedText(fstrs))
|
|
@@ -47,7 +48,7 @@ def print_ftext(
|
|
|
47
48
|
|
|
48
49
|
def print_flist(
|
|
49
50
|
fstrs: collections.abc.Iterable[tuple[str, str]]
|
|
50
|
-
| collections.abc.Iterator[tuple[str, str]]
|
|
51
|
+
| collections.abc.Iterator[tuple[str, str]],
|
|
51
52
|
) -> None:
|
|
52
53
|
"""Print with :meth:`print_ftext` but interperse spaces."""
|
|
53
54
|
cols = os.get_terminal_size().columns
|
|
@@ -70,7 +71,7 @@ def print_row(
|
|
|
70
71
|
ftext: collections.abc.Iterable[tuple[str, str]]
|
|
71
72
|
| collections.abc.Iterator[tuple[str, str]]
|
|
72
73
|
| tuple[str, str]
|
|
73
|
-
| str
|
|
74
|
+
| str,
|
|
74
75
|
) -> None:
|
|
75
76
|
"""Print one line with hyphens when needed."""
|
|
76
77
|
if isinstance(ftext, str):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Validator for CLI."""
|
|
2
|
+
|
|
2
3
|
from prompt_toolkit.document import Document
|
|
3
4
|
from prompt_toolkit.validation import ValidationError, Validator
|
|
4
5
|
|
|
@@ -22,7 +23,7 @@ class CliValidator(Validator):
|
|
|
22
23
|
|
|
23
24
|
# Parameters
|
|
24
25
|
if CliFlags.COMPLETE_CALL in items.flags:
|
|
25
|
-
if items.method in
|
|
26
|
+
if items.method in {"ls", "dir"} and not self.config.raw:
|
|
26
27
|
return
|
|
27
28
|
if builtin.get_builtin(items.method[1:]):
|
|
28
29
|
# TODO we can add validation to the builtins as well
|
|
@@ -30,7 +31,7 @@ class CliValidator(Validator):
|
|
|
30
31
|
# Any other command should have CPON as argument and thus validate
|
|
31
32
|
# it as such.
|
|
32
33
|
try:
|
|
33
|
-
items.param
|
|
34
|
+
_ = items.param
|
|
34
35
|
except (ValueError, EOFError) as exc:
|
|
35
36
|
raise ValidationError(message=f"Invalid CPON: {exc}") from exc
|
|
36
37
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: shvcli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Silicon Heaven CLI access application
|
|
5
5
|
Author: Elektroline a.s.
|
|
6
6
|
License: MIT License
|
|
@@ -25,7 +25,7 @@ License: MIT License
|
|
|
25
25
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
|
|
28
|
-
Project-URL: repository, https://gitlab.com/
|
|
28
|
+
Project-URL: repository, https://gitlab.com/silicon-heaven/shvcli
|
|
29
29
|
Classifier: Development Status :: 4 - Beta
|
|
30
30
|
Classifier: License :: OSI Approved :: MIT License
|
|
31
31
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -38,27 +38,22 @@ Classifier: Topic :: Terminals
|
|
|
38
38
|
Requires-Python: >=3.11
|
|
39
39
|
Description-Content-Type: text/x-rst
|
|
40
40
|
License-File: LICENSE
|
|
41
|
-
Requires-Dist: pyshv>=0.
|
|
41
|
+
Requires-Dist: pyshv>=0.6.0
|
|
42
42
|
Requires-Dist: prompt_toolkit
|
|
43
|
+
Requires-Dist: pygments
|
|
43
44
|
Requires-Dist: pyxdg
|
|
44
|
-
Provides-Extra: lint
|
|
45
|
-
Requires-Dist: black; extra == "lint"
|
|
46
|
-
Requires-Dist: isort; extra == "lint"
|
|
47
|
-
Requires-Dist: mypy; extra == "lint"
|
|
48
|
-
Requires-Dist: pydocstyle; extra == "lint"
|
|
49
|
-
Requires-Dist: pylint; extra == "lint"
|
|
50
45
|
|
|
51
46
|
=====================================
|
|
52
47
|
Silicon Heaven CLI access application
|
|
53
48
|
=====================================
|
|
54
|
-
.. image:: https://gitlab.com/
|
|
49
|
+
.. image:: https://gitlab.com/silicon-heaven/shvcli/-/raw/master/logo.svg
|
|
55
50
|
:align: right
|
|
56
51
|
:height: 128px
|
|
57
52
|
|
|
58
53
|
This provides an easy to use CLI interfase to access the SHV network.
|
|
59
54
|
|
|
60
|
-
* `📃 Sources <https://gitlab.com/
|
|
61
|
-
* `⁉️ Issue tracker <https://gitlab.com/
|
|
55
|
+
* `📃 Sources <https://gitlab.com/silicon-heaven/shvcli>`__
|
|
56
|
+
* `⁉️ Issue tracker <https://gitlab.com/silicon-heaven/shvcli/-/issues>`__
|
|
62
57
|
* `📕 Silicon Heaven protocol documentation <https://silicon-heaven.github.io/shv-doc/>`__
|
|
63
58
|
|
|
64
59
|
|
shvcli-0.3.0/pyproject.toml
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "shvcli"
|
|
3
|
-
description = "Silicon Heaven CLI access application"
|
|
4
|
-
readme = "README.rst"
|
|
5
|
-
license = {file = "LICENSE"}
|
|
6
|
-
dynamic = ["version"]
|
|
7
|
-
authors = [
|
|
8
|
-
{ name="Elektroline a.s." },
|
|
9
|
-
]
|
|
10
|
-
classifiers = [
|
|
11
|
-
"Development Status :: 4 - Beta",
|
|
12
|
-
"License :: OSI Approved :: MIT License",
|
|
13
|
-
"Programming Language :: Python :: 3",
|
|
14
|
-
"Operating System :: OS Independent",
|
|
15
|
-
"Environment :: Console",
|
|
16
|
-
"Intended Audience :: End Users/Desktop",
|
|
17
|
-
"Topic :: Home Automation",
|
|
18
|
-
"Topic :: System :: Shells",
|
|
19
|
-
"Topic :: Terminals",
|
|
20
|
-
]
|
|
21
|
-
requires-python = ">=3.11"
|
|
22
|
-
dependencies = [
|
|
23
|
-
"pyshv >= 0.5.0",
|
|
24
|
-
"prompt_toolkit",
|
|
25
|
-
"pyxdg",
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
[project.urls]
|
|
29
|
-
repository = "https://gitlab.com/elektroline-predator/shvcli"
|
|
30
|
-
|
|
31
|
-
[project.optional-dependencies]
|
|
32
|
-
lint = [
|
|
33
|
-
"black",
|
|
34
|
-
"isort",
|
|
35
|
-
"mypy",
|
|
36
|
-
"pydocstyle",
|
|
37
|
-
"pylint",
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
[project.scripts]
|
|
41
|
-
shvcli = "shvcli.__main__:main"
|
|
42
|
-
|
|
43
|
-
[build-system]
|
|
44
|
-
requires = ["setuptools"]
|
|
45
|
-
build-backend = "setuptools.build_meta"
|
|
46
|
-
|
|
47
|
-
[tool.setuptools.dynamic]
|
|
48
|
-
version = {file = "shvcli/version"}
|
|
49
|
-
[tool.setuptools.package-data]
|
|
50
|
-
"*" = ["version", "py.typed"]
|
|
51
|
-
|
|
52
|
-
[tool.coverage.run]
|
|
53
|
-
branch = true
|
|
54
|
-
[tool.coverage.report]
|
|
55
|
-
exclude_lines = [
|
|
56
|
-
"pragma: no cover",
|
|
57
|
-
"if typing.TYPE_CHECKING:"
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
[tool.isort]
|
|
61
|
-
profile = 'black'
|
|
62
|
-
|
|
63
|
-
[tool.pylint.main]
|
|
64
|
-
recursive = "y"
|
|
65
|
-
fail-under = 9.5
|
|
66
|
-
[tool.pylint.format]
|
|
67
|
-
disable = [
|
|
68
|
-
# Exception can be safely caught because there is BaseException for unsafe
|
|
69
|
-
# ones (such as SystemExit) and there are reasons to catch all exceptions.
|
|
70
|
-
"W0718",
|
|
71
|
-
# Prevent usage of module name in documentation (seems unreasonable)
|
|
72
|
-
"C0104",
|
|
73
|
-
# We have pydocstyle for the following ones
|
|
74
|
-
"C0103", "C0114", "C0115", "C0116",
|
|
75
|
-
]
|
|
76
|
-
# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html
|
|
77
|
-
max-line-length = 88
|
|
78
|
-
|
|
79
|
-
[tool.mypy]
|
|
80
|
-
python_version = "3.11"
|
|
81
|
-
ignore_missing_imports = true
|
|
82
|
-
disallow_untyped_defs = true
|
|
83
|
-
warn_redundant_casts = true
|
|
84
|
-
warn_unused_ignores = true
|
|
85
|
-
warn_return_any = true
|
|
86
|
-
warn_unreachable = true
|
|
87
|
-
|
|
88
|
-
[tool.pydocstyle]
|
|
89
|
-
convention = "pep257"
|
shvcli-0.3.0/shvcli/__init__.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|