• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kimata / py-project / 20766602140

07 Jan 2026 12:34AM UTC coverage: 93.621% (-4.3%) from 97.924%
20766602140

push

github

kimata
refactor: CLI を標準的なパッケージ構成に変更

- src/app.py を src/py_project/cli.py に移動
- src/py_project/__main__.py を追加
- pyproject.toml に [project.scripts] を追加
- `uv run py-project` で実行可能に(`uv run src/app.py` から変更)
- CLAUDE.md を更新
- tests/unit/test_app.py を tests/unit/test_cli.py にリネーム

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

2 of 7 new or added lines in 2 files covered. (28.57%)

1086 of 1160 relevant lines covered (93.62%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.81
/src/py_project/cli.py
1
#!/usr/bin/env python3
2
"""
3
複数の Python プロジェクトに標準的な設定を一括適用します。
4

5
Usage:
6
  py-project [-c CONFIG] [-a] [-p PROJECT]... [-t TYPE]... [-d] [-b] [-v] [options]
7
  py-project [-c CONFIG] --validate
8
  py-project [-c CONFIG] --list-projects
9
  py-project --list-configs
10
  py-project [-c CONFIG] --update-deps [-a]
11

12
Options:
13
  -c CONFIG, --config CONFIG    CONFIG を設定ファイルとして読み込みます。[default: config.yaml]
14
  -a, --apply                   実際に変更を適用します。(指定しないとドライラン)
15
  -p PROJECT, --project PROJECT 対象プロジェクトを限定します。(複数指定可)
16
  -t TYPE, --type TYPE          対象設定タイプを限定します。(複数指定可)
17
  -d, --diff                    差分を詳細表示します。
18
  -b, --backup                  適用前にバックアップを作成します。
19
  -v, --verbose                 詳細ログを出力します。
20
  --no-sync                     pyproject.toml 更新後に uv sync を実行しません。
21
  --git-commit                  更新したファイルを git add & commit します。
22
  --git-push                    更新したファイルを git add & commit & push します。
23
  --validate                    設定ファイルの検証のみ行います。
24
  --list-projects               プロジェクト一覧を表示します。
25
  --list-configs                設定タイプ一覧を表示します。
26
  --update-deps                 テンプレートの依存関係を最新バージョンに更新します。
27
"""
28

29
from __future__ import annotations
1✔
30

31
import logging
1✔
32
import pathlib
1✔
33
import sys
1✔
34

35
import my_lib.config
1✔
36
import my_lib.cui_progress
1✔
37
import my_lib.logger
1✔
38
import rich.console
1✔
39
import rich.table
1✔
40

41
import py_project.applier
1✔
42
import py_project.config
1✔
43
import py_project.dep_updater
1✔
44
import py_project.handlers
1✔
45

46
_SCHEMA_CONFIG = "schema/config.schema"
1✔
47

48

49
def execute(
1✔
50
    config: py_project.config.Config,
51
    options: py_project.config.ApplyOptions,
52
    projects: list[str] | None = None,
53
    config_types: list[str] | None = None,
54
) -> int:
55
    """設定を適用する
56

57
    Args:
58
        config: アプリケーション設定
59
        options: 適用オプション
60
        projects: 対象プロジェクト名のリスト
61
        config_types: 対象設定タイプのリスト
62

63
    Returns:
64
        エラー数(0 なら成功)
65

66
    """
67
    console = rich.console.Console()
1✔
68
    progress = my_lib.cui_progress.ProgressManager(
1✔
69
        console=console,
70
        title=" 🐍 py-project ",
71
    )
72

73
    try:
1✔
74
        progress.start()
1✔
75

76
        summary = py_project.applier.apply_configs(
1✔
77
            config=config,
78
            options=options,
79
            projects=projects,
80
            config_types=config_types,
81
            console=console,
82
            progress=progress,
83
        )
84

85
        return summary.errors
1✔
86
    finally:
87
        progress.stop()
1✔
88

89

90
def show_config_types() -> None:
1✔
91
    """設定タイプ一覧を表示"""
92
    console = rich.console.Console()
1✔
93
    table = rich.table.Table(title="設定タイプ一覧")
1✔
94
    table.add_column("名前", style="cyan")
1✔
95
    table.add_column("説明")
1✔
96

97
    descriptions = {
1✔
98
        "pre-commit": "pre-commit 設定ファイル",
99
        "ruff": "ruff 設定ファイル",
100
        "yamllint": "yamllint 設定ファイル",
101
        "prettier": "prettier 設定ファイル",
102
        "python-version": ".python-version ファイル",
103
        "dockerignore": ".dockerignore ファイル",
104
        "gitignore": ".gitignore ファイル",
105
        "renovate": "renovate 設定ファイル",
106
        "pyproject": "pyproject.toml 共通セクション",
107
        "my-py-lib": "my-py-lib 依存関係の更新",
108
    }
109

110
    for name in py_project.handlers.HANDLERS:
1✔
111
        table.add_row(name, descriptions.get(name, ""))
1✔
112

113
    console.print(table)
1✔
114

115

116
def show_projects(config: py_project.config.Config) -> None:
1✔
117
    """プロジェクト一覧を表示"""
118
    console = rich.console.Console()
1✔
119
    table = rich.table.Table(title="プロジェクト一覧")
1✔
120
    table.add_column("名前", style="cyan")
1✔
121
    table.add_column("パス")
1✔
122
    table.add_column("設定タイプ")
1✔
123

124
    default_configs = config.defaults.configs
1✔
125

126
    for proj in config.projects:
1✔
127
        configs = proj.configs if proj.configs is not None else default_configs
1✔
128
        configs_str = ", ".join(configs) if configs else "(デフォルト)"
1✔
129

130
        table.add_row(proj.name, proj.path, configs_str)
1✔
131

132
    console.print(table)
1✔
133

134

135
def main() -> None:
1✔
136
    """CLI エントリポイント"""
137
    import docopt
×
138

NEW
139
    if __doc__ is None:
×
NEW
140
        raise RuntimeError("__doc__ is not set")
×
141

NEW
142
    args = docopt.docopt(__doc__)
×
143

144
    config_file: str = args["--config"]
×
145
    apply_mode: bool = args["--apply"]
×
146
    projects: list[str] | None = args["--project"] if args["--project"] else None
×
147
    config_types: list[str] | None = args["--type"] if args["--type"] else None
×
148
    show_diff: bool = args["--diff"]
×
149
    backup: bool = args["--backup"]
×
150
    verbose: bool = args["--verbose"]
×
151
    no_sync: bool = args["--no-sync"]
×
152
    git_commit_flag: bool = args["--git-commit"]
×
153
    git_push_flag: bool = args["--git-push"]
×
154
    validate_only: bool = args["--validate"]
×
155
    list_projects_flag: bool = args["--list-projects"]
×
156
    list_configs_flag: bool = args["--list-configs"]
×
157
    update_deps_flag: bool = args["--update-deps"]
×
158

159
    log_level = logging.DEBUG if verbose else logging.INFO
×
160
    my_lib.logger.init("py-project", level=log_level)
×
161

162
    console = rich.console.Console()
×
163

164
    # 設定タイプ一覧表示
165
    if list_configs_flag:
×
166
        show_config_types()
×
167
        sys.exit(0)
×
168

169
    # 設定ファイル読み込み
170
    try:
×
NEW
171
        config_dict = my_lib.config.load(config_file, pathlib.Path(_SCHEMA_CONFIG))
×
172
    except my_lib.config.ConfigFileNotFoundError as e:
×
173
        console.print(f"[red]設定ファイルが見つかりません: {e}[/red]")
×
174
        sys.exit(1)
×
175
    except my_lib.config.ConfigParseError as e:
×
176
        console.print(f"[red]設定ファイルの形式が不正です:[/red]\n{e.details}")
×
177
        sys.exit(1)
×
178
    except my_lib.config.ConfigValidationError as e:
×
179
        console.print(f"[red]設定ファイルの検証に失敗しました:[/red]\n{e.details}")
×
180
        sys.exit(1)
×
181

182
    # 検証のみ
183
    if validate_only:
×
184
        console.print("[green]設定ファイルは正常です[/green]")
×
185
        sys.exit(0)
×
186

187
    # dict を Config オブジェクトに変換
188
    config = py_project.config.Config.from_dict(config_dict)
×
189

190
    # プロジェクト一覧表示
191
    if list_projects_flag:
×
192
        show_projects(config)
×
193
        sys.exit(0)
×
194

195
    # 依存関係更新
196
    if update_deps_flag:
×
197
        template_dir = config.get_template_dir()
×
198
        template_path = template_dir / "pyproject" / "sections.toml"
×
199
        py_project.dep_updater.update_template_deps(
×
200
            template_path=template_path,
201
            dry_run=not apply_mode,
202
            console=console,
203
        )
204
        sys.exit(0)
×
205

206
    # 設定適用
207
    options = py_project.config.ApplyOptions(
×
208
        dry_run=not apply_mode,
209
        backup=backup,
210
        show_diff=show_diff,
211
        run_sync=not no_sync,
212
        git_commit=git_commit_flag,
213
        git_push=git_push_flag,
214
    )
215
    ret_code = execute(
×
216
        config=config,
217
        options=options,
218
        projects=projects,
219
        config_types=config_types,
220
    )
221

222
    sys.exit(ret_code)
×
223

224

225
if __name__ == "__main__":
226
    main()
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc