Skip to content
Closed
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
7 changes: 7 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- id: pyastgrep
name: pyastgrep
description: Search by XPath over AST
entry: pyastgrep
language: python
types: [python]
args: ["--pre-commit-mode", "match_is_error"]
32 changes: 32 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,38 @@ the same code.
You’ll also need some understanding of how to write XPath expressions (see links
at the bottom), but the examples in the next section should get you started.

Usage as pre-commit hook
========================

pyastgrep can be used as pre-commit hook.

Blocking usage of a function (it's just an example for simplicity, for this exact case
see also banned API functionality in ruff/flake8):

.. code-block::

- repo: https://github.com/spookylukey/pyastgrep
rev: <put a version here>
hooks:
- id: pyastgrep
name: No usage of legacy_function_name
args: ['.//FunctionDef[@name="legacy_function_name"]']

Requiring something to present in the files

.. code-block::

- repo: https://github.com/spookylukey/pyastgrep
rev: <put a version here>
hooks:
- id: pyastgrep
name: needed_function_name is added to migration files
args: [
"--pre-commit-mode",
"non_match_is_error",
'.//FunctionDef[@name="needed_function_name"]'
]
files: 'migrations/.*\.py$'


Links
Expand Down
28 changes: 28 additions & 0 deletions src/pyastgrep/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
from .printer import print_results
from .search import search_python_files

if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum


NAME_AND_VERSION = "pyastgrep " + __version__
parser = argparse.ArgumentParser(
prog=NAME_AND_VERSION,
Expand All @@ -36,6 +42,11 @@ def context_parameter(param: str) -> int | StatementContext:
return int(param) # Will raise ValueError if invalid, which is handled by argparse


class PreCommitMode(StrEnum):
MATCH_IS_ERROR = "match_is_error"
NON_MATCH_IS_ERROR = "non_match_is_error"


# Names of arguments:
#
# We try to match names from ripgrep where the behaviour is basically the same
Expand Down Expand Up @@ -176,6 +187,15 @@ def context_parameter(param: str) -> int | StatementContext:
action="store_true",
default=False,
)
parser.add_argument(
"--pre-commit-mode",
help="""Define what exit code should be used for match/non-match cases:
match_is_error - pyastgrep will return non-zero exit code for match
non_match_is_error - pyastgrep will return non-zero exit code if NO match
""",
type=PreCommitMode,
choices=list(PreCommitMode),
)
parser.add_argument(
"expr",
help="XPath search expression\n\n",
Expand Down Expand Up @@ -279,6 +299,14 @@ def main(sys_args: list[str] | None = None, stdin: BinaryIO | None = None) -> in
return ERROR
except KeyboardInterrupt:
sys.exit(1)
if args.pre_commit_mode:
if errors:
return ERROR
if args.pre_commit_mode == PreCommitMode.MATCH_IS_ERROR and matches:
return ERROR
if args.pre_commit_mode == PreCommitMode.NON_MATCH_IS_ERROR and not matches:
return ERROR
return 0
# Match ripgrep:
if errors and not args.quiet:
return ERROR
Expand Down