Skip to content

Commit 9627183

Browse files
takemi-ohamaclaude
andcommitted
fix: TUI(list) 経路で env の変数参照を展開する
_load_project_env が $VAR / ${VAR} を展開しない仕様だったため、 全プロジェクト env が依存する WORK_DIR=/work/$GIT_REPO が devbase list 経由ではリテラルのまま VS Code に渡り、ワークスペースが $GIT_REPO という パスで開いてしまっていた。shell `source ./env` 相当に os.path.expandvars で 変数展開する (単一引用符値はリテラル、$(...) コマンド置換は非対応のまま)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 03dbb04 commit 9627183

2 files changed

Lines changed: 53 additions & 13 deletions

File tree

lib/devbase/commands/container.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,26 +166,32 @@ def _load_project_env(env_file: Path) -> None:
166166
167167
env は環境変数定義のみを想定したファイル (bin/devbase 冒頭コメント参照) の
168168
ため、ここでは ``export`` 接頭辞付き / 無しの単純な ``KEY=VALUE`` 行のみを
169-
解釈する。``#`` コメント・空行は無視し、値の前後のクォートは除去する。shell
170-
の変数展開やコマンド置換は意図的にサポートしない (安全側に倒す)。
169+
解釈する。``#`` コメント・空行は無視し、値の前後のクォートは除去する。
170+
171+
変数参照 (``$VAR`` / ``${VAR}``) は shell ``source ./env`` (wrapper 経路) と
172+
同様に展開する。実 env が ``WORK_DIR=/work/$GIT_REPO`` のように同一ファイル内で
173+
先に定義した変数を参照しており、展開しないと TUI (``list``) 経路でワークスペース
174+
パスが ``$GIT_REPO`` 等の未展開文字列のまま VS Code で開いてしまうため
175+
(行は file 順に ``os.environ`` へ載せるので、参照時には先行行の値が解決済み)。
176+
単一引用符 ``'...'`` の値は shell 同様リテラル扱いで展開しない。
171177
172178
.. note:: shell ``source`` との仕様乖離について
173179
174-
本パーサは完全な POSIX shell パーサではなく、shell ``source ./env``
175-
(wrapper 経路) とは以下のケースで挙動が乖離する。env は単純な
180+
本パーサは完全な POSIX shell パーサではなく、変数展開はサポートするが
181+
以下のケースでは shell ``source ./env`` と挙動が乖離する。env は単純な
176182
``KEY=VALUE`` 定義に限定する運用前提のため、これらは意図的な制約として
177-
受容し、ファイル側で利用しない方針とする (仕様統一ではなく制約の明示)::
183+
受容する (仕様統一ではなく制約の明示)::
178184
179-
FOO=$BAR # shell: 展開 → 本実装: リテラル文字列 "$BAR"
180185
FOO=$(cmd) # shell: コマンド置換 → 本実装: リテラル "$(cmd)"
186+
# (os.path.expandvars は $(...) を変数とみなさない)
181187
FOO=a"b"c # shell: クォート除去で "abc" → 本実装: 行頭/行末以外の
182188
# クォートは除去せず "a\"b\"c"
183189
FOO=bar # x # shell: インラインコメント無効 (値は "bar # x") →
184190
# 本実装も値は "bar # x" (行頭 # のみコメント扱い)
185191
186192
いずれも wrapper を経ない直接起動 (例:
187-
``python -m devbase.cli project up <name>``) のフォールバック時のみ影響し、
188-
通常運用の wrapper 経路では shell が env を解釈するため差異は生じない
193+
``python -m devbase.cli project up <name>`` / TUI ``list``) 経路で用いる。
194+
通常の wrapper 経路では shell が env を解釈する
189195
"""
190196
if not env_file.is_file():
191197
return
@@ -199,8 +205,19 @@ def _load_project_env(env_file: Path) -> None:
199205
continue
200206
key, value = assignment
201207
value = value.strip()
208+
single_quoted = False
202209
if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
210+
single_quoted = value[0] == "'"
203211
value = value[1:-1]
212+
# shell `source ./env` 相当の変数展開 ($VAR / ${VAR}) を行う。実 env は
213+
# `WORK_DIR=/work/$GIT_REPO` のように同一ファイル内で先に定義した変数を
214+
# 参照しており (行順に os.environ へ載せるため参照時には解決済み)、展開
215+
# しないと TUI (list) 経路でワークスペースパスが未展開のまま開いてしまう。
216+
# 単一引用符はリテラル ($BAR を展開しない) という shell 規則に合わせ、
217+
# `'...'` の場合のみ展開しない。os.path.expandvars は `$(...)` を変数とは
218+
# みなさず素通しするため、コマンド置換は従来どおりリテラルのまま残る。
219+
if not single_quoted:
220+
value = os.path.expandvars(value)
204221
os.environ[key] = value
205222

206223

tests/cli/test_project_name_resolution.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,27 +192,50 @@ def test_resolve_clears_caller_only_env_keys(fake_root, monkeypatch):
192192
def test_load_project_env_diverges_from_shell_source(tmp_path, monkeypatch):
193193
"""shell ``source`` との仕様乖離を固定する回帰テスト (docstring の note 対応)。
194194
195-
本パーサは変数展開・コマンド置換・行中クォート除去・インラインコメントを
196-
解釈せず、値を安全側にリテラルとして扱う。この意図的な制約を pin する。
195+
変数展開 (``$VAR`` / ``${VAR}``) は shell ``source`` 同様にサポートするが、
196+
コマンド置換・行中クォート除去・インラインコメントは解釈しない。この境界を pin する。
197197
"""
198-
for k in ("LIT_VAR", "LIT_CMD", "INNER_Q", "INLINE_C"):
198+
for k in ("LIT_CMD", "INNER_Q", "INLINE_C"):
199199
monkeypatch.delenv(k, raising=False)
200200
env_path = tmp_path / "env"
201201
env_path.write_text(
202-
"LIT_VAR=$HOME\n" # 変数展開しない (リテラル "$HOME")
203202
"LIT_CMD=$(echo x)\n" # コマンド置換しない (リテラル "$(echo x)")
204203
'INNER_Q=a"b"c\n' # 行中クォートは除去しない
205204
"INLINE_C=bar # note\n" # 行頭以外の # はコメント扱いしない
206205
)
207206

208207
container._load_project_env(env_path)
209208

210-
assert os.environ["LIT_VAR"] == "$HOME"
211209
assert os.environ["LIT_CMD"] == "$(echo x)"
212210
assert os.environ["INNER_Q"] == 'a"b"c'
213211
assert os.environ["INLINE_C"] == "bar # note"
214212

215213

214+
def test_load_project_env_expands_variable_references(tmp_path, monkeypatch):
215+
"""``$VAR`` / ``${VAR}`` を shell ``source`` 同様に展開する回帰テスト。
216+
217+
実 env の ``WORK_DIR=/work/$GIT_REPO`` (同一ファイル内で先に定義した変数を参照)
218+
が TUI (``list``) 経路で未展開のまま VS Code に渡る不具合の回帰防止。
219+
単一引用符値はリテラル扱いで展開しないことも併せて pin する。
220+
"""
221+
for k in ("GIT_REPO", "WORK_DIR", "WORK_DIR_BRACE", "SINGLE_Q"):
222+
monkeypatch.delenv(k, raising=False)
223+
env_path = tmp_path / "env"
224+
env_path.write_text(
225+
"GIT_REPO=adminer\n"
226+
"WORK_DIR=/work/$GIT_REPO\n" # 行順に解決済みの GIT_REPO を展開
227+
"WORK_DIR_BRACE=/work/${GIT_REPO}\n" # ${VAR} 形式も展開
228+
"SINGLE_Q='/work/$GIT_REPO'\n" # 単一引用符はリテラル
229+
)
230+
231+
container._load_project_env(env_path)
232+
233+
assert os.environ["GIT_REPO"] == "adminer"
234+
assert os.environ["WORK_DIR"] == "/work/adminer"
235+
assert os.environ["WORK_DIR_BRACE"] == "/work/adminer"
236+
assert os.environ["SINGLE_Q"] == "/work/$GIT_REPO"
237+
238+
216239
# ===========================================================================
217240
# wrapper: cd + argv strip + 存在性ベースの曖昧性回避
218241
# ===========================================================================

0 commit comments

Comments
 (0)