Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 85 additions & 30 deletions cppython/console/entry.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""A Typer CLI for CPPython interfacing"""

from importlib.metadata import entry_points
from pathlib import Path
from typing import Annotated

Expand All @@ -14,6 +15,12 @@

app = typer.Typer(no_args_is_help=True)

info_app = typer.Typer(no_args_is_help=True, help='Prints project information including plugin configuration, managed files, and templates.')
app.add_typer(info_app, name='info')

list_app = typer.Typer(no_args_is_help=True, help='List project entities.')
app.add_typer(list_app, name='list')


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


@app.command()
def info(
def _print_plugin_report(role: str, name: str, report: PluginReport) -> None:
"""Print a single plugin's report to the console.
Args:
role: The plugin role label (e.g. 'Provider', 'Generator')
name: The plugin name
report: The plugin report to display
"""
print(f'\n[bold]{role}:[/bold] {name}')

if report.configuration:
print(' [bold]Configuration:[/bold]')
for key, value in report.configuration.items():
print(f' {key}: {value}')

if report.managed_files:
print(' [bold]Managed files:[/bold]')
for file_path in report.managed_files:
print(f' {file_path}')

if report.template_files:
print(' [bold]Templates:[/bold]')
for filename, content in report.template_files.items():
print(f' [cyan]{filename}[/cyan]')
print()
print(Syntax(content, 'python', theme='monokai', line_numbers=True))


@info_app.command()
def info_provider(
context: typer.Context,
) -> None:
"""Prints project information including plugin configuration, managed files, and templates."""
"""Show provider plugin information."""
project = get_enabled_project(context)
project_info = project.info()

if not project_info:
entry = project_info.get('provider')
if entry is None:
return

for role in ('provider', 'generator'):
entry = project_info.get(role)
if entry is None:
continue

name: str = entry['name']
report: PluginReport = entry['report']
_print_plugin_report('Provider', entry['name'], entry['report'])

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

if report.configuration:
print(' [bold]Configuration:[/bold]')
for key, value in report.configuration.items():
print(f' {key}: {value}')
@info_app.command()
def info_generator(
context: typer.Context,
) -> None:
"""Show generator plugin information."""
project = get_enabled_project(context)
project_info = project.info()

if report.managed_files:
print(' [bold]Managed files:[/bold]')
for path in report.managed_files:
print(f' {path}')
entry = project_info.get('generator')
if entry is None:
return

if report.template_files:
print(' [bold]Templates:[/bold]')
for filename, content in report.template_files.items():
print(f' [cyan]{filename}[/cyan]')
print()
print(Syntax(content, 'python', theme='monokai', line_numbers=True))
_print_plugin_report('Generator', entry['name'], entry['report'])


@app.command()
Expand Down Expand Up @@ -218,11 +244,40 @@ def update(
project.update(groups=group_list)


@app.command(name='list')
def list_command(
_: typer.Context,
@list_app.command()
def plugins() -> None:
"""List all installed CPPython plugins."""
groups = {
'Generators': 'cppython.generator',
'Providers': 'cppython.provider',
'SCM': 'cppython.scm',
}

for label, group in groups.items():
entries = entry_points(group=group)
print(f'\n[bold]{label}:[/bold]')
if not entries:
print(' (none installed)')
else:
for ep in sorted(entries, key=lambda e: e.name):
print(f' {ep.name}')


@list_app.command()
def targets(
context: typer.Context,
) -> None:
"""Prints project information"""
"""List discovered build targets."""
project = get_enabled_project(context)
target_list = project.list_targets()

if not target_list:
print('[dim]No targets found. Have you run install and build?[/dim]')
return

print('\n[bold]Targets:[/bold]')
for target_name in sorted(target_list):
print(f' {target_name}')


@app.command()
Expand Down
9 changes: 9 additions & 0 deletions cppython/core/plugin_schema/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,12 @@ def run(self, target: str, configuration: str | None = None) -> None:
configuration: Optional named configuration override.
"""
raise NotImplementedError

@abstractmethod
def list_targets(self) -> list[str]:
"""Lists discovered build targets/executables.
Returns:
A list of target names found in the build directory.
"""
raise NotImplementedError
23 changes: 23 additions & 0 deletions cppython/plugins/cmake/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,29 @@ def run(self, target: str, configuration: str | None = None) -> None:
executable = executables[0]
subprocess.run([str(executable)], check=True, cwd=self.data.preset_file.parent)

def list_targets(self) -> list[str]:
"""Lists discovered build targets/executables in the CMake build directory.
Searches the build directory for executable files, excluding common
non-target files.
Returns:
A sorted list of unique target names found.
"""
build_path = self.core_data.cppython_data.build_path

if not build_path.exists():
return []

# Collect executable files from the build directory
targets: set[str] = set()
for candidate in build_path.rglob('*'):
if candidate.is_file() and (candidate.stat().st_mode & 0o111 or candidate.suffix == '.exe'):
# Use the stem (name without extension) as the target name
targets.add(candidate.stem)

return sorted(targets)

def plugin_info(self) -> PluginReport:
"""Return a report describing the CMake generator's configuration and managed files.
Expand Down
20 changes: 20 additions & 0 deletions cppython/plugins/meson/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,23 @@ def run(self, target: str, configuration: str | None = None) -> None:

executable = executables[0]
subprocess.run([str(executable)], check=True, cwd=self.data.build_file.parent)

def list_targets(self) -> list[str]:
"""Lists discovered build targets/executables in the Meson build directory.
Searches the build directory for executable files.
Returns:
A sorted list of unique target names found.
"""
build_dir = self._build_dir()

if not build_dir.exists():
return []

targets: set[str] = set()
for candidate in build_dir.rglob('*'):
if candidate.is_file() and (candidate.stat().st_mode & 0o111 or candidate.suffix == '.exe'):
targets.add(candidate.stem)

return sorted(targets)
13 changes: 13 additions & 0 deletions cppython/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,16 @@ def run(self, target: str, configuration: str | None = None) -> None:
self.logger.info('Running target: %s', target)
self._data.sync()
self._data.plugins.generator.run(target, configuration=configuration)

def list_targets(self) -> list[str]:
"""Lists discovered build targets/executables.
Returns:
A list of target names found in the build directory, or an empty list
if the project is not enabled.
"""
if not self._enabled:
self.logger.info('Skipping list_targets because the project is not enabled')
return []

return self._data.plugins.generator.list_targets()
9 changes: 9 additions & 0 deletions cppython/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,12 @@ def run(self, target: str, configuration: str | None = None) -> None:
configuration: Optional named configuration to use. Interpretation is generator-specific.
"""
raise NotImplementedError()

@abstractmethod
def list_targets(self) -> list[str]:
"""Lists discovered build targets/executables.
Returns:
A list of target names found in the build directory.
"""
raise NotImplementedError()
4 changes: 4 additions & 0 deletions cppython/test/mock/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,7 @@ def bench(self, configuration: str | None = None) -> None:

def run(self, target: str, configuration: str | None = None) -> None:
"""No-op run for testing"""

def list_targets(self) -> list[str]:
"""No-op list_targets for testing"""
return []
Loading