Saving before switching to Java.

This commit is contained in:
Jesse Brault 2023-01-24 10:14:15 +01:00
parent bee1dead98
commit 67cbb11dc3
55 changed files with 396 additions and 126 deletions

View File

@ -8,9 +8,6 @@ repositories {
}
dependencies {
// https://mvnrepository.com/artifact/org.apache.groovy/groovy
implementation 'org.apache.groovy:groovy:4.0.7'
// https://mvnrepository.com/artifact/org.apache.groovy/groovy-templates
implementation 'org.apache.groovy:groovy-templates:4.0.7'
@ -19,8 +16,10 @@ dependencies {
// https://archiva.jessebrault.com/#artifact/com.jessebrault.fsm/groovy-extension/0.1.0-SNAPSHOT
implementation 'com.jessebrault.fsm:groovy-extension:0.1.0-SNAPSHOT'
testRuntimeOnly project(':gcp-impl')
}
jar {
archivesBaseName = 'gcp'
archivesBaseName = 'gcp-api'
}

View File

@ -1,4 +1,4 @@
package com.jessebrault.gcp.component
package com.jessebrault.gcp
interface Component {
String render(Map<String, ?> attr, String body)

View File

@ -1,4 +1,4 @@
package com.jessebrault.gcp.component
package com.jessebrault.gcp
interface ComponentFactory {
Component get()

View File

@ -1,4 +1,4 @@
package com.jessebrault.gcp.component
package com.jessebrault.gcp
import org.slf4j.Logger
import org.slf4j.LoggerFactory

View File

@ -1,6 +1,5 @@
package com.jessebrault.gcp
import com.jessebrault.gcp.component.ComponentsContainer
import groovy.text.Template
class GcpTemplate implements Template {

View File

@ -1,7 +1,5 @@
package com.jessebrault.gcp
import com.jessebrault.gcp.component.Component
import com.jessebrault.gcp.component.ComponentsContainer
import groovy.text.Template
import groovy.text.TemplateEngine
import groovy.transform.TupleConstructor
@ -16,6 +14,12 @@ final class GcpTemplateEngine extends TemplateEngine {
private static final Logger logger = LoggerFactory.getLogger(GcpTemplateEngine)
private static GcpToScriptConverter getConverter() {
ServiceLoader.load(GcpToScriptConverter).findFirst().orElseThrow({
new NullPointerException('Could not find an implementation of GcpToScriptConverter')
})
}
@TupleConstructor(defaults = false)
static class Configuration {
Supplier<GcpTemplate> ssgTemplateSupplier
@ -37,7 +41,7 @@ final class GcpTemplateEngine extends TemplateEngine {
Template createTemplate(Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
def templateSrc = reader.text
def converter = new TemplateToScriptConverter()
def converter = getConverter()
def scriptSrc = converter.convert(templateSrc)
logger.debug('scriptSrc: {}', scriptSrc)
def scriptName = "SsgTemplate${ this.templateCount.getAndIncrement() }.groovy"

View File

@ -0,0 +1,5 @@
package com.jessebrault.gcp
interface GcpToScriptConverter {
String convert(String gcpSrc)
}

View File

@ -1,13 +1,12 @@
package com.jessebrault.gcp
import com.jessebrault.gcp.component.Component
import groovy.text.TemplateEngine
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
class GcpTemplateEngineTests {
class GcpTemplateEngineIntegrationTests {
private final TemplateEngine engine = new GcpTemplateEngine(new GcpTemplateEngine.Configuration(
{ new GcpTemplate() },

View File

@ -1,6 +1,6 @@
package components
import com.jessebrault.gcp.component.Component
import com.jessebrault.gcp.Component
class Greeting implements Component {

View File

@ -1,6 +1,6 @@
package components
import com.jessebrault.gcp.component.Component
import com.jessebrault.gcp.Component
class Head implements Component {

25
gcp-impl/build.gradle Normal file
View File

@ -0,0 +1,25 @@
plugins {
id 'ssg.common'
id 'ssg.lib'
}
repositories {
mavenCentral()
}
dependencies {
implementation project(':gcp-api')
// https://mvnrepository.com/artifact/org.apache.groovy/groovy-templates
implementation 'org.apache.groovy:groovy-templates:4.0.7'
// https://archiva.jessebrault.com/#artifact/com.jessebrault.fsm/lib/0.1.0-SNAPSHOT
implementation 'com.jessebrault.fsm:lib:0.1.0-SNAPSHOT'
// https://archiva.jessebrault.com/#artifact/com.jessebrault.fsm/groovy-extension/0.1.0-SNAPSHOT
implementation 'com.jessebrault.fsm:groovy-extension:0.1.0-SNAPSHOT'
}
jar {
archivesBaseName = 'gcp-impl'
}

View File

@ -0,0 +1,80 @@
package com.jessebrault.gcp
import com.jessebrault.gcp.groovy.BlockScriptletParser
import com.jessebrault.gcp.groovy.DollarReferenceParser
import com.jessebrault.gcp.groovy.DollarScriptletParser
import com.jessebrault.gcp.groovy.ExpressionScriptletParser
import com.jessebrault.gcp.node.Document
import com.jessebrault.gcp.node.DollarReference
import com.jessebrault.gcp.node.DollarScriptlet
import com.jessebrault.gcp.node.ExpressionScriptlet
import com.jessebrault.gcp.node.Html
import com.jessebrault.gcp.node.BlockScriptlet
import java.util.regex.Matcher
import java.util.regex.Pattern
class GcpParser {
// private static enum State {
// HTML, DOLLAR_GROOVY, SCRIPTLET, EXPRESSION_SCRIPTLET
// }
//
// private static FunctionFsmBuilder<String, State, String> getFsmBuilder() {
// new FunctionFsmBuilderImpl<>()
// }
private static final Pattern html = ~/^(?:[\w\W&&[^<$]]|<(?![%\p{Lu}]))+/
private static final Pattern groovyIdentifier = ~/^[a-zA-Z_\u0024\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\ufff3][a-zA-Z_\u00240-9\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\ufff3]*/
static Document parse(String gcp) {
}
private static Document document(String gcp) {
def document = new Document()
def remaining = gcp
while (remaining.length() > 0) {
Matcher m
DollarReferenceParser.Result dollarReferenceResult
DollarScriptletParser.Result dollarScriptletResult
BlockScriptletParser.Result blockScriptletResult
ExpressionScriptletParser.Result expressionScriptletResult
String match
if ((m = html.matcher(remaining)).find()) {
match = m.group()
document.children << new Html().tap {
text = match
}
} else if (dollarReferenceResult = DollarReferenceParser.parse(remaining)) {
match = dollarReferenceResult.fullMatch
document.children << new DollarReference().tap {
reference = dollarReferenceResult.reference
}
} else if (dollarScriptletResult = DollarScriptletParser.parseResult(remaining)) {
match = dollarScriptletResult.fullMatch
document.children << new DollarScriptlet().tap {
scriptlet = dollarScriptletResult.scriptlet
}
} else if (blockScriptletResult = BlockScriptletParser.parseResult(remaining)) {
match = blockScriptletResult.fullMatch
document.children << new BlockScriptlet().tap {
scriptlet = blockScriptletResult.scriptlet
}
} else if (expressionScriptletResult = ExpressionScriptletParser.parseResult(remaining)) {
match = expressionScriptletResult.fullMatch
document.children << new ExpressionScriptlet().tap {
scriptlet = expressionScriptletResult.scriptlet
}
}
remaining = remaining.substring(match.length())
}
document
}
}

View File

@ -2,12 +2,28 @@ package com.jessebrault.gcp
import com.jessebrault.fsm.stackfunction.StackFunctionFsmBuilder
import com.jessebrault.fsm.stackfunction.StackFunctionFsmBuilderImpl
import groovy.transform.PackageScope
import com.jessebrault.gcp.util.PatternFunction
import static FsmState.*
class GcpToScriptConverterImpl implements GcpToScriptConverter {
@PackageScope
class TemplateToScriptConverter {
enum State {
HTML,
SCRIPTLET,
EXPRESSION_SCRIPTLET,
DOLLAR,
COMPONENT,
COMPONENT_IDENTIFIER,
COMPONENT_ATTR_KEY,
COMPONENT_ATTR_VALUE_OPEN,
COMPONENT_ATTR_VALUE_STRING,
COMPONENT_ATTR_VALUE_STRING_CLOSE,
COMPONENT_CLOSE
}
private static final PatternFunction html = new PatternFunction(~/^(?:[\w\W&&[^<$]]|<(?!%|\p{Lu})|\$(?!\{))+/)
private static final PatternFunction scriptletOpen = new PatternFunction(~/^<%(?!=)/)
@ -25,11 +41,11 @@ class TemplateToScriptConverter {
private static final PatternFunction attrValueStringContents = new PatternFunction(~/^(?:[\w\W&&[^\\"]]|\\\\|\\")*(?=")/)
private static final PatternFunction attrValueStringClose = new PatternFunction(~/["']/)
private static StackFunctionFsmBuilder<String, FsmState, String> getFsmBuilder() {
private static StackFunctionFsmBuilder<String, State, String> getFsmBuilder() {
new StackFunctionFsmBuilderImpl<>()
}
@SuppressWarnings('GrMethodMayBeStatic')
@Override
String convert(String src) {
def b = new StringBuilder()
def stringAcc = new StringBuilder()
@ -37,22 +53,22 @@ class TemplateToScriptConverter {
b << 'def getTemplate() {\nreturn { out ->\n'
def fsm = getFsmBuilder().with {
initialState = HTML
initialState = State.HTML
whileIn(HTML) {
whileIn(State.HTML) {
on html exec {
stringAcc << it
}
on scriptletOpen shiftTo SCRIPTLET exec {
on scriptletOpen shiftTo State.SCRIPTLET exec {
if (stringAcc.length() > 0) {
b << 'out << """' << stringAcc.toString() << '""";\n'
stringAcc = new StringBuilder()
}
}
on expressionScriptletOpen shiftTo EXPRESSION_SCRIPTLET exec {
on expressionScriptletOpen shiftTo State.EXPRESSION_SCRIPTLET exec {
stringAcc << '${'
}
on componentOpen shiftTo COMPONENT_IDENTIFIER exec {
on componentOpen shiftTo State.COMPONENT_IDENTIFIER exec {
if (stringAcc.length() > 0) {
b << 'out << """' << stringAcc.toString() << '""";\n'
stringAcc = new StringBuilder()
@ -60,7 +76,7 @@ class TemplateToScriptConverter {
}
}
whileIn(SCRIPTLET) {
whileIn(State.SCRIPTLET) {
on scriptletText exec {
b << it
}
@ -69,7 +85,7 @@ class TemplateToScriptConverter {
}
}
whileIn(EXPRESSION_SCRIPTLET) {
whileIn(State.EXPRESSION_SCRIPTLET) {
on scriptletText exec {
stringAcc << it
}
@ -78,7 +94,7 @@ class TemplateToScriptConverter {
}
}
whileIn(COMPONENT) {
whileIn(State.COMPONENT) {
// tokenize component, figure out body, and tokenize closing component
}

View File

@ -1,16 +1,17 @@
package com.jessebrault.gcp.component
import com.jessebrault.gcp.component.ComponentToken.Type
import com.jessebrault.gcp.component.node.ComponentNode
import com.jessebrault.gcp.component.node.ComponentRoot
import com.jessebrault.gcp.component.node.DollarReferenceValue
import com.jessebrault.gcp.component.node.DollarScriptletValue
import com.jessebrault.gcp.component.node.ExpressionScriptletValue
import com.jessebrault.gcp.component.node.GStringValue
import com.jessebrault.gcp.component.node.KeyAndValue
import com.jessebrault.gcp.component.node.KeysAndValues
import com.jessebrault.gcp.component.node.Node
import com.jessebrault.gcp.component.node.ComponentNode
import com.jessebrault.gcp.component.node.ScriptletValue
import com.jessebrault.gcp.component.node.StringValue
import com.jessebrault.gcp.util.PeekBefore
import groovy.transform.PackageScope
import static com.jessebrault.gcp.component.ComponentToken.Type.*
@ -24,7 +25,7 @@ class ComponentParser {
private Queue<ComponentToken> tokens
private String currentIdentifier
ComponentNode parse(Queue<ComponentToken> tokens) {
ComponentRoot parse(Queue<ComponentToken> tokens) {
this.tokens = tokens
this.selfClosingComponent()
}
@ -72,22 +73,22 @@ class ComponentParser {
t && t.type == type
}
private ComponentNode selfClosingComponent() {
private ComponentRoot selfClosingComponent() {
this.startOfOpeningOrSelfClosingComponent()
def keysAndValues = this.keysAndValues()
this.expect(FORWARD_SLASH)
this.expect(GT)
new ComponentNode().tap {
new ComponentRoot().tap {
it.identifier = this.currentIdentifier
it.children << keysAndValues
}
}
private ComponentNode openingComponent() {
private ComponentRoot openingComponent() {
this.startOfOpeningOrSelfClosingComponent()
def keysAndValues = this.keysAndValues()
this.expect(GT)
new ComponentNode().tap {
new ComponentRoot().tap {
it.identifier = this.currentIdentifier
it.children << keysAndValues
}
@ -110,7 +111,7 @@ class ComponentParser {
}
private KeysAndValues keysAndValues() {
List<Node> children = []
List<ComponentNode> children = []
while (true) {
if (this.peek(KEY)) {
def keyAndValue = this.keyAndValue()
@ -137,7 +138,7 @@ class ComponentParser {
}
}
private Node value() {
private ComponentNode value() {
if (this.peek(DOUBLE_QUOTE)) {
return this.doubleQuoteStringValue()
} else if (this.peek(SINGLE_QUOTE)) {

View File

@ -1,83 +1,77 @@
package com.jessebrault.gcp.component
import com.jessebrault.gcp.component.node.BooleanValue
import com.jessebrault.gcp.component.node.ComponentNode
import com.jessebrault.gcp.component.node.ComponentRoot
import com.jessebrault.gcp.component.node.DollarReferenceValue
import com.jessebrault.gcp.component.node.DollarScriptletValue
import com.jessebrault.gcp.component.node.ExpressionScriptletValue
import com.jessebrault.gcp.component.node.GStringValue
import com.jessebrault.gcp.component.node.KeyAndValue
import com.jessebrault.gcp.component.node.KeysAndValues
import com.jessebrault.gcp.component.node.NodeVisitor
import com.jessebrault.gcp.component.node.ComponentNodeVisitor
import com.jessebrault.gcp.component.node.ScriptletValue
import com.jessebrault.gcp.component.node.StringValue
// NOT THREAD SAFE, and must be used exactly once
class ComponentToScriptVisitor extends NodeVisitor {
// NOT THREAD SAFE
class ComponentToClosureVisitor extends ComponentNodeVisitor {
private final Writer w = new StringWriter()
private final IndentPrinter p = new IndentPrinter(this.w, ' ', true, true)
private StringBuilder b = new StringBuilder()
String getResult() {
w.toString()
b.toString()
}
void visit(ComponentNode componentNode) {
p.println('{ attr, bodyOut ->')
p.incrementIndent()
void reset() {
b = new StringBuilder()
}
void visit(ComponentRoot componentNode) {
b << '{ '
super.visit(componentNode)
if (componentNode.body != null) {
p.println("bodyOut << ${ componentNode.body };")
b << "bodyOut << ${ componentNode.body }; "
}
p.decrementIndent()
p.println('};')
b << '};'
}
void visit(KeysAndValues keysAndValues) {
p.println('attr {')
p.incrementIndent()
b << 'attr { '
super.visit(keysAndValues)
p.decrementIndent()
p.println('};')
b << '}; '
}
void visit(KeyAndValue keyAndValue) {
p.printIndent()
p.print("${ keyAndValue.key } = ")
b << "${ keyAndValue.key } = "
super.visit(keyAndValue)
p.print(';\n')
b << '; '
}
void visit(GStringValue gStringValue) {
p.print("\"${ gStringValue.gString }\"")
b << "\"${ gStringValue.gString }\""
}
void visit(StringValue stringValue) {
p.print("'${ stringValue.string }'")
b << "'${ stringValue.string }'"
}
void visit(DollarReferenceValue dollarReferenceValue) {
p.print(dollarReferenceValue.reference)
b << dollarReferenceValue.reference
}
void visit(DollarScriptletValue dollarScriptletValue) {
p.print(dollarScriptletValue.scriptlet)
b << dollarScriptletValue.scriptlet
}
void visit(ScriptletValue scriptletValue) {
p.println("render { out ->")
p.incrementIndent()
p.print(scriptletValue.scriptlet)
p.decrementIndent()
p.println('}')
b << "render { out -> ${ scriptletValue.scriptlet } }"
}
void visit(ExpressionScriptletValue expressionScriptletValue) {
p.print(expressionScriptletValue.scriptlet)
b << expressionScriptletValue.scriptlet
}
void visit(BooleanValue booleanValue) {
p.print(booleanValue.value.toString())
b << booleanValue.value.toString()
}
}

View File

@ -2,7 +2,8 @@ package com.jessebrault.gcp.component
import com.jessebrault.fsm.function.FunctionFsmBuilder
import com.jessebrault.fsm.function.FunctionFsmBuilderImpl
import com.jessebrault.gcp.PatternFunction
import com.jessebrault.gcp.groovy.DollarScriptletParser
import com.jessebrault.gcp.util.PatternFunction
import static ComponentToken.Type
@ -89,7 +90,7 @@ class ComponentTokenizer {
tokens << new ComponentToken(Type.GROOVY_IDENTIFIER, s.substring(1)) // skip opening $
}
//noinspection GroovyAssignabilityCheck // for some reason IntelliJ is confused by this
on DollarGroovyParser::parse exec { String s ->
on DollarScriptletParser::parse exec { String s ->
tokens << new ComponentToken(Type.GROOVY, s.substring(2, s.length() - 1))
}
on percent shiftTo State.EXPRESSION_SCRIPTLET_GROOVY exec {

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class BooleanValue extends Node {
class BooleanValue extends ComponentNode {
boolean value
@Override

View File

@ -0,0 +1,5 @@
package com.jessebrault.gcp.component.node
abstract class ComponentNode {
List<ComponentNode> children = []
}

View File

@ -1,12 +1,12 @@
package com.jessebrault.gcp.component.node
abstract class NodeVisitor {
abstract class ComponentNodeVisitor {
void visit(Node node) {
void visit(ComponentNode node) {
this.visitChildren(node)
}
void visitChildren(Node node) {
void visitChildren(ComponentNode node) {
node.children.each {
this.visit(it)
}

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class ComponentNode extends Node {
class ComponentRoot extends ComponentNode {
String identifier
String body

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class DollarReferenceValue extends Node {
class DollarReferenceValue extends ComponentNode {
String reference

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class DollarScriptletValue extends Node {
class DollarScriptletValue extends ComponentNode {
String scriptlet

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class ExpressionScriptletValue extends Node {
class ExpressionScriptletValue extends ComponentNode {
String scriptlet

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class GStringValue extends Node {
class GStringValue extends ComponentNode {
String gString

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class KeyAndValue extends Node {
class KeyAndValue extends ComponentNode {
String key

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class KeysAndValues extends Node {
class KeysAndValues extends ComponentNode {
@Override
String toString() {

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class ScriptletValue extends Node {
class ScriptletValue extends ComponentNode {
String scriptlet

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node
class StringValue extends Node {
class StringValue extends ComponentNode {
String string

View File

@ -0,0 +1,22 @@
package com.jessebrault.gcp.groovy
class BlockScriptletParser {
static class Result {
String fullMatch
String scriptlet
}
static String parse(String input) {
}
static Result parseResult(String input) {
def match = parse(input)
match != null ? new Result().tap {
fullMatch = match
scriptlet = fullMatch.substring(2, fullMatch.length() - 2)
} : null
}
}

View File

@ -0,0 +1,14 @@
package com.jessebrault.gcp.groovy
class DollarReferenceParser {
static class Result {
String fullMatch
String reference
}
static Result parse(String input) {
}
}

View File

@ -1,11 +1,16 @@
package com.jessebrault.gcp.component
package com.jessebrault.gcp.groovy
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class DollarGroovyParser {
class DollarScriptletParser {
private static Logger logger = LoggerFactory.getLogger(DollarGroovyParser)
private static Logger logger = LoggerFactory.getLogger(DollarScriptletParser)
static class Result {
String fullMatch
String scriptlet
}
private enum State {
NO_STRING, G_STRING, SINGLE_QUOTE_STRING
@ -126,4 +131,16 @@ class DollarGroovyParser {
acc.toString()
}
static Result parseResult(String input) {
def match = parse(input)
if (match) {
new Result().tap {
fullMatch = match
scriptlet = fullMatch.substring(2, fullMatch.length() - 1)
}
} else {
null
}
}
}

View File

@ -0,0 +1,22 @@
package com.jessebrault.gcp.groovy
class ExpressionScriptletParser {
static class Result {
String fullMatch
String scriptlet
}
static String parse(String input) {
}
static Result parseResult(String input) {
def match = parse(input)
match != null ? new Result().tap {
fullMatch = match
scriptlet = fullMatch.substring(3, fullMatch.length() - 2)
} : null
}
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.gcp.node
class BlockScriptlet extends GcpNode {
String scriptlet
}

View File

@ -0,0 +1,6 @@
package com.jessebrault.gcp.node
class ComponentInstance extends GcpNode {
String opening
String closing
}

View File

@ -0,0 +1,3 @@
package com.jessebrault.gcp.node
class Document extends GcpNode {}

View File

@ -0,0 +1,5 @@
package com.jessebrault.gcp.node
class DollarReference extends GcpNode {
String reference
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.gcp.node
class DollarScriptlet extends GcpNode {
String scriptlet
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.gcp.node
class ExpressionScriptlet extends GcpNode {
String scriptlet
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.gcp.node
abstract class GcpNode {
List<GcpNode> children = []
}

View File

@ -0,0 +1,13 @@
package com.jessebrault.gcp.node
abstract class GcpNodeVisitor {
void visit(GcpNode node) {
this.visitChildren(node)
}
void visitChildren(GcpNode node) {
node.children.each(this.&visit)
}
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.gcp.node
class Html extends GcpNode {
String text
}

View File

@ -1,4 +1,4 @@
package com.jessebrault.gcp
package com.jessebrault.gcp.util
import groovy.transform.PackageScope
import groovy.transform.TupleConstructor

View File

@ -1,5 +1,6 @@
package com.jessebrault.gcp.component
package com.jessebrault.gcp.util
import com.jessebrault.gcp.component.ComponentToken
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy

View File

@ -0,0 +1 @@
com.jessebrault.gcp.GcpToScriptConverterImpl

View File

@ -9,6 +9,7 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static com.jessebrault.gcp.component.ComponentToken.Type.*
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue
@ -16,7 +17,7 @@ class ComponentParserTests {
private static final Logger logger = LoggerFactory.getLogger(ComponentParserTests)
private static class NodeSpec<T extends Node> {
private static class NodeSpec<T extends ComponentNode> {
Class<T> nodeClass
Closure<Void> tests
@ -31,7 +32,7 @@ class ComponentParserTests {
this.tests = tests
}
void test(Node actual) {
void test(ComponentNode actual) {
logger.debug('actual: {}', actual)
assertTrue(nodeClass.isAssignableFrom(actual.class))
if (this.tests != null) {
@ -60,9 +61,9 @@ class ComponentParserTests {
private static class NodeTester {
List<NodeSpec<? extends Node>> childSpecs = []
List<NodeSpec<? extends ComponentNode>> childSpecs = []
def <T extends Node> void expect(
def <T extends ComponentNode> void expect(
Class<T> childNodeClass,
@DelegatesTo(value = NodeTester, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(FirstParam.FirstGenericType)
@ -88,7 +89,7 @@ class ComponentParserTests {
def componentNode = this.parser.parse(tokens)
logger.debug('componentNode: {}', componentNode)
def componentSpec = new NodeSpec(ComponentNode, tests)
def componentSpec = new NodeSpec(ComponentRoot, tests)
logger.debug('nodeSpec: {}', componentSpec)
componentSpec.test(componentNode)
}

View File

@ -0,0 +1,40 @@
package com.jessebrault.gcp.component
import com.jessebrault.gcp.component.node.ComponentRoot
import com.jessebrault.gcp.component.node.GStringValue
import com.jessebrault.gcp.component.node.KeyAndValue
import com.jessebrault.gcp.component.node.KeysAndValues
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
class ComponentToClosureVisitorTests {
@Test
void withEmptyKeysAndValues() {
def cn = new ComponentRoot().tap {
it.children << new KeysAndValues()
}
def v = new ComponentToClosureVisitor()
v.visit(cn)
assertEquals('{ attr { }; };', v.result)
}
@Test
void withGStringKeyAndValue() {
def cn = new ComponentRoot().tap {
it.children << new KeysAndValues().tap {
it.children << new KeyAndValue().tap {
key = 'greeting'
it.children << new GStringValue().tap {
gString = 'Hello, ${ frontMatter.person }!'
}
}
}
}
def v = new ComponentToClosureVisitor()
v.visit(cn)
assertEquals('{ attr { greeting = "Hello, ${ frontMatter.person }!"; }; };', v.result)
}
}

View File

@ -1,11 +1,11 @@
package com.jessebrault.gcp.component
package com.jessebrault.gcp.groovy
import org.junit.jupiter.api.Test
import static com.jessebrault.gcp.component.DollarGroovyParser.parse
import static com.jessebrault.gcp.groovy.DollarScriptletParser.parse
import static org.junit.jupiter.api.Assertions.assertEquals
class DollarGroovyParserTests {
class DollarScriptletParserTests {
@Test
void empty() {

View File

@ -1,23 +0,0 @@
package com.jessebrault.gcp
import groovy.transform.PackageScope
@PackageScope
enum FsmState {
HTML,
SCRIPTLET,
EXPRESSION_SCRIPTLET,
DOLLAR,
COMPONENT,
COMPONENT_IDENTIFIER,
COMPONENT_ATTR_KEY,
COMPONENT_ATTR_VALUE_OPEN,
COMPONENT_ATTR_VALUE_STRING,
COMPONENT_ATTR_VALUE_STRING_CLOSE,
COMPONENT_CLOSE
}

View File

@ -1,5 +0,0 @@
package com.jessebrault.gcp.component.node
abstract class Node {
List<Node> children = []
}

View File

@ -1,2 +1,2 @@
rootProject.name = 'ssg'
include 'cli', 'gcp', 'lib'
include 'cli', 'gcp-api', 'gcp-impl', 'lib'