From 2460fb430fe814bfef420d1e03e2c2aa975f432f Mon Sep 17 00:00:00 2001 From: Edward Emmett Date: Tue, 9 Nov 2021 18:24:11 -0500 Subject: [PATCH 1/2] Redirected the Assistant to stderr, ensuring that it and the traceback are printed within the same buffer. Changed the sizing method, taking the best of manual formatting and automatic formatting. `traceback` doesn't use cpythons current format for determining the pathname of an object, but it does format most of the messages correct. Small limitation with 3.10 in mind, the suggestions added by 3.10's error handling update doesn't cross over to python's traceback. Namely the suggestion system added is done completely in python, negating the ability to grab it. The result of STDERR could be captured to parse that first, ensuring that it's always the correct length. --- coding_assistant/__init__.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/coding_assistant/__init__.py b/coding_assistant/__init__.py index 2be4d5c..0ea152a 100644 --- a/coding_assistant/__init__.py +++ b/coding_assistant/__init__.py @@ -3,7 +3,7 @@ _old_hook = sys.excepthook -# I don't want to be sued my MS so I'll use a single 'p' +# I don't want to be sued by MS so I'll use a single 'p' clipy = """ _-_ | / /_ \\ |/ @@ -14,11 +14,26 @@ ¯--¯""" +def pathname(obj): + """Create qualified pathname for objs. eg. module.sub.obj, module.sub.inner.obj""" + + module = obj.__module__ + + # TB doesn't include builtin path + if module is None or module == "builtins": + return obj.__name__ + return module + '.' + obj.__name__ + + def assistant_print(type_, value, tb): _old_hook(type_, value, tb) - width = len(f"{type_.__qualname__}{f': {value}' if value else ''}")-2 - print(f"\\{'_'*width}/" + clipy) - + # Traceback is inconsistent about which path to use, `format_exception_only` displays relative paths, + # dropping magic roots, eg. "__main__" but still formatting necessary message information + # When the exception hook uses them, it drops absolute paths, eg. "__main__.Super.Sub" becomes "__main__.Sub" + message = traceback.format_exception_only(value).pop().strip().split(": ", 1) + message[0] = pathname(type_) + width = len(": ".join(message)) - 2 + print(f"\\{'_' * width}/" + clipy, file=sys.stderr) sys.excepthook = assistant_print From 467fcde92a05336a9c365f310b61e4f829aef9d4 Mon Sep 17 00:00:00 2001 From: Edward Emmett Date: Tue, 9 Nov 2021 18:32:21 -0500 Subject: [PATCH 2/2] A barebones CLI tool, the new __main__.py file can be run from a package, `python -m __main__.py test.py` or by installing it through pip with the local sdist. A console script was added to the setup.py and can be used as `assistant test.py`. Notable limitation: Using `input("prompt")` buffers the output of the prompt until user input has been given. This likely extends to other areas of user interaction as well. This also seemingly varies from terminal to terminal, PyCharms builtin terminal ignores this limitation and functions fine. Windows powershell and cmd both buffer the input, alongside the buffered input, both terminals also pass the prompt of the input to stderr. --- coding_assistant/__init__.py | 6 +++++ coding_assistant/__main__.py | 44 ++++++++++++++++++++++++++++++++++++ setup.py | 4 ++++ 3 files changed, 54 insertions(+) create mode 100644 coding_assistant/__main__.py diff --git a/coding_assistant/__init__.py b/coding_assistant/__init__.py index 0ea152a..9bbc4f2 100644 --- a/coding_assistant/__init__.py +++ b/coding_assistant/__init__.py @@ -1,5 +1,11 @@ # type: ignore import sys +import traceback + + +class AssistantException(Exception): + ... + _old_hook = sys.excepthook diff --git a/coding_assistant/__main__.py b/coding_assistant/__main__.py new file mode 100644 index 0000000..9b3f154 --- /dev/null +++ b/coding_assistant/__main__.py @@ -0,0 +1,44 @@ +import sys +from pathlib import Path +from subprocess import Popen, PIPE + +import coding_assistant + + +# Hardcoded values, helps refactoring for multiple assistants down the line +PATH_IDX = 1 +ARGS_IDX = 2 +ASSISTANT = coding_assistant.clipy + + +def cli(): + try: + path = Path(sys.argv[PATH_IDX]).resolve() + except IndexError: + raise coding_assistant.AssistantException("Path to file is missing!") from None + args = sys.argv[ARGS_IDX:] + + # Path to Python Interpreter + python = sys.executable + p = Popen([python, path, *args], text=True, stdin=sys.stdin, stdout=sys.stdout, stderr=PIPE) + _, err = p.communicate() + if err: + # Normal encoding of the ASCII art was causing errors, this is Windows-1252, a legacy encoding scheme + err = err + + # Break into lines + split_assistant = ASSISTANT.splitlines() + split_err = err.splitlines() + + # Prevent double assistant by comparing lines that should be equivalent + # Skip first line due to funky spacing of decoded characters + if split_assistant[1:] != split_err[-len(split_assistant)+1:]: + width = len(split_err[-1]) - 2 + err += f"\\{'_' * width}/" + ASSISTANT + + print(err, file=sys.stderr) + return p.returncode + + +if __name__ == '__main__': + sys.exit(cli()) diff --git a/setup.py b/setup.py index 7db5e68..1fa1900 100644 --- a/setup.py +++ b/setup.py @@ -25,4 +25,8 @@ "Operating System :: OS Independent", ], python_requires='>=3.9', + entry_points={'console_scripts': [ + 'assistant = coding_assistant.__main__:cli', + ], + } )