Skip to content

Commit

Permalink
Add mutli-character newlines support in CodeWriter
Browse files Browse the repository at this point in the history
CodeWriter now support multi-character newlines like '\r\n'.
  • Loading branch information
mtdowling committed Aug 16, 2021
1 parent 6084e16 commit 562a88e
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 52 deletions.
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

0 comments on commit 562a88e

Please sign in to comment.