From 85df7d016afa9f8c9f783c727c7000be2cd672ba Mon Sep 17 00:00:00 2001 From: Christoph Koschel Date: Sat, 8 Jul 2023 21:17:23 +0200 Subject: [PATCH 1/2] Enable issue escaping --- action.yml | 6 +++++- main.py | 25 ++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 8ab5762..9591184 100644 --- a/action.yml +++ b/action.yml @@ -75,4 +75,8 @@ inputs: GITHUB_URL: description: 'Base url of GitHub API' required: false - default: ${{ github.api_url }} \ No newline at end of file + default: ${{ github.api_url }} + ESCAPE: + description: 'Escape all special Markdown characters' + required: false + default: true \ No newline at end of file diff --git a/main.py b/main.py index 0558149..cbc6f0a 100644 --- a/main.py +++ b/main.py @@ -295,6 +295,9 @@ class TodoParser(object): ORG_PROJECTS_PATTERN = re.compile(r'(?<=org projects:\s).+') def __init__(self): + # Determine if the Issues should be escaped. + self.should_escape = os.getenv('INPUT_ESCAPE', 'true') == 'true' + # Load any custom identifiers, otherwise use the default. custom_identifiers = os.getenv('INPUT_IDENTIFIERS') self.identifiers = ['TODO'] @@ -530,7 +533,10 @@ class TodoParser(object): elif org_projects: issue.org_projects.extend(org_projects) elif len(cleaned_line): - issue.body.append(cleaned_line) + if self.should_escape: + issue.body.append(self._escape_markdown(cleaned_line)) + else: + issue.body.append(cleaned_line) if issue is not None and issue.identifier is not None and self.identifiers_dict is not None: for identifier_dict in self.identifiers_dict: @@ -541,6 +547,23 @@ class TodoParser(object): return issue + @staticmethod + def _escape_markdown(comment): + # All basic characters according to: https://www.markdownguide.org/basic-syntax + must_escaped = ['\\', '<', '>', '#', '`', '*', '_', '[', ']', '(', ')', '!', '+', '-', '.', '|', '{', '}', '~', '='] + + escaped = '' + + # Linear Escape Algorithm, because the algorithm ends in an infinite loop when using the function 'replace', + # which tries to replace all backslashes with duplicate backslashes, i.e. also the already other escaped + # characters. + for c in comment: + if c in must_escaped: + escaped += '\\' + c + else: + escaped += c + return escaped + def _get_line_status(self, comment): """Return a Tuple indicating whether this is an addition/deletion/unchanged, plus the cleaned comment.""" addition_search = self.ADDITION_PATTERN.search(comment) From b11a01643018bfe1df5706dbac6beae0749193fb Mon Sep 17 00:00:00 2001 From: Christoph Koschel Date: Sat, 8 Jul 2023 21:19:27 +0200 Subject: [PATCH 2/2] Unit tests for testing escaping --- tests/test_escape.diff | 19 +++++++++++++++++++ tests/test_todo_parser.py | 25 ++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/test_escape.diff diff --git a/tests/test_escape.diff b/tests/test_escape.diff new file mode 100644 index 0000000..d06ebfd --- /dev/null +++ b/tests/test_escape.diff @@ -0,0 +1,19 @@ +diff --git a/tests/ExampleFile.java b/tests/ExampleFile.java +index d340f6a..29b54da 100644 +--- a/tests/ExampleFile.java ++++ b/tests/ExampleFile.java +@@ -1,13 +1,5 @@ + package com.mydomain.myapp; + + public class JavaTests { +- // TODO: Some Java +- // # Some title +- // + +- /* +- TODO: Definitely some Java +- # Another title +- +- */ + } +\ No newline at end of file \ No newline at end of file diff --git a/tests/test_todo_parser.py b/tests/test_todo_parser.py index 0b0c1f8..d71bb0a 100644 --- a/tests/test_todo_parser.py +++ b/tests/test_todo_parser.py @@ -169,7 +169,7 @@ class ClosedIssueTests(unittest.TestCase): def test_nix_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'nix'), 2) - + def test_xaml_issues(self): self.assertEqual(count_issues_for_file_type(self.raw_issues, 'xml'), 2) @@ -204,3 +204,26 @@ class IgnorePatternTests(unittest.TestCase): # Includes 2 tests for Crystal. self.assertEqual(count_issues_for_file_type(self.raw_issues, 'ruby'), 5) os.environ['INPUT_IGNORE'] = '' + +class EscapeMarkdownTest(unittest.TestCase): + def test_simple_escape(self): + os.environ['INPUT_ESCAPE'] = 'true' + parser = TodoParser() + with open('syntax.json', 'r') as syntax_json: + parser.syntax_dict = json.load(syntax_json) + diff_file = open('tests/test_escape.diff', 'r') + + # I had no other idea to make these checks dynamic. + self.raw_issues = parser.parse(diff_file) + self.assertEqual(len(self.raw_issues), 2) + + issue = self.raw_issues[0] + self.assertEqual(len(issue.body), 2) + self.assertEqual(issue.body[0], '\\# Some title') + self.assertEqual(issue.body[1], '\\') + + issue = self.raw_issues[1] + self.assertEqual(len(issue.body), 2) + self.assertEqual(issue.body[0], '\\# Another title') + self.assertEqual(issue.body[1], '\\') +