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 { 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 // https://mvnrepository.com/artifact/org.apache.groovy/groovy-templates
implementation 'org.apache.groovy:groovy-templates:4.0.7' 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 // https://archiva.jessebrault.com/#artifact/com.jessebrault.fsm/groovy-extension/0.1.0-SNAPSHOT
implementation 'com.jessebrault.fsm:groovy-extension:0.1.0-SNAPSHOT' implementation 'com.jessebrault.fsm:groovy-extension:0.1.0-SNAPSHOT'
testRuntimeOnly project(':gcp-impl')
} }
jar { jar {
archivesBaseName = 'gcp' archivesBaseName = 'gcp-api'
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
package com.jessebrault.gcp package com.jessebrault.gcp
import com.jessebrault.gcp.component.Component
import com.jessebrault.gcp.component.ComponentsContainer
import groovy.text.Template import groovy.text.Template
import groovy.text.TemplateEngine import groovy.text.TemplateEngine
import groovy.transform.TupleConstructor import groovy.transform.TupleConstructor
@ -16,6 +14,12 @@ final class GcpTemplateEngine extends TemplateEngine {
private static final Logger logger = LoggerFactory.getLogger(GcpTemplateEngine) 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) @TupleConstructor(defaults = false)
static class Configuration { static class Configuration {
Supplier<GcpTemplate> ssgTemplateSupplier Supplier<GcpTemplate> ssgTemplateSupplier
@ -37,7 +41,7 @@ final class GcpTemplateEngine extends TemplateEngine {
Template createTemplate(Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException { Template createTemplate(Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
def templateSrc = reader.text def templateSrc = reader.text
def converter = new TemplateToScriptConverter() def converter = getConverter()
def scriptSrc = converter.convert(templateSrc) def scriptSrc = converter.convert(templateSrc)
logger.debug('scriptSrc: {}', scriptSrc) logger.debug('scriptSrc: {}', scriptSrc)
def scriptName = "SsgTemplate${ this.templateCount.getAndIncrement() }.groovy" 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 package com.jessebrault.gcp
import com.jessebrault.gcp.component.Component
import groovy.text.TemplateEngine import groovy.text.TemplateEngine
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertEquals
class GcpTemplateEngineTests { class GcpTemplateEngineIntegrationTests {
private final TemplateEngine engine = new GcpTemplateEngine(new GcpTemplateEngine.Configuration( private final TemplateEngine engine = new GcpTemplateEngine(new GcpTemplateEngine.Configuration(
{ new GcpTemplate() }, { new GcpTemplate() },

View File

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

View File

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

View File

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

View File

@ -1,83 +1,77 @@
package com.jessebrault.gcp.component package com.jessebrault.gcp.component
import com.jessebrault.gcp.component.node.BooleanValue 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.DollarReferenceValue
import com.jessebrault.gcp.component.node.DollarScriptletValue import com.jessebrault.gcp.component.node.DollarScriptletValue
import com.jessebrault.gcp.component.node.ExpressionScriptletValue import com.jessebrault.gcp.component.node.ExpressionScriptletValue
import com.jessebrault.gcp.component.node.GStringValue import com.jessebrault.gcp.component.node.GStringValue
import com.jessebrault.gcp.component.node.KeyAndValue import com.jessebrault.gcp.component.node.KeyAndValue
import com.jessebrault.gcp.component.node.KeysAndValues 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.ScriptletValue
import com.jessebrault.gcp.component.node.StringValue import com.jessebrault.gcp.component.node.StringValue
// NOT THREAD SAFE, and must be used exactly once // NOT THREAD SAFE
class ComponentToScriptVisitor extends NodeVisitor { class ComponentToClosureVisitor extends ComponentNodeVisitor {
private final Writer w = new StringWriter() private StringBuilder b = new StringBuilder()
private final IndentPrinter p = new IndentPrinter(this.w, ' ', true, true)
String getResult() { String getResult() {
w.toString() b.toString()
} }
void visit(ComponentNode componentNode) { void reset() {
p.println('{ attr, bodyOut ->') b = new StringBuilder()
p.incrementIndent() }
void visit(ComponentRoot componentNode) {
b << '{ '
super.visit(componentNode) super.visit(componentNode)
if (componentNode.body != null) { if (componentNode.body != null) {
p.println("bodyOut << ${ componentNode.body };") b << "bodyOut << ${ componentNode.body }; "
} }
p.decrementIndent() b << '};'
p.println('};')
} }
void visit(KeysAndValues keysAndValues) { void visit(KeysAndValues keysAndValues) {
p.println('attr {') b << 'attr { '
p.incrementIndent()
super.visit(keysAndValues) super.visit(keysAndValues)
p.decrementIndent() b << '}; '
p.println('};')
} }
void visit(KeyAndValue keyAndValue) { void visit(KeyAndValue keyAndValue) {
p.printIndent() b << "${ keyAndValue.key } = "
p.print("${ keyAndValue.key } = ")
super.visit(keyAndValue) super.visit(keyAndValue)
p.print(';\n') b << '; '
} }
void visit(GStringValue gStringValue) { void visit(GStringValue gStringValue) {
p.print("\"${ gStringValue.gString }\"") b << "\"${ gStringValue.gString }\""
} }
void visit(StringValue stringValue) { void visit(StringValue stringValue) {
p.print("'${ stringValue.string }'") b << "'${ stringValue.string }'"
} }
void visit(DollarReferenceValue dollarReferenceValue) { void visit(DollarReferenceValue dollarReferenceValue) {
p.print(dollarReferenceValue.reference) b << dollarReferenceValue.reference
} }
void visit(DollarScriptletValue dollarScriptletValue) { void visit(DollarScriptletValue dollarScriptletValue) {
p.print(dollarScriptletValue.scriptlet) b << dollarScriptletValue.scriptlet
} }
void visit(ScriptletValue scriptletValue) { void visit(ScriptletValue scriptletValue) {
p.println("render { out ->") b << "render { out -> ${ scriptletValue.scriptlet } }"
p.incrementIndent()
p.print(scriptletValue.scriptlet)
p.decrementIndent()
p.println('}')
} }
void visit(ExpressionScriptletValue expressionScriptletValue) { void visit(ExpressionScriptletValue expressionScriptletValue) {
p.print(expressionScriptletValue.scriptlet) b << expressionScriptletValue.scriptlet
} }
void visit(BooleanValue booleanValue) { 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.FunctionFsmBuilder
import com.jessebrault.fsm.function.FunctionFsmBuilderImpl 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 import static ComponentToken.Type
@ -89,7 +90,7 @@ class ComponentTokenizer {
tokens << new ComponentToken(Type.GROOVY_IDENTIFIER, s.substring(1)) // skip opening $ tokens << new ComponentToken(Type.GROOVY_IDENTIFIER, s.substring(1)) // skip opening $
} }
//noinspection GroovyAssignabilityCheck // for some reason IntelliJ is confused by this //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)) tokens << new ComponentToken(Type.GROOVY, s.substring(2, s.length() - 1))
} }
on percent shiftTo State.EXPRESSION_SCRIPTLET_GROOVY exec { on percent shiftTo State.EXPRESSION_SCRIPTLET_GROOVY exec {

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node package com.jessebrault.gcp.component.node
class BooleanValue extends Node { class BooleanValue extends ComponentNode {
boolean value boolean value
@Override @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 package com.jessebrault.gcp.component.node
abstract class NodeVisitor { abstract class ComponentNodeVisitor {
void visit(Node node) { void visit(ComponentNode node) {
this.visitChildren(node) this.visitChildren(node)
} }
void visitChildren(Node node) { void visitChildren(ComponentNode node) {
node.children.each { node.children.each {
this.visit(it) this.visit(it)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
package com.jessebrault.gcp.component.node package com.jessebrault.gcp.component.node
class StringValue extends Node { class StringValue extends ComponentNode {
String string 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.Logger
import org.slf4j.LoggerFactory 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 { private enum State {
NO_STRING, G_STRING, SINGLE_QUOTE_STRING NO_STRING, G_STRING, SINGLE_QUOTE_STRING
@ -126,4 +131,16 @@ class DollarGroovyParser {
acc.toString() 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.PackageScope
import groovy.transform.TupleConstructor 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.Retention
import java.lang.annotation.RetentionPolicy 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 org.slf4j.LoggerFactory
import static com.jessebrault.gcp.component.ComponentToken.Type.* import static com.jessebrault.gcp.component.ComponentToken.Type.*
import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue import static org.junit.jupiter.api.Assertions.assertTrue
@ -16,7 +17,7 @@ class ComponentParserTests {
private static final Logger logger = LoggerFactory.getLogger(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 Class<T> nodeClass
Closure<Void> tests Closure<Void> tests
@ -31,7 +32,7 @@ class ComponentParserTests {
this.tests = tests this.tests = tests
} }
void test(Node actual) { void test(ComponentNode actual) {
logger.debug('actual: {}', actual) logger.debug('actual: {}', actual)
assertTrue(nodeClass.isAssignableFrom(actual.class)) assertTrue(nodeClass.isAssignableFrom(actual.class))
if (this.tests != null) { if (this.tests != null) {
@ -60,9 +61,9 @@ class ComponentParserTests {
private static class NodeTester { 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, Class<T> childNodeClass,
@DelegatesTo(value = NodeTester, strategy = Closure.DELEGATE_FIRST) @DelegatesTo(value = NodeTester, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(FirstParam.FirstGenericType) @ClosureParams(FirstParam.FirstGenericType)
@ -88,7 +89,7 @@ class ComponentParserTests {
def componentNode = this.parser.parse(tokens) def componentNode = this.parser.parse(tokens)
logger.debug('componentNode: {}', componentNode) logger.debug('componentNode: {}', componentNode)
def componentSpec = new NodeSpec(ComponentNode, tests) def componentSpec = new NodeSpec(ComponentRoot, tests)
logger.debug('nodeSpec: {}', componentSpec) logger.debug('nodeSpec: {}', componentSpec)
componentSpec.test(componentNode) 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 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 import static org.junit.jupiter.api.Assertions.assertEquals
class DollarGroovyParserTests { class DollarScriptletParserTests {
@Test @Test
void empty() { 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' rootProject.name = 'ssg'
include 'cli', 'gcp', 'lib' include 'cli', 'gcp-api', 'gcp-impl', 'lib'