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 // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
implementation 'org.apache.logging.log4j:log4j-core:2.19.0' 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 { application {
mainClassName = 'com.jessebrault.ssg.StaticSiteGeneratorCli' 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 final Logger logger = LogManager.getLogger(StaticSiteGeneratorCli)
private static class LogLevel { static class LogLevel {
@CommandLine.Option(names = ['--info'], description = 'Log at INFO level.') @CommandLine.Option(names = ['--info'], description = 'Log at INFO level.')
boolean info boolean info
@ -50,10 +50,12 @@ class StaticSiteGeneratorCli implements Callable<Integer> {
} }
@CommandLine.ArgGroup(exclusive = true, heading = 'Log Level') @CommandLine.ArgGroup(exclusive = true, heading = 'Log Level')
private LogLevel logLevel LogLevel logLevel
@Override @Override
Integer call() throws Exception { Integer call() {
logger.traceEntry()
// Setup Loggers // Setup Loggers
def context = (LoggerContext) LogManager.getContext(false) def context = (LoggerContext) LogManager.getContext(false)
def configuration = context.getConfiguration() def configuration = context.getConfiguration()
@ -82,42 +84,51 @@ class StaticSiteGeneratorCli implements Callable<Integer> {
def defaultPartsProvider = new PartFilePartsProvider([gspPart], new File('parts')) def defaultPartsProvider = new PartFilePartsProvider([gspPart], new File('parts'))
def defaultSpecialPagesProvider = new SpecialPageFileSpecialPagesProvider([gspSpecialPage], new File('specialPages')) def defaultSpecialPagesProvider = new SpecialPageFileSpecialPagesProvider([gspSpecialPage], new File('specialPages'))
def config = new Config( def defaultConfig = new Config(
textProviders: [defaultTextsProvider], textProviders: [defaultTextsProvider],
templatesProviders: [defaultTemplatesProvider], templatesProviders: [defaultTemplatesProvider],
partsProviders: [defaultPartsProvider], partsProviders: [defaultPartsProvider],
specialPagesProviders: [defaultSpecialPagesProvider] specialPagesProviders: [defaultSpecialPagesProvider]
) )
def defaultGlobals = [:]
def globals = [:] Collection<Build> builds = []
// Run build script, if applicable // Run build script, if applicable
if (new File('build.groovy').exists()) { if (new File('ssgBuilds.groovy').exists()) {
logger.info('found buildScript: build.groovy') logger.info('found buildScript: ssgBuilds.groovy')
def buildScriptRunner = new GroovyBuildScriptRunner() def buildScriptRunner = new GroovyBuildScriptRunner()
buildScriptRunner.runBuildScript(config, globals) builds.addAll(buildScriptRunner.runBuildScript('ssgBuilds.groovy', defaultConfig, defaultGlobals))
logger.debug('after running buildScript, config: {}', config) logger.debug('after running ssgBuilds.groovy, builds: {}', builds)
logger.debug('after running buildScript, globals: {}', globals)
} }
// Generate if (builds.empty) {
def ssg = new SimpleStaticSiteGenerator(config) // Add default build
def result = ssg.generate(globals) builds << new Build('default', defaultConfig, defaultGlobals, new File('build'))
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
} }
// 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.NullCheck
import groovy.transform.TupleConstructor import groovy.transform.TupleConstructor
@TupleConstructor @TupleConstructor(force = true)
@MapConstructor @MapConstructor
@NullCheck @NullCheck
@EqualsAndHashCode @EqualsAndHashCode
@ -21,6 +21,21 @@ class Config {
Collection<PartsProvider> partsProviders Collection<PartsProvider> partsProviders
Collection<SpecialPagesProvider> specialPagesProviders 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() { String toString() {
"Config(textProviders: ${ this.textProviders }, templatesProviders: ${ this.templatesProviders }, " + "Config(textProviders: ${ this.textProviders }, templatesProviders: ${ this.templatesProviders }, " +
"partsProviders: ${ this.partsProviders }, specialPagesProviders: ${ this.specialPagesProviders })" "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 enter = MarkerFactory.getMarker('ENTER')
private static final Marker exit = MarkerFactory.getMarker('EXIT') private static final Marker exit = MarkerFactory.getMarker('EXIT')
private final Config config
@Override @Override
Tuple2<Collection<Diagnostic>, Collection<GeneratedPage>> generate(Map globals) { Tuple2<Collection<Diagnostic>, Collection<GeneratedPage>> generate(Build build) {
logger.trace(enter, 'globals: {}', globals) logger.trace(enter, 'build: {}', build)
def config = build.config
// Get all texts, templates, parts, and specialPages // Get all texts, templates, parts, and specialPages
def texts = this.config.textProviders.collectMany { it.getTextFiles() } def texts = config.textProviders.collectMany { it.getTextFiles() }
def templates = this.config.templatesProviders.collectMany { it.getTemplates() } def templates = config.templatesProviders.collectMany { it.getTemplates() }
def parts = this.config.partsProviders.collectMany { it.getParts() } def parts = config.partsProviders.collectMany { it.getParts() }
def specialPages = this.config.specialPagesProviders.collectMany { it.getSpecialPages() } def specialPages = config.specialPagesProviders.collectMany { it.getSpecialPages() }
logger.debug('\n\ttexts: {}\n\ttemplates: {}\n\tparts: {}\n\tspecialPages: {}', texts, templates, parts, specialPages) logger.debug('\n\ttexts: {}\n\ttemplates: {}\n\tparts: {}\n\tspecialPages: {}', texts, templates, parts, specialPages)
def globals = build.globals
Collection<Diagnostic> diagnostics = [] Collection<Diagnostic> diagnostics = []
Collection<GeneratedPage> generatedPages = [] Collection<GeneratedPage> generatedPages = []

View File

@ -1,5 +1,5 @@
package com.jessebrault.ssg package com.jessebrault.ssg
interface StaticSiteGenerator { 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 package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config 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 { abstract class BuildScriptBase extends Script {
static class ConfigClosureDelegate { Config defaultConfig
Map defaultGlobals
@Delegate Collection<Build> builds = []
private final Config config
private final Collection<TextType> defaultTextTypes private int currentBuildNumber = 0
private final Collection<TemplateType> defaultTemplateTypes
private final Collection<PartType> defaultPartTypes
private final Collection<SpecialPageType> defaultSpecialPageTypes
ConfigClosureDelegate(Config config) { void build(
this.config = config @DelegatesTo(value = BuildClosureDelegate, strategy = Closure.DELEGATE_FIRST)
this.defaultTextTypes = this.config.textProviders.collectMany { it.textTypes } Closure buildClosure
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
) { ) {
configClosure.setDelegate(new ConfigClosureDelegate(this.config)) def buildClosureDelegate = new BuildClosureDelegate().tap {
configClosure.setResolveStrategy(Closure.DELEGATE_FIRST) // Default values for Build properties
configClosure.run() name = 'build' + currentBuildNumber
} config = new Config(defaultConfig)
globals = new LinkedHashMap(defaultGlobals)
void globals( outDir = new File(name)
@DelegatesTo(value = GlobalsClosureDelegate, strategy = Closure.DELEGATE_FIRST) }
Closure globalsClosure buildClosure.setDelegate(buildClosureDelegate)
) { buildClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
def globalsConfigurator = new GlobalsClosureDelegate(this.globals) buildClosure.run()
globalsClosure.setDelegate(globalsConfigurator) this.builds << new Build(buildClosureDelegate.name, buildClosureDelegate.config, buildClosureDelegate.globals, buildClosureDelegate.outDir)
globalsClosure.setResolveStrategy(Closure.DELEGATE_FIRST) currentBuildNumber++
globalsClosure.run()
} }
} }

View File

@ -1,7 +1,8 @@
package com.jessebrault.ssg.buildscript package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config import com.jessebrault.ssg.Config
interface BuildScriptRunner { 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 package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config import com.jessebrault.ssg.Config
import groovy.transform.NullCheck
import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer import org.codehaus.groovy.control.customizers.ImportCustomizer
@NullCheck
class GroovyBuildScriptRunner implements BuildScriptRunner { class GroovyBuildScriptRunner implements BuildScriptRunner {
@Override @Override
void runBuildScript(Config config, Map globals) { Collection<Build> runBuildScript(String relativePath, Config defaultConfig, Map defaultGlobals) {
Objects.requireNonNull(config)
Objects.requireNonNull(globals)
def engine = new GroovyScriptEngine([new File('.').toURI().toURL()] as URL[]) def engine = new GroovyScriptEngine([new File('.').toURI().toURL()] as URL[])
engine.config = new CompilerConfiguration().tap { engine.config = new CompilerConfiguration().tap {
addCompilationCustomizers(new ImportCustomizer().tap { addCompilationCustomizers(new ImportCustomizer().tap {
@ -24,11 +25,13 @@ class GroovyBuildScriptRunner implements BuildScriptRunner {
}) })
scriptBaseClass = 'com.jessebrault.ssg.buildscript.BuildScriptBase' 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 assert buildScript instanceof BuildScriptBase
buildScript.config = config buildScript.defaultConfig = defaultConfig
buildScript.globals = globals buildScript.defaultGlobals = defaultGlobals
buildScript.run() 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.TextFileTextsProvider
import com.jessebrault.ssg.text.TextType import com.jessebrault.ssg.text.TextType
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.*
import static org.junit.jupiter.api.Assertions.assertTrue
class SimpleStaticSiteGeneratorIntegrationTests { class SimpleStaticSiteGeneratorIntegrationTests {
@ -27,6 +25,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
private File textsDir private File textsDir
private File specialPagesDir private File specialPagesDir
private Build build
private StaticSiteGenerator ssg private StaticSiteGenerator ssg
@BeforeEach @BeforeEach
@ -52,7 +51,10 @@ class SimpleStaticSiteGeneratorIntegrationTests {
partsProviders: [partsProvider], partsProviders: [partsProvider],
specialPagesProviders: [specialPagesProvider] specialPagesProviders: [specialPagesProvider]
) )
this.ssg = new SimpleStaticSiteGenerator(config) def globals = [:]
this.build = new Build('testBuild', config, globals, new File('build'))
this.ssg = new SimpleStaticSiteGenerator()
} }
@Test @Test
@ -60,7 +62,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
new File(this.textsDir, 'test.md').write('---\ntemplate: test.gsp\n---\n**Hello, World!**') new File(this.textsDir, 'test.md').write('---\ntemplate: test.gsp\n---\n**Hello, World!**')
new File(this.templatesDir, 'test.gsp').write('<%= text %>') 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.v1.size() == 0)
assertTrue(result.v2.size() == 1) assertTrue(result.v2.size() == 1)
@ -80,7 +82,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
new File(this.templatesDir, 'nested.gsp').write('<%= text %>') 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.v1.size() == 0)
assertTrue(result.v2.size() == 1) assertTrue(result.v2.size() == 1)
@ -91,20 +93,23 @@ class SimpleStaticSiteGeneratorIntegrationTests {
} }
@Test @Test
@Disabled('have to figure out what to do when we need just a plain text for a special page')
void outputsSpecialPage() { void outputsSpecialPage() {
new FileTreeBuilder(this.specialPagesDir).file('special.gsp', $/<%= texts.find { it.path == 'test' }.render() %>/$) 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.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(0, result.v1.size())
assertEquals(2, result.v2.size()) assertEquals(2, result.v2.size())
def p0 = result.v2[0] def testPage = result.v2.find { it.path == 'test' }
assertEquals('special', p0.path) assertNotNull(testPage)
assertEquals('<p>Hello, World!</p>\n', p0.html) assertEquals('2', testPage.html)
def specialPage = result.v2.find { it.path == 'special' }
assertNotNull(specialPage)
assertEquals('<p>Hello, World!</p>\n', specialPage.html)
} }
} }