From 3e9601033aa92d309f2be4478705452af6c7e866 Mon Sep 17 00:00:00 2001 From: Andrew Hamilton Date: Fri, 1 Jan 2016 17:55:43 +0000 Subject: [PATCH] Include the python_gut tool --- gut.py | 72 +++++++++++++++++++++ gut_test.py | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tools.py | 16 +++-- 3 files changed, 257 insertions(+), 9 deletions(-) create mode 100755 gut.py create mode 100755 gut_test.py diff --git a/gut.py b/gut.py new file mode 100755 index 0000000..a1cb3a0 --- /dev/null +++ b/gut.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2015-2016 Andrew Hamilton. All rights reserved. +# Licensed under the Artistic License 2.0. + +import re +import sys + + +USAGE = """Usage: gut.py + +# gut.py test.py""" + + +INDENT_SIZE = 4 +TAB_SIZE = 4 + + +def indentation_of_line(line): + indentation = 0 + for character in line: + if character == " ": + indentation += 1 + elif character == "\t": + indentation += TAB_SIZE + elif character == "\n": + return None + else: # Is a non-whitespace character. + return indentation + + +def is_start_line_of_signature(line): + return re.match("^\s*def\s", line) is not None + + +def is_end_line_of_signature(line): + return (re.match(".*\):\s*\n$", line) is not None or + re.match(".*\):\s*#.*\n$", line) is not None) + + +def gut_module(module_contents): + SIGNATURE, BODY, TOP_LEVEL = 1, 2, 3 + state = TOP_LEVEL + body_depth = 0 + result = [] + for line in module_contents.splitlines(keepends=True): + indent = indentation_of_line(line) + if state == BODY and indent is not None and \ + indent < body_depth: + state = TOP_LEVEL + result.append("\n") + if state == TOP_LEVEL and is_start_line_of_signature(line): + state = SIGNATURE + body_depth = indent + INDENT_SIZE + if state == SIGNATURE and is_end_line_of_signature(line): + result.append(line) + state = BODY + elif state != BODY: + result.append(line) + return "".join(result) + + +def main(module_path): + with open(module_path) as module_file: + print(gut_module(module_file.read())) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(USAGE) + sys.exit(-1) + main(sys.argv[1]) diff --git a/gut_test.py b/gut_test.py new file mode 100755 index 0000000..1b39432 --- /dev/null +++ b/gut_test.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2015-2016 Andrew Hamilton. All rights reserved. +# Licensed under the Artistic License 2.0. + +import textwrap +import unittest + +import gut + + +class GutTestCase(unittest.TestCase): + + def test_import(self): + program = "import hello" + self.assertEqual(gut.gut_module(program), program) + + def test_import_and_function(self): + program = textwrap.dedent(""" + import hello + + def first(): + a = 1 + """) + expected = textwrap.dedent(""" + import hello + + def first(): + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_import_and_function_and_command(self): + program = textwrap.dedent(""" + import hello + + def first(): + a = 1 + + b = 1 + """) + expected = textwrap.dedent(""" + import hello + + def first(): + + b = 1 + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_import_and_class(self): + program = textwrap.dedent(""" + import hello + + class Foo: + + def bar(): + a = 1 + """) + expected = textwrap.dedent(""" + import hello + + class Foo: + + def bar(): + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_short_blank_line_in_def(self): + program = textwrap.dedent(""" + def bar(): + a = 1 + + b = 2 + """) + expected = textwrap.dedent(""" + def bar(): + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_nested_functions(self): + program = textwrap.dedent(""" + def bar(): + a = 1 + def foo(): + pass + b = 2 + """) + expected = textwrap.dedent(""" + def bar(): + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_multiline_signature(self): + program = textwrap.dedent(""" + def bar(a, b + c, d): + a = 1 + """) + expected = textwrap.dedent(""" + def bar(a, b + c, d): + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_tab_in_indentation(self): + program = textwrap.dedent(""" + def bar(): + a = 1 + \tb=2 + """) + expected = textwrap.dedent(""" + def bar(): + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_comment_in_signature_line(self): + program = textwrap.dedent(""" + def bar(): # comment + pass + """) + expected = textwrap.dedent(""" + def bar(): # comment + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_indented_comment_in_body(self): + program = textwrap.dedent(""" + def bar(): + pass + # comment + pass + """) + expected = textwrap.dedent(""" + def bar(): + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_non_indented_comment_in_body(self): + program = textwrap.dedent(""" + def bar(): + pass + # comment + pass + """) + expected = textwrap.dedent(""" + def bar(): + + # comment + pass + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_non_indented_comment_after_body(self): + program = textwrap.dedent(""" + def bar(): + pass + pass + # comment + pass + """) + expected = textwrap.dedent(""" + def bar(): + + # comment + pass + """) + self.assertEqual(gut.gut_module(program), expected) + + def test_commented_out_function(self): + program = textwrap.dedent(""" + # def bar(): + # pass + """) + self.assertEqual(gut.gut_module(program), program) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools.py b/tools.py index 4b67dc5..e9277a3 100644 --- a/tools.py +++ b/tools.py @@ -25,6 +25,7 @@ import pygments.styles import traceback import fill3 +import gut import termstr @@ -245,15 +246,11 @@ def unittests(path): unittests.dependencies = {"python3"} -def gut(path): - status, output = Status.info, "" - try: - output = subprocess.check_output( - ["/home/ahamilton/code/python-gut/gut.py", path]) - except subprocess.CalledProcessError: - status = Status.failure +def python_gut(path): + with open(path) as module_file: + output = gut.gut_module(module_file.read()) source_widget = _syntax_highlight_code(fix_input(output), path) - return status, source_widget + return Status.info, source_widget def pydoc3(path): @@ -502,7 +499,8 @@ def generic_tools(): def tools_for_extension(): return { "py": [python_syntax, unittests, pydoc3, python3_coverage, profile, - pep8, pyflakes, pylint3, gut, modulefinder, python3_mccabe], + pep8, pyflakes, pylint3, python_gut, modulefinder, + python3_mccabe], "pyc": [disassemble_pyc], "pl": [perl_syntax, perldoc, perltidy], "pm": [perl_syntax, perldoc, perltidy],