meatyInitAndBuild test written; added Jsoup; notion of baseDir; ResourceUtil.

This commit is contained in:
JesseBrault0709 2023-05-02 17:55:22 +02:00
parent 5a70f9c91c
commit 4c920fb485
21 changed files with 220 additions and 152 deletions

View File

@ -20,9 +20,14 @@ Here will be kept all of the various todos for this project, organized by releas
- [ ] Add a way for CLI to choose a build to do, or multiple builds, defaulting to 'default' if it exists.
- [ ] Write lots of tests for buildscript dsl, etc.
- [ ] Explore `base` in buildScript dsl.
- Get rid of `allBuilds` concept, and replace it with composable/concat-able builds. In the dsl we could have a notion of `abstractBuild` which can be 'extended' (i.e., on the left side of a concat operation) but not actually run (since it doesn't have a name).
- `OutputDir` should be concat-able, such that the left is the *base* for the right.
- `OutputDirFunctions.concat` should be concat-able as well, such that both are `BiFunction<OutputDir, Build, OutputDir>`, and the output of the left is the input of the right.
### Fix
- [ ] Update CHANGELOG to reflect the gsp-dsl changes.
- [ ] `taskTypes` gone, use class name instead
- [ ] introduction of `models`
- [x] Change most instances of `Closure<Void>` to `Closure<?>` to help with IDE expectations.
## Finished

View File

@ -15,6 +15,9 @@ dependencies {
// https://mvnrepository.com/artifact/org.commonmark/commonmark-ext-yaml-front-matter
implementation 'org.commonmark:commonmark-ext-yaml-front-matter:0.21.0'
// https://mvnrepository.com/artifact/org.jsoup/jsoup
implementation 'org.jsoup:jsoup:1.16.1'
}
jar {

View File

@ -4,6 +4,7 @@ import com.jessebrault.ssg.buildscript.Build
import com.jessebrault.ssg.buildscript.BuildScriptConfiguratorFactory
import com.jessebrault.ssg.buildscript.BuildScriptRunner
import com.jessebrault.ssg.util.Diagnostic
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
@ -30,7 +31,7 @@ final class BuildScriptBasedStaticSiteGenerator implements StaticSiteGenerator {
BuildScriptBasedStaticSiteGenerator(
BuildScriptRunner buildScriptRunner,
BuildScriptConfiguratorFactory configuratorFactory,
File buildScript = null,
@Nullable File buildScript = null,
Collection<File> buildSrcDirs = [],
Map<String, Object> scriptArgs = [:]
) {

View File

@ -12,12 +12,21 @@ import com.jessebrault.ssg.template.TemplateTypes
import com.jessebrault.ssg.template.TemplatesProviders
import com.jessebrault.ssg.text.TextTypes
import com.jessebrault.ssg.text.TextsProviders
import com.jessebrault.ssg.util.ExtensionUtil
import com.jessebrault.ssg.util.Result
import groovy.transform.EqualsAndHashCode
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 DefaultBuildScriptConfiguratorFactory implements BuildScriptConfiguratorFactory {
private final File baseDir
@Override
Consumer<BuildScriptBase> get() {
return {
@ -30,10 +39,10 @@ final class DefaultBuildScriptConfiguratorFactory implements BuildScriptConfigur
}
providers { types ->
texts(TextsProviders.from(new File('texts'), types.textTypes))
pages(PagesProviders.from(new File('pages'), types.pageTypes))
templates(TemplatesProviders.from(new File('templates'), types.templateTypes))
parts(PartsProviders.of(new File('parts'), types.partTypes))
texts(TextsProviders.from(new File(this.baseDir, 'texts'), types.textTypes))
pages(PagesProviders.from(new File(this.baseDir, 'pages'), types.pageTypes))
templates(TemplatesProviders.from(new File(this.baseDir, 'templates'), types.templateTypes))
parts(PartsProviders.of(new File(this.baseDir, 'parts'), types.partTypes))
}
taskFactories { sourceProviders ->
@ -48,7 +57,11 @@ final class DefaultBuildScriptConfiguratorFactory implements BuildScriptConfigur
def templateValue = frontMatterResult.get().get('template')
if (templateValue) {
def template = templates.find { it.path == templateValue }
return Result.of(new TextToHtmlSpec(it, template, it.path))
return Result.of(new TextToHtmlSpec(
it,
template,
ExtensionUtil.stripExtension(it.path) + '.html'
))
} else {
return null
}
@ -67,7 +80,7 @@ final class DefaultBuildScriptConfiguratorFactory implements BuildScriptConfigur
}
it.build('default') {
outputDir = new File('build')
outputDir = new File(this.baseDir, 'build')
}
}
}

View File

@ -6,6 +6,7 @@ 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
@ -29,9 +30,12 @@ abstract class AbstractHtmlTask extends AbstractTask implements HtmlTask {
transformResult.diagnostics
} else {
def content = transformResult.get()
def document = Jsoup.parse(content)
document.outputSettings().indentAmount(4)
def formatted = document.toString()
def target = new File(this.buildDir, this.path)
target.createParentDirectories()
target.write(content)
target.write(formatted)
[]
}
}

View File

@ -0,0 +1,27 @@
package com.jessebrault.ssg.util
final class ResourceUtil {
static void copyResourceToWriter(String name, Writer target) {
ResourceUtil.getClassLoader().getResourceAsStream(name).withReader {
it.transferTo(target)
}
}
static void copyResourceToFile(String name, File target) {
ResourceUtil.getClassLoader().getResourceAsStream(name).withReader { Reader reader ->
target.withWriter { Writer writer ->
reader.transferTo(writer)
}
}
}
static String loadResourceAsString(String name) {
def sw = new StringWriter()
copyResourceToWriter(name, sw)
sw.toString()
}
private ResourceUtil() {}
}

View File

@ -3,16 +3,16 @@ package com.jessebrault.ssg
import com.jessebrault.ssg.buildscript.BuildScriptBase
import com.jessebrault.ssg.buildscript.BuildScriptConfiguratorFactory
import com.jessebrault.ssg.buildscript.SimpleBuildScriptRunner
import com.jessebrault.ssg.util.FileUtil
import com.jessebrault.ssg.util.ResourceUtil
import org.junit.jupiter.api.Test
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.function.Consumer
import static com.jessebrault.ssg.util.FileAssertions.assertFileStructureAndContents
import static org.junit.jupiter.api.Assertions.assertTrue
// TODO: everything is working, now to expand this and refactor out common test code.
final class BuildScriptBasedStaticSiteGeneratorTests {
private static final Logger logger = LoggerFactory.getLogger(BuildScriptBasedStaticSiteGeneratorTests)
@ -22,7 +22,7 @@ final class BuildScriptBasedStaticSiteGeneratorTests {
def sourceDir = File.createTempDir()
def buildScript = new File(sourceDir, 'build.groovy')
FileUtil.copyResourceToFile('oneTextAndTemplate.groovy', buildScript)
ResourceUtil.copyResourceToFile('oneTextAndTemplate.groovy', buildScript)
new FileTreeBuilder(sourceDir).tap {
dir('texts') {
@ -52,9 +52,11 @@ final class BuildScriptBasedStaticSiteGeneratorTests {
})
def expectedBase = File.createTempDir()
new File(expectedBase, 'hello.html').write('<p>Hello, World!</p>\n')
new File(expectedBase, 'hello.html').tap {
ResourceUtil.copyResourceToFile('outputs/hello.html', it)
}
FileUtil.assertFileStructureAndContents(expectedBase, new File(sourceDir, 'build'))
assertFileStructureAndContents(expectedBase, new File(sourceDir, 'build'))
}
}

View File

@ -0,0 +1,26 @@
package com.jessebrault.ssg.util
import org.junit.jupiter.api.Test
import static ResourceUtil.copyResourceToFile
import static ResourceUtil.copyResourceToWriter
import static org.junit.jupiter.api.Assertions.assertEquals
final class ResourceUtilTests {
@Test
void copyResourceToWriterTest() {
def writer = new StringWriter()
copyResourceToWriter('testResource.txt', writer)
assertEquals('Hello, World!', writer.toString())
}
@Test
void copyResourceToTargetFileTest() {
def tempDir = File.createTempDir()
def target = new File(tempDir, 'testResource.txt')
copyResourceToFile('testResource.txt', target)
assertEquals('Hello, World!', target.text)
}
}

View File

@ -0,0 +1,6 @@
<html>
<head></head>
<body>
<p>Hello, World!</p>
</body>
</html>

View File

@ -0,0 +1 @@
Hello, World!

View File

@ -7,9 +7,9 @@ import java.nio.file.Path
import static org.junit.jupiter.api.Assertions.assertEquals
final class FileUtil {
final class FileAssertions {
private static final Logger logger = LoggerFactory.getLogger(FileUtil)
private static final Logger logger = LoggerFactory.getLogger(ResourceUtil)
private static Map<String, Object> fileToMap(File file) {
[
@ -46,20 +46,6 @@ final class FileUtil {
}
}
static void copyResourceToWriter(String name, Writer target) {
FileUtil.getClassLoader().getResourceAsStream(name).withReader {
it.transferTo(target)
}
}
static void copyResourceToFile(String name, File target) {
FileUtil.getClassLoader().getResourceAsStream(name).withReader { Reader reader ->
target.withWriter { Writer writer ->
reader.transferTo(writer)
}
}
}
private FileUtil() {}
private FileAssertions() {}
}

View File

@ -1,42 +0,0 @@
package com.jessebrault.ssg.util
import org.junit.jupiter.api.Test
import static com.jessebrault.ssg.util.FileUtil.assertFileStructureAndContents
import static com.jessebrault.ssg.util.FileUtil.copyResourceToFile
import static com.jessebrault.ssg.util.FileUtil.copyResourceToWriter
import static org.junit.jupiter.api.Assertions.assertEquals
final class FileUtilTests {
@Test
void sameStructureAndContentsTest() {
def b0 = File.createTempDir()
def b1 = File.createTempDir()
[b0, b1].each {
new FileTreeBuilder(it).tap {
file('testFile', 'test content')
dir('testDir') {
file('testNestedFile', 'test content')
}
}
}
assertFileStructureAndContents(b0, b1)
}
@Test
void copyResourceToWriterTest() {
def writer = new StringWriter()
copyResourceToWriter('testResource.txt', writer)
assertEquals('Hello, World!', writer.toString())
}
@Test
void copyResourceToTargetFileTest() {
def tempDir = File.createTempDir()
def target = new File(tempDir, 'testResource.txt')
copyResourceToFile('testResource.txt', target)
assertEquals('Hello, World!', target.text)
}
}

View File

@ -12,32 +12,38 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
private static final Logger logger = LogManager.getLogger(AbstractBuildCommand)
@CommandLine.Option(
names = ['-s', '--script', '--buildScript'],
description = 'The build script file to execute.'
names = '--baseDir',
description = 'The base directory for all components.'
)
protected File buildScript = null
File baseDir = new File('.')
@CommandLine.Option(
names = ['-s', '--script', '--buildScript'],
description = 'The build script file to execute, relative to the baseDir.'
)
File buildScript = new File('ssgBuilds.groovy')
@CommandLine.Option(
names = '--scriptArgs',
description = 'Named argument(s) to pass directly to the build script.',
split = ','
)
protected Map<String, String> scriptArgs = [:]
Map<String, String> scriptArgs = [:]
@CommandLine.Option(
names = '--buildSrcDirs',
description = 'Path(s) to director(ies) containing Groovy classes and scripts which should be visible to the main build script.',
description = 'Path(s) to director(ies) containing Groovy classes and scripts which should be visible to the main build script, relative to the baseDir.',
split = ',',
paramLabel = 'buildSrcDir'
)
protected Collection<File> buildSrcDirs = [new File('buildSrc')]
Collection<File> buildSrcDirs = [new File('buildSrc')]
@CommandLine.Option(
names = ['-b', '--build'],
description = 'The name of a build to execute.',
paramLabel = 'buildName'
)
protected Collection<String> requestedBuilds = ['default']
Collection<String> requestedBuilds = ['default']
protected StaticSiteGenerator staticSiteGenerator = null
@ -47,9 +53,11 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
if (this.staticSiteGenerator == null) {
this.staticSiteGenerator = new BuildScriptBasedStaticSiteGenerator(
new SimpleBuildScriptRunner(),
new DefaultBuildScriptConfiguratorFactory(),
this.buildScript,
this.buildSrcDirs,
new DefaultBuildScriptConfiguratorFactory(this.baseDir),
this.buildScript == new File('ssgBuilds.groovy') || this.buildScript.exists()
? new File(this.baseDir, this.buildScript.path)
: null,
this.buildSrcDirs.collect { new File(this.baseDir, it.path) },
this.scriptArgs
)
}

View File

@ -14,7 +14,7 @@ final class SsgBuild extends AbstractBuildCommand {
private static final Logger logger = LogManager.getLogger(SsgBuild)
@Override
Integer doSubCommand() {
protected Integer doSubCommand() {
logger.traceEntry()
def result = 0
this.requestedBuilds.each {

View File

@ -4,6 +4,8 @@ import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import picocli.CommandLine
import static com.jessebrault.ssg.util.ResourceUtil.copyResourceToFile
@CommandLine.Command(
name = 'init',
mixinStandardHelpOptions = true,
@ -11,44 +13,62 @@ import picocli.CommandLine
)
final class SsgInit extends AbstractSubCommand {
static void init(File targetDir, boolean meaty) {
new FileTreeBuilder(targetDir).with {
dir('texts') {
if (meaty) {
file('hello.md').tap {
copyResourceToFile('hello.md', it)
}
}
}
dir('pages') {
if (meaty) {
file('page.gsp').tap {
copyResourceToFile('page.gsp', it)
}
}
}
dir('templates') {
if (meaty) {
file('hello.gsp').tap {
copyResourceToFile('hello.gsp', it)
}
}
}
dir('parts') {
if (meaty) {
file('head.gsp').tap {
copyResourceToFile('head.gsp', it)
}
}
}
if (meaty) {
file('ssgBuilds.groovy').tap {
copyResourceToFile('ssgBuilds.groovy', it)
}
} else {
file('ssgBuilds.groovy').tap {
copyResourceToFile('ssgBuildsBasic.groovy', it)
}
}
}
}
private static final Logger logger = LogManager.getLogger(SsgInit)
@CommandLine.Option(names = ['-s', '--skeleton'], description = 'Include some basic files in the generated project.')
boolean withSkeletonFiles
@CommandLine.Option(names = ['-m', '--meaty'], description = 'Include some basic files in the generated project.')
boolean meaty
@CommandLine.Option(names = '--targetDir', description = 'The directory in which to generate the project')
File target = new File('.')
@Override
Integer doSubCommand() {
logger.traceEntry()
new FileTreeBuilder().with {
// Generate dirs
dir('texts') {
if (this.withSkeletonFiles) {
file('hello.md', this.getClass().getResource('/hello.md').text)
}
}
dir('templates') {
if (this.withSkeletonFiles) {
file('hello.gsp', this.getClass().getResource('/hello.gsp').text)
}
}
dir('parts') {
if (this.withSkeletonFiles) {
file('head.gsp', this.getClass().getResource('/head.gsp').text)
}
}
dir('specialPages') {
if (this.withSkeletonFiles) {
file('page.gsp', this.getClass().getResource('/page.gsp').text)
}
}
// Generate ssgBuilds.groovy
if (this.withSkeletonFiles) {
file('ssgBuilds.groovy', this.getClass().getResource('/ssgBuilds.groovy').text)
} else {
file('ssgBuilds.groovy', this.getClass().getResource('/ssgBuildsBasic.groovy').text)
}
}
init(this.target, this.meaty)
logger.traceExit(0)
}
}

View File

@ -1,7 +1,7 @@
<html>
<%
out << parts['head.gsp'].render([
title: "${ globals.siteTitle }: ${ text.frontMatter.title }"
title: "${ siteSpec.name }: ${ text.frontMatter.title }"
])
%>
<body>

View File

@ -1,6 +1,6 @@
<html>
<head>
<title>${ globals.siteTitle }: Page</title>
<title>${ siteSpec.name }: Page</title>
</head>
<body>
<%= texts.find { it.path == 'hello.md' }.render() %>

View File

@ -1 +1 @@
// This file was auto-generated by the ssg init command.
// This file was auto-generated by the ssg init command.

View File

@ -1,6 +1,6 @@
package com.jessebrault.ssg
import org.junit.jupiter.api.Disabled
import com.jessebrault.ssg.util.ResourceUtil
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
@ -9,44 +9,36 @@ import static org.junit.jupiter.api.Assertions.assertTrue
final 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()
}
void meatyInitAndBuild() {
def tempDir = File.createTempDir()
SsgInit.init(tempDir, true)
new File(partsDir, 'part.gsp').write('<%= binding.test %>')
new File(specialPagesDir, 'page.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()
def ssgBuild = new SsgBuild().tap {
it.cli = new StaticSiteGeneratorCli().tap {
it.logLevel = new StaticSiteGeneratorCli.LogLevel().tap {
it.trace = true
}
}
it.baseDir = tempDir
it.requestedBuilds = ['production']
}
assertEquals(0, ssgBuild.call())
def buildDir = new File(tempDir, 'build')
assertTrue(buildDir.exists())
assertTrue(buildDir.directory)
def textHtml = new File(buildDir, 'text.html')
assertTrue(textHtml.exists())
assertEquals('<p><strong>Hello, World!</strong></p>\n', textHtml.text)
def textOutputFile = new File(buildDir, 'hello.html')
assertTrue(textOutputFile.exists())
assertTrue(textOutputFile.file)
def pageOutputFile = new File(buildDir, 'page.html')
assertTrue(pageOutputFile.exists())
assertTrue(pageOutputFile.file)
def specialPage = new File(buildDir, 'specialPage.html')
assertTrue(specialPage.exists())
assertEquals('Greetings!', specialPage.text)
def expectedText = ResourceUtil.loadResourceAsString('hello.html')
def expectedPage = ResourceUtil.loadResourceAsString('page.html')
assertEquals(expectedText, textOutputFile.text)
assertEquals(expectedPage, pageOutputFile)
}
}

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>My Site: Greeting</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>

View File

@ -0,0 +1,8 @@
<html>
<head>
<title>My Site: Page</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>