From e6a6ac996c3a17a21b35ab90dcf728b7fa86fc40 Mon Sep 17 00:00:00 2001 From: mlisi1 Date: Wed, 24 Jun 2026 13:26:31 +0200 Subject: [PATCH 1/4] added color to ros2 param list with its variants and flags --- dendROS/dendROS.sh | 3 + dendROS/dendros_param_list.py | 182 ++++++++++++++++++ install.sh | 3 + test/unit/test_param_list.py | 334 ++++++++++++++++++++++++++++++++++ 4 files changed, 522 insertions(+) create mode 100644 dendROS/dendros_param_list.py create mode 100644 test/unit/test_param_list.py diff --git a/dendROS/dendROS.sh b/dendROS/dendROS.sh index fbac6be..76c9d63 100644 --- a/dendROS/dendROS.sh +++ b/dendROS/dendROS.sh @@ -77,6 +77,9 @@ ros2() { elif [[ "$1" == "action" && "$2" == "list" ]]; then "$_ROS2_BIN" "$@" | python3 "${_DENDROS_DIR}/dendros_action_list.py" return ${PIPESTATUS[0]} + elif [[ "$1" == "param" && "$2" == "list" ]]; then + "$_ROS2_BIN" "$@" | python3 "${_DENDROS_DIR}/dendros_param_list.py" "${@:3}" + return ${PIPESTATUS[0]} else "$_ROS2_BIN" "$@" fi diff --git a/dendROS/dendros_param_list.py b/dendROS/dendros_param_list.py new file mode 100644 index 0000000..cd5c4d7 --- /dev/null +++ b/dendROS/dendros_param_list.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""Colorize ros2 param list output using dendROS node colors.""" + +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +try: + import yaml +except ImportError: + yaml = None + +from lib.config_loader import load_config, merge_color_maps, resolve_node, resolve_node_style +from lib.colors import RESET, _resolve_color +from lib.global_config import load_global_config, get_node_colors_path + + +def _load_shared_colors(): + if yaml is None: + return {}, {}, {} + path = get_node_colors_path() + if not os.path.isfile(path): + return {}, {}, {} + try: + with open(path) as f: + data = yaml.safe_load(f) or {} + return ( + data.get('color_map', {}), + data.get('tag_map', {}), + data.get('style_map', {}), + ) + except Exception: + return {}, {}, {} + + +def _scan_configs(): + if yaml is None: + return [] + seen, paths = set(), [] + for prefix in os.environ.get('AMENT_PREFIX_PATH', '').split(':'): + share = os.path.join(prefix, 'share') if prefix else '' + if not share or not os.path.isdir(share): + continue + try: + for pkg in sorted(os.listdir(share)): + candidate = os.path.join(share, pkg, 'config', 'dendROS.yaml') + if os.path.isfile(candidate) and candidate not in seen: + seen.add(candidate) + paths.append(candidate) + except OSError: + pass + return paths + + +def _badge(label, ansi_code, style): + if style == 'inverted': + return f'\033[{ansi_code};7m[{label}]{RESET}' + return f'\033[{ansi_code}m[{label}]{RESET}' + + +def _parse_node_arg(argv): + """Return the first positional argument that looks like a node path (starts with '/').""" + for arg in argv: + if arg.startswith('/'): + return arg + return None + + +def _split_param_type(param): + """Split 'name (type)' → (name, type_str) or (name, None).""" + if param.endswith(')') and ' (' in param: + name, rest = param.rsplit(' (', 1) + return name, rest[:-1] + return param, None + + +def main(): + cfg = load_global_config() + show_tag = cfg['show_tag_cli'] + tag_style = cfg['tag_style'] + unmatched_clr = cfg['unmatched_color'] + unmatched_tag = cfg['unmatched_tag'] + dim_unmatched = cfg['dim_unmatched'] + unmatched_ansi = _resolve_color(unmatched_clr) if unmatched_clr else None + + color_map, tag_map, style_map = _load_shared_colors() + + if not color_map: + config_paths = _scan_configs() + tuples = [] + for path in config_paths: + try: + c, t, m, s, k, _ = load_config(path) + tuples.append((c, t, m, s, k)) + except Exception: + pass + if tuples: + c0, t0, m0, s0, k0 = tuples[0] + color_map, tag_map, _, style_map, _ = merge_color_maps( + c0, t0, m0, s0, k0, tuples[1:] + ) + + current_ansi = None # color code of the most recent node header + current_style = None # tag_style of the most recent node header + + # Pre-seed color from the node argument so bare output (no header line) is colored. + # When a node header IS in the output the header parser will simply overwrite this. + node_arg = _parse_node_arg(sys.argv[1:]) + if node_arg: + _ansi, _label = resolve_node(node_arg, color_map, tag_map) + if _ansi: + current_ansi = _ansi + current_style = resolve_node_style(node_arg, style_map) or tag_style + elif unmatched_ansi: + current_ansi = unmatched_ansi + current_style = tag_style + + for line in sys.stdin: + raw = line.rstrip('\n') + stripped = raw.lstrip() + + if not raw: + sys.stdout.write('\n') + continue + + indent = raw[: len(raw) - len(stripped)] + + # Node header: '/node_name:' — no leading whitespace, ends with ':' + if not indent and stripped.endswith(':'): + node_name = stripped[:-1] # drop the trailing ':' + ansi_code, label = resolve_node(node_name, color_map, tag_map) + + if ansi_code: + current_ansi = ansi_code + node_style = resolve_node_style(node_name, style_map) or tag_style + current_style = node_style + colored_name = f'\033[{ansi_code}m{node_name}:{RESET}' + if show_tag and label: + badge = _badge(label, ansi_code, node_style) + out = f'{badge} {colored_name}' + else: + out = colored_name + elif unmatched_ansi: + current_ansi = unmatched_ansi + current_style = tag_style + colored_name = f'\033[{unmatched_ansi}m{node_name}:{RESET}' + if show_tag and unmatched_tag: + badge = _badge(unmatched_tag, unmatched_ansi, tag_style) + out = f'{badge} {colored_name}' + else: + out = colored_name + elif dim_unmatched: + current_ansi = None + current_style = None + out = f'\033[2m{node_name}:{RESET}' + else: + current_ansi = None + current_style = None + out = raw + + sys.stdout.write(out + '\n') + sys.stdout.flush() + continue + + # Param line: indented under a node header + param_name, type_str = _split_param_type(stripped) + type_part = f' (\033[2m{type_str}{RESET})' if type_str else '' + + if current_ansi: + out = f'{indent}\033[{current_ansi}m\033[2m{param_name}{RESET}{type_part}' + elif dim_unmatched: + out = f'{indent}\033[2m{param_name}{RESET}{type_part}' + else: + out = f'{indent}{param_name}{type_part}' + + sys.stdout.write(out + '\n') + sys.stdout.flush() + + +if __name__ == '__main__': + main() diff --git a/install.sh b/install.sh index d1c22cc..4024a6d 100755 --- a/install.sh +++ b/install.sh @@ -52,14 +52,17 @@ $SUDO cp "${SCRIPT_DIR}/dendROS/dendros_node_info.py" "$INSTALL_DIR/" $SUDO cp "${SCRIPT_DIR}/dendROS/dendros_node_list.py" "$INSTALL_DIR/" $SUDO cp "${SCRIPT_DIR}/dendROS/dendros_service_list.py" "$INSTALL_DIR/" $SUDO cp "${SCRIPT_DIR}/dendROS/dendros_action_list.py" "$INSTALL_DIR/" +$SUDO cp "${SCRIPT_DIR}/dendROS/dendros_param_list.py" "$INSTALL_DIR/" $SUDO cp "${SCRIPT_DIR}/dendROS/dendROS.sh" "$INSTALL_DIR/" $SUDO cp -r "${SCRIPT_DIR}/dendROS/lib" "$INSTALL_DIR/" $SUDO chmod +x "$INSTALL_DIR/dendROS_pipe.py" $SUDO chmod +x "$INSTALL_DIR/dendros_config.py" $SUDO chmod +x "$INSTALL_DIR/dendros_init.py" $SUDO chmod +x "$INSTALL_DIR/dendros_node_list.py" +$SUDO chmod +x "$INSTALL_DIR/dendros_node_info.py" $SUDO chmod +x "$INSTALL_DIR/dendros_service_list.py" $SUDO chmod +x "$INSTALL_DIR/dendros_action_list.py" +$SUDO chmod +x "$INSTALL_DIR/dendros_param_list.py" $SUDO chmod 644 "$INSTALL_DIR/dendROS.sh" echo -e "${GREEN}[DendROS] Files installed to ${INSTALL_DIR}${RESET}" diff --git a/test/unit/test_param_list.py b/test/unit/test_param_list.py new file mode 100644 index 0000000..c207505 --- /dev/null +++ b/test/unit/test_param_list.py @@ -0,0 +1,334 @@ +"""Tests for dendros_param_list.py colorization.""" + +import os +import sys +import subprocess + +import pytest +import yaml + +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +PARAM_LIST_PATH = os.path.join(REPO_ROOT, 'dendROS', 'dendros_param_list.py') + +from conftest import assert_segment_colored, assert_segment_uncolored, colored_segments, strip_ansi + + +# ── Helper ──────────────────────────────────────────────────────────────────── + +def run_param_list(tmp_prefix, lines, global_cfg=None, node_colors=None, + argv_extra=None, timeout=10): + """Run dendros_param_list.py with text lines as stdin; return (stdout, stderr, rc). + + argv_extra: list of extra args appended to the command (mirrors ${@:3} from the shell). + """ + env = os.environ.copy() + env['AMENT_PREFIX_PATH'] = tmp_prefix + env.pop('ROS_DISTRO', None) + env['HOME'] = tmp_prefix + + cfg_dir = os.path.join(tmp_prefix, '.config', 'dendROS') + os.makedirs(cfg_dir, exist_ok=True) + + if global_cfg: + with open(os.path.join(cfg_dir, 'defaults.yaml'), 'w') as f: + yaml.dump(global_cfg, f) + if node_colors: + with open(os.path.join(cfg_dir, 'node_colors.yaml'), 'w') as f: + yaml.dump(node_colors, f) + + stdin = '\n'.join(lines) + '\n' + cmd = [sys.executable, PARAM_LIST_PATH] + (argv_extra or []) + result = subprocess.run( + cmd, + input=stdin.encode(), + capture_output=True, + env=env, + timeout=timeout, + ) + return result.stdout.decode(), result.stderr.decode(), result.returncode + + +# ── Node header colorization ────────────────────────────────────────────────── + +class TestParamNodeHeader: + """Node header lines (/node_name:) are colored with the group's color.""" + + def test_matched_node_header_colored(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:'], node_colors=nc) + assert '\033[32m/talker:\033[0m' in stdout + + def test_unmatched_node_header_passthrough(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/unknown_node:'], node_colors=nc) + assert_segment_uncolored(stdout, '/unknown_node:') + + def test_multiple_node_headers(self, tmp_path): + nc = {'color_map': {'talker': '32', 'listener': '34'}, + 'tag_map': {'talker': '', 'listener': ''}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/talker:', ' p1', '/listener:', ' p2'], node_colors=nc) + assert '\033[32m/talker:\033[0m' in stdout + assert '\033[34m/listener:\033[0m' in stdout + + def test_namespaced_node_header(self, tmp_path): + nc = {'color_map': {'talker': '33'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/my_ns/talker:'], node_colors=nc) + assert '\033[33m/my_ns/talker:\033[0m' in stdout + + def test_wildcard_pattern(self, tmp_path): + nc = {'color_map': {'nav2_*': '35'}, 'tag_map': {'nav2_*': ''}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/nav2_planner:'], node_colors=nc) + assert '\033[35m/nav2_planner:\033[0m' in stdout + + def test_no_config_passthrough(self, tmp_path): + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:']) + assert '\033[' not in stdout + assert '/talker:' in stdout + + def test_empty_lines_preserved(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/talker:', '', ' p1'], node_colors=nc) + assert '' in stdout.splitlines() + + +# ── Param line colorization (dimmed) ───────────────────────────────────────── + +class TestParamLines: + """Param lines (indented) use the current node's color, dimmed.""" + + def test_param_colored_dim_with_node_color(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:', ' use_sim_time'], + node_colors=nc) + assert '\033[32m\033[2muse_sim_time\033[0m' in stdout + + def test_param_indent_preserved(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:', ' use_sim_time'], + node_colors=nc) + line = [l for l in stdout.splitlines() if 'use_sim_time' in l][0] + plain = strip_ansi(line) + assert plain.startswith(' ') + + def test_param_under_unmatched_node_passthrough(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/unknown:', ' some_param'], node_colors=nc) + assert_segment_uncolored(stdout, 'some_param') + + def test_params_inherit_node_color_change(self, tmp_path): + nc = {'color_map': {'talker': '32', 'listener': '34'}, + 'tag_map': {'talker': '', 'listener': ''}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), + ['/talker:', ' p1', '/listener:', ' p2'], + node_colors=nc) + assert '\033[32m\033[2mp1\033[0m' in stdout + assert '\033[34m\033[2mp2\033[0m' in stdout + + def test_multiple_params_same_node(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/talker:', ' param_a', ' param_b'], node_colors=nc) + assert '\033[32m\033[2mparam_a\033[0m' in stdout + assert '\033[32m\033[2mparam_b\033[0m' in stdout + + def test_param_no_config_passthrough(self, tmp_path): + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:', ' use_sim_time']) + assert '\033[' not in stdout + + +# ── --param-type flag: type annotation dimmed ───────────────────────────────── + +class TestParamListTypeFlag: + """--param-type appends ' (type)'; type content must be rendered dim.""" + + def test_type_dimmed_for_matched_param(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/talker:', ' use_sim_time (bool)'], node_colors=nc) + assert '(\033[2mbool\033[0m)' in stdout + + def test_type_dimmed_for_unmatched_param(self, tmp_path): + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/unknown:', ' foo (integer)'], node_colors=nc) + assert '(\033[2minteger\033[0m)' in stdout + + def test_param_name_and_type_both_present(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/talker:', ' my_param (string)'], node_colors=nc) + assert '\033[32m\033[2mmy_param\033[0m' in stdout + assert '(\033[2mstring\033[0m)' in stdout + + def test_no_type_no_parens(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:', ' my_param'], + node_colors=nc) + assert '(' not in strip_ansi(stdout) + + +# ── Tag badge ───────────────────────────────────────────────────────────────── + +class TestParamListTag: + """Tag badge appears left of the node header when show_tag_cli is true.""" + + def test_tag_shown_before_node_header(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': 'TLK'}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:'], node_colors=nc) + assert '[TLK]' in stdout + line = [l for l in stdout.splitlines() if '/talker:' in strip_ansi(l)][0] + assert line.index('[TLK]') < line.index('/talker:') + + def test_tag_hidden_when_show_tag_cli_false(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': 'TLK'}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:'], + node_colors=nc, + global_cfg={'show_tag_cli': False}) + assert '[TLK]' not in stdout + + def test_inverted_tag_style(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': 'TLK'}, + 'style_map': {'talker': 'inverted'}} + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:'], node_colors=nc) + assert '\033[32;7m[TLK]' in stdout + + def test_unmatched_tag(self, tmp_path): + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/unknown:'], node_colors=nc, + global_cfg={'unmatched_color': 'white', 'unmatched_tag': '?'}) + assert '[?]' in stdout + + def test_tag_not_shown_on_param_lines(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': 'TLK'}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:', ' p1'], node_colors=nc) + param_line = [l for l in stdout.splitlines() if 'p1' in strip_ansi(l)][0] + assert '[TLK]' not in param_line + + +# ── Unmatched / dim_unmatched ───────────────────────────────────────────────── + +class TestParamListUnmatched: + + def test_unmatched_color_applied_to_header(self, tmp_path): + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/unknown:'], node_colors=nc, + global_cfg={'unmatched_color': 'cyan'}) + assert '\033[' in stdout + + def test_dim_unmatched_header(self, tmp_path): + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/unknown:'], node_colors=nc, + global_cfg={'dim_unmatched': True}) + assert '\033[2m/unknown:\033[0m' in stdout + + def test_dim_unmatched_param(self, tmp_path): + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_param_list( + str(tmp_path), ['/unknown:', ' my_param'], node_colors=nc, + global_cfg={'dim_unmatched': True}) + assert '\033[2mmy_param\033[0m' in stdout + + def test_passthrough_when_no_config(self, tmp_path): + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['/unknown:', ' p1'], node_colors=nc) + assert_segment_uncolored(stdout, '/unknown:') + assert_segment_uncolored(stdout, 'p1') + + +# ── AMENT_PREFIX_PATH fallback ──────────────────────────────────────────────── + +class TestParamListFallback: + + def test_fallback_scan_colors_header(self, tmp_path): + cfg_dir = tmp_path / 'share' / 'my_pkg' / 'config' + cfg_dir.mkdir(parents=True) + (cfg_dir / 'dendROS.yaml').write_text(yaml.dump({ + 'groups': {'loc': {'color': 'bold blue', 'label': 'LOC', + 'nodes': ['talker']}} + })) + stdout, _, _ = run_param_list(str(tmp_path), ['/talker:']) + assert '\033[' in stdout + + +# ── ros2 param list /node_name — bare output format ────────────────────────── + +class TestParamListNodeArg: + """When a node path is passed as argv, bare param lines (no header, no indent) + are colored using that node's color, pre-seeded before reading stdin.""" + + def test_bare_params_colored_via_node_arg(self, tmp_path): + """Bare param names (no header, no indentation) colored when node arg given.""" + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['use_sim_time', 'publish_rate'], + node_colors=nc, argv_extra=['/talker']) + assert '\033[32m\033[2muse_sim_time\033[0m' in stdout + assert '\033[32m\033[2mpublish_rate\033[0m' in stdout + + def test_bare_params_with_type_flag(self, tmp_path): + """--param-type type dimming works for bare output.""" + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['use_sim_time (bool)'], + node_colors=nc, argv_extra=['/talker']) + assert '\033[32m\033[2muse_sim_time\033[0m' in stdout + assert '(\033[2mbool\033[0m)' in stdout + + def test_node_arg_with_namespace(self, tmp_path): + """Namespaced node path resolved by basename matching.""" + nc = {'color_map': {'talker': '33'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['use_sim_time'], + node_colors=nc, argv_extra=['/my_ns/talker']) + assert '\033[33m\033[2muse_sim_time\033[0m' in stdout + + def test_node_arg_unmatched_passthrough(self, tmp_path): + """Unknown node arg leaves bare params uncolored.""" + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['use_sim_time'], + node_colors=nc, argv_extra=['/unknown_node']) + assert_segment_uncolored(stdout, 'use_sim_time') + + def test_node_arg_unmatched_dim(self, tmp_path): + """dim_unmatched applies to bare params from an unmatched node arg.""" + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['use_sim_time'], + node_colors=nc, argv_extra=['/unknown_node'], + global_cfg={'dim_unmatched': True}) + assert '\033[2muse_sim_time\033[0m' in stdout + + def test_node_arg_unmatched_color(self, tmp_path): + """unmatched_color pre-seeds bare params from an unmatched node arg.""" + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['use_sim_time'], + node_colors=nc, argv_extra=['/unknown_node'], + global_cfg={'unmatched_color': 'cyan'}) + assert '\033[' in stdout + + def test_header_in_output_overrides_node_arg(self, tmp_path): + """When output includes a node header, header color wins (even if arg differs).""" + nc = {'color_map': {'talker': '32', 'listener': '34'}, + 'tag_map': {'talker': '', 'listener': ''}, 'style_map': {}} + # arg says /talker but output has /listener: header — listener color should apply + stdout, _, _ = run_param_list(str(tmp_path), ['/listener:', ' p1'], + node_colors=nc, argv_extra=['/talker']) + assert '\033[34m\033[2mp1\033[0m' in stdout + + def test_flag_args_ignored_for_node_detection(self, tmp_path): + """Flags like --param-type passed in argv_extra do not confuse node detection.""" + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['use_sim_time (bool)'], + node_colors=nc, + argv_extra=['--param-type', '/talker']) + assert '\033[32m\033[2muse_sim_time\033[0m' in stdout + + def test_no_node_arg_bare_params_passthrough(self, tmp_path): + """Without a node arg, bare param names are passed through without color.""" + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_param_list(str(tmp_path), ['use_sim_time'], node_colors=nc) + assert_segment_uncolored(stdout, 'use_sim_time') From f5a1921e485ce3211cb318e1f87fa0ff17034d49 Mon Sep 17 00:00:00 2001 From: mlisi1 Date: Wed, 24 Jun 2026 13:35:36 +0200 Subject: [PATCH 2/4] added formatting and color to ros2 param describe --- dendROS/dendROS.sh | 3 + dendROS/dendros_param_describe.py | 167 ++++++++++++++++++ install.sh | 2 + test/unit/test_param_describe.py | 273 ++++++++++++++++++++++++++++++ 4 files changed, 445 insertions(+) create mode 100644 dendROS/dendros_param_describe.py create mode 100644 test/unit/test_param_describe.py diff --git a/dendROS/dendROS.sh b/dendROS/dendROS.sh index 76c9d63..660d615 100644 --- a/dendROS/dendROS.sh +++ b/dendROS/dendROS.sh @@ -80,6 +80,9 @@ ros2() { elif [[ "$1" == "param" && "$2" == "list" ]]; then "$_ROS2_BIN" "$@" | python3 "${_DENDROS_DIR}/dendros_param_list.py" "${@:3}" return ${PIPESTATUS[0]} + elif [[ "$1" == "param" && "$2" == "describe" ]]; then + "$_ROS2_BIN" "$@" | python3 "${_DENDROS_DIR}/dendros_param_describe.py" "${@:3}" + return ${PIPESTATUS[0]} else "$_ROS2_BIN" "$@" fi diff --git a/dendROS/dendros_param_describe.py b/dendROS/dendros_param_describe.py new file mode 100644 index 0000000..5ecc4bf --- /dev/null +++ b/dendROS/dendros_param_describe.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Colorize and format ros2 param describe output using dendROS node colors.""" + +import os +import re +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +try: + import yaml +except ImportError: + yaml = None + +from lib.config_loader import load_config, merge_color_maps, resolve_node, resolve_node_style +from lib.colors import RESET, _resolve_color +from lib.global_config import load_global_config, get_node_colors_path + +_PARAM_NAME_RE = re.compile(r'^(Parameter name):\s+(.*)') + + +def _load_shared_colors(): + if yaml is None: + return {}, {}, {} + path = get_node_colors_path() + if not os.path.isfile(path): + return {}, {}, {} + try: + with open(path) as f: + data = yaml.safe_load(f) or {} + return ( + data.get('color_map', {}), + data.get('tag_map', {}), + data.get('style_map', {}), + ) + except Exception: + return {}, {}, {} + + +def _scan_configs(): + if yaml is None: + return [] + seen, paths = set(), [] + for prefix in os.environ.get('AMENT_PREFIX_PATH', '').split(':'): + share = os.path.join(prefix, 'share') if prefix else '' + if not share or not os.path.isdir(share): + continue + try: + for pkg in sorted(os.listdir(share)): + candidate = os.path.join(share, pkg, 'config', 'dendROS.yaml') + if os.path.isfile(candidate) and candidate not in seen: + seen.add(candidate) + paths.append(candidate) + except OSError: + pass + return paths + + +def _badge(label, ansi_code, style): + if style == 'inverted': + return f'\033[{ansi_code};7m[{label}]{RESET}' + return f'\033[{ansi_code}m[{label}]{RESET}' + + +def _parse_node_arg(argv): + """Return the first positional argument that looks like a node path (starts with '/').""" + for arg in argv: + if arg.startswith('/'): + return arg + return None + + +def _format_key_value(indent, key, value): + """Dim the key label; bold the section header when there is no value.""" + if value: + return f'{indent}\033[2m{key}:\033[0m {value}' + return f'{indent}\033[1m{key}:\033[0m' + + +def _colorize_line(raw, ansi_code, label, node_style, + unmatched_ansi, unmatched_tag, tag_style, + show_tag, dim_unmatched): + """Return a formatted version of one output line.""" + if not raw.strip(): + return raw + + # ── Parameter name block header ────────────────────────────────────────── + m = _PARAM_NAME_RE.match(raw) + if m: + param_name = m.group(2).strip() + dim_label = f'\033[2mParameter name:\033[0m' + + if ansi_code: + colored = f'\033[{ansi_code};1m{param_name}\033[0m' + if show_tag and label: + return f'{_badge(label, ansi_code, node_style)} {dim_label} {colored}' + return f'{dim_label} {colored}' + + if unmatched_ansi: + colored = f'\033[{unmatched_ansi};1m{param_name}\033[0m' + if show_tag and unmatched_tag: + return f'{_badge(unmatched_tag, unmatched_ansi, tag_style)} {dim_label} {colored}' + return f'{dim_label} {colored}' + + if dim_unmatched: + return f'{dim_label} \033[2m{param_name}\033[0m' + + return f'{dim_label} {param_name}' + + # ── Key: value lines (all indented fields) ──────────────────────────────── + stripped = raw.lstrip() + indent = raw[:len(raw) - len(stripped)] + + if ':' in stripped: + key, _, rest = stripped.partition(':') + value = rest.lstrip() + return _format_key_value(indent, key, value) + + return raw + + +def main(): + cfg = load_global_config() + show_tag = cfg['show_tag_cli'] + tag_style = cfg['tag_style'] + unmatched_clr = cfg['unmatched_color'] + unmatched_tag = cfg['unmatched_tag'] + dim_unmatched = cfg['dim_unmatched'] + unmatched_ansi = _resolve_color(unmatched_clr) if unmatched_clr else None + + color_map, tag_map, style_map = _load_shared_colors() + + if not color_map: + config_paths = _scan_configs() + tuples = [] + for path in config_paths: + try: + c, t, m, s, k, _ = load_config(path) + tuples.append((c, t, m, s, k)) + except Exception: + pass + if tuples: + c0, t0, m0, s0, k0 = tuples[0] + color_map, tag_map, _, style_map, _ = merge_color_maps( + c0, t0, m0, s0, k0, tuples[1:] + ) + + node_arg = _parse_node_arg(sys.argv[1:]) + if node_arg: + ansi_code, label = resolve_node(node_arg, color_map, tag_map) + node_style = (resolve_node_style(node_arg, style_map) or tag_style) if ansi_code else tag_style + else: + ansi_code, label, node_style = None, None, tag_style + + for line in sys.stdin: + raw = line.rstrip('\n') + out = _colorize_line( + raw, ansi_code, label, node_style, + unmatched_ansi, unmatched_tag, tag_style, + show_tag, dim_unmatched, + ) + sys.stdout.write(out + '\n') + sys.stdout.flush() + + +if __name__ == '__main__': + main() diff --git a/install.sh b/install.sh index 4024a6d..eeac30b 100755 --- a/install.sh +++ b/install.sh @@ -53,6 +53,7 @@ $SUDO cp "${SCRIPT_DIR}/dendROS/dendros_node_list.py" "$INSTALL_DIR/" $SUDO cp "${SCRIPT_DIR}/dendROS/dendros_service_list.py" "$INSTALL_DIR/" $SUDO cp "${SCRIPT_DIR}/dendROS/dendros_action_list.py" "$INSTALL_DIR/" $SUDO cp "${SCRIPT_DIR}/dendROS/dendros_param_list.py" "$INSTALL_DIR/" +$SUDO cp "${SCRIPT_DIR}/dendROS/dendros_param_describe.py" "$INSTALL_DIR/" $SUDO cp "${SCRIPT_DIR}/dendROS/dendROS.sh" "$INSTALL_DIR/" $SUDO cp -r "${SCRIPT_DIR}/dendROS/lib" "$INSTALL_DIR/" $SUDO chmod +x "$INSTALL_DIR/dendROS_pipe.py" @@ -63,6 +64,7 @@ $SUDO chmod +x "$INSTALL_DIR/dendros_node_info.py" $SUDO chmod +x "$INSTALL_DIR/dendros_service_list.py" $SUDO chmod +x "$INSTALL_DIR/dendros_action_list.py" $SUDO chmod +x "$INSTALL_DIR/dendros_param_list.py" +$SUDO chmod +x "$INSTALL_DIR/dendros_param_describe.py" $SUDO chmod 644 "$INSTALL_DIR/dendROS.sh" echo -e "${GREEN}[DendROS] Files installed to ${INSTALL_DIR}${RESET}" diff --git a/test/unit/test_param_describe.py b/test/unit/test_param_describe.py new file mode 100644 index 0000000..c4656f7 --- /dev/null +++ b/test/unit/test_param_describe.py @@ -0,0 +1,273 @@ +"""Tests for dendros_param_describe.py colorization and formatting.""" + +import os +import sys +import subprocess + +import pytest +import yaml + +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +PARAM_DESCRIBE_PATH = os.path.join(REPO_ROOT, 'dendROS', 'dendros_param_describe.py') + +from conftest import assert_segment_colored, assert_segment_uncolored, colored_segments, strip_ansi + +# ── Fixtures ────────────────────────────────────────────────────────────────── + +_SIMPLE = [ + 'Parameter name: use_sim_time', + ' Type: boolean', + ' Constraints:', +] + +_WITH_DESC = [ + 'Parameter name: my_param', + ' Type: string', + ' Description: Controls the output rate.', + ' Constraints:', + ' Read only: true', +] + +_MULTI = [ + 'Parameter name: use_sim_time', + ' Type: boolean', + ' Constraints:', + 'Parameter name: publish_rate', + ' Type: double', + ' Description: Rate in Hz.', + ' Constraints:', + ' Min value: 0.0', + ' Max value: 100.0', + ' Step: 0.0', +] + + +# ── Helper ──────────────────────────────────────────────────────────────────── + +def run_describe(tmp_prefix, lines, global_cfg=None, node_colors=None, + argv_extra=None, timeout=10): + """Run dendros_param_describe.py with text lines as stdin.""" + env = os.environ.copy() + env['AMENT_PREFIX_PATH'] = tmp_prefix + env.pop('ROS_DISTRO', None) + env['HOME'] = tmp_prefix + + cfg_dir = os.path.join(tmp_prefix, '.config', 'dendROS') + os.makedirs(cfg_dir, exist_ok=True) + + if global_cfg: + with open(os.path.join(cfg_dir, 'defaults.yaml'), 'w') as f: + yaml.dump(global_cfg, f) + if node_colors: + with open(os.path.join(cfg_dir, 'node_colors.yaml'), 'w') as f: + yaml.dump(node_colors, f) + + stdin = '\n'.join(lines) + '\n' + cmd = [sys.executable, PARAM_DESCRIBE_PATH] + (argv_extra or []) + result = subprocess.run( + cmd, input=stdin.encode(), capture_output=True, env=env, timeout=timeout, + ) + return result.stdout.decode(), result.stderr.decode(), result.returncode + + +# ── Parameter name header ───────────────────────────────────────────────────── + +class TestParamDescribeHeader: + """'Parameter name: X' line: dim label, bold+node-colored param name.""" + + def test_param_name_label_dimmed(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/talker']) + assert '\033[2mParameter name:\033[0m' in stdout + + def test_param_name_value_bold_node_color(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/talker']) + assert '\033[32;1muse_sim_time\033[0m' in stdout + + def test_param_name_unmatched_passthrough(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/unknown']) + # label still dimmed, name not colored + assert '\033[2mParameter name:\033[0m' in stdout + assert '\033[32;1m' not in stdout + + def test_param_name_no_node_arg(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc) + # no color on param name but label is still formatted + assert '\033[2mParameter name:\033[0m' in stdout + + def test_namespaced_node_resolved_by_basename(self, tmp_path): + nc = {'color_map': {'talker': '33'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/my_ns/talker']) + assert '\033[33;1muse_sim_time\033[0m' in stdout + + def test_wildcard_pattern(self, tmp_path): + nc = {'color_map': {'nav2_*': '35'}, 'tag_map': {'nav2_*': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/nav2_planner']) + assert '\033[35;1muse_sim_time\033[0m' in stdout + + def test_multi_param_each_header_colored(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _MULTI, node_colors=nc, + argv_extra=['/talker']) + headers = [l for l in stdout.splitlines() if 'Parameter name:' in strip_ansi(l)] + assert len(headers) == 2 + for h in headers: + assert '\033[32;1m' in h + + +# ── Field formatting ────────────────────────────────────────────────────────── + +class TestParamDescribeFields: + """Indented field lines: key dimmed, value plain; section headers bold.""" + + def test_type_key_dimmed(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/talker']) + assert '\033[2mType:\033[0m' in stdout + + def test_type_value_plain(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/talker']) + type_line = [l for l in stdout.splitlines() if 'Type:' in strip_ansi(l)][0] + # 'boolean' should appear outside any color code + segs = colored_segments(type_line) + assert any('boolean' in t and c is None for t, c in segs) + + def test_description_key_dimmed(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _WITH_DESC, node_colors=nc, + argv_extra=['/talker']) + assert '\033[2mDescription:\033[0m' in stdout + + def test_constraints_header_bold(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/talker']) + assert '\033[1mConstraints:\033[0m' in stdout + + def test_read_only_key_dimmed(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _WITH_DESC, node_colors=nc, + argv_extra=['/talker']) + assert '\033[2mRead only:\033[0m' in stdout + + def test_range_constraints_formatted(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _MULTI, node_colors=nc, + argv_extra=['/talker']) + assert '\033[2mMin value:\033[0m' in stdout + assert '\033[2mMax value:\033[0m' in stdout + assert '\033[2mStep:\033[0m' in stdout + + def test_formatting_applied_without_node_color(self, tmp_path): + """Field formatting applies even when the node has no config color.""" + stdout, _, _ = run_describe(str(tmp_path), _WITH_DESC) + assert '\033[2mType:\033[0m' in stdout + assert '\033[1mConstraints:\033[0m' in stdout + assert '\033[2mDescription:\033[0m' in stdout + + def test_indent_preserved(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _WITH_DESC, node_colors=nc, + argv_extra=['/talker']) + lines = stdout.splitlines() + type_line = [strip_ansi(l) for l in lines if 'Type:' in strip_ansi(l)][0] + constraint_line = [strip_ansi(l) for l in lines if 'Read only:' in strip_ansi(l)][0] + assert type_line.startswith(' ') + assert constraint_line.startswith(' ') + + def test_empty_lines_preserved(self, tmp_path): + lines = _SIMPLE + ['', 'Parameter name: other_param', ' Type: integer', ' Constraints:'] + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': ''}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), lines, node_colors=nc, + argv_extra=['/talker']) + assert '' in stdout.splitlines() + + +# ── Tag badge ───────────────────────────────────────────────────────────────── + +class TestParamDescribeTag: + + def test_tag_shown_before_param_name(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': 'TLK'}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/talker']) + header = [l for l in stdout.splitlines() if 'Parameter name:' in strip_ansi(l)][0] + assert '[TLK]' in header + assert header.index('[TLK]') < header.index('Parameter name:') + + def test_tag_hidden_when_show_tag_cli_false(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': 'TLK'}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/talker'], + global_cfg={'show_tag_cli': False}) + assert '[TLK]' not in stdout + + def test_inverted_tag_style(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': 'TLK'}, + 'style_map': {'talker': 'inverted'}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/talker']) + assert '\033[32;7m[TLK]' in stdout + + def test_tag_not_shown_on_field_lines(self, tmp_path): + nc = {'color_map': {'talker': '32'}, 'tag_map': {'talker': 'TLK'}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _WITH_DESC, node_colors=nc, + argv_extra=['/talker']) + for line in stdout.splitlines(): + if 'Type:' in strip_ansi(line) or 'Description:' in strip_ansi(line): + assert '[TLK]' not in line + + def test_unmatched_tag(self, tmp_path): + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/unknown'], + global_cfg={'unmatched_color': 'white', 'unmatched_tag': '?'}) + assert '[?]' in stdout + + +# ── Unmatched / dim_unmatched ───────────────────────────────────────────────── + +class TestParamDescribeUnmatched: + + def test_unmatched_color_applied_to_param_name(self, tmp_path): + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/unknown'], + global_cfg={'unmatched_color': 'cyan'}) + assert '\033[' in stdout + # at least the param name should be colored + header = [l for l in stdout.splitlines() if 'Parameter name:' in strip_ansi(l)][0] + assert '\033[' in header + + def test_dim_unmatched_param_name(self, tmp_path): + nc = {'color_map': {}, 'tag_map': {}, 'style_map': {}} + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, node_colors=nc, + argv_extra=['/unknown'], + global_cfg={'dim_unmatched': True}) + assert '\033[2muse_sim_time\033[0m' in stdout + + +# ── AMENT_PREFIX_PATH fallback ──────────────────────────────────────────────── + +class TestParamDescribeFallback: + + def test_fallback_scan_colors_param_name(self, tmp_path): + cfg_dir = tmp_path / 'share' / 'my_pkg' / 'config' + cfg_dir.mkdir(parents=True) + (cfg_dir / 'dendROS.yaml').write_text(yaml.dump({ + 'groups': {'loc': {'color': 'bold blue', 'label': 'LOC', + 'nodes': ['talker']}} + })) + stdout, _, _ = run_describe(str(tmp_path), _SIMPLE, argv_extra=['/talker']) + assert '\033[' in stdout From 1e0ebaf74e7368316868e530fcbd1170adfa8db6 Mon Sep 17 00:00:00 2001 From: mlisi1 Date: Wed, 24 Jun 2026 16:22:01 +0200 Subject: [PATCH 3/4] added params change alert --- dendROS/dendROS_pipe.py | 12 + dendROS/dendros_config.py | 23 +- dendROS/lib/global_config.py | 17 +- dendROS/lib/param_watcher.py | 274 ++++++++++++++++++ test/unit/conftest.py | 4 +- test/unit/test_global_config.py | 6 + test/unit/test_param_watcher.py | 484 ++++++++++++++++++++++++++++++++ 7 files changed, 808 insertions(+), 12 deletions(-) create mode 100644 dendROS/lib/param_watcher.py create mode 100644 test/unit/test_param_watcher.py diff --git a/dendROS/dendROS_pipe.py b/dendROS/dendROS_pipe.py index 0fe84a1..9d125bb 100755 --- a/dendROS/dendROS_pipe.py +++ b/dendROS/dendROS_pipe.py @@ -34,6 +34,7 @@ from lib.global_config import load_global_config, get_node_colors_path import lib.crash_alert as ca import lib.traceback_color as tc +import lib.param_watcher as pw _DEBUG = os.environ.get('DENDROS_DEBUG', '') not in ('', '0') @@ -150,6 +151,12 @@ def main(): if config_path: _save_node_colors(color_map, tag_map, style_map) + param_alert = bool(global_cfg.get('param_change_alert', False)) + param_alert_scope = global_cfg.get('param_change_alert_scope', 'tracked') + param_alert_style = global_cfg.get('param_change_alert_style', 'inline') + if param_alert: + pw.setup(color_map, tag_map, param_alert_scope) + show_tag = defaults.get('show_group_tag', True) color_mode = defaults.get('color_mode', 'tag_only') tag_position = defaults.get('tag_position', 'after') @@ -364,6 +371,11 @@ def _iter_stdin(): _emit(_colorize(line)) + if param_alert: + for notif in pw.drain(color_map, tag_map, style_map, tag_style, show_tag, + param_alert_style): + _emit(notif) + if ca._crash_alert_enabled: if new_death: ca.print_alert_banner() diff --git a/dendROS/dendros_config.py b/dendROS/dendros_config.py index 3a8822e..798c0ee 100644 --- a/dendROS/dendros_config.py +++ b/dendROS/dendros_config.py @@ -43,10 +43,13 @@ ("init_color", "Init: color", "cycle", ["palette", "null"]), ("init_color_bold", "Init: bold colors", "cycle", [False, True]), ("init_label", "Init: auto label", "cycle", [False, True]), - ("crash_alert", "Crash alert", "cycle", [False, True]), - ("crash_alert_color", "Alert color", "cycle", ["node", "red"]), - ("crash_alert_interval", "Alert interval (s)", "text", None), - ("traceback_color", "Traceback color", "cycle", ["fancy", "red", "off"]), + ("crash_alert", "Crash alert", "cycle", [False, True]), + ("crash_alert_color", "Alert color", "cycle", ["node", "red"]), + ("crash_alert_interval", "Alert interval (s)", "text", None), + ("traceback_color", "Traceback color", "cycle", ["fancy", "red", "off"]), + ("param_change_alert", "Param change alert", "cycle", [False, True]), + ("param_change_alert_scope", "Param alert scope", "cycle", ["tracked", "all"]), + ("param_change_alert_style", "Param alert style", "cycle", ["inline", "inverted"]), ] _DESCS = { @@ -140,6 +143,18 @@ "fancy — bold red header/exception, dim red frame lines (default)", "red — entire traceback in bold red | off — no coloring (white)", ), + "param_change_alert": ( + "on — print an inline notification in the launch terminal whenever a parameter changes at runtime", + "off — parameter changes are silent; use ros2 param get to check values manually", + ), + "param_change_alert_scope": ( + "tracked — only notify for nodes that have a color group in a dendROS.yaml config", + "all — notify for every node on the ROS graph (includes nodes with no config entry)", + ), + "param_change_alert_style": ( + "inline — compact single line: [dendROS] param [TAG] /node param_name → value", + "inverted — reverse-video block with node color as background; harder to miss", + ), } _VAL_LABEL = {True: "on", False: "off", None: "null"} diff --git a/dendROS/lib/global_config.py b/dendROS/lib/global_config.py index c12f432..3000924 100644 --- a/dendROS/lib/global_config.py +++ b/dendROS/lib/global_config.py @@ -27,12 +27,15 @@ "init_color": "palette", "init_color_bold": False, "init_label": False, - "crash_alert": True, - "crash_alert_color": "node", - "crash_alert_interval": 30, - "traceback_color": "fancy", - "tag_style": "normal", - "show_default_services": True, + "crash_alert": True, + "crash_alert_color": "node", + "crash_alert_interval": 30, + "traceback_color": "fancy", + "tag_style": "normal", + "show_default_services": True, + "param_change_alert": True, + "param_change_alert_scope": "tracked", + "param_change_alert_style": "inline", } @@ -72,4 +75,4 @@ def save_global_config(cfg): path = get_global_config_path() os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, "w") as f: - yaml.dump({k: cfg[k] for k in DEFAULTS}, f, default_flow_style=False) + yaml.dump({k: cfg.get(k, DEFAULTS[k]) for k in DEFAULTS}, f, default_flow_style=False) diff --git a/dendROS/lib/param_watcher.py b/dendROS/lib/param_watcher.py new file mode 100644 index 0000000..27fb528 --- /dev/null +++ b/dendROS/lib/param_watcher.py @@ -0,0 +1,274 @@ +"""Background watcher for /parameter_events — queues param change notifications. + +Architecture +------------ +A single daemon thread runs `ros2 topic echo /parameter_events` as a subprocess +and parses its YAML output (message chunks separated by `---` lines). Each +chunk that represents a genuine runtime change (changed_parameters non-empty, +not a CLI daemon node) is pushed to `_queue`. + +The main pipe thread calls `drain()` between stdin lines to pop all pending +events and get back formatted notification strings. No locking is needed +because only the daemon thread writes to _queue and only the main thread reads +from it (queue.Queue is already thread-safe). +""" + +import atexit +import os +import queue +import shutil +import subprocess +import threading + +try: + import yaml +except ImportError: + yaml = None + +_RESET = '\033[0m' +# White-bg + black-text strip used in the inverted alert style +_WB = '\033[107;30m' + +# [dendROS] header matches the logo title: "[dend" in logo-blue, "ROS]" in logo-orange +_DENDROS = '\033[38;2;0;75;107;1m[dend\033[38;2;224;127;0;1mROS]\033[0m' + +# Inverted-style header: logo colors as BACKGROUNDS, black text (hollow/cutout letters). +# \033[30m sets black fg once; subsequent bg changes leave fg unchanged. +_DENDROS_INV = ( + '\033[48;2;0;75;107;1m\033[30m[dend' # logo-blue bg + bold + black text + '\033[48;2;224;127;0;1mROS]' # logo-orange bg + bold (fg stays black) + '\033[0m\033[107;30m' # reset → white bg + black text for rest +) + + +def _fg_to_bg(code: str) -> str: + """Convert a foreground ANSI SGR code string to its background equivalent. + + Standard 30-37 → 40-47, bright 90-97 → 100-107, 24-bit 38;2;R;G;B → 48;2;R;G;B. + Bold and other modifiers are dropped so the caller can add its own text attributes. + """ + parts = code.split(';') + out = [] + i = 0 + while i < len(parts): + p = parts[i] + n = int(p) if p.isdigit() else -1 + if 30 <= n <= 37: + out.append(str(n + 10)); i += 1 + elif 90 <= n <= 97: + out.append(str(n + 10)); i += 1 + elif n == 38 and i + 1 < len(parts): + if parts[i + 1] == '2' and i + 4 < len(parts): + out += ['48', '2'] + parts[i + 2:i + 5]; i += 5 + elif parts[i + 1] == '5' and i + 2 < len(parts): + out += ['48', '5', parts[i + 2]]; i += 3 + else: + i += 1 + else: + i += 1 # drop bold and other non-color modifiers + return ';'.join(out) if out else '0' + +_queue = queue.Queue() +_proc: 'subprocess.Popen | None' = None +_param_cache: dict = {} # (node, param_name) → last-seen value string + +# ParameterType enum → value field name in the YAML message +_TYPE_FIELDS = { + 1: 'bool_value', + 2: 'integer_value', + 3: 'double_value', + 4: 'string_value', + 5: 'byte_array_value', + 6: 'bool_array_value', + 7: 'integer_array_value', + 8: 'double_array_value', + 9: 'string_array_value', +} +_MAX_VALUE_LEN = 60 + + +def _find_ros2(): + b = shutil.which('ros2') + if b: + return b + distro = os.environ.get('ROS_DISTRO', '') + if distro: + c = f'/opt/ros/{distro}/bin/ros2' + if os.path.isfile(c): + return c + return None + + +def _extract_value(v): + """Return a human-readable string for a ParameterValue dict.""" + if not isinstance(v, dict): + return str(v) if v is not None else '' + field = _TYPE_FIELDS.get(v.get('type', 0)) + if not field: + return '' + val = v.get(field, '') + if isinstance(val, bool): + return 'true' if val else 'false' + if isinstance(val, list): + s = str(val) + return (s[:_MAX_VALUE_LEN] + '…') if len(s) > _MAX_VALUE_LEN else s + return str(val) + + +def _process_chunk(lines, color_map, tag_map, scope, resolve_node_fn): + """Parse one YAML message chunk; enqueue any changed-parameter events.""" + try: + msg = yaml.safe_load('\n'.join(lines)) + except Exception: + return + if not isinstance(msg, dict): + return + + node = msg.get('node', '') + # /_ros2cli_XXXXX nodes are transient CLI tool nodes — always skip + if not node or node.startswith('/_ros2cli_'): + return + + changed = msg.get('changed_parameters') or [] + if not changed: + return + + if scope == 'tracked': + code, _ = resolve_node_fn(node, color_map, tag_map) + if not code: + return + + for param in changed: + if not isinstance(param, dict): + continue + name = param.get('name', '') + if not name: + continue + new_val = _extract_value(param.get('value', {})) + cache_key = (node, name) + old_val = _param_cache.get(cache_key) # None on first ever change + _param_cache[cache_key] = new_val + _queue.put((node, name, old_val, new_val)) + + +def _watch(color_map, tag_map, scope): + """Background thread: stream /parameter_events and fill the queue.""" + global _proc + ros2 = _find_ros2() + if not ros2: + return + + try: + _proc = subprocess.Popen( + [ros2, 'topic', 'echo', '/parameter_events'], + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + text=True, + ) + except Exception: + return + + from lib.config_loader import resolve_node # deferred — avoids import cycles + + chunk: list[str] = [] + try: + for raw in _proc.stdout: + line = raw.rstrip('\n') + if line == '---': + if chunk: + _process_chunk(chunk, color_map, tag_map, scope, resolve_node) + chunk = [] + else: + chunk.append(line) + except Exception: + pass + + +@atexit.register +def _cleanup(): + if _proc is not None and _proc.poll() is None: + try: + _proc.terminate() + except Exception: + pass + + +def setup(color_map, tag_map, scope: str): + """Start the background watcher. No-op when yaml or ros2 binary is unavailable.""" + if yaml is None or not _find_ros2(): + return + t = threading.Thread(target=_watch, args=(color_map, tag_map, scope), daemon=True) + t.start() + + +def _fmt_inline(node, name, old_val, value, code, label, node_style, show_tag): + """Rainbow header + plain 'param' + node color identity + bold param data.""" + old_part = f'{old_val} → ' if old_val is not None else '? → ' + if code: + node_str = f'\033[{code}m{node}{_RESET}' + if show_tag and label: + badge = (f'\033[{code};7m[{label}]{_RESET}' + if node_style == 'inverted' + else f'\033[{code}m[{label}]{_RESET}') + node_str = f'{badge} {node_str}' + else: + node_str = node # untracked in "all" mode + + return ( + f'{_DENDROS} param' + f' {node_str} \033[1m{name}\033[0m: {old_part}{value}\n' + ) + + +def _fmt_inverted(node, name, old_val, value, code, label, node_style, show_tag): + """[dendROS] header + continuous white-bg strip from 'param' to EOL. + + Layout: + [blue/orange][dend|ROS][reset][WB] param [node-bg;black][TAG] /node[WB] [bold]name[/bold]: old→new [K][reset] + + The white-bg (_WB) starts immediately after [dendROS] and is never interrupted + by a bare reset — the node-identity island switches to its explicit bg and then + returns to _WB, so every space in the line (including between sections) is white. + \033[K fills the remainder of the console line with the white bg. + """ + old_part = f'{old_val} → ' if old_val is not None else '? → ' + + if code: + node_bg = _fg_to_bg(code) + if show_tag and label: + node_section = f'\033[{node_bg};30m[{label}] {node}{_WB}' + else: + node_section = f'\033[{node_bg};30m{node}{_WB}' + else: + node_section = node # untracked — plain black text on the white bg + + return ( + f'{_DENDROS_INV} param {node_section}' + f' \033[1m{name}\033[22m: {old_part}{value} \033[K{_RESET}\n' + ) + + +def drain(color_map, tag_map, style_map, tag_style: str, show_tag: bool, + alert_style: str = 'inline') -> list[str]: + """Pop all pending change events; return formatted notification lines (with \\n). + + Must be called from the main thread only. + """ + from lib.config_loader import resolve_node, resolve_node_style + + lines: list[str] = [] + while True: + try: + node, name, old_val, value = _queue.get_nowait() + except queue.Empty: + break + + code, label = resolve_node(node, color_map, tag_map) + node_style = (resolve_node_style(node, style_map) or tag_style) if code else tag_style + + if alert_style == 'inverted': + lines.append(_fmt_inverted(node, name, old_val, value, code, label, node_style, show_tag)) + else: + lines.append(_fmt_inline(node, name, old_val, value, code, label, node_style, show_tag)) + + return lines diff --git a/test/unit/conftest.py b/test/unit/conftest.py index 15ac633..c84fc03 100644 --- a/test/unit/conftest.py +++ b/test/unit/conftest.py @@ -23,11 +23,13 @@ # ── ANSI helpers ───────────────────────────────────────────────────────────── ANSI_RE = re.compile(r'\033\[([0-9;]*)m') +# Broader regex that strips any CSI sequence (e.g. \033[K for erase-to-EOL) +_FULL_ANSI_RE = re.compile(r'\033\[[^a-zA-Z]*[a-zA-Z]') def strip_ansi(s: str) -> str: """Remove all ANSI escape sequences from s.""" - return ANSI_RE.sub('', s) + return _FULL_ANSI_RE.sub('', s) def ansi_codes(s: str) -> list: diff --git a/test/unit/test_global_config.py b/test/unit/test_global_config.py index c63df2f..e4e4f70 100644 --- a/test/unit/test_global_config.py +++ b/test/unit/test_global_config.py @@ -90,6 +90,9 @@ def test_loads_existing_file(self, tmp_config): "traceback_color": "red", "tag_style": "inverted", "show_default_services": False, + "param_change_alert": True, + "param_change_alert_scope": "all", + "param_change_alert_style": "inverted", } with open(tmp_config, "w") as f: yaml.dump(data, f) @@ -172,6 +175,9 @@ def test_roundtrip_custom_values(self, tmp_config): "traceback_color": "off", "tag_style": "inverted", "show_default_services": False, + "param_change_alert": True, + "param_change_alert_scope": "all", + "param_change_alert_style": "inverted", } save_global_config(custom) result = load_global_config() diff --git a/test/unit/test_param_watcher.py b/test/unit/test_param_watcher.py new file mode 100644 index 0000000..7f3e1b8 --- /dev/null +++ b/test/unit/test_param_watcher.py @@ -0,0 +1,484 @@ +"""Tests for lib/param_watcher.py — param change notification subsystem.""" + +import os +import queue +import sys + +import pytest + +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +sys.path.insert(0, os.path.join(REPO_ROOT, 'dendROS')) + +import lib.param_watcher as pw +from conftest import strip_ansi + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +def _resolve_node_mock(node, color_map, tag_map): + """Minimal stand-in for lib.config_loader.resolve_node (exact basename match).""" + basename = node.rsplit('/', 1)[-1] + code = color_map.get(node) or color_map.get(basename) + label = tag_map.get(node) or tag_map.get(basename) if code else None + return code, label + + +def _make_param_event(node, changed=None, new=None, deleted=None): + """Build a list of YAML lines representing one ParameterEvent message.""" + lines = [f'node: {node}', 'stamp:', ' sec: 0', ' nanosec: 0'] + for section, params in [('new_parameters', new), ('changed_parameters', changed), + ('deleted_parameters', deleted)]: + if params: + lines.append(f'{section}:') + for name, type_id, val_field, val in params: + lines += [ + f'- name: {name}', + f' value:', + f' type: {type_id}', + f' {val_field}: {val}', + ] + else: + lines.append(f'{section}: []') + return lines + + +def _drain_all(color_map, tag_map, style_map=None, tag_style='normal', show_tag=True, + alert_style='inline'): + """Drain the queue using the real drain() but with a mock resolve_node.""" + return pw.drain(color_map, tag_map, style_map or {}, tag_style, show_tag, alert_style) + + +def _flush_queue(): + while True: + try: + pw._queue.get_nowait() + except queue.Empty: + break + + +# ── _extract_value ──────────────────────────────────────────────────────────── + +class TestExtractValue: + + def test_bool_true(self): + assert pw._extract_value({'type': 1, 'bool_value': True}) == 'true' + + def test_bool_false(self): + assert pw._extract_value({'type': 1, 'bool_value': False}) == 'false' + + def test_integer(self): + assert pw._extract_value({'type': 2, 'integer_value': 42}) == '42' + + def test_double(self): + assert pw._extract_value({'type': 3, 'double_value': 3.14}) == '3.14' + + def test_string(self): + assert pw._extract_value({'type': 4, 'string_value': 'hello'}) == 'hello' + + def test_bool_array(self): + result = pw._extract_value({'type': 6, 'bool_array_value': [True, False]}) + assert 'True' in result or 'False' in result + + def test_long_array_truncated(self): + big = list(range(100)) + result = pw._extract_value({'type': 7, 'integer_array_value': big}) + assert '…' in result + assert len(result) <= pw._MAX_VALUE_LEN + 2 + + def test_unknown_type_returns_empty(self): + assert pw._extract_value({'type': 99}) == '' + + def test_non_dict_input(self): + result = pw._extract_value('bare_string') + assert result == 'bare_string' + + +# ── _process_chunk ──────────────────────────────────────────────────────────── + +class TestProcessChunk: + + def setup_method(self): + _flush_queue() + pw._param_cache.clear() + + def _process(self, lines, color_map=None, tag_map=None, scope='tracked'): + cm = color_map or {} + tm = tag_map or {} + pw._process_chunk(lines, cm, tm, scope, _resolve_node_mock) + + def test_changed_param_enqueued(self): + lines = _make_param_event( + '/talker', + changed=[('use_sim_time', 1, 'bool_value', 'true')], + ) + color_map = {'talker': '32'} + self._process(lines, color_map) + node, name, old_val, value = pw._queue.get_nowait() + assert node == '/talker' + assert name == 'use_sim_time' + assert value == 'true' + assert old_val is None # first-ever change for this param + + def test_new_params_not_enqueued(self): + """new_parameters (startup declarations) must be silently ignored.""" + lines = _make_param_event( + '/talker', + new=[('use_sim_time', 1, 'bool_value', 'false')], + ) + self._process(lines, {'talker': '32'}) + assert pw._queue.empty() + + def test_deleted_params_not_enqueued(self): + lines = _make_param_event( + '/talker', + deleted=[('old_param', 4, 'string_value', 'gone')], + ) + self._process(lines, {'talker': '32'}) + assert pw._queue.empty() + + def test_cli_daemon_node_filtered(self): + lines = _make_param_event( + '/_ros2cli_12345', + changed=[('use_sim_time', 1, 'bool_value', 'false')], + ) + self._process(lines, {}) + assert pw._queue.empty() + + def test_untracked_node_filtered_in_tracked_scope(self): + lines = _make_param_event( + '/unknown_node', + changed=[('my_param', 2, 'integer_value', '5')], + ) + self._process(lines, {'talker': '32'}, scope='tracked') + assert pw._queue.empty() + + def test_untracked_node_passes_in_all_scope(self): + lines = _make_param_event( + '/unknown_node', + changed=[('my_param', 2, 'integer_value', '5')], + ) + self._process(lines, {'talker': '32'}, scope='all') + assert not pw._queue.empty() + node, name, old_val, value = pw._queue.get_nowait() + assert node == '/unknown_node' + assert name == 'my_param' + + def test_multiple_changed_params_all_enqueued(self): + lines = _make_param_event( + '/talker', + changed=[ + ('param_a', 1, 'bool_value', 'true'), + ('param_b', 2, 'integer_value', '7'), + ], + ) + self._process(lines, {'talker': '32'}) + assert pw._queue.qsize() == 2 + + def test_invalid_yaml_silently_ignored(self): + self._process(['not: valid: yaml: :::'], {'talker': '32'}) + # should not raise; queue stays empty + assert pw._queue.empty() + + def test_empty_node_field_filtered(self): + lines = ['node: ', 'changed_parameters:', '- name: p', ' value:', ' type: 1', + ' bool_value: true', 'new_parameters: []', 'deleted_parameters: []'] + self._process(lines, {}, scope='all') + assert pw._queue.empty() + + def test_namespaced_node_matched_by_basename(self): + lines = _make_param_event( + '/my_ns/talker', + changed=[('rate', 3, 'double_value', '10.0')], + ) + self._process(lines, {'talker': '32'}, scope='tracked') + assert not pw._queue.empty() + + def test_wildcard_pattern_matched(self): + """Wildcard patterns in color_map are resolved by _resolve_node_mock (exact only) + — this test verifies that a direct match reaches the queue.""" + lines = _make_param_event( + '/nav2_planner', + changed=[('max_vel', 3, 'double_value', '1.5')], + ) + self._process(lines, {'nav2_planner': '35'}, scope='tracked') + assert not pw._queue.empty() + + +# ── drain() formatting ──────────────────────────────────────────────────────── + +class TestDrain: + + def setup_method(self): + _flush_queue() + pw._param_cache.clear() + + def _push(self, node, name, value, old_val=None): + pw._queue.put((node, name, old_val, value)) + + def test_drain_empty_queue_returns_empty_list(self): + result = _drain_all({}, {}) + assert result == [] + + def test_notification_contains_node_name(self): + self._push('/talker', 'use_sim_time', 'true') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert len(lines) == 1 + assert '/talker' in strip_ansi(lines[0]) + + def test_notification_contains_param_name(self): + self._push('/talker', 'use_sim_time', 'true') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert 'use_sim_time' in strip_ansi(lines[0]) + + def test_notification_contains_value(self): + self._push('/talker', 'use_sim_time', 'true') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert 'true' in strip_ansi(lines[0]) + + def test_notification_contains_dendros_header(self): + self._push('/talker', 'rate', '10.0') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert '[dendROS]' in strip_ansi(lines[0]) + + def test_dendros_header_logo_colors(self): + """[dendROS] must split on the logo palette: [dend in logo-blue, ROS] in logo-orange.""" + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert '[dendROS]' in strip_ansi(lines[0]) + # Logo-blue (0,75,107) for '[dend' + assert '\033[38;2;0;75;107;1m[dend' in lines[0] + # Logo-orange (224,127,0) for 'ROS]' + assert '\033[38;2;224;127;0;1mROS]' in lines[0] + + def test_node_colored(self): + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert '\033[32m/talker\033[0m' in lines[0] + + def test_tag_shown_when_label_present(self): + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': 'TLK'}) + assert '[TLK]' in lines[0] + + def test_tag_hidden_when_show_tag_false(self): + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': 'TLK'}, show_tag=False) + assert '[TLK]' not in lines[0] + + def test_inverted_tag_style(self): + self._push('/talker', 'p', 'v') + lines = _drain_all( + {'talker': '32'}, {'talker': 'TLK'}, + style_map={'talker': 'inverted'}, tag_style='inverted', + ) + assert '\033[32;7m[TLK]\033[0m' in lines[0] + + def test_param_name_bold(self): + self._push('/talker', 'my_param', 'val') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert '\033[1mmy_param\033[0m' in lines[0] + + def test_param_keyword_plain_white(self): + """'param' keyword must appear without any ANSI color code (plain/white).""" + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert 'param' in strip_ansi(lines[0]) + assert '\033[35mparam\033[0m' not in lines[0] + assert '\033[2mparam\033[0m' not in lines[0] + + def test_untracked_node_no_color(self): + """In 'all' mode, untracked nodes appear without ANSI color wrapping the node name.""" + self._push('/unknown_node', 'p', 'v') + lines = _drain_all({}, {}) + assert '/unknown_node' in lines[0] + import re + assert not re.search(r'\033\[[0-9;]+m/unknown_node\033\[0m', lines[0]) + + def test_multiple_events_all_drained(self): + for i in range(5): + self._push('/talker', f'param_{i}', str(i)) + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert len(lines) == 5 + + def test_line_ends_with_newline(self): + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert lines[0].endswith('\n') + + def test_arrow_separator_present(self): + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert '→' in lines[0] + + def test_unknown_old_value_shown_as_question_mark(self): + """First-ever change (old_val=None) should show '? →' in output.""" + self._push('/talker', 'p', 'new', old_val=None) + lines = _drain_all({'talker': '32'}, {'talker': ''}) + assert '? →' in strip_ansi(lines[0]) + + def test_known_old_value_shown(self): + self._push('/talker', 'p', 'new', old_val='old') + lines = _drain_all({'talker': '32'}, {'talker': ''}) + plain = strip_ansi(lines[0]) + assert 'old → new' in plain + + def test_process_chunk_caches_and_reports_old_value(self): + """Second change via _process_chunk should carry the first change's value as old.""" + import queue as _queue_mod + pw._param_cache.clear() + _flush_queue() + + def _resolve(node, cm, tm): + return cm.get(node.rsplit('/', 1)[-1]), None + + lines1 = _make_param_event('/talker', changed=[('rate', 3, 'double_value', '5.0')]) + pw._process_chunk(lines1, {'talker': '32'}, {}, 'all', _resolve) + _, _, old1, val1 = pw._queue.get_nowait() + assert old1 is None + assert val1 == '5.0' + + lines2 = _make_param_event('/talker', changed=[('rate', 3, 'double_value', '10.0')]) + pw._process_chunk(lines2, {'talker': '32'}, {}, 'all', _resolve) + _, _, old2, val2 = pw._queue.get_nowait() + assert old2 == '5.0' + assert val2 == '10.0' + + +# ── Inverted alert style ────────────────────────────────────────────────────── + +class TestInvertedAlertStyle: + + def setup_method(self): + _flush_queue() + pw._param_cache.clear() + + def _push(self, node, name, value, old_val=None): + pw._queue.put((node, name, old_val, value)) + + def test_inverted_sections_use_correct_backgrounds(self): + """Node identity uses explicit node-color bg; everything else on white bg.""" + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + # Node identity: explicit green bg + black text (_fg_to_bg('32') == '42') + assert '\033[42;30m' in lines[0] + # White bg present (for param section and separating spaces) + assert '\033[107;30m' in lines[0] + # No reverse-video for node identity + assert '\033[32;7m' not in lines[0] + + def test_inverted_contains_node_name(self): + self._push('/talker', 'use_sim_time', 'true') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert '/talker' in strip_ansi(lines[0]) + + def test_inverted_contains_param_name(self): + self._push('/talker', 'use_sim_time', 'true') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert 'use_sim_time' in strip_ansi(lines[0]) + + def test_inverted_contains_value(self): + self._push('/talker', 'use_sim_time', 'true') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert 'true' in strip_ansi(lines[0]) + + def test_inverted_tag_and_node_use_explicit_node_bg(self): + """Tag and node name together in one colored-bg island (_fg_to_bg of node code).""" + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': 'TLK'}, alert_style='inverted') + assert '[TLK]' in strip_ansi(lines[0]) + # _fg_to_bg('32') == '42' → green bg + black text; returns to white bg (_WB) after + assert '\033[42;30m[TLK] /talker\033[107;30m' in lines[0] + + def test_inverted_tag_hidden_when_show_tag_false(self): + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': 'TLK'}, + show_tag=False, alert_style='inverted') + assert '[TLK]' not in strip_ansi(lines[0]) + + def test_inverted_no_param_bold_marker(self): + """Inverted block wraps everything; no standalone bold param marker needed.""" + self._push('/talker', 'my_param', 'val') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + # Content must still contain the param name + assert 'my_param' in strip_ansi(lines[0]) + + def test_inverted_untracked_uses_explicit_white_bg(self): + """Untracked nodes (all mode) get the white bg strip; no reverse-video or color bg.""" + self._push('/unknown', 'p', 'v') + lines = _drain_all({}, {}, alert_style='inverted') + assert '\033[107;30m' in lines[0] # white bg present + assert '\033[7m' not in lines[0] # no reverse-video at all + # No node-color bg escape (no fg→bg conversion applied for untracked) + import re + assert not re.search(r'\033\[4[0-9];30m', lines[0]) + + def test_inverted_block_reset_at_end(self): + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert '\033[0m' in lines[0] + + def test_inverted_extends_to_eol_with_erase_sequence(self): + """\\033[K (erase to EOL) must be present to fill background to console edge.""" + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert '\033[K' in lines[0] + + def test_inverted_dendros_header_present(self): + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert '[dendROS]' in strip_ansi(lines[0]) + + def test_inverted_dendros_header_logo_colors(self): + """[dend on logo-blue bg, ROS] on logo-orange bg; black text (hollow/cutout).""" + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert '\033[48;2;0;75;107;1m' in lines[0] # logo-blue background + assert '\033[48;2;224;127;0;1m' in lines[0] # logo-orange background + # Black text applied before [dend (hollow cutout look) + assert '\033[48;2;0;75;107;1m\033[30m[dend' in lines[0] + + def test_inverted_arrow_present(self): + self._push('/talker', 'p', 'v') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert '→' in strip_ansi(lines[0]) + + def test_inverted_same_content_as_inline(self): + """Both styles must expose the same semantic content in plain text.""" + self._push('/talker', 'use_sim_time', 'true', old_val='false') + inline_lines = _drain_all({'talker': '32'}, {'talker': 'CTR'}, alert_style='inline') + self._push('/talker', 'use_sim_time', 'true', old_val='false') + inv_lines = _drain_all({'talker': '32'}, {'talker': 'CTR'}, alert_style='inverted') + assert strip_ansi(inline_lines[0]).strip() == strip_ansi(inv_lines[0]).strip() + + def test_inverted_old_value_unknown_shows_question_mark(self): + self._push('/talker', 'p', 'new', old_val=None) + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert '? →' in strip_ansi(lines[0]) + + def test_inverted_known_old_value_shown(self): + self._push('/talker', 'p', 'new', old_val='old') + lines = _drain_all({'talker': '32'}, {'talker': ''}, alert_style='inverted') + assert 'old → new' in strip_ansi(lines[0]) + + +# ── Global config defaults ──────────────────────────────────────────────────── + +class TestGlobalConfigDefaults: + + def test_param_change_alert_default_false(self): + from lib.global_config import DEFAULTS + assert DEFAULTS['param_change_alert'] is False + + def test_param_change_alert_scope_default_tracked(self): + from lib.global_config import DEFAULTS + assert DEFAULTS['param_change_alert_scope'] == 'tracked' + + def test_param_change_alert_style_default_inline(self): + from lib.global_config import DEFAULTS + assert DEFAULTS['param_change_alert_style'] == 'inline' + + def test_all_keys_present_in_defaults(self): + from lib.global_config import DEFAULTS + assert 'param_change_alert' in DEFAULTS + assert 'param_change_alert_scope' in DEFAULTS + assert 'param_change_alert_style' in DEFAULTS From 8dd1c1f0d8217aece76217893af269fc631e2152 Mon Sep 17 00:00:00 2001 From: mlisi1 Date: Wed, 24 Jun 2026 16:37:47 +0200 Subject: [PATCH 4/4] updated docs and readme --- README.md | 24 +++++ .../images/screenshots/param_alert_inline.png | Bin 0 -> 31651 bytes .../screenshots/param_alert_inverted.png | Bin 0 -> 32995 bytes .../images/screenshots/param_describe.png | Bin 0 -> 28144 bytes docs/assets/images/screenshots/param_list.png | Bin 0 -> 100662 bytes docs/global-config.md | 13 ++- docs/index.md | 15 +++ docs/param-change-alert.md | 93 ++++++++++++++++++ docs/param-describe.md | 37 +++++++ docs/param-list.md | 40 ++++++++ docs/reference.md | 5 + mkdocs.yml | 3 + test/unit/test_param_watcher.py | 4 +- 13 files changed, 231 insertions(+), 3 deletions(-) create mode 100644 docs/assets/images/screenshots/param_alert_inline.png create mode 100644 docs/assets/images/screenshots/param_alert_inverted.png create mode 100644 docs/assets/images/screenshots/param_describe.png create mode 100644 docs/assets/images/screenshots/param_list.png create mode 100644 docs/param-change-alert.md create mode 100644 docs/param-describe.md create mode 100644 docs/param-list.md diff --git a/README.md b/README.md index 1a25499..f3f1b76 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,30 @@ It also features some quality of life improvements for ROS outputs. ros2 action list

+- ### **```ros2 param list``` coloring** + Node headers are colored with the group badge; parameter names are rendered dim so the structure is easy to scan. `--param-type` type annotations are dimmed automatically. + +

+ros2 param list +

+ +- ### **```ros2 param describe``` coloring** + The group badge and parameter name are highlighted; field labels (`Type:`, `Description:`) are dimmed; section headers like `Constraints:` are bolded so the output structure is immediately readable. + +

+ros2 param describe +

+ +- ### **Parameter change alert** + When a node's parameter changes at runtime — via `ros2 param set` or any parameter service client — an inline notification appears in the launch terminal showing the node, parameter name, and old→new value. Two styles: compact inline and a full-width inverted block that's hard to miss in busy logs. + +

+param change alert inline +

+

+param change alert inverted +

+ - ### **Truly non-invasive** Shell-level pipe; you won't loose autocompletion or aliases for launch files diff --git a/docs/assets/images/screenshots/param_alert_inline.png b/docs/assets/images/screenshots/param_alert_inline.png new file mode 100644 index 0000000000000000000000000000000000000000..ee49d547c8142b5d78da97a2b8ab2443596b992b GIT binary patch literal 31651 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 -9NVT6Wn6zcqJ(%n!jeY z^MmPnf*@_+u3;@qyspnH3tIN{rU==gFm&SpgX9RpPkr4i+Z2kWv#8Dnnt9I>^4gKGlbfhPu sZ}8Pi|JKa?@BFy}zwG~icHJA{2fqE7&17@<=Y(G*L}Z1l1oZ>|7k_3p-v9sr literal 0 HcmV?d00001 diff --git a/docs/assets/images/screenshots/param_describe.png b/docs/assets/images/screenshots/param_describe.png new file mode 100644 index 0000000000000000000000000000000000000000..3cb91a901477d3f2e8c068067342ccd20bb3bc7e GIT binary patch literal 28144 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 literal 0 HcmV?d00001 diff --git a/docs/assets/images/screenshots/param_list.png b/docs/assets/images/screenshots/param_list.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc598934da1c78bf375ffd7a67c2c61296156f1 GIT binary patch literal 100662 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
+
📝
+ros2 param list colors +

Node headers colored with group badges; parameter names dimmed. Type annotations from --param-type rendered dim automatically.

+
+
+
🔬
+ros2 param describe colors +

Badge and bold colored param name at the top; field labels dimmed; section headers like Constraints: bolded for instant readability.

+
+
+
🔔
+Parameter change alert +

Inline notification whenever a node's parameter changes at runtime — shows node, param name, and old→new value directly in the launch log.

+
+
🚫
Truly non-invasive

No launch file changes. Exit codes preserved. DENDROS_DISABLE=1 bypasses everything instantly.

diff --git a/docs/param-change-alert.md b/docs/param-change-alert.md new file mode 100644 index 0000000..8227b0d --- /dev/null +++ b/docs/param-change-alert.md @@ -0,0 +1,93 @@ +# Parameter Change Alert + +When a node's parameter is changed at runtime via `ros2 param set` or any ROS 2 parameter service client, DendROS prints an inline notification in the launch terminal — so you always know when and what changed without leaving the log view. + +Enabled by default. Disable with: + +```bash +dendros config # → "Param change alert" → off +``` + +or in `~/.config/dendROS/defaults.yaml`: + +```yaml +param_change_alert: false +``` + +--- + +## What it looks like + +### Inline + +
+
+
+
+
+
+
+
ros2 launch my_pkg bringup.launch.py
+
+
+

+Inline parameter change alert +

+
+
+ +### Inverted + +
+
+
+
+
+
+
+
ros2 launch my_pkg bringup.launch.py
+
+
+

+Inverted parameter change alert +

+
+
+ +--- + +## Scope + +| `param_change_alert_scope` | Behavior | +|---|---| +| `tracked` (default) | Only nodes that appear in a `dendROS.yaml` group generate notifications | +| `all` | Every node on the ROS graph generates notifications, including unmatched nodes | + +--- + +## Alert styles + +| `param_change_alert_style` | Appearance | +|---|---| +| `inline` (default) | Compact single line: `[dendROS]` tag + colored node identity + bold param name | +| `inverted` | Full white-background strip from `[dendROS]` to end of line; node identity shown as a colored background island; harder to miss in busy logs | + +--- + +## Configuring via `dendros config` + +| TUI field | Values | Description | +|---|---|---| +| **Param change alert** | `on` / `off` | Enable or disable the feature. | +| **Param alert scope** | `tracked` / `all` | Which nodes trigger notifications. | +| **Param alert style** | `inline` / `inverted` | Visual style of the notification line. | + +--- + +## Notes + +- Notifications are delayed until the next log line from the launch process arrives (drain is called after each line). If the process is silent, notifications queue up and appear on the next output. +- Transient CLI daemon nodes (`/_ros2cli_*`) are always filtered out regardless of scope. +- Startup parameter declarations (`new_parameters`) are ignored — only `changed_parameters` events generate alerts. +- The background thread terminates automatically when the launch process exits. +- Covers: `ros2 param set`, programmatic `node->set_parameters()`, and any parameter service client that goes through the ROS 2 parameter service. diff --git a/docs/param-describe.md b/docs/param-describe.md new file mode 100644 index 0000000..26bd981 --- /dev/null +++ b/docs/param-describe.md @@ -0,0 +1,37 @@ +# ros2 param describe Colorization + +When you run `ros2 param describe /node param_name`, DendROS colorizes the output: the group badge and param name are highlighted, labels are dimmed so they recede, and section headers like `Constraints:` are rendered bold so the structure is immediately readable. + +--- + +## What it looks like + +
+
+
+
+
+
+
+
ros2 param describe /battery_monitor start_type_description_service
+
+
+

+ros2 param describe colorized output +

+
+
+ +--- + + +## Badge and style options + +| Setting | Effect on param describe | +|---|---| +| `show_tag_cli: true` | Badge shown to the left of the `Parameter name:` line | +| `tag_style: inverted` | Badge rendered with colored background | +| `unmatched_color` | Unmatched node uses the fallback color for the param name | +| `dim_unmatched` | Unmatched param name dimmed (only when `unmatched_color: null`) | + +--- diff --git a/docs/param-list.md b/docs/param-list.md new file mode 100644 index 0000000..73746c9 --- /dev/null +++ b/docs/param-list.md @@ -0,0 +1,40 @@ +# ros2 param list Colorization + +When you run `ros2 param list`, DendROS automatically colorizes node headers with your group colors and badges, and dims the parameter names so the structure is easier to scan at a glance. The `--param-type` flag is fully supported. + +--- + +## What it looks like + +
+
+
+
+
+
+
+
ros2 param list --param-type
+
+
+

+ros2 param list colorized output +

+
+
+ +--- + + +## Badge and style options + +| Setting | Effect on param list | +|---|---| +| `show_tag_cli: true` | Badge shown to the left of the node header | +| `tag_style: inverted` | Badge rendered with colored background | +| Per-group `show_tag: false` | Badge suppressed for that group only | +| `unmatched_color` | Unmatched node headers shown in the fallback color | +| `unmatched_tag` | Badge shown next to unmatched headers (requires `unmatched_color`) | +| `dim_unmatched` | Unmatched headers dimmed (only when `unmatched_color: null`) | + +--- + diff --git a/docs/reference.md b/docs/reference.md index ef25e2c..6a073b2 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -76,6 +76,9 @@ Stored in `~/.config/dendROS/defaults.yaml`, managed via `dendros config`: | `crash_alert_color` | `node` | `node` = use group color; `red` = always bold red. | | `crash_alert_interval` | `30` | Seconds between periodic banner reprints. `0` = only on new crashes. | | `traceback_color` | `fancy` | `fancy` = bold red header + dim red frames; `red` = all bold red; `off` = passthrough. | +| `param_change_alert` | `true` | Print inline notification on runtime parameter changes. | +| `param_change_alert_scope` | `tracked` | `tracked` = only config-listed nodes; `all` = entire graph. | +| `param_change_alert_style` | `inline` | `inline` = compact line; `inverted` = white-background strip. | | `init_modify_build` | `true` | `dendros init`: patch build files. | | `init_on_existing` | `abort` | `dendros init`: `abort`, `merge`, or `overwrite`. | | `init_color` | `palette` | `dendros init`: `palette` or `null`. | @@ -96,6 +99,8 @@ The `ros2()` shell wrapper intercepts specific subcommands; everything else call | `ros2 node info …` | Output piped through `dendros_node_info.py` — node name, sections, and entries colorized by group. See [ros2 node info](node-info.md). | | `ros2 service list` | Output piped through `dendros_service_list.py` — services colored by owning node; default system services dimmed. See [ros2 service list](service-list.md). | | `ros2 action list` | Output piped through `dendros_action_list.py` — actions colored by owning node. See [ros2 action list](action-list.md). | +| `ros2 param list` | Output piped through `dendros_param_list.py` — node headers colored, param lines dimmed. See [ros2 param list](param-list.md). | +| `ros2 param describe` | Output piped through `dendros_param_describe.py` — badge + colored param name, dim labels, bold section headers. See [ros2 param describe](param-describe.md). | | Everything else | Passed directly to the real `ros2` binary, untouched. | --- diff --git a/mkdocs.yml b/mkdocs.yml index d7bad7e..2874ae2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -75,10 +75,13 @@ nav: - Features: - Crash Alert: crash-alert.md - Traceback Highlighting: traceback-highlighting.md + - Parameter Change Alert: param-change-alert.md - ros2 node list: node-list.md - ros2 node info: node-info.md - ros2 service list: service-list.md - ros2 action list: action-list.md + - ros2 param list: param-list.md + - ros2 param describe: param-describe.md - Tools: - dendros init: dendros-init.md - dendros config: global-config.md diff --git a/test/unit/test_param_watcher.py b/test/unit/test_param_watcher.py index 7f3e1b8..26e6086 100644 --- a/test/unit/test_param_watcher.py +++ b/test/unit/test_param_watcher.py @@ -465,9 +465,9 @@ def test_inverted_known_old_value_shown(self): class TestGlobalConfigDefaults: - def test_param_change_alert_default_false(self): + def test_param_change_alert_default_true(self): from lib.global_config import DEFAULTS - assert DEFAULTS['param_change_alert'] is False + assert DEFAULTS['param_change_alert'] is True def test_param_change_alert_scope_default_tracked(self): from lib.global_config import DEFAULTS

M~(QU?P&JqSj|bd#2^(e+7wzfD1Ls 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 list output colored by owning node group, with the same badge and style options available in all other CLI commands.