playground-ls-cli 4.14.1.dev8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localstack_cli/__init__.py +0 -0
- localstack_cli/cli/__init__.py +10 -0
- localstack_cli/cli/console.py +11 -0
- localstack_cli/cli/core_plugin.py +12 -0
- localstack_cli/cli/exceptions.py +19 -0
- localstack_cli/cli/localstack.py +951 -0
- localstack_cli/cli/lpm.py +138 -0
- localstack_cli/cli/main.py +22 -0
- localstack_cli/cli/plugin.py +39 -0
- localstack_cli/cli/plugins.py +134 -0
- localstack_cli/cli/profiles.py +65 -0
- localstack_cli/config.py +1689 -0
- localstack_cli/constants.py +165 -0
- localstack_cli/logging/__init__.py +0 -0
- localstack_cli/logging/format.py +194 -0
- localstack_cli/logging/setup.py +142 -0
- localstack_cli/packages/__init__.py +25 -0
- localstack_cli/packages/api.py +418 -0
- localstack_cli/packages/core.py +416 -0
- localstack_cli/pro/__init__.py +0 -0
- localstack_cli/pro/core/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/__init__.py +1 -0
- localstack_cli/pro/core/bootstrap/auth.py +213 -0
- localstack_cli/pro/core/bootstrap/dns_utils.py +55 -0
- localstack_cli/pro/core/bootstrap/entitlements.py +117 -0
- localstack_cli/pro/core/bootstrap/extensions/__init__.py +3 -0
- localstack_cli/pro/core/bootstrap/extensions/__main__.py +106 -0
- localstack_cli/pro/core/bootstrap/extensions/autoinstall.py +63 -0
- localstack_cli/pro/core/bootstrap/extensions/bootstrap.py +97 -0
- localstack_cli/pro/core/bootstrap/extensions/repository.py +374 -0
- localstack_cli/pro/core/bootstrap/licensingv2.py +1259 -0
- localstack_cli/pro/core/bootstrap/pods/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/pods/api_types.py +17 -0
- localstack_cli/pro/core/bootstrap/pods/constants.py +26 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/api.py +75 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/configs.py +69 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/params.py +86 -0
- localstack_cli/pro/core/bootstrap/pods_client.py +834 -0
- localstack_cli/pro/core/cli/__init__.py +0 -0
- localstack_cli/pro/core/cli/auth.py +226 -0
- localstack_cli/pro/core/cli/aws.py +16 -0
- localstack_cli/pro/core/cli/cli.py +99 -0
- localstack_cli/pro/core/cli/click_utils.py +21 -0
- localstack_cli/pro/core/cli/cloud_pods.py +465 -0
- localstack_cli/pro/core/cli/diff_view.py +41 -0
- localstack_cli/pro/core/cli/ephemeral.py +199 -0
- localstack_cli/pro/core/cli/extensions.py +492 -0
- localstack_cli/pro/core/cli/iam.py +180 -0
- localstack_cli/pro/core/cli/license.py +90 -0
- localstack_cli/pro/core/cli/localstack.py +118 -0
- localstack_cli/pro/core/cli/replicator.py +378 -0
- localstack_cli/pro/core/cli/state.py +183 -0
- localstack_cli/pro/core/cli/tree_view.py +235 -0
- localstack_cli/pro/core/config.py +556 -0
- localstack_cli/pro/core/constants.py +54 -0
- localstack_cli/pro/core/plugins.py +169 -0
- localstack_cli/runtime/__init__.py +6 -0
- localstack_cli/runtime/exceptions.py +7 -0
- localstack_cli/runtime/hooks.py +73 -0
- localstack_cli/testing/__init__.py +1 -0
- localstack_cli/testing/config.py +4 -0
- localstack_cli/utils/__init__.py +0 -0
- localstack_cli/utils/analytics/__init__.py +12 -0
- localstack_cli/utils/analytics/cli.py +67 -0
- localstack_cli/utils/analytics/client.py +111 -0
- localstack_cli/utils/analytics/events.py +30 -0
- localstack_cli/utils/analytics/logger.py +48 -0
- localstack_cli/utils/analytics/metadata.py +250 -0
- localstack_cli/utils/analytics/publisher.py +160 -0
- localstack_cli/utils/analytics/service_request_aggregator.py +133 -0
- localstack_cli/utils/archives.py +271 -0
- localstack_cli/utils/batching.py +258 -0
- localstack_cli/utils/bootstrap.py +1418 -0
- localstack_cli/utils/checksum.py +313 -0
- localstack_cli/utils/collections.py +554 -0
- localstack_cli/utils/common.py +229 -0
- localstack_cli/utils/container_networking.py +142 -0
- localstack_cli/utils/container_utils/__init__.py +0 -0
- localstack_cli/utils/container_utils/container_client.py +1585 -0
- localstack_cli/utils/container_utils/docker_cmd_client.py +987 -0
- localstack_cli/utils/container_utils/docker_sdk_client.py +1018 -0
- localstack_cli/utils/crypto.py +294 -0
- localstack_cli/utils/docker_utils.py +272 -0
- localstack_cli/utils/files.py +327 -0
- localstack_cli/utils/functions.py +92 -0
- localstack_cli/utils/http.py +326 -0
- localstack_cli/utils/json.py +219 -0
- localstack_cli/utils/net.py +516 -0
- localstack_cli/utils/no_exit_argument_parser.py +19 -0
- localstack_cli/utils/numbers.py +49 -0
- localstack_cli/utils/objects.py +235 -0
- localstack_cli/utils/patch.py +260 -0
- localstack_cli/utils/platform.py +77 -0
- localstack_cli/utils/run.py +514 -0
- localstack_cli/utils/server/__init__.py +0 -0
- localstack_cli/utils/server/tcp_proxy.py +108 -0
- localstack_cli/utils/serving.py +187 -0
- localstack_cli/utils/ssl.py +71 -0
- localstack_cli/utils/strings.py +245 -0
- localstack_cli/utils/sync.py +267 -0
- localstack_cli/utils/threads.py +163 -0
- localstack_cli/utils/time.py +81 -0
- localstack_cli/utils/urls.py +21 -0
- localstack_cli/utils/venv.py +100 -0
- localstack_cli/utils/xml.py +41 -0
- localstack_cli/version.py +34 -0
- playground_ls_cli-4.14.1.dev8.dist-info/METADATA +95 -0
- playground_ls_cli-4.14.1.dev8.dist-info/RECORD +112 -0
- playground_ls_cli-4.14.1.dev8.dist-info/WHEEL +5 -0
- playground_ls_cli-4.14.1.dev8.dist-info/entry_points.txt +17 -0
- playground_ls_cli-4.14.1.dev8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from localstack_cli.cli import console
|
|
5
|
+
from localstack_cli.cli.exceptions import CLIError
|
|
6
|
+
from localstack_cli.pro.core.bootstrap.pods_client import CloudPodsClient, StateService
|
|
7
|
+
from localstack_cli.pro.core.cli.cli import RequiresLicenseGroup, _assert_host_reachable
|
|
8
|
+
from localstack_cli.pro.core.cli.tree_view import TreeRenderer
|
|
9
|
+
from localstack_cli.utils.analytics.cli import publish_invocation
|
|
10
|
+
from localstack_cli.utils.collections import is_comma_delimited_list
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group(
|
|
14
|
+
name="state",
|
|
15
|
+
short_help="(Preview) Export, restore, and reset LocalStack state.",
|
|
16
|
+
help="""
|
|
17
|
+
(Preview) Manage and manipulate the localstack state.
|
|
18
|
+
|
|
19
|
+
The state command group allows you to interact with LocalStack's state backend.
|
|
20
|
+
|
|
21
|
+
Read more: https://docs.localstack.cloud/references/persistence-mechanism/#snapshot-based-persistence
|
|
22
|
+
""",
|
|
23
|
+
cls=RequiresLicenseGroup,
|
|
24
|
+
)
|
|
25
|
+
def state() -> None:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@state.command(
|
|
30
|
+
name="reset",
|
|
31
|
+
short_help="Reset the state of LocalStack services",
|
|
32
|
+
help="""
|
|
33
|
+
Reset the service states of the current LocalStack runtime.
|
|
34
|
+
|
|
35
|
+
This command invokes a reset of services in the currently running LocalStack container.
|
|
36
|
+
By default, all services are rest. The `services` options allows to select a subset of services
|
|
37
|
+
which should be reset.
|
|
38
|
+
|
|
39
|
+
This command tries to automatically discover the running LocalStack instance.
|
|
40
|
+
If LocalStack has not been started with `localstack start` (and is not automatically discoverable),
|
|
41
|
+
please set `LOCALSTACK_HOST`.
|
|
42
|
+
""",
|
|
43
|
+
)
|
|
44
|
+
@click.option(
|
|
45
|
+
"-s",
|
|
46
|
+
"--services",
|
|
47
|
+
help="Comma-delimited list of services to reset. By default, the state of all running services is reset.",
|
|
48
|
+
)
|
|
49
|
+
@publish_invocation
|
|
50
|
+
def reset(services: str | None) -> None:
|
|
51
|
+
from localstack_cli.pro.core.bootstrap import pods_client
|
|
52
|
+
|
|
53
|
+
if services and not is_comma_delimited_list(services):
|
|
54
|
+
raise click.ClickException("Input the services as a comma-delimited list")
|
|
55
|
+
|
|
56
|
+
service_list = [x.strip() for x in services.split(",")] if services else None
|
|
57
|
+
pods_client.reset_state(services=service_list)
|
|
58
|
+
console.print("LocalStack state successfully reset")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@state.command(
|
|
62
|
+
name="export",
|
|
63
|
+
short_help="Export the state of LocalStack services",
|
|
64
|
+
help="""
|
|
65
|
+
Save the current state of the LocalStack container to a file on the local disk. This file can be restored at any
|
|
66
|
+
point in time using the `localstack state import` command. Please be aware that this might not be possible when
|
|
67
|
+
importing the state with a different version of LocalStack.
|
|
68
|
+
|
|
69
|
+
If you are looking for a managed solution to handle the state of your LocalStack container, please check out
|
|
70
|
+
the Cloud Pods feature: https://docs.localstack.cloud/user-guide/tools/cloud-pods/.
|
|
71
|
+
|
|
72
|
+
Use the DESTINATION argument to specify an absolute or relative path for the exported file.
|
|
73
|
+
If no destination is specified, a file named `ls-state-export` will be saved in the current working directory.
|
|
74
|
+
|
|
75
|
+
\b
|
|
76
|
+
Examples:
|
|
77
|
+
localstack state export my-state
|
|
78
|
+
localstack state export ../parent-dir/my-state
|
|
79
|
+
localstack state export /home/johndoe/my-state
|
|
80
|
+
|
|
81
|
+
You can also specify a subset of services to export with the `--services` option.
|
|
82
|
+
|
|
83
|
+
\b
|
|
84
|
+
For example:
|
|
85
|
+
localstack state export my-state --services s3,lambda
|
|
86
|
+
|
|
87
|
+
By default, the state of all running services is exported.
|
|
88
|
+
""",
|
|
89
|
+
)
|
|
90
|
+
@click.argument("destination", type=click.Path(resolve_path=True), default="ls-state-export")
|
|
91
|
+
@click.option(
|
|
92
|
+
"-s",
|
|
93
|
+
"--services",
|
|
94
|
+
help="Comma-delimited list of services to reset. By default, the state of all running services is exported.",
|
|
95
|
+
)
|
|
96
|
+
@click.option(
|
|
97
|
+
"-f",
|
|
98
|
+
"--format",
|
|
99
|
+
"format_",
|
|
100
|
+
type=click.Choice(["json"]),
|
|
101
|
+
help="The formatting style for the save command output.",
|
|
102
|
+
)
|
|
103
|
+
@publish_invocation
|
|
104
|
+
def export(destination: str, services: str | None = None, format_: str | None = None) -> None:
|
|
105
|
+
_assert_host_reachable()
|
|
106
|
+
|
|
107
|
+
client = StateService()
|
|
108
|
+
try:
|
|
109
|
+
services = [x.strip() for x in services.split(",") if x] if services else None
|
|
110
|
+
pod_info = client.export_pod(target=destination, services=services)
|
|
111
|
+
if format_ == "json":
|
|
112
|
+
console.print_json(json.dumps(pod_info))
|
|
113
|
+
else:
|
|
114
|
+
console.print(f"LocalStack state successfully exported to: {destination} ✅")
|
|
115
|
+
return
|
|
116
|
+
except Exception as e:
|
|
117
|
+
raise CLIError(f"Failed to export LocalStack state - {e}") from e
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@state.command(
|
|
121
|
+
name="import",
|
|
122
|
+
short_help="Import the state of LocalStack services",
|
|
123
|
+
help="""
|
|
124
|
+
Load the state of LocalStack from a file into the running container.
|
|
125
|
+
The SOURCE argument is the absolute or relative path to the file containing the state to import.
|
|
126
|
+
This file must have been generated from a previous `localstack state export` command.
|
|
127
|
+
Please be aware that it might not be possible to import a state generated from a different version of LocalStack.
|
|
128
|
+
|
|
129
|
+
\b
|
|
130
|
+
Examples:
|
|
131
|
+
localstack state import my-state
|
|
132
|
+
localstack state import ../parent-dir/my-state
|
|
133
|
+
localstack state import /home/johndoe/my-state
|
|
134
|
+
""",
|
|
135
|
+
)
|
|
136
|
+
@click.argument("source", type=click.Path(exists=True, resolve_path=True))
|
|
137
|
+
@publish_invocation
|
|
138
|
+
def import_state(source: str) -> None:
|
|
139
|
+
_assert_host_reachable()
|
|
140
|
+
|
|
141
|
+
client = StateService()
|
|
142
|
+
try:
|
|
143
|
+
client.import_pod(source=source, show_progress=True)
|
|
144
|
+
console.print(f"LocalStack state successfully imported from {source} ✅")
|
|
145
|
+
except Exception as e:
|
|
146
|
+
raise CLIError(f"Failed to import LocalStack state - {e}") from e
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@state.command(
|
|
150
|
+
name="inspect",
|
|
151
|
+
short_help="Inspect the state of LocalStack services",
|
|
152
|
+
help="""
|
|
153
|
+
Inspect the state of the Localstack Container.
|
|
154
|
+
|
|
155
|
+
By default, it starts a curses interface which allows an interactive inspection of the contents of the LocalStack
|
|
156
|
+
running instance.
|
|
157
|
+
""",
|
|
158
|
+
)
|
|
159
|
+
@click.option(
|
|
160
|
+
"-f",
|
|
161
|
+
"--format",
|
|
162
|
+
"format_",
|
|
163
|
+
type=click.Choice(["curses", "rich", "json"]),
|
|
164
|
+
default="curses",
|
|
165
|
+
help="The formatting style for the inspect command output.",
|
|
166
|
+
)
|
|
167
|
+
@publish_invocation
|
|
168
|
+
def inspect_state(format_: str) -> None:
|
|
169
|
+
_assert_host_reachable()
|
|
170
|
+
|
|
171
|
+
client = CloudPodsClient()
|
|
172
|
+
try:
|
|
173
|
+
result = client.get_state_data()
|
|
174
|
+
except Exception:
|
|
175
|
+
raise CLIError("Error occurred while fetching the metamodel")
|
|
176
|
+
|
|
177
|
+
# filter out too noisy/verbose services like CloudWatch
|
|
178
|
+
skipped_services = ["cloudwatch"]
|
|
179
|
+
for account, details in result.items():
|
|
180
|
+
result[account] = {k: v for k, v in details.items() if k not in skipped_services}
|
|
181
|
+
|
|
182
|
+
# render tree with given format
|
|
183
|
+
TreeRenderer.get(format_).render_tree(result, "LocalStack State")
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Utilities to display trees in the terminal (e.g., interactive using `curses`, via `rich`, or as JSON)"""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from abc import ABC
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from localstack_cli.utils.objects import SubtypesInstanceManager
|
|
11
|
+
|
|
12
|
+
# constants
|
|
13
|
+
ESC = 27
|
|
14
|
+
INDENTATION = 2
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TreeRenderer(SubtypesInstanceManager, ABC):
|
|
18
|
+
"""Abstract base class for rendering trees (e.g., cloud pod contents) on the terminal."""
|
|
19
|
+
|
|
20
|
+
def render_tree(self, tree: dict[str, Any], tree_name: str):
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TreeRendererRich(TreeRenderer):
|
|
25
|
+
@staticmethod
|
|
26
|
+
def impl_name() -> str:
|
|
27
|
+
return "rich"
|
|
28
|
+
|
|
29
|
+
def render_tree(self, tree: dict[str, Any], tree_name: str):
|
|
30
|
+
from rich import print
|
|
31
|
+
from rich.tree import Tree
|
|
32
|
+
|
|
33
|
+
def _convert(obj, parent):
|
|
34
|
+
if isinstance(obj, list):
|
|
35
|
+
for idx, o in enumerate(obj):
|
|
36
|
+
subtree = Tree(str(idx))
|
|
37
|
+
parent.add(_convert(o, subtree))
|
|
38
|
+
return parent
|
|
39
|
+
elif isinstance(obj, dict):
|
|
40
|
+
for k, v in obj.items():
|
|
41
|
+
if isinstance(v, (dict, list)):
|
|
42
|
+
if not v:
|
|
43
|
+
return Tree(f"{k} = {v}")
|
|
44
|
+
subtree = Tree(str(k))
|
|
45
|
+
_convert(v, subtree)
|
|
46
|
+
parent.add(subtree)
|
|
47
|
+
else:
|
|
48
|
+
parent.add(Tree(f"{k} = {v}"))
|
|
49
|
+
return parent
|
|
50
|
+
return Tree(str(obj))
|
|
51
|
+
|
|
52
|
+
tree_obj = Tree(tree_name)
|
|
53
|
+
_convert(tree, tree_obj)
|
|
54
|
+
print(tree_obj)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Tree:
|
|
58
|
+
def __init__(self, name, obj):
|
|
59
|
+
self.name = name
|
|
60
|
+
self.object = obj
|
|
61
|
+
self.expanded = True
|
|
62
|
+
|
|
63
|
+
def render(self, depth, width):
|
|
64
|
+
padding = " " * INDENTATION * depth
|
|
65
|
+
return self.pad(f"{padding}{self.icon()} {self.name}", width)
|
|
66
|
+
|
|
67
|
+
@functools.cache
|
|
68
|
+
def children(self):
|
|
69
|
+
def _name(key, value):
|
|
70
|
+
if isinstance(value, (list, dict)):
|
|
71
|
+
return key
|
|
72
|
+
return f"{value}" if isinstance(key, int) else f"{key} = {value}"
|
|
73
|
+
|
|
74
|
+
if isinstance(self.object, dict):
|
|
75
|
+
return [Tree(_name(k, v), v) for k, v in self.object.items()]
|
|
76
|
+
if isinstance(self.object, list):
|
|
77
|
+
return [Tree(_name(idx, v), v) for idx, v in enumerate(self.object)]
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
def icon(self):
|
|
81
|
+
if self.children() and not self.expanded:
|
|
82
|
+
return "+"
|
|
83
|
+
return "-"
|
|
84
|
+
|
|
85
|
+
def expand(self):
|
|
86
|
+
self.expanded = True
|
|
87
|
+
|
|
88
|
+
def collapse(self):
|
|
89
|
+
self.expanded = False
|
|
90
|
+
|
|
91
|
+
def toggle(self):
|
|
92
|
+
self.expanded = not self.expanded
|
|
93
|
+
|
|
94
|
+
def traverse(self):
|
|
95
|
+
yield self, 0
|
|
96
|
+
if not self.expanded:
|
|
97
|
+
return
|
|
98
|
+
for _child in self.children():
|
|
99
|
+
for child, depth in _child.traverse():
|
|
100
|
+
yield child, depth + 1
|
|
101
|
+
|
|
102
|
+
def pad(self, data, width):
|
|
103
|
+
return data + " " * (width - len(data))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class TreeRendererCurses(TreeRenderer):
|
|
107
|
+
"""
|
|
108
|
+
Renders an interactive tree in the terminal via the curses library.
|
|
109
|
+
Loosely based on https://github.com/mcchae/treesel
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
# file logger, lazily initialized
|
|
113
|
+
LOG = None
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def impl_name() -> str:
|
|
117
|
+
return "curses"
|
|
118
|
+
|
|
119
|
+
def render_tree(self, dict_obj: dict, tree_name: str):
|
|
120
|
+
from curses import wrapper # note: keep here to avoid import issues on some systems
|
|
121
|
+
|
|
122
|
+
saved_fds = (os.dup(0), os.dup(1))
|
|
123
|
+
|
|
124
|
+
def curses_main_wrapper(_tree: Tree):
|
|
125
|
+
def _main(win):
|
|
126
|
+
return self.curses_main(win, _tree)
|
|
127
|
+
|
|
128
|
+
return _main
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
saved_fds = self.open_tty()
|
|
132
|
+
tree = Tree(tree_name, dict_obj)
|
|
133
|
+
wrapper(curses_main_wrapper(tree))
|
|
134
|
+
finally:
|
|
135
|
+
os.close(0)
|
|
136
|
+
os.close(1)
|
|
137
|
+
os.dup(saved_fds[0])
|
|
138
|
+
os.dup(saved_fds[1])
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def curses_main(win, tree: Tree):
|
|
142
|
+
"""
|
|
143
|
+
Curses main loop that performs the rendering.
|
|
144
|
+
:param win: the `curses.window` instance to render on
|
|
145
|
+
:param tree: the tree structure to render
|
|
146
|
+
"""
|
|
147
|
+
import curses # note: keep here to avoid import issues on some systems
|
|
148
|
+
|
|
149
|
+
# initialize window
|
|
150
|
+
win.clear()
|
|
151
|
+
win.refresh()
|
|
152
|
+
curses.nl()
|
|
153
|
+
curses.noecho()
|
|
154
|
+
win.timeout(0)
|
|
155
|
+
win.nodelay(False)
|
|
156
|
+
tree.expand()
|
|
157
|
+
selected_line = 3
|
|
158
|
+
pending_action = None
|
|
159
|
+
|
|
160
|
+
# set up default terminal colors
|
|
161
|
+
curses.use_default_colors()
|
|
162
|
+
|
|
163
|
+
# main render loop
|
|
164
|
+
while True:
|
|
165
|
+
win.clear()
|
|
166
|
+
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
|
|
167
|
+
line = 0
|
|
168
|
+
offset = max(0, selected_line - curses.LINES + 3)
|
|
169
|
+
for data, depth in tree.traverse():
|
|
170
|
+
if line == selected_line:
|
|
171
|
+
win.attrset(curses.color_pair(1) | curses.A_BOLD)
|
|
172
|
+
if pending_action:
|
|
173
|
+
getattr(data, pending_action)()
|
|
174
|
+
pending_action = None
|
|
175
|
+
else:
|
|
176
|
+
win.attrset(curses.color_pair(0))
|
|
177
|
+
if 0 <= line - offset < curses.LINES - 1:
|
|
178
|
+
win.addstr(line - offset, 0, data.render(depth, curses.COLS))
|
|
179
|
+
line += 1
|
|
180
|
+
win.refresh()
|
|
181
|
+
ch = win.getch()
|
|
182
|
+
if ch == curses.KEY_UP:
|
|
183
|
+
selected_line -= 1
|
|
184
|
+
elif ch == curses.KEY_DOWN:
|
|
185
|
+
selected_line += 1
|
|
186
|
+
elif ch == curses.KEY_PPAGE:
|
|
187
|
+
selected_line -= curses.LINES
|
|
188
|
+
if selected_line < 0:
|
|
189
|
+
selected_line = 0
|
|
190
|
+
elif ch == curses.KEY_NPAGE:
|
|
191
|
+
selected_line += curses.LINES
|
|
192
|
+
if selected_line >= line:
|
|
193
|
+
selected_line = line - 1
|
|
194
|
+
elif ch == curses.KEY_RIGHT:
|
|
195
|
+
pending_action = "expand"
|
|
196
|
+
elif ch == curses.KEY_LEFT:
|
|
197
|
+
pending_action = "collapse"
|
|
198
|
+
elif ch == ord(" "):
|
|
199
|
+
pending_action = "toggle"
|
|
200
|
+
elif ch == ESC:
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
selected_line %= line
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def open_tty():
|
|
207
|
+
saved_stdin = os.dup(0)
|
|
208
|
+
saved_stdout = os.dup(1)
|
|
209
|
+
os.close(0)
|
|
210
|
+
os.close(1)
|
|
211
|
+
os.open("/dev/tty", os.O_RDONLY)
|
|
212
|
+
os.open("/dev/tty", os.O_RDWR)
|
|
213
|
+
return saved_stdin, saved_stdout
|
|
214
|
+
|
|
215
|
+
@classmethod
|
|
216
|
+
def log(cls, *args, **kwargs):
|
|
217
|
+
"""Logger that can be used to log debug output from curses program"""
|
|
218
|
+
if cls.LOG:
|
|
219
|
+
LOG = logging.getLogger(__file__)
|
|
220
|
+
handler = logging.FileHandler("cloud_pods_viewer.log")
|
|
221
|
+
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
|
222
|
+
handler.setFormatter(formatter)
|
|
223
|
+
LOG.addHandler(handler)
|
|
224
|
+
LOG.setLevel(logging.INFO)
|
|
225
|
+
cls.LOG.info(*args, **kwargs)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class TreeRendererJSON(TreeRenderer):
|
|
229
|
+
@staticmethod
|
|
230
|
+
def impl_name() -> str:
|
|
231
|
+
return "json"
|
|
232
|
+
|
|
233
|
+
def render_tree(self, dict_obj: dict, tree_name: str):
|
|
234
|
+
# simply print out the JSON with indentation
|
|
235
|
+
print(json.dumps(dict_obj, indent=4))
|