Projects STRLCPY stackexplain Commits ae07e76c
🤬
  • ■ ■ ■ ■ ■
    stackexplain/__init__.py
     1 + 
  • ■ ■ ■ ■ ■ ■
    stackexplain/__main__.py
     1 +from stackexplain import main
     2 + 
     3 + 
     4 +if __name__ == "__main__":
     5 + main()
     6 + 
  • ■ ■ ■ ■ ■
    stackexplain/config.json
     1 + 
  • ■ ■ ■ ■ ■ ■
    stackexplain/stackexplain.py
     1 +import sys
     2 + 
     3 +import utilities.chatgpt as gpt
     4 +import utilities.parsers as parsers
     5 +import utilities.printers as printers
     6 +import utilities.code_execution as code_exec
     7 + 
     8 + 
     9 +def main():
     10 + # TODO: Check if local config file is populated
     11 + # If not, prompt user to enter OpenAI credentials
     12 + 
     13 + args = sys.argv
     14 + if len(args) == 1 or args[1].lower() in ("-h", "--help"):
     15 + printers.print_help_message()
     16 + return
     17 + 
     18 + language = parsers.get_language(args)
     19 + if not language:
     20 + printers.print_invalid_language_message()
     21 + return
     22 + 
     23 + if not gpt.is_user_registered():
     24 + gpt.register_openai_credentials()
     25 + 
     26 + error_message = code_exec.execute_code(args, language)
     27 + if not error_message:
     28 + return
     29 + 
     30 + print()
     31 + 
     32 + with printers.LoadingMessage():
     33 + explanation = gpt.get_chatgpt_explanation(language, error_message)
     34 + 
     35 + # TODO: Add syntax highlighting to code
     36 + # TODO: Print word-by-word
     37 + print(explanation)
     38 + 
  • ■ ■ ■ ■ ■
    stackexplain/utilities/__init__.py
     1 + 
  • ■ ■ ■ ■ ■ ■
    stackexplain/utilities/chatgpt.py
     1 +from revChatGPT.revChatGPT import Chatbot
     2 + 
     3 +# TEMP: Have user fill this out
     4 +config = {}
     5 + 
     6 + 
     7 +#########
     8 +# HELPERS
     9 +#########
     10 + 
     11 + 
     12 +def construct_query(language, error_message):
     13 + language = "java" if language == "javac" else language
     14 + language = "python" if language == "python3" else language
     15 + language = "go" if language == "go run" else language
     16 + 
     17 + # TODO: Create an enum for mapping languages to exec commands
     18 + query = f"Explain this {language} error message in brief and simple terms:"
     19 + query += "\n```"
     20 + query += f"\n{error_message}"
     21 + query += "\n```"
     22 + 
     23 + return query
     24 + 
     25 + 
     26 +######
     27 +# MAIN
     28 +######
     29 + 
     30 + 
     31 +def is_user_registered():
     32 + return True # TODO
     33 + 
     34 + 
     35 +def register_openai_credentials():
     36 + pass # TODO
     37 + 
     38 + 
     39 +def get_chatgpt_explanation(language, error_message):
     40 + query = construct_query(language, error_message)
     41 + chatbot = Chatbot(config, conversation_id=None)
     42 + return chatbot.get_chat_response(query)["message"].strip()
     43 + 
  • ■ ■ ■ ■ ■ ■
    stackexplain/utilities/code_execution.py
     1 +import os
     2 +from queue import Queue
     3 +from subprocess import PIPE, Popen
     4 +from threading import Thread
     5 + 
     6 +from utilities.parsers import get_code_exec_command, get_error_message
     7 + 
     8 + 
     9 +#########
     10 +# HELPERS
     11 +#########
     12 + 
     13 + 
     14 +def read(pipe, funcs):
     15 + """
     16 + Reads and pushes piped output to a shared queue and appropriate lists.
     17 + """
     18 + 
     19 + for line in iter(pipe.readline, b''):
     20 + for func in funcs:
     21 + func(line.decode("utf-8"))
     22 + 
     23 + pipe.close()
     24 + 
     25 + 
     26 +def write(get):
     27 + """
     28 + Pulls output from shared queue and prints to terminal.
     29 + """
     30 + 
     31 + for line in iter(get, None):
     32 + line = line.replace("\n", "")
     33 + print(line)
     34 + 
     35 + 
     36 +######
     37 +# MAIN
     38 +######
     39 + 
     40 + 
     41 +def execute_code(args, language):
     42 + """
     43 + Executes a given command in a subshell, pipes stdout/err to the current
     44 + shell, and returns the stderr.
     45 + """
     46 + 
     47 + command = get_code_exec_command(args, language)
     48 + process = Popen(
     49 + command,
     50 + cwd=None,
     51 + shell=False,
     52 + close_fds=True,
     53 + stdout=PIPE,
     54 + stderr=PIPE,
     55 + bufsize=-1
     56 + )
     57 + 
     58 + output, errors = [], []
     59 + pipe_queue = Queue()
     60 + 
     61 + # Threads for reading stdout and stderr pipes and pushing to a shared queue
     62 + stdout_thread = Thread(target=read, args=(process.stdout, [pipe_queue.put, output.append]))
     63 + stderr_thread = Thread(target=read, args=(process.stderr, [pipe_queue.put, errors.append]))
     64 + 
     65 + # Thread for printing items in the queue
     66 + writer_thread = Thread(target=write, args=(pipe_queue.get,))
     67 + 
     68 + # Spawns each thread
     69 + for thread in (stdout_thread, stderr_thread, writer_thread):
     70 + thread.daemon = True
     71 + thread.start()
     72 + 
     73 + process.wait()
     74 + 
     75 + for thread in (stdout_thread, stderr_thread):
     76 + thread.join()
     77 + 
     78 + pipe_queue.put(None)
     79 + 
     80 + # output = ''.join(output)
     81 + errors = ''.join(errors)
     82 + 
     83 + # File doesn't exist, for java, command[1] is a class name instead of a file
     84 + if "java" != command[0] and not os.path.isfile(command[1]):
     85 + return None
     86 + 
     87 + return get_error_message(errors, language)
     88 + 
  • ■ ■ ■ ■ ■ ■
    stackexplain/utilities/parsers.py
     1 +import re
     2 + 
     3 + 
     4 +######
     5 +# MAIN
     6 +######
     7 + 
     8 + 
     9 +def get_language(args):
     10 + """
     11 + Returns the language a file is written in.
     12 + """
     13 + 
     14 + file_path = args[1].lower()
     15 + if file_path.endswith(".py"):
     16 + return "python3"
     17 + elif file_path.endswith(".js"):
     18 + return "node"
     19 + elif file_path.endswith(".go"):
     20 + return "go run"
     21 + elif file_path.endswith(".rb"):
     22 + return "ruby"
     23 + elif file_path.endswith(".java"):
     24 + return "javac" # Compile Java Source File
     25 + elif file_path.endswith(".class"):
     26 + return "java" # Run Java Class File
     27 + else:
     28 + return "" # Unknown language
     29 + 
     30 + 
     31 +def get_code_exec_command(args, language):
     32 + fp_and_args = args[1:]
     33 + if language == "java":
     34 + fp_and_args = [arg.replace(".class", "") for arg in fp_and_args]
     35 + 
     36 + return [language] + fp_and_args
     37 + 
     38 + 
     39 +def get_error_message(error, language):
     40 + """Filters the stack trace from stderr and returns only the error message."""
     41 + if error == '' or error is None:
     42 + return None
     43 + elif language == "python3":
     44 + if any(e in error for e in ["KeyboardInterrupt", "SystemExit", "GeneratorExit"]): # Non-compiler errors
     45 + return None
     46 + else:
     47 + return error.split('\n')[-2].strip()
     48 + elif language == "node":
     49 + return error.split('\n')[4][1:]
     50 + elif language == "go run":
     51 + return error.split('\n')[1].split(": ", 1)[1][1:]
     52 + elif language == "ruby":
     53 + error_message = error.split('\n')[0]
     54 + return error_message[error_message.rfind(": ") + 2:]
     55 + elif language == "javac":
     56 + m = re.search(r'.*error:(.*)', error.split('\n')[0])
     57 + return m.group(1) if m else None
     58 + elif language == "java":
     59 + for line in error.split('\n'):
     60 + # Multiple error formats
     61 + m = re.search(r'.*(Exception|Error):(.*)', line)
     62 + if m and m.group(2):
     63 + return m.group(2)
     64 + 
     65 + m = re.search(r'Exception in thread ".*" (.*)', line)
     66 + if m and m.group(1):
     67 + return m.group(1)
     68 + 
     69 + return None
     70 + 
  • ■ ■ ■ ■ ■ ■
    stackexplain/utilities/printers.py
     1 +from itertools import cycle
     2 +from shutil import get_terminal_size
     3 +from threading import Thread
     4 +from time import sleep
     5 + 
     6 +# ASCII color codes
     7 +GREEN = '\033[92m'
     8 +GRAY = '\033[90m'
     9 +CYAN = '\033[36m'
     10 +RED = '\033[31m'
     11 +YELLOW = '\033[33m'
     12 +END = '\033[0m'
     13 +UNDERLINE = '\033[4m'
     14 +BOLD = '\033[1m'
     15 + 
     16 + 
     17 +######
     18 +# MAIN
     19 +######
     20 + 
     21 + 
     22 +def register_openai_credentials():
     23 + pass # TODO
     24 + 
     25 + 
     26 +def print_help_message():
     27 + pass # TODO
     28 + 
     29 + 
     30 +def print_invalid_language_message():
     31 + # TODO: Colorize this better
     32 + print(f"\n{RED}Sorry, stackexplain doesn't support this file type.\n{END}")
     33 + 
     34 + 
     35 +class LoadingMessage:
     36 + def __init__(self, timeout=0.1):
     37 + """
     38 + A loader-like context manager
     39 + 
     40 + Args:
     41 + desc (str, optional): The loader's description. Defaults to "Loading...".
     42 + end (str, optional): Final print. Defaults to "Done!".
     43 + timeout (float, optional): Sleep time between prints. Defaults to 0.1.
     44 + """
     45 + 
     46 + self.steps = ["⢿", "⣻", "⣽", "⣾", "⣷", "⣯", "⣟", "⡿"]
     47 + self.message = "Asking ChatGPT to explain your error"
     48 + # self.end = ""
     49 + self.timeout = timeout
     50 + 
     51 + self._thread = Thread(target=self._animate, daemon=True)
     52 + self.done = False
     53 + 
     54 + def start(self):
     55 + self._thread.start()
     56 + return self
     57 + 
     58 + def stop(self):
     59 + self.done = True
     60 + cols = get_terminal_size((80, 20)).columns
     61 + 
     62 + print("\r" + " " * cols, end="", flush=True)
     63 + # print(f"\r{self.end}", flush=True)
     64 + 
     65 + def _animate(self):
     66 + for step in cycle(self.steps):
     67 + if self.done:
     68 + break
     69 + 
     70 + print(f"\r{step} {self.message}", flush=True, end="")
     71 + 
     72 + sleep(self.timeout)
     73 + 
     74 + def __enter__(self):
     75 + self.start()
     76 + 
     77 + def __exit__(self, exc_type, exc_value, tb):
     78 + # handle exceptions with those variables ^
     79 + self.stop()
     80 + 
Please wait...
Page is in error, reload to recover