1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2025-07-15 01:24:29 +02:00

proof of concept for passing command exit codes when using magic syntax. Needs more testing

This commit is contained in:
Kelly Brazil
2021-05-10 18:31:30 -07:00
parent f2ffb93eea
commit 19b540041a

131
jc/cli.py
View File

@ -10,6 +10,7 @@ import shlex
import importlib import importlib
import textwrap import textwrap
import signal import signal
import subprocess
import json import json
import jc import jc
import jc.appdirs as appdirs import jc.appdirs as appdirs
@ -395,15 +396,21 @@ def json_out(data, pretty=False, env_colors=None, mono=False, piped_out=False):
return json.dumps(data, separators=(',', ':'), ensure_ascii=False) return json.dumps(data, separators=(',', ':'), ensure_ascii=False)
def generate_magic_command(args): def magic_parser(args):
""" """
Return a tuple with a boolean and a command, where the boolean signifies that Return a tuple:
the command is valid, and the command is either a command string or None. valid_command (bool) is this a valid command? (exists in magic dict)
run_command (list) list of the user's command to run. None if no command.
jc_parser (str) parser to use for this user's command.
jc_options (list) list of jc options
Side-effect:
This function will reset sys.argv to an empty list
""" """
# Parse with magic syntax: jc -p ls -al # Parse with magic syntax: jc -p ls -al
if len(args) <= 1 or args[1].startswith('--'): if len(args) <= 1 or args[1].startswith('--'):
return False, None return False, None, None, []
# correctly parse escape characters and spaces with shlex # correctly parse escape characters and spaces with shlex
args_given = ' '.join(map(shlex.quote, args[1:])).split() args_given = ' '.join(map(shlex.quote, args[1:])).split()
@ -413,7 +420,7 @@ def generate_magic_command(args):
for arg in list(args_given): for arg in list(args_given):
# parser found - use standard syntax # parser found - use standard syntax
if arg.startswith('--'): if arg.startswith('--'):
return False, None return False, None, None, []
# option found - populate option list # option found - populate option list
elif arg.startswith('-'): elif arg.startswith('-'):
@ -423,13 +430,16 @@ def generate_magic_command(args):
else: else:
break break
# reset sys.argv since we are now done with it
sys.argv = []
# if -h, -a, or -v found in options, then bail out # if -h, -a, or -v found in options, then bail out
if 'h' in options or 'a' in options or 'v' in options: 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 -a'
if len(args_given) == 0: if len(args_given) == 0:
return False, None return False, None, None, []
magic_dict = {} magic_dict = {}
parser_info = about_jc()['parsers'] parser_info = about_jc()['parsers']
@ -446,26 +456,25 @@ def generate_magic_command(args):
# try to get a parser for two_word_command, otherwise get one for one_word_command # 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)) 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 = args_given
run_command = ' '.join(args_given) return (
if found_parser: True if found_parser else False,
cmd_options = ('-' + ''.join(options)) if options else '' run_command,
return True, ' '.join([run_command, '|', 'jc', found_parser, cmd_options]) found_parser,
else: options
return False, run_command )
def magic(): def run_user_command(command):
"""Runs the command generated by generate_magic_command() to support magic syntax""" """Use subprocess to run the user's command. Returns the STDOUT, STDERR, and the Exit Code as a tuple."""
valid_command, run_command = generate_magic_command(sys.argv) proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
if valid_command: stdout, stderr = proc.communicate()
os.system(run_command)
sys.exit(0) return (
elif run_command is None: stdout or '\n',
return stderr,
else: proc.returncode
jc.utils.error_message(f'parser not found for "{run_command}". Use "jc -h" for help.') )
sys.exit(1)
def main(): def main():
@ -481,13 +490,27 @@ def main():
pass pass
# try magic syntax first: e.g. jc -p ls -al # try magic syntax first: e.g. jc -p ls -al
magic() magic_stdout, magic_stderr, magic_exit_code, magic_options = None, None, None, []
valid_command, run_command, magic_found_parser, magic_options = magic_parser(sys.argv)
if valid_command:
magic_stdout, magic_stderr, magic_exit_code = run_user_command(run_command)
if magic_stderr:
print(magic_stderr, file=sys.stderr)
elif run_command is None:
pass
else:
run_command_str = ' '.join(run_command)
jc.utils.error_message(f'parser not found for "{run_command_str}". Use "jc -h" for help.')
sys.exit(1)
# set colors
jc_colors = os.getenv('JC_COLORS') jc_colors = os.getenv('JC_COLORS')
# set options
options = [] options = []
options.extend(magic_options)
# options # note that sys.argv will be an empty list after magic_parser finds the magic syntax is used
for opt in sys.argv: for opt in sys.argv:
if opt.startswith('-') and not opt.startswith('--'): if opt.startswith('-') and not opt.startswith('--'):
options.extend(opt[1:]) options.extend(opt[1:])
@ -521,40 +544,48 @@ def main():
import jc.tracebackplus import jc.tracebackplus
jc.tracebackplus.enable(context=11) jc.tracebackplus.enable(context=11)
if sys.stdin.isatty(): if sys.stdin.isatty() and magic_stdout is None:
jc.utils.error_message('Missing piped data. Use "jc -h" for help.') jc.utils.error_message('Missing piped data. Use "jc -h" for help.')
sys.exit(1) sys.exit(magic_exit_code + 1 if magic_exit_code else 1)
data = sys.stdin.read() data = magic_stdout or sys.stdin.read()
found = False # find the correct parser
if magic_found_parser:
parser = parser_module(magic_found_parser)
for arg in sys.argv: else:
parser_name = parser_shortname(arg) found = False
for arg in sys.argv:
parser_name = parser_shortname(arg)
if parser_name in parsers: if parser_name in parsers:
# load parser module just in time so we don't need to load all modules # load parser module just in time so we don't need to load all modules
parser = parser_module(arg) parser = parser_module(arg)
try:
result = parser.parse(data, raw=raw, quiet=quiet)
found = True found = True
break break
except Exception: if not found:
if debug: jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.')
raise sys.exit(1)
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: # parse the data
jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.') try:
sys.exit(1) result = parser.parse(data, raw=raw, quiet=quiet)
except Exception:
if debug:
raise
else:
import jc.utils
jc.utils.error_message(
f'{parser_name} parser could not parse the input data. Did you use the correct parser?\n'
' For details use the -d or -dd option. Use "jc -h" for help.')
sys.exit(magic_exit_code or 1)
# output the json
print(json_out(result, pretty=pretty, env_colors=jc_colors, mono=mono, piped_out=piped_output())) print(json_out(result, pretty=pretty, env_colors=jc_colors, mono=mono, piped_out=piped_output()))
sys.exit(magic_exit_code or 0)
if __name__ == '__main__': if __name__ == '__main__':