2019-11-07 08:04:07 -08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""jc - JSON CLI output utility
|
|
|
|
JC cli module
|
|
|
|
"""
|
|
|
|
import sys
|
2020-02-11 12:16:23 -08:00
|
|
|
import os
|
2020-02-05 10:55:08 -08:00
|
|
|
import importlib
|
2019-11-07 08:04:07 -08:00
|
|
|
import textwrap
|
|
|
|
import signal
|
|
|
|
import json
|
2019-11-11 16:16:41 -08:00
|
|
|
import jc.utils
|
2020-02-05 10:55:08 -08:00
|
|
|
|
2020-02-05 22:26:47 -08:00
|
|
|
|
|
|
|
class info():
|
2020-02-11 18:08:37 -08:00
|
|
|
version = '1.7.3'
|
2020-02-05 22:26:47 -08:00
|
|
|
description = 'jc cli output JSON conversion tool'
|
|
|
|
author = 'Kelly Brazil'
|
|
|
|
author_email = 'kellyjonbrazil@gmail.com'
|
|
|
|
|
|
|
|
|
|
|
|
__version__ = info.version
|
|
|
|
|
2020-02-11 18:08:37 -08:00
|
|
|
parsers = [
|
|
|
|
'arp',
|
|
|
|
'crontab',
|
|
|
|
'crontab-u',
|
|
|
|
'df',
|
|
|
|
'dig',
|
|
|
|
'du',
|
|
|
|
'env',
|
|
|
|
'free',
|
|
|
|
'fstab',
|
|
|
|
'history',
|
|
|
|
'hosts',
|
|
|
|
'id',
|
|
|
|
'ifconfig',
|
|
|
|
'ini',
|
|
|
|
'iptables',
|
|
|
|
'jobs',
|
|
|
|
'ls',
|
|
|
|
'lsblk',
|
|
|
|
'lsmod',
|
|
|
|
'lsof',
|
|
|
|
'mount',
|
|
|
|
'netstat',
|
|
|
|
'pip-list',
|
|
|
|
'pip-show',
|
|
|
|
'ps',
|
|
|
|
'route',
|
|
|
|
'ss',
|
|
|
|
'stat',
|
|
|
|
'systemctl',
|
|
|
|
'systemctl-lj',
|
|
|
|
'systemctl-ls',
|
|
|
|
'systemctl-luf',
|
|
|
|
'uname',
|
|
|
|
'uptime',
|
|
|
|
'w',
|
|
|
|
'xml',
|
|
|
|
'yaml'
|
|
|
|
]
|
2020-02-05 10:55:08 -08:00
|
|
|
|
|
|
|
|
2020-02-05 22:26:47 -08:00
|
|
|
def ctrlc(signum, frame):
|
2020-02-11 12:16:23 -08:00
|
|
|
"""exit with error on SIGINT"""
|
2020-02-05 22:26:47 -08:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
2020-02-05 10:55:08 -08:00
|
|
|
def parser_shortname(parser_argument):
|
2020-02-05 13:50:12 -08:00
|
|
|
"""short name of the parser with dashes and no -- prefix"""
|
2020-02-05 10:55:08 -08:00
|
|
|
return parser_argument[2:]
|
|
|
|
|
|
|
|
|
|
|
|
def parser_argument(parser):
|
2020-02-05 13:50:12 -08:00
|
|
|
"""short name of the parser with dashes and with -- prefix"""
|
2020-02-05 10:55:08 -08:00
|
|
|
return f'--{parser}'
|
|
|
|
|
|
|
|
|
|
|
|
def parser_mod_shortname(parser):
|
2020-02-05 13:50:12 -08:00
|
|
|
"""short name of the parser's module name (no -- prefix and dashes converted to underscores)"""
|
2020-02-05 10:55:08 -08:00
|
|
|
return parser.replace('--', '').replace('-', '_')
|
|
|
|
|
|
|
|
|
|
|
|
def parser_module(parser):
|
2020-02-13 10:03:11 -05:00
|
|
|
"""import the module just in time and return the module object"""
|
2020-02-05 10:55:08 -08:00
|
|
|
importlib.import_module('jc.parsers.' + parser_mod_shortname(parser))
|
|
|
|
return getattr(jc.parsers, parser_mod_shortname(parser))
|
2019-12-13 20:01:51 -08:00
|
|
|
|
|
|
|
|
2020-02-05 13:57:34 -08:00
|
|
|
def parsers_text(indent=0, pad=0):
|
2019-12-13 20:01:51 -08:00
|
|
|
ptext = ''
|
2020-02-11 18:08:37 -08:00
|
|
|
for parser in parsers:
|
2020-02-05 10:55:08 -08:00
|
|
|
parser_arg = parser_argument(parser)
|
|
|
|
parser_mod = parser_module(parser)
|
|
|
|
|
|
|
|
if hasattr(parser_mod, 'info'):
|
2020-02-05 14:10:22 -08:00
|
|
|
parser_desc = parser_mod.info.description
|
2020-02-05 11:08:47 -08:00
|
|
|
padding = pad - len(parser_arg)
|
2019-12-13 20:01:51 -08:00
|
|
|
padding_char = ' '
|
2020-02-05 13:57:34 -08:00
|
|
|
indent_text = padding_char * indent
|
2019-12-13 20:01:51 -08:00
|
|
|
padding_text = padding_char * padding
|
2020-02-05 13:57:34 -08:00
|
|
|
ptext += indent_text + parser_arg + padding_text + parser_desc + '\n'
|
2019-12-13 20:01:51 -08:00
|
|
|
|
|
|
|
return ptext
|
|
|
|
|
|
|
|
|
2019-12-14 23:15:15 -08:00
|
|
|
def about_jc():
|
|
|
|
parser_list = []
|
2020-02-05 10:55:08 -08:00
|
|
|
|
2020-02-11 18:08:37 -08:00
|
|
|
for parser in parsers:
|
2020-02-05 10:55:08 -08:00
|
|
|
parser_mod = parser_module(parser)
|
|
|
|
|
|
|
|
if hasattr(parser_mod, 'info'):
|
2019-12-16 09:00:16 -08:00
|
|
|
info_dict = {}
|
2020-02-05 14:10:22 -08:00
|
|
|
info_dict['name'] = parser_mod.__name__.split('.')[-1]
|
2020-02-05 10:55:08 -08:00
|
|
|
info_dict['argument'] = parser_argument(parser)
|
2020-02-05 14:10:22 -08:00
|
|
|
parser_entry = vars(parser_mod.info)
|
2020-02-05 10:55:08 -08:00
|
|
|
|
2019-12-16 09:00:16 -08:00
|
|
|
for k, v in parser_entry.items():
|
|
|
|
if not k.startswith('__'):
|
|
|
|
info_dict[k] = v
|
2020-02-05 10:55:08 -08:00
|
|
|
|
2019-12-16 09:00:16 -08:00
|
|
|
parser_list.append(info_dict)
|
2019-12-14 23:15:15 -08:00
|
|
|
|
2019-12-14 23:56:22 -08:00
|
|
|
return {
|
2019-12-16 09:08:47 -08:00
|
|
|
'name': 'jc',
|
2019-12-14 23:15:15 -08:00
|
|
|
'version': info.version,
|
|
|
|
'description': info.description,
|
|
|
|
'author': info.author,
|
|
|
|
'author_email': info.author_email,
|
2019-12-16 11:52:18 -08:00
|
|
|
'parser_count': len(parser_list),
|
2019-12-14 23:15:15 -08:00
|
|
|
'parsers': parser_list
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-11-07 08:04:07 -08:00
|
|
|
def helptext(message):
|
2020-02-05 13:57:34 -08:00
|
|
|
parsers_string = parsers_text(indent=12, pad=17)
|
2019-12-13 20:01:51 -08:00
|
|
|
|
2019-11-07 08:04:07 -08:00
|
|
|
helptext_string = f'''
|
|
|
|
jc: {message}
|
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
Usage: COMMAND | jc PARSER [OPTIONS]
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2020-02-11 19:14:51 -08:00
|
|
|
or
|
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
COMMAND | jc [OPTIONS] PARSER
|
2020-02-11 19:14:51 -08:00
|
|
|
|
2020-02-11 18:08:37 -08:00
|
|
|
or magic syntax:
|
|
|
|
|
2020-02-11 19:14:51 -08:00
|
|
|
jc [OPTIONS] COMMAND
|
2020-02-11 18:08:37 -08:00
|
|
|
|
2019-11-07 08:04:07 -08:00
|
|
|
Parsers:
|
2019-12-13 20:01:51 -08:00
|
|
|
{parsers_string}
|
2019-11-07 08:04:07 -08:00
|
|
|
Options:
|
2020-02-05 11:08:47 -08:00
|
|
|
-a about jc
|
|
|
|
-d debug - show trace messages
|
|
|
|
-p pretty print output
|
|
|
|
-q quiet - suppress warnings
|
|
|
|
-r raw JSON output
|
2019-11-07 08:04:07 -08:00
|
|
|
|
|
|
|
Example:
|
|
|
|
ls -al | jc --ls -p
|
2020-02-11 18:08:37 -08:00
|
|
|
|
|
|
|
or using the magic syntax:
|
|
|
|
|
2020-02-11 19:14:51 -08:00
|
|
|
jc -p ls -al
|
2019-11-07 08:04:07 -08:00
|
|
|
'''
|
|
|
|
print(textwrap.dedent(helptext_string), file=sys.stderr)
|
|
|
|
|
|
|
|
|
2019-12-14 23:15:15 -08:00
|
|
|
def json_out(data, pretty=False):
|
|
|
|
if pretty:
|
|
|
|
print(json.dumps(data, indent=2))
|
|
|
|
else:
|
|
|
|
print(json.dumps(data))
|
|
|
|
|
|
|
|
|
2020-02-11 12:16:23 -08:00
|
|
|
def magic():
|
2020-02-11 19:14:51 -08:00
|
|
|
"""Parse with magic syntax: jc -p ls -al"""
|
|
|
|
if len(sys.argv) > 1 and not sys.argv[1].startswith('--'):
|
2020-02-11 18:08:37 -08:00
|
|
|
parser_info = about_jc()['parsers']
|
2020-02-13 09:47:16 -05:00
|
|
|
# how can i get the literal text of the command entered instead of the argument list?
|
2020-02-11 12:16:23 -08:00
|
|
|
args_given = sys.argv[1:]
|
2020-02-11 19:14:51 -08:00
|
|
|
options = []
|
2020-02-11 12:16:23 -08:00
|
|
|
found_parser = None
|
|
|
|
|
2020-02-11 19:14:51 -08:00
|
|
|
# find the options
|
|
|
|
if args_given[0].startswith('-'):
|
|
|
|
p = 0
|
|
|
|
for i, arg in list(enumerate(args_given)):
|
2020-02-13 09:47:16 -05:00
|
|
|
# parser found
|
2020-02-11 19:14:51 -08:00
|
|
|
if arg.startswith('--'):
|
|
|
|
return
|
2020-02-13 09:47:16 -05:00
|
|
|
# option found
|
2020-02-11 19:14:51 -08:00
|
|
|
elif arg.startswith('-'):
|
2020-02-13 09:47:16 -05:00
|
|
|
options.append(args_given.pop(i - p)[1:])
|
2020-02-11 19:14:51 -08:00
|
|
|
p = p + 1
|
2020-02-13 09:47:16 -05:00
|
|
|
# command found
|
2020-02-11 19:14:51 -08:00
|
|
|
else:
|
|
|
|
break
|
|
|
|
|
|
|
|
# find the command and parser
|
2020-02-11 18:08:37 -08:00
|
|
|
for parser in parser_info:
|
|
|
|
if 'magic_commands' in parser:
|
|
|
|
for magic_command in parser['magic_commands']:
|
2020-02-12 00:11:48 -05:00
|
|
|
try:
|
|
|
|
if ' '.join(args_given[0:2]) == magic_command:
|
|
|
|
found_parser = parser['argument']
|
|
|
|
break
|
|
|
|
elif ''.join(args_given[0]) == magic_command:
|
|
|
|
found_parser = parser['argument']
|
|
|
|
break
|
|
|
|
except Exception:
|
|
|
|
return
|
2020-02-11 18:08:37 -08:00
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
run_command = ' '.join(args_given)
|
2020-02-11 18:08:37 -08:00
|
|
|
if found_parser:
|
2020-02-13 09:47:16 -05:00
|
|
|
if options:
|
|
|
|
cmd_options = '-' + ''.join(options)
|
|
|
|
else:
|
|
|
|
cmd_options = ''
|
|
|
|
whole_command = ' '.join([run_command, '|', 'jc', found_parser, cmd_options])
|
2020-02-11 19:14:51 -08:00
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
os.system(whole_command)
|
2020-02-11 12:16:23 -08:00
|
|
|
exit()
|
|
|
|
else:
|
2020-02-13 09:47:16 -05:00
|
|
|
helptext(f'parser not found for "{run_command}"')
|
2020-02-11 12:16:23 -08:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
2019-11-07 08:04:07 -08:00
|
|
|
def main():
|
|
|
|
signal.signal(signal.SIGINT, ctrlc)
|
|
|
|
|
2020-02-12 00:16:17 -05:00
|
|
|
# try magic syntax first: jc -p ls -al
|
2020-02-11 12:16:23 -08:00
|
|
|
magic()
|
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
options = []
|
2019-11-11 16:16:41 -08:00
|
|
|
debug = False
|
2019-11-07 08:04:07 -08:00
|
|
|
pretty = False
|
|
|
|
quiet = False
|
|
|
|
raw = False
|
|
|
|
|
|
|
|
# options
|
2020-02-13 09:47:16 -05:00
|
|
|
for opt in sys.argv:
|
|
|
|
if opt.startswith('-') and not opt.startswith('--'):
|
|
|
|
for flag in opt[1:]:
|
|
|
|
options.append(flag)
|
|
|
|
|
|
|
|
if 'd' in options:
|
2019-11-11 16:16:41 -08:00
|
|
|
debug = True
|
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
if 'p' in options:
|
2019-11-07 08:04:07 -08:00
|
|
|
pretty = True
|
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
if 'q' in options:
|
2019-11-07 08:04:07 -08:00
|
|
|
quiet = True
|
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
if 'r' in options:
|
2019-11-07 08:04:07 -08:00
|
|
|
raw = True
|
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
if 'a' in options:
|
2019-12-16 08:18:37 -08:00
|
|
|
json_out(about_jc(), pretty=pretty)
|
|
|
|
exit()
|
2019-12-14 23:15:15 -08:00
|
|
|
|
2019-12-16 08:18:37 -08:00
|
|
|
if sys.stdin.isatty():
|
2019-12-14 23:15:15 -08:00
|
|
|
helptext('missing piped data')
|
2020-02-05 16:18:58 -08:00
|
|
|
sys.exit(1)
|
2019-12-14 23:15:15 -08:00
|
|
|
|
2019-12-16 08:18:37 -08:00
|
|
|
data = sys.stdin.read()
|
2019-12-14 23:15:15 -08:00
|
|
|
|
2019-11-07 08:23:11 -08:00
|
|
|
found = False
|
|
|
|
|
2019-12-16 08:18:37 -08:00
|
|
|
if debug:
|
2019-11-11 16:16:41 -08:00
|
|
|
for arg in sys.argv:
|
2020-02-05 10:55:08 -08:00
|
|
|
parser_name = parser_shortname(arg)
|
|
|
|
|
2020-02-11 18:08:37 -08:00
|
|
|
if parser_name in parsers:
|
2020-02-05 10:55:08 -08:00
|
|
|
# load parser module just in time so we don't need to load all modules
|
2020-02-05 14:10:22 -08:00
|
|
|
parser = parser_module(arg)
|
|
|
|
result = parser.parse(data, raw=raw, quiet=quiet)
|
2019-11-11 16:16:41 -08:00
|
|
|
found = True
|
|
|
|
break
|
2019-12-16 08:18:37 -08:00
|
|
|
else:
|
2019-11-11 16:16:41 -08:00
|
|
|
for arg in sys.argv:
|
2020-02-05 10:55:08 -08:00
|
|
|
parser_name = parser_shortname(arg)
|
|
|
|
|
2020-02-11 18:08:37 -08:00
|
|
|
if parser_name in parsers:
|
2020-02-05 10:55:08 -08:00
|
|
|
# load parser module just in time so we don't need to load all modules
|
2020-02-05 14:10:22 -08:00
|
|
|
parser = parser_module(arg)
|
2019-11-11 16:16:41 -08:00
|
|
|
try:
|
2020-02-05 14:10:22 -08:00
|
|
|
result = parser.parse(data, raw=raw, quiet=quiet)
|
2019-11-11 16:16:41 -08:00
|
|
|
found = True
|
|
|
|
break
|
2020-02-11 12:16:23 -08:00
|
|
|
except Exception:
|
2019-11-11 16:16:41 -08:00
|
|
|
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 option.')
|
2020-02-05 16:18:58 -08:00
|
|
|
sys.exit(1)
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2019-12-16 08:18:37 -08:00
|
|
|
if not found:
|
2019-11-07 08:04:07 -08:00
|
|
|
helptext('missing or incorrect arguments')
|
2020-02-05 16:18:58 -08:00
|
|
|
sys.exit(1)
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2019-12-14 23:15:15 -08:00
|
|
|
json_out(result, pretty=pretty)
|
2019-11-07 08:04:07 -08:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|