Created
January 22, 2022 11:05
-
-
Save akuafif/ad33decf0ce97f995eb07f7a14d98fe0 to your computer and use it in GitHub Desktop.
ICT162 Class Generator with typehints. To save time during the TOA exam.
This file contains 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
# class template generator to save time during 162 toa exam | |
# with typehints | |
# no gui validation | |
import tkinter as tk | |
from tkinter import Frame, Button, Label, StringVar, messagebox | |
from tkinter.scrolledtext import ScrolledText | |
from tkinter.constants import DISABLED, NORMAL, END, WORD | |
class ClassBuilder(Frame): | |
""" ClassBuilder is a subclass of Frame class """ | |
def __init__(self, parent): | |
Frame.__init__(self, parent) | |
self.createWidgets() | |
self.grid(row=0,column=0) | |
def createWidgets(self) -> None: | |
""" Loads the widget to the GUI. """ | |
self.__classFrame = Frame(self) | |
Label(self.__classFrame, text='Class Name', width=15).grid(row=0,column=0, padx=5, pady=2, sticky="ew") | |
Label(self.__classFrame, text='Class Variable w/\nassignment value', width=15).grid(row=1,column=0, padx=5, pady=2, sticky="w") | |
self.__strClassName = StringVar() | |
self.__strClassName.set('Employee{abstract} OR Manager(Employee)') | |
self.__enClassName = tk.Entry(self.__classFrame,width=25, textvariable=self.__strClassName) | |
self.__sclClassVar = ScrolledText(self.__classFrame, width=30, height=3, wrap=WORD, state=NORMAL) | |
self.__sclClassVar.insert(END, '_id = 1') | |
self.__enClassName.grid(row=0,column=1, sticky="we") | |
self.__sclClassVar.grid(row=1,column=1, sticky="we") | |
self.__classFrame.grid_columnconfigure(0, weight=1) # auto width | |
self.__instanceVarFrame = Frame(self) | |
Label(self.__instanceVarFrame, text='Instance Variable\n\nFormat\n_varName: varType', width=15).grid(row=0,column=0, padx=5, pady=2, sticky="w") | |
self.__sclInstanceVar = ScrolledText(self.__instanceVarFrame, width=35, height=5, wrap=WORD, state=NORMAL, font=("Arial 10")) | |
self.__sclInstanceVar.insert(END, '_name: str') | |
self.__sclInstanceVar.grid(row=0,column=1, sticky="we") | |
self.__methodsFrame = Frame(self) | |
Label(self.__methodsFrame, text='Methods\n\nFormat\nname(self, param)', width=15).grid(row=0,column=0, padx=5, pady=2, sticky="w") | |
self.__sclMethods = ScrolledText(self.__methodsFrame, width=35, height=8, wrap=WORD, state=NORMAL, font=("Arial 10")) | |
self.__sclMethods.insert(END, '__init__(self, name)\n__str__(self): str') | |
self.__sclMethods.grid(row=0,column=1, sticky="we") | |
self.__buttonFrame = Frame(self) | |
self.__btnClear = Button(self.__buttonFrame, text='Clear All',command=self.clearOutput) | |
self.__btnGenerate = Button(self.__buttonFrame, text='Generate',command=self.generateClick) | |
self.__btnClear.grid(row=0,column=0, pady=5, padx=5, sticky="we") | |
self.__btnGenerate.grid(row=0,column=1, pady=5, padx=5, sticky="we") | |
self.__outputFrame = Frame(self) | |
Label(self.__outputFrame, text='Output', width=15).grid(row=0,column=0, padx=5, pady=2) | |
self.__sclOutput = ScrolledText(self.__outputFrame, width=50, height=15, wrap=WORD, state=NORMAL, font=("Arial 10")) | |
self.__sclOutput.insert(END, f'....') | |
self.__sclOutput.config(state = DISABLED) | |
self.__sclOutput.grid(row=1,column=0) | |
# Positioning Frame in the grid | |
self.__classFrame.grid(row=0,column=0, pady=2) | |
self.__instanceVarFrame.grid(row=1,column=0, pady=2) | |
self.__methodsFrame.grid(row=2,column=0, pady=2) | |
self.__buttonFrame.grid(row=3,column=0, pady=2) | |
self.__outputFrame.grid(row=4,column=0, pady=5, padx=5) | |
def displayOutput(self, message: str) -> None: | |
""" Displays the message in the output scrollable text widget | |
Args: | |
message (str): the string to display in the scrollable text widget | |
""" | |
self.__sclOutput.config(state = NORMAL) | |
self.__sclOutput.insert(END, message + '\n') | |
self.__sclOutput.see(END) | |
self.__sclOutput.config(state = DISABLED) | |
def clearOutput(self): | |
self.__sclOutput.config(state = NORMAL) | |
self.__sclOutput.delete(1.0,END) | |
self.__sclOutput.config(state = DISABLED) | |
def generateClick(self): | |
self.clearOutput() | |
# Get Data | |
class_name, isAbstract = self.get_class_name() | |
classvar_list = self.get_class_variables() | |
instancevar_list = self.get_instance_variables() | |
methods = self.get_methods(instancevar_list) | |
outStr = '' | |
# Class Name | |
if isAbstract : | |
outStr += 'from abc import ABC, abstractmethod\n' | |
outStr += f'class {class_name}{"(ABC)" if isAbstract else ""}:\n' | |
# Class Variable | |
for cvar in classvar_list: | |
outStr += f'{"":<4}{cvar}\n' | |
outStr += '\n' | |
## Init method | |
method_names = [var[0] for var in methods] | |
if '__init__' in method_names: | |
instance_var = [var[0] for var in instancevar_list] | |
init_index = method_names.index('__init__') | |
# build param with typehint | |
param_list = methods[init_index][2] | |
paramStr = '' | |
for p in param_list: | |
if p != 'self': | |
if p.strip() in instance_var: | |
instance_var_index = instance_var.index(p.strip()) | |
paramStr += f', {p.rstrip()}: {instancevar_list[instance_var_index][1]}' | |
else: | |
paramStr += f', {p.rstrip()}' | |
outStr += f'{"":<4}def __init__(self{paramStr}) -> None:\n' | |
for v in instancevar_list: | |
if v[0] in param_list: | |
outStr += f'{"":<8}self._{v[0]} = {v[0]}\n' | |
else: | |
if v[1] == 'list': | |
outStr += f'{"":<8}self._{v[0]} = []\n' | |
elif v[1] == 'dict': | |
outStr += f'{"":<8}self._{v[0]} = {"{}"}\n' | |
else: | |
outStr += f'{"":<8}self._{v[0]} = None\n' | |
outStr += '\n' | |
# getter setter method | |
for varname, vartype, needGet, needSet in instancevar_list: | |
if needGet: | |
outStr += f'{"":<4}@property\n' | |
outStr += f'{"":<4}def {varname}(self) -> {vartype}:\n' | |
outStr += f'{"":<8}return self._{varname}\n\n' | |
if needSet: | |
outStr += f'{"":<4}@{varname}.setter\n' | |
outStr += f'{"":<4}def {varname}(self, new{varname[0].upper()+varname[1::]}: {vartype}) -> None:\n' | |
outStr += f'{"":<8}self._{varname} = new{varname[0].upper()+varname[1::]}\n\n' | |
# Abstract/Class/Instance Method | |
# methods = [ [methodname, returnType, [param..], methodtype] ] | |
for method in methods: | |
if method[0] != '__init__': | |
# abstract or classmethod ? | |
if method[-1].lower() == 'abstract': | |
outStr += f'{"":<4}@abstractmethod\n' | |
params = method[2] | |
if params[0] == 'cls': | |
outStr += f'{"":<4}@classmethod\n' | |
outStr += f'{"":<4}def {method[0]}({f", ".join(param for param in params)}) -> {method[1]}:\n' | |
outStr += f'{"":<8}pass\n\n' | |
self.displayOutput(outStr) | |
def get_class_name(self) -> tuple: # (classname, isAbstract) | |
class_name = self.__strClassName.get() | |
if '{' in class_name: | |
return class_name[:class_name.find('{')], True | |
else: | |
return class_name.rstrip(), False | |
def get_class_variables(self) -> list: # [ ] | |
method_list = self.__sclClassVar.get("1.0", END).rstrip().split('\n') | |
if len(method_list) == 1 and method_list[0] == '': | |
return [] | |
return self.__sclClassVar.get("1.0", END).rstrip().split('\n') | |
def get_instance_variables(self) -> list: # [ [name, type, get, set ] ] | |
instancevar_list = [] | |
input_list = self.__sclInstanceVar.get("1.0", END).rstrip().split('\n') | |
if len(input_list) == 1 and input_list[0] == '': | |
return [] | |
# Getter / Setter Instance Variable | |
try: | |
for n in input_list: | |
varname, vartype = n.split(':') | |
if varname[0] == '_': | |
varname = varname[1::] | |
# check for suss love for [] as list and {} as dictionary in class diagram | |
if '[]' in vartype: | |
vartype = 'list' | |
elif '{}' in vartype: | |
vartype = 'dict' | |
instancevar_list.append([varname.strip(), vartype.strip(), False, False]) | |
except ValueError: | |
messagebox.showinfo('Error','Incorrect SUSS class diagram format for variables: variableName variableType') | |
return instancevar_list | |
def get_methods(self, instancevar_list: list) -> list: # [ [methodname, returnType, [param..], methodtype] ] | |
methods = [] | |
input_list = self.__sclMethods.get("1.0", END).rstrip().split('\n') | |
if len(input_list) == 1 and input_list[0] == '': | |
return [] | |
# check for get and set when method name is entered | |
instance_var = [var[0] for var in instancevar_list] | |
try: | |
for n in input_list: | |
open_bracket_index = n.find('(') | |
close_bracket_index = n.find(')') | |
name = n[:open_bracket_index] | |
method_type = n[open_bracket_index+1:close_bracket_index] | |
method_type_split = [n.lstrip() for n in method_type.split(',')] | |
return_type = 'None' | |
abstarct_type = '' | |
if name in instance_var: # get/set | |
var_index = instance_var.index(name) | |
# Check if the parameter is more than 1. if it is, means its set. else get | |
if len(method_type_split) > 1: # set | |
instancevar_list[var_index][3] = True | |
else: # get | |
instancevar_list[var_index][2] = True | |
else: #other methods | |
if '{abstract}' in n: | |
return_type_index = n.find(':') | |
abstract_index = n.find('{') | |
abstarct_type = n[abstract_index+1:-1:] | |
if return_type_index != -1: | |
return_type = n[return_type_index+1:abstract_index:].strip() | |
elif ':' in n: | |
return_type_index = n.find(':') | |
if return_type_index != -1: | |
return_type = n[return_type_index+1::].strip() | |
methods.append([name, return_type, method_type_split, abstarct_type]) | |
except ValueError: | |
messagebox.showinfo('Error','Incorrect SUSS class diagram format for methods: methodName(self, param1, param2)') | |
return methods | |
def main() -> None: | |
root = tk.Tk() | |
root.title('Class Builder for ICT162 TOA July 21 Exam') | |
root.resizable(False, False) | |
ClassBuilder(root).pack() | |
root.mainloop() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment