self.comment = halves[1] if len(halves) > 1 else ''
self.code = halves[0]
self.booking_line: Optional[BookingLine] = None
- self.hide_comment_in_ledger = False
@property
def comment_instructions(self) -> dict[str, str]:
def load(self) -> None:
"""Read into ledger file at .path_dat."""
- self.accounts, self.bookings, self.initial_gap_lines = {}, [], []
self.dat_lines = [
DatLine(line)
for line in self._path_dat.read_text(encoding='utf8').splitlines()]
self.last_save_hash = self._hash_dat_lines()
- booked_lines: list[DatLine] = []
+ self.bookings, self.initial_gap_lines = [], []
+ booked: list[DatLine] = []
gap_lines: list[DatLine] = []
booking: Optional[Booking] = None
for dat_line in self.dat_lines + [DatLine('')]:
else:
self.initial_gap_lines = gap_lines[:]
gap_lines.clear()
- booked_lines += [dat_line]
+ booked += [dat_line]
else:
- if booked_lines:
- booking = Booking(len(self.bookings), booked_lines[:])
- self._apply_booking_to_accounts(booking)
- self.bookings += [booking]
- booked_lines.clear()
+ if booked:
+ self.bookings += [Booking(len(self.bookings), booked[:])]
+ booked.clear()
+ if booking:
+ booking.gap_lines = gap_lines[:-1]
+ self._sync(recalc_datlines=False)
+
+ def _sync(self, recalc_datlines=True, check_dates=True):
+ if recalc_datlines:
+ self.dat_lines = self.initial_gap_lines[:]
+ for booking in self.bookings:
+ self.dat_lines += booking.booked_lines
+ self.dat_lines += booking.gap_lines
+ for idx, booking in enumerate(self.bookings[1:]):
+ booking.prev = self.bookings[idx]
+ for idx, booking in enumerate(self.bookings[:-1]):
+ booking.next = self.bookings[idx + 1]
+ self.bookings[-1].next = None
+ if check_dates:
+ last_date = ''
+ err_msg = 'date < previous valid date'
+ for booking in self.bookings:
+ if err_msg in booking.intro_line.errors:
+ booking.intro_line.errors.remove(err_msg)
+ if last_date > booking.date:
+ booking.intro_line.errors += [err_msg]
+ else:
+ last_date = booking.date
+ self._recalc_prev_line_empty()
+ self.accounts = {}
+ for dat_line in self.dat_lines:
for acc_name, desc in dat_line.comment_instructions.items():
self.ensure_account(acc_name)
self.accounts[acc_name].desc = desc
- self._check_date_order()
for booking in self.bookings:
- booking.recalc_prev_next(self.bookings)
- if booking:
- booking.gap_lines = gap_lines[:-1]
- self._recalc_prev_line_empty()
-
- def _apply_booking_to_accounts(self, booking: Booking) -> None:
- for acc_name, wealth in booking.account_changes.items():
- self.ensure_account(acc_name)
- self.accounts[acc_name].add_wealth_diff(booking.id_, wealth)
+ for acc_name, wealth in booking.account_changes.items():
+ self.ensure_account(acc_name)
+ self.accounts[acc_name].add_wealth_diff(booking.id_, wealth)
def ensure_account(self, full_path: str) -> None:
"""If full_path not in self.accounts, add its tree with Accounts."""
step_name)
parent_path = path
- def _check_date_order(self) -> None:
- last_date = ''
- err_msg = 'date < previous valid date'
- for booking in self.bookings:
- if err_msg in booking.intro_line.errors:
- booking.intro_line.errors.remove(err_msg)
- if last_date > booking.date:
- booking.intro_line.errors += [err_msg]
- else:
- last_date = booking.date
-
def save(self) -> None:
"""Save current state to ._path_dat."""
self._path_dat.write_text(
if prev_line or line.code + line.comment_in_ledger: # jump over
prev_line = line # empty start
- def _recalc_dat_lines(self) -> None:
- self.dat_lines = self.initial_gap_lines[:]
- for booking in self.bookings:
- self.dat_lines += booking.booked_lines
- self.dat_lines += booking.gap_lines
- self._recalc_prev_line_empty()
-
- def _move_booking(self, idx_from, idx_to) -> None:
+ def _move_booking(self, idx_from: int, idx_to: int):
moving = self.bookings[idx_from]
if idx_from >= idx_to: # moving upward, deletion must
del self.bookings[idx_from] # precede insertion to keep
min_idx, max_idx = min(idx_from, idx_to), max(idx_from, idx_to)
for idx, booking in enumerate(self.bookings[min_idx:max_idx + 1]):
booking.id_ = min_idx + idx
- booking.recalc_prev_next(self.bookings)
- def move_booking(self, old_id: int, up: bool) -> int:
+ def move_booking(self, idx_from: int, up: bool) -> int:
"""Move Booking of old_id one step up or downwards"""
- new_id = old_id + (-1 if up else 1)
- self._move_booking(old_id, # moving down implies
- new_id + (0 if up else 1)) # jumping over next item
- self._recalc_dat_lines()
+ new_id = idx_from + (-1 if up else 1)
+ idx_to = new_id + (0 if up else 1) # down-move imlies jump over next
+ self._move_booking(new_id, idx_to)
+ self._sync()
return new_id
- def _remove_booking(
- self,
- to_remove: Booking,
- summed_gap: list[DatLine]
- ) -> None:
- del self.bookings[to_remove.id_]
- for booking in self.bookings[to_remove.id_:]:
- booking.id_ -= 1
- if to_remove.id_ == 0:
- self.initial_gap_lines += summed_gap
- else:
- assert to_remove.prev is not None
- to_remove.prev.gap_lines += summed_gap
- for neighbour in to_remove.prev, to_remove.next:
- if neighbour:
- neighbour.recalc_prev_next(self.bookings)
- self._recalc_dat_lines()
-
def rewrite_booking(self, old_id: int, new_lines: list[DatLine]) -> int:
"""Rewrite Booking with new_lines, move if changed date."""
old_booking = self.bookings[old_id]
after_gap = old_booking.gap_lines_copied # new gap be old gap _plus_
after_gap += new_lines[booked_end:] # any new gap lines
if not new_booked_lines: # interpret empty posting as deletion request
- self._remove_booking(old_booking, before_gap + after_gap)
+ del self.bookings[old_id]
+ for booking in self.bookings[old_id:]:
+ booking.id_ -= 1
+ leftover_gap = before_gap + after_gap
+ if old_id == 0:
+ self.initial_gap_lines += leftover_gap
+ else:
+ self.bookings[old_id - 1].gap_lines += leftover_gap
+ self._sync(check_dates=False)
return old_id if old_id < len(self.bookings) else 0
+ if old_id == 0:
+ self.initial_gap_lines += before_gap
+ else:
+ self.bookings[old_id - 1].gap_lines += before_gap
+ new_date = new_booked_lines[0].code.lstrip().split(maxsplit=1)[0]
updated = Booking(old_id, new_booked_lines, after_gap)
self.bookings[old_id] = updated
- updated.recalc_prev_next(self.bookings)
- new_date = new_booked_lines[0].code.lstrip().split(maxsplit=1)[0]
if new_date != old_booking.date: # if changed date, move to there
- i_booking = self.bookings[0]
- new_idx = None
- while i_booking.next:
- if (not i_booking.prev) and i_booking.date > new_date:
- new_idx = i_booking.id_
- break
- if i_booking.next.date > new_date:
- break
- i_booking = i_booking.next
- if new_idx is None:
- new_idx = i_booking.id_ + 1
- # ensure that, if we land in group of like-dated Bookings, we
- # land on the edge closest to our last position
- if i_booking.date == new_date and old_id < i_booking.id_:
- new_idx = [b for b in self.bookings
- if b != updated and b.date == new_date][0].id_
- self._move_booking(old_id, new_idx)
- if updated.id_ == 0:
- self.initial_gap_lines += before_gap
- else:
- assert updated.prev is not None
- updated.prev.gap_lines += before_gap
- self._recalc_dat_lines()
- self._check_date_order()
+ if self.bookings[0].date > new_date:
+ new_id = 0
+ elif self.bookings[-1].date < new_date:
+ new_id = self.bookings[-1].id_ + 1
+ else:
+ of_date_1st = i_booking = self.bookings[0]
+ while i_booking.next:
+ if of_date_1st.date != i_booking.date:
+ of_date_1st = i_booking
+ if i_booking.next.date > new_date:
+ break
+ i_booking = i_booking.next
+ # ensure that, if we land in group of like-dated Bookings, we
+ # land on the edge closest to our last position
+ new_id = (of_date_1st.id_ if old_id < i_booking.id_
+ else i_booking.id_ + 1)
+ self._move_booking(old_id, new_id)
+ self._sync(check_dates=False)
return updated.id_
def _add_new_booking(
+ ' ; '.join([''] + [s for s in [intro_comment] if s]))
] + dat_lines_transaction)
self.bookings += [booking]
- booking.recalc_prev_next(self.bookings)
- self._recalc_dat_lines()
- self._check_date_order()
+ self._sync()
return booking.id_
def add_empty_booking(self) -> int: