Refactoring of DefaultStaticSiteGenerator and related bugs.

This commit is contained in:
JesseBrault0709 2024-06-02 13:42:54 +02:00
parent 8ae16e327f
commit 599fb919c7
7 changed files with 287 additions and 174 deletions

View File

@ -5,7 +5,7 @@ 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.DefaultWvcPage
import com.jessebrault.ssg.page.Page
import com.jessebrault.ssg.page.PageFactory
import com.jessebrault.ssg.page.PageSpec
@ -13,23 +13,19 @@ import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.util.Diagnostic
import com.jessebrault.ssg.view.PageView
import com.jessebrault.ssg.view.SkipTemplate
import com.jessebrault.ssg.view.WvcCompiler
import com.jessebrault.ssg.view.WvcPageView
import groovy.transform.TupleConstructor
import groowt.util.di.ObjectFactory
import groowt.util.di.RegistryObjectFactory
import groowt.util.fp.either.Either
import groowt.view.component.ComponentTemplate
import groowt.view.component.ViewComponent
import groowt.view.component.compiler.ComponentTemplateClassFactory
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration
import groowt.util.fp.option.Option
import groowt.view.component.compiler.DefaultComponentTemplateCompilerConfiguration
import groowt.view.component.compiler.SimpleComponentTemplateClassFactory
import groowt.view.component.compiler.source.ComponentTemplateSource
import groowt.view.component.factory.ComponentFactories
import groowt.view.component.web.DefaultWebViewComponentContext
import groowt.view.component.web.WebViewComponent
import groowt.view.component.web.WebViewComponentContext
import groowt.view.component.web.WebViewComponentScope
import groowt.view.component.web.compiler.DefaultWebViewComponentTemplateCompileUnit
import groowt.view.component.web.lib.Each
import io.github.classgraph.ClassGraph
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@ -74,29 +70,111 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
texts
}
protected Either<Diagnostic, ComponentTemplate> compileTemplate(
Class<? extends ViewComponent> componentClass,
String resourceName,
ComponentTemplateCompilerConfiguration compilerConfiguration,
ComponentTemplateClassFactory templateClassFactory
protected WvcCompiler getWvcCompiler() {
def wvcCompilerConfiguration = new DefaultComponentTemplateCompilerConfiguration()
wvcCompilerConfiguration.groovyClassLoader = this.groovyClassLoader
def templateClassFactory = new SimpleComponentTemplateClassFactory(this.groovyClassLoader)
new WvcCompiler(wvcCompilerConfiguration, templateClassFactory)
}
protected WebViewComponentContext makeContext(
Set<Class<? extends WebViewComponent>> allWvc,
RegistryObjectFactory objectFactory,
WvcPageView pageView
) {
def templateUrl = componentClass.getResource(resourceName)
if (templateUrl == null) {
return Either.left(new Diagnostic(
"Could not find templateResource: $resourceName"
new DefaultWebViewComponentContext().tap {
configureRootScope(WebViewComponentScope) {
// custom components
allWvc.each { wvcClass ->
//noinspection GroovyAssignabilityCheck
add(wvcClass, ComponentFactories.ofClosureClassType(wvcClass) { Map attr, Object[] args ->
WebViewComponent component
if (!attr.isEmpty() && args.length > 0) {
component = objectFactory.createInstance(wvcClass, attr, *args)
} else if (!attr.isEmpty()) {
component = objectFactory.createInstance(wvcClass, attr)
} else if (args.length > 0) {
component = objectFactory.createInstance(wvcClass, *args)
} else {
component = objectFactory.createInstance(wvcClass)
}
component.context = pageView.context
if (component.componentTemplate == null
&& !wvcClass.isAnnotationPresent(SkipTemplate)) {
def compileResult = objectFactory.get(WvcCompiler).compileTemplate(
wvcClass,
wvcClass.simpleName + 'Template.wvc'
)
if (compileResult.isRight()) {
component.componentTemplate = compileResult.getRight()
} else {
def left = compileResult.getLeft()
throw new RuntimeException(left.message, left.exception)
}
}
return component
})
}
}
}
}
protected Option<Diagnostic> handlePage(
String buildScriptFqn,
Page page,
BuildSpec buildSpec,
Set<Class<? extends WebViewComponent>> allWvc,
ObjectFactory objectFactory
) {
// instantiate PageView
def pageViewResult = page.createView()
if (pageViewResult.isLeft()) {
return Option.lift(pageViewResult.getLeft())
}
PageView pageView = pageViewResult.getRight()
// Prepare for rendering
pageView.pageTitle = page.name
pageView.url = buildSpec.baseUrl.get() + page.path
if (pageView instanceof WvcPageView) {
pageView.context = this.makeContext(allWvc, objectFactory, pageView)
}
// Render the page
def sw = new StringWriter()
try {
pageView.renderTo(sw)
} catch (Exception exception) {
return Option.lift(new Diagnostic(
"There was an exception while rendering $page.name as $pageView.class.name",
exception
))
}
def source = ComponentTemplateSource.of(templateUrl)
def compileUnit = new DefaultWebViewComponentTemplateCompileUnit(
source.descriptiveName,
componentClass,
source,
componentClass.packageName
)
def compileResult = compileUnit.compile(compilerConfiguration)
def templateClass = templateClassFactory.getTemplateClass(compileResult)
def componentTemplate = templateClass.getConstructor().newInstance()
return Either.right(componentTemplate)
// 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,
page.path.replace('/', File.separator) + page.fileExtension
)
outputFile.parentFile.mkdirs()
try {
outputFile.write(sw.toString())
} catch (Exception exception) {
return Option.lift(new Diagnostic(
"There was an exception while writing $page.name to $outputFile",
exception
))
}
}
return Option.empty()
}
@Override
@ -106,6 +184,8 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
String buildScriptFqn,
Map<String, String> buildScriptCliArgs
) {
def wvcCompiler = this.getWvcCompiler()
// run build script(s) and get buildSpec
def buildScriptGetter = new BuildScriptGetter(
this.groovyClassLoader,
@ -126,7 +206,7 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
)
}
// configure for Page instantiation
// configure objectFactory for Page/PageFactory instantiation
objectFactoryBuilder.configureRegistry {
// extensions
addExtension(new TextsExtension().tap {
@ -151,6 +231,7 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
bind(named('baseUrl', String), toSingleton(buildSpec.baseUrl.get {
new SsgException("The baseUrl Property in $buildScriptFqn must be set.")
}))
bind(WvcCompiler, toSingleton(wvcCompiler))
}
// get the objectFactory
@ -178,30 +259,31 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
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(),
!pageSpec.templateResource().empty
pages << new DefaultWvcPage(
name: pageSpec.name(),
path: pageSpec.path(),
fileExtension: pageSpec.fileExtension(),
viewType: (Class<? extends PageView>) pageViewInfo.loadClass(),
templateResource: !pageSpec.templateResource().empty
? pageSpec.templateResource()
: pageViewInfo.simpleName + 'Template.wvc'
: pageViewInfo.simpleName + 'Template.wvc',
objectFactory: objectFactory,
wvcCompiler: wvcCompiler
)
}
}
// page factories
def pageFactoryTypes = [] as Set<Class<? extends PageFactory>>
def pageFactoryInfoList = scanResult.getClassesImplementing(PageFactory)
pageFactoryInfoList.each { pageFactoryInfo ->
pageFactoryTypes << (pageFactoryInfo.loadClass() as Class<? extends PageFactory>)
def pageFactoryTypes = pageFactoryInfoList.collect() { pageFactoryInfo ->
(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())
def created = pageFactory.create()
pages.addAll(created)
}
// get all web view components
@ -211,8 +293,8 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
}
}
// Configure for PageView instantiation
objectFactoryBuilder.configureRegistry {
// Configure objectFactory for PageView instantiation with the Pages we found/created
objectFactory.configureRegistry {
// extensions
addExtension(new PagesExtension().tap {
allPages.addAll(pages)
@ -221,112 +303,11 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
}
def diagnostics = [] as Collection<Diagnostic>
def wvcCompilerConfiguration = new DefaultComponentTemplateCompilerConfiguration()
wvcCompilerConfiguration.groovyClassLoader = this.groovyClassLoader
def componentTemplateClassFactory = new SimpleComponentTemplateClassFactory(this.groovyClassLoader)
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
}
// Prepare for rendering
pageView.pageTitle = it.name
pageView.url = buildSpec.baseUrl.get() + it.path
if (pageView instanceof WvcPageView) {
pageView.context = new DefaultWebViewComponentContext().tap {
configureRootScope(WebViewComponentScope) {
// custom components
allWvc.each { wvcClass ->
//noinspection GroovyAssignabilityCheck
add(wvcClass, ComponentFactories.ofClosureClassType(wvcClass) { Map attr, Object[] args ->
WebViewComponent component
if (!attr.isEmpty() && args.length > 0) {
component = objectFactory.createInstance(wvcClass, attr, *args)
} else if (!attr.isEmpty()) {
component = objectFactory.createInstance(wvcClass, attr)
} else if (args.length > 0) {
component = objectFactory.createInstance(wvcClass, *args)
} else {
component = objectFactory.createInstance(wvcClass)
}
component.context = pageView.context
if (component.componentTemplate == null
&& !wvcClass.isAnnotationPresent(SkipTemplate)) {
def compileResult = this.compileTemplate(
wvcClass,
wvcClass.simpleName + 'Template.wvc',
wvcCompilerConfiguration,
componentTemplateClassFactory
)
if (compileResult.isRight()) {
component.componentTemplate = compileResult.getRight()
} else {
diagnostics << compileResult.getLeft()
}
}
return component
})
}
}
}
if (pageView.componentTemplate == null) {
def compileResult = this.compileTemplate(
pageView.class,
it.templateResource,
wvcCompilerConfiguration,
componentTemplateClassFactory
)
if (compileResult.isRight()) {
pageView.componentTemplate = compileResult.getRight()
} else {
diagnostics << compileResult.getLeft()
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
)
}
def result = this.handlePage(buildScriptFqn, it, buildSpec, allWvc, objectFactory)
if (result.isPresent()) {
diagnostics << result.get()
}
}

View File

@ -0,0 +1,33 @@
package com.jessebrault.ssg.page
abstract class AbstractPage implements Page {
final String name
final String path
final String fileExtension
AbstractPage(Map args) {
name = args.name
path = args.path
fileExtension = args.fileExtension
}
@Override
int hashCode() {
Objects.hash(name, path, fileExtension)
}
@Override
boolean equals(Object obj) {
if (this.is(obj)) {
return true
} else if (obj instanceof Page) {
return name == obj.name
&& path == obj.path
&& fileExtension == obj.fileExtension
} else {
return false
}
}
}

View File

@ -1,24 +0,0 @@
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
final String templateResource
@Override
String toString() {
"SimplePage(name: $name, path: $path, fileExtension: $fileExtension, templateResource: $templateResource)"
}
}

View File

@ -0,0 +1,76 @@
package com.jessebrault.ssg.page
import com.jessebrault.ssg.di.SelfPageExtension
import com.jessebrault.ssg.util.Diagnostic
import com.jessebrault.ssg.view.PageView
import com.jessebrault.ssg.view.WvcCompiler
import com.jessebrault.ssg.view.WvcPageView
import groowt.util.di.RegistryObjectFactory
import groowt.util.fp.either.Either
class DefaultWvcPage extends AbstractPage implements Page {
final Class<? extends WvcPageView> viewType
final String templateResource
final RegistryObjectFactory objectFactory
final WvcCompiler wvcCompiler
DefaultWvcPage(Map args) {
super(args)
viewType = args.viewType
templateResource = args.templateResource ?: viewType.simpleName + 'Template.wvc'
objectFactory = args.objectFactory
wvcCompiler = args.wvcCompiler
}
@Override
Either<Diagnostic, PageView> createView() {
WvcPageView pageView
try {
objectFactory.registry.getExtension(SelfPageExtension).currentPage = this
pageView = objectFactory.createInstance(viewType)
} catch (Exception exception) {
return Either.left(new Diagnostic(
"There was an exception while constructing $viewType.name for $name",
exception
))
}
if (pageView.componentTemplate == null) {
def compileResult = wvcCompiler.compileTemplate(viewType, templateResource)
if (compileResult.isRight()) {
pageView.componentTemplate = compileResult.getRight()
} else {
return Either.left(compileResult.getLeft())
}
}
return Either.right(pageView)
}
@Override
int hashCode() {
Objects.hash(name, path, fileExtension, viewType, templateResource, objectFactory, wvcCompiler)
}
@Override
boolean equals(Object obj) {
if (!super.equals(obj)) {
return false
} else if (obj instanceof DefaultWvcPage) {
return viewType == obj.viewType
&& templateResource == obj.templateResource
&& objectFactory == obj.objectFactory
&& wvcCompiler == obj.wvcCompiler
} else {
return false
}
}
@Override
String toString() {
"DefaultPage(name: $name, path: $path, fileExtension: $fileExtension, " +
"viewType: $viewType, templateResource: $templateResource)"
}
}

View File

@ -1,11 +1,15 @@
package com.jessebrault.ssg.page
import com.jessebrault.ssg.util.Diagnostic
import com.jessebrault.ssg.view.PageView
import groowt.util.fp.either.Either
interface Page {
String getName()
String getPath()
String getFileExtension()
Class<? extends PageView> getViewType()
String getTemplateResource()
Either<Diagnostic, PageView> createView()
}

View File

@ -0,0 +1,42 @@
package com.jessebrault.ssg.view
import com.jessebrault.ssg.util.Diagnostic
import groovy.transform.TupleConstructor
import groowt.util.fp.either.Either
import groowt.view.component.ComponentTemplate
import groowt.view.component.ViewComponent
import groowt.view.component.compiler.ComponentTemplateClassFactory
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration
import groowt.view.component.compiler.source.ComponentTemplateSource
import groowt.view.component.web.compiler.DefaultWebViewComponentTemplateCompileUnit
@TupleConstructor
class WvcCompiler {
final ComponentTemplateCompilerConfiguration compilerConfiguration
final ComponentTemplateClassFactory templateClassFactory
Either<Diagnostic, ComponentTemplate> compileTemplate(
Class<? extends ViewComponent> componentClass,
String resourceName
) {
def templateUrl = componentClass.getResource(resourceName)
if (templateUrl == null) {
return Either.left(new Diagnostic(
"Could not find templateResource: $resourceName"
))
}
def source = ComponentTemplateSource.of(templateUrl)
def compileUnit = new DefaultWebViewComponentTemplateCompileUnit(
source.descriptiveName,
componentClass,
source,
componentClass.packageName
)
def compileResult = compileUnit.compile(compilerConfiguration)
def templateClass = templateClassFactory.getTemplateClass(compileResult)
def componentTemplate = templateClass.getConstructor().newInstance()
return Either.right(componentTemplate)
}
}

View File

@ -14,5 +14,6 @@
<AppenderRef ref="standard" />
</Root>
<Logger name="org.gradle" level="off" />
<Logger name="groowt" level="off" />
</Loggers>
</Configuration>