Skip to content

Commit 438ca9e

Browse files
takemi-ohamaclaude
andcommitted
feat: devbase env init で DEVBASE_OPEN_EDITOR を対話設定する (既定1)
up/list 後の VS Code 自動オープン (DEVBASE_OPEN_EDITOR) を env init の 対話フローで設定できる collector を追加。既定は有効 (1) で [Y/n] 選択式、 空入力/非対話(EOF)でも 1。0/n/no/false/off で無効化可能。プロジェクト個別の env で上書きできる。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9627183 commit 438ca9e

3 files changed

Lines changed: 153 additions & 3 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""エディタ動作設定コレクター (VS Code 自動オープン)。
2+
3+
``devbase up`` / ``devbase list`` 後に dev コンテナへ接続した VS Code を自動で
4+
開くか (``DEVBASE_OPEN_EDITOR``) を ``devbase env init`` 時に対話的に設定する。
5+
既定は有効 (``1``)。プロジェクト個別の ``env`` で ``DEVBASE_OPEN_EDITOR=0`` を
6+
指定すれば、このグローバル既定を上書きして個別に無効化できる
7+
(プロジェクト env はグローバル ``.env`` より後に読まれるため優先される)。
8+
"""
9+
10+
from devbase.log import get_logger
11+
from devbase.env import keys
12+
from devbase.env.store import EnvFile, safe_input
13+
from devbase.env.collector import Collector
14+
15+
logger = get_logger(__name__)
16+
17+
# 応答の真偽解釈 (大小無視)。空入力は default に倒れる (safe_input が処理)。
18+
_YES = {"1", "y", "yes", "true", "on"}
19+
_NO = {"0", "n", "no", "false", "off"}
20+
21+
22+
def _normalize(answer: str, default: str) -> str:
23+
"""ユーザー応答を ``"1"`` / ``"0"`` に正規化する。未知の値は default。"""
24+
a = answer.strip().lower()
25+
if a in _YES:
26+
return "1"
27+
if a in _NO:
28+
return "0"
29+
return default
30+
31+
32+
def collect_open_editor(env_file: EnvFile) -> None:
33+
"""``DEVBASE_OPEN_EDITOR`` を対話的に設定する (既定: ``1`` = 有効)。
34+
35+
既存値 (``0`` / ``1``) があればそれを既定として提示し、空入力で維持する。
36+
非対話 (EOF) 環境では default が確定する。
37+
"""
38+
existing = env_file.get(keys.DEVBASE_OPEN_EDITOR)
39+
default = existing if existing in ("0", "1") else "1"
40+
answer = safe_input(
41+
f"{keys.DEVBASE_OPEN_EDITOR}: devbase up/list 後に VS Code を自動オープンしますか? "
42+
f"[Y/n] (既定={default}): ",
43+
default,
44+
)
45+
value = _normalize(answer, default)
46+
env_file.set(keys.DEVBASE_OPEN_EDITOR, value)
47+
logger.info("%s = %s", keys.DEVBASE_OPEN_EDITOR, value)
48+
49+
50+
COLLECTOR = Collector(
51+
name="editor",
52+
display_name="VS Code 自動オープン (DEVBASE_OPEN_EDITOR)",
53+
collect_fn=collect_open_editor,
54+
)

lib/devbase/env/keys.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ def gcp_credentials_key(profile: str) -> str:
5252
HOST_SSH_HOST = "HOST_SSH_HOST" # 任意。default: host.docker.internal
5353

5454
# --- Editor (devbase up 後の自動オープン / PLAN31_3) ---
55-
# いずれも env collection (env init) の対象外で、プロジェクト env / グローバル
56-
# .env に手書きする devbase 動作設定。詳細: docs/user/environment-variables.md
57-
DEVBASE_OPEN_EDITOR = "DEVBASE_OPEN_EDITOR" # 真偽。up 後にエディタを開くか (既定 OFF)
55+
# DEVBASE_OPEN_EDITOR は env init (collectors/editor.py) で対話設定する (既定 1)。
56+
# 他はプロジェクト env / グローバル .env に手書きする devbase 動作設定。
57+
# 詳細: docs/user/environment-variables.md
58+
DEVBASE_OPEN_EDITOR = "DEVBASE_OPEN_EDITOR" # 真偽。up 後にエディタを開くか (env init 既定 1)
5859
DEVBASE_EDITOR = "DEVBASE_EDITOR" # 任意。起動コマンド (既定 code)
5960
DEVBASE_OPEN_INDEX = "DEVBASE_OPEN_INDEX" # 任意。開く dev インスタンス番号 (既定 1)
6061
# Remote-SSH 跨ホスト構成 (Windows VS Code → ssh → Mac のコンテナ) 用。

tests/env/test_collector_editor.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"""collectors/editor.py: VS Code 自動オープン (DEVBASE_OPEN_EDITOR) コレクタ"""
2+
3+
from __future__ import annotations
4+
5+
import builtins
6+
7+
import pytest
8+
9+
from devbase.env import keys
10+
from devbase.env.store import EnvFile
11+
from devbase.env.collector import CollectorRegistry
12+
from devbase.env.collectors import editor
13+
14+
15+
@pytest.fixture
16+
def env_file(tmp_path):
17+
return EnvFile(tmp_path / ".env")
18+
19+
20+
def _patch_input(monkeypatch, responses):
21+
"""input() を順番に responses で返すモックに差し替える (尽きたら EOFError)。"""
22+
it = iter(responses)
23+
24+
def fake_input(prompt=""):
25+
try:
26+
return next(it)
27+
except StopIteration:
28+
raise EOFError
29+
30+
monkeypatch.setattr(builtins, "input", fake_input)
31+
32+
33+
def test_default_enabled_on_eof(monkeypatch, env_file):
34+
"""入力 EOF (非対話/CI) → 既定 1 が設定される"""
35+
_patch_input(monkeypatch, [])
36+
37+
editor.collect_open_editor(env_file)
38+
39+
assert env_file.get(keys.DEVBASE_OPEN_EDITOR) == "1"
40+
41+
42+
def test_empty_input_keeps_default(monkeypatch, env_file):
43+
"""空入力 (Enter のみ) → 既定 1 を維持"""
44+
_patch_input(monkeypatch, [""])
45+
46+
editor.collect_open_editor(env_file)
47+
48+
assert env_file.get(keys.DEVBASE_OPEN_EDITOR) == "1"
49+
50+
51+
@pytest.mark.parametrize("answer", ["n", "N", "no", "0", "false", "off"])
52+
def test_can_disable(monkeypatch, env_file, answer):
53+
"""否定的な応答 → 0 (無効) に設定できる (選択可能)"""
54+
_patch_input(monkeypatch, [answer])
55+
56+
editor.collect_open_editor(env_file)
57+
58+
assert env_file.get(keys.DEVBASE_OPEN_EDITOR) == "0"
59+
60+
61+
@pytest.mark.parametrize("answer", ["y", "Y", "yes", "1", "true", "on"])
62+
def test_can_enable(monkeypatch, env_file, answer):
63+
"""肯定的な応答 → 1 (有効) に設定できる"""
64+
_patch_input(monkeypatch, [answer])
65+
66+
editor.collect_open_editor(env_file)
67+
68+
assert env_file.get(keys.DEVBASE_OPEN_EDITOR) == "1"
69+
70+
71+
def test_existing_value_used_as_default(monkeypatch, env_file):
72+
"""既存値 (0) があれば空入力でそれを既定として維持する"""
73+
env_file.set(keys.DEVBASE_OPEN_EDITOR, "0")
74+
_patch_input(monkeypatch, [""]) # Enter → 既存の 0 を維持
75+
76+
editor.collect_open_editor(env_file)
77+
78+
assert env_file.get(keys.DEVBASE_OPEN_EDITOR) == "0"
79+
80+
81+
def test_unknown_answer_falls_back_to_default(monkeypatch, env_file):
82+
"""未知の応答は既定 (1) にフォールバック"""
83+
_patch_input(monkeypatch, ["maybe"])
84+
85+
editor.collect_open_editor(env_file)
86+
87+
assert env_file.get(keys.DEVBASE_OPEN_EDITOR) == "1"
88+
89+
90+
def test_collector_registered():
91+
"""CollectorRegistry が editor コレクタを自動検出する"""
92+
registry = CollectorRegistry()
93+
registry.discover()
94+
names = {c.name for c in registry.collectors}
95+
assert "editor" in names

0 commit comments

Comments
 (0)