From 29cda4ae39b995b3f9f6dccd973f96ef752aa323 Mon Sep 17 00:00:00 2001 From: zml Date: Sun, 15 Jun 2025 14:51:01 -0700 Subject: [PATCH 1/2] feat: add option to configure whether newlines are trimmed fixes GH-86 --- .../net/kyori/blossom/GenerateTemplates.java | 1 + .../java/net/kyori/blossom/TemplateSet.java | 12 +++++++++ .../internal/ResourceTemplateSetImpl.java | 5 ++-- .../internal/SourceTemplateSetImpl.java | 5 ++-- .../blossom/internal/TemplateSetImpl.java | 26 ++++++++++--------- .../internal/worker/GenerateWorker.java | 5 +++- .../blossom/ResourceTemplateSetTest.java | 15 +++++++++++ .../blossom/test/BlossomFunctionalTest.java | 2 +- .../blossom/trimTrailing/in/build.gradle | 18 +++++++++++++ .../blossom/trimTrailing/in/meta.yaml.peb | 3 +++ .../kyori/blossom/trimTrailing/out/meta.yaml | 3 +++ .../worker/GenerateWorkerInvokerImpl.java | 4 ++- .../worker/GenerateWorkerInvoker.java | 3 ++- 13 files changed, 82 insertions(+), 20 deletions(-) create mode 100644 src/test/resources/net/kyori/blossom/trimTrailing/in/build.gradle create mode 100644 src/test/resources/net/kyori/blossom/trimTrailing/in/meta.yaml.peb create mode 100644 src/test/resources/net/kyori/blossom/trimTrailing/out/meta.yaml diff --git a/src/main/java/net/kyori/blossom/GenerateTemplates.java b/src/main/java/net/kyori/blossom/GenerateTemplates.java index 8473221..db2588a 100644 --- a/src/main/java/net/kyori/blossom/GenerateTemplates.java +++ b/src/main/java/net/kyori/blossom/GenerateTemplates.java @@ -142,6 +142,7 @@ void generate() throws IOException { // general properties spec.getHeader().set(this.getBaseSet().flatMap(TemplateSet::getHeader)); + spec.getTrimNewlines().set(this.getBaseSet().flatMap(TemplateSet::getTrimNewlines)); spec.getSourceDirectories().from(this.getSourceDirectories()); spec.getIncludesDirectories().from(this.getIncludesDirectories()); spec.getDestinationDirectory().set(this.getOutputDir()); diff --git a/src/main/java/net/kyori/blossom/TemplateSet.java b/src/main/java/net/kyori/blossom/TemplateSet.java index 6dac752..85fd002 100644 --- a/src/main/java/net/kyori/blossom/TemplateSet.java +++ b/src/main/java/net/kyori/blossom/TemplateSet.java @@ -51,6 +51,18 @@ public interface TemplateSet extends Named { @Override @NotNull String getName(); + /** + * Whether to strip trailing newlines from Pebble tokens that occur at the end of a line. + * + *

The default is {@code true}, which can cause strange behaviour in some file formats + * that are whitespace-sensitive.

+ * + * @return whether to trim newlines + * @since 2.2.0 + */ + @Input + @NotNull Property getTrimNewlines(); + /** * A collection of data files in YAML format. * diff --git a/src/main/java/net/kyori/blossom/internal/ResourceTemplateSetImpl.java b/src/main/java/net/kyori/blossom/internal/ResourceTemplateSetImpl.java index 793695a..17f331d 100644 --- a/src/main/java/net/kyori/blossom/internal/ResourceTemplateSetImpl.java +++ b/src/main/java/net/kyori/blossom/internal/ResourceTemplateSetImpl.java @@ -24,6 +24,7 @@ import net.kyori.blossom.GenerateTemplates; import net.kyori.blossom.ResourceTemplateSet; import org.gradle.api.file.Directory; +import org.gradle.api.model.ObjectFactory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; @@ -32,8 +33,8 @@ */ public abstract class ResourceTemplateSetImpl extends TemplateSetImpl implements ResourceTemplateSet { @Inject - public ResourceTemplateSetImpl(final String name) { - super(name); + public ResourceTemplateSetImpl(final ObjectFactory objects, final String name) { + super(objects, name); } @Override diff --git a/src/main/java/net/kyori/blossom/internal/SourceTemplateSetImpl.java b/src/main/java/net/kyori/blossom/internal/SourceTemplateSetImpl.java index aa15d32..a128726 100644 --- a/src/main/java/net/kyori/blossom/internal/SourceTemplateSetImpl.java +++ b/src/main/java/net/kyori/blossom/internal/SourceTemplateSetImpl.java @@ -27,6 +27,7 @@ import org.gradle.api.GradleException; import org.gradle.api.file.Directory; import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.model.ObjectFactory; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskProvider; import org.jetbrains.annotations.NotNull; @@ -36,8 +37,8 @@ public abstract class SourceTemplateSetImpl extends TemplateSetImpl implements S private transient TaskProvider pendingGenerateTask; @Inject - public SourceTemplateSetImpl(final String name) { - super(name); + public SourceTemplateSetImpl(final ObjectFactory objects, final String name) { + super(objects, name); } @Override diff --git a/src/main/java/net/kyori/blossom/internal/TemplateSetImpl.java b/src/main/java/net/kyori/blossom/internal/TemplateSetImpl.java index 82e864e..1fb04c2 100644 --- a/src/main/java/net/kyori/blossom/internal/TemplateSetImpl.java +++ b/src/main/java/net/kyori/blossom/internal/TemplateSetImpl.java @@ -32,8 +32,6 @@ /** * A directory of templates. - * - *

While it's perfectly possible to

*/ public abstract class TemplateSetImpl implements TemplateSetInternal { // shared @@ -41,24 +39,23 @@ public abstract class TemplateSetImpl implements TemplateSetInternal { private final MapProperty properties; private final NamedDomainObjectContainer variants; private final Property header; + private final Property trimNewlines; private transient final SourceDirectorySet includes; private transient final SourceDirectorySet templates; private final String name; @Inject - public TemplateSetImpl(final String name) { + public TemplateSetImpl(final ObjectFactory objects, final String name) { this.name = name; - this.dataFiles = this.getObjects().fileCollection(); - this.properties = this.getObjects().mapProperty(String.class, Object.class); - this.variants = this.getObjects().domainObjectContainer(Variant.class, n -> this.getObjects().newInstance(VariantImpl.class, n)); - this.header = this.getObjects().property(String.class); - this.includes = this.getObjects().sourceDirectorySet(name + "-template-includes", name + " template includes"); - this.templates = this.getObjects().sourceDirectorySet(name + "-templates", name + " templates"); + this.dataFiles = objects.fileCollection(); + this.properties = objects.mapProperty(String.class, Object.class); + this.variants = objects.domainObjectContainer(Variant.class, n -> objects.newInstance(VariantImpl.class, n)); + this.header = objects.property(String.class); + this.trimNewlines = objects.property(Boolean.class).convention(true); + this.includes = objects.sourceDirectorySet(name + "-template-includes", name + " template includes"); + this.templates = objects.sourceDirectorySet(name + "-templates", name + " templates"); } - @Inject - protected abstract ObjectFactory getObjects(); - @Override public @NotNull String getName() { return this.name; @@ -81,6 +78,11 @@ public TemplateSetImpl(final String name) { return this.header; } + @Override + public @NotNull Property getTrimNewlines() { + return this.trimNewlines; + } + @Override public @NotNull SourceDirectorySet getIncludes() { return this.includes; diff --git a/src/main/java/net/kyori/blossom/internal/worker/GenerateWorker.java b/src/main/java/net/kyori/blossom/internal/worker/GenerateWorker.java index a853cf9..ec542ee 100644 --- a/src/main/java/net/kyori/blossom/internal/worker/GenerateWorker.java +++ b/src/main/java/net/kyori/blossom/internal/worker/GenerateWorker.java @@ -53,6 +53,8 @@ public interface Params extends WorkParameters { Property getHeader(); + Property getTrimNewlines(); + ConfigurableFileCollection getSourceDirectories(); ConfigurableFileCollection getIncludesDirectories(); @@ -92,7 +94,8 @@ public void execute() { toPaths(params.getIncludesDirectories()), toPaths(params.getSourceDirectories()), params.getDestinationDirectory().get().getAsFile().toPath(), - params.getHeader().getOrNull() + params.getHeader().getOrNull(), + params.getTrimNewlines().getOrElse(false) ); } catch (final IOException ex) { throw new GradleException("Failed to process templates:" + ex.getMessage(), ex); diff --git a/src/test/java/net/kyori/blossom/ResourceTemplateSetTest.java b/src/test/java/net/kyori/blossom/ResourceTemplateSetTest.java index 3601968..b6eebb8 100644 --- a/src/test/java/net/kyori/blossom/ResourceTemplateSetTest.java +++ b/src/test/java/net/kyori/blossom/ResourceTemplateSetTest.java @@ -65,4 +65,19 @@ void testResourceSingleSet(final TestContext ctx) throws IOException { } } + @BlossomFunctionalTest + void testTrimTrailing(final TestContext ctx) throws IOException { + SettingsFactory.writeSettings(ctx, "trimTrailing"); + ctx.copyInput("build.gradle"); + ctx.copyInput("meta.yaml.peb", "src/main/resource-templates/meta.yaml.peb"); + + final BuildResult result = ctx.build("generateTemplates"); // build templates + assertEquals(TaskOutcome.SUCCESS, result.task(":generateResourceTemplates").getOutcome()); + + ctx.assertOutputEquals( + "meta.yaml", + "build/generated/resources/blossom/main/resource/meta.yaml" + ); + } + } diff --git a/src/test/java/net/kyori/blossom/test/BlossomFunctionalTest.java b/src/test/java/net/kyori/blossom/test/BlossomFunctionalTest.java index 355a81b..5419127 100644 --- a/src/test/java/net/kyori/blossom/test/BlossomFunctionalTest.java +++ b/src/test/java/net/kyori/blossom/test/BlossomFunctionalTest.java @@ -34,7 +34,7 @@ @GradleParameters({"--warning-mode", "fail", "--stacktrace"}) // parameters for all variants @TestVariant(gradleVersion = "7.6.4", maximumRuntimeVersion = 20) @TestVariant(gradleVersion = "8.9", extraArguments = {"--configuration-cache"}) // last version with non-deprecated support for runtimes <17 -@TestVariant(gradleVersion = "8.10", extraArguments = {"--configuration-cache"}, minimumRuntimeVersion = 17) +@TestVariant(gradleVersion = "8.14.2", extraArguments = {"--configuration-cache"}, minimumRuntimeVersion = 17) @TestVariantResource(value = "/injected-gradle-versions", optional = true, minimumRuntimeVersion = 17) // newer Gradle versions deprecate running on JDK <17, and this is only for RC's @Documented @Retention(RetentionPolicy.RUNTIME) diff --git a/src/test/resources/net/kyori/blossom/trimTrailing/in/build.gradle b/src/test/resources/net/kyori/blossom/trimTrailing/in/build.gradle new file mode 100644 index 0000000..2f3afe9 --- /dev/null +++ b/src/test/resources/net/kyori/blossom/trimTrailing/in/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java' + id 'net.kyori.blossom' +} + +version = "1.0.3" + +sourceSets { + main { + blossom { + resources { + trimNewlines = false + property('name', project.name) + property('version', project.version.toString()) + } + } + } +} diff --git a/src/test/resources/net/kyori/blossom/trimTrailing/in/meta.yaml.peb b/src/test/resources/net/kyori/blossom/trimTrailing/in/meta.yaml.peb new file mode 100644 index 0000000..774a032 --- /dev/null +++ b/src/test/resources/net/kyori/blossom/trimTrailing/in/meta.yaml.peb @@ -0,0 +1,3 @@ +info: + name: {{ name }} + version: {{ version }} diff --git a/src/test/resources/net/kyori/blossom/trimTrailing/out/meta.yaml b/src/test/resources/net/kyori/blossom/trimTrailing/out/meta.yaml new file mode 100644 index 0000000..3610b37 --- /dev/null +++ b/src/test/resources/net/kyori/blossom/trimTrailing/out/meta.yaml @@ -0,0 +1,3 @@ +info: + name: trimTrailing + version: 1.0.3 diff --git a/src/workerClasspath/java/net/kyori/blossom/internal/worker/GenerateWorkerInvokerImpl.java b/src/workerClasspath/java/net/kyori/blossom/internal/worker/GenerateWorkerInvokerImpl.java index 94548f5..148fb45 100644 --- a/src/workerClasspath/java/net/kyori/blossom/internal/worker/GenerateWorkerInvokerImpl.java +++ b/src/workerClasspath/java/net/kyori/blossom/internal/worker/GenerateWorkerInvokerImpl.java @@ -53,13 +53,15 @@ public void generate( final Set includePaths, final Set sourcePaths, final Path outputDirectory, - final @Nullable String header + final @Nullable String header, + final boolean trimNewlines ) throws IOException { // By default, resolves FS paths // todo: restrict inputs to inputs and includes final Loader loader = this.makeLoader(sourcePaths, includePaths); final PebbleEngine engine = new PebbleEngine.Builder() .autoEscaping(false) // no html escaping + .newLineTrimming(trimNewlines) // can mess with line breaks in yaml files .defaultLocale(Locale.ROOT) .loader(loader) // .cacheActive(false) // xX: overlap between file names and template names causes issues diff --git a/src/workerShared/java/net/kyori/blossom/internal/worker/GenerateWorkerInvoker.java b/src/workerShared/java/net/kyori/blossom/internal/worker/GenerateWorkerInvoker.java index 925073a..16b7448 100644 --- a/src/workerShared/java/net/kyori/blossom/internal/worker/GenerateWorkerInvoker.java +++ b/src/workerShared/java/net/kyori/blossom/internal/worker/GenerateWorkerInvoker.java @@ -31,6 +31,7 @@ void generate( final Set includePaths, final Set sourcePaths, final Path outputDir, - final /* @Nullable */ String header + final /* @Nullable */ String header, + final boolean trimNewlines ) throws IOException; } From 7e88bd036d544daf5ad3842250bee3d94b32fc3e Mon Sep 17 00:00:00 2001 From: zml Date: Sun, 15 Jun 2025 15:01:54 -0700 Subject: [PATCH 2/2] chore: document trimming behaviour --- readme.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/readme.md b/readme.md index a3a19de..32c3c5f 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,12 @@ sourceSets { } ``` +> [!IMPORTANT] +> When processing templates, Pebble will trim newlines immediately after a template tag. +> This might not be desired when using formats like YAML or Java properties files, so Blossom offers a way to disable this behaviour: +> +> Add a `trimNewlines = false` line to the template set block and stripping will be disabled for any templates in the set. + Then place a file in the `src/main/resource-templates` folder: `build-vars.properties`: