Skip to content

Commit 86e9712

Browse files
authored
feat: support line output formatting (#110)
1 parent eeec8b9 commit 86e9712

File tree

3 files changed

+74
-36
lines changed

3 files changed

+74
-36
lines changed

src/git_draft/__main__.py

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
from __future__ import annotations
44

55
import asyncio
6+
import dataclasses
67
import enum
78
import importlib.metadata
89
import logging
910
import optparse
1011
from pathlib import Path
1112
import sys
13+
from typing import Any
1214

1315
from .bots import load_bot
1416
from .common import PROGRAM, Config, UnreachableError, ensure_state_home
@@ -28,7 +30,27 @@
2830
_logger = logging.getLogger(__name__)
2931

3032

31-
def new_parser() -> optparse.OptionParser:
33+
class Accept(enum.Enum):
34+
"""Valid change accept mode"""
35+
36+
MANUAL = 0
37+
MERGE = enum.auto()
38+
MERGE_THEIRS = enum.auto()
39+
MERGE_THEN_QUIT = enum.auto()
40+
41+
def merge_strategy(self) -> DraftMergeStrategy | None:
42+
match self:
43+
case Accept.MANUAL:
44+
return None
45+
case Accept.MERGE:
46+
return "ignore-all-space"
47+
case Accept.MERGE_THEIRS | Accept.MERGE_THEN_QUIT:
48+
return "theirs"
49+
case _:
50+
raise UnreachableError()
51+
52+
53+
def _new_parser() -> optparse.OptionParser:
3254
parser = optparse.OptionParser(
3355
prog=PROGRAM,
3456
version=importlib.metadata.version("git_draft"),
@@ -94,39 +116,24 @@ def callback(
94116
help="edit prompt or template",
95117
action="store_true",
96118
)
97-
98119
parser.add_option(
99120
"--no-accept",
100121
help="do not merge draft",
101122
dest="accept",
102123
action="store_const",
103124
const=0,
104125
)
126+
parser.add_option(
127+
"-f",
128+
"--format",
129+
dest="format",
130+
help="formatting string",
131+
)
105132

106133
return parser
107134

108135

109-
class Accept(enum.Enum):
110-
"""Valid change accept mode"""
111-
112-
MANUAL = 0
113-
MERGE = enum.auto()
114-
MERGE_THEIRS = enum.auto()
115-
MERGE_THEN_QUIT = enum.auto()
116-
117-
def merge_strategy(self) -> DraftMergeStrategy | None:
118-
match self:
119-
case Accept.MANUAL:
120-
return None
121-
case Accept.MERGE:
122-
return "ignore-all-space"
123-
case Accept.MERGE_THEIRS | Accept.MERGE_THEN_QUIT:
124-
return "theirs"
125-
case _:
126-
raise UnreachableError()
127-
128-
129-
def edit(*, path: Path | None = None, text: str | None = None) -> str:
136+
def _edit(*, path: Path | None = None, text: str | None = None) -> str:
130137
if sys.stdin.isatty():
131138
return open_editor(text or "", path)
132139
# We exit with a custom code to allow the caller to act accordingly.
@@ -145,12 +152,17 @@ def edit(*, path: Path | None = None, text: str | None = None) -> str:
145152
sys.exit(199)
146153

147154

155+
def _format(props: Any, spec: str) -> str:
156+
"""Formats an instance of a dataclass using the provided pattern"""
157+
return spec.format(**dataclasses.asdict(props))
158+
159+
148160
_PROMPT_PLACEHOLDER = "Enter your prompt here..."
149161

150162

151163
async def run() -> None: # noqa: PLR0912 PLR0915
152164
config = Config.load()
153-
(opts, args) = new_parser().parse_args()
165+
(opts, args) = _new_parser().parse_args()
154166

155167
log_path = ensure_state_home() / "log"
156168
if opts.log_path:
@@ -191,7 +203,7 @@ async def run() -> None: # noqa: PLR0912 PLR0915
191203
prompt = TemplatedPrompt.public(args[0], args[1:])
192204
editable = opts.edit
193205
else:
194-
prompt = edit(
206+
prompt = _edit(
195207
text=drafter.latest_draft_prompt() or _PROMPT_PLACEHOLDER
196208
).strip()
197209
if prompt.strip() == _PROMPT_PLACEHOLDER:
@@ -212,25 +224,27 @@ async def run() -> None: # noqa: PLR0912 PLR0915
212224
drafter.quit_folio()
213225
case "list-events":
214226
draft_id = args[0] if args else None
215-
for line in drafter.list_draft_events(draft_id):
216-
print(line)
227+
spec = opts.format or "{occurred_at}\t{description}"
228+
for event_props in drafter.list_draft_events(draft_id):
229+
print(_format(event_props, spec))
217230
case "show-template":
218231
if len(args) != 1:
219232
raise ValueError("Expected exactly one argument")
220233
name = args[0]
221234
meta = find_prompt_metadata(name)
222235
if opts.edit:
223236
if meta:
224-
edit(path=meta.local_path(), text=meta.source())
237+
_edit(path=meta.local_path(), text=meta.source())
225238
else:
226-
edit(path=PromptMetadata.local_path_for(name))
239+
_edit(path=PromptMetadata.local_path_for(name))
227240
else:
228241
if not meta:
229242
raise ValueError(f"No template named {name!r}")
230243
print(meta.source())
231244
case "list-templates":
232-
for line in list_templates():
233-
print(line)
245+
spec = opts.format or "{name}: {description}"
246+
for template_props in list_templates():
247+
print(_format(template_props, spec))
234248
case _:
235249
raise UnreachableError()
236250

src/git_draft/drafter.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,9 @@ def latest_draft_prompt(self) -> str | None:
452452
prompt = "\n\n".join([prompt, reindent(question, prefix="> ")])
453453
return prompt
454454

455-
def list_draft_events(self, draft_ref: str | None = None) -> Iterator[str]:
455+
def list_draft_events(
456+
self, draft_ref: str | None = None
457+
) -> Iterator[DraftEventProperties]:
456458
if draft_ref:
457459
folio_id, seqno = _parse_draft_ref(draft_ref)
458460
else:
@@ -469,7 +471,18 @@ def list_draft_events(self, draft_ref: str | None = None) -> Iterator[str]:
469471
occurred_at, class_name, data = row
470472
event = decoders[class_name].decode(data)
471473
description = _format_event(event)
472-
yield "\t".join([occurred_at, class_name, description])
474+
yield DraftEventProperties(
475+
occurred_at, class_name, description
476+
)
477+
478+
479+
@dataclasses.dataclass(frozen=True)
480+
class DraftEventProperties:
481+
"""Formattable properties corresponding to a draft's event"""
482+
483+
occurred_at: str
484+
class_name: str
485+
description: str
473486

474487

475488
@dataclasses.dataclass(frozen=True)

src/git_draft/prompt.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,9 @@ def find_prompt_metadata(name: PromptName) -> PromptMetadata | None:
210210
return prompt.metadata
211211

212212

213-
def list_templates(*, include_local: bool = True) -> Iterator[str]:
213+
def list_templates(
214+
*, include_local: bool = True
215+
) -> Iterator[TemplateProperties]:
214216
env = _jinja_environment(include_local=include_local)
215217
worktree = EmptyWorktree()
216218
for rel_path in env.list_templates(extensions=[_extension]):
@@ -219,5 +221,14 @@ def list_templates(*, include_local: bool = True) -> Iterator[str]:
219221
name, _ext = os.path.splitext(rel_path)
220222
prompt = _load_prompt(env, name, worktree)
221223
metadata = prompt.metadata
222-
local = "y" if metadata.is_local() else "n"
223-
yield "\t".join([name, local, metadata.description or ""])
224+
scope = "local" if metadata.is_local() else "global"
225+
yield TemplateProperties(name, scope, metadata.description or "")
226+
227+
228+
@dataclasses.dataclass(frozen=True)
229+
class TemplateProperties:
230+
"""Prompt template formattable properties"""
231+
232+
name: str
233+
scope: str
234+
description: str

0 commit comments

Comments
 (0)