mirror of
https://github.com/kellyjonbrazil/jc.git
synced 2025-06-19 00:17:51 +02:00
simplify the run() method
This commit is contained in:
329
jc/cli.py
329
jc/cli.py
@ -11,12 +11,15 @@ import signal
|
|||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
from .lib import (__version__, parser_info, all_parser_info, parsers,
|
from .lib import (
|
||||||
_get_parser, _parser_is_streaming, parser_mod_list,
|
__version__, parser_info, all_parser_info, parsers, _get_parser, _parser_is_streaming,
|
||||||
standard_parser_mod_list, plugin_parser_mod_list,
|
parser_mod_list, standard_parser_mod_list, plugin_parser_mod_list, streaming_parser_mod_list
|
||||||
streaming_parser_mod_list)
|
)
|
||||||
from . import utils
|
from . import utils
|
||||||
from .cli_data import long_options_map, new_pygments_colors, old_pygments_colors
|
from .cli_data import (
|
||||||
|
long_options_map, new_pygments_colors, old_pygments_colors, helptext_preamble_string,
|
||||||
|
helptext_end_string
|
||||||
|
)
|
||||||
from .shell_completions import bash_completion, zsh_completion
|
from .shell_completions import bash_completion, zsh_completion
|
||||||
from . import tracebackplus
|
from . import tracebackplus
|
||||||
from .exceptions import LibraryNotInstalled, ParseError
|
from .exceptions import LibraryNotInstalled, ParseError
|
||||||
@ -54,15 +57,15 @@ if PYGMENTS_INSTALLED:
|
|||||||
|
|
||||||
|
|
||||||
class JcCli():
|
class JcCli():
|
||||||
__slots__ = [
|
__slots__ = (
|
||||||
'data_in', 'data_out', 'options', 'args', 'parser_module', 'parser_name',
|
'data_in', 'data_out', 'options', 'args', 'parser_module', 'parser_name', 'indent', 'pad',
|
||||||
'indent', 'pad', 'env_colors', 'custom_colors', 'show_hidden', 'ascii_only', 'json_separators',
|
'env_colors', 'custom_colors', 'show_hidden', 'ascii_only', 'json_separators',
|
||||||
'json_indent', 'path_string', 'jc_exit', 'JC_ERROR_EXIT', 'exit_code', 'run_timestamp',
|
'json_indent', 'path_string', 'jc_exit', 'JC_ERROR_EXIT', 'exit_code', 'run_timestamp',
|
||||||
'about', 'debug', 'verbose_debug', 'force_color', 'mono', 'help_me', 'pretty', 'quiet',
|
'about', 'debug', 'verbose_debug', 'force_color', 'mono', 'help_me', 'pretty', 'quiet',
|
||||||
'ignore_exceptions', 'raw', 'meta_out', 'unbuffer', 'version_info', 'yaml_output',
|
'ignore_exceptions', 'raw', 'meta_out', 'unbuffer', 'version_info', 'yaml_output',
|
||||||
'bash_comp', 'zsh_comp', 'magic_found_parser', 'magic_options', 'magic_run_command',
|
'bash_comp', 'zsh_comp', 'magic_found_parser', 'magic_options', 'magic_run_command',
|
||||||
'magic_run_command_str', 'magic_stdout', 'magic_stderr', 'magic_returncode'
|
'magic_run_command_str', 'magic_stdout', 'magic_stderr', 'magic_returncode'
|
||||||
]
|
)
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.data_in = None
|
self.data_in = None
|
||||||
@ -238,46 +241,7 @@ class JcCli():
|
|||||||
self.pad = 20
|
self.pad = 20
|
||||||
parsers_string = self.parsers_text()
|
parsers_string = self.parsers_text()
|
||||||
options_string = self.options_text()
|
options_string = self.options_text()
|
||||||
|
helptext_string = f'{helptext_preamble_string}{parsers_string}\nOptions:\n{options_string}\n{helptext_end_string}'
|
||||||
helptext_string = f'''\
|
|
||||||
jc converts the output of many commands, file-types, and strings to JSON or YAML
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
Standard syntax:
|
|
||||||
|
|
||||||
COMMAND | jc [OPTIONS] PARSER
|
|
||||||
|
|
||||||
cat FILE | jc [OPTIONS] PARSER
|
|
||||||
|
|
||||||
echo STRING | jc [OPTIONS] PARSER
|
|
||||||
|
|
||||||
Magic syntax:
|
|
||||||
|
|
||||||
jc [OPTIONS] COMMAND
|
|
||||||
|
|
||||||
jc [OPTIONS] /proc/<path-to-procfile>
|
|
||||||
|
|
||||||
Parsers:
|
|
||||||
{parsers_string}
|
|
||||||
Options:
|
|
||||||
{options_string}
|
|
||||||
Examples:
|
|
||||||
Standard Syntax:
|
|
||||||
$ dig www.google.com | jc --pretty --dig
|
|
||||||
$ cat /proc/meminfo | jc --pretty --proc
|
|
||||||
|
|
||||||
Magic Syntax:
|
|
||||||
$ jc --pretty dig www.google.com
|
|
||||||
$ jc --pretty /proc/meminfo
|
|
||||||
|
|
||||||
Parser Documentation:
|
|
||||||
$ jc --help --dig
|
|
||||||
|
|
||||||
Show Hidden Parsers:
|
|
||||||
$ jc -hh
|
|
||||||
'''
|
|
||||||
|
|
||||||
return helptext_string
|
return helptext_string
|
||||||
|
|
||||||
def help_doc(self):
|
def help_doc(self):
|
||||||
@ -479,6 +443,145 @@ Examples:
|
|||||||
self.magic_stdout = self.magic_stdout or '\n'
|
self.magic_stdout = self.magic_stdout or '\n'
|
||||||
self.magic_returncode = proc.returncode
|
self.magic_returncode = proc.returncode
|
||||||
|
|
||||||
|
def do_magic(self):
|
||||||
|
"""
|
||||||
|
Try to run the command and error if it's not found, executable, etc.
|
||||||
|
|
||||||
|
Supports running magic commands or opening /proc files to set the
|
||||||
|
output to magic_stdout.
|
||||||
|
"""
|
||||||
|
if self.magic_run_command:
|
||||||
|
try:
|
||||||
|
self.magic_run_command_str = shlex.join(self.magic_run_command) # python 3.8+
|
||||||
|
except AttributeError:
|
||||||
|
self.magic_run_command_str = ' '.join(self.magic_run_command) # older python versions
|
||||||
|
|
||||||
|
if self.magic_run_command_str.startswith('/proc'):
|
||||||
|
try:
|
||||||
|
self.magic_found_parser = 'proc'
|
||||||
|
self.magic_stdout = self.open_text_file()
|
||||||
|
|
||||||
|
except OSError as e:
|
||||||
|
if self.debug:
|
||||||
|
raise
|
||||||
|
|
||||||
|
error_msg = os.strerror(e.errno)
|
||||||
|
utils.error_message([
|
||||||
|
f'"{self.magic_run_command_str}" file could not be opened: {error_msg}.'
|
||||||
|
])
|
||||||
|
self.jc_exit = self.JC_ERROR_EXIT
|
||||||
|
sys.exit(self.combined_exit_code())
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
if self.debug:
|
||||||
|
raise
|
||||||
|
|
||||||
|
utils.error_message([
|
||||||
|
f'"{self.magic_run_command_str}" file could not be opened. For details use the -d or -dd option.'
|
||||||
|
])
|
||||||
|
self.jc_exit = self.JC_ERROR_EXIT
|
||||||
|
sys.exit(self.combined_exit_code())
|
||||||
|
|
||||||
|
elif self.magic_found_parser:
|
||||||
|
try:
|
||||||
|
self.run_user_command()
|
||||||
|
if self.magic_stderr:
|
||||||
|
utils._safe_print(self.magic_stderr[:-1], file=sys.stderr)
|
||||||
|
|
||||||
|
except OSError as e:
|
||||||
|
if self.debug:
|
||||||
|
raise
|
||||||
|
|
||||||
|
error_msg = os.strerror(e.errno)
|
||||||
|
utils.error_message([
|
||||||
|
f'"{self.magic_run_command_str}" command could not be run: {error_msg}.'
|
||||||
|
])
|
||||||
|
self.jc_exit = self.JC_ERROR_EXIT
|
||||||
|
sys.exit(self.combined_exit_code())
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
if self.debug:
|
||||||
|
raise
|
||||||
|
|
||||||
|
utils.error_message([
|
||||||
|
f'"{self.magic_run_command_str}" command could not be run. For details use the -d or -dd option.'
|
||||||
|
])
|
||||||
|
self.jc_exit = self.JC_ERROR_EXIT
|
||||||
|
sys.exit(self.combined_exit_code())
|
||||||
|
|
||||||
|
elif self.magic_run_command is not None:
|
||||||
|
utils.error_message([f'"{self.magic_run_command_str}" cannot be used with Magic syntax. Use "jc -h" for help.'])
|
||||||
|
sys.exit(self.combined_exit_code())
|
||||||
|
|
||||||
|
def find_parser(self):
|
||||||
|
if self.magic_found_parser:
|
||||||
|
self.parser_module = _get_parser(self.magic_found_parser)
|
||||||
|
self.parser_name = self.parser_shortname(self.magic_found_parser)
|
||||||
|
|
||||||
|
else:
|
||||||
|
found = False
|
||||||
|
for arg in self.args:
|
||||||
|
self.parser_name = self.parser_shortname(arg)
|
||||||
|
|
||||||
|
if self.parser_name in parsers:
|
||||||
|
self.parser_module = _get_parser(arg)
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
utils.error_message(['Missing or incorrect arguments. Use "jc -h" for help.'])
|
||||||
|
self.jc_exit = self.JC_ERROR_EXIT
|
||||||
|
sys.exit(self.combined_exit_code())
|
||||||
|
|
||||||
|
if sys.stdin.isatty() and self.magic_stdout is None:
|
||||||
|
utils.error_message(['Missing piped data. Use "jc -h" for help.'])
|
||||||
|
self.jc_exit = self.JC_ERROR_EXIT
|
||||||
|
sys.exit(self.combined_exit_code())
|
||||||
|
|
||||||
|
def streaming_parse_and_print(self):
|
||||||
|
"""only supports UTF-8 string data for now"""
|
||||||
|
self.data_in = sys.stdin
|
||||||
|
result = self.parser_module.parse(
|
||||||
|
self.data_in,
|
||||||
|
raw=self.raw,
|
||||||
|
quiet=self.quiet,
|
||||||
|
ignore_exceptions=self.ignore_exceptions
|
||||||
|
)
|
||||||
|
|
||||||
|
for line in result:
|
||||||
|
self.data_out = line
|
||||||
|
if self.meta_out:
|
||||||
|
self.run_timestamp = datetime.now(timezone.utc)
|
||||||
|
self.add_metadata_to_output()
|
||||||
|
|
||||||
|
self.safe_print_out()
|
||||||
|
|
||||||
|
sys.exit(self.combined_exit_code())
|
||||||
|
|
||||||
|
def standard_parse_and_print(self):
|
||||||
|
"""supports binary and UTF-8 string data"""
|
||||||
|
self.data_in = self.magic_stdout or sys.stdin.buffer.read()
|
||||||
|
|
||||||
|
# convert to UTF-8, if possible. Otherwise, leave as bytes
|
||||||
|
try:
|
||||||
|
if isinstance(self.data_in, bytes):
|
||||||
|
self.data_in = self.data_in.decode('utf-8')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.data_out = self.parser_module.parse(
|
||||||
|
self.data_in,
|
||||||
|
raw=self.raw,
|
||||||
|
quiet=self.quiet
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.meta_out:
|
||||||
|
self.run_timestamp = datetime.now(timezone.utc)
|
||||||
|
self.add_metadata_to_output()
|
||||||
|
|
||||||
|
self.safe_print_out()
|
||||||
|
sys.exit(self.combined_exit_code())
|
||||||
|
|
||||||
def combined_exit_code(self):
|
def combined_exit_code(self):
|
||||||
self.exit_code = self.magic_returncode + self.jc_exit
|
self.exit_code = self.magic_returncode + self.jc_exit
|
||||||
self.exit_code = min(self.exit_code, 255)
|
self.exit_code = min(self.exit_code, 255)
|
||||||
@ -605,138 +708,18 @@ Examples:
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# if magic syntax used, try to run the command and error if it's not found, etc.
|
# if magic syntax used, try to run the command and error if it's not found, etc.
|
||||||
if self.magic_run_command:
|
self.do_magic()
|
||||||
try:
|
|
||||||
self.magic_run_command_str = shlex.join(self.magic_run_command) # python 3.8+
|
|
||||||
except AttributeError:
|
|
||||||
self.magic_run_command_str = ' '.join(self.magic_run_command) # older python versions
|
|
||||||
|
|
||||||
if self.magic_run_command_str.startswith('/proc'):
|
|
||||||
try:
|
|
||||||
self.magic_found_parser = 'proc'
|
|
||||||
self.magic_stdout = self.open_text_file()
|
|
||||||
|
|
||||||
except OSError as e:
|
|
||||||
if self.debug:
|
|
||||||
raise
|
|
||||||
|
|
||||||
error_msg = os.strerror(e.errno)
|
|
||||||
utils.error_message([
|
|
||||||
f'"{self.magic_run_command_str}" file could not be opened: {error_msg}.'
|
|
||||||
])
|
|
||||||
self.jc_exit = self.JC_ERROR_EXIT
|
|
||||||
sys.exit(self.combined_exit_code())
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
if self.debug:
|
|
||||||
raise
|
|
||||||
|
|
||||||
utils.error_message([
|
|
||||||
f'"{self.magic_run_command_str}" file could not be opened. For details use the -d or -dd option.'
|
|
||||||
])
|
|
||||||
self.jc_exit = self.JC_ERROR_EXIT
|
|
||||||
sys.exit(self.combined_exit_code())
|
|
||||||
|
|
||||||
elif self.magic_found_parser:
|
|
||||||
try:
|
|
||||||
self.run_user_command()
|
|
||||||
if self.magic_stderr:
|
|
||||||
utils._safe_print(self.magic_stderr[:-1], file=sys.stderr)
|
|
||||||
|
|
||||||
except OSError as e:
|
|
||||||
if self.debug:
|
|
||||||
raise
|
|
||||||
|
|
||||||
error_msg = os.strerror(e.errno)
|
|
||||||
utils.error_message([
|
|
||||||
f'"{self.magic_run_command_str}" command could not be run: {error_msg}.'
|
|
||||||
])
|
|
||||||
self.jc_exit = self.JC_ERROR_EXIT
|
|
||||||
sys.exit(self.combined_exit_code())
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
if self.debug:
|
|
||||||
raise
|
|
||||||
|
|
||||||
utils.error_message([
|
|
||||||
f'"{self.magic_run_command_str}" command could not be run. For details use the -d or -dd option.'
|
|
||||||
])
|
|
||||||
self.jc_exit = self.JC_ERROR_EXIT
|
|
||||||
sys.exit(self.combined_exit_code())
|
|
||||||
|
|
||||||
elif self.magic_run_command is not None:
|
|
||||||
utils.error_message([f'"{self.magic_run_command_str}" cannot be used with Magic syntax. Use "jc -h" for help.'])
|
|
||||||
sys.exit(self.combined_exit_code())
|
|
||||||
|
|
||||||
# find the correct parser
|
# find the correct parser
|
||||||
if self.magic_found_parser:
|
self.find_parser()
|
||||||
self.parser_module = _get_parser(self.magic_found_parser)
|
|
||||||
self.parser_name = self.parser_shortname(self.magic_found_parser)
|
|
||||||
|
|
||||||
else:
|
|
||||||
found = False
|
|
||||||
for arg in self.args:
|
|
||||||
self.parser_name = self.parser_shortname(arg)
|
|
||||||
|
|
||||||
if self.parser_name in parsers:
|
|
||||||
self.parser_module = _get_parser(arg)
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not found:
|
|
||||||
utils.error_message(['Missing or incorrect arguments. Use "jc -h" for help.'])
|
|
||||||
self.jc_exit = self.JC_ERROR_EXIT
|
|
||||||
sys.exit(self.combined_exit_code())
|
|
||||||
|
|
||||||
if sys.stdin.isatty() and self.magic_stdout is None:
|
|
||||||
utils.error_message(['Missing piped data. Use "jc -h" for help.'])
|
|
||||||
self.jc_exit = self.JC_ERROR_EXIT
|
|
||||||
sys.exit(self.combined_exit_code())
|
|
||||||
|
|
||||||
# parse and print to stdout
|
# parse and print to stdout
|
||||||
try:
|
try:
|
||||||
# differentiate between regular and streaming parsers
|
|
||||||
|
|
||||||
# streaming (only supports UTF-8 string data for now)
|
|
||||||
if _parser_is_streaming(self.parser_module):
|
if _parser_is_streaming(self.parser_module):
|
||||||
self.data_in = sys.stdin
|
self.streaming_parse_and_print()
|
||||||
result = self.parser_module.parse(self.data_in,
|
|
||||||
raw=self.raw,
|
|
||||||
quiet=self.quiet,
|
|
||||||
ignore_exceptions=self.ignore_exceptions)
|
|
||||||
|
|
||||||
for line in result:
|
|
||||||
self.data_out = line
|
|
||||||
if self.meta_out:
|
|
||||||
self.run_timestamp = datetime.now(timezone.utc)
|
|
||||||
self.add_metadata_to_output()
|
|
||||||
|
|
||||||
self.safe_print_out()
|
|
||||||
|
|
||||||
sys.exit(self.combined_exit_code())
|
|
||||||
|
|
||||||
# regular (supports binary and UTF-8 string data)
|
|
||||||
else:
|
else:
|
||||||
self.data_in = self.magic_stdout or sys.stdin.buffer.read()
|
self.standard_parse_and_print()
|
||||||
|
|
||||||
# convert to UTF-8, if possible. Otherwise, leave as bytes
|
|
||||||
try:
|
|
||||||
if isinstance(self.data_in, bytes):
|
|
||||||
self.data_in = self.data_in.decode('utf-8')
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.data_out = self.parser_module.parse(self.data_in,
|
|
||||||
raw=self.raw,
|
|
||||||
quiet=self.quiet)
|
|
||||||
|
|
||||||
if self.meta_out:
|
|
||||||
self.run_timestamp = datetime.now(timezone.utc)
|
|
||||||
self.add_metadata_to_output()
|
|
||||||
|
|
||||||
self.safe_print_out()
|
|
||||||
|
|
||||||
sys.exit(self.combined_exit_code())
|
|
||||||
|
|
||||||
except (ParseError, LibraryNotInstalled) as e:
|
except (ParseError, LibraryNotInstalled) as e:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
|
@ -55,3 +55,42 @@ old_pygments_colors = {
|
|||||||
'brightcyan': '#ansiturquoise',
|
'brightcyan': '#ansiturquoise',
|
||||||
'white': '#ansiwhite',
|
'white': '#ansiwhite',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
helptext_preamble_string = f'''\
|
||||||
|
jc converts the output of many commands, file-types, and strings to JSON or YAML
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
Standard syntax:
|
||||||
|
|
||||||
|
COMMAND | jc [OPTIONS] PARSER
|
||||||
|
|
||||||
|
cat FILE | jc [OPTIONS] PARSER
|
||||||
|
|
||||||
|
echo STRING | jc [OPTIONS] PARSER
|
||||||
|
|
||||||
|
Magic syntax:
|
||||||
|
|
||||||
|
jc [OPTIONS] COMMAND
|
||||||
|
|
||||||
|
jc [OPTIONS] /proc/<path-to-procfile>
|
||||||
|
|
||||||
|
Parsers:
|
||||||
|
'''
|
||||||
|
|
||||||
|
helptext_end_string = '''\
|
||||||
|
Examples:
|
||||||
|
Standard Syntax:
|
||||||
|
$ dig www.google.com | jc --pretty --dig
|
||||||
|
$ cat /proc/meminfo | jc --pretty --proc
|
||||||
|
|
||||||
|
Magic Syntax:
|
||||||
|
$ jc --pretty dig www.google.com
|
||||||
|
$ jc --pretty /proc/meminfo
|
||||||
|
|
||||||
|
Parser Documentation:
|
||||||
|
$ jc --help --dig
|
||||||
|
|
||||||
|
Show Hidden Parsers:
|
||||||
|
$ jc -hh
|
||||||
|
'''
|
Reference in New Issue
Block a user