~trufas/ledgeroni

8c70e73a68c4c9b796859852ccfef3208f5d4095 — Rafael Castillo 1 year, 10 months ago bc69071 + f62814f
Merge branch 'master' of github.com:rafacastillol/ledgeroni
M ledgeroni/expression.py => ledgeroni/expression.py +6 -3
@@ 15,7 15,7 @@ def tokenize_expression(expr_str):
        match = TOKEN_REGEX.match(expr_str)


PRECEDENCE = {'and': 1, 'or': 1, 'not': 2}
PRECEDENCE = {'and': 1, 'or': 1, 'not': 2, '(': 0}

def build_postfix_expression(expr_str):
    operator_stack = []


@@ 23,6 23,9 @@ def build_postfix_expression(expr_str):
    last_was_expr = False
    for token in tokenize_expression(expr_str):
        if token == '(':
            if last_was_expr:
                flush_op_stack('or', operator_stack, output)
                operator_stack.append('or')
            operator_stack.append(token)
        elif token == ')':
            while operator_stack:


@@ 36,10 39,10 @@ def build_postfix_expression(expr_str):
            flush_op_stack(token, operator_stack, output)
            operator_stack.append(token)
        else:
            output.append(token)
            if last_was_expr:
                flush_not(operator_stack, output)
                flush_op_stack('or', operator_stack, output)
                operator_stack.append('or')
            output.append(token)

        last_was_expr = token not in ('and', 'or', '(', 'not')


A tests/test_aggregate.py => tests/test_aggregate.py +108 -0
@@ 0,0 1,108 @@
from datetime import datetime
from fractions import Fraction
import arrow
from ledgeroni.aggregate import AccountAggregate
from ledgeroni.types import Commodity, Transaction, Posting
from ledgeroni.journal import Journal

USD = Commodity(is_prefix=True, name='$')
AU = Commodity(is_prefix=False, name='AU')
BTC = Commodity(is_prefix=False, name=' BTC')

def test_add_commodity():
    agg = AccountAggregate()
    agg.add_commodity(('Bank', 'Paypal'), Fraction (10, 1), USD)
    agg.add_commodity(('Bank', 'Banorte'), Fraction (20, 1), USD)
    agg.add_commodity(('Asset', 'Bitcoin'), Fraction (2, 1), BTC)

    assert agg.aggregates == {USD: 30, BTC: 2}

def test_add_transaction():
    agg = AccountAggregate()

    trans = Transaction(
        date=arrow.get(datetime(2013, 2, 20)),
        description='Purchased reddit gold for the year')
    trans.add_posting(Posting(
        account=('Asset', 'Bitcoin Wallet'),
        amounts={BTC: -10}))
    trans.add_posting(Posting(
        account=('Expense', 'Web Services', 'Reddit'),
        amounts=None))

    agg.add_transaction(trans)

    trans = Transaction(
        date=arrow.get(datetime(2013, 2, 20)),
        description='2012/7/1 Partial payment from Client X')
    trans.add_posting(Posting(
        account=('Bank', 'Paypal'),
        amounts={USD: 350}))
    trans.add_posting(Posting(
        account=('Expense', 'Web Services', 'Reddit'),
        amounts=None))

    agg.add_transaction(trans)

    assert agg.aggregates == {USD: 0, BTC: 0}


def test_add_transaction():
    agg = AccountAggregate()

    trans = Transaction(
        date=arrow.get(datetime(2013, 2, 20)),
        description='Purchased reddit gold for the year')
    trans.add_posting(Posting(
        account=('Asset', 'Bitcoin Wallet'),
        amounts={BTC: -10}))
    trans.add_posting(Posting(
        account=('Expense', 'Web Services', 'Reddit'),
        amounts=None))

    agg.add_transaction(trans)

    trans = Transaction(
        date=arrow.get(datetime(2013, 2, 20)),
        description='2012/7/1 Partial payment from Client X')
    trans.add_posting(Posting(
        account=('Bank', 'Paypal'),
        amounts={USD: 350}))
    trans.add_posting(Posting(
        account=('Expense', 'Web Services', 'Reddit'),
        amounts=None))

    agg.add_transaction(trans)

    assert agg.aggregates == {USD: 0, BTC: 0}


def test_add_from_journal():
    agg = AccountAggregate()
    journal = Journal()

    trans = Transaction(
        date=arrow.get(datetime(2013, 2, 20)),
        description='Purchased reddit gold for the year')
    trans.add_posting(Posting(
        account=('Asset', 'Bitcoin Wallet'),
        amounts={BTC: -10}))
    trans.add_posting(Posting(
        account=('Expense', 'Web Services', 'Reddit'),
        amounts=None))
    journal.add_transaction(trans)

    trans = Transaction(
        date=arrow.get(datetime(2013, 2, 20)),
        description='2012/7/1 Partial payment from Client X')
    trans.add_posting(Posting(
        account=('Bank', 'Paypal'),
        amounts={USD: 350}))
    trans.add_posting(Posting(
        account=('Expense', 'Web Services', 'Reddit'),
        amounts=None))
    journal.add_transaction(trans)

    agg.add_from_journal(journal)

    assert agg.aggregates == {USD: 0, BTC: 0}

A tests/test_cli.py => tests/test_cli.py +37 -0
@@ 0,0 1,37 @@
from click.testing import CliRunner
from ledgeroni.cli import cli

def test_balance():
    runner = CliRunner()
    result = runner.invoke(cli, [
        '-f', 'tests/sample_data/index.ledger', '--price-db',
        'tests/sample_data/prices_db', 'bal', 'Expense'])
    assert result.exit_code == 0
    result = runner.invoke(cli, [
        '-f', 'tests/sample_data/index.ledger', '--price-db',
        'tests/sample_data/prices_db', 'balance', 'Expense'])
    assert result.exit_code == 0


def test_print():
    runner = CliRunner()
    result = runner.invoke(cli, [
        '-f', 'tests/sample_data/index.ledger', '--price-db',
        'tests/sample_data/prices_db', 'print', 'Expense'])
    assert result.exit_code == 0


def test_register():
    runner = CliRunner()
    result = runner.invoke(cli, [
        '-f', 'tests/sample_data/index.ledger', '--price-db',
        'tests/sample_data/prices_db', 'register', 'Expense'])
    assert result.exit_code == 0
    result = runner.invoke(cli, [
        '-f', 'tests/sample_data/index.ledger', '--price-db',
        'tests/sample_data/prices_db', 'reg', 'Expense'])
    assert result.exit_code == 0
    result = runner.invoke(cli, [
        '-f', 'tests/sample_data/index.ledger', '--price-db',
        'tests/sample_data/prices_db', 'r', 'Expense'])
    assert result.exit_code == 0

A tests/test_expression.py => tests/test_expression.py +127 -0
@@ 0,0 1,127 @@
import re
from ledgeroni import expression
from ledgeroni.query import Or, And, Not, RegexQuery


def test_tokenize_expression():
    expr = 'not (Expense and Reddit) or Asset'
    tokens = list(expression.tokenize_expression(expr))
    assert tokens == ['not', '(', 'Expense', 'and', 'Reddit', ')', 'or', 'Asset']


class TestBuildPostfixExpression:
    def test_binary(self):
        expr = 'x and y'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'y', 'and']

        expr = 'x and y and z'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'y', 'and', 'z', 'and']

        expr = 'x or y or z'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'y', 'or', 'z', 'or']

    def test_unary(self):
        expr = 'not x'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'not']

    def test_precedence(self):
        expr = 'not x and y'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'not', 'y', 'and']

    def test_parens(self):
        expr = 'not (x and y)'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'y', 'and', 'not']

    def test_implicit_or(self):
        expr = 'x y'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'y', 'or']

        expr = 'x y z'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'y', 'or', 'z', 'or']

        expr = '(x y) z'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'y', 'or', 'z', 'or']

        expr = 'x (y or z)'
        postfix = list(expression.build_postfix_expression(expr))
        assert postfix == ['x', 'y', 'z', 'or', 'or']


class TestBuildExprFromPostfix:
    def test_binary(self):
        postfix = ['x', 'y', 'or']
        result = expression.build_expr_from_postfix(postfix)
        query = Or((RegexQuery(re.compile('y')),
                    RegexQuery(re.compile('x'))))

        assert result == query

        postfix = ['x', 'y', 'and']
        result = expression.build_expr_from_postfix(postfix)
        query = And((RegexQuery(re.compile('y')),
                     RegexQuery(re.compile('x'))))

        assert result == query

    def test_unary(self):
        postfix = ['x', 'not']
        result = expression.build_expr_from_postfix(postfix)
        query = Not(RegexQuery(re.compile('x')))

        assert result == query

    def test_single(self):
        postfix = ['x']
        result = expression.build_expr_from_postfix(postfix)
        query = RegexQuery(re.compile('x'))

        assert result == query


class TestBuildExpression:
    def test_binary(self):
        postfix = 'x or y'
        result = expression.build_expression(postfix)
        query = Or((RegexQuery(re.compile('y')),
                    RegexQuery(re.compile('x'))))

        assert result == query

        postfix = 'x and y'
        result = expression.build_expression(postfix)
        query = And((RegexQuery(re.compile('y')),
                     RegexQuery(re.compile('x'))))

        assert result == query

    def test_unary(self):
        postfix = 'not x'
        result = expression.build_expression(postfix)
        query = Not(RegexQuery(re.compile('x')))

        assert result == query

    def test_single(self):
        postfix = 'x'
        result = expression.build_expression(postfix)
        query = RegexQuery(re.compile('x'))

        assert result == query

    def test_implicit_or(self):
        postfix = 'x y'
        result = expression.build_expression(postfix)
        query = Or((RegexQuery(re.compile('y')),
                    RegexQuery(re.compile('x'))))

        assert result == query


M tests/test_parser.py => tests/test_parser.py +4 -3
@@ 1,10 1,11 @@
import unittest
from datetime import datetime
from fractions import Fraction
import arrow

from ledgeroni import parser
from ledgeroni.types import (Transaction, Posting, Commodity, Price,
                                   IgnoreSymbol, DefaultCommodity)
import arrow
from datetime import datetime
from fractions import Fraction

USD = Commodity(is_prefix=True, name='$')
AU = Commodity(is_prefix=False, name='AU')

M tests/test_query.py => tests/test_query.py +27 -0
@@ 1,9 1,36 @@
from ledgeroni.query import And, Or, Not, RegexQuery
import re


def test_regex_query():
    q = RegexQuery(re.compile('Expense'))

    assert q.execute('Expense:Web Services:Reddit') == True
    assert q.execute('Something:Expense:Web Services:Reddit') == True


def test_and():
    q = And((RegexQuery(re.compile('Expense')),
             RegexQuery(re.compile('Reddit'))))

    assert q.execute('Expense:Web Services:Reddit') == True
    assert q.execute('Expense:Food') == False
    assert q.execute('We:Did:It:Reddit') == False
    assert q.execute('Bank:Paypal') == False


def test_or():
    q = Or((RegexQuery(re.compile('Expense')),
             RegexQuery(re.compile('Reddit'))))

    assert q.execute('Expense:Web Services:Reddit') == True
    assert q.execute('Expense:Food') == True
    assert q.execute('We:Did:It:Reddit') == True
    assert q.execute('Bank:Paypal') == False


def test_not():
    q = Not(RegexQuery(re.compile('Expense')))

    assert q.execute('Expense:Web Services:Reddit') == False
    assert q.execute('Bank:Paypal') == True

A tests/test_sorting.py => tests/test_sorting.py +49 -0
@@ 0,0 1,49 @@
from datetime import datetime
import arrow

from ledgeroni.sorter import JournalSorter
from ledgeroni.types import Transaction

def test_from_term_list():
    terms = ['d']

    sorter = JournalSorter.from_term_list(terms)
    assert sorter.trans_terms == [(False, 'date')]

    terms = ['date']

    sorter = JournalSorter.from_term_list(terms)
    assert sorter.trans_terms == [(False, 'date')]

    terms = ['-d']

    sorter = JournalSorter.from_term_list(terms)
    assert sorter.trans_terms == [(True, 'date')]


def test_sort_transactions():
    trans1 = Transaction(
        date=arrow.get(datetime(2013, 2, 20)),
        description='Purchased reddit gold for the year')

    trans2 = Transaction(
        date=arrow.get(datetime(2012, 2, 20)),
        description='Paid a parking ticket')

    trans3 = Transaction(
        date=arrow.get(datetime(2011, 2, 20)),
        description='Donated to charity')

    trans_list = [trans1, trans2, trans3]

    sorter = JournalSorter.from_term_list(('d',))
    sorter.sort_transactions(trans_list)

    assert trans_list == [trans3, trans2, trans1]

    trans_list = [trans2, trans1, trans3]

    sorter = JournalSorter.from_term_list(('-d',))
    sorter.sort_transactions(trans_list)

    assert trans_list == [trans1, trans2, trans3]