Files
Jean-Pierre MartineauandClaude Opus 5.5 6b90e0505b Docstrings and comments of meshterm/tools are in ASD-STE100
The same rewrite as for the core package. No help= string, epilog, or
other UX text changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 03:48:44 -04:00

121 lines
4.6 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""The ``map`` tool: plot the located nodes of the mesh on an OpenStreetMap terminal map.
The tool collects the nodes that share their location in adverts from the passive-monitor
history (:meth:`~meshterm.persistence.repository.Repository.heard_nodes`). It adds our node
when its position is known. Then it draws all the nodes over a real street basemap,
rendered as Unicode braille (streets, rivers, place names). This is the same terminal-map
idea as ``mapscii``.
Menu only: the tool opens a full-screen map that the user can pan and zoom (refer to
:mod:`meshterm.ui.map_screen`). It registers no CLI subcommand, because a map is a picture,
and the scripted CLI gives facts (refer to :meth:`MapTool.register_cli`). Repeaters have
priority over ordinary nodes: they have a different marker, drawn on top. The basemap is a
best effort. With no network (and no cached tiles), the tool plots the nodes on a blank
grid, and it still works offline.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
import typer
from ..context import AppContext
from ..services.markers import gather_markers
from .base import Tool, ToolResult, register
def _fraction(ctx: AppContext, params: dict[str, Any]) -> float:
"""The fraction of the mesh that a new map frames: the flag if given, else the preference.
``--fraction`` is resolved here, not as the default of the Typer option, because the
options are declared when the CLI is registered. That occurs before a context exists,
and thus before MeshTerm reads the preferences file.
Args:
ctx: The shared application context.
params: The parameters of this run.
Returns:
The fraction to frame.
"""
fraction = params.get("fraction")
return ctx.preferences.map_view_fraction if fraction is None else float(fraction)
if TYPE_CHECKING:
from ..core.models import MapMarker
@register
class MapTool(Tool):
"""Show the nodes of the mesh that share a location on a braille OpenStreetMap map."""
name = "map"
title = "Map"
icon = "🌍"
help = "Mesh nodes on a pannable street map"
category = "Explore"
order = 10
async def run(self, ctx: AppContext, params: dict[str, Any]) -> ToolResult:
"""Collect the located nodes and open the interactive map.
The tool is for the menu only (refer to :meth:`register_cli`), so this method has
one path.
Args:
ctx: The shared application context.
params: An optional ``fraction``: the part of the mesh to frame when the map
opens.
Returns:
A :class:`ToolResult` with the number of plotted nodes.
"""
from ..ui.map_screen import open_map
markers = await self._gather(ctx)
if not markers:
ctx.ui.note(
"[muted]no contacts or heard nodes have shared a location yet — a node "
"appears here once it advertises coordinates[/muted]"
)
return ToolResult(summary={"located": 0})
await open_map(ctx, markers, fraction=_fraction(ctx, params))
repeaters = sum(1 for m in markers if m.is_repeater and not m.is_self)
self_located = any(m.is_self for m in markers)
return ToolResult(
summary={
"located": len(markers),
"repeaters": repeaters,
"nodes": len(markers) - repeaters - (1 if self_located else 0),
"self_located": self_located,
},
)
# -- the markers ------------------------------------------------------------
async def _gather(self, ctx: AppContext) -> list[MapMarker]:
"""Collect each located node to plot (refer to :func:`gather_markers`)."""
return await gather_markers(ctx)
def register_cli(self, app: typer.Typer) -> None:
"""Register no CLI command, because a map is a picture, and pictures are menu only.
Each other feature has a scripted face, because its answer is a set of facts that
a script can use. The answer of a map is a drawing. Its braille cells get their
meaning from their position on a grid, and colour tells its nodes apart. The
scripted CLI does not have these (refer to :mod:`meshterm.ui.script`). A one-shot
render on the CLI is a block of glyphs that no program can parse. If the render
has no colour, as the rest of the CLI, a person cannot read it either.
A script can still get the located nodes: ``meshterm contacts`` lists each of
them, with their coordinates.
Args:
app: The Typer application (not changed).
"""