diff --git a/src/reuse/comment.py b/src/reuse/comment.py index d8421616..90ff9c68 100644 --- a/src/reuse/comment.py +++ b/src/reuse/comment.py @@ -20,6 +20,7 @@ # SPDX-FileCopyrightText: 2024 Anthony Loiseau # SPDX-FileCopyrightText: 2025 Raphael Schlarb # SPDX-FileCopyrightText: 2025 Kiko Fernandez-Reyes +# SPDX-FileCopyrightText: 2025 Matthias Schoettle # # SPDX-License-Identifier: GPL-3.0-or-later @@ -386,6 +387,16 @@ class FortranCommentStyle(CommentStyle): INDENT_AFTER_SINGLE = " " +class FrontmatterCommentStyle(CommentStyle): + """Frontmatter comment style.""" + + SHORTHAND = "frontmatter" + + SINGLE_LINE = "#" + INDENT_AFTER_SINGLE = " " + SHEBANGS = ["---"] + + class ModernFortranCommentStyle(CommentStyle): """Fortran (free form) comment style.""" diff --git a/src/reuse/header.py b/src/reuse/header.py index 914c19c8..c22d045e 100644 --- a/src/reuse/header.py +++ b/src/reuse/header.py @@ -10,6 +10,7 @@ # SPDX-FileCopyrightText: 2022 Yaman Qalieh # SPDX-FileCopyrightText: 2022 Carmen Bianca Bakker # SPDX-FileCopyrightText: 2025 Rivos Inc. +# SPDX-FileCopyrightText: 2025 Matthias Schoettle # # SPDX-License-Identifier: GPL-3.0-or-later @@ -24,7 +25,13 @@ from license_expression import ExpressionError from . import ReuseInfo -from .comment import CommentStyle, EmptyCommentStyle, PythonCommentStyle +from .comment import ( + CommentStyle, + EmptyCommentStyle, + FrontmatterCommentStyle, + HtmlCommentStyle, + PythonCommentStyle, +) from .copyright import merge_copyright_lines from .exceptions import ( CommentCreateError, @@ -270,6 +277,12 @@ def find_and_replace_header( if style is None: style = PythonCommentStyle + # Workaround: Treat Markdown files with frontmatter with the Frontmatter style instead + if style is HtmlCommentStyle and text.startswith( + FrontmatterCommentStyle.SHEBANGS[0] + ): + style = FrontmatterCommentStyle + try: before, header, after = _find_first_spdx_comment(text, style=style) except MissingReuseInfoError: @@ -279,6 +292,7 @@ def find_and_replace_header( if style is EmptyCommentStyle: after = "" + _LOGGER.debug(f"before = {repr(before)}") _LOGGER.debug(f"header = {repr(header)}") _LOGGER.debug(f"after = {repr(after)}") diff --git a/tests/test_cli_annotate.py b/tests/test_cli_annotate.py index 0326af28..3fa41531 100644 --- a/tests/test_cli_annotate.py +++ b/tests/test_cli_annotate.py @@ -393,6 +393,191 @@ def test_shebang_wrong_comment_style(self, fake_repository): assert result.exit_code == 0 assert simple_file.read_text() == expected + def test_xml(self, fake_repository): + """Keep the XML header when annotating.""" + simple_file = fake_repository / "foo.xml" + simple_file.write_text( + cleandoc( + """ + + + + """ + ) + ) + expected = cleandoc( + """ + + + + + + + """ + ) + + result = CliRunner().invoke( + main, + [ + "annotate", + "--license", + "MIT", + "foo.xml", + ], + ) + + assert result.exit_code == 0 + assert simple_file.read_text() == expected + + def test_xml_wrong_comment_style(self, fake_repository): + """If a comment style does not support the XML header at the top, don't + treat the shebang as special. + """ + simple_file = fake_repository / "foo.sh" + simple_file.write_text( + cleandoc( + """ + + + + """ + ) + ) + expected = cleandoc( + """ + # SPDX-License-Identifier: MIT + + + + + """ + ) + + result = CliRunner().invoke( + main, + [ + "annotate", + "--license", + "MIT", + "foo.sh", + ], + ) + + assert result.exit_code == 0 + assert simple_file.read_text() == expected + + def test_markdown(self, fake_repository): + """Markdown uses the HTML comment style.""" + simple_file = fake_repository / "foo.md" + simple_file.write_text( + cleandoc( + """ + # Heading + """ + ) + ) + expected = cleandoc( + """ + + + # Heading + """ + ) + + result = CliRunner().invoke( + main, + [ + "annotate", + "--license", + "MIT", + "foo.md", + ], + ) + + assert result.exit_code == 0 + assert simple_file.read_text() == expected + + def test_frontmatter(self, fake_repository): + """Keep the frontmatter when annotating.""" + simple_file = fake_repository / "foo.md" + simple_file.write_text( + cleandoc( + """ + --- + description: test + --- + # Heading + """ + ) + ) + expected = cleandoc( + """ + --- + + # SPDX-License-Identifier: MIT + + description: test + --- + # Heading + """ + ) + + result = CliRunner().invoke( + main, + [ + "annotate", + "--license", + "MIT", + "foo.md", + ], + ) + + assert result.exit_code == 0 + assert simple_file.read_text() == expected + + def test_frontmatter_wrong_comment_style(self, fake_repository): + """If a comment style does not support the frontmatter header at the top, don't + treat the shebang as special. + """ + simple_file = fake_repository / "foo.sh" + simple_file.write_text( + cleandoc( + """ + --- + description: test + --- + # Heading + """ + ) + ) + expected = cleandoc( + """ + # SPDX-License-Identifier: MIT + + --- + description: test + --- + # Heading + """ + ) + + result = CliRunner().invoke( + main, + [ + "annotate", + "--license", + "MIT", + "foo.sh", + ], + ) + + assert result.exit_code == 0 + assert simple_file.read_text() == expected + def test_contributors_only( self, fake_repository, mock_date_today, contributors ):