home · contact · privacy
Refactor all accounting scripts.
[misc] / ledger.py
index b4d9fd99191af8cdf3a45d2686654b4b371eb3fe..244a68444c6d60901330cd3df6ff637be24553f6 100755 (executable)
--- a/ledger.py
+++ b/ledger.py
@@ -1,6 +1,4 @@
-from http.server import BaseHTTPRequestHandler, HTTPServer
 import os
-import html
 import jinja2
 import decimal
 from datetime import datetime, timedelta
@@ -8,6 +6,89 @@ from urllib.parse import parse_qs, urlparse
 from plomlib import PlomDB, PlomException, run_server, run_server, PlomServer
 
 server_port = 8082
+db_path = '/home/plom/org/ledger2023.dat'
+
+html_head = """
+<style>
+body { color: #000000; }
+table { margin-bottom: 2em; }
+th, td { text-align: left }
+input[type=number] { text-align: right; font-family: monospace; }
+.money { font-family: monospace; text-align: right; }
+.comment { font-style: italic; color: #777777; }
+.meta { font-size: 0.75em; color: #777777; }
+.full_line_comment { display: block; white-space: nowrap; width: 0; }
+</style>
+<body>
+<a href="/">ledger</a>
+<a href="/balance">balance</a>
+<a href="/add_free">add free</a>
+<a href="/add_structured">add structured</a>
+<hr />
+"""
+booking_html = """
+<p id="{{nth}}"><a href="#{{nth}}">{{date}}</a> {{desc}} <span class="comment">{{head_comment|e}}</span><br />
+<span class="meta">[edit: <a href="/add_structured?start={{start}}&end={{end}}">structured</a>
+/ <a href="/add_free?start={{start}}&end={{end}}">free</a>
+| copy:<a href="/copy_structured?start={{start}}&end={{end}}">structured</a>
+/ <a href="/copy_free?start={{start}}&end={{end}}">free</a>
+| move {% if move_up %}<a href="/move_up?start={{start}}&end={{end}}">up</a>{% else %}up{% endif %}/{% if move_down %}<a href="/move_down?start={{start}}&end={{end}}">down</a>{% else %}down{% endif %}
+| <a href="/balance?stop={{nth+1}}">balance after</a>
+]</span>
+<table>
+{% for l in booking_lines %}
+{% if l.acc %}
+<tr><td>{{l.acc|e}}</td><td class="money">{{l.money|e}}</td><td class="comment">{{l.comment|e}}</td></tr>
+{% else %}
+<tr><td><div class="comment full_line_comment">{{l.comment|e}}</div></td></tr>
+{% endif %}
+{% endfor %}
+</table></p>
+"""
+add_form_header = """<form method="POST" action="{{action|e}}">
+<input type="submit" name="check" value="check" />
+<input type="submit" name="revert" value="revert" />
+"""
+add_form_footer = """
+<input type="hidden" name="start" value={{start}} />
+<input type="hidden" name="end" value={{end}} />
+<input type="submit" name="save" value="save!">
+</form>
+"""
+add_free_html = """<br />
+<textarea name="booking" rows=10 cols=80>
+{% for line in lines %}{{ line }}
+{% endfor %}
+</textarea>
+"""
+add_structured_html = """
+<input type="submit" name="add_taxes" value="add taxes" />
+<input type="submit" name="add_taxes2" value="add taxes2" />
+<input type="submit" name="add_sink" value="add sink" />
+<br />
+<input name="date" value="{{date|e}}" size=9 />
+<input name="description" value="{{desc|e}}" list="descriptions" />
+<textarea name="line_0_comment" rows=1 cols=20>{{head_comment|e}}</textarea>
+<input type="submit" name="line_0_add" value="[+]" />
+<br />
+{% for line in booking_lines %}
+<input name="line_{{line.i}}_account" value="{{line.acc|e}}" size=40 list="accounts" />
+<input type="number" name="line_{{line.i}}_amount" step=0.01 value="{{line.amt}}" size=10 />
+<input name="line_{{line.i}}_currency" value="{{line.curr|e}}" size=3 list="currencies" />
+<input type="submit" name="line_{{line.i}}_delete" value="[x]" />
+<input type="submit" name="line_{{line.i}}_delete_after" value="[XX]" />
+<input type="submit" name="line_{{line.i}}_add" value="[+]" />
+<textarea name="line_{{line.i}}_comment" rows=1 cols={% if line.comm_cols %}{{line.comm_cols}}{% else %}20{% endif %}>{{line.comment|e}}</textarea>
+<br />
+{% endfor %}
+{% for name, items in datalist_sets.items() %}
+<datalist id="{{name}}">
+{% for item in items %}
+  <option value="{{item|e}}">{{item|e}}</option>
+{% endfor %}
+</datalist>
+{% endfor %}
+"""
 
 
 def apply_booking_to_account_balances(account_sums, account, currency, amount):
@@ -256,7 +337,7 @@ class LedgerDB(PlomDB):
         self.bookings = []
         self.comments = []
         self.real_lines = []
-        super().__init__('_ledger')
+        super().__init__(db_path)
         ret = parse_lines(self.real_lines)
         self.bookings += ret[0]
         self.comments += ret[1]
@@ -433,55 +514,9 @@ class LedgerDB(PlomDB):
 
 
 class LedgerServer(PlomServer):
-    header = """<html>
-<meta charset="UTF-8">
-<style>
-body { color: #000000; }
-table { margin-bottom: 2em; }
-th, td { text-align: left }
-input[type=number] { text-align: right; font-family: monospace; }
-.money { font-family: monospace; text-align: right; }
-.comment { font-style: italic; color: #777777; }
-.meta { font-size: 0.75em; color: #777777; }
-.full_line_comment { display: block; white-space: nowrap; width: 0; }
-</style>
-<body>
-<a href="/">ledger</a>
-<a href="/balance">balance</a>
-<a href="/add_free">add free</a>
-<a href="/add_structured">add structured</a>
-<hr />
-"""
-    booking_tmpl = jinja2.Template("""
-<p id="{{nth}}"><a href="#{{nth}}">{{date}}</a> {{desc}} <span class="comment">{{head_comment|e}}</span><br />
-<span class="meta">[edit: <a href="/add_structured?start={{start}}&end={{end}}">structured</a>
-/ <a href="/add_free?start={{start}}&end={{end}}">free</a>
-| copy:<a href="/copy_structured?start={{start}}&end={{end}}">structured</a>
-/ <a href="/copy_free?start={{start}}&end={{end}}">free</a>
-| move {% if move_up %}<a href="/move_up?start={{start}}&end={{end}}">up</a>{% else %}up{% endif %}/{% if move_down %}<a href="/move_down?start={{start}}&end={{end}}">down</a>{% else %}down{% endif %}
-| <a href="/balance?stop={{nth+1}}">balance after</a>
-]</span>
-<table>
-{% for l in booking_lines %}
-{% if l.acc %}
-<tr><td>{{l.acc|e}}</td><td class="money">{{l.money|e}}</td><td class="comment">{{l.comment|e}}</td></tr>
-{% else %}
-<tr><td><div class="comment full_line_comment">{{l.comment|e}}</div></td></tr>
-{% endif %}
-{% endfor %}
-</table></p>
-""")
-    add_form_header = """<form method="POST" action="{{action|e}}">
-<input type="submit" name="check" value="check" />
-<input type="submit" name="revert" value="revert" />
-"""
-    add_form_footer = """
-<input type="hidden" name="start" value={{start}} />
-<input type="hidden" name="end" value={{end}} />
-<input type="submit" name="save" value="save!">
-</form>
-"""
-    footer = "</body>\n<html>"
+
+    def pre_init(self):
+        self.html_head += [html_head]
 
     def do_POST(self):
         try:
@@ -522,15 +557,14 @@ input[type=number] { text-align: right; font-family: monospace; }
                     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)])
+                self.redirect( f'/#{nth}')
             # otherwise just re-build editing form
             else:
                 if '/add_structured' == parsed_url.path: 
                     edit_content = self.add_structured(db, start, end, temp_lines=lines, add_empty_line=add_empty_line)
                 else:
                     edit_content = self.add_free(db, start, end)
-                self.send_HTML(self.header + edit_content + self.footer)
+                self.send_HTML(edit_content)
         except PlomException as e:
             self.fail_400(e)
 
@@ -541,29 +575,27 @@ input[type=number] { text-align: right; font-family: monospace; }
             start = int(params.get('start', ['0'])[0])
             end = int(params.get('end', ['0'])[0])
             db = LedgerDB()
-            page = self.header
             if parsed_url.path == '/balance':
                 stop = params.get('stop', [None])[0]
-                page += self.balance_as_html(db, stop)
+                page = self.balance_as_html(db, stop)
             elif parsed_url.path == '/add_free':
-                page += self.add_free(db, start, end)
+                page = self.add_free(db, start, end)
             elif parsed_url.path == '/add_structured':
-                page += self.add_structured(db, start, end)
+                page = self.add_structured(db, start, end)
             elif parsed_url.path == '/copy_free':
-                page += self.add_free(db, start, end, copy=True)
+                page = self.add_free(db, start, end, copy=True)
             elif parsed_url.path == '/copy_structured':
-                page += self.add_structured(db, start, end, copy=True)
+                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}')])
+                self.redirect(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}')])
+                self.redirect(f'/#{nth}')
                 return
             else:
-                page += self.ledger_as_html(db)
-            page += self.footer
+                page = self.ledger_as_html(db)
             self.send_HTML(page)
         except PlomException as e:
             self.fail_400(e)
@@ -644,6 +676,7 @@ input[type=number] { text-align: right; font-family: monospace; }
         return f"<pre>{content}</pre>"
 
     def ledger_as_html(self, db):
+        booking_tmpl = jinja2.Template(booking_html)
         single_c_tmpl = jinja2.Template('<span class="comment">{{c|e}}</span><br />')  ##
         elements_to_write = []
         last_i = i = 0  ##
@@ -665,7 +698,7 @@ input[type=number] { text-align: right; font-family: monospace; }
                  if booking_line[1] is not None:
                      money = f'{booking_line[1]} {booking_line[2]}'
                  booking_lines += [{'acc': booking_line[0], 'money':money, 'comment':comment}]  ##
-            elements_to_write += [self.booking_tmpl.render(
+            elements_to_write += [booking_tmpl.render(
                 nth=nth,
                 start=booking.start_line,
                 end=booking_end,
@@ -679,46 +712,14 @@ input[type=number] { text-align: right; font-family: monospace; }
         return '\n'.join(elements_to_write)
 
     def add_free(self, db, start=0, end=0, copy=False):
-        tmpl = jinja2.Template(self.add_form_header + """<br />
-<textarea name="booking" rows=10 cols=80>
-{% for line in lines %}{{ line }}
-{% endfor %}
-</textarea>
-""" + self.add_form_footer)
+        tmpl = jinja2.Template(add_form_header + add_free_html + add_form_footer) 
         lines = db.get_lines(start, end)
         if copy:
             start = end = 0
         return tmpl.render(action='add_free', start=start, end=end, lines=lines)
 
     def add_structured(self, db, start=0, end=0, copy=False, temp_lines=[], add_empty_line=None):
-        tmpl = jinja2.Template(self.add_form_header + """
-<input type="submit" name="add_taxes" value="add taxes" />
-<input type="submit" name="add_taxes2" value="add taxes2" />
-<input type="submit" name="add_sink" value="add sink" />
-<br />
-<input name="date" value="{{date|e}}" size=9 />
-<input name="description" value="{{desc|e}}" list="descriptions" />
-<textarea name="line_0_comment" rows=1 cols=20>{{head_comment|e}}</textarea>
-<input type="submit" name="line_0_add" value="[+]" />
-<br />
-{% for line in booking_lines %}
-<input name="line_{{line.i}}_account" value="{{line.acc|e}}" size=40 list="accounts" />
-<input type="number" name="line_{{line.i}}_amount" step=0.01 value="{{line.amt}}" size=10 />
-<input name="line_{{line.i}}_currency" value="{{line.curr|e}}" size=3 list="currencies" />
-<input type="submit" name="line_{{line.i}}_delete" value="[x]" />
-<input type="submit" name="line_{{line.i}}_delete_after" value="[XX]" />
-<input type="submit" name="line_{{line.i}}_add" value="[+]" />
-<textarea name="line_{{line.i}}_comment" rows=1 cols={% if line.comm_cols %}{{line.comm_cols}}{% else %}20{% endif %}>{{line.comment|e}}</textarea>
-<br />
-{% endfor %}
-{% for name, items in datalist_sets.items() %}
-<datalist id="{{name}}">
-{% for item in items %}
-  <option value="{{item|e}}">{{item|e}}</option>
-{% endfor %}
-</datalist>
-{% endfor %}
-""" + self.add_form_footer)
+        tmpl = jinja2.Template(add_form_header + add_structured_html + add_form_footer) 
         lines = temp_lines if len(''.join(temp_lines)) > 0 else db.get_lines(start, end)
         bookings, comments = parse_lines(lines, validate_bookings=False)
         if len(bookings) > 1: