307 lines
8.8 KiB
Python
307 lines
8.8 KiB
Python
from pathlib import Path
|
|
import argparse
|
|
import datetime as dt
|
|
|
|
from .graph import discrete, monthly
|
|
from .state import pfstate
|
|
from .transactions import load_transactions, save_transactions
|
|
from . import report
|
|
from . import tools
|
|
|
|
|
|
class PfBudgetInitialized(Exception):
|
|
pass
|
|
|
|
|
|
class PfBudgetNotInitialized(Exception):
|
|
pass
|
|
|
|
|
|
class DataFileMissing(Exception):
|
|
pass
|
|
|
|
|
|
def get_argparser():
|
|
parser = argparse.ArgumentParser(description="does cool finance stuff")
|
|
parser.add_argument("-q", "--quiet", help="quiet")
|
|
|
|
subparsers = parser.add_subparsers(
|
|
dest="task", required=True, help="sub-command help"
|
|
)
|
|
|
|
p_init = subparsers.add_parser("init", help="init help")
|
|
p_restart = subparsers.add_parser("restart", help="restart help")
|
|
p_backup = subparsers.add_parser("backup", help="backup help")
|
|
p_parse = subparsers.add_parser("parse", help="parse help")
|
|
p_vacation = subparsers.add_parser(
|
|
"vacation", help="vacation help format: [YYYY/MM/DD]"
|
|
)
|
|
p_graph = subparsers.add_parser("graph", help="graph help")
|
|
p_report = subparsers.add_parser("report", help="report help")
|
|
p_status = subparsers.add_parser("status", help="status help")
|
|
|
|
p_init.add_argument("raw", help="the raw data dir")
|
|
p_init.add_argument("data", help="the parsed data dir")
|
|
|
|
p_restart.add_argument("--raw", help="new raw data dir")
|
|
p_restart.add_argument("--data", help="new parsed data dir")
|
|
|
|
p_backup.add_argument(
|
|
"option",
|
|
type=str,
|
|
choices=["single", "all", "restore"],
|
|
nargs="?",
|
|
default="single",
|
|
help="backup option help",
|
|
)
|
|
|
|
subparser_vacation = p_vacation.add_subparsers(
|
|
dest="option", required=True, help="vacation suboption help"
|
|
)
|
|
p_vacation_add = subparser_vacation.add_parser("add", help="add help")
|
|
p_vacation_add.add_argument(
|
|
"start", type=str, nargs=1, help="new vacation start date"
|
|
)
|
|
p_vacation_add.add_argument("end", type=str, nargs=1, help="new vacation end date")
|
|
_ = subparser_vacation.add_parser("list", help="list help")
|
|
p_vacation_remove = subparser_vacation.add_parser("remove", help="remove help")
|
|
p_vacation_remove.add_argument(
|
|
"pos", help="position of vacation to remove", type=int, nargs=1
|
|
)
|
|
|
|
p_graph.add_argument(
|
|
"option",
|
|
type=str,
|
|
choices=["monthly", "discrete"],
|
|
nargs="?",
|
|
default="monthly",
|
|
help="graph option help",
|
|
)
|
|
p_graph_interval = p_graph.add_mutually_exclusive_group()
|
|
p_graph_interval.add_argument(
|
|
"--interval", type=str, nargs=2, help="graph interval", metavar=("START", "END")
|
|
)
|
|
p_graph_interval.add_argument("--start", type=str, nargs=1, help="graph start date")
|
|
p_graph_interval.add_argument("--end", type=str, nargs=1, help="graph end date")
|
|
p_graph_interval.add_argument("--year", type=str, nargs=1, help="graph year")
|
|
|
|
p_init.set_defaults(func=init)
|
|
p_restart.set_defaults(func=restart)
|
|
p_backup.set_defaults(func=backup)
|
|
p_parse.set_defaults(func=parse)
|
|
p_vacation.set_defaults(func=vacation)
|
|
p_status.set_defaults(func=status)
|
|
p_graph.set_defaults(func=graph)
|
|
p_report.set_defaults(func=f_report)
|
|
|
|
return parser
|
|
|
|
|
|
def init(state, args):
|
|
"""Initialization
|
|
|
|
Creates the state file which stores the internal state of the program
|
|
for later use.
|
|
Calls parse, that parses all raw files into the data directory.
|
|
|
|
Args:
|
|
state (PFState): Internal state of the program
|
|
args (dict): argparse variables
|
|
Raises:
|
|
PfBudgetInitialized: Raised when there's already an initialized state
|
|
"""
|
|
if not state:
|
|
s = dict(
|
|
filename=tools.STATE_FILE,
|
|
raw_dir=args.raw,
|
|
data_dir=args.data,
|
|
raw_files=[],
|
|
data_files=[],
|
|
vacations=[],
|
|
last_backup="",
|
|
last_datadir_backup="",
|
|
)
|
|
try:
|
|
state = pfstate(tools.STATE_FILE, s)
|
|
parse(state, args)
|
|
except Exception as e:
|
|
print(e)
|
|
if Path(tools.STATE_FILE).is_file():
|
|
print(f"Deleting {tools.STATE_FILE}")
|
|
Path(tools.STATE_FILE).unlink()
|
|
|
|
else:
|
|
raise PfBudgetInitialized(f"{Path(tools.STATE_FILE)} already exists")
|
|
|
|
|
|
def restart(state, args):
|
|
"""Restart
|
|
|
|
Deletes state and creates a new one.
|
|
Parses all raw files into the data directory. New dirs can be passed as
|
|
arguments, otherwise uses previous values.
|
|
|
|
Args:
|
|
state (PFState): Internal state of the program
|
|
args (dict): argparse variables
|
|
|
|
Raises:
|
|
DataFileMissing: Missing data files from those listed in state
|
|
PfBudgetNotInitialized: Raised when no state has been initialized yet
|
|
"""
|
|
if state is not None:
|
|
for fn in state.data_files:
|
|
try:
|
|
(Path(state.data_dir) / fn).unlink()
|
|
except FileNotFoundError:
|
|
raise DataFileMissing("missing {}".format(Path(state.data_dir) / fn))
|
|
|
|
if args.raw:
|
|
state.raw_dir = args.raw
|
|
if args.data:
|
|
state.data_dir = args.data
|
|
state.raw_files = []
|
|
state.data_files = []
|
|
parse(state, args)
|
|
else:
|
|
raise PfBudgetNotInitialized(f"{Path(tools.STATE_FILE)} doesn't exist")
|
|
|
|
|
|
def backup(state, args):
|
|
"""Backup
|
|
|
|
Saves all transactions on transactions_#.csv
|
|
|
|
Args:
|
|
state (PFState): Internal state of the program
|
|
args (dict): argparse variables
|
|
"""
|
|
if args.option == "single":
|
|
tools.backup(state)
|
|
elif args.option == "all":
|
|
tools.full_backup(state)
|
|
elif args.option == "restore":
|
|
tools.restore(state)
|
|
|
|
|
|
def parse(state, args):
|
|
"""Parser
|
|
|
|
Parses the contents of the raw directory into the data files, and
|
|
categorizes the transactions.
|
|
|
|
Args:
|
|
state (PFState): Internal state of the program
|
|
args (dict): argparse variables
|
|
"""
|
|
raw_dir = args.raw if hasattr(args, "raw") else None
|
|
data_dir = args.data if hasattr(args, "data") else None
|
|
|
|
tools.parser(state, raw_dir, data_dir)
|
|
categorize(state, args)
|
|
|
|
|
|
def categorize(state, args):
|
|
"""Categorization
|
|
|
|
Automatically categorizes transactions based on the regex of each
|
|
category. Manually present the remaining to the user.
|
|
|
|
Args:
|
|
state (PFState): Internal state of the program
|
|
args (dict): argparse variables
|
|
"""
|
|
transactions = load_transactions(state.data_dir)
|
|
missing = tools.auto_categorization(state, transactions)
|
|
if missing:
|
|
tools.manual_categorization(state, transactions)
|
|
save_transactions(state.data_dir, transactions)
|
|
|
|
|
|
def vacation(state, args):
|
|
"""Vacations
|
|
|
|
Adds vacations to the pfstate.
|
|
|
|
Args:
|
|
state (PFState): Internal state of the program
|
|
args (dict): argparse variables
|
|
"""
|
|
if args.option == "list":
|
|
print(state.vacations)
|
|
elif args.option == "remove":
|
|
vacations = state.vacations
|
|
del state.vacations[args.pos[0]]
|
|
state.vacations = vacations
|
|
elif args.option == "add":
|
|
start = dt.datetime.strptime(args.start[0], "%Y/%m/%d").date()
|
|
end = dt.datetime.strptime(args.end[0], "%Y/%m/%d").date()
|
|
|
|
vacations = state.vacations
|
|
vacations.append((start, end))
|
|
state.vacations = vacations
|
|
|
|
|
|
def status(state, args):
|
|
"""Status
|
|
|
|
Prints the state file.
|
|
|
|
Args:
|
|
state (PFState): Internal state of the program
|
|
args (dict): argparse variables
|
|
"""
|
|
print(state)
|
|
|
|
|
|
def graph(state, args):
|
|
"""Graph
|
|
|
|
Plots the transactions over a period of time.
|
|
|
|
Args:
|
|
state (PFState): Internal state of the program
|
|
args (dict): argparse variables
|
|
"""
|
|
start, end = None, None
|
|
if args.start or args.interval:
|
|
start = dt.datetime.strptime(args.start[0], "%Y/%m/%d").date()
|
|
|
|
if args.end or args.interval:
|
|
end = dt.datetime.strptime(args.end[0], "%Y/%m/%d").date()
|
|
|
|
if args.interval:
|
|
start = dt.datetime.strptime(args.interval[0], "%Y/%m/%d").date()
|
|
end = dt.datetime.strptime(args.interval[1], "%Y/%m/%d").date()
|
|
|
|
if args.year:
|
|
start = dt.datetime.strptime(args.year[0], "%Y").date()
|
|
end = dt.datetime.strptime(
|
|
str(int(args.year[0]) + 1), "%Y"
|
|
).date() - dt.timedelta(days=1)
|
|
|
|
if args.option == "monthly":
|
|
monthly(state, start, end)
|
|
elif args.option == "discrete":
|
|
discrete(state, start, end)
|
|
|
|
|
|
def f_report(state, args):
|
|
"""Report
|
|
|
|
Prints a detailed report of the transactions over a period of time.
|
|
|
|
Args:
|
|
state (PFState): Internal state of the program
|
|
args (dict): argparse variables
|
|
"""
|
|
report.net(state)
|
|
|
|
|
|
def run():
|
|
parser = get_argparser()
|
|
args = parser.parse_args()
|
|
|
|
state = pfstate(tools.STATE_FILE)
|
|
args.func(state, args)
|