Skip to content

Commit 14a2046

Browse files
committed
update test cases
1 parent b6d7d17 commit 14a2046

File tree

2 files changed

+101
-79
lines changed

2 files changed

+101
-79
lines changed

jsonfmt.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,26 @@
22
'''JSON Formatter'''
33

44
import json
5-
import jmespath
6-
import pyperclip
75
import re
86
import toml
97
import yaml
108
from argparse import ArgumentParser
119
from functools import partial
1210
from io import TextIOBase
13-
from jmespath.exceptions import JMESPathError
1411
from pydoc import pager
15-
from pygments import highlight
16-
from pygments.formatters import TerminalFormatter
17-
from pygments.lexers import JsonLexer, TOMLLexer, YamlLexer
1812
from shutil import get_terminal_size
19-
from sys import stdin, stdout, stderr
13+
from sys import stdin, stdout, stderr, exit as sys_exit
2014
from typing import Any, List, IO, Optional, Sequence, Tuple, Union
2115
from unittest.mock import patch
2216

17+
import jmespath
18+
import pyperclip
19+
from jmespath.exceptions import JMESPathError
20+
from jmespath.parser import ParsedResult
21+
from pygments import highlight
22+
from pygments.formatters import TerminalFormatter
23+
from pygments.lexers import JsonLexer, TOMLLexer, YamlLexer
24+
2325
__version__ = '0.2.5'
2426

2527
NUMERIC = re.compile(r'-?\d+$|-?\d+\.\d+$|^-?\d+\.?\d+e-?\d+$')
@@ -45,7 +47,7 @@ def is_clipboard_available() -> bool:
4547
and paste_fn.__class__.__name__ != 'ClipboardUnavailable'
4648

4749

48-
def parse_to_pyobj(text: str, jpath: Optional[str]) -> Tuple[Any, str]:
50+
def parse_to_pyobj(text: str, jpath: Optional[ParsedResult]) -> Tuple[Any, str]:
4951
'''read json, toml or yaml from IO and then match sub-element by jmespath'''
5052
# parse json, toml or yaml to python object
5153
loads_methods = {
@@ -68,7 +70,7 @@ def parse_to_pyobj(text: str, jpath: Optional[str]) -> Tuple[Any, str]:
6870
return py_obj, fmt
6971
else:
7072
# match sub-elements via jmespath
71-
return jmespath.search(jpath, py_obj), fmt
73+
return jpath.search(py_obj), fmt
7274

7375

7476
def forward_by_keys(py_obj: Any, keys: str) -> Tuple[Any, Union[str, int]]:
@@ -160,7 +162,7 @@ def output(output_fp: IO, text: str, fmt: str, cp2clip: bool):
160162
# copy the result to clipboard
161163
if cp2clip:
162164
pyperclip.copy(text)
163-
print_inf('result copied to clipboard.')
165+
print_inf('result copied to clipboard')
164166
return
165167
elif output_fp.isatty():
166168
# highlight the text when output to TTY divice
@@ -178,11 +180,11 @@ def output(output_fp: IO, text: str, fmt: str, cp2clip: bool):
178180
output_fp.truncate()
179181
output_fp.write(text)
180182
if output_fp.fileno() > 2:
181-
print_inf(f'result written to {output_fp.name}.')
183+
print_inf(f'result written to {output_fp.name}')
182184

183185

184-
def process(input_fp: IO, jpath: Optional[str], convert_fmt: Optional[str], *,
185-
compact: bool, cp2clip: bool, escape: bool, indent: Union[int, str],
186+
def process(input_fp: IO, jpath: Optional[ParsedResult], convert_fmt: Optional[str],
187+
*, compact: bool, cp2clip: bool, escape: bool, indent: Union[int, str],
186188
overview: bool, overwrite: bool, sort_keys: bool,
187189
sets: Optional[list], pops: Optional[list]):
188190
# parse and format
@@ -244,6 +246,12 @@ def parse_cmdline_args(args: Optional[Sequence[str]] = None):
244246
def main():
245247
args = parse_cmdline_args()
246248

249+
try:
250+
jpath = None if args.jmespath is None else jmespath.compile(args.jmespath)
251+
except JMESPathError:
252+
print_err(f'invalid JMESPath expression: {args.jmespath}')
253+
sys_exit(1)
254+
247255
# check if the clipboard is available
248256
cp2clip = args.cp2clip and is_clipboard_available()
249257
if args.cp2clip and not cp2clip:
@@ -266,7 +274,7 @@ def main():
266274
# read from file
267275
input_fp = open(file, 'r+') if isinstance(file, str) else file
268276
process(input_fp,
269-
args.jmespath,
277+
jpath,
270278
args.format,
271279
compact=args.compact,
272280
cp2clip=cp2clip,
@@ -279,10 +287,10 @@ def main():
279287
pops=pops)
280288
except FormatError as err:
281289
print_err(err)
282-
except JMESPathError as err:
283-
print_err(err)
284290
except FileNotFoundError:
285-
print_err(f'no such file `{file}`')
291+
print_err(f'no such file: {file}')
292+
except PermissionError:
293+
print_err(f'permission denied: {file}')
286294
finally:
287295
input_fp = locals().get('input_fp')
288296
if isinstance(input_fp, TextIOBase):

test/test.py

Lines changed: 76 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from copy import deepcopy
99
from functools import partial
1010
from io import StringIO
11+
from jmespath import compile as jp_compile
1112
from pygments import highlight
1213
from pygments.formatters import TerminalFormatter
1314
from pygments.lexers import JsonLexer, YamlLexer, TOMLLexer
@@ -17,14 +18,16 @@
1718
sys.path.insert(0, BASE_DIR)
1819
import jsonfmt
1920

20-
21-
with open(f'{BASE_DIR}/test/example.json') as json_fp:
21+
JSON_FILE = f'{BASE_DIR}/test/example.json'
22+
with open(JSON_FILE) as json_fp:
2223
JSON_TEXT = json_fp.read()
2324

24-
with open(f'{BASE_DIR}/test/example.toml') as toml_fp:
25+
TOML_FILE = f'{BASE_DIR}/test/example.toml'
26+
with open(TOML_FILE) as toml_fp:
2527
TOML_TEXT = toml_fp.read()
2628

27-
with open(f'{BASE_DIR}/test/example.yaml') as yaml_fp:
29+
YAML_FILE = f'{BASE_DIR}/test/example.yaml'
30+
with open(YAML_FILE) as yaml_fp:
2831
YAML_TEXT = yaml_fp.read()
2932

3033

@@ -44,12 +47,21 @@ def color(text, format):
4447

4548

4649
class FakeStdStream(StringIO):
50+
51+
def __init__(self, initial_value='', newline='\n', tty=True):
52+
super().__init__(initial_value, newline)
53+
self._istty = tty
54+
4755
def isatty(self):
48-
return True
56+
return self._istty
4957

5058
def read(self):
5159
self.seek(0)
52-
return super().read()
60+
content = super().read()
61+
62+
self.seek(0)
63+
self.truncate()
64+
return content
5365

5466

5567
class FakeStdIn(FakeStdStream):
@@ -84,28 +96,27 @@ def test_is_clipboard_available(self):
8496

8597
def test_parse_to_pyobj(self):
8698
# normal parameters test
87-
matched_obj = jsonfmt.parse_to_pyobj(JSON_TEXT, "actions[:].calorie")
99+
matched_obj = jsonfmt.parse_to_pyobj(JSON_TEXT, jp_compile("actions[:].calorie"))
88100
self.assertEqual(matched_obj, ([294.9, -375], 'json'))
89-
matched_obj = jsonfmt.parse_to_pyobj(TOML_TEXT, "actions[*].name")
101+
matched_obj = jsonfmt.parse_to_pyobj(TOML_TEXT, jp_compile("actions[*].name"))
90102
self.assertEqual(matched_obj, (['eat', 'sport'], 'toml'))
91-
matched_obj = jsonfmt.parse_to_pyobj(YAML_TEXT, "actions[*].date")
103+
matched_obj = jsonfmt.parse_to_pyobj(YAML_TEXT, jp_compile("actions[*].date"))
92104
self.assertEqual(matched_obj, (['2021-03-02', '2023-04-27'], 'yaml'))
93105
# test not exists key
94-
matched_obj = jsonfmt.parse_to_pyobj(TOML_TEXT, "not_exist_key")
106+
matched_obj = jsonfmt.parse_to_pyobj(TOML_TEXT, jp_compile("not_exist_key"))
95107
self.assertEqual(matched_obj, (None, 'toml'))
96108
# test index out of range
97-
matched_obj = jsonfmt.parse_to_pyobj(YAML_TEXT, 'actions[7]')
109+
matched_obj = jsonfmt.parse_to_pyobj(YAML_TEXT, jp_compile('actions[7]'))
98110
self.assertEqual(matched_obj, (None, 'yaml'))
99111

100112
# test empty jmespath
101-
with patch('jsonfmt.stderr', FakeStdErr()), \
102-
self.assertRaises(jsonfmt.JMESPathError):
103-
matched_obj = jsonfmt.parse_to_pyobj(JSON_TEXT, "")
113+
with patch('jsonfmt.stderr', FakeStdErr()), self.assertRaises(jsonfmt.JMESPathError):
114+
matched_obj = jsonfmt.parse_to_pyobj(JSON_TEXT, jp_compile(""))
104115

105116
# test for unsupported format
106117
with self.assertRaises(jsonfmt.FormatError), open(__file__) as fp:
107118
text = fp.read()
108-
matched_obj = jsonfmt.parse_to_pyobj(text, "actions[0].calorie")
119+
matched_obj = jsonfmt.parse_to_pyobj(text, jp_compile("actions[0].calorie"))
109120

110121
def test_modify_pyobj_for_adding(self):
111122
# test empty sets and pops
@@ -297,7 +308,7 @@ def test_parse_cmdline_args(self):
297308
self.assertEqual(actual_args, expected_args)
298309

299310
@patch.multiple(sys, argv=['jsonfmt', '-i', 't', '-p', 'actions[*].name',
300-
f'{BASE_DIR}/test/example.json'])
311+
JSON_FILE])
301312
@patch.multiple(jsonfmt, stdout=FakeStdOut())
302313
def test_main_with_file(self):
303314
expected_output = color('[\n\t"eat",\n\t"sport"\n]', 'json')
@@ -311,97 +322,100 @@ def test_main_with_stdin(self):
311322
jsonfmt.main()
312323
self.assertEqual(jsonfmt.stdout.read(), expected_output)
313324

314-
@patch.multiple(sys, argv=['jsonfmt', 'not_exist_file.json', __file__])
315325
@patch.multiple(jsonfmt, stderr=FakeStdErr())
316-
def test_main_invalid_file(self):
317-
jsonfmt.main()
318-
errmsg = jsonfmt.stderr.read()
319-
self.assertIn('no such file `not_exist_file.json`', errmsg)
320-
self.assertIn('no json, toml or yaml found', errmsg)
321-
322-
@patch.multiple(sys, argv=['jsonfmt', '-f', 'json'])
323-
@patch.multiple(jsonfmt, stdin=FakeStdIn(']a, b, c]'), stderr=FakeStdErr())
324326
def test_main_invalid_input(self):
325-
jsonfmt.main()
326-
self.assertIn('no json, toml or yaml found', jsonfmt.stderr.read())
327+
# test not exist file and wrong format
328+
with patch.multiple(sys, argv=['jsonfmt', 'not_exist_file.json', __file__]):
329+
jsonfmt.main()
330+
errmsg = jsonfmt.stderr.read()
331+
self.assertIn('no such file: not_exist_file.json', errmsg)
332+
self.assertIn('no json, toml or yaml found', errmsg)
327333

328-
@patch.multiple(sys, argv=['jsonfmt', '-f', 'toml'])
329-
@patch.multiple(jsonfmt, stdin=FakeStdIn(JSON_TEXT), stdout=FakeStdOut())
330-
def test_json_to_toml(self):
331-
colored_output = color(TOML_TEXT, 'toml')
332-
jsonfmt.main()
333-
self.assertEqual(jsonfmt.stdout.read(), colored_output)
334+
# test wrong jmespath
335+
with patch('sys.argv', ['jsonfmt', JSON_FILE, '-p', '$.-[=]']),\
336+
self.assertRaises(SystemExit):
337+
jsonfmt.main()
338+
self.assertIn('invalid JMESPath expression', jsonfmt.stderr.read())
334339

335-
@patch.multiple(sys, argv=['jsonfmt', '-s', '-f', 'yaml'])
336-
@patch.multiple(jsonfmt, stdin=FakeStdIn(TOML_TEXT), stdout=FakeStdOut())
337-
def test_toml_to_yaml(self):
338-
colored_output = color(YAML_TEXT, 'yaml')
339-
jsonfmt.main()
340-
self.assertEqual(jsonfmt.stdout.read(), colored_output)
340+
@patch('jsonfmt.stdout', FakeStdOut())
341+
def test_main_convert(self):
342+
# test json to toml
343+
with patch.multiple(sys, argv=['jsonfmt', '-f', 'toml', JSON_FILE]):
344+
colored_output = color(TOML_TEXT, 'toml')
345+
jsonfmt.main()
346+
self.assertEqual(jsonfmt.stdout.read(), colored_output)
347+
348+
# test toml to yaml
349+
with patch.multiple(sys, argv=['jsonfmt', '-s', '-f', 'yaml', TOML_FILE]):
350+
colored_output = color(YAML_TEXT, 'yaml')
351+
jsonfmt.main()
352+
self.assertEqual(jsonfmt.stdout.read(), colored_output)
353+
354+
# test yaml to json
355+
with patch.multiple(sys, argv=['jsonfmt', '-c', '-f', 'json', YAML_FILE]):
356+
colored_output = color(JSON_TEXT, 'json')
357+
jsonfmt.main()
358+
self.assertEqual(jsonfmt.stdout.read(), colored_output)
341359

342-
@patch.multiple(sys, argv=['jsonfmt', '-c', '-f', 'json'])
343-
@patch.multiple(jsonfmt, stdin=FakeStdIn(YAML_TEXT), stdout=FakeStdOut())
344-
def test_yaml_to_json(self):
345-
colored_output = color(JSON_TEXT, 'json')
360+
@patch.multiple(sys, argv=['jsonfmt', '-oc'])
361+
@patch.multiple(jsonfmt, stdin=FakeStdIn('{"a": "asfd", "b": [1, 2, 3]}'), stdout=FakeStdOut(tty=False))
362+
def test_main_overview(self):
346363
jsonfmt.main()
347-
self.assertEqual(jsonfmt.stdout.read(), colored_output)
364+
self.assertEqual(jsonfmt.stdout.read(), '{"a":"...","b":[]}')
348365

349-
@patch.multiple(sys, argv=['jsonfmt', '-Ocsf', 'json',
350-
f'{BASE_DIR}/test/example.toml'])
351-
def test_overwrite_to_original_file(self):
366+
@patch('sys.argv', ['jsonfmt', '-Ocsf', 'json', TOML_FILE])
367+
def test_main_overwrite_to_original_file(self):
352368
try:
353369
jsonfmt.main()
354-
with open(f'{BASE_DIR}/test/example.toml') as toml_fp:
370+
with open(TOML_FILE) as toml_fp:
355371
new_content = toml_fp.read().strip()
356372
self.assertEqual(new_content, JSON_TEXT.strip())
357373
finally:
358-
with open(f'{BASE_DIR}/test/example.toml', 'w') as toml_fp:
374+
with open(TOML_FILE, 'w') as toml_fp:
359375
toml_fp.write(TOML_TEXT)
360376

361377
@patch.multiple(jsonfmt, stdout=FakeStdOut(), stderr=FakeStdErr())
362-
def test_copy_to_clipboard(self):
378+
def test_main_copy_to_clipboard(self):
363379
if jsonfmt.is_clipboard_available():
364380
with patch("sys.argv",
365-
['jsonfmt', '-Ccs', f'{BASE_DIR}/test/example.json']):
381+
['jsonfmt', '-Ccs', JSON_FILE]):
366382
jsonfmt.main()
367383
copied_text = pyperclip.paste().strip()
368384
self.assertEqual(copied_text, JSON_TEXT.strip())
369385

370386
with patch("sys.argv",
371-
['jsonfmt', '-Cs', f'{BASE_DIR}/test/example.toml']):
387+
['jsonfmt', '-Cs', TOML_FILE]):
372388
jsonfmt.main()
373389
copied_text = pyperclip.paste().strip()
374390
self.assertEqual(copied_text, TOML_TEXT.strip())
375391

376392
with patch("sys.argv",
377-
['jsonfmt', '-Cs', f'{BASE_DIR}/test/example.yaml']):
393+
['jsonfmt', '-Cs', YAML_FILE]):
378394
jsonfmt.main()
379395
copied_text = pyperclip.paste().strip()
380396
self.assertEqual(copied_text, YAML_TEXT.strip())
381397

382398
@patch.multiple(jsonfmt, is_clipboard_available=lambda: False)
383399
@patch.multiple(jsonfmt, stdout=FakeStdOut(), stderr=FakeStdErr())
384-
@patch.multiple(sys, argv=['jsonfmt', f'{BASE_DIR}/test/example.json', '-cC'])
385-
def test_clipboard_unavailable(self):
400+
@patch.multiple(sys, argv=['jsonfmt', JSON_FILE, '-cC'])
401+
def test_main_clipboard_unavailable(self):
386402
errmsg = '\033[1;91mjsonfmt:\033[0m \033[0;91mclipboard unavailable\033[0m\n'
387403
jsonfmt.main()
388404
self.assertEqual(jsonfmt.stderr.read(), errmsg)
389405
self.assertEqual(jsonfmt.stdout.read(), color(JSON_TEXT, 'json'))
390406

391-
@patch.multiple(sys, argv=['jsonfmt', '-O', f'{BASE_DIR}/test/example.json',
392-
'--set', 'age=32; box=[1,2,3]',
393-
'--pop', 'money; actions.1'])
394-
def test_modify_and_pop(self):
407+
@patch.multiple(sys, argv=['jsonfmt', '--set', 'age=32; box=[1,2,3]', '--pop', 'money; actions.1'])
408+
@patch.multiple(jsonfmt, stdin=FakeStdIn(JSON_TEXT), stdout=FakeStdOut(tty=False))
409+
def test_main_modify_and_pop(self):
395410
try:
396411
jsonfmt.main()
397-
with open(f'{BASE_DIR}/test/example.json') as fp:
398-
py_obj = json.load(fp)
412+
py_obj = json.loads(jsonfmt.stdout.read())
399413
self.assertEqual(py_obj['age'], 32)
400414
self.assertEqual(py_obj['box'], [1, 2, 3])
401415
self.assertNotIn('money', py_obj)
402416
self.assertEqual(len(py_obj['actions']), 1)
403417
finally:
404-
with open(f'{BASE_DIR}/test/example.json', 'w') as fp:
418+
with open(JSON_FILE, 'w') as fp:
405419
fp.write(JSON_TEXT)
406420

407421

0 commit comments

Comments
 (0)