X-Git-Url: https://plomlompom.com/repos/feed.xml?a=blobdiff_plain;ds=sidebyside;f=ledger.py;h=c004d22126846415a916e02a6bc4aad6052df9bf;hb=75169bb51854ec32d147976ac4381c6011c5320d;hp=02e0273c74f26590e426e7860d72576e0721073f;hpb=c8e39d2444ae290dba713c542a5f95a47ab67ba9;p=misc diff --git a/ledger.py b/ledger.py index 02e0273..c004d22 100755 --- a/ledger.py +++ b/ledger.py @@ -3,7 +3,7 @@ import os import html import jinja2 import decimal -import datetime +from datetime import datetime, timedelta from urllib.parse import parse_qs, urlparse hostName = "localhost" serverPort = 8082 @@ -67,7 +67,6 @@ def bookings_to_account_tree(bookings): def parse_lines(lines, validate_bookings=True): - import datetime inside_booking = False date_string, description = None, None booking_lines = [] @@ -109,7 +108,7 @@ def parse_lines(lines, validate_bookings=True): toks = non_comment.split(maxsplit=1) date_string = toks[0] try: - datetime.datetime.strptime(date_string, '%Y-%m-%d') + datetime.strptime(date_string, '%Y-%m-%d') except ValueError: raise HandledException(f"{prefix} bad date string: {date_string}") if last_date > date_string: @@ -280,37 +279,30 @@ class Database: f = open(self.lock_file, 'w+') f.close() - # always back up most recent to .bak - bakpath = f'{self.db_file}.bak' - shutil.copy(self.db_file, bakpath) - # collect modification times of numbered .bak files - bak_prefix = f'{bakpath}.' + bak_prefix = f'{self.db_file}.bak.' backup_dates = [] i = 0 bak_as = f'{bak_prefix}{i}' while os.path.exists(bak_as): mod_time = os.path.getmtime(bak_as) - backup_dates += [str(datetime.datetime.fromtimestamp(mod_time))] + backup_dates += [str(datetime.fromtimestamp(mod_time))] i += 1 bak_as = f'{bak_prefix}{i}' - # collect what numbered .bak files to save: - # shrink datetime string right to left character by character, - # on each step add the oldest file whose mtime still fits the pattern - # (privilege older files to keep existing longer) + # collect what numbered .bak files to save: the older, the fewer; for each + # timedelta, keep the newest file that's older + ages_to_keep = [timedelta(minutes=4**i) for i in range(0, 8)] + now = datetime.now() to_save = [] - datetime_len = 19 - now = str(datetime.datetime.now())[:datetime_len] - while datetime_len > 2: - # assume backup_dates starts with oldest dates - for i, date in enumerate(backup_dates): - if date[:datetime_len] == now: - if i not in to_save: - to_save += [i] - break - datetime_len -= 1 - now = now[:datetime_len] + for age in ages_to_keep: + limit = now - age + for i, date in enumerate(reversed(backup_dates)): + if datetime.strptime(date, '%Y-%m-%d %H:%M:%S.%f') < limit: + unreversed_i = len(backup_dates) - i - 1 + if unreversed_i not in to_save: + to_save += [unreversed_i] + break # remove redundant backup files j = 0 @@ -326,25 +318,51 @@ class Database: except FileNotFoundError: pass - # put second backup copy of current state at end of bak list + # put copy of current state at end of bak list shutil.copy(self.db_file, f'{bak_prefix}{j}') with open(self.db_file, mode) as f: f.write(text); os.remove(self.lock_file) - def replace(self, start, end, lines): - total_lines = self.real_lines[:start] + lines + self.real_lines[end:] + def insert_at_date(self, lines, date): + start_at = len(self.real_lines) + for b in self.bookings: + if b.date_string == date: + start_at = b.start_line + break + elif b.date_string > date: + break + if start_at == len(self.real_lines): + lines = [''] + lines + return self.write_lines_in_total_lines_at(self.real_lines, start_at, lines) + + def update(self, start, end, lines, date): + total_lines = self.real_lines[:start] + self.real_lines[end:] + n_original_lines = end - start + start_at = len(total_lines) + for b in self.bookings: + if b.date_string == date: + if start_at == len(total_lines) or b.start_line == start: + start_at = b.start_line + if b.start_line > start: + start_at -= n_original_lines + elif b.date_string > date: + break + if start_at == len(total_lines): + lines = [''] + lines + return self.write_lines_in_total_lines_at(total_lines, start_at, lines) + + def write_lines_in_total_lines_at(self, total_lines, start_at, lines): + total_lines = total_lines[:start_at] + lines + [''] + total_lines[start_at:] + _, _ = parse_lines(lines) text = '\n'.join(total_lines) self.write_db(text) - - def append(self, lines): - text = '\n\n' + '\n'.join(lines) + '\n\n' - self.write_db(text, 'a') + return start_at def get_nth_for_booking_of_start_line(self, start_line): nth = 0 for b in self.bookings: - if b.start_line == start_line: + if b.start_line >= start_line: break nth += 1 return nth @@ -494,6 +512,7 @@ input[type=number] { text-align: right; font-family: monospace; } / free | copy:structured / free +| move {% if move_up %}up{% else %}up{% endif %}/{% if move_down %}down{% else %}down{% endif %} | balance after ] @@ -538,14 +557,27 @@ input[type=number] { text-align: right; font-family: monospace; } _, _ = parse_lines(lines) # if saving, process where to and where to redirect after if 'save' in postvars.keys(): + last_date = str(datetime.now())[:10] + if len(db.bookings) > 0: + last_date = db.bookings[-1].date_string + target_date = last_date[:] + first_line_tokens = lines[0].split() if len(lines) > 0 else '' + first_token = first_line_tokens[0] if len(first_line_tokens) > 0 else '' + try: + datetime.strptime(first_token, '%Y-%m-%d') + target_date = first_token + except ValueError: + pass if start == end == 0: - db.append(lines) - redir_url = f'/#last' - else: - db.replace(start, end, lines) + start = db.insert_at_date(lines, target_date) nth = db.get_nth_for_booking_of_start_line(start) - redir_url = f'/#{nth}' - self.send_code_and_headers(301, [('Location', redir_url)]) + else: + new_start = db.update(start, end, lines, target_date) + nth = db.get_nth_for_booking_of_start_line(new_start) + if new_start > start: + nth -= 1 + redir_url = f'/#{nth}' + self.send_code_and_headers(302, [('Location', redir_url)]) # otherwise just re-build editing form else: if '/add_structured' == parsed_url.path: @@ -575,6 +607,14 @@ input[type=number] { text-align: right; font-family: monospace; } page += self.add_free(db, start, end, copy=True) elif parsed_url.path == '/copy_structured': page += self.add_structured(db, start, end, copy=True) + elif parsed_url.path == '/move_up': + nth = self.move_up(db, start, end) + self.send_code_and_headers(302, [('Location', f'/#{nth}')]) + return + elif parsed_url.path == '/move_down': + nth = self.move_down(db, start, end) + self.send_code_and_headers(302, [('Location', f'/#{nth}')]) + return else: page += self.ledger_as_html(db) page += self.footer @@ -676,6 +716,8 @@ input[type=number] { text-align: right; font-family: monospace; } elements_to_write = [] last_i = i = 0 ## for nth, booking in enumerate(db.bookings): + move_up = nth > 0 and db.bookings[nth - 1].date_string == booking.date_string + move_down = nth < len(db.bookings) - 1 and db.bookings[nth + 1].date_string == booking.date_string booking_end = last_i = booking.start_line + len(booking.lines) booking_lines = [] i = booking.start_line ## @@ -698,6 +740,8 @@ input[type=number] { text-align: right; font-family: monospace; } date=booking.date_string, desc=booking.description, head_comment=db.comments[booking.start_line], + move_up=move_up, + move_down=move_down, booking_lines = booking_lines)] elements_to_write += [single_c_tmpl.render(c=c) for c in db.comments[last_i:] if c != ''] # return '\n'.join(elements_to_write) @@ -760,7 +804,7 @@ input[type=number] { text-align: right; font-family: monospace; } for currency in moneys.keys(): datalist_sets['currencies'].add(currency) content = '' - today = str(datetime.datetime.now())[:10] + today = str(datetime.now())[:10] booking_lines = [] if copy: start = end = 0 @@ -799,6 +843,31 @@ input[type=number] { text-align: right; font-family: monospace; } end=end) return content + def move_up(self, db, start, end): + prev_booking = None + for redir_nth, b in enumerate(db.bookings): + if b.start_line >= start: + break + prev_booking = b + start_at = prev_booking.start_line + self.make_move(db, start, end, start_at) + return redir_nth - 1 + + def move_down(self, db, start, end): + next_booking = None + for redir_nth, b in enumerate(db.bookings): + if b.start_line > start: + next_booking = b + break + start_at = next_booking.start_line + len(next_booking.lines) - (end - start) + 1 + self.make_move(db, start, end, start_at) + return redir_nth + + def make_move(self, db, start, end, start_at): + lines = db.get_lines(start, end) + total_lines = db.real_lines[:start] + db.real_lines[end:] + db.write_lines_in_total_lines_at(total_lines, start_at, lines) + if __name__ == "__main__": webServer = HTTPServer((hostName, serverPort), MyServer)