component node

This commit is contained in:
JesseBrault0709 2023-01-22 12:51:54 -06:00
parent 5d4df7662a
commit a4bfea92ef
30 changed files with 606 additions and 274 deletions

View File

@ -1,5 +0,0 @@
package com.jessebrault.ssg.template.gspe
interface ComponentFactory {
Component get()
}

View File

@ -1,256 +0,0 @@
package com.jessebrault.ssg.template.gspe
import groovy.transform.PackageScope
import static com.jessebrault.ssg.template.gspe.ComponentToken.Type.*
/**
* NOT thread safe
*/
@PackageScope
class ComponentParser {
private Queue<ComponentToken> tokens
private StringBuilder b
private String identifier
String parse(List<ComponentToken> tokens) {
this.b = new StringBuilder()
this.tokens = new LinkedList<>(tokens)
this.selfClosingComponent()
this.b.toString()
}
String parse(List<ComponentToken> openingTokens, String bodyClosure, List<ComponentToken> closingTokens) {
this.b = new StringBuilder()
this.tokens = new LinkedList<>(openingTokens)
this.openingComponent()
this.b << "bodyOut << ${ bodyClosure };"
this.tokens = new LinkedList<>(closingTokens)
this.closingComponent()
this.b.toString()
}
private static void error(Collection<ComponentToken.Type> expectedTypes, ComponentToken actual) {
throw new RuntimeException("expected ${ expectedTypes.join(' or ') } but got ${ actual ? "'${ actual }'" : 'null' }")
}
private void selfClosingComponent() {
this.startOfOpeningOrSelfClosingComponent()
this.keysAndValues()
def t0 = this.tokens.poll()
if (!t0 || t0.type != FORWARD_SLASH) {
error([FORWARD_SLASH], t0)
} else {
def t1 = tokens.poll()
if (!t1 || t1.type != GT) {
error([GT], t1)
} else {
this.b << '};'
}
}
}
private void openingComponent() {
this.startOfOpeningOrSelfClosingComponent()
this.keysAndValues()
def t0 = tokens.poll()
if (!t0 || t0.type != GT) {
error([GT], t0)
}
}
private void closingComponent() {
def t0 = this.tokens.poll()
if (!t0 || t0.type != LT) {
error([LT], t0)
} else {
def t1 = this.tokens.poll()
if (!t1 || t1.type != FORWARD_SLASH) {
error([FORWARD_SLASH], t1)
} else {
def t2 = this.tokens.poll()
if (!t2 || t2.type != IDENTIFIER) {
error([IDENTIFIER], t2)
} else if (t2.text != identifier) {
throw new RuntimeException("expected '${ this.identifier }' but got '${ t2.text }'")
} else {
def t3 = this.tokens.poll()
if (!t3 || t3.type != GT) {
error([GT], t3)
} else {
this.b << '};'
}
}
}
}
}
private void startOfOpeningOrSelfClosingComponent() {
def t0 = this.tokens.poll()
if (!t0 || t0.type != LT) {
error([LT], t0)
} else {
def t1 = this.tokens.poll()
if (!t1 || t1.type != IDENTIFIER) {
error([IDENTIFIER], t1)
} else {
this.identifier = t1.text
this.b << "renderComponent('${ this.identifier }') { attr, bodyOut ->\n"
}
}
}
private void keysAndValues() {
while (true) {
def t0 = this.tokens.peek()
if (!t0 || !t0.type.isAnyOf([KEY, FORWARD_SLASH])) {
error([KEY, FORWARD_SLASH], t0)
} else if (t0.type == KEY) {
this.keyAndValue()
} else if (t0.type == FORWARD_SLASH) {
break
}
}
}
@PeekBefore(KEY)
private void keyAndValue() {
def t0 = this.tokens.remove()
if (t0.type != KEY) {
throw new RuntimeException('programmer error')
} else {
def t1 = this.tokens.poll()
if (!t1 || t1.type != EQUALS) {
error([EQUALS], t1)
} else {
this.b << "attr['${ t0.text }'] = "
this.value()
}
}
}
private void value() {
def t0 = this.tokens.peek()
if (!t0 || !t0.type.isAnyOf([DOUBLE_QUOTE, SINGLE_QUOTE, DOLLAR, LT])) {
error([DOUBLE_QUOTE, SINGLE_QUOTE, DOLLAR, LT], t0)
} else if (t0.type == DOUBLE_QUOTE) {
this.doubleQuoteStringValue()
} else if (t0.type == SINGLE_QUOTE) {
this.singleQuoteStringValue()
} else if (t0.type == DOLLAR) {
this.dollarValue()
} else if (t0.type == LT) {
this.scriptletValue()
}
}
@PeekBefore(DOUBLE_QUOTE)
private void doubleQuoteStringValue() {
def t0 = this.tokens.remove()
if (t0.type != DOUBLE_QUOTE) {
throw new RuntimeException('programmer error')
} else {
def t1 = this.tokens.poll()
if (!t1 || t1.type != STRING) {
error([STRING], t1)
} else {
def t2 = this.tokens.poll()
if (!t2 || t2.type != DOUBLE_QUOTE) {
error([DOUBLE_QUOTE], t2)
} else {
this.b << /"${ t1.text }";/ + '\n'
}
}
}
}
@PeekBefore(SINGLE_QUOTE)
private void singleQuoteStringValue() {
def t0 = this.tokens.remove()
if (t0.type != SINGLE_QUOTE) {
throw new RuntimeException('programmer error')
} else {
def t1 = tokens.poll()
if (!t1 || t1.type != STRING) {
error([STRING], t1)
} else {
def t2 = this.tokens.poll()
if (!t2 || t2.type != SINGLE_QUOTE) {
error([SINGLE_QUOTE], t2)
} else {
this.b << /'${ t1.text }';/ + '\n'
}
}
}
}
@PeekBefore(DOLLAR)
private void dollarValue() {
def t0 = this.tokens.remove()
if (t0.type != DOLLAR) {
throw new RuntimeException('programmer error')
} else {
def t1 = this.tokens.poll()
if (!t1 || t1.type != CURLY_OPEN) {
error([CURLY_OPEN], t1)
} else {
def t2 = this.tokens.poll()
if (!t2 || t2.type != GROOVY) {
error([GROOVY], t2)
} else {
def t3 = this.tokens.poll()
if (!t3 || t3.type != CURLY_CLOSE) {
error([CURLY_CLOSE], t3)
} else {
this.b << "${ t2.text };\n"
}
}
}
}
}
@PeekBefore(LT)
private void scriptletValue() {
def t0 = this.tokens.remove()
if (t0.type != LT) {
throw new RuntimeException('programmer error')
} else {
def t1 = this.tokens.poll()
if (!t1 || t1.type != PERCENT) {
error([PERCENT], t1)
} else {
def t2 = this.tokens.poll()
if (!t2 || t2.type != EQUALS) {
error([EQUALS], t2)
} else {
def t3 = this.tokens.poll()
if (!t3 || t3.type != GROOVY) {
error([GROOVY], t3)
} else {
def t4 = this.tokens.poll()
if (!t4.type || t4.type != PERCENT) {
error([PERCENT], t4)
} else {
def t5 = this.tokens.poll()
if (!t5 || t5.type != GT) {
error([GT], t5)
} else {
this.b << "${ t3.text };\n"
}
}
}
}
}
}
}
}

View File

@ -1,5 +1,6 @@
package com.jessebrault.ssg.template.gspe
import com.jessebrault.ssg.template.gspe.component.ComponentsContainer
import groovy.text.Template
class GspeTemplate implements Template {

View File

@ -1,6 +1,7 @@
package com.jessebrault.ssg.template.gspe
import com.jessebrault.ssg.template.gspe.component.Component
import com.jessebrault.ssg.template.gspe.component.ComponentsContainer
import groovy.text.Template
import groovy.text.TemplateEngine
import groovy.transform.TupleConstructor

View File

@ -6,7 +6,6 @@ import groovy.transform.TupleConstructor
import java.util.function.Function
import java.util.regex.Pattern
@PackageScope
@TupleConstructor(includeFields = true, defaults = false)
class PatternFunction implements Function<String, String> {

View File

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

View File

@ -0,0 +1,5 @@
package com.jessebrault.ssg.template.gspe.component
interface ComponentFactory {
Component get()
}

View File

@ -0,0 +1,224 @@
package com.jessebrault.ssg.template.gspe.component
import com.jessebrault.ssg.template.gspe.component.ComponentToken.Type
import com.jessebrault.ssg.template.gspe.component.node.ComponentNode
import com.jessebrault.ssg.template.gspe.component.node.DollarReferenceValue
import com.jessebrault.ssg.template.gspe.component.node.DollarScriptletValue
import com.jessebrault.ssg.template.gspe.component.node.ExpressionScriptletValue
import com.jessebrault.ssg.template.gspe.component.node.GStringValue
import com.jessebrault.ssg.template.gspe.component.node.KeyAndValue
import com.jessebrault.ssg.template.gspe.component.node.KeysAndValues
import com.jessebrault.ssg.template.gspe.component.node.Node
import com.jessebrault.ssg.template.gspe.component.node.ScriptletValue
import com.jessebrault.ssg.template.gspe.component.node.StringValue
import groovy.transform.PackageScope
import static com.jessebrault.ssg.template.gspe.component.ComponentToken.Type.*
/**
* NOT thread safe
*/
@PackageScope
class ComponentParser {
private Queue<ComponentToken> tokens
private String currentIdentifier
ComponentNode parse(Queue<ComponentToken> tokens) {
this.tokens = tokens
this.selfClosingComponent()
}
String parse(Queue<ComponentToken> openingTokens, String bodyClosure, Queue<ComponentToken> closingTokens) {
this.tokens = openingTokens
def componentNode = this.openingComponent()
componentNode.body = bodyClosure
this.tokens = closingTokens
this.closingComponent()
componentNode
}
private static void error(Collection<Type> expectedTypes, ComponentToken actual) {
throw new RuntimeException("expected ${ expectedTypes.join(' or ') } but got ${ actual ? "'${ actual }'" : 'null' }")
}
private ComponentToken expect(Collection<Type> types) {
def t = this.tokens.poll()
if (!t || !t.type.isAnyOf(types)) {
error(types, t)
}
t
}
private ComponentToken expect(Type type) {
this.expect([type])
}
private boolean peek(Type type) {
def t = this.tokens.peek()
t && t.type == type
}
private boolean peekSecond(Type type) {
def t = this.tokens[1]
t && t.type == type
}
private boolean peekThird(Type type) {
def t = this.tokens[2]
t && t.type == type
}
private ComponentNode selfClosingComponent() {
this.startOfOpeningOrSelfClosingComponent()
def keysAndValues = this.keysAndValues()
this.expect(FORWARD_SLASH)
this.expect(GT)
new ComponentNode().tap {
it.identifier = this.currentIdentifier
it.children << keysAndValues
}
}
private ComponentNode openingComponent() {
this.startOfOpeningOrSelfClosingComponent()
def keysAndValues = this.keysAndValues()
this.expect(GT)
new ComponentNode().tap {
it.identifier = this.currentIdentifier
it.children << keysAndValues
}
}
private void closingComponent() {
this.expect(LT)
this.expect(FORWARD_SLASH)
def identifierToken = this.expect(IDENTIFIER)
if (identifierToken.text != this.currentIdentifier) {
throw new RuntimeException("expected '${ this.currentIdentifier }' but got '${ t2.text }'")
}
this.expect(GT)
}
private void startOfOpeningOrSelfClosingComponent() {
this.expect(LT)
def identifierToken = this.expect(IDENTIFIER)
this.currentIdentifier = identifierToken.text
}
private KeysAndValues keysAndValues() {
List<Node> children = []
while (true) {
if (this.peek(KEY)) {
def keyAndValue = this.keyAndValue()
children << keyAndValue
} else if (this.peek(FORWARD_SLASH)) {
break
} else {
error([KEY, FORWARD_SLASH], this.tokens.poll())
}
}
new KeysAndValues().tap {
it.children.addAll(children)
}
}
@PeekBefore(KEY)
private KeyAndValue keyAndValue() {
def keyToken = this.expect(KEY)
this.expect(EQUALS)
def value = this.value()
new KeyAndValue().tap {
key = keyToken.text
it.children << value
}
}
private Node value() {
if (this.peek(DOUBLE_QUOTE)) {
return this.doubleQuoteStringValue()
} else if (this.peek(SINGLE_QUOTE)) {
return this.singleQuoteStringValue()
} else if (this.peek(DOLLAR) && this.peekSecond(GROOVY_IDENTIFIER)) {
return this.dollarReferenceValue()
} else if (this.peek(DOLLAR) && this.peekSecond(CURLY_OPEN)) {
return this.dollarScriptletValue()
} else if (this.peek(LT) && this.peekSecond(PERCENT) && this.peekThird(EQUALS)) {
return this.expressionScriptletValue()
} else if (this.peek(LT) && this.peekSecond(PERCENT)) {
return this.scriptletValue()
} else {
error([DOUBLE_QUOTE, SINGLE_QUOTE, DOLLAR, LT], this.tokens.poll())
}
throw new RuntimeException('should not get here')
}
@PeekBefore(DOUBLE_QUOTE)
private GStringValue doubleQuoteStringValue() {
this.expect(DOUBLE_QUOTE)
def stringToken = this.expect(STRING)
this.expect(DOUBLE_QUOTE)
new GStringValue().tap {
gString = stringToken.text
}
}
@PeekBefore(SINGLE_QUOTE)
private StringValue singleQuoteStringValue() {
this.expect(SINGLE_QUOTE)
def stringToken = this.expect(STRING)
this.expect(SINGLE_QUOTE)
new StringValue().tap {
string = stringToken.text
}
}
@PeekBefore([DOLLAR, GROOVY_IDENTIFIER])
private DollarReferenceValue dollarReferenceValue() {
this.expect(DOLLAR)
def groovyIdentifierToken = this.expect(GROOVY_IDENTIFIER)
new DollarReferenceValue().tap {
reference = groovyIdentifierToken.text
}
}
@PeekBefore([DOLLAR, CURLY_OPEN])
private DollarScriptletValue dollarScriptletValue() {
this.expect(DOLLAR)
this.expect(CURLY_OPEN)
def groovyToken = this.expect(GROOVY)
this.expect(CURLY_CLOSE)
new DollarScriptletValue().tap {
scriptlet = groovyToken.text
}
}
@PeekBefore([LT, PERCENT, EQUALS])
private ExpressionScriptletValue expressionScriptletValue() {
this.expect(LT)
this.expect(PERCENT)
this.expect(EQUALS)
def groovyToken = this.expect(GROOVY)
this.expect(PERCENT)
this.expect(GT)
new ExpressionScriptletValue().tap {
scriptlet = groovyToken.text
}
}
@PeekBefore([LT, PERCENT])
private ScriptletValue scriptletValue() {
this.expect(LT)
this.expect(PERCENT)
def groovyToken = this.expect(GROOVY)
this.expect(PERCENT)
this.expect(GT)
new ScriptletValue().tap {
scriptlet = groovyToken.text
}
}
}

View File

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

View File

@ -1,8 +1,8 @@
package com.jessebrault.ssg.template.gspe
package com.jessebrault.ssg.template.gspe.component
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@TupleConstructor
class ComponentToken {
enum Type {

View File

@ -1,7 +1,8 @@
package com.jessebrault.ssg.template.gspe
package com.jessebrault.ssg.template.gspe.component
import com.jessebrault.fsm.function.FunctionFsmBuilder
import com.jessebrault.fsm.function.FunctionFsmBuilderImpl
import com.jessebrault.ssg.template.gspe.PatternFunction
import static ComponentToken.Type

View File

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

View File

@ -1,4 +1,4 @@
package com.jessebrault.ssg.template.gspe
package com.jessebrault.ssg.template.gspe.component
import java.lang.annotation.Retention

View File

@ -0,0 +1,11 @@
package com.jessebrault.ssg.template.gspe.component.node
class BooleanValue extends Node {
boolean value
@Override
String toString() {
"BooleanValue(${ this.value })"
}
}

View File

@ -0,0 +1,13 @@
package com.jessebrault.ssg.template.gspe.component.node
class ComponentNode extends Node {
String identifier
String body
@Override
String toString() {
"ComponentNode(identifier: ${ this.identifier }, body: ${ this.body }, children: ${ this.children })"
}
}

View File

@ -0,0 +1,12 @@
package com.jessebrault.ssg.template.gspe.component.node
class DollarReferenceValue extends Node {
String reference
@Override
String toString() {
"DollarReferenceValue(${ this.reference })"
}
}

View File

@ -0,0 +1,12 @@
package com.jessebrault.ssg.template.gspe.component.node
class DollarScriptletValue extends Node {
String scriptlet
@Override
String toString() {
"DollarScriptletValue(${ this.scriptlet })"
}
}

View File

@ -0,0 +1,12 @@
package com.jessebrault.ssg.template.gspe.component.node
class ExpressionScriptletValue extends Node {
String scriptlet
@Override
String toString() {
"ExpressionScriptletValue(${ this.scriptlet })"
}
}

View File

@ -0,0 +1,12 @@
package com.jessebrault.ssg.template.gspe.component.node
class GStringValue extends Node {
String gString
@Override
String toString() {
"GStringValue(${ this.gString })"
}
}

View File

@ -0,0 +1,12 @@
package com.jessebrault.ssg.template.gspe.component.node
class KeyAndValue extends Node {
String key
@Override
String toString() {
"KeyAndValue(key: ${ this.key }, children: ${ this.children })"
}
}

View File

@ -0,0 +1,10 @@
package com.jessebrault.ssg.template.gspe.component.node
class KeysAndValues extends Node {
@Override
String toString() {
"KeysAndValues(${ this.children })"
}
}

View File

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

View File

@ -0,0 +1,15 @@
package com.jessebrault.ssg.template.gspe.component.node
abstract class NodeVisitor {
void visit(Node node) {
this.visitChildren(node)
}
void visitChildren(Node node) {
node.children.each {
this.visit(it)
}
}
}

View File

@ -0,0 +1,12 @@
package com.jessebrault.ssg.template.gspe.component.node
class ScriptletValue extends Node {
String scriptlet
@Override
String toString() {
"ScriptletValue(${ this.scriptlet })"
}
}

View File

@ -0,0 +1,12 @@
package com.jessebrault.ssg.template.gspe.component.node
class StringValue extends Node {
String string
@Override
String toString() {
"StringValue(${ this.string })"
}
}

View File

@ -1,6 +1,6 @@
package com.jessebrault.ssg.template.gspe
import com.jessebrault.ssg.template.gspe.component.Component
import groovy.text.TemplateEngine
import org.junit.jupiter.api.Test

View File

@ -0,0 +1,140 @@
package com.jessebrault.ssg.template.gspe.component
import com.jessebrault.ssg.template.gspe.component.node.ComponentNode
import com.jessebrault.ssg.template.gspe.component.node.GStringValue
import com.jessebrault.ssg.template.gspe.component.node.KeyAndValue
import com.jessebrault.ssg.template.gspe.component.node.KeysAndValues
import com.jessebrault.ssg.template.gspe.component.node.Node
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.FirstParam
import groovy.transform.stc.SimpleType
import org.junit.jupiter.api.Test
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static com.jessebrault.ssg.template.gspe.component.ComponentToken.Type.*
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue
class ComponentParserTests {
private static final Logger logger = LoggerFactory.getLogger(ComponentParserTests)
private static class NodeSpec<T extends Node> {
Class<T> nodeClass
Closure<Void> tests
NodeSpec(
Class<T> nodeClass,
@DelegatesTo(value = NodeTester, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(FirstParam.FirstGenericType)
Closure<Void> tests = null
) {
this.nodeClass = Objects.requireNonNull(nodeClass)
this.tests = tests
}
void test(Node actual) {
logger.debug('actual: {}', actual)
assertTrue(nodeClass.isAssignableFrom(actual.class))
if (this.tests != null) {
def nodeTester = new NodeTester()
this.tests.setDelegate(nodeTester)
this.tests.setResolveStrategy(Closure.DELEGATE_FIRST)
this.tests(actual)
def childIterator = actual.children.iterator()
assertEquals(nodeTester.childSpecs.size(), actual.children.size())
nodeTester.childSpecs.each {
assertTrue(childIterator.hasNext())
def next = childIterator.next()
it.test(next)
}
}
}
@Override
String toString() {
"NodeSpec(${ this.nodeClass.simpleName })"
}
}
private static class NodeTester {
List<NodeSpec<? extends Node>> childSpecs = []
def <T extends Node> void expect(
Class<T> childNodeClass,
@DelegatesTo(value = NodeTester, strategy = Closure.DELEGATE_ONLY)
@ClosureParams(FirstParam.FirstGenericType)
Closure<Void> furtherTests
) {
this.childSpecs << new NodeSpec<T>(childNodeClass, furtherTests)
}
void expect(Class nodeClass) {
this.childSpecs << new NodeSpec(nodeClass)
}
}
private final ComponentParser parser = new ComponentParser()
private void selfClosing(
List<ComponentToken> tokens,
@DelegatesTo(value = NodeTester, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(value = SimpleType, options = ['com.jessebrault.ssg.template.gspe.component.node.ComponentNode'])
Closure<Void> tests
) {
def componentNode = this.parser.parse(tokens)
logger.debug('componentNode: {}', componentNode)
def componentSpec = new NodeSpec(ComponentNode, tests)
logger.debug('nodeSpec: {}', componentSpec)
componentSpec.test(componentNode)
}
@Test
void selfClosingNoKeysOrValues() {
this.selfClosing([
new ComponentToken(LT),
new ComponentToken(IDENTIFIER, 'Test'),
new ComponentToken(FORWARD_SLASH),
new ComponentToken(GT)
]) {
assertEquals('Test', it.identifier)
expect(KeysAndValues) {
assertEquals(0, it.children.size())
}
}
}
@Test
void selfClosingWithGStringValue() {
this.selfClosing([
new ComponentToken(LT),
new ComponentToken(IDENTIFIER, 'Test'),
new ComponentToken(KEY, 'test'),
new ComponentToken(EQUALS),
new ComponentToken(DOUBLE_QUOTE),
new ComponentToken(STRING, 'Hello, World!'),
new ComponentToken(DOUBLE_QUOTE),
new ComponentToken(FORWARD_SLASH),
new ComponentToken(GT)
]) {
assertEquals('Test', it.identifier)
expect(KeysAndValues) {
expect(KeyAndValue) {
assertEquals('test', it.key)
expect(GStringValue) {
assertEquals('Hello, World!', it.gString)
}
}
}
}
}
}

View File

@ -1,10 +1,11 @@
package com.jessebrault.ssg.template.gspe
package com.jessebrault.ssg.template.gspe.component
import org.junit.jupiter.api.Test
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static com.jessebrault.ssg.template.gspe.ComponentToken.Type.*
import static com.jessebrault.ssg.template.gspe.component.ComponentToken.Type.*
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue

View File

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

View File

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