Skip to content

Commit

Permalink
Improved comment support
Browse files Browse the repository at this point in the history
- Support for advanced comments
- Support for compound lines (e.g. comment after statement)
  • Loading branch information
cogu committed Aug 27, 2023
1 parent d2f0081 commit ba82965
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 28 deletions.
19 changes: 19 additions & 0 deletions examples/comments.py
Original file line number Diff line number Diff line change
@@ -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))
41 changes: 32 additions & 9 deletions src/cfile/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
Expand All @@ -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):
Expand All @@ -42,7 +58,7 @@ class LineComment(Comment):
"""


class Whitespace:
class Whitespace(Element):
"""
Whitespace
"""
Expand All @@ -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):
Expand Down
18 changes: 14 additions & 4 deletions src/cfile/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down
85 changes: 70 additions & 15 deletions src/cfile/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__
Expand All @@ -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
"""
Expand All @@ -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}>')
Expand Down Expand Up @@ -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:
Expand Down
61 changes: 61 additions & 0 deletions tests/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down Expand Up @@ -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):

Expand Down Expand Up @@ -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()

0 comments on commit ba82965

Please sign in to comment.