Skip to content

Commit

Permalink
🔀 Merge pull request #20 from davep/save-pep-source
Browse files Browse the repository at this point in the history
Add support for saving a PEP's source to a file
  • Loading branch information
davep authored Jan 29, 2025
2 parents 665f937 + a13e1e1 commit 736da58
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 1 deletion.
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
([#18](https://github.com/davep/peplum/pull/18))
- Dropped Python 3.8 as a supported Python version.
([#19](https://github.com/davep/peplum/pull/19))
- Added support for saving the source of a PEP to a file.
(#20[](https://github.com/davep/peplum/pull/20))

## v0.2.0

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies = [
"typing-extensions>=4.12.2",
"packaging>=24.2",
"humanize>=4.11.0",
"textual-fspicker>=0.1.1",
]
readme = "README.md"
requires-python = ">= 3.9"
Expand Down
3 changes: 3 additions & 0 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ sniffio==1.3.1
textual==1.0.0
# via peplum
# via textual-dev
# via textual-fspicker
# via textual-serve
textual-dev==1.7.0
textual-fspicker==0.1.1
# via peplum
textual-serve==1.1.1
# via textual-dev
typing-extensions==4.12.2
Expand Down
3 changes: 3 additions & 0 deletions requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ sniffio==1.3.1
# via anyio
textual==1.0.0
# via peplum
# via textual-fspicker
textual-fspicker==0.1.1
# via peplum
typing-extensions==4.12.2
# via peplum
# via textual
Expand Down
101 changes: 101 additions & 0 deletions src/peplum/app/screens/confirm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Provides a confirmation dialog."""

##############################################################################
# Textual imports.
from textual import on
from textual.app import ComposeResult
from textual.containers import Horizontal, Vertical
from textual.screen import ModalScreen
from textual.widgets import Button, Label


##############################################################################
class Confirm(ModalScreen[bool]):
"""A modal dialog for confirming things."""

CSS = """
Confirm {
align: center middle;
&> Vertical {
padding: 1 2;
height: auto;
width: auto;
max-width: 80vw;
background: $surface;
border: panel $error;
border-title-color: $text;
&> Horizontal {
height: auto;
width: 100%;
align-horizontal: center;
}
}
Label {
width: auto;
max-width: 70vw;
padding-left: 1;
padding-right: 1;
margin-bottom: 1;
}
Button {
margin-right: 1;
}
}
"""

BINDINGS = [
("escape", "no"),
("f2", "yes"),
("left", "focus_previous"),
("right", "focus_next"),
]

def __init__(
self, title: str, question: str, yes_text: str = "Yes", no_text: str = "No"
) -> None:
"""Initialise the dialog.
Args:
title: The title for the dialog.
question: The question to ask the user.
yes_text: The text for the yes button.
no_text: The text for the no button.
"""
super().__init__()
self._title = title
"""The title for the dialog."""
self._question = question
"""The question to ask the user."""
self._yes = yes_text
"""The text of the yes button."""
self._no = no_text
"""The text of the no button."""

def compose(self) -> ComposeResult:
"""Compose the layout of the dialog."""
key_colour = (
"dim" if self.app.current_theme is None else self.app.current_theme.accent
)
with Vertical() as dialog:
dialog.border_title = self._title
yield Label(self._question)
with Horizontal():
yield Button(f"{self._no} [{key_colour}]\\[Esc][/]", id="no")
yield Button(f"{self._yes} [{key_colour}]\\[F2][/]", id="yes")

@on(Button.Pressed, "#yes")
def action_yes(self) -> None:
"""Send back the positive response."""
self.dismiss(True)

@on(Button.Pressed, "#no")
def action_no(self) -> None:
"""Send back the negative response."""
self.dismiss(False)


### confirm.py ends here
31 changes: 30 additions & 1 deletion src/peplum/app/screens/pep_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
from textual.screen import ModalScreen
from textual.widgets import Button, TextArea

##############################################################################
# Textual fspicker imports.
from textual_fspicker import FileSave

##############################################################################
# Local imports.
from ...peps import API
from ..data import PEP, cache_dir
from ..widgets import TextViewer
from .confirm import Confirm


##############################################################################
Expand Down Expand Up @@ -58,7 +63,12 @@ class PEPViewer(ModalScreen[None]):
}
"""

BINDINGS = [("escape", "close"), ("ctrl+r", "refresh"), ("ctrl+c", "copy")]
BINDINGS = [
("ctrl+c", "copy"),
("ctrl+r", "refresh"),
("ctrl+s", "save"),
("escape", "close"),
]

def __init__(self, pep: PEP) -> None:
"""Initialise the dialog.
Expand All @@ -80,6 +90,7 @@ def compose(self) -> ComposeResult:
yield TextViewer()
with Horizontal(id="buttons"):
yield Button(f"Copy [{key_colour}]\\[^c][/]", id="copy")
yield Button(f"Save [{key_colour}]\\[^s][/]", id="save")
yield Button(f"Refresh [{key_colour}]\\[^r][/]", id="refresh")
yield Button(f"Close [{key_colour}]\\[Esc][/]", id="close")

Expand Down Expand Up @@ -147,5 +158,23 @@ async def action_copy(self) -> None:
"""Copy PEP text to the clipboard."""
await self.query_one(TextArea).run_action("copy")

@on(Button.Pressed, "#save")
@work
async def action_save(self) -> None:
"""Save the source of the PEP to a file."""
if target := await self.app.push_screen_wait(FileSave()):
if target.exists() and not await self.app.push_screen_wait(
Confirm(
"Overwrite?", f"{target}\n\nAre you sure you want to overwrite?"
)
):
return
try:
target.write_text(self.query_one(TextArea).text, encoding="utf-8")
except IOError as error:
self.notify(str(error), title="Save Failed", severity="error")
return
self.notify(str(target), title="Saved")


### pep_viewer.py ends here

0 comments on commit 736da58

Please sign in to comment.