From b1e27d12c7062b95cb5fa814b98e0055e77ed443 Mon Sep 17 00:00:00 2001
From: Ekaitz Zarraga <ekaitz@elenq.tech>
Date: Mon, 20 Jul 2020 19:10:33 +0200
Subject: Add subcommands sketch

---
 README.md            |  3 ++-
 fracture/__main__.py | 65 ++++++++++++++++++++++++++++++++++++++++++++--------
 fracture/invoice.py  | 61 +++++++++++++++++++++++++++++-------------------
 3 files changed, 94 insertions(+), 35 deletions(-)

diff --git a/README.md b/README.md
index b6d0525..579d11f 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,8 @@ SQLite database.
 Run `fracture configure` for configuration. It will automatically open a sample
 configuration file for you. Once it's filled it will store the configuration
 file in `$XDG_CONFIG_HOME/fracture/config` (`XDG_CONFIG_HOME` falls back to
-`$HOME/.config`).
+`$HOME/.config`). If configuration file is already set it will load the current
+configuration file for you to edit.
 
 ## Storage
 
diff --git a/fracture/__main__.py b/fracture/__main__.py
index 71ca639..b33dca4 100644
--- a/fracture/__main__.py
+++ b/fracture/__main__.py
@@ -1,7 +1,7 @@
 from invoice import Invoice, Tax
 import db
 
-import argparse
+from argparse import ArgumentParser as ap
 import tempfile
 import subprocess
 import sqlite3
@@ -9,14 +9,19 @@ from configparser import ConfigParser
 import os
 from os import path
 
+CONFIG_FILE = ""
+
 
 def load_config():
-    basedir = os.environ.get('XDG_CONFIG_HOME', None) or \
+    basedir = os.environ.get("XDG_CONFIG_HOME", None) or \
               path.join(os.environ["HOME"], ".config")
 
     basedir = path.join(basedir, "fracture")
     confile = path.join(basedir, "config")
 
+    global CONFIG_FILE
+    CONFIG_FILE = confile
+
     conf = ConfigParser()
     conf.read(confile)
 
@@ -40,6 +45,13 @@ def load_config():
     Invoice.CURRENCY  = conf["invoice"].get("currency", "€")
     Invoice.CURRENCY_DECIMAL = conf["invoice"].getint("currency_decimal", 2)
 
+    # TEMPLATE ?
+    # TODO
+    # Or dump a json or something?
+    templatefile = conf["invoice"]["template"]
+    if not path.isabs(templatefile):
+        templatefile = path.join(confile, templatefile)
+
     # INVOICE LEVEL TAXES (like IRPF in Spain)
     tax = []
     for k in conf["taxes"]:
@@ -48,7 +60,7 @@ def load_config():
 
     # DATABASE
     Invoice.DB_FILE = path.join(basedir, "invoice.db")
-    if not os.path.exists(Invoice.DB_FILE):
+    if not path.exists(Invoice.DB_FILE):
         db.create(Invoice.DB_FILE)
 
 def call_editor(filename):
@@ -59,7 +71,7 @@ def call_editor(filename):
     process.wait()
 
 def edit(contents):
-    """ Edit temporary file with initial `contents` and return it's edited
+    """ Edit temporary file with initial `contents` and return it"s edited
     content """
 
     with tempfile.NamedTemporaryFile(mode="w", delete=False) as t:
@@ -75,14 +87,47 @@ def edit(contents):
 
     return edited_content
 
+def edit_config():
+    # TODO: Generate config file if not created
+    call_editor(CONFIG_FILE)
+
+def new_invoice():
+    num = Invoice.from_config( edit( Invoice().to_config() )).persist()
+    #
+    print(num)
+    # edit(Invoice.load(num).to_config())
+
+def summarize():
+    pass
 
 if __name__ == "__main__":
     load_config()
 
 
-    # TODO
-    a = Invoice()
-    a.from_config( edit( a.to_config() ))
-    num = a.persist()
-    print(num)
-    edit(Invoice.load(num).to_config())
+
+    # create the top-level parser
+    parser = ap(prog="fracture")
+    #parser.add_argument("--foo", action="store_true", help="foo help")
+    parser.set_defaults(func=lambda: parser.print_help())
+
+    # make subparsers
+    subparsers = parser.add_subparsers(title= "Subcommands",
+                help="sub-command help")
+
+    new_parser = subparsers.add_parser("new", aliases=["n"], help="a help")
+    new_parser.set_defaults(func=new_invoice)
+
+    configure_parser = subparsers.add_parser("configure", aliases=["c","conf"],
+                help="b help")
+    configure_parser.set_defaults(func=edit_config)
+
+
+    summary_parser = subparsers.add_parser("summary", aliases=["s", "sum"],
+                help="get summary")
+    summary_parser.add_argument("--xlsx", action="store_true", help="aaa")
+    summary_parser.add_argument("--quarter", action="store_true", help="aaa")
+    summary_parser.add_argument("--year", action="store_true", help="aaa")
+    summary_parser.set_defaults(func=summarize)
+
+    # parse
+    parser.parse_args().func()
diff --git a/fracture/invoice.py b/fracture/invoice.py
index f0ab2f7..4ea9d71 100644
--- a/fracture/invoice.py
+++ b/fracture/invoice.py
@@ -57,6 +57,7 @@ class Invoice:
         self.taxes    = taxes or Invoice.DEFAULT_TAXES
 
     def set_series(self, series):
+        """ Series is an integer """
         if series not in Invoice.SERIES.keys():
             raise ValueError ("Not valid series for Invoice. Valid are %s"
                     % Invoice.SERIES)
@@ -100,12 +101,13 @@ class Invoice:
         return strf.getvalue()
 
 
-    def from_config(self, config):
+    @classmethod
+    def from_config(cls, config):
         cfg = ConfigParser()
         cfg.read_string(config)
 
         # PRODUCTS SECTIONS
-        self.products = ()
+        products = ()
         for s in cfg.sections():
             if not s.startswith("product"):
                 continue
@@ -113,32 +115,41 @@ class Invoice:
             un   = float(cfg[s]["units"])
             pu   = float(cfg[s]["price_unit"])
             vat  = float(cfg[s]["vat"])
-            self.products += ( Product(desc,un,pu,vat) ,)
-            if cfg[s]["description"] == Invoice.DEFAULT_PRODUCT_DESC:
+            products += ( Product(desc,un,pu,vat) ,)
+            if cfg[s]["description"] == cls.DEFAULT_PRODUCT_DESC:
                 raise ValueError("Product name not set")
-        if len(self.products) == 0:
+        if len(products) == 0:
             raise ValueError("No products assigned")
 
         # TAXES SECTION
-        self.taxes = ()
+        taxes = ()
         if cfg.has_section("taxes"):
             for x in cfg["taxes"]:
-                self.taxes += ( Tax(x, float(cfg["taxes"][x])) ,)
+                taxes += ( Tax(x, float(cfg["taxes"][x])) ,)
 
         # INVOICE SECTION
         if not cfg.has_section("invoice"):
             raise ValueError("[invoice] section needed")
-        i = cfg["invoice"]
-        self.set_series(int(i["series"]))
-        self.date   = datetime.strptime(i["date"], "%Y-%m-%d").date()
-        self.notes  = i["notes"]
-        self.set_type(i["type"])
-        self.customer= Customer(i["customer-name"],
-                                i["customer-address"],
-                                i["customer-id"])
-        if i["customer-name"] == Invoice.DEFAULT_CUSTOMER_DESC:
+        i        = cfg["invoice"]
+        series   = int(i["series"])
+        date     = datetime.strptime(i["date"], "%Y-%m-%d").date()
+        notes    = i["notes"]
+        type     = i["type"]
+        customer = Customer( i["customer-name"],
+                             i["customer-address"],
+                             i["customer-id"] )
+        if i["customer-name"] == cls.DEFAULT_CUSTOMER_DESC:
             raise ValueError("Customer name not set")
 
+        return cls(
+                type,
+                series,
+                date,
+                notes,
+                products,
+                customer,
+                taxes)
+
     def persist(self):
         conn = sqlite3.connect(Invoice.DB_FILE)
         c = conn.cursor()
@@ -185,17 +196,16 @@ class Invoice:
         conn.close()
         return self.id
 
-    def load(id):
-        with sqlite3.connect(Invoice.DB_FILE) as conn:
+    @classmethod
+    def load(cls, id):
+        with sqlite3.connect(cls.DB_FILE) as conn:
             conn.row_factory = sqlite3.Row
 
-            invoice = Invoice()
-
             c = conn.cursor()
 
             # PRODUCTS
             products = ()
-            c.execute("""SELECT * FROM products WHERE invoice_id = ?""", (id,))
+            c.execute("SELECT * FROM products WHERE invoice_id = ?", (id,))
             res = c.fetchone()
             while res is not None:
                 desc = res["description"]
@@ -207,16 +217,16 @@ class Invoice:
 
             # TAXES
             taxes = ()
-            c.execute("""SELECT * FROM taxes    WHERE invoice_id = ?""", (id,))
+            c.execute("SELECT * FROM taxes WHERE invoice_id = ?", (id,))
             res = c.fetchone()
             while res is not None:
                 taxes +=  (Tax(res["name"], res["ratio"]) ,)
                 res = c.fetchone()
 
             # INVOICE
-            c.execute("""SELECT * FROM invoices WHERE         id = ?""", (id,))
+            c.execute("SELECT * FROM invoices WHERE id = ?", (id,))
             res = c.fetchone()
-            return Invoice(
+            return cls(
                     res["type"],
                     res["series"],
                     res["date"],
@@ -229,4 +239,7 @@ class Invoice:
 
     def format(self):
         # TODO
+        # https://bugs.python.org/issue35111
         pass
+
+
-- 
cgit v1.2.3