2019-11-07 08:04:07 -08:00
|
|
|
"""jc - JSON CLI output utility
|
|
|
|
JC cli module
|
|
|
|
"""
|
2020-06-07 12:41:50 -07:00
|
|
|
|
2019-11-07 08:04:07 -08:00
|
|
|
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
|
2021-08-13 15:19:17 -07:00
|
|
|
import shlex
|
2021-05-10 18:31:30 -07:00
|
|
|
import subprocess
|
2019-11-07 08:04:07 -08:00
|
|
|
import json
|
2022-01-18 13:10:14 -08:00
|
|
|
from .lib import __version__, parsers, local_parsers
|
|
|
|
from . import utils
|
|
|
|
from . import tracebackplus
|
|
|
|
from .exceptions import LibraryNotInstalled, ParseError
|
2021-05-10 21:02:28 -07:00
|
|
|
|
2021-03-26 15:54:45 -07:00
|
|
|
# make pygments import optional
|
|
|
|
try:
|
|
|
|
import pygments
|
|
|
|
from pygments import highlight
|
|
|
|
from pygments.style import Style
|
|
|
|
from pygments.token import (Name, Number, String, Keyword)
|
|
|
|
from pygments.lexers import JsonLexer
|
|
|
|
from pygments.formatters import Terminal256Formatter
|
2021-09-24 08:43:09 -07:00
|
|
|
PYGMENTS_INSTALLED = True
|
2021-03-26 15:54:45 -07:00
|
|
|
except Exception:
|
2021-09-24 08:43:09 -07:00
|
|
|
PYGMENTS_INSTALLED = False
|
2020-02-05 10:55:08 -08:00
|
|
|
|
2020-02-05 22:26:47 -08:00
|
|
|
|
2022-01-18 13:10:14 -08:00
|
|
|
JC_ERROR_EXIT = 100
|
|
|
|
|
|
|
|
|
2020-02-05 22:26:47 -08:00
|
|
|
class info():
|
2022-01-18 13:10:14 -08:00
|
|
|
version = __version__
|
2020-07-20 16:54:43 -07:00
|
|
|
description = 'JSON CLI output utility'
|
2020-02-05 22:26:47 -08:00
|
|
|
author = 'Kelly Brazil'
|
|
|
|
author_email = 'kellyjonbrazil@gmail.com'
|
2021-03-30 08:05:26 -07:00
|
|
|
website = 'https://github.com/kellyjonbrazil/jc'
|
2022-01-02 11:07:15 -08:00
|
|
|
copyright = '© 2019-2022 Kelly Brazil'
|
2021-04-04 15:52:50 -07:00
|
|
|
license = 'MIT License'
|
2020-02-05 22:26:47 -08:00
|
|
|
|
|
|
|
|
2020-07-09 12:44:41 -04:00
|
|
|
# We only support 2.3.0+, pygments changed color names in 2.4.0.
|
|
|
|
# startswith is sufficient and avoids potential exceptions from split and int.
|
2021-09-24 08:43:09 -07:00
|
|
|
if PYGMENTS_INSTALLED:
|
2021-03-26 15:54:45 -07:00
|
|
|
if pygments.__version__.startswith('2.3.'):
|
|
|
|
PYGMENT_COLOR = {
|
|
|
|
'black': '#ansiblack',
|
|
|
|
'red': '#ansidarkred',
|
|
|
|
'green': '#ansidarkgreen',
|
|
|
|
'yellow': '#ansibrown',
|
|
|
|
'blue': '#ansidarkblue',
|
|
|
|
'magenta': '#ansipurple',
|
|
|
|
'cyan': '#ansiteal',
|
|
|
|
'gray': '#ansilightgray',
|
|
|
|
'brightblack': '#ansidarkgray',
|
|
|
|
'brightred': '#ansired',
|
|
|
|
'brightgreen': '#ansigreen',
|
|
|
|
'brightyellow': '#ansiyellow',
|
|
|
|
'brightblue': '#ansiblue',
|
|
|
|
'brightmagenta': '#ansifuchsia',
|
|
|
|
'brightcyan': '#ansiturquoise',
|
|
|
|
'white': '#ansiwhite',
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
PYGMENT_COLOR = {
|
|
|
|
'black': 'ansiblack',
|
|
|
|
'red': 'ansired',
|
|
|
|
'green': 'ansigreen',
|
|
|
|
'yellow': 'ansiyellow',
|
|
|
|
'blue': 'ansiblue',
|
|
|
|
'magenta': 'ansimagenta',
|
|
|
|
'cyan': 'ansicyan',
|
|
|
|
'gray': 'ansigray',
|
|
|
|
'brightblack': 'ansibrightblack',
|
|
|
|
'brightred': 'ansibrightred',
|
|
|
|
'brightgreen': 'ansibrightgreen',
|
|
|
|
'brightyellow': 'ansibrightyellow',
|
|
|
|
'brightblue': 'ansibrightblue',
|
|
|
|
'brightmagenta': 'ansibrightmagenta',
|
|
|
|
'brightcyan': 'ansibrightcyan',
|
|
|
|
'white': 'ansiwhite',
|
|
|
|
}
|
2020-07-09 11:27:01 -04:00
|
|
|
|
2020-07-09 09:59:00 -07:00
|
|
|
|
2020-07-10 12:23:25 -07:00
|
|
|
def set_env_colors(env_colors=None):
|
2020-04-12 12:43:51 -07:00
|
|
|
"""
|
2020-07-09 11:11:29 -07:00
|
|
|
Return a dictionary to be used in Pygments custom style class.
|
|
|
|
|
2020-04-12 13:03:09 -07:00
|
|
|
Grab custom colors from JC_COLORS environment variable. JC_COLORS env variable takes 4 comma
|
|
|
|
separated string values and should be in the format of:
|
2020-04-12 12:43:51 -07:00
|
|
|
|
2020-04-12 13:03:09 -07:00
|
|
|
JC_COLORS=<keyname_color>,<keyword_color>,<number_color>,<string_color>
|
2020-04-12 12:43:51 -07:00
|
|
|
|
|
|
|
Where colors are: black, red, green, yellow, blue, magenta, cyan, gray, brightblack, brightred,
|
|
|
|
brightgreen, brightyellow, brightblue, brightmagenta, brightcyan, white, default
|
|
|
|
|
|
|
|
Default colors:
|
|
|
|
|
2021-05-11 10:36:55 -07:00
|
|
|
JC_COLORS=blue,brightblack,magenta,green
|
2020-04-12 12:43:51 -07:00
|
|
|
or
|
2021-05-11 10:36:55 -07:00
|
|
|
JC_COLORS=default,default,default,default
|
2020-04-12 12:43:51 -07:00
|
|
|
"""
|
|
|
|
input_error = False
|
|
|
|
|
|
|
|
if env_colors:
|
|
|
|
color_list = env_colors.split(',')
|
|
|
|
else:
|
2020-07-09 08:30:10 -07:00
|
|
|
color_list = ['default', 'default', 'default', 'default']
|
2020-04-12 12:43:51 -07:00
|
|
|
|
2020-07-09 08:30:10 -07:00
|
|
|
if len(color_list) != 4:
|
2020-04-12 12:43:51 -07:00
|
|
|
input_error = True
|
|
|
|
|
2020-07-09 08:30:10 -07:00
|
|
|
for color in color_list:
|
2020-07-09 11:27:01 -04:00
|
|
|
if color != 'default' and color not in PYGMENT_COLOR:
|
2020-07-09 08:30:10 -07:00
|
|
|
input_error = True
|
2020-04-12 12:43:51 -07:00
|
|
|
|
|
|
|
# if there is an issue with the env variable, just set all colors to default and move on
|
|
|
|
if input_error:
|
2022-01-18 13:10:14 -08:00
|
|
|
utils.warning_message(['Could not parse JC_COLORS environment variable'])
|
2020-04-12 12:43:51 -07:00
|
|
|
color_list = ['default', 'default', 'default', 'default']
|
|
|
|
|
2020-04-12 13:03:09 -07:00
|
|
|
# Try the color set in the JC_COLORS env variable first. If it is set to default, then fall back to default colors
|
|
|
|
return {
|
2021-01-08 11:56:56 -08:00
|
|
|
Name.Tag: f'bold {PYGMENT_COLOR[color_list[0]]}' if color_list[0] != 'default' else f"bold {PYGMENT_COLOR['blue']}", # key names
|
|
|
|
Keyword: PYGMENT_COLOR[color_list[1]] if color_list[1] != 'default' else PYGMENT_COLOR['brightblack'], # true, false, null
|
|
|
|
Number: PYGMENT_COLOR[color_list[2]] if color_list[2] != 'default' else PYGMENT_COLOR['magenta'], # numbers
|
|
|
|
String: PYGMENT_COLOR[color_list[3]] if color_list[3] != 'default' else PYGMENT_COLOR['green'] # strings
|
2020-04-12 12:43:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-12-08 08:14:28 -08:00
|
|
|
def piped_output(force_color):
|
|
|
|
"""Return False if stdout is a TTY. True if output is being piped to another program
|
|
|
|
and foce_color is True. This allows forcing of ANSI color codes even when using pipes.
|
|
|
|
"""
|
|
|
|
return not sys.stdout.isatty() and not force_color
|
2020-04-02 17:29:25 -07:00
|
|
|
|
|
|
|
|
2020-02-05 22:26:47 -08:00
|
|
|
def ctrlc(signum, frame):
|
2020-07-09 10:54:49 -07:00
|
|
|
"""Exit with error on SIGINT"""
|
2021-05-11 10:36:55 -07:00
|
|
|
sys.exit(JC_ERROR_EXIT)
|
2020-02-05 22:26:47 -08:00
|
|
|
|
|
|
|
|
2021-09-24 08:43:09 -07:00
|
|
|
def parser_shortname(parser_arg):
|
2020-07-09 11:11:29 -07:00
|
|
|
"""Return short name of the parser with dashes and no -- prefix"""
|
2021-09-24 08:43:09 -07:00
|
|
|
return parser_arg[2:]
|
2020-02-05 10:55:08 -08:00
|
|
|
|
|
|
|
|
|
|
|
def parser_argument(parser):
|
2020-07-09 11:11:29 -07:00
|
|
|
"""Return 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-07-09 11:11:29 -07:00
|
|
|
"""Return 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-07-09 10:54:49 -07:00
|
|
|
"""Import the module just in time and return the module object"""
|
2020-06-06 14:30:40 -04:00
|
|
|
shortname = parser_mod_shortname(parser)
|
|
|
|
path = ('jcparsers.' if shortname in local_parsers else 'jc.parsers.')
|
|
|
|
return importlib.import_module(path + shortname)
|
2019-12-13 20:01:51 -08:00
|
|
|
|
|
|
|
|
2020-02-05 13:57:34 -08:00
|
|
|
def parsers_text(indent=0, pad=0):
|
2020-07-09 10:54:49 -07:00
|
|
|
"""Return the argument and description information from each parser"""
|
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():
|
2020-07-09 10:54:49 -07:00
|
|
|
"""Return jc info and the contents of each parser.info as a dictionary"""
|
2019-12-14 23:15:15 -08:00
|
|
|
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,
|
2021-03-30 08:05:26 -07:00
|
|
|
'website': info.website,
|
2021-03-29 09:41:15 -07:00
|
|
|
'copyright': info.copyright,
|
2021-04-04 15:52:50 -07:00
|
|
|
'license': info.license,
|
2019-12-16 11:52:18 -08:00
|
|
|
'parser_count': len(parser_list),
|
2019-12-14 23:15:15 -08:00
|
|
|
'parsers': parser_list
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-26 09:28:03 -07:00
|
|
|
def helptext():
|
2020-07-09 10:54:49 -07:00
|
|
|
"""Return the help text with the list of parsers"""
|
2020-02-05 13:57:34 -08:00
|
|
|
parsers_string = parsers_text(indent=12, pad=17)
|
2019-12-13 20:01:51 -08:00
|
|
|
|
2021-03-29 10:52:02 -07:00
|
|
|
helptext_string = f'''\
|
|
|
|
jc converts the output of many commands and file-types to JSON
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2020-02-13 09:47:16 -05:00
|
|
|
Usage: COMMAND | jc PARSER [OPTIONS]
|
2019-11-07 08:04:07 -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:
|
2021-09-23 11:48:39 -07:00
|
|
|
-a about jc
|
2021-12-08 08:14:28 -08:00
|
|
|
-C force color output even when using pipes (overrides -m)
|
2021-09-23 11:48:39 -07:00
|
|
|
-d debug (-dd for verbose debug)
|
|
|
|
-h help (-h --parser_name for parser documentation)
|
|
|
|
-m monochrome output
|
|
|
|
-p pretty print output
|
|
|
|
-q quiet - suppress parser warnings (-qq to ignore streaming errors)
|
|
|
|
-r raw JSON output
|
|
|
|
-u unbuffer output
|
|
|
|
-v version info
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2021-04-16 08:46:20 -07:00
|
|
|
Examples:
|
|
|
|
Standard Syntax:
|
|
|
|
$ dig www.google.com | jc --dig -p
|
2020-02-11 18:08:37 -08:00
|
|
|
|
2021-04-16 08:46:20 -07:00
|
|
|
Magic Syntax:
|
|
|
|
$ jc -p dig www.google.com
|
2020-02-11 18:08:37 -08:00
|
|
|
|
2021-04-16 08:46:20 -07:00
|
|
|
Parser Documentation:
|
|
|
|
$ jc -h --dig
|
2019-11-07 08:04:07 -08:00
|
|
|
'''
|
2020-07-08 16:40:28 -07:00
|
|
|
return textwrap.dedent(helptext_string)
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2021-04-16 08:46:20 -07:00
|
|
|
|
2021-04-09 09:12:41 -07:00
|
|
|
def help_doc(options):
|
|
|
|
"""
|
|
|
|
Returns the parser documentation if a parser is found in the arguments, otherwise
|
|
|
|
the general help text is returned.
|
|
|
|
"""
|
|
|
|
for arg in options:
|
|
|
|
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)
|
|
|
|
compatible = ', '.join(parser.info.compatible)
|
2021-08-13 17:17:51 -07:00
|
|
|
doc_text = \
|
|
|
|
f'{parser.__doc__}\n'\
|
|
|
|
f'Compatibility: {compatible}\n\n'\
|
|
|
|
f'Version {parser.info.version} by {parser.info.author} ({parser.info.author_email})\n'
|
2021-04-09 09:12:41 -07:00
|
|
|
|
|
|
|
return doc_text
|
|
|
|
|
|
|
|
return helptext()
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2021-04-16 08:46:20 -07:00
|
|
|
|
2021-03-29 09:41:15 -07:00
|
|
|
def versiontext():
|
|
|
|
"""Return the version text"""
|
2021-03-29 10:52:02 -07:00
|
|
|
versiontext_string = f'''\
|
2021-03-29 09:41:15 -07:00
|
|
|
jc version {info.version}
|
2021-03-30 08:05:26 -07:00
|
|
|
{info.website}
|
|
|
|
{info.copyright}'''
|
2021-03-29 09:41:15 -07:00
|
|
|
return textwrap.dedent(versiontext_string)
|
|
|
|
|
|
|
|
|
2020-07-10 12:23:25 -07:00
|
|
|
def json_out(data, pretty=False, env_colors=None, mono=False, piped_out=False):
|
2020-07-09 11:11:29 -07:00
|
|
|
"""Return a JSON formatted string. String may include color codes or be pretty printed."""
|
2021-05-12 09:40:22 -07:00
|
|
|
separators = (',', ':')
|
2021-05-16 20:33:03 -07:00
|
|
|
indent = None
|
|
|
|
|
|
|
|
if pretty:
|
|
|
|
separators = None
|
|
|
|
indent = 2
|
|
|
|
|
2020-04-02 17:29:25 -07:00
|
|
|
if not mono and not piped_out:
|
2020-06-25 07:29:28 -07:00
|
|
|
# set colors
|
|
|
|
class JcStyle(Style):
|
2020-07-10 12:23:25 -07:00
|
|
|
styles = set_env_colors(env_colors)
|
2020-06-25 07:29:28 -07:00
|
|
|
|
2021-05-16 20:33:03 -07:00
|
|
|
return str(highlight(json.dumps(data, indent=indent, separators=separators, ensure_ascii=False),
|
|
|
|
JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1])
|
|
|
|
|
2021-09-24 08:43:09 -07:00
|
|
|
return json.dumps(data, indent=indent, separators=separators, ensure_ascii=False)
|
2019-12-14 23:15:15 -08:00
|
|
|
|
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
def magic_parser(args):
|
2020-03-08 13:20:38 -07:00
|
|
|
"""
|
2021-05-12 08:27:39 -07:00
|
|
|
Parse command arguments for magic syntax: jc -p ls -al
|
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
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
|
2020-03-08 13:20:38 -07:00
|
|
|
"""
|
2021-05-12 08:27:39 -07:00
|
|
|
# bail immediately if there are no args or a parser is defined
|
2020-03-08 13:26:15 -07:00
|
|
|
if len(args) <= 1 or args[1].startswith('--'):
|
2021-05-10 18:31:30 -07:00
|
|
|
return False, None, None, []
|
2020-03-08 12:58:26 -07:00
|
|
|
|
2021-08-13 14:19:26 -07:00
|
|
|
args_given = args[1:]
|
2020-03-04 10:33:42 -08:00
|
|
|
options = []
|
|
|
|
|
|
|
|
# find the options
|
2020-03-11 09:21:14 -07:00
|
|
|
for arg in list(args_given):
|
2020-03-08 12:58:26 -07:00
|
|
|
# parser found - use standard syntax
|
|
|
|
if arg.startswith('--'):
|
2021-05-10 18:31:30 -07:00
|
|
|
return False, None, None, []
|
2020-03-08 12:58:26 -07:00
|
|
|
|
|
|
|
# option found - populate option list
|
2021-09-24 08:43:09 -07:00
|
|
|
if arg.startswith('-'):
|
2020-03-11 09:21:14 -07:00
|
|
|
options.extend(args_given.pop(0)[1:])
|
2020-03-08 12:58:26 -07:00
|
|
|
|
|
|
|
# command found if iterator didn't already stop - stop iterating
|
|
|
|
else:
|
|
|
|
break
|
|
|
|
|
2021-04-08 16:42:45 -07:00
|
|
|
# if -h, -a, or -v found in options, then bail out
|
|
|
|
if 'h' in options or 'a' in options or 'v' in options:
|
2021-05-10 18:31:30 -07:00
|
|
|
return False, None, None, []
|
2021-04-08 10:54:15 -07:00
|
|
|
|
2021-05-12 08:27:39 -07:00
|
|
|
# all options popped and no command found - for case like 'jc -x'
|
2020-03-04 13:16:35 -08:00
|
|
|
if len(args_given) == 0:
|
2021-05-10 18:31:30 -07:00
|
|
|
return False, None, None, []
|
2020-03-08 13:20:38 -07:00
|
|
|
|
|
|
|
magic_dict = {}
|
|
|
|
parser_info = about_jc()['parsers']
|
|
|
|
|
2020-07-09 10:54:49 -07:00
|
|
|
# create a dictionary of magic_commands to their respective parsers.
|
2020-03-08 13:20:38 -07:00
|
|
|
for entry in parser_info:
|
|
|
|
# Update the dict with all of the magic commands for this parser, if they exist.
|
|
|
|
magic_dict.update({mc: entry['argument'] for mc in entry.get('magic_commands', [])})
|
2020-03-04 13:16:35 -08:00
|
|
|
|
2020-03-04 10:33:42 -08:00
|
|
|
# find the command and parser
|
2020-03-08 12:58:26 -07:00
|
|
|
one_word_command = args_given[0]
|
|
|
|
two_word_command = ' '.join(args_given[0:2])
|
|
|
|
|
2020-07-09 10:54:49 -07:00
|
|
|
# try to get a parser for two_word_command, otherwise get one for one_word_command
|
2020-03-08 12:58:26 -07:00
|
|
|
found_parser = magic_dict.get(two_word_command, magic_dict.get(one_word_command))
|
2020-03-04 10:33:42 -08:00
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
return (
|
2021-09-24 08:43:09 -07:00
|
|
|
bool(found_parser), # was a suitable parser found?
|
2021-05-10 21:23:23 -07:00
|
|
|
args_given, # run_command
|
|
|
|
found_parser, # the parser selected
|
|
|
|
options # jc options to preserve
|
2021-05-10 18:31:30 -07:00
|
|
|
)
|
2020-03-08 13:20:38 -07:00
|
|
|
|
2020-03-08 12:58:26 -07:00
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
def run_user_command(command):
|
|
|
|
"""Use subprocess to run the user's command. Returns the STDOUT, STDERR, and the Exit Code as a tuple."""
|
2021-08-12 16:36:35 -07:00
|
|
|
proc = subprocess.Popen(command,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
2021-08-12 16:55:07 -07:00
|
|
|
close_fds=False, # Allows inheriting file descriptors. Useful for process substitution
|
2021-08-12 16:36:35 -07:00
|
|
|
universal_newlines=True)
|
2021-05-10 18:31:30 -07:00
|
|
|
stdout, stderr = proc.communicate()
|
|
|
|
|
|
|
|
return (
|
|
|
|
stdout or '\n',
|
|
|
|
stderr,
|
|
|
|
proc.returncode
|
|
|
|
)
|
2020-02-11 12:16:23 -08:00
|
|
|
|
|
|
|
|
2021-05-11 10:36:55 -07:00
|
|
|
def combined_exit_code(program_exit=0, jc_exit=0):
|
2021-05-11 10:50:35 -07:00
|
|
|
exit_code = program_exit + jc_exit
|
2021-09-24 08:43:09 -07:00
|
|
|
exit_code = min(exit_code, 255)
|
2021-05-11 10:50:35 -07:00
|
|
|
return exit_code
|
2021-05-11 10:36:55 -07:00
|
|
|
|
|
|
|
|
2019-11-07 08:04:07 -08:00
|
|
|
def main():
|
2020-02-13 10:14:32 -05:00
|
|
|
# break on ctrl-c keyboard interrupt
|
2019-11-07 08:04:07 -08:00
|
|
|
signal.signal(signal.SIGINT, ctrlc)
|
|
|
|
|
2020-04-09 13:38:33 -07:00
|
|
|
# break on pipe error. need try/except for windows compatibility
|
|
|
|
try:
|
|
|
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
|
2021-05-16 17:43:05 -07:00
|
|
|
# enable colors for Windows cmd.exe terminal
|
2021-05-16 17:48:40 -07:00
|
|
|
if sys.platform.startswith('win32'):
|
|
|
|
os.system('')
|
2021-05-16 17:43:05 -07:00
|
|
|
|
2021-05-12 13:18:58 -07:00
|
|
|
# parse magic syntax first: e.g. jc -p ls -al
|
2021-05-12 08:48:49 -07:00
|
|
|
magic_options = []
|
2021-05-10 18:31:30 -07:00
|
|
|
valid_command, run_command, magic_found_parser, magic_options = magic_parser(sys.argv)
|
2020-02-11 12:16:23 -08:00
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
# set colors
|
2021-04-08 10:54:15 -07:00
|
|
|
jc_colors = os.getenv('JC_COLORS')
|
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
# set options
|
2020-02-13 09:47:16 -05:00
|
|
|
options = []
|
2021-05-10 18:31:30 -07:00
|
|
|
options.extend(magic_options)
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2021-05-12 13:18:58 -07:00
|
|
|
# find options if magic_parser did not find a command
|
2021-05-10 18:58:45 -07:00
|
|
|
if not valid_command:
|
|
|
|
for opt in sys.argv:
|
|
|
|
if opt.startswith('-') and not opt.startswith('--'):
|
|
|
|
options.extend(opt[1:])
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2021-03-29 09:41:15 -07:00
|
|
|
about = 'a' in options
|
2020-03-04 10:33:42 -08:00
|
|
|
debug = 'd' in options
|
2021-09-24 08:43:09 -07:00
|
|
|
verbose_debug = options.count('d') > 1
|
2021-12-08 08:14:28 -08:00
|
|
|
force_color = 'C' in options
|
|
|
|
mono = ('m' in options or bool(os.getenv('NO_COLOR'))) and not force_color
|
2021-03-26 09:28:03 -07:00
|
|
|
help_me = 'h' in options
|
2020-03-04 10:33:42 -08:00
|
|
|
pretty = 'p' in options
|
|
|
|
quiet = 'q' in options
|
2021-09-24 08:43:09 -07:00
|
|
|
ignore_exceptions = options.count('q') > 1
|
2020-03-04 12:03:40 -08:00
|
|
|
raw = 'r' in options
|
2021-09-16 20:41:56 -07:00
|
|
|
unbuffer = 'u' in options
|
2021-03-29 09:41:15 -07:00
|
|
|
version_info = 'v' in options
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2021-05-12 13:18:58 -07:00
|
|
|
if verbose_debug:
|
2022-01-18 13:10:14 -08:00
|
|
|
tracebackplus.enable(context=11)
|
2021-05-12 13:18:58 -07:00
|
|
|
|
2021-09-24 08:43:09 -07:00
|
|
|
if not PYGMENTS_INSTALLED:
|
2021-03-26 15:54:45 -07:00
|
|
|
mono = True
|
|
|
|
|
2021-03-29 09:41:15 -07:00
|
|
|
if about:
|
2021-12-08 08:14:28 -08:00
|
|
|
print(json_out(about_jc(),
|
|
|
|
pretty=pretty,
|
|
|
|
env_colors=jc_colors,
|
|
|
|
mono=mono,
|
|
|
|
piped_out=piped_output(force_color)))
|
2021-03-29 09:41:15 -07:00
|
|
|
sys.exit(0)
|
|
|
|
|
2021-03-26 09:28:03 -07:00
|
|
|
if help_me:
|
2021-04-09 09:12:41 -07:00
|
|
|
print(help_doc(sys.argv))
|
2021-03-26 09:28:03 -07:00
|
|
|
sys.exit(0)
|
|
|
|
|
2021-03-29 09:41:15 -07:00
|
|
|
if version_info:
|
|
|
|
print(versiontext())
|
|
|
|
sys.exit(0)
|
|
|
|
|
2021-05-12 13:18:58 -07:00
|
|
|
# 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
|
2021-05-12 13:44:15 -07:00
|
|
|
if run_command:
|
2021-08-13 16:01:45 -07:00
|
|
|
try:
|
|
|
|
run_command_str = shlex.join(run_command) # python 3.8+
|
|
|
|
except AttributeError:
|
2021-08-13 16:03:46 -07:00
|
|
|
run_command_str = ' '.join(run_command) # older python versions
|
2021-05-12 13:38:08 -07:00
|
|
|
|
2021-05-12 13:44:15 -07:00
|
|
|
if valid_command:
|
2021-05-12 13:18:58 -07:00
|
|
|
try:
|
|
|
|
magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command)
|
|
|
|
if magic_stderr:
|
|
|
|
print(magic_stderr[:-1], file=sys.stderr)
|
|
|
|
|
2022-01-21 12:15:34 -08:00
|
|
|
except OSError as e:
|
2021-05-12 13:18:58 -07:00
|
|
|
if debug:
|
|
|
|
raise
|
2021-09-26 12:00:30 -07:00
|
|
|
|
2022-01-21 12:15:34 -08:00
|
|
|
error_msg = os.strerror(e.errno)
|
|
|
|
utils.error_message([f'"{run_command_str}" command could not be run: {error_msg}. For details use the -d or -dd option.'])
|
2021-09-24 08:43:09 -07:00
|
|
|
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
2021-05-12 13:18:58 -07:00
|
|
|
|
|
|
|
except Exception:
|
|
|
|
if debug:
|
|
|
|
raise
|
2021-09-24 08:43:09 -07:00
|
|
|
|
2022-01-18 13:10:14 -08:00
|
|
|
utils.error_message([f'"{run_command_str}" command could not be run. For details use the -d or -dd option.'])
|
2021-09-24 08:43:09 -07:00
|
|
|
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
2021-05-12 13:18:58 -07:00
|
|
|
|
2021-05-12 15:19:11 -07:00
|
|
|
elif run_command is not None:
|
2022-01-18 13:10:14 -08:00
|
|
|
utils.error_message([f'"{run_command_str}" cannot be used with Magic syntax. Use "jc -h" for help.'])
|
2021-05-12 13:18:58 -07:00
|
|
|
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
2020-07-08 15:41:46 -07:00
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
# find the correct parser
|
|
|
|
if magic_found_parser:
|
|
|
|
parser = parser_module(magic_found_parser)
|
2021-05-12 11:36:27 -07:00
|
|
|
parser_name = parser_shortname(magic_found_parser)
|
2019-11-07 08:23:11 -08:00
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
else:
|
|
|
|
found = False
|
|
|
|
for arg in sys.argv:
|
|
|
|
parser_name = parser_shortname(arg)
|
2020-06-25 07:29:28 -07:00
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
if parser_name in parsers:
|
|
|
|
parser = parser_module(arg)
|
2019-11-11 16:16:41 -08:00
|
|
|
found = True
|
|
|
|
break
|
2020-06-30 11:26:09 -07:00
|
|
|
|
2021-05-10 18:31:30 -07:00
|
|
|
if not found:
|
2022-01-18 13:10:14 -08:00
|
|
|
utils.error_message(['Missing or incorrect arguments. Use "jc -h" for help.'])
|
2021-05-11 10:36:55 -07:00
|
|
|
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
2021-05-10 18:31:30 -07:00
|
|
|
|
2021-05-12 17:01:09 -07:00
|
|
|
# check for input errors (pipe vs magic)
|
|
|
|
if not sys.stdin.isatty() and magic_stdout:
|
2022-01-18 13:10:14 -08:00
|
|
|
utils.error_message(['Piped data and Magic syntax used simultaneously. Use "jc -h" for help.'])
|
2021-05-12 17:01:09 -07:00
|
|
|
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
|
|
|
|
|
|
|
elif sys.stdin.isatty() and magic_stdout is None:
|
2022-01-18 13:10:14 -08:00
|
|
|
utils.error_message(['Missing piped data. Use "jc -h" for help.'])
|
2021-05-12 15:19:11 -07:00
|
|
|
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
|
|
|
|
2021-09-12 16:27:00 -07:00
|
|
|
# parse and print to stdout
|
2021-05-10 18:31:30 -07:00
|
|
|
try:
|
2021-09-10 14:14:10 -07:00
|
|
|
# differentiate between regular and streaming parsers
|
|
|
|
|
|
|
|
# streaming
|
|
|
|
if getattr(parser.info, 'streaming', None):
|
2021-09-23 11:48:39 -07:00
|
|
|
result = parser.parse(sys.stdin, raw=raw, quiet=quiet, ignore_exceptions=ignore_exceptions)
|
2021-09-10 14:14:10 -07:00
|
|
|
for line in result:
|
|
|
|
print(json_out(line,
|
|
|
|
pretty=pretty,
|
|
|
|
env_colors=jc_colors,
|
|
|
|
mono=mono,
|
2021-12-08 08:14:28 -08:00
|
|
|
piped_out=piped_output(force_color)),
|
2021-09-16 20:41:56 -07:00
|
|
|
flush=unbuffer)
|
2021-09-10 14:14:10 -07:00
|
|
|
|
|
|
|
sys.exit(combined_exit_code(magic_exit_code, 0))
|
|
|
|
|
|
|
|
# regular
|
|
|
|
else:
|
|
|
|
data = magic_stdout or sys.stdin.read()
|
|
|
|
result = parser.parse(data, raw=raw, quiet=quiet)
|
2021-09-12 16:27:00 -07:00
|
|
|
print(json_out(result,
|
|
|
|
pretty=pretty,
|
|
|
|
env_colors=jc_colors,
|
|
|
|
mono=mono,
|
2021-12-08 08:14:28 -08:00
|
|
|
piped_out=piped_output(force_color)),
|
2021-09-16 20:41:56 -07:00
|
|
|
flush=unbuffer)
|
2021-09-12 16:27:00 -07:00
|
|
|
|
|
|
|
sys.exit(combined_exit_code(magic_exit_code, 0))
|
2021-05-10 18:31:30 -07:00
|
|
|
|
2021-05-20 15:46:31 -07:00
|
|
|
except (ParseError, LibraryNotInstalled) as e:
|
|
|
|
if debug:
|
|
|
|
raise
|
2021-09-24 08:43:09 -07:00
|
|
|
|
2022-01-18 13:10:14 -08:00
|
|
|
utils.error_message([f'Parser issue with {parser_name}:',
|
|
|
|
f'{e.__class__.__name__}: {e}',
|
|
|
|
'For details use the -d or -dd option. Use "jc -h" for help.'])
|
2021-09-24 08:43:09 -07:00
|
|
|
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
2021-05-20 15:46:31 -07:00
|
|
|
|
2021-09-12 16:27:00 -07:00
|
|
|
except json.JSONDecodeError:
|
2021-05-10 18:31:30 -07:00
|
|
|
if debug:
|
|
|
|
raise
|
2021-09-24 08:43:09 -07:00
|
|
|
|
2022-01-18 13:10:14 -08:00
|
|
|
utils.error_message(['There was an issue generating the JSON output.',
|
|
|
|
'For details use the -d or -dd option.'])
|
2021-09-24 08:43:09 -07:00
|
|
|
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
2019-11-07 08:04:07 -08:00
|
|
|
|
2021-05-12 13:18:58 -07:00
|
|
|
except Exception:
|
|
|
|
if debug:
|
|
|
|
raise
|
2021-09-24 08:43:09 -07:00
|
|
|
|
|
|
|
streaming_msg = ''
|
|
|
|
if getattr(parser.info, 'streaming', None):
|
|
|
|
streaming_msg = 'Use the -qq option to ignore streaming parser errors.'
|
|
|
|
|
2022-01-18 13:10:14 -08:00
|
|
|
utils.error_message([
|
2021-09-24 08:43:09 -07:00
|
|
|
f'{parser_name} parser could not parse the input data. Did you use the correct parser?',
|
|
|
|
f'{streaming_msg}',
|
|
|
|
'For details use the -d or -dd option. Use "jc -h" for help.'
|
|
|
|
])
|
|
|
|
sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT))
|
2019-11-07 08:04:07 -08:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|