diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..e717505
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,122 @@
+# Changelog
+
+All notable changes to SSG will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## Next
+
+### Added
+
+- Templates, SpecialPages, and Parts all have access to two related objects: `tasks` and `taskTypes`. The first is an instance of [`TaskContainer`](lib/src/main/groovy/com/jessebrault/ssg/task/TaskContainer.groovy) and can be used to access all of the [`Task`](lib/src/main/groovy/com/jessebrault/ssg/task/Task.groovy) instances for a given build. The second is in an instance of [`TaskTypeContainer`](lib/src/main/groovy/com/jessebrault/ssg/task/TaskTypeContainer.groovy) and can be used to access the various [`TaskType`](lib/src/main/groovy/com/jessebrault/ssg/task/TaskType.groovy) instances for a given build. For example, one could use these together to obtain the output path of an html file from another task (assume one is in a `.gsp` file):
+ ```groovy
+ def otherTask = tasks.findAllByType(taskTypes.textToHtmlFile).find { it.input.path == 'someText.md' }
+ assert otherTask.output.htmlPath == 'someText.html'
+ ```
+ This is a complicated and experimental feature and may be changed frequently depending on future developments. [92c8108](https://github.com/JesseBrault0709/ssg/commit/92c8108).
+- Templates, SpecialPages, and Parts all have access to a `logger` of type `org.slf4j.Logger`. [64f342a](https://github.com/JesseBrault0709/ssg/commit/64f342a).
+- There is now the notion of a `siteSpec`, an object of type of [`SiteSpec`](lib/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy). It is simmilar to the `globals` object in that it contains properties that are available in all Templates, SpecialPages, and Parts. Unlike `globals`, which contains user-defined keys, `siteSpec` contains (for now) the pre-defined keys `baseUrl` and `title`. To configure the `siteSpec`, add the following block to a `build` block in `ssgBuilds.groovy`:
+ ```groovy
+ siteSpec {
+ baseUrl = 'https://test.com' // or whatever, defaults to an empty string
+ title = 'My Great Website' // or whatever, defaults to an empty string
+ }
+ ```
+ Then use it in any Template, SpecialPage, or part like so:
+ ```gsp
+ <%
+ assert siteSpec.baseUrl == 'https://test.com' && siteSpec.title == 'My Great Website'
+ %>
+ ```
+ [111bdea](https://github.com/JesseBrault0709/ssg/commit/111bdea), [ef9e566](https://github.com/JesseBrault0709/ssg/commit/ef9e566).
+- Templates, SpecialPages, and Parts all have access to `targetPath` of type `String` representing the path of the 'output' file. For now, this is always a `.html` file.
+ ```gsp
+ <%
+ // in a template where the source text is 'foo/bar.md'
+ assert targetPath == 'foo/bar.html'
+
+ // in a special page whose path is 'special.gsp'
+ assert targetPath == 'special.html'
+
+ // in a part with a source text of 'foo/bar/hello.md'
+ assert targetPath == 'foo/bar/hello.html'
+
+ // in a part with a source special page of 'foo/bar/baz/special.gsp'
+ assert targetParth == 'foo/bar/baz/special.html'
+ %>
+ ```
+ [6de83df](https://github.com/JesseBrault0709/ssg/commit/6de83df), [06499a9](https://github.com/JesseBrault0709/ssg/commit/06499a9).
+- Templates, SpecialPages, and Parts all have access to `sourcePath` of type `String` representing the path of the 'source' file: either a text file or a special page. In Templates, the 'source' comes from the path of the Text being rendered; in SpecialPages, this comes from the path of the SpecialPage being rendered (i.e., itself); in Parts, this comes from either the Template or SpecialPage which called (i.e., embedded) the Part.
+ ```gsp
+ <%
+ // in a template or part when rendering a text at 'home.md'
+ assert sourcePath == 'home.md'
+
+ // in a template or part when rendering a text at 'posts/helloWorld.md'
+ assert sourcePath == 'posts/helloWorld.md'
+
+ // in a special page or part when rendering a special page at 'foo/bar/specialPage.gsp'
+ assert sourcePath == 'foo/bar/specialPage.gsp'
+ %>
+ ```
+ [0371d41](https://github.com/JesseBrault0709/ssg/commit/0371d41), [9983685](https://github.com/JesseBrault0709/ssg/commit/9983685), [076bc9b](https://github.com/JesseBrault0709/ssg/commit/076bc9b), [c5ac810](https://github.com/JesseBrault0709/ssg/commit/c5ac810).
+- Templates, SpecialPages, and Parts all have access to a `urlBuilder` of type [`PathBasedUrlBuilder`](lib/src/main/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilder.groovy) (implementing [`UrlBuilder`](lib/src/main/groovy/com/jessebrault/ssg/url/UrlBuilder.groovy)). It can be used to obtain both absolute (using `siteSpec.baseUrl`) and relative urls (to the current `targetPath`). Use it like so:
+ ```gsp
+ <%
+ // when targetPath == 'simple.html'
+ assert urlBuilder.relative('images/test.jpg') == 'images/test.jpg'
+
+ // when targetPath == 'nested/post.html'
+ assert urlBuilder.relative('images/test.jpg') == '../images/test.jpg'
+
+ // when baseUrl is 'https://test.com' and targetPath is 'simple.html'
+ assert urlBuilder.absolute == 'https://test.com/simple.html
+
+ // when baseUrl is 'https://test.com' and we want an absolute to another file
+ assert urlBuilder.absolute('images/test.jpg') == 'https://test.com/images/test.jpg'
+ %>
+ ```
+ *Nota bene:* likely will break on Windows since `PathBasedUrlBuilder` currently uses Java's `Path` api and paths would be thusly rendered using Windows-style backslashes. This will be explored in the future. [0371d41](https://github.com/JesseBrault0709/ssg/commit/0371d41), [0762dc6](https://github.com/JesseBrault0709/ssg/commit/0762dc6), [60f4c14](https://github.com/JesseBrault0709/ssg/commit/60f4c14).
+- Parts have access to all other parts now via `parts`, an object of type [`EmbeddablePartsMap`](lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy). For example:
+
+ ```gsp
+ <%
+ // myPart.gsp
+ out << parts['otherPart.gsp'].render()
+ %>
+ ```
+
+ [0e49414](https://github.com/JesseBrault0709/ssg/commit/0e49414).
+- A `tagBuilder` object of type [`DynamicTagBuilder`](lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy) (implementing [`TagBuilder`](lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/TagBuilder.groovy)) is available in Templates, SpecialPages, and Parts.
+
+ ```gsp
+ <%
+ def simpleTag = tagBuilder.test()
+ assert simpleTag == ''
+
+ def tagWithBody = tagBuilder.title 'Hello, World!'
+ assert tagWithBody == '
Hello, World!'
+
+ def tagWithAttributes = tagBuilder.meta name: 'og:title', content: 'Hello, World!'
+ assert tagWithAttributes == ''
+
+ def tagWithAttributesAndBody = tagBuilder.p([id: 'my-paragraph'], 'Hello, World!')
+ assert tagWithAttributesAndBody == 'Hello, World!
'
+ %>
+ ```
+
+ This is likely most useful for building simple, one-line html/xml tags. [93687d](https://github.com/JesseBrault0709/ssg/commit/936587d).
+- Parts have a `text` object of type [`EmbeddableText`](lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy). If one is rendering a Part called from anything other than a Template (which has an associated text), this will be `null`. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5).
+
+### Breaking Changes
+- The path of a file was stripped of its extension when previously referring to Texts or SpecialPages; now, the extension is present. For example:
+ ```gsp
+ <%
+ // suppose we have a text called 'test.md' and we are in a template, special page, or part
+ assert texts['test'] == null
+ assert texts['test.md'] != null
+ %>
+ ```
+ [0371d41](https://github.com/JesseBrault0709/ssg/commit/0371d41).
+- The `text` object in Templates is now an instance of [`EmbeddableText`](lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy) instead of `String`. Thus, one must use `text.render()` to obtain the rendered text. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5).
+- The `frontMatter` object is no longer available. Use `text.frontMatter` instead. [eafc8cd](https://github.com/JesseBrault0709/ssg/commit/eafc8cd), [c5ac810](https://github.com/JesseBrault0709/ssg/commit/c5ac810).
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..45d4d52
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,19 @@
+# TODO
+
+Here will be kept all of the various todos for this project, organized by release.
+
+## Next
+
+### Add
+- [ ] Add some kind of `outputs` map to dsl that can be used to retrieve various info about another output of the current build. For example:
+ ```groovy
+ // while in a special page 'special.gsp' we could get the 'output' info for a text 'blog/post.md'
+ def post = outputs['blog/post.md']
+ assert post instanceof Output // or something
+ assert post.path == 'blog/post.md'
+ assert post.targetPath = 'blog/post.html'
+ // as well as some other information, perhaps such as the Type, extension, *etc.*
+ ```
+- [ ] Add `extensionUtil` object to dsl.
+
+### Fix
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..663921b
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,11 @@
+plugins {
+ id 'org.asciidoctor.jvm.convert' version '3.3.2'
+}
+
+repositories {
+ mavenCentral()
+}
+
+asciidoctor {
+ sourceDir = 'docs/asciidoc'
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/ssg.common.gradle b/buildSrc/src/main/groovy/ssg.common.gradle
index 901fd91..c89d81f 100644
--- a/buildSrc/src/main/groovy/ssg.common.gradle
+++ b/buildSrc/src/main/groovy/ssg.common.gradle
@@ -1,6 +1,8 @@
plugins {
id 'com.jessebrault.jbarchiva'
id 'groovy'
+ id 'java-library'
+ id 'java-test-fixtures'
}
group 'com.jessebrault.ssg'
@@ -12,16 +14,45 @@ repositories {
dependencies {
// https://mvnrepository.com/artifact/org.apache.groovy/groovy
- implementation 'org.apache.groovy:groovy:4.0.9'
+ api 'org.apache.groovy:groovy:4.0.9'
+
+ // https://mvnrepository.com/artifact/org.jetbrains/annotations
+ api 'org.jetbrains:annotations:24.0.0'
+
+ /**
+ * Logging
+ */
+ // https://mvnrepository.com/artifact/org.slf4j/slf4j-api
+ implementation 'org.slf4j:slf4j-api:1.7.36'
+
+ testFixturesImplementation 'org.slf4j:slf4j-api:1.7.36'
/**
* TESTING
*/
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
- testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
+ testFixturesApi 'org.junit.jupiter:junit-jupiter-api:5.9.2'
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
+
+ /**
+ * Mockito
+ */
+ // https://mvnrepository.com/artifact/org.mockito/mockito-core
+ testFixturesApi 'org.mockito:mockito-core:5.1.1'
+
+ // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
+ testFixturesApi 'org.mockito:mockito-junit-jupiter:5.1.1'
+
+ /**
+ * Test Logging
+ */
+ // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl
+ testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0'
+
+ // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
+ testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.19.0'
}
test {
diff --git a/buildSrc/src/main/groovy/ssg.lib.gradle b/buildSrc/src/main/groovy/ssg.lib.gradle
deleted file mode 100644
index 443abeb..0000000
--- a/buildSrc/src/main/groovy/ssg.lib.gradle
+++ /dev/null
@@ -1,33 +0,0 @@
-plugins {
- id 'java-library'
-}
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- /**
- * Logging
- */
- // https://mvnrepository.com/artifact/org.slf4j/slf4j-api
- implementation 'org.slf4j:slf4j-api:1.7.36'
-
- /**
- * Test Logging
- */
- // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl
- testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0'
-
- // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
- testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.19.0'
-
- /**
- * Mockito
- */
- // https://mvnrepository.com/artifact/org.mockito/mockito-core
- testImplementation 'org.mockito:mockito-core:4.11.0'
-
- // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
- testImplementation 'org.mockito:mockito-junit-jupiter:4.11.0'
-}
\ No newline at end of file
diff --git a/cli/src/main/groovy/com/jessebrault/ssg/AbstractBuildCommand.groovy b/cli/src/main/groovy/com/jessebrault/ssg/AbstractBuildCommand.groovy
index cc087f7..cd483a7 100644
--- a/cli/src/main/groovy/com/jessebrault/ssg/AbstractBuildCommand.groovy
+++ b/cli/src/main/groovy/com/jessebrault/ssg/AbstractBuildCommand.groovy
@@ -1,12 +1,14 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.buildscript.GroovyBuildScriptRunner
+import com.jessebrault.ssg.task.Output
import com.jessebrault.ssg.part.GspPartRenderer
import com.jessebrault.ssg.part.PartFilePartsProvider
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.specialpage.GspSpecialPageRenderer
import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider
import com.jessebrault.ssg.specialpage.SpecialPageType
+import com.jessebrault.ssg.task.TaskExecutorContext
import com.jessebrault.ssg.template.GspTemplateRenderer
import com.jessebrault.ssg.template.TemplateFileTemplatesProvider
import com.jessebrault.ssg.template.TemplateType
@@ -32,10 +34,10 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
def gspPart = new PartType(['.gsp'], new GspPartRenderer())
def gspSpecialPage = new SpecialPageType(['.gsp'], new GspSpecialPageRenderer())
- def defaultTextsProvider = new TextFileTextsProvider([markdownText], new File('texts'))
- def defaultTemplatesProvider = new TemplateFileTemplatesProvider([gspTemplate], new File('templates'))
- def defaultPartsProvider = new PartFilePartsProvider([gspPart], new File('parts'))
- def defaultSpecialPagesProvider = new SpecialPageFileSpecialPagesProvider([gspSpecialPage], new File('specialPages'))
+ def defaultTextsProvider = new TextFileTextsProvider(new File('texts'), [markdownText])
+ def defaultTemplatesProvider = new TemplateFileTemplatesProvider(new File('templates'), [gspTemplate])
+ def defaultPartsProvider = new PartFilePartsProvider(new File('parts'), [gspPart])
+ def defaultSpecialPagesProvider = new SpecialPageFileSpecialPagesProvider(new File('specialPages'), [gspSpecialPage])
def defaultConfig = new Config(
textProviders: [defaultTextsProvider],
@@ -43,6 +45,10 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
partsProviders: [defaultPartsProvider],
specialPagesProviders: [defaultSpecialPagesProvider]
)
+ def defaultSiteSpec = new SiteSpec(
+ name: '',
+ baseUrl: ''
+ )
def defaultGlobals = [:]
// Run build script, if applicable
@@ -55,7 +61,13 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
if (this.builds.empty) {
// Add default build
- builds << new Build('default', defaultConfig, defaultGlobals, new File('build'))
+ builds << new Build(
+ 'default',
+ defaultConfig,
+ defaultSiteSpec,
+ defaultGlobals,
+ new File('build')
+ )
}
// Get ssg object
@@ -69,16 +81,28 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
// Do each build
this.builds.each {
def result = this.ssg.generate(it)
- if (result.v1.size() > 0) {
+ if (result.hasDiagnostics()) {
hadDiagnostics = true
- result.v1.each {
+ result.diagnostics.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)
+ def tasks = result.get()
+ Collection executionDiagnostics = []
+ def context = new TaskExecutorContext(
+ it,
+ tasks,
+ this.ssg.taskTypes,
+ { Collection diagnostics ->
+ executionDiagnostics.addAll(diagnostics)
+ }
+ )
+ result.get().each { it.execute(context) }
+ if (!executionDiagnostics.isEmpty()) {
+ hadDiagnostics = true
+ executionDiagnostics.each {
+ logger.error(it.message)
+ }
}
}
}
diff --git a/docs/asciidoc/manual.asciidoc b/docs/asciidoc/manual.asciidoc
new file mode 100644
index 0000000..3bd22a1
--- /dev/null
+++ b/docs/asciidoc/manual.asciidoc
@@ -0,0 +1,19 @@
+= com.jessebrault.ssg
+Jesse Brault
+v0.1.0
+:toc:
+:source-highlighter: rouge
+
+*com.jessebrault.ssg* is a static site generator written in Groovy, giving access to the entire JVM ecosystem through its templating system.
+
+== Some Examples
+
+.Tag Builder
+[source,groovy]
+----
+def a = tagBuilder.a(href: 'hello.html', 'Hello!') // <1>
+assert a == 'Hello!'
+out << a // <2>
+----
+<1> Create an tag.
+<2> Output the tag in the current script block.
\ No newline at end of file
diff --git a/lib/build.gradle b/lib/build.gradle
index 21b511d..53cb59f 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -1,6 +1,5 @@
plugins {
id 'ssg.common'
- id 'ssg.lib'
}
repositories {
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/Build.groovy b/lib/src/main/groovy/com/jessebrault/ssg/Build.groovy
index c5d611a..d53e40c 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/Build.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/Build.groovy
@@ -11,12 +11,14 @@ class Build {
String name
Config config
+ SiteSpec siteSpec
Map globals
File outDir
@Override
String toString() {
- "Build(name: ${ this.name }, config: ${ this.config }, globals: ${ this.globals }, outDir: ${ this.outDir })"
+ "Build(name: ${ this.name }, config: ${ this.config }, siteSpec: ${ this.siteSpec }, " +
+ "globals: ${ this.globals }, outDir: ${ this.outDir })"
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/GeneratedPage.groovy b/lib/src/main/groovy/com/jessebrault/ssg/GeneratedPage.groovy
deleted file mode 100644
index 6b92787..0000000
--- a/lib/src/main/groovy/com/jessebrault/ssg/GeneratedPage.groovy
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.jessebrault.ssg
-
-import groovy.transform.EqualsAndHashCode
-import groovy.transform.NullCheck
-import groovy.transform.TupleConstructor
-
-@TupleConstructor(includeFields = true, defaults = false)
-@NullCheck
-@EqualsAndHashCode
-class GeneratedPage {
-
- String path
- String html
-
- @Override
- String toString() {
- "GeneratedPage(path: ${ this.path })"
- }
-
-}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/Result.groovy b/lib/src/main/groovy/com/jessebrault/ssg/Result.groovy
new file mode 100644
index 0000000..94702bd
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/Result.groovy
@@ -0,0 +1,28 @@
+package com.jessebrault.ssg
+
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.NullCheck
+import groovy.transform.TupleConstructor
+
+@TupleConstructor(defaults = false, includeFields = true)
+@NullCheck(includeGenerated = true)
+@EqualsAndHashCode
+final class Result {
+
+ final Collection diagnostics
+ private final T t
+
+ boolean hasDiagnostics() {
+ !this.diagnostics.isEmpty()
+ }
+
+ T get() {
+ this.t
+ }
+
+ @Override
+ String toString() {
+ "Result(diagnostics: ${ this.diagnostics }, t: ${ this.t })"
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy b/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy
index f2dd9cf..0219f67 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy
@@ -1,122 +1,48 @@
package com.jessebrault.ssg
-import com.jessebrault.ssg.text.FrontMatter
-import groovy.transform.EqualsAndHashCode
+import com.jessebrault.ssg.task.SpecialPageToHtmlFileTaskFactory
+import com.jessebrault.ssg.task.TaskContainer
+import com.jessebrault.ssg.task.TaskTypeContainer
+import com.jessebrault.ssg.task.TextToHtmlFileTaskFactory
import groovy.transform.NullCheck
-import groovy.transform.TupleConstructor
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import org.slf4j.MarkerFactory
-@TupleConstructor(includeFields = true)
@NullCheck
-@EqualsAndHashCode(includeFields = true)
class SimpleStaticSiteGenerator implements StaticSiteGenerator {
private static final Logger logger = LoggerFactory.getLogger(SimpleStaticSiteGenerator)
private static final Marker enter = MarkerFactory.getMarker('ENTER')
private static final Marker exit = MarkerFactory.getMarker('EXIT')
+ private static final TextToHtmlFileTaskFactory textHtmlFactory = new TextToHtmlFileTaskFactory()
+ private static final SpecialPageToHtmlFileTaskFactory specialPageHtmlFactory = new SpecialPageToHtmlFileTaskFactory()
+
@Override
- Tuple2, Collection> generate(Build build) {
+ TaskTypeContainer getTaskTypes() {
+ new TaskTypeContainer([textHtmlFactory.taskType, specialPageHtmlFactory.taskType])
+ }
+
+ @Override
+ Result generate(Build build) {
logger.trace(enter, 'build: {}', build)
logger.info('processing build with name: {}', build.name)
- def config = build.config
+ def tasks = new TaskContainer()
+ def diagnostics = []
- // Get all texts, templates, parts, and specialPages
- def texts = config.textProviders.collectMany { it.provide() }
- def templates = config.templatesProviders.collectMany { it.provide() }
- def parts = config.partsProviders.collectMany { it.provide() }
- def specialPages = config.specialPagesProviders.collectMany { it.provide() }
+ def textsResult = textHtmlFactory.getTasks(build)
+ tasks.addAll(textsResult.get())
+ diagnostics.addAll(textsResult.diagnostics)
- logger.debug('\n\ttexts: {}\n\ttemplates: {}\n\tparts: {}\n\tspecialPages: {}', texts, templates, parts, specialPages)
+ def specialPagesResult = specialPageHtmlFactory.getTasks(build)
+ tasks.addAll(specialPagesResult.get())
+ diagnostics.addAll(specialPagesResult.diagnostics)
- def globals = build.globals
- Collection diagnostics = []
- Collection generatedPages = []
-
- // Generate pages from each text, but only those that have a 'template' frontMatter field with a valid value
- texts.each {
- logger.trace(enter, 'text: {}', it)
- logger.info('processing text: {}', it.path)
-
- // Extract frontMatter from text
- def frontMatterResult = it.type.frontMatterGetter.get(it)
- FrontMatter frontMatter
- if (frontMatterResult.v1.size() > 0) {
- logger.debug('diagnostics for getting frontMatter for {}: {}', it.path, frontMatterResult.v1)
- diagnostics.addAll(frontMatterResult.v1)
- logger.trace(exit, '')
- return
- } else {
- frontMatter = frontMatterResult.v2
- logger.debug('frontMatter: {}', frontMatter)
- }
-
- // Find the appropriate template from the frontMatter
- def desiredTemplate = frontMatter.find('template')
- if (desiredTemplate.isEmpty()) {
- logger.info('{} has no \'template\' key in its frontMatter; skipping generation', it)
- return
- }
- def template = templates.find { it.path == desiredTemplate.get() }
- if (template == null) {
- diagnostics << new Diagnostic('in textFile' + it.path + ' frontMatter.template references an unknown template: ' + desiredTemplate, null)
- logger.trace(exit, '')
- return
- }
- logger.debug('found template: {}', template)
-
- // Render the text (i.e., transform text to html)
- def textRenderResult = it.type.renderer.render(it, globals)
- String renderedText
- if (textRenderResult.v1.size() > 0) {
- logger.debug('diagnostics for rendering {}: {}', it.path, textRenderResult.v1)
- diagnostics.addAll(textRenderResult.v1)
- logger.trace(exit, '')
- return
- } else {
- renderedText = textRenderResult.v2
- logger.debug('renderedText: {}', renderedText)
- }
-
- // Render the template using the result of rendering the text earlier
- def templateRenderResult = template.type.renderer.render(template, frontMatter, renderedText, parts, globals)
- String renderedTemplate
- if (templateRenderResult.v1.size() > 0) {
- diagnostics.addAll(templateRenderResult.v1)
- logger.trace(exit, '')
- return
- } else {
- renderedTemplate = templateRenderResult.v2
- }
-
- // Create a GeneratedPage
- generatedPages << new GeneratedPage(it.path, renderedTemplate)
- }
-
- // Generate special pages
- specialPages.each {
- logger.info('processing specialPage: {}', it.path)
-
- def specialPageRenderResult = it.type.renderer.render(it, texts, parts, globals)
- String renderedSpecialPage
- if (specialPageRenderResult.v1.size() > 0) {
- diagnostics.addAll(specialPageRenderResult.v1)
- logger.trace(exit, '')
- return
- } else {
- renderedSpecialPage = specialPageRenderResult.v2
- }
-
- // Create a GeneratedPage
- generatedPages << new GeneratedPage(it.path, renderedSpecialPage)
- }
-
- logger.trace(exit, '\n\tdiagnostics: {}\n\tgeneratedPages: {}', diagnostics, generatedPages)
- new Tuple2<>(diagnostics, generatedPages)
+ logger.trace(exit, '\n\tdiagnostics: {}\n\ttasks: {}', diagnostics, tasks)
+ new Result<>(diagnostics, tasks)
}
@Override
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy b/lib/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy
new file mode 100644
index 0000000..37a8835
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy
@@ -0,0 +1,27 @@
+package com.jessebrault.ssg
+
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.MapConstructor
+import groovy.transform.NullCheck
+import groovy.transform.TupleConstructor
+
+@TupleConstructor(force = true, defaults = false)
+@MapConstructor
+@NullCheck
+@EqualsAndHashCode
+final class SiteSpec {
+
+ String name
+ String baseUrl
+
+ SiteSpec(SiteSpec source) {
+ this.name = source.name
+ this.baseUrl = source.baseUrl
+ }
+
+ @Override
+ String toString() {
+ "SiteSpec(${ this.name }, ${ this.baseUrl })"
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/StaticSiteGenerator.groovy b/lib/src/main/groovy/com/jessebrault/ssg/StaticSiteGenerator.groovy
index 8553be4..fe72287 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/StaticSiteGenerator.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/StaticSiteGenerator.groovy
@@ -1,5 +1,9 @@
package com.jessebrault.ssg
+import com.jessebrault.ssg.task.TaskContainer
+import com.jessebrault.ssg.task.TaskTypeContainer
+
interface StaticSiteGenerator {
- Tuple2, Collection> generate(Build build)
+ TaskTypeContainer getTaskTypes()
+ Result generate(Build build)
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/buildscript/BuildClosureDelegate.groovy b/lib/src/main/groovy/com/jessebrault/ssg/buildscript/BuildClosureDelegate.groovy
index 6572ee3..cc24ef2 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/buildscript/BuildClosureDelegate.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/buildscript/BuildClosureDelegate.groovy
@@ -1,26 +1,37 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Config
+import com.jessebrault.ssg.SiteSpec
class BuildClosureDelegate {
String name
Config config
+ SiteSpec siteSpec
Map globals
File outDir
void config(
@DelegatesTo(value = ConfigClosureDelegate, strategy = Closure.DELEGATE_FIRST)
- Closure configClosure
+ Closure configClosure
) {
configClosure.setDelegate(new ConfigClosureDelegate(this.config))
configClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
configClosure.run()
}
+ void siteSpec(
+ @DelegatesTo(value = SiteSpecClosureDelegate, strategy = Closure.DELEGATE_FIRST)
+ Closure siteSpecClosure
+ ) {
+ siteSpecClosure.setDelegate(new SiteSpecClosureDelegate(this.siteSpec))
+ siteSpecClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
+ siteSpecClosure.run()
+ }
+
void globals(
@DelegatesTo(value = GlobalsClosureDelegate, strategy = Closure.DELEGATE_FIRST)
- Closure globalsClosure
+ Closure globalsClosure
) {
def globalsConfigurator = new GlobalsClosureDelegate(this.globals)
globalsClosure.setDelegate(globalsConfigurator)
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptBase.groovy b/lib/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptBase.groovy
index 10907fa..3c9a84d 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptBase.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/buildscript/BuildScriptBase.groovy
@@ -2,10 +2,12 @@ package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config
+import com.jessebrault.ssg.SiteSpec
abstract class BuildScriptBase extends Script {
Config defaultConfig
+ SiteSpec defaultSiteSpec
Map defaultGlobals
Collection builds = []
@@ -20,13 +22,20 @@ abstract class BuildScriptBase extends Script {
// Default values for Build properties
name = 'build' + this.currentBuildNumber
config = new Config(defaultConfig)
+ siteSpec = new SiteSpec(defaultSiteSpec)
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)
+ this.builds << new Build(
+ buildClosureDelegate.name,
+ buildClosureDelegate.config,
+ buildClosureDelegate.siteSpec,
+ buildClosureDelegate.globals,
+ buildClosureDelegate.outDir
+ )
this.currentBuildNumber++
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/buildscript/SiteSpecClosureDelegate.groovy b/lib/src/main/groovy/com/jessebrault/ssg/buildscript/SiteSpecClosureDelegate.groovy
new file mode 100644
index 0000000..14a4e71
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/buildscript/SiteSpecClosureDelegate.groovy
@@ -0,0 +1,14 @@
+package com.jessebrault.ssg.buildscript
+
+import com.jessebrault.ssg.SiteSpec
+
+class SiteSpecClosureDelegate {
+
+ @Delegate
+ private final SiteSpec siteSpec
+
+ SiteSpecClosureDelegate(SiteSpec siteSpec) {
+ this.siteSpec = siteSpec
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/dsl/StandardDslMap.groovy b/lib/src/main/groovy/com/jessebrault/ssg/dsl/StandardDslMap.groovy
new file mode 100644
index 0000000..c57760e
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/dsl/StandardDslMap.groovy
@@ -0,0 +1,84 @@
+package com.jessebrault.ssg.dsl
+
+import com.jessebrault.ssg.part.EmbeddablePartsMap
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.tagbuilder.DynamicTagBuilder
+import com.jessebrault.ssg.text.EmbeddableText
+import com.jessebrault.ssg.text.EmbeddableTextsCollection
+import com.jessebrault.ssg.text.Text
+import com.jessebrault.ssg.url.PathBasedUrlBuilder
+import groovy.transform.NullCheck
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.SimpleType
+import org.slf4j.LoggerFactory
+
+final class StandardDslMap {
+
+ @NullCheck(includeGenerated = true)
+ static final class Builder {
+
+ private final Map custom = [:]
+
+ String loggerName = ''
+ Closure onDiagnostics = { }
+ Text text = null
+
+ void putCustom(key, value) {
+ this.custom.put(key, value)
+ }
+
+ void putAllCustom(Map m) {
+ this.custom.putAll(m)
+ }
+
+ }
+
+ static Map get(
+ RenderContext context,
+ @DelegatesTo(value = Builder, strategy = Closure.DELEGATE_FIRST)
+ @ClosureParams(
+ value = SimpleType,
+ options = ['com.jessebrault.ssg.dsl.StandardDslMap.Builder']
+ )
+ Closure builderClosure
+ ) {
+ def b = new Builder()
+ builderClosure.resolveStrategy = Closure.DELEGATE_FIRST
+ builderClosure.delegate = b
+ builderClosure(b)
+
+ [:].tap {
+ it.globals = context.globals
+ it.logger = LoggerFactory.getLogger(b.loggerName)
+ it.parts = new EmbeddablePartsMap(
+ context.parts,
+ context,
+ b.onDiagnostics,
+ b.text
+ )
+ it.siteSpec = context.siteSpec
+ it.sourcePath = context.sourcePath
+ it.tagBuilder = new DynamicTagBuilder()
+ it.targetPath = context.targetPath
+ it.tasks = context.tasks
+ it.taskTypes = context.taskTypes
+ it.text = b.text ? new EmbeddableText(
+ b.text,
+ context.globals,
+ b.onDiagnostics
+ ) : null
+ it.texts = new EmbeddableTextsCollection(
+ context.texts,
+ context.globals,
+ b.onDiagnostics
+ )
+ it.urlBuilder = new PathBasedUrlBuilder(
+ context.targetPath,
+ context.siteSpec.baseUrl
+ )
+
+ it.putAll(b.custom)
+ }
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy
index 9e7d1ab..82ede31 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy
@@ -1,20 +1,43 @@
package com.jessebrault.ssg.part
+
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
-import groovy.transform.TupleConstructor
+import org.jetbrains.annotations.Nullable
+
+import static java.util.Objects.requireNonNull
-@TupleConstructor(includeFields = true, defaults = false)
-@NullCheck
@EqualsAndHashCode(includeFields = true)
class EmbeddablePart {
private final Part part
- private final Map globals
+ private final RenderContext context
private final Closure onDiagnostics
+ @Nullable
+ private final Text text
+
+ EmbeddablePart(
+ Part part,
+ RenderContext context,
+ Closure onDiagnostics,
+ @Nullable Text text
+ ) {
+ this.part = requireNonNull(part)
+ this.context = requireNonNull(context)
+ this.onDiagnostics = requireNonNull(onDiagnostics)
+ this.text = text
+ }
+
String render(Map binding = [:]) {
- def result = part.type.renderer.render(this.part, binding, this.globals)
+ def result = part.type.renderer.render(
+ this.part,
+ binding,
+ this.context,
+ this.text
+ )
if (result.v1.size() > 0) {
this.onDiagnostics.call(result.v1)
''
@@ -25,7 +48,7 @@ class EmbeddablePart {
@Override
String toString() {
- "EmbeddablePart(part: ${ this.part }, globals: ${ this.globals })"
+ "EmbeddablePart(part: ${ this.part })"
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy
index 0db363b..26fe807 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy
@@ -1,18 +1,29 @@
package com.jessebrault.ssg.part
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode
-import groovy.transform.NullCheck
+import org.jetbrains.annotations.Nullable
+
+import static java.util.Objects.requireNonNull
-@NullCheck
@EqualsAndHashCode(includeFields = true)
class EmbeddablePartsMap {
@Delegate
private final Map partsMap = [:]
- EmbeddablePartsMap(Collection parts, Map globals, Closure onDiagnostics) {
+ EmbeddablePartsMap(
+ Collection parts,
+ RenderContext context,
+ Closure onDiagnostics,
+ @Nullable Text text = null
+ ) {
+ requireNonNull(parts)
+ requireNonNull(context)
+ requireNonNull(onDiagnostics)
parts.each {
- this.put(it.path, new EmbeddablePart(it, globals, onDiagnostics))
+ this.put(it.path, new EmbeddablePart(it, context, onDiagnostics, text))
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy
index c1f783e..d4360c8 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy
@@ -1,9 +1,14 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic
+import com.jessebrault.ssg.dsl.StandardDslMap
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.text.EmbeddableText
+import com.jessebrault.ssg.text.Text
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
+import org.jetbrains.annotations.Nullable
@EqualsAndHashCode
class GspPartRenderer implements PartRenderer {
@@ -11,15 +16,35 @@ class GspPartRenderer implements PartRenderer {
private static final TemplateEngine engine = new GStringTemplateEngine()
@Override
- Tuple2, String> render(Part part, Map binding, Map globals) {
+ Tuple2, String> render(
+ Part part,
+ Map binding,
+ RenderContext context,
+ @Nullable Text text
+ ) {
+ Objects.requireNonNull(part)
+ Objects.requireNonNull(binding)
+ Objects.requireNonNull(context)
+ def diagnostics = []
try {
- def result = engine.createTemplate(part.text).make([
- binding: binding,
- globals: globals
- ])
- new Tuple2<>([], result.toString())
+ def dslMap = StandardDslMap.get(context) {
+ it.putCustom('binding', binding)
+ it.loggerName = "GspPart(${ part.path })"
+ it.onDiagnostics = diagnostics.&addAll
+ if (text) {
+ it.text = text
+ }
+ }
+ def result = engine.createTemplate(part.text).make(dslMap)
+ new Tuple2<>(diagnostics, result.toString())
} catch (Exception e) {
- new Tuple2<>([new Diagnostic("An exception occurred while rendering part ${ part.path }:\n${ e }", e)], '')
+ new Tuple2<>(
+ [*diagnostics, new Diagnostic(
+ "An exception occurred while rendering part ${ part.path }:\n${ e }",
+ e
+ )],
+ ''
+ )
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy
index 72e7cc8..f445690 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy
@@ -1,53 +1,38 @@
package com.jessebrault.ssg.part
-import com.jessebrault.ssg.provider.WithWatchableDir
-import com.jessebrault.ssg.util.FileNameHandler
-import groovy.io.FileType
+import com.jessebrault.ssg.provider.AbstractFileCollectionProvider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
+import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode(includeFields = true)
-class PartFilePartsProvider implements PartsProvider, WithWatchableDir {
+class PartFilePartsProvider extends AbstractFileCollectionProvider implements PartsProvider {
private static final Logger logger = LoggerFactory.getLogger(PartFilePartsProvider)
private final Collection partTypes
- private final File partsDir
- PartFilePartsProvider(Collection partTypes, File partsDir) {
- this.partTypes = partTypes
- this.partsDir = partsDir
- this.watchableDir = this.partsDir
+ PartFilePartsProvider(File partsDir, Collection partTypes) {
+ super(partsDir)
+ this.partTypes = Objects.requireNonNull(partTypes)
}
- private PartType getPartType(File file) {
+ private @Nullable PartType getPartType(String extension) {
this.partTypes.find {
- it.ids.contains(new FileNameHandler(file).getExtension())
+ it.ids.contains(extension)
}
}
@Override
- Collection provide() {
- if (!partsDir.isDirectory()) {
- logger.warn('partsDir {} does not exist or is not a directory; skipping and providing no Parts', this.partsDir)
- []
- } else {
- def parts = []
- this.partsDir.eachFileRecurse(FileType.FILES) {
- def type = this.getPartType(it)
- if (type != null) {
- def relativePath = this.partsDir.relativePath(it)
- logger.debug('found part {}', relativePath)
- parts << new Part(relativePath, type, it.text)
- } else {
- logger.warn('ignoring {} since there is no partType for it', it)
- }
- }
- parts
+ protected @Nullable Part transformFileToT(File file, String relativePath, String extension) {
+ def partType = getPartType(extension)
+ if (!partType) {
+ logger.warn('there is no PartType for {}, ignoring', relativePath)
}
+ partType ? new Part(relativePath, partType, file.text) : null
}
@Override
@@ -57,7 +42,7 @@ class PartFilePartsProvider implements PartsProvider, WithWatchableDir {
@Override
String toString() {
- "PartFilePartsProvider(partsDir: ${ this.partsDir }, partTypes: ${ this.partTypes })"
+ "PartFilePartsProvider(partsDir: ${ this.dir }, partTypes: ${ this.partTypes })"
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy
index 7675fdd..9d27b39 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy
@@ -1,7 +1,17 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.text.Text
+import org.jetbrains.annotations.Nullable
interface PartRenderer {
- Tuple2, String> render(Part part, Map binding, Map globals)
+
+ Tuple2, String> render(
+ Part part,
+ Map binding,
+ RenderContext context,
+ @Nullable Text text
+ )
+
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/provider/AbstractFileCollectionProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/provider/AbstractFileCollectionProvider.groovy
new file mode 100644
index 0000000..56ae2c7
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/provider/AbstractFileCollectionProvider.groovy
@@ -0,0 +1,40 @@
+package com.jessebrault.ssg.provider
+
+import groovy.io.FileType
+import org.jetbrains.annotations.Nullable
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension
+
+abstract class AbstractFileCollectionProvider implements Provider>, WithWatchableDir {
+
+ private static final Logger logger = LoggerFactory.getLogger(AbstractFileCollectionProvider)
+
+ protected final File dir
+
+ AbstractFileCollectionProvider(File dir) {
+ this.dir = Objects.requireNonNull(dir)
+ this.watchableDir = dir
+ }
+
+ protected abstract @Nullable T transformFileToT(File file, String relativePath, String extension)
+
+ @Override
+ Collection provide() {
+ if (!this.dir.isDirectory()) {
+ logger.warn('{} does not exist or is not a directory; skipping', this.dir)
+ []
+ } else {
+ def ts = []
+ this.dir.eachFileRecurse(FileType.FILES) {
+ def t = transformFileToT(it, this.dir.relativePath(it), getExtension(it.path))
+ if (t) {
+ ts << t
+ }
+ }
+ ts
+ }
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/renderer/RenderContext.groovy b/lib/src/main/groovy/com/jessebrault/ssg/renderer/RenderContext.groovy
new file mode 100644
index 0000000..5a4171a
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/renderer/RenderContext.groovy
@@ -0,0 +1,26 @@
+package com.jessebrault.ssg.renderer
+
+import com.jessebrault.ssg.Config
+import com.jessebrault.ssg.SiteSpec
+import com.jessebrault.ssg.part.Part
+import com.jessebrault.ssg.task.TaskContainer
+import com.jessebrault.ssg.task.TaskTypeContainer
+import com.jessebrault.ssg.text.Text
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.NullCheck
+import groovy.transform.TupleConstructor
+
+@TupleConstructor(defaults = false)
+@NullCheck(includeGenerated = true)
+@EqualsAndHashCode
+final class RenderContext {
+ final Config config
+ final SiteSpec siteSpec
+ final Map globals
+ final Collection texts
+ final Collection parts
+ final String sourcePath
+ final String targetPath
+ final TaskContainer tasks
+ final TaskTypeContainer taskTypes
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy
index cadb675..335651c 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy
@@ -1,10 +1,8 @@
package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.Diagnostic
-import com.jessebrault.ssg.part.Part
-import com.jessebrault.ssg.part.EmbeddablePartsMap
-import com.jessebrault.ssg.text.EmbeddableTextsCollection
-import com.jessebrault.ssg.text.Text
+import com.jessebrault.ssg.dsl.StandardDslMap
+import com.jessebrault.ssg.renderer.RenderContext
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
@@ -17,21 +15,26 @@ class GspSpecialPageRenderer implements SpecialPageRenderer {
private static final TemplateEngine engine = new GStringTemplateEngine()
@Override
- Tuple2, String> render(SpecialPage specialPage, Collection texts, Collection parts, Map globals) {
+ Tuple2, String> render(
+ SpecialPage specialPage,
+ RenderContext context
+ ) {
+ def diagnostics = []
try {
- Collection diagnostics = []
- def result = engine.createTemplate(specialPage.text).make([
- globals: globals,
- parts: new EmbeddablePartsMap(parts, globals, { Collection partDiagnostics ->
- diagnostics.addAll(partDiagnostics)
- }),
- texts: new EmbeddableTextsCollection(texts, globals, { Collection textDiagnostics ->
- diagnostics.addAll(textDiagnostics)
- })
- ])
+ def dslMap = StandardDslMap.get(context) {
+ it.loggerName = "GspSpecialPage(${ specialPage.path })"
+ it.onDiagnostics = diagnostics.&addAll
+ }
+ def result = engine.createTemplate(specialPage.text).make(dslMap)
new Tuple2<>(diagnostics, result.toString())
} catch (Exception e) {
- new Tuple2<>([new Diagnostic("An exception occurred while rendering specialPage ${ specialPage.path }:\n${ e }", e)], '')
+ new Tuple2<>(
+ [*diagnostics, new Diagnostic(
+ "An exception occurred while rendering specialPage ${ specialPage.path }:\n${ e }",
+ e
+ )],
+ ''
+ )
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPage.groovy b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPage.groovy
index 869a412..d1af35d 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPage.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPage.groovy
@@ -5,13 +5,13 @@ import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
-@NullCheck
+@NullCheck(includeGenerated = true)
@EqualsAndHashCode
-class SpecialPage {
+final class SpecialPage {
- String text
- String path
- SpecialPageType type
+ final String text
+ final String path
+ final SpecialPageType type
@Override
String toString() {
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy
index a2ef1e3..a8d9f9c 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy
@@ -1,55 +1,39 @@
package com.jessebrault.ssg.specialpage
-import com.jessebrault.ssg.provider.WithWatchableDir
-import com.jessebrault.ssg.util.FileNameHandler
-import com.jessebrault.ssg.util.RelativePathHandler
-import groovy.io.FileType
+import com.jessebrault.ssg.provider.AbstractFileCollectionProvider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
+import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode(includeFields = true)
-class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider, WithWatchableDir {
+class SpecialPageFileSpecialPagesProvider extends AbstractFileCollectionProvider
+ implements SpecialPagesProvider {
private static final Logger logger = LoggerFactory.getLogger(SpecialPageFileSpecialPagesProvider)
private final Collection specialPageTypes
- private final File specialPagesDir
- SpecialPageFileSpecialPagesProvider(Collection specialPageTypes, File specialPagesDir) {
- this.specialPageTypes = specialPageTypes
- this.specialPagesDir = specialPagesDir
- this.watchableDir = this.specialPagesDir
+ SpecialPageFileSpecialPagesProvider(File specialPagesDir, Collection specialPageTypes) {
+ super(specialPagesDir)
+ this.specialPageTypes = Objects.requireNonNull(specialPageTypes)
}
- private SpecialPageType getSpecialPageType(File file) {
+ private @Nullable SpecialPageType getSpecialPageType(String extension) {
this.specialPageTypes.find {
- it.ids.contains(new FileNameHandler(file).getExtension())
+ it.ids.contains(extension)
}
}
@Override
- Collection provide() {
- if (!this.specialPagesDir.isDirectory()) {
- logger.warn('specialPagesDir {} does not exist or is not a directory; skipping and providing no SpecialPages', this.specialPagesDir)
- []
- } else {
- def specialPages = []
- this.specialPagesDir.eachFileRecurse(FileType.FILES) {
- def type = this.getSpecialPageType(it)
- if (type != null) {
- def relativePath = this.specialPagesDir.relativePath(it)
- def path = new RelativePathHandler(relativePath).getWithoutExtension()
- logger.info('found specialPage {} with type {}', path, type)
- specialPages << new SpecialPage(it.text, path, type)
- } else {
- logger.warn('ignoring {} since there is no specialPageType for it', it)
- }
- }
- specialPages
+ protected @Nullable SpecialPage transformFileToT(File file, String relativePath, String extension) {
+ def specialPageType = getSpecialPageType(extension)
+ if (!specialPageType) {
+ logger.warn('there is no SpecialPageType for {}, ignoring', relativePath)
}
+ specialPageType ? new SpecialPage(file.text, relativePath, specialPageType) : null
}
@Override
@@ -59,7 +43,8 @@ class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider, WithW
@Override
String toString() {
- "SpecialPageFileSpecialPagesProvider(specialPagesDir: ${ this.specialPagesDir }, specialPageTypes: ${ this.specialPageTypes })"
+ "SpecialPageFileSpecialPagesProvider(specialPagesDir: ${ this.dir }, " +
+ "specialPageTypes: ${ this.specialPageTypes })"
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageRenderer.groovy
index a87f57c..44e5363 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageRenderer.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageRenderer.groovy
@@ -1,16 +1,16 @@
package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.Diagnostic
+import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.part.Part
+import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text
interface SpecialPageRenderer {
Tuple2, String> render(
SpecialPage specialPage,
- Collection texts,
- Collection parts,
- Map globals
+ RenderContext context
)
}
\ No newline at end of file
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy b/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy
new file mode 100644
index 0000000..9acb4d0
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy
@@ -0,0 +1,77 @@
+package com.jessebrault.ssg.tagbuilder
+
+import org.codehaus.groovy.runtime.InvokerHelper
+
+class DynamicTagBuilder implements TagBuilder {
+
+ @Override
+ String create(String name) {
+ "<$name />"
+ }
+
+ @Override
+ String create(String name, Map attributes) {
+ def formattedAttributes = attributes.collect {
+ if (it.value instanceof String) {
+ it.key + '="' + it.value + '"'
+ } else if (it.value instanceof Integer) {
+ it.key + '=' + it.value
+ } else if (it.value instanceof Boolean && it.value == true) {
+ it.key
+ } else {
+ it.key + '="' + it.value.toString() + '"'
+ }
+ }.join(' ')
+ "<$name $formattedAttributes />"
+ }
+
+ @Override
+ String create(String name, String body) {
+ "<$name>$body$name>"
+ }
+
+ @Override
+ String create(String name, Map attributes, String body) {
+ def formattedAttributes = attributes.collect {
+ if (it.value instanceof String) {
+ it.key + '="' + it.value + '"'
+ } else if (it.value instanceof Integer) {
+ it.key + '=' + it.value
+ } else if (it.value instanceof Boolean && it.value == true) {
+ it.key
+ } else {
+ it.key + '="' + it.value.toString() + '"'
+ }
+ }.join(' ')
+ "<$name $formattedAttributes>$body$name>"
+ }
+
+ @Override
+ Object invokeMethod(String name, Object args) {
+ def argsList = InvokerHelper.asList(args)
+ return switch (argsList.size()) {
+ case 0 -> this.create(name)
+ case 1 -> {
+ def arg0 = argsList[0]
+ if (arg0 instanceof Map) {
+ this.create(name, arg0)
+ } else if (arg0 instanceof String) {
+ this.create(name, arg0)
+ } else {
+ throw new MissingMethodException(name, this.class, args, false)
+ }
+ }
+ case 2 -> {
+ def arg0 = argsList[0]
+ def arg1 = argsList[1]
+ if (arg0 instanceof Map && arg1 instanceof String) {
+ this.create(name, arg0, arg1)
+ } else {
+ throw new MissingMethodException(name, this.class, args, false)
+ }
+ }
+ default -> throw new MissingMethodException(name, this.class, args, false)
+ }
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/TagBuilder.groovy b/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/TagBuilder.groovy
new file mode 100644
index 0000000..a92c64a
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/TagBuilder.groovy
@@ -0,0 +1,8 @@
+package com.jessebrault.ssg.tagbuilder
+
+interface TagBuilder {
+ String create(String name)
+ String create(String name, Map attributes)
+ String create(String name, String body)
+ String create(String name, Map attributes, String body)
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/AbstractTask.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/AbstractTask.groovy
new file mode 100644
index 0000000..bfc3565
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/AbstractTask.groovy
@@ -0,0 +1,31 @@
+package com.jessebrault.ssg.task
+
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.NullCheck
+
+@NullCheck
+@EqualsAndHashCode
+abstract class AbstractTask implements Task {
+
+ final TaskType type
+ final String name
+
+ AbstractTask(TaskType type, String name) {
+ this.type = type
+ this.name = name
+ }
+
+ protected abstract T getThis()
+
+ @Override
+ void execute(TaskExecutorContext context) {
+ // I am guessing that if we put this.getThis(), it will think the runtime type is AbstractTask? Not sure.
+ this.type.executor.execute(getThis(), context)
+ }
+
+ @Override
+ String toString() {
+ "AbstractTask(name: ${ this.name }, type: ${ this.type })"
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/FileInput.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/FileInput.groovy
new file mode 100644
index 0000000..496b2fa
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/FileInput.groovy
@@ -0,0 +1,5 @@
+package com.jessebrault.ssg.task
+
+interface FileInput extends Input {
+ File getFile()
+}
\ No newline at end of file
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/FileOutput.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/FileOutput.groovy
new file mode 100644
index 0000000..34bd660
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/FileOutput.groovy
@@ -0,0 +1,6 @@
+package com.jessebrault.ssg.task
+
+interface FileOutput extends Output {
+ File getFile()
+ String getContent()
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/HtmlFileOutput.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/HtmlFileOutput.groovy
new file mode 100644
index 0000000..bf69c28
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/HtmlFileOutput.groovy
@@ -0,0 +1,26 @@
+package com.jessebrault.ssg.task
+
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.NullCheck
+import groovy.transform.TupleConstructor
+
+@TupleConstructor(defaults = false, includeFields = true)
+@NullCheck(includeGenerated = true)
+@EqualsAndHashCode
+final class HtmlFileOutput {
+
+ final File file
+ final String htmlPath
+
+ private final Closure contentClosure
+
+ String getContent(TaskContainer tasks, TaskTypeContainer taskTypes, Closure onDiagnostics) {
+ this.contentClosure(tasks, taskTypes, onDiagnostics)
+ }
+
+ @Override
+ String toString() {
+ "HtmlFileOutput(file: ${ this.file }, htmlPath: ${ this.htmlPath })"
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/Input.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/Input.groovy
new file mode 100644
index 0000000..ac7ff57
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/Input.groovy
@@ -0,0 +1,5 @@
+package com.jessebrault.ssg.task
+
+interface Input {
+ String getName()
+}
\ No newline at end of file
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/Output.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/Output.groovy
new file mode 100644
index 0000000..f03dcda
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/Output.groovy
@@ -0,0 +1,5 @@
+package com.jessebrault.ssg.task
+
+interface Output {
+ String getName()
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/SpecialPageToHtmlFileTask.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/SpecialPageToHtmlFileTask.groovy
new file mode 100644
index 0000000..fc4eed6
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/SpecialPageToHtmlFileTask.groovy
@@ -0,0 +1,51 @@
+package com.jessebrault.ssg.task
+
+import com.jessebrault.ssg.specialpage.SpecialPage
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.NullCheck
+
+@NullCheck
+@EqualsAndHashCode
+final class SpecialPageToHtmlFileTask extends AbstractTask {
+
+ private static final class SpecialPageToHtmlFileTaskExecutor implements TaskExecutor {
+
+ @Override
+ void execute(SpecialPageToHtmlFileTask task, TaskExecutorContext context) {
+ task.output.file.createParentDirectories()
+ task.output.file.write(task.output.getContent(
+ context.allTasks, context.allTypes, context.onDiagnostics
+ ))
+ }
+
+ @Override
+ String toString() {
+ 'SpecialPageToHtmlFileTaskExecutor()'
+ }
+
+ }
+
+ static final TaskType TYPE = new TaskType<>(
+ 'specialPageToHtmlFile', new SpecialPageToHtmlFileTaskExecutor()
+ )
+
+ final SpecialPage input
+ final HtmlFileOutput output
+
+ SpecialPageToHtmlFileTask(String name, SpecialPage input, HtmlFileOutput output) {
+ super(TYPE, name)
+ this.input = input
+ this.output = output
+ }
+
+ @Override
+ protected SpecialPageToHtmlFileTask getThis() {
+ this
+ }
+
+ @Override
+ String toString() {
+ "SpecialPageToHtmlFileTask(input: ${ this.input }, output: ${ this.output }, super: ${ super })"
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/SpecialPageToHtmlFileTaskFactory.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/SpecialPageToHtmlFileTaskFactory.groovy
new file mode 100644
index 0000000..45017e5
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/SpecialPageToHtmlFileTaskFactory.groovy
@@ -0,0 +1,87 @@
+package com.jessebrault.ssg.task
+
+import com.jessebrault.ssg.Build
+import com.jessebrault.ssg.Result
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.util.ExtensionsUtil
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.slf4j.Marker
+import org.slf4j.MarkerFactory
+
+final class SpecialPageToHtmlFileTaskFactory implements TaskFactory {
+
+ private static final Logger logger = LoggerFactory.getLogger(SpecialPageToHtmlFileTaskFactory)
+ private static final Marker enter = MarkerFactory.getMarker('ENTER')
+ private static final Marker exit = MarkerFactory.getMarker('EXIT')
+
+ @Override
+ TaskType getTaskType() {
+ SpecialPageToHtmlFileTask.TYPE
+ }
+
+ @Override
+ Result> getTasks(Build build) {
+ logger.trace(enter, 'build: {}', build)
+ logger.info('processing build with name {} for SpecialPageToHtmlFileTasks', build.name)
+
+ def config = build.config
+ def siteSpec = build.siteSpec
+ def globals = build.globals
+
+ def specialPages = config.specialPagesProviders.collectMany { it.provide() }
+ def templates = config.templatesProviders.collectMany { it.provide() }
+ def parts = config.partsProviders.collectMany { it.provide() }
+ def texts = config.textProviders.collectMany { it.provide() }
+
+ logger.debug('\n\tspecialPages: {}\n\ttemplates: {}\n\tparts: {}', specialPages, templates, parts)
+
+ def tasks = new TaskCollection(specialPages.findResults {
+ logger.trace(enter, 'specialPage: {}', it)
+ logger.info('processing specialPage with path: {}', it.path)
+
+ def htmlPath = ExtensionsUtil.stripExtension(it.path) + '.html'
+
+ def renderSpecialPage = { TaskContainer tasks, TaskTypeContainer taskTypes, Closure onDiagnostics ->
+ def renderResult = it.type.renderer.render(
+ it,
+ new RenderContext(
+ config,
+ siteSpec,
+ globals,
+ texts,
+ parts,
+ it.path,
+ htmlPath,
+ tasks,
+ taskTypes
+ )
+ )
+
+ if (!renderResult.v1.isEmpty()) {
+ onDiagnostics(renderResult.v1)
+ ''
+ } else {
+ renderResult.v2
+ }
+ }
+
+ def result = new SpecialPageToHtmlFileTask(
+ "specialPageToHtmlFileTask:${ it.path }:${ htmlPath }",
+ it,
+ new HtmlFileOutput(
+ new File(build.outDir, htmlPath),
+ htmlPath,
+ renderSpecialPage
+ )
+ )
+ logger.trace(exit, 'result: {}', result)
+ result
+ })
+
+ def result = new Result<>([], tasks)
+ logger.trace(exit, 'result: {}', result)
+ result
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/Task.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/Task.groovy
new file mode 100644
index 0000000..47217d2
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/Task.groovy
@@ -0,0 +1,7 @@
+package com.jessebrault.ssg.task
+
+interface Task {
+ TaskType extends Task> getType()
+ String getName()
+ void execute(TaskExecutorContext context)
+}
\ No newline at end of file
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TaskCollection.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskCollection.groovy
new file mode 100644
index 0000000..50a3f37
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskCollection.groovy
@@ -0,0 +1,24 @@
+package com.jessebrault.ssg.task
+
+import static java.util.Objects.requireNonNull
+
+class TaskCollection {
+
+ @Delegate
+ private final Collection tasks = new ArrayList()
+
+ TaskCollection(Collection extends T> tasks = null) {
+ if (tasks != null) {
+ this.tasks.addAll(requireNonNull(tasks))
+ }
+ }
+
+ def TaskCollection findAllByType(
+ TaskType taskType
+ ) {
+ new TaskCollection<>(this.tasks.findResults {
+ it.type == taskType ? it : null
+ })
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TaskContainer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskContainer.groovy
new file mode 100644
index 0000000..3661476
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskContainer.groovy
@@ -0,0 +1,9 @@
+package com.jessebrault.ssg.task
+
+final class TaskContainer extends TaskCollection {
+
+ TaskContainer(Collection extends Task> tasks = null) {
+ super(tasks)
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TaskExecutor.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskExecutor.groovy
new file mode 100644
index 0000000..35cb769
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskExecutor.groovy
@@ -0,0 +1,5 @@
+package com.jessebrault.ssg.task
+
+interface TaskExecutor {
+ void execute(T task, TaskExecutorContext context)
+}
\ No newline at end of file
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TaskExecutorContext.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskExecutorContext.groovy
new file mode 100644
index 0000000..a1da7b1
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskExecutorContext.groovy
@@ -0,0 +1,23 @@
+package com.jessebrault.ssg.task
+
+import com.jessebrault.ssg.Build
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.NullCheck
+import groovy.transform.TupleConstructor
+
+@TupleConstructor(defaults = false)
+@NullCheck(includeGenerated = true)
+@EqualsAndHashCode
+final class TaskExecutorContext {
+
+ final Build build
+ final TaskContainer allTasks
+ final TaskTypeContainer allTypes
+ final Closure onDiagnostics
+
+ @Override
+ String toString() {
+ "TaskExecutorContext(build: ${ this.build }, allTasks: ${ this.allTasks }, allTypes: ${ this.allTypes })"
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TaskFactory.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskFactory.groovy
new file mode 100644
index 0000000..60b78ff
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskFactory.groovy
@@ -0,0 +1,9 @@
+package com.jessebrault.ssg.task
+
+import com.jessebrault.ssg.Build
+import com.jessebrault.ssg.Result
+
+interface TaskFactory {
+ TaskType getTaskType()
+ Result> getTasks(Build build)
+}
\ No newline at end of file
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TaskType.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskType.groovy
new file mode 100644
index 0000000..2b2652b
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskType.groovy
@@ -0,0 +1,20 @@
+package com.jessebrault.ssg.task
+
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.NullCheck
+import groovy.transform.TupleConstructor
+
+@TupleConstructor(defaults = false)
+@NullCheck(includeGenerated = true)
+@EqualsAndHashCode
+final class TaskType {
+
+ final String name
+ final TaskExecutor executor
+
+ @Override
+ String toString() {
+ "TaskType(${ this.name }, ${ this.executor })"
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TaskTypeContainer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskTypeContainer.groovy
new file mode 100644
index 0000000..b4092cc
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TaskTypeContainer.groovy
@@ -0,0 +1,31 @@
+package com.jessebrault.ssg.task
+
+final class TaskTypeContainer {
+
+ @Delegate
+ private final Set> taskTypes = []
+
+ TaskTypeContainer(Collection> taskTypes) {
+ if (taskTypes != null) {
+ this.taskTypes.addAll(taskTypes)
+ }
+ }
+
+ TaskTypeContainer(TaskTypeContainer taskTypeContainer) {
+ if (taskTypeContainer != null) {
+ this.taskTypes.addAll(taskTypeContainer)
+ }
+ }
+
+ TaskTypeContainer() {}
+
+ @Override
+ TaskType extends Task> getProperty(String propertyName) {
+ def taskType = this.taskTypes.find { it.name == propertyName }
+ if (!taskType) {
+ throw new IllegalArgumentException("no such taskType: ${ propertyName }")
+ }
+ taskType
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TextInput.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TextInput.groovy
new file mode 100644
index 0000000..beed338
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TextInput.groovy
@@ -0,0 +1,14 @@
+package com.jessebrault.ssg.task
+
+import com.jessebrault.ssg.text.Text
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.NullCheck
+import groovy.transform.TupleConstructor
+
+@TupleConstructor(defaults = false)
+@NullCheck(includeGenerated = true)
+@EqualsAndHashCode
+final class TextInput implements Input {
+ final String name
+ final Text text
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TextToHtmlFileTask.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TextToHtmlFileTask.groovy
new file mode 100644
index 0000000..f89a063
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TextToHtmlFileTask.groovy
@@ -0,0 +1,51 @@
+package com.jessebrault.ssg.task
+
+import com.jessebrault.ssg.text.Text
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.NullCheck
+
+@NullCheck
+@EqualsAndHashCode(callSuper = true)
+final class TextToHtmlFileTask extends AbstractTask {
+
+ private static final class TextToHtmlFileTaskExecutor implements TaskExecutor {
+
+ @Override
+ void execute(TextToHtmlFileTask task, TaskExecutorContext context) {
+ task.output.file.createParentDirectories()
+ task.output.file.write(task.output.getContent(
+ context.allTasks, context.allTypes, context.onDiagnostics
+ ))
+ }
+
+ @Override
+ String toString() {
+ 'TextToHtmlFileTaskExecutor()'
+ }
+
+ }
+
+ static final TaskType TYPE = new TaskType<>(
+ 'textToHtmlFile', new TextToHtmlFileTaskExecutor()
+ )
+
+ final Text input
+ final HtmlFileOutput output
+
+ TextToHtmlFileTask(String name, Text input, HtmlFileOutput output) {
+ super(TYPE, name)
+ this.input = input
+ this.output = output
+ }
+
+ @Override
+ protected TextToHtmlFileTask getThis() {
+ this
+ }
+
+ @Override
+ String toString() {
+ "TextToHtmlFileTask(input: ${ this.input }, output: ${ this.output }, super: ${ super })"
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/TextToHtmlFileTaskFactory.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/TextToHtmlFileTaskFactory.groovy
new file mode 100644
index 0000000..0f8d1c0
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/TextToHtmlFileTaskFactory.groovy
@@ -0,0 +1,117 @@
+package com.jessebrault.ssg.task
+
+import com.jessebrault.ssg.Build
+import com.jessebrault.ssg.Diagnostic
+import com.jessebrault.ssg.Result
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.text.FrontMatter
+import com.jessebrault.ssg.util.ExtensionsUtil
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.slf4j.Marker
+import org.slf4j.MarkerFactory
+
+final class TextToHtmlFileTaskFactory implements TaskFactory {
+
+ private static final Logger logger = LoggerFactory.getLogger(TextToHtmlFileTaskFactory)
+ private static final Marker enter = MarkerFactory.getMarker('ENTER')
+ private static final Marker exit = MarkerFactory.getMarker('EXIT')
+
+ @Override
+ TaskType getTaskType() {
+ TextToHtmlFileTask.TYPE
+ }
+
+ @Override
+ Result> getTasks(Build build) {
+ logger.trace(enter, 'build: {}', build)
+ logger.info('getting TextToHtmlFileTasks for build with name: {}', build.name)
+
+ def config = build.config
+ def siteSpec = build.siteSpec
+ def globals = build.globals
+ def diagnostics = []
+
+ // Get all texts, templates, parts, and specialPages
+ def texts = config.textProviders.collectMany { it.provide() }
+ def templates = config.templatesProviders.collectMany { it.provide() }
+ def parts = config.partsProviders.collectMany { it.provide() }
+
+ logger.debug('\n\ttexts: {}\n\ttemplates: {}\n\tparts: {}', texts, templates, parts)
+
+ def tasks = new TaskCollection(texts.findResults {
+ logger.trace(enter, 'text: {}', it)
+ logger.info('processing text with path: {}', it.path)
+
+ def frontMatterResult = it.type.frontMatterGetter.get(it)
+ FrontMatter frontMatter
+ if (!frontMatterResult.v1.isEmpty()) {
+ diagnostics.addAll(frontMatterResult.v1)
+ logger.trace(exit, 'result: {}', null)
+ return null
+ } else {
+ frontMatter = frontMatterResult.v2
+ logger.debug('frontMatter: {}', frontMatter)
+ }
+
+ def desiredTemplate = frontMatter.find('template')
+ if (desiredTemplate.isEmpty()) {
+ logger.info('text with path {} has no \'template\' key in its frontMatter; skipping', it.path)
+ logger.trace(exit, 'result: {}', null)
+ return null
+ }
+ def template = templates.find { it.path == desiredTemplate.get() }
+ if (template == null) {
+ diagnostics << new Diagnostic("in text with path ${ it.path }, frontMatter.template refers to an unknown template: ${ desiredTemplate.get() }")
+ logger.trace(exit, 'result: {}', null)
+ return null
+ }
+ logger.debug('found template: {}', template)
+
+ def htmlPath = ExtensionsUtil.stripExtension(it.path) + '.html'
+
+ def renderTemplate = { TaskContainer tasks, TaskTypeContainer taskTypes, Closure onDiagnostics ->
+ def templateRenderResult = template.type.renderer.render(
+ template,
+ it,
+ new RenderContext(
+ config,
+ siteSpec,
+ globals,
+ texts,
+ parts,
+ it.path,
+ htmlPath,
+ tasks,
+ taskTypes
+ )
+ )
+
+ if (!templateRenderResult.v1.isEmpty()) {
+ onDiagnostics(templateRenderResult.v1)
+ ''
+ } else {
+ templateRenderResult.v2
+ }
+ }
+
+ def result = new TextToHtmlFileTask(
+ "textToHtmlFileTask:${ it.path }:${ htmlPath }",
+ it,
+ new HtmlFileOutput(
+ new File(build.outDir, htmlPath),
+ htmlPath,
+ renderTemplate
+ )
+ )
+
+ logger.trace(exit, 'result: {}', result)
+ result
+ })
+
+ def result = new Result<>(diagnostics, tasks)
+ logger.trace(exit, 'result: {}', result)
+ result
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/WithInput.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/WithInput.groovy
new file mode 100644
index 0000000..7482c8a
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/WithInput.groovy
@@ -0,0 +1,5 @@
+package com.jessebrault.ssg.task
+
+interface WithInput {
+ I getInput()
+}
\ No newline at end of file
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/task/WithOutput.groovy b/lib/src/main/groovy/com/jessebrault/ssg/task/WithOutput.groovy
new file mode 100644
index 0000000..b181686
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/task/WithOutput.groovy
@@ -0,0 +1,5 @@
+package com.jessebrault.ssg.task
+
+interface WithOutput {
+ O getOutput()
+}
\ No newline at end of file
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy
index ec538b6..271b240 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy
@@ -1,9 +1,9 @@
package com.jessebrault.ssg.template
import com.jessebrault.ssg.Diagnostic
-import com.jessebrault.ssg.part.EmbeddablePartsMap
-import com.jessebrault.ssg.part.Part
-import com.jessebrault.ssg.text.FrontMatter
+import com.jessebrault.ssg.dsl.StandardDslMap
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.text.Text
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
@@ -18,24 +18,26 @@ class GspTemplateRenderer implements TemplateRenderer {
@Override
Tuple2, String> render(
Template template,
- FrontMatter frontMatter,
- String text,
- Collection parts,
- Map globals
+ Text text,
+ RenderContext context
) {
+ def diagnostics = []
try {
- Collection diagnostics = []
- def result = engine.createTemplate(template.text).make([
- frontMatter: frontMatter,
- globals: globals,
- parts: new EmbeddablePartsMap(parts, globals, { Collection partDiagnostics ->
- diagnostics.addAll(partDiagnostics)
- }),
- text: text
- ])
+ def dslMap = StandardDslMap.get(context) {
+ it.loggerName = "GspTemplate(${ template.path })"
+ it.onDiagnostics = diagnostics.&addAll
+ it.text = text
+ }
+ def result = engine.createTemplate(template.text).make(dslMap)
new Tuple2<>(diagnostics, result.toString())
} catch (Exception e) {
- new Tuple2<>([new Diagnostic("An exception occurred while rendering Template ${ template.path }:\n${ e }", e)], '')
+ new Tuple2<>(
+ [*diagnostics, new Diagnostic(
+ "An exception occurred while rendering Template ${ template.path }:\n${ e }",
+ e
+ )],
+ ''
+ )
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy
index a656c1e..1a08903 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy
@@ -1,53 +1,38 @@
package com.jessebrault.ssg.template
-import com.jessebrault.ssg.provider.WithWatchableDir
-import com.jessebrault.ssg.util.FileNameHandler
-import groovy.io.FileType
+import com.jessebrault.ssg.provider.AbstractFileCollectionProvider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
+import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode(includeFields = true)
-class TemplateFileTemplatesProvider implements TemplatesProvider, WithWatchableDir {
+class TemplateFileTemplatesProvider extends AbstractFileCollectionProvider implements TemplatesProvider {
private static final Logger logger = LoggerFactory.getLogger(TemplateFileTemplatesProvider)
private final Collection templateTypes
- private final File templatesDir
- TemplateFileTemplatesProvider(Collection templateTypes, File templatesDir) {
- this.templateTypes = templateTypes
- this.templatesDir = templatesDir
- this.watchableDir = this.templatesDir
+ TemplateFileTemplatesProvider(File templatesDir, Collection templateTypes) {
+ super(templatesDir)
+ this.templateTypes = Objects.requireNonNull(templateTypes)
}
- private TemplateType getType(File file) {
+ private @Nullable TemplateType getType(String extension) {
this.templateTypes.find {
- it.ids.contains(new FileNameHandler(file).getExtension())
+ it.ids.contains(extension)
}
}
@Override
- Collection provide() {
- if (!this.templatesDir.isDirectory()) {
- logger.warn('templatesDir {} does not exist or is not a directory; skipping and providing no Templates', this.templatesDir)
- []
- } else {
- def templates = []
- this.templatesDir.eachFileRecurse(FileType.FILES) {
- def type = this.getType(it)
- if (type != null) {
- def relativePath = this.templatesDir.relativePath(it)
- logger.debug('found template {}', relativePath)
- templates << new Template(it.text, relativePath, type)
- } else {
- logger.warn('ignoring {} because there is no templateType for it', it)
- }
- }
- templates
+ protected Template transformFileToT(File file, String relativePath, String extension) {
+ def templateType = getType(extension)
+ if (templateType == null) {
+ logger.warn('there is no TemplateType for template {}, ignoring', relativePath)
}
+ templateType ? new Template(file.text, relativePath, templateType) : null
}
@Override
@@ -57,7 +42,7 @@ class TemplateFileTemplatesProvider implements TemplatesProvider, WithWatchableD
@Override
String toString() {
- "TemplateFileTemplatesProvider(templatesDir: ${ this.templatesDir }, templateTypes: ${ this.templateTypes })"
+ "TemplateFileTemplatesProvider(templatesDir: ${ this.dir }, templateTypes: ${ this.templateTypes })"
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy
index 71e2b5a..ee10f8b 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy
@@ -1,17 +1,15 @@
package com.jessebrault.ssg.template
import com.jessebrault.ssg.Diagnostic
-import com.jessebrault.ssg.part.Part
-import com.jessebrault.ssg.text.FrontMatter
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.text.Text
interface TemplateRenderer {
Tuple2, String> render(
Template template,
- FrontMatter frontMatter,
- String text,
- Collection parts,
- Map globals
+ Text text,
+ RenderContext context
)
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy b/lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy
index ebea25c..90575cb 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy
@@ -6,7 +6,7 @@ import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(includeFields = true, defaults = false)
-@NullCheck
+@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
class EmbeddableText {
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/text/Text.groovy b/lib/src/main/groovy/com/jessebrault/ssg/text/Text.groovy
index ad6fccc..2231f5d 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/text/Text.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/text/Text.groovy
@@ -5,13 +5,13 @@ import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
-@NullCheck
+@NullCheck(includeGenerated = true)
@EqualsAndHashCode
-class Text {
+final class Text {
- String text
- String path
- TextType type
+ final String text
+ final String path
+ final TextType type
@Override
String toString() {
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy
index 1eacbf9..88eea80 100644
--- a/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy
+++ b/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy
@@ -1,55 +1,38 @@
package com.jessebrault.ssg.text
-import com.jessebrault.ssg.provider.WithWatchableDir
-import com.jessebrault.ssg.util.FileNameHandler
-import com.jessebrault.ssg.util.RelativePathHandler
-import groovy.io.FileType
+import com.jessebrault.ssg.provider.AbstractFileCollectionProvider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
+import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode(includeFields = true)
-class TextFileTextsProvider implements TextsProvider, WithWatchableDir {
+class TextFileTextsProvider extends AbstractFileCollectionProvider implements TextsProvider {
private static final Logger logger = LoggerFactory.getLogger(TextFileTextsProvider)
private final Collection textTypes
- private final File textsDir
- TextFileTextsProvider(Collection textTypes, File textsDir) {
- this.textTypes = textTypes
- this.textsDir = textsDir
- this.watchableDir = this.textsDir
+ TextFileTextsProvider(File textsDir, Collection textTypes) {
+ super(textsDir)
+ this.textTypes = Objects.requireNonNull(textTypes)
}
- private TextType getTextType(File file) {
+ private TextType getTextType(String extension) {
this.textTypes.find {
- it.ids.contains(new FileNameHandler(file).getExtension())
+ it.ids.contains(extension)
}
}
@Override
- Collection provide() {
- if (!this.textsDir.isDirectory()) {
- logger.warn('textsDir {} does not exist or is not a directory; skipping and providing no Texts', this.textsDir)
- []
- } else {
- def textFiles = []
- this.textsDir.eachFileRecurse(FileType.FILES) {
- def type = this.getTextType(it)
- if (type != null) {
- def relativePath = this.textsDir.relativePath(it)
- def path = new RelativePathHandler(relativePath).getWithoutExtension()
- logger.debug('found textFile {} with type {}', path, type)
- textFiles << new Text(it.text, path, type)
- } else {
- logger.warn('ignoring {} because there is no textType for it', it)
- }
- }
- textFiles
+ protected @Nullable Text transformFileToT(File file, String relativePath, String extension) {
+ def textType = getTextType(extension)
+ if (!textType) {
+ logger.warn('no TextType for text {}, ignoring', file.path)
}
+ textType ? new Text(file.text, relativePath, textType) : null
}
@Override
@@ -59,7 +42,7 @@ class TextFileTextsProvider implements TextsProvider, WithWatchableDir {
@Override
String toString() {
- "TextFileTextsProvider(textsDir: ${ this.textsDir }, textTypes: ${ this.textTypes })"
+ "TextFileTextsProvider(textsDir: ${ this.dir }, textTypes: ${ this.textTypes })"
}
}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilder.groovy b/lib/src/main/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilder.groovy
new file mode 100644
index 0000000..76ffb5d
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilder.groovy
@@ -0,0 +1,37 @@
+package com.jessebrault.ssg.url
+
+import java.nio.file.Path
+
+class PathBasedUrlBuilder implements UrlBuilder {
+
+ private final String absolute
+ private final String baseUrl
+ private final Path fromDirectory
+
+ PathBasedUrlBuilder(String targetPath, String baseUrl) {
+ this.absolute = baseUrl + '/' + targetPath
+ this.baseUrl = baseUrl
+ def fromFilePath = Path.of(targetPath)
+ if (fromFilePath.parent) {
+ this.fromDirectory = fromFilePath.parent
+ } else {
+ this.fromDirectory = Path.of('')
+ }
+ }
+
+ @Override
+ String getAbsolute() {
+ this.absolute
+ }
+
+ @Override
+ String absolute(String to) {
+ this.baseUrl + '/' + to
+ }
+
+ @Override
+ String relative(String to) {
+ this.fromDirectory.relativize(Path.of(to)).toString()
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/url/UrlBuilder.groovy b/lib/src/main/groovy/com/jessebrault/ssg/url/UrlBuilder.groovy
new file mode 100644
index 0000000..9991136
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/url/UrlBuilder.groovy
@@ -0,0 +1,7 @@
+package com.jessebrault.ssg.url
+
+interface UrlBuilder {
+ String getAbsolute()
+ String absolute(String to)
+ String relative(String to)
+}
\ No newline at end of file
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/util/ExtensionsUtil.groovy b/lib/src/main/groovy/com/jessebrault/ssg/util/ExtensionsUtil.groovy
new file mode 100644
index 0000000..b072c4c
--- /dev/null
+++ b/lib/src/main/groovy/com/jessebrault/ssg/util/ExtensionsUtil.groovy
@@ -0,0 +1,24 @@
+package com.jessebrault.ssg.util
+
+import java.util.regex.Pattern
+
+class ExtensionsUtil {
+
+ private static final Pattern stripExtensionPattern = ~/(.+)\..+$/
+ private static final Pattern getExtensionPattern = ~/.+(\..+)$/
+
+ static String stripExtension(String path) {
+ def m = stripExtensionPattern.matcher(path)
+ m.matches() ? m.group(1) : path
+ }
+
+ static String getExtension(String path) {
+ def m = getExtensionPattern.matcher(path)
+ if (m.matches()) {
+ m.group(1)
+ } else {
+ throw new IllegalArgumentException("cannot get extension for path: ${ path }")
+ }
+ }
+
+}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/util/FileNameHandler.groovy b/lib/src/main/groovy/com/jessebrault/ssg/util/FileNameHandler.groovy
deleted file mode 100644
index 086cf35..0000000
--- a/lib/src/main/groovy/com/jessebrault/ssg/util/FileNameHandler.groovy
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.jessebrault.ssg.util
-
-import groovy.transform.EqualsAndHashCode
-import groovy.transform.NullCheck
-import groovy.transform.TupleConstructor
-
-@TupleConstructor(includeFields = true, defaults = false)
-@NullCheck
-@EqualsAndHashCode(includeFields = true)
-class FileNameHandler {
-
- private final File file
-
- String getExtension() {
- def lastIndexOfDot = this.file.name.lastIndexOf('.')
- if (lastIndexOfDot == -1) {
- ''
- } else {
- this.file.name.substring(lastIndexOfDot)
- }
- }
-
- String getWithoutExtension() {
- this.file.name.substring(0, this.file.name.lastIndexOf('.'))
- }
-
- @Override
- String toString() {
- "FileNameHandler(file: ${ this.file })"
- }
-
-}
diff --git a/lib/src/main/groovy/com/jessebrault/ssg/util/RelativePathHandler.groovy b/lib/src/main/groovy/com/jessebrault/ssg/util/RelativePathHandler.groovy
deleted file mode 100644
index 2258bb0..0000000
--- a/lib/src/main/groovy/com/jessebrault/ssg/util/RelativePathHandler.groovy
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.jessebrault.ssg.util
-
-import groovy.transform.EqualsAndHashCode
-import groovy.transform.NullCheck
-import groovy.transform.TupleConstructor
-
-@TupleConstructor(includeFields = true, defaults = false)
-@NullCheck
-@EqualsAndHashCode(includeFields = true)
-class RelativePathHandler {
-
- private final String relativePath
-
- String getWithoutExtension() {
- this.relativePath.subSequence(0, this.relativePath.lastIndexOf('.'))
- }
-
- @Override
- String toString() {
- "RelativePathHandler(relativePath: ${ this.relativePath })"
- }
-
-}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/SimpleStaticSiteGeneratorIntegrationTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/SimpleStaticSiteGeneratorIntegrationTests.groovy
index dee6973..bc6416c 100644
--- a/lib/src/test/groovy/com/jessebrault/ssg/SimpleStaticSiteGeneratorIntegrationTests.groovy
+++ b/lib/src/test/groovy/com/jessebrault/ssg/SimpleStaticSiteGeneratorIntegrationTests.groovy
@@ -6,6 +6,10 @@ import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.specialpage.GspSpecialPageRenderer
import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider
import com.jessebrault.ssg.specialpage.SpecialPageType
+import com.jessebrault.ssg.task.SpecialPageToHtmlFileTask
+import com.jessebrault.ssg.task.Task
+import com.jessebrault.ssg.task.TaskTypeContainer
+import com.jessebrault.ssg.task.TextToHtmlFileTask
import com.jessebrault.ssg.template.GspTemplateRenderer
import com.jessebrault.ssg.template.TemplateFileTemplatesProvider
import com.jessebrault.ssg.template.TemplateType
@@ -17,6 +21,8 @@ import com.jessebrault.ssg.text.TextType
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
+import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
+import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
import static org.junit.jupiter.api.Assertions.*
class SimpleStaticSiteGeneratorIntegrationTests {
@@ -41,10 +47,10 @@ class SimpleStaticSiteGeneratorIntegrationTests {
def gspPartType = new PartType(['.gsp'], new GspPartRenderer())
def gspSpecialPageType = new SpecialPageType(['.gsp'], new GspSpecialPageRenderer())
- def textsProvider = new TextFileTextsProvider([markdownTextType], this.textsDir)
- def templatesProvider = new TemplateFileTemplatesProvider([gspTemplateType], this.templatesDir)
- def partsProvider = new PartFilePartsProvider([gspPartType], this.partsDir)
- def specialPagesProvider = new SpecialPageFileSpecialPagesProvider([gspSpecialPageType], this.specialPagesDir)
+ def textsProvider = new TextFileTextsProvider(this.textsDir, [markdownTextType])
+ def templatesProvider = new TemplateFileTemplatesProvider(this.templatesDir, [gspTemplateType])
+ def partsProvider = new PartFilePartsProvider(this.partsDir, [gspPartType])
+ def specialPagesProvider = new SpecialPageFileSpecialPagesProvider(this.specialPagesDir, [gspSpecialPageType])
def config = new Config(
textProviders: [textsProvider],
@@ -52,25 +58,30 @@ class SimpleStaticSiteGeneratorIntegrationTests {
partsProviders: [partsProvider],
specialPagesProviders: [specialPagesProvider]
)
+ def siteSpec = new SiteSpec('Test Site', 'https://test.com')
def globals = [:]
- this.build = new Build('testBuild', config, globals, new File('build'))
+ this.build = new Build('testBuild', config, siteSpec, globals, new File('build'))
this.ssg = new SimpleStaticSiteGenerator()
}
@Test
void simple() {
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.render() %>')
def result = this.ssg.generate(this.build)
- assertTrue(result.v1.size() == 0)
- assertTrue(result.v2.size() == 1)
+ assertEmptyDiagnostics(result)
+ def tasks = result.get()
+ assertTrue(tasks.size() == 1)
- def p0 = result.v2[0]
- assertEquals('test', p0.path)
- assertEquals('Hello, World!
\n', p0.html)
+ def t0 = tasks.findAllByType(TextToHtmlFileTask.TYPE)[0]
+ assertEquals('test.html', t0.output.htmlPath)
+ def contentResult = t0.output.getContent(tasks, new TaskTypeContainer([TextToHtmlFileTask.TYPE])) { Collection diagnostics ->
+ fail(getDiagnosticsMessageSupplier(diagnostics))
+ }
+ assertEquals('Hello, World!
\n', contentResult)
}
@Test
@@ -81,44 +92,65 @@ class SimpleStaticSiteGeneratorIntegrationTests {
}
}
- new File(this.templatesDir, 'nested.gsp').write('<%= text %>')
+ new File(this.templatesDir, 'nested.gsp').write('<%= text.render() %>')
def result = this.ssg.generate(this.build)
- assertTrue(result.v1.size() == 0)
- assertTrue(result.v2.size() == 1)
+ assertEmptyDiagnostics(result)
+ def tasks = result.get()
+ assertTrue(tasks.size() == 1)
- def p0 = result.v2[0]
- assertEquals('nested/nested', p0.path)
- assertEquals('Hello, World!
\n', p0.html)
+ def t0 = tasks.findAllByType(TextToHtmlFileTask.TYPE)[0]
+ assertEquals('nested/nested.html', t0.output.htmlPath)
+ def contentResult = t0.output.getContent(
+ tasks,
+ new TaskTypeContainer([TextToHtmlFileTask.TYPE])
+ ) { Collection diagnostics ->
+ fail(getDiagnosticsMessageSupplier(diagnostics))
+ }
+ assertEquals('Hello, World!
\n', contentResult)
}
@Test
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.md' }.render() %>/$)
new FileTreeBuilder(this.templatesDir).file('template.gsp', '<%= 1 + 1 %>')
new FileTreeBuilder(this.textsDir).file('test.md', '---\ntemplate: template.gsp\n---\nHello, World!')
def result = this.ssg.generate(this.build)
- assertEquals(0, result.v1.size())
- assertEquals(2, result.v2.size())
+ assertEmptyDiagnostics(result)
+ def tasks = result.get()
+ assertEquals(2, tasks.size())
- def testPage = result.v2.find { it.path == 'test' }
- assertNotNull(testPage)
- assertEquals('2', testPage.html)
+ def taskTypes = new TaskTypeContainer([TextToHtmlFileTask.TYPE, SpecialPageToHtmlFileTask.TYPE])
- def specialPage = result.v2.find { it.path == 'special' }
- assertNotNull(specialPage)
- assertEquals('Hello, World!
\n', specialPage.html)
+ def testPageTask = tasks.findAllByType(TextToHtmlFileTask.TYPE).find {
+ it.output.htmlPath == 'test.html'
+ }
+ assertNotNull(testPageTask)
+ def testPageContent = testPageTask.output.getContent(tasks, taskTypes) { Collection diagnostics ->
+ fail(getDiagnosticsMessageSupplier(diagnostics))
+ }
+ assertEquals('2', testPageContent)
+
+ def specialPageTask = tasks.findAllByType(SpecialPageToHtmlFileTask.TYPE).find {
+ it.output.htmlPath == 'special.html'
+ }
+ assertNotNull(specialPageTask)
+ def specialPageContent = specialPageTask.output.getContent(tasks, taskTypes) { Collection diagnostics ->
+ fail(getDiagnosticsMessageSupplier(diagnostics))
+ }
+ assertEquals('Hello, World!
\n', specialPageContent)
}
@Test
void doesNotGenerateIfNoTemplateInFrontMatter() {
new File(this.textsDir, 'test.md').write('Hello, World!')
def result = this.ssg.generate(this.build)
- assertEquals(0, result.v1.size())
- assertEquals(0, result.v2.size())
+ assertEmptyDiagnostics(result)
+ assertEquals(0, result.get().size())
}
}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy
index d905341..0e85049 100644
--- a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy
+++ b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy
@@ -1,35 +1,109 @@
package com.jessebrault.ssg.part
+import com.jessebrault.ssg.Diagnostic
+import com.jessebrault.ssg.dsl.StandardDslConsumerTests
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.text.Text
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.junit.jupiter.MockitoExtension
+import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertDiagnosticException
+import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext
+import static com.jessebrault.ssg.text.TextMocks.renderableText
import static org.junit.jupiter.api.Assertions.assertEquals
-import static org.junit.jupiter.api.Assertions.assertTrue
-class GspPartRendererTests {
+@ExtendWith(MockitoExtension)
+class GspPartRendererTests implements StandardDslConsumerTests {
private final PartRenderer renderer = new GspPartRenderer()
- @Test
- void rendersWithNoBindingOrGlobals() {
- def part = new Part('', null, 'Hello, World!')
- def r = this.renderer.render(part, [:], [:])
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello, World!', r.v2)
+ private Tuple2, String> doRender(
+ String scriptlet,
+ RenderContext context,
+ Map binding = [:],
+ Text text = null
+ ) {
+ this.renderer.render(
+ new Part('', new PartType([], this.renderer), scriptlet),
+ binding,
+ context,
+ text
+ )
+ }
+
+ @Override
+ Tuple2, String> render(String scriptlet, RenderContext context) {
+ this.doRender(scriptlet, context)
}
@Test
void rendersWithBinding() {
- def part = new Part('', null, "<%= binding['greeting'] %>")
- def r = this.renderer.render(part, [greeting: 'Hello, World!'], [:])
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello, World!', r.v2)
+ this.checkResult(
+ 'Hello, World!',
+ this.doRender('<%= binding.greeting %>', getRenderContext(), [greeting: 'Hello, World!'])
+ )
}
@Test
- void rendersWithGlobals() {
- def part = new Part(null, null, "<%= globals['greeting'] %>")
- def r = this.renderer.render(part, [:], [greeting: 'Hello, World!'])
- assertTrue(r.v1.size() == 0)
+ void textAvailable() {
+ this.checkResult('Hello, World!', this.renderer.render(
+ new Part('', new PartType([], this.renderer), '<%= text.render() %>'),
+ [:],
+ getRenderContext(),
+ renderableText('Hello, World!')
+ ))
+ }
+
+ @Test
+ void nestedPartDiagnosticBubblesUp() {
+ def nestedProblemPart = new Part(
+ 'nestedProblem.gsp',
+ new PartType([], this.renderer),
+ '<% throw new RuntimeException() %>'
+ )
+ def callerPart = new Part(
+ 'caller.gsp',
+ null,
+ '<% parts["nestedProblem.gsp"].render() %>'
+ )
+ def r = this.renderer.render(
+ callerPart,
+ [:],
+ getRenderContext(parts: [callerPart, nestedProblemPart]),
+ null
+ )
+ assertEquals(1, r.v1.size())
+ assertDiagnosticException(RuntimeException, r.v1[0])
+ assertEquals('', r.v2)
+ }
+
+ @Test
+ void nestedPartIsBlankWhenThrowingExceptionButCallerRendered() {
+ def nestedProblemPart = new Part(
+ 'nestedProblem.gsp',
+ new PartType([], this.renderer),
+ '<% throw new RuntimeException("nested problem exception") %>'
+ )
+ def callerPart = new Part(
+ 'caller.gsp',
+ null,
+ 'Hello, World!<% parts["nestedProblem.gsp"].render() %>'
+ )
+ def r = this.renderer.render(
+ callerPart,
+ [:],
+ getRenderContext(parts: [callerPart, nestedProblemPart]),
+ null
+ )
+ assertEquals(1, r.v1.size())
+ assertDiagnosticException(RuntimeException, r.v1[0]) { e ->
+ assertEquals('nested problem exception', e.message, {
+ def w = new StringWriter()
+ e.printStackTrace(new PrintWriter(w))
+ w.toString()
+ })
+ }
assertEquals('Hello, World!', r.v2)
}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/part/PartFilePartsProviderTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/part/PartFilePartsProviderTests.groovy
index 753f631..f2d5c30 100644
--- a/lib/src/test/groovy/com/jessebrault/ssg/part/PartFilePartsProviderTests.groovy
+++ b/lib/src/test/groovy/com/jessebrault/ssg/part/PartFilePartsProviderTests.groovy
@@ -4,10 +4,11 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
+import static org.mockito.Mockito.mock
class PartFilePartsProviderTests {
- private static final PartType gspPartType = new PartType(['.gsp'], null)
+ private static final PartType gspPartType = new PartType(['.gsp'], mock(PartRenderer))
private File partsDir
private PartsProvider partsProvider
@@ -15,7 +16,7 @@ class PartFilePartsProviderTests {
@BeforeEach
void beforeEach() {
this.partsDir = File.createTempDir()
- partsProvider = new PartFilePartsProvider([gspPartType], this.partsDir)
+ partsProvider = new PartFilePartsProvider(this.partsDir, [gspPartType])
}
@Test
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy
index 15c3b20..5f7561b 100644
--- a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy
+++ b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy
@@ -1,72 +1,22 @@
package com.jessebrault.ssg.specialpage
-import com.jessebrault.ssg.part.Part
-import com.jessebrault.ssg.part.PartRenderer
-import com.jessebrault.ssg.part.PartType
-import com.jessebrault.ssg.text.FrontMatterGetter
-import com.jessebrault.ssg.text.MarkdownExcerptGetter
-import com.jessebrault.ssg.text.Text
-import com.jessebrault.ssg.text.TextRenderer
-import com.jessebrault.ssg.text.TextType
-import org.junit.jupiter.api.Test
+import com.jessebrault.ssg.Diagnostic
+import com.jessebrault.ssg.dsl.StandardDslConsumerTests
+import com.jessebrault.ssg.renderer.RenderContext
import org.junit.jupiter.api.extension.ExtendWith
-import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
-import static org.junit.jupiter.api.Assertions.assertEquals
-import static org.junit.jupiter.api.Assertions.assertTrue
-import static org.mockito.ArgumentMatchers.any
-import static org.mockito.ArgumentMatchers.argThat
-import static org.mockito.Mockito.when
-
@ExtendWith(MockitoExtension)
-class GspSpecialPageRendererTests {
+class GspSpecialPageRendererTests implements StandardDslConsumerTests {
private final SpecialPageRenderer renderer = new GspSpecialPageRenderer()
- @Test
- void rendersGlobal() {
- def specialPage = new SpecialPage("<%= globals['greeting'] %>", null, null)
- def globals = [greeting: 'Hello, World!']
- def r = this.renderer.render(specialPage, [], [], globals)
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello, World!', r.v2)
- }
-
- @Test
- void rendersPartWithNoBinding(@Mock PartRenderer partRenderer) {
- when(partRenderer.render(any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
- def partType = new PartType([], partRenderer)
- def part = new Part('test', partType , '')
-
- def specialPage = new SpecialPage("<%= parts['test'].render() %>", null, null)
- def r = this.renderer.render(specialPage, [], [part], [:])
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello, World!', r.v2)
- }
-
- @Test
- void rendersPartWithBinding(@Mock PartRenderer partRenderer) {
- when(partRenderer.render(any(), argThat { Map m -> m.get('greeting') == 'Hello, World!'}, any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
- def partType = new PartType([], partRenderer)
- def part = new Part('test', partType, '')
-
- def specialPage = new SpecialPage("<%= parts['test'].render([greeting: 'Hello, World!'])", null, null)
- def r = this.renderer.render(specialPage, [], [part], [:])
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello, World!', r.v2)
- }
-
- @Test
- void rendersText(@Mock TextRenderer textRenderer, @Mock FrontMatterGetter frontMatterGetter) {
- when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!
\n'))
- def textType = new TextType([], textRenderer, frontMatterGetter, new MarkdownExcerptGetter())
- def text = new Text('', 'test', textType)
-
- def specialPage = new SpecialPage("<%= texts.find { it.path == 'test' }.render() %>", null, null)
- def r = this.renderer.render(specialPage, [text], [], [:])
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello, World!
\n', r.v2)
+ @Override
+ Tuple2, String> render(String scriptlet, RenderContext context) {
+ this.renderer.render(
+ new SpecialPage(scriptlet, '', new SpecialPageType([], this.renderer)),
+ context
+ )
}
}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProviderTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProviderTests.groovy
index e51b3e1..cebaed0 100644
--- a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProviderTests.groovy
+++ b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProviderTests.groovy
@@ -4,10 +4,11 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
+import static org.mockito.Mockito.mock
class SpecialPageFileSpecialPagesProviderTests {
- private static final SpecialPageType gspType = new SpecialPageType(['.gsp'], null)
+ private static final SpecialPageType gspType = new SpecialPageType(['.gsp'], mock(SpecialPageRenderer))
private File specialPagesDir
private SpecialPagesProvider specialPagesProvider
@@ -15,7 +16,7 @@ class SpecialPageFileSpecialPagesProviderTests {
@BeforeEach
void beforeEach() {
this.specialPagesDir = File.createTempDir()
- this.specialPagesProvider = new SpecialPageFileSpecialPagesProvider([gspType], this.specialPagesDir)
+ this.specialPagesProvider = new SpecialPageFileSpecialPagesProvider(this.specialPagesDir, [gspType])
}
@Test
@@ -26,7 +27,7 @@ class SpecialPageFileSpecialPagesProviderTests {
def r = this.specialPagesProvider.provide()
assertEquals(1, r.size())
def f0 = r[0]
- assertEquals('test', f0.path)
+ assertEquals('test.gsp', f0.path)
assertEquals('<%= "Hello, World!" %>', f0.text)
assertEquals(gspType, f0.type)
}
@@ -40,7 +41,7 @@ class SpecialPageFileSpecialPagesProviderTests {
def r = this.specialPagesProvider.provide()
assertEquals(1, r.size())
def f0 = r[0]
- assertEquals('nested/nested', f0.path)
+ assertEquals('nested/nested.gsp', f0.path)
assertEquals('<%= "Hello, World!" %>', f0.text)
assertEquals(gspType, f0.type)
}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/tagbuilder/TagBuilderTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/tagbuilder/TagBuilderTests.groovy
new file mode 100644
index 0000000..e598822
--- /dev/null
+++ b/lib/src/test/groovy/com/jessebrault/ssg/tagbuilder/TagBuilderTests.groovy
@@ -0,0 +1,54 @@
+package com.jessebrault.ssg.tagbuilder
+
+import org.junit.jupiter.api.Test
+
+import static org.junit.jupiter.api.Assertions.assertEquals
+
+class TagBuilderTests {
+
+ private final TagBuilder tagBuilder = new DynamicTagBuilder()
+
+ @Test
+ void simple() {
+ assertEquals('', this.tagBuilder.create('test'))
+ }
+
+ @Test
+ void withAttributes() {
+ assertEquals('', this.tagBuilder.create('test', [test: 'abc']))
+ }
+
+ @Test
+ void withBody() {
+ assertEquals('test', this.tagBuilder.create('test', 'test'))
+ }
+
+ @Test
+ void withAttributesAndBody() {
+ assertEquals(
+ 'test',
+ this.tagBuilder.create('test', [test: 'abc'], 'test')
+ )
+ }
+
+ @Test
+ void dynamicSimple() {
+ assertEquals('', this.tagBuilder.test())
+ }
+
+ @Test
+ void dynamicWithAttributes() {
+ assertEquals('', this.tagBuilder.test([test: 'abc']))
+ }
+
+ @Test
+ void dynamicWithBody() {
+ assertEquals('test', this.tagBuilder.test('test'))
+ }
+
+ @Test
+ void dynamicWithAttributesAndBody() {
+ assertEquals('test', this.tagBuilder.test([test: 'abc'], 'test'))
+ }
+
+}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy
index 8fac879..94e342b 100644
--- a/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy
+++ b/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy
@@ -1,80 +1,46 @@
package com.jessebrault.ssg.template
-import com.jessebrault.ssg.part.Part
-import com.jessebrault.ssg.part.PartRenderer
-import com.jessebrault.ssg.part.PartType
-import com.jessebrault.ssg.text.FrontMatter
+import com.jessebrault.ssg.Diagnostic
+import com.jessebrault.ssg.dsl.StandardDslConsumerTests
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.text.Text
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
-import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
+import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
+import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext
+import static com.jessebrault.ssg.text.TextMocks.blankText
+import static com.jessebrault.ssg.text.TextMocks.renderableText
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue
-import static org.mockito.ArgumentMatchers.any
-import static org.mockito.ArgumentMatchers.argThat
-import static org.mockito.Mockito.when
@ExtendWith(MockitoExtension)
-class GspTemplateRendererTests {
+class GspTemplateRendererTests implements StandardDslConsumerTests {
private final TemplateRenderer renderer = new GspTemplateRenderer()
+ private Tuple2, String> doRender(String scriptlet, Text text, RenderContext context) {
+ this.renderer.render(new Template(scriptlet, '', null), text, context)
+ }
+
+ @Override
+ Tuple2, String> render(String scriptlet, RenderContext context) {
+ this.doRender(scriptlet, blankText(), context)
+ }
+
+ /**
+ * TODO: refactor this and the super interface methods so that we can re-use rendering logic
+ */
@Test
- void rendersPartWithNoBinding(@Mock PartRenderer partRenderer) {
- def template = new Template(
- "<%= parts['test'].render() %>",
- null,
- null
+ void textAvailableToRender() {
+ def template = new Template('<%= text.render() %>', null, null)
+ def r = this.renderer.render(
+ template,
+ renderableText('Hello, World!'),
+ getRenderContext()
)
-
- when(partRenderer.render(any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
- def partType = new PartType([], partRenderer)
- def part = new Part('test', partType, null)
-
- def r = this.renderer.render(template, new FrontMatter(null, [:]), '', [part], [:])
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello, World!', r.v2)
- }
-
- @Test
- void rendersPartWithBinding(@Mock PartRenderer partRenderer) {
- def template = new Template(
- "<%= parts['greeting'].render([person: 'World']) %>",
- null,
- null
- )
-
- when(partRenderer.render(any(), argThat { Map m -> m.get('person') == 'World' }, any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
- def partType = new PartType([], partRenderer)
- def part = new Part('greeting', partType, null)
-
- def r = this.renderer.render(template, new FrontMatter(null, [:]), '', [part], [:])
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello, World!', r.v2)
- }
-
- @Test
- void rendersFrontMatter() {
- def template = new Template("<%= frontMatter['title'] %>", null, null)
- def r = this.renderer.render(template, new FrontMatter(null, [title: ['Hello!']]), '', [], [:])
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello!', r.v2)
- }
-
- @Test
- void rendersGlobal() {
- def template = new Template("<%= globals['test'] %>", null, null)
- def r = this.renderer.render(template, new FrontMatter(null, [:]), '', [], [test: 'Hello, World!'])
- assertTrue(r.v1.size() == 0)
- assertEquals('Hello, World!', r.v2)
- }
-
- @Test
- void rendersText() {
- def template = new Template('<%= text %>', null, null)
- def r = this.renderer.render(template, new FrontMatter(null, [:]), 'Hello, World!', [], [:])
- assertTrue(r.v1.size() == 0)
+ assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello, World!', r.v2)
}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/template/PageTemplatesProviderTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProviderTests.groovy
similarity index 90%
rename from lib/src/test/groovy/com/jessebrault/ssg/template/PageTemplatesProviderTests.groovy
rename to lib/src/test/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProviderTests.groovy
index 6099e06..871135d 100644
--- a/lib/src/test/groovy/com/jessebrault/ssg/template/PageTemplatesProviderTests.groovy
+++ b/lib/src/test/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProviderTests.groovy
@@ -4,10 +4,11 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
+import static org.mockito.Mockito.mock
-class PageTemplatesProviderTests {
+class TemplateFileTemplatesProviderTests {
- private static final TemplateType gspType = new TemplateType(['.gsp'], null)
+ private static final TemplateType gspType = new TemplateType(['.gsp'], mock(TemplateRenderer))
private File templatesDir
private TemplatesProvider templatesProvider
@@ -15,7 +16,7 @@ class PageTemplatesProviderTests {
@BeforeEach
void beforeEach() {
this.templatesDir = File.createTempDir()
- this.templatesProvider = new TemplateFileTemplatesProvider([gspType], this.templatesDir)
+ this.templatesProvider = new TemplateFileTemplatesProvider(this.templatesDir, [gspType])
}
@Test
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/text/ExcerptGetterTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/text/ExcerptGetterTests.groovy
index 7f8abc1..fe4ec29 100644
--- a/lib/src/test/groovy/com/jessebrault/ssg/text/ExcerptGetterTests.groovy
+++ b/lib/src/test/groovy/com/jessebrault/ssg/text/ExcerptGetterTests.groovy
@@ -2,6 +2,7 @@ package com.jessebrault.ssg.text
import org.junit.jupiter.api.Test
+import static com.jessebrault.ssg.text.TextMocks.renderableText
import static org.junit.jupiter.api.Assertions.assertEquals
class ExcerptGetterTests {
@@ -10,7 +11,7 @@ class ExcerptGetterTests {
@Test
void takesAllIfTextLessThanLimit() {
- def text = new Text('One Two Three Four Five', null, null)
+ def text = renderableText('One Two Three Four Five')
def result = this.excerptGetter.getExcerpt(text, 10)
assertEquals(0, result.v1.size())
assertEquals('One Two Three Four Five', result.v2)
@@ -18,7 +19,7 @@ class ExcerptGetterTests {
@Test
void takesTheLimit() {
- def text = new Text('One Two Three Four Five', null, null)
+ def text = renderableText('One Two Three Four Five')
def result = this.excerptGetter.getExcerpt(text, 2)
assertEquals(0, result.v1.size())
assertEquals('One Two', result.v2)
@@ -26,7 +27,7 @@ class ExcerptGetterTests {
@Test
void worksWithHeading() {
- def text = new Text('# Heading\nOne Two Three', null, null)
+ def text = renderableText('# Heading\nOne Two Three')
def result = this.excerptGetter.getExcerpt(text, 1)
assertEquals(0, result.v1.size())
assertEquals('Heading', result.v2)
@@ -34,7 +35,7 @@ class ExcerptGetterTests {
@Test
void worksWithFrontMatter() {
- def text = new Text('---\ntest: hello\n---\nOne Two Three', null, null)
+ def text = renderableText('---\ntest: hello\n---\nOne Two Three')
def result = this.excerptGetter.getExcerpt(text, 1)
assertEquals(0, result.v1.size())
assertEquals('One', result.v2)
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/text/TextFileTextsProviderTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/text/TextFileTextsProviderTests.groovy
index b862a10..210c412 100644
--- a/lib/src/test/groovy/com/jessebrault/ssg/text/TextFileTextsProviderTests.groovy
+++ b/lib/src/test/groovy/com/jessebrault/ssg/text/TextFileTextsProviderTests.groovy
@@ -4,10 +4,16 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
+import static org.mockito.Mockito.mock
class TextFileTextsProviderTests {
- private static final TextType markdownType = new TextType(['.md'], null, null, null)
+ private static final TextType markdownType = new TextType(
+ ['.md'],
+ mock(TextRenderer),
+ mock(FrontMatterGetter),
+ mock(ExcerptGetter)
+ )
private File textsDir
private TextsProvider textsProvider
@@ -15,7 +21,7 @@ class TextFileTextsProviderTests {
@BeforeEach
void beforeEach() {
this.textsDir = File.createTempDir()
- this.textsProvider = new TextFileTextsProvider([markdownType], this.textsDir)
+ this.textsProvider = new TextFileTextsProvider(this.textsDir, [markdownType])
}
@Test
@@ -25,7 +31,7 @@ class TextFileTextsProviderTests {
def r = this.textsProvider.provide()
assertEquals(1, r.size())
def f0 = r[0]
- assertEquals('test', f0.path)
+ assertEquals('test.md', f0.path)
assertEquals('**Hello, World!**', f0.text)
assertEquals(markdownType, f0.type)
}
@@ -39,7 +45,7 @@ class TextFileTextsProviderTests {
def r = this.textsProvider.provide()
assertEquals(1, r.size())
def f0 = r[0]
- assertEquals('nested/nested', f0.path)
+ assertEquals('nested/nested.md', f0.path)
assertEquals('**Hello!**', f0.text)
assertEquals(markdownType, f0.type)
}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilderTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilderTests.groovy
new file mode 100644
index 0000000..4b56f50
--- /dev/null
+++ b/lib/src/test/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilderTests.groovy
@@ -0,0 +1,10 @@
+package com.jessebrault.ssg.url
+
+class PathBasedUrlBuilderTests extends AbstractUrlBuilderTests {
+
+ @Override
+ protected UrlBuilder getUrlBuilder(String targetPath, String baseUrl) {
+ new PathBasedUrlBuilder(targetPath, baseUrl)
+ }
+
+}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/util/ExtensionsUtilTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/util/ExtensionsUtilTests.groovy
new file mode 100644
index 0000000..438c3c8
--- /dev/null
+++ b/lib/src/test/groovy/com/jessebrault/ssg/util/ExtensionsUtilTests.groovy
@@ -0,0 +1,49 @@
+package com.jessebrault.ssg.util
+
+import org.junit.jupiter.api.Test
+
+import static com.jessebrault.ssg.util.ExtensionsUtil.stripExtension
+import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension
+import static org.junit.jupiter.api.Assertions.assertEquals
+
+class ExtensionsUtilTests {
+
+ static class StripExtensionTests {
+
+ @Test
+ void simple() {
+ assertEquals('test', stripExtension('test.txt'))
+ }
+
+ @Test
+ void withSlashes() {
+ assertEquals('test/test', stripExtension('test/test.txt'))
+ }
+
+ @Test
+ void withMultipleExtensions() {
+ assertEquals('test.txt', stripExtension('test.txt.html'))
+ }
+
+ }
+
+ static class GetExtensionTests {
+
+ @Test
+ void simple() {
+ assertEquals('.txt', getExtension('test.txt'))
+ }
+
+ @Test
+ void withSlashes() {
+ assertEquals('.txt', getExtension('test/test.txt'))
+ }
+
+ @Test
+ void withMultipleExtensions() {
+ assertEquals('.txt', getExtension('test.test.txt'))
+ }
+
+ }
+
+}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/util/FileNameHandlerTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/util/FileNameHandlerTests.groovy
deleted file mode 100644
index d15f57b..0000000
--- a/lib/src/test/groovy/com/jessebrault/ssg/util/FileNameHandlerTests.groovy
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.jessebrault.ssg.util
-
-import org.junit.jupiter.api.Test
-
-import static org.junit.jupiter.api.Assertions.assertEquals
-
-class FileNameHandlerTests {
-
- @Test
- void getsCorrectExtension() {
- def file = new File('hello.txt')
- def extension = new FileNameHandler(file).getExtension()
- assertEquals('.txt', extension)
- }
-
-}
diff --git a/lib/src/test/groovy/com/jessebrault/ssg/util/RelativePathHandlerTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/util/RelativePathHandlerTests.groovy
deleted file mode 100644
index a82f87e..0000000
--- a/lib/src/test/groovy/com/jessebrault/ssg/util/RelativePathHandlerTests.groovy
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.jessebrault.ssg.util
-
-import org.junit.jupiter.api.Test
-
-import static org.junit.jupiter.api.Assertions.assertEquals
-
-class RelativePathHandlerTests {
-
- @Test
- void stripsExtension() {
- def stripped = new RelativePathHandler('hello.txt').getWithoutExtension()
- assertEquals('hello', stripped)
- }
-
-}
diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/dsl/DslScriptletProvider.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/dsl/DslScriptletProvider.groovy
new file mode 100644
index 0000000..3b9689b
--- /dev/null
+++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/dsl/DslScriptletProvider.groovy
@@ -0,0 +1,28 @@
+package com.jessebrault.ssg.dsl
+
+interface DslScriptletProvider {
+ String getGlobalsAvailable()
+ String getRenderGlobal()
+
+ String getLoggerAvailable()
+
+ String getPartsAvailable()
+ String getRenderPartFromParts()
+
+ String getSiteSpecAvailable()
+ String getRenderSiteSpecValues()
+
+ String getSourcePathAvailable()
+ String getRenderSourcePathValue()
+
+ String getTagBuilderAvailable()
+
+ String getTargetPathAvailable()
+ String getRenderTargetPath()
+
+ String getTextsAvailable()
+ String getRenderTextFromTexts()
+
+ String getUrlBuilderAvailable()
+ String getUrlBuilderCorrectlyConfigured()
+}
\ No newline at end of file
diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/dsl/StandardDslConsumerTests.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/dsl/StandardDslConsumerTests.groovy
new file mode 100644
index 0000000..715bbab
--- /dev/null
+++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/dsl/StandardDslConsumerTests.groovy
@@ -0,0 +1,240 @@
+package com.jessebrault.ssg.dsl
+
+import com.jessebrault.ssg.Diagnostic
+import com.jessebrault.ssg.SiteSpec
+import com.jessebrault.ssg.part.EmbeddablePartsMap
+import com.jessebrault.ssg.part.Part
+import com.jessebrault.ssg.part.PartRenderer
+import com.jessebrault.ssg.part.PartType
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.tagbuilder.TagBuilder
+import com.jessebrault.ssg.task.HtmlFileOutput
+import com.jessebrault.ssg.task.SpecialPageToHtmlFileTask
+import com.jessebrault.ssg.task.TaskContainer
+import com.jessebrault.ssg.task.TaskTypeContainer
+import com.jessebrault.ssg.task.TextToHtmlFileTask
+import com.jessebrault.ssg.task.TextToHtmlFileTaskFactory
+import com.jessebrault.ssg.text.EmbeddableTextsCollection
+import com.jessebrault.ssg.url.UrlBuilder
+import net.bytebuddy.implementation.bytecode.Throw
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.mockito.Mock
+import org.mockito.MockedStatic
+import org.mockito.junit.jupiter.MockitoExtension
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import static com.jessebrault.ssg.task.SpecialPageToHtmlFileTaskMocks.blankSpecialPageToHtmlFileTask
+import static com.jessebrault.ssg.task.TextToHtmlFileTaskMocks.blankTextToHtmlFileTask
+import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
+import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext
+import static com.jessebrault.ssg.text.TextMocks.blankText
+import static com.jessebrault.ssg.text.TextMocks.renderableTextWithPath
+import static org.junit.jupiter.api.Assertions.assertEquals
+import static org.junit.jupiter.api.Assertions.fail
+import static org.mockito.ArgumentMatchers.any
+import static org.mockito.ArgumentMatchers.anyString
+import static org.mockito.Mockito.*
+
+@ExtendWith(MockitoExtension)
+interface StandardDslConsumerTests {
+
+ Tuple2, String> render(String scriptlet, RenderContext context)
+
+ default void checkResult(String expected, Tuple2, String> result) {
+ assertEmptyDiagnostics(result)
+ assertEquals(expected, result.v2)
+ }
+
+ default void doDslRenderTest(String expected, String scriptlet, RenderContext context = null) {
+ this.checkResult(expected, this.render(scriptlet, context ?: getRenderContext()))
+ }
+
+ default void doDslAssertionTest(String scriptlet, RenderContext context = null) {
+ Tuple2, String> result = null
+ try {
+ result = this.render(scriptlet, context ?: getRenderContext())
+ } catch (Throwable e) {
+ fail(e)
+ }
+ assertEmptyDiagnostics(result)
+ }
+
+ @Test
+ default void rendersGlobal() {
+ this.doDslRenderTest(
+ 'Hello, World!',
+ '<%= globals.test %>',
+ getRenderContext(globals: [test: 'Hello, World!'])
+ )
+ }
+
+ @Test
+ default void loggerAvailable(@Mock Logger logger) {
+ try (MockedStatic loggerFactory = mockStatic(LoggerFactory)) {
+ loggerFactory.when { LoggerFactory.getLogger(anyString()) }
+ .thenReturn(logger)
+
+ this.doDslAssertionTest('<% assert logger; logger.info("Hello, World!") %>')
+ verify(logger).info('Hello, World!')
+ }
+ }
+
+ @Test
+ default void partsAvailable() {
+ this.doDslAssertionTest("<% assert parts != null && parts instanceof ${ EmbeddablePartsMap.name } %>")
+ }
+
+ @Test
+ default void partAvailableAndRenderable(@Mock PartRenderer partRenderer) {
+ when(partRenderer.render(any(), any(), any(), any()))
+ .thenReturn(new Tuple2<>([], 'Hello, World!'))
+ def part = new Part(
+ 'test.gsp',
+ new PartType([], partRenderer),
+ 'Hello, World!'
+ )
+ this.doDslRenderTest(
+ 'Hello, World!',
+ '<%= parts["test.gsp"].render() %>',
+ getRenderContext(parts: [part])
+ )
+ }
+
+ @Test
+ default void siteSpecAvailable() {
+ this.doDslAssertionTest(
+ "<% assert siteSpec && siteSpec instanceof ${ SiteSpec.name } %>"
+ )
+ }
+
+ @Test
+ default void siteSpecRendersCorrectValues() {
+ def siteSpec = new SiteSpec('Test Site', 'https://test.com')
+ this.doDslRenderTest(
+ 'Test Site https://test.com',
+ '<%= siteSpec.name + " " + siteSpec.baseUrl %>',
+ getRenderContext(siteSpec: siteSpec)
+ )
+ }
+
+ @Test
+ default void sourcePathAvailable() {
+ this.doDslAssertionTest(
+ '<% assert sourcePath && sourcePath instanceof String %>',
+ getRenderContext(sourcePath: 'test.md')
+ )
+ }
+
+ @Test
+ default void sourcePathRendersCorrectValue() {
+ this.doDslRenderTest(
+ 'test.md',
+ '<%= sourcePath %>',
+ getRenderContext(sourcePath: 'test.md')
+ )
+ }
+
+ @Test
+ default void tagBuilderAvailable() {
+ this.doDslAssertionTest("<% assert tagBuilder && tagBuilder instanceof ${ TagBuilder.name } %>")
+ }
+
+ @Test
+ default void targetPathAvailable() {
+ this.doDslAssertionTest(
+ '<% assert targetPath && targetPath instanceof String %>',
+ getRenderContext(targetPath: 'test/test.html')
+ )
+ }
+
+ @Test
+ default void targetPathRendersCorrectValue() {
+ this.doDslRenderTest(
+ 'test/test.html',
+ '<%= targetPath %>',
+ getRenderContext(targetPath: 'test/test.html')
+ )
+ }
+
+ @Test
+ default void tasksAvailable() {
+ this.doDslAssertionTest("<% assert tasks != null && tasks instanceof ${ TaskContainer.name } %>")
+ }
+
+ @Test
+ default void tasksFind() {
+ def task = new TextToHtmlFileTask(
+ 'testTask',
+ blankText(),
+ new HtmlFileOutput(
+ new File('test.html'),
+ 'test.html',
+ { '' }
+ )
+ )
+ this.doDslRenderTest(
+ 'test.html',
+ '<%= tasks.find { it.name == "testTask" }.output.htmlPath %>',
+ getRenderContext(
+ tasks: new TaskContainer([task]),
+ taskTypes: new TaskTypeContainer([TextToHtmlFileTask.TYPE])
+ )
+ )
+ }
+
+ @Test
+ default void taskTypesAvailable() {
+ this.doDslAssertionTest(
+ "<% assert taskTypes != null && taskTypes instanceof ${ TaskTypeContainer.name } %>"
+ )
+ }
+
+ @Test
+ default void taskFindAllByType() {
+ def t0 = blankTextToHtmlFileTask()
+ def t1 = blankSpecialPageToHtmlFileTask()
+ this.doDslAssertionTest(
+ '<% assert tasks.size() == 2 && ' +
+ 'tasks.findAllByType(taskTypes.textToHtmlFile).size() == 1 &&' +
+ 'tasks.findAllByType(taskTypes.specialPageToHtmlFile).size() == 1 %>',
+ getRenderContext(
+ tasks: new TaskContainer([t0, t1]),
+ taskTypes: new TaskTypeContainer([TextToHtmlFileTask.TYPE, SpecialPageToHtmlFileTask.TYPE])
+ )
+ )
+ }
+
+ @Test
+ default void textsAvailable() {
+ this.doDslAssertionTest(
+ "<% assert texts != null && texts instanceof ${ EmbeddableTextsCollection.name } %>"
+ )
+ }
+
+ @Test
+ default void textsTextAvailableAndRenderable() {
+ def testText = renderableTextWithPath('Hello, World!', 'test.md')
+ this.doDslRenderTest(
+ 'Hello, World!',
+ '<%= texts.find { it.path == "test.md" }.render() %>',
+ getRenderContext(texts: [testText])
+ )
+ }
+
+ @Test
+ default void urlBuilderAvailable() {
+ this.doDslAssertionTest("<% assert urlBuilder && urlBuilder instanceof ${ UrlBuilder.name } %>")
+ }
+
+ @Test
+ default void urlBuilderCorrectlyConfigured() {
+ this.doDslRenderTest(
+ '../images/test.jpg',
+ '<%= urlBuilder.relative("images/test.jpg") %>',
+ getRenderContext(targetPath: 'test/test.html')
+ )
+ }
+
+}
diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/specialpage/SpecialPageMocks.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/specialpage/SpecialPageMocks.groovy
new file mode 100644
index 0000000..8dfdda7
--- /dev/null
+++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/specialpage/SpecialPageMocks.groovy
@@ -0,0 +1,16 @@
+package com.jessebrault.ssg.specialpage
+
+import static org.mockito.Mockito.mock
+
+final class SpecialPageMocks {
+
+ static SpecialPage blankSpecialPage() {
+ def renderer = mock(SpecialPageRenderer)
+ new SpecialPage(
+ '',
+ '',
+ new SpecialPageType([], renderer)
+ )
+ }
+
+}
diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/task/SpecialPageToHtmlFileTaskMocks.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/task/SpecialPageToHtmlFileTaskMocks.groovy
new file mode 100644
index 0000000..df07cb9
--- /dev/null
+++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/task/SpecialPageToHtmlFileTaskMocks.groovy
@@ -0,0 +1,19 @@
+package com.jessebrault.ssg.task
+
+import static com.jessebrault.ssg.specialpage.SpecialPageMocks.blankSpecialPage
+
+final class SpecialPageToHtmlFileTaskMocks {
+
+ static SpecialPageToHtmlFileTask blankSpecialPageToHtmlFileTask() {
+ new SpecialPageToHtmlFileTask(
+ '',
+ blankSpecialPage(),
+ new HtmlFileOutput(
+ new File(''),
+ '',
+ { '' }
+ )
+ )
+ }
+
+}
diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/task/TextToHtmlFileTaskMocks.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/task/TextToHtmlFileTaskMocks.groovy
new file mode 100644
index 0000000..2a13a01
--- /dev/null
+++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/task/TextToHtmlFileTaskMocks.groovy
@@ -0,0 +1,19 @@
+package com.jessebrault.ssg.task
+
+import static com.jessebrault.ssg.text.TextMocks.blankText
+
+final class TextToHtmlFileTaskMocks {
+
+ static TextToHtmlFileTask blankTextToHtmlFileTask() {
+ new TextToHtmlFileTask(
+ '',
+ blankText(),
+ new HtmlFileOutput(
+ new File(''),
+ '',
+ { '' }
+ )
+ )
+ }
+
+}
diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy
new file mode 100644
index 0000000..7f51645
--- /dev/null
+++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy
@@ -0,0 +1,47 @@
+package com.jessebrault.ssg.testutil
+
+import com.jessebrault.ssg.Diagnostic
+import com.jessebrault.ssg.Result
+import com.jessebrault.ssg.text.ExcerptGetter
+import groovy.transform.stc.ClosureParams
+import groovy.transform.stc.FirstParam
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf
+import static org.junit.jupiter.api.Assertions.assertTrue
+
+class DiagnosticsUtil {
+
+ static Closure getDiagnosticsMessageSupplier(Collection diagnostics) {
+ return {
+ diagnostics.collect {
+ def writer = new StringWriter()
+ it.exception.printStackTrace(new PrintWriter(writer))
+ def stackTrace = writer.toString()
+ "$it.message\n$stackTrace"
+ }.join('\n')
+ }
+ }
+
+ static void assertEmptyDiagnostics(Tuple2, ?> result) {
+ assertTrue(result.v1.isEmpty(), getDiagnosticsMessageSupplier(result.v1))
+ }
+
+ static void assertEmptyDiagnostics(Result> result) {
+ assertTrue(!result.hasDiagnostics(), getDiagnosticsMessageSupplier(result.diagnostics))
+ }
+
+ static void assertDiagnosticException(
+ Class expectedException,
+ Diagnostic diagnostic,
+ @ClosureParams(FirstParam.FirstGenericType)
+ Closure additionalAssertions = null
+ ) {
+ assertInstanceOf(expectedException, diagnostic.exception, {
+ "Incorrect diagnostic exception class; message: ${ diagnostic.message }"
+ })
+ if (additionalAssertions) {
+ additionalAssertions(diagnostic.exception)
+ }
+ }
+
+}
diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/RenderContextUtil.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/RenderContextUtil.groovy
new file mode 100644
index 0000000..2477d6e
--- /dev/null
+++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/RenderContextUtil.groovy
@@ -0,0 +1,26 @@
+package com.jessebrault.ssg.testutil
+
+import com.jessebrault.ssg.Config
+import com.jessebrault.ssg.SiteSpec
+import com.jessebrault.ssg.renderer.RenderContext
+import com.jessebrault.ssg.task.Task
+import com.jessebrault.ssg.task.TaskContainer
+import com.jessebrault.ssg.task.TaskTypeContainer
+
+class RenderContextUtil {
+
+ static RenderContext getRenderContext(Map args = null) {
+ new RenderContext(
+ args?.config as Config ?: new Config(),
+ args?.siteSpec as SiteSpec ?: new SiteSpec('', ''),
+ args?.globals as Map ?: [:],
+ args?.texts as Collection ?: [],
+ args?.parts as Collection ?: [],
+ args?.sourcePath as String ?: '',
+ args?.targetPath as String ?: '',
+ args?.tasks as TaskContainer ?: new TaskContainer(),
+ args?.taskTypes as TaskTypeContainer ?: new TaskTypeContainer()
+ )
+ }
+
+}
diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/text/TextMocks.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/text/TextMocks.groovy
new file mode 100644
index 0000000..ee8a81c
--- /dev/null
+++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/text/TextMocks.groovy
@@ -0,0 +1,39 @@
+package com.jessebrault.ssg.text
+
+import static org.mockito.ArgumentMatchers.any
+import static org.mockito.Mockito.mock
+import static org.mockito.Mockito.when
+
+class TextMocks {
+
+ static Text blankText() {
+ def textRenderer = mock(TextRenderer)
+ def frontMatterGetter = mock(FrontMatterGetter)
+ def excerptGetter = mock(ExcerptGetter)
+ new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
+ }
+
+ static Text renderableText(String text) {
+ def textRenderer = mock(TextRenderer)
+ when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
+ def frontMatterGetter = mock(FrontMatterGetter)
+ def excerptGetter = mock(ExcerptGetter)
+ new Text(text, '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
+ }
+
+ static Text textWithPath(String path) {
+ def textRenderer = mock(TextRenderer)
+ def frontMatterGetter = mock(FrontMatterGetter)
+ def excerptGetter = mock(ExcerptGetter)
+ new Text('', path, new TextType([], textRenderer, frontMatterGetter, excerptGetter))
+ }
+
+ static Text renderableTextWithPath(String text, String path) {
+ def textRenderer = mock(TextRenderer)
+ when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
+ def frontMatterGetter = mock(FrontMatterGetter)
+ def excerptGetter = mock(ExcerptGetter)
+ new Text(text, path, new TextType([], textRenderer, frontMatterGetter, excerptGetter))
+ }
+
+}
diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/url/AbstractUrlBuilderTests.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/url/AbstractUrlBuilderTests.groovy
new file mode 100644
index 0000000..402d471
--- /dev/null
+++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/url/AbstractUrlBuilderTests.groovy
@@ -0,0 +1,49 @@
+package com.jessebrault.ssg.url
+
+import org.junit.jupiter.api.Test
+
+import static org.junit.jupiter.api.Assertions.assertEquals
+
+abstract class AbstractUrlBuilderTests {
+
+ protected abstract UrlBuilder getUrlBuilder(String targetPath, String baseUrl);
+
+ @Test
+ void upDownDown() {
+ def builder = this.getUrlBuilder('posts/post.html', '')
+ assertEquals('../images/test.jpg', builder.relative('images/test.jpg'))
+ }
+
+ @Test
+ void downDown() {
+ assertEquals(
+ 'images/test.jpg',
+ this.getUrlBuilder('test.html', '').relative('images/test.jpg')
+ )
+ }
+
+ @Test
+ void upUpDownDown() {
+ assertEquals(
+ '../../images/test.jpg',
+ this.getUrlBuilder('posts/old/test.html', '').relative('images/test.jpg')
+ )
+ }
+
+ @Test
+ void absoluteMatchesTargetPath() {
+ assertEquals(
+ 'https://test.com/test/test.html',
+ this.getUrlBuilder('test/test.html', 'https://test.com').absolute
+ )
+ }
+
+ @Test
+ void absoluteToCorrect() {
+ assertEquals(
+ 'https://test.com/images/test.jpg',
+ this.getUrlBuilder('', 'https://test.com').absolute('images/test.jpg')
+ )
+ }
+
+}