Skip to content

Instantly share code, notes, and snippets.

@akuafif
Created January 22, 2022 11:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akuafif/ad33decf0ce97f995eb07f7a14d98fe0 to your computer and use it in GitHub Desktop.
Save akuafif/ad33decf0ce97f995eb07f7a14d98fe0 to your computer and use it in GitHub Desktop.
ICT162 Class Generator with typehints. To save time during the TOA exam.
# 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