1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2025-06-19 00:17:51 +02:00

Merge pull request #138 from kellyjonbrazil/dev

Dev v1.15.4
This commit is contained in:
Kelly Brazil
2021-05-17 08:35:13 -07:00
committed by GitHub
39 changed files with 758 additions and 192 deletions

View File

@ -1,5 +1,16 @@
jc changelog
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)
- 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
20210426 v1.15.3
- Add ufw status command parser tested on linux
- Add ufw-appinfo command parser tested on linux

View File

@ -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
[

View File

@ -203,6 +203,18 @@ 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 determine if an error was from the parsed program or `jc`.
Consider the following examples using `ifconfig`:
| `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:
```bash

View File

@ -3,6 +3,10 @@
# jc.parsers.dig
jc - JSON CLI output utility `dig` command output parser
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)
The `when_epoch_utc` calculated timestamp field is timezone-aware and is only available if the timezone field is UTC.
@ -274,6 +278,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
@ -299,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.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 2.1 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -38,14 +38,26 @@ Schema:
"round_trip_ms_stddev": float,
"responses": [
{
"type": string, # ('reply' or 'timeout')
"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,
"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 +181,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)

View File

@ -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'

236
jc/cli.py
View File

@ -10,9 +10,13 @@ import shlex
import importlib
import textwrap
import signal
import subprocess
import json
import jc
import jc.appdirs as appdirs
import jc.utils
import jc.tracebackplus
# make pygments import optional
try:
import pygments
@ -114,6 +118,9 @@ parsers = [
'yaml'
]
JC_ERROR_EXIT = 100
# List of custom or override parsers.
# Allow any <user_data_dir>/jc/jcparsers/*.py
local_parsers = []
@ -186,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
@ -207,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
@ -221,15 +227,12 @@ 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):
"""Exit with error on SIGINT"""
sys.exit(1)
sys.exit(JC_ERROR_EXIT)
def parser_shortname(parser_argument):
@ -377,33 +380,38 @@ 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=(',', ':'), 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=(',', ':'), ensure_ascii=False)
return json.dumps(data, indent=indent, separators=separators, ensure_ascii=False)
def generate_magic_command(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.
def magic_parser(args):
"""
Parse command arguments for magic syntax: jc -p ls -al
# Parse with 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
"""
# bail immediately if there are no args or a parser is defined
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 +421,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('-'):
@ -425,11 +433,11 @@ def generate_magic_command(args):
# 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'
# all options popped and no command found - for case like 'jc -x'
if len(args_given) == 0:
return False, None
return False, None, None, []
magic_dict = {}
parser_info = about_jc()['parsers']
@ -446,31 +454,34 @@ 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
return (
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
)
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 combined_exit_code(program_exit=0, jc_exit=0):
exit_code = program_exit + jc_exit
if exit_code > 255:
exit_code = 255
return exit_code
def main():
import jc.utils
# break on ctrl-c keyboard interrupt
signal.signal(signal.SIGINT, ctrlc)
@ -480,17 +491,26 @@ def main():
except AttributeError:
pass
# try magic syntax first: e.g. jc -p ls -al
magic()
# enable colors for Windows cmd.exe terminal
if sys.platform.startswith('win32'):
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)
# set colors
jc_colors = os.getenv('JC_COLORS')
# set options
options = []
options.extend(magic_options)
# options
for opt in sys.argv:
if opt.startswith('-') and not opt.startswith('--'):
options.extend(opt[1:])
# 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
@ -502,6 +522,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
@ -517,44 +540,91 @@ def main():
print(versiontext())
sys.exit(0)
if verbose_debug:
import jc.tracebackplus
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
if run_command:
run_command_str = ' '.join(run_command)
if sys.stdin.isatty():
jc.utils.error_message('Missing piped data. Use "jc -h" for help.')
sys.exit(1)
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)
data = sys.stdin.read()
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))
found = False
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))
for arg in sys.argv:
parser_name = parser_shortname(arg)
elif run_command is not None:
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))
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)
# find the correct parser
if magic_found_parser:
parser = parser_module(magic_found_parser)
parser_name = parser_shortname(magic_found_parser)
else:
found = False
for arg in sys.argv:
parser_name = parser_shortname(arg)
if parser_name in parsers:
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(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
if not found:
jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.')
sys.exit(1)
# 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))
print(json_out(result, pretty=pretty, env_colors=jc_colors, mono=mono, piped_out=piped_output()))
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:
result = parser.parse(data, raw=raw, quiet=quiet)
except Exception:
if debug:
raise
else:
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(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
# output the json
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.\n'
' For details use the -d or -dd option.')
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
if __name__ == '__main__':

5
jc/exceptions.py Normal file
View File

@ -0,0 +1,5 @@
"""jc - JSON CLI output utility exceptions"""
class ParseError(Exception):
pass

Binary file not shown.

View File

@ -1,5 +1,9 @@
"""jc - JSON CLI output utility `dig` command output parser
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)
The `when_epoch_utc` calculated timestamp field is timezone-aware and is only available if the timezone field is UTC.
@ -270,19 +274,55 @@ 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'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux', 'aix', 'freebsd', 'darwin']
compatible = ['linux', 'aix', 'freebsd', 'darwin', 'win32', 'cygwin']
magic_commands = ['dig']
@ -500,6 +540,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 +622,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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -35,14 +35,26 @@ Schema:
"round_trip_ms_stddev": float,
"responses": [
{
"type": string, # ('reply' or 'timeout')
"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,
"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,12 +152,13 @@ Examples:
}
"""
import string
import ipaddress
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'
@ -170,7 +183,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 +206,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 = []
@ -302,36 +364,41 @@ def _linux_parse(data):
continue
# normal responses
else:
elif ' bytes from ' in line:
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',
'unparsed_line': line
}
ping_responses.append(response)
continue
@ -346,6 +413,7 @@ def _bsd_parse(data):
ping_responses = []
pattern = None
footer = False
ping_error = False
linedata = data.splitlines()
@ -419,7 +487,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,9 +498,80 @@ def _bsd_parse(data):
ping_responses.append(response)
continue
# catch error responses
err = _error_type(line)
if err:
response = {
'type': err
}
try:
response['bytes'] = line.split()[0]
response['response_ip'] = line.split()[4].strip(':').strip('(').strip(')')
except Exception:
pass
ping_error = True
continue
if ping_error:
if line.startswith('Vr'):
continue
else:
error_line = line.split()
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
else:
line = line.replace(':', ' ').replace('=', ' ')
elif ' bytes from ' in line:
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',
'unparsed_line': line
}
ping_responses.append(response)
continue
# ipv6 lines
elif ' bytes from ' in line:
try:
line = line.replace(',', ' ').replace('=', ' ')
response = {
'type': 'reply',
'bytes': line.split()[0],
@ -441,20 +580,12 @@ def _bsd_parse(data):
'ttl': line.split()[7],
'time_ms': line.split()[9]
}
ping_responses.append(response)
continue
except Exception:
response = {
'type': 'unparsable_line',
'unparsed_line': 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
@ -462,8 +593,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

View File

@ -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.

View File

@ -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:
@ -254,6 +257,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

Binary file not shown.

View File

@ -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.',

View File

@ -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 \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 = \fB0\fP, jc exit code = \fB0\fP, combined exit code = \fB0\fP (no errors)
ifconfig exit code = \fB1\fP, jc exit code = \fB0\fP, combined exit code = \fB1\fP (error in ifconfig)
ifconfig exit code = \fB0\fP, jc exit code = \fB100\fP, combined exit code = \fB100\fP (error in 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
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:

View File

@ -131,6 +131,18 @@ 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 determine if an error was from the parsed program or `jc`.
Consider the following examples using `ifconfig`:
| `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:
```bash

View File

@ -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}]}

View File

@ -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

View File

@ -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}]}

View File

@ -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

View File

@ -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"}]}]

View File

@ -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

View File

@ -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}]}

View File

@ -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

View File

@ -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}]}

View File

@ -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

View File

@ -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}]}

View File

@ -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

View File

@ -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.'):

View File

@ -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)

View File

@ -30,6 +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()
@ -45,6 +48,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()
@ -157,6 +163,12 @@ 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/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()
@ -175,6 +187,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()
@ -209,6 +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())
@ -224,6 +242,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())
@ -336,6 +357,12 @@ 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/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())
@ -354,6 +381,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())
@ -409,6 +439,12 @@ 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 <ip> -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 <ip> -O -D -p' on Centos 7.7
@ -439,6 +475,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 <ip> -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 <ip> -O' on Ubuntu 18.4
@ -651,10 +693,22 @@ class MyTests(unittest.TestCase):
def test_ping_ip_osx_10_14_6(self):
"""
Test 'ping6 <ip>' on osx 10.14.6
Test 'ping <ip>' 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 <ip>' 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_ping_ip_unknown_errors_osx_10_14_6(self):
"""
Test 'ping <ip>' 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 <hostname> -p' on osx 10.14.6
@ -691,6 +745,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 <ip>' 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 <ip>' to broadcast IP to get duplicate replies on osx 10.14.6

View File

@ -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},