Skip to content

Commit

Permalink
feat: supply eclipse formatter settings as XML content
Browse files Browse the repository at this point in the history
  • Loading branch information
tomasbjerre committed Dec 11, 2024
1 parent 671b6ee commit 6efa10a
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 38 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (

## [Unreleased]
### Changed
* TODO ([#](https://github.com/diffplug/spotless/pull/X))
* Allow setting Eclipse config from a string, not only from files ([#2337](https://github.com/diffplug/spotless/pull/2337))
* Bump default `ktlint` version to latest `1.3.0` -> `1.4.0`. ([#2314](https://github.com/diffplug/spotless/pull/2314))
* Add _Sort Members_ feature based on [Eclipse JDT](plugin-gradle/README.md#eclipse-jdt) implementation. ([#2312](https://github.com/diffplug/spotless/pull/2312))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public abstract class EquoBasedStepBuilder {
private String formatterVersion;
private Iterable<File> settingsFiles = new ArrayList<>();
private List<String> settingProperties = new ArrayList<>();
private List<String> settingXml = new ArrayList<>();
private Map<String, String> p2Mirrors = Map.of();
private File cacheDirectory;

Expand Down Expand Up @@ -86,6 +87,10 @@ public void setPropertyPreferences(List<String> propertyPreferences) {
this.settingProperties = propertyPreferences;
}

public void setXmlPreferences(List<String> settingXml) {
this.settingXml = settingXml;
}

public void setP2Mirrors(Map<String, String> p2Mirrors) {
this.p2Mirrors = Map.copyOf(p2Mirrors);
}
Expand Down Expand Up @@ -119,7 +124,7 @@ protected void addPlatformRepo(P2Model model, String version) {

/** Returns the FormatterStep (whose state will be calculated lazily). */
public FormatterStep build() {
var roundtrippableState = new EquoStep(formatterVersion, settingProperties, FileSignature.promise(settingsFiles), JarState.promise(() -> {
var roundtrippableState = new EquoStep(formatterVersion, settingProperties, settingXml, FileSignature.promise(settingsFiles), JarState.promise(() -> {
P2QueryResult query;
try {
if (null != cacheDirectory) {
Expand Down Expand Up @@ -174,23 +179,26 @@ static class EquoStep implements Serializable {
private final JarState.Promised jarPromise;
private final ImmutableMap<String, String> stepProperties;
private List<String> settingProperties;
private List<String> settingXml;

EquoStep(
String semanticVersion,
List<String> settingProperties,
List<String> settingXml,
FileSignature.Promised settingsPromise,
JarState.Promised jarPromise,
ImmutableMap<String, String> stepProperties) {

this.semanticVersion = semanticVersion;
this.settingProperties = Optional.ofNullable(settingProperties).orElse(new ArrayList<>());
this.settingXml = Optional.ofNullable(settingXml).orElse(new ArrayList<>());
this.settingsPromise = settingsPromise;
this.jarPromise = jarPromise;
this.stepProperties = stepProperties;
}

private State state() {
return new State(semanticVersion, jarPromise.get(), settingProperties, settingsPromise.get(), stepProperties);
return new State(semanticVersion, jarPromise.get(), settingProperties, settingXml, settingsPromise.get(), stepProperties);
}
}

Expand All @@ -205,11 +213,13 @@ public static class State implements Serializable {
final FileSignature settingsFiles;
final ImmutableMap<String, String> stepProperties;
private List<String> settingProperties;
private List<String> settingXml;

public State(String semanticVersion, JarState jarState, List<String> settingProperties, FileSignature settingsFiles, ImmutableMap<String, String> stepProperties) {
public State(String semanticVersion, JarState jarState, List<String> settingProperties, List<String> settingXml, FileSignature settingsFiles, ImmutableMap<String, String> stepProperties) {
this.semanticVersion = semanticVersion;
this.jarState = jarState;
this.settingProperties = Optional.ofNullable(settingProperties).orElse(new ArrayList<>());
this.settingXml = Optional.ofNullable(settingXml).orElse(new ArrayList<>());
this.settingsFiles = settingsFiles;
this.stepProperties = stepProperties;
}
Expand All @@ -225,7 +235,8 @@ public String getSemanticVersion() {
public Properties getPreferences() {
FormatterProperties fromFiles = FormatterProperties.from(settingsFiles.files());
FormatterProperties fromPropertiesContent = FormatterProperties.fromPropertiesContent(settingProperties);
return FormatterProperties.merge(fromFiles.getProperties(), fromPropertiesContent.getProperties()).getProperties();
FormatterProperties fromXmlContent = FormatterProperties.fromXmlContent(settingXml);
return FormatterProperties.merge(fromFiles.getProperties(), fromPropertiesContent.getProperties(), fromXmlContent.getProperties()).getProperties();
}

public ImmutableMap<String, String> getStepProperties() {
Expand Down
65 changes: 53 additions & 12 deletions lib/src/main/java/com/diffplug/spotless/FormatterProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
Expand All @@ -28,6 +29,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

Expand Down Expand Up @@ -90,6 +92,25 @@ public static FormatterProperties fromPropertiesContent(Iterable<String> content
return properties;
}

public static FormatterProperties fromXmlContent(final Iterable<String> content) throws IllegalArgumentException {
final List<String> nonNullElements = toNullHostileList(content);
final FormatterProperties properties = new FormatterProperties();
nonNullElements.forEach(contentElement -> {
try {
final Properties newSettings = FileParser.XML.executeXmlContent(contentElement);
properties.properties.putAll(newSettings);
} catch (IOException | IllegalArgumentException exception) {
String message = String.format("Failed to add preferences from XML:%n%s%n", contentElement);
final String detailedMessage = exception.getMessage();
if (null != detailedMessage) {
message += String.format(" %s", detailedMessage);
}
throw new IllegalArgumentException(message, exception);
}
});
return properties;
}

public static FormatterProperties merge(Properties... properties) {
FormatterProperties merged = new FormatterProperties();
List.of(properties).stream().forEach((source) -> merged.properties.putAll(source));
Expand Down Expand Up @@ -139,20 +160,40 @@ protected Properties execute(final File file) throws IOException, IllegalArgumen
}
return properties;
}

@Override
protected Properties executeXmlContent(String content) throws IOException, IllegalArgumentException {
throw new RuntimeException("Not implemented");
}
},

XML("xml") {
@Override
protected Properties execute(final File file) throws IOException, IllegalArgumentException {
Node rootNode = getRootNode(file);
return executeWithSupplier(() -> {
try {
return new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException("File not found: " + file, e);
}
});
}

@Override
protected Properties executeXmlContent(String content) throws IOException, IllegalArgumentException {
return executeWithSupplier(() -> new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
}

private Properties executeWithSupplier(Supplier<InputStream> isSupplier) throws IOException, IllegalArgumentException {
Node rootNode = getRootNode(isSupplier.get());
String nodeName = rootNode.getNodeName();
if (null == nodeName) {
throw new IllegalArgumentException("XML document does not contain a root node.");
}
return XmlParser.parse(file, rootNode);
return XmlParser.parse(isSupplier.get(), rootNode);
}

private Node getRootNode(final File file) throws IOException, IllegalArgumentException {
private Node getRootNode(final InputStream is) throws IOException, IllegalArgumentException {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
/*
Expand All @@ -166,7 +207,7 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc
*/
dbf.setFeature(LOAD_EXTERNAL_DTD_PROP, false);
DocumentBuilder db = dbf.newDocumentBuilder();
return db.parse(file).getDocumentElement();
return db.parse(is).getDocumentElement();
} catch (SAXException | ParserConfigurationException e) {
throw new IllegalArgumentException("File has no valid XML syntax.", e);
}
Expand All @@ -186,6 +227,8 @@ private Node getRootNode(final File file) throws IOException, IllegalArgumentExc

protected abstract Properties execute(File file) throws IOException, IllegalArgumentException;

protected abstract Properties executeXmlContent(String content) throws IOException, IllegalArgumentException;

public static Properties parse(final File file) throws IOException, IllegalArgumentException {
String fileNameExtension = getFileNameExtension(file);
for (FileParser parser : FileParser.values()) {
Expand All @@ -211,19 +254,17 @@ private static String getFileNameExtension(File file) {
private enum XmlParser {
PROPERTIES("properties") {
@Override
protected Properties execute(final File xmlFile, final Node rootNode)
protected Properties execute(final InputStream xmlFile, final Node rootNode)
throws IOException, IllegalArgumentException {
final Properties properties = new Properties();
try (InputStream xmlInput = new FileInputStream(xmlFile)) {
properties.loadFromXML(xmlInput);
}
properties.loadFromXML(xmlFile);
return properties;
}
},

PROFILES("profiles") {
@Override
protected Properties execute(File file, Node rootNode) throws IOException, IllegalArgumentException {
protected Properties execute(InputStream file, Node rootNode) throws IOException, IllegalArgumentException {
final Properties properties = new Properties();
Node firstProfile = getSingleProfile(rootNode);
for (Object settingObj : getChildren(firstProfile, "setting")) {
Expand Down Expand Up @@ -285,14 +326,14 @@ public String toString() {
return this.rootNodeName;
}

protected abstract Properties execute(File file, Node rootNode) throws IOException, IllegalArgumentException;
protected abstract Properties execute(InputStream is, Node rootNode) throws IOException, IllegalArgumentException;

public static Properties parse(final File file, final Node rootNode)
public static Properties parse(final InputStream is, final Node rootNode)
throws IOException, IllegalArgumentException {
String rootNodeName = rootNode.getNodeName();
for (XmlParser parser : XmlParser.values()) {
if (parser.rootNodeName.equals(rootNodeName)) {
return parser.execute(file, rootNode);
return parser.execute(is, rootNode);
}
}
String msg = String.format("The XML root node '%1$s' is not part of the supported root nodes [%2$s].",
Expand Down
18 changes: 15 additions & 3 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,14 @@ spotless {
eclipse()
// optional: you can specify a specific version and/or config file
eclipse('4.26').configFile('eclipse-prefs.xml')
// Or supply the configuration as a string
// Or supply the configuration properties as a string
eclipse('4.26').configProperties("""
...
""")
// Or supply the configuration XML as a string
eclipse('4.26').configXml("""
...
""")
// if the access to the p2 repositories is restricted, mirrors can be
// specified using a URI prefix map as follows:
eclipse().withP2Mirrors(['https://download.eclipse.org/eclipse/updates/4.29/':'https://some.internal.mirror/4-29-updates-p2/'])
Expand Down Expand Up @@ -422,10 +426,14 @@ spotless {
greclipse()
// optional: you can specify a specific version or config file(s), version matches the Eclipse Platform
greclipse('4.26').configFile('spotless.eclipseformat.xml', 'org.codehaus.groovy.eclipse.ui.prefs')
// Or supply the configuration as a string
// Or supply the configuration properties as a string
greclipse('4.26').configProperties("""
...
""")
// Or supply the configuration XML as a string
greclipse('4.26').configXml("""
...
""")
```

Groovy-Eclipse formatting errors/warnings lead per default to a build failure. This behavior can be changed by adding the property/key value `ignoreFormatterProblems=true` to a configuration file. In this scenario, files causing problems, will not be modified by this formatter step.
Expand Down Expand Up @@ -580,10 +588,14 @@ spotles {
cpp {
// version and configFile are both optional
eclipseCdt('4.13.0').configFile('eclipse-cdt.xml')
// Or supply the configuration as a string
// Or supply the configuration properties as a string
eclipseCdt('4.13.0').configProperties("""
...
""")
// Or supply the configuration XML as a string
eclipseCdt('4.13.0').configXml("""
...
""")
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ public GrEclipseConfig configProperties(String... configs) {
return this;
}

public GrEclipseConfig configXml(String... configs) {
requireElementsNonNull(configs);
builder.setXmlPreferences(List.of(configs));
extension.replaceStep(builder.build());
return this;
}

public GrEclipseConfig withP2Mirrors(Map<String, String> mirrors) {
builder.setP2Mirrors(mirrors);
extension.replaceStep(builder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public EclipseConfig configProperties(String... configs) {
return this;
}

public EclipseConfig configXml(String... configs) {
requireElementsNonNull(configs);
builder.setXmlPreferences(List.of(configs));
replaceStep(builder.build());
return this;
}

public EclipseConfig withP2Mirrors(Map<String, String> mirrors) {
builder.setP2Mirrors(mirrors);
replaceStep(builder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,13 @@ public EclipseConfig configProperties(String... configs) {
return this;
}

public EclipseConfig configXml(String... configs) {
requireElementsNonNull(configs);
builder.setXmlPreferences(List.of(configs));
replaceStep(builder.build());
return this;
}

public EclipseConfig sortMembersDoNotSortFields(boolean doNotSortFields) {
builder.sortMembersDoNotSortFields(doNotSortFields);
replaceStep(builder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

class JavaEclipseTest extends GradleIntegrationHarness {
@Test
void settingsWithContentWithoutFile() throws IOException {
void settingsWithProprtiesContent() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
Expand All @@ -37,4 +37,27 @@ void settingsWithContentWithoutFile() throws IOException {

gradleRunner().withArguments("spotlessApply").build();
}

@Test
void settingsWithXmlContent() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
" id 'java'",
"}",
"repositories { mavenCentral() }",
"",
"spotless {",
" java { eclipse().configProperties(\"\"\"",
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>",
"<profiles version=\"12\">",
" <profile kind=\"CodeFormatterProfile\" name=\"Spotless\" version=\"12\">",
" <setting id=\"valid_line_oriented.prefs.string\" value=\"string\" />",
" </profile>",
"</profiles>",
"\"\"\") }",
"}");

gradleRunner().withArguments("spotlessApply").build();
}
}
Loading

0 comments on commit 6efa10a

Please sign in to comment.