diff --git a/docs/utils.md b/docs/utils.md index 3eb14873..3df5e8fc 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -4,14 +4,16 @@ jc - JSON CLI output utility utils ## warning_message ```python -warning_message(message) +warning_message(message_lines) ``` -Prints a warning message for non-fatal issues +Prints warning message for non-fatal issues. The first line is appended with +'jc: Warning - ' and subsequent lines are indented. Wraps text as needed based +on the terminal width. Parameters: - message: (string) text of message + message: (list) list of string lines Returns: @@ -20,14 +22,16 @@ Returns: ## error_message ```python -error_message(message) +error_message(message_lines) ``` -Prints an error message for fatal issues +Prints an error message for fatal issues. The first line is appended with +'jc: Error - ' and subsequent lines are indented. Wraps text as needed based +on the terminal width. Parameters: - message: (string) text of message + message: (list) list of string lines Returns: diff --git a/jc/cli.py b/jc/cli.py index a0190206..41b4dc25 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -219,7 +219,7 @@ def set_env_colors(env_colors=None): # if there is an issue with the env variable, just set all colors to default and move on if input_error: - jc.utils.warning_message('Could not parse JC_COLORS environment variable') + jc.utils.warning_message(['Could not parse JC_COLORS environment variable']) color_list = ['default', 'default', 'default', 'default'] # Try the color set in the JC_COLORS env variable first. If it is set to default, then fall back to default colors @@ -569,25 +569,25 @@ def main(): if debug: raise else: - jc.utils.error_message(f'"{run_command_str}" command could not be found. For details use the -d or -dd option.') + jc.utils.error_message([f'"{run_command_str}" command could not be found. For details use the -d or -dd option.']) sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) except OSError: if debug: raise else: - jc.utils.error_message(f'"{run_command_str}" command could not be run due to too many open files. For details use the -d or -dd option.') + jc.utils.error_message([f'"{run_command_str}" command could not be run due to too many open files. For details use the -d or -dd option.']) sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) except Exception: if debug: raise else: - jc.utils.error_message(f'"{run_command_str}" command could not be run. For details use the -d or -dd option.') + jc.utils.error_message([f'"{run_command_str}" command could not be run. For details use the -d or -dd option.']) sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) elif run_command is not None: - jc.utils.error_message(f'"{run_command_str}" cannot be used with Magic syntax. Use "jc -h" for help.') + jc.utils.error_message([f'"{run_command_str}" cannot be used with Magic syntax. Use "jc -h" for help.']) sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # find the correct parser @@ -606,16 +606,16 @@ def main(): break if not found: - jc.utils.error_message('Missing or incorrect arguments. Use "jc -h" for help.') + jc.utils.error_message(['Missing or incorrect arguments. Use "jc -h" for help.']) sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # check for input errors (pipe vs magic) if not sys.stdin.isatty() and magic_stdout: - jc.utils.error_message('Piped data and Magic syntax used simultaneously. Use "jc -h" for help.') + jc.utils.error_message(['Piped data and Magic syntax used simultaneously. Use "jc -h" for help.']) sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) elif 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(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) # parse and print to stdout @@ -652,19 +652,17 @@ def main(): if debug: raise else: - jc.utils.error_message( - f'Parser issue with {parser_name}:\n' - f' {e.__class__.__name__}: {e}\n' - ' For details use the -d or -dd option. Use "jc -h" for help.') + jc.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.']) sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) except json.JSONDecodeError: if debug: raise else: - jc.utils.error_message( - 'There was an issue generating the JSON output.\n' - ' For details use the -d or -dd option.') + jc.utils.error_message(['There was an issue generating the JSON output.', + 'For details use the -d or -dd option.']) sys.exit(combined_exit_code(magic_exit_code, JC_ERROR_EXIT)) except Exception: @@ -673,12 +671,13 @@ def main(): else: streaming_msg = '' if getattr(parser.info, 'streaming', None): - streaming_msg = ' Use the -qq option to ignore streaming parser errors.\n' + streaming_msg = 'Use the -qq option to ignore streaming parser errors.' - jc.utils.error_message( - f'{parser_name} parser could not parse the input data. Did you use the correct parser?\n' - f'{streaming_msg}' - ' For details use the -d or -dd option. Use "jc -h" for help.') + jc.utils.error_message([ + 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)) diff --git a/jc/parsers/file.py b/jc/parsers/file.py index b9b453e4..d34258c6 100644 --- a/jc/parsers/file.py +++ b/jc/parsers/file.py @@ -130,7 +130,7 @@ def parse(data, raw=False, quiet=False): ) except IndexError: if not warned: - jc.utils.warning_message('Filenames with newline characters detected. Some filenames may be truncated.') + jc.utils.warning_message(['Filenames with newline characters detected. Some filenames may be truncated.']) warned = True if raw: diff --git a/jc/parsers/ls.py b/jc/parsers/ls.py index c0c6ce6c..d9f385d4 100644 --- a/jc/parsers/ls.py +++ b/jc/parsers/ls.py @@ -255,7 +255,7 @@ def parse(data, raw=False, quiet=False): continue if not quiet and next_is_parent and not entry.endswith(':') and not warned: - jc.utils.warning_message('Newline characters detected. Filenames probably corrupted. Use ls -l or -b instead.') + jc.utils.warning_message(['Newline characters detected. Filenames probably corrupted. Use ls -l or -b instead.']) warned = True output_line['filename'] = entry diff --git a/jc/parsers/traceroute.py b/jc/parsers/traceroute.py index e7d3e99f..740a06a6 100644 --- a/jc/parsers/traceroute.py +++ b/jc/parsers/traceroute.py @@ -389,7 +389,7 @@ def parse(data, raw=False, quiet=False): # print warning to STDERR if not quiet: - jc.utils.warning_message('No header row found. For destination info redirect STDERR to STDOUT') + jc.utils.warning_message(['No header row found. For destination info redirect STDERR to STDOUT']) data = '\n'.join(new_data) diff --git a/jc/utils.py b/jc/utils.py index 2ac5d1c0..7156ea52 100644 --- a/jc/utils.py +++ b/jc/utils.py @@ -2,41 +2,77 @@ import sys import re import locale +import shutil from datetime import datetime, timezone +from textwrap import TextWrapper -def warning_message(message): +def warning_message(message_lines): """ - Prints a warning message for non-fatal issues + Prints warning message for non-fatal issues. The first line is appended with + 'jc: Warning - ' and subsequent lines are indented. Wraps text as needed based + on the terminal width. Parameters: - message: (string) text of message + message: (list) list of string lines Returns: None - just prints output to STDERR """ + # this is for backwards compatibility with existing custom parsers + if isinstance(message_lines, str): + message_lines = [message_lines] - error_string = f'jc: Warning - {message}' - print(error_string, file=sys.stderr) + columns = shutil.get_terminal_size().columns + + first_wrapper = TextWrapper(width=columns, subsequent_indent=' ' * 15) + next_wrapper = TextWrapper(width=columns, initial_indent=' ' * 15, + subsequent_indent=' ' * 15) + + first_line = message_lines.pop(0) + first_str = f'jc: Warning - {first_line}' + first_str = first_wrapper.fill(first_str) + print(first_str, file=sys.stderr) + + for line in message_lines: + if line == '': + continue + message = next_wrapper.fill(line) + print(message, file=sys.stderr) -def error_message(message): +def error_message(message_lines): """ - Prints an error message for fatal issues + Prints an error message for fatal issues. The first line is appended with + 'jc: Error - ' and subsequent lines are indented. Wraps text as needed based + on the terminal width. Parameters: - message: (string) text of message + message: (list) list of string lines Returns: None - just prints output to STDERR """ + columns = shutil.get_terminal_size().columns - error_string = f'jc: Error - {message}' - print(error_string, file=sys.stderr) + first_wrapper = TextWrapper(width=columns, subsequent_indent=' ' * 13) + next_wrapper = TextWrapper(width=columns, initial_indent=' ' * 13, + subsequent_indent=' ' * 13) + + first_line = message_lines.pop(0) + first_str = f'jc: Error - {first_line}' + first_str = first_wrapper.fill(first_str) + print(first_str, file=sys.stderr) + + for line in message_lines: + if line == '': + continue + message = next_wrapper.fill(line) + print(message, file=sys.stderr) def compatibility(mod_name, compatible): @@ -64,8 +100,8 @@ def compatibility(mod_name, compatible): if not platform_found: mod = mod_name.split('.')[-1] compat_list = ', '.join(compatible) - warning_message(f'{mod} parser not compatible with your OS ({sys.platform}).\n' - f' Compatible platforms: {compat_list}') + warning_message([f'{mod} parser not compatible with your OS ({sys.platform}).', + f'Compatible platforms: {compat_list}']) def has_data(data):