From b1e27d12c7062b95cb5fa814b98e0055e77ed443 Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga 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