From ba8296509c072c8f90ccf18094b1c421b78f5fd1 Mon Sep 17 00:00:00 2001 From: cogu Date: Sun, 27 Aug 2023 16:24:42 +0200 Subject: [PATCH] Improved comment support - Support for advanced comments - Support for compound lines (e.g. comment after statement) --- examples/comments.py | 19 ++++++++++ src/cfile/core.py | 41 ++++++++++++++++----- src/cfile/factory.py | 18 +++++++--- src/cfile/writer.py | 85 ++++++++++++++++++++++++++++++++++++-------- tests/test_writer.py | 61 +++++++++++++++++++++++++++++++ 5 files changed, 196 insertions(+), 28 deletions(-) create mode 100644 examples/comments.py diff --git a/examples/comments.py b/examples/comments.py new file mode 100644 index 0000000..64aa5de --- /dev/null +++ b/examples/comments.py @@ -0,0 +1,19 @@ +""" +Examples of comments +""" +import cfile + +C = cfile.CFactory() + +code = C.sequence() +code.append(C.line_comment(" Simple line Comment ")) +code.append(C.blank()) +code.append(C.line(C.block_comment(" Simple Block Comment "))) +code.append(C.blank()) +code.append(C.block_comment("* SECTION *", width=20)) +code.append(C.blank()) +code.append(C.block_comment(["Description 1", "Description 2", "Description 3"], width=20, line_start="* ")) +code.append(C.blank()) +code.append([C.statement(C.variable("value", "int")), C.line_comment(" Statement followed by comment", adjust=2)]) +writer = cfile.Writer(cfile.StyleOptions()) +print(writer.write_str(code)) diff --git a/src/cfile/core.py b/src/cfile/core.py index ec5d1a1..74c1b78 100644 --- a/src/cfile/core.py +++ b/src/cfile/core.py @@ -4,7 +4,13 @@ from typing import Union, Any -class Directive: +class Element: + """ + A code element, for example an expression + """ + + +class Directive(Element): """ Preprocessor directive """ @@ -19,21 +25,31 @@ def __init__(self, path_to_file: str, system: bool = False) -> None: self.system = system -class Comment: +class Comment(Element): """ Comment base + adjust: Adds spaces before comment begins to allow right-adjustment """ - def __init__(self, text: str) -> None: + def __init__(self, text: str, adjust: int = 1) -> None: self.text = text + self.adjust = adjust class BlockComment(Comment): """ Block Comment + width: When > 0, sets the number of asterisks used on first and last line. + Also puts the text between first and last line. + line_start: Combine with width > 0. Puts this string at beginning of each line + inside the comment """ - def __init__(self, text: str, width=1) -> None: - super().__init__(text) - self.width = width # how many asterisk characters to generate before/after the slash + def __init__(self, + text: str | list[str], + adjust: int = 1, width: int = 0, + line_start: str = "", ) -> None: + super().__init__(text, adjust) + self.width = width + self.line_start = line_start class LineComment(Comment): @@ -42,7 +58,7 @@ class LineComment(Comment): """ -class Whitespace: +class Whitespace(Element): """ Whitespace """ @@ -58,10 +74,17 @@ def __init__(self) -> None: super().__init__(0) -class Element: +class Line(Element): """ - A code element, for example an expression + Adds a newline once all inner parts have been written """ + def __init__(self, parts: str | Element | list) -> None: + if isinstance(parts, (str, Element)): + self.parts = [parts] + elif isinstance(parts, list): + self.parts = parts + else: + raise TypeError("Invalid type:" + str(type(parts))) class Type(Element): diff --git a/src/cfile/factory.py b/src/cfile/factory.py index 6d76b76..f68b73d 100644 --- a/src/cfile/factory.py +++ b/src/cfile/factory.py @@ -32,23 +32,33 @@ def blank(self) -> core.Blank: """ return core.Blank() + def line(self, inner: Any) -> core.Blank: + """ + Like statement but doesn't add ';' before new-line. + """ + return core.Line(inner) + def whitespace(self, width: int) -> core.Whitespace: """ White space of user-defined length """ return core.Whitespace(width) - def line_comment(self, text) -> core.LineComment: + def line_comment(self, text: str, adjust: int = 1) -> core.LineComment: """ New line comment """ - return core.LineComment(text) + return core.LineComment(text, adjust) - def block_comment(self, text) -> core.BlockComment: + def block_comment(self, + text: str | list[str], + adjust: int = 1, + width: int = 0, + line_start: str = "") -> core.BlockComment: """ New block comment """ - return core.BlockComment(text) + return core.BlockComment(text, adjust, width, line_start) def sequence(self) -> core.Sequence: """ diff --git a/src/cfile/writer.py b/src/cfile/writer.py index ca38013..f05dd90 100644 --- a/src/cfile/writer.py +++ b/src/cfile/writer.py @@ -112,6 +112,7 @@ def __init__(self, style: c_style.StyleOptions) -> None: "BlockComment": self._write_block_comment, "Block": self._write_block, "Statement": self._write_statement, + "Line": self._write_line_element, } self.switcher_all.update(self.switcher_elem) self.last_element = ElementType.NONE @@ -128,17 +129,19 @@ def write_str(self, sequence: core.Sequence) -> str: """ Writes the sequence to string using pre-selected format style """ + assert isinstance(sequence, core.Sequence) self._str_open() self._write_sequence(sequence) return self.fh.getvalue() - def write_str_elem(self, elem: Any): + def write_str_elem(self, elem: Any, trim_end: bool = True) -> str: """ Writes single item to string using pre-selected format style """ self._str_open() self._write_element(elem) - return self.fh.getvalue().removesuffix("\n") + value = self.fh.getvalue() + return value.removesuffix("\n") if trim_end else value def _write_element(self, elem: Any) -> None: class_name = elem.__class__.__name__ @@ -153,21 +156,57 @@ def _write_sequence(self, sequence: core.Sequence) -> None: Writes a sequence """ for elem in sequence.elements: - if isinstance(elem, core.Function): + if isinstance(elem, list): + tmp = core.Line(elem) + self._start_line() + self._write_line_element(tmp) + elif isinstance(elem, core.Function): self._start_line() self._write_function(elem) elif isinstance(elem, core.Statement): self._start_line() self._write_statement(elem) + self._eol() + elif isinstance(elem, core.LineComment): + self._start_line() + self._write_line_comment(elem) + self._eol() elif isinstance(elem, core.Block): self._start_line() self._write_block(elem) - elif isinstance(elem, core.IncludeDirective): - self._write_include_directive(elem) - elif isinstance(elem, core.Blank): - self._write_blank(elem) + elif isinstance(elem, core.Line): + self._start_line() + self._write_line_element(elem) + else: + class_name = elem.__class__.__name__ + write_method = self.switcher_all.get(class_name, None) + if write_method is not None: + write_method(elem) + else: + raise NotImplementedError(f"Found no writer for element {class_name}") + + def _write_line_element(self, elem: core.Line) -> None: + for i, part in enumerate(elem.parts): + if i > 0: + if isinstance(part, core.Comment): + self._write(" " * part.adjust) + else: + self._write(" ") + self._write_line_part(part) + self._eol() + + def _write_line_part(self, elem: str | core.Element) -> None: + if isinstance(elem, core.Element): + class_name = elem.__class__.__name__ + write_method = self.switcher_all.get(class_name, None) + if write_method is not None: + write_method(elem) else: - raise NotImplementedError(str(type(elem))) + raise NotImplementedError(f"Found no writer for element {class_name}") + elif isinstance(elem, str): + self._write(elem) + else: + raise NotImplementedError(str(type(elem))) def _write_blank(self, white_space: core.Blank) -> None: # pylint: disable=unused-argument """ @@ -185,20 +224,37 @@ def _write_line_comment(self, elem: core.LineComment) -> None: """ Writes line comment """ - self._write_line("//" + elem.text) + self._write("//" + elem.text) self.last_element = ElementType.COMMENT def _write_block_comment(self, elem: core.BlockComment) -> None: """ Writes block comment """ - lines = elem.text.splitlines() - self._write("/*") - for line in lines[:-1]: - self._write_line(line) - self._write(lines[-1] + "*/") + if isinstance(elem.text, str): + lines = elem.text.splitlines() + elif isinstance(elem.text, list): + lines = elem.text + else: + raise TypeError("Unsupported type", str(type(elem.text))) + if elem.width == 0: + self._format_block_comment(lines, False, 1, "") + else: + self._format_block_comment(lines, True, elem.width, elem.line_start) self.last_element = ElementType.COMMENT + def _format_block_comment(self, lines: list[str], wrap_text: bool, width: int, line_start: str) -> None: + self._write(f"/{'*'*width}") + if wrap_text: + self._eol() + for line in lines: + self._write_line(line_start + line) + self._write_line(f"{'*'*(width+1)}/") + else: + for line in lines[:-1]: + self._write_line(line_start + line) + self._write(lines[-1] + f"{'*'*width}/") + def _write_include_directive(self, elem: core.IncludeDirective) -> None: if elem.system: self._write_line(f'#include <{elem.path_to_file}>') @@ -400,7 +456,6 @@ def _write_statement(self, elem: core.Statement) -> None: else: self._write_expression(elem.parts[0]) self._write(";") - self._eol() self.last_element = ElementType.STATEMENT def _write_expression(self, elem: Any) -> None: diff --git a/tests/test_writer.py b/tests/test_writer.py index 2036f72..2dc6964 100644 --- a/tests/test_writer.py +++ b/tests/test_writer.py @@ -54,6 +54,36 @@ def test_multi_line_block_comment(self): Line 4 */""" self.assertEqual(output, expected) + def test_block_comment_with_user_defined_width_and_line_start__str_input(self): + comment = """Line 1 +Line 2 +Line 3 +Line 4""" + element = core.BlockComment(comment, width=10, line_start="* ") + writer = cfile.Writer(cfile.StyleOptions()) + output = writer.write_str_elem(element) + expected = """/********** +* Line 1 +* Line 2 +* Line 3 +* Line 4 +***********/""" + self.assertEqual(output, expected) + + def test_block_comment_with_user_defined_width_and_line_start__list_input(self): + comment = ["Line 1", "Line 2", "Line 3", "Line 4"] + element = core.BlockComment(comment, width=10, line_start="* ") + writer = cfile.Writer(cfile.StyleOptions()) + output = writer.write_str_elem(element, trim_end=False) + expected = """/********** +* Line 1 +* Line 2 +* Line 3 +* Line 4 +***********/ +""" + self.assertEqual(output, expected) + class TestIncludeDirective(unittest.TestCase): @@ -307,6 +337,22 @@ def test_variable_declarations(self): expected = "static int a;\n" + "static void* b;\n" self.assertEqual(output, expected) + def test_statement_and_comment_using_line_element(self): + seq = core.Sequence() + seq.append(core.Line([core.Statement(core.Variable("a", "int")), core.BlockComment(" Comment ")])) + writer = cfile.Writer(cfile.StyleOptions()) + output = writer.write_str(seq) + expected = "int a; /* Comment */\n" + self.assertEqual(output, expected) + + def test_statement_and_comment_using_python_list(self): + seq = core.Sequence() + seq.append([core.Statement(core.Variable("a", "int")), core.BlockComment(" Comment ")]) + writer = cfile.Writer(cfile.StyleOptions()) + output = writer.write_str(seq) + expected = "int a; /* Comment */\n" + self.assertEqual(output, expected) + class TestBlock(unittest.TestCase): @@ -463,5 +509,20 @@ def test_type_is_int_ptr__type_def_is_ptr(self): self.assertEqual(output, "typedef int** MyIntPtrPtr") +class TestLine(unittest.TestCase): + + def test_str_line(self): + element = core.Line("Any code expression") + writer = cfile.Writer(cfile.StyleOptions()) + output = writer.write_str_elem(element, trim_end=False) + self.assertEqual(output, "Any code expression\n") + + def test_statement_with_comment(self): + element = core.Line([core.Statement(core.Variable("value", "int")), core.LineComment("Comment")]) + writer = cfile.Writer(cfile.StyleOptions()) + output = writer.write_str_elem(element, trim_end=False) + self.assertEqual(output, "int value; //Comment\n") + + if __name__ == '__main__': unittest.main()