def command(args): from decimal import Decimal as D import sympy import sys import sr.tools.budget as budget try: root = budget.find_root() except budget.NotBudgetRepo: print("Error: Please run in budget.git", file=sys.stderr) exit(1) t = budget.load_budget(root) config = None # find the first BudgetItem to get its config for i in t.walk(): if isinstance(i, budget.BudgetItem): config = i.conf break assert config is not None r = sympy.S(args.expression) r = D("%.2f" % r.evalf(subs=config.vars)) print(r)
def command(args): import sys from sr.tools import budget try: root = budget.find_root() except budget.NotBudgetRepo: print("Error: Please run in budget.git", file=sys.stderr) sys.exit(1) b = budget.load_budget(root) if args.path is not None: b = b.path(args.path) b.draw()
def command(args): from decimal import Decimal as D import os from subprocess import check_output import sys import yaml from sr.tools import budget, spending from sr.tools.config import Config config = Config() try: root = budget.find_root() except budget.NotBudgetRepo: print("Error: Please run in budget.git", file=sys.stderr) exit(1) os.chdir(root) b = budget.load_budget("./") try: f = open("funds-in.yaml", "r") except IOError: if os.path.exists("check"): os.execv("./check", ["./check"] + sys.argv[1:]) exit(1) funds_in = yaml.load(f) total_in = D(0) for i in funds_in["incoming"]: # Convert to str to avoid some rounding issues! total_in += D(str(i["amount"])) # All the lines in funds-in.yaml are divided by 1.1 at the moment MAX = total_in * budget.FUDGE_FACTOR # Calculate how much has been budgeted in total budgeted = D(0) for line in b.walk(): budgeted += (D(1) + line.uncertainty) * line.cost if budgeted > MAX: print("Fail: Budget is £%s too high" % (budgeted - MAX), file=sys.stderr) exit(1) print("OK: ", end='') if budgeted == MAX: print("Budget is at maximum.") else: print("Budget is £%s below maximum." % (MAX - budgeted)) def list_referenced_lines(root): """Return a list of budget lines referenced in ``spending.git``.""" cmd = [ "ledger", "--file", os.path.join(root, "spending.dat"), "reg", "^Expenses:", "--format", "%A\n" ] output = check_output(cmd, universal_newlines=True).strip() lines = output.splitlines() return [spending.account_to_budget_line(line) for line in lines] spending_root = args.spending if spending_root is None: spending_root_config = config.get("spending") if spending_root_config is not None: spending_root = os.path.expanduser(spending_root_config) if spending_root is not None: # Check stuff with spending.git inv_budget_lines = set() # Check that all transactions reference valid budget lines for line in list_referenced_lines(spending_root): try: b.path(line) except budget.InvalidPath: inv_budget_lines.add(line) if len(inv_budget_lines): print("{} non-existent budget lines referenced from spending.git:". format(len(inv_budget_lines))) for line in inv_budget_lines: print(" -", line) else: print("OK: All spending.git budget line references are valid.") # Lines that have been closed but don't match their spends close_fail = {} # Lines that are over budget over_budget = {} # Load the budget again with spending information b = spending.load_budget_with_spending(spending_root) # Check that the spends line up with the budget moneywise for bline in b.walk(): if bline.closed: # Closed lines must match their spent amount if bline.cost != bline.spent: close_fail[bline] = bline.spent continue line_max = bline.cost * (D(1) + bline.uncertainty) if bline.spent > line_max: over_budget[bline] = bline.spent if len(close_fail): print("{0} closed lines do not match the amount spent against " "them:".format(len(close_fail))) for line, spent in close_fail.items(): print(" - {0}: £{1} spent, £{2} allocated".format( line.name, spent, line.cost)) else: print("OK: All closed lines have the correct value.") if len(over_budget): print("{0} lines are over budget:".format(len(over_budget))) for line, spent in over_budget.items(): print(" - {0}: £{1} spent, £{2} allocated (including fudge)". format(line.name, spent, line.cost * budget.FUDGE_FACTOR)) else: print("OK: No open budget lines are overspent.")
def command(args): from decimal import Decimal as D import sys import sr.tools.budget as budget from sr.tools.budget import diff_trees, AddedItem, RemovedItem, \ ChangedItem, changes_to_tree try: root = budget.find_root() except budget.NotBudgetRepo: print("Error: Please run in budget.git", file=sys.stderr) sys.exit(1) # Build the two trees a = budget.load_budget_rev(root, args.old) if args.new is None: # Use current working copy b = budget.load_budget(root) else: # Use specified revision b = budget.load_budget_rev(root, args.new) if args.limit is not None: # Limit the diff to the given subtree try: a = a.path(args.limit) except budget.InvalidPath: # Tree wasn't present in the original # Create an empty tree a = budget.BudgetTree("") try: b = b.path(args.limit) except budget.InvalidPath: # Tree isn't present in the other # Create an empty tree b = budget.BudgetTree("") changes = [] for change in diff_trees(a, b): if args.zero_hide \ and (isinstance(change, AddedItem) or isinstance(change, RemovedItem)) \ and change.a.cost == 0: continue changes.append(change) if args.tree: tree = changes_to_tree(changes) tree.draw() exit(0) class Summary: def __init__(self): self.added = D(0) self.removed = D(0) summary = Summary() for x in changes: if isinstance(x, AddedItem): print("A", x.a.name, "({0})".format(x.a.cost)) summary.added += x.a.cost if isinstance(x, RemovedItem): print("D", x.a.name, "({0})".format(x.a.cost)) summary.removed += x.a.cost if isinstance(x, ChangedItem): d = x.b.cost - x.a.cost s = "" if d > 0: s = "+" summary.added += d else: summary.removed += -d print("M", x.a.name, "({0}{1})".format(s, d)) print("---") print(" Summary") print(" Added: £{0}".format(summary.added)) print(" Removed: £{0}".format(summary.removed)) print(" Net change: {0:+}".format(summary.added - summary.removed))
def command(args): from decimal import Decimal as D import os from subprocess import check_output import sys import yaml from sr.tools import budget, spending from sr.tools.config import Config config = Config() try: root = budget.find_root() except budget.NotBudgetRepo: print("Error: Please run in budget.git", file=sys.stderr) exit(1) os.chdir(root) b = budget.load_budget("./") try: f = open("funds-in.yaml", "r") except IOError: if os.path.exists("check"): os.execv("./check", ["./check"] + sys.argv[1:]) exit(1) funds_in = yaml.load(f) total_in = D(0) for i in funds_in["incoming"]: # Convert to str to avoid some rounding issues! total_in += D(str(i["amount"])) # All the lines in funds-in.yaml are divided by 1.1 at the moment MAX = total_in * budget.FUDGE_FACTOR # Calculate how much has been budgeted in total budgeted = D(0) for line in b.walk(): budgeted += (D(1) + line.uncertainty) * line.cost if budgeted > MAX: print("Fail: Budget is £%s too high" % (budgeted - MAX), file=sys.stderr) exit(1) print("OK: ", end='') if budgeted == MAX: print("Budget is at maximum.") else: print("Budget is £%s below maximum." % (MAX - budgeted)) def list_referenced_lines(root): """Return a list of budget lines referenced in ``spending.git``.""" cmd = ["ledger", "--file", os.path.join(root, "spending.dat"), "reg", "^Expenses:", "--format", "%A\n"] output = check_output(cmd, universal_newlines=True).strip() lines = output.splitlines() return [spending.account_to_budget_line(line) for line in lines] spending_root = args.spending if spending_root is None: spending_root_config = config.get("spending") if spending_root_config is not None: spending_root = os.path.expanduser(spending_root_config) if spending_root is not None: # Check stuff with spending.git inv_budget_lines = set() # Check that all transactions reference valid budget lines for line in list_referenced_lines(spending_root): try: b.path(line) except budget.InvalidPath: inv_budget_lines.add(line) if len(inv_budget_lines): print("{} non-existent budget lines referenced from spending.git:" .format(len(inv_budget_lines))) for line in inv_budget_lines: print(" -", line) else: print("OK: All spending.git budget line references are valid.") # Lines that have been closed but don't match their spends close_fail = {} # Lines that are over budget over_budget = {} # Load the budget again with spending information b = spending.load_budget_with_spending(spending_root) # Check that the spends line up with the budget moneywise for bline in b.walk(): if bline.closed: # Closed lines must match their spent amount if bline.cost != bline.spent: close_fail[bline] = bline.spent continue line_max = bline.cost * (D(1) + bline.uncertainty) if bline.spent > line_max: over_budget[bline] = bline.spent if len(close_fail): print("{0} closed lines do not match the amount spent against " "them:".format(len(close_fail))) for line, spent in close_fail.items(): print(" - {0}: £{1} spent, £{2} allocated".format( line.name, spent, line.cost)) else: print("OK: All closed lines have the correct value.") if len(over_budget): print("{0} lines are over budget:".format(len(over_budget))) for line, spent in over_budget.items(): print(" - {0}: £{1} spent, £{2} allocated (including fudge)" .format(line.name, spent, line.cost * budget.FUDGE_FACTOR)) else: print("OK: No open budget lines are overspent.")