From 1f8bd3270c7f3d7aa1615812c00ad773833f75d5 Mon Sep 17 00:00:00 2001 From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com> Date: Sat, 13 May 2023 13:51:43 +0200 Subject: [PATCH] Major work on buildscript dsl; new abstract build concept. N.B.: task factories not merged yet. --- TODO.md | 3 +- api/build.gradle | 5 +- .../com/jessebrault/ssg/SiteSpec.groovy | 6 +- .../jessebrault/ssg/buildscript/Build.groovy | 37 --- .../ssg/buildscript/BuildExtension.groovy | 57 ++++ .../ssg/buildscript/BuildGraphUtil.groovy | 79 +++++ .../ssg/buildscript/BuildScriptBase.groovy | 91 +++--- .../ssg/buildscript/BuildSpec.groovy | 37 +++ .../ssg/buildscript/BuildSpecUtil.groovy | 91 ++++++ .../ssg/buildscript/OutputDirFunctions.groovy | 5 + .../SimpleBuildScriptRunner.groovy | 5 +- .../ssg/buildscript/SourceProviders.groovy | 11 + .../ssg/buildscript/TypesContainer.groovy | 27 +- .../delegates/BuildDelegate.groovy | 287 +++++++++++++++++ .../{dsl => delegates}/GlobalsDelegate.groovy | 2 +- .../delegates/SiteSpecDelegate.groovy | 38 +++ .../SourceProvidersDelegate.groovy | 6 +- .../TaskFactoriesDelegate.groovy | 35 ++- .../{dsl => delegates}/TypesDelegate.groovy | 2 +- .../buildscript/domain/MutableSiteSpec.groovy | 29 -- .../dsl/AbstractBuildDelegate.groovy | 117 ------- .../buildscript/dsl/AllBuildsDelegate.groovy | 16 - .../ssg/buildscript/dsl/BuildDelegate.groovy | 37 --- .../buildscript/dsl/SiteSpecDelegate.groovy | 20 -- .../com/jessebrault/ssg/mutable/Mutable.java | 33 ++ .../com/jessebrault/ssg/mutable/Mutables.java | 36 +++ .../ssg/mutable/SimpleMutable.java | 94 ++++++ .../ssg/property/Properties.groovy | 21 -- .../jessebrault/ssg/property/Property.groovy | 19 -- .../ssg/property/SimpleProperty.groovy | 85 ----- .../ssg/task/TaskFactorySpec.groovy | 14 +- .../com/jessebrault/ssg/util/Monoid.groovy | 16 +- .../com/jessebrault/ssg/util/Monoids.groovy | 16 + .../com/jessebrault/ssg/util/Semigroup.groovy | 10 + .../jessebrault/ssg/util/Semigroups.groovy | 13 + .../jessebrault/ssg/util/SimpleMonoid.groovy | 19 ++ .../ssg/util/SimpleSemigroup.groovy | 18 ++ .../com/jessebrault/ssg/util/Zero.groovy | 5 + .../buildscript/BuildGraphUtilTests.groovy | 98 ++++++ .../buildscript/BuildScriptBaseTests.groovy | 142 ++++----- .../ssg/buildscript/BuildTests.groovy | 66 +--- .../SimpleBuildScriptRunnerTests.groovy | 95 +++++- .../delegates/BuildDelegateTests.groovy | 297 ++++++++++++++++++ .../delegates/SiteSpecDelegateTests.groovy | 18 ++ .../TaskFactoriesDelegateTests.groovy | 20 +- .../dsl/AbstractBuildDelegateTests.groovy | 97 ------ .../dsl/AllBuildsDelegateTests.groovy | 12 - .../buildscript/dsl/BuildDelegateTests.groovy | 12 - .../ssg/mutable/SimpleMutableTests.groovy | 64 ++++ .../ssg/property/SimplePropertyTests.groovy | 24 -- .../ssg/buildscript/buildSrcTest.groovy | 2 +- .../jessebrault/ssg/buildscript/simple.groovy | 4 +- .../ssg/buildscript/testImport.groovy | 4 +- .../test/resources/oneTextAndTemplate.groovy | 10 +- buildSrc/src/main/groovy/ssg.common.gradle | 2 +- sketchingBaseBuild.groovy | 21 +- 56 files changed, 1614 insertions(+), 816 deletions(-) create mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildExtension.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildGraphUtil.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildSpec.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildSpecUtil.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/BuildDelegate.groovy rename api/src/main/groovy/com/jessebrault/ssg/buildscript/{dsl => delegates}/GlobalsDelegate.groovy (88%) create mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/SiteSpecDelegate.groovy rename api/src/main/groovy/com/jessebrault/ssg/buildscript/{dsl => delegates}/SourceProvidersDelegate.groovy (91%) rename api/src/main/groovy/com/jessebrault/ssg/buildscript/{dsl => delegates}/TaskFactoriesDelegate.groovy (52%) rename api/src/main/groovy/com/jessebrault/ssg/buildscript/{dsl => delegates}/TypesDelegate.groovy (93%) delete mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/domain/MutableSiteSpec.groovy delete mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/AbstractBuildDelegate.groovy delete mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/AllBuildsDelegate.groovy delete mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/BuildDelegate.groovy delete mode 100644 api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/SiteSpecDelegate.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/mutable/Mutable.java create mode 100644 api/src/main/groovy/com/jessebrault/ssg/mutable/Mutables.java create mode 100644 api/src/main/groovy/com/jessebrault/ssg/mutable/SimpleMutable.java delete mode 100644 api/src/main/groovy/com/jessebrault/ssg/property/Properties.groovy delete mode 100644 api/src/main/groovy/com/jessebrault/ssg/property/Property.groovy delete mode 100644 api/src/main/groovy/com/jessebrault/ssg/property/SimpleProperty.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/util/Monoids.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/util/Semigroup.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/util/Semigroups.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/util/SimpleMonoid.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/util/SimpleSemigroup.groovy create mode 100644 api/src/main/groovy/com/jessebrault/ssg/util/Zero.groovy create mode 100644 api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildGraphUtilTests.groovy create mode 100644 api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/BuildDelegateTests.groovy create mode 100644 api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/SiteSpecDelegateTests.groovy rename api/src/test/groovy/com/jessebrault/ssg/buildscript/{dsl => delegates}/TaskFactoriesDelegateTests.groovy (65%) delete mode 100644 api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/AbstractBuildDelegateTests.groovy delete mode 100644 api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/AllBuildsDelegateTests.groovy delete mode 100644 api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/BuildDelegateTests.groovy create mode 100644 api/src/test/groovy/com/jessebrault/ssg/mutable/SimpleMutableTests.groovy delete mode 100644 api/src/test/groovy/com/jessebrault/ssg/property/SimplePropertyTests.groovy diff --git a/TODO.md b/TODO.md index eeb05d3..14ec1ab 100644 --- a/TODO.md +++ b/TODO.md @@ -19,10 +19,11 @@ Here will be kept all of the various todos for this project, organized by releas - [x] Remove `lib` module. - [ ] Add a way for CLI to choose a build to do, or multiple builds, defaulting to 'default' if it exists. - [ ] Write lots of tests for buildscript dsl, etc. -- [ ] Explore `base` in buildScript dsl. +- [x] Explore `base` in buildScript dsl. - Get rid of `allBuilds` concept, and replace it with composable/concat-able builds. In the dsl we could have a notion of `abstractBuild` which can be 'extended' (i.e., on the left side of a concat operation) but not actually run (since it doesn't have a name). - `OutputDir` should be concat-able, such that the left is the *base* for the right. - `OutputDirFunctions.concat` should be concat-able as well, such that both are `BiFunction`, and the output of the left is the input of the right. + - Make the delegates as dumb as possible; no more `getResult` methods; make different classes/object handle concat'ing and getting results. ### Fix - [ ] Update CHANGELOG to reflect the gsp-dsl changes. diff --git a/api/build.gradle b/api/build.gradle index deb8ed9..dca3560 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -8,7 +8,7 @@ repositories { dependencies { // https://mvnrepository.com/artifact/org.apache.groovy/groovy-templates - implementation 'org.apache.groovy:groovy-templates:4.0.9' + implementation 'org.apache.groovy:groovy-templates:4.0.12' // https://mvnrepository.com/artifact/org.commonmark/commonmark implementation 'org.commonmark:commonmark:0.21.0' @@ -18,6 +18,9 @@ dependencies { // https://mvnrepository.com/artifact/org.jsoup/jsoup implementation 'org.jsoup:jsoup:1.16.1' + + // https://mvnrepository.com/artifact/org.jgrapht/jgrapht-core + implementation 'org.jgrapht:jgrapht-core:1.5.2' } jar { diff --git a/api/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy b/api/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy index 3b5f0e6..0e0c12b 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy @@ -1,6 +1,7 @@ package com.jessebrault.ssg import com.jessebrault.ssg.util.Monoid +import com.jessebrault.ssg.util.Monoids import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck import groovy.transform.TupleConstructor @@ -10,10 +11,7 @@ import groovy.transform.TupleConstructor @EqualsAndHashCode final class SiteSpec { - static final Monoid defaultSemiGroup = new Monoid<>( - SiteSpec::concat, - SiteSpec::getBlank - ) + static final Monoid DEFAULT_MONOID = Monoids.of(getBlank(), SiteSpec::concat) static SiteSpec getBlank() { new SiteSpec('', '') diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/Build.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/Build.groovy index 43bd3d5..fa2eb0f 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/Build.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/Build.groovy @@ -14,33 +14,6 @@ import java.util.function.Function @EqualsAndHashCode final class Build { - @TupleConstructor(defaults = false) - @NullCheck(includeGenerated = true) - @EqualsAndHashCode - static final class AllBuilds { - - static AllBuilds concat(AllBuilds ab0, AllBuilds ab1) { - new AllBuilds( - ab0.siteSpec + ab1.siteSpec, - ab0.globals + ab1.globals, - ab0.taskFactorySpecs + ab1.taskFactorySpecs - ) - } - - static AllBuilds getEmpty() { - new AllBuilds(SiteSpec.getBlank(), [:], []) - } - - final SiteSpec siteSpec - final Map globals - final Collection> taskFactorySpecs - - AllBuilds plus(AllBuilds other) { - concat(this, other) - } - - } - static Build getEmpty() { new Build( '', @@ -71,16 +44,6 @@ final class Build { ) } - static Build from(AllBuilds allBuilds) { - new Build( - '', - OutputDirFunctions.DEFAULT, - allBuilds.siteSpec, - allBuilds.globals, - allBuilds.taskFactorySpecs - ) - } - final String name final Function outputDirFunction final SiteSpec siteSpec diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildExtension.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildExtension.groovy new file mode 100644 index 0000000..1290c3d --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildExtension.groovy @@ -0,0 +1,57 @@ +package com.jessebrault.ssg.buildscript + +import groovy.transform.NullCheck +import groovy.transform.PackageScope + +@PackageScope +@NullCheck +final class BuildExtension { + + static BuildExtension getEmpty() { + new BuildExtension() + } + + static BuildExtension get(String buildName) { + new BuildExtension(buildName) + } + + private final String buildName + + private BuildExtension(String buildName) { + this.buildName = buildName + } + + private BuildExtension() { + this.buildName = null + } + + boolean isPresent() { + this.buildName != null + } + + boolean isEmpty() { + !this.present + } + + String getBuildName() { + Objects.requireNonNull(this.buildName) + } + + @Override + String toString() { + this.present ? "BuildExtension(extending: ${ this.buildName })" : "BuildExtension(empty)" + } + + @Override + int hashCode() { + Objects.hash(this.buildName) + } + + @Override + boolean equals(Object obj) { + obj.is(this) + || (obj instanceof BuildExtension && obj.present && obj.buildName == this.buildName) + || (obj instanceof BuildExtension && !obj.present && !this.present) + } + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildGraphUtil.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildGraphUtil.groovy new file mode 100644 index 0000000..dacaf39 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildGraphUtil.groovy @@ -0,0 +1,79 @@ +package com.jessebrault.ssg.buildscript + +import groovy.transform.PackageScope +import org.jgrapht.Graph +import org.jgrapht.graph.DefaultEdge +import org.jgrapht.graph.DirectedAcyclicGraph +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.slf4j.Marker +import org.slf4j.MarkerFactory + +@PackageScope +final class BuildGraphUtil { + + private static final Logger logger = LoggerFactory.getLogger(BuildGraphUtil) + private static final Marker enter = MarkerFactory.getMarker('ENTER') + private static final Marker exit = MarkerFactory.getMarker('EXIT') + + private static void addParentEdges( + Graph graph, + BuildSpec spec, + Collection allBuildSpecs + ) { + logger.trace(enter, 'graph: {}, spec: {}', graph, spec) + if (!graph.containsVertex(spec)) { + throw new IllegalStateException("given spec is not in the graph") + } + if (spec.extending.present) { + def parent = allBuildSpecs.find { it.name == spec.extending.buildName } + if (parent == null) { + throw new IllegalStateException("no such parent/extends from build: ${ spec.extending.buildName }") + } + if (!graph.containsVertex(parent)) { + graph.addVertex(parent) + } + graph.addEdge(parent, spec) + addParentEdges(graph, parent, allBuildSpecs) + } + logger.trace(exit, '') + } + + static Graph getDependencyGraph(Collection buildSpecs) { + logger.trace(enter, '') + final Graph graph = new DirectedAcyclicGraph<>(DefaultEdge) + buildSpecs.each { + if (!graph.containsVertex(it)) { + graph.addVertex(it) + } + addParentEdges(graph, it, buildSpecs) + } + logger.trace(exit, 'graph: {}', graph) + graph + } + + static Collection getAncestors(BuildSpec child, Graph graph) { + logger.trace(enter, 'child: {}, graph: {}', child, graph) + if (child.extending.isEmpty()) { + def r = [] as Collection + logger.trace(exit, 'r: {}', r) + r + } else { + // use incoming to get edges pointing to child + def incomingEdges = graph.incomingEdgesOf(child) + if (incomingEdges.size() == 0) { + throw new IllegalArgumentException("child does not have an edge to its parent") + } + if (incomingEdges.size() > 1) { + throw new IllegalArgumentException("child has more than one parent") + } + def parent = graph.getEdgeSource(incomingEdges[0]) + def r = getAncestors(parent, graph) + [parent] // needs to be 'oldest' -> 'youngest' + logger.trace(exit, 'r: {}', r) + r + } + } + + private BuildGraphUtil() {} + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptBase.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptBase.groovy index c8ff3bc..f1e4808 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptBase.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptBase.groovy @@ -1,78 +1,63 @@ package com.jessebrault.ssg.buildscript -import com.jessebrault.ssg.buildscript.Build.AllBuilds -import com.jessebrault.ssg.buildscript.dsl.AllBuildsDelegate -import com.jessebrault.ssg.buildscript.dsl.BuildDelegate +import com.jessebrault.ssg.buildscript.delegates.BuildDelegate +import groovy.transform.PackageScope import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.Marker import org.slf4j.MarkerFactory +import static java.util.Objects.requireNonNull + abstract class BuildScriptBase extends Script { - private static final Logger logger = LoggerFactory.getLogger(BuildScriptBase) - private static final Marker enter = MarkerFactory.getMarker('ENTER') - private static final Marker exit = MarkerFactory.getMarker('EXIT') + protected static final Logger logger = LoggerFactory.getLogger(BuildScriptBase) + protected static final Marker enter = MarkerFactory.getMarker('ENTER') + protected static final Marker exit = MarkerFactory.getMarker('EXIT') - private final Collection allBuildsDelegates = [] - private final Collection buildDelegates = [] + protected final Collection buildSpecs = [] - private int currentBuildNumber = 0 - - final AllBuilds defaultAllBuilds = AllBuilds.getEmpty() - - void build( + /** + * args keys: name (required), extending (optional) + * + * @param args + * @param buildClosure + */ + void abstractBuild( + Map args, @DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST) Closure buildClosure ) { - logger.trace(enter, 'buildClosure: {}', buildClosure) - this.build('build' + this.currentBuildNumber, buildClosure) - logger.trace(exit, '') + this.buildSpecs << new BuildSpec( + requireNonNull(args.name as String), + true, + args.extending != null ? BuildExtension.get(args.extending as String) : BuildExtension.getEmpty(), + buildClosure + ) } + /** + * args keys: name (required), extending (optional) + * + * @param args + * @param buildClosure + */ void build( - String name, + Map args, @DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST) Closure buildClosure ) { - logger.trace(enter, 'name: {}, buildClosure: {}', name, buildClosure) - def d = new BuildDelegate().tap { - it.name = name - } - buildClosure.setDelegate(d) - buildClosure.setResolveStrategy(Closure.DELEGATE_FIRST) - buildClosure() - this.buildDelegates << d - this.currentBuildNumber++ - logger.trace(exit, '') + this.buildSpecs << new BuildSpec( + requireNonNull(args.name as String), + false, + args.extending != null ? BuildExtension.get(args.extending as String) : BuildExtension.getEmpty(), + buildClosure + ) } - void allBuilds( - @DelegatesTo(value = AllBuildsDelegate, strategy = Closure.DELEGATE_FIRST) - Closure allBuildsClosure - ) { - logger.trace(enter, 'allBuildsClosure: {}', allBuildsClosure) - def d = new AllBuildsDelegate() - allBuildsClosure.setDelegate(d) - allBuildsClosure.setResolveStrategy(Closure.DELEGATE_FIRST) - allBuildsClosure() - this.allBuildsDelegates << d - logger.trace(exit, '') - } - - Collection getBuilds() { - logger.trace(enter, '') - def allBuilds = this.defaultAllBuilds - this.allBuildsDelegates.each { - allBuilds += it.getResult() - } - - def baseBuild = Build.from(allBuilds) - def result = this.buildDelegates.collect { - baseBuild + it.getResult() - } - logger.trace(exit, 'result: {}', result) - result + @PackageScope + Collection getBuildSpecs() { + this.buildSpecs } } diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildSpec.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildSpec.groovy new file mode 100644 index 0000000..670beb5 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildSpec.groovy @@ -0,0 +1,37 @@ +package com.jessebrault.ssg.buildscript + +import groovy.transform.EqualsAndHashCode +import groovy.transform.NullCheck +import groovy.transform.PackageScope +import groovy.transform.TupleConstructor + +@PackageScope +@TupleConstructor(defaults = false) +@NullCheck(includeGenerated = true) +@EqualsAndHashCode(excludes = 'buildClosure') +final class BuildSpec { + + static BuildSpec getEmpty() { + new BuildSpec('', false, BuildExtension.getEmpty(), { }) + } + + static BuildSpec get(Map args) { + new BuildSpec( + args.name as String ?: '', + args.isAbstract as boolean ?: false, + args.extending as BuildExtension ?: BuildExtension.getEmpty(), + args.buildClosure as Closure ?: { } + ) + } + + final String name + final boolean isAbstract + final BuildExtension extending + final Closure buildClosure + + @Override + String toString() { + "BuildSpec(name: ${ this.name }, isAbstract: ${ this.isAbstract }, extending: ${ this.extending })" + } + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildSpecUtil.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildSpecUtil.groovy new file mode 100644 index 0000000..0266cf6 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/BuildSpecUtil.groovy @@ -0,0 +1,91 @@ +package com.jessebrault.ssg.buildscript + +import com.jessebrault.ssg.SiteSpec +import com.jessebrault.ssg.buildscript.delegates.BuildDelegate +import com.jessebrault.ssg.util.Monoid +import com.jessebrault.ssg.util.Monoids +import com.jessebrault.ssg.util.Zero +import org.jgrapht.traverse.DepthFirstIterator +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.slf4j.Marker +import org.slf4j.MarkerFactory + +import java.util.function.BiFunction + +final class BuildSpecUtil { + + private static final Logger logger = LoggerFactory.getLogger(BuildSpecUtil) + private static final Marker enter = MarkerFactory.getMarker('ENTER') + private static final Marker exit = MarkerFactory.getMarker('EXIT') + + private static T reduceResults( + Collection resultsCollection, + Zero tZero, + BiFunction resultsToT + ) { + resultsCollection.inject(tZero.zero) { acc, r -> + resultsToT.apply(acc, r) + } + } + + private static Collection mapBuildSpecsToResults(Collection buildSpecs) { + buildSpecs.collect { + def delegate = new BuildDelegate() + it.buildClosure.delegate = delegate + //noinspection UnnecessaryQualifiedReference + it.buildClosure.resolveStrategy = Closure.DELEGATE_FIRST + it.buildClosure() + new BuildDelegate.Results(delegate) + } + } + + private static Monoid> getGlobalsMonoid() { + Monoids.of([:]) { m0, m1 -> + m0 + m1 + } + } + + private static Build toBuild( + Collection specs + ) { + if (specs.empty) { + throw new IllegalArgumentException('specs must contain at least one BuildSpec') + } + def allResults = mapBuildSpecsToResults(specs) + def outputDirFunctionResult = reduceResults(allResults, OutputDirFunctions.DEFAULT_MONOID) { acc, r -> + r.getOutputDirFunctionResult(acc, { OutputDirFunctions.DEFAULT_MONOID.zero }) + } + def siteSpecResult = reduceResults(allResults, SiteSpec.DEFAULT_MONOID) { acc, r -> + r.getSiteSpecResult(acc, true, SiteSpec.DEFAULT_MONOID) + } + def globalsResult = reduceResults(allResults, getGlobalsMonoid()) { acc, r -> + r.getGlobalsResult(acc, true, getGlobalsMonoid()) + } + new Build( + specs[0].name, + outputDirFunctionResult, + siteSpecResult, + globalsResult, + [] // TODO + ) + } + + static Collection getBuilds(Collection buildSpecs) { + logger.trace(enter, '') + def graph = BuildGraphUtil.getDependencyGraph(buildSpecs) + def r = new DepthFirstIterator<>(graph).findResults { + if (it.isAbstract) { + return null + } + def ancestors = BuildGraphUtil.getAncestors(it, graph) + logger.debug('ancestors of {}: {}', it, ancestors) + toBuild([*ancestors, it]) + } + logger.trace(exit, 'r: {}', r) + r + } + + private BuildSpecUtil() {} + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/OutputDirFunctions.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/OutputDirFunctions.groovy index b82f302..f6e33ba 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/OutputDirFunctions.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/OutputDirFunctions.groovy @@ -1,11 +1,16 @@ package com.jessebrault.ssg.buildscript +import com.jessebrault.ssg.util.Monoid +import com.jessebrault.ssg.util.Monoids + import java.util.function.Function final class OutputDirFunctions { static final Function DEFAULT = { Build build -> new OutputDir(build.name) } + static final Monoid> DEFAULT_MONOID = Monoids.of(DEFAULT, OutputDirFunctions::concat) + static Function concat( Function f0, Function f1 diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/SimpleBuildScriptRunner.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/SimpleBuildScriptRunner.groovy index 15c0a3c..dfe9186 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/SimpleBuildScriptRunner.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/SimpleBuildScriptRunner.groovy @@ -4,6 +4,7 @@ import groovy.transform.NullCheck import groovy.transform.stc.ClosureParams import groovy.transform.stc.SimpleType import org.codehaus.groovy.control.CompilerConfiguration +import org.jgrapht.traverse.DepthFirstIterator import java.util.function.Consumer @@ -28,7 +29,7 @@ final class SimpleBuildScriptRunner implements BuildScriptRunner { assert buildScript instanceof BuildScriptBase configureBuildScript.accept(buildScript) buildScript.run() - buildScript.getBuilds() + BuildSpecUtil.getBuilds(buildScript.buildSpecs) } @Override @@ -48,7 +49,7 @@ final class SimpleBuildScriptRunner implements BuildScriptRunner { } base.run() - base.getBuilds() + BuildSpecUtil.getBuilds(base.buildSpecs) } } diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/SourceProviders.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/SourceProviders.groovy index 45ce4a2..b3c3f92 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/SourceProviders.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/SourceProviders.groovy @@ -7,6 +7,8 @@ import com.jessebrault.ssg.provider.CollectionProvider import com.jessebrault.ssg.provider.CollectionProviders import com.jessebrault.ssg.template.Template import com.jessebrault.ssg.text.Text +import com.jessebrault.ssg.util.Monoid +import com.jessebrault.ssg.util.Monoids import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck import groovy.transform.TupleConstructor @@ -16,6 +18,8 @@ import groovy.transform.TupleConstructor @EqualsAndHashCode final class SourceProviders { + static final Monoid DEFAULT_MONOID = Monoids.of(getEmpty(), SourceProviders::concat) + static SourceProviders concat(SourceProviders sp0, SourceProviders sp1) { new SourceProviders( sp0.textsProvider + sp1.textsProvider, @@ -61,4 +65,11 @@ final class SourceProviders { concat(this, other) } + @Override + String toString() { + "SourceProviders(textsProvider: ${ this.textsProvider }, modelsProvider: ${ this.modelsProvider }, " + + "pagesProvider: ${ this.pagesProvider }, templatesProvider: ${ this.templatesProvider }, " + + "partsProvider: ${ this.partsProvider })" + } + } diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/TypesContainer.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/TypesContainer.groovy index 7392d3f..c5bd6c3 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/TypesContainer.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/TypesContainer.groovy @@ -4,6 +4,8 @@ import com.jessebrault.ssg.page.PageType import com.jessebrault.ssg.part.PartType import com.jessebrault.ssg.template.TemplateType import com.jessebrault.ssg.text.TextType +import com.jessebrault.ssg.util.Monoid +import com.jessebrault.ssg.util.Monoids import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck import groovy.transform.TupleConstructor @@ -13,10 +15,21 @@ import groovy.transform.TupleConstructor @EqualsAndHashCode final class TypesContainer { + static final Monoid DEFAULT_MONOID = Monoids.of(getEmpty(), TypesContainer::concat) + static TypesContainer getEmpty() { new TypesContainer([], [], [], []) } + static TypesContainer get(Map args) { + new TypesContainer( + args.textTypes ? args.textTypes as Collection : [], + args.pageTypes ? args.pageTypes as Collection : [], + args.templateTypes ? args.templateTypes as Collection : [], + args.partTypes ? args.partTypes as Collection : [] + ) + } + static TypesContainer concat(TypesContainer tc0, TypesContainer tc1) { new TypesContainer( tc0.textTypes + tc1.textTypes, @@ -26,13 +39,19 @@ final class TypesContainer { ) } - Collection textTypes - Collection pageTypes - Collection templateTypes - Collection partTypes + final Collection textTypes + final Collection pageTypes + final Collection templateTypes + final Collection partTypes TypesContainer plus(TypesContainer other) { concat(this, other) } + @Override + String toString() { + "TypesContainer(textTypes: ${ this.textTypes }, pageTypes: ${ this.pageTypes }, " + + "templateTypes: ${ this.templateTypes }, partTypes: ${ this.partTypes })" + } + } diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/BuildDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/BuildDelegate.groovy new file mode 100644 index 0000000..ee9a850 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/BuildDelegate.groovy @@ -0,0 +1,287 @@ +package com.jessebrault.ssg.buildscript.delegates + +import com.jessebrault.ssg.SiteSpec +import com.jessebrault.ssg.buildscript.Build +import com.jessebrault.ssg.buildscript.OutputDir +import com.jessebrault.ssg.buildscript.SourceProviders +import com.jessebrault.ssg.buildscript.TypesContainer +import com.jessebrault.ssg.mutable.Mutable +import com.jessebrault.ssg.mutable.Mutables +import com.jessebrault.ssg.task.TaskFactory +import com.jessebrault.ssg.task.TaskFactorySpec +import com.jessebrault.ssg.util.Monoid +import groovy.transform.EqualsAndHashCode +import groovy.transform.NullCheck +import groovy.transform.TupleConstructor +import groovy.transform.stc.ClosureParams +import groovy.transform.stc.FromString +import groovy.transform.stc.SimpleType + +import java.util.function.Function +import java.util.function.Supplier +import java.util.function.UnaryOperator + +@NullCheck(includeGenerated = true) +@EqualsAndHashCode(includeFields = true) +final class BuildDelegate { + + @TupleConstructor(includeFields = true, defaults = false) + @NullCheck(includeGenerated = true) + @EqualsAndHashCode(includeFields = true) + static final class Results { + + private final BuildDelegate delegate + + Function getOutputDirFunctionResult( + Function base, + Supplier> onEmpty + ) { + this.delegate.outputDirFunction.getOrElse { + this.delegate.outputDirFunctionMapper.match(onEmpty) { + it.apply(base) + } + } + } + + SiteSpec getSiteSpecResult( + SiteSpec base, + boolean onConcatWithBaseEmpty, + Monoid siteSpecMonoid + ) { + def concatWithBase = this.delegate.siteSpecConcatBase.isPresent() + ? this.delegate.siteSpecConcatBase.get() + : onConcatWithBaseEmpty + def onEmpty = { concatWithBase ? base : siteSpecMonoid.zero } + this.delegate.siteSpecClosure.match(onEmpty) { + def d = new SiteSpecDelegate(siteSpecMonoid) + it.delegate = d + //noinspection UnnecessaryQualifiedReference + it.resolveStrategy = Closure.DELEGATE_FIRST + it(base) + def r = d.getResult() + concatWithBase ? siteSpecMonoid.concat.apply(base, r) : r + } + } + + Map getGlobalsResult( + Map base, + boolean onConcatWithBaseEmpty, + Monoid> globalsMonoid + ) { + def concatWithBase = this.delegate.globalsConcatBase.isPresent() + ? this.delegate.globalsConcatBase.get() + : onConcatWithBaseEmpty + def onEmpty = { concatWithBase ? base : globalsMonoid.zero } + this.delegate.globalsClosure.match(onEmpty) { + def d = new GlobalsDelegate() + it.delegate = d + //noinspection UnnecessaryQualifiedReference + it.resolveStrategy = Closure.DELEGATE_FIRST + it(base) + def r = d.getResult() + concatWithBase ? globalsMonoid.concat.apply(base, r) : r + } + } + + TypesContainer getTypesResult( + TypesContainer base, + boolean onConcatWithBaseEmpty, + Monoid typesContainerMonoid + ) { + def concatWithBase = this.delegate.typesConcatBase.isPresent() + ? this.delegate.typesConcatBase.get() + : onConcatWithBaseEmpty + def onEmpty = { concatWithBase ? base : typesContainerMonoid.zero } + this.delegate.typesClosure.match(onEmpty) { + def d = new TypesDelegate() + it.delegate = d + //noinspection UnnecessaryQualifiedReference + it.resolveStrategy = Closure.DELEGATE_FIRST + it(base) + def r = d.getResult() + concatWithBase ? typesContainerMonoid.concat.apply(base, r) : r + } + } + + SourceProviders getSourcesResult( + SourceProviders base, + boolean onConcatWithBaseEmpty, + Monoid sourceProvidersMonoid, + TypesContainer types + ) { + def concatWithBase = this.delegate.sourcesConcatBase.isPresent() + ? this.delegate.sourcesConcatBase.get() + : onConcatWithBaseEmpty + def onEmpty = { concatWithBase ? base : sourceProvidersMonoid.zero } + this.delegate.sourcesClosure.match(onEmpty) { + def d = new SourceProvidersDelegate() + it.delegate = d + //noinspection UnnecessaryQualifiedReference + it.resolveStrategy = Closure.DELEGATE_FIRST + it(base, types) + def r = d.getResult() + concatWithBase ? sourceProvidersMonoid.concat.apply(base, r) : r + } + } + + Collection> getTaskFactoriesResult( + Collection> base, + boolean onConcatWithBaseEmpty, + Monoid>> taskFactorySpecsMonoid, + SourceProviders sources + ) { + def concatWithBase = this.delegate.taskFactoriesConcatBase.isPresent() + ? this.delegate.taskFactoriesConcatBase.get() + : onConcatWithBaseEmpty + def onEmpty = { concatWithBase ? base : taskFactorySpecsMonoid.zero } + this.delegate.taskFactoriesClosure.match(onEmpty) { + def d = new TaskFactoriesDelegate() + it.delegate = d + //noinspection UnnecessaryQualifiedReference + it.resolveStrategy = Closure.DELEGATE_FIRST + it(base, sources) + def r = d.getResult() + concatWithBase ? taskFactorySpecsMonoid.concat.apply(base, r) : r + } + } + + } + + private final Mutable> outputDirFunction = Mutables.getEmpty() + private final Mutable>> outputDirFunctionMapper = Mutables.getEmpty() + + private final Mutable siteSpecConcatBase = Mutables.getEmpty() + private final Mutable> siteSpecClosure = Mutables.getEmpty() + + private final Mutable globalsConcatBase = Mutables.getEmpty() + private final Mutable> globalsClosure = Mutables.getEmpty() + + private final Mutable typesConcatBase = Mutables.getEmpty() + private final Mutable> typesClosure = Mutables.getEmpty() + + private final Mutable sourcesConcatBase = Mutables.getEmpty() + private final Mutable> sourcesClosure = Mutables.getEmpty() + + private final Mutable taskFactoriesConcatBase = Mutables.getEmpty() + private final Mutable> taskFactoriesClosure = Mutables.getEmpty() + + void setOutputDirFunction(Function outputDirFunction) { + this.outputDirFunction.set(outputDirFunction) + } + + void setOutputDir(File file) { + this.outputDirFunction.set { new OutputDir(file) } + } + + void setOutputDir(String path) { + this.outputDirFunction.set { new OutputDir(path) } + } + + // Maps the *base* + void outputDirFunction(UnaryOperator> mapper) { + this.outputDirFunctionMapper.set(mapper) + } + + void siteSpec( + @DelegatesTo(value = SiteSpecDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.SiteSpec') + Closure siteSpecClosure + ) { + this.siteSpec(true, siteSpecClosure) + } + + void siteSpec( + boolean concatWithBase, + @DelegatesTo(value = SiteSpecDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.SiteSpec') + Closure siteSpecClosure + ) { + this.siteSpecConcatBase.set(concatWithBase) + this.siteSpecClosure.set(siteSpecClosure) + } + + void globals( + @DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = FromString, options = 'Map') + Closure globalsClosure + ) { + this.globals(true, globalsClosure) + } + + void globals( + boolean concatWithBase, + @DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = FromString, options = 'Map') + Closure globalsClosure + ) { + this.globalsConcatBase.set(concatWithBase) + this.globalsClosure.set(globalsClosure) + } + + void types( + @DelegatesTo(value = TypesDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.TypesContainer') + Closure typesClosure + ) { + this.types(true, typesClosure) + } + + void types( + boolean concatWithBase, + @DelegatesTo(value = TypesDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.TypesContainer') + Closure typesClosure + ) { + this.typesConcatBase.set(concatWithBase) + this.typesClosure.set(typesClosure) + } + + void sources( + @DelegatesTo(value = SourceProvidersDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams( + value = FromString, + options = 'com.jessebrault.ssg.buildscript.SourceProviders, com.jessebrault.ssg.buildscript.TypesContainer' + ) + Closure sourcesClosure + ) { + this.sources(true, sourcesClosure) + } + + void sources( + boolean concatWithBase, + @DelegatesTo(value = SourceProvidersDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams( + value = FromString, + options = 'com.jessebrault.ssg.buildscript.SourceProviders, com.jessebrault.ssg.buildscript.TypesContainer' + ) + Closure sourcesClosure + ) { + this.sourcesConcatBase.set(concatWithBase) + this.sourcesClosure.set(sourcesClosure) + } + + void taskFactories( + @DelegatesTo(value = TaskFactoriesDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams( + value = FromString, + options = 'java.util.Collection>, com.jessebrault.ssg.buildscript.SourceProviders' + ) + Closure taskFactoriesClosure + ) { + this.taskFactories(true, taskFactoriesClosure) + } + + void taskFactories( + boolean concatWithBase, + @DelegatesTo(value = TaskFactoriesDelegate, strategy = Closure.DELEGATE_FIRST) + @ClosureParams( + value = FromString, + options = 'java.util.Collection>, com.jessebrault.ssg.buildscript.SourceProviders' + ) + Closure taskFactoriesClosure + ) { + this.taskFactoriesConcatBase.set(concatWithBase) + this.taskFactoriesClosure.set(taskFactoriesClosure) + } + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/GlobalsDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/GlobalsDelegate.groovy similarity index 88% rename from api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/GlobalsDelegate.groovy rename to api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/GlobalsDelegate.groovy index 32041f3..e1048ab 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/GlobalsDelegate.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/GlobalsDelegate.groovy @@ -1,4 +1,4 @@ -package com.jessebrault.ssg.buildscript.dsl +package com.jessebrault.ssg.buildscript.delegates final class GlobalsDelegate { diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/SiteSpecDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/SiteSpecDelegate.groovy new file mode 100644 index 0000000..3f3ce0c --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/SiteSpecDelegate.groovy @@ -0,0 +1,38 @@ +package com.jessebrault.ssg.buildscript.delegates + +import com.jessebrault.ssg.SiteSpec +import com.jessebrault.ssg.util.Zero + +import static java.util.Objects.requireNonNull + +final class SiteSpecDelegate { + + private String name + private String baseUrl + + SiteSpecDelegate(Zero siteSpecZero) { + this.name = siteSpecZero.zero.name + this.baseUrl = siteSpecZero.zero.baseUrl + } + + String getName() { + return this.name + } + + void setName(String name) { + this.name = requireNonNull(name) + } + + String getBaseUrl() { + return this.baseUrl + } + + void setBaseUrl(String baseUrl) { + this.baseUrl = requireNonNull(baseUrl) + } + + SiteSpec getResult() { + new SiteSpec(this.name, this.baseUrl) + } + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/SourceProvidersDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/SourceProvidersDelegate.groovy similarity index 91% rename from api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/SourceProvidersDelegate.groovy rename to api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/SourceProvidersDelegate.groovy index 2568f2b..c4ab368 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/SourceProvidersDelegate.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/SourceProvidersDelegate.groovy @@ -1,4 +1,4 @@ -package com.jessebrault.ssg.buildscript.dsl +package com.jessebrault.ssg.buildscript.delegates import com.jessebrault.ssg.buildscript.SourceProviders import com.jessebrault.ssg.model.Model @@ -8,7 +8,11 @@ import com.jessebrault.ssg.provider.CollectionProvider import com.jessebrault.ssg.provider.CollectionProviders import com.jessebrault.ssg.template.Template import com.jessebrault.ssg.text.Text +import groovy.transform.EqualsAndHashCode +import groovy.transform.NullCheck +@NullCheck +@EqualsAndHashCode final class SourceProvidersDelegate { private CollectionProvider textsProvider = CollectionProviders.getEmpty() diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/TaskFactoriesDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/TaskFactoriesDelegate.groovy similarity index 52% rename from api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/TaskFactoriesDelegate.groovy rename to api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/TaskFactoriesDelegate.groovy index 41e25f7..5f68554 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/TaskFactoriesDelegate.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/TaskFactoriesDelegate.groovy @@ -1,24 +1,32 @@ -package com.jessebrault.ssg.buildscript.dsl +package com.jessebrault.ssg.buildscript.delegates import com.jessebrault.ssg.task.TaskFactory import com.jessebrault.ssg.task.TaskFactorySpec +import groovy.transform.EqualsAndHashCode +import groovy.transform.NullCheck import java.util.function.Consumer import java.util.function.Supplier +@NullCheck +@EqualsAndHashCode(includeFields = true) final class TaskFactoriesDelegate { - private final Map> specs = [:] + private final Collection> specs = [] + + private boolean isRegistered(String name) { + this.specs.find { it.name == name } != null + } private void checkNotRegistered(String name) { - if (this.specs.containsKey(name)) { + if (this.isRegistered(name)) { throw new IllegalArgumentException("a TaskFactory is already registered by the name ${ name }") } } void register(String name, Supplier factorySupplier) { this.checkNotRegistered(name) - this.specs[name] = new TaskFactorySpec<>(factorySupplier, []) + this.specs << new TaskFactorySpec<>(name, factorySupplier, []) } def void register( @@ -27,22 +35,31 @@ final class TaskFactoriesDelegate { Consumer factoryConfigurator ) { this.checkNotRegistered(name) - this.specs[name] = new TaskFactorySpec<>(factorySupplier, [factoryConfigurator]) + this.specs << new TaskFactorySpec<>(name, factorySupplier, [factoryConfigurator]) + } + + void register(TaskFactorySpec spec) { + this.specs << spec + } + + void registerAll(Collection> specs) { + this.specs.addAll(specs) } def void configure( String name, Class factoryClass, // Dummy so we get better auto-complete - Consumer factoryConfigureClosure + Consumer factoryConfigurator ) { - if (!this.specs.containsKey(name)) { + if (!this.isRegistered(name)) { throw new IllegalArgumentException("there is no TaskFactory registered by name ${ name }") } + def spec = this.specs.find { it.name == name } // Potentially dangerous, but the configurators Collection *should* only contain the correct types. - this.specs[name].configurators << (factoryConfigureClosure as Consumer) + spec.configurators << (factoryConfigurator as Consumer) } - Map getResult() { + Collection> getResult() { this.specs } diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/TypesDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/TypesDelegate.groovy similarity index 93% rename from api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/TypesDelegate.groovy rename to api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/TypesDelegate.groovy index d834181..adb4d46 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/TypesDelegate.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/buildscript/delegates/TypesDelegate.groovy @@ -1,4 +1,4 @@ -package com.jessebrault.ssg.buildscript.dsl +package com.jessebrault.ssg.buildscript.delegates import com.jessebrault.ssg.buildscript.TypesContainer import com.jessebrault.ssg.page.PageType diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/domain/MutableSiteSpec.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/domain/MutableSiteSpec.groovy deleted file mode 100644 index 8c738ad..0000000 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/domain/MutableSiteSpec.groovy +++ /dev/null @@ -1,29 +0,0 @@ -package com.jessebrault.ssg.buildscript.domain - -import com.jessebrault.ssg.property.Properties -import com.jessebrault.ssg.property.Property -import com.jessebrault.ssg.util.Monoid - -final class MutableSiteSpec { - - private static final Monoid nameAndBaseUrlMonoid = new Monoid<>( - { s0, s1 -> s1 }, - { '' } - ) - - static final Monoid MONOID = new Monoid<>( - MutableSiteSpec::concat, - MutableSiteSpec::new - ) - - private static MutableSiteSpec concat(MutableSiteSpec ms0, MutableSiteSpec ms1) { - new MutableSiteSpec().tap { - name.set(ms1.name.get()) - baseUrl.set(ms1.baseUrl.get()) - } - } - - final Property name = Properties.get(nameAndBaseUrlMonoid) - final Property baseUrl = Properties.get(nameAndBaseUrlMonoid) - -} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/AbstractBuildDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/AbstractBuildDelegate.groovy deleted file mode 100644 index 6bcdfda..0000000 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/AbstractBuildDelegate.groovy +++ /dev/null @@ -1,117 +0,0 @@ -package com.jessebrault.ssg.buildscript.dsl - -import com.jessebrault.ssg.SiteSpec -import com.jessebrault.ssg.buildscript.SourceProviders -import com.jessebrault.ssg.buildscript.TypesContainer -import com.jessebrault.ssg.task.TaskFactory -import com.jessebrault.ssg.task.TaskFactorySpec -import groovy.transform.stc.ClosureParams -import groovy.transform.stc.SimpleType - -abstract class AbstractBuildDelegate { - - private final Collection> siteSpecClosures = [] - private final Collection> globalsClosures = [] - private final Collection> typesClosures = [] - private final Collection> sourcesClosures = [] - private final Collection> taskFactoriesClosures = [] - - abstract T getResult() - - protected final SiteSpec getSiteSpecResult() { - this.siteSpecClosures.inject(SiteSpec.getBlank()) { acc, closure -> - def d = new SiteSpecDelegate() - closure.delegate = d - //noinspection UnnecessaryQualifiedReference - closure.resolveStrategy = Closure.DELEGATE_FIRST - closure() - acc + d.getResult() - } - } - - protected final Map getGlobalsResult() { - this.globalsClosures.inject([:] as Map) { acc, closure -> - def d = new GlobalsDelegate() - closure.delegate = d - //noinspection UnnecessaryQualifiedReference - closure.resolveStrategy = Closure.DELEGATE_FIRST - closure() - acc + d.getResult() - } - } - - protected final TypesContainer getTypesResult() { - this.typesClosures.inject(TypesContainer.getEmpty()) { acc, closure -> - def d = new TypesDelegate() - closure.delegate = d - //noinspection UnnecessaryQualifiedReference - closure.resolveStrategy = Closure.DELEGATE_FIRST - closure() - acc + d.getResult() - } - } - - protected final SourceProviders getSourcesResult(TypesContainer typesContainer) { - this.sourcesClosures.inject(SourceProviders.getEmpty()) { acc, closure -> - def d = new SourceProvidersDelegate() - closure.delegate = d - //noinspection UnnecessaryQualifiedReference - closure.resolveStrategy = Closure.DELEGATE_FIRST - closure(typesContainer) - acc + d.getResult() - } - } - - protected final Collection> getTaskFactoriesResult(SourceProviders sourceProviders) { - this.taskFactoriesClosures.inject([:] as Map) { acc, closure -> - def d = new TaskFactoriesDelegate() - closure.delegate = d - //noinspection UnnecessaryQualifiedReference - closure.resolveStrategy = Closure.DELEGATE_FIRST - closure(sourceProviders) - def specs = d.getResult() - specs.forEach { name, spec -> - acc.merge(name, spec) { spec0, spec1 -> spec0 + spec1 } - } - acc - }.values() - } - - void siteSpec( - @DelegatesTo(value = SiteSpecDelegate, strategy = Closure.DELEGATE_FIRST) - Closure siteSpecClosure - ) { - this.siteSpecClosures << siteSpecClosure - } - - void globals( - @DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST) - Closure globalsClosure - ) { - this.globalsClosures << globalsClosure - } - - void types( - @DelegatesTo(value = TypesDelegate, strategy = Closure.DELEGATE_FIRST) - Closure typesClosure - ) { - this.typesClosures << typesClosure - } - - void providers( - @DelegatesTo(value = SourceProvidersDelegate, strategy = Closure.DELEGATE_FIRST) - @ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.TypesContainer') - Closure providersClosure - ) { - this.sourcesClosures << providersClosure - } - - void taskFactories( - @DelegatesTo(value = TaskFactoriesDelegate, strategy = Closure.DELEGATE_FIRST) - @ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.SourceProviders') - Closure taskFactoriesClosure - ) { - this.taskFactoriesClosures << taskFactoriesClosure - } - -} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/AllBuildsDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/AllBuildsDelegate.groovy deleted file mode 100644 index f1b0623..0000000 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/AllBuildsDelegate.groovy +++ /dev/null @@ -1,16 +0,0 @@ -package com.jessebrault.ssg.buildscript.dsl - -import com.jessebrault.ssg.buildscript.Build - -final class AllBuildsDelegate extends AbstractBuildDelegate { - - @Override - Build.AllBuilds getResult() { - new Build.AllBuilds( - this.getSiteSpecResult(), - this.getGlobalsResult(), - this.getTaskFactoriesResult(this.getSourcesResult(this.getTypesResult())) - ) - } - -} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/BuildDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/BuildDelegate.groovy deleted file mode 100644 index 7e38823..0000000 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/BuildDelegate.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package com.jessebrault.ssg.buildscript.dsl - -import com.jessebrault.ssg.buildscript.Build -import com.jessebrault.ssg.buildscript.OutputDir -import com.jessebrault.ssg.buildscript.OutputDirFunctions - -import java.util.function.Function - -final class BuildDelegate extends AbstractBuildDelegate { - - String name = '' - private Function outputDirFunction = OutputDirFunctions.DEFAULT - - @Override - Build getResult() { - new Build( - this.name, - this.outputDirFunction, - this.getSiteSpecResult(), - this.getGlobalsResult(), - this.getTaskFactoriesResult(this.getSourcesResult(this.getTypesResult())) - ) - } - - void setOutputDirFunction(Function outputDirFunction) { - this.outputDirFunction = outputDirFunction - } - - void setOutputDir(File file) { - this.outputDirFunction = OutputDirFunctions.of(file) - } - - void setOutputDir(String path) { - this.outputDirFunction = OutputDirFunctions.of(path) - } - -} diff --git a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/SiteSpecDelegate.groovy b/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/SiteSpecDelegate.groovy deleted file mode 100644 index 6fe2f2c..0000000 --- a/api/src/main/groovy/com/jessebrault/ssg/buildscript/dsl/SiteSpecDelegate.groovy +++ /dev/null @@ -1,20 +0,0 @@ -package com.jessebrault.ssg.buildscript.dsl - -import com.jessebrault.ssg.SiteSpec - -final class SiteSpecDelegate { - - String name - String baseUrl - - SiteSpecDelegate() { - def blank = SiteSpec.getBlank() - this.name = blank.name - this.baseUrl = blank.baseUrl - } - - SiteSpec getResult() { - new SiteSpec(this.name, this.baseUrl) - } - -} diff --git a/api/src/main/groovy/com/jessebrault/ssg/mutable/Mutable.java b/api/src/main/groovy/com/jessebrault/ssg/mutable/Mutable.java new file mode 100644 index 0000000..f029a24 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/mutable/Mutable.java @@ -0,0 +1,33 @@ +package com.jessebrault.ssg.mutable; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.Optional; +import java.util.function.*; + +@ApiStatus.Experimental +public interface Mutable { + T get(); + void set(T t); + void unset(); + + boolean isPresent(); + + default boolean isEmpty() { + return !this.isPresent(); + } + + void filterInPlace(Predicate filter); + void mapInPlace(UnaryOperator mapper); + void zipInPlace(Mutable other, Supplier onEmpty, Supplier onOtherEmpty, BiFunction zipper); + + U match(Supplier onEmpty, Function onPresentMapper); + T getOrElse(Supplier onEmpty); + + Mutable chain(Function> mapper); + Mutable map(Function mapper); + + Mutable zip(Mutable other, BiFunction zipper); + + Optional asOptional(); +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/mutable/Mutables.java b/api/src/main/groovy/com/jessebrault/ssg/mutable/Mutables.java new file mode 100644 index 0000000..8d71b25 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/mutable/Mutables.java @@ -0,0 +1,36 @@ +package com.jessebrault.ssg.mutable; + +import com.jessebrault.ssg.util.Monoid; +import com.jessebrault.ssg.util.Monoids; +import com.jessebrault.ssg.util.Semigroup; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +@ApiStatus.Experimental +public final class Mutables { + + public static Monoid> getMonoid(final Semigroup tSemigroup) { + return Monoids.of(Mutables.getEmpty(), (m0, m1) -> { + if (m0.isPresent() && m1.isPresent()) { + return get(tSemigroup.getConcat().apply(m0.get(), m1.get())); + } else if (m0.isPresent()) { + return m0; + } else if (m1.isPresent()) { + return m1; + } else { + return getEmpty(); + } + }); + } + + public static @NotNull Mutable getEmpty() { + return new SimpleMutable<>(); + } + + public static Mutable get(T initialValue) { + return new SimpleMutable<>(initialValue); + } + + private Mutables() {} + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/mutable/SimpleMutable.java b/api/src/main/groovy/com/jessebrault/ssg/mutable/SimpleMutable.java new file mode 100644 index 0000000..e444054 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/mutable/SimpleMutable.java @@ -0,0 +1,94 @@ +package com.jessebrault.ssg.mutable; + +import org.jetbrains.annotations.ApiStatus; + +import java.util.Optional; +import java.util.function.*; + +@ApiStatus.Experimental +final class SimpleMutable implements Mutable { + + private T t; + + public SimpleMutable(T initialValue) { + this.t = initialValue; + } + + public SimpleMutable() {} + + @Override + public T get() { + if (this.t == null) { + throw new NullPointerException(); + } + return this.t; + } + + @Override + public void set(T t) { + this.t = t; + } + + @Override + public void unset() { + this.t = null; + } + + @Override + public boolean isPresent() { + return this.t != null; + } + + @Override + public void filterInPlace(Predicate filter) { + if (this.t != null && !filter.test(this.t)) { + this.unset(); + } + } + + @Override + public void mapInPlace(UnaryOperator mapper) { + this.t = mapper.apply(this.t); + } + + @Override + public void zipInPlace(Mutable other, Supplier onEmpty, Supplier onOtherEmpty, BiFunction zipper) { + this.t = zipper.apply( + this.isPresent() ? this.t : onEmpty.get(), + other.isPresent() ? other.get() : onOtherEmpty.get() + ); + } + + @Override + public U match(Supplier onEmpty, Function onPresentMapper) { + return this.t != null ? onPresentMapper.apply(this.t) : onEmpty.get(); + } + + @Override + public T getOrElse(Supplier other) { + return this.t != null ? this.t : other.get(); + } + + @Override + public Mutable chain(Function> mapper) { + return this.map(mapper).get(); + } + + @Override + public Mutable map(Function mapper) { + return this.t != null ? Mutables.get(mapper.apply(this.t)) : Mutables.getEmpty(); + } + + @Override + public Mutable zip(Mutable other, BiFunction zipper) { + return this.isPresent() && other.isPresent() + ? Mutables.get(zipper.apply(this.get(), other.get())) + : Mutables.getEmpty(); + } + + @Override + public Optional asOptional() { + return Optional.ofNullable(this.t); + } + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/property/Properties.groovy b/api/src/main/groovy/com/jessebrault/ssg/property/Properties.groovy deleted file mode 100644 index fb95e30..0000000 --- a/api/src/main/groovy/com/jessebrault/ssg/property/Properties.groovy +++ /dev/null @@ -1,21 +0,0 @@ -package com.jessebrault.ssg.property - -import com.jessebrault.ssg.util.Monoid - -final class Properties { - - static Property get(Monoid semiGroup) { - new SimpleProperty<>(semiGroup) - } - - static Property get(Monoid semiGroup, T convention) { - new SimpleProperty<>(semiGroup, convention) - } - - static Property get(Monoid semiGroup, T convention, T t) { - new SimpleProperty<>(semiGroup, convention, t) - } - - private Properties() {} - -} diff --git a/api/src/main/groovy/com/jessebrault/ssg/property/Property.groovy b/api/src/main/groovy/com/jessebrault/ssg/property/Property.groovy deleted file mode 100644 index 654ae78..0000000 --- a/api/src/main/groovy/com/jessebrault/ssg/property/Property.groovy +++ /dev/null @@ -1,19 +0,0 @@ -package com.jessebrault.ssg.property - -import java.util.function.UnaryOperator - -interface Property { - T get() - void set(T t) - void unset() - - T getConvention() - void setConvention(T t) - void unsetConvention() - - void map(UnaryOperator mapper) - void merge( - @DelegatesTo(type = 'T', strategy = Closure.DELEGATE_FIRST) - Closure configurator - ) -} \ No newline at end of file diff --git a/api/src/main/groovy/com/jessebrault/ssg/property/SimpleProperty.groovy b/api/src/main/groovy/com/jessebrault/ssg/property/SimpleProperty.groovy deleted file mode 100644 index 792daeb..0000000 --- a/api/src/main/groovy/com/jessebrault/ssg/property/SimpleProperty.groovy +++ /dev/null @@ -1,85 +0,0 @@ -package com.jessebrault.ssg.property - -import com.jessebrault.ssg.util.Monoid -import groovy.transform.EqualsAndHashCode -import groovy.transform.NullCheck -import groovy.transform.PackageScope - -import java.util.function.UnaryOperator - -import static java.util.Objects.requireNonNull - -@PackageScope -@NullCheck(includeGenerated = true) -@EqualsAndHashCode(includeFields = true) -final class SimpleProperty implements Property { - - private final Monoid monoid - - private T t - private T convention - - SimpleProperty(Monoid monoid) { - this.monoid = monoid - this.convention = this.monoid.empty.get() - } - - SimpleProperty(Monoid monoid, T convention) { - this.monoid = monoid - this.convention = convention - } - - SimpleProperty(Monoid monoid, T convention, T t) { - this.monoid = monoid - this.t = t - this.convention = convention - } - - @Override - T get() { - this.t != null ? this.t : requireNonNull(this.convention) - } - - @Override - void set(T t) { - this.t = t - } - - @Override - void unset() { - this.t = null - } - - @Override - T getConvention() { - requireNonNull(this.convention) - } - - @Override - void setConvention(T t) { - this.convention = t - } - - @Override - void unsetConvention() { - this.t = null - } - - @Override - void map(UnaryOperator mapper) { - this.t = requireNonNull(mapper.apply(this.t)) - } - - @Override - void merge( - @DelegatesTo(type = 'T', strategy = Closure.DELEGATE_FIRST) - Closure configurator - ) { - def d = requireNonNull(this.monoid.empty.get()) - configurator.delegate = d - configurator.resolveStrategy = Closure.DELEGATE_FIRST - configurator() - this.t = requireNonNull(this.monoid.concat.apply(this.t != null ? this.t : this.monoid.empty.get(), d)) - } - -} diff --git a/api/src/main/groovy/com/jessebrault/ssg/task/TaskFactorySpec.groovy b/api/src/main/groovy/com/jessebrault/ssg/task/TaskFactorySpec.groovy index d765f1d..6d16047 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/task/TaskFactorySpec.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/task/TaskFactorySpec.groovy @@ -1,25 +1,35 @@ package com.jessebrault.ssg.task +import com.jessebrault.ssg.util.Semigroup +import com.jessebrault.ssg.util.Semigroups import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck import groovy.transform.TupleConstructor -import java.util.function.BiFunction import java.util.function.Consumer import java.util.function.Supplier +/** + * TODO: Make this deeply immutable + */ @TupleConstructor(defaults = false) @NullCheck(includeGenerated = true) @EqualsAndHashCode final class TaskFactorySpec { + static Semigroup> DEFAULT_SEMIGROUP = Semigroups.of(TaskFactorySpec::concat) + static TaskFactorySpec concat(TaskFactorySpec spec0, TaskFactorySpec spec1) { + if (spec0.name != spec1.name) { + throw new IllegalArgumentException('names must be equal!') + } if (spec0.supplier != spec1.supplier) { throw new IllegalArgumentException("suppliers must be equal!") } - new TaskFactorySpec(spec0.supplier, spec0.configurators + spec1.configurators) + new TaskFactorySpec(spec0.name, spec0.supplier, spec0.configurators + spec1.configurators) } + final String name final Supplier supplier final Collection> configurators diff --git a/api/src/main/groovy/com/jessebrault/ssg/util/Monoid.groovy b/api/src/main/groovy/com/jessebrault/ssg/util/Monoid.groovy index 71becf2..7998467 100644 --- a/api/src/main/groovy/com/jessebrault/ssg/util/Monoid.groovy +++ b/api/src/main/groovy/com/jessebrault/ssg/util/Monoid.groovy @@ -1,16 +1,6 @@ package com.jessebrault.ssg.util -import groovy.transform.EqualsAndHashCode -import groovy.transform.NullCheck -import groovy.transform.TupleConstructor +import org.jetbrains.annotations.ApiStatus -import java.util.function.BinaryOperator -import java.util.function.Supplier - -@TupleConstructor(defaults = false) -@NullCheck(includeGenerated = true) -@EqualsAndHashCode -final class Monoid { - final BinaryOperator concat - final Supplier empty -} +@ApiStatus.Experimental +interface Monoid extends Semigroup, Zero {} diff --git a/api/src/main/groovy/com/jessebrault/ssg/util/Monoids.groovy b/api/src/main/groovy/com/jessebrault/ssg/util/Monoids.groovy new file mode 100644 index 0000000..5a52d32 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/util/Monoids.groovy @@ -0,0 +1,16 @@ +package com.jessebrault.ssg.util + +import org.jetbrains.annotations.ApiStatus + +import java.util.function.BinaryOperator + +@ApiStatus.Experimental +final class Monoids { + + static Monoid of(T zero, BinaryOperator concat) { + new SimpleMonoid<>(zero, concat) + } + + private Monoids() {} + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/util/Semigroup.groovy b/api/src/main/groovy/com/jessebrault/ssg/util/Semigroup.groovy new file mode 100644 index 0000000..3e46d2f --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/util/Semigroup.groovy @@ -0,0 +1,10 @@ +package com.jessebrault.ssg.util + +import org.jetbrains.annotations.ApiStatus + +import java.util.function.BinaryOperator + +@ApiStatus.Experimental +interface Semigroup { + BinaryOperator getConcat() +} \ No newline at end of file diff --git a/api/src/main/groovy/com/jessebrault/ssg/util/Semigroups.groovy b/api/src/main/groovy/com/jessebrault/ssg/util/Semigroups.groovy new file mode 100644 index 0000000..ee1c2a9 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/util/Semigroups.groovy @@ -0,0 +1,13 @@ +package com.jessebrault.ssg.util + +import java.util.function.BinaryOperator + +final class Semigroups { + + static Semigroup of(BinaryOperator concat) { + new SimpleSemigroup<>(concat) + } + + private Semigroups() {} + +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/util/SimpleMonoid.groovy b/api/src/main/groovy/com/jessebrault/ssg/util/SimpleMonoid.groovy new file mode 100644 index 0000000..869042d --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/util/SimpleMonoid.groovy @@ -0,0 +1,19 @@ +package com.jessebrault.ssg.util + +import groovy.transform.EqualsAndHashCode +import groovy.transform.NullCheck +import groovy.transform.PackageScope +import groovy.transform.TupleConstructor +import org.jetbrains.annotations.ApiStatus + +import java.util.function.BinaryOperator + +@ApiStatus.Experimental +@PackageScope +@TupleConstructor(defaults = false) +@NullCheck(includeGenerated = true) +@EqualsAndHashCode +final class SimpleMonoid implements Monoid { + final T zero + final BinaryOperator concat +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/util/SimpleSemigroup.groovy b/api/src/main/groovy/com/jessebrault/ssg/util/SimpleSemigroup.groovy new file mode 100644 index 0000000..62a5719 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/util/SimpleSemigroup.groovy @@ -0,0 +1,18 @@ +package com.jessebrault.ssg.util + +import groovy.transform.EqualsAndHashCode +import groovy.transform.NullCheck +import groovy.transform.PackageScope +import groovy.transform.TupleConstructor +import org.jetbrains.annotations.ApiStatus + +import java.util.function.BinaryOperator + +@ApiStatus.Experimental +@PackageScope +@TupleConstructor(defaults = false) +@NullCheck(includeGenerated = true) +@EqualsAndHashCode +final class SimpleSemigroup implements Semigroup { + final BinaryOperator concat +} diff --git a/api/src/main/groovy/com/jessebrault/ssg/util/Zero.groovy b/api/src/main/groovy/com/jessebrault/ssg/util/Zero.groovy new file mode 100644 index 0000000..c4d61e5 --- /dev/null +++ b/api/src/main/groovy/com/jessebrault/ssg/util/Zero.groovy @@ -0,0 +1,5 @@ +package com.jessebrault.ssg.util + +interface Zero { + T getZero() +} \ No newline at end of file diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildGraphUtilTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildGraphUtilTests.groovy new file mode 100644 index 0000000..2edb4e2 --- /dev/null +++ b/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildGraphUtilTests.groovy @@ -0,0 +1,98 @@ +package com.jessebrault.ssg.buildscript + +import groovy.transform.PackageScope +import org.junit.jupiter.api.Test + +import static com.jessebrault.ssg.buildscript.BuildGraphUtil.getAncestors +import static com.jessebrault.ssg.buildscript.BuildGraphUtil.getDependencyGraph +import static org.junit.jupiter.api.Assertions.* + +@PackageScope +final class BuildGraphUtilTests { + + @Test + void oneBuildGraph() { + def spec = BuildSpec.getEmpty() + def graph = getDependencyGraph([spec]) + assertTrue(graph.containsVertex(spec)) + assertEquals(0, graph.edgesOf(spec).size()) + } + + @Test + void twoBuildsNotConnectedGraph() { + def spec0 = BuildSpec.getEmpty() + def spec1 = BuildSpec.getEmpty() + def graph = getDependencyGraph([spec0, spec1]) + [spec0, spec1].each { + assertTrue(graph.containsVertex(it)) + assertEquals(0, graph.edgesOf(it).size()) + } + } + + @Test + void twoBuildsParentAndChildGraph() { + def child = BuildSpec.get(name: 'child', extending: BuildExtension.get('parent')) + def parent = BuildSpec.get(name: 'parent') + def graph = getDependencyGraph([child, parent]) + [child, parent].each { + assertTrue(graph.containsVertex(it)) + } + assertEquals(1, graph.edgeSet().size()) + assertTrue(graph.containsEdge(parent, child)) + } + + @Test + void threeBuildPyramidGraph() { + def left = BuildSpec.get(name: 'left', extending: BuildExtension.get('parent')) + def right = BuildSpec.get(name: 'right', extending: BuildExtension.get('parent')) + def parent = BuildSpec.get(name: 'parent') + def graph = getDependencyGraph([left, right, parent]) + [left, right, parent].each { + assertTrue(graph.containsVertex(it)) + } + assertEquals(2, graph.edgeSet().size()) + assertTrue(graph.containsEdge(parent, left)) + assertTrue(graph.containsEdge(parent, right)) + } + + @Test + void noAncestors() { + def spec = BuildSpec.getEmpty() + def graph = getDependencyGraph([spec]) + def ancestors = getAncestors(spec, graph) + assertEquals(0, ancestors.size()) + } + + @Test + void oneAncestor() { + def child = BuildSpec.get(name: 'child', extending: BuildExtension.get('parent')) + def parent = BuildSpec.get(name: 'parent') + def graph = getDependencyGraph([child, parent]) + def ancestors = getAncestors(child, graph) + assertEquals(1, ancestors.size()) + assertIterableEquals([parent], ancestors) + } + + @Test + void pyramidNotConfusedBySibling() { + def child = BuildSpec.get(name: 'child', extending: BuildExtension.get('parent')) + def sibling = BuildSpec.get(name: 'sibling', extending: BuildExtension.get('parent')) + def parent = BuildSpec.get(name: 'parent') + def graph = getDependencyGraph([child, sibling, parent]) + def ancestorsOfChild = getAncestors(child, graph) + assertEquals(1, ancestorsOfChild.size()) + assertIterableEquals([parent], ancestorsOfChild) + } + + @Test + void threeGenerationAncestors() { + def child = BuildSpec.get(name: 'child', extending: BuildExtension.get('parent')) + def parent = BuildSpec.get(name: 'parent', extending: BuildExtension.get('grandparent')) + def grandparent = BuildSpec.get(name: 'grandparent') + def graph = getDependencyGraph([child, parent, grandparent]) + def ancestorsOfChild = getAncestors(child, graph) + assertEquals(2, ancestorsOfChild.size()) + assertIterableEquals([grandparent, parent], ancestorsOfChild) + } + +} diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildScriptBaseTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildScriptBaseTests.groovy index b5cd630..50e5d88 100644 --- a/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildScriptBaseTests.groovy +++ b/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildScriptBaseTests.groovy @@ -13,11 +13,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals @ExtendWith(MockitoExtension) final class BuildScriptBaseTests { - private static Collection scriptToBuilds( + private static Collection scriptToBuildSpecs( @DelegatesTo(value = BuildScriptBase, strategy = Closure.DELEGATE_FIRST) Closure script ) { - def s = new BuildScriptBase() { + def base = new BuildScriptBase() { @Override Object run() { @@ -27,14 +27,22 @@ final class BuildScriptBaseTests { } } - s.run() - s.getBuilds() + base.run() + base.buildSpecs + } + + @Deprecated + private static Collection scriptToBuilds( + @DelegatesTo(value = BuildScriptBase, strategy = Closure.DELEGATE_FIRST) + Closure script + ) { + BuildSpecUtil.getBuilds(scriptToBuildSpecs(script)) } @Test void oneBuildWithName0() { - def r = scriptToBuilds { - build('test') { } + def r = scriptToBuildSpecs { + build(name: 'test') { } } assertEquals(1, r.size()) def b0 = r[0] @@ -43,10 +51,8 @@ final class BuildScriptBaseTests { @Test void oneBuildWithName1() { - def r = scriptToBuilds { - build { - name = 'test' - } + def r = scriptToBuildSpecs { + build(name: 'test') { } } assertEquals(1, r.size()) def b0 = r[0] @@ -54,90 +60,64 @@ final class BuildScriptBaseTests { } @Test - void oneBuildOutputDirWithFunction(@Mock Function mockOutputDirFunction) { - def r = scriptToBuilds { - build { - outputDirFunction = mockOutputDirFunction - } + void twoBuildsNotRelated() { + def r = scriptToBuildSpecs { + build(name: 'b0') { } + build(name: 'b1') { } } - assertEquals(1, r.size()) - def b0 = r[0] - assertEquals(mockOutputDirFunction, b0.outputDirFunction) + assertEquals(2, r.size()) } @Test - void oneBuildOutputDirWithFile() { - def f = new File('test') - def r = scriptToBuilds { - build { - outputDir = f - } + void childParentBuild() { + def r = scriptToBuildSpecs { + build(name: 'child', extending: 'parent') { } + build(name: 'parent') { } } - assertEquals(1, r.size()) - def b0 = r[0] - assertEquals(f, b0.outputDirFunction.apply(b0) as File) + assertEquals(2, r.size()) + assertEquals( + [ + BuildSpec.get(name: 'child', extending: BuildExtension.get('parent')), + BuildSpec.get(name: 'parent') + ], + r + ) } @Test - void oneBuildOutputDirWithString() { - def r = scriptToBuilds { - build { - outputDir = 'test' - } + void threeGenerations() { + def r = scriptToBuildSpecs { + build(name: 'child', extending: 'parent') { } + build(name: 'parent', extending: 'grandparent') { } + build(name: 'grandparent') { } } - assertEquals(1, r.size()) - def b0 = r[0] - assertEquals('test', b0.outputDirFunction.apply(b0) as String) + assertEquals(3, r.size()) + assertEquals( + [ + BuildSpec.get(name: 'child', extending: BuildExtension.get('parent')), + BuildSpec.get(name: 'parent', extending: BuildExtension.get('grandparent')), + BuildSpec.get(name: 'grandparent') + ], + r + ) } @Test - void oneBuildSiteSpec() { - def r = scriptToBuilds { - build { - siteSpec { - name = 'testSite' - baseUrl = 'https://testsite.com' - } - } + void siblingsAndParent() { + def r = scriptToBuildSpecs { + build(name: 'child0', extending: 'parent') { } + build(name: 'child1', extending: 'parent') { } + build(name: 'parent') { } } - assertEquals(1, r.size()) - def b0 = r[0] - assertEquals(new SiteSpec('testSite', 'https://testsite.com'), b0.siteSpec) - } - - @Test - void allBuildsProvidesSiteSpec() { - def r = scriptToBuilds { - allBuilds { - siteSpec { - name = 'testSite' - baseUrl = 'https://testsite.com' - } - } - build('test') { } - } - assertEquals(1, r.size()) - def b0 = r[0] - assertEquals(new SiteSpec('testSite', 'https://testsite.com'), b0.siteSpec) - } - - @Test - void allBuildsSiteSpecOverwritten() { - def r = scriptToBuilds { - allBuilds { - siteSpec { - name = 'no' - } - } - build { - siteSpec { - name = 'yes' - } - } - } - assertEquals(1, r.size()) - def b0 = r[0] - assertEquals(new SiteSpec('yes', ''), b0.siteSpec) + assertEquals(3, r.size()) + assertEquals( + [ + BuildSpec.get(name: 'child0', extending: BuildExtension.get('parent')), + BuildSpec.get(name: 'child1', extending: BuildExtension.get('parent')), + BuildSpec.get(name: 'parent') + ], + r + ) } } diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildTests.groovy index a114e54..f2b95cd 100644 --- a/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildTests.groovy +++ b/api/src/test/groovy/com/jessebrault/ssg/buildscript/BuildTests.groovy @@ -16,68 +16,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals @ExtendWith(MockitoExtension) final class BuildTests { - @ExtendWith(MockitoExtension) - static final class AllBuildsTests { - - @Test - void twoEmptiesEqual() { - def ab0 = Build.AllBuilds.getEmpty() - def ab1 = Build.AllBuilds.getEmpty() - assertEquals(ab0, ab1) - } - - @Test - void siteSpecsAdded() { - def ab0 = new Build.AllBuilds( - new SiteSpec('test', ''), - [:], - [] - ) - def ab1 = new Build.AllBuilds( - new SiteSpec('', 'test'), - [:], - [] - ) - def sum = ab0 + ab1 - assertEquals(new SiteSpec('test', 'test'), sum.siteSpec) - } - - @Test - void globalsAdded() { - def ab0 = new Build.AllBuilds( - SiteSpec.getBlank(), - [a: 0], - [] - ) - def ab1 = new Build.AllBuilds( - SiteSpec.getBlank(), - [b: 1], - [] - ) - def sum = ab0 + ab1 - assertEquals([a: 0, b: 1], sum.globals) - } - - @Test - void taskFactorySpecsAdded(@Mock Supplier taskFactorySupplier) { - def spec0 = new TaskFactorySpec<>(taskFactorySupplier, []) - def spec1 = new TaskFactorySpec<>(taskFactorySupplier, []) - def ab0 = new Build.AllBuilds( - SiteSpec.getBlank(), - [:], - [spec0] - ) - def ab1 = new Build.AllBuilds( - SiteSpec.getBlank(), - [:], - [spec1] - ) - def sum = ab0 + ab1 - assertEquals([spec0, spec1], sum.taskFactorySpecs) - } - - } - @Test void twoEmptiesEqual() { def b0 = Build.getEmpty() @@ -131,8 +69,8 @@ final class BuildTests { @Test void taskFactorySpecsAdded(@Mock Supplier taskFactorySupplier) { - def spec0 = new TaskFactorySpec<>(taskFactorySupplier, []) - def spec1 = new TaskFactorySpec<>(taskFactorySupplier, []) + def spec0 = new TaskFactorySpec<>('spec0', taskFactorySupplier, []) + def spec1 = new TaskFactorySpec<>('spec1', taskFactorySupplier, []) def b0 = Build.get(taskFactorySpecs: [spec0]) def b1 = Build.get(taskFactorySpecs: [spec1]) def sum = b0 + b1 diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/SimpleBuildScriptRunnerTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/SimpleBuildScriptRunnerTests.groovy index ecb7b4a..49882df 100644 --- a/api/src/test/groovy/com/jessebrault/ssg/buildscript/SimpleBuildScriptRunnerTests.groovy +++ b/api/src/test/groovy/com/jessebrault/ssg/buildscript/SimpleBuildScriptRunnerTests.groovy @@ -1,5 +1,6 @@ package com.jessebrault.ssg.buildscript +import com.jessebrault.ssg.SiteSpec import groovy.transform.NullCheck import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -7,6 +8,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import java.util.function.Consumer +import java.util.function.Function import static org.junit.jupiter.api.Assertions.assertEquals import static org.mockito.Mockito.verify @@ -15,6 +17,8 @@ import static org.mockito.Mockito.verify @ExtendWith(MockitoExtension) final class SimpleBuildScriptRunnerTests { + private final BuildScriptRunner runner = new SimpleBuildScriptRunner() + /** * Must be non-static, otherwise Groovy gets confused inside the Closures. * @@ -46,8 +50,7 @@ final class SimpleBuildScriptRunnerTests { @Test void simpleScript() { def baseDir = this.setupScripts(['simple.groovy']) - def runner = new SimpleBuildScriptRunner() - def builds = runner.runBuildScript('simple.groovy', baseDir.toURI().toURL()) + def builds = this.runner.runBuildScript('simple.groovy', baseDir.toURI().toURL()) assertEquals(1, builds.size()) assertEquals('test', builds[0].name) } @@ -55,8 +58,7 @@ final class SimpleBuildScriptRunnerTests { @Test void testImport() { def baseDir = this.setupScripts(['testImport.groovy', 'TestHtmlTask.groovy']) - def runner = new SimpleBuildScriptRunner() - def builds = runner.runBuildScript('testImport.groovy', baseDir.toURI().toURL()) + def builds = this.runner.runBuildScript('testImport.groovy', baseDir.toURI().toURL()) assertEquals(1, builds.size()) assertEquals('test', builds[0].name) } @@ -74,8 +76,7 @@ final class SimpleBuildScriptRunnerTests { } } } - def runner = new SimpleBuildScriptRunner() - def builds = runner.runBuildScript( + def builds = this.runner.runBuildScript( 'buildSrcTest.groovy', baseDir.toURI().toURL(), [new File(baseDir, 'buildSrc').toURI().toURL()] @@ -87,8 +88,7 @@ final class SimpleBuildScriptRunnerTests { @Test void withBinding(@Mock Consumer stringConsumer) { def baseDir = this.setupScripts(['withBinding.groovy']) - def runner = new SimpleBuildScriptRunner() - runner.runBuildScript( + this.runner.runBuildScript( 'withBinding.groovy', baseDir.toURI().toURL(), [], @@ -99,12 +99,85 @@ final class SimpleBuildScriptRunnerTests { @Test void customScript() { - def runner = new SimpleBuildScriptRunner() - def result = runner.runBuildScript { - build('test') { } + def result = this.runner.runBuildScript { + build(name: 'test') { } } assertEquals(1, result.size()) assertEquals('test', result[0].name) } + @Test + void oneBuildOutputDirWithFunction(@Mock Function mockOutputDirFunction) { + def r = this.runner.runBuildScript { + build(name: 'test') { + outputDirFunction = mockOutputDirFunction + } + } + assertEquals(1, r.size()) + def b0 = r[0] + assertEquals(mockOutputDirFunction, b0.outputDirFunction) + } + + @Test + void oneBuildOutputDirWithFile() { + def f = new File('test') + def r = this.runner.runBuildScript { + build(name: 'test') { + outputDir = f + } + } + assertEquals(1, r.size()) + def b0 = r[0] + assertEquals(f, b0.outputDirFunction.apply(b0) as File) + } + + @Test + void oneBuildOutputDirWithString() { + def r = this.runner.runBuildScript { + build(name: 'test') { + outputDir = 'test' + } + } + assertEquals(1, r.size()) + def b0 = r[0] + assertEquals('test', b0.outputDirFunction.apply(b0) as String) + } + + @Test + void oneBuildSiteSpec() { + def r = this.runner.runBuildScript { + build(name: 'test') { + siteSpec { + name = 'testSite' + baseUrl = 'https://testsite.com' + } + } + } + assertEquals(1, r.size()) + def b0 = r[0] + assertEquals(new SiteSpec('testSite', 'https://testsite.com'), b0.siteSpec) + } + + @Test + void oneBuildWithAbstractParent() { + def r = this.runner.runBuildScript { + abstractBuild(name: 'parent') { + siteSpec { + name = 'Test Site' + baseUrl = 'https://test.com' + } + } + + build(name: 'child', extending: 'parent') { + siteSpec { base -> + baseUrl = base.baseUrl + '/child' + } + } + } + + assertEquals(1, r.size()) + def b0 = r[0] + assertEquals(new SiteSpec('Test Site', 'https://test.com/child'), b0.siteSpec) + } + } diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/BuildDelegateTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/BuildDelegateTests.groovy new file mode 100644 index 0000000..d3e9e63 --- /dev/null +++ b/api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/BuildDelegateTests.groovy @@ -0,0 +1,297 @@ +package com.jessebrault.ssg.buildscript.delegates + +import com.jessebrault.ssg.SiteSpec +import com.jessebrault.ssg.buildscript.Build +import com.jessebrault.ssg.buildscript.OutputDir +import com.jessebrault.ssg.buildscript.SourceProviders +import com.jessebrault.ssg.buildscript.TypesContainer +import com.jessebrault.ssg.provider.CollectionProvider +import com.jessebrault.ssg.provider.CollectionProviders +import com.jessebrault.ssg.task.TaskFactory +import com.jessebrault.ssg.task.TaskFactorySpec +import com.jessebrault.ssg.text.Text +import com.jessebrault.ssg.text.TextTypes +import com.jessebrault.ssg.util.Monoid +import com.jessebrault.ssg.util.Monoids +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension + +import java.util.function.Function +import java.util.function.Supplier + +import static org.junit.jupiter.api.Assertions.assertEquals +import static org.junit.jupiter.api.Assertions.assertTrue + +@ExtendWith(MockitoExtension) +final class BuildDelegateTests { + + private final BuildDelegate d = new BuildDelegate() + + private BuildDelegate.Results getResults() { + new BuildDelegate.Results(this.d) + } + + @Test + void simpleOutputDirFunction( + @Mock Function expected, + @Mock Function base, + @Mock Supplier> onEmpty + ) { + this.d.outputDirFunction = expected + def r = this.results.getOutputDirFunctionResult(base, onEmpty) + assertEquals(expected, r) + } + + @Test + void mappedOutputDirFunction( + @Mock Supplier> onEmpty + ) { + final Function base = { Build b -> + new OutputDir('test') + } + this.d.outputDirFunction { + it.andThen { new OutputDir(it.asString() + '/nested') } + } + def r = this.results.getOutputDirFunctionResult(base, onEmpty) + def outputDir = r.apply(Build.getEmpty()) + assertEquals('test/nested', outputDir.asString()) + } + + @Test + void emptyOutputDirFunction( + @Mock Function base, + @Mock Function empty + ) { + assertEquals(empty, this.results.getOutputDirFunctionResult(base, { empty })) + } + + @Test + void siteSpecNoBase() { + this.d.siteSpec { + name = 'test' + baseUrl = 'testUrl' + } + def r = this.results.getSiteSpecResult( + SiteSpec.getBlank(), + true, + SiteSpec.DEFAULT_MONOID + ) + assertEquals('test', r.name) + assertEquals('testUrl', r.baseUrl) + } + + @Test + void mappedSiteSpec() { + this.d.siteSpec { base -> + name = base.name + 'Preview' + baseUrl = base.baseUrl + '/preview' + } + def r = this.results.getSiteSpecResult( + new SiteSpec('mySite', 'https://mysite.com'), + true, + SiteSpec.DEFAULT_MONOID + ) + assertEquals('mySitePreview', r.name) + assertEquals('https://mysite.com/preview', r.baseUrl) + } + + @Test + void siteSpecBaseConcat() { + this.d.siteSpec { + name = '123' + } + def r = this.results.getSiteSpecResult( + new SiteSpec('', '456'), + true, + SiteSpec.DEFAULT_MONOID + ) + assertEquals('123', r.name) + assertEquals('456', r.baseUrl) + } + + @Test + void siteSpecBaseNoConcat() { + this.d.siteSpec(false) { + name = '123' + } + def r = this.results.getSiteSpecResult( + new SiteSpec('', '456'), + true, + SiteSpec.DEFAULT_MONOID + ) + assertEquals('123', r.name) + assertEquals(SiteSpec.DEFAULT_MONOID.zero.baseUrl, r.baseUrl) + } + + @Test + void emptySiteSpec() { + assertEquals( + SiteSpec.getBlank(), + this.results.getSiteSpecResult(SiteSpec.getBlank(), true, SiteSpec.DEFAULT_MONOID) + ) + } + + private static Monoid> getGlobalsMonoid() { + Monoids.of([:]) { m0, m1 -> m0 + m1 } + } + + @Test + void globalsNoBase() { + this.d.globals { + test = 'abc' + } + def r = this.results.getGlobalsResult([:], true, getGlobalsMonoid()) + assertEquals([test: 'abc'], r) + } + + @Test + void globalsWithBase() { + this.d.globals { base -> + test = base.test + } + def r = this.results.getGlobalsResult( + [test: 'abc'], + true, + getGlobalsMonoid() + ) + assertEquals([test: 'abc'], r) + } + + @Test + void globalsEmpty() { + assertEquals([:], this.results.getGlobalsResult([:], true, getGlobalsMonoid())) + } + + @Test + void typesNoBase() { + this.d.types { + textTypes << TextTypes.MARKDOWN + } + def r = this.results.getTypesResult( + TypesContainer.getEmpty(), + true, + TypesContainer.DEFAULT_MONOID + ) + assertEquals(TypesContainer.get(textTypes: [TextTypes.MARKDOWN]), r) + } + + @Test + void typesWithBaseNoConcat() { + this.d.types(false) { base -> + textTypes.addAll(base.textTypes) + } + def r = this.results.getTypesResult( + TypesContainer.get(textTypes: [TextTypes.MARKDOWN]), + true, + TypesContainer.DEFAULT_MONOID + ) + assertEquals(TypesContainer.get(textTypes: [TextTypes.MARKDOWN]), r) + } + + @Test + void sourceProvidersNoBase(@Mock CollectionProvider textsProvider) { + this.d.sources { base, types -> + texts textsProvider + } + def r = this.results.getSourcesResult( + SourceProviders.getEmpty(), + true, + SourceProviders.DEFAULT_MONOID, + TypesContainer.getEmpty() + ) + assertTrue(textsProvider in r.textsProvider) + } + + // Cannot use Mockito for not yet understood reasons; + // it seems that the mock is a part of an addition somewhere on the left side, + // and because it doesn't have the method stubbed/mocked it + // returns null. Why this confuses Groovy much later with + // SourceProviders is not clear; perhaps because the mock + // returns null, it throws an exception somewhere that is + // swallowed? + @Test + void sourceProvidersWithBase(/* @Mock CollectionProvider textsProvider */) { + def textsProvider = CollectionProviders.fromCollection([] as Collection) + this.d.sources { base, types -> + texts base.textsProvider + } + def r = this.results.getSourcesResult( + SourceProviders.get(textsProvider: textsProvider), + true, + SourceProviders.DEFAULT_MONOID, + TypesContainer.getEmpty() + ) + assertTrue(textsProvider in r.textsProvider) + } + + @Test + void sourceProvidersMergeBase() { + def textsProvider = CollectionProviders.fromCollection([] as Collection) + def r = this.results.getSourcesResult( + SourceProviders.get(textsProvider: textsProvider), + true, + SourceProviders.DEFAULT_MONOID, + TypesContainer.getEmpty() + ) + assertTrue(textsProvider in r.textsProvider) + } + + private static Monoid>> getTaskFactoriesMonoid() { + Monoids.of([] as Collection>) { c0, c1 -> + c0 + c1 + } + } + + @Test + void taskFactoriesNoBase(@Mock Supplier taskFactorySupplier) { + this.d.taskFactories { base, sources -> + register('f0', taskFactorySupplier) + } + def r = this.results.getTaskFactoriesResult( + [], + true, + getTaskFactoriesMonoid(), + SourceProviders.getEmpty() + ) + assertEquals(1, r.size()) + def spec0 = r[0] + assertEquals('f0', spec0.name) + assertEquals(taskFactorySupplier, spec0.supplier) + } + + @Test + void taskFactoriesWithBase(@Mock Supplier taskFactorySupplier) { + this.d.taskFactories(false) { base, sources -> + registerAll(base) + } + def r = this.results.getTaskFactoriesResult( + [new TaskFactorySpec('spec0', taskFactorySupplier, [])], + true, + getTaskFactoriesMonoid(), + SourceProviders.getEmpty() + ) + assertEquals(1, r.size()) + def spec0 = r[0] + assertEquals('spec0', spec0.name) + assertEquals(taskFactorySupplier, spec0.supplier) + assertEquals([], spec0.configurators) + } + + @Test + void taskFactoriesMergeBase(@Mock Supplier taskFactorySupplier) { + def r = this.results.getTaskFactoriesResult( + [new TaskFactorySpec('spec0', taskFactorySupplier, [])], + true, + getTaskFactoriesMonoid(), + SourceProviders.getEmpty() + ) + assertEquals(1, r.size()) + def spec0 = r[0] + assertEquals('spec0', spec0.name) + assertEquals(taskFactorySupplier, spec0.supplier) + assertEquals([], spec0.configurators) + } + +} diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/SiteSpecDelegateTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/SiteSpecDelegateTests.groovy new file mode 100644 index 0000000..9f5b05b --- /dev/null +++ b/api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/SiteSpecDelegateTests.groovy @@ -0,0 +1,18 @@ +package com.jessebrault.ssg.buildscript.delegates + +import com.jessebrault.ssg.SiteSpec +import org.junit.jupiter.api.Test + +import static org.junit.jupiter.api.Assertions.assertThrows + +final class SiteSpecDelegateTests { + + @Test + void nullNotAllowed() { + def d = new SiteSpecDelegate(SiteSpec.DEFAULT_MONOID) + assertThrows(NullPointerException, { + d.name = null + }) + } + +} diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/TaskFactoriesDelegateTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/TaskFactoriesDelegateTests.groovy similarity index 65% rename from api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/TaskFactoriesDelegateTests.groovy rename to api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/TaskFactoriesDelegateTests.groovy index bb7222f..e390bc2 100644 --- a/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/TaskFactoriesDelegateTests.groovy +++ b/api/src/test/groovy/com/jessebrault/ssg/buildscript/delegates/TaskFactoriesDelegateTests.groovy @@ -1,4 +1,4 @@ -package com.jessebrault.ssg.buildscript.dsl +package com.jessebrault.ssg.buildscript.delegates import com.jessebrault.ssg.task.TaskFactory import org.junit.jupiter.api.Test @@ -10,9 +10,7 @@ import org.mockito.junit.jupiter.MockitoExtension import java.util.function.Consumer import java.util.function.Supplier -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow -import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertTrue +import static org.junit.jupiter.api.Assertions.* @ExtendWith(MockitoExtension) final class TaskFactoriesDelegateTests { @@ -28,9 +26,10 @@ final class TaskFactoriesDelegateTests { d.configure('test', TaskFactory, taskFactoryConsumer) } as Executable) def result = d.getResult() - assertTrue(result.containsKey('test')) - assertEquals(taskFactorySupplier, result['test'].supplier) - assertTrue(result['test'].configurators.contains(taskFactoryConsumer)) + def testSpec = result.find { it.name == 'test' } + assertNotNull(testSpec) + assertEquals(taskFactorySupplier, testSpec.supplier) + assertTrue(testSpec.configurators.contains(taskFactoryConsumer)) } @Test @@ -41,9 +40,10 @@ final class TaskFactoriesDelegateTests { def d = new TaskFactoriesDelegate() d.register('test', taskFactorySupplier, taskFactoryConsumer) def result = d.getResult() - assertTrue(result.containsKey('test')) - assertEquals(taskFactorySupplier, result['test'].supplier) - assertTrue(result['test'].configurators.contains(taskFactoryConsumer)) + def testSpec = result.find { it.name == 'test' } + assertNotNull(testSpec) + assertEquals(taskFactorySupplier, testSpec.supplier) + assertTrue(testSpec.configurators.contains(taskFactoryConsumer)) } } diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/AbstractBuildDelegateTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/AbstractBuildDelegateTests.groovy deleted file mode 100644 index 8f19569..0000000 --- a/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/AbstractBuildDelegateTests.groovy +++ /dev/null @@ -1,97 +0,0 @@ -package com.jessebrault.ssg.buildscript.dsl - -import com.jessebrault.ssg.SiteSpec -import com.jessebrault.ssg.buildscript.SourceProviders -import com.jessebrault.ssg.buildscript.TypesContainer -import com.jessebrault.ssg.page.Page -import com.jessebrault.ssg.page.PageTypes -import com.jessebrault.ssg.provider.CollectionProvider -import com.jessebrault.ssg.task.TaskFactory -import com.jessebrault.ssg.text.Text -import com.jessebrault.ssg.text.TextTypes -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.Mock -import org.mockito.junit.jupiter.MockitoExtension - -import java.util.function.Supplier - -import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertTrue - -@ExtendWith(MockitoExtension) -abstract class AbstractBuildDelegateTests { - - protected abstract AbstractBuildDelegate getDelegate() - - @Test - void siteSpecsAdded() { - def d = this.getDelegate() - d.siteSpec { - name = 'test' - } - d.siteSpec { - baseUrl = 'test' - } - def sum = d.getSiteSpecResult() - assertEquals(new SiteSpec('test', 'test'), sum) - } - - @Test - void globalsAdded() { - def d = this.getDelegate() - d.globals { - a = 0 - } - d.globals { - b = 1 - } - def sum = d.getGlobalsResult() - assertEquals([a: 0, b: 1], sum) - } - - @Test - void typesAdded() { - def d = this.getDelegate() - d.types { - textTypes << TextTypes.MARKDOWN - } - d.types { - pageTypes << PageTypes.GSP - } - def sum = d.getTypesResult() - assertTrue(TextTypes.MARKDOWN in sum.textTypes) - assertTrue(PageTypes.GSP in sum.pageTypes) - } - - @Test - void sourcesAdded(@Mock CollectionProvider textsProvider, @Mock CollectionProvider pagesProvider) { - def d = this.getDelegate() - d.providers { - texts(textsProvider) - } - d.providers { - pages(pagesProvider) - } - def sum = d.getSourcesResult(TypesContainer.getEmpty()) - assertTrue(textsProvider in sum.textsProvider) - assertTrue(pagesProvider in sum.pagesProvider) - } - - @Test - void taskFactoriesAdded(@Mock Supplier taskFactorySupplier) { - def d = this.getDelegate() - d.taskFactories { - register('tf0', taskFactorySupplier) - } - d.taskFactories { - register('tf1', taskFactorySupplier) - } - def sum = d.getTaskFactoriesResult(SourceProviders.getEmpty()) - assertEquals(2, sum.size()) - assertTrue(sum.inject(true) { acc, spec -> - acc && spec.supplier == taskFactorySupplier - }) - } - -} diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/AllBuildsDelegateTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/AllBuildsDelegateTests.groovy deleted file mode 100644 index 6e7b269..0000000 --- a/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/AllBuildsDelegateTests.groovy +++ /dev/null @@ -1,12 +0,0 @@ -package com.jessebrault.ssg.buildscript.dsl - -import com.jessebrault.ssg.buildscript.Build - -final class AllBuildsDelegateTests extends AbstractBuildDelegateTests { - - @Override - protected AbstractBuildDelegate getDelegate() { - new AllBuildsDelegate() - } - -} diff --git a/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/BuildDelegateTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/BuildDelegateTests.groovy deleted file mode 100644 index f9063ee..0000000 --- a/api/src/test/groovy/com/jessebrault/ssg/buildscript/dsl/BuildDelegateTests.groovy +++ /dev/null @@ -1,12 +0,0 @@ -package com.jessebrault.ssg.buildscript.dsl - -import com.jessebrault.ssg.buildscript.Build - -final class BuildDelegateTests extends AbstractBuildDelegateTests { - - @Override - protected AbstractBuildDelegate getDelegate() { - new BuildDelegate() - } - -} diff --git a/api/src/test/groovy/com/jessebrault/ssg/mutable/SimpleMutableTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/mutable/SimpleMutableTests.groovy new file mode 100644 index 0000000..f151577 --- /dev/null +++ b/api/src/test/groovy/com/jessebrault/ssg/mutable/SimpleMutableTests.groovy @@ -0,0 +1,64 @@ +package com.jessebrault.ssg.mutable + + +import org.junit.jupiter.api.Test + +import static org.junit.jupiter.api.Assertions.assertEquals +import static org.junit.jupiter.api.Assertions.assertThrows +import static org.junit.jupiter.api.Assertions.assertTrue + +final class SimpleMutableTests { + + @Test + void set() { + final Mutable p = Mutables.getEmpty() + p.set('test') + assertEquals('test', p.get()) + } + + @Test + void unset() { + final Mutable p = Mutables.getEmpty() + p.set('test') + p.unset() + assertThrows(NullPointerException, p.&get) + } + + @Test + void mapInPlace() { + final Mutable p = Mutables.getEmpty() + p.set('abc') + p.mapInPlace { it + 'def' } + assertEquals('abcdef', p.get()) + } + + @Test + void filterInPlace() { + final Mutable p = Mutables.getEmpty() + p.set('abc') + p.filterInPlace { it.startsWith('z') } + assertTrue(p.isEmpty()) + } + + @Test + void matchEmpty() { + final Mutable p = Mutables.getEmpty() + def matched = p.match( + () -> 'a', + { it + 'z' } + ) + assertEquals('a', matched) + } + + @Test + void matchPresent() { + final Mutable p = Mutables.getEmpty() + p.set('a') + def matched = p.match( + () -> 'a', + {it + 'z' } + ) + assertEquals('az', matched) + } + +} diff --git a/api/src/test/groovy/com/jessebrault/ssg/property/SimplePropertyTests.groovy b/api/src/test/groovy/com/jessebrault/ssg/property/SimplePropertyTests.groovy deleted file mode 100644 index 64729f9..0000000 --- a/api/src/test/groovy/com/jessebrault/ssg/property/SimplePropertyTests.groovy +++ /dev/null @@ -1,24 +0,0 @@ -package com.jessebrault.ssg.property - -import com.jessebrault.ssg.buildscript.domain.MutableSiteSpec -import org.junit.jupiter.api.Test - -import static org.junit.jupiter.api.Assertions.assertEquals - -final class SimplePropertyTests { - - @Test - void merge() { - def p = Properties.get(MutableSiteSpec.MONOID) - p.merge { - name.set('Hello') - } - p.map { - it.name.map { it + ', World!' } - it - } - def ms = p.get() - assertEquals('Hello, World!', ms.name.get()) - } - -} diff --git a/api/src/test/resources/com/jessebrault/ssg/buildscript/buildSrcTest.groovy b/api/src/test/resources/com/jessebrault/ssg/buildscript/buildSrcTest.groovy index 02c9b84..cf578a3 100644 --- a/api/src/test/resources/com/jessebrault/ssg/buildscript/buildSrcTest.groovy +++ b/api/src/test/resources/com/jessebrault/ssg/buildscript/buildSrcTest.groovy @@ -1,3 +1,3 @@ def t = new AnotherTask() -build('test') { } +build(name: 'test') { } diff --git a/api/src/test/resources/com/jessebrault/ssg/buildscript/simple.groovy b/api/src/test/resources/com/jessebrault/ssg/buildscript/simple.groovy index 27f5a9c..beaae28 100644 --- a/api/src/test/resources/com/jessebrault/ssg/buildscript/simple.groovy +++ b/api/src/test/resources/com/jessebrault/ssg/buildscript/simple.groovy @@ -11,7 +11,5 @@ final Logger logger = LoggerFactory.getLogger('simple.groovy') logger.debug('executing simple buildScript') -build { - name = 'test' -} +build(name: 'test') { } diff --git a/api/src/test/resources/com/jessebrault/ssg/buildscript/testImport.groovy b/api/src/test/resources/com/jessebrault/ssg/buildscript/testImport.groovy index f29e09b..e4e3728 100644 --- a/api/src/test/resources/com/jessebrault/ssg/buildscript/testImport.groovy +++ b/api/src/test/resources/com/jessebrault/ssg/buildscript/testImport.groovy @@ -1,5 +1,3 @@ def t = new TestHtmlTask() -build { - name = 'test' -} +build(name: 'test') { } diff --git a/api/src/test/resources/oneTextAndTemplate.groovy b/api/src/test/resources/oneTextAndTemplate.groovy index dc15c05..6951cab 100644 --- a/api/src/test/resources/oneTextAndTemplate.groovy +++ b/api/src/test/resources/oneTextAndTemplate.groovy @@ -13,20 +13,20 @@ import com.jessebrault.ssg.util.Result import groovy.transform.BaseScript @BaseScript -BuildScriptBase base +BuildScriptBase b -build('test') { - outputDirFunction = { Build b -> new OutputDir(new File(args.sourceDir, 'build')) } +build(name: 'test') { + outputDirFunction = { Build build -> new OutputDir(new File(args.sourceDir, 'build')) } types { textTypes << TextTypes.MARKDOWN templateTypes << TemplateTypes.GSP } - providers { types -> + sources { base, types -> texts TextsProviders.from(new File(args.sourceDir, 'texts'), types.textTypes) templates TemplatesProviders.from(new File(args.sourceDir, 'templates'), types.templateTypes) } - taskFactories { sources -> + taskFactories { base, sources -> register('textToHtml', TextToHtmlTaskFactory::new) { it.specProvider += CollectionProviders.fromSupplier { def templates = sources.templatesProvider.provide() diff --git a/buildSrc/src/main/groovy/ssg.common.gradle b/buildSrc/src/main/groovy/ssg.common.gradle index a1254d4..f3b28af 100644 --- a/buildSrc/src/main/groovy/ssg.common.gradle +++ b/buildSrc/src/main/groovy/ssg.common.gradle @@ -13,7 +13,7 @@ repositories { dependencies { // https://mvnrepository.com/artifact/org.apache.groovy/groovy - api 'org.apache.groovy:groovy:4.0.9' + api 'org.apache.groovy:groovy:4.0.12' // https://mvnrepository.com/artifact/org.jetbrains/annotations api 'org.jetbrains:annotations:24.0.0' diff --git a/sketchingBaseBuild.groovy b/sketchingBaseBuild.groovy index f52faa5..7456b35 100644 --- a/sketchingBaseBuild.groovy +++ b/sketchingBaseBuild.groovy @@ -1,5 +1,4 @@ abstractBuild(name: 'redDogAll', extends: 'default') { - // siteSpec(Closure) is short for siteSpec.merge(Closure) siteSpec { name = 'Red Dog Ensemble' baseUrl = 'https://reddogensemble.com' @@ -8,30 +7,14 @@ abstractBuild(name: 'redDogAll', extends: 'default') { globals { greeting = 'Say hello to good music!' } - - sources { types -> - models.map { acc -> - acc + someMethodThatGetsEventModels() - } - } - - taskFactories { sources -> - register(name: 'eventToHtml', supplier: ModelToHtmlFactory::new) { - modelsProvider.map { acc -> - acc + CollectionProviders.fromSupplier { - sources.models.provide().findAll { it.name.startsWith('event/') } - } - } - } - } } build(name: 'preview', extends: 'redDogAll') { siteSpec { base -> - baseUrl = base.baseUrl + '/preview' // if possible + baseUrl = base.baseUrl + '/preview' } globals { - greeting = 'Hello from preview!' + greeting = 'Hello from preview!' // overwrite } } \ No newline at end of file