nds-mapviewer 2026.2.1.dev92__tar.gz → 2026.2.1.dev93__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.
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/CHANGELOG.md +16 -0
- {nds_mapviewer-2026.2.1.dev92/src/nds_mapviewer.egg-info → nds_mapviewer-2026.2.1.dev93}/PKG-INFO +25 -1
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/README.md +24 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93/src/nds_mapviewer.egg-info}/PKG-INFO +25 -1
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/nds_mapviewer.egg-info/SOURCES.txt +3 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/cli.py +65 -0
- nds_mapviewer-2026.2.1.dev93/src/ndslive/mapviewer/clipboard.py +81 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/docker_client.py +36 -14
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/tui.py +110 -13
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/viewer.py +43 -4
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/tests/test_cli.py +53 -0
- nds_mapviewer-2026.2.1.dev93/tests/test_clipboard.py +76 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/tests/test_docker_client.py +57 -0
- nds_mapviewer-2026.2.1.dev93/tests/test_tui.py +67 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/tests/test_viewer.py +179 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/.gitignore +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/pyproject.toml +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/setup.cfg +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/setup.py +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/nds_mapviewer.egg-info/dependency_links.txt +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/nds_mapviewer.egg-info/entry_points.txt +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/nds_mapviewer.egg-info/requires.txt +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/nds_mapviewer.egg-info/top_level.txt +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/__init__.py +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/browser.py +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/config.py +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/container_runtime.py +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/exceptions.py +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/ui.py +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/tests/conftest.py +0 -0
- {nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/tests/test_config.py +0 -0
|
@@ -9,12 +9,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- `--debug` flag to enable container debug logging (sets `HTTP_LOG_LEVEL=debug`)
|
|
13
|
+
- `--env KEY=VALUE` (repeatable, short `-E`) to pass custom environment
|
|
14
|
+
variables into the container; overrides `--debug` on key collision
|
|
15
|
+
- `--network NAME` to attach the container to a specific network (e.g. `host`);
|
|
16
|
+
in `host` mode port publishing is skipped and the viewer is reached on the
|
|
17
|
+
container's internal port
|
|
18
|
+
- Run screen now shows a config line below the info banner with the source
|
|
19
|
+
(config file or directly-passed data-source path, e.g. a `*.ndslive`
|
|
20
|
+
filestore) and the mapped config mounted into the container
|
|
12
21
|
- Default Docker image version pinned to package (`--image-version` defaults to
|
|
13
22
|
matching calver instead of `"latest"`)
|
|
14
23
|
- SCM-based versioning — package version derived from MapViewer git tags
|
|
15
24
|
|
|
16
25
|
### Fixed
|
|
17
26
|
|
|
27
|
+
- Clipboard copy in the Docker screen now uses the native OS clipboard
|
|
28
|
+
(`clip.exe` / `pbcopy` / `wl-copy`/`xclip`/`xsel`) instead of relying on the
|
|
29
|
+
OSC 52 terminal escape, which is unreliable on Windows; warns when a copy
|
|
30
|
+
cannot be confirmed
|
|
31
|
+
- Quitting the TUI no longer hangs — the log-follow stream is torn down by
|
|
32
|
+
terminating the process before closing pipes, and container shutdown is
|
|
33
|
+
bounded so the terminal is always returned to the user
|
|
18
34
|
- Container readiness check uses log sentinel before HTTP polling to avoid
|
|
19
35
|
startup crash (mapget#151)
|
|
20
36
|
|
{nds_mapviewer-2026.2.1.dev92/src/nds_mapviewer.egg-info → nds_mapviewer-2026.2.1.dev93}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nds-mapviewer
|
|
3
|
-
Version: 2026.2.1.
|
|
3
|
+
Version: 2026.2.1.dev93
|
|
4
4
|
Summary: CLI to run the NDS MapViewer Docker container for visualizing map data (NDS.Live, GeoJSON, and more)
|
|
5
5
|
Author-email: NDS Association <support@nds-association.org>
|
|
6
6
|
License: Proprietary
|
|
@@ -78,10 +78,30 @@ mapviewer --demo # Try it with NDS Islands sample data
|
|
|
78
78
|
|
|
79
79
|
mapviewer mywork # Load a saved config by name
|
|
80
80
|
mapviewer /path/a.ndslive /path/b # Multiple sources at once
|
|
81
|
+
|
|
82
|
+
mapviewer --demo --debug # Enable debug logging in the container
|
|
83
|
+
mapviewer --demo --env KEY=VALUE # Set a custom container env var (repeatable)
|
|
84
|
+
mapviewer --demo --network host # Run the container on the host network
|
|
81
85
|
```
|
|
82
86
|
|
|
83
87
|
All commands accept `-e community|member` and `-i VERSION`.
|
|
84
88
|
|
|
89
|
+
### Container environment variables
|
|
90
|
+
|
|
91
|
+
`--debug` turns on verbose logging in the MapViewer container (shorthand for
|
|
92
|
+
`--env HTTP_LOG_LEVEL=debug`). Use `--env KEY=VALUE` (repeatable, short form
|
|
93
|
+
`-E`) to pass arbitrary environment variables into the container; explicit
|
|
94
|
+
`--env` entries override `--debug` on key collision. The resulting `-e` flags
|
|
95
|
+
appear in the Docker screen's "Copy Command" output.
|
|
96
|
+
|
|
97
|
+
### Container network
|
|
98
|
+
|
|
99
|
+
`--network NAME` attaches the container to a specific network (e.g. a custom
|
|
100
|
+
bridge or `host`). With `--network host` the container binds the host's port
|
|
101
|
+
directly, so `--port`/`--bind` are ignored and the MapViewer is reached on its
|
|
102
|
+
internal port (8089). Host networking behaves differently across platforms and
|
|
103
|
+
runtimes — it is intended as a connectivity workaround.
|
|
104
|
+
|
|
85
105
|
If `~/.nds/config.yaml` exists, `mapviewer` (with no arguments) loads it
|
|
86
106
|
automatically and goes straight to the RunScreen.
|
|
87
107
|
|
|
@@ -89,6 +109,10 @@ If you edit `~/.nds/config.yaml` manually, use host paths there, not container
|
|
|
89
109
|
paths. The CLI translates them into the correct container mounts when it starts
|
|
90
110
|
the container runtime.
|
|
91
111
|
|
|
112
|
+
The run screen shows, below the info banner, the source you passed (a config
|
|
113
|
+
file or a directly-given data-source path such as a `*.ndslive` filestore) and
|
|
114
|
+
the mapped config that was generated and mounted into the container.
|
|
115
|
+
|
|
92
116
|
## Editions
|
|
93
117
|
|
|
94
118
|
| Edition | Formats | Access |
|
|
@@ -45,10 +45,30 @@ mapviewer --demo # Try it with NDS Islands sample data
|
|
|
45
45
|
|
|
46
46
|
mapviewer mywork # Load a saved config by name
|
|
47
47
|
mapviewer /path/a.ndslive /path/b # Multiple sources at once
|
|
48
|
+
|
|
49
|
+
mapviewer --demo --debug # Enable debug logging in the container
|
|
50
|
+
mapviewer --demo --env KEY=VALUE # Set a custom container env var (repeatable)
|
|
51
|
+
mapviewer --demo --network host # Run the container on the host network
|
|
48
52
|
```
|
|
49
53
|
|
|
50
54
|
All commands accept `-e community|member` and `-i VERSION`.
|
|
51
55
|
|
|
56
|
+
### Container environment variables
|
|
57
|
+
|
|
58
|
+
`--debug` turns on verbose logging in the MapViewer container (shorthand for
|
|
59
|
+
`--env HTTP_LOG_LEVEL=debug`). Use `--env KEY=VALUE` (repeatable, short form
|
|
60
|
+
`-E`) to pass arbitrary environment variables into the container; explicit
|
|
61
|
+
`--env` entries override `--debug` on key collision. The resulting `-e` flags
|
|
62
|
+
appear in the Docker screen's "Copy Command" output.
|
|
63
|
+
|
|
64
|
+
### Container network
|
|
65
|
+
|
|
66
|
+
`--network NAME` attaches the container to a specific network (e.g. a custom
|
|
67
|
+
bridge or `host`). With `--network host` the container binds the host's port
|
|
68
|
+
directly, so `--port`/`--bind` are ignored and the MapViewer is reached on its
|
|
69
|
+
internal port (8089). Host networking behaves differently across platforms and
|
|
70
|
+
runtimes — it is intended as a connectivity workaround.
|
|
71
|
+
|
|
52
72
|
If `~/.nds/config.yaml` exists, `mapviewer` (with no arguments) loads it
|
|
53
73
|
automatically and goes straight to the RunScreen.
|
|
54
74
|
|
|
@@ -56,6 +76,10 @@ If you edit `~/.nds/config.yaml` manually, use host paths there, not container
|
|
|
56
76
|
paths. The CLI translates them into the correct container mounts when it starts
|
|
57
77
|
the container runtime.
|
|
58
78
|
|
|
79
|
+
The run screen shows, below the info banner, the source you passed (a config
|
|
80
|
+
file or a directly-given data-source path such as a `*.ndslive` filestore) and
|
|
81
|
+
the mapped config that was generated and mounted into the container.
|
|
82
|
+
|
|
59
83
|
## Editions
|
|
60
84
|
|
|
61
85
|
| Edition | Formats | Access |
|
{nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93/src/nds_mapviewer.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nds-mapviewer
|
|
3
|
-
Version: 2026.2.1.
|
|
3
|
+
Version: 2026.2.1.dev93
|
|
4
4
|
Summary: CLI to run the NDS MapViewer Docker container for visualizing map data (NDS.Live, GeoJSON, and more)
|
|
5
5
|
Author-email: NDS Association <support@nds-association.org>
|
|
6
6
|
License: Proprietary
|
|
@@ -78,10 +78,30 @@ mapviewer --demo # Try it with NDS Islands sample data
|
|
|
78
78
|
|
|
79
79
|
mapviewer mywork # Load a saved config by name
|
|
80
80
|
mapviewer /path/a.ndslive /path/b # Multiple sources at once
|
|
81
|
+
|
|
82
|
+
mapviewer --demo --debug # Enable debug logging in the container
|
|
83
|
+
mapviewer --demo --env KEY=VALUE # Set a custom container env var (repeatable)
|
|
84
|
+
mapviewer --demo --network host # Run the container on the host network
|
|
81
85
|
```
|
|
82
86
|
|
|
83
87
|
All commands accept `-e community|member` and `-i VERSION`.
|
|
84
88
|
|
|
89
|
+
### Container environment variables
|
|
90
|
+
|
|
91
|
+
`--debug` turns on verbose logging in the MapViewer container (shorthand for
|
|
92
|
+
`--env HTTP_LOG_LEVEL=debug`). Use `--env KEY=VALUE` (repeatable, short form
|
|
93
|
+
`-E`) to pass arbitrary environment variables into the container; explicit
|
|
94
|
+
`--env` entries override `--debug` on key collision. The resulting `-e` flags
|
|
95
|
+
appear in the Docker screen's "Copy Command" output.
|
|
96
|
+
|
|
97
|
+
### Container network
|
|
98
|
+
|
|
99
|
+
`--network NAME` attaches the container to a specific network (e.g. a custom
|
|
100
|
+
bridge or `host`). With `--network host` the container binds the host's port
|
|
101
|
+
directly, so `--port`/`--bind` are ignored and the MapViewer is reached on its
|
|
102
|
+
internal port (8089). Host networking behaves differently across platforms and
|
|
103
|
+
runtimes — it is intended as a connectivity workaround.
|
|
104
|
+
|
|
85
105
|
If `~/.nds/config.yaml` exists, `mapviewer` (with no arguments) loads it
|
|
86
106
|
automatically and goes straight to the RunScreen.
|
|
87
107
|
|
|
@@ -89,6 +109,10 @@ If you edit `~/.nds/config.yaml` manually, use host paths there, not container
|
|
|
89
109
|
paths. The CLI translates them into the correct container mounts when it starts
|
|
90
110
|
the container runtime.
|
|
91
111
|
|
|
112
|
+
The run screen shows, below the info banner, the source you passed (a config
|
|
113
|
+
file or a directly-given data-source path such as a `*.ndslive` filestore) and
|
|
114
|
+
the mapped config that was generated and mounted into the container.
|
|
115
|
+
|
|
92
116
|
## Editions
|
|
93
117
|
|
|
94
118
|
| Edition | Formats | Access |
|
{nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/nds_mapviewer.egg-info/SOURCES.txt
RENAMED
|
@@ -12,6 +12,7 @@ src/nds_mapviewer.egg-info/top_level.txt
|
|
|
12
12
|
src/ndslive/mapviewer/__init__.py
|
|
13
13
|
src/ndslive/mapviewer/browser.py
|
|
14
14
|
src/ndslive/mapviewer/cli.py
|
|
15
|
+
src/ndslive/mapviewer/clipboard.py
|
|
15
16
|
src/ndslive/mapviewer/config.py
|
|
16
17
|
src/ndslive/mapviewer/container_runtime.py
|
|
17
18
|
src/ndslive/mapviewer/docker_client.py
|
|
@@ -21,6 +22,8 @@ src/ndslive/mapviewer/ui.py
|
|
|
21
22
|
src/ndslive/mapviewer/viewer.py
|
|
22
23
|
tests/conftest.py
|
|
23
24
|
tests/test_cli.py
|
|
25
|
+
tests/test_clipboard.py
|
|
24
26
|
tests/test_config.py
|
|
25
27
|
tests/test_docker_client.py
|
|
28
|
+
tests/test_tui.py
|
|
26
29
|
tests/test_viewer.py
|
|
@@ -73,6 +73,37 @@ def _get_pull_policy_default() -> str:
|
|
|
73
73
|
return "prompt"
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
def _build_environment(args: argparse.Namespace) -> dict[str, str]:
|
|
77
|
+
"""Build the container environment dict from ``--debug`` and ``--env``.
|
|
78
|
+
|
|
79
|
+
``--debug`` is shorthand for ``HTTP_LOG_LEVEL=debug``. Explicit ``--env``
|
|
80
|
+
entries are applied afterwards, so they win on key collision.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
SystemExit: If an ``--env`` entry is not in ``KEY=VALUE`` form.
|
|
84
|
+
"""
|
|
85
|
+
env: dict[str, str] = {}
|
|
86
|
+
if getattr(args, "debug", False):
|
|
87
|
+
env["HTTP_LOG_LEVEL"] = "debug"
|
|
88
|
+
for item in getattr(args, "env", None) or []:
|
|
89
|
+
if "=" not in item:
|
|
90
|
+
console.print(
|
|
91
|
+
f"[red]Error:[/red] Invalid --env value '{item}', "
|
|
92
|
+
"expected KEY=VALUE"
|
|
93
|
+
)
|
|
94
|
+
raise SystemExit(2)
|
|
95
|
+
key, _, value = item.partition("=")
|
|
96
|
+
key = key.strip()
|
|
97
|
+
if not key:
|
|
98
|
+
console.print(
|
|
99
|
+
f"[red]Error:[/red] Invalid --env value '{item}', "
|
|
100
|
+
"empty key"
|
|
101
|
+
)
|
|
102
|
+
raise SystemExit(2)
|
|
103
|
+
env[key] = value
|
|
104
|
+
return env
|
|
105
|
+
|
|
106
|
+
|
|
76
107
|
# ---------------------------------------------------------------------------
|
|
77
108
|
# Command handler
|
|
78
109
|
# ---------------------------------------------------------------------------
|
|
@@ -97,6 +128,8 @@ def cmd_main(args: argparse.Namespace) -> int:
|
|
|
97
128
|
bind_address = args.bind
|
|
98
129
|
pull_policy = args.pull_policy
|
|
99
130
|
source_args = args.sources
|
|
131
|
+
environment = _build_environment(args)
|
|
132
|
+
network = getattr(args, "network", None)
|
|
100
133
|
|
|
101
134
|
# --demo → demo config
|
|
102
135
|
if args.demo:
|
|
@@ -108,6 +141,8 @@ def cmd_main(args: argparse.Namespace) -> int:
|
|
|
108
141
|
port=port,
|
|
109
142
|
bind_address=bind_address,
|
|
110
143
|
pull_policy=pull_policy,
|
|
144
|
+
environment=environment,
|
|
145
|
+
network=network,
|
|
111
146
|
run_config=config,
|
|
112
147
|
run_label="Demo (NDS Islands)",
|
|
113
148
|
is_demo=True,
|
|
@@ -128,6 +163,8 @@ def cmd_main(args: argparse.Namespace) -> int:
|
|
|
128
163
|
port=port,
|
|
129
164
|
bind_address=bind_address,
|
|
130
165
|
pull_policy=pull_policy,
|
|
166
|
+
environment=environment,
|
|
167
|
+
network=network,
|
|
131
168
|
run_config=config,
|
|
132
169
|
run_config_path=DEFAULT_CONFIG,
|
|
133
170
|
run_label="~/.nds/config.yaml",
|
|
@@ -151,6 +188,8 @@ def cmd_main(args: argparse.Namespace) -> int:
|
|
|
151
188
|
port=port,
|
|
152
189
|
bind_address=bind_address,
|
|
153
190
|
pull_policy=pull_policy,
|
|
191
|
+
environment=environment,
|
|
192
|
+
network=network,
|
|
154
193
|
run_config=config,
|
|
155
194
|
run_config_path=LEGACY_CONFIG,
|
|
156
195
|
run_label="~/.nds/mapviewer.yaml",
|
|
@@ -167,6 +206,8 @@ def cmd_main(args: argparse.Namespace) -> int:
|
|
|
167
206
|
port=port,
|
|
168
207
|
bind_address=bind_address,
|
|
169
208
|
pull_policy=pull_policy,
|
|
209
|
+
environment=environment,
|
|
210
|
+
network=network,
|
|
170
211
|
)
|
|
171
212
|
app.run()
|
|
172
213
|
return 0
|
|
@@ -240,9 +281,12 @@ def cmd_main(args: argparse.Namespace) -> int:
|
|
|
240
281
|
port=port,
|
|
241
282
|
bind_address=bind_address,
|
|
242
283
|
pull_policy=pull_policy,
|
|
284
|
+
environment=environment,
|
|
285
|
+
network=network,
|
|
243
286
|
run_config=merged,
|
|
244
287
|
run_config_path=source_config_path,
|
|
245
288
|
run_label=source_desc,
|
|
289
|
+
run_source_paths=source_args,
|
|
246
290
|
)
|
|
247
291
|
app.run()
|
|
248
292
|
return 0
|
|
@@ -323,6 +367,27 @@ Examples:
|
|
|
323
367
|
"--bind", default="127.0.0.1",
|
|
324
368
|
help="Host address to bind (default: 127.0.0.1, use 0.0.0.0 for network access)",
|
|
325
369
|
)
|
|
370
|
+
parser.add_argument(
|
|
371
|
+
"--env", "-E",
|
|
372
|
+
action="append",
|
|
373
|
+
metavar="KEY=VALUE",
|
|
374
|
+
dest="env",
|
|
375
|
+
default=[],
|
|
376
|
+
help="Set a container environment variable (repeatable), "
|
|
377
|
+
"e.g. --env HTTP_LOG_LEVEL=debug",
|
|
378
|
+
)
|
|
379
|
+
parser.add_argument(
|
|
380
|
+
"--debug",
|
|
381
|
+
action="store_true",
|
|
382
|
+
help="Enable debug logging in the container (sets HTTP_LOG_LEVEL=debug)",
|
|
383
|
+
)
|
|
384
|
+
parser.add_argument(
|
|
385
|
+
"--network",
|
|
386
|
+
metavar="NAME",
|
|
387
|
+
default=None,
|
|
388
|
+
help="Container network to attach (e.g. 'host'). With 'host' the "
|
|
389
|
+
"MapViewer binds the host port directly and --port/--bind are ignored.",
|
|
390
|
+
)
|
|
326
391
|
|
|
327
392
|
pull_group = parser.add_mutually_exclusive_group()
|
|
328
393
|
pull_group.add_argument(
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Cross-platform clipboard helper.
|
|
2
|
+
|
|
3
|
+
Textual's ``App.copy_to_clipboard`` relies on the OSC 52 terminal escape
|
|
4
|
+
sequence, which many Windows terminals (conhost, some Windows Terminal
|
|
5
|
+
configs, PowerShell hosts) silently drop — so the copy buttons appear to do
|
|
6
|
+
nothing. This module copies via the native OS clipboard utility first and
|
|
7
|
+
only falls back to OSC 52 when no native tool is available.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import platform
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _run_copy(cmd: list[str], text: str) -> bool:
|
|
18
|
+
"""Pipe *text* into *cmd* on stdin; return True on success."""
|
|
19
|
+
try:
|
|
20
|
+
proc = subprocess.run(
|
|
21
|
+
cmd,
|
|
22
|
+
input=text,
|
|
23
|
+
text=True,
|
|
24
|
+
capture_output=True,
|
|
25
|
+
timeout=5,
|
|
26
|
+
)
|
|
27
|
+
return proc.returncode == 0
|
|
28
|
+
except (OSError, subprocess.SubprocessError):
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _native_copy(text: str) -> bool:
|
|
33
|
+
"""Copy *text* using the platform-native clipboard tool.
|
|
34
|
+
|
|
35
|
+
Returns True if a tool was found and reported success.
|
|
36
|
+
"""
|
|
37
|
+
system = platform.system()
|
|
38
|
+
|
|
39
|
+
if system == "Windows":
|
|
40
|
+
# clip.exe ships with every supported Windows version.
|
|
41
|
+
clip = shutil.which("clip") or shutil.which("clip.exe")
|
|
42
|
+
if clip:
|
|
43
|
+
return _run_copy([clip], text)
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
if system == "Darwin":
|
|
47
|
+
if shutil.which("pbcopy"):
|
|
48
|
+
return _run_copy(["pbcopy"], text)
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
# Linux / other Unix — try Wayland then X11 helpers.
|
|
52
|
+
if shutil.which("wl-copy"):
|
|
53
|
+
if _run_copy(["wl-copy"], text):
|
|
54
|
+
return True
|
|
55
|
+
if shutil.which("xclip"):
|
|
56
|
+
if _run_copy(["xclip", "-selection", "clipboard"], text):
|
|
57
|
+
return True
|
|
58
|
+
if shutil.which("xsel"):
|
|
59
|
+
if _run_copy(["xsel", "--clipboard", "--input"], text):
|
|
60
|
+
return True
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def copy_text(text: str, app=None) -> bool:
|
|
65
|
+
"""Copy *text* to the clipboard, returning True on confirmed success.
|
|
66
|
+
|
|
67
|
+
Tries the native OS clipboard utility first (reliable on Windows where
|
|
68
|
+
OSC 52 is commonly unsupported). If no native tool is available and an
|
|
69
|
+
*app* is given, falls back to Textual's OSC 52 escape — which cannot be
|
|
70
|
+
confirmed, so this returns False to let callers warn the user.
|
|
71
|
+
"""
|
|
72
|
+
if _native_copy(text):
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
if app is not None:
|
|
76
|
+
try:
|
|
77
|
+
app.copy_to_clipboard(text)
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
return False
|
{nds_mapviewer-2026.2.1.dev92 → nds_mapviewer-2026.2.1.dev93}/src/ndslive/mapviewer/docker_client.py
RENAMED
|
@@ -36,6 +36,9 @@ IMAGE_NAME = "nds-mapviewer"
|
|
|
36
36
|
|
|
37
37
|
KNOWN_TAGS = ["latest", "dev", "dev-rc"]
|
|
38
38
|
|
|
39
|
+
# Port the MapViewer HTTP server listens on inside the container.
|
|
40
|
+
CONTAINER_PORT = 8089
|
|
41
|
+
|
|
39
42
|
|
|
40
43
|
# ---------------------------------------------------------------------------
|
|
41
44
|
# Container info dataclass (replaces Docker SDK Container object)
|
|
@@ -543,6 +546,7 @@ def run_container(
|
|
|
543
546
|
config_dir: str | Path | None = None,
|
|
544
547
|
volumes: list[str] | None = None,
|
|
545
548
|
environment: dict[str, str] | None = None,
|
|
549
|
+
network: str | None = None,
|
|
546
550
|
name: str = CONTAINER_NAME,
|
|
547
551
|
detach: bool = True,
|
|
548
552
|
remove_existing: bool = False,
|
|
@@ -556,6 +560,9 @@ def run_container(
|
|
|
556
560
|
config_dir: Config directory to mount at ``/config``.
|
|
557
561
|
volumes: Additional volume mounts as ``"host:container:mode"`` strings.
|
|
558
562
|
environment: Environment variables.
|
|
563
|
+
network: Container network to attach (e.g. ``"host"``). When ``"host"``,
|
|
564
|
+
port publishing is skipped because the container binds the host's
|
|
565
|
+
port directly.
|
|
559
566
|
name: Container name.
|
|
560
567
|
detach: Run in detached mode.
|
|
561
568
|
remove_existing: Remove existing container if running.
|
|
@@ -571,12 +578,20 @@ def run_container(
|
|
|
571
578
|
rt.run("stop", "-t", "2", name, timeout=30)
|
|
572
579
|
rt.run("rm", name, check=False, timeout=10)
|
|
573
580
|
|
|
581
|
+
use_host_net = network == "host"
|
|
582
|
+
|
|
574
583
|
# Build command
|
|
575
584
|
cmd: list[str] = ["run"]
|
|
576
585
|
if detach:
|
|
577
586
|
cmd.append("-d")
|
|
578
587
|
cmd.extend(["--name", name])
|
|
579
|
-
|
|
588
|
+
if network:
|
|
589
|
+
cmd.extend(["--network", network])
|
|
590
|
+
# Host networking ignores published ports — the container binds the
|
|
591
|
+
# host's port directly — so skip the -p mapping to avoid the runtime
|
|
592
|
+
# warning/error.
|
|
593
|
+
if not use_host_net:
|
|
594
|
+
cmd.extend(["-p", f"{bind_address}:{port}:{CONTAINER_PORT}"])
|
|
580
595
|
|
|
581
596
|
# Volume mounts
|
|
582
597
|
if config_dir:
|
|
@@ -592,8 +607,10 @@ def run_container(
|
|
|
592
607
|
for key, value in environment.items():
|
|
593
608
|
cmd.extend(["-e", f"{key}={value}"])
|
|
594
609
|
|
|
595
|
-
# Extra hosts for host.docker.internal on Linux
|
|
596
|
-
|
|
610
|
+
# Extra hosts for host.docker.internal on Linux. The host-gateway mapping
|
|
611
|
+
# conflicts with host networking, so skip it in that mode.
|
|
612
|
+
if not use_host_net:
|
|
613
|
+
cmd.extend(rt.get_extra_host_args())
|
|
597
614
|
|
|
598
615
|
# Labels
|
|
599
616
|
cmd.extend(["--label", "app=ndslive-mapviewer"])
|
|
@@ -673,6 +690,22 @@ class ContainerLogStream:
|
|
|
673
690
|
def close(self, timeout: float = 3.0) -> None:
|
|
674
691
|
if self._proc.poll() is not None:
|
|
675
692
|
return
|
|
693
|
+
# Terminate the process FIRST so the OS closes the write end of the
|
|
694
|
+
# pipe and any thread blocked in `for line in proc.stdout` unblocks
|
|
695
|
+
# via EOF. Closing the pipe from another thread while it is being
|
|
696
|
+
# read is unsafe in CPython and can leave the reader (and thus app
|
|
697
|
+
# shutdown) hung indefinitely.
|
|
698
|
+
self._proc.terminate()
|
|
699
|
+
try:
|
|
700
|
+
self._proc.wait(timeout=timeout)
|
|
701
|
+
except subprocess.TimeoutExpired:
|
|
702
|
+
self._proc.kill()
|
|
703
|
+
try:
|
|
704
|
+
self._proc.wait(timeout=timeout)
|
|
705
|
+
except subprocess.TimeoutExpired:
|
|
706
|
+
pass
|
|
707
|
+
# Now that the process is gone, closing the pipes is safe and frees
|
|
708
|
+
# the file descriptors.
|
|
676
709
|
for pipe in (getattr(self._proc, "stdout", None), getattr(self._proc, "stderr", None)):
|
|
677
710
|
if pipe is None:
|
|
678
711
|
continue
|
|
@@ -680,17 +713,6 @@ class ContainerLogStream:
|
|
|
680
713
|
pipe.close()
|
|
681
714
|
except Exception:
|
|
682
715
|
pass
|
|
683
|
-
self._proc.terminate()
|
|
684
|
-
try:
|
|
685
|
-
self._proc.wait(timeout=timeout)
|
|
686
|
-
return
|
|
687
|
-
except subprocess.TimeoutExpired:
|
|
688
|
-
pass
|
|
689
|
-
self._proc.kill()
|
|
690
|
-
try:
|
|
691
|
-
self._proc.wait(timeout=timeout)
|
|
692
|
-
except subprocess.TimeoutExpired:
|
|
693
|
-
pass
|
|
694
716
|
|
|
695
717
|
|
|
696
718
|
_READY_SENTINEL = "NDS MapViewer is ready!"
|