shvcli 0.5.0__tar.gz → 0.6.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.
Files changed (28) hide show
  1. {shvcli-0.5.0/shvcli.egg-info → shvcli-0.6.0}/PKG-INFO +2 -2
  2. {shvcli-0.5.0 → shvcli-0.6.0}/pyproject.toml +2 -2
  3. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/__main__.py +7 -1
  4. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/builtin_impl.py +15 -6
  5. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/cli.py +3 -1
  6. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/client.py +2 -2
  7. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/lsdir.py +19 -1
  8. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/parse.py +9 -6
  9. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/tools.py +27 -11
  10. {shvcli-0.5.0 → shvcli-0.6.0/shvcli.egg-info}/PKG-INFO +2 -2
  11. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli.egg-info/requires.txt +1 -1
  12. {shvcli-0.5.0 → shvcli-0.6.0}/LICENSE +0 -0
  13. {shvcli-0.5.0 → shvcli-0.6.0}/README.rst +0 -0
  14. {shvcli-0.5.0 → shvcli-0.6.0}/setup.cfg +0 -0
  15. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/__init__.py +0 -0
  16. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/__version__.py +0 -0
  17. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/builtin.py +0 -0
  18. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/complet.py +0 -0
  19. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/complet_tools.py +0 -0
  20. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/config.py +0 -0
  21. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/py.typed +0 -0
  22. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/scan.py +0 -0
  23. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/valid.py +0 -0
  24. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli/version +0 -0
  25. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli.egg-info/SOURCES.txt +0 -0
  26. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli.egg-info/dependency_links.txt +0 -0
  27. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli.egg-info/entry_points.txt +0 -0
  28. {shvcli-0.5.0 → shvcli-0.6.0}/shvcli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: shvcli
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Silicon Heaven CLI access application
5
5
  Author: Elektroline a.s.
6
6
  License: MIT License
@@ -38,7 +38,7 @@ 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.7.0
41
+ Requires-Dist: pyshv>=0.7.1
42
42
  Requires-Dist: prompt_toolkit
43
43
  Requires-Dist: pygments
44
44
  Requires-Dist: pyxdg
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "shvcli"
3
- version = "0.5.0"
3
+ version = "0.6.0"
4
4
  description = "Silicon Heaven CLI access application"
5
5
  readme = "README.rst"
6
6
  license = {file = "LICENSE"}
@@ -20,7 +20,7 @@ classifiers = [
20
20
  ]
21
21
  requires-python = ">=3.11"
22
22
  dependencies = [
23
- "pyshv >= 0.7.0",
23
+ "pyshv >= 0.7.1",
24
24
  "prompt_toolkit",
25
25
  "pygments",
26
26
  "pyxdg",
@@ -21,6 +21,12 @@ def parse_args() -> argparse.Namespace:
21
21
  )
22
22
  parser.add_argument(
23
23
  "-s",
24
+ "--subscribe",
25
+ action="append",
26
+ default=[],
27
+ help="Automatic subscription right after connection.",
28
+ )
29
+ parser.add_argument(
24
30
  "--scan",
25
31
  action="store_true",
26
32
  help="Perform scan as a first action right after connect.",
@@ -49,7 +55,7 @@ def main() -> None:
49
55
  config.debug = config.debug or args.debug
50
56
  config.initial_scan = args.scan
51
57
  config.initial_scan_depth = args.scan_depth
52
- asyncio.run(run(config))
58
+ asyncio.run(run(config, args.subscribe))
53
59
 
54
60
 
55
61
  if __name__ == "__main__":
@@ -13,7 +13,7 @@ from .config import CliConfig
13
13
  from .lsdir import ls_node_format
14
14
  from .parse import CliItems
15
15
  from .scan import scan_nodes
16
- from .tools import lookahead, print_block, print_ftext, print_row
16
+ from .tools import lookahead, print_block, print_flist, print_ftext, print_row
17
17
 
18
18
 
19
19
  def argument_signal_comp(
@@ -21,10 +21,10 @@ def argument_signal_comp(
21
21
  ) -> collections.abc.Iterable[Completion]:
22
22
  """Completion for subscribe argument."""
23
23
  if ":" in items.param_raw:
24
- path, method, signal = items.interpret_param_ri(config).split(":")
24
+ path, method, signal = items.interpret_param_ri(config)[-1].split(":")
25
25
  node = shvclient.tree.get_path(path)
26
26
  if node is not None:
27
- if items.param_raw.count(":") == 1:
27
+ if items.param_raw.rsplit(maxsplit=1)[-1].count(":") == 1:
28
28
  yield from comp_from(method, node.methods)
29
29
  elif method in node.methods:
30
30
  yield from comp_from(signal, node.methods[method])
@@ -71,19 +71,28 @@ def _help(_: SHVClient, __: CliConfig, ___: CliItems) -> None:
71
71
  @builtin(aliases={"sub"}, argument=argument_signal)
72
72
  async def subscribe(shvclient: SHVClient, config: CliConfig, items: CliItems) -> None:
73
73
  """Add new subscribe."""
74
- await shvclient.subscribe(items.interpret_param_ri(config))
74
+ for ri in items.interpret_param_ri(config):
75
+ await shvclient.subscribe(ri)
75
76
 
76
77
 
77
78
  @builtin(aliases={"usub"}, argument=argument_signal)
78
79
  async def unsubscribe(shvclient: SHVClient, config: CliConfig, items: CliItems) -> None:
79
80
  """Unsubscribe existing subscription."""
80
- await shvclient.unsubscribe(items.interpret_param_ri(config))
81
+ for ri in items.interpret_param_ri(config):
82
+ await shvclient.unsubscribe(ri)
81
83
 
82
84
 
83
85
  @builtin(aliases={"subs", "test"})
84
86
  async def subscriptions(shvclient: SHVClient, _: CliConfig, __: CliItems) -> None:
85
87
  """List current subscriptions."""
86
- print(await shvclient.call(".app/broker/currentClient", "subscriptions"))
88
+ subs = await shvclient.call(".broker/currentClient", "subscriptions")
89
+ if isinstance(subs, dict):
90
+ print_flist(
91
+ ("", sub + (f"?{timeout}secs" if timeout is not None else ""))
92
+ for sub, timeout in subs.items()
93
+ )
94
+ else:
95
+ print(subs)
87
96
 
88
97
 
89
98
  @builtin(aliases={"t"}, argument=argument_path)
@@ -59,10 +59,12 @@ async def _app(config: CliConfig, shvclient: SHVClient) -> None:
59
59
  continue
60
60
 
61
61
 
62
- async def run(config: CliConfig) -> None:
62
+ async def run(config: CliConfig, subscriptions: list[str]) -> None:
63
63
  """Loop to run interactive CLI session."""
64
64
  shvclient = await SHVClient.connect(config.url)
65
65
  assert isinstance(shvclient, SHVClient)
66
+ for ri in subscriptions:
67
+ await shvclient.subscribe(ri)
66
68
 
67
69
  if config.cache:
68
70
  cacheurl = RpcUrl(
@@ -15,9 +15,9 @@ from shv import (
15
15
  SHVType,
16
16
  SimpleClient,
17
17
  )
18
- from shv.cpon import Cpon
19
18
 
20
19
  from . import VERSION
20
+ from .tools import print_cpon
21
21
 
22
22
 
23
23
  class Node(collections.abc.Mapping[str, "Node"]):
@@ -123,7 +123,7 @@ class SHVClient(SimpleClient):
123
123
  self.tree.valid_path(msg.path or "").methods.setdefault(
124
124
  msg.source, set()
125
125
  ).add(msg.signal_name)
126
- print(f"{msg.path}:{msg.source}:{msg.signal_name}: {Cpon.pack(msg.param)}")
126
+ print_cpon(msg.param, f"{msg.path}:{msg.source}:{msg.signal_name}: ", True)
127
127
 
128
128
  async def ls(self, path: str) -> list[str]:
129
129
  """List same as in ValueClient but with result being preserved in tree."""
@@ -42,7 +42,7 @@ def dir_method_format(method: RpcMethodDesc) -> tuple[str, str]:
42
42
  methstyle = "ansimagenta"
43
43
  else:
44
44
  methstyle = "ansigreen"
45
- elif method.signals or RpcMethodFlags.NOT_CALLABLE in method.flags:
45
+ elif method.signals:
46
46
  methstyle = "ansipurple"
47
47
  name = method.name
48
48
  return (
@@ -51,6 +51,20 @@ def dir_method_format(method: RpcMethodDesc) -> tuple[str, str]:
51
51
  )
52
52
 
53
53
 
54
+ def dir_signal_format(method: RpcMethodDesc, signal: str) -> tuple[str, str]:
55
+ """Print format for signals."""
56
+ sigstyle = "ansipurple"
57
+ if method.name in {"ls", "dir"}:
58
+ sigstyle = "ansibrightblack"
59
+ elif RpcMethodFlags.GETTER in method.flags:
60
+ sigstyle = "ansimagenta"
61
+ label = f"{method.name}:{signal}"
62
+ return (
63
+ sigstyle,
64
+ f'"{label}"' if any(c in label for c in string.whitespace) else label,
65
+ )
66
+
67
+
54
68
  async def ls_method(shvclient: SHVClient, config: CliConfig, items: CliItems) -> None:
55
69
  """SHV ls method that is just smarter than regular call."""
56
70
  shvpath = items.interpret_param_path(config)
@@ -94,6 +108,8 @@ async def dir_method(shvclient: SHVClient, config: CliConfig, items: CliItems) -
94
108
  if config.autoget and any(_use_autoget(d) for d in dirr):
95
109
  w = max(len(d.name) for d in dirr)
96
110
  for d in dirr:
111
+ if RpcMethodFlags.NOT_CALLABLE in d.flags:
112
+ continue # Ignore not callable
97
113
  n = [("", " " * (w - len(d.name))), dir_method_format(d)]
98
114
  if _use_autoget(d):
99
115
  try:
@@ -110,6 +126,8 @@ async def dir_method(shvclient: SHVClient, config: CliConfig, items: CliItems) -
110
126
  print_row(n)
111
127
  else:
112
128
  print_flist(dir_method_format(d) for d in dirr)
129
+ if any(d.signals for d in dirr):
130
+ print_flist(dir_signal_format(d, s) for d in dirr for s in d.signals)
113
131
 
114
132
 
115
133
  def _use_autoget(method: RpcMethodDesc) -> bool:
@@ -46,18 +46,21 @@ class CliItems:
46
46
  """
47
47
  return config.shvpath([self.path, self.param_raw])
48
48
 
49
- def interpret_param_ri(self, config: CliConfig, expand: bool = True) -> str:
49
+ def interpret_param_ri(self, config: CliConfig, expand: bool = True) -> list[str]:
50
50
  """Interpret parameter as RI.
51
51
 
52
52
  The path in RI is combined with pat in the configuration.
53
53
 
54
54
  :return: SHV RPC RI.
55
55
  """
56
- parts = self.param_raw.split(":")
57
- path = parts[0] if parts[0] else "**"
58
- method = parts[1] if len(parts) == 2 else "*"
59
- signal = parts[2] if len(parts) == 3 else "*"
60
- return f"{self.path}{"/" if path else ""}{path}:{method}:{signal}"
56
+ res = []
57
+ for item in self.param_raw.split():
58
+ parts = item.split(":")
59
+ path = parts[0] if parts[0] else "**"
60
+ method = parts[1] if len(parts) == 2 else "*"
61
+ signal = parts[2] if len(parts) == 3 else "*"
62
+ res.append(f"{self.path}{"/" if self.path else ""}{path}:{method}:{signal}")
63
+ return res
61
64
 
62
65
  def interpret_param_set(self) -> tuple[str | None, str | None]:
63
66
  """Interpret parameter as 'set' method specification.
@@ -1,6 +1,7 @@
1
1
  """Various tools used in the code."""
2
2
 
3
3
  import collections.abc
4
+ import itertools
4
5
  import os
5
6
  import textwrap
6
7
  import typing
@@ -118,16 +119,31 @@ def cpon_ftext(cpon: str) -> collections.abc.Iterator[tuple[str, str]]:
118
119
  yield "", "\n"
119
120
 
120
121
 
121
- def print_cpon(data: shv.SHVType, short: bool = False) -> None:
122
+ def _wrap_cpon(cpon: str) -> collections.abc.Iterator[str]:
123
+ """Wrap CPON.
124
+
125
+ It is better to wrap CPON on division characters rather than white spaces
126
+ because those are part of the data.
127
+ """
128
+ cols = os.get_terminal_size().columns
129
+ while len(cpon) > cols:
130
+ i = max(cpon.rfind(sep, 0, cols) for sep in (",", "]", "}"))
131
+ if i == -1:
132
+ i = cols
133
+ yield cpon[: i + 1]
134
+ cpon = " " + cpon[i + 1 :]
135
+ yield cpon
136
+
137
+
138
+ def print_cpon(data: shv.SHVType, prefix: str = "", short: bool = False) -> None:
122
139
  """Print given data in CPON format."""
123
- simple = shv.Cpon.pack(data)
124
- if len(simple) > os.get_terminal_size().columns:
125
- if short:
126
- print_row(cpon_ftext(simple))
127
- else:
128
- for line in shv.Cpon.pack(
129
- data, shv.CponWriter.Options(indent=b" ")
130
- ).splitlines():
131
- print_row(cpon_ftext(line))
140
+ if short:
141
+ cpon = "\n".join(_wrap_cpon(prefix + shv.Cpon.pack(data)))[len(prefix) :]
142
+ print_ftext(itertools.chain(iter((("", prefix),)), cpon_ftext(cpon)))
132
143
  else:
133
- print_ftext(cpon_ftext(simple))
144
+ print_row(
145
+ itertools.chain(
146
+ iter((("", prefix),)),
147
+ cpon_ftext(shv.Cpon.pack(data, shv.CponWriter.Options(indent=b" "))),
148
+ )
149
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: shvcli
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Silicon Heaven CLI access application
5
5
  Author: Elektroline a.s.
6
6
  License: MIT License
@@ -38,7 +38,7 @@ 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.7.0
41
+ Requires-Dist: pyshv>=0.7.1
42
42
  Requires-Dist: prompt_toolkit
43
43
  Requires-Dist: pygments
44
44
  Requires-Dist: pyxdg
@@ -1,4 +1,4 @@
1
- pyshv>=0.7.0
1
+ pyshv>=0.7.1
2
2
  prompt_toolkit
3
3
  pygments
4
4
  pyxdg
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes