mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-09-17 05:04:19 +00:00
Enhance path formatting in MultitestCommand with new grouping logic
- Updated the path formatting logic in `MultitestCommand` to support nested branches using shared prefixes and horizontal continuations. - Introduced new utility functions for grouping and formatting suffix lines, improving the clarity of output paths. - Modified the configuration example to clarify the behavior of the `condense_paths` option. - Expanded test coverage to validate the new path formatting behavior, ensuring accurate representation of unique paths.
This commit is contained in:
+1
-1
@@ -974,7 +974,7 @@ enabled = false
|
||||
# Leave empty to use default format
|
||||
# Example: "Found {path_count} unique path(s) for @[{sender}]:\n{paths}"
|
||||
response_format = @[{sender}] found {path_count} unique path(s):\n{paths}
|
||||
# When true, {paths} uses shared-prefix lines plus tree branches (├ U+251C / └ U+2514) instead of repeating full paths
|
||||
# When true, {paths} uses shared-prefix lines plus tree branches (├ U+251C / └ U+2514; continuations may use ├─ / └─ with U+2500)
|
||||
condense_paths = false
|
||||
|
||||
[Greeter_Command]
|
||||
|
||||
@@ -7,6 +7,7 @@ Listens for a period of time and collects all unique paths from incoming message
|
||||
import asyncio
|
||||
import re
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
@@ -16,10 +17,14 @@ from .base_command import BaseCommand
|
||||
|
||||
_BRANCH_INTER = "\u251c" # ├ (intermediate branch)
|
||||
_BRANCH_LAST = "\u2514" # └ (last branch)
|
||||
_HORIZ = "\u2500" # ─ (BOX DRAWINGS LIGHT HORIZONTAL)
|
||||
# Second-level branches: ├─ / └─ (horizontal continues the tee)
|
||||
_BRANCH_CHILD_INTER = f"{_BRANCH_INTER}{_HORIZ} "
|
||||
_BRANCH_CHILD_LAST = f"{_BRANCH_LAST}{_HORIZ} "
|
||||
|
||||
|
||||
def _tree_branch_lines(suffixes: list[str]) -> list[str]:
|
||||
"""Format branch rows: ├ for all but the last, └ for the last; space after the tree char."""
|
||||
def _tree_branch_lines_flat(suffixes: list[str]) -> list[str]:
|
||||
"""Format branch rows: ├ for all but the last, └ for the last; space after the tee only."""
|
||||
if not suffixes:
|
||||
return []
|
||||
n = len(suffixes)
|
||||
@@ -30,6 +35,61 @@ def _tree_branch_lines(suffixes: list[str]) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
def _grouped_suffix_line_specs(non_empty: list[list[str]]) -> list[tuple[str, str]]:
|
||||
"""Build (line_kind, text) rows: 'head' uses ├/└ + space; 'nest' uses ├─/└─ + text."""
|
||||
by_first: dict[str, list[list[str]]] = defaultdict(list)
|
||||
for suf in non_empty:
|
||||
by_first[suf[0]].append(suf[1:])
|
||||
|
||||
specs: list[tuple[str, str]] = []
|
||||
for ft in sorted(by_first.keys()):
|
||||
rests = by_first[ft]
|
||||
if len(rests) == 1:
|
||||
r = rests[0]
|
||||
if not r:
|
||||
specs.append(("head", ft))
|
||||
else:
|
||||
specs.append(("head", ",".join([ft, *r])))
|
||||
continue
|
||||
specs.append(("head", ft))
|
||||
for rem in sorted((r for r in rests if r), key=lambda x: ",".join(x)):
|
||||
specs.append(("nest", ",".join(rem)))
|
||||
return specs
|
||||
|
||||
|
||||
def _apply_tee_prefixes(specs: list[tuple[str, str]]) -> list[str]:
|
||||
"""Assign ├/└ and ├─/└─ from flattened order; only the final row uses └/└─."""
|
||||
n = len(specs)
|
||||
out: list[str] = []
|
||||
for i, (kind, text) in enumerate(specs):
|
||||
last = i == n - 1
|
||||
if kind == "head":
|
||||
p = _BRANCH_LAST if last else _BRANCH_INTER
|
||||
out.append(f"{p} {text}")
|
||||
else:
|
||||
p = _BRANCH_CHILD_LAST if last else _BRANCH_CHILD_INTER
|
||||
out.append(f"{p}{text}")
|
||||
return out
|
||||
|
||||
|
||||
def _format_suffix_branch_lines(suffix_tokens: list[list[str]], short_ellipsis: bool) -> list[str]:
|
||||
"""Format suffixes after display LCP: group by first hop, nest continuations with U+2500."""
|
||||
non_empty = [s for s in suffix_tokens if s]
|
||||
if not non_empty:
|
||||
return _tree_branch_lines_flat(["..."]) if short_ellipsis else []
|
||||
|
||||
if len(non_empty) == 1:
|
||||
parts = [",".join(non_empty[0])]
|
||||
if short_ellipsis:
|
||||
parts.append("...")
|
||||
return _tree_branch_lines_flat(parts)
|
||||
|
||||
specs = _grouped_suffix_line_specs(non_empty)
|
||||
if short_ellipsis:
|
||||
specs.append(("head", "..."))
|
||||
return _apply_tee_prefixes(specs)
|
||||
|
||||
|
||||
def _path_to_tokens(path: str) -> list[str]:
|
||||
"""Split a comma-separated path into non-empty hex token strings."""
|
||||
return [p.strip() for p in path.split(",") if p.strip()]
|
||||
@@ -62,8 +122,32 @@ def _longest_common_prefix(token_lists: list[list[str]]) -> list[str]:
|
||||
return list(first[: min(len(tl) for tl in token_lists)])
|
||||
|
||||
|
||||
def _shrink_display_lcp(maximal: list[list[str]], lcp: list[str]) -> list[str]:
|
||||
"""Shorten displayed LCP when one path ends exactly at LCP and another continues past it.
|
||||
|
||||
Avoids showing the shorter path as the 'trunk' with only the tail as a branch (e.g. …0101
|
||||
on the common line and └ 0970), which reads like a single endpoint plus an offshoot.
|
||||
"""
|
||||
lcp = list(lcp)
|
||||
while len(lcp) > 1:
|
||||
has_exact = any(t == lcp for t in maximal)
|
||||
has_extend = any(_is_strict_prefix(lcp, t) for t in maximal)
|
||||
if has_exact and has_extend:
|
||||
lcp.pop()
|
||||
else:
|
||||
break
|
||||
return lcp
|
||||
|
||||
|
||||
def _format_path_cluster(token_lists: list[list[str]], use_brackets: bool) -> list[str]:
|
||||
"""Format a cluster of token lists into condensed lines (common prefix + ├/└ branches)."""
|
||||
"""Format a cluster into condensed lines (common prefix + ├/└ and ├─/└─).
|
||||
|
||||
Suffixes are grouped by their first hop after the display LCP; multiple variants under the same
|
||||
hop use one ├ line for that hop and ├─/└─ for continuations. The last line of the block uses └/└─.
|
||||
|
||||
If one path stops exactly where another continues, the displayed LCP is shortened so the shared
|
||||
segment is not mistaken for a single endpoint (e.g. only └ tail after a full shorter path).
|
||||
"""
|
||||
token_lists = [t for t in token_lists if t]
|
||||
if not token_lists:
|
||||
return []
|
||||
@@ -75,29 +159,24 @@ def _format_path_cluster(token_lists: list[list[str]], use_brackets: bool) -> li
|
||||
short_ellipsis = len(maximal) < len(token_lists)
|
||||
|
||||
if not maximal:
|
||||
return _tree_branch_lines(["..."]) if short_ellipsis else []
|
||||
return _tree_branch_lines_flat(["..."]) if short_ellipsis else []
|
||||
|
||||
lcp = _longest_common_prefix(maximal)
|
||||
raw_lcp = _longest_common_prefix(maximal)
|
||||
lcp = _shrink_display_lcp(maximal, raw_lcp)
|
||||
|
||||
if len(lcp) > 0:
|
||||
suffix_tokens = [t[len(lcp) :] for t in maximal]
|
||||
lines = [",".join(lcp)]
|
||||
branches: list[str] = []
|
||||
for t in maximal:
|
||||
suf = t[len(lcp) :]
|
||||
if suf:
|
||||
branches.append(",".join(suf))
|
||||
suffix_parts = sorted(branches)
|
||||
if short_ellipsis:
|
||||
suffix_parts.append("...")
|
||||
if suffix_parts:
|
||||
lines.extend(_tree_branch_lines(suffix_parts))
|
||||
branch_lines = _format_suffix_branch_lines(suffix_tokens, short_ellipsis)
|
||||
if branch_lines:
|
||||
lines.extend(branch_lines)
|
||||
return lines
|
||||
|
||||
if len(maximal) == 1:
|
||||
s = ",".join(maximal[0])
|
||||
lines = [f"[{s}]"] if use_brackets else [s]
|
||||
if short_ellipsis:
|
||||
lines.extend(_tree_branch_lines(["..."]))
|
||||
lines.extend(_tree_branch_lines_flat(["..."]))
|
||||
return lines
|
||||
|
||||
groups: dict[str, list[list[str]]] = {}
|
||||
@@ -110,7 +189,7 @@ def _format_path_cluster(token_lists: list[list[str]], use_brackets: bool) -> li
|
||||
sub_lines = _format_path_cluster(groups[ft], use_brackets=multi)
|
||||
lines.extend(sub_lines)
|
||||
if short_ellipsis:
|
||||
lines.extend(_tree_branch_lines(["..."]))
|
||||
lines.extend(_tree_branch_lines_flat(["..."]))
|
||||
return lines
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ from tests.conftest import mock_message
|
||||
|
||||
_INTER = "\u251c"
|
||||
_LAST = "\u2514"
|
||||
_HORIZ = "\u2500"
|
||||
_CHILD_INTER = f"{_INTER}{_HORIZ} "
|
||||
_CHILD_LAST = f"{_LAST}{_HORIZ} "
|
||||
|
||||
|
||||
def _make_bot():
|
||||
@@ -162,7 +165,7 @@ class TestCondensePathLines:
|
||||
"e6,0c,85,82,28,1a,cd,7e",
|
||||
f"{_INTER} 01",
|
||||
f"{_INTER} 7a",
|
||||
f"{_INTER} 7a,09",
|
||||
f"{_CHILD_INTER}09",
|
||||
f"{_LAST} ...",
|
||||
]
|
||||
)
|
||||
@@ -177,11 +180,124 @@ class TestCondensePathLines:
|
||||
]
|
||||
)
|
||||
out = _condense_path_lines(paths)
|
||||
# LCP shrinks so cc is not the whole “trunk” while dd/ee branch off
|
||||
expected = "\n".join(
|
||||
[
|
||||
"aa,bb,cc",
|
||||
f"{_INTER} dd",
|
||||
f"{_LAST} ee",
|
||||
"aa,bb",
|
||||
f"{_INTER} cc",
|
||||
f"{_CHILD_INTER}dd",
|
||||
f"{_CHILD_LAST}ee",
|
||||
]
|
||||
)
|
||||
assert out == expected
|
||||
|
||||
def test_one_path_ends_at_lcp_other_extends(self):
|
||||
"""Shorter LCP so both 0101 and 0101,0970 appear as branches, not one hidden on the trunk."""
|
||||
paths = sorted(["cdf1,7e76,0101", "cdf1,7e76,0101,0970"])
|
||||
out = _condense_path_lines(paths)
|
||||
expected = "\n".join(
|
||||
[
|
||||
"cdf1,7e76",
|
||||
f"{_INTER} 0101",
|
||||
f"{_CHILD_LAST}0970",
|
||||
]
|
||||
)
|
||||
assert out == expected
|
||||
|
||||
def test_overlapping_suffix_branches_under_common_prefix(self):
|
||||
paths = sorted(
|
||||
[
|
||||
"cdf119,860cca,010101",
|
||||
"cdf119,860cca,e0eed9",
|
||||
"cdf119,860cca,e0eed9,1ed612",
|
||||
]
|
||||
)
|
||||
out = _condense_path_lines(paths)
|
||||
expected = "\n".join(
|
||||
[
|
||||
"cdf119,860cca",
|
||||
f"{_INTER} 010101",
|
||||
f"{_INTER} e0eed9",
|
||||
f"{_CHILD_LAST}1ed612",
|
||||
]
|
||||
)
|
||||
assert out == expected
|
||||
|
||||
def test_divergent_routes_with_shared_mid_prefix(self):
|
||||
"""TRM-style: group by first hop (13) so 01 vs 01,1e nest under ├─."""
|
||||
paths = sorted(["41,96,13,01", "41,96,13,01,1e", "41,96,83,09"])
|
||||
out = _condense_path_lines(paths)
|
||||
expected = "\n".join(
|
||||
[
|
||||
"41,96",
|
||||
f"{_INTER} 13",
|
||||
f"{_CHILD_INTER}01",
|
||||
f"{_CHILD_INTER}01,1e",
|
||||
f"{_LAST} 83,09",
|
||||
]
|
||||
)
|
||||
assert out == expected
|
||||
|
||||
def test_mixed_first_hops_nest_per_group(self):
|
||||
"""Ill Eagle-style: 01 vs 01,1e share a group; 09 and e0 are separate top-level branches."""
|
||||
paths = sorted(
|
||||
[
|
||||
"e2,ab,1f,ef,55,21,01",
|
||||
"e2,ab,1f,ef,55,21,01,1e",
|
||||
"e2,ab,1f,ef,55,21,09",
|
||||
"e2,ab,1f,ef,55,21,e0",
|
||||
]
|
||||
)
|
||||
out = _condense_path_lines(paths)
|
||||
expected = "\n".join(
|
||||
[
|
||||
"e2,ab,1f,ef,55,21",
|
||||
f"{_INTER} 01",
|
||||
f"{_CHILD_INTER}1e",
|
||||
f"{_INTER} 09",
|
||||
f"{_LAST} e0",
|
||||
]
|
||||
)
|
||||
assert out == expected
|
||||
|
||||
def test_shorter_path_one_extra_hop_still_trees(self):
|
||||
"""860cca vs 860cca,010101: shrink trunk so both show as branches."""
|
||||
paths = sorted(
|
||||
[
|
||||
"d38a05,c4a86a,067b75,cafee0,1ffbd6,e8154b,860cca,010101",
|
||||
"d38a05,c4a86a,067b75,cafee0,1ffbd6,e8154b,860cca",
|
||||
]
|
||||
)
|
||||
out = _condense_path_lines(paths)
|
||||
expected = "\n".join(
|
||||
[
|
||||
"d38a05,c4a86a,067b75,cafee0,1ffbd6,e8154b",
|
||||
f"{_INTER} 860cca",
|
||||
f"{_CHILD_LAST}010101",
|
||||
]
|
||||
)
|
||||
assert out == expected
|
||||
|
||||
def test_shared_hop_then_horiz_continuations(self):
|
||||
"""All paths share first hop after LCP → one ├ hop line then ├─/└─ remainders (U+2500)."""
|
||||
paths = sorted(
|
||||
[
|
||||
"d38a05,479198,a837bc,7e7662,e0eed9",
|
||||
"d38a05,479198,a837bc,7e7662,e0eed9,010101",
|
||||
"d38a05,479198,a837bc,7e7662,e0eed9,0970d6",
|
||||
"d38a05,479198,a837bc,7e7662,e0eed9,1ed612",
|
||||
"d38a05,479198,a837bc,7e7662,e0eed9,f",
|
||||
]
|
||||
)
|
||||
out = _condense_path_lines(paths)
|
||||
expected = "\n".join(
|
||||
[
|
||||
"d38a05,479198,a837bc,7e7662",
|
||||
f"{_INTER} e0eed9",
|
||||
f"{_CHILD_INTER}010101",
|
||||
f"{_CHILD_INTER}0970d6",
|
||||
f"{_CHILD_INTER}1ed612",
|
||||
f"{_CHILD_LAST}f",
|
||||
]
|
||||
)
|
||||
assert out == expected
|
||||
|
||||
Reference in New Issue
Block a user