Add writer with indent support

This commit adds an `IndentingWriter` with a factory that supports
different indenting option according to a content identifier (e.g. a
language).

Closes gh-812

Co-authored-by: Andy Wilkinson <awilkinson@pivotal.io>
This commit is contained in:
Stephane Nicoll 2019-02-07 14:54:22 +01:00
parent 3fa03b8438
commit b6e675de40
7 changed files with 526 additions and 0 deletions

View File

@ -0,0 +1,113 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.io;
import java.io.IOException;
import java.io.Writer;
import java.util.function.Function;
/**
* A {@link Writer} with support for indenting.
*
* @author Andy Wilkinson
*/
public class IndentingWriter extends Writer {
private final Writer out;
private final Function<Integer, String> indentStrategy;
private int level = 0;
private String indent = "";
private boolean prependIndent = false;
public IndentingWriter(Writer out) {
this(out, new SimpleIndentStrategy(" "));
}
public IndentingWriter(Writer out, Function<Integer, String> indentStrategy) {
this.out = out;
this.indentStrategy = indentStrategy;
}
public void print(String string) {
write(string.toCharArray(), 0, string.length());
}
public void println(String string) {
write(string.toCharArray(), 0, string.length());
println();
}
public void println() {
String separator = System.lineSeparator();
try {
this.out.write(separator.toCharArray(), 0, separator.length());
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
this.prependIndent = true;
}
public void indented(Runnable runnable) {
indent();
runnable.run();
outdent();
}
private void indent() {
this.level++;
refreshIndent();
}
private void outdent() {
this.level--;
refreshIndent();
}
private void refreshIndent() {
this.indent = this.indentStrategy.apply(this.level);
}
@Override
public void write(char[] chars, int offset, int length) {
try {
if (this.prependIndent) {
this.out.write(this.indent.toCharArray(), 0, this.indent.length());
this.prependIndent = false;
}
this.out.write(chars, offset, length);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public void flush() throws IOException {
this.out.flush();
}
@Override
public void close() throws IOException {
this.out.close();
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.io;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* A factory for {@link IndentingWriter} that provides customizations according to the
* chosen content.
*
* @author Stephane Nicoll
* @see SimpleIndentStrategy
*/
public final class IndentingWriterFactory {
private final Function<Integer, String> defaultIndentingStrategy;
private final Map<String, Function<Integer, String>> indentingStrategies;
private IndentingWriterFactory(Builder builder) {
this.defaultIndentingStrategy = builder.defaultIndentingStrategy;
this.indentingStrategies = new HashMap<>(builder.indentingStrategies);
}
/**
* Create an {@link IndentingWriter} for the specified content and output.
* @param contentId the identifier of the content
* @param out the output to use
* @return a configured {@link IndentingWriter}
*/
public IndentingWriter createIndentingWriter(String contentId, Writer out) {
Function<Integer, String> indentingStrategy = this.indentingStrategies
.getOrDefault(contentId, this.defaultIndentingStrategy);
return new IndentingWriter(out, indentingStrategy);
}
/**
* Create an {@link IndentingWriterFactory} with default settings.
* @return an {@link IndentingWriterFactory} with default settings
*/
public static IndentingWriterFactory withDefaultSettings() {
return create(new SimpleIndentStrategy(" "));
}
/**
* Create a {@link IndentingWriterFactory} with a single indenting strategy.
* @param defaultIndentingStrategy the default indenting strategy to use
* @return an {@link IndentingWriterFactory}
*/
public static IndentingWriterFactory create(
Function<Integer, String> defaultIndentingStrategy) {
return new IndentingWriterFactory(new Builder(defaultIndentingStrategy));
}
/**
* Create a {@link IndentingWriterFactory}.
* @param defaultIndentingStrategy the default indenting strategy to use
* @param factory a consumer of the builder to apply further customizations
* @return an {@link IndentingWriterFactory}
*/
public static IndentingWriterFactory create(
Function<Integer, String> defaultIndentingStrategy,
Consumer<Builder> factory) {
Builder factoryBuilder = new Builder(defaultIndentingStrategy);
factory.accept(factoryBuilder);
return new IndentingWriterFactory(factoryBuilder);
}
/**
* Settings customizer for {@link IndentingWriterFactory}.
*/
public static final class Builder {
private final Function<Integer, String> defaultIndentingStrategy;
private final Map<String, Function<Integer, String>> indentingStrategies = new HashMap<>();
private Builder(Function<Integer, String> defaultIndentingStrategy) {
this.defaultIndentingStrategy = defaultIndentingStrategy;
}
/**
* Register an indenting strategy for the specified content.
* @param contentId the identifier of the content to configure
* @param indentingStrategy the indent strategy for that particular content
* @return this builder
* @see #createIndentingWriter(String, Writer)
*/
public Builder indentingStrategy(String contentId,
Function<Integer, String> indentingStrategy) {
this.indentingStrategies.put(contentId, indentingStrategy);
return this;
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.io;
import java.util.function.Function;
import org.springframework.util.Assert;
/**
* A simple indenting strategy that uses a configurable {@code indent} value.
*
* @author Stephane Nicoll
*/
public class SimpleIndentStrategy implements Function<Integer, String> {
private final String indent;
/**
* Create a new instance with the indent style to apply.
* @param indent the indent to apply for a single level
*/
public SimpleIndentStrategy(String indent) {
Assert.notNull(indent, "Indent must be provided");
this.indent = indent;
}
@Override
public String apply(Integer level) {
if (level < 0) {
throw new IllegalArgumentException(
"Indent level must not be negative, got" + level);
}
StringBuilder indentBuilder = new StringBuilder();
for (int i = 0; i < level; i++) {
indentBuilder.append(this.indent);
}
return indentBuilder.toString();
}
}

View File

@ -0,0 +1,20 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Support for writing project assets.
*/
package io.spring.initializr.generator.io;

View File

@ -0,0 +1,63 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.io;
import java.io.StringWriter;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link IndentingWriterFactory}.
*
* @author Stephane Nicoll
*/
class IndentingWriterFactoryTests {
private static final SimpleIndentStrategy SPACE_STRATEGY = new SimpleIndentStrategy(
" ");
private static final SimpleIndentStrategy TAB_STRATEGY = new SimpleIndentStrategy(
"\t");
private final StringWriter out = new StringWriter();
@Test
void createWithSingleIndentStrategy() {
IndentingWriter writer = IndentingWriterFactory.create((SPACE_STRATEGY))
.createIndentingWriter("test", this.out);
assertThat(writer).hasFieldOrPropertyWithValue("indentStrategy", SPACE_STRATEGY);
}
@Test
void createWithSpecializedIndentStrategy() {
SimpleIndentStrategy twoSpacesStrategy = new SimpleIndentStrategy(" ");
IndentingWriterFactory indentingWriterFactory = IndentingWriterFactory
.create(SPACE_STRATEGY, (factory) -> {
factory.indentingStrategy("java", TAB_STRATEGY);
factory.indentingStrategy("pom", twoSpacesStrategy);
});
assertThat(indentingWriterFactory.createIndentingWriter("java", this.out))
.hasFieldOrPropertyWithValue("indentStrategy", TAB_STRATEGY);
assertThat(indentingWriterFactory.createIndentingWriter("pom", this.out))
.hasFieldOrPropertyWithValue("indentStrategy", twoSpacesStrategy);
assertThat(indentingWriterFactory.createIndentingWriter("c", this.out))
.hasFieldOrPropertyWithValue("indentStrategy", SPACE_STRATEGY);
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.io;
import java.io.StringWriter;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link IndentingWriter}.
*
* @author Andy Wilkinson
*/
class IndentingWriterTests {
private final StringWriter stringWriter = new StringWriter();
private final IndentingWriter indentingWriter = new IndentingWriter(
this.stringWriter);
@Test
void linesAreNotIndentedByDefault() {
this.indentingWriter.println("a");
this.indentingWriter.println("b");
this.indentingWriter.println("c");
assertThat(this.stringWriter.toString()).isEqualTo("a\nb\nc\n");
}
@Test
void linesCanBeIndented() {
this.indentingWriter.println("a");
this.indentingWriter.indented(() -> this.indentingWriter.println("b"));
this.indentingWriter.println("c");
assertThat(this.stringWriter.toString()).isEqualTo("a\n b\nc\n");
}
@Test
void blankLinesAreNotIndented() {
this.indentingWriter.println("a");
this.indentingWriter.indented(() -> {
this.indentingWriter.println("b");
this.indentingWriter.println();
});
this.indentingWriter.println("c");
assertThat(this.stringWriter.toString()).isEqualTo("a\n b\n\nc\n");
}
@Test
void useOfPrintDoesNotAddIndent() {
this.indentingWriter.println("a");
this.indentingWriter.indented(() -> {
this.indentingWriter.print("b");
this.indentingWriter.print("b");
this.indentingWriter.println("b");
});
this.indentingWriter.println("c");
assertThat(this.stringWriter.toString()).isEqualTo("a\n bbb\nc\n");
}
@Test
void customIndentStrategyIsUsed() {
IndentingWriter customIndentingWriter = new IndentingWriter(this.stringWriter,
new SimpleIndentStrategy("\t"));
customIndentingWriter.println("a");
customIndentingWriter.indented(() -> {
customIndentingWriter.println("b");
customIndentingWriter.indented(() -> {
customIndentingWriter.print("c");
customIndentingWriter.println("e");
});
});
assertThat(this.stringWriter.toString()).isEqualTo("a\n\tb\n\t\tce\n");
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.io;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link SimpleIndentStrategy}.
*
* @author Stephane Nicoll
*/
class SimpleIndentStrategyTests {
@Test
void noLevelIsAllowed() {
assertThat(new SimpleIndentStrategy(" ").apply(0)).isEqualTo("");
}
@Test
void singleLevelIndentSpace() {
assertThat(new SimpleIndentStrategy(" ").apply(1)).isEqualTo(" ");
}
@Test
void singleLevelIndentTab() {
assertThat(new SimpleIndentStrategy("\t").apply(1)).isEqualTo("\t");
}
@Test
void multiLevelIndentSpace() {
assertThat(new SimpleIndentStrategy(" ").apply(3)).isEqualTo(" ");
}
@Test
void multiLevelIndentTab() {
assertThat(new SimpleIndentStrategy("\t").apply(3)).isEqualTo("\t\t\t");
}
@Test
void mustHaveIndent() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new SimpleIndentStrategy(null));
}
@Test
void indentLevelMustNotBeNegative() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new SimpleIndentStrategy(" ").apply(-1))
.withMessageContaining("Indent level must not be negative")
.withMessageContaining("-1");
}
}