Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mutli-character newlines support in CodeWriter #892

Merged
merged 1 commit into from
Aug 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ public String toString() {
}

if (result.isEmpty()) {
return trailingNewline ? String.valueOf(currentState.newline) : "";
return trailingNewline ? currentState.newline : "";
}

// This accounts for cases where the only write on the CodeWriter was
Expand All @@ -556,10 +556,10 @@ public String toString() {

if (trailingNewline) {
// Add a trailing newline if needed.
return result.charAt(result.length() - 1) != currentState.newline ? result + currentState.newline : result;
} else if (result.charAt(result.length() - 1) == currentState.newline) {
return result.endsWith(currentState.newline) ? result : result + currentState.newline;
} else if (result.endsWith(currentState.newline)) {
// Strip the trailing newline if present.
return result.substring(0, result.length() - 1);
return result.substring(0, result.length() - currentState.newline.length());
} else {
return result;
}
Expand Down Expand Up @@ -663,8 +663,9 @@ public CodeWriter popState() {
// and not written to the builder of the parent state. This ensures that
// inline sections are captured inside of strings and then later written
// back into a parent state.
popped.builder.setLength(0);
popped.builder.append(result);
StringBuilder builder = popped.getBuilder();
builder.setLength(0);
builder.append(result);
} else if (!result.isEmpty()) {
// Sections can be added that are just placeholders. In those cases,
// do not write anything unless the section emitted a non-empty string.
Expand All @@ -677,16 +678,12 @@ public CodeWriter popState() {
}

private String getTrimmedPoppedStateContents(State state) {
StringBuilder builder = state.builder;
String result = "";
String result = state.toString();

// Remove the trailing newline, if present, since it gets added in the
// final call to writeOptional.
if (builder != null && builder.length() > 0) {
if (builder.charAt(builder.length() - 1) == currentState.newline) {
builder.delete(builder.length() - 1, builder.length());
}
result = builder.toString();
if (result.endsWith(currentState.newline)) {
result = result.substring(0, result.length() - currentState.newline.length());
}

return result;
Expand Down Expand Up @@ -823,20 +820,18 @@ public CodeWriter enableNewlines() {
* {@link #disableNewlines()}, and does not actually change the newline
* character of the current state.
*
* <p>When the provided string is not empty, then the string must contain
* exactly one character. Setting the newline character to a non-empty
* string also implicitly enables newlines in the current state.
* <p>Setting the newline character to a non-empty string implicitly
* enables newlines in the current state.
*
* @param newline Newline character to use.
* @return Returns the CodeWriter.
*/
public final CodeWriter setNewline(String newline) {
if (newline.isEmpty()) {
return disableNewlines();
} else if (newline.length() > 1) {
throw new IllegalArgumentException("newline must be set to an empty string or a single character");
} else {
return setNewline(newline.charAt(0));
currentState.newline = newline;
return enableNewlines();
}
}

Expand All @@ -851,9 +846,7 @@ public final CodeWriter setNewline(String newline) {
* @return Returns the CodeWriter.
*/
public final CodeWriter setNewline(char newline) {
currentState.newline = newline;
enableNewlines();
return this;
return setNewline(String.valueOf(newline));
}

/**
Expand Down Expand Up @@ -1350,7 +1343,7 @@ private final class State {
private int indentation;
private boolean trimTrailingSpaces;
private boolean disableNewline;
private char newline = '\n';
private String newline = "\n";
private char expressionStart = '$';

private transient String sectionName;
Expand Down Expand Up @@ -1422,41 +1415,62 @@ void putInterceptor(String section, Consumer<Object> interceptor) {
interceptors.computeIfAbsent(section, s -> new ArrayList<>()).add(interceptor);
}

void write(String contents) {
StringBuilder getBuilder() {
if (builder == null) {
builder = new StringBuilder();
}
return builder;
}

void write(String contents) {
int position = 0;
int nextNewline = contents.indexOf(newline);

// Write each character, accounting for newlines along the way.
for (int i = 0; i < contents.length(); i++) {
append(contents.charAt(i));
while (nextNewline > -1) {
for (; position < nextNewline; position++) {
append(contents.charAt(position));
}
writeNewline();
position += newline.length();
nextNewline = contents.indexOf(newline, position);
}

// Write anything remaining in the string after the last newline.
for (; position < contents.length(); position++) {
append(contents.charAt(position));
}
}

private void append(char c) {
checkIndentationBeforeWriting();
getBuilder().append(c);
}

void append(char c) {
private void checkIndentationBeforeWriting() {
if (needsIndentation) {
builder.append(leadingIndentString);
builder.append(newlinePrefix);
getBuilder().append(leadingIndentString).append(newlinePrefix);
needsIndentation = false;
}
}

if (c == newline) {
// The next appended character will get indentation and a
// leading prefix string.
needsIndentation = true;
// Trim spaces before each newline. This only mutates the builder
// if space trimming is enabled.
trimSpaces();
}

builder.append(c);
private void writeNewline() {
checkIndentationBeforeWriting();
// Trim spaces before each newline. This only mutates the builder
// if space trimming is enabled.
trimSpaces();
// Newlines are never split across writes, which could potentially cause
// indentation logic to mess it up.
getBuilder().append(newline);
// The next appended character will get indentation and a
// leading prefix string.
needsIndentation = true;
}

void writeLine(String line) {
private void writeLine(String line) {
write(line);

if (!disableNewline) {
append(newline);
writeNewline();
}
}

Expand All @@ -1465,17 +1479,18 @@ private void trimSpaces() {
return;
}

StringBuilder buffer = getBuilder();
int toRemove = 0;
for (int i = builder.length() - 1; i > 0; i--) {
if (builder.charAt(i) == ' ') {
for (int i = buffer.length() - 1; i > 0; i--) {
if (buffer.charAt(i) == ' ') {
toRemove++;
} else {
break;
}
}

if (toRemove > 0) {
builder.delete(builder.length() - toRemove, builder.length());
buffer.delete(buffer.length() - toRemove, buffer.length());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -619,13 +619,6 @@ public void writeInlineDoesNotAllowIndentationToBeEscaped() {
assertThat(result, equalTo("\t\t{foo: [\n\t\t\thi,\n\t\t\tbye\n\t\t]\n\t}"));
}

@Test
public void newlineLengthMustBe1() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
CodeWriter.createDefault().setNewline(" ");
});
}

@Test
public void newlineCanBeDisabled() {
CodeWriter writer = CodeWriter
Expand Down Expand Up @@ -653,6 +646,34 @@ public void newlineCanBeDisabledWithEmptyString() {
assertThat(result, equalTo("[hi]\n"));
}

@Test
public void newlineCanBeMultipleCharacters() {
CodeWriter writer = CodeWriter
.createDefault()
.insertTrailingNewline()
.setNewline("\r\n");
String result = writer
.openBlock("[", "]", () -> writer.write("hi"))
.enableNewlines()
.toString();

assertThat(result, equalTo("[\r\n hi\r\n]\r\n"));
}

@Test
public void newlineCanBeLotsOfCharacters() {
CodeWriter writer = CodeWriter
.createDefault()
.insertTrailingNewline()
.setNewline("HELLO_THIS_IS_A_NEWLINE!!!");
String result = writer
.write("Hi.")
.write("There.")
.toString();

assertThat(result, equalTo("Hi.HELLO_THIS_IS_A_NEWLINE!!!There.HELLO_THIS_IS_A_NEWLINE!!!"));
}

@Test
public void settingNewlineEnablesNewlines() {
CodeWriter writer = CodeWriter.createDefault();
Expand Down Expand Up @@ -726,6 +747,7 @@ public void canComposeSetWithSection() {
assertThat(writer.toString(), equalTo("[1, 2, 3]\n"));
}

@Test
public void sectionWithWrite() {
String testSection = "TEST_SECTION";
CodeWriter writer = new CodeWriter();
Expand Down