commit
f05ee20408
122
CHANGELOG.md
Normal file
122
CHANGELOG.md
Normal file
@ -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 == '<test />'
|
||||
|
||||
def tagWithBody = tagBuilder.title 'Hello, World!'
|
||||
assert tagWithBody == '<title>Hello, World!</title>'
|
||||
|
||||
def tagWithAttributes = tagBuilder.meta name: 'og:title', content: 'Hello, World!'
|
||||
assert tagWithAttributes == '<meta name="og:title" content="Hello, World!" />'
|
||||
|
||||
def tagWithAttributesAndBody = tagBuilder.p([id: 'my-paragraph'], 'Hello, World!')
|
||||
assert tagWithAttributesAndBody == '<p id="my-paragraph">Hello, World!</p>'
|
||||
%>
|
||||
```
|
||||
|
||||
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).
|
19
TODO.md
Normal file
19
TODO.md
Normal file
@ -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
|
11
build.gradle
Normal file
11
build.gradle
Normal file
@ -0,0 +1,11 @@
|
||||
plugins {
|
||||
id 'org.asciidoctor.jvm.convert' version '3.3.2'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
asciidoctor {
|
||||
sourceDir = 'docs/asciidoc'
|
||||
}
|
@ -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 {
|
||||
|
@ -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'
|
||||
}
|
@ -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<Diagnostic> executionDiagnostics = []
|
||||
def context = new TaskExecutorContext(
|
||||
it,
|
||||
tasks,
|
||||
this.ssg.taskTypes,
|
||||
{ Collection<Diagnostic> diagnostics ->
|
||||
executionDiagnostics.addAll(diagnostics)
|
||||
}
|
||||
)
|
||||
result.get().each { it.execute(context) }
|
||||
if (!executionDiagnostics.isEmpty()) {
|
||||
hadDiagnostics = true
|
||||
executionDiagnostics.each {
|
||||
logger.error(it.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
docs/asciidoc/manual.asciidoc
Normal file
19
docs/asciidoc/manual.asciidoc
Normal file
@ -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 == '<a href="hello.html">Hello!</a>'
|
||||
out << a // <2>
|
||||
----
|
||||
<1> Create an <a> tag.
|
||||
<2> Output the tag in the current script block.
|
@ -1,6 +1,5 @@
|
||||
plugins {
|
||||
id 'ssg.common'
|
||||
id 'ssg.lib'
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
28
lib/src/main/groovy/com/jessebrault/ssg/Result.groovy
Normal file
28
lib/src/main/groovy/com/jessebrault/ssg/Result.groovy
Normal file
@ -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<T> {
|
||||
|
||||
final Collection<Diagnostic> diagnostics
|
||||
private final T t
|
||||
|
||||
boolean hasDiagnostics() {
|
||||
!this.diagnostics.isEmpty()
|
||||
}
|
||||
|
||||
T get() {
|
||||
this.t
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"Result(diagnostics: ${ this.diagnostics }, t: ${ this.t })"
|
||||
}
|
||||
|
||||
}
|
@ -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<Diagnostic>, Collection<GeneratedPage>> generate(Build build) {
|
||||
TaskTypeContainer getTaskTypes() {
|
||||
new TaskTypeContainer([textHtmlFactory.taskType, specialPageHtmlFactory.taskType])
|
||||
}
|
||||
|
||||
@Override
|
||||
Result<TaskContainer> 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<Diagnostic> diagnostics = []
|
||||
Collection<GeneratedPage> 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
|
||||
|
27
lib/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy
Normal file
27
lib/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy
Normal file
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
import com.jessebrault.ssg.task.TaskContainer
|
||||
import com.jessebrault.ssg.task.TaskTypeContainer
|
||||
|
||||
interface StaticSiteGenerator {
|
||||
Tuple2<Collection<Diagnostic>, Collection<GeneratedPage>> generate(Build build)
|
||||
TaskTypeContainer getTaskTypes()
|
||||
Result<TaskContainer> generate(Build build)
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
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
|
||||
|
||||
@ -18,6 +20,15 @@ class BuildClosureDelegate {
|
||||
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
|
||||
|
@ -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<Build> 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++
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<String, EmbeddablePart> partsMap = [:]
|
||||
|
||||
EmbeddablePartsMap(Collection<Part> parts, Map globals, Closure onDiagnostics) {
|
||||
EmbeddablePartsMap(
|
||||
Collection<Part> 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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Collection<Diagnostic>, String> render(Part part, Map binding, Map globals) {
|
||||
Tuple2<Collection<Diagnostic>, 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
|
||||
)],
|
||||
''
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Part> implements PartsProvider {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PartFilePartsProvider)
|
||||
|
||||
private final Collection<PartType> partTypes
|
||||
private final File partsDir
|
||||
|
||||
PartFilePartsProvider(Collection<PartType> partTypes, File partsDir) {
|
||||
this.partTypes = partTypes
|
||||
this.partsDir = partsDir
|
||||
this.watchableDir = this.partsDir
|
||||
PartFilePartsProvider(File partsDir, Collection<PartType> 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<Part> 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 })"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Collection<Diagnostic>, String> render(Part part, Map binding, Map globals)
|
||||
|
||||
Tuple2<Collection<Diagnostic>, String> render(
|
||||
Part part,
|
||||
Map binding,
|
||||
RenderContext context,
|
||||
@Nullable Text text
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -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<T> implements Provider<Collection<T>>, 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<T> 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<Text> texts
|
||||
final Collection<Part> parts
|
||||
final String sourcePath
|
||||
final String targetPath
|
||||
final TaskContainer tasks
|
||||
final TaskTypeContainer taskTypes
|
||||
}
|
@ -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<Collection<Diagnostic>, String> render(SpecialPage specialPage, Collection<Text> texts, Collection<Part> parts, Map globals) {
|
||||
Tuple2<Collection<Diagnostic>, String> render(
|
||||
SpecialPage specialPage,
|
||||
RenderContext context
|
||||
) {
|
||||
def diagnostics = []
|
||||
try {
|
||||
Collection<Diagnostic> diagnostics = []
|
||||
def result = engine.createTemplate(specialPage.text).make([
|
||||
globals: globals,
|
||||
parts: new EmbeddablePartsMap(parts, globals, { Collection<Diagnostic> partDiagnostics ->
|
||||
diagnostics.addAll(partDiagnostics)
|
||||
}),
|
||||
texts: new EmbeddableTextsCollection(texts, globals, { Collection<Diagnostic> 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
|
||||
)],
|
||||
''
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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<SpecialPage>
|
||||
implements SpecialPagesProvider {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SpecialPageFileSpecialPagesProvider)
|
||||
|
||||
private final Collection<SpecialPageType> specialPageTypes
|
||||
private final File specialPagesDir
|
||||
|
||||
SpecialPageFileSpecialPagesProvider(Collection<SpecialPageType> specialPageTypes, File specialPagesDir) {
|
||||
this.specialPageTypes = specialPageTypes
|
||||
this.specialPagesDir = specialPagesDir
|
||||
this.watchableDir = this.specialPagesDir
|
||||
SpecialPageFileSpecialPagesProvider(File specialPagesDir, Collection<SpecialPageType> 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<SpecialPage> 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 })"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Collection<Diagnostic>, String> render(
|
||||
SpecialPage specialPage,
|
||||
Collection<Text> texts,
|
||||
Collection <Part> parts,
|
||||
Map globals
|
||||
RenderContext context
|
||||
)
|
||||
|
||||
}
|
@ -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<String, ?> 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<String, ?> 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.jessebrault.ssg.tagbuilder
|
||||
|
||||
interface TagBuilder {
|
||||
String create(String name)
|
||||
String create(String name, Map<String, ?> attributes)
|
||||
String create(String name, String body)
|
||||
String create(String name, Map<String, ?> attributes, String body)
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
|
||||
@NullCheck
|
||||
@EqualsAndHashCode
|
||||
abstract class AbstractTask<T extends Task> implements Task {
|
||||
|
||||
final TaskType<T> type
|
||||
final String name
|
||||
|
||||
AbstractTask(TaskType<T> 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 })"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
interface FileInput extends Input {
|
||||
File getFile()
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
interface FileOutput extends Output {
|
||||
File getFile()
|
||||
String getContent()
|
||||
}
|
@ -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<String> contentClosure
|
||||
|
||||
String getContent(TaskContainer tasks, TaskTypeContainer taskTypes, Closure onDiagnostics) {
|
||||
this.contentClosure(tasks, taskTypes, onDiagnostics)
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"HtmlFileOutput(file: ${ this.file }, htmlPath: ${ this.htmlPath })"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
interface Input {
|
||||
String getName()
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
interface Output {
|
||||
String getName()
|
||||
}
|
@ -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<SpecialPageToHtmlFileTask> {
|
||||
|
||||
private static final class SpecialPageToHtmlFileTaskExecutor implements TaskExecutor<SpecialPageToHtmlFileTask> {
|
||||
|
||||
@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<SpecialPageToHtmlFileTask> 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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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<SpecialPageToHtmlFileTask> {
|
||||
|
||||
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<SpecialPageToHtmlFileTask> getTaskType() {
|
||||
SpecialPageToHtmlFileTask.TYPE
|
||||
}
|
||||
|
||||
@Override
|
||||
Result<TaskCollection<SpecialPageToHtmlFileTask>> 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<SpecialPageToHtmlFileTask>(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<Void> 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
|
||||
}
|
||||
|
||||
}
|
7
lib/src/main/groovy/com/jessebrault/ssg/task/Task.groovy
Normal file
7
lib/src/main/groovy/com/jessebrault/ssg/task/Task.groovy
Normal file
@ -0,0 +1,7 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
interface Task {
|
||||
TaskType<? extends Task> getType()
|
||||
String getName()
|
||||
void execute(TaskExecutorContext context)
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
import static java.util.Objects.requireNonNull
|
||||
|
||||
class TaskCollection<T extends Task> {
|
||||
|
||||
@Delegate
|
||||
private final Collection<T> tasks = new ArrayList<T>()
|
||||
|
||||
TaskCollection(Collection<? extends T> tasks = null) {
|
||||
if (tasks != null) {
|
||||
this.tasks.addAll(requireNonNull(tasks))
|
||||
}
|
||||
}
|
||||
|
||||
def <U extends T> TaskCollection<U> findAllByType(
|
||||
TaskType<U> taskType
|
||||
) {
|
||||
new TaskCollection<>(this.tasks.findResults {
|
||||
it.type == taskType ? it : null
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
final class TaskContainer extends TaskCollection<Task> {
|
||||
|
||||
TaskContainer(Collection<? extends Task> tasks = null) {
|
||||
super(tasks)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
interface TaskExecutor<T extends Task> {
|
||||
void execute(T task, TaskExecutorContext context)
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
import com.jessebrault.ssg.Build
|
||||
import com.jessebrault.ssg.Result
|
||||
|
||||
interface TaskFactory<T extends Task> {
|
||||
TaskType<T> getTaskType()
|
||||
Result<TaskCollection<T>> getTasks(Build build)
|
||||
}
|
20
lib/src/main/groovy/com/jessebrault/ssg/task/TaskType.groovy
Normal file
20
lib/src/main/groovy/com/jessebrault/ssg/task/TaskType.groovy
Normal file
@ -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<T extends Task> {
|
||||
|
||||
final String name
|
||||
final TaskExecutor<T> executor
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"TaskType(${ this.name }, ${ this.executor })"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
final class TaskTypeContainer {
|
||||
|
||||
@Delegate
|
||||
private final Set<TaskType<? extends Task>> taskTypes = []
|
||||
|
||||
TaskTypeContainer(Collection<TaskType<? extends Task>> 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
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
@ -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<TextToHtmlFileTask> {
|
||||
|
||||
private static final class TextToHtmlFileTaskExecutor implements TaskExecutor<TextToHtmlFileTask> {
|
||||
|
||||
@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<TextToHtmlFileTask> 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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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<TextToHtmlFileTask> {
|
||||
|
||||
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<TextToHtmlFileTask> getTaskType() {
|
||||
TextToHtmlFileTask.TYPE
|
||||
}
|
||||
|
||||
@Override
|
||||
Result<TaskCollection<TextToHtmlFileTask>> 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<TextToHtmlFileTask>(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<Void> 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
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
interface WithInput<I extends Input> {
|
||||
I getInput()
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.task
|
||||
|
||||
interface WithOutput<O extends Output> {
|
||||
O getOutput()
|
||||
}
|
@ -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<Collection<Diagnostic>, String> render(
|
||||
Template template,
|
||||
FrontMatter frontMatter,
|
||||
String text,
|
||||
Collection<Part> parts,
|
||||
Map globals
|
||||
Text text,
|
||||
RenderContext context
|
||||
) {
|
||||
def diagnostics = []
|
||||
try {
|
||||
Collection<Diagnostic> diagnostics = []
|
||||
def result = engine.createTemplate(template.text).make([
|
||||
frontMatter: frontMatter,
|
||||
globals: globals,
|
||||
parts: new EmbeddablePartsMap(parts, globals, { Collection<Diagnostic> 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
|
||||
)],
|
||||
''
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Template> implements TemplatesProvider {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TemplateFileTemplatesProvider)
|
||||
|
||||
private final Collection<TemplateType> templateTypes
|
||||
private final File templatesDir
|
||||
|
||||
TemplateFileTemplatesProvider(Collection<TemplateType> templateTypes, File templatesDir) {
|
||||
this.templateTypes = templateTypes
|
||||
this.templatesDir = templatesDir
|
||||
this.watchableDir = this.templatesDir
|
||||
TemplateFileTemplatesProvider(File templatesDir, Collection<TemplateType> 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<Template> 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 })"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Collection<Diagnostic>, String> render(
|
||||
Template template,
|
||||
FrontMatter frontMatter,
|
||||
String text,
|
||||
Collection<Part> parts,
|
||||
Map globals
|
||||
Text text,
|
||||
RenderContext context
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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<Text> implements TextsProvider {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TextFileTextsProvider)
|
||||
|
||||
private final Collection<TextType> textTypes
|
||||
private final File textsDir
|
||||
|
||||
TextFileTextsProvider(Collection<TextType> textTypes, File textsDir) {
|
||||
this.textTypes = textTypes
|
||||
this.textsDir = textsDir
|
||||
this.watchableDir = this.textsDir
|
||||
TextFileTextsProvider(File textsDir, Collection<TextType> 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<Text> 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 })"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.jessebrault.ssg.url
|
||||
|
||||
interface UrlBuilder {
|
||||
String getAbsolute()
|
||||
String absolute(String to)
|
||||
String relative(String to)
|
||||
}
|
@ -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 }")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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 })"
|
||||
}
|
||||
|
||||
}
|
@ -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('<p><strong>Hello, World!</strong></p>\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<Diagnostic> diagnostics ->
|
||||
fail(getDiagnosticsMessageSupplier(diagnostics))
|
||||
}
|
||||
assertEquals('<p><strong>Hello, World!</strong></p>\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('<p><strong>Hello, World!</strong></p>\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<Diagnostic> diagnostics ->
|
||||
fail(getDiagnosticsMessageSupplier(diagnostics))
|
||||
}
|
||||
assertEquals('<p><strong>Hello, World!</strong></p>\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('<p>Hello, World!</p>\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<Diagnostic> 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<Diagnostic> diagnostics ->
|
||||
fail(getDiagnosticsMessageSupplier(diagnostics))
|
||||
}
|
||||
assertEquals('<p>Hello, World!</p>\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())
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<Collection<Diagnostic>, 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<Collection<Diagnostic>, 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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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<>([], '<p><strong>Hello, World!</strong></p>\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('<p><strong>Hello, World!</strong></p>\n', r.v2)
|
||||
@Override
|
||||
Tuple2<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context) {
|
||||
this.renderer.render(
|
||||
new SpecialPage(scriptlet, '', new SpecialPageType([], this.renderer)),
|
||||
context
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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('<test />', this.tagBuilder.create('test'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void withAttributes() {
|
||||
assertEquals('<test test="abc" />', this.tagBuilder.create('test', [test: 'abc']))
|
||||
}
|
||||
|
||||
@Test
|
||||
void withBody() {
|
||||
assertEquals('<test>test</test>', this.tagBuilder.create('test', 'test'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void withAttributesAndBody() {
|
||||
assertEquals(
|
||||
'<test test="abc">test</test>',
|
||||
this.tagBuilder.create('test', [test: 'abc'], 'test')
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
void dynamicSimple() {
|
||||
assertEquals('<test />', this.tagBuilder.test())
|
||||
}
|
||||
|
||||
@Test
|
||||
void dynamicWithAttributes() {
|
||||
assertEquals('<test test="abc" />', this.tagBuilder.test([test: 'abc']))
|
||||
}
|
||||
|
||||
@Test
|
||||
void dynamicWithBody() {
|
||||
assertEquals('<test>test</test>', this.tagBuilder.test('test'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void dynamicWithAttributesAndBody() {
|
||||
assertEquals('<test test="abc">test</test>', this.tagBuilder.test([test: 'abc'], 'test'))
|
||||
}
|
||||
|
||||
}
|
@ -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<Collection<Diagnostic>, String> doRender(String scriptlet, Text text, RenderContext context) {
|
||||
this.renderer.render(new Template(scriptlet, '', null), text, context)
|
||||
}
|
||||
|
||||
@Override
|
||||
Tuple2<Collection<Diagnostic>, 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)
|
||||
}
|
||||
|
||||
|
@ -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
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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'))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
}
|
@ -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<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context)
|
||||
|
||||
default void checkResult(String expected, Tuple2<Collection<Diagnostic>, 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<Collection<Diagnostic>, 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> 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')
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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(''),
|
||||
'',
|
||||
{ '' }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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(''),
|
||||
'',
|
||||
{ '' }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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<String> getDiagnosticsMessageSupplier(Collection<Diagnostic> 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<Collection<Diagnostic>, ?> result) {
|
||||
assertTrue(result.v1.isEmpty(), getDiagnosticsMessageSupplier(result.v1))
|
||||
}
|
||||
|
||||
static void assertEmptyDiagnostics(Result<?> result) {
|
||||
assertTrue(!result.hasDiagnostics(), getDiagnosticsMessageSupplier(result.diagnostics))
|
||||
}
|
||||
|
||||
static <E extends Exception> void assertDiagnosticException(
|
||||
Class<E> expectedException,
|
||||
Diagnostic diagnostic,
|
||||
@ClosureParams(FirstParam.FirstGenericType)
|
||||
Closure<Void> additionalAssertions = null
|
||||
) {
|
||||
assertInstanceOf(expectedException, diagnostic.exception, {
|
||||
"Incorrect diagnostic exception class; message: ${ diagnostic.message }"
|
||||
})
|
||||
if (additionalAssertions) {
|
||||
additionalAssertions(diagnostic.exception)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
||||
}
|
@ -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')
|
||||
)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user