Skip to content

Commit

Permalink
Merge pull request #66 from coveo/feat/match-project-by-path
Browse files Browse the repository at this point in the history
feat: support path as project name
  • Loading branch information
jonapich authored Nov 5, 2024
2 parents d122ac7 + 28a09fd commit 600ce57
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 17 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,28 @@ Please read these guides in order to learn how to organize your repository for m

## General command usage

Unless a project name is specified, most commands will operate on all projects in a git repository based on the current working folder:
Unless a project name is specified, commands will operate on all projects in a git repository based on the current working folder:

- `stew <command>`
- Perform a command on all projects
- `stew <command> --help`
- Obtain help about a particular command


Some commands allow specifying a project name:

- `stew <command> <project-name>`
- Perform the command on all projects with `<project-name>` in their name (partial match)
- Perform the command on all projects with `<project-name>` in their name (partial, case-insensitive)
- e.g.: `stew ci tools` will run on `tools`, `tools-common`, `my-tools`, etc.
- `stew <command> <project-name> --exact-match`
- Disable partial project name matching
- e.g.: `stew ci tools --exact-match` will run on `tools` but not on `tools-common`, `my-tools`, etc.
- `stew <command> .<path>`
- **v3.1.3** Only consider the project at this location
- The path must start with `.`
- Nested projects will not run
- e.g.: `stew ci .` will only run on the project in the current folder
- e.g.: `stew ci ./tools` will only run on the project in the `tools` folder

The main commands are explained below.

Expand Down
41 changes: 31 additions & 10 deletions coveo_stew/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
import re
from collections import defaultdict
from pathlib import Path
from typing import Generator, Iterable, Set, Tuple, Union
from typing import Generator, Iterable, Optional, Set, Tuple, Union

import click
from coveo_functools.finalizer import finalizer
from coveo_styles.styles import ExitWithFailure, echo, install_pretty_exception_hook
from coveo_systools.filesystem import find_repo_root

from coveo_stew.ci.runner_status import RunnerStatus
from coveo_stew.discovery import discover_pyprojects, find_pyproject
from coveo_stew.discovery import Predicate, discover_pyprojects, find_pyproject
from coveo_stew.exceptions import (
CheckFailed,
PythonProjectNotFound,
Expand Down Expand Up @@ -47,7 +47,7 @@ def _pull_dev_requirements(
) -> Generator[Path, None, None]:
"""Writes the dev-dependencies of pydev projects' local dependencies into pydev's pyproject.toml file."""
dry_run_text = "(dry run) " if dry_run else ""
for pydev_project in discover_pyprojects(predicate=is_pydev_project, verbose=verbose):
for pydev_project in _discover_pyprojects(predicate=is_pydev_project, verbose=verbose):
echo.step(f"Analyzing dev requirements for {pydev_project}")
if pull_and_write_dev_requirements(pydev_project, dry_run=dry_run):
echo.outcome(
Expand Down Expand Up @@ -76,7 +76,7 @@ def check_outdated(verbose: bool = False) -> None:
echo.step("Analyzing all pyproject.toml files and artifacts:")
outdated: Set[Path] = set()
try:
for project in discover_pyprojects(verbose=verbose):
for project in _discover_pyprojects(verbose=verbose):
echo.noise(project, item=True)
if not project.lock_path.exists() or project.lock_is_outdated():
outdated.add(project.lock_path)
Expand Down Expand Up @@ -109,7 +109,7 @@ def fix_outdated(verbose: bool = False) -> None:
updated: Set[Path] = set()
with finalizer(_echo_updated, updated):
try:
for project in discover_pyprojects(verbose=verbose):
for project in _discover_pyprojects(verbose=verbose):
echo.noise(project, item=True)
if project.lock_if_needed():
updated.add(project.lock_path)
Expand All @@ -130,7 +130,7 @@ def bump(verbose: bool = False) -> None:
updated: Set[Path] = set()
with finalizer(_echo_updated, updated):
try:
for project in discover_pyprojects(verbose=verbose):
for project in _discover_pyprojects(verbose=verbose):
echo.step(f"Bumping {project.lock_path}")
if project.bump():
updated.add(project.toml_path)
Expand Down Expand Up @@ -204,8 +204,9 @@ def fresh_eggs(project_name: str = None, verbose: bool = False) -> None:
"""
echo.step("Removing *.egg-info folders.")
deleted = False

try:
for project in discover_pyprojects(query=project_name, verbose=verbose):
for project in _discover_pyprojects(query=project_name, verbose=verbose):
if project.remove_egg_info():
echo.outcome("Deleted: ", project.egg_path, item=True)
deleted = True
Expand Down Expand Up @@ -265,7 +266,7 @@ def locate(project_name: str, verbose: bool = False) -> None:
# check for partial matches to guide the user
partial_matches = (
project.package.name
for project in discover_pyprojects(query=project_name, verbose=verbose)
for project in _discover_pyprojects(query=project_name, verbose=verbose)
)
try:
raise ExitWithFailure(
Expand All @@ -287,7 +288,7 @@ def refresh(project_name: str = None, exact_match: bool = False, verbose: bool =
echo.step("Refreshing python project environments...")
pydev_projects = []
try:
for project in discover_pyprojects(
for project in _discover_pyprojects(
query=project_name, exact_match=exact_match, verbose=verbose
):
if project.options.pydev:
Expand Down Expand Up @@ -342,7 +343,7 @@ def ci(
) -> None:
failures = defaultdict(list)
try:
for project in discover_pyprojects(
for project in _discover_pyprojects(
query=project_name, exact_match=exact_match, verbose=verbose
):
echo.step(project.package.name, pad_after=False)
Expand Down Expand Up @@ -375,3 +376,23 @@ def ci(
raise ExitWithFailure(failures=projects, exit_code=exit_code) from CheckFailed(
f"{len(failures)} project(s) failed ci steps."
)


def _discover_pyprojects(
query: Optional[str] = None,
predicate: Optional[Predicate] = None,
exact_match: bool = False,
verbose: bool = False,
) -> Generator[PythonProject, None, None]:
if query and query.startswith("."):
if exact_match:
echo.warning(
f"--exact-match only works with project names, but we are targeting a path: {query}"
)
yield from discover_pyprojects(
path=Path(query), verbose=verbose, find_nested=False, predicate=predicate
)
else:
yield from discover_pyprojects(
query=query, exact_match=exact_match, verbose=verbose, predicate=predicate
)
17 changes: 12 additions & 5 deletions coveo_stew/discovery.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Callable, Generator
from typing import Callable, Generator, Optional

from coveo_styles.styles import echo
from coveo_systools.filesystem import find_paths, find_repo_root
Expand All @@ -25,20 +25,25 @@ def find_pyproject(project_name: str, path: Path = None, *, verbose: bool = Fals
def discover_pyprojects(
path: Path = None,
*,
query: str = None,
query: Optional[str] = None,
exact_match: bool = False,
verbose: bool = False,
predicate: Predicate = None,
predicate: Optional[Predicate] = None,
find_nested: bool = True,
) -> Generator[PythonProject, None, None]:
"""
Search for Python projects in a path and return PythonProject instances.
Parameters:
path: where to start looking for pyproject.toml files. Default: git root or '.'
query: substring for package selection. '-' and '_' are equivalent. case insensitive.
query: Name of the project:
- case-insensitive
- Substring match if exact_match is false.
- '-' and '_' are equivalent.
exact_match: turns query into an exact match. Recommended use: CI scripts
verbose: output more details to command line
predicate: optional inclusion filter
find_nested: search in subdirectories
"""
if not path:
path = find_repo_root(default=".")
Expand All @@ -47,7 +52,9 @@ def discover_pyprojects(
raise PythonProjectNotFound("An exact match was requested but no query was provided.")

predicate = predicate or (lambda _: True)
paths = find_paths(PythonFile.PyProjectToml, search_from=path, in_children=True, in_root=True)
paths = find_paths(
PythonFile.PyProjectToml, search_from=path, in_children=find_nested, in_root=True
)

count_projects = 0
for file in paths:
Expand Down

0 comments on commit 600ce57

Please sign in to comment.