Coverage for src/pullapprove/cli.py: 0%
79 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-10 15:55 -0500
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-10 15:55 -0500
1import os
2import sys
3from pathlib import Path
4from textwrap import dedent
6import click
7from pydantic import ValidationError
9from . import git
10from .config import CONFIG_FILENAME, CONFIG_FILENAME_PREFIX, ConfigModel, ConfigModels
11from .matches import match_diff, match_files
14@click.group()
15def cli():
16 pass
19@cli.command()
20@click.option("--filename", default=CONFIG_FILENAME, help="Configuration filename")
21def init(filename):
22 config_path = Path(filename)
23 if config_path.exists():
24 click.secho(f"{CONFIG_FILENAME} already exists!", fg="red")
25 sys.exit(1)
27 # Could we use blame to guess?
28 # go straight to agent?
29 # gh auth status can give us the user? or ask what's their username?
30 # keep it simple - agent can do more when I get to it
32 contents = """
33 [[scopes]]
34 name = "default"
35 paths = ["**/*"]
36 request = 1
37 require = 1
38 reviewers = ["<YOU>"]
40 [[scopes]]
41 name = "pullapprove"
42 paths = ["**/CODEREVIEW.toml"]
43 request = 1
44 require = 1
45 """
46 config_path.write_text(dedent(contents).strip() + "\n")
47 click.secho(f"Created {filename}")
50@cli.command()
51@click.argument("path", type=click.Path(exists=True), default=".")
52@click.option("--quiet", is_flag=True)
53def validate(path, quiet):
54 """
55 Locally validate config files
56 """
58 errors = {}
60 configs = ConfigModels(root={})
62 for root, _, files in os.walk(path):
63 for f in files:
64 if f.startswith(CONFIG_FILENAME_PREFIX):
65 config_path = Path(root) / f
67 if not quiet:
68 click.echo(config_path, nl=False)
69 try:
70 configs.add_config(
71 ConfigModel.from_filesystem(config_path), config_path
72 )
74 if not quiet:
75 click.secho(" -> OK", fg="green")
76 except ValidationError as e:
77 if not quiet:
78 click.secho(" -> ERROR", fg="red")
80 errors[config_path] = e
82 for path, error in errors.items():
83 click.secho(str(path), fg="red")
84 print(error)
86 if errors:
87 sys.exit(1)
89 return configs
92@cli.command()
93@click.argument("path", type=click.Path(exists=True), default=".")
94@click.option("--changed", is_flag=True)
95@click.option("--json", "as_json", is_flag=True)
96@click.option("--by", type=click.Choice(["scope", "path"]), default="path")
97@click.pass_context
98def ls(ctx, path, changed, as_json, by):
99 """
100 List files and lines that match scopes
101 """
102 configs = ctx.invoke(validate, path=path, quiet=True)
104 if changed:
105 iterator = git.git_ls_changes(path)
106 else:
107 iterator = git.git_ls_files(path)
109 results = match_files(configs, iterator)
110 if as_json:
111 click.echo(results.model_dump_json(indent=2))
112 else:
113 results.print(by=by)
116@cli.command()
117@click.argument("path", type=click.Path(exists=True), default=".")
118@click.option("--json", "as_json", is_flag=True)
119@click.option("--staged", is_flag=True)
120@click.option("--by", type=click.Choice(["scope", "path"]), default="path")
121@click.pass_context
122def diff(ctx, path, as_json, staged, by):
123 configs = ctx.invoke(validate, path=path, quiet=True)
125 diff_args = []
126 if staged:
127 diff_args.append("--staged")
129 diff_stream = git.git_diff_stream(path, *diff_args)
131 results, _ = match_diff(configs, diff_stream)
132 if as_json:
133 click.echo(results.model_dump_json(indent=2))
134 else:
135 results.print(by=by)
138# @cli.command()
139# @click.option("--check", is_flag=True)
140# @click.argument("path", type=click.Path(exists=True), default=".")
141# def coverage(path, check):
142# config = load_root_config()
143# num_matched = 0
144# num_total = 0
146# for f in git.git_ls_files(path):
147# file_path = Path(f)
148# matched = False
150# # TODO doesn't include line patterns...
152# for scope in config.config.scopes:
153# if scope.matches_path(file_path):
154# matched = True
155# break
157# if matched:
158# num_matched += 1
159# num_total += 1
161# percentage = f"{num_matched / num_total:.1%}"
162# click.echo(f"{num_matched}/{num_total} files covered ({percentage})")
164# if check and num_matched != num_total:
165# sys.exit(1)
168# list - find open PRs, find status url and send json request (needs PA token)