diff --git a/api/ECNQueue.py b/api/ECNQueue.py index c42942d..a80dce7 100644 --- a/api/ECNQueue.py +++ b/api/ECNQueue.py @@ -36,8 +36,13 @@ class Item: # TODO: Add Item class documentation def __init__(self, queue: str, number: int) -> None: + self.queue = queue - self.number = number + try: + self.number = int(number) + except ValueError: + raise ValueError("Could not convert \"" + number + "\" to an integer") + #self.number = number self.__path = "/".join([queueDirectory, self.queue, str(self.number)]) self.lastUpdated = self.__getLastUpdated() @@ -181,23 +186,20 @@ def __parseSections(self) -> list: contentStart = self.__getHeaderBoundary() + 1 contentEnd = len(self.__rawItem) - 1 - #directoryInfo = {"type": "directoryInformation"} - initialMessageContent = [] - initialMessageSection = True - - # Delimiter info - delimiters = [ - {"name": "edit", "pattern": "*** Edited"}, - {"name": "status", "pattern": "*** Status"}, - {"name": "replyToUser", "pattern": "*** Replied"}, - {"name": "replyFromUser", "pattern": "=== "}, - ] + # List of assignments for the item + assignementLsit = self.__assignmentParsing(contentStart) + # Appends each assignment individually to sections + for assignment in assignementLsit: + sections.append(assignment) + # Checks for Directory Identifiers if self.__rawItem[contentStart] == "\n" and self.__rawItem[contentStart + 1].startswith("\t"): + directoryStartLine = contentStart + 1 + # Parses the directory information and returns a dictionary of directory values - directoryInfo = self.__directoryParsing(contentStart + 1) + directoryInfo = self.__directoryParsing(directoryStartLine) # Appends Directory Information into the sections array sections.append(directoryInfo) @@ -205,67 +207,46 @@ def __parseSections(self) -> list: # Sets the initial message start to the next line after all directory lines and newlines contentStart = contentStart + len(directoryInfo) + 1 + # The start line, type, and end line for item events sectionBoundaries = [] + # Delimiter info + delimiters = [ + {"name": "edit", "pattern": "*** Edited"}, + {"name": "status", "pattern": "*** Status"}, + {"name": "replyToUser", "pattern": "*** Replied"}, + {"name": "replyFromUser", "pattern": "=== "}, + ] + + # Signifies that there is an initial message to parse + initialMessageSection = True + # Parses the entire contents of the message, stores everything before any delimiter as the initial message - # and the line number of any delimiters + # and the line number of any delimiters as well as the type for lineNumber in range(contentStart, contentEnd + 1): line = self.__rawItem[lineNumber] + # Looks for a starting delimiter and explicity excludes the reply-from-user ending delimiter if line.startswith("***") or line.startswith("===") and not line.startswith("===="): + + # Sets the delimiter type based on the pattern within the delimiters list for delimiter in delimiters: if line.startswith(delimiter["pattern"]): - sectionBoundaries.append({"start": lineNumber, "type": delimiter["name"]}) break - # Signifies that the inital message has been completely parsed - initialMessageSection = False + # If a starting delimiter was encountered, then there is no initial message + if initialMessageSection: + initialMessageSection = False elif initialMessageSection == True: - - # Delimiter not encountered yet, so append line to initial message list + # Delimiter not encountered yet, so append initial message starting line as the current lin number sectionBoundaries.append({"start": lineNumber, "type": "initial_message"}) - initialMessageSection = False - # Assignment Information - assignedBy = "" - assignedDateTime = "" - assignedTo = "" - - # Parses the header looking for assignment delimeters and stores info into their respective variables - for headerContent in range(0, contentStart): - - line = self.__rawItem[headerContent] - - # Gets who the Item was assigned to - if line.startswith("Assigned-To: "): - - assignedTo = (re.search("(?<=Assigned-To: )(.*)", line)).group() - - # Gets the date the Item was assigned - elif line.startswith("Assigned-To-Updated-Time: "): - - dateFromLine = (re.search("(?<=Assigned-To-Updated-Time: )(.*)", line)).group() - - assignedDateTime = self.__getFormattedDate(dateFromLine) - - # Gets who assigned the Item - elif line.startswith("Assigned-To-Updated-By: "): - - assignedBy = (re.search("(?<=Assigned-To-Updated-By: )(.*)", line)).group() - - # Appends the assignment to the sections list - sections.append( - {"type": "assignment", - "datetime": assignedDateTime, - "by": assignedBy, - "to": assignedTo} - ) - + # Used to set the end line of the last delimiter sectionBoundaries.append({"start": contentEnd + 1}) # Sets the end of the section boundary to the begining of the next section boundary @@ -273,7 +254,7 @@ def __parseSections(self) -> list: sectionBoundaries[boundaryIndex]["end"] = sectionBoundaries[boundaryIndex + 1]["start"] - # Remove End of File boundary + # Remove End of File boundary since the line number has been assigned to the last delimiter del sectionBoundaries[-1] # Parses through all the boundaries in section boundaries @@ -285,120 +266,100 @@ def __parseSections(self) -> list: # Returns all of the lines within the current section sectionContent = self.__rawItem[boundary["start"] : boundary["end"]] + # Appends an initial message dictionary to sections if boundary["type"] == "initial_message": initialMessageDictionary = self.__initialMessageParsing(sectionContent) sections.append(initialMessageDictionary) - - # Checks for each section type - elif boundary["type"] == "edit": - + + elif boundary["type"] == "edit": # Returns a dictionary with edit information - editInfo = self.__editParsing(line, boundary["start"]) + editInfo = self.__editParsing(sectionContent, boundary["start"]) - # Checks for a parse error and appends it to sections and exits the function + # Checks for a parse error and appends it, returning the sections list which stops the parsing if editInfo["type"] == "parse_error": - sections.append(editInfo) - return sections - # Remove the delimiter String and unecessary newlines - sectionContent = self.__getFormattedMessageContent(sectionContent) - - # Appends content of the edit message to the dictionary - editInfo["content"] = sectionContent - # Appends the edit dictionary to sections sections.append(editInfo) - continue - elif boundary["type"] == "replyToUser": - # Returns a dictionary with reply-to information - replyToInfo = self.__replyToParsing(line, boundary["start"]) - + replyToInfo = self.__replyToParsing(sectionContent, boundary["start"]) + + # Checks for a parse error and appends it, returning the sections list which stops the parsing if replyToInfo["type"] == "parse_error": - sections.append(replyToInfo) - return sections - # Removes the begining delimiter - sectionContent = self.__getFormattedMessageContent(sectionContent) - - # Appends content of the reply-to message to the dicionary - replyToInfo['content'] = sectionContent - # Appends the reply-to to sections sections.append(replyToInfo) - continue - elif boundary["type"] == "status": - # Returns a dictionary with status information - statusInfo = self.__statusParsing(line, boundary["start"]) + statusInfo = self.__statusParsing(sectionContent, boundary["start"]) if statusInfo["type"] == "parse_error": - sections.append(statusInfo) - return sections - # Removes the begining delimiter - sectionContent = self.__getFormattedMessageContent(sectionContent) - - # Appends content to empty content key to avoid passing large amounts of info that isnt used within the function - statusInfo['content'] = sectionContent - # Appends the status to sections sections.append(statusInfo) - continue - elif boundary["type"] == "replyFromUser": - # Returns a dictionary with userReply information replyFromInfo = self.__userReplyParsing(sectionContent, boundary["start"]) if replyFromInfo["type"] == "parse_error": - sections.append(replyFromInfo) - return sections # Appends the replyFrom to sections sections.append(replyFromInfo) - return sections def __directoryParsing(self, directoryStartLine: int) -> dict: """Returns a dictionary with directory information - Returns: dictionary: - "type": "directoryInformation" - "Name": name, - "Login": login, - "Computer": computer, - "Location": location, - "Email": email, - "Phone": phone, - "Office": office, - "UNIX Dir": unix_dir, - "Zero Dir": zero_dir, - "User ECNDB": user_ecndb, - "Host ECNDB": host_ecdbn, - "Subject": subject + Example: + Name: Nestor Fabian Rodriguez Buitrago + Login: rodri563 + Computer: ce-205-38 (128.46.205.67) + Location: HAMP G230 + Email: rodri563@purdue.edu + Phone: 7654766893 + Office: HAMP G230 + UNIX Dir: /home/bridge/b/rodri563 + Zero Dir: U=\\bridge.ecn.purdue.edu\rodri563 + User ECNDB: http://eng.purdue.edu/jump/2e8399a + Host ECNDB: http://eng.purdue.edu/jump/2e83999 + Subject: Autocad installation + + Args: + directoryStartLine (int): line number within the item that the directory starts on + + Returns: + dict: dictionary that splits each line within the directory into a key and a value """ directoryInformation = {"type": "directory_information"} - # Assumes a full directory with 12 items including the starting line - directoryEndingLine = directoryStartLine + 11 - + directoryPossibleKeys=[ + "Name", + "Login", + "Computer", + "Location", + "Email", + "Phone", + "Office", + "UNIX Dir", + "Zero Dir", + "User ECNDB", + "Host ECNDB", + "Subject" + ] # Executies until the directory start line is greater than the directory ending line - while directoryStartLine <= directoryEndingLine: + while True: # Returns the line number at directory start line info = self.__rawItem[directoryStartLine] @@ -420,34 +381,133 @@ def __directoryParsing(self, directoryStartLine: int) -> dict: # swt1 key, value = strippedInfo.split(": ", 1) - # Adds the key value pair to the directory info dictionary - directoryInformation[key] = value - + if key in directoryPossibleKeys: + # Adds the key value pair to the directory info dictionary + directoryInformation[key] = value + else: + # Casts the list type on to a dictionary + dictionaryList = list(directoryInformation) + # Length of dictionary list + lenDictionaryList = len(dictionaryList) + # The last key appended to the directory dictionary + lastKeyAppended = dictionaryList[lenDictionaryList - 1] + + directoryInformation[lastKeyAppended] = directoryInformation[lastKeyAppended] + " " + strippedInfo + elif ":" in strippedInfo: # Seperates the directory info line into two variables, the first variable being the key, the second being the value key, value = strippedInfo.split(":", 1) - # Adds the key value pair to the directory info dictionary - directoryInformation[key] = value - + if key in directoryPossibleKeys: + # Adds the key value pair to the directory info dictionary + directoryInformation[key] = value + else: + # Casts the list type on to a dictionary + dictionaryList = list(directoryInformation) + # Length of dictionary list + lenDictionaryList = len(dictionaryList) + # The last key appended to the directory dictionary + lastKeyAppended = dictionaryList[lenDictionaryList - 1] + + directoryInformation[lastKeyAppended] = directoryInformation[lastKeyAppended] + " " + strippedInfo + + # Signifies that this line belongs to the most previous line + elif ": " not in strippedInfo and ":" not in strippedInfo: + # Casts the list type on to a dictionary + dictionaryList = list(directoryInformation) + # Length of dictionary list + lenDictionaryList = len(dictionaryList) + # The last key appended to the directory dictionary + lastKeyAppended = dictionaryList[lenDictionaryList - 1] + + directoryInformation[lastKeyAppended] = directoryInformation[lastKeyAppended] + " " + strippedInfo # Counter to denote the end of the directory directoryStartLine = directoryStartLine + 1 # Returns the directory information dictionary return directoryInformation - + + def __assignmentParsing(self, contentStart: int) -> list: + """Returns a list with assignment information dictionaries + + Example: + Assigned-To: campb303 + Assigned-To-Updated-Time: Tue, 23 Jun 2020 13:27:00 EDT + Assigned-To-Updated-By: campb303 + + Args: + contentStart (int): line number where the content starts + + Returns: + list: [ + {"type": "assignment", + "datetime": datetime of the assignment, + "by": user who initiated the assignment, + "to": user who was assigned + }, + ] + """ + assignmentList =[] + + # Assignment Information + assignedBy = "" + assignedDateTime = "" + assignedTo = "" + + # Parses the header looking for assignment delimeters and stores info into their respective variables + for headerContent in range(0, contentStart): + + line = self.__rawItem[headerContent] + + # Gets who the Item was assigned to + if line.startswith("Assigned-To: "): + + assignedTo = (re.search("(?<=Assigned-To: )(.*)", line)).group() + + # Gets the date the Item was assigned + elif line.startswith("Assigned-To-Updated-Time: "): + + dateFromLine = (re.search("(?<=Assigned-To-Updated-Time: )(.*)", line)).group() + + assignedDateTime = self.__getFormattedDate(dateFromLine) + + # Gets who assigned the Item + elif line.startswith("Assigned-To-Updated-By: "): + + assignedBy = (re.search("(?<=Assigned-To-Updated-By: )(.*)", line)).group() + + # Appends the assignment to the sections list + assignmentList.append( + {"type": "assignment", + "datetime": assignedDateTime, + "by": assignedBy, + "to": assignedTo} + ) + + return assignmentList + def __initialMessageParsing(self, content: list) -> dict: """Returns a dictionary with initial message information + Example: + \n + Testtest\n + \n + + Args: + content (list): content of the initial message + Returns: - dictionary: "type": "initial_message", - "datetime": utcdate, - "from_name": fromName, - "from_email": userEmail, + dict: + "type": "initial_message", + "datetime": datetime the initial message was sent, + "from_name": from_name, + "from_email": user_email, "to": [{email, name}], "cc": [{email, name}], - "content": ["message_content"] + "subject": initial message subject + "content": content of the initial message """ initialMessageDictionary = {} @@ -491,305 +551,393 @@ def __initialMessageParsing(self, content: list) -> dict: "email": ccRecipients[1]} ) - #rawMessageContent = self.__rawItem[startLine : endLine] + initialMessageDictionary["subject"] = self.__getMostRecentHeaderByType("Subject") # Removes unecessary newlines from the begining and the end of the initial message - initialMessageDictionary["content"] = self.__getFormattedMessageContent(content) + initialMessageDictionary["content"] = self.__getFormattedSectionContent(content) return initialMessageDictionary - def __editParsing(self, line: str, lineNum: int) -> dict: + def __editParsing(self, content: list, lineNum: int) -> dict: """Returns a dictionary with edit information - Returns: - dictionary: "type": "edit", by, datetime and content - """ - - formattedDateTime = "" - editedBy = "" + Example: + *** Edited by: campb303 at: 06/23/20 13:27:56 ***\n + \n + This be an edit my boy\n + \n + \n + \n - if not line.endswith(" ***\n"): + Args: + content (list): content of an edit + lineNum (int): line number of an edit within an item - columnNum = len(line) - 1 - errorMessage = "Expected the delimiter to end with \" ***\n\"" + Returns: + dict: a dictionary with these keys, + "type": "edi", + "by": initiator of the edit, + "datetime": datetime of the edit, + "content": content of the edit + """ - return self.__errorParsing(line, lineNum, columnNum, errorMessage) + # Edit Info dictionary + editInfo = {} - # Parses for the author of the edit, which is located between the "*** Edited by: " and " at:" substrings - editedBy = (re.search("(?<=\*{3} Edited by: )(.*)(?= at:)", line)).group() + for count, line in enumerate(content): + if line.startswith("===="): + errorMessage = "Reply-from-user ending delimter encountered without Reply-from-user starting delimter" + return self.__errorParsing(line, lineNum + count + 1, errorMessage) + + editInfo["type"] = "edit" - # ece23 + delimiterLine = content[0] + # Parses for the author of the edit, which is located between the "*** Edited by: " and " at:" substrings try: - # Parses for the date and time of the edit, which is located between the " at: " and "***\n" substrings - dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + editInfo["by"] = (re.search("(?<=\*{3} Edited by: )(.*)(?= at:)", delimiterLine)).group() + except: + errorMessage = "*** Edited by: [username] at: [date and time] ***\n" + return self.__errorParsing(delimiterLine, lineNum, errorMessage) + try: + # Parses for the date and time of the edit, which is located between the " at: " and "***\n" substrings + dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", delimiterLine)).group() except: # Returns an error message if there is no space after "at:" - - columnNum = line.find("at:") + 3 - errorMessage = "Expected a space after \"at:\" followed by the date which is followed by \" ***\n\"" - - return self.__errorParsing(line, lineNum, columnNum, errorMessage) + errorMessage = "*** Edited by: [username] at: [date and time] ***\n" + return self.__errorParsing(delimiterLine, lineNum, errorMessage) # Attempts to format the date and time into utc format - formattedDateTime = self.__getFormattedDate(dateTimeString) + editInfo["datetime"] = self.__getFormattedDate(dateTimeString) - editInfo = { - "type": "edit", - "datetime": formattedDateTime, - "by": editedBy, - "content": "" - } + # Remove the delimiter String and unecessary newlines + editInfo["content"] = self.__getFormattedSectionContent(content) return editInfo - def __replyToParsing(self, line: str, lineNum: int) -> dict: + def __replyToParsing(self, content: list, lineNum: int) -> dict: """Returns a dictionary with reply to user information + Example: + *** Replied by: campb303 at: 06/23/20 13:28:18 ***\n + \n + This be a reply my son\n + \n + Justin\n + ECN\n + \n + + Args: + content (list): content of a reply to user + lineNum (int): line number of a reply to user in an item + Returns: - dictionary: "type": "replyToUser", by, datetime and content + dict: a dictionary with these keys, + "type": "reply_to_user", + "by": initiator of the reply to user, + "datetime": datetime of the reply to user, + "content": content of the reply to user """ + replyInfo = {} - formattedDateTime = "" - repliedBy = "" + replyInfo["type"] = "reply_to_user" - #tech112 - # Checks for malformed delimiter - if not line.endswith(" ***\n"): + delimiterLine = content[0] - columnNum = len(line) - 1 - errorMessage = "Expected the delimiter to end with \" ***\n\"" - - return self.__errorParsing(line, lineNum, columnNum, errorMessage) + for count, line in enumerate(content): + if line.startswith("===="): + errorMessage = "Reply-from-user ending delimter encountered without Reply-from-user starting delimter" + return self.__errorParsing(line, lineNum + count + 1, errorMessage) - # Parses for the author of the reply, which is located between the "*** Replied by: " and " at:" substrings - repliedBy = (re.search("(?<=\*{3} Replied by: )(.*)(?= at:)", line)).group() + try: + # Parses for the author of the reply, which is located between the "*** Replied by: " and " at:" substrings + replyInfo["by"] = (re.search("(?<=\*{3} Replied by: )(.*)(?= at:)", delimiterLine)).group() + except: + errorMessage = "*** Replied by: [username] at: [date and time] ***\n" + return self.__errorParsing(delimiterLine, lineNum, errorMessage) # Parses for the date and time of the reply, which is located between the " at: " and "***\n" substrings - dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", line)).group() + try: + dateTimeString = (re.search("(?<= at: )(.*)(?= \*\*\*\n)", delimiterLine)).group() + except: + errorMessage = "*** Replied by: [username] at: [date and time] ***\n" + return self.__errorParsing(delimiterLine, lineNum, errorMessage) # Formats date to UTC - formattedDateTime = self.__getFormattedDate(dateTimeString) + replyInfo["datetime"] = self.__getFormattedDate(dateTimeString) - replyInfo = { - "type": "reply_to_user", - "datetime": formattedDateTime, - "by": repliedBy, - "content": "" - } + replyInfo["content"] = self.__getFormattedSectionContent(content) return replyInfo - def __statusParsing(self, line: str, lineNum: int) -> dict: + def __statusParsing(self, content: list, lineNum: int) -> dict: """Returns a dictionary with status information + Example: + *** Status updated by: campb303 at: 6/23/2020 13:26:55 ***\n + Dont Delete\n + + Args: + content (list): The content of a status update + lineNum (int): The line number of a status update in an item + Returns: - dictionary: "type": "status", by, datetime and content + dict: a dictionary with these keys, + "type": "status", + "by": initiator of the status update, + "datetime": datetime of the status update, + "content": content of the status update """ + statusInfo = {} - formattedDateTime = "" - updatedBy = "" - - # Parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings - updatedBy = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", line)).group() + statusInfo["type"] = "status" - # tech 56 - if not line.endswith(" ***\n"): + delimiterLine = content[0] + + for count, line in enumerate(content): + if line.startswith("===="): + errorMessage = "Reply-from-user ending delimter encountered without Reply-from-user starting delimter" + return self.__errorParsing(line, lineNum + count + 1, errorMessage) - columnNum = len(line) - 1 - errorMessage = "Expected the delimiter to end with \" ***\n\"" + # Parses for the author of the status change, which is located between the "*** Status updated by: " and " at:" substrings + try: + statusInfo["by"] = (re.search("(?<=\*{3} Status updated by: )(.*)(?= at:)", delimiterLine)).group() + except: + errorMessage = "*** Status updated by: [username] at: [date and time] ***\n" - return self.__errorParsing(line, lineNum, columnNum, errorMessage) + return self.__errorParsing(delimiterLine, lineNum, errorMessage) # Parses for the date and time of the status change, which is located between the " at: " and "***\n" substrings - dateTimeString = re.search("(?<= at: )(.*)(?= \*\*\*\n)", line).group() + try: + dateTimeString = re.search("(?<= at: )(.*)(?= \*\*\*\n)", delimiterLine).group() + except: + errorMessage = "*** Status updated by: [username] at: [date and time] ***\n" + + return self.__errorParsing(delimiterLine, lineNum, errorMessage) # Formats the date to UTC - formattedDateTime = self.__getFormattedDate(dateTimeString) - - statusInfo = { - "type": "status", - "datetime": formattedDateTime, - "by": updatedBy, - "content": "" - } + statusInfo["datetime"] = self.__getFormattedDate(dateTimeString) + + # Remove the delimiter String and unecessary newlines + statusInfo["content"] = self.__getFormattedSectionContent(content) return statusInfo def __userReplyParsing(self, replyContent: list, lineNumber: int) -> dict: - """Returns a dictionary with user Reply information information + """Returns a dictionary with user reply information + + Example: + === Additional information supplied by user ===\n + \n + Subject: Re: Beepboop\n + From: Justin Campbell \n + Date: Tue, 23 Jun 2020 13:30:45 -0400\n + X-ECN-Queue-Original-Path: /home/pier/e/queue/Attachments/inbox/2020-06-23/212-original.txt\n + X-ECN-Queue-Original-URL: https://engineering.purdue.edu/webqueue/Attachments/inbox/2020-06-23/212-original.txt\n + \n + Huzzah!\n + \n + ===============================================\n + \n + Args: + replyContent (list): The entire section of a reply-from-user + lineNumber (int): The line number of the begining of a reply-from-user section within and item Returns: - dictionary: "type": "replyFromUser", datetime, subject, userName, userEmail, content, ccRecipients + dict: a dictionary with these keys, + "type": "reply_from_user", + "from_name": name of the user that sent the reply, + "from_email": email of the user that sent the reply, + "subject": subject of the reply, + "datetime": the datetime of the reply, + "cc": [ + {"name": name of the carbon copied recipient, + "email": email of the carbon copied recipient + }, + ] + "content": content of the reply + "headers": [ + {"type": headerType, + "content": content + }, + ] """ - formattedDateTime = "" - repliedByName = "" - repliedByEmail = "" - subject = "" - ccRecipientsList = [] + replyFromInfo = {} + + replyFromInfo["type"] = "reply_from_user" + + replyFromHeaders = [] newLineCounter = 0 endingDelimiterCount = 0 + # Delimiter information line numbers to remove from reply from user linesToRemove =[] - # Parses the section content looking for any line that starts with a metadata, also tracks the line # number with the enumerate function for lineNum, line in enumerate(replyContent): - - #Checks for a newline and breaks for loop on second occurance of a newline - if line == "\n": - - newLineCounter = newLineCounter + 1 - - if line.startswith("===="): - - endingDelimiterCount = endingDelimiterCount + 1 - - if endingDelimiterCount > 1: - - errorMessage = "Encountered two reply-from-user ending delimiters and expected only one" - - return self.__errorParsing(line, lineNumber + lineNum + 1, 0, errorMessage) - - elif endingDelimiterCount == 0 and lineNum == len(replyContent) - 1: + if endingDelimiterCount == 0 and lineNum == len(replyContent) - 1: errorMessage = "Did not encounter a reply-from-user ending delimiter" - - return self.__errorParsing(line, lineNumber + lineNum + 1, 0, errorMessage) - - # Checks for lines starting with Subject, From, Date and CC - elif line.startswith("Subject: ") and newLineCounter < 2: - - # Matches everything after "Subject: " in the line - subject = (re.search("(?<=Subject: )(.*)", line)).group() - - linesToRemove.append(lineNum) - - elif line.startswith("From: ") and newLineCounter < 2: + return self.__errorParsing(line, lineNumber + lineNum + 1, errorMessage) + + if newLineCounter == 1 and line != "\n": - # Returns a list of tuples with name and email information - emailList = email.utils.getaddresses([line]) - - # The name in stored in the first index of the tuple - repliedByName = emailList[0][0] + try: + # Append header information for each headr line + headerType, content = line.split(": ", 1) + replyFromHeaders.append( + {"type": headerType, + "content": content + } + ) + except: + lenReplyFromHeaders = len(replyFromHeaders) - # The email is stored in the second index of the tuple - repliedByEmail = emailList[0][1] + replyFromHeaders[lenReplyFromHeaders - 1]["content"] = replyFromHeaders[lenReplyFromHeaders - 1]["content"] + " " + line linesToRemove.append(lineNum) + #Checks for a newline and breaks for loop on second occurance of a newline + if line == "\n": + newLineCounter = newLineCounter + 1 + + elif line.startswith("===="): + endingDelimiterCount = endingDelimiterCount + 1 - elif line.startswith("Date: ") and newLineCounter < 2: + elif line.startswith("From: ") and newLineCounter == 1: + # Returns a list of one tuples with a name stored in the first index of the tuple and an email stored in the second index of the tuple + emailList = email.utils.getaddresses([line]) + replyFromInfo["from_name"] = emailList[0][0] + replyFromInfo["from_email"] = emailList[0][1] + + elif line.startswith("Subject: ") and newLineCounter == 1: + # Matches everything after "Subject: " + try: + subjectStr = (re.search("(?<=Subject: )(.*)", line)).group() + except: + errorMessage = "Expeted syntax of \"Subject: [subject]\"" + return self.__errorParsing(line, lineNumber + lineNum + 1, errorMessage) + # Formatts the date to UTC + replyFromInfo["subject"] = subjectStr + + elif line.startswith("Date: ") and newLineCounter == 1: # Matches everything after "Date: " + try: + dateStr = (re.search("(?<=Date: )(.*)", line)).group() + except: + errorMessage = "\"Date: [datetime]\"" + return self.__errorParsing(line, lineNumber + lineNum + 1, errorMessage) - dateStr = (re.search("(?<=Date: )(.*)", line)).group() - - dateStr = "" # Formatts the date to UTC - formattedDateTime = self.__getFormattedDate(dateStr) + replyFromInfo["datetime"] = self.__getFormattedDate(dateStr) - linesToRemove.append(lineNum) + elif line.startswith("Cc: ") and newLineCounter == 1: + + replyFromInfo["cc"] = [] - elif line.startswith("Cc: ") and newLineCounter < 2: - # Returns a list of tuples with email information recipientsList = email.utils.getaddresses([line]) # Parses through the cc tuple list for cc in recipientsList: - # Stores the cc information in a dictionary and appends it to the ccRecipientsList - ccRecipientsList.append( - {"name":cc[0], - "email":cc[1]} - ) - - linesToRemove.append(lineNum) + replyFromInfo["cc"].append( + {"name": cc[0], + "email": cc[1]} + ) # Deletes reduntant lines from the message content in reverse order for lineNum in sorted(linesToRemove, reverse = True): replyContent.pop(lineNum) # Strips any unnecessary newlines or any delimiters frm the message content - replyContent = self.__getFormattedMessageContent(replyContent) + replyFromInfo["content"] = self.__getFormattedSectionContent(replyContent) - replyFromInfo = { - "type": "reply_from_user", - "datetime": formattedDateTime, - "from_name": repliedByName, - "from_email": repliedByEmail, - "cc": ccRecipientsList, - "content": replyContent - } + replyFromInfo["headers"] = replyFromHeaders return replyFromInfo - def __getFormattedMessageContent(self, messageContent: list) -> list: + def __getFormattedSectionContent(self, sectionContent: list) -> list: """Returns a list with message content that is stripped of unnecessary newlines and begining delimiters - Returns: - list: formattedMessageContent - """ - # Parses looking for the reply-from-user ending delimiter adn removes the first line that contains the ending delimiter. - - - # Continually loops while looking at the first line of messageContent, removing it from messageContent - # if its a newline. Doesn't run if there is only one line in message content. + Example: + *** Edited by: mph at: 02/21/20 10:27:16 ***\n + \n + Still need to rename machines - but the networking issue now seems to \n + be resolved via another ticket.\n + \n + \n + \n + \n + \n - while len(messageContent) > 1: - if messageContent[0] == "\n" or messageContent[0].startswith("***") or messageContent[0].startswith("===") : - messageContent.pop(0) + Args: + sectionContent (list): The section content of a parsed section + Returns: + list: the section content of a parsed section without any delimiters and unnecessary newlines + """ + # Continually removes the first line of sectionContent if it is a newline or delimiter in each iteration + while len(sectionContent) > 1: + if sectionContent[0] == "\n" or sectionContent[0].startswith("***") or sectionContent[0].startswith("===") : + sectionContent.pop(0) else: - # Breaks the loop if the first line isn't a newline + # Breaks the loop if the first line isn't a newline or delimiter break + + # Continually removes the last line of sectionContent if it is a newline or delimiter in each iteration + while len(sectionContent) > 1: + # Initializes the Length of sectionContent each iteration of the loop + sectionContentLength = len(sectionContent) - while len(messageContent) > 1: - - # Initializes the Length of messageContent each iteration of the loop - messagecontentLength = len(messageContent) - - # Checks if the last line is a newline - if messageContent[messagecontentLength -1] == "\n" or messageContent[messagecontentLength -1].startswith("===="): - - # Deletes the last line if it is a newline - messageContent.pop(messagecontentLength - 1) - + if sectionContent[sectionContentLength -1] == "\n" or sectionContent[sectionContentLength -1].startswith("===="): + sectionContent.pop(sectionContentLength - 1) else: - # Breaks the loop if the last line isn't a newline + # Breaks the loop if the last line isn't a newline or delimiter break - return messageContent + return sectionContent - def __errorParsing(self, line: str, lineNum: int, lineColumn: int, errorMessage: str) -> dict: - """Returns a dictionary with error parse information + def __errorParsing(self, line: str, lineNum: int, expectedSyntax: str) -> dict: + """Returns a dictionary with error parse information when a line is malformed + + Example: + "*** Status updated by: ewhile at: 5/7/2020 10:59:11 *** sharing between\n" + + Args: + line (str): line of that threw error + lineNum (int): line number in the item that threw error + expectedSyntax (str): a message stating the syntax the line should follow Returns: - { - dictionary: "type": "parse_error", - datetime: time_of_execution, - content: [ - 'expected value item_row_num:item_column_num', - 'current line in item' - ] - } + dict: a dictionary with these keys, + "type": "parse_error", + "datetime": time the error was encountered, + "file_path": path of the item with erroneos line, + "expected": expectedSyntax, + "got": line, + "line_num": lineNum """ + errorDictionary = {} - errorDictionary = { - "type": "parse_error", - "datetime": self.__getFormattedDate(str(datetime.datetime.now())), - "content": [] - } + # Type + errorDictionary["type"] = "parse_error" + + # Dateime of the parse error + errorDictionary["datetime"] = self.__getFormattedDate(str(datetime.datetime.now())) + + # Item filepath + errorDictionary["file_path"] = self.__path - # Error message with itemm line and column numbers - errorMessage = errorMessage + " at " + str(lineNum) + ":" + str(lineColumn) + # Expected value + errorDictionary["expected"] = expectedSyntax - # Appends the error message to the content list in the error dictionary - errorDictionary["content"].append(errorMessage) + # line that threw error + errorDictionary["got"] = line - # Appends the item line to the content list in the error dictionary - errorDictionary["content"].append(line) + # line number that threw error + errorDictionary["line_num"] = lineNum # returns the error dictionary return errorDictionary @@ -982,10 +1130,4 @@ def getQueues() -> list: if isDirectory and isValid: queues.append(Queue(file)) - return queues -if __name__ == "__main__": - item = Item("aae", 2) - print() -# for queue in getQueues(): -# for item in queue.items: -# print(f"${item.queue} ${item.number}") \ No newline at end of file + return queues \ No newline at end of file