Source code for rechu.command.new.step.edit
"""
Edit step of new subcommand.
"""
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
from typing import Optional
from .base import ResultMeta, ReturnToMenu, Step
from ..input import InputSource
from ....database import Database
from ....io.receipt import ReceiptReader, ReceiptWriter
from ....matcher.product import ProductMatcher
from ....models.receipt import Receipt
[docs]
class Edit(Step):
"""
Step to edit the receipt in its YAML representation via a temporary file.
"""
def __init__(self, receipt: Receipt, input_source: InputSource,
matcher: ProductMatcher,
editor: Optional[str] = None) -> None:
super().__init__(receipt, input_source)
self._matcher = matcher
self.editor = editor
[docs]
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
return {'receipt_path': update_path}
except (StopIteration, TypeError, ValueError) as error:
raise ReturnToMenu('Invalid or missing edited receipt YAML') \
from error
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:
match.product = meta
[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
def description(self) -> str:
return "Edit the current receipt via its YAML format"