Skip to content

Commit 8078161

Browse files
authored
feat: add bot state folder method (#34)
1 parent 73c5c04 commit 8078161

File tree

9 files changed

+46
-18
lines changed

9 files changed

+46
-18
lines changed

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616
template name. Otherwise an inline prompt.
1717
* Only include files that the bot has written in draft commits.
1818
* Add `--generate` timeout option.
19-
* Add `Bot.state_folder` class method, returning a path to a folder specific to
20-
the bot implementation (derived from the class' name) for storing arbitrary
21-
data.
2219
* Add URL and API key to `openai_bot`. Also add a compatibility version which
2320
does not use threads, so that it can be used with tools only. Gemini only
2421
supports the latter.

src/git_draft/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""CLI entry point"""
2+
13
from __future__ import annotations
24

35
import importlib.metadata

src/git_draft/bots/common.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
import dataclasses
44
from datetime import datetime
5-
from pathlib import PurePosixPath
5+
from pathlib import Path, PurePosixPath
66
from typing import Callable, Sequence
77

8-
from ..common import JSONObject
8+
from ..common import ensure_state_home, JSONObject
99

1010

1111
class Toolbox:
@@ -97,5 +97,12 @@ class Action:
9797

9898

9999
class Bot:
100+
@classmethod
101+
def state_folder_path(cls) -> Path:
102+
name = cls.__qualname__
103+
if cls.__module__:
104+
name = f"{cls.__module__}.{name}"
105+
return ensure_state_home() / "bots" / name
106+
100107
def act(self, prompt: str, toolbox: Toolbox) -> Action:
101108
raise NotImplementedError()

src/git_draft/common.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Miscellaneous utilities"""
2+
13
from __future__ import annotations
24

35
import dataclasses

src/git_draft/editor.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""CLI interactive editing utilities"""
2+
13
import os
24
import shutil
35
import subprocess
@@ -24,6 +26,11 @@ def _get_tty_filename():
2426

2527

2628
def open_editor(placeholder="", *, _open_tty=open) -> str:
29+
"""Open an editor to edit a file and return its contents
30+
31+
The method returns once the editor is closed. It respects the `$EDITOR`
32+
environment variable.
33+
"""
2734
with tempfile.NamedTemporaryFile(delete_on_close=False) as temp:
2835
binpath = _guess_editor_binpath()
2936
if not binpath:

src/git_draft/prompt.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Prompt templating support"""
2+
13
import dataclasses
24
import git
35
import jinja2

src/git_draft/store.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Persistent state storage"""
2+
13
import contextlib
24
from datetime import datetime
35
import functools

tests/git_draft/bots/common_test.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,12 @@ def test_delete_file(self):
4646
self._toolbox.delete_file(PurePosixPath("/mock/path"))
4747
self._hook.assert_called_once()
4848
assert self._toolbox.operations[0].tool == "delete_file"
49+
50+
51+
class FakeBot(sut.Bot):
52+
pass
53+
54+
55+
class TestBot:
56+
def test_state_folder_path(self) -> None:
57+
assert "bots.common_test.FakeBot" in str(FakeBot.state_folder_path())

tests/git_draft/drafter_test.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def test_write_file(self, repo: git.Repo) -> None:
4545
assert f.read() == "hi"
4646

4747

48-
class _FakeBot(Bot):
48+
class FakeBot(Bot):
4949
def act(self, prompt: str, toolbox: Toolbox) -> Action:
5050
toolbox.write_file(PurePosixPath("PROMPT"), prompt)
5151
return Action()
@@ -72,33 +72,33 @@ def _commits(self) -> Sequence[git.Commit]:
7272
return list(self._repo.iter_commits())
7373

7474
def test_generate_draft(self) -> None:
75-
self._drafter.generate_draft("hello", _FakeBot())
75+
self._drafter.generate_draft("hello", FakeBot())
7676
assert len(self._commits()) == 2
7777

7878
def test_generate_then_discard_draft(self) -> None:
79-
self._drafter.generate_draft("hello", _FakeBot())
79+
self._drafter.generate_draft("hello", FakeBot())
8080
self._drafter.discard_draft()
8181
assert len(self._commits()) == 1
8282

8383
def test_generate_outside_branch(self) -> None:
8484
self._repo.git.checkout("--detach")
8585
with pytest.raises(RuntimeError):
86-
self._drafter.generate_draft("ok", _FakeBot())
86+
self._drafter.generate_draft("ok", FakeBot())
8787

8888
def test_generate_empty_prompt(self) -> None:
8989
with pytest.raises(ValueError):
90-
self._drafter.generate_draft("", _FakeBot())
90+
self._drafter.generate_draft("", FakeBot())
9191

9292
def test_generate_dirty_index_no_reset(self) -> None:
9393
self._write("log")
9494
self._repo.git.add(all=True)
9595
with pytest.raises(ValueError):
96-
self._drafter.generate_draft("hi", _FakeBot())
96+
self._drafter.generate_draft("hi", FakeBot())
9797

9898
def test_generate_dirty_index_reset_sync(self) -> None:
9999
self._write("log", "11")
100100
self._repo.git.add(all=True)
101-
self._drafter.generate_draft("hi", _FakeBot(), reset=True, sync=True)
101+
self._drafter.generate_draft("hi", FakeBot(), reset=True, sync=True)
102102
assert self._read("log") == "11"
103103
assert not self._path("PROMPT").exists()
104104
self._repo.git.checkout(".")
@@ -107,21 +107,21 @@ def test_generate_dirty_index_reset_sync(self) -> None:
107107

108108
def test_generate_clean_index_sync(self) -> None:
109109
prompt = TemplatedPrompt("add-test", {"symbol": "abc"})
110-
self._drafter.generate_draft(prompt, _FakeBot(), sync=True)
110+
self._drafter.generate_draft(prompt, FakeBot(), sync=True)
111111
self._repo.git.checkout(".")
112112
assert "abc" in self._read("PROMPT")
113113
assert len(self._commits()) == 2 # init, prompt
114114

115115
def test_generate_reuse_branch(self) -> None:
116-
bot = _FakeBot()
116+
bot = FakeBot()
117117
self._drafter.generate_draft("prompt1", bot)
118118
self._drafter.generate_draft("prompt2", bot)
119119
self._repo.git.checkout(".")
120120
assert self._read("PROMPT") == "prompt2"
121121
assert len(self._commits()) == 3 # init, prompt, prompt
122122

123123
def test_generate_reuse_branch_sync(self) -> None:
124-
bot = _FakeBot()
124+
bot = FakeBot()
125125
self._drafter.generate_draft("prompt1", bot)
126126
self._drafter.generate_draft("prompt2", bot, sync=True)
127127
assert len(self._commits()) == 4 # init, prompt, sync, prompt
@@ -132,7 +132,7 @@ def test_discard_outside_draft(self) -> None:
132132

133133
def test_discard_after_branch_move(self) -> None:
134134
self._write("log", "11")
135-
self._drafter.generate_draft("hi", _FakeBot(), sync=True)
135+
self._drafter.generate_draft("hi", FakeBot(), sync=True)
136136
branch = self._repo.active_branch
137137
self._repo.git.checkout("main")
138138
self._repo.index.commit("advance")
@@ -143,15 +143,15 @@ def test_discard_after_branch_move(self) -> None:
143143
def test_discard_restores_worktree(self) -> None:
144144
self._write("p1.txt", "a1")
145145
self._write("p2.txt", "b1")
146-
self._drafter.generate_draft("hello", _FakeBot(), sync=True)
146+
self._drafter.generate_draft("hello", FakeBot(), sync=True)
147147
self._write("p1.txt", "a2")
148148
self._drafter.discard_draft(delete=True)
149149
assert self._read("p1.txt") == "a1"
150150
assert self._read("p2.txt") == "b1"
151151

152152
def test_finalize_keeps_changes(self) -> None:
153153
self._write("p1.txt", "a1")
154-
self._drafter.generate_draft("hello", _FakeBot(), checkout=True)
154+
self._drafter.generate_draft("hello", FakeBot(), checkout=True)
155155
self._write("p1.txt", "a2")
156156
self._drafter.finalize_draft()
157157
assert self._read("p1.txt") == "a2"

0 commit comments

Comments
 (0)