diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java b/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java index ce35d8789d..6828b594c2 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/utils/MessageUtils.java @@ -74,7 +74,11 @@ public static void disableButtons(@NotNull Message message) { public static @NotNull String escapeMarkdown(@NotNull String text) { // NOTE Unfortunately the utility does not escape backslashes '\', so we have to do it // ourselves - return MarkdownSanitizer.escape(text.replace("\\", "\\\\")); + // NOTE It also does not properly escape three backticks '```', it makes it '\```' but we + // need '\`\`\`' + String beforeEscape = text.replace("\\", "\\\\"); + String afterEscape = MarkdownSanitizer.escape(beforeEscape); + return afterEscape.replace("\\```", "\\`\\`\\`"); } } diff --git a/application/src/test/java/org/togetherjava/tjbot/commands/utils/MessageUtilsTest.java b/application/src/test/java/org/togetherjava/tjbot/commands/utils/MessageUtilsTest.java index f5170c4235..79327494e4 100644 --- a/application/src/test/java/org/togetherjava/tjbot/commands/utils/MessageUtilsTest.java +++ b/application/src/test/java/org/togetherjava/tjbot/commands/utils/MessageUtilsTest.java @@ -12,28 +12,29 @@ final class MessageUtilsTest { void escapeMarkdown() { List tests = List.of(new TestCase("empty", "", ""), new TestCase("no markdown", "hello world", "hello world"), - new TestCase("basic markdown", "\\*\\*hello\\*\\* \\_world\\_", - "**hello** _world_"), + new TestCase( + "basic markdown", "\\*\\*hello\\*\\* \\_world\\_", "**hello** _world_"), new TestCase("code block", """ - \\```java + \\`\\`\\`java int x = 5; - \\``` + \\`\\`\\` """, """ ```java int x = 5; ``` - """), new TestCase("escape simple", "hello\\\\\\\\world\\\\\\\\test", + """), + new TestCase("escape simple", "hello\\\\\\\\world\\\\\\\\test", "hello\\\\world\\\\test"), new TestCase("escape complex", """ Hello\\\\\\\\world - \\```java + \\`\\`\\`java Hello\\\\\\\\ world - \\``` + \\`\\`\\` test out this - \\```java + \\`\\`\\`java "Hello \\\\" World\\\\\\\\\\\\"" haha - \\``` + \\`\\`\\` """, """ Hello\\\\world ```java @@ -44,7 +45,118 @@ void escapeMarkdown() { ```java "Hello \\" World\\\\\\"" haha ``` - """)); + """), + new TestCase("escape real example", + """ + Graph traversal can be accomplished easily using \\*\\*BFS\\*\\* or \\*\\*DFS\\*\\*. The algorithms only differ in the order in which nodes are visited: https://i.imgur.com/n9WrkQG.png + + The code to accomplish them is identical and only differs in the behavior of the \\`Queue\\` they are based on. \\*\\*BFS\\*\\* uses a \\*\\*FIFO\\*\\*-queue and \\*\\*DFS\\*\\* a \\*\\*LIFO\\*\\*-queue. + \\`\\`\\`java + Queue nodesToProcess = ... // depending on BFS or DFS + nodesToProcess.add(rootNode); // add all starting nodes + + Set visitedNodes = new HashSet<>(); + while (!nodesToProcess.isEmpty()) { + // Settle node + Node currentNode = visitedNodes.poll(); + if (!visitedNodes.add(currentNode)) { + continue; // Already visited before + } + + // Do something with the node + System.out.println(currentNode); // Replace by whatever you need + + // Relax all outgoing edges + for (Node neighbor : currentNode.getNeighbors()) { + nodesToProcess.add(neighbor); + } + } + \\`\\`\\` + To get \\*\\*BFS\\*\\*, use a \\*\\*FIFO\\*\\*-queue: + \\`\\`\\`java + Queue nodesToProcess = new ArrayDeque<>(); + \\`\\`\\` + And for \\*\\*DFS\\*\\* a \\*\\*LIFO\\*\\*-queue: + \\`\\`\\`java + Queue nodesToProcess = Collections.asLifoQueue(new ArrayDeque<>()); + \\`\\`\\` + That's all, very simple to setup and use. + + For directed graphs relax all \\*\\*outgoing edges\\*\\*. + For \\*\\*tree\\*\\*s the \\`visitedNodes\\` logic can be dropped, since each node can only have maximally one parent, simplifying the algorithm to just: + \\`\\`\\`java + Queue nodesToProcess = ... // depending on BFS or DFS + nodesToProcess.add(rootNode); // add all starting nodes + + while (!nodesToProcess.isEmpty()) { + // Settle node + Node currentNode = visitedNodes.poll(); + + // Do something with the node + System.out.println(currentNode); // Replace by whatever you need + + // Relax all outgoing edges + for (Node child : currentNode.getChildren()) { + nodesToProcess.add(child); + } + } + \\`\\`\\` + """, + """ + Graph traversal can be accomplished easily using **BFS** or **DFS**. The algorithms only differ in the order in which nodes are visited: https://i.imgur.com/n9WrkQG.png + + The code to accomplish them is identical and only differs in the behavior of the `Queue` they are based on. **BFS** uses a **FIFO**-queue and **DFS** a **LIFO**-queue. + ```java + Queue nodesToProcess = ... // depending on BFS or DFS + nodesToProcess.add(rootNode); // add all starting nodes + + Set visitedNodes = new HashSet<>(); + while (!nodesToProcess.isEmpty()) { + // Settle node + Node currentNode = visitedNodes.poll(); + if (!visitedNodes.add(currentNode)) { + continue; // Already visited before + } + + // Do something with the node + System.out.println(currentNode); // Replace by whatever you need + + // Relax all outgoing edges + for (Node neighbor : currentNode.getNeighbors()) { + nodesToProcess.add(neighbor); + } + } + ``` + To get **BFS**, use a **FIFO**-queue: + ```java + Queue nodesToProcess = new ArrayDeque<>(); + ``` + And for **DFS** a **LIFO**-queue: + ```java + Queue nodesToProcess = Collections.asLifoQueue(new ArrayDeque<>()); + ``` + That's all, very simple to setup and use. + + For directed graphs relax all **outgoing edges**. + For **tree**s the `visitedNodes` logic can be dropped, since each node can only have maximally one parent, simplifying the algorithm to just: + ```java + Queue nodesToProcess = ... // depending on BFS or DFS + nodesToProcess.add(rootNode); // add all starting nodes + + while (!nodesToProcess.isEmpty()) { + // Settle node + Node currentNode = visitedNodes.poll(); + + // Do something with the node + System.out.println(currentNode); // Replace by whatever you need + + // Relax all outgoing edges + for (Node child : currentNode.getChildren()) { + nodesToProcess.add(child); + } + } + ``` + """)); for (TestCase test : tests) { assertEquals(test.escapedMessage(), MessageUtils.escapeMarkdown(test.originalMessage()),