lean-explore 0.2.2__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lean_explore/__init__.py +14 -1
- lean_explore/api/__init__.py +12 -1
- lean_explore/api/client.py +60 -80
- lean_explore/cli/__init__.py +10 -1
- lean_explore/cli/data_commands.py +157 -479
- lean_explore/cli/display.py +171 -0
- lean_explore/cli/main.py +51 -608
- lean_explore/config.py +244 -0
- lean_explore/extract/__init__.py +5 -0
- lean_explore/extract/__main__.py +368 -0
- lean_explore/extract/doc_gen4.py +200 -0
- lean_explore/extract/doc_parser.py +499 -0
- lean_explore/extract/embeddings.py +371 -0
- lean_explore/extract/github.py +110 -0
- lean_explore/extract/index.py +317 -0
- lean_explore/extract/informalize.py +653 -0
- lean_explore/extract/package_config.py +59 -0
- lean_explore/extract/package_registry.py +45 -0
- lean_explore/extract/package_utils.py +105 -0
- lean_explore/extract/types.py +25 -0
- lean_explore/mcp/__init__.py +11 -1
- lean_explore/mcp/app.py +14 -46
- lean_explore/mcp/server.py +20 -35
- lean_explore/mcp/tools.py +70 -177
- lean_explore/models/__init__.py +9 -0
- lean_explore/models/search_db.py +76 -0
- lean_explore/models/search_types.py +53 -0
- lean_explore/search/__init__.py +32 -0
- lean_explore/search/engine.py +655 -0
- lean_explore/search/scoring.py +156 -0
- lean_explore/search/service.py +68 -0
- lean_explore/search/tokenization.py +71 -0
- lean_explore/util/__init__.py +28 -0
- lean_explore/util/embedding_client.py +92 -0
- lean_explore/util/logging.py +22 -0
- lean_explore/util/openrouter_client.py +63 -0
- lean_explore/util/reranker_client.py +189 -0
- {lean_explore-0.2.2.dist-info → lean_explore-1.0.0.dist-info}/METADATA +55 -10
- lean_explore-1.0.0.dist-info/RECORD +43 -0
- {lean_explore-0.2.2.dist-info → lean_explore-1.0.0.dist-info}/WHEEL +1 -1
- lean_explore-1.0.0.dist-info/entry_points.txt +2 -0
- lean_explore/cli/agent.py +0 -781
- lean_explore/cli/config_utils.py +0 -481
- lean_explore/defaults.py +0 -114
- lean_explore/local/__init__.py +0 -1
- lean_explore/local/search.py +0 -1050
- lean_explore/local/service.py +0 -392
- lean_explore/shared/__init__.py +0 -1
- lean_explore/shared/models/__init__.py +0 -1
- lean_explore/shared/models/api.py +0 -117
- lean_explore/shared/models/db.py +0 -396
- lean_explore-0.2.2.dist-info/RECORD +0 -26
- lean_explore-0.2.2.dist-info/entry_points.txt +0 -2
- {lean_explore-0.2.2.dist-info → lean_explore-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {lean_explore-0.2.2.dist-info → lean_explore-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# src/lean_explore/cli/display.py
|
|
2
|
+
|
|
3
|
+
"""Display and formatting utilities for CLI output."""
|
|
4
|
+
|
|
5
|
+
import textwrap
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
|
|
10
|
+
from lean_explore.models import SearchResponse
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _wrap_line(line: str, width: int) -> list[str]:
|
|
14
|
+
"""Wraps a single line of text to the specified width.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
line: The line to wrap.
|
|
18
|
+
width: The target width for wrapped text.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
List of wrapped line segments, each padded to the target width.
|
|
22
|
+
"""
|
|
23
|
+
empty_line = " " * width
|
|
24
|
+
if not line.strip():
|
|
25
|
+
return [empty_line]
|
|
26
|
+
|
|
27
|
+
segments = textwrap.wrap(
|
|
28
|
+
line,
|
|
29
|
+
width=width,
|
|
30
|
+
replace_whitespace=True,
|
|
31
|
+
drop_whitespace=True,
|
|
32
|
+
break_long_words=True,
|
|
33
|
+
break_on_hyphens=True,
|
|
34
|
+
)
|
|
35
|
+
return [segment.ljust(width) for segment in segments] if segments else [empty_line]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _format_text_for_panel(text_content: str | None, width: int = 80) -> str:
|
|
39
|
+
"""Wraps text and pads lines to ensure fixed content width for a Panel.
|
|
40
|
+
|
|
41
|
+
Splits text into paragraphs (by double newline), wraps each line within
|
|
42
|
+
paragraphs, and pads all lines to the target width.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
text_content: The text to format.
|
|
46
|
+
width: The target width for wrapped text.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Formatted text with proper line wrapping and padding.
|
|
50
|
+
"""
|
|
51
|
+
empty_line = " " * width
|
|
52
|
+
if not text_content:
|
|
53
|
+
return empty_line
|
|
54
|
+
|
|
55
|
+
output_lines = []
|
|
56
|
+
paragraphs = text_content.split("\n\n")
|
|
57
|
+
|
|
58
|
+
for index, paragraph in enumerate(paragraphs):
|
|
59
|
+
# Handle empty paragraphs (preserve blank lines between paragraphs)
|
|
60
|
+
if not paragraph.strip():
|
|
61
|
+
if index < len(paragraphs) - 1:
|
|
62
|
+
output_lines.append(empty_line)
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# Process each line within the paragraph
|
|
66
|
+
for line in paragraph.splitlines():
|
|
67
|
+
output_lines.extend(_wrap_line(line, width))
|
|
68
|
+
|
|
69
|
+
# Add separator between paragraphs (except after the last one)
|
|
70
|
+
if index < len(paragraphs) - 1:
|
|
71
|
+
output_lines.append(empty_line)
|
|
72
|
+
|
|
73
|
+
return "\n".join(output_lines) if output_lines else empty_line
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def display_search_results(
|
|
77
|
+
response: SearchResponse,
|
|
78
|
+
display_limit: int = 5,
|
|
79
|
+
console: Console | None = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Displays search results using fixed-width Panels for each item.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
response: The search response containing results to display.
|
|
85
|
+
display_limit: Maximum number of results to show.
|
|
86
|
+
console: The Rich console to use for output. If None, creates a new one.
|
|
87
|
+
"""
|
|
88
|
+
if console is None:
|
|
89
|
+
console = Console()
|
|
90
|
+
|
|
91
|
+
console.print(
|
|
92
|
+
Panel(
|
|
93
|
+
f"[bold cyan]Search Query:[/bold cyan] {response.query}",
|
|
94
|
+
expand=False,
|
|
95
|
+
border_style="dim",
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
num_results_to_show = min(len(response.results), display_limit)
|
|
100
|
+
time_info = (
|
|
101
|
+
f"Time: {response.processing_time_ms}ms" if response.processing_time_ms else ""
|
|
102
|
+
)
|
|
103
|
+
console.print(
|
|
104
|
+
f"Showing {num_results_to_show} of {response.count} results. {time_info}"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if not response.results:
|
|
108
|
+
console.print("[yellow]No results found.[/yellow]")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
console.print("")
|
|
112
|
+
|
|
113
|
+
for i, item in enumerate(response.results):
|
|
114
|
+
if i >= display_limit:
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
console.rule(f"[bold]Result {i + 1}[/bold]", style="dim")
|
|
118
|
+
console.print(f"[bold cyan]ID:[/bold cyan] [dim]{item.id}[/dim]")
|
|
119
|
+
console.print(f"[bold cyan]Name:[/bold cyan] {item.name}")
|
|
120
|
+
console.print(f"[bold cyan]Module:[/bold cyan] [green]{item.module}[/green]")
|
|
121
|
+
source_formatted = (
|
|
122
|
+
f"[bold cyan]Source:[/bold cyan] "
|
|
123
|
+
f"[link={item.source_link}]{item.source_link}[/link]"
|
|
124
|
+
)
|
|
125
|
+
console.print(source_formatted)
|
|
126
|
+
|
|
127
|
+
if item.source_text:
|
|
128
|
+
formatted_code = _format_text_for_panel(item.source_text)
|
|
129
|
+
console.print(
|
|
130
|
+
Panel(
|
|
131
|
+
formatted_code,
|
|
132
|
+
title="[bold green]Code[/bold green]",
|
|
133
|
+
border_style="green",
|
|
134
|
+
expand=False,
|
|
135
|
+
padding=(0, 1),
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if item.docstring:
|
|
140
|
+
formatted_doc = _format_text_for_panel(item.docstring)
|
|
141
|
+
console.print(
|
|
142
|
+
Panel(
|
|
143
|
+
formatted_doc,
|
|
144
|
+
title="[bold blue]Docstring[/bold blue]",
|
|
145
|
+
border_style="blue",
|
|
146
|
+
expand=False,
|
|
147
|
+
padding=(0, 1),
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if item.informalization:
|
|
152
|
+
formatted_informal = _format_text_for_panel(item.informalization)
|
|
153
|
+
console.print(
|
|
154
|
+
Panel(
|
|
155
|
+
formatted_informal,
|
|
156
|
+
title="[bold magenta]Informalization[/bold magenta]",
|
|
157
|
+
border_style="magenta",
|
|
158
|
+
expand=False,
|
|
159
|
+
padding=(0, 1),
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if i < num_results_to_show - 1:
|
|
164
|
+
console.print("")
|
|
165
|
+
|
|
166
|
+
console.rule(style="dim")
|
|
167
|
+
if len(response.results) > num_results_to_show:
|
|
168
|
+
console.print(
|
|
169
|
+
f"...and {len(response.results) - num_results_to_show} more results "
|
|
170
|
+
"received but not shown due to limit."
|
|
171
|
+
)
|