From 1d7d4f20957174d98254f009509c227acd057531 Mon Sep 17 00:00:00 2001 From: Eric Chea Date: Sun, 16 Nov 2014 14:39:20 -0500 Subject: [PATCH 1/4] Change lines of code to calculate lines of code instead of lines in file. --- radon/raw.py | 129 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 3 deletions(-) diff --git a/radon/raw.py b/radon/raw.py index f566334..df34dbc 100644 --- a/radon/raw.py +++ b/radon/raw.py @@ -148,6 +148,129 @@ def aux(sub_tokens): return sum(aux(sub) for sub in _split_tokens(tokens, OP, ';')) +def remove_lines(doc, lines_to_remove): + ''' + Removes lines from a document. + + @param doc: [str], document cast into an array. + @param lines_to_remove: [int], list of lines to remove from the doc. + @return: [str], doc with specified lines removed. + ''' + for line_number in lines_to_remove: + doc[line_number] = [] + return [line.strip() for line in doc if line] + + +def is_multiline_string(doc, line_count, quote_type): + ''' + Cases to catch multiline_strings. + + @param doc: [str], a document cast into an array. + @param line_count: int, zero based index that points to the current line + in an docuement. + @param quote_type: str, one of the two multiline quotes available in python. + @return: bool, True if the triple quoted line is a multiline string. + ''' + + line = doc[line_count] + previous_line = doc[line_count-1] + + if line.count('=') and line.index('=') < line.index(quote_type)\ + or line_count != 0 and '=' in previous_line: + return True + + else: + return False + + +def find_multiline_comments(lines_to_remove, end, doc, line_count, quote_type): + ''' + @param lines_to_remove: [int], a zero based index that represents lines to + to be removed from a document. + @param end: bool, if True then the first of the two multiline comments has + been found. + @param doc: [str], a document cast into an array. + @param line_count: int, zero based index that points to the current line + in an docuement. + @param quote_type: str, one of the two multiline quotes available in python. + @return lines_to_remove: same as that passed in, with additions. + @return end: bool, updated version of end paramater. + ''' + + # Exceptions: Quote type needs to exist, to get the first line of a + # multine comment it cannot be the last. + if quote_type and end is False and line_count < len(doc) - 1: + quote_type = quote_type[0] + end = True + + if is_multiline_string(doc, line_count, quote_type): + return lines_to_remove, False + + lines_to_remove.append(line_count) + return lines_to_remove, end + + elif end and not quote_type: + lines_to_remove.append(line_count) + + elif end and quote_type: + lines_to_remove.append(line_count) + end = False + + return lines_to_remove, end + + +def find_comments(lines_to_remove, line_count, line): + ''' + Find single line comments in a python file. + + @param lines_to_remove: [int], a zero based index that represents lines to + to be removed from a document. + @param line_count: int, zero based index that points to the current line + in an docuement. + @param line: str, the current line in a document being examined. + @return lines_to_remove: [int], same as parameter with additional indices + that were found. + ''' + if not line: + return lines_to_remove + + if line[0] == "#" or line.count("'''") == 2 or line.count('"""') == 2: + lines_to_remove.append(line_count) + + return lines_to_remove + + +def remove_python_documentation(doc): + ''' + Removes all the documentation from python code. + + @param doc: [str], each line of a code recasted as an array + @return [str], doc that was passed in, excluding lines of documentation. + ''' + + multi_quos = ["'''", '"""'] + lines_to_remove = [] + end = False + + for line_count, line in enumerate(doc): + + lines_to_remove = find_comments(lines_to_remove, line_count, line) + + quote_type = [multi_quo for multi_quo in multi_quos\ + if multi_quo in doc[line_count]] + + # end is True if the first of a pair of multiline comments is found and + # end will revert back to False when both pairs are found. + lines_to_remove, end = find_multiline_comments( + lines_to_remove=lines_to_remove, + end=end, + doc=doc, + line_count=line_count, + quote_type=quote_type) + + return remove_lines(doc, lines_to_remove) + + def analyze(source): '''Analyze the source code and return a namedtuple with the following fields: @@ -164,10 +287,11 @@ def analyze(source): Multiline strings are not counted as comments, since, to the Python interpreter, they are not comments but strings. ''' - loc = sloc = lloc = comments = multi = blank = 0 + sloc = lloc = comments = multi = blank = 0 + loc = len(remove_python_documentation([line.strip() for line in source\ + if line])) lines = iter(source.splitlines()) for lineno, line in enumerate(lines, 1): - loc += 1 line = line.strip() if not line: blank += 1 @@ -181,7 +305,6 @@ def analyze(source): except StopIteration: raise SyntaxError('SyntaxError at line: {0}'.format(lineno)) # Update tracked metrics - loc += sloc_incr # LOC and SLOC increments are the same sloc += sloc_incr multi += multi_incr # Add the comments From 5cb5cc174f90a7696de7e347cd780783f72b753d Mon Sep 17 00:00:00 2001 From: Eric Chea Date: Mon, 17 Nov 2014 18:10:28 -0500 Subject: [PATCH 2/4] Update doctstrings to fit Sphinx format. --- radon/raw.py | 57 ++++++++++++++++++----------------------- radon/tests/test_raw.py | 10 ++++---- 2 files changed, 30 insertions(+), 37 deletions(-) diff --git a/radon/raw.py b/radon/raw.py index df34dbc..ff95cc2 100644 --- a/radon/raw.py +++ b/radon/raw.py @@ -149,27 +149,24 @@ def aux(sub_tokens): def remove_lines(doc, lines_to_remove): + '''Removes lines from a document. + :param doc: [str], document cast into an array. + :param lines_to_remove: [int], list of lines to remove from the doc. + :return: [str], doc with specified lines removed. ''' - Removes lines from a document. - @param doc: [str], document cast into an array. - @param lines_to_remove: [int], list of lines to remove from the doc. - @return: [str], doc with specified lines removed. - ''' for line_number in lines_to_remove: doc[line_number] = [] return [line.strip() for line in doc if line] def is_multiline_string(doc, line_count, quote_type): - ''' - Cases to catch multiline_strings. - - @param doc: [str], a document cast into an array. - @param line_count: int, zero based index that points to the current line + '''Cases to catch multiline_strings. + :param doc: [str], a document cast into an array. + :param line_count: int, zero based index that points to the current line in an docuement. - @param quote_type: str, one of the two multiline quotes available in python. - @return: bool, True if the triple quoted line is a multiline string. + :param quote_type: str, one of the two multiline quotes available in python. + :return: bool, True if the triple quoted line is a multiline string. ''' line = doc[line_count] @@ -185,16 +182,16 @@ def is_multiline_string(doc, line_count, quote_type): def find_multiline_comments(lines_to_remove, end, doc, line_count, quote_type): ''' - @param lines_to_remove: [int], a zero based index that represents lines to + :param lines_to_remove: [int], a zero based index that represents lines to to be removed from a document. - @param end: bool, if True then the first of the two multiline comments has + :param end: bool, if True then the first of the two multiline comments has been found. - @param doc: [str], a document cast into an array. - @param line_count: int, zero based index that points to the current line + :param doc: [str], a document cast into an array. + :param line_count: int, zero based index that points to the current line in an docuement. - @param quote_type: str, one of the two multiline quotes available in python. - @return lines_to_remove: same as that passed in, with additions. - @return end: bool, updated version of end paramater. + :param quote_type: str, one of the two multiline quotes available in python. + :return: tuple, lines_to_remove = same as that passed in, with additions. + end = bool, updated version of end paramater. ''' # Exceptions: Quote type needs to exist, to get the first line of a @@ -220,17 +217,15 @@ def find_multiline_comments(lines_to_remove, end, doc, line_count, quote_type): def find_comments(lines_to_remove, line_count, line): - ''' - Find single line comments in a python file. - - @param lines_to_remove: [int], a zero based index that represents lines to + '''Find single line comments in a python file. + :param lines_to_remove: [int], a zero based index that represents lines to to be removed from a document. - @param line_count: int, zero based index that points to the current line + :param line_count: int, zero based index that points to the current line in an docuement. - @param line: str, the current line in a document being examined. - @return lines_to_remove: [int], same as parameter with additional indices - that were found. + :param line: str, the current line in a document being examined. + :return: [int], same as parameter with additional indices that were found. ''' + if not line: return lines_to_remove @@ -241,11 +236,9 @@ def find_comments(lines_to_remove, line_count, line): def remove_python_documentation(doc): - ''' - Removes all the documentation from python code. - - @param doc: [str], each line of a code recasted as an array - @return [str], doc that was passed in, excluding lines of documentation. + '''Removes all the documentation from python code. + :param doc: [str], each line of a code recasted as an array + :return: [str], doc that was passed in, excluding lines of documentation. ''' multi_quos = ["'''", '"""'] diff --git a/radon/tests/test_raw.py b/radon/tests/test_raw.py index 4ea3846..37a3bab 100644 --- a/radon/tests/test_raw.py +++ b/radon/tests/test_raw.py @@ -147,7 +147,7 @@ def test_logical(self): """ doc? """ - ''', (3, 1, 3, 0, 3, 0)), + ''', (0, 1, 3, 0, 3, 0)), (''' # just a comment @@ -156,13 +156,13 @@ def test_logical(self): else: # you'll never get here print('ven') - ''', (6, 4, 6, 2, 0, 0)), + ''', (4, 4, 6, 2, 0, 0)), (''' # # # - ''', (3, 0, 3, 3, 0, 0)), + ''', (0, 0, 3, 3, 0, 0)), (''' if a: @@ -171,7 +171,7 @@ def test_logical(self): else: print - ''', (6, 4, 4, 0, 0, 2)), + ''', (4, 4, 4, 0, 0, 2)), # In this case the docstring is not counted as a multi-line string # because in fact it is on one line! @@ -179,7 +179,7 @@ def test_logical(self): def f(n): """here""" return n * f(n - 1) - ''', (3, 3, 3, 0, 0, 0)), + ''', (2, 3, 3, 0, 0, 0)), (''' def hip(a, k): From 6fdcdc3da02b36a9d807fe0e05faad36ecd631a3 Mon Sep 17 00:00:00 2001 From: Eric Chea Date: Fri, 28 Nov 2014 11:12:06 -0500 Subject: [PATCH 3/4] Add single comments variable, update tests to reflect changes in lines of code logic, don't count blank lines in multiline comments as part of number of lines in source code. --- radon/raw.py | 38 +++++++++++++++++++---------------- radon/tests/run.py | 2 +- radon/tests/test_cli_tools.py | 4 ++-- radon/tests/test_raw.py | 18 ++++++++--------- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/radon/raw.py b/radon/raw.py index ff95cc2..0e692b3 100644 --- a/radon/raw.py +++ b/radon/raw.py @@ -30,7 +30,7 @@ # comments = Comments lines # blank = Blank lines (or whitespace-only lines) Module = collections.namedtuple('Module', ['loc', 'lloc', 'sloc', - 'comments', 'multi', 'blank']) + 'comments', 'multi', 'blank', 'single_comments']) def _generate(code): @@ -227,12 +227,13 @@ def find_comments(lines_to_remove, line_count, line): ''' if not line: - return lines_to_remove + return (lines_to_remove, True) if line[0] == "#" or line.count("'''") == 2 or line.count('"""') == 2: lines_to_remove.append(line_count) + return (lines_to_remove, True) - return lines_to_remove + return (lines_to_remove, False) def remove_python_documentation(doc): @@ -244,10 +245,14 @@ def remove_python_documentation(doc): multi_quos = ["'''", '"""'] lines_to_remove = [] end = False - + comments = 0 + multi = 1 for line_count, line in enumerate(doc): - lines_to_remove = find_comments(lines_to_remove, line_count, line) + lines_to_remove, removed = find_comments(lines_to_remove, line_count, line) + if removed: + comments += 1 + continue quote_type = [multi_quo for multi_quo in multi_quos\ if multi_quo in doc[line_count]] @@ -260,8 +265,12 @@ def remove_python_documentation(doc): doc=doc, line_count=line_count, quote_type=quote_type) - - return remove_lines(doc, lines_to_remove) + if end: + multi += 1 + # Set multi equal to 0 if no multi strings were found + if multi == 1: + multi = 0 + return len(remove_lines(doc, lines_to_remove)), comments, multi def analyze(source): @@ -280,26 +289,21 @@ def analyze(source): Multiline strings are not counted as comments, since, to the Python interpreter, they are not comments but strings. ''' - sloc = lloc = comments = multi = blank = 0 - loc = len(remove_python_documentation([line.strip() for line in source\ - if line])) + lloc = comments = multi = blank = 0 + sloc = len([line.strip() for line in source.split('\n') if line]) + loc, single_comments, multi = remove_python_documentation([line.strip() for line in source.split('\n')\ + if line]) lines = iter(source.splitlines()) for lineno, line in enumerate(lines, 1): line = line.strip() if not line: blank += 1 continue - # If this is not a blank line, then it counts as a - # source line of code - sloc += 1 try: # Process a logical line that spans on multiple lines tokens, sloc_incr, multi_incr = _get_all_tokens(line, lines) except StopIteration: raise SyntaxError('SyntaxError at line: {0}'.format(lineno)) - # Update tracked metrics - sloc += sloc_incr - multi += multi_incr # Add the comments comments += list(map(TOKEN_NUMBER, tokens)).count(COMMENT) # Process a logical line @@ -307,4 +311,4 @@ def analyze(source): # lines for sub_tokens in _split_tokens(tokens, OP, ';'): lloc += _logical(sub_tokens) - return Module(loc, lloc, sloc, comments, multi, blank) + return Module(loc, lloc, sloc, comments, multi, blank, single_comments) diff --git a/radon/tests/run.py b/radon/tests/run.py index e4dd162..f9a175d 100644 --- a/radon/tests/run.py +++ b/radon/tests/run.py @@ -5,5 +5,5 @@ except ImportError: argv = None else: - argv = ['nosetests', '--rednose'] + argv = ['nosetests', '--rednose', '--nocapture', '--pdb'] nose.main(argv=argv) diff --git a/radon/tests/test_cli_tools.py b/radon/tests/test_cli_tools.py index 5c5a38f..f7654f4 100644 --- a/radon/tests/test_cli_tools.py +++ b/radon/tests/test_cli_tools.py @@ -160,9 +160,9 @@ def testCCToDict(self): class TestDictConversion(unittest.TestCase): def test_raw_to_dict(self): - self.assertEqual(tools.raw_to_dict(Module(103, 123, 98, 8, 19, 5)), + self.assertEqual(tools.raw_to_dict(Module(103, 123, 98, 8, 19, 5, 3)), {'loc': 103, 'lloc': 123, 'sloc': 98, 'comments': 8, - 'multi': 19, 'blank': 5}) + 'multi': 19, 'blank': 5, 'single_comments': 3}) def test_cc_to_xml(self): self.assertEqual(tools.dict_to_xml({'filename': CC_TO_XML_CASE}), diff --git a/radon/tests/test_raw.py b/radon/tests/test_raw.py index 37a3bab..bf80931 100644 --- a/radon/tests/test_raw.py +++ b/radon/tests/test_raw.py @@ -141,13 +141,13 @@ def test_logical(self): ANALYZE_CASES = [ (''' - ''', (0, 0, 0, 0, 0, 0)), + ''', (0, 0, 0, 0, 0, 0, 0)), (''' """ doc? """ - ''', (0, 1, 3, 0, 3, 0)), + ''', (0, 1, 3, 0, 3, 0, 0)), (''' # just a comment @@ -156,13 +156,13 @@ def test_logical(self): else: # you'll never get here print('ven') - ''', (4, 4, 6, 2, 0, 0)), + ''', (4, 4, 6, 2, 0, 0, 2)), (''' # # # - ''', (0, 0, 3, 3, 0, 0)), + ''', (0, 0, 3, 3, 0, 0, 3)), (''' if a: @@ -171,7 +171,7 @@ def test_logical(self): else: print - ''', (4, 4, 4, 0, 0, 2)), + ''', (4, 4, 4, 0, 0, 2, 0)), # In this case the docstring is not counted as a multi-line string # because in fact it is on one line! @@ -179,7 +179,7 @@ def test_logical(self): def f(n): """here""" return n * f(n - 1) - ''', (2, 3, 3, 0, 0, 0)), + ''', (2, 3, 3, 0, 0, 0, 1)), (''' def hip(a, k): @@ -194,7 +194,7 @@ def fib(n): """ if n <= 1: return 1 # otherwise it will melt the cpu return fib(n - 2) + fib(n - 1) - ''', (12, 9, 11, 2, 4, 1)), + ''', (6, 9, 10, 2, 3, 1, 1)), (''' a = [1, 2, 3, @@ -217,5 +217,5 @@ def test_analyze(self): else: result = analyze(self.code) self.assertEqual(result, self.expected) - # blank + sloc = loc - self.assertTrue(result[0] == result[2] + result[5]) + # sloc - single_comments - multi = loc + self.assertTrue(result[0] == result[2] - result[6] - result[4]) From bcef6c2f8c76d7b80035254985a837e3bda999ce Mon Sep 17 00:00:00 2001 From: Eric Chea Date: Fri, 28 Nov 2014 11:20:41 -0500 Subject: [PATCH 4/4] Remove uneeded variables, delint raw.py, and remove options used for debugging. --- radon/raw.py | 17 +++++++++++------ radon/tests/run.py | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/radon/raw.py b/radon/raw.py index 0e692b3..dbae799 100644 --- a/radon/raw.py +++ b/radon/raw.py @@ -30,7 +30,8 @@ # comments = Comments lines # blank = Blank lines (or whitespace-only lines) Module = collections.namedtuple('Module', ['loc', 'lloc', 'sloc', - 'comments', 'multi', 'blank', 'single_comments']) + 'comments', 'multi', 'blank', + 'single_comments']) def _generate(code): @@ -249,7 +250,9 @@ def remove_python_documentation(doc): multi = 1 for line_count, line in enumerate(doc): - lines_to_remove, removed = find_comments(lines_to_remove, line_count, line) + lines_to_remove, removed = find_comments(lines_to_remove, + line_count, + line) if removed: comments += 1 continue @@ -290,9 +293,11 @@ def analyze(source): interpreter, they are not comments but strings. ''' lloc = comments = multi = blank = 0 - sloc = len([line.strip() for line in source.split('\n') if line]) - loc, single_comments, multi = remove_python_documentation([line.strip() for line in source.split('\n')\ - if line]) + + # Cast source code into an array, devoid of blank lines + source_array = [line.strip() for line in source.split('\n') if line] + sloc = len(source_array) + loc, single_comments, multi = remove_python_documentation(source_array) lines = iter(source.splitlines()) for lineno, line in enumerate(lines, 1): line = line.strip() @@ -301,7 +306,7 @@ def analyze(source): continue try: # Process a logical line that spans on multiple lines - tokens, sloc_incr, multi_incr = _get_all_tokens(line, lines) + tokens, _, _ = _get_all_tokens(line, lines) except StopIteration: raise SyntaxError('SyntaxError at line: {0}'.format(lineno)) # Add the comments diff --git a/radon/tests/run.py b/radon/tests/run.py index f9a175d..e4dd162 100644 --- a/radon/tests/run.py +++ b/radon/tests/run.py @@ -5,5 +5,5 @@ except ImportError: argv = None else: - argv = ['nosetests', '--rednose', '--nocapture', '--pdb'] + argv = ['nosetests', '--rednose'] nose.main(argv=argv)