mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 12:17:02 +00:00
cli: Allow "project index" with project name (not just path), refactoring
This commit is contained in:
1 parent
1d0e38be85
commit
1ec5a731a3
3 files changed
+54
-30
No files matched your search
+21
-24
@@ -510,16 +510,16 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _create_project(project_path: str, name: str | None, language: tuple[str, ...]) -> ProjectConfig:
|
||||
def _create_project(project_path: str, name: str | None, language: tuple[str, ...]) -> RegisteredProject:
|
||||
"""
|
||||
Helper method to create a project configuration file.
|
||||
|
||||
:param project_path: Path to the project directory
|
||||
:param name: Optional project name (defaults to directory name if not specified)
|
||||
:param language: Tuple of language names
|
||||
:return: The generated ProjectConfig instance
|
||||
:raises FileExistsError: If project.yml already exists
|
||||
:raises ValueError: If an unsupported language is specified
|
||||
:return: the RegisteredProject instance
|
||||
"""
|
||||
project_root = Path(project_path).resolve()
|
||||
yml_path = ProjectConfig.path_to_project_yml(project_root)
|
||||
@@ -544,13 +544,12 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
|
||||
# add to SerenaConfig's list of registered projects
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
if serena_config.get_registered_project(str(project_root)) is None:
|
||||
registered_project = serena_config.get_registered_project(str(project_root))
|
||||
if registered_project is None:
|
||||
registered_project = RegisteredProject(str(project_root), generated_conf)
|
||||
serena_config.projects.append(registered_project)
|
||||
click.echo(f"Saving project registration to {serena_config}")
|
||||
serena_config.save()
|
||||
serena_config.add_registered_project(registered_project)
|
||||
|
||||
return generated_conf
|
||||
return registered_project
|
||||
|
||||
@staticmethod
|
||||
@click.command("create", help="Create a new Serena project configuration.", context_settings={"max_content_width": _MAX_CONTENT_WIDTH})
|
||||
@@ -569,10 +568,10 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
@click.option("--timeout", type=float, default=10, help="Timeout for indexing a single file (only used if --index is set).")
|
||||
def create(project_path: str, name: str | None, language: tuple[str, ...], index: bool, log_level: str, timeout: float) -> None:
|
||||
try:
|
||||
ProjectCommands._create_project(project_path, name, language)
|
||||
registered_project = ProjectCommands._create_project(project_path, name, language)
|
||||
if index:
|
||||
click.echo("Indexing project...")
|
||||
ProjectCommands._index_project(project_path, log_level, timeout=timeout)
|
||||
ProjectCommands._index_project(registered_project, log_level, timeout=timeout)
|
||||
except FileExistsError as e:
|
||||
raise click.ClickException(f"Project already exists: {e}\nUse 'serena project index' to index an existing project.")
|
||||
except ValueError as e:
|
||||
@@ -584,7 +583,7 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
help="Index a project by saving symbols to the LSP cache. Auto-creates project.yml if it doesn't exist.",
|
||||
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
|
||||
)
|
||||
@click.argument("project", type=click.Path(exists=True), default=os.getcwd(), required=False)
|
||||
@click.argument("project", type=PROJECT_TYPE, default=os.getcwd(), required=False)
|
||||
@click.option("--name", type=str, default=None, help="Project name (only used if auto-creating project.yml).")
|
||||
@click.option(
|
||||
"--language",
|
||||
@@ -600,32 +599,30 @@ class ProjectCommands(AutoRegisteringGroup):
|
||||
)
|
||||
@click.option("--timeout", type=float, default=10, help="Timeout for indexing a single file.")
|
||||
def index(project: str, name: str | None, language: tuple[str, ...], log_level: str, timeout: float) -> None:
|
||||
# Check if project.yml exists, if not auto-create it
|
||||
yml_path = os.path.join(project, ProjectConfig.rel_path_to_project_yml())
|
||||
if not os.path.exists(yml_path):
|
||||
click.echo(f"Project configuration not found at {yml_path}. Auto-creating...")
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
registered_project = serena_config.get_registered_project(project, autoregister=True)
|
||||
if registered_project is None:
|
||||
# Project not found; auto-create it
|
||||
click.echo(f"No existing project found for '{project}'. Attempting auto-creation ...")
|
||||
try:
|
||||
ProjectCommands._create_project(project, name, language)
|
||||
except FileExistsError:
|
||||
# Race condition - file was created between check and creation
|
||||
pass
|
||||
except ValueError as e:
|
||||
registered_project = ProjectCommands._create_project(project, name, language)
|
||||
except Exception as e:
|
||||
raise click.ClickException(str(e))
|
||||
|
||||
ProjectCommands._index_project(project, log_level, timeout=timeout)
|
||||
ProjectCommands._index_project(registered_project, log_level, timeout=timeout)
|
||||
|
||||
@staticmethod
|
||||
def _index_project(project: str, log_level: str, timeout: float) -> None:
|
||||
def _index_project(registered_project: RegisteredProject, log_level: str, timeout: float) -> None:
|
||||
lvl = logging.getLevelNamesMapping()[log_level.upper()]
|
||||
logging.configure(level=lvl)
|
||||
serena_config = SerenaConfig.from_config_file()
|
||||
proj = Project.load(os.path.abspath(project))
|
||||
click.echo(f"Indexing symbols in project {project} …")
|
||||
proj = registered_project.get_project_instance()
|
||||
click.echo(f"Indexing symbols in {proj} …")
|
||||
ls_mgr = proj.create_language_server_manager(
|
||||
log_level=lvl, ls_timeout=timeout, ls_specific_settings=serena_config.ls_specific_settings
|
||||
)
|
||||
try:
|
||||
log_file = os.path.join(project, ".serena", "logs", "indexing.txt")
|
||||
log_file = os.path.join(proj.project_root, ".serena", "logs", "indexing.txt")
|
||||
|
||||
files = proj.gather_source_files()
|
||||
|
||||
|
||||
@@ -374,6 +374,14 @@ class RegisteredProject(ToStringMixin):
|
||||
project_instance=project_instance,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_project_root(cls, project_root: str | Path) -> "RegisteredProject":
|
||||
project_config = ProjectConfig.load(project_root)
|
||||
return RegisteredProject(
|
||||
project_root=str(project_root),
|
||||
project_config=project_config,
|
||||
)
|
||||
|
||||
def matches_root_path(self, path: str | Path) -> bool:
|
||||
"""
|
||||
Check if the given path matches the project root path.
|
||||
@@ -613,7 +621,12 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
def project_names(self) -> list[str]:
|
||||
return sorted(project.project_config.project_name for project in self.projects)
|
||||
|
||||
def get_registered_project(self, project_root_or_name: str) -> Optional[RegisteredProject]:
|
||||
def get_registered_project(self, project_root_or_name: str, autoregister: bool = False) -> Optional[RegisteredProject]:
|
||||
"""
|
||||
:param project_root_or_name: path to the project root or the name of the project
|
||||
:param autoregister: whether to register the project if it exists but is not registered yet
|
||||
:return: the registered project, or None if not found
|
||||
"""
|
||||
# look for project by name
|
||||
project_candidates = []
|
||||
for project in self.projects:
|
||||
@@ -631,6 +644,14 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
for project in self.projects:
|
||||
if project.matches_root_path(project_root_or_name):
|
||||
return project
|
||||
# no registered project found; auto-register if project configuration exists
|
||||
if autoregister:
|
||||
config_path = ProjectConfig.path_to_project_yml(project_root_or_name)
|
||||
if os.path.isfile(config_path):
|
||||
registered_project = RegisteredProject.from_project_root(project_root_or_name)
|
||||
self.add_registered_project(registered_project)
|
||||
return registered_project
|
||||
# nothing found
|
||||
return None
|
||||
|
||||
def get_project(self, project_root_or_name: str) -> Optional["Project"]:
|
||||
@@ -640,6 +661,13 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
else:
|
||||
return registered_project.get_project_instance()
|
||||
|
||||
def add_registered_project(self, registered_project: RegisteredProject) -> None:
|
||||
"""
|
||||
Adds a registered project, saving the configuration file.
|
||||
"""
|
||||
self.projects.append(registered_project)
|
||||
self.save()
|
||||
|
||||
def add_project_from_path(self, project_root: Path | str) -> "Project":
|
||||
"""
|
||||
Add a new project to the Serena configuration from a given path, auto-generating the project
|
||||
@@ -666,8 +694,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
|
||||
project_config = ProjectConfig.load(project_root, autogenerate=True)
|
||||
|
||||
new_project = Project(project_root=str(project_root), project_config=project_config, is_newly_created=True)
|
||||
self.projects.append(RegisteredProject.from_project_instance(new_project))
|
||||
self.save()
|
||||
self.add_registered_project(RegisteredProject.from_project_instance(new_project))
|
||||
|
||||
return new_project
|
||||
|
||||
|
||||
@@ -236,20 +236,20 @@ class TestProjectCreateHelper:
|
||||
|
||||
def test_create_project_helper_returns_config(self, temp_project_dir):
|
||||
"""Test that _create_project returns a ProjectConfig with explicit language."""
|
||||
config = ProjectCommands._create_project(temp_project_dir, "test-project", ("python",))
|
||||
config = ProjectCommands._create_project(temp_project_dir, "test-project", ("python",)).project_config
|
||||
assert isinstance(config, ProjectConfig)
|
||||
assert config.project_name == "test-project"
|
||||
|
||||
def test_create_project_helper_with_auto_detect(self, temp_project_dir_with_python_file):
|
||||
"""Test _create_project with auto-detected language."""
|
||||
config = ProjectCommands._create_project(temp_project_dir_with_python_file, "my-project", ())
|
||||
config = ProjectCommands._create_project(temp_project_dir_with_python_file, "my-project", ()).project_config
|
||||
assert isinstance(config, ProjectConfig)
|
||||
assert config.project_name == "my-project"
|
||||
assert len(config.languages) >= 1
|
||||
|
||||
def test_create_project_helper_with_languages(self, temp_project_dir):
|
||||
"""Test _create_project with language specification."""
|
||||
config = ProjectCommands._create_project(temp_project_dir, None, ("python", "typescript"))
|
||||
config = ProjectCommands._create_project(temp_project_dir, None, ("python", "typescript")).project_config
|
||||
assert isinstance(config, ProjectConfig)
|
||||
assert len(config.languages) >= 1
|
||||
|
||||
|
||||
Reference in new issue
Block a user