Skip to content

Commit 5e47138

Browse files
committed
List/Info Groups
1 parent 5afecea commit 5e47138

File tree

7 files changed

+163
-30
lines changed

7 files changed

+163
-30
lines changed

cppython/console/entry.py

Lines changed: 85 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""A Typer CLI for CPPython interfacing"""
22

3+
from importlib.metadata import entry_points
34
from pathlib import Path
45
from typing import Annotated
56

@@ -14,6 +15,12 @@
1415

1516
app = typer.Typer(no_args_is_help=True)
1617

18+
info_app = typer.Typer(no_args_is_help=True, help='Prints project information including plugin configuration, managed files, and templates.')
19+
app.add_typer(info_app, name='info')
20+
21+
list_app = typer.Typer(no_args_is_help=True, help='List project entities.')
22+
app.add_typer(list_app, name='list')
23+
1724

1825
def get_enabled_project(context: typer.Context) -> Project:
1926
"""Helper to load and validate an enabled Project from CLI context."""
@@ -123,43 +130,62 @@ def main(
123130
context.obj = ConsoleConfiguration(project_configuration=project_configuration, interface=interface)
124131

125132

126-
@app.command()
127-
def info(
133+
def _print_plugin_report(role: str, name: str, report: PluginReport) -> None:
134+
"""Print a single plugin's report to the console.
135+
136+
Args:
137+
role: The plugin role label (e.g. 'Provider', 'Generator')
138+
name: The plugin name
139+
report: The plugin report to display
140+
"""
141+
print(f'\n[bold]{role}:[/bold] {name}')
142+
143+
if report.configuration:
144+
print(' [bold]Configuration:[/bold]')
145+
for key, value in report.configuration.items():
146+
print(f' {key}: {value}')
147+
148+
if report.managed_files:
149+
print(' [bold]Managed files:[/bold]')
150+
for file_path in report.managed_files:
151+
print(f' {file_path}')
152+
153+
if report.template_files:
154+
print(' [bold]Templates:[/bold]')
155+
for filename, content in report.template_files.items():
156+
print(f' [cyan]{filename}[/cyan]')
157+
print()
158+
print(Syntax(content, 'python', theme='monokai', line_numbers=True))
159+
160+
161+
@info_app.command()
162+
def info_provider(
128163
context: typer.Context,
129164
) -> None:
130-
"""Prints project information including plugin configuration, managed files, and templates."""
165+
"""Show provider plugin information."""
131166
project = get_enabled_project(context)
132167
project_info = project.info()
133168

134-
if not project_info:
169+
entry = project_info.get('provider')
170+
if entry is None:
135171
return
136172

137-
for role in ('provider', 'generator'):
138-
entry = project_info.get(role)
139-
if entry is None:
140-
continue
141-
142-
name: str = entry['name']
143-
report: PluginReport = entry['report']
173+
_print_plugin_report('Provider', entry['name'], entry['report'])
144174

145-
print(f'\n[bold]{role.title()}:[/bold] {name}')
146175

147-
if report.configuration:
148-
print(' [bold]Configuration:[/bold]')
149-
for key, value in report.configuration.items():
150-
print(f' {key}: {value}')
176+
@info_app.command()
177+
def info_generator(
178+
context: typer.Context,
179+
) -> None:
180+
"""Show generator plugin information."""
181+
project = get_enabled_project(context)
182+
project_info = project.info()
151183

152-
if report.managed_files:
153-
print(' [bold]Managed files:[/bold]')
154-
for path in report.managed_files:
155-
print(f' {path}')
184+
entry = project_info.get('generator')
185+
if entry is None:
186+
return
156187

157-
if report.template_files:
158-
print(' [bold]Templates:[/bold]')
159-
for filename, content in report.template_files.items():
160-
print(f' [cyan]{filename}[/cyan]')
161-
print()
162-
print(Syntax(content, 'python', theme='monokai', line_numbers=True))
188+
_print_plugin_report('Generator', entry['name'], entry['report'])
163189

164190

165191
@app.command()
@@ -218,11 +244,40 @@ def update(
218244
project.update(groups=group_list)
219245

220246

221-
@app.command(name='list')
222-
def list_command(
223-
_: typer.Context,
247+
@list_app.command()
248+
def plugins() -> None:
249+
"""List all installed CPPython plugins."""
250+
groups = {
251+
'Generators': 'cppython.generator',
252+
'Providers': 'cppython.provider',
253+
'SCM': 'cppython.scm',
254+
}
255+
256+
for label, group in groups.items():
257+
entries = entry_points(group=group)
258+
print(f'\n[bold]{label}:[/bold]')
259+
if not entries:
260+
print(' (none installed)')
261+
else:
262+
for ep in sorted(entries, key=lambda e: e.name):
263+
print(f' {ep.name}')
264+
265+
266+
@list_app.command()
267+
def targets(
268+
context: typer.Context,
224269
) -> None:
225-
"""Prints project information"""
270+
"""List discovered build targets."""
271+
project = get_enabled_project(context)
272+
target_list = project.list_targets()
273+
274+
if not target_list:
275+
print('[dim]No targets found. Have you run install and build?[/dim]')
276+
return
277+
278+
print('\n[bold]Targets:[/bold]')
279+
for target_name in sorted(target_list):
280+
print(f' {target_name}')
226281

227282

228283
@app.command()

cppython/core/plugin_schema/generator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,12 @@ def run(self, target: str, configuration: str | None = None) -> None:
109109
configuration: Optional named configuration override.
110110
"""
111111
raise NotImplementedError
112+
113+
@abstractmethod
114+
def list_targets(self) -> list[str]:
115+
"""Lists discovered build targets/executables.
116+
117+
Returns:
118+
A list of target names found in the build directory.
119+
"""
120+
raise NotImplementedError

cppython/plugins/cmake/plugin.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,29 @@ def run(self, target: str, configuration: str | None = None) -> None:
182182
executable = executables[0]
183183
subprocess.run([str(executable)], check=True, cwd=self.data.preset_file.parent)
184184

185+
def list_targets(self) -> list[str]:
186+
"""Lists discovered build targets/executables in the CMake build directory.
187+
188+
Searches the build directory for executable files, excluding common
189+
non-target files.
190+
191+
Returns:
192+
A sorted list of unique target names found.
193+
"""
194+
build_path = self.core_data.cppython_data.build_path
195+
196+
if not build_path.exists():
197+
return []
198+
199+
# Collect executable files from the build directory
200+
targets: set[str] = set()
201+
for candidate in build_path.rglob('*'):
202+
if candidate.is_file() and (candidate.stat().st_mode & 0o111 or candidate.suffix == '.exe'):
203+
# Use the stem (name without extension) as the target name
204+
targets.add(candidate.stem)
205+
206+
return sorted(targets)
207+
185208
def plugin_info(self) -> PluginReport:
186209
"""Return a report describing the CMake generator's configuration and managed files.
187210

cppython/plugins/meson/plugin.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,23 @@ def run(self, target: str, configuration: str | None = None) -> None:
194194

195195
executable = executables[0]
196196
subprocess.run([str(executable)], check=True, cwd=self.data.build_file.parent)
197+
198+
def list_targets(self) -> list[str]:
199+
"""Lists discovered build targets/executables in the Meson build directory.
200+
201+
Searches the build directory for executable files.
202+
203+
Returns:
204+
A sorted list of unique target names found.
205+
"""
206+
build_dir = self._build_dir()
207+
208+
if not build_dir.exists():
209+
return []
210+
211+
targets: set[str] = set()
212+
for candidate in build_dir.rglob('*'):
213+
if candidate.is_file() and (candidate.stat().st_mode & 0o111 or candidate.suffix == '.exe'):
214+
targets.add(candidate.stem)
215+
216+
return sorted(targets)

cppython/project.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,16 @@ def run(self, target: str, configuration: str | None = None) -> None:
268268
self.logger.info('Running target: %s', target)
269269
self._data.sync()
270270
self._data.plugins.generator.run(target, configuration=configuration)
271+
272+
def list_targets(self) -> list[str]:
273+
"""Lists discovered build targets/executables.
274+
275+
Returns:
276+
A list of target names found in the build directory, or an empty list
277+
if the project is not enabled.
278+
"""
279+
if not self._enabled:
280+
self.logger.info('Skipping list_targets because the project is not enabled')
281+
return []
282+
283+
return self._data.plugins.generator.list_targets()

cppython/schema.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,12 @@ def run(self, target: str, configuration: str | None = None) -> None:
7171
configuration: Optional named configuration to use. Interpretation is generator-specific.
7272
"""
7373
raise NotImplementedError()
74+
75+
@abstractmethod
76+
def list_targets(self) -> list[str]:
77+
"""Lists discovered build targets/executables.
78+
79+
Returns:
80+
A list of target names found in the build directory.
81+
"""
82+
raise NotImplementedError()

cppython/test/mock/generator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,7 @@ def bench(self, configuration: str | None = None) -> None:
7272

7373
def run(self, target: str, configuration: str | None = None) -> None:
7474
"""No-op run for testing"""
75+
76+
def list_targets(self) -> list[str]:
77+
"""No-op list_targets for testing"""
78+
return []

0 commit comments

Comments
 (0)