0 3 0
Brett Smith - 4 months ago 2020-05-17 18:05:49
brettcsmith@brettcsmith.org
historical: Add Beancount output format.
3 files changed with 57 insertions and 14 deletions:
↑ Collapse Diff ↑
...
 
@@ -172,9 +172,32 @@ class LedgerFormatter(Formatter):
172 172
 
        )
173 173
 

	
174 174
 

	
175
 
class BeancountFormatter(LedgerFormatter):
176
 
    COST_FMT = '{{{}}}'
177
 

	
178
 
    def __init__(self, cost_rates, price_rates=None,
179
 
                 signed_currencies=(), base_fmt='###0.###',
180
 
                 rate_precision=5, denomination=None):
181
 
        super().__init__(
182
 
            cost_rates,
183
 
            price_rates,
184
 
            (),
185
 
            base_fmt.replace(',', ''),
186
 
            rate_precision,
187
 
            denomination,
188
 
        )
189
 

	
190
 
    def price_rate(self, from_amt, from_curr, to_curr):
191
 
        if self.price_rates is None:
192
 
            return None
193
 
        else:
194
 
            return self.price_rates.convert(from_amt, from_curr, to_curr)
195
 

	
196
 

	
175 197
 
class Formats(enum.Enum):
176 198
 
    RAW = Formatter
177 199
 
    LEDGER = LedgerFormatter
200
 
    BEANCOUNT = BeancountFormatter
178 201
 

	
179 202
 
    @classmethod
180 203
 
    def from_arg(cls, s):
...
 
@@ -110,8 +110,9 @@ class Configuration:
110 110
 
        hist_parser.add_argument(
111 111
 
            '--output-format',
112 112
 
            type=historical.Formats.from_arg,
113
 
            choices=[fmt.name.lower() for fmt in historical.Formats],
114
 
            help="Output format. Choices are %(choices)s. Default `raw`.",
113
 
            help="Output format."
114
 
            " Choices are `raw`, `ledger`, `beancount`."
115
 
            " Default `raw`.",
115 116
 
        )
116 117
 
        # --ledger and --no-ledger predate --output-format.
117 118
 
        hist_parser.add_argument(
...
 
@@ -40,6 +40,7 @@ class FakeConfig:
40 40
 
output = pytest.fixture(lambda: io.StringIO())
41 41
 
parametrize_format = pytest.mark.parametrize('output_format', [
42 42
 
    oxrhist.Formats.LEDGER,
43
 
    oxrhist.Formats.BEANCOUNT,
43 44
 
])
44 45
 

	
45 46
 
@pytest.fixture(scope='module')
...
 
@@ -85,20 +86,38 @@ def lines_from_run(config, output):
85 86
 
def check_fx_amount(config, lines, amount, cost, fx_code, fx_sign=None, price=None):
86 87
 
    if price is None:
87 88
 
        price = cost
89
 
    rate_fmt = f'{{}} {re.escape(fx_code)}'
88 90
 
    cost = re.escape(cost) + r'\d*'
89 91
 
    price = re.escape(price) + r'\d*'
90
 
    if fx_sign is not None and fx_code in config.args.signed_currencies:
91
 
        rate_fmt = f'{re.escape(fx_sign)}{{}}'
92
 
    if config.args.output_format is oxrhist.Formats.LEDGER:
93
 
        if fx_sign is not None and fx_code in config.args.signed_currencies:
94
 
            rate_fmt = f'{re.escape(fx_sign)}{{}}'
95
 
        cost_re = '{{={}}}'.format(rate_fmt.format(cost))
96
 
        price_re = ' @ {}'.format(rate_fmt.format(price))
92 97
 
    else:
93
 
        rate_fmt = f'{{}} {re.escape(fx_code)}'
94
 
    pattern = r'^{} {{={}}} @ {}$'.format(
95
 
        re.escape(amount),
96
 
        rate_fmt.format(cost),
97
 
        rate_fmt.format(price),
98
 
    )
98
 
        amount = amount.replace(',', '')
99
 
        cost_re = '{{{}}}'.format(rate_fmt.format(cost))
100
 
        if config.args.from_date is None:
101
 
            price_re = ''
102
 
        else:
103
 
            price_re = ' @ {}'.format(rate_fmt.format(price))
104
 
    pattern = r'^{} {}{}$'.format(re.escape(amount), cost_re, price_re)
99 105
 
    line = next(lines, "<EOF>")
100 106
 
    assert re.match(pattern, line)
101 107
 

	
108
 
def check_nonfx_amount(config, lines, amount, code=None, sign=None):
109
 
    if config.args.output_format is oxrhist.Formats.LEDGER:
110
 
        if code is None:
111
 
            code = 'USD'
112
 
            sign = '$'
113
 
        if code in config.args.signed_currencies and sign is not None:
114
 
            expected = f'{sign}{amount}\n'
115
 
        else:
116
 
            expected = f'{amount} {code}\n'
117
 
    else:
118
 
        expected = f'{amount.replace(",", "")} {code or "USD"}\n'
119
 
    assert next(lines, "<EOF>") == expected
120
 

	
102 121
 
def test_rate_list(single_responder, output, any_date):
103 122
 
    config = build_config(single_responder, any_date)
104 123
 
    lines = lines_from_run(config, output)
...
 
@@ -144,7 +163,7 @@ def test_ledger_conversion(single_responder, output, any_date, output_format):
144 163
 
                          amount=300, output_format=output_format)
145 164
 
    lines = lines_from_run(config, output)
146 165
 
    check_fx_amount(config, lines, '300 ALL', '0.00691', 'USD', '$')
147
 
    assert next(lines) == '$2.08\n'
166
 
    check_nonfx_amount(config, lines, '2.08')
148 167
 
    assert next(lines, None) is None
149 168
 

	
150 169
 
@parametrize_format
...
 
@@ -173,7 +192,7 @@ def test_redundant_denomination(single_responder, output, any_date, output_forma
173 192
 
                          output_format=output_format, denomination='USD')
174 193
 
    lines = lines_from_run(config, output)
175 194
 
    check_fx_amount(config, lines, '10.00 ANG', '0.558', 'USD', '$')
176
 
    assert next(lines) == '$5.59\n'
195
 
    check_nonfx_amount(config, lines, '5.59')
177 196
 
    assert next(lines, None) is None
178 197
 

	
179 198
 
@parametrize_format
...
 
@@ -182,7 +201,7 @@ def test_from_denomination(single_responder, output, any_date, output_format):
182 201
 
                          from_currency='USD', to_currency='ALL', amount=10,
183 202
 
                          output_format=output_format, denomination='USD')
184 203
 
    lines = lines_from_run(config, output)
185
 
    assert next(lines) == '$10.00\n'
204
 
    check_nonfx_amount(config, lines, '10.00')
186 205
 
    check_fx_amount(config, lines, '1,445 ALL', '0.00691', 'USD', '$')
187 206
 
    assert next(lines, None) is None
188 207
 

	
...
 
@@ -197,7 +216,7 @@ def test_rate_precision_added_as_needed(single_responder, output, any_date, outp
197 216
 
    # Make sure the rate is specified with enough precision to get the
198 217
 
    # correct conversion amount.
199 218
 
    check_fx_amount(config, lines, '63,805.00 RUB', '0.0175204', 'USD', '$')
200
 
    assert next(lines) == '$1,117.89\n'
219
 
    check_nonfx_amount(config, lines, '1,117.89')
201 220
 
    assert next(lines, None) is None
202 221
 

	
203 222
 
@parametrize_format
0 comments (0 inline, 0 general)