BuildScriptBase has better api.

This commit is contained in:
JesseBrault0709 2023-01-09 17:27:33 -06:00
parent 25a06332d4
commit 46f5395247
14 changed files with 293 additions and 130 deletions

View File

@ -27,8 +27,21 @@ dependencies {
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
implementation 'org.apache.logging.log4j:log4j-core:2.19.0'
/**
* TESTING
*/
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
}
application {
mainClassName = 'com.jessebrault.ssg.StaticSiteGeneratorCli'
}
test {
useJUnitPlatform()
}

View File

@ -32,7 +32,7 @@ class StaticSiteGeneratorCli implements Callable<Integer> {
private static final Logger logger = LogManager.getLogger(StaticSiteGeneratorCli)
private static class LogLevel {
static class LogLevel {
@CommandLine.Option(names = ['--info'], description = 'Log at INFO level.')
boolean info
@ -50,10 +50,12 @@ class StaticSiteGeneratorCli implements Callable<Integer> {
}
@CommandLine.ArgGroup(exclusive = true, heading = 'Log Level')
private LogLevel logLevel
LogLevel logLevel
@Override
Integer call() throws Exception {
Integer call() {
logger.traceEntry()
// Setup Loggers
def context = (LoggerContext) LogManager.getContext(false)
def configuration = context.getConfiguration()
@ -82,42 +84,51 @@ class StaticSiteGeneratorCli implements Callable<Integer> {
def defaultPartsProvider = new PartFilePartsProvider([gspPart], new File('parts'))
def defaultSpecialPagesProvider = new SpecialPageFileSpecialPagesProvider([gspSpecialPage], new File('specialPages'))
def config = new Config(
def defaultConfig = new Config(
textProviders: [defaultTextsProvider],
templatesProviders: [defaultTemplatesProvider],
partsProviders: [defaultPartsProvider],
specialPagesProviders: [defaultSpecialPagesProvider]
)
def defaultGlobals = [:]
def globals = [:]
Collection<Build> builds = []
// Run build script, if applicable
if (new File('build.groovy').exists()) {
logger.info('found buildScript: build.groovy')
if (new File('ssgBuilds.groovy').exists()) {
logger.info('found buildScript: ssgBuilds.groovy')
def buildScriptRunner = new GroovyBuildScriptRunner()
buildScriptRunner.runBuildScript(config, globals)
logger.debug('after running buildScript, config: {}', config)
logger.debug('after running buildScript, globals: {}', globals)
builds.addAll(buildScriptRunner.runBuildScript('ssgBuilds.groovy', defaultConfig, defaultGlobals))
logger.debug('after running ssgBuilds.groovy, builds: {}', builds)
}
// Generate
def ssg = new SimpleStaticSiteGenerator(config)
def result = ssg.generate(globals)
if (result.v1.size() > 0) {
result.v1.each {
logger.error(it.message)
}
return 1
} else {
def buildDir = new File('build')
result.v2.each {
def target = new File(buildDir, it.path + '.html')
target.createParentDirectories()
target.write(it.html)
}
return 0
if (builds.empty) {
// Add default build
builds << new Build('default', defaultConfig, defaultGlobals, new File('build'))
}
// Get ssg object
def ssg = new SimpleStaticSiteGenerator()
def hadDiagnostics = false
// Do each build
builds.each {
def result = ssg.generate(it)
if (result.v1.size() > 0) {
hadDiagnostics = true
result.v1.each {
logger.error(it.message)
}
} else {
result.v2.each { GeneratedPage generatedPage ->
def target = new File(it.outDir, generatedPage.path + '.html')
target.createParentDirectories()
target.write(generatedPage.html)
}
}
}
logger.traceExit(hadDiagnostics ? 1 : 0)
}
}

View File

@ -0,0 +1,52 @@
package com.jessebrault.ssg
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue
class StaticSiteGeneratorCliIntegrationTests {
@Test
@Disabled('until we figure out how to do the base dir arg')
void defaultConfiguration() {
def partsDir = new File('parts').tap {
mkdir()
deleteOnExit()
}
def specialPagesDir = new File('specialPages').tap {
mkdir()
deleteOnExit()
}
def templatesDir = new File('templatesDir').tap {
mkdir()
deleteOnExit()
}
def textsDir = new File('textsDir').tap {
mkdir()
deleteOnExit()
}
new File(partsDir, 'part.gsp').write('<%= binding.test %>')
new File(specialPagesDir, 'specialPage.gsp').write('<%= parts.part.render([test: "Greetings!"]) %>')
new File(templatesDir, 'template.gsp').write('<%= text %>')
new File(textsDir, 'text.md').write('---\ntemplate: template.gsp\n---\n**Hello, World!**')
StaticSiteGeneratorCli.main('--trace')
def buildDir = new File('build').tap {
deleteOnExit()
}
assertTrue(buildDir.exists())
def textHtml = new File(buildDir, 'text.html')
assertTrue(textHtml.exists())
assertEquals('<p><strong>Hello, World!</strong></p>\n', textHtml.text)
def specialPage = new File(buildDir, 'specialPage.html')
assertTrue(specialPage.exists())
assertEquals('Greetings!', specialPage.text)
}
}

View File

@ -0,0 +1,22 @@
package com.jessebrault.ssg
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
class Build {
String name
Config config
Map globals
File outDir
@Override
String toString() {
"Build(name: ${ this.name }, config: ${ this.config }, globals: ${ this.globals }, outDir: ${ this.outDir })"
}
}

View File

@ -10,7 +10,7 @@ import groovy.transform.MapConstructor
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor
@TupleConstructor(force = true)
@MapConstructor
@NullCheck
@EqualsAndHashCode
@ -21,6 +21,21 @@ class Config {
Collection<PartsProvider> partsProviders
Collection<SpecialPagesProvider> specialPagesProviders
Config(Config source) {
this.textProviders = [].tap {
addAll(source.textProviders)
}
this.templatesProviders = [].tap {
addAll(source.templatesProviders)
}
this.partsProviders = [].tap {
addAll(source.partsProviders)
}
this.specialPagesProviders = [].tap {
addAll(source.specialPagesProviders)
}
}
String toString() {
"Config(textProviders: ${ this.textProviders }, templatesProviders: ${ this.templatesProviders }, " +
"partsProviders: ${ this.partsProviders }, specialPagesProviders: ${ this.specialPagesProviders })"

View File

@ -18,20 +18,21 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
private static final Marker enter = MarkerFactory.getMarker('ENTER')
private static final Marker exit = MarkerFactory.getMarker('EXIT')
private final Config config
@Override
Tuple2<Collection<Diagnostic>, Collection<GeneratedPage>> generate(Map globals) {
logger.trace(enter, 'globals: {}', globals)
Tuple2<Collection<Diagnostic>, Collection<GeneratedPage>> generate(Build build) {
logger.trace(enter, 'build: {}', build)
def config = build.config
// Get all texts, templates, parts, and specialPages
def texts = this.config.textProviders.collectMany { it.getTextFiles() }
def templates = this.config.templatesProviders.collectMany { it.getTemplates() }
def parts = this.config.partsProviders.collectMany { it.getParts() }
def specialPages = this.config.specialPagesProviders.collectMany { it.getSpecialPages() }
def texts = config.textProviders.collectMany { it.getTextFiles() }
def templates = config.templatesProviders.collectMany { it.getTemplates() }
def parts = config.partsProviders.collectMany { it.getParts() }
def specialPages = config.specialPagesProviders.collectMany { it.getSpecialPages() }
logger.debug('\n\ttexts: {}\n\ttemplates: {}\n\tparts: {}\n\tspecialPages: {}', texts, templates, parts, specialPages)
def globals = build.globals
Collection<Diagnostic> diagnostics = []
Collection<GeneratedPage> generatedPages = []

View File

@ -1,5 +1,5 @@
package com.jessebrault.ssg
interface StaticSiteGenerator {
Tuple2<Collection<Diagnostic>, Collection<GeneratedPage>> generate(Map globals)
Tuple2<Collection<Diagnostic>, Collection<GeneratedPage>> generate(Build build)
}

View File

@ -0,0 +1,31 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Config
class BuildClosureDelegate {
String name
Config config
Map globals
File outDir
void config(
@DelegatesTo(value = ConfigClosureDelegate, strategy = Closure.DELEGATE_FIRST)
Closure configClosure
) {
configClosure.setDelegate(new ConfigClosureDelegate(this.config))
configClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
configClosure.run()
}
void globals(
@DelegatesTo(value = GlobalsClosureDelegate, strategy = Closure.DELEGATE_FIRST)
Closure globalsClosure
) {
def globalsConfigurator = new GlobalsClosureDelegate(this.globals)
globalsClosure.setDelegate(globalsConfigurator)
globalsClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
globalsClosure.run()
}
}

View File

@ -1,87 +1,33 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.specialpage.SpecialPageType
import com.jessebrault.ssg.template.TemplateType
import com.jessebrault.ssg.text.TextType
import groovy.transform.TupleConstructor
abstract class BuildScriptBase extends Script {
static class ConfigClosureDelegate {
Config defaultConfig
Map defaultGlobals
@Delegate
private final Config config
Collection<Build> builds = []
private final Collection<TextType> defaultTextTypes
private final Collection<TemplateType> defaultTemplateTypes
private final Collection<PartType> defaultPartTypes
private final Collection<SpecialPageType> defaultSpecialPageTypes
private int currentBuildNumber = 0
ConfigClosureDelegate(Config config) {
this.config = config
this.defaultTextTypes = this.config.textProviders.collectMany { it.textTypes }
this.defaultTemplateTypes = this.config.templatesProviders.collectMany { it.templateTypes }
this.defaultPartTypes = this.config.partsProviders.collectMany { it.partTypes }
this.defaultSpecialPageTypes = this.config.specialPagesProviders.collectMany { it.specialPageTypes }
}
Collection<TextType> getDefaultTextTypes() {
this.defaultTextTypes
}
Collection<TemplateType> getDefaultTemplateTypes() {
this.defaultTemplateTypes
}
Collection<PartType> getDefaultPartTypes() {
this.defaultPartTypes
}
Collection<SpecialPageType> getDefaultSpecialPageTypes() {
this.defaultSpecialPageTypes
}
}
@TupleConstructor(includeFields = true, defaults = false)
static class GlobalsClosureDelegate {
private final Map globals
@Override
Object getProperty(String propertyName) {
this.globals[propertyName]
}
@Override
void setProperty(String propertyName, Object newValue) {
this.globals.put(propertyName, newValue)
}
}
Config config
Map globals
void config(
@DelegatesTo(value = ConfigClosureDelegate, strategy = Closure.DELEGATE_FIRST)
Closure configClosure
void build(
@DelegatesTo(value = BuildClosureDelegate, strategy = Closure.DELEGATE_FIRST)
Closure buildClosure
) {
configClosure.setDelegate(new ConfigClosureDelegate(this.config))
configClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
configClosure.run()
}
void globals(
@DelegatesTo(value = GlobalsClosureDelegate, strategy = Closure.DELEGATE_FIRST)
Closure globalsClosure
) {
def globalsConfigurator = new GlobalsClosureDelegate(this.globals)
globalsClosure.setDelegate(globalsConfigurator)
globalsClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
globalsClosure.run()
def buildClosureDelegate = new BuildClosureDelegate().tap {
// Default values for Build properties
name = 'build' + currentBuildNumber
config = new Config(defaultConfig)
globals = new LinkedHashMap(defaultGlobals)
outDir = new File(name)
}
buildClosure.setDelegate(buildClosureDelegate)
buildClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
buildClosure.run()
this.builds << new Build(buildClosureDelegate.name, buildClosureDelegate.config, buildClosureDelegate.globals, buildClosureDelegate.outDir)
currentBuildNumber++
}
}

View File

@ -1,7 +1,8 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config
interface BuildScriptRunner {
void runBuildScript(Config config, Map globals)
Collection<Build> runBuildScript(String relativePath, Config defaultConfig, Map defaultGlobals)
}

View File

@ -0,0 +1,43 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Config
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.specialpage.SpecialPageType
import com.jessebrault.ssg.template.TemplateType
import com.jessebrault.ssg.text.TextType
class ConfigClosureDelegate {
@Delegate
private final Config config
private final Collection<TextType> defaultTextTypes
private final Collection<TemplateType> defaultTemplateTypes
private final Collection<PartType> defaultPartTypes
private final Collection<SpecialPageType> defaultSpecialPageTypes
ConfigClosureDelegate(Config config) {
this.config = config
this.defaultTextTypes = this.config.textProviders.collectMany { it.textTypes }
this.defaultTemplateTypes = this.config.templatesProviders.collectMany { it.templateTypes }
this.defaultPartTypes = this.config.partsProviders.collectMany { it.partTypes }
this.defaultSpecialPageTypes = this.config.specialPagesProviders.collectMany { it.specialPageTypes }
}
Collection<TextType> getDefaultTextTypes() {
this.defaultTextTypes
}
Collection<TemplateType> getDefaultTemplateTypes() {
this.defaultTemplateTypes
}
Collection<PartType> getDefaultPartTypes() {
this.defaultPartTypes
}
Collection<SpecialPageType> getDefaultSpecialPageTypes() {
this.defaultSpecialPageTypes
}
}

View File

@ -0,0 +1,20 @@
package com.jessebrault.ssg.buildscript
import groovy.transform.TupleConstructor
@TupleConstructor(includeFields = true, defaults = false)
class GlobalsClosureDelegate {
private final Map globals
@Override
Object getProperty(String propertyName) {
this.globals[propertyName]
}
@Override
void setProperty(String propertyName, Object newValue) {
this.globals.put(propertyName, newValue)
}
}

View File

@ -1,15 +1,16 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config
import groovy.transform.NullCheck
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer
@NullCheck
class GroovyBuildScriptRunner implements BuildScriptRunner {
@Override
void runBuildScript(Config config, Map globals) {
Objects.requireNonNull(config)
Objects.requireNonNull(globals)
Collection<Build> runBuildScript(String relativePath, Config defaultConfig, Map defaultGlobals) {
def engine = new GroovyScriptEngine([new File('.').toURI().toURL()] as URL[])
engine.config = new CompilerConfiguration().tap {
addCompilationCustomizers(new ImportCustomizer().tap {
@ -24,11 +25,13 @@ class GroovyBuildScriptRunner implements BuildScriptRunner {
})
scriptBaseClass = 'com.jessebrault.ssg.buildscript.BuildScriptBase'
}
def buildScript = engine.createScript('build.groovy', new Binding())
def buildScript = engine.createScript(relativePath, new Binding())
assert buildScript instanceof BuildScriptBase
buildScript.config = config
buildScript.globals = globals
buildScript.defaultConfig = defaultConfig
buildScript.defaultGlobals = defaultGlobals
buildScript.run()
buildScript.builds
}
}

View File

@ -14,11 +14,9 @@ import com.jessebrault.ssg.text.MarkdownTextRenderer
import com.jessebrault.ssg.text.TextFileTextsProvider
import com.jessebrault.ssg.text.TextType
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue
import static org.junit.jupiter.api.Assertions.*
class SimpleStaticSiteGeneratorIntegrationTests {
@ -27,6 +25,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
private File textsDir
private File specialPagesDir
private Build build
private StaticSiteGenerator ssg
@BeforeEach
@ -52,7 +51,10 @@ class SimpleStaticSiteGeneratorIntegrationTests {
partsProviders: [partsProvider],
specialPagesProviders: [specialPagesProvider]
)
this.ssg = new SimpleStaticSiteGenerator(config)
def globals = [:]
this.build = new Build('testBuild', config, globals, new File('build'))
this.ssg = new SimpleStaticSiteGenerator()
}
@Test
@ -60,7 +62,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
new File(this.textsDir, 'test.md').write('---\ntemplate: test.gsp\n---\n**Hello, World!**')
new File(this.templatesDir, 'test.gsp').write('<%= text %>')
def result = this.ssg.generate([:])
def result = this.ssg.generate(this.build)
assertTrue(result.v1.size() == 0)
assertTrue(result.v2.size() == 1)
@ -80,7 +82,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
new File(this.templatesDir, 'nested.gsp').write('<%= text %>')
def result = this.ssg.generate([:])
def result = this.ssg.generate(this.build)
assertTrue(result.v1.size() == 0)
assertTrue(result.v2.size() == 1)
@ -91,20 +93,23 @@ class SimpleStaticSiteGeneratorIntegrationTests {
}
@Test
@Disabled('have to figure out what to do when we need just a plain text for a special page')
void outputsSpecialPage() {
new FileTreeBuilder(this.specialPagesDir).file('special.gsp', $/<%= texts.find { it.path == 'test' }.render() %>/$)
new FileTreeBuilder(this.templatesDir).file('template.gsp', '<%= 1 + 1 %>')
new FileTreeBuilder(this.textsDir).file('test.md', 'Hello, World!')
new FileTreeBuilder(this.textsDir).file('test.md', '---\ntemplate: template.gsp\n---\nHello, World!')
def result = this.ssg.generate([:])
def result = this.ssg.generate(this.build)
assertEquals(0, result.v1.size())
assertEquals(2, result.v2.size())
def p0 = result.v2[0]
assertEquals('special', p0.path)
assertEquals('<p>Hello, World!</p>\n', p0.html)
def testPage = result.v2.find { it.path == 'test' }
assertNotNull(testPage)
assertEquals('2', testPage.html)
def specialPage = result.v2.find { it.path == 'special' }
assertNotNull(specialPage)
assertEquals('<p>Hello, World!</p>\n', specialPage.html)
}
}