diff --git a/README.md b/README.md index 039e47e..28666df 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,15 @@ ## /deliverables -A directory holding the documents/assignments requested by Dr. Elva. +A directory holding the documents/assignments requested by Dr. Elva. Currently contains Requirements Engineering Document (.docx, and .pdf). ### /deliverables/oldStatisticsProject -A directory holding documents/assignments submitted as part of the old satastics project +A directory holding documents/assignments submitted as part of the old satastics project. ## /documentation -A directory to hold project documents for analysis. +A directory to hold project documents for analysis. Currently contains use case diagrams. ### /documentation/oldStatisticsProject @@ -20,8 +20,13 @@ A directory holding the old statistics project interview questions, and the fina ## Narative -Computer Science students (or students taking computer science courses) who would like addition practice, are limited by the availablity of the computer science tutors. To provide students additional resources for practice and review independant of tutor availability, this project will create a slack bot to ask questions and review code snippets. This functionality will be handeled with a python3 program running in the cloud. +Students in CMS 120 have come to tutoring sessions with several consistent issues. For instance, students write algorithms with incorrect data types for the variables they use. This type of issue comes from how Python is formatted (not making programmers declare data types on assignment/declaration) and obfuscates details from programmers. Although the format of Python is oriented towards making programming easier, beginners do not inherently learn necessary concepts because the language covers up many aspects of programming complexity for the sake of ease of use. +Because CMS 120 is an introductory course, students are not expected to have prior knowledge of these concepts, however, it is good practice to teach students to code defensively and understand how the code they have written works. Since CMS 120 students are beginners coding in python – which allows them to program with more freedom than many other high-level languages – they may face more difficulty when they reach upper level CS classes if they never have a chance to develop a proper understanding of key programming concepts. This emphasizes the importance of having beginner students learn effective coding practices early on. +To combat this problem, there is a need for a web application that can read through student code snippets, give feedback, and ask questions. The program will not format the code for students, but it will ask related questions and challenge them to further their understanding of python. This would be a comprehensive way for CMS 120 students to exercise and reflect on their understanding of good coding practice taught in class while they are programming. + ## other info + + Here be dragons... diff --git a/deliverables/01.g2RequirementsEngineeringDoc.docx b/deliverables/01.g2RequirementsEngineeringDoc.docx new file mode 100644 index 0000000..9a504bf Binary files /dev/null and b/deliverables/01.g2RequirementsEngineeringDoc.docx differ diff --git a/deliverables/01.g2RequirementsEngineeringDoc.pdf b/deliverables/01.g2RequirementsEngineeringDoc.pdf new file mode 100644 index 0000000..6ad994e Binary files /dev/null and b/deliverables/01.g2RequirementsEngineeringDoc.pdf differ diff --git a/documentation/00.useCaseDiagrams.pptx b/documentation/00.useCaseDiagrams.pptx deleted file mode 100644 index 6f53a97..0000000 Binary files a/documentation/00.useCaseDiagrams.pptx and /dev/null differ diff --git a/documentation/UCDchatbotSystem.pptx b/documentation/UCDchatbotSystem.pptx new file mode 100644 index 0000000..3eae12c Binary files /dev/null and b/documentation/UCDchatbotSystem.pptx differ diff --git a/documentation/tutorBot/Activity_Diagrams_Final.pdf b/documentation/tutorBot/Activity_Diagrams_Final.pdf new file mode 100644 index 0000000..bbccee9 Binary files /dev/null and b/documentation/tutorBot/Activity_Diagrams_Final.pdf differ diff --git a/documentation/tutorBot/CompDiagram_Final.pdf b/documentation/tutorBot/CompDiagram_Final.pdf new file mode 100644 index 0000000..792881b Binary files /dev/null and b/documentation/tutorBot/CompDiagram_Final.pdf differ diff --git a/documentation/tutorBot/Decomp_Final.pdf b/documentation/tutorBot/Decomp_Final.pdf new file mode 100644 index 0000000..f645297 Binary files /dev/null and b/documentation/tutorBot/Decomp_Final.pdf differ diff --git a/documentation/tutorBot/UseCaseFinal.pdf b/documentation/tutorBot/UseCaseFinal.pdf new file mode 100644 index 0000000..c69f4c2 Binary files /dev/null and b/documentation/tutorBot/UseCaseFinal.pdf differ diff --git a/tutorBot/.env b/tutorBot/.env deleted file mode 100644 index f74c661..0000000 --- a/tutorBot/.env +++ /dev/null @@ -1 +0,0 @@ -TOKEN= \ No newline at end of file diff --git a/tutorBot/Question_Test.py b/tutorBot/Question_Test.py new file mode 100644 index 0000000..05d7055 --- /dev/null +++ b/tutorBot/Question_Test.py @@ -0,0 +1,173 @@ +import unittest +from tutor_Question import check_question, check_indent, create_iData_type, generate_questions + + +# UnitTesting for our components and its functions. + +class Test_tutor_Question(unittest.TestCase): + + def test_check_question_empty(self): + + data = [] + + result = check_question(data) + self.assertEqual(result, [0]) + + def test_check_question_single(self): + + data = list() + # example of question that could be created. + q = {'id': 1, 'Question': 'what is the data type of (variable name)?', 'A': 'int', 'B': 'float', 'C': 'str', 'D': 'bool', 'Answer': 'TBD', 'Link': 'http://greenteapress.com/thinkpython/html/thinkpython003.html#toc12'} + data.append(q) + + result = check_question(data) + + self.assertEqual(result,[1]) + + def test_check_question_multiple(self): + + data = [] + + # two examples of questions + q = {'id': 1, 'Question': 'what is the data type of (variable name)?', 'A': 'int', 'B': 'float', 'C': 'str', 'D': 'bool', 'Answer': 'TBD', 'Link': 'http://greenteapress.com/thinkpython/html/thinkpython003.html#toc12'} + q2 = {'id': 10, 'Question': 'Is indentation important in Python?', 'A': 'Yes it is important because it would make my code look nicer', 'B': 'Yes it is very important so that we and python can identify code blocks', 'C': "No, Python doesn't care, they can handle anything", 'D': 'No, because we can use semicolons and parentheses', 'Answer': 'B', 'Link': ""} + data.append(q) + data.append(q2) + + result = check_question(data) + + self.assertEqual(result,[1,10]) + + def test_check_indent(self): + + data = [ [0,'def','hello', '(',')',':'], + [4, 'print','(','"hello"',')'] + ] + result = check_indent(data) + + self.assertEqual(result, True) + + def test_check_indent_single_line(self): + + data = [[0,'x', '=', '9']] + result = check_indent(data) + + self.assertEqual(result, True) + + def test_check_indent_flag_single(self): + + data = [[2,'x', '=', '9']] + result = check_indent(data) + self.assertEqual(result, False) + + def test_check_indent_flag(self): + + data = [ [0,'def','hello', '(',')',':'], + [5, 'print','(','"hello"',')'] + ] + + result = check_indent(data) + self.assertEqual(result, False) + + def test_check_indent(self): + + data = [ [0,'def','hello', '(',')',':'], + [4, 'print','(','"hello"',')'] + ] + result = check_indent(data) + self.assertEqual(result, True) + + # check if the function really created the create_iData_type question. + # Given that the id of the only iData_type question is 1. we can test the function by checking the id of question generated. + + def test_create_iData_type(self): + + data = [0,'x', '=', '9'] + result = create_iData_type(data, 2) + + q_type = result['id'] + self.assertEqual(q_type, 1) + + def test_create_iData_type_int(self): + + data = [0,'x', '=', '9'] + result = create_iData_type(data, 2) + + a_key = result['Answer'] + answer = result[a_key] + + self.assertEqual(answer, 'int') + + # def test_create_iData_type_list(self): + + # data + + def test_create_iData_type_bool(self): + + data = [0, 'x', '=', 'True'] + + result = create_iData_type(data, 2) + + a_key = result['Answer'] + answer = result[a_key] + + self.assertEqual(answer, 'boolean') + + + # checking if it creates any questions at all. + + def test_generate_questions(self): + + data = [[0, 'def', 'function', '(',')',] + ] + + questions = generate_questions(data) + + self.assertNotEqual(len(questions),0) + + + def test_generate_questions_syntax(self): + + data = [[1, 'def', 'function', '(',')']] + + questions = generate_questions(data) + + syntax = False + for x in questions: + + if x['id'] == 10: + syntax = True + self.assertEqual(syntax, True) + + def test_generate_questions_iData(self): + + data = [[0, 'x', '=', '5']] + questions = generate_questions(data) + idata = False + + for x in questions: + + if x['id'] == 1: + idata = True + self.assertEqual(idata, True) + + def test_generate_questions_generic(self): + + data = [[0, 'x', '=', '5']] + + questions = generate_questions(data) + gqs = [11, 16] + generic = False + + for x in questions: + if x['id'] in gqs: + generic = True + self.assertEqual(generic, True) + + +if __name__ == '__main__': + unittest.main() + + + + \ No newline at end of file diff --git a/tutorBot/Question_psudo.txt b/tutorBot/Question_psudo.txt new file mode 100644 index 0000000..c3e9683 --- /dev/null +++ b/tutorBot/Question_psudo.txt @@ -0,0 +1,37 @@ + + +the structure of the Qeustion component: Function Generate Questions. + + Input: a two dimensional list + + Objective of generate question, create a question + There are syntax question, data type quetion and generic quesiton + + 1. first thing we can do is to check the current questions we have. + 2. we can iterate through the two dimensional list and check for number of spaces + Flag a indentation boolean if... + a. the number of space is not multiple of 4, + b. the number of space is multiple of 4 but, the number of space does not match with the next line and + the previous line although the last character is not : + + 3. if we see the flag for the syntax question then create a syntax question. with id 10 question about the indentation + (this can be done before any loop) + + ** also having a list of keyword that we want to be paying attention might be a good idea. + keyword = ["'", "if", "else", "def"] + matching of parenthesis to figure out if we need another syntax question. + 4. let's iterate throught the two dimensional list one by one. + + processing the row. + + 1. we want to catch the assignment of values (iData Type) question + so we want to have a catch in = but make sure in the condition that it is actually the assignment by looking at the previous and the next + + if type of the value was list, we could generate more questions on list + + 2. if the current element that we are looking at is in the list of keyword then create syntax questions + + 3. Generic is kindo of like a fun trivia quesiton so create the question at least one. + + + \ No newline at end of file diff --git a/tutorBot/Questions.csv b/tutorBot/Questions.csv deleted file mode 100644 index ffbfdfe..0000000 --- a/tutorBot/Questions.csv +++ /dev/null @@ -1,4 +0,0 @@ -Question Typ,Question,A,B,C,D,Answer,Link -Data Types,what is the data type of (variable name) ,int ,float,str,bool,, -Data Types,how many buit-in data type are there in python,5,4,6,3,A, -Basic Syntax,"what is the logical operator of ""and"" in condition ",&,|,!,%,&, \ No newline at end of file diff --git a/tutorBot/Questions_11_27.csv b/tutorBot/Questions_11_27.csv new file mode 100644 index 0000000..5ca4135 --- /dev/null +++ b/tutorBot/Questions_11_27.csv @@ -0,0 +1,19 @@ +id,Question Type,Question,A,B,C,D,Answer,Link +1,iData Type,what is the data type of (variable name)?,int,float,str,bool,TBD,http://greenteapress.com/thinkpython/html/thinkpython003.html#toc12 +2,Data Type,how many buit-in data type are there in python,5,4,6,3,A,http://greenteapress.com/thinkpython/html/thinkpython003.html#toc12 +3,Syntax,"what is the formal form of logical operator: ""and""? ",&&,||,!,%,A, +4,Syntax,Tell me about = and == !,"""="" is used for assignment and ""=="" is used for equality","""="" is used for equality and ""=="" is used for assignment","both ""="" and ""=="" can be used as equality ","both ""="" and ""=="" can be used as assignment",A,http://greenteapress.com/thinkpython/html/thinkpython003.html#toc13 +5,Syntax,What do we have to have after () in function declaration?,Semicolon(;),Colon(:),Period(.),Exclamation mark(!),B, +6,Data Type,Are 67 and '67' same,Yes,No,,,B, +7,Syntax,** arithmatic expression represents…,multiplication by 2,Exponents,multiplication,"Nothing, it is an invalid arithmatic operator",B, +8,Data Type,Can you contain different types of data in a list?,Yes,No,,,Yes, +9,Data Type,How can you store additional values to Python's list?,use append(),use add(),You can't add anything to a Python's list once it's initialized with some values,,A, +10,Syntax,Is indentation important in Python?,Yes it is important because it would make my code look nicer,Yes it is very important so that we and python can identify code blocks,"No, Python doesn't care, they can handle anything","No, because we can use semicolons and parentheses",B, +11,Generic,Who invented Python?,Alan Turing,Alan Kays,Guido van Rossum,Steve Jobs,C, +12,Data Type,Explain String Concatenation,Python allows us to combine two different strings using + and = operators,"Python allows us to add and subtract two different strings using +, -, and = operator",Python allows you to change modify the string once it is declared,,A, +13,Data Type,What is the return type of this function?,int ,float,str,bool,(Depends on the parsing code), +14,Data Type,the first index of a list starts from 1,TRUE,FALSE,,,FALSE, +15,Syntax,we create chained condition using…,keyword: elif,keyword: else if,keyword: if ,keyword: else,A or D,http://greenteapress.com/thinkpython/html/thinkpython006.html#toc57 +16,Generic,Does Python require compiler?,Sometimes,Yes,No,Depends,C, +17,Syntax,print 3.4. Is this a valid syntax?,Yes it is a valid syntax,No because you have white space in between, No because you don't have parentheses around,Yes Python does not care,C +18,Data Type,Identify int value,4,'4',4.0,[4],A, \ No newline at end of file diff --git a/tutorBot/Questions_Recent.csv b/tutorBot/Questions_Recent.csv new file mode 100644 index 0000000..2c80bef --- /dev/null +++ b/tutorBot/Questions_Recent.csv @@ -0,0 +1,28 @@ +id ,Question Type,Question,A,B,C,D,Answer,Link +1,iData Types,what is the data type of (variable name) ,int ,float,str,bool,(Depends on the parsing code),http://greenteapress.com/thinkpython/html/thinkpython003.html#toc12 +2,Data Types,how many buit-in data type are there in python,5,4,6,3,A,http://greenteapress.com/thinkpython/html/thinkpython003.html#toc12 +3,Syntax,"what is the formal logical operator of ""and""? ",&&,||,!,%,A, +4,Data Types,Tell me about = and == !,"""="" is used for assignment and ""=="" is used for equality","""="" is used for equality and ""=="" is used for assignment","both ""="" and ""=="" can be used as equality ","both ""="" and ""=="" can be used as assignment",A,http://greenteapress.com/thinkpython/html/thinkpython003.html#toc13 +5,Syntax,What do we have to have after () in function declaration?,Semicolon(;),Colon(:),Period(.),Exclamation mark(!),B, +6,Data Types,Are 67 and '67' same,Yes,No,,,B, +7,Syntax,** arithmatic expression represents…,multiplication by 2,Exponents,multiplication,"Nothing, it is an invalid arithmatic operator",B, +8,Data Types,Can you contain different types of data in a list?,Yes,No,,,Yes, +9,Data Types,How can you store additional values to Python's list?,use append(),use add(),You can't add anything to a Python's list once it's initialized with some values,,A, +10,Syntax ,Is indentation important in Python?,Yes it is important because it would make my code look nicer,Yes it is very important so that we and python can identify code blocks,"No, Python doesn't care, they can handle anything","No, because we can use semicolons and parentheses",B, +11,Generic,Who invented Python?,Alan Turing,Alan Kays,Guido van Rossum,Steve Jobs,C, +12,Data Types,Explain String Concatenation,Python allows us to combine two different strings using + and = operators,"Python allows us to add and subtract two different strings using +, -, and = operator",Python allows you to change modify the string once it is declared,,A, +13,Data Types,What is the return type of this function?,int ,float,str,bool,(Depends on the parsing code), +14,Data Types,the first index of a list starts from 1,TRUE,FALSE,,,FALSE, +15,Syntax,we create chained condition using…,keyword: elif,keyword: else if,keyword: if ,keyword: else,A or D,http://greenteapress.com/thinkpython/html/thinkpython006.html#toc57 +16,Generic,Does Python require compiler?,Sometimes,Yes,No,Depends,C, +17,Syntax,print 3.4. Is this a valid syntax?,Yes it is a valid syntax,No because you have white space in between, No because you don't have parentheses around,Yes Python does not care,C , +18,Generic,What is a key component that python uses to determine the flow of a program,Semicolon(;),Indentation,Capatalization,number of lines between components,B, +19,Generic,What is the keyword you need to use a package?,"""install""","""integrate""","""use""","""import""",D, +20,Generic,Where should you put main?,after your first function,In the middle of the program,At the bottom of the program,At the top of the program,C, +21,Generic,How does variable assignment work?,The variable on the right hand side gets the value on the lefthand side,The variable on the lefthand side gets the value on the righthand side,Both variables get each other's value,,B, +22,Generic,"If you want multiple lines of code within a condition (if or ifelse), how should you format them?","Make sure they're within the if or ifelse, not necessarily in line with each other","Make sure they are within the if/ifelse, in line with each other",Make them directly in line with if/ifelse,Just make sure they are below if/ifelse,B, +23,Syntax,What is the proper practice for naming variables?,camelCase,spaces between words (ex: first num),use underscores: first_num,,C, +24,Data Types,Can you use integers and floats interchangeably?,No,Yes,,,A, +25,Data Types,What is the difference between a string and a character?,They are the same,"A string is a combination of characters, whereas a character is a single value",characters just have to be shorter than strings,,B, +26,Syntax,What does ``` represent?,Block comments,In line comments,Character notation,Documentation string,D, +27,Generic,How is NULL represented in Python?,null,\0',None,none,C, diff --git a/tutorBot/__pycache__/interface_test.cpython-38.pyc b/tutorBot/__pycache__/interface_test.cpython-38.pyc new file mode 100644 index 0000000..5cc9c29 Binary files /dev/null and b/tutorBot/__pycache__/interface_test.cpython-38.pyc differ diff --git a/tutorBot/__pycache__/tutor_Parser.cpython-38.pyc b/tutorBot/__pycache__/tutor_Parser.cpython-38.pyc new file mode 100644 index 0000000..447e63d Binary files /dev/null and b/tutorBot/__pycache__/tutor_Parser.cpython-38.pyc differ diff --git a/tutorBot/__pycache__/tutor_Question.cpython-38.pyc b/tutorBot/__pycache__/tutor_Question.cpython-38.pyc new file mode 100644 index 0000000..0a61203 Binary files /dev/null and b/tutorBot/__pycache__/tutor_Question.cpython-38.pyc differ diff --git a/tutorBot/start.sh b/tutorBot/start.sh new file mode 100644 index 0000000..e94b5f5 --- /dev/null +++ b/tutorBot/start.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +################################################################################################################################ +# NAME: Jenny Goldsher, Noah Harvey, Deandra Martin, Hiroki Sato +# DATE: 07112020 +# IDEA: this script will start the tutor flask server and open the appropriate ports in the firewall +################################################################################################################################ + +echo "" | sudo -S ufw allow 5000 # pipes user pwd to sudo to open port 5000 in firewall +export FLASK_APP=/slack/tutorBot.py # sets env vairable for flask to tutorBot script +flask run --host=0.0.0.0 # runs flask server as publicly available diff --git a/tutorBot/stop.sh b/tutorBot/stop.sh new file mode 100644 index 0000000..a287958 --- /dev/null +++ b/tutorBot/stop.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +################################################################################################################################ +# NAME: Jenny Goldsher, Noah Harvey, Deandra Martin, Hiroki Sato +# DATE: 07112020 +# IDEA: this script will close the port used for the tutor flask server +################################################################################################################################ + +echo "" | sudo -S ufw deny 5000 # pipes user pwd to sudo to close port 5000 in firewall diff --git a/tutorBot/tutorBot.py b/tutorBot/tutorBot.py index a3ab2e5..443ba8c 100644 --- a/tutorBot/tutorBot.py +++ b/tutorBot/tutorBot.py @@ -1 +1,44 @@ -# here be dragons \ No newline at end of file +#!/usr/bin/python3 + +################################################################################################################################ +# NAME: Jenny Goldsher, Noah Harvey, Deandra Martin, Hiroki Sato +# DATE: 05112020 +# IDEA: this script will listen for a message and respond +################################################################################################################################ + +#### IMPORTS ################################################################################################################# + +from random import random +import slack +from flask import Flask +from slackeventsapi import SlackEventAdapter + +#### GLOBALS ################################################################################################################# + +token = "" +secret = "" +responses = ["Hey!", "Hi!", "What's up?", "Does this syntax make me look fat?", "That's Tudor to you!", "Genaric response"] + +#### FUNCTIONS ############################################################################################################### + +#### MAIN #################################################################################################################### + +server = Flask(__name__) +slackEvent = SlackEventAdapter(secret,"/slack/events",server) # authenticates provides url for events +bot = slack.WebClient(token=token) +botID = bot.api_call("auth.test")["user_id"] # save bot id so the bot knows if a message is its own + +@slackEvent.on("message") # message.channels event ("event"{"type":"message"}) +def message(content): + event = content.get("event", {}) + channel = event.get("channel") + user = event.get("user") + text = event.get("text") + + print(content) # print entire event (use for log (pipe)) + + if(user != botID): # post a genaric message + bot.chat_postMessage(channel=channel,text=responses[int(random()*len(responses))]) + + +if(__name__=="__main__"): server.run() # might need to change debug to False diff --git a/tutorBot/tutorParser.py b/tutorBot/tutorParser.py new file mode 100644 index 0000000..7d4fe12 --- /dev/null +++ b/tutorBot/tutorParser.py @@ -0,0 +1,108 @@ +#!/usr/bin/python3 + +################################################################################################################################ +# NAME: Jenny Goldsher, Noah Harvey, Deandra Martin, Hiroki Sato +# DATE: 09112020 +# IDEA: holds parsing funcion +################################################################################################################################ + +#### IMPORTS ################################################################################################################# + +#### GLOBALS ################################################################################################################# + +#### FUNCTIONS ############################################################################################################### + +# this function takes in an input which is the string that bot receives from user and validates if they used a code block or not +# Output: Boolean value, True if they used the codeblock which is the correct format. False if they did not use the correct Format. +# By the string slicing and checking the first three character being the triple backquote or not will determine the format which the user used. + +def is_valid(inp): + + valid = True + + if len(inp) == 0: + return False + + elif inp[0:3] != "```": + return False + + return valid + + + +def par(inp): # Function takes a line to parse and return as an array + str1 = inp.split() # Split the line by spaces + length = len(str1) + + + arr2 = [] + count1 = 0 + for element in str1: + if element.isalpha() or len(element) == 1: + arr2.append(element) + elif len(element) > 1: + count1 = 0 + for x in range(len(element)): + has_underscore = 0 + has_number = 0 + if element[x] == '_': + has_underscore +=1 + elif element[x].isdigit(): + has_number += 1 + elif element[x].isalpha(): + count1 +=1 + if has_number + has_underscore + count1== len(element): + arr2.append(element) + else: + store = 0 + for f in range(len(element)): + store+=1 + if element[f].isalpha() == False and element[f].isdigit() == False and element[f] != '_': + store = f + break + if store != 0: + word = "" + for v in range(0,store): + word = word + element[v] + arr2.append(word) + for a in range(store,len(element)): + arr2.append(element[a]) + + + + if str1[0] == "#" or str1[0] == "```": # Disallow comments being parsed by + return # returning a 'None' value + + count = 0 # Count all of the spaces and then subtract non starter + for i in range(len(inp)): # spaces to look at line indentation + if inp[i] == " ": + count+=1 + count = count - len(str1)+ 1 + + li = [count] # Make another array that has the count of spaces + for j in arr2: # then add the parsed line + li.append(j) + return li + + +def callSplit(): + arr = [] # Array to store each line array + #inputtxt = "def function():\n x = 15" # TEST + #inputtxt = "''' testing comments\nx = 15" # TEST + inputtxt = 'def func_one():\n x = "hello"\n y = 12 + x\n for i in range(10):' # TEST + #inputtxt = (input()) + lines = inputtxt.split("\n") # Make an array where each value is one line + + for i in lines: # Pass each line to the parser, which will return its value + if par(i) != None: # Populate 2D array with lines without including 'None' values + arr.append(par(i)) + + print(arr) + return arr + +#### MAIN #################################################################################################################### + +def main(): + callSplit() + +if(__name__=="__main__"): main() diff --git a/tutorBot/tutorTest.py b/tutorBot/tutorTest.py index e92cae3..dcb89cd 100644 --- a/tutorBot/tutorTest.py +++ b/tutorBot/tutorTest.py @@ -9,20 +9,17 @@ #### IMPORTS ################################################################################################################# import slack -import os -from pathlib import Path -from dotenv import load_dotenv #### GLOBALS ################################################################################################################# -env = Path('.')/".env" +token = "" #### FUNCTIONS ############################################################################################################### #### MAIN #################################################################################################################### -load_dotenv(dotenv_path=env) +def main(): + bot = slack.WebClient(token=token) + bot.chat_postMessage(channel="#tutoring-bot",text="Hello World\nI come from tutorTest.py running on a cloud server!") -bot = slack.WebClient(token=os.environ["TOKEN"]) - -bot.chat_postMessage(channel="#tutoring-bot",text="Hello World\nI come from python3 running on a cloud server!") \ No newline at end of file +if(__name__=="__main__"): main() diff --git a/tutorBot/tutor_Parser.py b/tutorBot/tutor_Parser.py new file mode 100644 index 0000000..e9d8300 --- /dev/null +++ b/tutorBot/tutor_Parser.py @@ -0,0 +1,122 @@ +#!/usr/bin/python3 + +################################################################################################################################ +# NAME: Jenny Goldsher, Noah Harvey, Deandra Martin, Hiroki Sato +# DATE: 09112020 +# IDEA: holds parsing funcion +################################################################################################################################ + + +#### FUNCTIONS ############################################################################################################### + +def is_valid(inp): # this function takes in an input which is the string that bot receives from user and + # validates if they used a code block or not + valid = False + + if len(inp) == 0: + return False + + elif inp[0:3] == "```" and inp[len(inp) - 3:len(inp)]: + return True + + return valid # Output: Boolean value, True if they used the codeblock which is the correct format. False + # if they did not use the correct Format. + # By the string slicing and checking the first three character being the triple backquote or not will determine the format which the user used. + + +def par(inp): # Function takes a line to parse and return as an array + str1 = inp.split() # Split the line by spaces + length = len(str1) + + if length == 0: # Returns an empty list if input is empty + return + + arr2 = [] + count1 = 0 + digit = 0 + for element in str1: + if element.isalpha() or len(element) <= 1: + arr2.append(element) + elif len(element) > 1: + + count1 = 0 + quote = 0 + digit = 0 + has_fp = 0 + + for x in range(len(element)): + has_underscore = 0 + has_number = 0 + + if element[x] == '_': + has_underscore +=1 + elif element[x] == '"' or element[x] == "'": + quote +=1 + elif element[x].isdigit(): + has_number += 1 + digit += 1 + elif element[x] == '.' and digit != 0: + has_fp += 1 + elif element[x].isalpha(): + count1 +=1 + if has_number + has_underscore + count1== len(element): + arr2.append(element) + elif quote + count1 == len(element): + arr2.append(element) + elif has_fp + digit == len(element): + arr2.append(element) + else: + store = 0 + for f in range(len(element)): + store+=1 + if element[f].isalpha() == False and element[f].isdigit() == False and element[f] != '_': + store = f + break + if store != 0: + word = "" + for v in range(0,store): + word = word + element[v] + arr2.append(word) + for a in range(store,len(element)): + arr2.append(element[a]) + + + + if str1[0] == "#": # Disallow comments being parsed by returning + return + + count = 0 # Count all of the spaces and then subtract non starter + for i in range(len(inp)): # spaces to look at line indentation + if inp[i] == " ": + count+=1 + count = count - len(str1)+ 1 + + li = [count] # Make another array that has the count of spaces + for j in arr2: # then add the parsed line + li.append(j) + return li + + +def callSplit(txt): + arr = [] # Array to store each line array + lines = txt.split("\n") # Make an array where each value is one line + + for i in lines: # Pass each line to the parser, which will return its value + if par(i) != None: # Populate 2D array with lines without including 'None' values + arr.append(par(i)) + + #print(arr) + return arr + + +#### MAIN #################################################################################################################### + +def main(): + #inputtxt = "def function():\n x = 'hello'" # TEST + #inputtxt = "''' testing comments\nx = 15" # TEST + inputtxt = "def func_one():\n x = 15\n y = 12 + x\n for i in range(10):" # TEST + #inputtxt = "x = 'hello'" + # inputtxt = (input()) + print(callSplit(inputtxt)) + +if(__name__=="__main__"): main() diff --git a/tutorBot/tutor_Question.py b/tutorBot/tutor_Question.py new file mode 100644 index 0000000..67859a1 --- /dev/null +++ b/tutorBot/tutor_Question.py @@ -0,0 +1,291 @@ +#!/usr/bin/python3 + +################################################################################################################################ +# NAME: Jenny Goldsher, Noah Harvey, Deandra Martin, Hiroki Sato +# DATE: 09112020 +# IDEA: opens questions file and generates questions +# There are certain types of questions that must be created depending on the syntax error that they make or +# what they implement within the codeblock. For example, syntax question about the indentation is always created when user +# used wrong indentation. + +################################################################################################################################ + +#### IMPORTS ################################################################################################################# +import pandas as pd +from random import randint, choice +from ast import literal_eval +import tutor_Parser +#### GLOBALS ################################################################################################################# +question_df = pd.read_csv('Questions_11_27.csv') +existing_q_id = [] +syntax_list = ['def','if','else','==','+','-','*','/',"'",'"'] +data_types = ['int','str','float','bool','list'] + # we are using a python libary called Pandas to manipulate the + # csv file easily by creating a pandas dataframe. + # .set_index would make one of the column in the csv file + # 'Question Type' as index and we can extract the matching + # question according to the input we'll receive + + + +#### FUNCTIONS ############################################################################################################### +################################################################################################################################ +#### check_question ############################################################################################################ + +# Input: list of questions +# Output list that contains question id +# Objective: iterate through the list of questions created and record the existing_question_id so that we can keep track of what questions can be created and +# what questions should not be created + + +def check_question(questions): + + existing_q_id = list() + + if len(questions) == 0: # if the questions list is empty, return a list with 0 + return [0] + else: # if the questions list is not empty, + + for q in questions: # iterate throught the list of questions + existing_q_id.append(q['id']) + + return existing_q_id + + + +################################################################################################################################ +#### check_indent function ##################################################################################################### + +# Input: two dimensional list +# Output: boolean +# Objective: iterate through the two dimensional list and make sure there is no error with indentation +# Returning True indicate that there is no indentation issue, and False represents an issue with spaces + +def check_indent(lines): + + # local variable flag, if anything weird didn't happen then this function will return True + flag = True + + for line in range(len(lines)): # iterating through the 2-d list + + cur = lines[line] + + if (cur[0] % 4 != 0): # if the number of space is not multiple of 4 + + flag = False # this is the base case that can be applied to either a single + break # line of code or multiple lines of code. + + + if len(lines) > 1: # if the code was not a single line. + + # get the previous lines + prev = lines[line - 1] + + if ((prev[len(prev)-1] == ':') and (cur[0] != (prev[0] + 4))): # the previous line ends with : but the number of space is not + # incremented by 4 + flag = False + break + + + return flag + +################################################################################################################################ +#### create_iData_type function ################################################################################################ + +# Input: list which is the entire row in the two-d list and an int which is an index of '=' +# Output: dictionary which is a question, and choices and its answer and link to the feedback +# Objective: create a iData type question + +def create_iData_type(line, index): + + # get the iData_type question as dictionary + question = question_df.set_index('Question Type').loc['iData Type'].to_dict() + question_df.reset_index() + print(question) # debugging + choices = data_types + answer = question['Answer'] + print(choices) # debugging + # list to keep track of data type that was chosen as the + chosen = [] + + # getting the variable name and getting the data_type of variable. + variable_name = line[index - 1] + variable_type = type(literal_eval(line[index + 1])) + + # print(variable_name, variable_type) # debugging + + if variable_type == str: + + # modifying the question choice to avoid overlap + answer = 'str' + + + if variable_type == int: + answer = 'int' + + + if variable_type == float: + answer = 'float' + + if variable_type == bool: + answer = 'boolean' + + if variable_type == list: + answer = 'list' + + print("Answer", answer) + + # using ascii value, selecting the key to put the answer and place the answer. + answer_key = chr(65 + randint(0,3)) + chosen.append(answer) + question[answer_key] = answer + question['Answer'] = answer_key + print("Printing chosen", chosen) + + # selecting the rest of the answers. + for i in range(3): + + key = chr(65 + randint(0,3)) + + while key == answer_key: + key = chr(65 + randint(0,3)) + + + dummy = choice(choices) + + while dummy in chosen or dummy == answer: + dummy = choice(choices) + + chosen.append(dummy) + question[key] = dummy + + + # modify the question sentence + question['Question'] = question['Question'].replace("(variable name)",variable_name) + + return question + + +################################################################################################################################ + +"""This function is going to create a set of questions """ + +# Input: two dimensional list called term_lists +# the first index, the row represents the line, second index columns are the terms on that line. +# Output: two dimensiocal list which contains a list of questions and answer keys. + +def generate_questions(term_lists): + + questions = list() + iData_type = False + Generic = False + + # have a for loop to create randomly selected 7 questions and append to the questions list + # first we could check for line indentation which is the very important and simple syntax question we can ask + indentation = check_indent(term_lists) + + # if we see the flag being False, append the syntax question about the indentation/spaces + if (indentation == False): + q = question_df.set_index('Question Type').loc['Syntax'].iloc[4].to_dict() + questions.append(q) # set_index is going to use certain column as index of the + # pandas dataframe which is id in this case, and we know the + # id for the indentation syntax so we get the question by + # loc[] operator and preserve the entire row as a list. + # going through the two dimensional list again + # we will visit each line to create data type question, or syntax question, or generic question + + + for line in range(len(term_lists)): + # question = generate_question(questions, tokens) + # we first want to make sure what questions are already in the list of questions + existing_q_id = check_question(questions) + # get the current line of code + current = term_lists[line] + print('Current line:', current) + # we will have an internal for loop to go through each terms except the first elements which indicates the number of spaces. + + for term in range(1,len(current)): + + t = current[term] + + if (t == '=' and iData_type == False): + # create iData_type_qestion + question = create_iData_type(current, term) + iData_type = True + questions.append(question) + continue + + if (t == '=' and iData_type == True): + # print('Creating data type question') + # create a data type question. + + dtq = question_df.set_index('Question Type').loc['Data Type'] + question_df.reset_index() + question = dtq.iloc[randint(0, len(dtq) - 1)] + + + while question['id'] in existing_q_id: + question = dtq.iloc[randint(0, len(dtq) - 1)] + + q = question.to_dict() + + q = question.to_dict() + questions.append(question) + continue + + if (t in syntax_list): + + # create syntax question + + stq = question_df.set_index('Question Type').loc['Syntax'] + question_df.reset_index() + question = stq.iloc[randint(0, len(stq) - 1)] + + while question['id'] in existing_q_id: + question = stq.iloc[randint(0, len(stq) - 1)] + + + q = question.to_dict() + questions.append(question) + continue + + + + + # check if we reached the number of questions we wanted. + + if len(questions) == 4: + break + + # after going through everything and you don't have enough question, add one generic question. + if len(questions) < 4: + + gnq = question_df.set_index('Question Type').loc['Generic'] + question_df.reset_index() + question = gnq.iloc[randint(0, len(gnq) - 1)].to_dict() + questions.append(question) + + return questions + + + + +#### MAIN #################################################################################################################### + +def main(): + + + user_inp = "```def func_one():\n x = 15\n y = 12 + x\n for i in range(10):```" + + if tutor_Parser.is_valid(user_inp) == True: + + user_inp = user_inp[3 : len(user_inp)-3] + # terms = tutor_Parser.callSplit(user_inp) + + questions = generate_questions(tutor_Parser.callSplit(user_inp)) + print(questions) + else: + print("invalid input") + print(questions) + +if(__name__=="__main__"): main() diff --git a/tutorBot/unit-parser-1.py b/tutorBot/unit-parser-1.py new file mode 100644 index 0000000..9f3cb3b --- /dev/null +++ b/tutorBot/unit-parser-1.py @@ -0,0 +1,85 @@ +import unittest +from tutor_Parser import callSplit, is_valid, par + +class Test_tutor_parser(unittest.TestCase): + + def test_parser_with_code_block(self): # OK test + arr = [] + data = "def func_one():\n x = 15" + desired = [[0, 'def', 'func_one', '(', ')', ':'], [4, 'x', '=', '15']] + lines = data.split("\n") + for i in lines: + if par(i) != None: + arr.append(par(i)) + + self.assertEqual(arr, desired) + + + def test_parser_if_input_empty(self): # OK test + arr = [] + data = " " + desired = [] + lines = data.split("\n") + for i in lines: + if par(i) != None: + arr.append(par(i)) + + self.assertEqual(arr, desired) + + + def test_parser_with_comment(self): # OK test + arr = [] + data = "# to print value of x\nprint(4)" + desired = [[0, 'print', '(', '4', ')']] + lines = data.split("\n") + for i in lines: + if par(i) != None: + arr.append(par(i)) + self.assertEqual(arr, desired) + + + # def test_parser_with_float(self): + + # def test_parser_with_triple_backquote(self): + + def test_callSplit_empty(self): # OK test + arr = [] + data = " " + + self.assertEqual(callSplit(data), arr) + + + def test_callSplit_norm_input(self): # OK test + arr = [] + data = "def func_one():\n x = 15" + + desired = [[0, 'def', 'func_one', '(', ')', ':'], [5, 'x', '=', '15']] + self.assertEqual(callSplit(data), desired) + + + def test_callSplit_ip_with_parameter(self): # OK test + data = "def fun1(x)" + + result = callSplit(data) + + self.assertEqual(result,[[0,'def','fun1','(','x',')']]) + + + def test_is_valid_empty_input(self): # OK test + data = " " + result = is_valid(data) + + self.assertFalse(result) + + + def test_isValid_with_triple_backquotes(self): # OK test + data = "``` testing backquotes\nx = 15" + + result = is_valid(data) + + self.assertTrue(result) + + +if __name__ == '__main__': + unittest.main() + \ No newline at end of file