""" """ # TODO: Put a comment above. import main_for_testing # Remove lines with only whitespace in them. # Remove triply-quoted strings (single or double quotes, but matched) # Remove comments (# sign to end of line) # Count functions: def that begins in column 1. # Count classes: class that begins in column 1. # Also count lines that begin in column 1 but are NOT def/class: should # be 1 for if .. main, but no more (others are probably global vars). # [TODO: classes and methods. IGNORE nested defs.] # For each def, count lines until next item in column 1. # Store total such. # Do the above for the initial version of the file (from instructor). # Ditto for student version. Report difference in loc. class ModuleEvaluator: def __init__(self, student_contents, original_contents): self.student_contents = student_contents self.original_contents = original_contents def evaluate(self): if self.is_unchanged(): return self return self def is_unchanged(self): if self.student_contents == self.original_contents: self.is_unchanged = True else: self.is_unchanged = False return self.is_unchanged # self.loc_all_lines = 0 # self.loc_wo_whitespace = 0 # self.loc_wo_whitespace_docstrings = 0 # self.loc_wo_whitespace_docstrings_comments = 0 # self.loc_gui = 0 # def __repr__(self): # s = '' # for item in [self.student, self.module, # self.loc_all_lines, self.loc_wo_whitespace]: # s = s + str(item) + ', ' # return s class WordCount(): def __init__(self, text, lines=0, words=0, characters=0): """ Computes and stores the number of lines, words and characters in the given text if text is not None, else stores the given values for lines, words and characters. :type lines: int :type words: int :type characters: int """ self.text = text if text: self.lines = len(text.splitlines()) self.words = len(text.split()) self.characters = len(text) else: self.lines = lines self.words = words self.characters = characters def __repr__(self): return 'WordCount(None, {}, {}, {})'.format(self.lines, self.words, self.characters) def __str__(self): return '{:3} {:4} {:5}'.format(self.lines, self.words, self.characters) def minus(self, other): """ Returns a WordCount that is this one minus the given other one. :type other: WordCount """ return WordCount(None, self.lines - other.lines, self.words - other.words, self.characters - other.characters) class StatisticsForModule(object): def __init__(self, original, after_blank_lines_removed, after_docstrings_removed, after_comments_removed): """ :type original: WordCount :type after_blank_lines_removed: WordCount :type after_docstrings_removed: WordCount :type after_comments_removed: WordCount """ self.nothing_removed = original self.wo_blank_lines = after_blank_lines_removed self.wo_docstrings = after_docstrings_removed self.wo_comments = after_comments_removed def __repr__(self): format_string = 'StatisticsForModule({!r}, {!r}, {!r})' return format_string.format(self.nothing_removed, self.wo_blank_lines, self.wo_docstrings, self.wo_comments) def __str__(self): format_string = 'WordCount for file as given: {}\n' format_string += 'After blank lines removed: {}\n' format_string += 'After docstrings removed too: {}\n' format_string += 'After comments removed too: {}\n' return format_string.format(str(self.nothing_removed), str(self.wo_blank_lines), str(self.wo_docstrings), str(self.wo_comments)) def minus(self, other): """ Returns a ModuleStatistics that is this ModuleStatistics minus the given other ModuleStatistics. :type other: ModuleStatistics """ orig = self.nothing_removed.minus(other.nothing_removed) blanks = self.wo_blank_lines.minus(other.wo_blank_lines) docs = self.wo_docstrings.minus(other.wo_docstrings) comments = self.wo_comments.minus(other.wo_comments) return StatisticsForModule(orig, blanks, docs, comments) def evaluate_module(module_name): contents = read_module(module_name) contents_wo_blank_lines = remove_whitespace_lines(contents) contents_wo_docstrings = remove_docstrings(contents_wo_blank_lines) contents_wo_comments = remove_comments(contents_wo_docstrings) stats = StatisticsForModule(WordCount(contents), WordCount(contents_wo_blank_lines), WordCount(contents_wo_docstrings), WordCount(contents_wo_comments)) return stats def read_module(module_name): """ Returns the contents of the given filename. """ with open(module_name, 'r') as file: return file.read() def remove_whitespace_lines(s): """ Returns a copy of the string s, but with all lines that contain only whitespace removed. """ lines = s.split('\n') new_lines = [] for line in lines: if len(line) > 0 and not line.isspace(): new_lines.append(line) return '\n'.join(new_lines) def remove_docstrings(s): """ Returns a copy of the string s, but with all docstrings removed. It does NOT parse the file correctly for Python; instead, it simply removes all characters between matched pairs of triple-quotes (either single or double quotes). """ IN_SINGLE_DOCSTRING = 0 IN_DOUBLE_DOCSTRING = 1 NOT_IN_DOCSTRING = 2 state = NOT_IN_DOCSTRING result = '' index = 0 while True: if state == NOT_IN_DOCSTRING: new_index1 = s.find('"""', index) new_index2 = s.find("'''", index) if new_index1 == -1 and new_index2 == -1: result = result + s[index:] break elif new_index2 == -1 or (new_index1 != -1 and new_index1 < new_index2): state = IN_DOUBLE_DOCSTRING new_index = new_index1 else: state = IN_SINGLE_DOCSTRING new_index = new_index2 result = result + s[index:new_index] elif state == IN_SINGLE_DOCSTRING: new_index = s.find("'''", index) if new_index == -1: break state = NOT_IN_DOCSTRING else: new_index = s.find('"""', index) if new_index == -1: break state = NOT_IN_DOCSTRING index = new_index + 3 return result def remove_comments(s): """ Returns a copy of string s, but with all comments removed. It does NOT parse the file correctly for Python; instead, it simply removes all characters from a # sign to the end of the line with the # sign. """ result = '' index = 0 while True: comment_begin = s.find('#', index) if comment_begin == -1: result = result + s[index:] break result = result + s[index:comment_begin] end_of_line = s.find('\n', comment_begin) if end_of_line == -1: break index = end_of_line return result # def main(): # test_evaluate_module() # # # def test_evaluate_module(): # for m in ('xxx.txt', 'lines_of_code.py'): # stats = evaluate_module(m, 'david') # print(stats) def main(): main_for_testing.main() if __name__ == '__main__': main()