Massive clean up and api/cli/gradle work. Running successfully now in test-ssg-project.
This commit is contained in:
parent
140dffefc6
commit
bc1d545297
16
.run/debug-test-project.run.xml
Normal file
16
.run/debug-test-project.run.xml
Normal 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>
|
19
.run/launch-debug-test-project.run.xml
Normal file
19
.run/launch-debug-test-project.run.xml
Normal 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>
|
19
.run/run-test-project.run.xml
Normal file
19
.run/run-test-project.run.xml
Normal 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>
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
13
api/src/main/groovy/com/jessebrault/ssg/SsgException.groovy
Normal file
13
api/src/main/groovy/com/jessebrault/ssg/SsgException.groovy
Normal 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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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)"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
15
api/src/main/groovy/com/jessebrault/ssg/di/Global.groovy
Normal file
15
api/src/main/groovy/com/jessebrault/ssg/di/Global.groovy
Normal 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()
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.jessebrault.ssg.objects
|
||||
package com.jessebrault.ssg.di
|
||||
|
||||
import jakarta.inject.Qualifier
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.jessebrault.ssg.objects
|
||||
package com.jessebrault.ssg.di
|
||||
|
||||
import jakarta.inject.Qualifier
|
||||
|
@ -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)
|
@ -1,4 +1,4 @@
|
||||
package com.jessebrault.ssg.objects
|
||||
package com.jessebrault.ssg.di
|
||||
|
||||
import jakarta.inject.Qualifier
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.jessebrault.ssg.objects
|
||||
package com.jessebrault.ssg.di
|
||||
|
||||
import jakarta.inject.Qualifier
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
13
api/src/main/groovy/com/jessebrault/ssg/di/SelfPage.groovy
Normal file
13
api/src/main/groovy/com/jessebrault/ssg/di/SelfPage.groovy
Normal 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 {}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.jessebrault.ssg.objects
|
||||
package com.jessebrault.ssg.di
|
||||
|
||||
import groowt.util.di.DefaultRegistryObjectFactory
|
||||
import groowt.util.di.RegistryObjectFactory
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
@ -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() })"
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package com.jessebrault.ssg.html
|
||||
|
||||
import com.jessebrault.ssg.task.FileOutput
|
||||
|
||||
interface HtmlOutput extends FileOutput {
|
||||
String getHtmlPath()
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
@ -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() })"
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
@ -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() })"
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
@ -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() })"
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -2,5 +2,6 @@ package com.jessebrault.ssg.model
|
||||
|
||||
interface Model<T> {
|
||||
String getName()
|
||||
Class<T> getType()
|
||||
T get()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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() {}
|
||||
|
@ -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)"
|
||||
}
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
}
|
||||
|
@ -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)"
|
||||
}
|
||||
|
||||
}
|
@ -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()"
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.page
|
||||
|
||||
interface PageFactory {
|
||||
Collection<Page> create()
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
)
|
||||
|
||||
}
|
14
api/src/main/groovy/com/jessebrault/ssg/page/PageSpec.groovy
Normal file
14
api/src/main/groovy/com/jessebrault/ssg/page/PageSpec.groovy
Normal 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'
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
@ -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()"
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
)
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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() {}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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)])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
interface FileOutput extends TaskOutput {
|
||||
File getFile()
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user