Worked on buildscript.

This commit is contained in:
JesseBrault0709 2024-05-14 15:52:37 +02:00
parent f11334c74b
commit c502727243
47 changed files with 355 additions and 1682 deletions

View File

@ -2,8 +2,7 @@ package com.jessebrault.ssg
import com.jessebrault.ssg.buildscript.Build
import com.jessebrault.ssg.buildscript.BuildScriptConfiguratorFactory
import com.jessebrault.ssg.buildscript.BuildScriptRunner
import com.jessebrault.ssg.buildscript.BuildUtil
import com.jessebrault.ssg.buildscript.FileBuildScriptGetter
import com.jessebrault.ssg.util.Diagnostic
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@ -50,7 +49,7 @@ final class BuildScriptBasedStaticSiteGenerator implements StaticSiteGenerator {
if (this.buildScript == null) {
logger.info('no specified build script; using defaults')
def result = BuildScriptRunner.runClosureScript { base ->
def result = FileBuildScriptGetter.runClosureScript { base ->
configuratorFactories.each {
it.get().accept(base)
}
@ -58,9 +57,9 @@ final class BuildScriptBasedStaticSiteGenerator implements StaticSiteGenerator {
this.builds.addAll(result)
} else if (this.buildScript.exists() && this.buildScript.isFile()) {
logger.info('running buildScript: {}', this.buildScript)
def buildScriptRunner = new BuildScriptRunner(this.buildScriptClassLoaderUrls)
def buildScriptRunner = new FileBuildScriptGetter(this.buildScriptClassLoaderUrls)
this.buildScriptClassLoader = buildScriptRunner.getBuildScriptClassLoader()
def result = buildScriptRunner.runBuildScript(
def result = buildScriptRunner.getBuildInfo(
this.buildScript.name,
[args: buildScriptArgs]
) { base ->

View File

@ -27,10 +27,6 @@ final class SiteSpec {
final String name
final String baseUrl
SiteSpec plus(SiteSpec other) {
concat(this, other)
}
@Override
String toString() {
"SiteSpec(${ this.name }, ${ this.baseUrl })"

View File

@ -1,66 +1,44 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.task.TaskFactory
import com.jessebrault.ssg.task.TaskFactorySpec
import com.jessebrault.ssg.model.Model
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import groowt.util.fp.property.Property
import groowt.util.fp.provider.NamedProvider
import groowt.util.fp.provider.NamedSetProvider
import groowt.util.fp.provider.Provider
import groowt.util.fp.provider.SetProvider
import java.util.function.Function
import static java.util.Objects.requireNonNull
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class Build {
static Build getEmpty() {
new Build(
'',
OutputDirFunctions.DEFAULT,
SiteSpec.getBlank(),
[:],
[],
[]
)
}
static Build get(Map<String, Object> args) {
new Build(
args.name as String ?: '',
args.outputDirFunction as Function<Build, OutputDir> ?: OutputDirFunctions.DEFAULT,
args.siteSpec as SiteSpec ?: SiteSpec.getBlank(),
args.globals as Map<String, Object> ?: [:],
args.taskFactorySpecs as Collection<TaskFactorySpec<TaskFactory>> ?: [],
args.includedBuilds as Collection<String> ?: []
)
}
static Build concat(Build b0, Build b1) {
new Build(
b0.name.blank ? b1.name : b0.name,
OutputDirFunctions.concat(b0.outputDirFunction, b1.outputDirFunction),
SiteSpec.concat(b0.siteSpec, b1.siteSpec),
b0.globals + b1.globals,
b0.taskFactorySpecs + b1.taskFactorySpecs,
b0.includedBuilds + b1.includedBuilds
)
}
final String name
final Function<Build, OutputDir> outputDirFunction
final SiteSpec siteSpec
final Map<String, Object> globals
final Collection<TaskFactorySpec<TaskFactory>> taskFactorySpecs
final Collection<String> includedBuilds
final Property<String> name
final Property<String> siteName
final Property<String> baseUrl
final Provider<File> outputDir
final Provider<Map<String, Object>> globals
final Set<Provider<File>> textsDirs
final Set<NamedProvider<Model>> models
Build plus(Build other) {
concat(this, other)
@SuppressWarnings('GroovyAssignabilityCheck')
Build(Map args) {
this.includedBuilds = requireNonNull(args.includedBuilds)
this.name = requireNonNull(args.name)
this.siteName = requireNonNull(args.siteName)
this.baseUrl = requireNonNull(args.baseUrl)
this.outputDir = requireNonNull(args.outputDir)
this.globals = requireNonNull(args.globals)
this.textsDirs = requireNonNull(args.textsDirs)
this.models = requireNonNull(args.models)
}
@Override
String toString() {
"Build(name: ${ this.name }, siteSpec: ${ this.siteSpec }, globals: ${ this.globals }, includedBuilds: ${ this.includedBuilds })"
"Build(name: ${ this.name })"
}
}

View File

@ -0,0 +1,51 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@NullCheck
@TupleConstructor(includeFields = true)
class BuildDelegateToBuildConverter {
protected Build getFromDelegate(String name, BuildDelegate delegate) {
new Build(
name: name,
siteName: delegate.siteName,
baseUrl: delegate.baseUrl,
outputDir: delegate.outputDir,
globals: delegate.globals,
models: delegate.models
)
}
protected void getFromBuild(Build source) {
new BuildDelegate().tap {
siteName(source.siteName)
baseUrl(source.siteName)
outputDir(source.outputDir)
globals.set(source.globals)
textsDirs(source.textsDirs)
models(source.models)
}
}
private final FileBuildScriptGetter buildScriptGetter
private final File buildScriptsDir
Build convert(String name, BuildScriptBase buildScript) {
final BuildDelegate delegate
if (buildScript.extending != null) {
def extendingFrom = buildScriptGetter.getBuildInfo(buildScript.extending)
def build = this.convert(buildScript.extending, extendingFrom)
delegate = this.getFromBuild(build)
} else {
delegate = new BuildDelegate()
}
def cl = buildScript.buildClosure
cl.delegate = delegate
cl()
this.getFromDelegate(name, delegate)
}
}

View File

@ -1,73 +0,0 @@
package com.jessebrault.ssg.buildscript;
import com.jessebrault.ssg.util.Monoid;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DirectedAcyclicGraph;
import java.util.Collection;
import java.util.List;
import java.util.function.BiFunction;
public final class BuildGraph {
private static void addAncestorEdges(
Graph<BuildSpec, DefaultEdge> graph,
BuildSpec buildSpec,
Collection<BuildSpec> allBuildSpecs
) {
if (!graph.containsVertex(buildSpec)) {
throw new IllegalStateException("Given buildSpec " + buildSpec.getName() + " is not in the given graph.");
}
for (final var extendingName : buildSpec.getExtending()) {
final var ancestor = allBuildSpecs.stream()
.filter(bs -> extendingName.equals(bs.getName()))
.findAny()
.orElseThrow(() -> new IllegalStateException(
"Could not find ancestor " + extendingName + " for buildSpec " + buildSpec.getName() + "."
));
graph.addEdge(ancestor, buildSpec);
addAncestorEdges(graph, ancestor, allBuildSpecs);
}
}
private final Graph<BuildSpec, DefaultEdge> graph;
public BuildGraph(Collection<BuildSpec> buildSpecs) {
this.graph = new DirectedAcyclicGraph<>(DefaultEdge.class);
for (final var buildSpec : buildSpecs) {
if (!this.graph.containsVertex(buildSpec)) {
this.graph.addVertex(buildSpec);
}
addAncestorEdges(this.graph, buildSpec, buildSpecs);
}
}
public List<BuildSpec> getParents(BuildSpec start) {
return this.graph.incomingEdgesOf(start).stream()
.map(this.graph::getEdgeSource)
.toList();
}
@Deprecated(forRemoval = true)
public BuildIntermediate toIntermediate(
BuildSpec rootSpec,
Monoid<BuildIntermediate> buildIntermediateMonoid,
BiFunction<BuildIntermediate, BuildSpec, BuildIntermediate> parentAndSpecToIntermediate
) {
final List<BuildSpec> parents = this.getParents(rootSpec);
if (parents.size() == 0) {
return parentAndSpecToIntermediate.apply(buildIntermediateMonoid.getZero(), rootSpec);
} else {
final List<BuildIntermediate> parentIntermediates = parents.stream()
.map(parent -> this.toIntermediate(parent, buildIntermediateMonoid, parentAndSpecToIntermediate))
.toList();
final BuildIntermediate parentsReduced = parentIntermediates.stream()
.reduce(buildIntermediateMonoid.getZero(), (bi0, bi1) ->
buildIntermediateMonoid.getConcat().apply(bi0, bi1)
);
return parentAndSpecToIntermediate.apply(parentsReduced, rootSpec);
}
}
}

View File

@ -1,20 +0,0 @@
package com.jessebrault.ssg.buildscript;
import com.jessebrault.ssg.SiteSpec;
import com.jessebrault.ssg.task.TaskFactory;
import com.jessebrault.ssg.task.TaskFactorySpec;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
public interface BuildIntermediate {
BuildSpec getBuildSpec();
Function<Build, OutputDir> getOutputDirFunction();
SiteSpec getSiteSpec();
Map<String, Object> getGlobals();
TypesContainer getTypes();
SourceProviders getSources();
Collection<TaskFactorySpec<TaskFactory>> getTaskFactorySpecs();
Collection<String> getIncludedBuilds();
}

View File

@ -1,48 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.task.TaskFactory
import com.jessebrault.ssg.task.TaskFactorySpec
import com.jessebrault.ssg.util.Monoid
import com.jessebrault.ssg.util.Monoids
import java.util.function.Function
final class BuildMonoids {
static final Monoid<Function<Build, OutputDir>> outputDirFunctionMonoid = OutputDirFunctions.DEFAULT_MONOID
static final Monoid<SiteSpec> siteSpecMonoid = SiteSpec.DEFAULT_MONOID
static final Monoid<Map<String, Object>> globalsMonoid = Monoids.getMapMonoid()
static final Monoid<TypesContainer> typesMonoid = TypesContainer.DEFAULT_MONOID
static final Monoid<SourceProviders> sourcesMonoid = SourceProviders.DEFAULT_MONOID
static final Monoid<Collection<TaskFactorySpec<TaskFactory>>> taskFactoriesMonoid =
Monoids.getMergeCollectionMonoid(
TaskFactorySpec.SAME_NAME_AND_SUPPLIER_EQ,
TaskFactorySpec.DEFAULT_SEMIGROUP
)
static final Monoid<Collection<String>> includedBuildsMonoid = Monoids.getCollectionMonoid()
static Monoid<BuildIntermediate> buildIntermediateMonoid = Monoids.of(SyntheticBuildIntermediate.getEmpty())
{ bi0, bi1 ->
new SyntheticBuildIntermediate(
BuildSpec.get(
name: "SyntheticBuildSpec(${ bi0.buildSpec.name }, ${ bi1.buildSpec.name })",
isAbstract: true,
extending: [],
buildClosure: { }
),
outputDirFunctionMonoid.concat.apply(bi0.outputDirFunction, bi1.outputDirFunction),
siteSpecMonoid.concat.apply(bi0.siteSpec, bi1.siteSpec),
globalsMonoid.concat.apply(bi0.globals, bi1.globals),
typesMonoid.concat.apply(bi0.types, bi1.types),
sourcesMonoid.concat.apply(bi0.sources, bi1.sources),
taskFactoriesMonoid.concat.apply(bi0.taskFactorySpecs, bi1.taskFactorySpecs),
includedBuildsMonoid.concat.apply(bi0.includedBuilds, bi1.includedBuilds)
)
}
private BuildMonoids() {}
}

View File

@ -1,7 +1,8 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
import groovy.transform.PackageScope
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
@ -9,70 +10,47 @@ import org.slf4j.MarkerFactory
import static java.util.Objects.requireNonNull
@SuppressWarnings('unused')
abstract class BuildScriptBase extends Script {
protected static final Logger logger = LoggerFactory.getLogger(BuildScriptBase)
protected static final Marker enter = MarkerFactory.getMarker('ENTER')
protected static final Marker exit = MarkerFactory.getMarker('EXIT')
static final Logger logger = LoggerFactory.getLogger(BuildScriptBase)
static final Marker enter = MarkerFactory.getMarker('ENTER')
static final Marker exit = MarkerFactory.getMarker('EXIT')
private static Collection<String> convertExtendingArg(Object arg) {
arg instanceof Collection<String> ? arg as Collection<String>
: arg instanceof String ? [arg] as Collection<String> : []
private String extending
private Closure buildClosure
private File projectRoot
File getProjectRoot() {
requireNonNull(this.projectRoot)
}
protected final Collection<BuildSpec> buildSpecs = []
/**
* args keys and values:
* <ul>
* <li><code>name: String<code></li>
* <li><code>extending?: String | Collection&lt;String&gt;</code></li>
* </ul>
*
* @param args
* @param buildClosure
*/
void abstractBuild(
Map<String, Object> args,
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
Closure<?> buildClosure
) {
final Collection<String> extending = convertExtendingArg(args.extending)
this.buildSpecs << BuildSpec.get(
name: requireNonNull(args.name as String),
isAbstract: true,
extending: extending,
buildClosure: buildClosure
)
void setProjectRoot(File projectRoot) {
this.projectRoot = requireNonNull(projectRoot)
}
/**
* args keys and values:
* <ul>
* <li><code>name: String<code></li>
* <li><code>extending?: String | Collection&lt;String&gt;</code></li>
* </ul>
*
* @param args
* @param buildClosure
*/
void build(
Map<String, Object> args,
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
Closure<?> buildClosure
) {
final Collection<String> extending = convertExtendingArg(args.extending)
this.buildSpecs << BuildSpec.get(
name: requireNonNull(args.name as String),
isAbstract: false,
extending: extending,
buildClosure: buildClosure
)
File file(String name) {
new File(this.projectRoot, name)
}
@PackageScope
Collection<BuildSpec> getBuildSpecs() {
this.buildSpecs
void build(@Nullable String extending, @DelegatesTo(value = BuildDelegate) Closure buildClosure) {
this.extending = extending
this.buildClosure = buildClosure
}
void build(@DelegatesTo(value = BuildDelegate) Closure buildClosure) {
this.extending = null
this.buildClosure = buildClosure
}
@ApiStatus.Internal
@Nullable String getExtending() {
this.extending
}
@ApiStatus.Internal
Closure getBuildClosure() {
this.buildClosure
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.buildscript
import java.util.function.Consumer
interface BuildScriptConfiguratorFactory {
Consumer<BuildScriptBase> get()
}

View File

@ -1,77 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.util.ExtensionUtil
import groovy.transform.NullCheck
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import org.codehaus.groovy.control.CompilerConfiguration
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import org.slf4j.MarkerFactory
import java.util.function.Consumer
@NullCheck
final class BuildScriptRunner {
private static final Logger logger = LoggerFactory.getLogger(BuildScriptRunner)
private static final Marker enter = MarkerFactory.getMarker('ENTER')
private static final Marker exit = MarkerFactory.getMarker('EXIT')
private static Collection<Build> runBase(BuildScriptBase base) {
base.run()
BuildSpecUtil.getBuilds(base.getBuildSpecs())
}
static Collection<Build> runClosureScript(
@DelegatesTo(value = BuildScriptBase, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.BuildScriptBase')
Closure<?> scriptBody
) {
def base = new BuildScriptBase() {
@Override
Object run() {
scriptBody.delegate = this
scriptBody.resolveStrategy = Closure.DELEGATE_FIRST
scriptBody.call(this)
}
}
runBase(base)
}
private final GroovyClassLoader buildScriptClassLoader
BuildScriptRunner(Collection<URL> classLoaderUrls) {
this.buildScriptClassLoader = new GroovyClassLoader(
Thread.currentThread().contextClassLoader,
new CompilerConfiguration().tap {
scriptBaseClass = BuildScriptBase.name
}
)
classLoaderUrls.each(this.buildScriptClassLoader::addURL)
}
GroovyClassLoader getBuildScriptClassLoader() {
this.buildScriptClassLoader
}
Collection<Build> runBuildScript(
String scriptName,
Map<String, Object> binding,
Consumer<BuildScriptBase> configureBuildScript
) {
logger.trace(enter, 'scriptName: {}, binding: {}', scriptName, binding)
Class<?> scriptClass = this.buildScriptClassLoader.loadClass(ExtensionUtil.stripExtension(scriptName))
def scriptObject = scriptClass.getConstructor().newInstance()
assert scriptObject instanceof BuildScriptBase
scriptObject.setBinding(new Binding(binding))
configureBuildScript.accept(scriptObject)
def result = runBase(scriptObject)
logger.trace(exit, 'result: {}', result)
result
}
}

View File

@ -1,57 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode(excludes = 'buildClosure')
final class BuildSpec {
static BuildSpec getEmpty() {
new BuildSpec('', false, [], { })
}
static BuildSpec get(Map<String, Object> args) {
new BuildSpec(
args.name as String ?: '',
args.isAbstract as boolean ?: false,
args.extending as Collection<String> ?: [],
args.buildClosure as Closure<?> ?: { }
)
}
static BuildSpec get(
String name,
boolean isAbstract,
Collection<String> extending,
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
Closure<?> buildClosure
) {
new BuildSpec(name, isAbstract, extending, buildClosure)
}
final String name
final boolean isAbstract
final Collection<String> extending
final Closure<?> buildClosure
private BuildSpec(
String name,
boolean isAbstract,
Collection<String> extending,
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
Closure<?> buildClosure
) {
this.name = name
this.isAbstract = isAbstract
this.extending = extending
this.buildClosure = buildClosure
}
@Override
String toString() {
"BuildSpec(name: ${ this.name }, isAbstract: ${ this.isAbstract }, extending: ${ this.extending })"
}
}

View File

@ -1,90 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
import com.jessebrault.ssg.util.Monoid
import groovy.transform.NullCheck
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import org.slf4j.MarkerFactory
import java.util.function.BiFunction
@NullCheck
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 Build intermediateToBuild(BuildIntermediate intermediate) {
Build.get(
name: intermediate.buildSpec.name,
outputDirFunction: intermediate.outputDirFunction,
siteSpec: intermediate.siteSpec,
globals: intermediate.globals,
taskFactorySpecs: intermediate.taskFactorySpecs,
includedBuilds: intermediate.includedBuilds
)
}
private static BuildIntermediate specToIntermediate(BuildSpec buildSpec, BuildIntermediate parent) {
def d = new BuildDelegate()
buildSpec.buildClosure.delegate = d
buildSpec.buildClosure.resolveStrategy = Closure.DELEGATE_FIRST
buildSpec.buildClosure()
new BuildDelegate.BuildDelegateBuildIntermediate(
buildSpec,
d,
parent,
BuildMonoids.siteSpecMonoid,
BuildMonoids.globalsMonoid,
BuildMonoids.typesMonoid,
BuildMonoids.sourcesMonoid,
BuildMonoids.taskFactoriesMonoid,
BuildMonoids.includedBuildsMonoid
)
}
private static BuildIntermediate reduceWithParents(
BuildSpec rootSpec,
BuildGraph buildGraph,
Monoid<BuildIntermediate> buildIntermediateMonoid,
BiFunction<BuildIntermediate, BuildSpec, BuildIntermediate> parentAndSpecToIntermediate
) {
final List<BuildSpec> parents = buildGraph.getParents(rootSpec)
if (parents.size() == 0) {
parentAndSpecToIntermediate.apply(buildIntermediateMonoid.zero, rootSpec)
} else {
final List<BuildIntermediate> parentIntermediates = parents.collect {
reduceWithParents(it, buildGraph, buildIntermediateMonoid, parentAndSpecToIntermediate)
}
final BuildIntermediate parentsReduced = parentIntermediates.inject(buildIntermediateMonoid.zero)
{ acc, bi ->
buildIntermediateMonoid.concat.apply(acc, bi)
}
parentAndSpecToIntermediate.apply(parentsReduced, rootSpec)
}
}
static Collection<Build> getBuilds(Collection<BuildSpec> buildSpecs) {
logger.trace(enter, 'buildSpecs: {}', buildSpecs)
def graph = new BuildGraph(buildSpecs)
def intermediates = buildSpecs.findResults {
if (it.isAbstract) {
null
} else {
reduceWithParents(it, graph, BuildMonoids.buildIntermediateMonoid)
{ parent, spec ->
specToIntermediate(spec, parent)
}
}
}
def builds = intermediates.collect { intermediateToBuild(it) }
logger.trace(exit, 'builds: {}', builds)
builds
}
private BuildSpecUtil() {}
}

View File

@ -1,24 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.util.Diagnostic
final class BuildUtil {
static Collection<Diagnostic> diagnoseIncludedBuilds(Collection<Build> allBuilds) {
allBuilds.inject([] as Collection<Diagnostic>) { allDiagnostics, build ->
allDiagnostics + build.includedBuilds.inject([] as Collection<Diagnostic>) { buildDiagnostics, includedBuildName ->
def includedBuild = allBuilds.find { it.name == includedBuildName }
if (includedBuild == null) {
buildDiagnostics + new Diagnostic(
"The includedBuild ${ includedBuildName } is not in the collection of allBuilds"
)
} else {
buildDiagnostics
}
}
}
}
private BuildUtil() {}
}

View File

@ -1,71 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.html.PageToHtmlSpecProviders
import com.jessebrault.ssg.html.PageToHtmlTaskFactory
import com.jessebrault.ssg.html.TextToHtmlSpecProviders
import com.jessebrault.ssg.html.TextToHtmlTaskFactory
import com.jessebrault.ssg.page.PageTypes
import com.jessebrault.ssg.page.PagesProviders
import com.jessebrault.ssg.part.PartTypes
import com.jessebrault.ssg.part.PartsProviders
import com.jessebrault.ssg.template.TemplateTypes
import com.jessebrault.ssg.template.TemplatesProviders
import com.jessebrault.ssg.text.TextTypes
import com.jessebrault.ssg.text.TextsProviders
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import java.util.function.Consumer
import java.util.function.Supplier
@TupleConstructor(includeFields = true, defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
final class DefaultBuildScriptConfiguratorFactory implements BuildScriptConfiguratorFactory {
private final File baseDir
private final Supplier<ClassLoader> classLoaderSupplier
@Override
Consumer<BuildScriptBase> get() {
return {
it.build(name: 'default') {
outputDirFunction = { Build build ->
new OutputDir(new File(this.baseDir, build.name == 'default' ? 'build' : build.name))
}
types {
textTypes << TextTypes.MARKDOWN
pageTypes << PageTypes.getGsp(['.gsp', '.ssg.gst'], this.classLoaderSupplier.get())
templateTypes << TemplateTypes.getGsp(['.gsp', '.ssg.gst'], this.classLoaderSupplier.get())
partTypes << PartTypes.getGsp(['.gsp', '.ssg.gst'], this.classLoaderSupplier.get())
}
sources { base, types ->
texts TextsProviders.from(new File(this.baseDir, 'texts'), types.textTypes)
pages PagesProviders.from(new File(this.baseDir, 'pages'), types.pageTypes)
templates TemplatesProviders.from(new File(this.baseDir, 'templates'), types.templateTypes)
parts PartsProviders.from(new File(this.baseDir, 'parts'), types.partTypes)
}
taskFactories { base, sources ->
register('textToHtml', TextToHtmlTaskFactory::new) {
it.specsProvider += TextToHtmlSpecProviders.from(sources)
it.allTextsProvider += sources.textsProvider
it.allPartsProvider += sources.partsProvider
it.allModelsProvider += sources.modelsProvider
}
register('pageToHtml', PageToHtmlTaskFactory::new) {
it.specsProvider += PageToHtmlSpecProviders.from(sources.pagesProvider)
it.allTextsProvider += sources.textsProvider
it.allPartsProvider += sources.partsProvider
it.allModelsProvider += sources.modelsProvider
}
}
}
}
}
}

View File

@ -0,0 +1,20 @@
package com.jessebrault.ssg.buildscript
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@NullCheck
@TupleConstructor(includeFields = true)
final class FileBuildScriptGetter {
private final File projectRoot
private final GroovyClassLoader gcl
BuildScriptBase getBuildInfo(String name) {
Class<?> scriptClass = this.gcl.loadClass(name, true, false)
def scriptObject = scriptClass.getConstructor().newInstance()
assert scriptObject instanceof BuildScriptBase
scriptObject
}
}

View File

@ -1,41 +0,0 @@
package com.jessebrault.ssg.buildscript
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode
final class OutputDir {
private final String path
OutputDir(String path) {
this.path = path
}
OutputDir(File file) {
this(file.path)
}
File asFile() {
new File(this.path)
}
String asString() {
this.path
}
Object asType(Class<?> clazz) {
switch (clazz) {
case File -> this.asFile()
case String -> this.asString()
default -> throw new IllegalArgumentException('cannot cast to a class other than File or String')
}
}
@Override
String toString() {
"OutputDir(path: ${ this.path })"
}
}

View File

@ -1,31 +0,0 @@
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
) {
f0 == OutputDirFunctions.DEFAULT ? f1 : f0
}
static Function<Build, OutputDir> of(File dir) {
return { new OutputDir(dir) }
}
static Function<Build, OutputDir> of(String path) {
return { new OutputDir(path) }
}
private OutputDirFunctions() {}
}

View File

@ -1,107 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.page.Page
import com.jessebrault.ssg.part.Part
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
@NullCheck
@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,
sp0.modelsProvider + sp1.modelsProvider,
sp0.pagesProvider + sp1.pagesProvider,
sp0.templatesProvider + sp1.templatesProvider,
sp0.partsProvider + sp1.partsProvider,
sp0.custom + sp1.custom
)
}
static SourceProviders get(Map<String, Object> args) {
new SourceProviders(
args.textsProvider as CollectionProvider<Text>
?: CollectionProviders.getEmpty() as CollectionProvider<Text>,
args.modelsProvider as CollectionProvider<Model<Object>>
?: CollectionProviders.getEmpty() as CollectionProvider<Model<Object>>,
args.pagesProvider as CollectionProvider<Page>
?: CollectionProviders.getEmpty() as CollectionProvider<Page>,
args.templatesProvider as CollectionProvider<Template>
?: CollectionProviders.getEmpty() as CollectionProvider<Template>,
args.partsProvider as CollectionProvider<Part>
?: CollectionProviders.getEmpty() as CollectionProvider<Part>,
args.custom as Map<String, CollectionProvider<?>> ?: [:]
)
}
static SourceProviders getEmpty() {
new SourceProviders(
CollectionProviders.getEmpty(),
CollectionProviders.getEmpty(),
CollectionProviders.getEmpty(),
CollectionProviders.getEmpty(),
CollectionProviders.getEmpty(),
[:]
)
}
final CollectionProvider<Text> textsProvider
final CollectionProvider<Model<Object>> modelsProvider
final CollectionProvider<Page> pagesProvider
final CollectionProvider<Template> templatesProvider
final CollectionProvider<Part> partsProvider
private final Map<String, CollectionProvider<Object>> custom
SourceProviders(
CollectionProvider<Text> textsProvider,
CollectionProvider<Model<Object>> modelsProvider,
CollectionProvider<Page> pagesProvider,
CollectionProvider<Template> templatesProvider,
CollectionProvider<Part> partsProvider,
Map<String, CollectionProvider<Object>> custom
) {
this.textsProvider = textsProvider
this.modelsProvider = modelsProvider
this.pagesProvider = pagesProvider
this.templatesProvider = templatesProvider
this.partsProvider = partsProvider
this.custom = custom
}
SourceProviders plus(SourceProviders other) {
concat(this, other)
}
def <T> CollectionProvider<T> getCustom(String name, Class<T> tClass) {
this.custom.get(name) as CollectionProvider<T>
}
void putCustom(String name, CollectionProvider<?> customProvider) {
this.custom.put(name, customProvider as CollectionProvider<Object>)
}
Map<String, CollectionProvider<Object>> getAllCustom() {
this.custom
}
@Override
String toString() {
"SourceProviders(textsProvider: ${ this.textsProvider }, modelsProvider: ${ this.modelsProvider }, " +
"pagesProvider: ${ this.pagesProvider }, templatesProvider: ${ this.templatesProvider }, " +
"partsProvider: ${ this.partsProvider })"
}
}

View File

@ -1,74 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.task.TaskFactory
import com.jessebrault.ssg.task.TaskFactorySpec
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import java.util.function.Function
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class SyntheticBuildIntermediate implements BuildIntermediate {
static BuildIntermediate getEmpty() {
new SyntheticBuildIntermediate(
BuildSpec.getEmpty(),
OutputDirFunctions.DEFAULT,
SiteSpec.getBlank(),
[:],
TypesContainer.getEmpty(),
SourceProviders.getEmpty(),
[],
[]
)
}
static BuildIntermediate get(Map<String, Object> args) {
new SyntheticBuildIntermediate(
args.buildSpec as BuildSpec ?: BuildSpec.getEmpty(),
args.outputDirFunction as Function<Build, OutputDir> ?: OutputDirFunctions.DEFAULT,
args.siteSpec as SiteSpec ?: SiteSpec.getBlank(),
args.globals as Map<String, Object> ?: [:],
args.types as TypesContainer ?: TypesContainer.getEmpty(),
args.sources as SourceProviders ?: SourceProviders.getEmpty(),
args.taskFactorySpecs as Collection<TaskFactorySpec<TaskFactory>> ?: [],
args.includedBuilds as Collection<String> ?: []
)
}
static BuildIntermediate get(
BuildSpec buildSpec,
Function<Build, OutputDir> outputDirFunction,
SiteSpec siteSpec,
Map<String, Object> globals,
TypesContainer types,
SourceProviders sources,
Collection<TaskFactorySpec<TaskFactory>> taskFactorySpecs,
Collection<String> includedBuilds
) {
new SyntheticBuildIntermediate(
buildSpec,
outputDirFunction,
siteSpec,
globals,
types,
sources,
taskFactorySpecs,
includedBuilds
)
}
final BuildSpec buildSpec
final Function<Build, OutputDir> outputDirFunction
final SiteSpec siteSpec
final Map<String, Object> globals
final TypesContainer types
final SourceProviders sources
final Collection<TaskFactorySpec<TaskFactory>> taskFactorySpecs
final Collection<String> includedBuilds
}

View File

@ -1,57 +0,0 @@
package com.jessebrault.ssg.buildscript
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
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@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,
tc0.pageTypes + tc1.pageTypes,
tc0.templateTypes + tc1.templateTypes,
tc0.partTypes + tc1.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

@ -1,283 +1,103 @@
package com.jessebrault.ssg.buildscript.delegates
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.buildscript.*
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 com.jessebrault.ssg.model.Model
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.UnaryOperator
import groowt.util.fp.property.Property
import groowt.util.fp.provider.DefaultNamedProvider
import groowt.util.fp.provider.DefaultNamedSetProvider
import groowt.util.fp.provider.DefaultSetProvider
import groowt.util.fp.provider.NamedProvider
import groowt.util.fp.provider.NamedSetProvider
import groowt.util.fp.provider.Provider
import groowt.util.fp.provider.SetProvider
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
final class BuildDelegate {
@TupleConstructor(includeFields = true, defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
static final class BuildDelegateBuildIntermediate implements BuildIntermediate {
final Property<String> siteName = Property.empty().tap {
convention = 'An Ssg Site'
}
private final BuildSpec buildSpec
private final BuildDelegate delegate
private final BuildIntermediate parent
final Property<String> baseUrl = Property.empty().tap {
convention = ''
}
private final Monoid<SiteSpec> siteSpecMonoid
private final Monoid<Map<String, Object>> globalsMonoid
private final Monoid<TypesContainer> typesMonoid
private final Monoid<SourceProviders> sourcesMonoid
private final Monoid<Collection<TaskFactorySpec<TaskFactory>>> taskFactoriesMonoid
private final Monoid<Collection<String>> includedBuildsMonoid
final Property<File> outputDir = Property.empty().tap {
convention = siteName.map { it.replace(' ', '-').toLowerCase() + '-build' }
}
@Override
BuildSpec getBuildSpec() {
this.buildSpec
final Property<Map<String, Object>> globals = Property.empty().tap {
convention = [:]
}
private final Set<Provider<File>> textsDirs = []
private final Set<NamedProvider<Model>> models = []
void siteName(String siteName) {
this.siteName.set(siteName)
}
void siteName(Provider<String> siteNameProvider) {
this.siteName.set(siteNameProvider)
}
void baseUrl(String baseUrl) {
this.baseUrl.set(baseUrl)
}
void baseUrl(Provider<String> baseUrlProvider) {
this.baseUrl.set(baseUrlProvider)
}
void outputDir(File outputDir) {
this.outputDir.set(outputDir)
}
void outputDir(Provider<File> outputDirProvider) {
this.outputDir.set(outputDirProvider)
}
void globals(@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST) Closure<?> globalsClosure) {
def globalsDelegate = new GlobalsDelegate()
globalsClosure.delegate = globalsDelegate
globalsClosure.resolveStrategy = Closure.DELEGATE_FIRST
globalsClosure()
this.globals.set {
this.globals.get() + globalsDelegate
}
@Override
Function<Build, OutputDir> getOutputDirFunction() {
this.delegate.outputDirFunction.getOrElse {
this.delegate.outputDirFunctionMapper.match({ parent.outputDirFunction }) {
it.apply(this.parent.outputDirFunction)
}
}
}
@Override
SiteSpec getSiteSpec() {
def siteSpecClosure = this.delegate.siteSpecClosure.getOrElse { return { } }
def d = new SiteSpecDelegate(this.siteSpecMonoid)
siteSpecClosure.delegate = d
siteSpecClosure.resolveStrategy = Closure.DELEGATE_FIRST
siteSpecClosure(this.parent.siteSpec)
if (this.delegate.siteSpecConcatBase.getOrElse { true }) {
this.siteSpecMonoid.concat.apply(this.parent.siteSpec, d.result)
} else {
d.result
}
}
@Override
Map<String, Object> getGlobals() {
def globalsClosure = this.delegate.globalsClosure.getOrElse { return { } }
def d = new GlobalsDelegate()
globalsClosure.delegate = d
globalsClosure.resolveStrategy = Closure.DELEGATE_FIRST
globalsClosure(this.parent.globals)
if (this.delegate.globalsConcatBase.getOrElse { true }) {
this.globalsMonoid.concat.apply(this.parent.globals, d.getResult())
} else {
d.getResult()
}
}
@Override
TypesContainer getTypes() {
def typesClosure = this.delegate.typesClosure.getOrElse { return { } }
def d = new TypesDelegate()
typesClosure.delegate = d
typesClosure.resolveStrategy = Closure.DELEGATE_FIRST
typesClosure(this.parent.types)
if (this.delegate.typesConcatBase.getOrElse { true }) {
this.typesMonoid.concat.apply(this.parent.types, d.result)
} else {
d.result
}
}
@Override
SourceProviders getSources() {
def sourcesClosure = this.delegate.sourcesClosure.getOrElse { return { base, types -> } }
def d = new SourceProvidersDelegate()
sourcesClosure.delegate = d
sourcesClosure.resolveStrategy = Closure.DELEGATE_FIRST
sourcesClosure(this.parent.sources, this.types)
if (this.delegate.sourcesConcatBase.getOrElse { true }) {
this.sourcesMonoid.concat.apply(this.parent.sources, d.result)
} else {
d.result
}
}
@Override
Collection<TaskFactorySpec<TaskFactory>> getTaskFactorySpecs() {
def taskFactoriesClosure = this.delegate.taskFactoriesClosure.getOrElse { return { base, sources -> } }
def d = new TaskFactoriesDelegate()
taskFactoriesClosure.delegate = d
taskFactoriesClosure.resolveStrategy = Closure.DELEGATE_FIRST
taskFactoriesClosure(this.parent.taskFactorySpecs, this.sources)
if (this.delegate.taskFactoriesConcatBase.getOrElse { true }) {
this.taskFactoriesMonoid.concat.apply(this.parent.taskFactorySpecs, d.result)
} else {
d.result
}
}
@Override
Collection<String> getIncludedBuilds() {
if (this.delegate.includedBuildsConcatBase.getOrElse { true }) {
this.includedBuildsMonoid.concat.apply(this.parent.includedBuilds, this.delegate.includedBuilds)
} else {
this.delegate.includedBuilds
}
}
}
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()
private final Mutable<Boolean> includedBuildsConcatBase = Mutables.getEmpty()
private final Collection<String> includedBuilds = []
void setOutputDirFunction(Function<Build, OutputDir> outputDirFunction) {
this.outputDirFunction.set(outputDirFunction)
void textsDir(File textsDir) {
this.textsDirs.add { textsDir }
}
void setOutputDir(File file) {
this.outputDirFunction.set { new OutputDir(file) }
void textsDir(Provider<File> textsDirProvider) {
this.textsDirs << textsDirProvider
}
void setOutputDir(String path) {
this.outputDirFunction.set { new OutputDir(path) }
void textsDirs(Set<Provider<File>> textsDirProviders) {
textsDirProviders.each { this.textsDir(it) }
}
// Maps the *base*
void outputDirFunction(UnaryOperator<Function<Build, OutputDir>> mapper) {
this.outputDirFunctionMapper.set(mapper)
SetProvider<File> getTextsDirs() {
new DefaultSetProvider(this.textsDirs)
}
void siteSpec(
@DelegatesTo(value = SiteSpecDelegate, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.SiteSpec')
Closure<?> siteSpecClosure
) {
this.siteSpec(true, siteSpecClosure)
void models(@DelegatesTo(ModelsDelegate) Closure modelsClosure) {
def modelsDelegate = new ModelsDelegate()
modelsClosure.delegate = modelsDelegate
modelsClosure()
models.addAll(modelsDelegate.result)
}
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 models(Set<NamedProvider<Model>> models) {
this.models.addAll(models)
}
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)
}
void setConcatIncludedBuildsWithBase(boolean value) {
this.includedBuildsConcatBase.set(value)
}
void includeBuild(String buildName) {
this.includedBuilds << buildName
NamedSetProvider<Model> getModels() {
new DefaultNamedSetProvider(this.models)
}
}

View File

@ -0,0 +1,49 @@
package com.jessebrault.ssg.buildscript.delegates
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.model.Models
import groowt.util.fp.provider.DefaultNamedProvider
import groowt.util.fp.provider.NamedProvider
import groowt.util.fp.provider.Provider
import org.jetbrains.annotations.ApiStatus
import java.util.function.Supplier
final class ModelsDelegate {
private final Map<String, Provider<Model>> modelsMap = [:]
void add(String name, Object modelObject) {
this.modelsMap.put(name) { Models.of(name, modelObject ) }
}
void add(Model model) {
this.modelsMap.put(model.name) { model }
}
void add(NamedProvider<Model> namedModelProvider) {
this.modelsMap.put(namedModelProvider.name, namedModelProvider)
}
void add(String name, Provider<Model> modelProvider) {
this.modelsMap.put(name, modelProvider)
}
void add(String name, Supplier objectSupplier) {
this.modelsMap.put(name) { Models.ofSupplier(name, objectSupplier) }
}
void addAll(Map<String, Object> namesAndModels) {
namesAndModels.each { name, modelObject ->
this.add(name, modelObject)
}
}
@ApiStatus.Internal
Set<NamedProvider<Model>> getResult() {
this.modelsMap.inject([] as Set<NamedProvider<Model>>) { acc, name, modelProvider ->
acc << new DefaultNamedProvider(name, modelProvider)
}
}
}

View File

@ -1,38 +0,0 @@
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,61 +0,0 @@
package com.jessebrault.ssg.buildscript.delegates
import com.jessebrault.ssg.buildscript.SourceProviders
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.page.Page
import com.jessebrault.ssg.part.Part
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()
private CollectionProvider<Model<Object>> modelsProvider = CollectionProviders.getEmpty()
private CollectionProvider<Page> pagesProvider = CollectionProviders.getEmpty()
private CollectionProvider<Template> templatesProvider = CollectionProviders.getEmpty()
private CollectionProvider<Part> partsProvider = CollectionProviders.getEmpty()
private final Map<String, CollectionProvider<Object>> custom = [:]
void texts(CollectionProvider<Text> textsProvider) {
this.textsProvider += textsProvider
}
void models(CollectionProvider<Model<?>> modelsProvider) {
this.modelsProvider += modelsProvider
}
void pages(CollectionProvider<Page> pagesProvider) {
this.pagesProvider += pagesProvider
}
void templates(CollectionProvider<Template> templatesProvider) {
this.templatesProvider += templatesProvider
}
void parts(CollectionProvider<Part> partsProvider) {
this.partsProvider += partsProvider
}
void custom(String name, CollectionProvider<?> customProvider) {
this.custom.put(name, customProvider as CollectionProvider<Object>)
}
SourceProviders getResult() {
new SourceProviders(
this.textsProvider,
this.modelsProvider,
this.pagesProvider,
this.templatesProvider,
this.partsProvider,
this.custom
)
}
}

View File

@ -1,66 +0,0 @@
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 Collection<TaskFactorySpec<TaskFactory>> specs = []
private boolean isRegistered(String name) {
this.specs.find { it.name == name } != null
}
private void checkNotRegistered(String 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 << new TaskFactorySpec<>(name, factorySupplier, [])
}
def <T extends TaskFactory> void register(
String name,
Supplier<T> factorySupplier,
Consumer<T> factoryConfigurator
) {
this.checkNotRegistered(name)
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> factoryConfigurator
) {
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.
spec.configurators << (factoryConfigurator as Consumer<TaskFactory>)
}
Collection<TaskFactorySpec<TaskFactory>> getResult() {
this.specs
}
}

View File

@ -1,24 +0,0 @@
package com.jessebrault.ssg.buildscript.delegates
import com.jessebrault.ssg.buildscript.TypesContainer
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 groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode
final class TypesDelegate {
final Collection<TextType> textTypes = []
final Collection<PageType> pageTypes = []
final Collection<TemplateType> templateTypes = []
final Collection<PartType> partTypes = []
TypesContainer getResult() {
new TypesContainer(this.textTypes, this.pageTypes, this.templateTypes, this.partTypes)
}
}

View File

@ -1,5 +1,8 @@
package com.jessebrault.ssg.model
import groowt.util.fp.provider.NamedProvider
import groowt.util.fp.provider.Provider
import java.util.function.Supplier
final class Models {
@ -8,10 +11,18 @@ final class Models {
new SimpleModel<>(name, t)
}
static <T> Model<T> fromSupplier(String name, Supplier<T> tClosure) {
static <T> Model<T> ofSupplier(String name, Supplier<? extends T> tClosure) {
new SupplierBasedModel<>(name, tClosure)
}
static <T> Model<T> ofProvider(String name, Provider<? extends T> modelProvider) {
new ProviderModel<T>(name, modelProvider)
}
static <T> Model<T> ofNamedProvider(NamedProvider<? extends T> namedModelProvider) {
new ProviderModel<T>(namedModelProvider.name, namedModelProvider)
}
private Models() {}
}

View File

@ -1,28 +1,27 @@
package com.jessebrault.ssg.provider
package com.jessebrault.ssg.model
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.PackageScope
import groovy.transform.TupleConstructor
import java.util.function.Supplier
import groowt.util.fp.provider.Provider
@PackageScope
@TupleConstructor(includeFields = true, defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
final class SupplierBasedProvider<T> extends AbstractProvider<T> {
@EqualsAndHashCode
class ProviderModel<T> implements Model<T> {
private final Supplier<T> supplier
final String name
private final Provider<T> modelProvider
@Override
T provide() {
this.supplier.get()
T get() {
this.modelProvider.get()
}
@Override
String toString() {
"SupplierBasedProvider(supplier: ${ this.supplier })"
"ProviderModel($this.name)"
}
}

View File

@ -8,13 +8,17 @@ import groovy.transform.TupleConstructor
import java.util.function.Supplier
@PackageScope
@TupleConstructor(includeFields = true, defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
final class SupplierBasedModel<T> implements Model<T> {
final String name
private final Supplier<T> supplier
private final Supplier<? extends T> supplier
SupplierBasedModel(String name, Supplier<? extends T> supplier) {
this.name = name
this.supplier = supplier
}
@Override
T get() {

View File

@ -6,6 +6,7 @@ import java.util.Optional;
import java.util.function.*;
@ApiStatus.Experimental
@Deprecated
public interface Mutable<T> {
T get();
void set(T t);

View File

@ -1,11 +0,0 @@
package com.jessebrault.ssg.property
final class Properties {
static <T> Property<T> get() {
new SimpleProperty<>()
}
private Properties() {}
}

View File

@ -1,38 +0,0 @@
package com.jessebrault.ssg.property
import com.jessebrault.ssg.provider.Provider
import java.util.function.Function
import java.util.function.UnaryOperator
interface Property<T> extends Provider<T> {
void set(T t)
void set(Provider<T> provider)
void unset()
default void leftShift(T t) {
this.set(t)
}
default void leftShift(Provider<T> provider) {
this.set(provider)
}
T getConvention()
void setConvention(T t)
void setConvention(Provider<T> provider)
void unsetConvention()
void map(UnaryOperator<T> operator)
void flatMap(Function<T, Provider<T>> function)
default void rightShift(UnaryOperator<T> operator) {
this.map(operator)
}
default void rightShift(Function<T, Provider<T>> function) {
this.flatMap(function)
}
}

View File

@ -1,82 +0,0 @@
package com.jessebrault.ssg.property
import com.jessebrault.ssg.provider.AbstractProvider
import com.jessebrault.ssg.provider.Provider
import com.jessebrault.ssg.provider.Providers
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.PackageScope
import java.util.function.Function
import java.util.function.UnaryOperator
@PackageScope
@NullCheck
@EqualsAndHashCode(includeFields = true)
final class SimpleProperty<T> extends AbstractProvider<T> implements Property<T> {
private Provider<T> convention = Providers.getEmpty()
private Provider<T> tProvider = Providers.getEmpty()
@Override
T provide() {
this.tProvider.present ? this.tProvider.provide() : this.convention.provide()
}
@Override
void set(T t) {
this.tProvider = Providers.of(t)
}
@Override
void set(Provider<T> provider) {
this.tProvider = provider
}
@Override
void unset() {
this.tProvider = Providers.getEmpty()
}
@Override
T getConvention() {
this.convention.provide()
}
@Override
void setConvention(T t) {
this.convention = Providers.of(t)
}
@Override
void setConvention(Provider<T> provider) {
this.convention = provider
}
@Override
void unsetConvention() {
this.convention = Providers.getEmpty()
}
@Override
void map(UnaryOperator<T> operator) {
def oldTProvider = this.tProvider
this.tProvider = Providers.fromSupplier {
operator.apply(oldTProvider.provide())
}
}
@Override
void flatMap(Function<T, Provider<T>> function) {
def oldTProvider = this.tProvider
this.tProvider = Providers.fromSupplier {
function.apply(oldTProvider.provide()).provide()
}
}
@Override
String toString() {
"SimpleProperty(convention: ${ this.convention }, tProvider: ${ this.tProvider })"
}
}

View File

@ -9,31 +9,16 @@ import groovy.transform.TupleConstructor
@EqualsAndHashCode(includeFields = true)
abstract class AbstractCollectionProvider<T> implements CollectionProvider<T> {
static <T> CollectionProvider<T> concat(
CollectionProvider<T> cp,
Provider<T> p
) {
new SupplierBasedCollectionProvider<>([cp], [p], {
[*cp.provide(), p.provide()]
})
}
static <T> CollectionProvider<T> concat(
CollectionProvider<T> cp0,
CollectionProvider<T> cp1
) {
new SupplierBasedCollectionProvider<>([cp0, cp1], [], {
new SupplierBasedCollectionProvider<>([cp0, cp1], {
cp0.provide() + cp1.provide()
})
}
private final Collection<CollectionProvider<T>> collectionProviderChildren
private final Collection<Provider<T>> providerChildren
@Override
boolean contains(Provider<T> provider) {
provider in this
}
@Override
boolean contains(CollectionProvider<T> collectionProvider) {
@ -68,36 +53,11 @@ abstract class AbstractCollectionProvider<T> implements CollectionProvider<T> {
this.getChildrenOfType(DirectoryCollectionProvider)
}
@Override
CollectionProvider<T> plus(Provider<T> other) {
concat(this, other)
}
@Override
CollectionProvider<T> plus(CollectionProvider<T> other) {
concat(this, other)
}
private boolean searchProviderChildrenFor(Provider<T> descendant) {
this.providerChildren.inject(false) { acc, childProvider ->
acc || descendant in childProvider
}
}
private boolean searchCollectionProviderChildrenFor(Provider<T> descendant) {
this.collectionProviderChildren.inject(false) { acc, childProvider ->
acc || descendant in childProvider
}
}
@Override
boolean isCase(Provider<T> provider) {
provider in this.providerChildren
|| this.searchProviderChildrenFor(provider)
|| this.searchCollectionProviderChildrenFor(provider)
}
@Override
boolean isCase(CollectionProvider<T> collectionProvider) {
collectionProvider == this

View File

@ -1,40 +0,0 @@
package com.jessebrault.ssg.provider
abstract class AbstractProvider<T> implements Provider<T> {
static <T> CollectionProvider<T> concat(
Provider<T> p0,
Provider<T> p1
) {
new SupplierBasedCollectionProvider<>([], [p0, p1], {
[p0.provide(), p1.provide()]
})
}
static <T> CollectionProvider<T> concat(
Provider<T> p0,
CollectionProvider<T> p1
) {
new SupplierBasedCollectionProvider<>([p1], [p0], {
[p0.provide(), *p1.provide()]
})
}
@Override
CollectionProvider<T> plus(Provider<T> other) {
concat(this, other)
}
@Override
CollectionProvider<T> plus(CollectionProvider<T> other) {
concat(this, other)
}
@Override
CollectionProvider<T> asType(Class<CollectionProvider> collectionProviderClass) {
new SupplierBasedCollectionProvider<>({
[this.provide() as T]
})
}
}

View File

@ -7,7 +7,6 @@ import java.util.Collection;
public interface CollectionProvider<T> {
Collection<T> provide();
boolean contains(Provider<T> provider);
boolean contains(CollectionProvider<T> collectionProvider);
@ApiStatus.Experimental
@ -18,9 +17,7 @@ public interface CollectionProvider<T> {
Collection<DirectoryCollectionProvider<T>> getDirectoryCollectionProviderChildren();
CollectionProvider<T> plus(Provider<T> other);
CollectionProvider<T> plus(CollectionProvider<T> other);
boolean isCase(Provider<T> provider);
boolean isCase(CollectionProvider<T> collectionProvider);
}
}

View File

@ -1,41 +0,0 @@
package com.jessebrault.ssg.provider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.PackageScope
@PackageScope
@NullCheck
@EqualsAndHashCode
final class EmptyProvider<T> implements Provider<T> {
@Override
T provide() {
throw new NullPointerException('EmptyProvider cannot provide a value.')
}
@Override
CollectionProvider<T> plus(CollectionProvider<T> other) {
CollectionProviders.fromSupplier {
other.provide()
}
}
@Override
CollectionProvider<T> plus(Provider<T> other) {
CollectionProviders.fromSupplier {
[other.provide()] as Collection<T>
}
}
@Override
CollectionProvider<T> asType(Class<CollectionProvider> collectionProviderClass) {
CollectionProviders.fromCollection([]) as CollectionProvider<T>
}
@Override
String toString() {
"EmptyProvider()"
}
}

View File

@ -1,8 +0,0 @@
package com.jessebrault.ssg.provider
interface Provider<T> {
T provide()
CollectionProvider<T> plus(Provider<T> other)
CollectionProvider<T> plus(CollectionProvider<T> other)
CollectionProvider<T> asType(Class<CollectionProvider> collectionProviderClass)
}

View File

@ -1,34 +0,0 @@
package com.jessebrault.ssg.provider
import groovy.transform.NullCheck
import java.util.function.Supplier
@NullCheck
final class Providers {
static <T> Provider<T> getEmpty() {
new EmptyProvider<>()
}
static <T> Provider<T> of(T t) {
new SimpleProvider<>(t)
}
static <T> Provider<T> fromSupplier(Supplier<T> supplier) {
new SupplierBasedProvider<>(supplier)
}
static <T> CollectionProvider<T> concat(Provider<T> ...providers) {
concat(List.of(providers))
}
static <T> CollectionProvider<T> concat(Collection<Provider<T>> providers) {
providers.inject(CollectionProviders.<T>getEmpty()) { acc, val ->
acc + val
}
}
private Providers() {}
}

View File

@ -1,26 +0,0 @@
package com.jessebrault.ssg.provider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.PackageScope
import groovy.transform.TupleConstructor
@PackageScope
@TupleConstructor(defaults = false, includeFields = true)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
final class SimpleProvider<T> extends AbstractProvider<T> {
private final T t
@Override
T provide() {
this.t
}
@Override
String toString() {
"SimpleProvider(t: ${ this.t })"
}
}

View File

@ -15,15 +15,14 @@ final class SupplierBasedCollectionProvider<T> extends AbstractCollectionProvide
SupplierBasedCollectionProvider(
Collection<CollectionProvider<T>> collectionProviderChildren,
Collection<Provider<T>> providerChildren,
Supplier<Collection<T>> supplier
) {
super(collectionProviderChildren, providerChildren)
super(collectionProviderChildren)
this.supplier = supplier
}
SupplierBasedCollectionProvider(Supplier<Collection<T>> supplier) {
this([], [], supplier)
this([], supplier)
}
@Override

View File

@ -2,6 +2,7 @@ package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.SiteSpec
import groovy.transform.NullCheck
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
@ -10,7 +11,6 @@ import org.mockito.junit.jupiter.MockitoExtension
import java.util.function.Consumer
import java.util.function.Function
import static BuildScriptRunner.runClosureScript
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.mockito.Mockito.verify
@ -52,7 +52,7 @@ final class BuildScriptsTests {
Map<String, Object> binding = [:],
Consumer<BuildScriptBase> configureBase = { }
) {
new BuildScriptRunner(urls).runBuildScript(scriptName, binding, configureBase)
new FileBuildScriptGetter(urls).getBuildInfo(scriptName, binding, configureBase)
}
@Test
@ -164,6 +164,8 @@ final class BuildScriptsTests {
}
@Test
@Disabled
@Deprecated(forRemoval = true)
void oneBuildWithAbstractParent() {
def r = runClosureScript {
abstractBuild(name: 'parent') {

View File

@ -20,11 +20,6 @@ abstract class AbstractCollectionProviderTests {
protected static final Logger logger = LoggerFactory.getLogger(AbstractCollectionProviderTests)
@SuppressWarnings('GrMethodMayBeStatic')
protected Provider<Integer> getProvider(Integer t) {
Providers.of(t)
}
protected abstract CollectionProvider<Integer> getCollectionProvider(Collection<Integer> ts)
@Test
@ -75,12 +70,10 @@ abstract class AbstractCollectionProviderTests {
@TestFactory
Collection<DynamicTest> containsAndIsCaseProviderChildren() {
def p0 = this.getProvider(0)
def p1 = this.getCollectionProvider([])
def sum = p0 + p1
([
contains: { CollectionProvider<Integer> p ->
assertTrue(p.contains(p0))
assertTrue(p.contains(p1))
} as Consumer<CollectionProvider<Integer>>,
in: { CollectionProvider<Integer> p ->

View File

@ -0,0 +1,10 @@
import com.jessebrault.ssg.buildscript.BuildScriptBase
import groovy.transform.BaseScript
@BaseScript
BuildScriptBase base
build {
siteName 'My Site'
baseUrl 'https://mysite.com'
}

View File

@ -0,0 +1,10 @@
import com.jessebrault.ssg.buildscript.BuildScriptBase
import groovy.transform.BaseScript
@BaseScript
BuildScriptBase base
build {
extending 'default'
baseUrl baseUrl.map { defaultBaseUrl -> defaultBaseUrl + '/preview' }
}

View File

@ -0,0 +1,9 @@
import com.jessebrault.ssg.buildscript.BuildScriptBase
import groovy.transform.BaseScript
@BaseScript
BuildScriptBase base
build {
extending 'default'
}

View File

@ -24,10 +24,43 @@ Have the following layout of dirs and files. It is a combined gradle/ssg project
- production.ssg.groovy: a 'production' build
- preview.ssg.groovy: a 'preview' build
- texts: a general folder for texts and other textual data, can be .md, .txt, .html, etc.
- build.gradle: the root project build.gradle
- build.gradle: the root project build.gradle
- settings.gradle: the usual gradle settings
## Api TODO
- [ ] Move from `Provider`/`Property` in `api` to `groowt.util.provider`.
- [ ] Move from all the fp-util stuff to `groowt.util.fp`.
- [ ] Get rid of graph-dependency.
## New Build Script DSL
Simple example:
```groovy
extendsFrom('default')
// isAbstract = true
siteName = 'Hello, World!'
baseUrl = 'https://helloworld.com/'
buildDir = new File('hello-world-build')
globals {
someProp = 'Some property.'
}
includedBuilds << 'anotherBuild'
```
Example with models:
```groovy
Set<NamedModel> myModels = [
new NamedModel(name: 'thing0'),
new NamedModel(name: 'thing1')
]
models {
myModels.each {
add(it.name, it)
}
}
```