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.BuildSpec
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
import com.jessebrault.ssg.di.* 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.Page
import com.jessebrault.ssg.page.PageFactory import com.jessebrault.ssg.page.PageFactory
import com.jessebrault.ssg.page.PageSpec 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.util.Diagnostic
import com.jessebrault.ssg.view.PageView import com.jessebrault.ssg.view.PageView
import com.jessebrault.ssg.view.SkipTemplate import com.jessebrault.ssg.view.SkipTemplate
import com.jessebrault.ssg.view.WvcCompiler
import com.jessebrault.ssg.view.WvcPageView import com.jessebrault.ssg.view.WvcPageView
import groovy.transform.TupleConstructor import groovy.transform.TupleConstructor
import groowt.util.di.ObjectFactory
import groowt.util.di.RegistryObjectFactory import groowt.util.di.RegistryObjectFactory
import groowt.util.fp.either.Either import groowt.util.fp.option.Option
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.DefaultComponentTemplateCompilerConfiguration import groowt.view.component.compiler.DefaultComponentTemplateCompilerConfiguration
import groowt.view.component.compiler.SimpleComponentTemplateClassFactory import groowt.view.component.compiler.SimpleComponentTemplateClassFactory
import groowt.view.component.compiler.source.ComponentTemplateSource
import groowt.view.component.factory.ComponentFactories import groowt.view.component.factory.ComponentFactories
import groowt.view.component.web.DefaultWebViewComponentContext import groowt.view.component.web.DefaultWebViewComponentContext
import groowt.view.component.web.WebViewComponent import groowt.view.component.web.WebViewComponent
import groowt.view.component.web.WebViewComponentContext
import groowt.view.component.web.WebViewComponentScope 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 io.github.classgraph.ClassGraph
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -74,29 +70,111 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
texts texts
} }
protected Either<Diagnostic, ComponentTemplate> compileTemplate( protected WvcCompiler getWvcCompiler() {
Class<? extends ViewComponent> componentClass, def wvcCompilerConfiguration = new DefaultComponentTemplateCompilerConfiguration()
String resourceName, wvcCompilerConfiguration.groovyClassLoader = this.groovyClassLoader
ComponentTemplateCompilerConfiguration compilerConfiguration, def templateClassFactory = new SimpleComponentTemplateClassFactory(this.groovyClassLoader)
ComponentTemplateClassFactory templateClassFactory new WvcCompiler(wvcCompilerConfiguration, templateClassFactory)
}
protected WebViewComponentContext makeContext(
Set<Class<? extends WebViewComponent>> allWvc,
RegistryObjectFactory objectFactory,
WvcPageView pageView
) { ) {
def templateUrl = componentClass.getResource(resourceName) new DefaultWebViewComponentContext().tap {
if (templateUrl == null) { configureRootScope(WebViewComponentScope) {
return Either.left(new Diagnostic( // custom components
"Could not find templateResource: $resourceName" 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( // Output the page if not dryRun
source.descriptiveName, if (!this.dryRun) {
componentClass, def outputDir = buildSpec.outputDir.get {
source, new SsgException("The outputDir Property in $buildScriptFqn must be set.")
componentClass.packageName }
) outputDir.mkdirs()
def compileResult = compileUnit.compile(compilerConfiguration)
def templateClass = templateClassFactory.getTemplateClass(compileResult) def outputFile = new File(
def componentTemplate = templateClass.getConstructor().newInstance() outputDir,
return Either.right(componentTemplate) 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 @Override
@ -106,6 +184,8 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
String buildScriptFqn, String buildScriptFqn,
Map<String, String> buildScriptCliArgs Map<String, String> buildScriptCliArgs
) { ) {
def wvcCompiler = this.getWvcCompiler()
// run build script(s) and get buildSpec // run build script(s) and get buildSpec
def buildScriptGetter = new BuildScriptGetter( def buildScriptGetter = new BuildScriptGetter(
this.groovyClassLoader, this.groovyClassLoader,
@ -126,7 +206,7 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
) )
} }
// configure for Page instantiation // configure objectFactory for Page/PageFactory instantiation
objectFactoryBuilder.configureRegistry { objectFactoryBuilder.configureRegistry {
// extensions // extensions
addExtension(new TextsExtension().tap { addExtension(new TextsExtension().tap {
@ -151,6 +231,7 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
bind(named('baseUrl', String), toSingleton(buildSpec.baseUrl.get { bind(named('baseUrl', String), toSingleton(buildSpec.baseUrl.get {
new SsgException("The baseUrl Property in $buildScriptFqn must be set.") new SsgException("The baseUrl Property in $buildScriptFqn must be set.")
})) }))
bind(WvcCompiler, toSingleton(wvcCompiler))
} }
// get the objectFactory // get the objectFactory
@ -178,30 +259,31 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
def pageSpecInfo = pageViewInfo.getAnnotationInfo(PageSpec) def pageSpecInfo = pageViewInfo.getAnnotationInfo(PageSpec)
if (pageSpecInfo != null) { if (pageSpecInfo != null) {
def pageSpec = (PageSpec) pageSpecInfo.loadClassAndInstantiate() def pageSpec = (PageSpec) pageSpecInfo.loadClassAndInstantiate()
pages << new DefaultPage( pages << new DefaultWvcPage(
pageSpec.name(), name: pageSpec.name(),
pageSpec.path(), path: pageSpec.path(),
pageSpec.fileExtension(), fileExtension: pageSpec.fileExtension(),
(Class<? extends PageView>) pageViewInfo.loadClass(), viewType: (Class<? extends PageView>) pageViewInfo.loadClass(),
!pageSpec.templateResource().empty templateResource: !pageSpec.templateResource().empty
? pageSpec.templateResource() ? pageSpec.templateResource()
: pageViewInfo.simpleName + 'Template.wvc' : pageViewInfo.simpleName + 'Template.wvc',
objectFactory: objectFactory,
wvcCompiler: wvcCompiler
) )
} }
} }
// page factories // page factories
def pageFactoryTypes = [] as Set<Class<? extends PageFactory>>
def pageFactoryInfoList = scanResult.getClassesImplementing(PageFactory) def pageFactoryInfoList = scanResult.getClassesImplementing(PageFactory)
pageFactoryInfoList.each { pageFactoryInfo -> def pageFactoryTypes = pageFactoryInfoList.collect() { pageFactoryInfo ->
pageFactoryTypes << (pageFactoryInfo.loadClass() as Class<? extends PageFactory>) (pageFactoryInfo.loadClass() as Class<? extends PageFactory>)
} }
// instantiate page factory and create the pages // instantiate page factory and create the pages
pageFactoryTypes.each { pageFactoryType -> pageFactoryTypes.each { pageFactoryType ->
def pageFactory = objectFactory.createInstance(pageFactoryType) def pageFactory = objectFactory.createInstance(pageFactoryType)
pages.addAll(pageFactory.create()) def created = pageFactory.create()
pages.addAll(created)
} }
// get all web view components // get all web view components
@ -211,8 +293,8 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
} }
} }
// Configure for PageView instantiation // Configure objectFactory for PageView instantiation with the Pages we found/created
objectFactoryBuilder.configureRegistry { objectFactory.configureRegistry {
// extensions // extensions
addExtension(new PagesExtension().tap { addExtension(new PagesExtension().tap {
allPages.addAll(pages) allPages.addAll(pages)
@ -221,112 +303,11 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
} }
def diagnostics = [] as Collection<Diagnostic> def diagnostics = [] as Collection<Diagnostic>
def wvcCompilerConfiguration = new DefaultComponentTemplateCompilerConfiguration()
wvcCompilerConfiguration.groovyClassLoader = this.groovyClassLoader
def componentTemplateClassFactory = new SimpleComponentTemplateClassFactory(this.groovyClassLoader)
pages.each { pages.each {
// instantiate PageView def result = this.handlePage(buildScriptFqn, it, buildSpec, allWvc, objectFactory)
PageView pageView if (result.isPresent()) {
try { diagnostics << result.get()
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
)
}
} }
} }

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 package com.jessebrault.ssg.page
import com.jessebrault.ssg.util.Diagnostic
import com.jessebrault.ssg.view.PageView import com.jessebrault.ssg.view.PageView
import groowt.util.fp.either.Either
interface Page { interface Page {
String getName() String getName()
String getPath() String getPath()
String getFileExtension() 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" /> <AppenderRef ref="standard" />
</Root> </Root>
<Logger name="org.gradle" level="off" /> <Logger name="org.gradle" level="off" />
<Logger name="groowt" level="off" />
</Loggers> </Loggers>
</Configuration> </Configuration>