Source code for rechu.command.new.step.edit
"""
Edit step of new subcommand.
"""
import logging
import os
import shutil
import subprocess
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing_extensions import override
from ....database import Database
from ....io.receipt import ReceiptReader, ReceiptWriter
from ....matcher.product import ProductMatcher
from ....models.receipt import Receipt
from .base import ResultMeta, ReturnToMenu, Step
LOGGER = logging.getLogger(__name__)
[docs]
@dataclass
class Edit(Step):
"""
Step to edit the receipt in its YAML representation via a temporary file.
"""
matcher: ProductMatcher
editor: str | None = None
[docs]
@override
def run(self) -> ResultMeta:
with tempfile.NamedTemporaryFile("w", suffix=".yml") as tmp_file:
tmp_path = Path(tmp_file.name)
writer = ReceiptWriter(tmp_path, (self.receipt,))
writer.write()
self.execute_editor(tmp_file.name)
reader = ReceiptReader(tmp_path, updated=self.receipt.updated)
try:
receipt = next(reader.read())
# Bring over any product metadata that still matches items
self._update_matches(receipt)
# Replace receipt
update_path = (
self.receipt.date != receipt.date
or self.receipt.shop != receipt.shop
)
self.receipt.date = receipt.date
self.receipt.shop = receipt.shop
self.receipt.products = receipt.products
self.receipt.discounts = receipt.discounts
except (StopIteration, TypeError, ValueError) as error:
raise ReturnToMenu(
"Invalid or missing edited receipt YAML"
) from error
return {"receipt_path": update_path}
def _update_matches(self, receipt: Receipt) -> None:
with Database() as session:
products = self._get_products_meta(session)
pairs = self.matcher.find_candidates(
session, receipt.products, products
)
for meta, match in self.matcher.filter_duplicate_candidates(pairs):
if meta in products or meta.generic in products:
match.product = meta
self._view_products_meta(
"Products that no longer match:",
self._update_products_meta(session, products),
)
[docs]
def execute_editor(self, filename: str) -> None:
"""
Open an editor to edit the provided filename.
"""
# Find editor which can be found in the PATH
editors = [
self.editor,
os.getenv("VISUAL"),
os.getenv("EDITOR"),
"editor",
"vim",
]
for editor in editors:
if (
editor is not None
and shutil.which(editor.split(" ", 1)[0]) is not None
):
break
else:
raise ReturnToMenu("No editor executable found")
# Spawn selected editor
try:
_ = subprocess.run([*editor.split(" "), filename], check=True)
except subprocess.CalledProcessError as exit_status:
raise ReturnToMenu(
"Editor returned non-zero exit status"
) from exit_status
@property
@override
def description(self) -> str:
return "Edit the current receipt via its YAML format"