示例#1
0
def command(args):
    import sys

    import sr.tools.budget as budget

    cmd = args.command
    subtree_path = args.subtree_path

    sub = budget.load_budget("./")

    if subtree_path != "/":
        for p in subtree_path.split("/"):
            if p == "":
                continue

            try:
                sub = sub.children[p]
            except KeyError:
                print("'%s' is not a child -- aborting." % p, file=sys.stderr)
                sys.exit(1)

    if isinstance(sub, budget.BudgetItem):
        # Put the BudgetItem in a BudgetTree to simplify commands
        i = sub
        sub = budget.BudgetTree("tmp")
        sub.add_child(i)

    commands[cmd](sub)
示例#2
0
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)
示例#3
0
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)
示例#4
0
def command(args):
    import sys

    import sr.tools.budget as budget

    cmd = args.command
    subtree_path = args.subtree_path

    sub = budget.load_budget("./")

    if subtree_path != "/":
        for p in subtree_path.split("/"):
            if p == "":
                continue

            try:
                sub = sub.children[p]
            except KeyError:
                print("'%s' is not a child -- aborting." % p, file=sys.stderr)
                sys.exit(1)

    if isinstance(sub, budget.BudgetItem):
        # Put the BudgetItem in a BudgetTree to simplify commands
        i = sub
        sub = budget.BudgetTree("tmp")
        sub.add_child(i)

    commands[cmd](sub)
示例#5
0
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()
示例#6
0
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()
示例#7
0
def load_budget_with_spending(root):
    """
    Load the budget with spending data.

    :param str root: The root path of the spending data.
    :returns: The budget with spending data.
    :rtype: budget.Budget
    """
    bud = budget.load_budget(os.path.join(root, "budget/"))
    lines = load_budget_spends(root)

    for b in bud.walk():
        if b.name not in lines:
            b.spent = D(0)
        else:
            b.spent = lines[b.name]

    return bud
示例#8
0
文件: spending.py 项目: srobo/tools
def load_budget_with_spending(root):
    """
    Load the budget with spending data.

    :param str root: The root path of the spending data.
    :returns: The budget with spending data.
    :rtype: budget.Budget
    """
    bud = budget.load_budget(os.path.join(root, "budget/"))
    lines = load_budget_spends(root)

    for b in bud.walk():
        if b.name not in lines:
            b.spent = D(0)
        else:
            b.spent = lines[b.name]

    return bud
示例#9
0
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.")
示例#10
0
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))
示例#11
0
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))
示例#12
0
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.")