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:
131
jc/cli.py
131
jc/cli.py
@ -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__':
|
||||||
|
Reference in New Issue
Block a user