Skip to content

Commit f3a9cc8

Browse files
author
Fischstaebchen
committed
Added IndentationHelper class with documentation and tests.
1 parent 0cd1b8e commit f3a9cc8

File tree

5 files changed

+1413
-7
lines changed

5 files changed

+1413
-7
lines changed

src/main/java/net/javadiscord/javabot/systems/user_commands/format_code/FormatCodeCommand.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package net.javadiscord.javabot.systems.user_commands.format_code;
22

3+
import net.javadiscord.javabot.util.*;
34
import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand;
45
import net.dv8tion.jda.api.entities.Message;
56
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
@@ -9,10 +10,6 @@
910
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
1011
import net.dv8tion.jda.api.interactions.components.ActionRow;
1112
import net.dv8tion.jda.api.interactions.components.buttons.Button;
12-
import net.javadiscord.javabot.util.Checks;
13-
import net.javadiscord.javabot.util.InteractionUtils;
14-
import net.javadiscord.javabot.util.Responses;
15-
import net.javadiscord.javabot.util.StringUtils;
1613
import org.jetbrains.annotations.Contract;
1714
import org.jetbrains.annotations.NotNull;
1815

@@ -49,7 +46,11 @@ public FormatCodeCommand() {
4946
.addChoice("SQL", "sql")
5047
.addChoice("Swift", "swift")
5148
.addChoice("TypeScript", "typescript")
52-
.addChoice("XML", "xml")
49+
.addChoice("XML", "xml"),
50+
new OptionData(OptionType.STRING,"auto-indent","The type of indentation that should be applied to the message, does not automatically indent if left blank",false)
51+
.addChoice("Four_Spaces","four")
52+
.addChoice("Two_Spaces","two")
53+
.addChoice("Tabs","tab")
5354
)
5455
);
5556
}
@@ -64,6 +65,7 @@ public FormatCodeCommand() {
6465
public void execute(@NotNull SlashCommandInteractionEvent event) {
6566
OptionMapping idOption = event.getOption("message-id");
6667
String format = event.getOption("format", "java", OptionMapping::getAsString);
68+
String indentation = event.getOption("auto-indent","null",OptionMapping::getAsString).toUpperCase();
6769
event.deferReply().queue();
6870
if (idOption == null) {
6971
event.getChannel().getHistory()
@@ -74,7 +76,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
7476
.filter(m -> !m.getAuthor().isBot()).findFirst()
7577
.orElse(null);
7678
if (target != null) {
77-
event.getHook().sendMessageFormat("```%s\n%s\n```", format, StringUtils.standardSanitizer().compute(target.getContentRaw()))
79+
event.getHook().sendMessageFormat("```%s\n%s\n```", format, IndentationHelper.formatIndentation(StringUtils.standardSanitizer().compute(target.getContentRaw()),IndentationHelper.IndentationType.valueOf(indentation)))
7880
.setAllowedMentions(List.of())
7981
.setComponents(buildActionRow(target))
8082
.queue();
@@ -89,7 +91,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
8991
}
9092
long messageId = idOption.getAsLong();
9193
event.getChannel().retrieveMessageById(messageId).queue(
92-
target -> event.getHook().sendMessageFormat("```%s\n%s\n```", format, StringUtils.standardSanitizer().compute(target.getContentRaw()))
94+
target -> event.getHook().sendMessageFormat("```%s\n%s\n```", format, IndentationHelper.formatIndentation(StringUtils.standardSanitizer().compute(target.getContentRaw()), IndentationHelper.IndentationType.valueOf(indentation)))
9395
.setAllowedMentions(List.of())
9496
.setComponents(buildActionRow(target))
9597
.queue(),
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package net.javadiscord.javabot.util;
2+
3+
/**
4+
* Utility class to help indent strings that contain code.
5+
*/
6+
public class IndentationHelper {
7+
/**
8+
* Enum which denotes the different types of indentation possible.
9+
* When {@link #NULL} is passed to any method in this class, the output of said method should match the input.
10+
*/
11+
public enum IndentationType {
12+
/**
13+
* A tab character (\t) should be used for indentation.
14+
*/
15+
TABS("\t"),
16+
/**
17+
* Four spaces should be used for indentation.
18+
*/
19+
FOUR_SPACES(" "),
20+
/**
21+
* Two spaces should be used for indentation
22+
*/
23+
TWO_SPACES(" "),
24+
/**
25+
* Doesn't define an indentation type but
26+
* Is used as a substitute to null to indicate that a String given to any method in {@link IndentationHelper} should not be changed.
27+
*/
28+
NULL("");
29+
30+
/**
31+
* Holds the characters used for indentation of the type.
32+
*/
33+
private final String pattern;
34+
35+
/**
36+
* Constructs the indentation type
37+
* @param pattern The pattern to be used as indentation
38+
*/
39+
IndentationType(String pattern) {
40+
this.pattern = pattern;
41+
}
42+
43+
/**
44+
* Get the pattern for a given Indentation type.
45+
* @return the pattern to be used for indenting.
46+
*/
47+
public String getPattern() {
48+
return pattern;
49+
}
50+
51+
/**
52+
* Returns the number of characters a pattern is using.
53+
* @return the number of characters the pattern of this type consists of.
54+
*/
55+
private int getNumberOfChars() {
56+
return pattern.length();
57+
}
58+
59+
}
60+
61+
/**
62+
* Aims to indent the given String using the pattern provided. Will return the String unchanged if {@link IndentationHelper.IndentationType#NULL} is passed as the IndentationType parameter.
63+
* @param text The text that should be indented.
64+
* @param type The type of indentation to be used.
65+
* @return The indented String with the format specified.
66+
*/
67+
public static String formatIndentation(String text, IndentationType type) {
68+
if(type == IndentationType.NULL) {
69+
return text;
70+
}
71+
int numberOfBrackets = 0;
72+
StringBuilder builder = new StringBuilder((int) (text.length()*1.25f));
73+
boolean startOfLine = true;
74+
boolean inString = false;
75+
boolean inChar = false;
76+
boolean singleLineComment = false;
77+
boolean multiLineComment = false;
78+
for(int i = 0; i< text.length(); i++) {
79+
char current = text.charAt(i);
80+
switch (current) {
81+
case '{' -> {
82+
if(!inString && !singleLineComment && !multiLineComment && !inChar) {
83+
numberOfBrackets++;
84+
}
85+
startOfLine = false;
86+
builder.append(current);
87+
}
88+
case '}' -> {
89+
if(!inString && !singleLineComment && !multiLineComment && !inChar) {
90+
numberOfBrackets--;
91+
}
92+
if(startOfLine && builder.length()-type.getNumberOfChars() > 0 && !multiLineComment) {
93+
startOfLine = false;
94+
builder.replace(builder.length()-type.getNumberOfChars(),builder.length(),"}");
95+
} else {
96+
startOfLine = false;
97+
builder.append(current);
98+
}
99+
}
100+
case '"'-> {
101+
if(builder.length() > 0) {
102+
if (isEscaped(builder,builder.length()) || inChar || singleLineComment ||multiLineComment) {
103+
builder.append(current);
104+
break;
105+
}
106+
}
107+
inString = !inString;
108+
builder.append(current);
109+
}
110+
case '\'' -> {
111+
if(builder.length() > 0) {
112+
if (isEscaped(builder,builder.length()) || inString || singleLineComment ||multiLineComment) {
113+
builder.append(current);
114+
break;
115+
}
116+
}
117+
inChar = !inChar;
118+
builder.append(current);
119+
}
120+
case '/' -> {
121+
builder.append(current);
122+
startOfLine = false;
123+
if(i+1 < text.length() && !inString && !inChar) {
124+
char nextChar = text.charAt(i+1);
125+
if(nextChar == '/'){
126+
singleLineComment = true;
127+
}
128+
if(nextChar == '*') {
129+
multiLineComment = true;
130+
}
131+
}
132+
}
133+
case '\n' -> {
134+
startOfLine = true;
135+
singleLineComment = false;
136+
builder.append("\n").append(type.getPattern().repeat(Math.max(numberOfBrackets,0)));
137+
}
138+
case '*' -> {
139+
builder.append(current);
140+
startOfLine = false;
141+
if(i+1 < text.length() && !inString && !inChar) {
142+
char nextChar = text.charAt(i+1);
143+
if(nextChar == '/'){
144+
multiLineComment = false;
145+
}
146+
}
147+
}
148+
default -> {
149+
if(startOfLine && !Character.isWhitespace(current)) {
150+
builder.append(current);
151+
startOfLine = false;
152+
} else if(!startOfLine) {
153+
builder.append(current);
154+
}
155+
}
156+
}
157+
}
158+
return builder.toString();
159+
}
160+
161+
/**
162+
* Determines if the character in the StringBuilder at the specified position is escaped.
163+
* @param builder the StringBuilder which holds the current String
164+
* @param index The index at which the character to be checked is located.
165+
* @return True, if the escape character is referring to the character at the index, false otherwise.
166+
*/
167+
private static boolean isEscaped(StringBuilder builder,int index) {
168+
int numberOfCharacters = 0;
169+
index--;
170+
if(index >= builder.length()) {
171+
return false;
172+
}
173+
while(index > 0 && builder.charAt(index) == '\\') {
174+
numberOfCharacters++;
175+
index--;
176+
}
177+
return numberOfCharacters % 2 == 1;
178+
}
179+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package net.javadiscord.javabot.util;
2+
import org.junit.jupiter.api.Test;
3+
4+
import java.io.IOException;
5+
import java.net.URISyntaxException;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.fail;
11+
12+
/**
13+
* Test for the {@link IndentationHelper} class.
14+
*/
15+
public class IndentationHelperTest {
16+
/**
17+
* Tests the {@link IndentationHelper#formatIndentation(String, IndentationHelper.IndentationType)} method
18+
*/
19+
@Test
20+
public void testFormatIndentation() {
21+
String[] unformatted = null;
22+
String[] formatted = null;
23+
try {
24+
unformatted = Files.readString(Path.of(IndentationHelper.class.getResource("/Unformatted Strings.txt").toURI())).split("----");
25+
formatted = Files.readString(Path.of(IndentationHelper.class.getResource("/Formatted Strings.txt").toURI())).split("----");
26+
} catch (NullPointerException | URISyntaxException e) {
27+
fail("Files to run the test not present");
28+
} catch (IOException e) {
29+
fail("IO Exception occurred");
30+
}
31+
32+
try {
33+
for(int i = 0,k=0; i< unformatted.length; i++) {
34+
assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.FOUR_SPACES),"Method failed to format a text with four spaces correctly");
35+
assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.TWO_SPACES),"Method failed to format a text with two spaces correctly");
36+
assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.TABS), "Method failed to format a text with tabs correctly.");
37+
assertEquals(formatted[k++], IndentationHelper.formatIndentation(unformatted[i], IndentationHelper.IndentationType.NULL), "Method returned a String not matching the input");
38+
}
39+
} catch (Exception e) {
40+
fail("Method threw exception",e.getCause());
41+
}
42+
43+
}
44+
}

0 commit comments

Comments
 (0)