Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions meshtastic/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
have_powermon = False
powermon_exception = e
meter = None
from meshtastic.protobuf import admin_pb2, channel_pb2, clientonly_pb2, config_pb2, portnums_pb2, mesh_pb2
from meshtastic.protobuf import admin_pb2, channel_pb2, clientonly_pb2, config_pb2, localonly_pb2, portnums_pb2, mesh_pb2
from meshtastic.version import get_active_version

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -1907,6 +1907,25 @@ def addImportExportArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPar
)
return parser

def _config_field_names() -> List[str]:
"""Return shell-completion candidates derived from config descriptors."""
names = set()
for config in (localonly_pb2.LocalConfig, localonly_pb2.LocalModuleConfig):
for section in config.DESCRIPTOR.fields:
if section.message_type is None:
continue
for field in section.message_type.fields:
snake_name = f"{section.name}.{field.name}"
names.add(snake_name)
names.add(meshtastic.util.snake_to_camel(snake_name))
return sorted(names)


def _complete_config_fields(prefix: str, **_kwargs: object) -> List[str]:
"""Complete static config paths without connecting to a device."""
return [name for name in _config_field_names() if name.startswith(prefix)]


def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"""Add arguments to do with configuring a device"""

Expand All @@ -1915,7 +1934,7 @@ def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
"Arguments that concern general configuration of Meshtastic devices",
)

group.add_argument(
get_action = group.add_argument(
"--get",
help=(
"Get a preferences field. Use an invalid field such as '0' to get a list of all fields."
Expand All @@ -1925,6 +1944,7 @@ def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
action="append",
metavar="FIELD"
)
get_action.completer = _complete_config_fields # type: ignore[attr-defined]

group.add_argument(
"--set",
Expand Down
42 changes: 42 additions & 0 deletions meshtastic/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
tunnelMain,
set_missing_flags_false,
_profile_from_yaml,
_config_field_names,
_complete_config_fields,
)
from meshtastic import mt_config

Expand All @@ -46,6 +48,46 @@
# from ..remote_hardware import onGPIOreceive
# from ..config_pb2 import Config


@pytest.mark.unit
def test_config_field_names_follow_protobuf_descriptors():
"""Completion candidates include every current local configuration field."""
expected = {
f"{section.name}.{field.name}"
for config in (LocalConfig, LocalModuleConfig)
for section in config.DESCRIPTOR.fields
if section.message_type is not None
for field in section.message_type.fields
}

names = _config_field_names()

assert expected <= set(names)
assert "power.ls_secs" in names
assert "power.lsSecs" in names
assert names == sorted(set(names))


@pytest.mark.unit
def test_complete_config_fields_filters_by_prefix():
"""Shell completion returns only fields matching the typed prefix."""
matches = list(_complete_config_fields("bluetooth.fixed"))

assert matches
assert all(name.startswith("bluetooth.fixed") for name in matches)
assert "bluetooth.fixed_pin" in matches
assert "bluetooth.fixedPin" in matches


@pytest.mark.unit
def test_get_argument_uses_config_field_completer():
"""The --get argparse action exposes config candidates to argcomplete."""
parser = mt_main.argparse.ArgumentParser()
mt_main.addConfigArgs(parser)
get_action = next(action for action in parser._actions if "--get" in action.option_strings)

assert get_action.completer is _complete_config_fields

@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_main_init_parser_no_args(capsys):
Expand Down