Major work on buildscript dsl; new abstract build concept. N.B.: task factories not merged yet.

This commit is contained in:
JesseBrault0709 2023-05-13 13:51:43 +02:00
parent c74d80a69e
commit 1f8bd3270c
56 changed files with 1614 additions and 816 deletions

View File

@ -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<OutputDir, Build, OutputDir>`, 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.

View File

@ -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 {

View File

@ -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<SiteSpec> defaultSemiGroup = new Monoid<>(
SiteSpec::concat,
SiteSpec::getBlank
)
static final Monoid<SiteSpec> DEFAULT_MONOID = Monoids.of(getBlank(), SiteSpec::concat)
static SiteSpec getBlank() {
new SiteSpec('', '')

View File

@ -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<String, Object> globals
final Collection<TaskFactorySpec<TaskFactory>> 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<Build, OutputDir> outputDirFunction
final SiteSpec siteSpec

View File

@ -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)
}
}

View File

@ -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<BuildSpec, DefaultEdge> graph,
BuildSpec spec,
Collection<BuildSpec> 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<BuildSpec, DefaultEdge> getDependencyGraph(Collection<BuildSpec> buildSpecs) {
logger.trace(enter, '')
final Graph<BuildSpec, DefaultEdge> 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<BuildSpec> getAncestors(BuildSpec child, Graph<BuildSpec, DefaultEdge> graph) {
logger.trace(enter, 'child: {}, graph: {}', child, graph)
if (child.extending.isEmpty()) {
def r = [] as Collection<BuildSpec>
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() {}
}

View File

@ -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<AllBuildsDelegate> allBuildsDelegates = []
private final Collection<BuildDelegate> buildDelegates = []
protected final Collection<BuildSpec> 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<String, Object> 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<String, Object> 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<Build> 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<BuildSpec> getBuildSpecs() {
this.buildSpecs
}
}

View File

@ -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<String, Object> 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 })"
}
}

View File

@ -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> T reduceResults(
Collection<BuildDelegate.Results> resultsCollection,
Zero<T> tZero,
BiFunction<T, BuildDelegate.Results, T> resultsToT
) {
resultsCollection.inject(tZero.zero) { acc, r ->
resultsToT.apply(acc, r)
}
}
private static Collection<BuildDelegate.Results> mapBuildSpecsToResults(Collection<BuildSpec> 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<Map<String, Object>> getGlobalsMonoid() {
Monoids.of([:]) { m0, m1 ->
m0 + m1
}
}
private static Build toBuild(
Collection<BuildSpec> 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<Build> getBuilds(Collection<BuildSpec> 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() {}
}

View File

@ -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<Build, OutputDir> DEFAULT = { Build build -> new OutputDir(build.name) }
static final Monoid<Function<Build, OutputDir>> DEFAULT_MONOID = Monoids.of(DEFAULT, OutputDirFunctions::concat)
static Function<Build, OutputDir> concat(
Function<Build, OutputDir> f0,
Function<Build, OutputDir> f1

View File

@ -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)
}
}

View File

@ -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<SourceProviders> 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 })"
}
}

View File

@ -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<TypesContainer> DEFAULT_MONOID = Monoids.of(getEmpty(), TypesContainer::concat)
static TypesContainer getEmpty() {
new TypesContainer([], [], [], [])
}
static TypesContainer get(Map<String, Object> args) {
new TypesContainer(
args.textTypes ? args.textTypes as Collection<TextType> : [],
args.pageTypes ? args.pageTypes as Collection<PageType> : [],
args.templateTypes ? args.templateTypes as Collection<TemplateType> : [],
args.partTypes ? args.partTypes as Collection<PartType> : []
)
}
static TypesContainer concat(TypesContainer tc0, TypesContainer tc1) {
new TypesContainer(
tc0.textTypes + tc1.textTypes,
@ -26,13 +39,19 @@ final class TypesContainer {
)
}
Collection<TextType> textTypes
Collection<PageType> pageTypes
Collection<TemplateType> templateTypes
Collection<PartType> partTypes
final Collection<TextType> textTypes
final Collection<PageType> pageTypes
final Collection<TemplateType> templateTypes
final Collection<PartType> partTypes
TypesContainer plus(TypesContainer other) {
concat(this, other)
}
@Override
String toString() {
"TypesContainer(textTypes: ${ this.textTypes }, pageTypes: ${ this.pageTypes }, " +
"templateTypes: ${ this.templateTypes }, partTypes: ${ this.partTypes })"
}
}

View File

@ -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<Build, OutputDir> getOutputDirFunctionResult(
Function<Build, OutputDir> base,
Supplier<Function<Build, OutputDir>> onEmpty
) {
this.delegate.outputDirFunction.getOrElse {
this.delegate.outputDirFunctionMapper.match(onEmpty) {
it.apply(base)
}
}
}
SiteSpec getSiteSpecResult(
SiteSpec base,
boolean onConcatWithBaseEmpty,
Monoid<SiteSpec> 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<String, Object> getGlobalsResult(
Map<String, Object> base,
boolean onConcatWithBaseEmpty,
Monoid<Map<String, Object>> 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<TypesContainer> 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<SourceProviders> 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<TaskFactorySpec<TaskFactory>> getTaskFactoriesResult(
Collection<TaskFactorySpec<TaskFactory>> base,
boolean onConcatWithBaseEmpty,
Monoid<Collection<TaskFactorySpec<TaskFactory>>> 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<Function<Build, OutputDir>> outputDirFunction = Mutables.getEmpty()
private final Mutable<UnaryOperator<Function<Build, OutputDir>>> outputDirFunctionMapper = Mutables.getEmpty()
private final Mutable<Boolean> siteSpecConcatBase = Mutables.getEmpty()
private final Mutable<Closure<?>> siteSpecClosure = Mutables.getEmpty()
private final Mutable<Boolean> globalsConcatBase = Mutables.getEmpty()
private final Mutable<Closure<?>> globalsClosure = Mutables.getEmpty()
private final Mutable<Boolean> typesConcatBase = Mutables.getEmpty()
private final Mutable<Closure<?>> typesClosure = Mutables.getEmpty()
private final Mutable<Boolean> sourcesConcatBase = Mutables.getEmpty()
private final Mutable<Closure<?>> sourcesClosure = Mutables.getEmpty()
private final Mutable<Boolean> taskFactoriesConcatBase = Mutables.getEmpty()
private final Mutable<Closure<?>> taskFactoriesClosure = Mutables.getEmpty()
void setOutputDirFunction(Function<Build, OutputDir> 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<Function<Build, OutputDir>> 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<String, Object>')
Closure<?> globalsClosure
) {
this.globals(true, globalsClosure)
}
void globals(
boolean concatWithBase,
@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = FromString, options = 'Map<String, Object>')
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.task.TaskFactorySpec<com.jessebrault.ssg.task.TaskFactory>>, 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.task.TaskFactorySpec<com.jessebrault.ssg.task.TaskFactory>>, com.jessebrault.ssg.buildscript.SourceProviders'
)
Closure<?> taskFactoriesClosure
) {
this.taskFactoriesConcatBase.set(concatWithBase)
this.taskFactoriesClosure.set(taskFactoriesClosure)
}
}

View File

@ -1,4 +1,4 @@
package com.jessebrault.ssg.buildscript.dsl
package com.jessebrault.ssg.buildscript.delegates
final class GlobalsDelegate {

View File

@ -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<SiteSpec> 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)
}
}

View File

@ -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<Text> textsProvider = CollectionProviders.getEmpty()

View File

@ -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<String, TaskFactorySpec<TaskFactory>> specs = [:]
private final Collection<TaskFactorySpec<TaskFactory>> 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<? extends TaskFactory> factorySupplier) {
this.checkNotRegistered(name)
this.specs[name] = new TaskFactorySpec<>(factorySupplier, [])
this.specs << new TaskFactorySpec<>(name, factorySupplier, [])
}
def <T extends TaskFactory> void register(
@ -27,22 +35,31 @@ final class TaskFactoriesDelegate {
Consumer<T> factoryConfigurator
) {
this.checkNotRegistered(name)
this.specs[name] = new TaskFactorySpec<>(factorySupplier, [factoryConfigurator])
this.specs << new TaskFactorySpec<>(name, factorySupplier, [factoryConfigurator])
}
void register(TaskFactorySpec<TaskFactory> spec) {
this.specs << spec
}
void registerAll(Collection<TaskFactorySpec<TaskFactory>> specs) {
this.specs.addAll(specs)
}
def <T extends TaskFactory> void configure(
String name,
Class<T> factoryClass, // Dummy so we get better auto-complete
Consumer<T> factoryConfigureClosure
Consumer<T> 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<TaskFactory>)
spec.configurators << (factoryConfigurator as Consumer<TaskFactory>)
}
Map<String, TaskFactorySpec> getResult() {
Collection<TaskFactorySpec<TaskFactory>> getResult() {
this.specs
}

View File

@ -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

View File

@ -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<String> nameAndBaseUrlMonoid = new Monoid<>(
{ s0, s1 -> s1 },
{ '' }
)
static final Monoid<MutableSiteSpec> 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<String> name = Properties.get(nameAndBaseUrlMonoid)
final Property<String> baseUrl = Properties.get(nameAndBaseUrlMonoid)
}

View File

@ -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<T> {
private final Collection<Closure<?>> siteSpecClosures = []
private final Collection<Closure<?>> globalsClosures = []
private final Collection<Closure<?>> typesClosures = []
private final Collection<Closure<?>> sourcesClosures = []
private final Collection<Closure<?>> 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<String, Object> getGlobalsResult() {
this.globalsClosures.inject([:] as Map<String, Object>) { 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<TaskFactorySpec<TaskFactory>> getTaskFactoriesResult(SourceProviders sourceProviders) {
this.taskFactoriesClosures.inject([:] as Map<String, TaskFactorySpec>) { 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
}
}

View File

@ -1,16 +0,0 @@
package com.jessebrault.ssg.buildscript.dsl
import com.jessebrault.ssg.buildscript.Build
final class AllBuildsDelegate extends AbstractBuildDelegate<Build.AllBuilds> {
@Override
Build.AllBuilds getResult() {
new Build.AllBuilds(
this.getSiteSpecResult(),
this.getGlobalsResult(),
this.getTaskFactoriesResult(this.getSourcesResult(this.getTypesResult()))
)
}
}

View File

@ -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<Build> {
String name = ''
private Function<Build, OutputDir> 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<Build, OutputDir> outputDirFunction) {
this.outputDirFunction = outputDirFunction
}
void setOutputDir(File file) {
this.outputDirFunction = OutputDirFunctions.of(file)
}
void setOutputDir(String path) {
this.outputDirFunction = OutputDirFunctions.of(path)
}
}

View File

@ -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)
}
}

View File

@ -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> {
T get();
void set(T t);
void unset();
boolean isPresent();
default boolean isEmpty() {
return !this.isPresent();
}
void filterInPlace(Predicate<T> filter);
void mapInPlace(UnaryOperator<T> mapper);
<U> void zipInPlace(Mutable<U> other, Supplier<T> onEmpty, Supplier<U> onOtherEmpty, BiFunction<T, U, T> zipper);
<U> U match(Supplier<U> onEmpty, Function<T, U> onPresentMapper);
T getOrElse(Supplier<T> onEmpty);
<U> Mutable<U> chain(Function<T, Mutable<U>> mapper);
<U> Mutable<U> map(Function<T, U> mapper);
<U, R> Mutable<R> zip(Mutable<U> other, BiFunction<T, U, R> zipper);
Optional<T> asOptional();
}

View File

@ -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 <T> Monoid<Mutable<T>> getMonoid(final Semigroup<T> 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 <T> @NotNull Mutable<T> getEmpty() {
return new SimpleMutable<>();
}
public static <T> Mutable<T> get(T initialValue) {
return new SimpleMutable<>(initialValue);
}
private Mutables() {}
}

View File

@ -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<T> implements Mutable<T> {
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<T> filter) {
if (this.t != null && !filter.test(this.t)) {
this.unset();
}
}
@Override
public void mapInPlace(UnaryOperator<T> mapper) {
this.t = mapper.apply(this.t);
}
@Override
public <U> void zipInPlace(Mutable<U> other, Supplier<T> onEmpty, Supplier<U> onOtherEmpty, BiFunction<T, U, T> zipper) {
this.t = zipper.apply(
this.isPresent() ? this.t : onEmpty.get(),
other.isPresent() ? other.get() : onOtherEmpty.get()
);
}
@Override
public <U> U match(Supplier<U> onEmpty, Function<T, U> onPresentMapper) {
return this.t != null ? onPresentMapper.apply(this.t) : onEmpty.get();
}
@Override
public T getOrElse(Supplier<T> other) {
return this.t != null ? this.t : other.get();
}
@Override
public <U> Mutable<U> chain(Function<T, Mutable<U>> mapper) {
return this.map(mapper).get();
}
@Override
public <U> Mutable<U> map(Function<T, U> mapper) {
return this.t != null ? Mutables.get(mapper.apply(this.t)) : Mutables.getEmpty();
}
@Override
public <U, R> Mutable<R> zip(Mutable<U> other, BiFunction<T, U, R> zipper) {
return this.isPresent() && other.isPresent()
? Mutables.get(zipper.apply(this.get(), other.get()))
: Mutables.getEmpty();
}
@Override
public Optional<T> asOptional() {
return Optional.ofNullable(this.t);
}
}

View File

@ -1,21 +0,0 @@
package com.jessebrault.ssg.property
import com.jessebrault.ssg.util.Monoid
final class Properties {
static <T> Property<T> get(Monoid<T> semiGroup) {
new SimpleProperty<>(semiGroup)
}
static <T> Property<T> get(Monoid<T> semiGroup, T convention) {
new SimpleProperty<>(semiGroup, convention)
}
static <T> Property<T> get(Monoid<T> semiGroup, T convention, T t) {
new SimpleProperty<>(semiGroup, convention, t)
}
private Properties() {}
}

View File

@ -1,19 +0,0 @@
package com.jessebrault.ssg.property
import java.util.function.UnaryOperator
interface Property<T> {
T get()
void set(T t)
void unset()
T getConvention()
void setConvention(T t)
void unsetConvention()
void map(UnaryOperator<T> mapper)
void merge(
@DelegatesTo(type = 'T', strategy = Closure.DELEGATE_FIRST)
Closure<?> configurator
)
}

View File

@ -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<T> implements Property<T> {
private final Monoid<T> monoid
private T t
private T convention
SimpleProperty(Monoid<T> monoid) {
this.monoid = monoid
this.convention = this.monoid.empty.get()
}
SimpleProperty(Monoid<T> monoid, T convention) {
this.monoid = monoid
this.convention = convention
}
SimpleProperty(Monoid<T> 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<T> 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))
}
}

View File

@ -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<T extends TaskFactory> {
static Semigroup<TaskFactorySpec<TaskFactory>> DEFAULT_SEMIGROUP = Semigroups.of(TaskFactorySpec::concat)
static <T extends TaskFactory> TaskFactorySpec<T> concat(TaskFactorySpec<T> spec0, TaskFactorySpec<T> 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<T> supplier
final Collection<Consumer<T>> configurators

View File

@ -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<T> {
final BinaryOperator<T> concat
final Supplier<T> empty
}
@ApiStatus.Experimental
interface Monoid<T> extends Semigroup<T>, Zero<T> {}

View File

@ -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 <T> Monoid<T> of(T zero, BinaryOperator<T> concat) {
new SimpleMonoid<>(zero, concat)
}
private Monoids() {}
}

View File

@ -0,0 +1,10 @@
package com.jessebrault.ssg.util
import org.jetbrains.annotations.ApiStatus
import java.util.function.BinaryOperator
@ApiStatus.Experimental
interface Semigroup<T> {
BinaryOperator<T> getConcat()
}

View File

@ -0,0 +1,13 @@
package com.jessebrault.ssg.util
import java.util.function.BinaryOperator
final class Semigroups {
static <T> Semigroup<T> of(BinaryOperator<T> concat) {
new SimpleSemigroup<>(concat)
}
private Semigroups() {}
}

View File

@ -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<T> implements Monoid<T> {
final T zero
final BinaryOperator<T> concat
}

View File

@ -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<T> implements Semigroup<T> {
final BinaryOperator<T> concat
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.ssg.util
interface Zero<T> {
T getZero()
}

View File

@ -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)
}
}

View File

@ -13,11 +13,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals
@ExtendWith(MockitoExtension)
final class BuildScriptBaseTests {
private static Collection<Build> scriptToBuilds(
private static Collection<BuildSpec> 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<Build> 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<Build, OutputDir> 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
)
}
}

View File

@ -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<TaskFactory> 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<TaskFactory> 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

View File

@ -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<String> 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<Build, OutputDir> 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)
}
}

View File

@ -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<Build, OutputDir> expected,
@Mock Function<Build, OutputDir> base,
@Mock Supplier<Function<Build, OutputDir>> onEmpty
) {
this.d.outputDirFunction = expected
def r = this.results.getOutputDirFunctionResult(base, onEmpty)
assertEquals(expected, r)
}
@Test
void mappedOutputDirFunction(
@Mock Supplier<Function<Build, OutputDir>> onEmpty
) {
final Function<Build, OutputDir> 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<Build, OutputDir> base,
@Mock Function<Build, OutputDir> 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<Map<String, Object>> 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<Text> 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<Text> textsProvider */) {
def textsProvider = CollectionProviders.fromCollection([] as Collection<Text>)
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<Text>)
def r = this.results.getSourcesResult(
SourceProviders.get(textsProvider: textsProvider),
true,
SourceProviders.DEFAULT_MONOID,
TypesContainer.getEmpty()
)
assertTrue(textsProvider in r.textsProvider)
}
private static Monoid<Collection<TaskFactorySpec<TaskFactory>>> getTaskFactoriesMonoid() {
Monoids.of([] as Collection<TaskFactorySpec<TaskFactory>>) { c0, c1 ->
c0 + c1
}
}
@Test
void taskFactoriesNoBase(@Mock Supplier<TaskFactory> 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<TaskFactory> taskFactorySupplier) {
this.d.taskFactories(false) { base, sources ->
registerAll(base)
}
def r = this.results.getTaskFactoriesResult(
[new TaskFactorySpec<TaskFactory>('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<TaskFactory> taskFactorySupplier) {
def r = this.results.getTaskFactoriesResult(
[new TaskFactorySpec<TaskFactory>('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)
}
}

View File

@ -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
})
}
}

View File

@ -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))
}
}

View File

@ -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<T> {
protected abstract AbstractBuildDelegate<T> 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<Text> textsProvider, @Mock CollectionProvider<Page> 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<TaskFactory> 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
})
}
}

View File

@ -1,12 +0,0 @@
package com.jessebrault.ssg.buildscript.dsl
import com.jessebrault.ssg.buildscript.Build
final class AllBuildsDelegateTests extends AbstractBuildDelegateTests<Build.AllBuilds> {
@Override
protected AbstractBuildDelegate<Build.AllBuilds> getDelegate() {
new AllBuildsDelegate()
}
}

View File

@ -1,12 +0,0 @@
package com.jessebrault.ssg.buildscript.dsl
import com.jessebrault.ssg.buildscript.Build
final class BuildDelegateTests extends AbstractBuildDelegateTests<Build> {
@Override
protected AbstractBuildDelegate<Build> getDelegate() {
new BuildDelegate()
}
}

View File

@ -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<String> p = Mutables.getEmpty()
p.set('test')
assertEquals('test', p.get())
}
@Test
void unset() {
final Mutable<String> p = Mutables.getEmpty()
p.set('test')
p.unset()
assertThrows(NullPointerException, p.&get)
}
@Test
void mapInPlace() {
final Mutable<String> p = Mutables.getEmpty()
p.set('abc')
p.mapInPlace { it + 'def' }
assertEquals('abcdef', p.get())
}
@Test
void filterInPlace() {
final Mutable<String> p = Mutables.getEmpty()
p.set('abc')
p.filterInPlace { it.startsWith('z') }
assertTrue(p.isEmpty())
}
@Test
void matchEmpty() {
final Mutable<String> p = Mutables.getEmpty()
def matched = p.match(
() -> 'a',
{ it + 'z' }
)
assertEquals('a', matched)
}
@Test
void matchPresent() {
final Mutable<String> p = Mutables.getEmpty()
p.set('a')
def matched = p.match(
() -> 'a',
{it + 'z' }
)
assertEquals('az', matched)
}
}

View File

@ -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())
}
}

View File

@ -1,3 +1,3 @@
def t = new AnotherTask()
build('test') { }
build(name: 'test') { }

View File

@ -11,7 +11,5 @@ final Logger logger = LoggerFactory.getLogger('simple.groovy')
logger.debug('executing simple buildScript')
build {
name = 'test'
}
build(name: 'test') { }

View File

@ -1,5 +1,3 @@
def t = new TestHtmlTask()
build {
name = 'test'
}
build(name: 'test') { }

View File

@ -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()

View File

@ -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'

View File

@ -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
}
}