From b921d5ec9583c8f8e992613ae2eaf62165722054 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 30 Apr 2021 16:53:52 -0700 Subject: [PATCH 01/60] initial support for error replies in bsd/osx --- jc/parsers/ping.py | 111 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/jc/parsers/ping.py b/jc/parsers/ping.py index 60e5df27..bcc3a015 100644 --- a/jc/parsers/ping.py +++ b/jc/parsers/ping.py @@ -35,14 +35,25 @@ Schema: "round_trip_ms_stddev": float, "responses": [ { - "type": string, # ('reply' or 'timeout') + "type": string, # 'reply', 'timeout', etc. See type_map for all options "timestamp": float, "bytes": integer, "response_ip": string, "icmp_seq": integer, "ttl": integer, "time_ms": float, - "duplicate": boolean + "duplicate": boolean, + "vr": integer, # hex value converted to decimal + "hl": integer, # hex value converted to decimal + "tos": integer, # hex value converted to decimal + "len": integer, # hex value converted to decimal + "id": integer, # hex value converted to decimal + "flg": integer, # hex value converted to decimal + "off": integer, # hex value converted to decimal + "pro": integer, # hex value converted to decimal + "cks": ingeger, # hex value converted to decimal + "src": string, + "dst": string } ] } @@ -140,6 +151,7 @@ Examples: } """ import string +import ipaddress import jc.utils @@ -170,7 +182,8 @@ def _process(proc_data): Dictionary. Structured data to conform to the schema. """ - int_list = ['data_bytes', 'packets_transmitted', 'packets_received', 'bytes', 'icmp_seq', 'ttl', 'duplicates'] + int_list = ['data_bytes', 'packets_transmitted', 'packets_received', 'bytes', 'icmp_seq', 'ttl', + 'duplicates', 'vr', 'hl', 'tos', 'len', 'id', 'flg', 'off', 'pro', 'cks'] float_list = ['packet_loss_percent', 'round_trip_ms_min', 'round_trip_ms_avg', 'round_trip_ms_max', 'round_trip_ms_stddev', 'timestamp', 'time_ms'] @@ -192,6 +205,54 @@ def _process(proc_data): return proc_data +def _ipv6_in(line): + line_list = line.replace('(', ' ').replace(')', ' ').replace(',', ' ').replace('%', ' ').split() + ipv6 = False + for item in line_list: + try: + _ = ipaddress.IPv6Address(item) + ipv6 = True + except Exception: + pass + return ipv6 + + +def _error_type(line): + # from https://github.com/dgibson/iputils/blob/master/ping.c + # https://android.googlesource.com/platform/external/ping/+/8fc3c91cf9e7f87bc20b9e6d3ea2982d87b70d9a/ping.c + # https://opensource.apple.com/source/network_cmds/network_cmds-328/ping.tproj/ping.c + type_map = { + 'Destination Net Unreachable': 'destination_net_unreachable', + 'Destination Host Unreachable': 'destination_host_unreachable', + 'Destination Protocol Unreachable': 'destination_protocol_unreachable', + 'Destination Port Unreachable': 'destination_port_unreachable', + 'Frag needed and DF set': 'frag_needed_and_df_set', + 'Source Route Failed': 'source_route_failed', + 'Destination Net Unknown': 'destination_net_unknown', + 'Destination Host Unknown': 'destination_host_unknown', + 'Source Host Isolated': 'source_host_isolated', + 'Destination Net Prohibited': 'destination_net_prohibited', + 'Destination Host Prohibited': 'destination_host_prohibited', + 'Destination Net Unreachable for Type of Service': 'destination_net_unreachable_for_type_of_service', + 'Destination Host Unreachable for Type of Service': 'destination_host_unreachable_for_type_of_service', + 'Packet filtered': 'packet_filtered', + 'Precedence Violation': 'precedence_violation', + 'Precedence Cutoff': 'precedence_cutoff', + 'Dest Unreachable, Bad Code': 'dest_unreachable_bad_code', + 'Redirect Network': 'redirect_network', + 'Redirect Host': 'redirect_host', + 'Redirect Type of Service and Network': 'redirect_type_of_service_and_network', + 'Redirect, Bad Code': 'redirect_bad_code', + 'Time to live exceeded': 'time_to_live_exceeded', + 'Frag reassembly time exceeded': 'frag_reassembly_time_exceeded', + 'Time exceeded, Bad Code': 'time_exceeded_bad_code' + } + + for err_type, code in type_map.items(): + if err_type in line: + return code + + def _linux_parse(data): raw_output = {} ping_responses = [] @@ -346,6 +407,7 @@ def _bsd_parse(data): ping_responses = [] pattern = None footer = False + ping_error = False linedata = data.splitlines() @@ -419,7 +481,7 @@ def _bsd_parse(data): # ping response lines else: # ipv4 lines - if ',' not in line: + if not _ipv6_in(line): # request timeout if line.startswith('Request timeout for '): @@ -430,6 +492,42 @@ def _bsd_parse(data): ping_responses.append(response) continue + # catch error responses + err = _error_type(line) + if err: + response = { + 'type': err, + 'bytes': line.split()[0], + 'response_ip': line.split()[4].strip(':').strip('(').strip(')'), + } + ping_error = True + continue + + if ping_error: + if line.startswith('Vr'): + continue + else: + error_line = line.split() + response.update( + { + 'vr': int(error_line[0], 16), + 'hl': int(error_line[1], 16), + 'tos': int(error_line[2], 16), + 'len': int(error_line[3], 16), + 'id': int(error_line[4], 16), + 'flg': int(error_line[5], 16), + 'off': int(error_line[6], 16), + 'ttl': int(error_line[7], 16), + 'pro': int(error_line[8], 16), + 'cks': int(error_line[9], 16), + 'src': error_line[10], + 'dst': error_line[11], + } + ) + ping_responses.append(response) + error_line = False + continue + # normal response else: line = line.replace(':', ' ').replace('=', ' ') @@ -462,8 +560,9 @@ def _bsd_parse(data): if ping_responses: seq_list = [] for reply in ping_responses: - seq_list.append(reply['icmp_seq']) - reply['duplicate'] = True if seq_list.count(reply['icmp_seq']) > 1 else False + if 'icmp_seq' in reply: + seq_list.append(reply['icmp_seq']) + reply['duplicate'] = True if seq_list.count(reply['icmp_seq']) > 1 else False raw_output['responses'] = ping_responses From 8ce155d843806be4ad23491b4401e4eda02e2a1c Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 3 May 2021 15:16:33 -0700 Subject: [PATCH 02/60] add support for error replies in v4 ping on osx and bsd --- CHANGELOG | 3 + jc/parsers/ping.py | 56 +++++++++++-------- .../osx-10.14.6/ping-ip-unreachable.json | 1 + .../osx-10.14.6/ping-ip-unreachable.out | 35 ++++++++++++ tests/test_ping.py | 14 ++++- 5 files changed, 86 insertions(+), 23 deletions(-) create mode 100644 tests/fixtures/osx-10.14.6/ping-ip-unreachable.json create mode 100644 tests/fixtures/osx-10.14.6/ping-ip-unreachable.out diff --git a/CHANGELOG b/CHANGELOG index f25f1bb0..3603b017 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ jc changelog +20210510 v1.15.4 +- Update ping parser to support error responses in OSX and BSD + 20210426 v1.15.3 - Add ufw status command parser tested on linux - Add ufw-appinfo command parser tested on linux diff --git a/jc/parsers/ping.py b/jc/parsers/ping.py index bcc3a015..f216616c 100644 --- a/jc/parsers/ping.py +++ b/jc/parsers/ping.py @@ -157,7 +157,7 @@ import jc.utils class info(): """Provides parser metadata (version, author, etc.)""" - version = '1.4' + version = '1.5' description = '`ping` and `ping6` command parser' author = 'Kelly Brazil' author_email = 'kellyjonbrazil@gmail.com' @@ -496,10 +496,15 @@ def _bsd_parse(data): err = _error_type(line) if err: response = { - 'type': err, - 'bytes': line.split()[0], - 'response_ip': line.split()[4].strip(':').strip('(').strip(')'), + 'type': err } + + try: + response['bytes'] = line.split()[0] + response['response_ip'] = line.split()[4].strip(':').strip('(').strip(')') + except Exception: + pass + ping_error = True continue @@ -508,24 +513,31 @@ def _bsd_parse(data): continue else: error_line = line.split() - response.update( - { - 'vr': int(error_line[0], 16), - 'hl': int(error_line[1], 16), - 'tos': int(error_line[2], 16), - 'len': int(error_line[3], 16), - 'id': int(error_line[4], 16), - 'flg': int(error_line[5], 16), - 'off': int(error_line[6], 16), - 'ttl': int(error_line[7], 16), - 'pro': int(error_line[8], 16), - 'cks': int(error_line[9], 16), - 'src': error_line[10], - 'dst': error_line[11], - } - ) - ping_responses.append(response) - error_line = False + + try: + response.update( + { + 'vr': int(error_line[0], 16), # convert from hex to decimal + 'hl': int(error_line[1], 16), + 'tos': int(error_line[2], 16), + 'len': int(error_line[3], 16), + 'id': int(error_line[4], 16), + 'flg': int(error_line[5], 16), + 'off': int(error_line[6], 16), + 'ttl': int(error_line[7], 16), + 'pro': int(error_line[8], 16), + 'cks': int(error_line[9], 16), + 'src': error_line[10], + 'dst': error_line[11], + } + ) + except Exception: + pass + + if response: + ping_responses.append(response) + + ping_error = False continue # normal response diff --git a/tests/fixtures/osx-10.14.6/ping-ip-unreachable.json b/tests/fixtures/osx-10.14.6/ping-ip-unreachable.json new file mode 100644 index 00000000..ad481bb3 --- /dev/null +++ b/tests/fixtures/osx-10.14.6/ping-ip-unreachable.json @@ -0,0 +1 @@ +{"destination_ip":"192.168.1.220","data_bytes":56,"pattern":null,"destination":"192.168.1.220","packets_transmitted":8,"packets_received":0,"packet_loss_percent":100.0,"duplicates":0,"responses":[{"type":"timeout","icmp_seq":0,"duplicate":false},{"type":"timeout","icmp_seq":1,"duplicate":false},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":22139,"flg":0,"off":0,"ttl":63,"pro":1,"cks":40996,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":11887,"flg":0,"off":0,"ttl":63,"pro":1,"cks":51248,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":41453,"flg":0,"off":0,"ttl":63,"pro":1,"cks":21682,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"timeout","icmp_seq":2,"duplicate":false},{"type":"timeout","icmp_seq":3,"duplicate":false},{"type":"timeout","icmp_seq":4,"duplicate":false},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":40674,"flg":0,"off":0,"ttl":63,"pro":1,"cks":22461,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":31035,"flg":0,"off":0,"ttl":63,"pro":1,"cks":32100,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":53536,"flg":0,"off":0,"ttl":63,"pro":1,"cks":9599,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"timeout","icmp_seq":5,"duplicate":false},{"type":"timeout","icmp_seq":6,"duplicate":false}]} diff --git a/tests/fixtures/osx-10.14.6/ping-ip-unreachable.out b/tests/fixtures/osx-10.14.6/ping-ip-unreachable.out new file mode 100644 index 00000000..e62f9ade --- /dev/null +++ b/tests/fixtures/osx-10.14.6/ping-ip-unreachable.out @@ -0,0 +1,35 @@ +PING 192.168.1.220 (192.168.1.220): 56 data bytes +Request timeout for icmp_seq 0 +Request timeout for icmp_seq 1 +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 567b 0 0000 3f 01 a024 192.168.1.221 192.168.1.220 + +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 2e6f 0 0000 3f 01 c830 192.168.1.221 192.168.1.220 + +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 a1ed 0 0000 3f 01 54b2 192.168.1.221 192.168.1.220 + +Request timeout for icmp_seq 2 +Request timeout for icmp_seq 3 +Request timeout for icmp_seq 4 +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 9ee2 0 0000 3f 01 57bd 192.168.1.221 192.168.1.220 + +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 793b 0 0000 3f 01 7d64 192.168.1.221 192.168.1.220 + +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 d120 0 0000 3f 01 257f 192.168.1.221 192.168.1.220 + +Request timeout for icmp_seq 5 +Request timeout for icmp_seq 6 + +--- 192.168.1.220 ping statistics --- +8 packets transmitted, 0 packets received, 100.0% packet loss diff --git a/tests/test_ping.py b/tests/test_ping.py index fa0508af..be903337 100644 --- a/tests/test_ping.py +++ b/tests/test_ping.py @@ -157,6 +157,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip.out'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping_ip = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unreachable.out'), 'r', encoding='utf-8') as f: + self.osx_10_14_6_ping_ip_unreachable = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-hostname-p.out'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping6_hostname_p = f.read() @@ -336,6 +339,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip.json'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping_ip_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unreachable.json'), 'r', encoding='utf-8') as f: + self.osx_10_14_6_ping_ip_unreachable_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-hostname-p.json'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping6_hostname_p_json = json.loads(f.read()) @@ -651,10 +657,16 @@ class MyTests(unittest.TestCase): def test_ping_ip_osx_10_14_6(self): """ - Test 'ping6 ' on osx 10.14.6 + Test 'ping ' on osx 10.14.6 """ self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping_ip, quiet=True), self.osx_10_14_6_ping_ip_json) + def test_ping_ip_unreachable_osx_10_14_6(self): + """ + Test 'ping ' with host unreachable error on osx 10.14.6 + """ + self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping_ip_unreachable, quiet=True), self.osx_10_14_6_ping_ip_unreachable_json) + def test_ping6_hostname_p_osx_10_14_6(self): """ Test 'ping6 -p' on osx 10.14.6 From 33de5f01e638953c83bb0960dc3550ca4547849b Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 3 May 2021 15:16:47 -0700 Subject: [PATCH 03/60] version bump --- jc/__init__.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/jc/__init__.py b/jc/__init__.py index 42fad9c8..0c8ac89a 100644 --- a/jc/__init__.py +++ b/jc/__init__.py @@ -70,7 +70,7 @@ Module Example: ... ... ;; ANSWER SECTION: ... example.com. 29658 IN A 93.184.216.34 - ... + ... ... ;; Query time: 52 msec ... ;; SERVER: 2600:1700:bab0:d40::1#53(2600:1700:bab0:d40::1) ... ;; WHEN: Fri Apr 16 16:13:00 PDT 2021 @@ -86,4 +86,4 @@ Module Example: """ name = 'jc' -__version__ = '1.15.3' +__version__ = '1.15.4' diff --git a/setup.py b/setup.py index 4deef0b3..6dd19f30 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open('README.md', 'r') as f: setuptools.setup( name='jc', - version='1.15.3', + version='1.15.4', author='Kelly Brazil', author_email='kellyjonbrazil@gmail.com', description='Converts the output of popular command-line tools and file-types to JSON.', From 5abe095beb4b668e5f55fe8c9588677547e4c2a7 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 3 May 2021 15:26:16 -0700 Subject: [PATCH 04/60] update ping docs --- docs/parsers/ping.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/parsers/ping.md b/docs/parsers/ping.md index fe0c0bc6..d2325648 100644 --- a/docs/parsers/ping.md +++ b/docs/parsers/ping.md @@ -38,14 +38,25 @@ Schema: "round_trip_ms_stddev": float, "responses": [ { - "type": string, # ('reply' or 'timeout') + "type": string, # 'reply', 'timeout', etc. See type_map for all options "timestamp": float, "bytes": integer, "response_ip": string, "icmp_seq": integer, "ttl": integer, "time_ms": float, - "duplicate": boolean + "duplicate": boolean, + "vr": integer, # hex value converted to decimal + "hl": integer, # hex value converted to decimal + "tos": integer, # hex value converted to decimal + "len": integer, # hex value converted to decimal + "id": integer, # hex value converted to decimal + "flg": integer, # hex value converted to decimal + "off": integer, # hex value converted to decimal + "pro": integer, # hex value converted to decimal + "cks": ingeger, # hex value converted to decimal + "src": string, + "dst": string } ] } @@ -169,4 +180,4 @@ Returns: ## Parser Information Compatibility: linux, darwin, freebsd -Version 1.4 by Kelly Brazil (kellyjonbrazil@gmail.com) +Version 1.5 by Kelly Brazil (kellyjonbrazil@gmail.com) From ff034e401d5c33efbc3c4bc2f0e386d81eaae7fc Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 4 May 2021 15:34:45 -0700 Subject: [PATCH 05/60] use try/except to make parser more resilient against unknown error types --- jc/parsers/ping.py | 98 +++++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/jc/parsers/ping.py b/jc/parsers/ping.py index f216616c..06401c1d 100644 --- a/jc/parsers/ping.py +++ b/jc/parsers/ping.py @@ -35,7 +35,7 @@ Schema: "round_trip_ms_stddev": float, "responses": [ { - "type": string, # 'reply', 'timeout', etc. See type_map for all options + "type": string, # 'reply', 'timeout', 'unparsable_line', etc. See `_error_type.type_map` for all options "timestamp": float, "bytes": integer, "response_ip": string, @@ -364,35 +364,39 @@ def _linux_parse(data): # normal responses else: + try: + line = line.replace('(', ' ').replace(')', ' ').replace('=', ' ') - line = line.replace('(', ' ').replace(')', ' ').replace('=', ' ') + # positions of items depend on whether ipv4/ipv6 and/or ip/hostname is used + if ipv4 and not hostname: + bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9) + elif ipv4 and hostname: + bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11) + elif not ipv4 and not hostname: + bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9) + elif not ipv4 and hostname: + bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11) - # positions of items depend on whether ipv4/ipv6 and/or ip/hostname is used - if ipv4 and not hostname: - bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9) - elif ipv4 and hostname: - bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11) - elif not ipv4 and not hostname: - bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9) - elif not ipv4 and hostname: - bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11) + # if timestamp option is specified, then shift everything right by one + timestamp = False + if line[0] == '[': + timestamp = True + bts, rip, iseq, t2l, tms = (bts + 1, rip + 1, iseq + 1, t2l + 1, tms + 1) - # if timestamp option is specified, then shift everything right by one - timestamp = False - if line[0] == '[': - timestamp = True - bts, rip, iseq, t2l, tms = (bts + 1, rip + 1, iseq + 1, t2l + 1, tms + 1) - - response = { - 'type': 'reply', - 'timestamp': line.split()[0].lstrip('[').rstrip(']') if timestamp else None, - 'bytes': line.split()[bts], - 'response_ip': line.split()[rip].rstrip(':'), - 'icmp_seq': line.split()[iseq], - 'ttl': line.split()[t2l], - 'time_ms': line.split()[tms], - 'duplicate': True if 'DUP!' in line else False - } + response = { + 'type': 'reply', + 'timestamp': line.split()[0].lstrip('[').rstrip(']') if timestamp else None, + 'bytes': line.split()[bts], + 'response_ip': line.split()[rip].rstrip(':'), + 'icmp_seq': line.split()[iseq], + 'ttl': line.split()[t2l], + 'time_ms': line.split()[tms], + 'duplicate': True if 'DUP!' in line else False + } + except Exception: + response = { + 'type': 'unparsable_line' + } ping_responses.append(response) continue @@ -542,7 +546,28 @@ def _bsd_parse(data): # normal response else: - line = line.replace(':', ' ').replace('=', ' ') + try: + line = line.replace(':', ' ').replace('=', ' ') + response = { + 'type': 'reply', + 'bytes': line.split()[0], + 'response_ip': line.split()[3], + 'icmp_seq': line.split()[5], + 'ttl': line.split()[7], + 'time_ms': line.split()[9] + } + except Exception: + response = { + 'type': 'unparsable_line' + } + + ping_responses.append(response) + continue + + # ipv6 lines + else: + try: + line = line.replace(',', ' ').replace('=', ' ') response = { 'type': 'reply', 'bytes': line.split()[0], @@ -551,20 +576,11 @@ def _bsd_parse(data): 'ttl': line.split()[7], 'time_ms': line.split()[9] } - ping_responses.append(response) - continue + except Exception: + response = { + 'type': 'unparsable_line' + } - # ipv6 lines - else: - line = line.replace(',', ' ').replace('=', ' ') - response = { - 'type': 'reply', - 'bytes': line.split()[0], - 'response_ip': line.split()[3], - 'icmp_seq': line.split()[5], - 'ttl': line.split()[7], - 'time_ms': line.split()[9] - } ping_responses.append(response) continue From c2af7d113ed8e8cf9af985c969cf29858a3c26a9 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 4 May 2021 15:35:47 -0700 Subject: [PATCH 06/60] add ping updates --- CHANGELOG | 1 + docs/parsers/ping.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 3603b017..6b4b9ac2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ jc changelog 20210510 v1.15.4 - Update ping parser to support error responses in OSX and BSD +- Update ping parser to be more resillient against parsing errors for unknown error types 20210426 v1.15.3 - Add ufw status command parser tested on linux diff --git a/docs/parsers/ping.md b/docs/parsers/ping.md index d2325648..570c8c7d 100644 --- a/docs/parsers/ping.md +++ b/docs/parsers/ping.md @@ -38,7 +38,7 @@ Schema: "round_trip_ms_stddev": float, "responses": [ { - "type": string, # 'reply', 'timeout', etc. See type_map for all options + "type": string, # 'reply', 'timeout', 'unparsable_line', etc. See `_error_type.type_map` for all options "timestamp": float, "bytes": integer, "response_ip": string, From a1fe7037e5267ec4aced719eff3715085eb35bca Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 4 May 2021 19:04:16 -0700 Subject: [PATCH 07/60] add unparsed_line field if line cannot be parsed --- jc/parsers/ping.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/jc/parsers/ping.py b/jc/parsers/ping.py index 06401c1d..c07a2b82 100644 --- a/jc/parsers/ping.py +++ b/jc/parsers/ping.py @@ -36,6 +36,7 @@ Schema: "responses": [ { "type": string, # 'reply', 'timeout', 'unparsable_line', etc. See `_error_type.type_map` for all options + "unparsed_line": string, # only if an 'unparsable_line' type "timestamp": float, "bytes": integer, "response_ip": string, @@ -395,7 +396,8 @@ def _linux_parse(data): } except Exception: response = { - 'type': 'unparsable_line' + 'type': 'unparsable_line', + 'unparsed_line': line } ping_responses.append(response) @@ -558,7 +560,8 @@ def _bsd_parse(data): } except Exception: response = { - 'type': 'unparsable_line' + 'type': 'unparsable_line', + 'unparsed_line': line } ping_responses.append(response) @@ -578,7 +581,8 @@ def _bsd_parse(data): } except Exception: response = { - 'type': 'unparsable_line' + 'type': 'unparsable_line', + 'unparsed_line': line } ping_responses.append(response) From 5ca0fc364eb22d583bee5633cdd7183c1d70bb6c Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 4 May 2021 19:05:20 -0700 Subject: [PATCH 08/60] add unparsed_line to docs --- docs/parsers/ping.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/parsers/ping.md b/docs/parsers/ping.md index 570c8c7d..92224057 100644 --- a/docs/parsers/ping.md +++ b/docs/parsers/ping.md @@ -39,6 +39,7 @@ Schema: "responses": [ { "type": string, # 'reply', 'timeout', 'unparsable_line', etc. See `_error_type.type_map` for all options + "unparsed_line": string, # only if an 'unparsable_line' type "timestamp": float, "bytes": integer, "response_ip": string, From fb14f5439f00d5eaf08651c552189fd4be7358d4 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 5 May 2021 08:03:27 -0700 Subject: [PATCH 09/60] fix and tests for unknown or unparsable errors --- jc/parsers/ping.py | 7 ++-- .../osx-10.14.6/ping-ip-unknown-errors.json | 1 + .../osx-10.14.6/ping-ip-unknown-errors.out | 35 +++++++++++++++++++ tests/test_ping.py | 12 +++++++ 4 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.json create mode 100644 tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.out diff --git a/jc/parsers/ping.py b/jc/parsers/ping.py index c07a2b82..a1a5f035 100644 --- a/jc/parsers/ping.py +++ b/jc/parsers/ping.py @@ -364,7 +364,7 @@ def _linux_parse(data): continue # normal responses - else: + elif ' bytes from ' in line: try: line = line.replace('(', ' ').replace(')', ' ').replace('=', ' ') @@ -547,9 +547,10 @@ def _bsd_parse(data): continue # normal response - else: + elif ' bytes from ' in line: try: line = line.replace(':', ' ').replace('=', ' ') + response = { 'type': 'reply', 'bytes': line.split()[0], @@ -568,7 +569,7 @@ def _bsd_parse(data): continue # ipv6 lines - else: + elif ' bytes from ' in line: try: line = line.replace(',', ' ').replace('=', ' ') response = { diff --git a/tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.json b/tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.json new file mode 100644 index 00000000..02cc13ea --- /dev/null +++ b/tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.json @@ -0,0 +1 @@ +{"destination_ip":"192.168.1.220","data_bytes":56,"pattern":null,"destination":"192.168.1.220","packets_transmitted":8,"packets_received":0,"packet_loss_percent":100.0,"duplicates":0,"responses":[{"type":"timeout","icmp_seq":0,"duplicate":false},{"type":"timeout","icmp_seq":1,"duplicate":false},{"type":"unparsable_line","unparsed_line":"92 bytes from fgt1.attlocal.net (192.168.1.220) Destination Network Unreachable"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":11887,"flg":0,"off":0,"ttl":63,"pro":1,"cks":51248,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"unparsable_line","unparsed_line":"92 bytes from fgt1.attlocal.net (192.168.1.220) Weird error message"},{"type":"timeout","icmp_seq":2,"duplicate":false},{"type":"timeout","icmp_seq":3,"duplicate":false},{"type":"timeout","icmp_seq":4,"duplicate":false},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":40674,"flg":0,"off":0,"ttl":63,"pro":1,"cks":22461,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":31035,"flg":0,"off":0,"ttl":63,"pro":1,"cks":32100,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"destination_host_unreachable","bytes":92,"response_ip":"192.168.1.220","vr":4,"hl":5,"tos":0,"len":21504,"id":53536,"flg":0,"off":0,"ttl":63,"pro":1,"cks":9599,"src":"192.168.1.221","dst":"192.168.1.220"},{"type":"timeout","icmp_seq":5,"duplicate":false},{"type":"timeout","icmp_seq":6,"duplicate":false}]} diff --git a/tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.out b/tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.out new file mode 100644 index 00000000..1d6b41d7 --- /dev/null +++ b/tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.out @@ -0,0 +1,35 @@ +PING 192.168.1.220 (192.168.1.220): 56 data bytes +Request timeout for icmp_seq 0 +Request timeout for icmp_seq 1 +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Network Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 567b 0 0000 3f 01 a024 192.168.1.221 192.168.1.220 + +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 2e6f 0 0000 3f 01 c830 192.168.1.221 192.168.1.220 + +92 bytes from fgt1.attlocal.net (192.168.1.220): Weird error message +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 a1ed 0 0000 3f 01 54b2 192.168.1.221 192.168.1.220 + +Request timeout for icmp_seq 2 +Request timeout for icmp_seq 3 +Request timeout for icmp_seq 4 +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 9ee2 0 0000 3f 01 57bd 192.168.1.221 192.168.1.220 + +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 793b 0 0000 3f 01 7d64 192.168.1.221 192.168.1.220 + +92 bytes from fgt1.attlocal.net (192.168.1.220): Destination Host Unreachable +Vr HL TOS Len ID Flg off TTL Pro cks Src Dst + 4 5 00 5400 d120 0 0000 3f 01 257f 192.168.1.221 192.168.1.220 + +Request timeout for icmp_seq 5 +Request timeout for icmp_seq 6 + +--- 192.168.1.220 ping statistics --- +8 packets transmitted, 0 packets received, 100.0% packet loss diff --git a/tests/test_ping.py b/tests/test_ping.py index be903337..f04dadb4 100644 --- a/tests/test_ping.py +++ b/tests/test_ping.py @@ -160,6 +160,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unreachable.out'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping_ip_unreachable = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.out'), 'r', encoding='utf-8') as f: + self.osx_10_14_6_ping_ip_unknown_errors = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-hostname-p.out'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping6_hostname_p = f.read() @@ -342,6 +345,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unreachable.json'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping_ip_unreachable_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-unknown-errors.json'), 'r', encoding='utf-8') as f: + self.osx_10_14_6_ping_ip_unknown_errors_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-hostname-p.json'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping6_hostname_p_json = json.loads(f.read()) @@ -667,6 +673,12 @@ class MyTests(unittest.TestCase): """ self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping_ip_unreachable, quiet=True), self.osx_10_14_6_ping_ip_unreachable_json) + def test_ping_ip_unknown_errors_osx_10_14_6(self): + """ + Test 'ping ' with unknown/unparsable errors on osx 10.14.6 + """ + self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping_ip_unknown_errors, quiet=True), self.osx_10_14_6_ping_ip_unknown_errors_json) + def test_ping6_hostname_p_osx_10_14_6(self): """ Test 'ping6 -p' on osx 10.14.6 From fe1f1013a74bdf6b4284f11dd59db8ea17e5e5d1 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 5 May 2021 09:16:05 -0700 Subject: [PATCH 10/60] add use cases section --- templates/readme_template | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/templates/readme_template b/templates/readme_template index 1159b930..733c4c7d 100644 --- a/templates/readme_template +++ b/templates/readme_template @@ -64,12 +64,10 @@ Two representations of the data are possible. The default representation uses a To access the raw, pre-processed JSON, use the `-r` cli option or the `raw=True` function parameter in `parse()`. -Schemas for each parser can be found at the documentation link beside each parser below. +Schemas for each parser can be found at the documentation link beside each [**Parser**](#parsers) below. Release notes can be found [here](https://blog.kellybrazil.com/category/jc-news/). -For Bash use case examples, see my blog post on [Practical JSON at the Command Line](https://blog.kellybrazil.com/2021/04/12/practical-json-at-the-command-line/). - ## Why Would Anyone Do This!? For more information on the motivations for this project, please see my [blog post](https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/). @@ -78,6 +76,12 @@ See also: - [powershell](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertto-json?view=powershell-7) - [blog: linux apps should have a json flag](https://thomashunter.name/posts/2012-06-06-linux-cli-apps-should-have-a-json-flag) +### Use Cases +- [Bash Scripting](https://blog.kellybrazil.com/2021/04/12/practical-json-at-the-command-line/) +- [Ansible command output parsing](https://blog.kellybrazil.com/2020/08/30/parsing-command-output-in-ansible-with-jc/) +- [Saltstack command output parsing](https://blog.kellybrazil.com/2020/09/15/parsing-command-output-in-saltstack-with-jc/) +- [Nornir command output parsing](https://blog.kellybrazil.com/2020/12/09/parsing-command-output-in-nornir-with-jc/) + ## Installation There are several ways to get `jc`. You can install via `pip`; other OS package repositories like `apt-get`, `dnf`, `zypper`, `pacman`, `nix-env`, `guix`, `brew`, or `portsnap`; via DEB/RPM packaged binaries; or by downloading the correct binary for your architecture and running it anywhere on your filesystem. From 3c51b2d83d301b4072bbb12fdba4f60b900573da Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 7 May 2021 13:50:28 -0700 Subject: [PATCH 11/60] add tests for unparsable lines on linux --- .../centos-7.7/ping-ip-O-unparsedlines.json | 1 + .../centos-7.7/ping-ip-O-unparsedlines.out | 27 +++++++++++++++++++ tests/test_ping.py | 12 +++++++++ 3 files changed, 40 insertions(+) create mode 100644 tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.json create mode 100644 tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.out diff --git a/tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.json b/tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.json new file mode 100644 index 00000000..0b594569 --- /dev/null +++ b/tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.json @@ -0,0 +1 @@ +{"destination_ip":"127.0.0.1","data_bytes":56,"pattern":null,"destination":"127.0.0.1","packets_transmitted":20,"packets_received":20,"packet_loss_percent":0.0,"duplicates":0,"time_ms":19070.0,"round_trip_ms_min":0.038,"round_trip_ms_avg":0.047,"round_trip_ms_max":0.08,"round_trip_ms_stddev":0.011,"responses":[{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":1,"ttl":64,"time_ms":0.038,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":2,"ttl":64,"time_ms":0.043,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":3,"ttl":64,"time_ms":0.044,"duplicate":false},{"type":"unparsable_line","unparsed_line":"64 bytes from 127.0.0.1: error - weird error"},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":5,"ttl":64,"time_ms":0.08,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":6,"ttl":64,"time_ms":0.043,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":7,"ttl":64,"time_ms":0.047,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":8,"ttl":64,"time_ms":0.04,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":9,"ttl":64,"time_ms":0.052,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":10,"ttl":64,"time_ms":0.044,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":11,"ttl":64,"time_ms":0.043,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":12,"ttl":64,"time_ms":0.043,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":13,"ttl":64,"time_ms":0.05,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":14,"ttl":64,"time_ms":0.045,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":15,"ttl":64,"time_ms":0.062,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":16,"ttl":64,"time_ms":0.046,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":17,"ttl":64,"time_ms":0.046,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":18,"ttl":64,"time_ms":0.045,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":19,"ttl":64,"time_ms":0.044,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"127.0.0.1","icmp_seq":20,"ttl":64,"time_ms":0.044,"duplicate":false}]} diff --git a/tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.out b/tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.out new file mode 100644 index 00000000..a5a61f38 --- /dev/null +++ b/tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.out @@ -0,0 +1,27 @@ +PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. +64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.038 ms +64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.043 ms: some weird error +64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.044 ms +64 bytes from 127.0.0.1: error - weird error +64 bytes from 127.0.0.1: icmp_seq=5 ttl=64 time=0.080 ms +64 bytes from 127.0.0.1: icmp_seq=6 ttl=64 time=0.043 ms +this is a weird error message +64 bytes from 127.0.0.1: icmp_seq=7 ttl=64 time=0.047 ms +64 bytes from 127.0.0.1: icmp_seq=8 ttl=64 time=0.040 ms +64 bytes from 127.0.0.1: icmp_seq=9 ttl=64 time=0.052 ms +64 bytes from 127.0.0.1: icmp_seq=10 ttl=64 time=0.044 ms +64 bytes from 127.0.0.1: icmp_seq=11 ttl=64 time=0.043 ms +unparsable line +64 bytes from 127.0.0.1: icmp_seq=12 ttl=64 time=0.043 ms +64 bytes from 127.0.0.1: icmp_seq=13 ttl=64 time=0.050 ms +64 bytes from 127.0.0.1: icmp_seq=14 ttl=64 time=0.045 ms +64 bytes from 127.0.0.1: icmp_seq=15 ttl=64 time=0.062 ms +64 bytes from 127.0.0.1: icmp_seq=16 ttl=64 time=0.046 ms +64 bytes from 127.0.0.1: icmp_seq=17 ttl=64 time=0.046 ms +64 bytes from 127.0.0.1: icmp_seq=18 ttl=64 time=0.045 ms +64 bytes from 127.0.0.1: icmp_seq=19 ttl=64 time=0.044 ms +64 bytes from 127.0.0.1: icmp_seq=20 ttl=64 time=0.044 ms + +--- 127.0.0.1 ping statistics --- +20 packets transmitted, 20 received, 0% packet loss, time 19070ms +rtt min/avg/max/mdev = 0.038/0.047/0.080/0.011 ms diff --git a/tests/test_ping.py b/tests/test_ping.py index f04dadb4..8897df65 100644 --- a/tests/test_ping.py +++ b/tests/test_ping.py @@ -45,6 +45,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-dup.out'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_dup = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.out'), 'r', encoding='utf-8') as f: + self.centos_7_7_ping_ip_O_unparsedlines = f.read() + # ubuntu with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ping-ip-O.out'), 'r', encoding='utf-8') as f: self.ubuntu_18_4_ping_ip_O = f.read() @@ -230,6 +233,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-dup.json'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_dup_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping-ip-O-unparsedlines.json'), 'r', encoding='utf-8') as f: + self.centos_7_7_ping_ip_O_unparsedlines_json = json.loads(f.read()) + # ubunutu with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/ping-ip-O.json'), 'r', encoding='utf-8') as f: self.ubuntu_18_4_ping_ip_O_json = json.loads(f.read()) @@ -451,6 +457,12 @@ class MyTests(unittest.TestCase): """ self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping6_ip_dup, quiet=True), self.centos_7_7_ping6_ip_dup_json) + def test_ping_ip_O_unparsedlines_centos_7_7(self): + """ + Test 'ping -O' on Centos 7.7 with unparsable lines and error messages + """ + self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping_ip_O_unparsedlines, quiet=True), self.centos_7_7_ping_ip_O_unparsedlines_json) + def test_ping_ip_O_ubuntu_18_4(self): """ Test 'ping -O' on Ubuntu 18.4 From 61851c1bd02aa82d5d82e68595e45630ead13d34 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 7 May 2021 16:42:09 -0700 Subject: [PATCH 12/60] add support for +noall +answer --- docs/parsers/dig.md | 40 ++++++++++++++++++++++++++++++++++++- jc/parsers/dig.py | 48 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/docs/parsers/dig.md b/docs/parsers/dig.md index 4b0dc801..6a7b6952 100644 --- a/docs/parsers/dig.md +++ b/docs/parsers/dig.md @@ -3,6 +3,8 @@ # jc.parsers.dig jc - JSON CLI output utility `dig` command output parser +The `+noall +answer` options are supported in cases where only the answer information is desired. + The `when_epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on) The `when_epoch_utc` calculated timestamp field is timezone-aware and is only available if the timezone field is UTC. @@ -274,6 +276,42 @@ Examples: } ] + $ dig +noall +answer cnn.com | jc --dig -p + [ + { + "answer": [ + { + "name": "cnn.com.", + "class": "IN", + "type": "A", + "ttl": 60, + "data": "151.101.193.67" + }, + { + "name": "cnn.com.", + "class": "IN", + "type": "A", + "ttl": 60, + "data": "151.101.65.67" + }, + { + "name": "cnn.com.", + "class": "IN", + "type": "A", + "ttl": 60, + "data": "151.101.1.67" + }, + { + "name": "cnn.com.", + "class": "IN", + "type": "A", + "ttl": 60, + "data": "151.101.129.67" + } + ] + } + ] + ## info ```python @@ -301,4 +339,4 @@ Returns: ## Parser Information Compatibility: linux, aix, freebsd, darwin -Version 2.0 by Kelly Brazil (kellyjonbrazil@gmail.com) +Version 2.1 by Kelly Brazil (kellyjonbrazil@gmail.com) diff --git a/jc/parsers/dig.py b/jc/parsers/dig.py index 25dde57f..e7ed438c 100644 --- a/jc/parsers/dig.py +++ b/jc/parsers/dig.py @@ -1,5 +1,7 @@ """jc - JSON CLI output utility `dig` command output parser +The `+noall +answer` options are supported in cases where only the answer information is desired. + The `when_epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on) The `when_epoch_utc` calculated timestamp field is timezone-aware and is only available if the timezone field is UTC. @@ -270,13 +272,49 @@ Examples: "rcvd": "78" } ] + + $ dig +noall +answer cnn.com | jc --dig -p + [ + { + "answer": [ + { + "name": "cnn.com.", + "class": "IN", + "type": "A", + "ttl": 60, + "data": "151.101.193.67" + }, + { + "name": "cnn.com.", + "class": "IN", + "type": "A", + "ttl": 60, + "data": "151.101.65.67" + }, + { + "name": "cnn.com.", + "class": "IN", + "type": "A", + "ttl": 60, + "data": "151.101.1.67" + }, + { + "name": "cnn.com.", + "class": "IN", + "type": "A", + "ttl": 60, + "data": "151.101.129.67" + } + ] + } + ] """ import jc.utils class info(): """Provides parser metadata (version, author, etc.)""" - version = '2.0' + version = '2.1' description = '`dig` command parser' author = 'Kelly Brazil' author_email = 'kellyjonbrazil@gmail.com' @@ -500,6 +538,7 @@ def parse(data, raw=False, quiet=False): # section can be: header, flags, question, authority, answer, axfr, additional, opt_pseudosection, footer section = '' output_entry = {} + answer_list = [] if jc.utils.has_data(data): for line in cleandata: @@ -581,7 +620,12 @@ def parse(data, raw=False, quiet=False): output_entry.update({'authority': authority_list}) continue - if not line.startswith(';') and section == 'answer': + # https://github.com/kellyjonbrazil/jc/issues/133 + # to allow parsing of output that only has the answer section - e.g: + # dig +noall +answer example.com + # we allow section to be 'answer' (normal output) or + # '', which means +noall +answer was used. + if not line.startswith(';') and (section == 'answer' or section == ''): answer_list.append(_parse_answer(line)) output_entry.update({'answer': answer_list}) continue From 48e534fa03b92b2583c5c6269c76651f3aa420cb Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 7 May 2021 16:55:18 -0700 Subject: [PATCH 13/60] add +noall +answer test --- tests/fixtures/osx-10.14.6/dig-noall-answer.json | 1 + tests/fixtures/osx-10.14.6/dig-noall-answer.out | 4 ++++ tests/test_dig.py | 14 ++++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/osx-10.14.6/dig-noall-answer.json create mode 100644 tests/fixtures/osx-10.14.6/dig-noall-answer.out diff --git a/tests/fixtures/osx-10.14.6/dig-noall-answer.json b/tests/fixtures/osx-10.14.6/dig-noall-answer.json new file mode 100644 index 00000000..7e50ae63 --- /dev/null +++ b/tests/fixtures/osx-10.14.6/dig-noall-answer.json @@ -0,0 +1 @@ +[{"answer":[{"name":"cnn.com.","class":"IN","type":"A","ttl":47,"data":"151.101.65.67"},{"name":"cnn.com.","class":"IN","type":"A","ttl":47,"data":"151.101.193.67"},{"name":"cnn.com.","class":"IN","type":"A","ttl":47,"data":"151.101.129.67"},{"name":"cnn.com.","class":"IN","type":"A","ttl":47,"data":"151.101.1.67"}]}] diff --git a/tests/fixtures/osx-10.14.6/dig-noall-answer.out b/tests/fixtures/osx-10.14.6/dig-noall-answer.out new file mode 100644 index 00000000..8944689d --- /dev/null +++ b/tests/fixtures/osx-10.14.6/dig-noall-answer.out @@ -0,0 +1,4 @@ +cnn.com. 47 IN A 151.101.65.67 +cnn.com. 47 IN A 151.101.193.67 +cnn.com. 47 IN A 151.101.129.67 +cnn.com. 47 IN A 151.101.1.67 diff --git a/tests/test_dig.py b/tests/test_dig.py index cba855fd..cca52ea8 100644 --- a/tests/test_dig.py +++ b/tests/test_dig.py @@ -55,10 +55,12 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/dig-axfr.out'), 'r', encoding='utf-8') as f: self.osx_10_14_6_dig_axfr = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/dig-noall-answer.out'), 'r', encoding='utf-8') as f: + self.osx_10_14_6_dig_noall_answer = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/dig-answer-spaces.out'), 'r', encoding='utf-8') as f: self.generic_dig_answer_spaces = f.read() - with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/dig-additional.out'), 'r', encoding='utf-8') as f: self.generic_dig_additional = f.read() @@ -123,10 +125,12 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/dig-axfr.json'), 'r', encoding='utf-8') as f: self.osx_10_14_6_dig_axfr_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/dig-noall-answer.json'), 'r', encoding='utf-8') as f: + self.osx_10_14_6_dig_noall_answer_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/dig-answer-spaces.json'), 'r', encoding='utf-8') as f: self.generic_dig_answer_spaces_json = json.loads(f.read()) - with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/dig-additional.json'), 'r', encoding='utf-8') as f: self.generic_dig_additional_json = json.loads(f.read()) @@ -241,6 +245,12 @@ class MyTests(unittest.TestCase): """ self.assertEqual(jc.parsers.dig.parse(self.osx_10_14_6_dig_axfr, quiet=True), self.osx_10_14_6_dig_axfr_json) + def test_dig_noall_answer_osx_10_14_6(self): + """ + Test 'dig +noall +answer' on OSX 10.14.6 + """ + self.assertEqual(jc.parsers.dig.parse(self.osx_10_14_6_dig_noall_answer, quiet=True), self.osx_10_14_6_dig_noall_answer_json) + def test_dig_answer_spaces(self): """ Test 'dig' with spaces in the answer data (e.g. TXT responses) From 8ab08a5231e082940a62fcde4be218b7ba94be31 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 10:13:52 -0700 Subject: [PATCH 14/60] doc update --- docs/parsers/dig.md | 4 +++- jc/parsers/dig.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/parsers/dig.md b/docs/parsers/dig.md index 6a7b6952..3719323c 100644 --- a/docs/parsers/dig.md +++ b/docs/parsers/dig.md @@ -3,7 +3,9 @@ # jc.parsers.dig jc - JSON CLI output utility `dig` command output parser -The `+noall +answer` options are supported in cases where only the answer information is desired. +Options supported: +- `+noall +answer` options are supported in cases where only the answer information is desired. +- `+axfr` option is supported on its own The `when_epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on) diff --git a/jc/parsers/dig.py b/jc/parsers/dig.py index e7ed438c..cac0cfcb 100644 --- a/jc/parsers/dig.py +++ b/jc/parsers/dig.py @@ -1,6 +1,8 @@ """jc - JSON CLI output utility `dig` command output parser -The `+noall +answer` options are supported in cases where only the answer information is desired. +Options supported: +- `+noall +answer` options are supported in cases where only the answer information is desired. +- `+axfr` option is supported on its own The `when_epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on) From 966978f17e40a338f982628825f30a19d4b84e4c Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 10:39:51 -0700 Subject: [PATCH 15/60] add more unparsable line ping tests --- .../centos-7.7/ping6-ip-O-p-unparsable.json | 1 + .../centos-7.7/ping6-ip-O-p-unparsable.out | 27 +++++++++++++++ .../osx-10.14.6/ping6-ip-unparsable.json | 1 + .../osx-10.14.6/ping6-ip-unparsable.out | 9 +++++ tests/test_ping.py | 34 +++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 100644 tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.json create mode 100644 tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.out create mode 100644 tests/fixtures/osx-10.14.6/ping6-ip-unparsable.json create mode 100644 tests/fixtures/osx-10.14.6/ping6-ip-unparsable.out diff --git a/tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.json b/tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.json new file mode 100644 index 00000000..9c011dab --- /dev/null +++ b/tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.json @@ -0,0 +1 @@ +{"destination_ip":"2a04:4e42:600::323","data_bytes":56,"pattern":"0xabcd","destination":"2a04:4e42:600::323","packets_transmitted":20,"packets_received":19,"packet_loss_percent":5.0,"duplicates":0,"time_ms":19067.0,"round_trip_ms_min":27.064,"round_trip_ms_avg":33.626,"round_trip_ms_max":38.146,"round_trip_ms_stddev":3.803,"responses":[{"type":"unparsable_line","unparsed_line":"64 bytes from 2a04:4e42:600::323: strange error"},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":2,"ttl":59,"time_ms":28.4,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":3,"ttl":59,"time_ms":36.0,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":4,"ttl":59,"time_ms":28.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":5,"ttl":59,"time_ms":35.8,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":6,"ttl":59,"time_ms":34.4,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":7,"ttl":59,"time_ms":30.7,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":8,"ttl":59,"time_ms":28.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":9,"ttl":59,"time_ms":36.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":10,"ttl":59,"time_ms":36.3,"duplicate":false},{"type":"timeout","timestamp":null,"icmp_seq":11},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":12,"ttl":59,"time_ms":37.4,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":13,"ttl":59,"time_ms":30.7,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":14,"ttl":59,"time_ms":36.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":15,"ttl":59,"time_ms":35.4,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":16,"ttl":59,"time_ms":36.3,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":17,"ttl":59,"time_ms":37.5,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":18,"ttl":59,"time_ms":36.2,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":19,"ttl":59,"time_ms":27.0,"duplicate":false},{"type":"reply","timestamp":null,"bytes":64,"response_ip":"2a04:4e42:600::323","icmp_seq":20,"ttl":59,"time_ms":38.1,"duplicate":false}]} diff --git a/tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.out b/tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.out new file mode 100644 index 00000000..f3805a21 --- /dev/null +++ b/tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.out @@ -0,0 +1,27 @@ +PATTERN: 0xabcd +PING 2a04:4e42:600::323(2a04:4e42:600::323) 56 data bytes +64 bytes from 2a04:4e42:600::323: strange error +64 bytes from 2a04:4e42:600::323: icmp_seq=2 ttl=59 time=28.4 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=3 ttl=59 time=36.0 ms +strange error here +64 bytes from 2a04:4e42:600::323: icmp_seq=4 ttl=59 time=28.5 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=5 ttl=59 time=35.8 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=6 ttl=59 time=34.4 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=7 ttl=59 time=30.7 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=8 ttl=59 time=28.5 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=9 ttl=59 time=36.5 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=10 ttl=59 time=36.3 ms +no answer yet for icmp_seq=11 +64 bytes from 2a04:4e42:600::323: icmp_seq=12 ttl=59 time=37.4 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=13 ttl=59 time=30.7 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=14 ttl=59 time=36.5 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=15 ttl=59 time=35.4 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=16 ttl=59 time=36.3 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=17 ttl=59 time=37.5 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=18 ttl=59 time=36.2 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=19 ttl=59 time=27.0 ms +64 bytes from 2a04:4e42:600::323: icmp_seq=20 ttl=59 time=38.1 ms + +--- 2a04:4e42:600::323 ping statistics --- +20 packets transmitted, 19 received, 5% packet loss, time 19067ms +rtt min/avg/max/mdev = 27.064/33.626/38.146/3.803 ms diff --git a/tests/fixtures/osx-10.14.6/ping6-ip-unparsable.json b/tests/fixtures/osx-10.14.6/ping6-ip-unparsable.json new file mode 100644 index 00000000..1a2eac42 --- /dev/null +++ b/tests/fixtures/osx-10.14.6/ping6-ip-unparsable.json @@ -0,0 +1 @@ +{"source_ip":"::1","destination_ip":"::1","data_bytes":56,"pattern":null,"destination":"::1","packets_transmitted":3,"packets_received":3,"packet_loss_percent":0.0,"duplicates":0,"round_trip_ms_min":0.071,"round_trip_ms_avg":0.115,"round_trip_ms_max":0.153,"round_trip_ms_stddev":0.034,"responses":[{"type":"reply","bytes":16,"response_ip":"::1","icmp_seq":0,"ttl":64,"time_ms":0.071,"duplicate":false},{"type":"unparsable_line","unparsed_line":"16 bytes from ::1 strange error"},{"type":"reply","bytes":16,"response_ip":"::1","icmp_seq":2,"ttl":64,"time_ms":0.122,"duplicate":false}]} diff --git a/tests/fixtures/osx-10.14.6/ping6-ip-unparsable.out b/tests/fixtures/osx-10.14.6/ping6-ip-unparsable.out new file mode 100644 index 00000000..c1f8b857 --- /dev/null +++ b/tests/fixtures/osx-10.14.6/ping6-ip-unparsable.out @@ -0,0 +1,9 @@ +PING6(56=40+8+8 bytes) ::1 --> ::1 +16 bytes from ::1, icmp_seq=0 hlim=64 time=0.071 ms +16 bytes from ::1, strange error +weird error message +16 bytes from ::1, icmp_seq=2 hlim=64 time=0.122 ms + +--- ::1 ping6 statistics --- +3 packets transmitted, 3 packets received, 0.0% packet loss +round-trip min/avg/max/std-dev = 0.071/0.115/0.153/0.034 ms diff --git a/tests/test_ping.py b/tests/test_ping.py index 8897df65..14478d3d 100644 --- a/tests/test_ping.py +++ b/tests/test_ping.py @@ -30,6 +30,12 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p.out'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_p = f.read() + + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.out'), 'r', encoding='utf-8') as f: + self.centos_7_7_ping6_ip_O_p_unparsable = f.read() + + + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-D-p.out'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_D_p = f.read() @@ -184,6 +190,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-ip.out'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping6_ip = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-ip-unparsable.out'), 'r', encoding='utf-8') as f: + self.osx_10_14_6_ping6_ip_unparsable = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-dup.out'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping_ip_dup = f.read() @@ -218,6 +227,12 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p.json'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_p_json = json.loads(f.read()) + + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.json'), 'r', encoding='utf-8') as f: + self.centos_7_7_ping6_ip_O_p_unparsable_json = json.loads(f.read()) + + + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-D-p.json'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_D_p_json = json.loads(f.read()) @@ -372,6 +387,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-ip.json'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping6_ip_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping6-ip-unparsable.json'), 'r', encoding='utf-8') as f: + self.osx_10_14_6_ping6_ip_unparsable_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/ping-ip-dup.json'), 'r', encoding='utf-8') as f: self.osx_10_14_6_ping_ip_dup_json = json.loads(f.read()) @@ -427,6 +445,16 @@ class MyTests(unittest.TestCase): """ self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping6_ip_O_p, quiet=True), self.centos_7_7_ping6_ip_O_p_json) + + def test_ping6_ip_O_p_unparsable_centos_7_7(self): + """ + Test 'ping6 -O -p' with unparsable lines on Centos 7.7 + """ + self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping6_ip_O_p_unparsable, quiet=True), self.centos_7_7_ping6_ip_O_p_unparsable_json) + + + + def test_ping6_ip_O_D_p_centos_7_7(self): """ Test 'ping6 -O -D -p' on Centos 7.7 @@ -727,6 +755,12 @@ class MyTests(unittest.TestCase): """ self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping6_ip, quiet=True), self.osx_10_14_6_ping6_ip_json) + def test_ping6_ip_unparsable_osx_10_14_6(self): + """ + Test 'ping6 ' with unparsable lines on osx 10.14.6 + """ + self.assertEqual(jc.parsers.ping.parse(self.osx_10_14_6_ping6_ip_unparsable, quiet=True), self.osx_10_14_6_ping6_ip_unparsable_json) + def test_ping_ip_dup_osx_10_14_6(self): """ Test 'ping ' to broadcast IP to get duplicate replies on osx 10.14.6 From c0c0e05642e0f473cde34178d815978fff8fe1ef Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 10:40:13 -0700 Subject: [PATCH 16/60] add dig `+noall +answer` support --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 6b4b9ac2..423f0909 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ jc changelog 20210510 v1.15.4 - Update ping parser to support error responses in OSX and BSD - Update ping parser to be more resillient against parsing errors for unknown error types +- Update dig parser to support `+noall +answer` use case 20210426 v1.15.3 - Add ufw status command parser tested on linux From f2ffb93eeaf4c8b5aa93f3e8808bdc044582e8a0 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 10:43:40 -0700 Subject: [PATCH 17/60] formatting --- tests/test_ping.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/test_ping.py b/tests/test_ping.py index 14478d3d..aa9704b2 100644 --- a/tests/test_ping.py +++ b/tests/test_ping.py @@ -30,12 +30,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p.out'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_p = f.read() - with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.out'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_p_unparsable = f.read() - - with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-D-p.out'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_D_p = f.read() @@ -227,12 +224,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p.json'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_p_json = json.loads(f.read()) - with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-p-unparsable.json'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_p_unparsable_json = json.loads(f.read()) - - with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/ping6-ip-O-D-p.json'), 'r', encoding='utf-8') as f: self.centos_7_7_ping6_ip_O_D_p_json = json.loads(f.read()) @@ -445,15 +439,11 @@ class MyTests(unittest.TestCase): """ self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping6_ip_O_p, quiet=True), self.centos_7_7_ping6_ip_O_p_json) - def test_ping6_ip_O_p_unparsable_centos_7_7(self): """ Test 'ping6 -O -p' with unparsable lines on Centos 7.7 """ self.assertEqual(jc.parsers.ping.parse(self.centos_7_7_ping6_ip_O_p_unparsable, quiet=True), self.centos_7_7_ping6_ip_O_p_unparsable_json) - - - def test_ping6_ip_O_D_p_centos_7_7(self): """ From 19b540041add64fe6650c3634ec869421ffbc769 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 18:31:30 -0700 Subject: [PATCH 18/60] proof of concept for passing command exit codes when using magic syntax. Needs more testing --- jc/cli.py | 131 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 81 insertions(+), 50 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index f8989e13..8c95fa0d 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -10,6 +10,7 @@ import shlex import importlib import textwrap import signal +import subprocess import json import jc import jc.appdirs as appdirs @@ -395,15 +396,21 @@ def json_out(data, pretty=False, env_colors=None, mono=False, piped_out=False): return json.dumps(data, separators=(',', ':'), ensure_ascii=False) -def generate_magic_command(args): +def magic_parser(args): """ - Return a tuple with a boolean and a command, where the boolean signifies that - the command is valid, and the command is either a command string or None. + Return a tuple: + valid_command (bool) is this a valid command? (exists in magic dict) + run_command (list) list of the user's command to run. None if no command. + jc_parser (str) parser to use for this user's command. + jc_options (list) list of jc options + + Side-effect: + This function will reset sys.argv to an empty list """ # Parse with magic syntax: jc -p ls -al if len(args) <= 1 or args[1].startswith('--'): - return False, None + return False, None, None, [] # correctly parse escape characters and spaces with shlex args_given = ' '.join(map(shlex.quote, args[1:])).split() @@ -413,7 +420,7 @@ def generate_magic_command(args): for arg in list(args_given): # parser found - use standard syntax if arg.startswith('--'): - return False, None + return False, None, None, [] # option found - populate option list elif arg.startswith('-'): @@ -423,13 +430,16 @@ def generate_magic_command(args): else: break + # reset sys.argv since we are now done with it + sys.argv = [] + # if -h, -a, or -v found in options, then bail out if 'h' in options or 'a' in options or 'v' in options: - return False, None + return False, None, None, [] # all options popped and no command found - for case like 'jc -a' if len(args_given) == 0: - return False, None + return False, None, None, [] magic_dict = {} parser_info = about_jc()['parsers'] @@ -446,26 +456,25 @@ def generate_magic_command(args): # try to get a parser for two_word_command, otherwise get one for one_word_command found_parser = magic_dict.get(two_word_command, magic_dict.get(one_word_command)) - # construct a new command line using the standard syntax: COMMAND | jc --PARSER -OPTIONS - run_command = ' '.join(args_given) - if found_parser: - cmd_options = ('-' + ''.join(options)) if options else '' - return True, ' '.join([run_command, '|', 'jc', found_parser, cmd_options]) - else: - return False, run_command + run_command = args_given + return ( + True if found_parser else False, + run_command, + found_parser, + options + ) -def magic(): - """Runs the command generated by generate_magic_command() to support magic syntax""" - valid_command, run_command = generate_magic_command(sys.argv) - if valid_command: - os.system(run_command) - sys.exit(0) - elif run_command is None: - return - else: - jc.utils.error_message(f'parser not found for "{run_command}". Use "jc -h" for help.') - sys.exit(1) +def run_user_command(command): + """Use subprocess to run the user's command. Returns the STDOUT, STDERR, and the Exit Code as a tuple.""" + proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + stdout, stderr = proc.communicate() + + return ( + stdout or '\n', + stderr, + proc.returncode + ) def main(): @@ -481,13 +490,27 @@ def main(): pass # try magic syntax first: e.g. jc -p ls -al - magic() + magic_stdout, magic_stderr, magic_exit_code, magic_options = None, None, None, [] + valid_command, run_command, magic_found_parser, magic_options = magic_parser(sys.argv) + if valid_command: + magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command) + if magic_stderr: + print(magic_stderr, file=sys.stderr) + elif run_command is None: + pass + else: + run_command_str = ' '.join(run_command) + jc.utils.error_message(f'parser not found for "{run_command_str}". Use "jc -h" for help.') + sys.exit(1) + # set colors jc_colors = os.getenv('JC_COLORS') + # set options options = [] + options.extend(magic_options) - # options + # note that sys.argv will be an empty list after magic_parser finds the magic syntax is used for opt in sys.argv: if opt.startswith('-') and not opt.startswith('--'): options.extend(opt[1:]) @@ -521,40 +544,48 @@ def main(): import jc.tracebackplus jc.tracebackplus.enable(context=11) - if sys.stdin.isatty(): + if sys.stdin.isatty() and magic_stdout is None: jc.utils.error_message('Missing piped data. Use "jc -h" for help.') - sys.exit(1) + sys.exit(magic_exit_code + 1 if magic_exit_code else 1) - data = sys.stdin.read() + data = magic_stdout or sys.stdin.read() - found = False + # find the correct parser + if magic_found_parser: + parser = parser_module(magic_found_parser) - for arg in sys.argv: - parser_name = parser_shortname(arg) + else: + found = False + for arg in sys.argv: + parser_name = parser_shortname(arg) - if parser_name in parsers: - # load parser module just in time so we don't need to load all modules - parser = parser_module(arg) - try: - result = parser.parse(data, raw=raw, quiet=quiet) + if parser_name in parsers: + # load parser module just in time so we don't need to load all modules + parser = parser_module(arg) found = True break - except Exception: - if debug: - raise - else: - import jc.utils - jc.utils.error_message( - f'{parser_name} parser could not parse the input data. Did you use the correct parser?\n' - ' For details use the -d or -dd option. Use "jc -h" for help.') - sys.exit(1) + if not found: + jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.') + sys.exit(1) - if not found: - jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.') - sys.exit(1) + # parse the data + try: + result = parser.parse(data, raw=raw, quiet=quiet) + except Exception: + if debug: + raise + else: + import jc.utils + jc.utils.error_message( + f'{parser_name} parser could not parse the input data. Did you use the correct parser?\n' + ' For details use the -d or -dd option. Use "jc -h" for help.') + sys.exit(magic_exit_code or 1) + + # output the json print(json_out(result, pretty=pretty, env_colors=jc_colors, mono=mono, piped_out=piped_output())) + sys.exit(magic_exit_code or 0) if __name__ == '__main__': From da904e4770a20ae85fc67476ae4a529564d0a3ff Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 18:50:59 -0700 Subject: [PATCH 19/60] remove final \n from stderr string when printing --- jc/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jc/cli.py b/jc/cli.py index 8c95fa0d..05e41e20 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -495,7 +495,7 @@ def main(): if valid_command: magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command) if magic_stderr: - print(magic_stderr, file=sys.stderr) + print(magic_stderr[:-1], file=sys.stderr) elif run_command is None: pass else: From 0d7d7951f82627d1fb1e20d1398bd7f88c5ce1be Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 18:58:45 -0700 Subject: [PATCH 20/60] don't reset sys.argv anymore. check for 'valid_command' instead --- jc/cli.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 05e41e20..c188e518 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -430,9 +430,6 @@ def magic_parser(args): else: break - # reset sys.argv since we are now done with it - sys.argv = [] - # if -h, -a, or -v found in options, then bail out if 'h' in options or 'a' in options or 'v' in options: return False, None, None, [] @@ -510,10 +507,11 @@ def main(): options = [] options.extend(magic_options) - # note that sys.argv will be an empty list after magic_parser finds the magic syntax is used - for opt in sys.argv: - if opt.startswith('-') and not opt.startswith('--'): - options.extend(opt[1:]) + # only find options if magic_parser did not find a command + if not valid_command: + for opt in sys.argv: + if opt.startswith('-') and not opt.startswith('--'): + options.extend(opt[1:]) about = 'a' in options debug = 'd' in options From e4574047a0ef6fcd4a47142421a1d05795c7925a Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 20:49:44 -0700 Subject: [PATCH 21/60] update tests for magic_parser function --- tests/test_cli.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index 623dd4d5..55b42bea 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -5,31 +5,31 @@ import jc.cli class MyTests(unittest.TestCase): - def test_cli_generate_magic_command(self): + def test_cli_magic_parser(self): commands = { - 'jc -p systemctl list-sockets': 'systemctl list-sockets | jc --systemctl-ls -p', - 'jc -p systemctl list-unit-files': 'systemctl list-unit-files | jc --systemctl-luf -p', - 'jc -p pip list': 'pip list | jc --pip-list -p', - 'jc -p pip3 list': 'pip3 list | jc --pip-list -p', - 'jc -p pip show jc': 'pip show jc | jc --pip-show -p', - 'jc -p pip3 show jc': 'pip3 show jc | jc --pip-show -p', - 'jc -prd last': 'last | jc --last -prd', - 'jc -prd lastb': 'lastb | jc --last -prd', - 'jc -p airport -I': 'airport -I | jc --airport -p', - 'jc -p -r airport -I': 'airport -I | jc --airport -pr', - 'jc -prd airport -I': 'airport -I | jc --airport -prd', - 'jc -p nonexistent command': 'nonexistent command', - 'jc -ap': None, - 'jc -a arp -a': None, - 'jc -v': None, - 'jc -h': None, - 'jc -h --arp': None, - 'jc -h arp': None, - 'jc -h arp -a': None + 'jc -p systemctl list-sockets': (True, ['systemctl', 'list-sockets'], '--systemctl-ls', ['p']), + 'jc -p systemctl list-unit-files': (True, ['systemctl', 'list-unit-files'], '--systemctl-luf', ['p']), + 'jc -p pip list': (True, ['pip', 'list'], '--pip-list', ['p']), + 'jc -p pip3 list': (True, ['pip3', 'list'], '--pip-list', ['p']), + 'jc -p pip show jc': (True, ['pip', 'show', 'jc'], '--pip-show', ['p']), + 'jc -p pip3 show jc': (True, ['pip3', 'show', 'jc'], '--pip-show', ['p']), + 'jc -prd last': (True, ['last'], '--last', ['p', 'r', 'd']), + 'jc -prdd lastb': (True, ['lastb'], '--last', ['p', 'r', 'd', 'd']), + 'jc -p airport -I': (True, ['airport', '-I'], '--airport', ['p']), + 'jc -p -r airport -I': (True, ['airport', '-I'], '--airport', ['p', 'r']), + 'jc -prd airport -I': (True, ['airport', '-I'], '--airport', ['p', 'r', 'd']), + 'jc -p nonexistent command': (False, ['nonexistent', 'command'], None, ['p']), + 'jc -ap': (False, None, None, []), + 'jc -a arp -a': (False, None, None, []), + 'jc -v': (False, None, None, []), + 'jc -h': (False, None, None, []), + 'jc -h --arp': (False, None, None, []), + 'jc -h arp': (False, None, None, []), + 'jc -h arp -a': (False, None, None, []) } for command, expected_command in commands.items(): - self.assertEqual(jc.cli.generate_magic_command(command.split(' '))[1], expected_command) + self.assertEqual(jc.cli.magic_parser(command.split(' ')), expected_command) def test_cli_set_env_colors(self): if pygments.__version__.startswith('2.3.'): From c3b814a15f571ac84a8ce0719d7fd88acf814caf Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 21:02:28 -0700 Subject: [PATCH 22/60] move imports to the top --- jc/cli.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index c188e518..1d1ac157 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -14,6 +14,9 @@ import subprocess import json import jc import jc.appdirs as appdirs +import jc.utils +import jc.tracebackplus + # make pygments import optional try: import pygments @@ -475,8 +478,6 @@ def run_user_command(command): def main(): - import jc.utils - # break on ctrl-c keyboard interrupt signal.signal(signal.SIGINT, ctrlc) @@ -539,7 +540,6 @@ def main(): sys.exit(0) if verbose_debug: - import jc.tracebackplus jc.tracebackplus.enable(context=11) if sys.stdin.isatty() and magic_stdout is None: @@ -575,7 +575,6 @@ def main(): if debug: raise else: - import jc.utils jc.utils.error_message( f'{parser_name} parser could not parse the input data. Did you use the correct parser?\n' ' For details use the -d or -dd option. Use "jc -h" for help.') From 422bb744a82472546d62c72ba05eaa1d18d5068a Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 21:03:30 -0700 Subject: [PATCH 23/60] update man pages --- jc/man/jc.1.gz | Bin 2413 -> 2412 bytes man/jc.1.gz | Bin 2413 -> 2412 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/jc/man/jc.1.gz b/jc/man/jc.1.gz index 51d228409630ffe51437fb0ed7fe450650a1a676..db001ebecc771660ddb2b08f6e954e361ac936e2 100644 GIT binary patch literal 2412 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 literal 2413 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 diff --git a/man/jc.1.gz b/man/jc.1.gz index 51d228409630ffe51437fb0ed7fe450650a1a676..db001ebecc771660ddb2b08f6e954e361ac936e2 100644 GIT binary patch literal 2412 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 literal 2413 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 From a56aebfe7024914d2c602278964b99d1443de335 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 21:09:00 -0700 Subject: [PATCH 24/60] remove side-effect comment since it is no longer relevant --- jc/cli.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 1d1ac157..9e22d636 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -406,9 +406,6 @@ def magic_parser(args): run_command (list) list of the user's command to run. None if no command. jc_parser (str) parser to use for this user's command. jc_options (list) list of jc options - - Side-effect: - This function will reset sys.argv to an empty list """ # Parse with magic syntax: jc -p ls -al From 5f88ecf8443bc6e9a7cda5bc60858f37cc09f4f4 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 10 May 2021 21:23:23 -0700 Subject: [PATCH 25/60] add comments to magic_parser return --- jc/cli.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 9e22d636..9bb1ce63 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -453,12 +453,11 @@ def magic_parser(args): # try to get a parser for two_word_command, otherwise get one for one_word_command found_parser = magic_dict.get(two_word_command, magic_dict.get(one_word_command)) - run_command = args_given return ( - True if found_parser else False, - run_command, - found_parser, - options + True if found_parser else False, # was a suitable parser found? + args_given, # run_command + found_parser, # the parser selected + options # jc options to preserve ) From 342db45edc94ab1c3b3ae70e7cf4ca4076e2c7eb Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 11 May 2021 10:36:55 -0700 Subject: [PATCH 26/60] fix combined exit codes --- jc/cli.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 9bb1ce63..20145bee 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -118,6 +118,9 @@ parsers = [ 'yaml' ] +JC_ERROR_EXIT = 100 + + # List of custom or override parsers. # Allow any /jc/jcparsers/*.py local_parsers = [] @@ -190,10 +193,9 @@ def set_env_colors(env_colors=None): Default colors: - JC_COLORS=blue,brightblack,magenta,green + JC_COLORS=blue,brightblack,magenta,green or - JC_COLORS=default,default,default,default - + JC_COLORS=default,default,default,default """ input_error = False @@ -233,7 +235,7 @@ def piped_output(): def ctrlc(signum, frame): """Exit with error on SIGINT""" - sys.exit(1) + sys.exit(JC_ERROR_EXIT) def parser_shortname(parser_argument): @@ -473,7 +475,14 @@ def run_user_command(command): ) +def combined_exit_code(program_exit=0, jc_exit=0): + return program_exit + jc_exit + + def main(): + magic_stdout, magic_stderr, magic_exit_code = None, None, 0 + magic_options = [] + # break on ctrl-c keyboard interrupt signal.signal(signal.SIGINT, ctrlc) @@ -484,7 +493,6 @@ def main(): pass # try magic syntax first: e.g. jc -p ls -al - magic_stdout, magic_stderr, magic_exit_code, magic_options = None, None, None, [] valid_command, run_command, magic_found_parser, magic_options = magic_parser(sys.argv) if valid_command: magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command) @@ -495,7 +503,7 @@ def main(): else: run_command_str = ' '.join(run_command) jc.utils.error_message(f'parser not found for "{run_command_str}". Use "jc -h" for help.') - sys.exit(1) + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # set colors jc_colors = os.getenv('JC_COLORS') @@ -540,7 +548,7 @@ def main(): if sys.stdin.isatty() and magic_stdout is None: jc.utils.error_message('Missing piped data. Use "jc -h" for help.') - sys.exit(magic_exit_code + 1 if magic_exit_code else 1) + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) data = magic_stdout or sys.stdin.read() @@ -561,7 +569,7 @@ def main(): if not found: jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.') - sys.exit(1) + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # parse the data try: @@ -574,11 +582,11 @@ def main(): jc.utils.error_message( f'{parser_name} parser could not parse the input data. Did you use the correct parser?\n' ' For details use the -d or -dd option. Use "jc -h" for help.') - sys.exit(magic_exit_code or 1) + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # output the json print(json_out(result, pretty=pretty, env_colors=jc_colors, mono=mono, piped_out=piped_output())) - sys.exit(magic_exit_code or 0) + sys.exit(combined_exit_code(magic_exit_code, 0)) if __name__ == '__main__': From 48921d4584068fadb0f776ed7e81c21d89172e2e Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 11 May 2021 10:50:35 -0700 Subject: [PATCH 27/60] ensure exit code never exceeds 255 --- jc/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jc/cli.py b/jc/cli.py index 20145bee..553b8baa 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -476,7 +476,10 @@ def run_user_command(command): def combined_exit_code(program_exit=0, jc_exit=0): - return program_exit + jc_exit + exit_code = program_exit + jc_exit + if exit_code > 255: + exit_code = 255 + return exit_code def main(): From 21a15225ebff645439b12d6249dc3e28d7bc09ed Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 11 May 2021 10:59:26 -0700 Subject: [PATCH 28/60] add exit codes section --- README.md | 9 +++++++++ templates/readme_template | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/README.md b/README.md index 42724901..c717584f 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,15 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio - `-r` raw output. Provides a more literal JSON output, typically with string values and no additional semantic processing - `-v` version information +### Exit Codes +Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to tell if an error was from the parsed program or `jc`. + +Consider the following examples using `ifconfig`: +- `ifconfig` exit code = `0`, `jc` exit code = `0`, combined exit code = `0` (no errors) +- `ifconfig` exit code = `1`, `jc` exit code = `0`, combined exit code = `1` (error in `ifconfig`) +- `ifconfig` exit code = `0`, `jc` exit code = `100`, combined exit code = `100` (error in `jc`) +- `ifconfig` exit code = `1`, `jc` exit code = `100`, combined exit code = `101` (error in both `ifconfig` and `jc`) + ### Setting Custom Colors via Environment Variable You can specify custom colors via the `JC_COLORS` environment variable. The `JC_COLORS` environment variable takes four comma separated string values in the following format: ```bash diff --git a/templates/readme_template b/templates/readme_template index b9758e8b..f077b5e3 100644 --- a/templates/readme_template +++ b/templates/readme_template @@ -131,6 +131,15 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio - `-r` raw output. Provides a more literal JSON output, typically with string values and no additional semantic processing - `-v` version information +### Exit Codes +Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to tell if an error was from the parsed program or `jc`. + +Consider the following examples using `ifconfig`: +- `ifconfig` exit code = `0`, `jc` exit code = `0`, combined exit code = `0` (no errors) +- `ifconfig` exit code = `1`, `jc` exit code = `0`, combined exit code = `1` (error in `ifconfig`) +- `ifconfig` exit code = `0`, `jc` exit code = `100`, combined exit code = `100` (error in `jc`) +- `ifconfig` exit code = `1`, `jc` exit code = `100`, combined exit code = `101` (error in both `ifconfig` and `jc`) + ### Setting Custom Colors via Environment Variable You can specify custom colors via the `JC_COLORS` environment variable. The `JC_COLORS` environment variable takes four comma separated string values in the following format: ```bash From 995ecc9bfb0425b0ee9e4250395b0200b46c1e89 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 11 May 2021 11:11:17 -0700 Subject: [PATCH 29/60] add exit codes section --- jc/man/jc.1.gz | Bin 2412 -> 2663 bytes man/jc.1.gz | Bin 2412 -> 2663 bytes templates/manpage_template | 15 +++++++++++++++ 3 files changed, 15 insertions(+) diff --git a/jc/man/jc.1.gz b/jc/man/jc.1.gz index db001ebecc771660ddb2b08f6e954e361ac936e2..ec57ac5680aab1ad75fbc247478f8e33668a66ca 100644 GIT binary patch literal 2663 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 diff --git a/man/jc.1.gz b/man/jc.1.gz index db001ebecc771660ddb2b08f6e954e361ac936e2..ec57ac5680aab1ad75fbc247478f8e33668a66ca 100644 GIT binary patch literal 2663 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 diff --git a/templates/manpage_template b/templates/manpage_template index e8852f5c..49eee6ae 100644 --- a/templates/manpage_template +++ b/templates/manpage_template @@ -62,6 +62,21 @@ raw JSON output \fB-v\fP version information +.SH EXIT CODES +Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to tell if an error was from the parsed program or `jc`. + +Consider the following examples using `ifconfig`: + +.RS +`ifconfig` exit code = `0`, `jc` exit code = `0`, combined exit code = `0` (no errors) + +`ifconfig` exit code = `1`, `jc` exit code = `0`, combined exit code = `1` (error in `ifconfig`) + +`ifconfig` exit code = `0`, `jc` exit code = `100`, combined exit code = `100` (error in `jc`) + +`ifconfig` exit code = `1`, `jc` exit code = `100`, combined exit code = `101` (error in both `ifconfig` and `jc`) +.RE + .SH ENVIRONMENT You can specify custom colors via the \fBJC_COLORS\fP environment variable. The \fBJC_COLORS\fP environment variable takes four comma separated string values in the following format: From 8d8c58742e5106c3387a627cd1b1323a76cc9623 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 11 May 2021 11:32:08 -0700 Subject: [PATCH 30/60] formatting --- README.md | 13 ++++++++----- jc/man/jc.1.gz | Bin 2663 -> 2663 bytes man/jc.1.gz | Bin 2663 -> 2663 bytes templates/manpage_template | 10 +++++----- templates/readme_template | 13 ++++++++----- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c717584f..517971b2 100644 --- a/README.md +++ b/README.md @@ -204,13 +204,16 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio - `-v` version information ### Exit Codes -Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to tell if an error was from the parsed program or `jc`. +Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to determine if an error was from the parsed program or `jc`. Consider the following examples using `ifconfig`: -- `ifconfig` exit code = `0`, `jc` exit code = `0`, combined exit code = `0` (no errors) -- `ifconfig` exit code = `1`, `jc` exit code = `0`, combined exit code = `1` (error in `ifconfig`) -- `ifconfig` exit code = `0`, `jc` exit code = `100`, combined exit code = `100` (error in `jc`) -- `ifconfig` exit code = `1`, `jc` exit code = `100`, combined exit code = `101` (error in both `ifconfig` and `jc`) +| `ifconfig` exit code | `jc` exit code | Combined exit code | Interpretation | +|----------------------|----------------|--------------------|------------------------------------| +| `0` | `0` | `0` | No errors | +| `1` | `0` | `1` | Error in `ifconfig` | +| `0` | `100` | `100` | Error in `jc` | +| `1` | `100` | `101` | Error in both `ifconfig` and `jc` | + ### Setting Custom Colors via Environment Variable You can specify custom colors via the `JC_COLORS` environment variable. The `JC_COLORS` environment variable takes four comma separated string values in the following format: diff --git a/jc/man/jc.1.gz b/jc/man/jc.1.gz index ec57ac5680aab1ad75fbc247478f8e33668a66ca..413e8b30bd3bd06beaf40e60f046af5b4a325ead 100644 GIT binary patch delta 2625 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 delta 2625 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 diff --git a/man/jc.1.gz b/man/jc.1.gz index ec57ac5680aab1ad75fbc247478f8e33668a66ca..413e8b30bd3bd06beaf40e60f046af5b4a325ead 100644 GIT binary patch delta 2625 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 delta 2625 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 diff --git a/templates/manpage_template b/templates/manpage_template index 49eee6ae..a1f7fb8c 100644 --- a/templates/manpage_template +++ b/templates/manpage_template @@ -63,18 +63,18 @@ raw JSON output version information .SH EXIT CODES -Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to tell if an error was from the parsed program or `jc`. +Any fatal errors within jc will generate an exit code of \fB100\fP, otherwise the exit code will be \fB0\fP. When using the "magic" syntax (e.g. \fBjc ifconfig eth0\fP), jc will store the exit code of the program being parsed and add it to the jc exit code. This way it is easier to determine if an error was from the parsed program or jc. Consider the following examples using `ifconfig`: .RS -`ifconfig` exit code = `0`, `jc` exit code = `0`, combined exit code = `0` (no errors) +ifconfig exit code = \fB0\fP, jc exit code = \fB0\fP, combined exit code = \fB0\fP (no errors) -`ifconfig` exit code = `1`, `jc` exit code = `0`, combined exit code = `1` (error in `ifconfig`) +ifconfig exit code = \fB1\fP, jc exit code = \fB0\fP, combined exit code = \fB1\fP (error in ifconfig) -`ifconfig` exit code = `0`, `jc` exit code = `100`, combined exit code = `100` (error in `jc`) +ifconfig exit code = \fB0\fP, jc exit code = \fB100\fP, combined exit code = \fB100\fP (error in jc) -`ifconfig` exit code = `1`, `jc` exit code = `100`, combined exit code = `101` (error in both `ifconfig` and `jc`) +ifconfig exit code = \fB1\fP, jc exit code = \fB100\fP, combined exit code = \fB101\fP (error in both ifconfig and jc) .RE .SH ENVIRONMENT diff --git a/templates/readme_template b/templates/readme_template index f077b5e3..501440a1 100644 --- a/templates/readme_template +++ b/templates/readme_template @@ -132,13 +132,16 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio - `-v` version information ### Exit Codes -Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to tell if an error was from the parsed program or `jc`. +Any fatal errors within `jc` will generate an exit code of `100`, otherwise the exit code will be `0`. When using the "magic" syntax (e.g. `jc ifconfig eth0`), `jc` will store the exit code of the program being parsed and add it to the `jc` exit code. This way it is easier to determine if an error was from the parsed program or `jc`. Consider the following examples using `ifconfig`: -- `ifconfig` exit code = `0`, `jc` exit code = `0`, combined exit code = `0` (no errors) -- `ifconfig` exit code = `1`, `jc` exit code = `0`, combined exit code = `1` (error in `ifconfig`) -- `ifconfig` exit code = `0`, `jc` exit code = `100`, combined exit code = `100` (error in `jc`) -- `ifconfig` exit code = `1`, `jc` exit code = `100`, combined exit code = `101` (error in both `ifconfig` and `jc`) +| `ifconfig` exit code | `jc` exit code | Combined exit code | Interpretation | +|----------------------|----------------|--------------------|------------------------------------| +| `0` | `0` | `0` | No errors | +| `1` | `0` | `1` | Error in `ifconfig` | +| `0` | `100` | `100` | Error in `jc` | +| `1` | `100` | `101` | Error in both `ifconfig` and `jc` | + ### Setting Custom Colors via Environment Variable You can specify custom colors via the `JC_COLORS` environment variable. The `JC_COLORS` environment variable takes four comma separated string values in the following format: From 947cf41dfab5811c0e76e371231c59a2d6372752 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 11 May 2021 12:42:21 -0700 Subject: [PATCH 31/60] add exit code info --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 423f0909..66c22ec0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,10 @@ jc changelog -20210510 v1.15.4 +20210511 v1.15.4 - Update ping parser to support error responses in OSX and BSD - Update ping parser to be more resillient against parsing errors for unknown error types - Update dig parser to support `+noall +answer` use case +- JC no longer swallows exit codes when using the "magic" syntax. See the Exit Codes section of the README and man page for details 20210426 v1.15.3 - Add ufw status command parser tested on linux From c543f00bd3c737f73144bb2980d12793042459b4 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Tue, 11 May 2021 14:30:46 -0700 Subject: [PATCH 32/60] simplify piped_output function --- jc/cli.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 553b8baa..77ad380f 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -227,10 +227,7 @@ def set_env_colors(env_colors=None): def piped_output(): """Return False if stdout is a TTY. True if output is being piped to another program""" - if sys.stdout.isatty(): - return False - else: - return True + return False if sys.stdout.isatty() else True def ctrlc(signum, frame): From 4d40808d2b70b7543eadcfd2d35c16ebe91f2f9f Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 08:27:39 -0700 Subject: [PATCH 33/60] update comments --- jc/cli.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 77ad380f..841f9e84 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -400,14 +400,15 @@ def json_out(data, pretty=False, env_colors=None, mono=False, piped_out=False): def magic_parser(args): """ + Parse command arguments for magic syntax: jc -p ls -al + Return a tuple: valid_command (bool) is this a valid command? (exists in magic dict) run_command (list) list of the user's command to run. None if no command. jc_parser (str) parser to use for this user's command. jc_options (list) list of jc options """ - - # Parse with magic syntax: jc -p ls -al + # bail immediately if there are no args or a parser is defined if len(args) <= 1 or args[1].startswith('--'): return False, None, None, [] @@ -433,7 +434,7 @@ def magic_parser(args): if 'h' in options or 'a' in options or 'v' in options: return False, None, None, [] - # all options popped and no command found - for case like 'jc -a' + # all options popped and no command found - for case like 'jc -x' if len(args_given) == 0: return False, None, None, [] From 4acebf4f621ac564f82e3a97e6810fbb08a9dbc5 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 08:48:49 -0700 Subject: [PATCH 34/60] move variables --- jc/cli.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 841f9e84..d9dda9da 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -481,9 +481,6 @@ def combined_exit_code(program_exit=0, jc_exit=0): def main(): - magic_stdout, magic_stderr, magic_exit_code = None, None, 0 - magic_options = [] - # break on ctrl-c keyboard interrupt signal.signal(signal.SIGINT, ctrlc) @@ -494,6 +491,8 @@ def main(): pass # try magic syntax first: e.g. jc -p ls -al + magic_stdout, magic_stderr, magic_exit_code = None, None, 0 + magic_options = [] valid_command, run_command, magic_found_parser, magic_options = magic_parser(sys.argv) if valid_command: magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command) From da528e7814b0cf25a359c3556d0e286e7bf004b8 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 09:40:22 -0700 Subject: [PATCH 35/60] move separators to a variable --- jc/cli.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index d9dda9da..e745ab2a 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -380,6 +380,7 @@ def versiontext(): def json_out(data, pretty=False, env_colors=None, mono=False, piped_out=False): """Return a JSON formatted string. String may include color codes or be pretty printed.""" + separators = (',', ':') if not mono and not piped_out: # set colors class JcStyle(Style): @@ -389,13 +390,13 @@ def json_out(data, pretty=False, env_colors=None, mono=False, piped_out=False): return str(highlight(json.dumps(data, indent=2, ensure_ascii=False), JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1]) else: - return str(highlight(json.dumps(data, separators=(',', ':'), ensure_ascii=False), + return str(highlight(json.dumps(data, separators=separators, ensure_ascii=False), JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1]) else: if pretty: return json.dumps(data, indent=2, ensure_ascii=False) else: - return json.dumps(data, separators=(',', ':'), ensure_ascii=False) + return json.dumps(data, separators=separators, ensure_ascii=False) def magic_parser(args): From b5a5d5b133f1c0df8023776e59697b7579f8c18a Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 11:36:27 -0700 Subject: [PATCH 36/60] set parser_name for magic syntax use --- jc/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jc/cli.py b/jc/cli.py index e745ab2a..60269950 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -556,6 +556,7 @@ def main(): # find the correct parser if magic_found_parser: parser = parser_module(magic_found_parser) + parser_name = parser_shortname(magic_found_parser) else: found = False From b6c8d6d01d740827273265dbb0cfbaf7875ac7f3 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 13:18:58 -0700 Subject: [PATCH 37/60] add exception handling for filenotfound or other subprocess.popen and json.dumps exceptions --- jc/cli.py | 62 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 17 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 60269950..88dcbdfb 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -491,20 +491,9 @@ def main(): except AttributeError: pass - # try magic syntax first: e.g. jc -p ls -al - magic_stdout, magic_stderr, magic_exit_code = None, None, 0 + # parse magic syntax first: e.g. jc -p ls -al magic_options = [] valid_command, run_command, magic_found_parser, magic_options = magic_parser(sys.argv) - if valid_command: - magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command) - if magic_stderr: - print(magic_stderr[:-1], file=sys.stderr) - elif run_command is None: - pass - else: - run_command_str = ' '.join(run_command) - jc.utils.error_message(f'parser not found for "{run_command_str}". Use "jc -h" for help.') - sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # set colors jc_colors = os.getenv('JC_COLORS') @@ -513,7 +502,7 @@ def main(): options = [] options.extend(magic_options) - # only find options if magic_parser did not find a command + # find options if magic_parser did not find a command if not valid_command: for opt in sys.argv: if opt.startswith('-') and not opt.startswith('--'): @@ -529,6 +518,9 @@ def main(): raw = 'r' in options version_info = 'v' in options + if verbose_debug: + jc.tracebackplus.enable(context=11) + if not pygments_installed: mono = True @@ -544,8 +536,36 @@ def main(): print(versiontext()) sys.exit(0) - if verbose_debug: - jc.tracebackplus.enable(context=11) + # if magic syntax used, try to run the command and error if it's not found, etc. + magic_stdout, magic_stderr, magic_exit_code = None, None, 0 + run_command_str = ' '.join(run_command) + + if valid_command: + try: + magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command) + if magic_stderr: + print(magic_stderr[:-1], file=sys.stderr) + + except FileNotFoundError: + if debug: + raise + else: + jc.utils.error_message(f'"{run_command_str}" command could not be found. For details use the -d or -dd option.') + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) + + except Exception: + if debug: + raise + else: + jc.utils.error_message(f'"{run_command_str}" command could not be run. For details use the -d or -dd option.') + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) + + elif run_command is None: + pass + + else: + jc.utils.error_message(f'parser not found for "{run_command_str}". Use "jc -h" for help.') + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) if sys.stdin.isatty() and magic_stdout is None: jc.utils.error_message('Missing piped data. Use "jc -h" for help.') @@ -587,8 +607,16 @@ def main(): sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # output the json - print(json_out(result, pretty=pretty, env_colors=jc_colors, mono=mono, piped_out=piped_output())) - sys.exit(combined_exit_code(magic_exit_code, 0)) + try: + print(json_out(result, pretty=pretty, env_colors=jc_colors, mono=mono, piped_out=piped_output())) + sys.exit(combined_exit_code(magic_exit_code, 0)) + + except Exception: + if debug: + raise + else: + jc.utils.error_message('There was an issue generating the JSON output. For details use the -d or -dd option.') + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) if __name__ == '__main__': From a6d983dd8f3871cbc2391c313340167829a575d9 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 13:38:08 -0700 Subject: [PATCH 38/60] move run_command_str variable --- jc/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jc/cli.py b/jc/cli.py index 88dcbdfb..c8e4aac6 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -538,9 +538,10 @@ def main(): # if magic syntax used, try to run the command and error if it's not found, etc. magic_stdout, magic_stderr, magic_exit_code = None, None, 0 - run_command_str = ' '.join(run_command) if valid_command: + run_command_str = ' '.join(run_command) + try: magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command) if magic_stderr: From a205afb6f355edbb4b98200a980b9601ed0f0658 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 13:44:15 -0700 Subject: [PATCH 39/60] set run_command_str if run_command is set --- jc/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index c8e4aac6..07e9d4b2 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -538,10 +538,10 @@ def main(): # if magic syntax used, try to run the command and error if it's not found, etc. magic_stdout, magic_stderr, magic_exit_code = None, None, 0 - - if valid_command: + if run_command: run_command_str = ' '.join(run_command) + if valid_command: try: magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command) if magic_stderr: From 3ed84f9f42e4e883ddaac28ab49d675600b49424 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 15:19:11 -0700 Subject: [PATCH 40/60] reorganize main function. remove pass condition. --- jc/cli.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 07e9d4b2..1aa9f5be 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -561,19 +561,13 @@ def main(): jc.utils.error_message(f'"{run_command_str}" command could not be run. For details use the -d or -dd option.') sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) - elif run_command is None: - pass + # elif run_command is None: + # pass - else: + elif run_command is not None: jc.utils.error_message(f'parser not found for "{run_command_str}". Use "jc -h" for help.') sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) - if sys.stdin.isatty() and magic_stdout is None: - jc.utils.error_message('Missing piped data. Use "jc -h" for help.') - sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) - - data = magic_stdout or sys.stdin.read() - # find the correct parser if magic_found_parser: parser = parser_module(magic_found_parser) @@ -595,6 +589,12 @@ def main(): sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # parse the data + if sys.stdin.isatty() and magic_stdout is None: + jc.utils.error_message('Missing piped data. Use "jc -h" for help.') + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) + + data = magic_stdout or sys.stdin.read() + try: result = parser.parse(data, raw=raw, quiet=quiet) From 070cac4ae12282458a1e1fc8618ebc473d23cacf Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 15:36:06 -0700 Subject: [PATCH 41/60] remove commented line --- jc/cli.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 1aa9f5be..37dcc102 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -561,9 +561,6 @@ def main(): jc.utils.error_message(f'"{run_command_str}" command could not be run. For details use the -d or -dd option.') sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) - # elif run_command is None: - # pass - elif run_command is not None: jc.utils.error_message(f'parser not found for "{run_command_str}". Use "jc -h" for help.') sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) From 796f61bfa417afdc6ab48fddbb5502cd0659b840 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Wed, 12 May 2021 17:01:09 -0700 Subject: [PATCH 42/60] handle case where the user pipes data and uses magic syntax simultaneously --- jc/cli.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 37dcc102..3504a624 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -585,11 +585,16 @@ def main(): jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.') sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) - # parse the data - if sys.stdin.isatty() and magic_stdout is None: + # check for input errors (pipe vs magic) + if not sys.stdin.isatty() and magic_stdout: + jc.utils.error_message('Piped data and Magic syntax used simultaneously. Use "jc -h" for help.') + sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) + + elif sys.stdin.isatty() and magic_stdout is None: jc.utils.error_message('Missing piped data. Use "jc -h" for help.') sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) + # parse the data data = magic_stdout or sys.stdin.read() try: @@ -613,7 +618,9 @@ def main(): if debug: raise else: - jc.utils.error_message('There was an issue generating the JSON output. For details use the -d or -dd option.') + jc.utils.error_message( + 'There was an issue generating the JSON output.\n' + ' For details use the -d or -dd option.') sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) From 83440ccb55d0db15b112b8b7d7352493ff516112 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Thu, 13 May 2021 08:02:38 -0700 Subject: [PATCH 43/60] error message capitalization --- jc/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 3504a624..d315ebb4 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -213,7 +213,7 @@ def set_env_colors(env_colors=None): # if there is an issue with the env variable, just set all colors to default and move on if input_error: - jc.utils.warning_message('could not parse JC_COLORS environment variable') + jc.utils.warning_message('Could not parse JC_COLORS environment variable') color_list = ['default', 'default', 'default', 'default'] # Try the color set in the JC_COLORS env variable first. If it is set to default, then fall back to default colors @@ -562,7 +562,7 @@ def main(): sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) elif run_command is not None: - jc.utils.error_message(f'parser not found for "{run_command_str}". Use "jc -h" for help.') + jc.utils.error_message(f'Parser not found for "{run_command_str}". Use "jc -h" for help.') sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # find the correct parser From ba963d98a0a2b3a0dfca6b211096d802253da5fe Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Thu, 13 May 2021 08:20:35 -0700 Subject: [PATCH 44/60] add exceptions module including ParseError --- jc/exceptions.py | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 jc/exceptions.py diff --git a/jc/exceptions.py b/jc/exceptions.py new file mode 100644 index 00000000..f7dd2571 --- /dev/null +++ b/jc/exceptions.py @@ -0,0 +1,5 @@ +"""jc - JSON CLI output utility exceptions""" + + +class ParseError(Exception): + pass From bb1439f0d53e87f636fba05fbd30c3c79fb16002 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Thu, 13 May 2021 08:20:58 -0700 Subject: [PATCH 45/60] use ParseError exception from jc.exceptions module --- jc/parsers/uname.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jc/parsers/uname.py b/jc/parsers/uname.py index 8fb2acab..f8c563fa 100644 --- a/jc/parsers/uname.py +++ b/jc/parsers/uname.py @@ -43,6 +43,7 @@ Example: } """ import jc.utils +from jc.exceptions import ParseError class info(): @@ -60,10 +61,6 @@ class info(): __version__ = info.version -class ParseError(Exception): - pass - - def _process(proc_data): """ Final processing to conform to the schema. From b1e95a60a2461dc401ffc299ef6338b65ef12691 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Thu, 13 May 2021 08:42:27 -0700 Subject: [PATCH 46/60] remove unnecessary comment --- jc/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jc/cli.py b/jc/cli.py index d315ebb4..13f8a51e 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -576,7 +576,6 @@ def main(): parser_name = parser_shortname(arg) if parser_name in parsers: - # load parser module just in time so we don't need to load all modules parser = parser_module(arg) found = True break From fd411fd77273cb7cf872c03ed6a258ccdee4d261 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 17:43:05 -0700 Subject: [PATCH 47/60] attempt to get colors working on windows --- jc/cli.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jc/cli.py b/jc/cli.py index 13f8a51e..d2bd4c74 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -491,6 +491,9 @@ def main(): except AttributeError: pass + # enable colors for Windows cmd.exe terminal + os.system('') + # parse magic syntax first: e.g. jc -p ls -al magic_options = [] valid_command, run_command, magic_found_parser, magic_options = magic_parser(sys.argv) @@ -562,7 +565,7 @@ def main(): sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) elif run_command is not None: - jc.utils.error_message(f'Parser not found for "{run_command_str}". Use "jc -h" for help.') + jc.utils.error_message(f'"{run_command_str}" cannot be used with Magic syntax. Use "jc -h" for help.') sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # find the correct parser From ab291b9eef6ed5c8ab59b5652f676178941d5ce5 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 17:48:40 -0700 Subject: [PATCH 48/60] only force enable colors when running on windows --- jc/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jc/cli.py b/jc/cli.py index d2bd4c74..2695fb0c 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -492,7 +492,8 @@ def main(): pass # enable colors for Windows cmd.exe terminal - os.system('') + if sys.platform.startswith('win32'): + os.system('') # parse magic syntax first: e.g. jc -p ls -al magic_options = [] From 5fdbe2962d84dad89de23f103d97b76b90450206 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 17:48:56 -0700 Subject: [PATCH 49/60] make dig compatible with all platforms --- jc/parsers/dig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jc/parsers/dig.py b/jc/parsers/dig.py index cac0cfcb..5c485a7a 100644 --- a/jc/parsers/dig.py +++ b/jc/parsers/dig.py @@ -322,7 +322,7 @@ class info(): author_email = 'kellyjonbrazil@gmail.com' # compatible options: linux, darwin, cygwin, win32, aix, freebsd - compatible = ['linux', 'aix', 'freebsd', 'darwin'] + compatible = ['linux', 'aix', 'freebsd', 'darwin', 'win32', 'cygwin'] magic_commands = ['dig'] From f475fe44df2fb2661c5bce4d1e602a4bba7e87c7 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 19:09:53 -0700 Subject: [PATCH 50/60] add new time format for systeminfo --- jc/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jc/utils.py b/jc/utils.py index c6ed4569..b7524089 100644 --- a/jc/utils.py +++ b/jc/utils.py @@ -254,6 +254,9 @@ class timestamp: {'id': 1500, 'format': '%Y-%m-%d %H:%M', 'locale': None}, # en_US.UTF-8 local format (found in who cli output): 2021-03-23 00:14 {'id': 1600, 'format': '%m/%d/%Y %I:%M %p', 'locale': None}, # Windows english format (found in dir cli output): 12/07/2019 02:09 AM {'id': 1700, 'format': '%m/%d/%Y, %I:%M:%S %p', 'locale': None}, # Windows english format wint non-UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC-0600) + + {'id': 1705, 'format': '%m/%d/%Y, %I:%M:%S %p %Z', 'locale': None}, # Windows english format with UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC) + {'id': 1710, 'format': '%m/%d/%Y, %I:%M:%S %p UTC%z', 'locale': None}, # Windows english format with UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC+0000) {'id': 2000, 'format': '%a %d %b %Y %I:%M:%S %p %Z', 'locale': None}, # en_US.UTF-8 local format (found in upower cli output): Tue 23 Mar 2021 04:12:11 PM UTC {'id': 3000, 'format': '%a %d %b %Y %I:%M:%S %p', 'locale': None}, # en_US.UTF-8 local format with non-UTC tz (found in upower cli output): Tue 23 Mar 2021 04:12:11 PM IST From f46b33eacf070b4dc72c8d8a66aae49abd149e5f Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 19:20:13 -0700 Subject: [PATCH 51/60] add windows time format --- jc/utils.py | 2 -- tests/test_utils.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jc/utils.py b/jc/utils.py index b7524089..cb8fc235 100644 --- a/jc/utils.py +++ b/jc/utils.py @@ -254,9 +254,7 @@ class timestamp: {'id': 1500, 'format': '%Y-%m-%d %H:%M', 'locale': None}, # en_US.UTF-8 local format (found in who cli output): 2021-03-23 00:14 {'id': 1600, 'format': '%m/%d/%Y %I:%M %p', 'locale': None}, # Windows english format (found in dir cli output): 12/07/2019 02:09 AM {'id': 1700, 'format': '%m/%d/%Y, %I:%M:%S %p', 'locale': None}, # Windows english format wint non-UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC-0600) - {'id': 1705, 'format': '%m/%d/%Y, %I:%M:%S %p %Z', 'locale': None}, # Windows english format with UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC) - {'id': 1710, 'format': '%m/%d/%Y, %I:%M:%S %p UTC%z', 'locale': None}, # Windows english format with UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC+0000) {'id': 2000, 'format': '%a %d %b %Y %I:%M:%S %p %Z', 'locale': None}, # en_US.UTF-8 local format (found in upower cli output): Tue 23 Mar 2021 04:12:11 PM UTC {'id': 3000, 'format': '%a %d %b %Y %I:%M:%S %p', 'locale': None}, # en_US.UTF-8 local format with non-UTC tz (found in upower cli output): Tue 23 Mar 2021 04:12:11 PM IST diff --git a/tests/test_utils.py b/tests/test_utils.py index 23e5a6db..bc1ea123 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -17,6 +17,8 @@ class MyTests(unittest.TestCase): # Windows english format wint non-UTC tz (found in systeminfo cli output) '3/22/2021, 1:15:51 PM (UTC-0600)': {'string': '3/22/2021, 1:15:51 PM (UTC-0600)', 'format': 1700, 'naive': 1616444151, 'utc': None}, # Windows english format with UTC tz (found in systeminfo cli output) + '3/22/2021, 1:15:51 PM (UTC)': {'string': '3/22/2021, 1:15:51 PM (UTC)', 'format': 1705, 'naive': 1616444151, 'utc': 1616418951}, + # Windows english format with UTC tz (found in systeminfo cli output) '3/22/2021, 1:15:51 PM (UTC+0000)': {'string': '3/22/2021, 1:15:51 PM (UTC+0000)', 'format': 1710, 'naive': 1616444151, 'utc': 1616418951}, # en_US.UTF-8 local format (found in upower cli output) 'Tue 23 Mar 2021 04:12:11 PM UTC': {'string': 'Tue 23 Mar 2021 04:12:11 PM UTC', 'format': 2000, 'naive': 1616541131, 'utc': 1616515931}, From e9bfc3dd29f914fd91e937f06d7f59c0f91b8449 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 19:21:37 -0700 Subject: [PATCH 52/60] add time format, dig compatibility, windows colors fix --- CHANGELOG | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 66c22ec0..7d7dea71 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,12 @@ jc changelog -20210511 v1.15.4 +20210517 v1.15.4 - Update ping parser to support error responses in OSX and BSD - Update ping parser to be more resillient against parsing errors for unknown error types - Update dig parser to support `+noall +answer` use case +- Update dig parser compatibility to all platforms +- Fix colors in Windows terminals (cmd.exe and PowerShell) +- Add Windows time format for systeminfo output - JC no longer swallows exit codes when using the "magic" syntax. See the Exit Codes section of the README and man page for details 20210426 v1.15.3 From 965717886e4ebaa6a4a494f189d9fdd01e33eb21 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 19:30:00 -0700 Subject: [PATCH 53/60] add exceptions module info --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 7d7dea71..8094f428 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ jc changelog - Update dig parser compatibility to all platforms - Fix colors in Windows terminals (cmd.exe and PowerShell) - Add Windows time format for systeminfo output +- Add exceptions module to standardize parser exceptions - JC no longer swallows exit codes when using the "magic" syntax. See the Exit Codes section of the README and man page for details 20210426 v1.15.3 From 9bf6facb0d7d76583802309253e7d13ba5148997 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 19:43:10 -0700 Subject: [PATCH 54/60] remove magic command capability since dir is a shell builtin --- jc/parsers/dir.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/jc/parsers/dir.py b/jc/parsers/dir.py index 851f93d7..ae1d2639 100644 --- a/jc/parsers/dir.py +++ b/jc/parsers/dir.py @@ -6,16 +6,14 @@ Options supported: - `/C, /-C` - `/S` +The "Magic" syntax is not supported since the `dir` command is a shell builtin. + The `epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on) Usage (cli): C:> dir | jc --dir - or - - C:> jc dir - Usage (module): import jc.parsers.dir @@ -121,14 +119,13 @@ import jc.utils class info(): """Provides parser metadata (version, author, etc.)""" - version = '1.2' + version = '1.3' description = '`dir` command parser' author = 'Rasheed Elsaleh' author_email = 'rasheed@rebelliondefense.com' # compatible options: win32 compatible = ['win32'] - magic_commands = ['dir'] __version__ = info.version From 038d4290248e6bb94762bb51f69e9958f7c1fecb Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 19:44:10 -0700 Subject: [PATCH 55/60] doc updates --- docs/parsers/dig.md | 2 +- docs/parsers/dir.md | 8 +++----- jc/man/jc.1.gz | Bin 2663 -> 2663 bytes man/jc.1.gz | Bin 2663 -> 2663 bytes 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/parsers/dig.md b/docs/parsers/dig.md index 3719323c..7b252b07 100644 --- a/docs/parsers/dig.md +++ b/docs/parsers/dig.md @@ -339,6 +339,6 @@ Returns: List of Dictionaries. Raw or processed structured data. ## Parser Information -Compatibility: linux, aix, freebsd, darwin +Compatibility: linux, aix, freebsd, darwin, win32, cygwin Version 2.1 by Kelly Brazil (kellyjonbrazil@gmail.com) diff --git a/docs/parsers/dir.md b/docs/parsers/dir.md index de4d2aad..ba3a19e8 100644 --- a/docs/parsers/dir.md +++ b/docs/parsers/dir.md @@ -9,16 +9,14 @@ Options supported: - `/C, /-C` - `/S` +The "Magic" syntax is not supported since the `dir` command is a shell builtin. + The `epoch` calculated timestamp field is naive (i.e. based on the local time of the system the parser is run on) Usage (cli): C:> dir | jc --dir - or - - C:> jc dir - Usage (module): import jc.parsers.dir @@ -145,4 +143,4 @@ Returns: ## Parser Information Compatibility: win32 -Version 1.2 by Rasheed Elsaleh (rasheed@rebelliondefense.com) +Version 1.3 by Rasheed Elsaleh (rasheed@rebelliondefense.com) diff --git a/jc/man/jc.1.gz b/jc/man/jc.1.gz index 413e8b30bd3bd06beaf40e60f046af5b4a325ead..b86a920ce3d7d1ea37be9d1b55f373e37ae39276 100644 GIT binary patch literal 2663 zcmV-t3YhgDiwFos*r8wo|7v3{F#w%eZExE+68^4VK?Dn20@!j=Tnk)qJ)G-gTkj@z z5WC$LZP8E?Z8MQXm89%waXnTpu=|*uu~(X?z(bbr#(iWvvHW5;FTTncmFCvtTs6z8+34*#FR&o8j&3@|Jy_-pt3- z$?SU&C=J4vGG?aAEq^=@P;>L1dG3H@UR=&bw_|#Rs9!RUEkL8-MheXeu8qu>OyRQArg3rVi#Iv?AzkqY<@AGd|TX*RHqU&kSR=yLJK2c8dj(C_oO zkl=#g4vD))dO;j}P^nRaLpz%F$1mdPn_Vu`!KDj_*TrFAls?FWphO)8#}B5=2u#!+ zMbaHn2idfO2#6e`qqhuoh#%tYm%F?zJljaS!l2Ui_uG?T*2p`WeR^I?03nI1LZM&59>#(_(NB4zR4a zSrHzI!?q*BFF8-{!P)U$mb}Il*n@=#7^1+A@MKR?EFqmPd|Wo84)xMlr7Oqc=ne*D ztl9d4h6g<1Gf!9Ak&Bk;^`L_#r<^>Vj1OopAQDpH?5zhKVmEyQTae?}utcocSNJDm z@txs|!`$~Oa>|f641J&#_NS-@pLkk|=O>YJtop39%7KaE@AA>7?RTeymIU*Z1f${y zVP7t!Ts$SE%%!D$`$d9ezECYGI6TZ|W#%GjP#QgmWIn(r-8@*C)gEt+8GhWd!&Iv| zG)3IHO}82O#n_DBygtM6pgEq55^21u&+R+GN!KW zdxxByd*2hq`n&^QsEx149N7hLzO5qxQ~L~?=lE)2Xja>Xk?f$F^d)N1?qx{O2YazF16DTxNBXzaxf9%uMAfUTKxp zClYN0)x^-WTZ_HB%V1DHB>NZKxlVIXZtM)08bmE;J+ySformO3Sco z!t)K#a&~$OYI;oBmCze$+_}LPMFJv$&{I0(_-iF{=r!@o;)w%}V-YSx1ml^6uTr5D zUm@>FudxWr=zUGid5cwr`^PK#%Uy^@D@|{y4VrtpuR4*>r2!n^6RTy^TbSBh-so%&nryr}6D{a(y|O2VYc4XKSVqv0PLvE+I^@ z*-UXwttF>O040BnevGD9)7u%U68Tzcm19a_Ypx}BP#BLdLo&-B=uorNE^9Lb8!;}{ z1Qvd`+5Dd#jyFmt%{VW!2-0FbbQ=6H2w-Vz zQO}oaagI}nQb@Pta~Nom)Z|hNk=KJtpjGuK0)uNB(gP;jdKgzc-%8+B!=mMKWp{MB zM=g!+!H!<{XivKrwY9-L*|WqwY}>P0N$YqZbYPr_1us)7po8D>)Y+~49;Ou|noQ(j z-fun&Y`+N_(rZnx=k4gxMDPH;{RO?S-ZQ`3z#V;@&8OG?Kz^R*?Q|Z_xJ%H3``96k+agm;cJR8wV04m>hEQZdoyl1uH58jlS4Vz^eRd`D z@^Jvg@*@SxZy%=DmxGWPv4hcKu%{ycri{naSqpz9Bh7U+7;bAgHgkuVPqF_cbLfnW z6L2*h4X=hbHy6YC@Y}chxc+OgkiV{;@(P}KL+P0(Ttn`!>mZ!yvLn07b#_o0n+@P&HMCL4o z>IRHpZ5vz+DX2v>?1)Xo``H=ciZ#%;yruE!NC{J>lvk8&+CJH z_~~*upJ6pb=7@9j6t2Zcz^tIM0yB(TjLxN3!^wNZP!x)1QZC)wjHsH~S=U0M`|x50 zBwEa|&Uo}&-o8)ir?B_HI=~=gsPgRyCo9|~+4e9UZ^uBIc*Jm~ipiynwpsqRMwdAj zh_wb&OLx3p3CV=Vb7(F8R=F_bj~N!LK9bmqufa^7#$`&Ih$x_(W!Q_rSI!EECXprv ziz#<^w7q(RfX`E`Rrx-gf(s-Y7tB9O8@q^IEaaoFxME)|2}H{^Y=Orzg#Qu8+RD4n zB^B$fuPZ*+H*Xh3Yi(hj2A%@S1LEccpj+1U(_q z@%{cx>$E3hRcoy8eW+1{nG8S9KTL0fKVgrq*gMVtmMJ@az|pP1C6EIW=**sIgCfA#UwoVArgR+X{H V4df|y4Xu+O{U4E!)DLDK006UqAMXGF literal 2663 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 VgFGd!pmhqP{{xJ(4Ju|I005hLAd>(9 diff --git a/man/jc.1.gz b/man/jc.1.gz index 413e8b30bd3bd06beaf40e60f046af5b4a325ead..b86a920ce3d7d1ea37be9d1b55f373e37ae39276 100644 GIT binary patch literal 2663 zcmV-t3YhgDiwFos*r8wo|7v3{F#w%eZExE+68^4VK?Dn20@!j=Tnk)qJ)G-gTkj@z z5WC$LZP8E?Z8MQXm89%waXnTpu=|*uu~(X?z(bbr#(iWvvHW5;FTTncmFCvtTs6z8+34*#FR&o8j&3@|Jy_-pt3- z$?SU&C=J4vGG?aAEq^=@P;>L1dG3H@UR=&bw_|#Rs9!RUEkL8-MheXeu8qu>OyRQArg3rVi#Iv?AzkqY<@AGd|TX*RHqU&kSR=yLJK2c8dj(C_oO zkl=#g4vD))dO;j}P^nRaLpz%F$1mdPn_Vu`!KDj_*TrFAls?FWphO)8#}B5=2u#!+ zMbaHn2idfO2#6e`qqhuoh#%tYm%F?zJljaS!l2Ui_uG?T*2p`WeR^I?03nI1LZM&59>#(_(NB4zR4a zSrHzI!?q*BFF8-{!P)U$mb}Il*n@=#7^1+A@MKR?EFqmPd|Wo84)xMlr7Oqc=ne*D ztl9d4h6g<1Gf!9Ak&Bk;^`L_#r<^>Vj1OopAQDpH?5zhKVmEyQTae?}utcocSNJDm z@txs|!`$~Oa>|f641J&#_NS-@pLkk|=O>YJtop39%7KaE@AA>7?RTeymIU*Z1f${y zVP7t!Ts$SE%%!D$`$d9ezECYGI6TZ|W#%GjP#QgmWIn(r-8@*C)gEt+8GhWd!&Iv| zG)3IHO}82O#n_DBygtM6pgEq55^21u&+R+GN!KW zdxxByd*2hq`n&^QsEx149N7hLzO5qxQ~L~?=lE)2Xja>Xk?f$F^d)N1?qx{O2YazF16DTxNBXzaxf9%uMAfUTKxp zClYN0)x^-WTZ_HB%V1DHB>NZKxlVIXZtM)08bmE;J+ySformO3Sco z!t)K#a&~$OYI;oBmCze$+_}LPMFJv$&{I0(_-iF{=r!@o;)w%}V-YSx1ml^6uTr5D zUm@>FudxWr=zUGid5cwr`^PK#%Uy^@D@|{y4VrtpuR4*>r2!n^6RTy^TbSBh-so%&nryr}6D{a(y|O2VYc4XKSVqv0PLvE+I^@ z*-UXwttF>O040BnevGD9)7u%U68Tzcm19a_Ypx}BP#BLdLo&-B=uorNE^9Lb8!;}{ z1Qvd`+5Dd#jyFmt%{VW!2-0FbbQ=6H2w-Vz zQO}oaagI}nQb@Pta~Nom)Z|hNk=KJtpjGuK0)uNB(gP;jdKgzc-%8+B!=mMKWp{MB zM=g!+!H!<{XivKrwY9-L*|WqwY}>P0N$YqZbYPr_1us)7po8D>)Y+~49;Ou|noQ(j z-fun&Y`+N_(rZnx=k4gxMDPH;{RO?S-ZQ`3z#V;@&8OG?Kz^R*?Q|Z_xJ%H3``96k+agm;cJR8wV04m>hEQZdoyl1uH58jlS4Vz^eRd`D z@^Jvg@*@SxZy%=DmxGWPv4hcKu%{ycri{naSqpz9Bh7U+7;bAgHgkuVPqF_cbLfnW z6L2*h4X=hbHy6YC@Y}chxc+OgkiV{;@(P}KL+P0(Ttn`!>mZ!yvLn07b#_o0n+@P&HMCL4o z>IRHpZ5vz+DX2v>?1)Xo``H=ciZ#%;yruE!NC{J>lvk8&+CJH z_~~*upJ6pb=7@9j6t2Zcz^tIM0yB(TjLxN3!^wNZP!x)1QZC)wjHsH~S=U0M`|x50 zBwEa|&Uo}&-o8)ir?B_HI=~=gsPgRyCo9|~+4e9UZ^uBIc*Jm~ipiynwpsqRMwdAj zh_wb&OLx3p3CV=Vb7(F8R=F_bj~N!LK9bmqufa^7#$`&Ih$x_(W!Q_rSI!EECXprv ziz#<^w7q(RfX`E`Rrx-gf(s-Y7tB9O8@q^IEaaoFxME)|2}H{^Y=Orzg#Qu8+RD4n zB^B$fuPZ*+H*Xh3Yi(hj2A%@S1LEccpj+1U(_q z@%{cx>$E3hRcoy8eW+1{nG8S9KTL0fKVgrq*gMVtmMJ@az|pP1C6EIW=**sIgCfA#UwoVArgR+X{H V4df|y4Xu+O{U4E!)DLDK006UqAMXGF literal 2663 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 VgFGd!pmhqP{{xJ(4Ju|I005hLAd>(9 From 9996c4fe23564dfb52e3c7dbdbd8bd67edfb1e95 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 19:55:53 -0700 Subject: [PATCH 56/60] update docs for shell builtins --- EXAMPLES.md | 4 ++-- docs/parsers/history.md | 2 ++ docs/parsers/jobs.md | 6 ++---- jc/man/jc.1.gz | Bin 2663 -> 2663 bytes jc/parsers/history.py | 2 ++ jc/parsers/jobs.py | 6 ++---- man/jc.1.gz | Bin 2663 -> 2663 bytes 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index 25279f31..5eb6e936 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -732,7 +732,7 @@ dig -x 1.1.1.1 | jc --dig -p # or: jc -p dig -x 1.1.1.1 ``` ### dir ```bash -dir | jc --dir -p # or: jc -p dir +dir | jc --dir -p ``` ```json [ @@ -1671,7 +1671,7 @@ iw dev wlan0 scan | jc --iw-scan -p # or: jc -p iw dev wlan0 scan ``` ### jobs ```bash -jobs -l | jc --jobs -p # or: jc -p jobs +jobs -l | jc --jobs -p ``` ```json [ diff --git a/docs/parsers/history.md b/docs/parsers/history.md index 81716d62..22eaaaec 100644 --- a/docs/parsers/history.md +++ b/docs/parsers/history.md @@ -5,6 +5,8 @@ jc - JSON CLI output utility `history` command output parser This parser will output a list of dictionaries each containing `line` and `command` keys. If you would like a simple dictionary output, then use the `-r` command-line option or the `raw=True` argument in the `parse()` function. +The "Magic" syntax is not supported since the `history` command is a shell builtin. + Usage (cli): $ history | jc --history diff --git a/docs/parsers/jobs.md b/docs/parsers/jobs.md index 80fff5e8..81cb5641 100644 --- a/docs/parsers/jobs.md +++ b/docs/parsers/jobs.md @@ -5,14 +5,12 @@ jc - JSON CLI output utility `jobs` command output parser Also supports the `-l` option. +The "Magic" syntax is not supported since the `jobs` command is a shell builtin. + Usage (cli): $ jobs | jc --jobs - or - - $ jc jobs - Usage (module): import jc.parsers.jobs diff --git a/jc/man/jc.1.gz b/jc/man/jc.1.gz index b86a920ce3d7d1ea37be9d1b55f373e37ae39276..391c1f87eb0378bacd9b2392c7fd293bb15df944 100644 GIT binary patch delta 15 WcmaDZ@?3;XzMF%C=k`XnL@od-Uj)Da delta 15 WcmaDZ@?3;XzMF%?^~OfFL@od;rUdo? diff --git a/jc/parsers/history.py b/jc/parsers/history.py index 24f9851d..88016928 100644 --- a/jc/parsers/history.py +++ b/jc/parsers/history.py @@ -2,6 +2,8 @@ This parser will output a list of dictionaries each containing `line` and `command` keys. If you would like a simple dictionary output, then use the `-r` command-line option or the `raw=True` argument in the `parse()` function. +The "Magic" syntax is not supported since the `history` command is a shell builtin. + Usage (cli): $ history | jc --history diff --git a/jc/parsers/jobs.py b/jc/parsers/jobs.py index b33154f5..a920d7bb 100644 --- a/jc/parsers/jobs.py +++ b/jc/parsers/jobs.py @@ -2,14 +2,12 @@ Also supports the `-l` option. +The "Magic" syntax is not supported since the `jobs` command is a shell builtin. + Usage (cli): $ jobs | jc --jobs - or - - $ jc jobs - Usage (module): import jc.parsers.jobs diff --git a/man/jc.1.gz b/man/jc.1.gz index b86a920ce3d7d1ea37be9d1b55f373e37ae39276..391c1f87eb0378bacd9b2392c7fd293bb15df944 100644 GIT binary patch delta 15 WcmaDZ@?3;XzMF%C=k`XnL@od-Uj)Da delta 15 WcmaDZ@?3;XzMF%?^~OfFL@od;rUdo? From fa5571486c3c19bb7e836f043545ea4f2509ed4d Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 20:33:03 -0700 Subject: [PATCH 57/60] simplify json_out function --- jc/cli.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 2695fb0c..e11b09b3 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -381,22 +381,22 @@ def versiontext(): def json_out(data, pretty=False, env_colors=None, mono=False, piped_out=False): """Return a JSON formatted string. String may include color codes or be pretty printed.""" separators = (',', ':') + indent = None + + if pretty: + separators = None + indent = 2 + if not mono and not piped_out: # set colors class JcStyle(Style): styles = set_env_colors(env_colors) - if pretty: - return str(highlight(json.dumps(data, indent=2, ensure_ascii=False), - JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1]) - else: - return str(highlight(json.dumps(data, separators=separators, ensure_ascii=False), - JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1]) + return str(highlight(json.dumps(data, indent=indent, separators=separators, ensure_ascii=False), + JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1]) + else: - if pretty: - return json.dumps(data, indent=2, ensure_ascii=False) - else: - return json.dumps(data, separators=separators, ensure_ascii=False) + return json.dumps(data, indent=indent, separators=separators, ensure_ascii=False) def magic_parser(args): From fc57bcfce24af23f077d6a73f1db4591d8f99c13 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 20:51:39 -0700 Subject: [PATCH 58/60] fix for when UTC is referenced as "Coordinated Universal Time" --- jc/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jc/utils.py b/jc/utils.py index cb8fc235..7974ef28 100644 --- a/jc/utils.py +++ b/jc/utils.py @@ -239,6 +239,9 @@ class timestamp: } utc_tz = False + # sometimes UTC is referenced as 'Coordinated Universal Time'. Convert to 'UTC' + data = data.replace('Coordinated Universal Time', 'UTC') + if 'UTC' in data: utc_tz = True if 'UTC+' in data or 'UTC-' in data: From 4d730a9de5a0d7c064a9a59e3add86e58d9ec492 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Sun, 16 May 2021 20:57:17 -0700 Subject: [PATCH 59/60] add UTC fix --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 8094f428..71ac17fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ jc changelog - Update dig parser to support `+noall +answer` use case - Update dig parser compatibility to all platforms - Fix colors in Windows terminals (cmd.exe and PowerShell) +- Fix epoch calculations when UTC is referenced as "Coordinated Universal Time" - Add Windows time format for systeminfo output - Add exceptions module to standardize parser exceptions - JC no longer swallows exit codes when using the "magic" syntax. See the Exit Codes section of the README and man page for details From 9c57c09c00e64a9031391b9b70083ad6ecf0bc0b Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 17 May 2021 08:24:31 -0700 Subject: [PATCH 60/60] doc update for release --- jc/man/jc.1.gz | Bin 2663 -> 2664 bytes man/jc.1.gz | Bin 2663 -> 2664 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/jc/man/jc.1.gz b/jc/man/jc.1.gz index 391c1f87eb0378bacd9b2392c7fd293bb15df944..967b30c97df1189b7f881a9149625a5fab41786c 100644 GIT binary patch literal 2664 zcmV-u3YYaCiwFpYilSfw|7v3{F#w%eZExE+68^4VK?Dn20@!j=Tz7H7^>D6}ZM~b= zLF{&0v_(Tnw9P~kRg$u!#r^o38B(%rBv#N5iHFiNFB}brGh{e_&+cP(#$KJiI_sal z?w|d}&cd_T;Xm2YAG7I%jjqN_m9{7?E3HhWt&W0l_MT0K*Ovise(f{F$%Pb#*_Eil zYQZv|SHy3XN)6+A!WJ?WeOnd6kdWEu$@FG6o&}@n_4ROa!TyKF+zfALm$&T8^kzPu zPG;YNKxq)Rlrb|^Zu#SRfSQ~4%yS1M^Wt(gx*gLiME#U;YylbtH&SR;aBXD1WHM(a z*0QiBL?d||3!qsc3z5J}6uZzWV_z30vhQ%ra%aVVX+ElB&#Oa4Qhz!{y%uTJBkm(|&XM;Hi+Ro>!Ab*$MHR{} zM4sRrz;2L-EM5-|BOK*xp{+r z+3`eKao)V(spJN{%7GzQmYK3B6skK3-8b`JG6>%WH{foe&3O>s&H{Klzrm;Af_|UJ zg#;G_cSzhd(hK6)gG!AW9NN*WKYkHU-|TXk4lZ3dye@FKL_p*i9ld3!L;MhDzue_*;n_yo6$X{Azu%q&vqs+0?BnxV5{MmAfW+ac zBnt;zbim}&;pDOls69}305h40SS8-jdep&GJS_XE;{cNN(+<4zb5b6LiJW|T2fv4b z=y5_K(FM(yLVGZh9dIqYWFR`ArjEUVuWPCt-0g;djXKy{|5WDFc*n%9#h9zRnzQ8{j zi*F2H9Ok}PkyD1mVdw*`us=mL_{h^zJU@w)8@Hbz;S(~F(P8I3z_=q{VpGU+J1LRXh|?nNiZsY z5ccIl%EePs%3NC7w_hYk<_p!5g2TgXR%R}e2Bp!1Nah24(#?aFS?%%GnBm7QJ505j zLsP`9+jN_eUyRN8*B40Hu7vHdEr%X-2r3sY+Tr&_<^!(Ji=uHg^1+zl1NH@|RL0b` zed~~ubMJeiSf6*`3$^hTnIpU4&9`;rVYu@_M}FGG4i$I6R?ai
kbQ3QRuzIr~r zRNR3+5BE{f`&|a%4MBZB>ifYKKSy33K^$OfdQ8o=>zPm5Fxo;A5tUGsQ=(=RKM{`~ z*f9YRK@C5&z>zXy3)m4vLA?@b;n-FT_b9aYyZ_uI!xxK*ip#8S@^{2giJ7Tf#Vf6{ z`b46Qpqdz(c5AVBm$`icLmTOjQVNT+f_6*`W7vl49Ckd@Mp9g9zF{o~wck_u12Z>x z8iA3nGcwvW=9izwb2ge@T+V`FUaSK~0Y-yApaMjXO8kqDVj_5PC|79Dl7u4!tIxSv+yTaV)}Rh+sUE@Kq|5 z;w$7m=`|K%8NIKmId4%*%`?!6D(IuR2AL-bleRu3K~W8F$mUR*gOe*n0Ob#e0GkLa zG$dsK=Ft4nLx42yNQg8#9kpV3eIJKGFoM{I4pS3mq0&@s&?523vjVmT?}KeEejd<5 z*v`6{*_+yXy&bFD&tb*FMgUek@s=Iu%70mCXr7aQ_EYlDNWOClL`*IGfS%{(Q3ukq znodu5b=}85F`I5Jdozk4u(y$jafEs?i@8-a`8d9vPOdK}^Wd{8>1@puB9@Da#U+F( zHk&D~skP)3383VU(f85xYI-|ERU%(Yt#V8$Y|XXA4hrM(Wk_cE108CX+GTBKU?axG zn!qB_7-4H{ehkiKHxOMufT(Mfy!rQos4(SyC!7D%!|_Jxq#5UB7C~C9hfaeZ1_3N> zE$aDlEzWTYQ3~mnd=3LGlA2sctz*MpqrKR(GZF(s53cBq=sTs>FUVOu+Oe! zUOo<>Sbn5H`Q`oe`f?BwBX%%a4EA&cz?AWLI&0ytWTd&S2E%O)$7b#j^C|YfWDcE? zaRRQUqv6%?=H_BJAAbFMAJ=~^7V_8CQ(nOnZzw(UglowCm0f{i=-A8D9WfX%c9#m6^jfv}Uf}rDv?^U z4?kWG=QFH^$Q*Hwp2D>l378dBR$zv4i_y9CYB+g^7>YviOvxMP`sVGTXss=*)4(&pkM~hy2wwJKKH5s~S7BJ>#nvS^6l2Ast)MJ(tN}RsNAQRTr z{qXwc3TpdVouX#`eid`?FA$hZw%KgLrBX|%H(-)3_F6YDtm#-N30~7J?XDEhfS@NN zI=~tFq7ej`TOZ@@F(oi6??1s-!f>-D4c|_y?E;BAuR0$Q~ z%G$!54+cwEjAeum2fJw46MS}<-h93tzk5H2QoX2Xhh@j{343*V_OCu(nzOc2$f`0H Wxq&>zuAz1EqyGa=U6BK39{>OZco)h5 literal 2663 zcmV-t3YhgDiwFn|+o50r|7v3{F#w%eZExE+68^4VK?Dn20@!j=Tnk)qJ)G-gTkj@z z5WC$LZP8E?Z8MQXm89%waXnTpu=|*uu~(X?z(bbr#(iWvvHW5;FTTncmFCvtTs6z8+34*#FR&o8j&3@|Jy_-pt3- z$?SU&C=J4vGG?aAEq^=@P;>L1dG3H@UR=&bw_|#Rs9!RUEkL8-MheXeu8qu>OyRQArg3rVi#Iv?AzkqY<@AGd|TX*RHqU&kSR=yLJK2c8dj(C_oO zkl=#g4vD))dO;j}P^nRaLpz%F$1mdPn_Vu`!KDj_*TrFAls?FWphO)8#}B5=2u#!+ zMbaHn2idfO2#6e`qqhuoh#%tYm%F?zJljaS!l2Ui_uG?T*2p`WeR^I?03nI1LZM&59>#(_(NB4zR4a zSrHzI!?q*BFF8-{!P)U$mb}Il*n@=#7^1+A@MKR?EFqmPd|Wo84)xMlr7Oqc=ne*D ztl9d4h6g<1Gf!9Ak&Bk;^`L_#r<^>Vj1OopAQDpH?5zhKVmEyQTae?}utcocSNJDm z@txs|!`$~Oa>|f641J&#_NS-@pLkk|=O>YJtop39%7KaE@AA>7?RTeymIU*Z1f${y zVP7t!Ts$SE%%!D$`$d9ezECYGI6TZ|W#%GjP#QgmWIn(r-8@*C)gEt+8GhWd!&Iv| zG)3IHO}82O#n_DBygtM6pgEq55^21u&+R+GN!KW zdxxByd*2hq`n&^QsEx149N7hLzO5qxQ~L~?=lE)2Xja>Xk?f$F^d)N1?qx{O2YazF16DTxNBXzaxf9%uMAfUTKxp zClYN0)x^-WTZ_HB%V1DHB>NZKxlVIXZtM)08bmE;J+ySformO3Sco z!t)K#a&~$OYI;oBmCze$+_}LPMFJv$&{I0(_-iF{=r!@o;)w%}V-YSx1ml^6uTr5D zUm@>FudxWr=zUGid5cwr`^PK#%Uy^@D@|{y4VrtpuR4*>r2!n^6RTy^TbSBh-so%&nryr}6D{a(y|O2VYc4XKSVqv0PLvE+I^@ z*-UXwttF>O040BnevGD9)7u%U68Tzcm19a_Ypx}BP#BLdLo&-B=uorNE^9Lb8!;}{ z1Qvd`+5Dd#jyFmt%{VW!2-0FbbQ=6H2w-Vz zQO}oaagI}nQb@Pta~Nom)Z|hNk=KJtpjGuK0)uNB(gP;jdKgzc-%8+B!=mMKWp{MB zM=g!+!H!<{XivKrwY9-L*|WqwY}>P0N$YqZbYPr_1us)7po8D>)Y+~49;Ou|noQ(j z-fun&Y`+N_(rZnx=k4gxMDPH;{RO?S-ZQ`3z#V;@&8OG?Kz^R*?Q|Z_xJ%H3``96k+agm;cJR8wV04m>hEQZdoyl1uH58jlS4Vz^eRd`D z@^Jvg@*@SxZy%=DmxGWPv4hcKu%{ycri{naSqpz9Bh7U+7;bAgHgkuVPqF_cbLfnW z6L2*h4X=hbHy6YC@Y}chxc+OgkiV{;@(P}KL+P0(Ttn`!>mZ!yvLn07b#_o0n+@P&HMCL4o z>IRHpZ5vz+DX2v>?1)Xo``H=ciZ#%;yruE!NC{J>lvk8&+CJH z_~~*upJ6pb=7@9j6t2Zcz^tIM0yB(TjLxN3!^wNZP!x)1QZC)wjHsH~S=U0M`|x50 zBwEa|&Uo}&-o8)ir?B_HI=~=gsPgRyCo9|~+4e9UZ^uBIc*Jm~ipiynwpsqRMwdAj zh_wb&OLx3p3CV=Vb7(F8R=F_bj~N!LK9bmqufa^7#$`&Ih$x_(W!Q_rSI!EECXprv ziz#<^w7q(RfX`E`Rrx-gf(s-Y7tB9O8@q^IEaaoFxME)|2}H{^Y=Orzg#Qu8+RD4n zB^B$fuPZ*+H*Xh3Yi(hj2A%@S1LEccpj+1U(_q z@%{cx>$E3hRcoy8eW+1{nG8S9KTL0fKVgrq*gMVtmMJ@az|pP1C6EIW=**sIgCfA#UwoVArgR+X{H V4df|y4Xu+O{U4E!)DLDK004r0AGrVk diff --git a/man/jc.1.gz b/man/jc.1.gz index 391c1f87eb0378bacd9b2392c7fd293bb15df944..967b30c97df1189b7f881a9149625a5fab41786c 100644 GIT binary patch literal 2664 zcmV-u3YYaCiwFpYilSfw|7v3{F#w%eZExE+68^4VK?Dn20@!j=Tz7H7^>D6}ZM~b= zLF{&0v_(Tnw9P~kRg$u!#r^o38B(%rBv#N5iHFiNFB}brGh{e_&+cP(#$KJiI_sal z?w|d}&cd_T;Xm2YAG7I%jjqN_m9{7?E3HhWt&W0l_MT0K*Ovise(f{F$%Pb#*_Eil zYQZv|SHy3XN)6+A!WJ?WeOnd6kdWEu$@FG6o&}@n_4ROa!TyKF+zfALm$&T8^kzPu zPG;YNKxq)Rlrb|^Zu#SRfSQ~4%yS1M^Wt(gx*gLiME#U;YylbtH&SR;aBXD1WHM(a z*0QiBL?d||3!qsc3z5J}6uZzWV_z30vhQ%ra%aVVX+ElB&#Oa4Qhz!{y%uTJBkm(|&XM;Hi+Ro>!Ab*$MHR{} zM4sRrz;2L-EM5-|BOK*xp{+r z+3`eKao)V(spJN{%7GzQmYK3B6skK3-8b`JG6>%WH{foe&3O>s&H{Klzrm;Af_|UJ zg#;G_cSzhd(hK6)gG!AW9NN*WKYkHU-|TXk4lZ3dye@FKL_p*i9ld3!L;MhDzue_*;n_yo6$X{Azu%q&vqs+0?BnxV5{MmAfW+ac zBnt;zbim}&;pDOls69}305h40SS8-jdep&GJS_XE;{cNN(+<4zb5b6LiJW|T2fv4b z=y5_K(FM(yLVGZh9dIqYWFR`ArjEUVuWPCt-0g;djXKy{|5WDFc*n%9#h9zRnzQ8{j zi*F2H9Ok}PkyD1mVdw*`us=mL_{h^zJU@w)8@Hbz;S(~F(P8I3z_=q{VpGU+J1LRXh|?nNiZsY z5ccIl%EePs%3NC7w_hYk<_p!5g2TgXR%R}e2Bp!1Nah24(#?aFS?%%GnBm7QJ505j zLsP`9+jN_eUyRN8*B40Hu7vHdEr%X-2r3sY+Tr&_<^!(Ji=uHg^1+zl1NH@|RL0b` zed~~ubMJeiSf6*`3$^hTnIpU4&9`;rVYu@_M}FGG4i$I6R?ai
kbQ3QRuzIr~r zRNR3+5BE{f`&|a%4MBZB>ifYKKSy33K^$OfdQ8o=>zPm5Fxo;A5tUGsQ=(=RKM{`~ z*f9YRK@C5&z>zXy3)m4vLA?@b;n-FT_b9aYyZ_uI!xxK*ip#8S@^{2giJ7Tf#Vf6{ z`b46Qpqdz(c5AVBm$`icLmTOjQVNT+f_6*`W7vl49Ckd@Mp9g9zF{o~wck_u12Z>x z8iA3nGcwvW=9izwb2ge@T+V`FUaSK~0Y-yApaMjXO8kqDVj_5PC|79Dl7u4!tIxSv+yTaV)}Rh+sUE@Kq|5 z;w$7m=`|K%8NIKmId4%*%`?!6D(IuR2AL-bleRu3K~W8F$mUR*gOe*n0Ob#e0GkLa zG$dsK=Ft4nLx42yNQg8#9kpV3eIJKGFoM{I4pS3mq0&@s&?523vjVmT?}KeEejd<5 z*v`6{*_+yXy&bFD&tb*FMgUek@s=Iu%70mCXr7aQ_EYlDNWOClL`*IGfS%{(Q3ukq znodu5b=}85F`I5Jdozk4u(y$jafEs?i@8-a`8d9vPOdK}^Wd{8>1@puB9@Da#U+F( zHk&D~skP)3383VU(f85xYI-|ERU%(Yt#V8$Y|XXA4hrM(Wk_cE108CX+GTBKU?axG zn!qB_7-4H{ehkiKHxOMufT(Mfy!rQos4(SyC!7D%!|_Jxq#5UB7C~C9hfaeZ1_3N> zE$aDlEzWTYQ3~mnd=3LGlA2sctz*MpqrKR(GZF(s53cBq=sTs>FUVOu+Oe! zUOo<>Sbn5H`Q`oe`f?BwBX%%a4EA&cz?AWLI&0ytWTd&S2E%O)$7b#j^C|YfWDcE? zaRRQUqv6%?=H_BJAAbFMAJ=~^7V_8CQ(nOnZzw(UglowCm0f{i=-A8D9WfX%c9#m6^jfv}Uf}rDv?^U z4?kWG=QFH^$Q*Hwp2D>l378dBR$zv4i_y9CYB+g^7>YviOvxMP`sVGTXss=*)4(&pkM~hy2wwJKKH5s~S7BJ>#nvS^6l2Ast)MJ(tN}RsNAQRTr z{qXwc3TpdVouX#`eid`?FA$hZw%KgLrBX|%H(-)3_F6YDtm#-N30~7J?XDEhfS@NN zI=~tFq7ej`TOZ@@F(oi6??1s-!f>-D4c|_y?E;BAuR0$Q~ z%G$!54+cwEjAeum2fJw46MS}<-h93tzk5H2QoX2Xhh@j{343*V_OCu(nzOc2$f`0H Wxq&>zuAz1EqyGa=U6BK39{>OZco)h5 literal 2663 zcmV-t3YhgDiwFn|+o50r|7v3{F#w%eZExE+68^4VK?Dn20@!j=Tnk)qJ)G-gTkj@z z5WC$LZP8E?Z8MQXm89%waXnTpu=|*uu~(X?z(bbr#(iWvvHW5;FTTncmFCvtTs6z8+34*#FR&o8j&3@|Jy_-pt3- z$?SU&C=J4vGG?aAEq^=@P;>L1dG3H@UR=&bw_|#Rs9!RUEkL8-MheXeu8qu>OyRQArg3rVi#Iv?AzkqY<@AGd|TX*RHqU&kSR=yLJK2c8dj(C_oO zkl=#g4vD))dO;j}P^nRaLpz%F$1mdPn_Vu`!KDj_*TrFAls?FWphO)8#}B5=2u#!+ zMbaHn2idfO2#6e`qqhuoh#%tYm%F?zJljaS!l2Ui_uG?T*2p`WeR^I?03nI1LZM&59>#(_(NB4zR4a zSrHzI!?q*BFF8-{!P)U$mb}Il*n@=#7^1+A@MKR?EFqmPd|Wo84)xMlr7Oqc=ne*D ztl9d4h6g<1Gf!9Ak&Bk;^`L_#r<^>Vj1OopAQDpH?5zhKVmEyQTae?}utcocSNJDm z@txs|!`$~Oa>|f641J&#_NS-@pLkk|=O>YJtop39%7KaE@AA>7?RTeymIU*Z1f${y zVP7t!Ts$SE%%!D$`$d9ezECYGI6TZ|W#%GjP#QgmWIn(r-8@*C)gEt+8GhWd!&Iv| zG)3IHO}82O#n_DBygtM6pgEq55^21u&+R+GN!KW zdxxByd*2hq`n&^QsEx149N7hLzO5qxQ~L~?=lE)2Xja>Xk?f$F^d)N1?qx{O2YazF16DTxNBXzaxf9%uMAfUTKxp zClYN0)x^-WTZ_HB%V1DHB>NZKxlVIXZtM)08bmE;J+ySformO3Sco z!t)K#a&~$OYI;oBmCze$+_}LPMFJv$&{I0(_-iF{=r!@o;)w%}V-YSx1ml^6uTr5D zUm@>FudxWr=zUGid5cwr`^PK#%Uy^@D@|{y4VrtpuR4*>r2!n^6RTy^TbSBh-so%&nryr}6D{a(y|O2VYc4XKSVqv0PLvE+I^@ z*-UXwttF>O040BnevGD9)7u%U68Tzcm19a_Ypx}BP#BLdLo&-B=uorNE^9Lb8!;}{ z1Qvd`+5Dd#jyFmt%{VW!2-0FbbQ=6H2w-Vz zQO}oaagI}nQb@Pta~Nom)Z|hNk=KJtpjGuK0)uNB(gP;jdKgzc-%8+B!=mMKWp{MB zM=g!+!H!<{XivKrwY9-L*|WqwY}>P0N$YqZbYPr_1us)7po8D>)Y+~49;Ou|noQ(j z-fun&Y`+N_(rZnx=k4gxMDPH;{RO?S-ZQ`3z#V;@&8OG?Kz^R*?Q|Z_xJ%H3``96k+agm;cJR8wV04m>hEQZdoyl1uH58jlS4Vz^eRd`D z@^Jvg@*@SxZy%=DmxGWPv4hcKu%{ycri{naSqpz9Bh7U+7;bAgHgkuVPqF_cbLfnW z6L2*h4X=hbHy6YC@Y}chxc+OgkiV{;@(P}KL+P0(Ttn`!>mZ!yvLn07b#_o0n+@P&HMCL4o z>IRHpZ5vz+DX2v>?1)Xo``H=ciZ#%;yruE!NC{J>lvk8&+CJH z_~~*upJ6pb=7@9j6t2Zcz^tIM0yB(TjLxN3!^wNZP!x)1QZC)wjHsH~S=U0M`|x50 zBwEa|&Uo}&-o8)ir?B_HI=~=gsPgRyCo9|~+4e9UZ^uBIc*Jm~ipiynwpsqRMwdAj zh_wb&OLx3p3CV=Vb7(F8R=F_bj~N!LK9bmqufa^7#$`&Ih$x_(W!Q_rSI!EECXprv ziz#<^w7q(RfX`E`Rrx-gf(s-Y7tB9O8@q^IEaaoFxME)|2}H{^Y=Orzg#Qu8+RD4n zB^B$fuPZ*+H*Xh3Yi(hj2A%@S1LEccpj+1U(_q z@%{cx>$E3hRcoy8eW+1{nG8S9KTL0fKVgrq*gMVtmMJ@az|pP1C6EIW=**sIgCfA#UwoVArgR+X{H V4df|y4Xu+O{U4E!)DLDK004r0AGrVk