Skip to content

Commit 2d08434

Browse files
authored
refactor: add editor module (#31)
1 parent 143107b commit 2d08434

File tree

4 files changed

+119
-50
lines changed

4 files changed

+119
-50
lines changed

src/git_draft/__main__.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,9 @@
66
import sys
77

88
from .bots import Operation, load_bot
9-
from .common import (
10-
Config,
11-
PROGRAM,
12-
UnreachableError,
13-
ensure_state_home,
14-
open_editor,
15-
)
9+
from .common import Config, PROGRAM, UnreachableError, ensure_state_home
1610
from .drafter import Drafter
11+
from .editor import open_editor
1712
from .prompt import TemplatedPrompt
1813
from .store import Store
1914

src/git_draft/common.py

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

33
import dataclasses
44
import logging
5-
import os
65
from pathlib import Path
76
import random
8-
import shutil
9-
import subprocess
107
import string
11-
import sys
12-
import tempfile
138
import tomllib
149
from typing import Any, Mapping, Self, Sequence
1510
import xdg_base_dirs
@@ -67,44 +62,6 @@ class BotConfig:
6762
pythonpath: str | None = None
6863

6964

70-
_default_editors = ["vim", "emacs", "nano"]
71-
72-
73-
def _guess_editor_binpath() -> str:
74-
editor = os.environ.get("EDITOR")
75-
if editor:
76-
return shutil.which(editor) or ""
77-
for editor in _default_editors:
78-
binpath = shutil.which(editor)
79-
if binpath:
80-
return binpath
81-
return ""
82-
83-
84-
def _get_tty_filename():
85-
return "CON:" if sys.platform == "win32" else "/dev/tty"
86-
87-
88-
def open_editor(placeholder="") -> str:
89-
with tempfile.NamedTemporaryFile(delete_on_close=False) as temp:
90-
binpath = _guess_editor_binpath()
91-
if not binpath:
92-
raise ValueError("Editor unavailable")
93-
94-
if placeholder:
95-
with open(temp.name, "w") as writer:
96-
writer.write(placeholder)
97-
98-
stdout = open(_get_tty_filename(), "wb")
99-
proc = subprocess.Popen(
100-
[binpath, temp.name], close_fds=True, stdout=stdout
101-
)
102-
proc.communicate()
103-
104-
with open(temp.name, mode="r") as reader:
105-
return reader.read()
106-
107-
10865
_random = random.Random()
10966
_alphabet = string.ascii_lowercase + string.digits
11067

src/git_draft/editor.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import os
2+
import shutil
3+
import subprocess
4+
import sys
5+
import tempfile
6+
7+
8+
_default_editors = ["vim", "emacs", "nano"]
9+
10+
11+
def _guess_editor_binpath() -> str:
12+
editor = os.environ.get("EDITOR")
13+
if editor:
14+
return shutil.which(editor) or ""
15+
for editor in _default_editors:
16+
binpath = shutil.which(editor)
17+
if binpath:
18+
return binpath
19+
return ""
20+
21+
22+
def _get_tty_filename():
23+
return "CON:" if sys.platform == "win32" else "/dev/tty"
24+
25+
26+
def open_editor(placeholder="", *, _open_tty=open) -> str:
27+
with tempfile.NamedTemporaryFile(delete_on_close=False) as temp:
28+
binpath = _guess_editor_binpath()
29+
if not binpath:
30+
raise ValueError("Editor unavailable")
31+
32+
if placeholder:
33+
with open(temp.name, "w") as writer:
34+
writer.write(placeholder)
35+
36+
stdout = _open_tty(_get_tty_filename(), "wb")
37+
proc = subprocess.Popen(
38+
[binpath, temp.name], close_fds=True, stdout=stdout
39+
)
40+
proc.communicate()
41+
42+
with open(temp.name, mode="r") as reader:
43+
return reader.read()

tests/git_draft/editor_test.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import pytest
2+
import shutil
3+
import subprocess
4+
5+
import git_draft.editor as sut
6+
7+
8+
class TestGuessEditorBinPath:
9+
def test_from_env_ok(self, monkeypatch) -> None:
10+
def which(editor):
11+
assert editor == "foo"
12+
return "/bin/bar"
13+
14+
monkeypatch.setattr(shutil, "which", which)
15+
monkeypatch.setenv("EDITOR", "foo")
16+
17+
assert sut._guess_editor_binpath() == "/bin/bar"
18+
19+
def test_from_env_missing(self, monkeypatch) -> None:
20+
def which(_editor):
21+
return ""
22+
23+
monkeypatch.setattr(shutil, "which", which)
24+
monkeypatch.setenv("EDITOR", "foo")
25+
26+
assert sut._guess_editor_binpath() == ""
27+
28+
def test_from_default_ok(self, monkeypatch) -> None:
29+
def which(editor):
30+
return "/bin/nano" if editor == "nano" else ""
31+
32+
monkeypatch.setattr(shutil, "which", which)
33+
monkeypatch.setenv("EDITOR", "")
34+
35+
assert sut._guess_editor_binpath() == "/bin/nano"
36+
37+
def test_from_default_missing(self, monkeypatch) -> None:
38+
def which(_editor):
39+
return ""
40+
41+
monkeypatch.setattr(shutil, "which", which)
42+
monkeypatch.setenv("EDITOR", "")
43+
44+
assert sut._guess_editor_binpath() == ""
45+
46+
47+
class TestOpenEditor:
48+
def test_no_binpath(self, monkeypatch) -> None:
49+
def which(_editor):
50+
return ""
51+
52+
monkeypatch.setattr(shutil, "which", which)
53+
54+
with pytest.raises(ValueError):
55+
sut.open_editor()
56+
57+
def test_ok(self, monkeypatch) -> None:
58+
def which(editor):
59+
return f"/bin/{editor}"
60+
61+
def open_tty(*_args):
62+
return None
63+
64+
class Popen:
65+
def __init__(self, *_args, **_kwargs):
66+
pass
67+
68+
def communicate(self):
69+
pass
70+
71+
monkeypatch.setattr(shutil, "which", which)
72+
monkeypatch.setattr(subprocess, "Popen", Popen)
73+
74+
assert sut.open_editor("hello", _open_tty=open_tty) == "hello"

0 commit comments

Comments
 (0)