Created
          January 4, 2025 17:06 
        
      - 
      
- 
        Save sccreeper/8e39485d694e5d6daab2f0cb45db7bca to your computer and use it in GitHub Desktop. 
    Runner for my university's programming worksheets.
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | import importlib | |
| import os | |
| from inspect import getmembers, isfunction, signature | |
| import typing | |
| # Purpose of this file is to act as a shim around all the files in the practical folders. | |
| # It allows you to run specific functions and also means they can share common libraries in the lib folder | |
| # All functions must have type hints in order to work properly | |
| # Never try and do "reflection" in Python, if you can even call it that | |
| def main(): | |
| practical_week = input("Practical week: ") | |
| files = os.listdir(f"./week{practical_week}") | |
| if len(files) == 0: | |
| print("No files in week!") | |
| return | |
| elif not f"pract{practical_week}.py" in files: | |
| file_list: list[str] = [ x.split(".")[0] for x in files if | |
| not os.path.isdir(f"./week{practical_week}/{x}") | |
| and os.path.splitext(f"./week{practical_week}/{x}")[1] == ".py" ] | |
| for i in range(len(file_list)): | |
| print(f"{i}: {file_list[i]}") | |
| sel: int = int(input("Select file number: ")) | |
| if sel < 0 or sel >= len(file_list): | |
| print("Invalid number!") | |
| return | |
| os.system("cls" if os.name == "nt" else "clear") | |
| os.chdir(f"./week{practical_week}") | |
| importlib.import_module(f"week{practical_week}.{file_list[sel]}") | |
| else: | |
| print("Importing module...") | |
| func_module = importlib.import_module(f"week{practical_week}.pract{practical_week}") | |
| method_list = getmembers(func_module, isfunction) | |
| for i in range(len(method_list)): | |
| # Default blank explanation if no doc string exists, otherwise format it nicely | |
| doc_str: str | |
| if method_list[i][1].__doc__ == None: | |
| doc_str = "" | |
| else: | |
| doc_str = f"| {str(method_list[i][1].__doc__).replace('\n', '')}" | |
| print(f"{i} | {method_list[i][0]} {doc_str}") | |
| func_no = int(input("Select function number: ")) | |
| if func_no < 0 or func_no >= len(method_list): | |
| print("Invalid number") | |
| return | |
| # Any file reads are relative to the relevant practical file in it's directory. | |
| os.chdir(f"./week{practical_week}") | |
| func_sig = signature(method_list[func_no][1]) | |
| # If we don't have any parameters, then we can just can the function normally. | |
| # Otherwise we loop through the parameters, asking the user for a value and converting it to the appropriate type. | |
| # In the case of arrays, we ask the user for multiple array items, before stopping on Ctrl+C followed by enter. | |
| # Doesn't support dictionaries yet | |
| if len(func_sig.parameters) == 0: | |
| os.system("cls" if os.name == "nt" else "clear") | |
| val = method_list[func_no][1]() | |
| if not isinstance(val, type(None)): | |
| print(val) | |
| else: | |
| params = [] | |
| for param_name, param_type in func_sig.parameters.items(): | |
| param_val: any = None | |
| # If the type annotation is a list, then we have different logic | |
| # Below is the logic for a dict | |
| # Both only go 1 level deep, no recursion yet. | |
| # Finally is the logic for a base type e.g. int, str, float etc. | |
| if typing.get_origin(param_type.annotation) is list: | |
| # Continuously add arguments of the right type until user presses Ctrl+C followed by Enter | |
| param_val: list = [] | |
| while True: | |
| try: | |
| list_item = typing.get_args(param_type.annotation)[0]( | |
| input(f"list[{typing.get_args(param_type.annotation)[0].__name__}][{len(param_val)}] -> ") | |
| ) | |
| param_val.append(list_item) | |
| except KeyboardInterrupt: | |
| break | |
| params.append(param_val) | |
| elif typing.get_origin(param_type.annotation) is dict: | |
| param_val: dict = {} | |
| while True: | |
| try: | |
| key_type = typing.get_args(param_type.annotation)[0] | |
| value_type = typing.get_args(param_type.annotation)[1] | |
| key_val = key_type(input(f"dict[{len(param_val)}].key: {key_type.__name__} -> ")) | |
| value_val = value_type(input(f"dict[{len(param_val)}].value: {value_type.__name__} -> ")) | |
| param_val[key_val] = value_val | |
| except KeyboardInterrupt: | |
| break | |
| params.append(param_val) | |
| else: | |
| param_val = input(f"{param_name}: {param_type.annotation.__name__} -> ") | |
| params.append(param_type.annotation(param_val)) | |
| # One liner to clear the screen | |
| os.system("cls" if os.name == "nt" else "clear") | |
| val = method_list[func_no][1](*params) | |
| if not isinstance(val, type(None)): | |
| print(val) | |
| if __name__ == "__main__": | |
| main() | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment