Massive clean up and api/cli/gradle work. Running successfully now in test-ssg-project.

This commit is contained in:
JesseBrault0709 2024-05-16 10:34:46 +02:00
parent 140dffefc6
commit bc1d545297
215 changed files with 1914 additions and 4618 deletions

View File

@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="debug-test-project" type="Remote">
<module name="ssg" />
<option name="USE_SOCKET_TRANSPORT" value="true" />
<option name="SERVER_MODE" value="false" />
<option name="SHMEM_ADDRESS" />
<option name="HOST" value="localhost" />
<option name="PORT" value="8192" />
<option name="AUTO_RESTART" value="false" />
<RunnerSettings RunnerId="Debug">
<option name="DEBUG_PORT" value="8192" />
<option name="LOCAL" value="false" />
</RunnerSettings>
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="launch-debug-test-project" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/test-ssg-project/bin/ssg" />
<option name="SCRIPT_OPTIONS" value="--debug build -g --dry-run -b default" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$/test-ssg-project" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/env" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2">
<option name="Gradle.BeforeRunTask" enabled="true" tasks="publishToMavenLocal" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="run-test-project" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/test-ssg-project/bin/ssg" />
<option name="SCRIPT_OPTIONS" value="build --log-level=debug --gradle --dry-run --build default" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$/test-ssg-project" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/usr/bin/env" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="false" />
<option name="EXECUTE_SCRIPT_FILE" value="true" />
<envs />
<method v="2">
<option name="Gradle.BeforeRunTask" enabled="true" tasks="publishToMavenLocal" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" />
</method>
</configuration>
</component>

View File

@ -3,6 +3,7 @@ plugins {
id 'groovy'
id 'java-library'
id 'java-test-fixtures'
id 'maven-publish'
}
repositories {
@ -18,26 +19,26 @@ configurations {
dependencies {
api libs.groovy
api libs.groovy.yaml
api libs.groowt.all
compileOnlyApi libs.jetbrains.anontations
implementation libs.groovy.templates
implementation libs.classgraph
implementation libs.commonmark
implementation libs.commonmark.frontmatter
implementation libs.jsoup
// https://archiva.jessebrault.com/#artifact/com.jessebrault.gst/lib
implementation 'com.jessebrault.gst:lib:0.0.5'
// https://mvnrepository.com/artifact/org.jgrapht/jgrapht-core
implementation 'org.jgrapht:jgrapht-core:1.5.2'
// So we can use Grape dependency management
// https://mvnrepository.com/artifact/org.apache.ivy/ivy
runtimeOnly 'org.apache.ivy:ivy:2.5.2'
}
jar {
archivesBaseName = 'ssg-api'
}
publishing {
publications {
create('ssgApi', MavenPublication) {
artifactId = 'ssg-api'
from components.java
}
}
}

View File

@ -1,158 +0,0 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.buildscript.BuildSpec
import com.jessebrault.ssg.buildscript.BuildScriptConfiguratorFactory
import com.jessebrault.ssg.buildscript.FileBuildScriptGetter
import com.jessebrault.ssg.util.Diagnostic
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import org.slf4j.MarkerFactory
import java.util.function.Consumer
final class BuildScriptBasedStaticSiteGenerator implements StaticSiteGenerator {
private static final Logger logger = LoggerFactory.getLogger(BuildScriptBasedStaticSiteGenerator)
private static final Marker enter = MarkerFactory.getMarker('enter')
private static final Marker exit = MarkerFactory.getMarker('exit')
private final Collection<URL> buildScriptClassLoaderUrls
private final @Nullable File buildScript
private final Collection<BuildSpec> builds = []
private GroovyClassLoader buildScriptClassLoader
/**
* @param buildScriptClassLoaderUrls the urls to pass to the buildScriptRunner's GroovyClassLoader.
* These should include the URL needed to find the given buildScript file, if present, as
* well as any script dependencies (such as the buildSrc dir).
* @param buildScript The buildScript File, may be <code>null</code>.
*/
BuildScriptBasedStaticSiteGenerator(
Collection<URL> buildScriptClassLoaderUrls,
@Nullable File buildScript = null
) {
this.buildScriptClassLoaderUrls = buildScriptClassLoaderUrls
this.buildScript = buildScript
}
private void runBuildScript(
Collection<BuildScriptConfiguratorFactory> configuratorFactories,
Map<String, Object> buildScriptArgs
) {
logger.trace(enter, 'configuratorFactories: {}, buildScriptArgs: {}', configuratorFactories, buildScriptArgs)
if (this.buildScript == null) {
logger.info('no specified build script; using defaults')
def result = FileBuildScriptGetter.runClosureScript { base ->
configuratorFactories.each {
it.get().accept(base)
}
}
this.builds.addAll(result)
} else if (this.buildScript.exists() && this.buildScript.isFile()) {
logger.info('running buildScript: {}', this.buildScript)
def buildScriptRunner = new FileBuildScriptGetter(this.buildScriptClassLoaderUrls)
this.buildScriptClassLoader = buildScriptRunner.getBuildScriptClassLoader()
def result = buildScriptRunner.getBuildScript(
this.buildScript.name,
[args: buildScriptArgs]
) { base ->
configuratorFactories.each { it.get().accept(base) }
}
this.builds.addAll(result)
} else {
throw new IllegalArgumentException("given buildScript ${ this.buildScript } either does not exist or is not a file")
}
logger.trace(exit, '')
}
/**
* @return The classLoader used to load the buildScript.
* @throws NullPointerException if the buildScriptRunner was not initialized yet (make sure to call
* {@link #doBuild} first).
*/
GroovyClassLoader getBuildScriptClassLoader() {
Objects.requireNonNull(this.buildScriptClassLoader)
}
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
private static final class IncludedBuildsResult {
final Collection<BuildSpec> builds
final Collection<Diagnostic> diagnostics
}
// TODO: cache build script results
@Override
boolean doBuild(
String buildName,
Collection<BuildScriptConfiguratorFactory> configuratorFactories,
Map<String, Object> buildScriptArgs,
Consumer<Collection<Diagnostic>> diagnosticsConsumer
) {
logger.trace(enter, 'buildName: {}, diagnosticsConsumer: {}', buildName, diagnosticsConsumer)
this.runBuildScript(configuratorFactories, buildScriptArgs)
def build = this.builds.find { it.name == buildName }
if (!build) {
throw new IllegalArgumentException("there is no registered build with name: ${ buildName }")
}
def includedBuildsResult = build.includedBuilds.inject(
new IncludedBuildsResult([], [])
) { acc, includedBuildName ->
def includedBuild = this.builds.find { it.name == includedBuildName }
if (includedBuild == null) {
acc.diagnostics << new Diagnostic("There is no registered build ${ includedBuildName } that can be included.")
} else {
acc.builds << includedBuild
}
acc
}
if (includedBuildsResult.diagnostics.size() > 0) {
diagnosticsConsumer.accept(includedBuildsResult.diagnostics)
logger.trace(exit, 'result: false')
return false
}
def buildTasksConverter = new SimpleBuildTasksConverter()
def allBuilds = includedBuildsResult.builds + build
def allBuildsConvertResults = allBuilds.collect {
buildTasksConverter.convert(it)
}
def allBuildsConvertDiagnostics = allBuildsConvertResults.collectMany {
it.diagnostics
}
if (allBuildsConvertDiagnostics.size() > 0) {
diagnosticsConsumer.accept(allBuildsConvertDiagnostics)
logger.trace(exit, 'result: false')
return false
}
def allTasks = allBuildsConvertResults.collectMany {
it.get()
}
def allTasksDiagnostics = allTasks.collectMany { it.execute(allTasks) }
if (allTasksDiagnostics.size() > 0) {
diagnosticsConsumer.accept(allTasksDiagnostics)
logger.trace(exit, 'result: false')
return false
}
logger.trace(exit, 'result: true')
return true
}
}

View File

@ -1,9 +0,0 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.buildscript.BuildSpec
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.util.Result
interface BuildTasksConverter {
Result<Collection<Task>> convert(BuildSpec buildScriptResult)
}

View File

@ -0,0 +1,228 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.buildscript.BuildScriptGetter
import com.jessebrault.ssg.buildscript.BuildScriptToBuildSpecConverter
import com.jessebrault.ssg.buildscript.BuildSpec
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
import com.jessebrault.ssg.di.*
import com.jessebrault.ssg.page.DefaultPage
import com.jessebrault.ssg.page.Page
import com.jessebrault.ssg.page.PageFactory
import com.jessebrault.ssg.page.PageSpec
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Diagnostic
import com.jessebrault.ssg.view.PageView
import groovy.transform.TupleConstructor
import groowt.util.di.RegistryObjectFactory
import io.github.classgraph.ClassGraph
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.nio.file.Files
import static groowt.util.di.BindingUtil.named
import static groowt.util.di.BindingUtil.toSingleton
@TupleConstructor(includeFields = true, defaults = false)
class DefaultStaticSiteGenerator implements StaticSiteGenerator {
private static final Logger logger = LoggerFactory.getLogger(DefaultStaticSiteGenerator)
protected final GroovyClassLoader groovyClassLoader
protected final URL[] buildScriptBaseUrls
protected final boolean dryRun
protected Set<Text> getTexts(String buildScriptFqn, BuildSpec buildSpec) {
def textConverters = buildSpec.textConverters.get {
new SsgException("The textConverters Property in $buildScriptFqn must contain at least an empty Set.")
}
def textDirs = buildSpec.textsDirs.get {
new SsgException("The textDirs Property in $buildScriptFqn must contain at least an empty Set.")
}
def texts = [] as Set<Text>
textDirs.each { textDir ->
if (textDir.exists()) {
Files.walk(textDir.toPath()).each {
def asFile = it.toFile()
def lastDot = asFile.name.lastIndexOf('.')
if (lastDot != -1) {
def extension = asFile.name.substring(lastDot)
def converter = textConverters.find {
it.handledExtensions.contains(extension)
}
texts << converter.convert(textDir, asFile)
}
}
}
}
texts
}
@Override
Collection<Diagnostic> doBuild(
File projectDir,
String buildName,
String buildScriptFqn,
Map<String, String> buildScriptCliArgs
) {
// run build script(s) and get buildSpec
def buildScriptGetter = new BuildScriptGetter(
this.groovyClassLoader,
this.buildScriptBaseUrls,
buildScriptCliArgs,
projectDir
)
def buildScriptToBuildSpecConverter = new BuildScriptToBuildSpecConverter(
buildScriptGetter,
BuildDelegate.withDefaults(projectDir)
)
def buildSpec = buildScriptToBuildSpecConverter.convert(buildScriptFqn)
// prepare objectFactory
def objectFactoryBuilder = buildSpec.objectFactoryBuilder.get {
new SsgException(
"the objectFactoryBuilder Property in $buildScriptFqn " +
"must contain a RegistryObjectFactory.Builder instance."
)
}
// configure for Page instantiation
objectFactoryBuilder.configureRegistry {
// extensions
addExtension(new TextsExtension().tap {
allTexts.addAll(this.getTexts(buildScriptFqn, buildSpec))
})
addExtension(new ModelsExtension().tap {
allModels.addAll(buildSpec.models.get {
new SsgException("The models Property in $buildScriptFqn must contain at least an empty Set.")
})
})
addExtension(new GlobalsExtension().tap {
globals.putAll(buildSpec.globals.get {
new SsgException("The globals Property in $buildScriptFqn must contain at least an empty Map.")
})
})
// bindings
bind(named('buildName', String), toSingleton(buildSpec.name))
bind(named('siteName', String), toSingleton(buildSpec.siteName.get {
new SsgException("The siteName Property in $buildScriptFqn must be set.")
}))
bind(named('baseUrl', String), toSingleton(buildSpec.baseUrl.get {
new SsgException("The baseUrl Property in $buildScriptFqn must be set.")
}))
}
// get the objectFactory
def objectFactory = objectFactoryBuilder.build()
objectFactory.configureRegistry {
bind(RegistryObjectFactory, toSingleton(objectFactory))
}
// prepare for basePackages scan for Pages and PageFactories
def basePackages = buildSpec.basePackages.get {
new SsgException("The basePackages Property in $buildScriptFqn must contain at least an empty Set.")
}
def classgraph = new ClassGraph()
.enableAnnotationInfo()
.addClassLoader(groovyClassLoader)
basePackages.each { classgraph.acceptPackages(it) }
def pages = [] as Set<Page>
try (def scanResult = classgraph.scan()) {
// single pages
def pageViewInfoList = scanResult.getClassesImplementing(PageView)
pageViewInfoList.each { pageViewInfo ->
def pageSpecInfo = pageViewInfo.getAnnotationInfo(PageSpec)
if (pageSpecInfo != null) {
def pageSpec = (PageSpec) pageSpecInfo.loadClassAndInstantiate()
pages << new DefaultPage(
pageSpec.name(),
pageSpec.path(),
pageSpec.fileExtension(),
(Class<? extends PageView>) pageViewInfo.loadClass()
)
}
}
// page factories
def pageFactoryTypes = [] as Set<Class<? extends PageFactory>>
def pageFactoryInfoList = scanResult.getClassesImplementing(PageFactory)
pageFactoryInfoList.each { pageFactoryInfo ->
pageFactoryTypes << (pageFactoryInfo.loadClass() as Class<? extends PageFactory>)
}
// instantiate page factory and create the pages
pageFactoryTypes.each { pageFactoryType ->
def pageFactory = objectFactory.createInstance(pageFactoryType)
pages.addAll(pageFactory.create())
}
}
// Configure for PageView instantiation
objectFactoryBuilder.configureRegistry {
// extensions
addExtension(new PagesExtension().tap {
allPages.addAll(pages)
})
addExtension(new SelfPageExtension())
}
def diagnostics = [] as Collection<Diagnostic>
pages.each {
// instantiate PageView
PageView pageView
try {
objectFactory.registry.getExtension(SelfPageExtension).currentPage = it
pageView = objectFactory.createInstance(it.viewType)
} catch (Exception exception) {
diagnostics << new Diagnostic(
"There was an exception while constructing $it.viewType.name for $it.name",
exception
)
return
}
// Render the page
def sw = new StringWriter()
try {
pageView.renderTo(sw)
} catch (Exception exception) {
diagnostics << new Diagnostic(
"There was an exception while rendering $it.name as $pageView.class.name",
exception
)
return
}
// Output the page if not dryRun
if (!this.dryRun) {
def outputDir = buildSpec.outputDir.get {
new SsgException("The outputDir Property in $buildScriptFqn must be set.")
}
outputDir.mkdirs()
def outputFile = new File(
outputDir,
it.path.replace('/', File.separator) + it.fileExtension
)
try {
outputFile.write(sw.toString())
} catch (Exception exception) {
diagnostics << new Diagnostic(
"There was an exception while writing $it.name to $outputFile",
exception
)
}
}
}
// return diagnostics
diagnostics
}
}

View File

@ -1,33 +0,0 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.buildscript.BuildSpec
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.util.Diagnostic
import com.jessebrault.ssg.util.Result
final class SimpleBuildTasksConverter implements BuildTasksConverter {
@Override
Result<Collection<Task>> convert(BuildSpec build) {
def taskSpec = new TaskSpec(
build.name,
build.outputDirFunction.apply(build).asFile(),
build.siteSpec,
build.globals
)
Collection<Task> tasks = []
Collection<Diagnostic> diagnostics = []
build.taskFactorySpecs.each {
def factory = it.supplier.get()
it.configurators.each { it.accept(factory) }
def result = factory.getTasks(taskSpec)
diagnostics.addAll(result.diagnostics)
tasks.addAll(result.get())
}
Result.of(diagnostics, tasks)
}
}

View File

@ -1,35 +0,0 @@
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
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class SiteSpec {
static final Monoid<SiteSpec> DEFAULT_MONOID = Monoids.of(getBlank(), SiteSpec::concat)
static SiteSpec getBlank() {
new SiteSpec('', '')
}
static SiteSpec concat(SiteSpec s0, SiteSpec s1) {
new SiteSpec(
s1.name.blank ? s0.name : s1.name,
s1.baseUrl.blank ? s0.baseUrl : s1.baseUrl
)
}
final String name
final String baseUrl
@Override
String toString() {
"SiteSpec(${ this.name }, ${ this.baseUrl })"
}
}

View File

@ -0,0 +1,13 @@
package com.jessebrault.ssg
class SsgException extends RuntimeException {
SsgException(String message) {
super(message)
}
SsgException(String message, Throwable cause) {
super(message, cause)
}
}

View File

@ -1,15 +1,12 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.buildscript.BuildScriptConfiguratorFactory
import com.jessebrault.ssg.util.Diagnostic
import java.util.function.Consumer
interface StaticSiteGenerator {
boolean doBuild(
Collection<Diagnostic> doBuild(
File projectDir,
String buildName,
Collection<BuildScriptConfiguratorFactory> configuratorFactories,
Map<String, Object> buildScriptArgs,
Consumer<Collection<Diagnostic>> diagnosticsConsumer
String buildScriptFqn,
Map<String, String> buildScriptCliArgs
)
}
}

View File

@ -1,38 +0,0 @@
package com.jessebrault.ssg.build
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.page.Page
import groowt.util.di.RegistryObjectFactory
import groowt.util.fp.provider.NamedSetProvider
import static com.jessebrault.ssg.util.ObjectUtil.*
class Build {
final String name
final String siteName
final String baseUrl
final File outputDir
final Map globals
final Set<File> textsDirs
final NamedSetProvider<Page> pages
final RegistryObjectFactory objectFactory
Build(Map args) {
this.name = requireString(args.name)
this.siteName = requireString(args.siteName)
this.baseUrl = requireString(args.baseUrl)
this.outputDir = requireFile(args.outputDir)
this.globals = requireMap(args.globals)
this.textsDirs = requireSet(args.textsDirs)
this.pages = requireType(NamedSetProvider, args.pages)
this.objectFactory = requireType(RegistryObjectFactory, args.objectFactory)
}
void doBuild() {
// set up object factory for di
// container should have: Build and all its properties
// container should also have @Text, @Texts, @Page, and @Pages resolvers
}
}

View File

@ -13,26 +13,26 @@ import static java.util.Objects.requireNonNull
@SuppressWarnings('unused')
abstract class BuildScriptBase extends Script {
/* --- Logging --- */
static final Logger logger = LoggerFactory.getLogger(BuildScriptBase)
static final Marker enter = MarkerFactory.getMarker('ENTER')
static final Marker exit = MarkerFactory.getMarker('EXIT')
/* --- build script proper --- */
private String extending
private Closure buildClosure
private Closure buildClosure = { }
private File projectRoot
File getProjectRoot() {
requireNonNull(this.projectRoot)
}
void setProjectRoot(File projectRoot) {
this.projectRoot = requireNonNull(projectRoot)
}
/* --- Instance DSL helpers --- */
File file(String name) {
new File(this.projectRoot, name)
}
/* --- DSL --- */
void build(@Nullable String extending, @DelegatesTo(value = BuildDelegate) Closure buildClosure) {
this.extending = extending
this.buildClosure = buildClosure
@ -43,8 +43,21 @@ abstract class BuildScriptBase extends Script {
this.buildClosure = buildClosure
}
/* --- internal --- */
@ApiStatus.Internal
@Nullable String getExtending() {
File getProjectRoot() {
requireNonNull(this.projectRoot)
}
@ApiStatus.Internal
void setProjectRoot(File projectRoot) {
this.projectRoot = requireNonNull(projectRoot)
}
@ApiStatus.Internal
@Nullable
String getExtending() {
this.extending
}

View File

@ -0,0 +1,29 @@
package com.jessebrault.ssg.buildscript
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import org.codehaus.groovy.control.CompilerConfiguration
@NullCheck(includeGenerated = true)
@TupleConstructor(includeFields = true, defaults = false)
final class BuildScriptGetter {
private final GroovyClassLoader groovyClassLoader
private final URL[] scriptBaseUrls
private final Map<String, String> scriptCliArgs
private final File projectDir
BuildScriptBase getAndRunBuildScript(String fqn) {
def gcl = new GroovyClassLoader(this.groovyClassLoader, new CompilerConfiguration().tap {
it.scriptBaseClass = BuildScriptBase.name
})
this.scriptBaseUrls.each { gcl.addURL(it) }
def scriptClass = gcl.loadClass(fqn, true, true) as Class<BuildScriptBase>
def buildScript = scriptClass.getConstructor().newInstance()
buildScript.binding = new Binding(this.scriptCliArgs)
buildScript.projectRoot = projectDir
buildScript.run()
buildScript
}
}

View File

@ -8,29 +8,32 @@ import java.util.function.Supplier
@NullCheck
@TupleConstructor(includeFields = true)
class BuildDelegateToBuildSpecConverter {
class BuildScriptToBuildSpecConverter {
private final FileBuildScriptGetter buildScriptGetter
private final BuildScriptGetter buildScriptGetter
private final Supplier<BuildDelegate> buildDelegateSupplier
protected BuildSpec getFromDelegate(String name, BuildDelegate delegate) {
new BuildSpec(
name: name,
basePackages: delegate.basePackages,
siteName: delegate.siteName,
baseUrl: delegate.baseUrl,
outputDir: delegate.outputDir,
globals: delegate.globals,
models: delegate.models,
textsDirs: delegate.textsDirs,
models: delegate.models
textConverters: delegate.textConverters,
objectFactoryBuilder: delegate.objectFactoryBuilder
)
}
BuildSpec convert(final String name, final BuildScriptBase buildScript) {
protected BuildSpec doConvert(String name, BuildScriptBase buildScript) {
final Deque<BuildScriptBase> buildHierarchy = new LinkedList<>()
buildHierarchy.push(buildScript)
String extending = buildScript.extending
while (extending != null) {
def from = this.buildScriptGetter.getBuildScript(extending)
def from = this.buildScriptGetter.getAndRunBuildScript(extending)
buildHierarchy.push(from)
extending = from.extending
}
@ -45,4 +48,9 @@ class BuildDelegateToBuildSpecConverter {
this.getFromDelegate(name, delegate)
}
BuildSpec convert(String buildScriptFqn) {
def start = this.buildScriptGetter.getAndRunBuildScript(buildScriptFqn)
this.doConvert(buildScriptFqn, start)
}
}

View File

@ -1,37 +1,48 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.text.TextConverter
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groowt.util.fp.provider.NamedProvider
import groowt.util.di.RegistryObjectFactory
import groowt.util.fp.provider.Provider
import static com.jessebrault.ssg.util.ObjectUtil.*
import static com.jessebrault.ssg.util.ObjectUtil.requireProvider
import static com.jessebrault.ssg.util.ObjectUtil.requireString
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class BuildSpec {
final String name
final Provider<Set<String>> basePackages
final Provider<String> siteName
final Provider<String> baseUrl
final Provider<File> outputDir
final Provider<Map<String, Object>> globals
final Set<Provider<File>> textsDirs
final Provider<Set<Model>> models
final Provider<Set<File>> textsDirs
final Provider<Set<TextConverter>> textConverters
final Provider<RegistryObjectFactory.Builder> objectFactoryBuilder
@SuppressWarnings('GroovyAssignabilityCheck')
BuildSpec(Map args) {
this.name = requireString(args.name)
this.basePackages = requireProvider(args.basePackages)
this.siteName = requireProvider(args.siteName)
this.baseUrl = requireProvider(args.baseUrl)
this.outputDir = requireProvider(args.outputDir)
this.globals = requireMap(args.globals)
this.textsDirs = requireSet(args.textsDirs)
this.globals = requireProvider(args.globals)
this.models = requireProvider(args.models)
this.textsDirs = requireProvider(args.textsDirs)
this.textConverters = requireProvider(args.textConverters)
this.objectFactoryBuilder = requireProvider(args.objectFactoryBuilder)
}
@Override
String toString() {
"Build(name: ${ this.name })"
"Build(name: ${this.name}, basePackages: $basePackages, siteName: $siteName, " +
"baseUrl: $baseUrl, outputDir: $outputDir, textsDirs: $textsDirs)"
}
}

View File

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

View File

@ -1,37 +1,61 @@
package com.jessebrault.ssg.buildscript.delegates
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.model.Models
import com.jessebrault.ssg.text.MarkdownTextConverter
import com.jessebrault.ssg.text.TextConverter
import groowt.util.di.DefaultRegistryObjectFactory
import groowt.util.di.RegistryObjectFactory
import groowt.util.fp.property.DefaultProperty
import groowt.util.fp.property.Property
import groowt.util.fp.provider.DefaultSetProvider
import groowt.util.fp.provider.DefaultProvider
import groowt.util.fp.provider.NamedProvider
import groowt.util.fp.provider.Provider
import groowt.util.fp.provider.SetProvider
import java.util.function.Supplier
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
final class BuildDelegate {
static Supplier<BuildDelegate> withDefaults() {
static Supplier<BuildDelegate> withDefaults(File projectDir) {
return {
new BuildDelegate().tap {
outputDir.convention = 'dist'
new BuildDelegate(projectDir).tap {
basePackages.convention = [] as Set<String>
outputDir.convention = new File(projectDir, 'dist')
globals.convention = [:]
objectFactory.convention = DefaultRegistryObjectFactory.Builder.withDefaults()
models.convention = [] as Set<Model>
textsDirs.convention = [new File(projectDir, 'texts')] as Set<File>
textConverters.convention = [new MarkdownTextConverter()] as Set<TextConverter>
objectFactoryBuilder.convention = DefaultRegistryObjectFactory.Builder.withDefaults()
}
}
}
final Property<String> siteName = Property.empty()
final Property<String> baseUrl = Property.empty()
final Property<File> outputDir = Property.empty()
final Property<Map<String, Object>> globals = Property.empty()
final Property<RegistryObjectFactory> objectFactory = Property.empty()
final File projectDir
private final Set<Provider<File>> textsDirs = []
final Property<Set<String>> basePackages = DefaultProperty.<Set<String>>empty(Set)
final Property<String> siteName = DefaultProperty.empty(String)
final Property<String> baseUrl = DefaultProperty.empty(String)
final Property<File> outputDir = DefaultProperty.empty(File)
final Property<Map<String, Object>> globals = DefaultProperty.<Map<String, Object>>empty(Map)
final Property<Set<Model>> models = DefaultProperty.<Set<Model>>empty(Set)
final Property<Set<File>> textsDirs = DefaultProperty.<Set<File>>empty(Set)
final Property<Set<TextConverter>> textConverters = DefaultProperty.<Set<TextConverter>>empty(Set)
final Property<RegistryObjectFactory.Builder> objectFactoryBuilder =
DefaultProperty.empty(RegistryObjectFactory.Builder)
private BuildDelegate(File projectDir) {
this.projectDir = projectDir
}
/* TODO: add friendly DSL methods for setting all properties */
void basePackage(String toAdd) {
this.basePackages.configure { it.add(toAdd) }
}
void basePackages(String... toAdd) {
toAdd.each { this.basePackage(it) }
}
void siteName(String siteName) {
this.siteName.set(siteName)
@ -62,25 +86,49 @@ final class BuildDelegate {
globalsClosure.delegate = globalsDelegate
globalsClosure.resolveStrategy = Closure.DELEGATE_FIRST
globalsClosure()
this.globals.set {
this.globals.set DefaultProvider.ofLazy(Map) {
this.globals.get() + globalsDelegate
}
}
void model(String name, Object obj) {
this.models.configure {it.add(Models.of(name, obj)) }
}
void model(Model model) {
this.models.configure { it.add(model) }
}
void model(String name, Provider tProvider) {
this.models.configure { it.add(Models.ofProvider(name, tProvider)) }
}
<T> void model(String name, Class<T> type, Supplier<? extends T> tSupplier) {
this.models.configure { it.add(Models.ofSupplier(name, type, tSupplier)) }
}
void model(NamedProvider namedProvider) {
this.models.configure { it.add(Models.ofNamedProvider(namedProvider)) }
}
void textsDir(File textsDir) {
this.textsDirs.add { textsDir }
this.textsDirs.configure { it.add(textsDir) }
}
void textsDir(Provider<File> textsDirProvider) {
this.textsDirs << textsDirProvider
void textsDirs(File... textsDirs) {
textsDirs.each { this.textsDir(it) }
}
void textsDirs(Set<Provider<File>> textsDirProviders) {
textsDirProviders.each { this.textsDir(it) }
void textConverter(TextConverter textConverter) {
this.textConverters.configure { it.add(textConverter) }
}
SetProvider<File> getTextsDirs() {
new DefaultSetProvider(this.textsDirs)
void textConverters(TextConverter... textConverters) {
textConverters.each { this.textConverter(it) }
}
void objectFactoryBuilder(RegistryObjectFactory.Builder builder) {
this.objectFactoryBuilder.set(builder)
}
}

View File

@ -0,0 +1,15 @@
package com.jessebrault.ssg.di
import jakarta.inject.Qualifier
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
@interface Global {
String value()
}

View File

@ -0,0 +1,43 @@
package com.jessebrault.ssg.di
import groovy.transform.TupleConstructor
import groowt.util.di.Binding
import groowt.util.di.QualifierHandler
import groowt.util.di.QualifierHandlerContainer
import groowt.util.di.RegistryExtension
import groowt.util.di.SingletonBinding
import java.lang.annotation.Annotation
class GlobalsExtension implements QualifierHandlerContainer, RegistryExtension {
@TupleConstructor(includeFields = true)
static class GlobalQualifierHandler implements QualifierHandler<Global> {
private final GlobalsExtension extension
@Override
<T> Binding<T> handle(Global global, Class<T> aClass) {
if (extension.globals.containsKey(global.value())) {
return new SingletonBinding<T>(extension.globals.get(global.value()) as T)
} else {
throw new IllegalArgumentException("There is no global for ${global.value()}")
}
}
}
final Map<String, Object> globals = [:]
private final GlobalQualifierHandler globalQualifierHandler = new GlobalQualifierHandler(this)
@Override
<A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> aClass) {
if (Global.is(aClass)) {
return this.globalQualifierHandler as QualifierHandler<A>
} else {
return null
}
}
}

View File

@ -0,0 +1,15 @@
package com.jessebrault.ssg.di
import jakarta.inject.Qualifier
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
@interface InjectModel {
String value()
}

View File

@ -0,0 +1,26 @@
package com.jessebrault.ssg.di
import groovy.transform.TupleConstructor
import groowt.util.di.Binding
import groowt.util.di.QualifierHandler
import groowt.util.di.SingletonBinding
@TupleConstructor(includeFields = true)
class InjectModelQualifierHandler implements QualifierHandler<InjectModel> {
private final ModelsExtension extension
@Override
<T> Binding<T> handle(InjectModel injectModel, Class<T> requestedClass) {
def found = this.extension.allModels.find {
requestedClass.isAssignableFrom(it.class) && it.name == injectModel.value()
}
if (found == null) {
throw new IllegalArgumentException(
"Could not find a Model with name ${injectModel.value()} and/or type $requestedClass.name"
)
}
new SingletonBinding<T>(found.get() as T)
}
}

View File

@ -0,0 +1,15 @@
package com.jessebrault.ssg.di
import jakarta.inject.Qualifier
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
@interface InjectModels {
String[] value()
}

View File

@ -0,0 +1,27 @@
package com.jessebrault.ssg.di
import groovy.transform.TupleConstructor
import groowt.util.di.Binding
import groowt.util.di.QualifierHandler
import groowt.util.di.SingletonBinding
@TupleConstructor(includeFields = true)
class InjectModelsQualifierHandler implements QualifierHandler<InjectModels> {
private final ModelsExtension extension
@Override
<T> Binding<T> handle(InjectModels injectModels, Class<T> requestedType) {
if (!List.is(requestedType)) {
throw new IllegalArgumentException("@InjectModels must be used with List.")
}
def allFound = this.extension.allModels.inject([] as T) { acc, model ->
if (model.type.isAssignableFrom(requestedType) && model.name in injectModels.value()) {
acc << model.get()
}
acc
}
new SingletonBinding<T>(allFound as T)
}
}

View File

@ -1,4 +1,4 @@
package com.jessebrault.ssg.objects
package com.jessebrault.ssg.di
import jakarta.inject.Qualifier

View File

@ -1,4 +1,4 @@
package com.jessebrault.ssg.objects
package com.jessebrault.ssg.di
import com.jessebrault.ssg.page.Page
import groovy.transform.TupleConstructor
@ -17,7 +17,7 @@ class InjectPageQualifierHandler implements QualifierHandler<InjectPage> {
throw new IllegalArgumentException("Cannot inject a Page into a non-Page parameter/setter/field.")
}
def requested = injectPage.value()
def found = this.pagesExtension.pageProviders.find {
def found = this.pagesExtension.allPages.find {
if (requested.startsWith('/')) {
it.path == requested
} else {
@ -27,7 +27,7 @@ class InjectPageQualifierHandler implements QualifierHandler<InjectPage> {
if (found == null) {
throw new IllegalArgumentException("Cannot find a page with the following name or path: $requested")
}
new SingletonBinding<T>(found.get() as T)
new SingletonBinding<T>(found as T)
}
}

View File

@ -1,4 +1,4 @@
package com.jessebrault.ssg.objects
package com.jessebrault.ssg.di
import jakarta.inject.Qualifier

View File

@ -1,4 +1,4 @@
package com.jessebrault.ssg.objects
package com.jessebrault.ssg.di
import com.jessebrault.ssg.page.Page
import com.jessebrault.ssg.util.Glob
@ -23,19 +23,19 @@ class InjectPagesQualifierHandler implements QualifierHandler<InjectPages> {
for (final String requested : injectPages.value()) {
if (requested.startsWith('/')) {
def glob = new Glob(requested)
def allFound = this.pagesExtension.pageProviders.inject([] as Set<Page>) { acc, pageProvider ->
if (glob.matches(pageProvider.path)) {
acc << pageProvider.get()
def allFound = this.pagesExtension.allPages.inject([] as Set<Page>) { acc, page ->
if (glob.matches(page.path)) {
acc << page
}
acc
}
allFound.each { foundPages << it }
} else {
def found = this.pagesExtension.pageProviders.find { it.name == requested }
def found = this.pagesExtension.allPages.find { it.name == requested }
if (found == null) {
throw new IllegalArgumentException("Cannot find page with the name: $requested")
}
foundPages << found.get()
foundPages << found
}
}
new SingletonBinding<T>(foundPages as T)

View File

@ -1,4 +1,4 @@
package com.jessebrault.ssg.objects
package com.jessebrault.ssg.di
import jakarta.inject.Qualifier

View File

@ -0,0 +1,28 @@
package com.jessebrault.ssg.di
import com.jessebrault.ssg.text.Text
import groovy.transform.TupleConstructor
import groowt.util.di.Binding
import groowt.util.di.QualifierHandler
import groowt.util.di.SingletonBinding
@TupleConstructor(includeFields = true)
class InjectTextQualifierHandler implements QualifierHandler<InjectText> {
private final TextsExtension extension
@Override
<T> Binding<T> handle(InjectText injectText, Class<T> requestedClass) {
if (!Text.isAssignableFrom(requestedClass)) {
throw new IllegalArgumentException("Cannot @InjectText on a non-Text parameter/method/field.")
}
def found = this.extension.allTexts.find {
it.name == injectText.value() || it.path == injectText.value()
}
if (found == null) {
throw new IllegalArgumentException("Could not find a Text with name or path ${injectText.value()}")
}
new SingletonBinding<T>(found as T)
}
}

View File

@ -1,4 +1,4 @@
package com.jessebrault.ssg.objects
package com.jessebrault.ssg.di
import jakarta.inject.Qualifier

View File

@ -0,0 +1,39 @@
package com.jessebrault.ssg.di
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Glob
import groovy.transform.TupleConstructor
import groowt.util.di.Binding
import groowt.util.di.QualifierHandler
import groowt.util.di.SingletonBinding
@TupleConstructor(includeFields = true)
class InjectTextsQualifierHandler implements QualifierHandler<InjectTexts> {
private final TextsExtension extension
@Override
<T> Binding<T> handle(InjectTexts injectTexts, Class<T> aClass) {
if (!Set.is(aClass)) {
throw new IllegalArgumentException('Cannot @InjectTexts on a non-Set parameter/method/field.')
}
def allFound = injectTexts.value().inject([] as Set<Text>) { acc, nameOrPathGlob ->
if (nameOrPathGlob.startsWith('/')) {
def glob = new Glob(nameOrPathGlob)
def matching = this.extension.allTexts.inject([] as Set<Text>) { matchingAcc, text ->
if (glob.matches(text.path)) {
matchingAcc << text
}
matchingAcc
}
acc.addAll(matching)
} else {
def found = this.extension.allTexts.find { it.name == nameOrPathGlob }
acc << found
}
acc
}
new SingletonBinding<T>(allFound as T)
}
}

View File

@ -0,0 +1,28 @@
package com.jessebrault.ssg.di
import com.jessebrault.ssg.model.Model
import groowt.util.di.QualifierHandler
import groowt.util.di.QualifierHandlerContainer
import groowt.util.di.RegistryExtension
import java.lang.annotation.Annotation
class ModelsExtension implements QualifierHandlerContainer, RegistryExtension {
final Set<Model> allModels = []
private final QualifierHandler<InjectModel> injectModelQualifierHandler = new InjectModelQualifierHandler(this)
private final QualifierHandler<InjectModels> injectModelsQualifierHandler = new InjectModelsQualifierHandler(this)
@Override
<A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> aClass) {
if (aClass == InjectModel) {
return this.injectModelQualifierHandler as QualifierHandler<A>
} else if (aClass == InjectModels) {
return this.injectModelsQualifierHandler as QualifierHandler<A>
} else {
return null
}
}
}

View File

@ -1,7 +1,6 @@
package com.jessebrault.ssg.objects
package com.jessebrault.ssg.di
import com.jessebrault.ssg.provider.PageProvider
import com.jessebrault.ssg.page.Page
import groowt.util.di.QualifierHandler
import groowt.util.di.QualifierHandlerContainer
import groowt.util.di.RegistryExtension
@ -10,7 +9,7 @@ import java.lang.annotation.Annotation
class PagesExtension implements QualifierHandlerContainer, RegistryExtension {
final Set<PageProvider> pageProviders = []
final Set<Page> allPages = []
private final QualifierHandler<InjectPage> injectPage = new InjectPageQualifierHandler(this)
private final QualifierHandler<InjectPages> injectPages = new InjectPagesQualifierHandler(this)

View File

@ -0,0 +1,13 @@
package com.jessebrault.ssg.di
import jakarta.inject.Qualifier
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
@interface SelfPage {}

View File

@ -0,0 +1,42 @@
package com.jessebrault.ssg.di
import com.jessebrault.ssg.page.Page
import groovy.transform.TupleConstructor
import groowt.util.di.*
import java.lang.annotation.Annotation
class SelfPageExtension implements RegistryExtension, QualifierHandlerContainer {
@TupleConstructor(includeFields = true)
static class SelfPageQualifierHandler implements QualifierHandler<SelfPage> {
private final SelfPageExtension extension
@Override
<T> Binding<T> handle(SelfPage selfPage, Class<T> requestedType) {
if (!Page.class.isAssignableFrom(requestedType)) {
throw new IllegalArgumentException('Cannot put @SelfPage on a non-Page parameter/method/field.')
}
if (this.extension.currentPage == null) {
throw new IllegalStateException('Cannot get @SelfPage because extension.currentPage is null.')
}
new SingletonBinding<T>(this.extension.currentPage as T)
}
}
Page currentPage
private final SelfPageQualifierHandler selfPageQualifierHandler = new SelfPageQualifierHandler(this)
@Override
<A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> annotationType) {
if (SelfPage.is(annotationType)) {
return this.selfPageQualifierHandler as QualifierHandler<A>
} else {
return null
}
}
}

View File

@ -1,4 +1,4 @@
package com.jessebrault.ssg.objects
package com.jessebrault.ssg.di
import groowt.util.di.DefaultRegistryObjectFactory
import groowt.util.di.RegistryObjectFactory

View File

@ -0,0 +1,28 @@
package com.jessebrault.ssg.di
import com.jessebrault.ssg.text.Text
import groowt.util.di.QualifierHandler
import groowt.util.di.QualifierHandlerContainer
import groowt.util.di.RegistryExtension
import java.lang.annotation.Annotation
class TextsExtension implements QualifierHandlerContainer, RegistryExtension {
final Set<Text> allTexts = []
private final QualifierHandler<InjectText> injectTextQualifierHandler = new InjectTextQualifierHandler(this)
private final QualifierHandler<InjectTexts> injectTextsQualifierHandler = new InjectTextsQualifierHandler(this)
@Override
<A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> aClass) {
if (InjectText.is(aClass)) {
return this.injectTextQualifierHandler as QualifierHandler<A>
} else if (InjectTexts.is(aClass)) {
return this.injectTextsQualifierHandler as QualifierHandler<A>
} else {
return null
}
}
}

View File

@ -1,56 +0,0 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Diagnostic
import groovy.transform.EqualsAndHashCode
import org.jetbrains.annotations.Nullable
import java.util.function.Consumer
import static java.util.Objects.requireNonNull
@EqualsAndHashCode(includeFields = true)
final class EmbeddablePart {
private final Part part
private final RenderContext context
private final Consumer<Collection<Diagnostic>> diagnosticsConsumer
@Nullable
private final Text text
EmbeddablePart(
Part part,
RenderContext context,
Consumer<Collection<Diagnostic>> diagnosticsConsumer,
@Nullable Text text
) {
this.part = requireNonNull(part)
this.context = requireNonNull(context)
this.diagnosticsConsumer = requireNonNull(diagnosticsConsumer)
this.text = text
}
String render(Map binding = [:]) {
def result = this.part.type.renderer.render(
this.part,
binding,
this.context,
this.text
)
if (result.hasDiagnostics()) {
this.diagnosticsConsumer.accept(result.diagnostics)
''
} else {
result.get()
}
}
@Override
String toString() {
"EmbeddablePart(part: ${ this.part })"
}
}

View File

@ -1,36 +0,0 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Diagnostic
import groovy.transform.EqualsAndHashCode
import org.jetbrains.annotations.Nullable
import java.util.function.Consumer
import static java.util.Objects.requireNonNull
@EqualsAndHashCode(includeFields = true)
final class EmbeddablePartsMap {
@Delegate
private final Map<String, EmbeddablePart> partsMap = [:]
EmbeddablePartsMap(
RenderContext context,
Consumer<Collection<Diagnostic>> diagnosticsConsumer,
@Nullable Text text = null
) {
requireNonNull(context)
requireNonNull(diagnosticsConsumer)
context.parts.each {
this[it.path] = new EmbeddablePart(it, context, diagnosticsConsumer, text)
}
}
@Override
String toString() {
"EmbeddablePartsMap(partsMap: ${ this.partsMap })"
}
}

View File

@ -1,63 +0,0 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Diagnostic
import groovy.transform.EqualsAndHashCode
import groovy.transform.Memoized
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import java.util.function.Consumer
@TupleConstructor(includeFields = true, defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
final class EmbeddableText {
private final Text text
private final Consumer<Collection<Diagnostic>> diagnosticsConsumer
@Memoized
String render() {
def result = this.text.type.renderer.render(this.text)
if (result.diagnostics.size() > 0) {
this.diagnosticsConsumer.accept(result.diagnostics)
''
} else {
result.get()
}
}
@Memoized
FrontMatter getFrontMatter() {
def result = this.text.type.frontMatterGetter.get(this.text)
if (result.hasDiagnostics()) {
this.diagnosticsConsumer.accept(result.diagnostics)
new FrontMatter(this.text, [:])
} else {
result.get()
}
}
@Memoized
String getExcerpt(int limit) {
def result = this.text.type.excerptGetter.getExcerpt(this.text, limit)
if (result.hasDiagnostics()) {
this.diagnosticsConsumer.accept(result.diagnostics)
''
} else {
result.get()
}
}
String getPath() {
this.text.path
}
@Override
String toString() {
"EmbeddableText(text: ${ this.text })"
}
}

View File

@ -1,28 +0,0 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Diagnostic
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import java.util.function.Consumer
@NullCheck
@EqualsAndHashCode(includeFields = true)
final class EmbeddableTextsCollection {
@Delegate
private final Collection<EmbeddableText> embeddableTexts = []
EmbeddableTextsCollection(Collection<Text> texts, Consumer<Collection<Diagnostic>> diagnosticsConsumer) {
texts.each {
this << new EmbeddableText(it, diagnosticsConsumer)
}
}
@Override
String toString() {
"EmbeddableTextsCollection(embeddableTexts: ${ this.embeddableTexts })"
}
}

View File

@ -1,84 +0,0 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.model.Model
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import org.slf4j.MarkerFactory
import java.util.function.Predicate
@NullCheck
@EqualsAndHashCode(includeFields = true)
final class ModelCollection<T> {
private static final Logger logger = LoggerFactory.getLogger(ModelCollection)
private static final Marker enter = MarkerFactory.getMarker('ENTER')
private static final Marker exit = MarkerFactory.getMarker('EXIT')
@Delegate
private final Collection<Model<T>> models = []
ModelCollection(Collection<Model<T>> models) {
this.models.addAll(models)
}
@Nullable
Model<T> getByName(String name) {
this.models.find { it.name == name }
}
@Nullable
<E extends T> Model<E> getByNameAndType(String name, Class<E> type) {
this.models.find { it.name == name && type.isAssignableFrom(it.get().class) } as Model<E>
}
Optional<Model<T>> findByName(String name) {
Optional.ofNullable(this.getByName(name))
}
def <E extends T> Optional<Model<E>> findByNameAndType(String name, Class<E> type) {
Optional.ofNullable(this.getByNameAndType(name, type))
}
def <E> ModelCollection<E> findAllByType(Class<E> type) {
logger.trace(enter, 'type: {}', type)
def es = this.models.findResults {
def itType = it.get().class
def itResult = type.isAssignableFrom(itType)
logger.debug(
'it: {}, itType: {}, itType.classLoader: {}, itResult: {}',
it, itType, itType.classLoader, itResult
)
itResult ? it as Model<E> : null
}
def result = new ModelCollection<>(es)
logger.trace(exit, 'result: {}', result)
result
}
def <E extends T> Optional<Model<E>> findOne(Class<E> type, Predicate<E> filter) {
Optional.ofNullable(this.models.find {
def t = it.get()
if (type.isAssignableFrom(t.class)) {
filter.test(t as E) ? it : null
} else {
null
}
} as Model<E>)
}
def <E extends T> Optional<Model<E>> findOne(Class<E> type) {
this.findOne(type) { true }
}
Optional<Model<T>> findOne(Predicate<T> filter) {
Optional.ofNullable(this.models.find {
filter.test(it.get()) ? it : null
})
}
}

View File

@ -1,81 +0,0 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.dsl.tagbuilder.DynamicTagBuilder
import com.jessebrault.ssg.dsl.urlbuilder.PathBasedUrlBuilder
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Diagnostic
import groovy.transform.NullCheck
import org.slf4j.LoggerFactory
import java.util.function.Consumer
final class StandardDslMap {
@NullCheck(includeGenerated = true)
static final class Builder {
private final Map<String, Object> custom = [:]
Consumer<Collection<Diagnostic>> diagnosticsConsumer = { }
String loggerName = ''
Text text = null
void putCustom(String key, Object value) {
this.custom.put(key, value)
}
void putAllCustom(Map<String, Object> m) {
this.custom.putAll(m)
}
}
static Map<String, Object> get(
RenderContext context,
Consumer<Builder> builderConsumer
) {
def b = new Builder()
builderConsumer.accept(b)
[:].tap {
// standard variables
it.globals = context.globals
it.logger = LoggerFactory.getLogger(b.loggerName)
it.models = new ModelCollection<Object>(context.models)
it.parts = new EmbeddablePartsMap(
context,
b.diagnosticsConsumer,
b.text
)
it.siteSpec = context.siteSpec
it.sourcePath = context.sourcePath
it.tagBuilder = new DynamicTagBuilder()
it.targetPath = context.targetPath
it.tasks = new TaskCollection(context.tasks)
it.text = b.text ? new EmbeddableText(
b.text,
b.diagnosticsConsumer
) : null
it.texts = new EmbeddableTextsCollection(
context.texts,
b.diagnosticsConsumer
)
it.urlBuilder = new PathBasedUrlBuilder(
context.targetPath,
context.siteSpec.baseUrl
)
// task types
it.Task = com.jessebrault.ssg.task.Task
it.HtmlTask = com.jessebrault.ssg.html.HtmlTask
it.ModelToHtmlTask = com.jessebrault.ssg.html.ModelToHtmlTask
it.PageToHtmlTask = com.jessebrault.ssg.html.PageToHtmlTask
it.TextToHtmlTask = com.jessebrault.ssg.html.TextToHtmlTask
// custom
it.putAll(b.custom)
}
}
}

View File

@ -1,21 +0,0 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.task.Task
import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode(includeFields = true)
final class TaskCollection {
@Delegate
private final Collection<Task> tasks
TaskCollection(Collection<? extends Task> src = []) {
this.tasks = []
this.tasks.addAll(src)
}
def <T extends Task> Collection<T> byType(Class<T> taskClass) {
this.tasks.findAll { taskClass.isAssignableFrom(it.class) } as Collection<T>
}
}

View File

@ -1,77 +0,0 @@
package com.jessebrault.ssg.dsl.tagbuilder
import org.codehaus.groovy.runtime.InvokerHelper
final class DynamicTagBuilder implements TagBuilder {
@Override
String create(String name) {
"<$name />"
}
@Override
String create(String name, Map<String, Object> attributes) {
def formattedAttributes = attributes.collect {
if (it.value instanceof String) {
it.key + '="' + it.value + '"'
} else if (it.value instanceof Integer) {
it.key + '=' + it.value
} else if (it.value instanceof Boolean && it.value == true) {
it.key
} else {
it.key + '="' + it.value.toString() + '"'
}
}.join(' ')
"<$name $formattedAttributes />"
}
@Override
String create(String name, String body) {
"<$name>$body</$name>"
}
@Override
String create(String name, Map<String, Object> attributes, String body) {
def formattedAttributes = attributes.collect {
if (it.value instanceof String) {
it.key + '="' + it.value + '"'
} else if (it.value instanceof Integer) {
it.key + '=' + it.value
} else if (it.value instanceof Boolean && it.value == true) {
it.key
} else {
it.key + '="' + it.value.toString() + '"'
}
}.join(' ')
"<$name $formattedAttributes>$body</$name>"
}
@Override
Object invokeMethod(String name, Object args) {
def argsList = InvokerHelper.asList(args)
return switch (argsList.size()) {
case 0 -> this.create(name)
case 1 -> {
def arg0 = argsList[0]
if (arg0 instanceof Map) {
this.create(name, arg0)
} else if (arg0 instanceof String) {
this.create(name, arg0)
} else {
throw new MissingMethodException(name, this.class, args, false)
}
}
case 2 -> {
def arg0 = argsList[0]
def arg1 = argsList[1]
if (arg0 instanceof Map && arg1 instanceof String) {
this.create(name, arg0, arg1)
} else {
throw new MissingMethodException(name, this.class, args, false)
}
}
default -> throw new MissingMethodException(name, this.class, args, false)
}
}
}

View File

@ -1,8 +0,0 @@
package com.jessebrault.ssg.dsl.tagbuilder
interface TagBuilder {
String create(String name)
String create(String name, Map<String, Object> attributes)
String create(String name, String body)
String create(String name, Map<String, Object> attributes, String body)
}

View File

@ -1,59 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.task.AbstractTask
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskInput
import com.jessebrault.ssg.util.Diagnostic
import com.jessebrault.ssg.util.Result
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.jsoup.Jsoup
@NullCheck
@EqualsAndHashCode
abstract class AbstractHtmlTask<I extends TaskInput> extends AbstractTask implements HtmlTask {
final String htmlPath
final I input
final HtmlOutput output
AbstractHtmlTask(
String name,
String htmlPath,
I input,
File buildDir
) {
super(name)
this.htmlPath = htmlPath
this.input = input
this.output = new SimpleHtmlOutput(
"htmlOutput:${ htmlPath }",
new File(buildDir, htmlPath),
htmlPath
)
}
protected abstract Result<String> transform(Collection<Task> allTasks)
@Override
final Collection<Diagnostic> execute(Collection<Task> allTasks) {
def transformResult = this.transform(allTasks)
if (transformResult.hasDiagnostics()) {
transformResult.diagnostics
} else {
def content = transformResult.get()
def document = Jsoup.parse(content)
document.outputSettings().indentAmount(4)
def formatted = document.toString()
this.output.file.createParentDirectories()
this.output.file.write(formatted)
[]
}
}
@Override
String toString() {
"AbstractHtmlTask(path: ${ this.htmlPath }, super: ${ super.toString() })"
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.task.FileOutput
interface HtmlOutput extends FileOutput {
String getHtmlPath()
}

View File

@ -1,12 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskInput
interface HtmlTask extends Task {
@Deprecated
String getHtmlPath()
TaskInput getInput()
HtmlOutput getOutput()
}

View File

@ -1,16 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.template.Template
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class ModelToHtmlSpec<T> {
final Model<T> model
final Template template
final String path
}

View File

@ -1,71 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.model.ModelInput
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.template.Template
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Result
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode
final class ModelToHtmlTask<T> extends AbstractHtmlTask<ModelInput<T>> {
private final SiteSpec siteSpec
private final Map<String, Object> globals
private final Model<T> model
private final Template template
private final Collection<Text> allTexts
private final Collection<Model<Object>> allModels
private final Collection<Part> allParts
ModelToHtmlTask(
String relativeHtmlPath,
TaskSpec taskSpec,
Model<T> model,
Template template,
Collection<Text> allTexts,
Collection<Model<Object>> allModels,
Collection<Part> allParts
) {
super(
"modelToHtml:${ relativeHtmlPath }",
relativeHtmlPath,
new ModelInput<>(model.name, model),
taskSpec.outputDir
)
this.siteSpec = taskSpec.siteSpec
this.globals = taskSpec.globals
this.model = model
this.template = template
this.allTexts = allTexts
this.allModels = allModels
this.allParts = allParts
}
@Override
protected Result<String> transform(Collection<Task> allTasks) {
this.template.type.renderer.render(this.template, null, new RenderContext(
sourcePath: this.model.name,
targetPath: this.htmlPath,
tasks: allTasks,
texts: this.allTexts,
models: this.allModels,
parts: this.allParts,
siteSpec: this.siteSpec,
globals: this.globals
))
}
@Override
String toString() {
"ModelToHtml(${ this.model }, ${ this.template }, ${ this.allTexts }, ${ this.allParts }, ${ super.toString() })"
}
}

View File

@ -1,33 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.provider.CollectionProvider
import com.jessebrault.ssg.provider.CollectionProviders
import com.jessebrault.ssg.task.AbstractRenderTaskFactory
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.util.Result
final class ModelToHtmlTaskFactory<T> extends AbstractRenderTaskFactory {
CollectionProvider<ModelToHtmlSpec<T>> specsProvider = CollectionProviders.getEmpty()
@Override
Result<Collection<Task>> getTasks(TaskSpec taskSpec) {
def allTexts = this.allTextsProvider.provide()
def allModels = this.allModelsProvider.provide()
def allParts = this.allPartsProvider.provide()
Result.of(specsProvider.provide().collect {
new ModelToHtmlTask<>(
it.path,
taskSpec,
it.model,
it.template,
allTexts,
allModels,
allParts
)
})
}
}

View File

@ -1,17 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.page.Page
import com.jessebrault.ssg.task.TaskSpec
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 PageToHtmlSpec {
final Page page
final Function<TaskSpec, String> toRelativeHtmlPath
}

View File

@ -1,34 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.page.Page
import com.jessebrault.ssg.provider.CollectionProvider
import com.jessebrault.ssg.provider.CollectionProviders
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.util.ExtensionUtil
import groovy.transform.NullCheck
import java.util.function.Function
@NullCheck
final class PageToHtmlSpecProviders {
static CollectionProvider<PageToHtmlSpec> from(CollectionProvider<Page> pagesProvider) {
CollectionProviders.fromCollection(pagesProvider.provide().collect { Page page ->
new PageToHtmlSpec(page, { TaskSpec taskSpec ->
ExtensionUtil.stripExtension(page.path) + '.html'
})
})
}
static CollectionProvider<PageToHtmlSpec> from(
CollectionProvider<Page> pagesProvider,
Function<Page, Function<TaskSpec, String>> toRelativeHtmlPath
) {
CollectionProviders.fromCollection(pagesProvider.provide().collect {
new PageToHtmlSpec(it, toRelativeHtmlPath.apply(it))
})
}
private PageToHtmlSpecProviders() {}
}

View File

@ -1,68 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.page.Page
import com.jessebrault.ssg.page.PageInput
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Result
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode(includeFields = true, callSuper = true)
final class PageToHtmlTask extends AbstractHtmlTask<PageInput> {
private final SiteSpec siteSpec
private final Map<String, Object> globals
private final Page page
private final Collection<Text> allTexts
private final Collection<Model<Object>> allModels
private final Collection<Part> allParts
PageToHtmlTask(
String relativeHtmlPath,
TaskSpec taskSpec,
Page page,
Collection<Text> allTexts,
Collection<Model<Object>> allModels,
Collection<Part> allParts
) {
super(
"pageToHtml:${ relativeHtmlPath }",
relativeHtmlPath,
new PageInput(page.path, page),
taskSpec.outputDir
)
this.siteSpec = taskSpec.siteSpec
this.globals = taskSpec.globals
this.page = page
this.allTexts = allTexts
this.allModels = allModels
this.allParts = allParts
}
@Override
protected Result<String> transform(Collection<Task> allTasks) {
this.page.type.renderer.render(this.page, new RenderContext(
this.page.path,
this.htmlPath,
allTasks,
this.allTexts,
this.allModels,
this.allParts,
this.siteSpec,
this.globals
))
}
@Override
String toString() {
"PageToHtml(${ this.page }, ${ this.allTexts }, ${ this.allParts }, ${ super.toString() })"
}
}

View File

@ -1,39 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.provider.CollectionProvider
import com.jessebrault.ssg.provider.CollectionProviders
import com.jessebrault.ssg.task.AbstractRenderTaskFactory
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.util.Result
import static java.util.Objects.requireNonNull
final class PageToHtmlTaskFactory extends AbstractRenderTaskFactory {
CollectionProvider<PageToHtmlSpec> specsProvider = CollectionProviders.getEmpty()
@Override
Result<Collection<Task>> getTasks(TaskSpec taskSpec) {
this.checkProviders()
requireNonNull(this.specsProvider)
def allTexts = this.allTextsProvider.provide()
def allModels = this.allModelsProvider.provide()
def allParts = this.allPartsProvider.provide()
final Collection<Task> tasks = this.specsProvider.provide()
.collect {
new PageToHtmlTask(
it.toRelativeHtmlPath.apply(taskSpec),
taskSpec,
it.page,
allTexts,
allModels,
allParts
)
}
Result.of(tasks)
}
}

View File

@ -1,15 +0,0 @@
package com.jessebrault.ssg.html
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.PackageScope
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class SimpleHtmlOutput implements HtmlOutput {
final String name
final File file
final String htmlPath
}

View File

@ -1,19 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.template.Template
import com.jessebrault.ssg.text.Text
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 TextToHtmlSpec {
final Text text
final Template template
final Function<TaskSpec, String> toRelativeHtmlPath
}

View File

@ -1,60 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.buildscript.SourceProviders
import com.jessebrault.ssg.provider.CollectionProvider
import com.jessebrault.ssg.provider.CollectionProviders
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.template.Template
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.ExtensionUtil
import com.jessebrault.ssg.util.Result
import java.util.function.Function
final class TextToHtmlSpecProviders {
static CollectionProvider<Result<TextToHtmlSpec>> from(SourceProviders sources) {
from(sources) { text ->
return { TaskSpec taskSpec ->
ExtensionUtil.stripExtension(text.path) + '.html'
}
}
}
static CollectionProvider<Result<TextToHtmlSpec>> from(
SourceProviders sources,
Function<Text, Function<TaskSpec, String>> toRelativeHtmlPath
) {
from(sources.textsProvider, sources.templatesProvider, toRelativeHtmlPath)
}
static CollectionProvider<Result<TextToHtmlSpec>> from(
CollectionProvider<Text> textsProvider,
CollectionProvider<Template> templatesProvider,
Function<Text, Function<TaskSpec, String>> toRelativeHtmlPath
) {
CollectionProviders.fromSupplier {
def templates = templatesProvider.provide()
textsProvider.provide().findResults {
def frontMatterResult = it.type.frontMatterGetter.get(it)
if (frontMatterResult.hasDiagnostics()) {
return Result.ofDiagnostics(frontMatterResult.diagnostics) as Result<TextToHtmlSpec>
}
def templateValue = frontMatterResult.get().get('template')
if (templateValue) {
def template = templates.find { it.path == templateValue }
return Result.of(new TextToHtmlSpec(
it,
template,
toRelativeHtmlPath.apply(it)
))
} else {
return null
}
}
}
}
private TextToHtmlSpecProviders() {}
}

View File

@ -1,71 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.template.Template
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.text.TextInput
import com.jessebrault.ssg.util.Result
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode(includeFields = true, callSuper = true)
final class TextToHtmlTask extends AbstractHtmlTask<TextInput> {
private final SiteSpec siteSpec
private final Map<String, Object> globals
private final Text text
private final Template template
private final Collection<Text> allTexts
private final Collection<Model<Object>> allModels
private final Collection<Part> allParts
TextToHtmlTask(
String relativeHtmlPath,
TaskSpec taskSpec,
Text text,
Template template,
Collection<Text> allTexts,
Collection<Model<Object>> allModels,
Collection<Part> allParts
) {
super(
"textToHtml:${ relativeHtmlPath }",
relativeHtmlPath,
new TextInput(text.path, text),
taskSpec.outputDir
)
this.siteSpec = taskSpec.siteSpec
this.globals = taskSpec.globals
this.text = text
this.template = template
this.allTexts = allTexts
this.allModels = allModels
this.allParts = allParts
}
@Override
protected Result<String> transform(Collection<Task> allTasks) {
this.template.type.renderer.render(this.template, this.text, new RenderContext(
this.text.path,
this.htmlPath,
allTasks,
this.allTexts,
this.allModels,
this.allParts,
this.siteSpec,
this.globals
))
}
@Override
String toString() {
"TextToHtml(${ this.text }, ${ this.template }, ${ this.allTexts }, ${ this.allParts }, ${ super.toString() })"
}
}

View File

@ -1,47 +0,0 @@
package com.jessebrault.ssg.html
import com.jessebrault.ssg.provider.CollectionProvider
import com.jessebrault.ssg.provider.CollectionProviders
import com.jessebrault.ssg.task.AbstractRenderTaskFactory
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskSpec
import com.jessebrault.ssg.util.Diagnostic
import com.jessebrault.ssg.util.Result
import static java.util.Objects.requireNonNull
final class TextToHtmlTaskFactory extends AbstractRenderTaskFactory {
CollectionProvider<Result<TextToHtmlSpec>> specsProvider = CollectionProviders.getEmpty()
@Override
Result<Collection<Task>> getTasks(TaskSpec taskSpec) {
super.checkProviders()
requireNonNull(this.specsProvider)
def allTexts = this.allTextsProvider.provide()
def allModels = this.allModelsProvider.provide()
def allParts = this.allPartsProvider.provide()
Collection<Diagnostic> diagnostics = []
final Collection<Task> tasks = this.specsProvider.provide().findResults {
if (it.hasDiagnostics()) {
diagnostics.addAll(it.diagnostics)
} else {
def spec = it.get()
new TextToHtmlTask(
spec.toRelativeHtmlPath.apply(taskSpec),
taskSpec,
spec.text,
spec.template,
allTexts,
allModels,
allParts
)
}
}
Result.of(diagnostics, tasks)
}
}

View File

@ -2,5 +2,6 @@ package com.jessebrault.ssg.model
interface Model<T> {
String getName()
Class<T> getType()
T get()
}

View File

@ -1,14 +0,0 @@
package com.jessebrault.ssg.model
import com.jessebrault.ssg.task.TaskInput
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class ModelInput<T> implements TaskInput {
final String name
final Model<T> model
}

View File

@ -7,20 +7,21 @@ import java.util.function.Supplier
final class Models {
@SuppressWarnings('GroovyAssignabilityCheck')
static <T> Model<T> of(String name, T t) {
new SimpleModel<>(name, t)
new SimpleModel<>(name, t.class, t)
}
static <T> Model<T> ofSupplier(String name, Supplier<? extends T> tClosure) {
new SupplierBasedModel<>(name, tClosure)
static <T> Model<T> ofSupplier(String name, Class<T> type, Supplier<? extends T> tClosure) {
new SupplierBasedModel<>(name, type, tClosure)
}
static <T> Model<T> ofProvider(String name, Provider<? extends T> modelProvider) {
new ProviderModel<T>(name, modelProvider)
new ProviderModel<T>(name, modelProvider.type, modelProvider)
}
static <T> Model<T> ofNamedProvider(NamedProvider<? extends T> namedModelProvider) {
new ProviderModel<T>(namedModelProvider.name, namedModelProvider)
new ProviderModel<T>(namedModelProvider.name, namedModelProvider.type, namedModelProvider)
}
private Models() {}

View File

@ -13,6 +13,7 @@ import groowt.util.fp.provider.Provider
class ProviderModel<T> implements Model<T> {
final String name
final Class<T> type
private final Provider<T> modelProvider
@Override
@ -22,6 +23,6 @@ class ProviderModel<T> implements Model<T> {
@Override
String toString() {
"ProviderModel($this.name)"
"ProviderModel(name: $name, type: $type)"
}
}

View File

@ -12,6 +12,7 @@ import groovy.transform.TupleConstructor
final class SimpleModel<T> implements Model<T> {
final String name
final Class<T> type
private final T t
@Override
@ -21,7 +22,7 @@ final class SimpleModel<T> implements Model<T> {
@Override
String toString() {
"SimpleModel(${ this.t })"
"SimpleModel(name: $name, type: $type.name)"
}
}

View File

@ -8,21 +8,23 @@ 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<? extends T> supplier
final Class<T> type
SupplierBasedModel(String name, Supplier<? extends T> supplier) {
this.name = name
this.supplier = supplier
}
private final Supplier<? extends T> supplier
@Override
T get() {
this.supplier.get()
}
@Override
String toString() {
"SupplierBasedModel(name: $name, type: $type.name)"
}
}

View File

@ -0,0 +1,23 @@
package com.jessebrault.ssg.page
import com.jessebrault.ssg.view.PageView
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
class DefaultPage implements Page {
final String name
final String path
final String fileExtension
final Class<? extends PageView> viewType
@Override
String toString() {
"SimplePage(name: $name, path: $path, fileExtension: $fileExtension)"
}
}

View File

@ -1,41 +0,0 @@
package com.jessebrault.ssg.page
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.render.StandardGspRenderer
import com.jessebrault.ssg.util.Result
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode
final class GspPageRenderer implements PageRenderer {
private final StandardGspRenderer gspRenderer
GspPageRenderer(ClassLoader parentClassLoader) {
this.gspRenderer = new StandardGspRenderer(parentClassLoader)
}
@Override
Result<String> render(
Page specialPage,
RenderContext context
) {
def diagnostics = []
def result = this.gspRenderer.render(specialPage.text, context) {
it.diagnosticsConsumer = diagnostics.&addAll
it.loggerName = "GspSpecialPage(${ specialPage.path })"
}
if (result.hasDiagnostics()) {
Result.ofDiagnostics(diagnostics + result.diagnostics)
} else {
Result.of(diagnostics, result.get())
}
}
@Override
String toString() {
"GspSpecialPageRenderer()"
}
}

View File

@ -1,21 +1,10 @@
package com.jessebrault.ssg.page
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class Page {
final String path
final PageType type
final String text
@Override
String toString() {
"Page(path: ${ this.path }, type: ${ this.type })"
}
import com.jessebrault.ssg.view.PageView
interface Page {
String getName()
String getPath()
String getFileExtension()
Class<? extends PageView> getViewType()
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.ssg.page
interface PageFactory {
Collection<Page> create()
}

View File

@ -1,14 +0,0 @@
package com.jessebrault.ssg.page
import com.jessebrault.ssg.task.TaskInput
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class PageInput implements TaskInput {
final String name
final Page page
}

View File

@ -1,13 +0,0 @@
package com.jessebrault.ssg.page
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.util.Result
interface PageRenderer {
Result<String> render(
Page specialPage,
RenderContext context
)
}

View File

@ -0,0 +1,14 @@
package com.jessebrault.ssg.page
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface PageSpec {
String name()
String path()
String fileExtension() default '.html'
}

View File

@ -1,20 +0,0 @@
package com.jessebrault.ssg.page
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class PageType {
Collection<String> ids
PageRenderer renderer
@Override
String toString() {
"PageType(ids: ${ this.ids }, renderer: ${ this.renderer })"
}
}

View File

@ -1,14 +0,0 @@
package com.jessebrault.ssg.page
import groovy.transform.NullCheck
@NullCheck
final class PageTypes {
static PageType getGsp(Collection<String> extensions, ClassLoader parentClassLoader) {
new PageType(extensions, new GspPageRenderer(parentClassLoader))
}
private PageTypes() {}
}

View File

@ -1,50 +0,0 @@
package com.jessebrault.ssg.page
import com.jessebrault.ssg.provider.CollectionProvider
import com.jessebrault.ssg.provider.CollectionProviders
import com.jessebrault.ssg.util.ExtensionUtil
import groovy.transform.NullCheck
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.function.BiPredicate
import java.util.function.Predicate
@NullCheck
final class PagesProviders {
private static final Logger logger = LoggerFactory.getLogger(PagesProviders)
static CollectionProvider<Page> from(File pagesDirectory, Collection<PageType> pageTypes) {
from(pagesDirectory, pageTypes) { file, path -> true }
}
static CollectionProvider<Page> from(
File pagesDirectory,
Collection<PageType> pageTypes,
BiPredicate<File, String> filter
) {
CollectionProviders.fromDirectory(pagesDirectory) { file, relativePath ->
if (filter.test(file, relativePath)) {
def extension = ExtensionUtil.getExtension(relativePath)
if (extension) {
def pageType = pageTypes.find { it.ids.contains(extension) }
if (!pageType) {
logger.debug('there is no PageType for file {}; skipping', file)
return null
} else {
return new Page(relativePath, pageType, file.getText())
}
} else {
logger.debug('there is no extension for file {}; skipping', file)
return null
}
} else {
return null
}
}
}
private PagesProviders() {}
}

View File

@ -1,52 +0,0 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.render.StandardGspRenderer
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Result
import groovy.transform.EqualsAndHashCode
import org.jetbrains.annotations.Nullable
import static java.util.Objects.requireNonNull
@EqualsAndHashCode
final class GspPartRenderer implements PartRenderer {
private final StandardGspRenderer gspRenderer
GspPartRenderer(ClassLoader parentClassLoader) {
this.gspRenderer = new StandardGspRenderer(parentClassLoader)
}
@Override
Result<String> render(
Part part,
Map<String, Object> binding,
RenderContext context,
@Nullable Text text
) {
requireNonNull(part)
requireNonNull(binding)
requireNonNull(context)
def diagnostics = []
def result = this.gspRenderer.render(part.text, context) {
it.putCustom('binding', binding)
it.diagnosticsConsumer = diagnostics.&addAll
it.loggerName = "GspPart(${ part.path })"
if (text) {
it.text = text
}
}
if (result.hasDiagnostics()) {
Result.ofDiagnostics(diagnostics + result.diagnostics)
} else {
Result.of(diagnostics, result.get())
}
}
@Override
String toString() {
"GspPartRenderer()"
}
}

View File

@ -1,21 +0,0 @@
package com.jessebrault.ssg.part
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class Part {
String path
PartType type
String text
@Override
String toString() {
"Part(path: ${ this.path }, type: ${ this.type })"
}
}

View File

@ -1,17 +0,0 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.render.RenderContext
import com.jessebrault.ssg.util.Result
import com.jessebrault.ssg.text.Text
import org.jetbrains.annotations.Nullable
interface PartRenderer {
Result<String> render(
Part part,
Map<String, Object> binding,
RenderContext context,
@Nullable Text text
)
}

View File

@ -1,20 +0,0 @@
package com.jessebrault.ssg.part
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class PartType {
Collection<String> ids
PartRenderer renderer
@Override
String toString() {
"PartType(ids: ${ this.ids }, renderer: ${ this.renderer })"
}
}

View File

@ -1,14 +0,0 @@
package com.jessebrault.ssg.part
import groovy.transform.NullCheck
@NullCheck
final class PartTypes {
static PartType getGsp(Collection<String> extensions, ClassLoader parentClassLoader) {
new PartType(extensions, new GspPartRenderer(parentClassLoader))
}
private PartTypes() {}
}

View File

@ -1,33 +0,0 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.provider.CollectionProvider
import com.jessebrault.ssg.provider.CollectionProviders
import com.jessebrault.ssg.util.ExtensionUtil
import org.slf4j.Logger
import org.slf4j.LoggerFactory
final class PartsProviders {
private static final Logger logger = LoggerFactory.getLogger(PartsProviders)
static CollectionProvider<Part> from(File partsDir, Collection<PartType> partTypes) {
CollectionProviders.fromDirectory(partsDir) { file, relativePath ->
def extension = ExtensionUtil.getExtension(relativePath)
if (extension) {
def partType = partTypes.find { it.ids.contains(extension) }
if (!partType) {
logger.debug('there is no PartType for file {}; skipping', file)
return null
} else {
return new Part(relativePath, partType, file.getText())
}
} else {
logger.debug('there is no extension for file {}; skipping', file)
return null
}
}
}
private PartsProviders() {}
}

View File

@ -1,73 +0,0 @@
package com.jessebrault.ssg.provider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false, includeFields = true)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
abstract class AbstractCollectionProvider<T> implements CollectionProvider<T> {
static <T> CollectionProvider<T> concat(
CollectionProvider<T> cp0,
CollectionProvider<T> cp1
) {
new SupplierBasedCollectionProvider<>([cp0, cp1], {
cp0.provide() + cp1.provide()
})
}
private final Collection<CollectionProvider<T>> collectionProviderChildren
@Override
boolean contains(CollectionProvider<T> collectionProvider) {
collectionProvider in this
}
@Override
<C extends CollectionProvider<T>> boolean containsType(Class<C> childCollectionProviderClass) {
if (childCollectionProviderClass.isAssignableFrom(this.class)) {
true
} else {
this.collectionProviderChildren.inject(false) { acc, childProvider ->
acc || childCollectionProviderClass.isAssignableFrom(childProvider.class)
|| childProvider.containsType(childCollectionProviderClass)
}
}
}
@Override
<C extends CollectionProvider<T>> Collection<C> getChildrenOfType(Class<C> childCollectionProviderClass) {
this.collectionProviderChildren.inject([] as Collection<C>) { acc, childProvider ->
if (childCollectionProviderClass.isAssignableFrom(childProvider.class)) {
acc + childProvider.getChildrenOfType(childCollectionProviderClass) + (childProvider as C)
} else {
acc + childProvider.getChildrenOfType(childCollectionProviderClass)
}
}
}
@Override
Collection<DirectoryCollectionProvider<T>> getDirectoryCollectionProviderChildren() {
this.getChildrenOfType(DirectoryCollectionProvider)
}
@Override
CollectionProvider<T> plus(CollectionProvider<T> other) {
concat(this, other)
}
@Override
boolean isCase(CollectionProvider<T> collectionProvider) {
collectionProvider == this
|| collectionProvider in this.collectionProviderChildren
|| this.collectionProviderChildren.inject(
false,
{ acc, childCollectionProvider ->
acc || collectionProvider in childCollectionProvider
}
)
}
}

View File

@ -1,23 +0,0 @@
package com.jessebrault.ssg.provider;
import org.jetbrains.annotations.ApiStatus;
import java.util.Collection;
public interface CollectionProvider<T> {
Collection<T> provide();
boolean contains(CollectionProvider<T> collectionProvider);
@ApiStatus.Experimental
<C extends CollectionProvider<T>> boolean containsType(Class<C> childCollectionProviderClass);
@ApiStatus.Experimental
<C extends CollectionProvider<T>> Collection<C> getChildrenOfType(Class<C> childCollectionProviderClass);
Collection<DirectoryCollectionProvider<T>> getDirectoryCollectionProviderChildren();
CollectionProvider<T> plus(CollectionProvider<T> other);
boolean isCase(CollectionProvider<T> collectionProvider);
}

View File

@ -1,31 +0,0 @@
package com.jessebrault.ssg.provider
import org.jetbrains.annotations.Nullable
import java.util.function.BiFunction
import java.util.function.Supplier
final class CollectionProviders {
static <T> CollectionProvider<T> getEmpty() {
new SimpleCollectionProvider<>([])
}
static <T> CollectionProvider<T> fromCollection(Collection<T> ts) {
new SimpleCollectionProvider<T>(ts)
}
static <T> CollectionProvider<T> fromSupplier(Supplier<Collection<T>> supplier) {
new SupplierBasedCollectionProvider<>(supplier)
}
static <T> DirectoryCollectionProvider<T> fromDirectory(
File baseDirectory,
BiFunction<File, String, @Nullable T> elementFunction
) {
new FileBasedCollectionProvider<>(baseDirectory, elementFunction)
}
private CollectionProviders() {}
}

View File

@ -1,11 +0,0 @@
package com.jessebrault.ssg.provider;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.function.BiFunction;
public interface DirectoryCollectionProvider<T> extends CollectionProvider<T> {
File getBaseDirectory();
BiFunction<File, String, @Nullable T> getMapper();
}

View File

@ -1,57 +0,0 @@
package com.jessebrault.ssg.provider
import com.jessebrault.ssg.util.PathUtil
import groovy.io.FileType
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.PackageScope
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.function.BiFunction
@PackageScope
@NullCheck
@EqualsAndHashCode(includeFields = true, callSuper = true)
final class FileBasedCollectionProvider<T> extends AbstractCollectionProvider<T> implements DirectoryCollectionProvider<T> {
private static final Logger logger = LoggerFactory.getLogger(FileBasedCollectionProvider)
private final File baseDirectory
private final BiFunction<File, String, @Nullable T> elementFunction
FileBasedCollectionProvider(File baseDirectory, BiFunction<File, String, T> elementFunction) {
super([], [])
this.baseDirectory = baseDirectory
this.elementFunction = elementFunction
}
@Override
File getBaseDirectory() {
this.baseDirectory
}
@Override
BiFunction<File, String, T> getMapper() {
this.elementFunction
}
@Override
Collection<T> provide() {
if (!this.baseDirectory.isDirectory()) {
logger.warn('{} does not exist or is not a directory; returning empty collection', this.baseDirectory)
[]
} else {
final Collection<T> ts = []
this.baseDirectory.eachFileRecurse(FileType.FILES) {
def t = this.elementFunction.apply(it, PathUtil.relative(this.baseDirectory.path, it.path)) as T
if (t != null) {
ts << t
}
}
ts
}
}
}

View File

@ -1,27 +0,0 @@
package com.jessebrault.ssg.provider
import com.jessebrault.ssg.page.Page
import groovy.transform.TupleConstructor
import groowt.util.fp.provider.NamedProvider
import groowt.util.fp.provider.Provider
@TupleConstructor(includeFields = true, force = true, defaults = false)
class PageProvider implements NamedProvider<Page> {
final String name
final String path
private final Provider<Page> lazyPage
PageProvider(String name, String path, Page page) {
this.name = name
this.path = path
this.lazyPage = { page }
}
@Override
Page get() {
this.lazyPage.get()
}
}

View File

@ -1,24 +0,0 @@
package com.jessebrault.ssg.provider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.PackageScope
@PackageScope
@NullCheck
@EqualsAndHashCode(includeFields = true, callSuper = true)
final class SimpleCollectionProvider<T> extends AbstractCollectionProvider<T> {
private final Collection<T> ts
SimpleCollectionProvider(Collection<T> ts) {
super([], [])
this.ts = ts
}
@Override
Collection<T> provide() {
this.ts
}
}

View File

@ -1,33 +0,0 @@
package com.jessebrault.ssg.provider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.PackageScope
import java.util.function.Supplier
@PackageScope
@NullCheck
@EqualsAndHashCode(includeFields = true, callSuper = true)
final class SupplierBasedCollectionProvider<T> extends AbstractCollectionProvider<T> {
private final Supplier<Collection<T>> supplier
SupplierBasedCollectionProvider(
Collection<CollectionProvider<T>> collectionProviderChildren,
Supplier<Collection<T>> supplier
) {
super(collectionProviderChildren)
this.supplier = supplier
}
SupplierBasedCollectionProvider(Supplier<Collection<T>> supplier) {
this([], supplier)
}
@Override
Collection<T> provide() {
this.supplier.get()
}
}

View File

@ -1,39 +0,0 @@
package com.jessebrault.ssg.render
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false, force = true)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class RenderContext {
RenderContext(Map<String, Object> args = [:]) {
this(
args.sourcePath as String ?: '',
args.targetPath as String ?: '',
args.tasks as Collection<Task> ?: [],
args.texts as Collection<Text> ?: [],
args.models as Collection<Model<Object>> ?: [],
args.parts as Collection<Part> ?: [],
args.siteSpec as SiteSpec ?: SiteSpec.getBlank(),
args.globals as Map<String, Object> ?: [:]
)
}
final String sourcePath
final String targetPath
final Collection<Task> tasks
final Collection<Text> texts
final Collection<Model<Object>> models
final Collection<Part> parts
final SiteSpec siteSpec
final Map<String, Object> globals
}

View File

@ -1,39 +0,0 @@
package com.jessebrault.ssg.render
import com.jessebrault.gst.TemplateCreator
import com.jessebrault.gst.groovy.GroovyTemplateCreator
import com.jessebrault.gst.parser.ExtendedGstParser
import com.jessebrault.ssg.dsl.StandardDslMap
import com.jessebrault.ssg.util.Diagnostic
import com.jessebrault.ssg.util.Result
import java.util.function.Consumer
final class StandardGspRenderer {
private final TemplateCreator templateCreator
StandardGspRenderer(ClassLoader parentClassLoader) {
this.templateCreator = new GroovyTemplateCreator(ExtendedGstParser::new, parentClassLoader, true)
}
Result<String> render(
String templateText,
RenderContext context,
Consumer<StandardDslMap.Builder> dslMapBuilderConsumer
) {
try {
def templateCreateResult = this.templateCreator.create(templateText)
if (templateCreateResult.hasDiagnostics()) {
Result.ofDiagnostics(templateCreateResult.diagnostics.collect {
new Diagnostic(it.message, it.exception)
})
} else {
Result.of(templateCreateResult.get().make(StandardDslMap.get(context, dslMapBuilderConsumer)))
}
} catch (Exception e) {
Result.ofDiagnostics([new Diagnostic(e.message, e)])
}
}
}

View File

@ -1,23 +0,0 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.model.Model
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.provider.CollectionProvider
import com.jessebrault.ssg.provider.CollectionProviders
import com.jessebrault.ssg.text.Text
import static java.util.Objects.requireNonNull
abstract class AbstractRenderTaskFactory implements TaskFactory {
CollectionProvider<Text> allTextsProvider = CollectionProviders.getEmpty()
CollectionProvider<Model<Object>> allModelsProvider = CollectionProviders.getEmpty()
CollectionProvider<Part> allPartsProvider = CollectionProviders.getEmpty()
protected final void checkProviders() {
requireNonNull(this.allTextsProvider)
requireNonNull(this.allModelsProvider)
requireNonNull(this.allPartsProvider)
}
}

View File

@ -1,19 +0,0 @@
package com.jessebrault.ssg.task
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
abstract class AbstractTask implements Task {
final String name
@Override
String toString() {
"AbstractTask(name: ${ this.name })"
}
}

View File

@ -1,29 +0,0 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.util.Result
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.PackageScope
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
@PackageScope
@NullCheck
@EqualsAndHashCode(includeFields = true)
final class ClosureBasedTaskFactory implements TaskFactory {
private final Closure<Collection<Task>> closure
ClosureBasedTaskFactory(
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.task.TaskSpec')
Closure<Collection<Task>> closure
) {
this.closure = closure
}
@Override
Result<Collection<Task>> getTasks(TaskSpec taskSpec) {
this.closure(taskSpec)
}
}

View File

@ -1,5 +0,0 @@
package com.jessebrault.ssg.task
interface FileOutput extends TaskOutput {
File getFile()
}

View File

@ -1,8 +0,0 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.util.Diagnostic
interface Task {
String getName()
Collection<Diagnostic> execute(Collection<Task> allTasks)
}

View File

@ -1,17 +0,0 @@
package com.jessebrault.ssg.task
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
final class TaskFactories {
static TaskFactory of(
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.task.TaskSpec')
Closure<Collection<Void>> closure
) {
new ClosureBasedTaskFactory(closure)
}
private TaskFactories() {}
}

Some files were not shown because too many files have changed in this diff Show More