summaryrefslogtreecommitdiffstats
path: root/tests/tabular_output/test_preprocessors.py
blob: e579bbe11514b22cce34d72f5b32aae856e83118 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# -*- coding: utf-8 -*-
"""Test CLI Helpers' tabular output preprocessors."""

from __future__ import unicode_literals
from decimal import Decimal

import pytest

from cli_helpers.compat import HAS_PYGMENTS
from cli_helpers.tabular_output.preprocessors import (
    align_decimals, bytes_to_string, convert_to_string, quote_whitespaces,
    override_missing_value, override_tab_value, style_output, format_numbers)

if HAS_PYGMENTS:
    from pygments.style import Style
    from pygments.token import Token

import inspect
import cli_helpers.tabular_output.preprocessors
import types


def test_convert_to_string():
    """Test the convert_to_string() function."""
    data = [[1, 'John'], [2, 'Jill']]
    headers = [0, 'name']
    expected = ([['1', 'John'], ['2', 'Jill']], ['0', 'name'])
    results = convert_to_string(data, headers)

    assert expected == (list(results[0]), results[1])


def test_override_missing_values():
    """Test the override_missing_values() function."""
    data = [[1, None], [2, 'Jill']]
    headers = [0, 'name']
    expected = ([[1, '<EMPTY>'], [2, 'Jill']], [0, 'name'])
    results = override_missing_value(data, headers, missing_value='<EMPTY>')

    assert expected == (list(results[0]), results[1])


@pytest.mark.skipif(not HAS_PYGMENTS, reason='requires the Pygments library')
def test_override_missing_value_with_style():
    """Test that *override_missing_value()* styles output."""

    class NullStyle(Style):
        styles = {
            Token.Output.Null: '#0f0'
        }

    headers = ['h1', 'h2']
    data = [[None, '2'], ['abc', None]]

    expected_headers = ['h1', 'h2']
    expected_data = [
        ['\x1b[38;5;10m<null>\x1b[39m', '2'],
        ['abc', '\x1b[38;5;10m<null>\x1b[39m']
    ]
    results = override_missing_value(data, headers, 
                                     style=NullStyle, missing_value="<null>")

    assert (expected_data, expected_headers) == (list(results[0]), results[1])


def test_override_tab_value():
    """Test the override_tab_value() function."""
    data = [[1, '\tJohn'], [2, 'Jill']]
    headers = ['id', 'name']
    expected = ([[1, '    John'], [2, 'Jill']], ['id', 'name'])
    results = override_tab_value(data, headers)

    assert expected == (list(results[0]), results[1])


def test_bytes_to_string():
    """Test the bytes_to_string() function."""
    data = [[1, 'John'], [2, b'Jill']]
    headers = [0, 'name']
    expected = ([[1, 'John'], [2, 'Jill']], [0, 'name'])
    results = bytes_to_string(data, headers)

    assert expected == (list(results[0]), results[1])


def test_align_decimals():
    """Test the align_decimals() function."""
    data = [[Decimal('200'), Decimal('1')], [
        Decimal('1.00002'), Decimal('1.0')]]
    headers = ['num1', 'num2']
    column_types = (float, float)
    expected = ([['200', '1'], ['  1.00002', '1.0']], ['num1', 'num2'])
    results = align_decimals(data, headers, column_types=column_types)

    assert expected == (list(results[0]), results[1])


def test_align_decimals_empty_result():
    """Test align_decimals() with no results."""
    data = []
    headers = ['num1', 'num2']
    column_types = ()
    expected = ([], ['num1', 'num2'])
    results = align_decimals(data, headers, column_types=column_types)

    assert expected == (list(results[0]), results[1])


def test_align_decimals_non_decimals():
    """Test align_decimals() with non-decimals."""
    data = [[Decimal('200.000'), Decimal('1.000')], [None, None]]
    headers = ['num1', 'num2']
    column_types = (float, float)
    expected = ([['200.000', '1.000'], [None, None]], ['num1', 'num2'])
    results = align_decimals(data, headers, column_types=column_types)

    assert expected == (list(results[0]), results[1])


def test_quote_whitespaces():
    """Test the quote_whitespaces() function."""
    data = [["  before", "after  "], ["  both  ", "none"]]
    headers = ['h1', 'h2']
    expected = ([["'  before'", "'after  '"], ["'  both  '", "'none'"]],
                ['h1', 'h2'])
    results = quote_whitespaces(data, headers)

    assert expected == (list(results[0]), results[1])


def test_quote_whitespaces_empty_result():
    """Test the quote_whitespaces() function with no results."""
    data = []
    headers = ['h1', 'h2']
    expected = ([], ['h1', 'h2'])
    results = quote_whitespaces(data, headers)

    assert expected == (list(results[0]), results[1])


def test_quote_whitespaces_non_spaces():
    """Test the quote_whitespaces() function with non-spaces."""
    data = [["\tbefore", "after \r"], ["\n both  ", "none"]]
    headers = ['h1', 'h2']
    expected = ([["'\tbefore'", "'after \r'"], ["'\n both  '", "'none'"]],
                ['h1', 'h2'])
    results = quote_whitespaces(data, headers)

    assert expected == (list(results[0]), results[1])


@pytest.mark.skipif(not HAS_PYGMENTS, reason='requires the Pygments library')
def test_style_output_no_styles():
    """Test that *style_output()* does not style without styles."""
    headers = ['h1', 'h2']
    data = [['1', '2'], ['a', 'b']]
    results = style_output(data, headers)

    assert (data, headers) == (list(results[0]), results[1])


@pytest.mark.skipif(HAS_PYGMENTS,
                    reason='requires the Pygments library be missing')
def test_style_output_no_pygments():
    """Test that *style_output()* does not try to style without Pygments."""
    headers = ['h1', 'h2']
    data = [['1', '2'], ['a', 'b']]
    results = style_output(data, headers)

    assert (data, headers) == (list(results[0]), results[1])


@pytest.mark.skipif(not HAS_PYGMENTS, reason='requires the Pygments library')
def test_style_output():
    """Test that *style_output()* styles output."""

    class CliStyle(Style):
        default_style = ""
        styles = {
            Token.Output.Header: 'bold ansibrightred',
            Token.Output.OddRow: 'bg:#eee #111',
            Token.Output.EvenRow: '#0f0'
        }
    headers = ['h1', 'h2']
    data = [['观音', '2'], ['Ποσειδῶν', 'b']]

    expected_headers = ['\x1b[91;01mh1\x1b[39;00m', '\x1b[91;01mh2\x1b[39;00m']
    expected_data = [['\x1b[38;5;233;48;5;7m观音\x1b[39;49m',
                      '\x1b[38;5;233;48;5;7m2\x1b[39;49m'],
                     ['\x1b[38;5;10mΠοσειδῶν\x1b[39m', '\x1b[38;5;10mb\x1b[39m']]
    results = style_output(data, headers, style=CliStyle)

    assert (expected_data, expected_headers) == (list(results[0]), results[1])


@pytest.mark.skipif(not HAS_PYGMENTS, reason='requires the Pygments library')
def test_style_output_with_newlines():
    """Test that *style_output()* styles output with newlines in it."""

    class CliStyle(Style):
        default_style = ""
        styles = {
            Token.Output.Header: 'bold ansibrightred',
            Token.Output.OddRow: 'bg:#eee #111',
            Token.Output.EvenRow: '#0f0'
        }
    headers = ['h1', 'h2']
    data = [['观音\nLine2', 'Ποσειδῶν']]

    expected_headers = ['\x1b[91;01mh1\x1b[39;00m', '\x1b[91;01mh2\x1b[39;00m']
    expected_data = [
        ['\x1b[38;5;233;48;5;7m观音\x1b[39;49m\n\x1b[38;5;233;48;5;7m'
         'Line2\x1b[39;49m',
         '\x1b[38;5;233;48;5;7mΠοσειδῶν\x1b[39;49m']]
    results = style_output(data, headers, style=CliStyle)


    assert (expected_data, expected_headers) == (list(results[0]), results[1])

@pytest.mark.skipif(not HAS_PYGMENTS, reason='requires the Pygments library')
def test_style_output_custom_tokens():
    """Test that *style_output()* styles output with custom token names."""

    class CliStyle(Style):
        default_style = ""
        styles = {
            Token.Results.Headers: 'bold ansibrightred',
            Token.Results.OddRows: 'bg:#eee #111',
            Token.Results.EvenRows: '#0f0'
        }
    headers = ['h1', 'h2']
    data = [['1', '2'], ['a', 'b']]

    expected_headers = ['\x1b[91;01mh1\x1b[39;00m', '\x1b[91;01mh2\x1b[39;00m']
    expected_data = [['\x1b[38;5;233;48;5;7m1\x1b[39;49m',
                      '\x1b[38;5;233;48;5;7m2\x1b[39;49m'],
                     ['\x1b[38;5;10ma\x1b[39m', '\x1b[38;5;10mb\x1b[39m']]

    output = style_output(
        data, headers, style=CliStyle,
        header_token='Token.Results.Headers',
        odd_row_token='Token.Results.OddRows',
        even_row_token='Token.Results.EvenRows')

    assert (expected_data, expected_headers) == (list(output[0]), output[1])


def test_format_integer():
    """Test formatting for an INTEGER datatype."""
    data = [[1], [1000], [1000000]]
    headers = ['h1']
    result_data, result_headers = format_numbers(data,
                                                 headers,
                                                 column_types=(int,),
                                                 integer_format=',',
                                                 float_format=',')

    expected = [['1'], ['1,000'], ['1,000,000']]
    assert expected == list(result_data)
    assert headers == result_headers


def test_format_decimal():
    """Test formatting for a DECIMAL(12, 4) datatype."""
    data = [[Decimal('1.0000')], [Decimal('1000.0000')], [Decimal('1000000.0000')]]
    headers = ['h1']
    result_data, result_headers = format_numbers(data,
                                                 headers,
                                                 column_types=(float,),
                                                 integer_format=',',
                                                 float_format=',')

    expected = [['1.0000'], ['1,000.0000'], ['1,000,000.0000']]
    assert expected == list(result_data)
    assert headers == result_headers


def test_format_float():
    """Test formatting for a REAL datatype."""
    data = [[1.0], [1000.0], [1000000.0]]
    headers = ['h1']
    result_data, result_headers = format_numbers(data,
                                                 headers,
                                                 column_types=(float,),
                                                 integer_format=',',
                                                 float_format=',')
    expected = [['1.0'], ['1,000.0'], ['1,000,000.0']]
    assert expected == list(result_data)
    assert headers == result_headers


def test_format_integer_only():
    """Test that providing one format string works."""
    data = [[1, 1.0], [1000, 1000.0], [1000000, 1000000.0]]
    headers = ['h1', 'h2']
    result_data, result_headers = format_numbers(data, headers, column_types=(int, float),
                                                 integer_format=',')

    expected = [['1', 1.0], ['1,000', 1000.0], ['1,000,000', 1000000.0]]
    assert expected == list(result_data)
    assert headers == result_headers


def test_format_numbers_no_format_strings():
    """Test that numbers aren't formatted without format strings."""
    data = ((1), (1000), (1000000))
    headers = ('h1',)
    result_data, result_headers = format_numbers(data, headers, column_types=(int,))
    assert list(data) == list(result_data)
    assert headers == result_headers


def test_format_numbers_no_column_types():
    """Test that numbers aren't formatted without column types."""
    data = ((1), (1000), (1000000))
    headers = ('h1',)
    result_data, result_headers = format_numbers(data, headers, integer_format=',',
                                  float_format=',')
    assert list(data) == list(result_data)
    assert headers == result_headers

def test_enforce_iterable():
    preprocessors = inspect.getmembers(cli_helpers.tabular_output.preprocessors, inspect.isfunction)
    loremipsum = 'lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod'.split(' ')
    for name, preprocessor in preprocessors:
        preprocessed = preprocessor(zip(loremipsum), ['lorem'], column_types=(str,))
        try:
            first = next(preprocessed[0])
        except StopIteration:
            assert False, "{} gives no output with iterator data".format(name)
        except TypeError:
            assert False, "{} doesn't return iterable".format(name)
        if isinstance(preprocessed[1], types.GeneratorType):
            assert False, "{} returns headers as iterator".format(name)