Loading muffin/__main__.py +1 −1 Changes for muffin/__main__.py: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ output_rows = [ remaining := item.amount - spent, item.amount, ) for item in budget_plan.items.values() for item in budget_plan.items ] formatted_output_rows = [ Loading muffin/budget_plan/BudgetPlan.py +22 −1 Changes for muffin/budget_plan/BudgetPlan.py: 22 added lines, 1 removed line. Original line number Diff line number Diff line from dataclasses import dataclass from typing import Self from collections.abc import Iterable, Sequence from .BudgetaryItem import BudgetaryItem from .BudgetaryGroup import BudgetaryGroup @dataclass(frozen=True, eq=False) class BudgetPlan: items: dict[str, BudgetaryItem] _groups: dict[str, BudgetaryGroup] @classmethod def from_groups(cls, groups: Iterable[BudgetaryGroup]) -> Self: return cls({group.id: group for group in groups}) @property def groups(self) -> Sequence[BudgetaryGroup]: return tuple(self._groups.values()) def group(self, id: str) -> BudgetaryGroup: return self._groups @property def items(self) -> Sequence[BudgetaryItem]: return [item for group in self.groups for item in group.items] def item(self, id: str) -> BudgetaryItem: pass # TODO muffin/budget_plan/BudgetPlanLoader.py +61 −7 Changes for muffin/budget_plan/BudgetPlanLoader.py: 61 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -3,25 +3,79 @@ import subprocess from pathlib import Path from collections.abc import Sequence, Iterable from ..amount.AmountParser import parse_euro_amount from ..cash_flow.CashFlowDirection import CashFlowDirection from .BudgetPlan import BudgetPlan from .BudgetaryGroup import BudgetaryGroup from .BudgetaryItem import BudgetaryItem from ..amount.AmountParser import parse_euro_amount def _is_budgetary_item_row(row: Sequence[str]) -> bool: return len(row) >= 3 and row[0] and row[1] and row[2] def _parse_budgetary_item_row(row: Sequence[str]) -> BudgetaryItem: return BudgetaryItem(id=row[0], name=row[1], amount=parse_euro_amount(row[2])) def _is_combined_row(row: Sequence[str]) -> bool: return row[0] and all(not row[i] for i in range(1, len(row))) def _parse_combined_row(row: Sequence[str]) -> ...: lines = row[0].split("\n") return (lines[0][0], lines[0][1:]) # TODO def _load_csv_lines(csv_lines: Iterable[str]) -> BudgetPlan: reader = csv.reader(csv_lines) items = [ BudgetaryItem(id=row[0], name=row[1], amount=parse_euro_amount(row[2])) for row in reader if _is_budgetary_item_row(row) ] return BudgetPlan({item.id: item for item in items}) groups: list[BudgetaryGroup] = [] current_group_id: Optional[str] = None current_group_name: Optional[str] = None current_cash_flow_direction: Optional[CashFlowDirection] = None current_items: list[BudgetaryItem] = [] def add_group(): nonlocal current_group_id nonlocal current_group_name nonlocal current_items if current_group_id is None: return groups.append( BudgetaryGroup.from_items( id=current_group_id, name=current_group_name, cash_flow_direction=current_cash_flow_direction, items=current_items, ) ) current_group_id = None current_group_name = None current_items = [] for row in reader: if not row: continue elif row[0] == "Einnahmen": current_cash_flow_direction = CashFlowDirection.INCOMING elif row[0] == "Ausgaben": current_cash_flow_direction = CashFlowDirection.OUTGOING elif _is_budgetary_item_row(row): current_items.append(_parse_budgetary_item_row(row)) elif _is_combined_row(row): add_group() # Add the previous group, which is now completed current_group_id, current_group_name = _parse_combined_row(row) add_group() # Add the last group return BudgetPlan.from_groups(groups) def load_csv(csv_path: Path | str) -> BudgetPlan: Loading muffin/cash_flow/CashFlowDirection.py 0 → 100644 +6 −0 Changes for muffin/cash_flow/CashFlowDirection.py: 6 added lines, 0 removed lines. Original line number Diff line number Diff line from enum import Enum class CashFlowDirection(Enum): INCOMING = 1 OUTGOING = -1 Loading
muffin/__main__.py +1 −1 Changes for muffin/__main__.py: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -28,7 +28,7 @@ output_rows = [ remaining := item.amount - spent, item.amount, ) for item in budget_plan.items.values() for item in budget_plan.items ] formatted_output_rows = [ Loading
muffin/budget_plan/BudgetPlan.py +22 −1 Changes for muffin/budget_plan/BudgetPlan.py: 22 added lines, 1 removed line. Original line number Diff line number Diff line from dataclasses import dataclass from typing import Self from collections.abc import Iterable, Sequence from .BudgetaryItem import BudgetaryItem from .BudgetaryGroup import BudgetaryGroup @dataclass(frozen=True, eq=False) class BudgetPlan: items: dict[str, BudgetaryItem] _groups: dict[str, BudgetaryGroup] @classmethod def from_groups(cls, groups: Iterable[BudgetaryGroup]) -> Self: return cls({group.id: group for group in groups}) @property def groups(self) -> Sequence[BudgetaryGroup]: return tuple(self._groups.values()) def group(self, id: str) -> BudgetaryGroup: return self._groups @property def items(self) -> Sequence[BudgetaryItem]: return [item for group in self.groups for item in group.items] def item(self, id: str) -> BudgetaryItem: pass # TODO
muffin/budget_plan/BudgetPlanLoader.py +61 −7 Changes for muffin/budget_plan/BudgetPlanLoader.py: 61 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -3,25 +3,79 @@ import subprocess from pathlib import Path from collections.abc import Sequence, Iterable from ..amount.AmountParser import parse_euro_amount from ..cash_flow.CashFlowDirection import CashFlowDirection from .BudgetPlan import BudgetPlan from .BudgetaryGroup import BudgetaryGroup from .BudgetaryItem import BudgetaryItem from ..amount.AmountParser import parse_euro_amount def _is_budgetary_item_row(row: Sequence[str]) -> bool: return len(row) >= 3 and row[0] and row[1] and row[2] def _parse_budgetary_item_row(row: Sequence[str]) -> BudgetaryItem: return BudgetaryItem(id=row[0], name=row[1], amount=parse_euro_amount(row[2])) def _is_combined_row(row: Sequence[str]) -> bool: return row[0] and all(not row[i] for i in range(1, len(row))) def _parse_combined_row(row: Sequence[str]) -> ...: lines = row[0].split("\n") return (lines[0][0], lines[0][1:]) # TODO def _load_csv_lines(csv_lines: Iterable[str]) -> BudgetPlan: reader = csv.reader(csv_lines) items = [ BudgetaryItem(id=row[0], name=row[1], amount=parse_euro_amount(row[2])) for row in reader if _is_budgetary_item_row(row) ] return BudgetPlan({item.id: item for item in items}) groups: list[BudgetaryGroup] = [] current_group_id: Optional[str] = None current_group_name: Optional[str] = None current_cash_flow_direction: Optional[CashFlowDirection] = None current_items: list[BudgetaryItem] = [] def add_group(): nonlocal current_group_id nonlocal current_group_name nonlocal current_items if current_group_id is None: return groups.append( BudgetaryGroup.from_items( id=current_group_id, name=current_group_name, cash_flow_direction=current_cash_flow_direction, items=current_items, ) ) current_group_id = None current_group_name = None current_items = [] for row in reader: if not row: continue elif row[0] == "Einnahmen": current_cash_flow_direction = CashFlowDirection.INCOMING elif row[0] == "Ausgaben": current_cash_flow_direction = CashFlowDirection.OUTGOING elif _is_budgetary_item_row(row): current_items.append(_parse_budgetary_item_row(row)) elif _is_combined_row(row): add_group() # Add the previous group, which is now completed current_group_id, current_group_name = _parse_combined_row(row) add_group() # Add the last group return BudgetPlan.from_groups(groups) def load_csv(csv_path: Path | str) -> BudgetPlan: Loading
muffin/cash_flow/CashFlowDirection.py 0 → 100644 +6 −0 Changes for muffin/cash_flow/CashFlowDirection.py: 6 added lines, 0 removed lines. Original line number Diff line number Diff line from enum import Enum class CashFlowDirection(Enum): INCOMING = 1 OUTGOING = -1