Skip to content

Commit e9ceaf2

Browse files
committed
Fix Reckless search command not finding partial matches
The Reckless search command was only returning a result if you searched a perfect match, which is not too helpful. This updates the command so that partial search matches return a result. Before: reckless search bolt Search exhausted all sources reckless search bol Search exhausted all sources reckless search bolt12-pris Search exhausted all sources After: reckless search bolt Plugins matching 'bolt': bolt12-prism (https://github.com/lightningd/plugins) reckless search bol Plugins matching 'bol': bolt12-prism (https://github.com/lightningd/plugins) reckless search bolt12-pris Plugins matching 'bolt12-pris': bolt12-prism (https://github.com/lightningd/plugins)
1 parent c03288c commit e9ceaf2

File tree

2 files changed

+79
-9
lines changed

2 files changed

+79
-9
lines changed

tests/test_reckless.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,32 @@ def test_search(node_factory):
224224
assert r.search_stdout('found testplugpass in source: https://github.com/lightningd/plugins')
225225

226226

227+
def test_search_partial_match(node_factory):
228+
"""test that partial/substring search returns multiple matches"""
229+
n = get_reckless_node(node_factory)
230+
231+
# Search for partial name "testplug" - should find all test plugins
232+
r = reckless([f"--network={NETWORK}", "search", "testplug"], dir=n.lightning_dir)
233+
# Should show the "Plugins matching" header
234+
assert r.search_stdout("Plugins matching 'testplug':")
235+
# Should list multiple plugins (all start with "testplug")
236+
assert r.search_stdout('testplugpass')
237+
assert r.search_stdout('testplugfail')
238+
assert r.search_stdout('testplugpyproj')
239+
assert r.search_stdout('testpluguv')
240+
241+
# Search for "pass" - should find testplugpass
242+
r = reckless([f"--network={NETWORK}", "search", "pass"], dir=n.lightning_dir)
243+
assert r.search_stdout("Plugins matching 'pass':")
244+
assert r.search_stdout('testplugpass')
245+
# Should not find plugins without "pass" in name
246+
assert not r.search_stdout('testplugfail')
247+
248+
# Search for something that doesn't exist
249+
r = reckless([f"--network={NETWORK}", "search", "nonexistent"], dir=n.lightning_dir)
250+
assert r.search_stdout("Search exhausted all sources")
251+
252+
227253
def test_install(node_factory):
228254
"""test search, git clone, and installation to folder."""
229255
n = get_reckless_node(node_factory)

tools/reckless

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,6 +1548,30 @@ def uninstall(plugin_name: str) -> str:
15481548
return "uninstalled"
15491549

15501550

1551+
def _get_all_plugins_from_source(src: str) -> list:
1552+
"""Get all plugin directories from a source repository.
1553+
Returns a list of (plugin_name, source_url) tuples."""
1554+
plugins = []
1555+
srctype = Source.get_type(src)
1556+
if srctype == Source.UNKNOWN:
1557+
return plugins
1558+
1559+
try:
1560+
root = SourceDir(src, srctype=srctype)
1561+
root.populate()
1562+
except Exception as e:
1563+
log.debug(f"Failed to populate source {src}: {e}")
1564+
return plugins
1565+
1566+
for item in root.contents:
1567+
if isinstance(item, SourceDir):
1568+
# Skip archive directories
1569+
if 'archive' in item.name.lower():
1570+
continue
1571+
plugins.append((item.name, src))
1572+
return plugins
1573+
1574+
15511575
def search(plugin_name: str) -> Union[InstInfo, None]:
15521576
"""searches plugin index for plugin"""
15531577
ordered_sources = RECKLESS_SOURCES.copy()
@@ -1563,6 +1587,22 @@ def search(plugin_name: str) -> Union[InstInfo, None]:
15631587
if Source.get_type(src) in [Source.DIRECTORY, Source.LOCAL_REPO]:
15641588
ordered_sources.remove(src)
15651589
ordered_sources.insert(0, src)
1590+
1591+
# First, collect all partial matches to display to user
1592+
partial_matches = []
1593+
for source in ordered_sources:
1594+
for plugin_name_found, src_url in _get_all_plugins_from_source(source):
1595+
if plugin_name.lower() in plugin_name_found.lower():
1596+
partial_matches.append((plugin_name_found, src_url))
1597+
1598+
# Display all partial matches
1599+
if partial_matches:
1600+
log.info(f"Plugins matching '{plugin_name}':")
1601+
for name, src_url in partial_matches:
1602+
log.info(f" {name} ({src_url})")
1603+
1604+
# Now try exact match for installation purposes
1605+
exact_match = None
15661606
for source in ordered_sources:
15671607
srctype = Source.get_type(source)
15681608
if srctype == Source.UNKNOWN:
@@ -1573,17 +1613,21 @@ def search(plugin_name: str) -> Union[InstInfo, None]:
15731613
found = _source_search(plugin_name, source)
15741614
if found:
15751615
log.debug(f"{found}, {found.srctype}")
1576-
if not found:
1577-
continue
1578-
log.info(f"found {found.name} in source: {found.source_loc}")
1579-
log.debug(f"entry: {found.entry}")
1580-
if found.subdir:
1581-
log.debug(f'sub-directory: {found.subdir}')
1616+
exact_match = found
1617+
break
1618+
1619+
if exact_match:
1620+
log.info(f"found {exact_match.name} in source: {exact_match.source_loc}")
1621+
log.debug(f"entry: {exact_match.entry}")
1622+
if exact_match.subdir:
1623+
log.debug(f'sub-directory: {exact_match.subdir}')
15821624
global LAST_FOUND
15831625
# Stashing the search result saves install() a call to _source_search.
1584-
LAST_FOUND = found
1585-
return str(found.source_loc)
1586-
log.info("Search exhausted all sources")
1626+
LAST_FOUND = exact_match
1627+
return str(exact_match.source_loc)
1628+
1629+
if not partial_matches:
1630+
log.info("Search exhausted all sources")
15871631
return None
15881632

15891633

0 commit comments

Comments
 (0)