pyfltr 2.2.2__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyfltr-2.2.2 → pyfltr-2.3.0}/CLAUDE.md +1 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/PKG-INFO +1 -1
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/guide/usage.md +29 -3
- {pyfltr-2.2.2 → pyfltr-2.3.0}/mkdocs.yml +3 -5
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/cli.py +5 -1
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/llm_output.py +40 -5
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/main.py +24 -1
- pyfltr-2.3.0/pyfltr/shell_completion.py +214 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/output_format_test.py +75 -16
- pyfltr-2.3.0/tests/shell_completion_test.py +111 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.claude/agents/error-parser-reviewer.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.claude/agents/tool-compat-checker.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.claude/skills/pyfltr-add-tool/SKILL.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.editorconfig +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.gitattributes +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.github/workflows/ci.yaml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.github/workflows/docs.yaml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.github/workflows/release.yaml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.gitignore +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.gitmessage +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.markdownlint-cli2.yaml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.npmrc +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.pre-commit-config.yaml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.pylintrc +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.python-version +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.textlintrc.yaml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.vscode/extensions.json +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/.vscode/settings.json +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/LICENSE +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/Makefile +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/README.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/cliff.toml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/.markdownlint-cli2.yaml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/development/development.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/development/index.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/guide/configuration-tools.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/guide/configuration.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/guide/custom-commands.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/guide/index.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/guide/recommended-nonpython.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/guide/recommended.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/docs/index.md +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/mise.toml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/__init__.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/__main__.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/command.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/config.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/error_parser.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/executor.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/precommit.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/ui.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyfltr/warnings_.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/pyproject.toml +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/__init__.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/cli_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/command_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/config_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/conftest.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/error_parser_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/executor_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/llm_output_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/main_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/precommit_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/ui_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/tests/warnings_test.py +0 -0
- {pyfltr-2.2.2 → pyfltr-2.3.0}/uv.lock +0 -0
|
@@ -15,4 +15,5 @@
|
|
|
15
15
|
|
|
16
16
|
- `uv run mkdocs build --strict`でリンク・nav整合性を検証(ただし日本語アンカーリンク`#見出し日本語`はMkDocs TOCで解決できずINFO通知のみで`--strict`でも検知されないため手動確認要)
|
|
17
17
|
- `docs/guide/index.md`の対応ツール一覧と`mkdocs.yml`内llmstxt `markdown_description`の「対応ツール」節は人手同期(SSOT化しない運用)
|
|
18
|
+
- `mkdocs.yml`内llmstxt `markdown_description`にはLLMが利用する際に有用な情報のみ記載する(`run-for-agent`サブコマンド、主要オプションなど)。LLMにとって不要な情報はdocs側をSSOTとし、多重管理を避ける
|
|
18
19
|
- ドキュメント構成変更時は`docs/development/development.md`の「READMEとdocsの役割分担」節を先に参照
|
|
@@ -70,6 +70,30 @@ pyfltr generate-config
|
|
|
70
70
|
設定ファイルの雛形を標準出力に書き出す。`[tool.pyfltr]`セクションに貼り付けて利用する。
|
|
71
71
|
このサブコマンドは他のオプションやターゲット指定を受け付けず、設定出力だけを行う。
|
|
72
72
|
|
|
73
|
+
### サブコマンド: generate-shell-completion
|
|
74
|
+
|
|
75
|
+
```shell
|
|
76
|
+
pyfltr generate-shell-completion bash
|
|
77
|
+
pyfltr generate-shell-completion powershell
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
シェル補完スクリプトを標準出力に書き出す。
|
|
81
|
+
引数にシェル種別(`bash`または`powershell`)を指定する。
|
|
82
|
+
|
|
83
|
+
bashでの設定例:
|
|
84
|
+
|
|
85
|
+
```shell
|
|
86
|
+
eval "$(pyfltr generate-shell-completion bash)"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
PowerShellでの設定例:
|
|
90
|
+
|
|
91
|
+
```powershell
|
|
92
|
+
pyfltr generate-shell-completion powershell | Out-String | Invoke-Expression
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
永続化する場合はプロファイルに上記を追記する。
|
|
96
|
+
|
|
73
97
|
### `[files and/or directories ...]`
|
|
74
98
|
|
|
75
99
|
対象を指定しなかった場合は、カレントディレクトリ(`.`)を指定した場合と同じ扱いとなる。
|
|
@@ -176,14 +200,16 @@ CLIオプション`--output-format`が指定されている場合は環境変数
|
|
|
176
200
|
|
|
177
201
|
### jsonlスキーマ
|
|
178
202
|
|
|
179
|
-
出力は以下
|
|
203
|
+
出力は以下5種別のレコードからなる。`kind`フィールドでレコード種別を判別する。
|
|
180
204
|
|
|
205
|
+
- `header`: 先頭1行。実行環境情報(`version` / `python` / `platform` / `cwd` / `commands` / `files`)
|
|
181
206
|
- `warning`: pyfltrが検出した設定・実行時の警告(`source`で発生元を識別)
|
|
182
207
|
- `diagnostic`: 個々の診断(`error_parser`対応ツールの抽出結果)
|
|
183
208
|
- `tool`: 1ツール1レコードの実行メタ情報
|
|
184
209
|
- `summary`: 最終1行、全体集計
|
|
185
210
|
|
|
186
211
|
```json
|
|
212
|
+
{"kind":"header","version":"1.25.0","python":"3.12.0 ...","executable":"/usr/bin/python3","platform":"linux","cwd":"/work","commands":["mypy","black"],"files":12}
|
|
187
213
|
{"kind":"warning","source":"config","msg":"pre-commit が有効化されていますが、設定ファイルが見つかりません: .pre-commit-config.yaml"}
|
|
188
214
|
{"kind":"diagnostic","tool":"mypy","file":"src/a.py","line":42,"col":5,"msg":"Incompatible return value type"}
|
|
189
215
|
{"kind":"tool","tool":"mypy","type":"linter","status":"failed","files":12,"elapsed":0.8,"diagnostics":1,"rc":1}
|
|
@@ -191,9 +217,9 @@ CLIオプション`--output-format`が指定されている場合は環境変数
|
|
|
191
217
|
{"kind":"summary","total":2,"succeeded":0,"formatted":1,"failed":1,"skipped":0,"diagnostics":1,"exit":1}
|
|
192
218
|
```
|
|
193
219
|
|
|
194
|
-
stdoutモード(`--output-file
|
|
220
|
+
stdoutモード(`--output-file`未指定)では、先頭にheader行を出力し、各ツールの完了時にdiagnostic行+tool行を随時書き出す。
|
|
195
221
|
ツール間の出力順は完了順となり、最後にwarning行+summary行が続く。
|
|
196
|
-
ファイル出力時(`--output-file
|
|
222
|
+
ファイル出力時(`--output-file`指定)では、先頭にheader行、続いて`pyproject.toml`の定義順にツール単位でグルーピングし、先頭にwarning行、末尾にsummary行を配置する。
|
|
197
223
|
|
|
198
224
|
`warning`レコードの`source`は`config`(設定ファイル不在など)/`tool-resolve`(ツール解決失敗)/`file-resolver`(対象ファイル選定時)/`git`(`git check-ignore`失敗)のいずれか。
|
|
199
225
|
|
|
@@ -39,7 +39,6 @@ plugins:
|
|
|
39
39
|
- `pyfltr run`: 全チェック。fix段→formatter段→linter/tester段の順で、fix-args定義済みlinterの自動修正→formatter→残りの検査を実行する(ローカル向け)
|
|
40
40
|
- `pyfltr fast`: 軽量チェック。`run`と同じ3段構成だが、mypy/pylint/pytest等の重いツールを除外(pre-commit向け)
|
|
41
41
|
- `pyfltr run-for-agent`: `pyfltr run --output-format=jsonl`のエイリアス。JSONL出力を既定にする(LLMエージェント向け)
|
|
42
|
-
- `pyfltr generate-config`: pyproject.toml用の設定雛形を出力
|
|
43
42
|
|
|
44
43
|
`--no-fix`で`run`/`fast`/`run-for-agent`のfix段を抑止できる。`ci`は副作用回避のため元々fix段を持たない。
|
|
45
44
|
|
|
@@ -68,16 +67,15 @@ plugins:
|
|
|
68
67
|
|
|
69
68
|
## LLMエージェント向け出力(`--output-format=jsonl`)
|
|
70
69
|
|
|
71
|
-
`--output-format=jsonl`でJSON Lines形式の構造化出力が得られる。レコードは
|
|
70
|
+
`--output-format=jsonl`でJSON Lines形式の構造化出力が得られる。レコードは5種類。
|
|
72
71
|
|
|
72
|
+
- `header`: 先頭1行。実行環境情報(`version` / `python` / `platform` / `cwd` / `commands` / `files`)
|
|
73
73
|
- `warning`: pyfltr が検出した設定・実行時の警告(`source` で発生元を識別)
|
|
74
74
|
- `diagnostic`: 1診断1行(`tool` / `file` / `line` / `col` / `rule` / `severity` / `msg`)
|
|
75
75
|
- `tool`: ツールごとの実行結果サマリ(`status` / `elapsed` / `diagnostics` / `rc`)
|
|
76
76
|
- `summary`: 最終1行の全体集計(`succeeded` / `formatted` / `failed` / `skipped` / `diagnostics` / `exit`)
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`--output-file`未指定時はstdoutへJSONLのみを書き、進捗ログやTUIは無音化される。指定時はファイルへJSONLを書き、stdoutには従来どおりのテキスト出力を並行出力する(ローカル実行で進捗を追える)。
|
|
78
|
+
stdoutモード(`--output-file`未指定)の出力順は`header`→ツール完了順に`diagnostic`+`tool`→`warning`→`summary`。stdoutへJSONLのみを書き、進捗ログやTUIは無音化される。`--output-file`指定時はファイルへJSONLを書き、stdoutには従来どおりのテキスト出力を並行出力する(ローカル実行で進捗を追える)。
|
|
81
79
|
|
|
82
80
|
```shell
|
|
83
81
|
pyfltr run-for-agent
|
|
@@ -140,6 +140,8 @@ def render_results(
|
|
|
140
140
|
output_format: str = "text",
|
|
141
141
|
output_file: pathlib.Path | None = None,
|
|
142
142
|
exit_code: int = 0,
|
|
143
|
+
commands: list[str] | None = None,
|
|
144
|
+
files: int | None = None,
|
|
143
145
|
warnings: list[dict[str, typing.Any]] | None = None,
|
|
144
146
|
) -> None:
|
|
145
147
|
"""実行結果を `成功コマンド → 失敗コマンド → summary` の順でまとめて出力する。
|
|
@@ -160,7 +162,9 @@ def render_results(
|
|
|
160
162
|
warnings = warnings or []
|
|
161
163
|
|
|
162
164
|
if output_format == "jsonl":
|
|
163
|
-
pyfltr.llm_output.write_jsonl(
|
|
165
|
+
pyfltr.llm_output.write_jsonl(
|
|
166
|
+
ordered, config, exit_code=exit_code, destination=output_file, commands=commands, files=files, warnings=warnings
|
|
167
|
+
)
|
|
164
168
|
if output_file is None:
|
|
165
169
|
return
|
|
166
170
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""LLM 向け JSON Lines 出力。
|
|
2
2
|
|
|
3
3
|
`--output-format=jsonl` で呼ばれ、CommandResult 群を LLM / エージェントが
|
|
4
|
-
読みやすいフラットな JSON Lines 形式 (diagnostic / tool / summary の
|
|
4
|
+
読みやすいフラットな JSON Lines 形式 (header / diagnostic / tool / summary の 4 種別) に
|
|
5
5
|
変換して書き出す。
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import importlib.metadata
|
|
8
9
|
import json
|
|
10
|
+
import os
|
|
9
11
|
import pathlib
|
|
10
12
|
import sys
|
|
11
13
|
import threading
|
|
@@ -47,14 +49,17 @@ def build_lines(
|
|
|
47
49
|
config: pyfltr.config.Config,
|
|
48
50
|
*,
|
|
49
51
|
exit_code: int,
|
|
52
|
+
commands: list[str] | None = None,
|
|
53
|
+
files: int | None = None,
|
|
50
54
|
warnings: list[dict[str, typing.Any]] | None = None,
|
|
51
55
|
) -> list[str]:
|
|
52
56
|
"""CommandResult群からJSONL各行を生成する。
|
|
53
57
|
|
|
54
58
|
出力順:
|
|
55
|
-
1. ``
|
|
56
|
-
2.
|
|
57
|
-
3.
|
|
59
|
+
1. ``commands``と``files``が指定されていればkind="header"行
|
|
60
|
+
2. ``warnings``が非空ならkind="warning"行
|
|
61
|
+
3. ツール単位でdiagnostic行+tool行(``config.command_names``の定義順)
|
|
62
|
+
4. summary行1行
|
|
58
63
|
|
|
59
64
|
resultsは順序を問わない。内部で``config.command_names``順にソートする。
|
|
60
65
|
``warnings``は``pyfltr.warnings_.collected_warnings()``の返り値を想定する。
|
|
@@ -63,6 +68,9 @@ def build_lines(
|
|
|
63
68
|
|
|
64
69
|
lines: list[str] = []
|
|
65
70
|
|
|
71
|
+
if commands is not None and files is not None:
|
|
72
|
+
lines.append(_dump(_build_header_record(commands, files)))
|
|
73
|
+
|
|
66
74
|
for warning in warnings or []:
|
|
67
75
|
lines.append(_dump(_build_warning_record(warning)))
|
|
68
76
|
|
|
@@ -86,6 +94,8 @@ def write_jsonl(
|
|
|
86
94
|
*,
|
|
87
95
|
exit_code: int,
|
|
88
96
|
destination: pathlib.Path | None,
|
|
97
|
+
commands: list[str] | None = None,
|
|
98
|
+
files: int | None = None,
|
|
89
99
|
warnings: list[dict[str, typing.Any]] | None = None,
|
|
90
100
|
) -> None:
|
|
91
101
|
"""JSONL を stdout もしくは指定ファイルに書き出す。
|
|
@@ -94,7 +104,7 @@ def write_jsonl(
|
|
|
94
104
|
親ディレクトリを自動作成し、atomic write せず単純に上書きする
|
|
95
105
|
(LLM 用途の使い捨てのため)。
|
|
96
106
|
"""
|
|
97
|
-
lines = build_lines(results, config, exit_code=exit_code, warnings=warnings)
|
|
107
|
+
lines = build_lines(results, config, exit_code=exit_code, commands=commands, files=files, warnings=warnings)
|
|
98
108
|
if destination is None:
|
|
99
109
|
for line in lines:
|
|
100
110
|
sys.stdout.write(line)
|
|
@@ -108,6 +118,17 @@ def write_jsonl(
|
|
|
108
118
|
f.write("\n")
|
|
109
119
|
|
|
110
120
|
|
|
121
|
+
def write_jsonl_header(commands: list[str], files: int) -> None:
|
|
122
|
+
"""header行をstdoutに書き出す(ストリーミングモード用)。
|
|
123
|
+
|
|
124
|
+
パイプライン開始直後、diagnostic行より前に1回だけ呼ぶ。
|
|
125
|
+
"""
|
|
126
|
+
with _write_lock:
|
|
127
|
+
sys.stdout.write(_dump(_build_header_record(commands, files)))
|
|
128
|
+
sys.stdout.write("\n")
|
|
129
|
+
sys.stdout.flush()
|
|
130
|
+
|
|
131
|
+
|
|
111
132
|
def write_jsonl_streaming(
|
|
112
133
|
result: pyfltr.command.CommandResult,
|
|
113
134
|
config: pyfltr.config.Config,
|
|
@@ -149,6 +170,20 @@ def _dump(record: dict[str, typing.Any]) -> str:
|
|
|
149
170
|
return json.dumps(record, ensure_ascii=False, separators=(",", ":"))
|
|
150
171
|
|
|
151
172
|
|
|
173
|
+
def _build_header_record(commands: list[str], files: int) -> dict[str, typing.Any]:
|
|
174
|
+
"""実行環境の基本情報を header レコード dict として返す。"""
|
|
175
|
+
return {
|
|
176
|
+
"kind": "header",
|
|
177
|
+
"version": importlib.metadata.version("pyfltr"),
|
|
178
|
+
"python": sys.version,
|
|
179
|
+
"executable": sys.executable,
|
|
180
|
+
"platform": sys.platform,
|
|
181
|
+
"cwd": os.getcwd(),
|
|
182
|
+
"commands": commands,
|
|
183
|
+
"files": files,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
152
187
|
def _build_warning_record(entry: dict[str, typing.Any]) -> dict[str, typing.Any]:
|
|
153
188
|
"""警告 dict を warning レコード dict に変換する。"""
|
|
154
189
|
return {
|
|
@@ -14,6 +14,7 @@ import pyfltr.cli
|
|
|
14
14
|
import pyfltr.command
|
|
15
15
|
import pyfltr.config
|
|
16
16
|
import pyfltr.llm_output
|
|
17
|
+
import pyfltr.shell_completion
|
|
17
18
|
import pyfltr.ui
|
|
18
19
|
import pyfltr.warnings_
|
|
19
20
|
|
|
@@ -41,6 +42,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
41
42
|
" fast 高速ツールのみ実行 (--commands=fast 相当)。\n"
|
|
42
43
|
" run-for-agent LLM エージェント向け (JSONL 出力を既定化)。\n"
|
|
43
44
|
" generate-config pyproject.toml 用の設定雛形を出力する。\n"
|
|
45
|
+
" generate-shell-completion <shell>\n"
|
|
46
|
+
" シェル補完スクリプトを出力する (bash / powershell)。\n"
|
|
44
47
|
"\n"
|
|
45
48
|
"ドキュメント: https://ak110.github.io/pyfltr/\n"
|
|
46
49
|
"llms.txt: https://ak110.github.io/pyfltr/llms.txt"
|
|
@@ -151,12 +154,16 @@ _SUBCOMMANDS: frozenset[str] = frozenset(
|
|
|
151
154
|
"fast",
|
|
152
155
|
"run-for-agent",
|
|
153
156
|
"generate-config",
|
|
157
|
+
"generate-shell-completion",
|
|
154
158
|
# 以下は廃止済み
|
|
155
159
|
"fix",
|
|
156
160
|
"dirty",
|
|
157
161
|
}
|
|
158
162
|
)
|
|
159
163
|
|
|
164
|
+
# 廃止済みサブコマンド
|
|
165
|
+
_DEPRECATED_SUBCOMMANDS: frozenset[str] = frozenset({"fix", "dirty"})
|
|
166
|
+
|
|
160
167
|
|
|
161
168
|
def _parse_subcommand(sys_args: typing.Sequence[str]) -> tuple[str, list[str]]:
|
|
162
169
|
"""第一引数からサブコマンドを判定し、(subcommand, remaining_args)を返す。
|
|
@@ -190,7 +197,7 @@ def run(sys_args: typing.Sequence[str] | None = None) -> int:
|
|
|
190
197
|
subcommand, remaining_args = _parse_subcommand(sys_args)
|
|
191
198
|
|
|
192
199
|
# 廃止済みサブコマンド
|
|
193
|
-
if subcommand in
|
|
200
|
+
if subcommand in _DEPRECATED_SUBCOMMANDS:
|
|
194
201
|
logger.error(f"{subcommand} サブコマンドは廃止されました。")
|
|
195
202
|
return 1
|
|
196
203
|
|
|
@@ -200,6 +207,17 @@ def run(sys_args: typing.Sequence[str] | None = None) -> int:
|
|
|
200
207
|
logger.info(pyfltr.config.generate_config_text())
|
|
201
208
|
return 0
|
|
202
209
|
|
|
210
|
+
# generate-shell-completionサブコマンド: 補完スクリプトをstdoutに出力する
|
|
211
|
+
if subcommand == "generate-shell-completion":
|
|
212
|
+
if not remaining_args or remaining_args[0] not in pyfltr.shell_completion.SUPPORTED_SHELLS:
|
|
213
|
+
supported = ", ".join(pyfltr.shell_completion.SUPPORTED_SHELLS)
|
|
214
|
+
logger.error(f"シェルを指定してください: pyfltr generate-shell-completion <{supported}>")
|
|
215
|
+
return 1
|
|
216
|
+
active_subcommands = _SUBCOMMANDS - _DEPRECATED_SUBCOMMANDS
|
|
217
|
+
script = pyfltr.shell_completion.generate(remaining_args[0], build_parser(), active_subcommands)
|
|
218
|
+
print(script, end="")
|
|
219
|
+
return 0
|
|
220
|
+
|
|
203
221
|
effective_args = _build_effective_args(subcommand, remaining_args)
|
|
204
222
|
|
|
205
223
|
parser = build_parser()
|
|
@@ -407,6 +425,9 @@ def run_pipeline(
|
|
|
407
425
|
# JSONL stdoutモード: ツール完了時に随時JSONL行を書き出す
|
|
408
426
|
jsonl_stdout = (args.output_format == "jsonl") and (args.output_file is None)
|
|
409
427
|
|
|
428
|
+
if jsonl_stdout:
|
|
429
|
+
pyfltr.llm_output.write_jsonl_header(commands=commands, files=len(all_files))
|
|
430
|
+
|
|
410
431
|
# run
|
|
411
432
|
include_fix_stage = bool(getattr(args, "include_fix_stage", False))
|
|
412
433
|
if use_ui:
|
|
@@ -448,6 +469,8 @@ def run_pipeline(
|
|
|
448
469
|
output_format=args.output_format or "text",
|
|
449
470
|
output_file=args.output_file,
|
|
450
471
|
exit_code=returncode,
|
|
472
|
+
commands=commands,
|
|
473
|
+
files=len(all_files),
|
|
451
474
|
warnings=pyfltr.warnings_.collected_warnings(),
|
|
452
475
|
)
|
|
453
476
|
return returncode
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""シェル補完スクリプトの生成。"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
import pyfltr.config
|
|
6
|
+
|
|
7
|
+
SUPPORTED_SHELLS: tuple[str, ...] = ("bash", "powershell")
|
|
8
|
+
"""対応シェル。"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate(
|
|
12
|
+
shell: str,
|
|
13
|
+
parser: argparse.ArgumentParser,
|
|
14
|
+
subcommands: frozenset[str],
|
|
15
|
+
) -> str:
|
|
16
|
+
"""シェル種別に応じた補完スクリプトを返す。"""
|
|
17
|
+
options, output_format_choices, commands_choices = _collect_completions(parser)
|
|
18
|
+
subcommand_list = sorted(subcommands)
|
|
19
|
+
if shell == "bash":
|
|
20
|
+
return _generate_bash(options, output_format_choices, commands_choices, subcommand_list)
|
|
21
|
+
if shell == "powershell":
|
|
22
|
+
return _generate_powershell(options, output_format_choices, commands_choices, subcommand_list)
|
|
23
|
+
raise ValueError(f"未対応のシェル: {shell!r}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _collect_completions(
|
|
27
|
+
parser: argparse.ArgumentParser,
|
|
28
|
+
) -> tuple[list[str], list[str], list[str]]:
|
|
29
|
+
"""パーサーからオプション名・choices・コマンド名を収集する。
|
|
30
|
+
|
|
31
|
+
戻り値: (options, output_format_choices, commands_choices)
|
|
32
|
+
"""
|
|
33
|
+
options: list[str] = []
|
|
34
|
+
output_format_choices: list[str] = []
|
|
35
|
+
for action in parser._actions: # noqa: SLF001 # pylint: disable=protected-access
|
|
36
|
+
for opt in action.option_strings:
|
|
37
|
+
options.append(opt)
|
|
38
|
+
if "--output-format" in action.option_strings and action.choices:
|
|
39
|
+
output_format_choices = list(action.choices)
|
|
40
|
+
|
|
41
|
+
# --commands の補完候補: ビルトインコマンド名 + 静的エイリアスキー
|
|
42
|
+
commands_choices = list(pyfltr.config.BUILTIN_COMMAND_NAMES)
|
|
43
|
+
aliases = pyfltr.config.DEFAULT_CONFIG.get("aliases", {})
|
|
44
|
+
assert isinstance(aliases, dict)
|
|
45
|
+
for alias_name in aliases:
|
|
46
|
+
if alias_name not in commands_choices:
|
|
47
|
+
commands_choices.append(alias_name)
|
|
48
|
+
commands_choices.sort()
|
|
49
|
+
|
|
50
|
+
return sorted(options), sorted(output_format_choices), commands_choices
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _generate_bash(
|
|
54
|
+
options: list[str],
|
|
55
|
+
output_format_choices: list[str],
|
|
56
|
+
commands_choices: list[str],
|
|
57
|
+
subcommands: list[str],
|
|
58
|
+
) -> str:
|
|
59
|
+
"""bash用補完スクリプトを生成する。"""
|
|
60
|
+
opts = " ".join(options)
|
|
61
|
+
subs = " ".join(subcommands)
|
|
62
|
+
shells = " ".join(SUPPORTED_SHELLS)
|
|
63
|
+
fmt_choices = " ".join(output_format_choices)
|
|
64
|
+
cmd_choices = " ".join(commands_choices)
|
|
65
|
+
|
|
66
|
+
return f'''\
|
|
67
|
+
_pyfltr_completions() {{
|
|
68
|
+
local cur prev words cword
|
|
69
|
+
_init_completion || return
|
|
70
|
+
|
|
71
|
+
local subcommands="{subs}"
|
|
72
|
+
local options="{opts}"
|
|
73
|
+
local shells="{shells}"
|
|
74
|
+
local output_formats="{fmt_choices}"
|
|
75
|
+
local commands="{cmd_choices}"
|
|
76
|
+
|
|
77
|
+
# generate-shell-completionの第2引数: シェル名を補完
|
|
78
|
+
if [[ ${{cword}} -ge 2 && "${{words[1]}}" == "generate-shell-completion" ]]; then
|
|
79
|
+
COMPREPLY=( $(compgen -W "${{shells}}" -- "${{cur}}") )
|
|
80
|
+
return
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# --output-format / --output-format= の補完
|
|
84
|
+
if [[ "${{prev}}" == "--output-format" ]]; then
|
|
85
|
+
COMPREPLY=( $(compgen -W "${{output_formats}}" -- "${{cur}}") )
|
|
86
|
+
return
|
|
87
|
+
fi
|
|
88
|
+
if [[ "${{cur}}" == --output-format=* ]]; then
|
|
89
|
+
local prefix="${{cur%%=*}}="
|
|
90
|
+
local typed="${{cur#*=}}"
|
|
91
|
+
COMPREPLY=( $(compgen -W "${{output_formats}}" -- "${{typed}}") )
|
|
92
|
+
COMPREPLY=( "${{COMPREPLY[@]/#/${{prefix}}}}" )
|
|
93
|
+
return
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# --commands / --commands= の補完
|
|
97
|
+
if [[ "${{prev}}" == "--commands" ]]; then
|
|
98
|
+
COMPREPLY=( $(compgen -W "${{commands}}" -- "${{cur}}") )
|
|
99
|
+
return
|
|
100
|
+
fi
|
|
101
|
+
if [[ "${{cur}}" == --commands=* ]]; then
|
|
102
|
+
local prefix="${{cur%%=*}}="
|
|
103
|
+
local typed="${{cur#*=}}"
|
|
104
|
+
COMPREPLY=( $(compgen -W "${{commands}}" -- "${{typed}}") )
|
|
105
|
+
COMPREPLY=( "${{COMPREPLY[@]/#/${{prefix}}}}" )
|
|
106
|
+
return
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# 第1引数: サブコマンド + オプション
|
|
110
|
+
if [[ ${{cword}} -eq 1 ]]; then
|
|
111
|
+
COMPREPLY=( $(compgen -W "${{subcommands}} ${{options}}" -- "${{cur}}") )
|
|
112
|
+
return
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# オプション開始
|
|
116
|
+
if [[ "${{cur}}" == -* ]]; then
|
|
117
|
+
COMPREPLY=( $(compgen -W "${{options}}" -- "${{cur}}") )
|
|
118
|
+
return
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# ファイル/ディレクトリ補完
|
|
122
|
+
_filedir
|
|
123
|
+
}}
|
|
124
|
+
|
|
125
|
+
complete -o default -F _pyfltr_completions pyfltr
|
|
126
|
+
'''
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _generate_powershell(
|
|
130
|
+
options: list[str],
|
|
131
|
+
output_format_choices: list[str],
|
|
132
|
+
commands_choices: list[str],
|
|
133
|
+
subcommands: list[str],
|
|
134
|
+
) -> str:
|
|
135
|
+
"""PowerShell用補完スクリプトを生成する。"""
|
|
136
|
+
# PowerShellの配列リテラルとして出力
|
|
137
|
+
opts_ps = ", ".join(f"'{o}'" for o in options)
|
|
138
|
+
subs_ps = ", ".join(f"'{s}'" for s in subcommands)
|
|
139
|
+
shells_ps = ", ".join(f"'{s}'" for s in SUPPORTED_SHELLS)
|
|
140
|
+
fmt_ps = ", ".join(f"'{f}'" for f in output_format_choices)
|
|
141
|
+
cmd_ps = ", ".join(f"'{c}'" for c in commands_choices)
|
|
142
|
+
|
|
143
|
+
return f"""\
|
|
144
|
+
Register-ArgumentCompleter -Native -CommandName pyfltr -ScriptBlock {{
|
|
145
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
146
|
+
|
|
147
|
+
$subcommands = @({subs_ps})
|
|
148
|
+
$options = @({opts_ps})
|
|
149
|
+
$shells = @({shells_ps})
|
|
150
|
+
$outputFormats = @({fmt_ps})
|
|
151
|
+
$commands = @({cmd_ps})
|
|
152
|
+
|
|
153
|
+
$tokens = $commandAst.ToString().Substring(0, $cursorPosition).Split(' ', [System.StringSplitOptions]::RemoveEmptyEntries)
|
|
154
|
+
$tokenCount = $tokens.Count
|
|
155
|
+
|
|
156
|
+
# generate-shell-completionの第2引数: シェル名を補完
|
|
157
|
+
if ($tokenCount -ge 2 -and $tokens[1] -eq 'generate-shell-completion') {{
|
|
158
|
+
$shells | Where-Object {{ $_ -like "$wordToComplete*" }} | ForEach-Object {{
|
|
159
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
160
|
+
}}
|
|
161
|
+
return
|
|
162
|
+
}}
|
|
163
|
+
|
|
164
|
+
# --output-format の値補完
|
|
165
|
+
if ($tokenCount -ge 2 -and $tokens[-1] -eq '--output-format') {{
|
|
166
|
+
$outputFormats | Where-Object {{ $_ -like "$wordToComplete*" }} | ForEach-Object {{
|
|
167
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
168
|
+
}}
|
|
169
|
+
return
|
|
170
|
+
}}
|
|
171
|
+
if ($wordToComplete -like '--output-format=*') {{
|
|
172
|
+
$typed = $wordToComplete.Substring('--output-format='.Length)
|
|
173
|
+
$outputFormats | Where-Object {{ $_ -like "$typed*" }} | ForEach-Object {{
|
|
174
|
+
$val = "--output-format=$_"
|
|
175
|
+
[System.Management.Automation.CompletionResult]::new($val, $val, 'ParameterValue', $_)
|
|
176
|
+
}}
|
|
177
|
+
return
|
|
178
|
+
}}
|
|
179
|
+
|
|
180
|
+
# --commands の値補完
|
|
181
|
+
if ($tokenCount -ge 2 -and $tokens[-1] -eq '--commands') {{
|
|
182
|
+
$commands | Where-Object {{ $_ -like "$wordToComplete*" }} | ForEach-Object {{
|
|
183
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
184
|
+
}}
|
|
185
|
+
return
|
|
186
|
+
}}
|
|
187
|
+
if ($wordToComplete -like '--commands=*') {{
|
|
188
|
+
$typed = $wordToComplete.Substring('--commands='.Length)
|
|
189
|
+
$commands | Where-Object {{ $_ -like "$typed*" }} | ForEach-Object {{
|
|
190
|
+
$val = "--commands=$_"
|
|
191
|
+
[System.Management.Automation.CompletionResult]::new($val, $val, 'ParameterValue', $_)
|
|
192
|
+
}}
|
|
193
|
+
return
|
|
194
|
+
}}
|
|
195
|
+
|
|
196
|
+
# 第1引数: サブコマンド + オプション
|
|
197
|
+
if ($tokenCount -le 1 -or ($tokenCount -eq 2 -and $wordToComplete -ne '')) {{
|
|
198
|
+
$all = $subcommands + $options
|
|
199
|
+
$all | Where-Object {{ $_ -like "$wordToComplete*" }} | ForEach-Object {{
|
|
200
|
+
$type = if ($_ -like '-*') {{ 'ParameterName' }} else {{ 'ParameterValue' }}
|
|
201
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, $type, $_)
|
|
202
|
+
}}
|
|
203
|
+
return
|
|
204
|
+
}}
|
|
205
|
+
|
|
206
|
+
# オプション開始
|
|
207
|
+
if ($wordToComplete -like '-*') {{
|
|
208
|
+
$options | Where-Object {{ $_ -like "$wordToComplete*" }} | ForEach-Object {{
|
|
209
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterName', $_)
|
|
210
|
+
}}
|
|
211
|
+
return
|
|
212
|
+
}}
|
|
213
|
+
}}
|
|
214
|
+
"""
|
|
@@ -33,11 +33,11 @@ def test_build_lines_supported_tool_diagnostics(default_config):
|
|
|
33
33
|
_make_error("mypy", "src/a.py", 20, "missing return"),
|
|
34
34
|
]
|
|
35
35
|
result = _make_result("mypy", returncode=1, errors=errors)
|
|
36
|
-
lines = pyfltr.llm_output.build_lines([result], default_config, exit_code=1)
|
|
36
|
+
lines = pyfltr.llm_output.build_lines([result], default_config, exit_code=1, commands=["mypy"], files=5)
|
|
37
37
|
parsed = [json.loads(line) for line in lines]
|
|
38
38
|
|
|
39
|
-
assert [r["kind"] for r in parsed] == ["diagnostic", "diagnostic", "tool", "summary"]
|
|
40
|
-
assert parsed[
|
|
39
|
+
assert [r["kind"] for r in parsed] == ["header", "diagnostic", "diagnostic", "tool", "summary"]
|
|
40
|
+
assert parsed[1] == {
|
|
41
41
|
"kind": "diagnostic",
|
|
42
42
|
"tool": "mypy",
|
|
43
43
|
"file": "src/a.py",
|
|
@@ -45,17 +45,17 @@ def test_build_lines_supported_tool_diagnostics(default_config):
|
|
|
45
45
|
"col": 4,
|
|
46
46
|
"msg": "bad type",
|
|
47
47
|
}
|
|
48
|
-
assert parsed[
|
|
48
|
+
assert parsed[2] == {
|
|
49
49
|
"kind": "diagnostic",
|
|
50
50
|
"tool": "mypy",
|
|
51
51
|
"file": "src/a.py",
|
|
52
52
|
"line": 20,
|
|
53
53
|
"msg": "missing return",
|
|
54
54
|
}
|
|
55
|
-
assert parsed[2]["diagnostics"] == 2
|
|
56
|
-
assert parsed[2]["status"] == "failed"
|
|
57
55
|
assert parsed[3]["diagnostics"] == 2
|
|
58
|
-
assert parsed[3]["
|
|
56
|
+
assert parsed[3]["status"] == "failed"
|
|
57
|
+
assert parsed[4]["diagnostics"] == 2
|
|
58
|
+
assert parsed[4]["failed"] == 1
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
def test_build_lines_warnings_prepended(default_config):
|
|
@@ -65,14 +65,15 @@ def test_build_lines_warnings_prepended(default_config):
|
|
|
65
65
|
{"source": "config", "message": "pre-commit 設定ファイル不在"},
|
|
66
66
|
{"source": "git", "message": "git が見つからない"},
|
|
67
67
|
]
|
|
68
|
-
lines = pyfltr.llm_output.build_lines([result], default_config, exit_code=0, warnings=warnings)
|
|
68
|
+
lines = pyfltr.llm_output.build_lines([result], default_config, exit_code=0, commands=["black"], files=1, warnings=warnings)
|
|
69
69
|
parsed = [json.loads(line) for line in lines]
|
|
70
70
|
|
|
71
|
-
assert [
|
|
72
|
-
assert
|
|
73
|
-
assert parsed[1] == {"kind": "warning", "source": "
|
|
71
|
+
assert parsed[0]["kind"] == "header"
|
|
72
|
+
assert [r["kind"] for r in parsed[1:3]] == ["warning", "warning"]
|
|
73
|
+
assert parsed[1] == {"kind": "warning", "source": "config", "msg": "pre-commit 設定ファイル不在"}
|
|
74
|
+
assert parsed[2] == {"kind": "warning", "source": "git", "msg": "git が見つからない"}
|
|
74
75
|
# warnings の後に tool レコード、最後に summary が並ぶ
|
|
75
|
-
assert [r["kind"] for r in parsed[
|
|
76
|
+
assert [r["kind"] for r in parsed[3:]] == ["tool", "summary"]
|
|
76
77
|
|
|
77
78
|
|
|
78
79
|
def test_build_lines_no_warnings_when_omitted(default_config):
|
|
@@ -84,7 +85,7 @@ def test_build_lines_no_warnings_when_omitted(default_config):
|
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
def test_build_lines_unsupported_tool_only(default_config):
|
|
87
|
-
"""error_parser 非対応ツール (black) は tool
|
|
88
|
+
"""error_parser 非対応ツール (black) は tool レコードのみ(header省略時)。"""
|
|
88
89
|
result = _make_result("black", returncode=1, command_type="formatter", has_error=False)
|
|
89
90
|
lines = pyfltr.llm_output.build_lines([result], default_config, exit_code=1)
|
|
90
91
|
parsed = [json.loads(line) for line in lines]
|
|
@@ -115,11 +116,18 @@ def test_build_lines_mixed_order(default_config):
|
|
|
115
116
|
black_result = _make_result("black", returncode=0, command_type="formatter")
|
|
116
117
|
|
|
117
118
|
# config.command_names 順では black → mypy → pylint
|
|
118
|
-
lines = pyfltr.llm_output.build_lines(
|
|
119
|
+
lines = pyfltr.llm_output.build_lines(
|
|
120
|
+
[mypy_result, pylint_result, black_result],
|
|
121
|
+
default_config,
|
|
122
|
+
exit_code=1,
|
|
123
|
+
commands=["black", "mypy", "pylint"],
|
|
124
|
+
files=10,
|
|
125
|
+
)
|
|
119
126
|
parsed = [json.loads(line) for line in lines]
|
|
120
127
|
|
|
121
|
-
# ツール単位のグルーピング: black(tool) → mypy(diagnostic, diagnostic, tool) → pylint(diagnostic, tool) → summary
|
|
128
|
+
# header → ツール単位のグルーピング: black(tool) → mypy(diagnostic, diagnostic, tool) → pylint(diagnostic, tool) → summary
|
|
122
129
|
assert [r["kind"] for r in parsed] == [
|
|
130
|
+
"header",
|
|
123
131
|
"tool", # black
|
|
124
132
|
"diagnostic",
|
|
125
133
|
"diagnostic",
|
|
@@ -253,7 +261,7 @@ def test_calculate_returncode_matches_summary_exit(default_config):
|
|
|
253
261
|
_make_result("black", returncode=0, command_type="formatter"),
|
|
254
262
|
]
|
|
255
263
|
exit_code = pyfltr.main.calculate_returncode(results, exit_zero_even_if_formatted=False)
|
|
256
|
-
lines = pyfltr.llm_output.build_lines(results, default_config, exit_code=exit_code)
|
|
264
|
+
lines = pyfltr.llm_output.build_lines(results, default_config, exit_code=exit_code, commands=["mypy", "black"], files=3)
|
|
257
265
|
summary = json.loads(lines[-1])
|
|
258
266
|
assert summary["exit"] == exit_code == 1
|
|
259
267
|
|
|
@@ -275,6 +283,9 @@ def test_run_cli_jsonl_stdout_suppresses_text(mocker, capsys):
|
|
|
275
283
|
assert "summary" not in captured.out or '"kind":"summary"' in captured.out
|
|
276
284
|
lines = [line for line in captured.out.splitlines() if line.strip()]
|
|
277
285
|
assert lines, "JSONL が 1 行も出ていない"
|
|
286
|
+
first = json.loads(lines[0])
|
|
287
|
+
assert first["kind"] == "header"
|
|
288
|
+
assert "mypy" in first["commands"]
|
|
278
289
|
last = json.loads(lines[-1])
|
|
279
290
|
assert last["kind"] == "summary"
|
|
280
291
|
assert last["exit"] == 0
|
|
@@ -467,3 +478,51 @@ def test_write_jsonl_footer_no_warnings(capsys):
|
|
|
467
478
|
assert len(parsed) == 1
|
|
468
479
|
assert parsed[0]["kind"] == "summary"
|
|
469
480
|
assert parsed[0]["succeeded"] == 1
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# ---------------------------------------------------------------------------
|
|
484
|
+
# header レコードのユニットテスト
|
|
485
|
+
# ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def test_build_header_record_fields():
|
|
489
|
+
"""_build_header_record が必要なフィールドをすべて含むこと。"""
|
|
490
|
+
record = pyfltr.llm_output._build_header_record(["ruff-format", "mypy"], 42)
|
|
491
|
+
assert record["kind"] == "header"
|
|
492
|
+
assert record["commands"] == ["ruff-format", "mypy"]
|
|
493
|
+
assert record["files"] == 42
|
|
494
|
+
assert "version" in record
|
|
495
|
+
assert "python" in record
|
|
496
|
+
assert "executable" in record
|
|
497
|
+
assert "platform" in record
|
|
498
|
+
assert "cwd" in record
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def test_build_lines_header_first(default_config):
|
|
502
|
+
"""commands/filesを指定するとheader行が先頭に出力されること。"""
|
|
503
|
+
result = _make_result("mypy", returncode=0)
|
|
504
|
+
lines = pyfltr.llm_output.build_lines([result], default_config, exit_code=0, commands=["mypy"], files=10)
|
|
505
|
+
parsed = [json.loads(line) for line in lines]
|
|
506
|
+
assert parsed[0]["kind"] == "header"
|
|
507
|
+
assert parsed[0]["commands"] == ["mypy"]
|
|
508
|
+
assert parsed[0]["files"] == 10
|
|
509
|
+
assert parsed[-1]["kind"] == "summary"
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def test_build_lines_no_header_when_omitted(default_config):
|
|
513
|
+
"""commands/filesを省略するとheader行は出力されないこと。"""
|
|
514
|
+
result = _make_result("mypy", returncode=0)
|
|
515
|
+
lines = pyfltr.llm_output.build_lines([result], default_config, exit_code=0)
|
|
516
|
+
parsed = [json.loads(line) for line in lines]
|
|
517
|
+
assert all(r["kind"] != "header" for r in parsed)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def test_write_jsonl_header_stdout(capsys):
|
|
521
|
+
"""write_jsonl_headerがstdoutにheader行を書き出すこと。"""
|
|
522
|
+
pyfltr.llm_output.write_jsonl_header(commands=["ruff-format", "mypy"], files=5)
|
|
523
|
+
captured = capsys.readouterr()
|
|
524
|
+
parsed = [json.loads(line) for line in captured.out.splitlines()]
|
|
525
|
+
assert len(parsed) == 1
|
|
526
|
+
assert parsed[0]["kind"] == "header"
|
|
527
|
+
assert parsed[0]["commands"] == ["ruff-format", "mypy"]
|
|
528
|
+
assert parsed[0]["files"] == 5
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# pylint: disable=missing-module-docstring
|
|
2
|
+
# pylint: disable=missing-function-docstring
|
|
3
|
+
# pylint: disable=protected-access
|
|
4
|
+
|
|
5
|
+
import pyfltr.config
|
|
6
|
+
import pyfltr.main
|
|
7
|
+
import pyfltr.shell_completion
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestCollectCompletions:
|
|
11
|
+
"""補完データ収集のテスト。"""
|
|
12
|
+
|
|
13
|
+
def test_options_contain_verbose(self):
|
|
14
|
+
parser = pyfltr.main.build_parser()
|
|
15
|
+
options, _, _ = pyfltr.shell_completion._collect_completions(parser)
|
|
16
|
+
assert "--verbose" in options
|
|
17
|
+
assert "-v" in options
|
|
18
|
+
|
|
19
|
+
def test_options_contain_output_format(self):
|
|
20
|
+
parser = pyfltr.main.build_parser()
|
|
21
|
+
options, _, _ = pyfltr.shell_completion._collect_completions(parser)
|
|
22
|
+
assert "--output-format" in options
|
|
23
|
+
|
|
24
|
+
def test_output_format_choices(self):
|
|
25
|
+
parser = pyfltr.main.build_parser()
|
|
26
|
+
_, output_format_choices, _ = pyfltr.shell_completion._collect_completions(parser)
|
|
27
|
+
assert "text" in output_format_choices
|
|
28
|
+
assert "jsonl" in output_format_choices
|
|
29
|
+
|
|
30
|
+
def test_commands_choices_contain_builtin_and_aliases(self):
|
|
31
|
+
parser = pyfltr.main.build_parser()
|
|
32
|
+
_, _, commands_choices = pyfltr.shell_completion._collect_completions(parser)
|
|
33
|
+
# ビルトインコマンド
|
|
34
|
+
for name in pyfltr.config.BUILTIN_COMMAND_NAMES:
|
|
35
|
+
assert name in commands_choices
|
|
36
|
+
# 静的エイリアス
|
|
37
|
+
for alias in pyfltr.config.DEFAULT_CONFIG["aliases"]:
|
|
38
|
+
assert alias in commands_choices
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestGenerateBash:
|
|
42
|
+
"""bash補完スクリプト生成のテスト。"""
|
|
43
|
+
|
|
44
|
+
def test_contains_function_and_complete(self):
|
|
45
|
+
parser = pyfltr.main.build_parser()
|
|
46
|
+
subcommands = frozenset({"ci", "run", "fast", "generate-shell-completion"})
|
|
47
|
+
script = pyfltr.shell_completion.generate("bash", parser, subcommands)
|
|
48
|
+
assert "_pyfltr_completions()" in script
|
|
49
|
+
assert "complete -o default -F _pyfltr_completions pyfltr" in script
|
|
50
|
+
|
|
51
|
+
def test_contains_subcommands(self):
|
|
52
|
+
parser = pyfltr.main.build_parser()
|
|
53
|
+
subcommands = frozenset({"ci", "run", "generate-shell-completion"})
|
|
54
|
+
script = pyfltr.shell_completion.generate("bash", parser, subcommands)
|
|
55
|
+
assert "ci" in script
|
|
56
|
+
assert "run" in script
|
|
57
|
+
assert "generate-shell-completion" in script
|
|
58
|
+
|
|
59
|
+
def test_contains_output_format_choices(self):
|
|
60
|
+
parser = pyfltr.main.build_parser()
|
|
61
|
+
script = pyfltr.shell_completion.generate("bash", parser, frozenset({"ci"}))
|
|
62
|
+
assert "text" in script
|
|
63
|
+
assert "jsonl" in script
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestGeneratePowershell:
|
|
67
|
+
"""PowerShell補完スクリプト生成のテスト。"""
|
|
68
|
+
|
|
69
|
+
def test_contains_register_argument_completer(self):
|
|
70
|
+
parser = pyfltr.main.build_parser()
|
|
71
|
+
subcommands = frozenset({"ci", "run"})
|
|
72
|
+
script = pyfltr.shell_completion.generate("powershell", parser, subcommands)
|
|
73
|
+
assert "Register-ArgumentCompleter -Native -CommandName pyfltr" in script
|
|
74
|
+
|
|
75
|
+
def test_contains_subcommands(self):
|
|
76
|
+
parser = pyfltr.main.build_parser()
|
|
77
|
+
subcommands = frozenset({"ci", "run", "generate-shell-completion"})
|
|
78
|
+
script = pyfltr.shell_completion.generate("powershell", parser, subcommands)
|
|
79
|
+
assert "'ci'" in script
|
|
80
|
+
assert "'run'" in script
|
|
81
|
+
assert "'generate-shell-completion'" in script
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestMainIntegration:
|
|
85
|
+
"""main.py経由の統合テスト。"""
|
|
86
|
+
|
|
87
|
+
def test_bash_success(self, capsys):
|
|
88
|
+
rc = pyfltr.main.run(["generate-shell-completion", "bash"])
|
|
89
|
+
assert rc == 0
|
|
90
|
+
captured = capsys.readouterr()
|
|
91
|
+
assert "_pyfltr_completions()" in captured.out
|
|
92
|
+
|
|
93
|
+
def test_powershell_success(self, capsys):
|
|
94
|
+
rc = pyfltr.main.run(["generate-shell-completion", "powershell"])
|
|
95
|
+
assert rc == 0
|
|
96
|
+
captured = capsys.readouterr()
|
|
97
|
+
assert "Register-ArgumentCompleter" in captured.out
|
|
98
|
+
|
|
99
|
+
def test_no_shell_argument(self):
|
|
100
|
+
rc = pyfltr.main.run(["generate-shell-completion"])
|
|
101
|
+
assert rc == 1
|
|
102
|
+
|
|
103
|
+
def test_invalid_shell_argument(self):
|
|
104
|
+
rc = pyfltr.main.run(["generate-shell-completion", "zsh"])
|
|
105
|
+
assert rc == 1
|
|
106
|
+
|
|
107
|
+
def test_subcommand_recognized(self):
|
|
108
|
+
"""generate-shell-completionがサブコマンドとして認識される。"""
|
|
109
|
+
sub, remaining = pyfltr.main._parse_subcommand(["generate-shell-completion", "bash"])
|
|
110
|
+
assert sub == "generate-shell-completion"
|
|
111
|
+
assert remaining == ["bash"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|